From 53308013c3f2bad224208805df0f54416067b8be Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Thu, 24 Apr 2025 14:38:36 -0700 Subject: Chapter 21: Instruction Encoding and ELF MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: godalming123 <68993177+godalming123@users.noreply.github.com> Co-authored-by: Hsiang-Ying Fu <66062782+fuhsnn@users.noreply.github.com> Co-authored-by: tejasae-afk Co-authored-by: fuhsnn <66062782+fuhsnn@users.noreply.github.com> Co-authored-by: Tejas Attarde <201814263+tejasae-afk@users.noreply.github.com> Co-authored-by: Tamás Hadházy <85063808+Hels15@users.noreply.github.com> Co-authored-by: Symious <14933944+symious@users.noreply.github.com> Co-authored-by: Jun Kawasaki Co-authored-by: Claude Opus 5 --- src/main/java/com/seaofnodes/simple/IterPeeps.java | 2 + src/main/java/com/seaofnodes/simple/Parser.java | 24 +- src/main/java/com/seaofnodes/simple/SB.java | 23 +- .../com/seaofnodes/simple/codegen/BuildLRG.java | 5 +- .../com/seaofnodes/simple/codegen/Coalesce.java | 105 ++++ .../com/seaofnodes/simple/codegen/CodeGen.java | 184 ++++-- .../com/seaofnodes/simple/codegen/ElfExpr.java | 87 +++ .../com/seaofnodes/simple/codegen/ElfFile.java | 382 +++++++++++++ .../com/seaofnodes/simple/codegen/Encoding.java | 475 ++++++++++++++++ .../simple/codegen/GlobalCodeMotion.java | 9 +- .../java/com/seaofnodes/simple/codegen/IFG.java | 60 +- .../java/com/seaofnodes/simple/codegen/LRG.java | 19 +- .../com/seaofnodes/simple/codegen/LinkMem.java | 23 + .../seaofnodes/simple/codegen/ListScheduler.java | 3 + .../com/seaofnodes/simple/codegen/Machine.java | 89 ++- .../com/seaofnodes/simple/codegen/RIPRelSize.java | 14 + .../com/seaofnodes/simple/codegen/RegAlloc.java | 138 +++-- .../com/seaofnodes/simple/codegen/RegMask.java | 35 +- .../com/seaofnodes/simple/codegen/RegMaskRW.java | 36 ++ .../java/com/seaofnodes/simple/node/AddNode.java | 2 +- .../java/com/seaofnodes/simple/node/BoolNode.java | 12 + .../com/seaofnodes/simple/node/BulkMemPhiNode.java | 2 +- .../java/com/seaofnodes/simple/node/CFGNode.java | 25 +- .../java/com/seaofnodes/simple/node/CProjNode.java | 14 +- .../com/seaofnodes/simple/node/CallEndNode.java | 51 +- .../java/com/seaofnodes/simple/node/CallNode.java | 15 +- .../com/seaofnodes/simple/node/CalleeSaveNode.java | 6 +- .../java/com/seaofnodes/simple/node/CastNode.java | 2 +- .../com/seaofnodes/simple/node/ConstantNode.java | 4 +- .../java/com/seaofnodes/simple/node/FRefNode.java | 2 +- .../java/com/seaofnodes/simple/node/FunNode.java | 57 +- .../java/com/seaofnodes/simple/node/IfNode.java | 43 +- .../java/com/seaofnodes/simple/node/LoadNode.java | 161 +++--- .../java/com/seaofnodes/simple/node/LoopNode.java | 53 +- .../java/com/seaofnodes/simple/node/MachNode.java | 11 +- .../com/seaofnodes/simple/node/MemMergeNode.java | 5 +- .../java/com/seaofnodes/simple/node/MemOpNode.java | 52 +- .../com/seaofnodes/simple/node/MemPhiNode.java | 4 +- .../java/com/seaofnodes/simple/node/NeverNode.java | 3 +- .../java/com/seaofnodes/simple/node/NewNode.java | 39 +- src/main/java/com/seaofnodes/simple/node/Node.java | 54 +- .../java/com/seaofnodes/simple/node/PhiNode.java | 28 +- .../java/com/seaofnodes/simple/node/ProjNode.java | 2 +- .../com/seaofnodes/simple/node/RegionNode.java | 45 +- .../com/seaofnodes/simple/node/ReturnNode.java | 40 +- .../java/com/seaofnodes/simple/node/ShlNode.java | 2 +- .../java/com/seaofnodes/simple/node/SplitNode.java | 2 +- .../java/com/seaofnodes/simple/node/StopNode.java | 3 +- .../java/com/seaofnodes/simple/node/StoreNode.java | 23 +- .../com/seaofnodes/simple/node/StructNode.java | 2 +- .../seaofnodes/simple/node/cpus/arm/AddARM.java | 29 +- .../seaofnodes/simple/node/cpus/arm/AddFARM.java | 24 +- .../seaofnodes/simple/node/cpus/arm/AddIARM.java | 53 +- .../seaofnodes/simple/node/cpus/arm/AndARM.java | 34 +- .../seaofnodes/simple/node/cpus/arm/AndIARM.java | 57 +- .../seaofnodes/simple/node/cpus/arm/AsrARM.java | 33 +- .../seaofnodes/simple/node/cpus/arm/AsrIARM.java | 55 +- .../seaofnodes/simple/node/cpus/arm/BranchARM.java | 63 ++- .../seaofnodes/simple/node/cpus/arm/CallARM.java | 35 +- .../simple/node/cpus/arm/CallEndARM.java | 29 +- .../seaofnodes/simple/node/cpus/arm/CallRRARM.java | 19 +- .../seaofnodes/simple/node/cpus/arm/CastARM.java | 18 + .../seaofnodes/simple/node/cpus/arm/CmpARM.java | 19 +- .../seaofnodes/simple/node/cpus/arm/CmpFARM.java | 21 +- .../seaofnodes/simple/node/cpus/arm/CmpIARM.java | 35 +- .../seaofnodes/simple/node/cpus/arm/CmpMemARM.java | 4 - .../seaofnodes/simple/node/cpus/arm/DivARM.java | 27 +- .../seaofnodes/simple/node/cpus/arm/DivFARM.java | 22 +- .../seaofnodes/simple/node/cpus/arm/F8ToIARM.java | 23 +- .../seaofnodes/simple/node/cpus/arm/FloatARM.java | 34 +- .../seaofnodes/simple/node/cpus/arm/FunARM.java | 27 +- .../seaofnodes/simple/node/cpus/arm/I2F8ARM.java | 27 +- .../seaofnodes/simple/node/cpus/arm/IntARM.java | 16 +- .../seaofnodes/simple/node/cpus/arm/LoadARM.java | 27 +- .../seaofnodes/simple/node/cpus/arm/LslARM.java | 33 +- .../seaofnodes/simple/node/cpus/arm/LslIARM.java | 58 +- .../seaofnodes/simple/node/cpus/arm/LsrARM.java | 32 +- .../seaofnodes/simple/node/cpus/arm/LsrIARM.java | 53 +- .../seaofnodes/simple/node/cpus/arm/MemOpARM.java | 67 ++- .../seaofnodes/simple/node/cpus/arm/MulARM.java | 28 +- .../seaofnodes/simple/node/cpus/arm/MulFARM.java | 21 +- .../seaofnodes/simple/node/cpus/arm/MulIARM.java | 43 -- .../seaofnodes/simple/node/cpus/arm/NegARM.java | 25 + .../seaofnodes/simple/node/cpus/arm/NewARM.java | 44 +- .../seaofnodes/simple/node/cpus/arm/NotARM.java | 27 +- .../com/seaofnodes/simple/node/cpus/arm/OrARM.java | 39 +- .../seaofnodes/simple/node/cpus/arm/OrIARM.java | 49 +- .../seaofnodes/simple/node/cpus/arm/ParmARM.java | 14 +- .../seaofnodes/simple/node/cpus/arm/ProjARM.java | 12 +- .../seaofnodes/simple/node/cpus/arm/RetARM.java | 49 +- .../seaofnodes/simple/node/cpus/arm/SetARM.java | 22 +- .../seaofnodes/simple/node/cpus/arm/SplitARM.java | 63 ++- .../seaofnodes/simple/node/cpus/arm/StoreARM.java | 46 +- .../seaofnodes/simple/node/cpus/arm/SubARM.java | 26 +- .../seaofnodes/simple/node/cpus/arm/SubFARM.java | 27 +- .../seaofnodes/simple/node/cpus/arm/SubIARM.java | 32 +- .../seaofnodes/simple/node/cpus/arm/TFPARM.java | 47 +- .../seaofnodes/simple/node/cpus/arm/UJmpARM.java | 47 ++ .../seaofnodes/simple/node/cpus/arm/XorARM.java | 41 +- .../seaofnodes/simple/node/cpus/arm/XorIARM.java | 51 +- .../com/seaofnodes/simple/node/cpus/arm/arm.java | 438 ++++++++------- .../com/seaofnodes/simple/node/cpus/arm/i2f8.java | 4 - .../seaofnodes/simple/node/cpus/riscv/AUIPC.java | 42 ++ .../simple/node/cpus/riscv/AddFRISC.java | 26 +- .../simple/node/cpus/riscv/AddIRISC.java | 17 +- .../seaofnodes/simple/node/cpus/riscv/AddRISC.java | 26 +- .../simple/node/cpus/riscv/AndIRISC.java | 8 +- .../seaofnodes/simple/node/cpus/riscv/AndRISC.java | 36 +- .../simple/node/cpus/riscv/BranchRISC.java | 78 ++- .../simple/node/cpus/riscv/CallEndRISC.java | 29 +- .../simple/node/cpus/riscv/CallRISC.java | 45 +- .../simple/node/cpus/riscv/CallRRISC.java | 25 +- .../simple/node/cpus/riscv/CastRISC.java | 8 +- .../simple/node/cpus/riscv/DivFRISC.java | 22 +- .../seaofnodes/simple/node/cpus/riscv/DivRISC.java | 27 +- .../simple/node/cpus/riscv/F8ToIRISC.java | 19 +- .../seaofnodes/simple/node/cpus/riscv/FltRISC.java | 51 +- .../seaofnodes/simple/node/cpus/riscv/FunRISC.java | 24 +- .../simple/node/cpus/riscv/I2F8RISC.java | 24 +- .../seaofnodes/simple/node/cpus/riscv/ImmRISC.java | 48 +- .../seaofnodes/simple/node/cpus/riscv/IntRISC.java | 30 +- .../com/seaofnodes/simple/node/cpus/riscv/LUI.java | 33 ++ .../simple/node/cpus/riscv/LoadRISC.java | 34 +- .../simple/node/cpus/riscv/MemOpRISC.java | 81 +-- .../simple/node/cpus/riscv/MulFRISC.java | 26 +- .../seaofnodes/simple/node/cpus/riscv/MulRISC.java | 25 +- .../seaofnodes/simple/node/cpus/riscv/NegRISC.java | 21 + .../seaofnodes/simple/node/cpus/riscv/NewRISC.java | 50 +- .../seaofnodes/simple/node/cpus/riscv/NotRISC.java | 29 +- .../seaofnodes/simple/node/cpus/riscv/OrIRISC.java | 6 +- .../seaofnodes/simple/node/cpus/riscv/OrRISC.java | 35 +- .../simple/node/cpus/riscv/ParmRISC.java | 17 +- .../simple/node/cpus/riscv/ProjRISC.java | 14 +- .../seaofnodes/simple/node/cpus/riscv/RetRISC.java | 48 +- .../simple/node/cpus/riscv/SetFRISC.java | 25 +- .../simple/node/cpus/riscv/SetIRISC.java | 24 +- .../seaofnodes/simple/node/cpus/riscv/SetRISC.java | 35 +- .../simple/node/cpus/riscv/SllIRISC.java | 6 +- .../seaofnodes/simple/node/cpus/riscv/SllRISC.java | 36 +- .../simple/node/cpus/riscv/SplitRISC.java | 62 ++- .../simple/node/cpus/riscv/SraIRISC.java | 6 +- .../seaofnodes/simple/node/cpus/riscv/SraRISC.java | 33 +- .../simple/node/cpus/riscv/SrlIRISC.java | 6 +- .../seaofnodes/simple/node/cpus/riscv/SrlRISC.java | 33 +- .../simple/node/cpus/riscv/StoreRISC.java | 44 +- .../simple/node/cpus/riscv/SubFRISC.java | 26 +- .../seaofnodes/simple/node/cpus/riscv/SubRISC.java | 21 +- .../seaofnodes/simple/node/cpus/riscv/TFPRISC.java | 54 +- .../simple/node/cpus/riscv/UJmpRISC.java | 48 ++ .../simple/node/cpus/riscv/XorIRISC.java | 6 +- .../seaofnodes/simple/node/cpus/riscv/XorRISC.java | 37 +- .../seaofnodes/simple/node/cpus/riscv/riscv.java | 395 +++++++------ .../simple/node/cpus/x86_64_v2/AddFMemX86.java | 14 +- .../simple/node/cpus/x86_64_v2/AddFX86.java | 21 +- .../simple/node/cpus/x86_64_v2/AddIX86.java | 42 +- .../simple/node/cpus/x86_64_v2/AddMemX86.java | 16 +- .../simple/node/cpus/x86_64_v2/AddX86.java | 32 +- .../simple/node/cpus/x86_64_v2/AndIX86.java | 45 +- .../simple/node/cpus/x86_64_v2/AndX86.java | 35 +- .../simple/node/cpus/x86_64_v2/CallEndX86.java | 29 +- .../simple/node/cpus/x86_64_v2/CallRX86.java | 22 +- .../simple/node/cpus/x86_64_v2/CallX86.java | 38 +- .../simple/node/cpus/x86_64_v2/CastX86.java | 8 +- .../simple/node/cpus/x86_64_v2/CmpFX86.java | 17 +- .../simple/node/cpus/x86_64_v2/CmpIX86.java | 43 +- .../simple/node/cpus/x86_64_v2/CmpMemX86.java | 37 +- .../simple/node/cpus/x86_64_v2/CmpX86.java | 23 +- .../simple/node/cpus/x86_64_v2/DivFX86.java | 27 +- .../simple/node/cpus/x86_64_v2/DivX86.java | 20 +- .../simple/node/cpus/x86_64_v2/F8ToIX86.java | 18 +- .../simple/node/cpus/x86_64_v2/FltX86.java | 43 +- .../simple/node/cpus/x86_64_v2/FunX86.java | 29 +- .../simple/node/cpus/x86_64_v2/I2f8X86.java | 19 +- .../simple/node/cpus/x86_64_v2/ImmX86.java | 31 ++ .../simple/node/cpus/x86_64_v2/IntX86.java | 25 +- .../simple/node/cpus/x86_64_v2/JmpX86.java | 60 +- .../simple/node/cpus/x86_64_v2/LeaX86.java | 24 +- .../simple/node/cpus/x86_64_v2/LoadX86.java | 32 +- .../simple/node/cpus/x86_64_v2/MemAddX86.java | 17 +- .../simple/node/cpus/x86_64_v2/MemOpX86.java | 25 +- .../simple/node/cpus/x86_64_v2/MulFX86.java | 22 +- .../simple/node/cpus/x86_64_v2/MulIX86.java | 49 +- .../simple/node/cpus/x86_64_v2/MulX86.java | 19 +- .../simple/node/cpus/x86_64_v2/NegX86.java | 19 + .../simple/node/cpus/x86_64_v2/NewX86.java | 26 +- .../simple/node/cpus/x86_64_v2/NotX86.java | 23 +- .../simple/node/cpus/x86_64_v2/OrIX86.java | 42 +- .../simple/node/cpus/x86_64_v2/OrX86.java | 36 +- .../simple/node/cpus/x86_64_v2/ParmX86.java | 18 +- .../simple/node/cpus/x86_64_v2/ProjX86.java | 12 +- .../simple/node/cpus/x86_64_v2/RegX86.java | 21 + .../simple/node/cpus/x86_64_v2/RetX86.java | 48 +- .../simple/node/cpus/x86_64_v2/SarIX86.java | 38 +- .../simple/node/cpus/x86_64_v2/SarX86.java | 46 +- .../simple/node/cpus/x86_64_v2/SetX86.java | 29 +- .../simple/node/cpus/x86_64_v2/ShlIX86.java | 47 +- .../simple/node/cpus/x86_64_v2/ShlX86.java | 44 +- .../simple/node/cpus/x86_64_v2/ShrIX86.java | 43 +- .../simple/node/cpus/x86_64_v2/ShrX86.java | 45 +- .../simple/node/cpus/x86_64_v2/SplitX86.java | 49 +- .../simple/node/cpus/x86_64_v2/StoreX86.java | 23 +- .../simple/node/cpus/x86_64_v2/SubFX86.java | 20 +- .../simple/node/cpus/x86_64_v2/SubX86.java | 31 +- .../simple/node/cpus/x86_64_v2/TFPX86.java | 44 +- .../simple/node/cpus/x86_64_v2/UJmpX86.java | 40 ++ .../simple/node/cpus/x86_64_v2/XorIX86.java | 40 +- .../simple/node/cpus/x86_64_v2/XorX86.java | 35 +- .../simple/node/cpus/x86_64_v2/x86_64_v2.java | 616 +++++++++++---------- .../com/seaofnodes/simple/print/ASMPrinter.java | 32 ++ .../simple/print/SimpleGraphAdapter.java | 2 +- .../simple/print/SimpleGraphObserver.java | 2 +- src/main/java/com/seaofnodes/simple/type/Type.java | 3 +- .../com/seaofnodes/simple/type/TypeFunPtr.java | 1 + .../com/seaofnodes/simple/type/TypeMemPtr.java | 2 +- .../com/seaofnodes/simple/type/TypeStruct.java | 7 +- .../java/com/seaofnodes/simple/type/TypeTuple.java | 16 + .../java/com/seaofnodes/simple/BrainFuckTest.java | 85 +-- .../java/com/seaofnodes/simple/Chapter05Test.java | 2 +- .../java/com/seaofnodes/simple/Chapter06Test.java | 4 +- .../java/com/seaofnodes/simple/Chapter08Test.java | 4 +- .../java/com/seaofnodes/simple/Chapter12Test.java | 2 +- .../java/com/seaofnodes/simple/Chapter13Test.java | 4 +- .../java/com/seaofnodes/simple/Chapter15Test.java | 49 +- .../java/com/seaofnodes/simple/Chapter17bTest.java | 2 +- .../java/com/seaofnodes/simple/Chapter18Test.java | 45 +- .../java/com/seaofnodes/simple/Chapter19Test.java | 193 +++---- .../java/com/seaofnodes/simple/Chapter20Test.java | 193 ++++++- .../com/seaofnodes/simple/Chapter21AllocTest.java | 55 ++ .../java/com/seaofnodes/simple/Chapter21Test.java | 532 ++++++++++++++++++ src/test/java/com/seaofnodes/simple/Compile.java | 65 +++ src/test/java/com/seaofnodes/simple/Eval2.java | 2 + src/test/java/com/seaofnodes/simple/Main.java | 1 - .../com/seaofnodes/simple/MemoryEncodingTest.java | 180 ++++++ .../java/com/seaofnodes/simple/MergeSortTest.java | 91 ++- src/test/java/com/seaofnodes/simple/Simple.java | 287 ++++++++++ .../java/com/seaofnodes/simple/SpillStats.java | 21 +- src/test/java/com/seaofnodes/simple/TestArm64.java | 44 ++ src/test/java/com/seaofnodes/simple/TestC.java | 88 +++ src/test/java/com/seaofnodes/simple/TestRisc5.java | 51 ++ .../simple/codegen/RegAllocTestSupport.java | 72 ++- .../seaofnodes/simple/codegen/X86EncodingTest.java | 97 ++++ .../com/seaofnodes/simple/evaluator/Scheduler.java | 16 +- .../com/seaofnodes/simple/fuzzer/FuzzerUtils.java | 2 +- .../java/com/seaofnodes/simple/progs/antiDep1.smp | 11 + .../java/com/seaofnodes/simple/progs/arg_count.c | 10 + .../java/com/seaofnodes/simple/progs/arg_count.smp | 6 + .../java/com/seaofnodes/simple/progs/array1.smp | 9 + .../java/com/seaofnodes/simple/progs/brain_fuck.c | 18 + .../com/seaofnodes/simple/progs/brain_fuck.smp | 46 ++ src/test/java/com/seaofnodes/simple/progs/fcn.smp | 15 - src/test/java/com/seaofnodes/simple/progs/fcn2.smp | 5 - src/test/java/com/seaofnodes/simple/progs/fcn3.smp | 42 -- src/test/java/com/seaofnodes/simple/progs/fib.c | 16 + src/test/java/com/seaofnodes/simple/progs/fib.smp | 11 + src/test/java/com/seaofnodes/simple/progs/jig.smp | 2 + src/test/java/com/seaofnodes/simple/progs/loop2.c | 7 + .../java/com/seaofnodes/simple/progs/loop2.smp | 10 + .../java/com/seaofnodes/simple/progs/newtonFloat.c | 16 + .../com/seaofnodes/simple/progs/newtonFloat.smp | 12 + .../seaofnodes/simple/progs/no_stack_arg_count.smp | 6 + src/test/java/com/seaofnodes/simple/progs/person.c | 34 ++ .../java/com/seaofnodes/simple/progs/person.smp | 9 + src/test/java/com/seaofnodes/simple/progs/sieve.c | 19 + .../java/com/seaofnodes/simple/progs/sieve.smp | 61 +- src/test/java/com/seaofnodes/simple/progs/sort.c | 28 + src/test/java/com/seaofnodes/simple/progs/sort.smp | 38 ++ .../java/com/seaofnodes/simple/progs/stringHash.c | 30 + .../com/seaofnodes/simple/progs/stringHash.smp | 31 ++ 268 files changed, 7370 insertions(+), 4238 deletions(-) create mode 100644 src/main/java/com/seaofnodes/simple/codegen/Coalesce.java create mode 100644 src/main/java/com/seaofnodes/simple/codegen/ElfExpr.java create mode 100644 src/main/java/com/seaofnodes/simple/codegen/ElfFile.java create mode 100644 src/main/java/com/seaofnodes/simple/codegen/Encoding.java create mode 100644 src/main/java/com/seaofnodes/simple/codegen/LinkMem.java create mode 100644 src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java create mode 100644 src/main/java/com/seaofnodes/simple/codegen/RegMaskRW.java create mode 100644 src/main/java/com/seaofnodes/simple/node/cpus/arm/CastARM.java delete mode 100644 src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpMemARM.java delete mode 100644 src/main/java/com/seaofnodes/simple/node/cpus/arm/MulIARM.java create mode 100644 src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java create mode 100644 src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java delete mode 100644 src/main/java/com/seaofnodes/simple/node/cpus/arm/i2f8.java create mode 100644 src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java create mode 100644 src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java create mode 100644 src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java create mode 100644 src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java create mode 100644 src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java create mode 100644 src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java create mode 100644 src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java create mode 100644 src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java create mode 100644 src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java create mode 100644 src/test/java/com/seaofnodes/simple/Chapter21Test.java create mode 100644 src/test/java/com/seaofnodes/simple/Compile.java create mode 100644 src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java create mode 100644 src/test/java/com/seaofnodes/simple/Simple.java create mode 100644 src/test/java/com/seaofnodes/simple/TestArm64.java create mode 100644 src/test/java/com/seaofnodes/simple/TestC.java create mode 100644 src/test/java/com/seaofnodes/simple/TestRisc5.java create mode 100644 src/test/java/com/seaofnodes/simple/codegen/X86EncodingTest.java create mode 100644 src/test/java/com/seaofnodes/simple/progs/antiDep1.smp create mode 100644 src/test/java/com/seaofnodes/simple/progs/arg_count.c create mode 100644 src/test/java/com/seaofnodes/simple/progs/arg_count.smp create mode 100644 src/test/java/com/seaofnodes/simple/progs/array1.smp create mode 100644 src/test/java/com/seaofnodes/simple/progs/brain_fuck.c create mode 100644 src/test/java/com/seaofnodes/simple/progs/brain_fuck.smp delete mode 100644 src/test/java/com/seaofnodes/simple/progs/fcn.smp delete mode 100644 src/test/java/com/seaofnodes/simple/progs/fcn2.smp delete mode 100644 src/test/java/com/seaofnodes/simple/progs/fcn3.smp create mode 100644 src/test/java/com/seaofnodes/simple/progs/fib.c create mode 100644 src/test/java/com/seaofnodes/simple/progs/fib.smp create mode 100644 src/test/java/com/seaofnodes/simple/progs/jig.smp create mode 100644 src/test/java/com/seaofnodes/simple/progs/loop2.c create mode 100644 src/test/java/com/seaofnodes/simple/progs/loop2.smp create mode 100644 src/test/java/com/seaofnodes/simple/progs/newtonFloat.c create mode 100644 src/test/java/com/seaofnodes/simple/progs/newtonFloat.smp create mode 100644 src/test/java/com/seaofnodes/simple/progs/no_stack_arg_count.smp create mode 100644 src/test/java/com/seaofnodes/simple/progs/person.c create mode 100644 src/test/java/com/seaofnodes/simple/progs/person.smp create mode 100644 src/test/java/com/seaofnodes/simple/progs/sieve.c create mode 100644 src/test/java/com/seaofnodes/simple/progs/sort.c create mode 100644 src/test/java/com/seaofnodes/simple/progs/sort.smp create mode 100644 src/test/java/com/seaofnodes/simple/progs/stringHash.c create mode 100644 src/test/java/com/seaofnodes/simple/progs/stringHash.smp (limited to 'src') diff --git a/src/main/java/com/seaofnodes/simple/IterPeeps.java b/src/main/java/com/seaofnodes/simple/IterPeeps.java index a21fb517..7c2f1b0c 100644 --- a/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ b/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -53,6 +53,7 @@ public class IterPeeps { _workInline = new WorkList<>(seed); } + @SuppressWarnings("unchecked") public N add( N n ) { return (N)_work.push(n); } public void addAll( Ary ary ) { _work.addAll(ary); } @@ -147,6 +148,7 @@ public class IterPeeps { * Classic WorkList, with a fast add/remove, dup removal, random pull. * The Node's nid is used to check membership in the worklist. */ + @SuppressWarnings("unchecked") public static class WorkList { private Node[] _es; diff --git a/src/main/java/com/seaofnodes/simple/Parser.java b/src/main/java/com/seaofnodes/simple/Parser.java index 697e84ce..6f307785 100644 --- a/src/main/java/com/seaofnodes/simple/Parser.java +++ b/src/main/java/com/seaofnodes/simple/Parser.java @@ -142,7 +142,25 @@ public class Parser { // Parse whole program, as-if function header "{ int arg -> body }" parseFunctionBody(_code._main,loc(),"arg"); - _code.link(_code._main)._name = "main"; + + // Kill an empty default main. Keep only if it was explicitly defined + // (programmer asked for a "main") or it has stuff (i.e. beginner + // default main). + FunNode main = _code.link(_code._main); + StopNode stop = _code._stop; + if( main.ret().expr() instanceof ConstantNode && main.ret().mem().in(0)==main && stop.nIns() > 1 ) { + // Kill an empty default main; so it does not attempt to put a + // "main" in any final ELF file + stop.delDef(stop._inputs.find(main.ret())); + } else { + // We have a non-empty default main. + // Check for an explicit main + for( Node n : stop._inputs ) + if( n instanceof FunNode fun && fun._name.equals("main") ) + // Found an explicit "main" AND we have a default "main" + throw error("Cannot define both an explicit main and a default main"); + main._name = "main"; + } if( !_lexer.isEOF() ) throw _errorSyntax("unexpected"); @@ -152,7 +170,7 @@ public class Parser { for( StructNode init : INITS.values() ) init.unkeep().kill(); INITS.clear(); - _code._stop.peephole(); + stop.peephole(); } /** @@ -1431,6 +1449,7 @@ public class Parser { // Post-call setup CallEndNode cend = (CallEndNode)new CallEndNode(call).peephole(); + call.peephole(); // Rerun peeps after CallEnd, allows early inlining // Control from CallEnd ctrl(new CProjNode(cend,0,ScopeNode.CTRL).peephole()); // A call may update any alias, so its memory result is the new whole state. @@ -1690,6 +1709,7 @@ public class Parser { while( Character.isDigit(c=nextChar()) ) ; if( !(c=='e' || c=='.') ) return --_position - old; + if( peek() == '-' ) nextChar(); while( Character.isDigit(c=nextChar()) || c=='e' || c=='.' ) ; return -(--_position - old); } diff --git a/src/main/java/com/seaofnodes/simple/SB.java b/src/main/java/com/seaofnodes/simple/SB.java index a45f0ddf..978fbed9 100644 --- a/src/main/java/com/seaofnodes/simple/SB.java +++ b/src/main/java/com/seaofnodes/simple/SB.java @@ -29,21 +29,28 @@ public final class SB { 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; } - // 4 hex digits - public SB hex4(int s) { - assert (s>>4*4)==0; // Fits in 16 bits - for( int i=0; i<4; i++ ) { - int digit = (s>>((3-i)*4)) & 0xf; - _sb.append((char)((digit <= 9 ? '0' : ('A'-10))+digit)); - } + // 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 v2.nadj() ) + { v1 = v2; v2 = alloc.lrg(n); } + int v2len = v2._adj==null ? 0 : v2._adj._len; + // See that they do not conflict (coalescing would make a self-conflict) + if( v1._adj!=null && v2._adj!=null && v1._mask.overlap(v2._mask) ) { + // Walk the smaller neighbors, and add to the larger. + // Check for direct conflicts along the way. + for( LRG v3 : v1._adj ) { + if( v3==v2 ) { + // Direct conflict, skip this LRG pair + v2._adj.setLen(v2len); + continue outer; + } + // Add missing LRGs from V1 to V2 + if( v2._adj.find(v3)== -1 ) + v2._adj.push(v3); + } + } + // Most constrained mask + RegMask mask = v1._mask==v2._mask ? v1._mask : v1._mask.copy().and(v2._mask); + // Check for capacity + if( v2.nadj() >= mask.size() ) { + // Fails capacity, will not be trivial colorable + if( v2._adj!=null ) v2._adj.setLen(v2len); + continue; + } + + // Union lrgs. + progress = true; + + // Since `n` is dying, remove from the LRGS BEFORE doing + // the union; this will let the union code keep the + // matching bits from the other side, preserving some + // info for biased coloring + if( ov1. _machDef==n ) ov1. _machDef = null; + if( ov1._splitDef==n ) ov1._splitDef = null; + if( ov2. _machUse==n ) ov2. _machUse = null; + if( ov2._splitUse==n ) ov2._splitUse = null; + // Keep larger adjacency list. + Ary 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/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java index 78cf4c06..2c44cb05 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java +++ b/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java @@ -6,22 +6,15 @@ import com.seaofnodes.simple.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.print.*; import com.seaofnodes.simple.type.*; +import java.io.IOException; import java.util.BitSet; import java.util.HashMap; import java.util.IdentityHashMap; +@SuppressWarnings("unchecked") public class CodeGen { public GraphObserver _obs; - - // Inlining can insert CFG edges and invalidate cached depth ordering. - private char _iDepthVersion; - public char iDepthVersion() { return _iDepthVersion; } - public void invalidateIDepthCaches() { - assert _iDepthVersion < Character.MAX_VALUE : "Dominator cache version exceeds 65535"; - ++_iDepthVersion; - } - - + public static final String PORTS = "com.seaofnodes.simple.node.cpus"; // Last created CodeGen as a global; used all over to avoid passing about a // "context". public static CodeGen CODE; @@ -30,11 +23,13 @@ public class CodeGen { Parse, // Parse ASCII text into Sea-of-Nodes IR Opto, // Run ideal optimizations TypeCheck, // Last check for bad programs + LoopTree, // Build a loop tree; break infinite loops 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; @@ -43,7 +38,6 @@ public class CodeGen { public final String _src; // Compile-time known initial argument type public final TypeInteger _arg; - // --------------------------- public CodeGen( String src ) { this(src, TypeInteger.BOT, 123L ); } public CodeGen( String src, TypeInteger arg, long workListSeed ) { @@ -59,6 +53,27 @@ public class CodeGen { } + // All passes up to Phase, except ELF + public CodeGen driver( Phase phase ) { return driver(phase,null,null); } + public CodeGen driver( Phase phase, String cpu, String callingConv ) { + if( _phase==null ) parse(); + if( _phase.ordinal() < phase.ordinal() ) opto(); + if( _phase.ordinal() < phase.ordinal() ) typeCheck(); + if( _phase.ordinal() < phase.ordinal() ) loopTree(); + if( _phase.ordinal() < phase.ordinal() && cpu != null ) instSelect(cpu,callingConv); + if( _phase.ordinal() < phase.ordinal() ) GCM(); + if( _phase.ordinal() < phase.ordinal() ) localSched(); + if( _phase.ordinal() < phase.ordinal() ) regAlloc(); + if( _phase.ordinal() < phase.ordinal() ) encode(); + return this; + } + + // Run all the phases through final ELF emission + public CodeGen driver( String cpu, String callingConv, String obj ) throws IOException { + return driver(Phase.Encoding,cpu,callingConv).exportELF(obj); + } + + // --------------------------- /** * A counter, for unique node id generation. Starting with value 1, to @@ -75,6 +90,14 @@ public class CodeGen { public int getALIAS() { return _alias++; } + // Inlining can insert CFG edges and invalidate cached depth ordering. + private char _iDepthVersion; + public char iDepthVersion() { return _iDepthVersion; } + public void invalidateIDepthCaches() { + assert _iDepthVersion < Character.MAX_VALUE : "Dominator cache version exceeds 65535"; + ++_iDepthVersion; + } + // Popular visit bitset, declared here, so it gets reused all over public final BitSet _visit = new BitSet(); public BitSet visit() { assert _visit.isEmpty(); return _visit; } @@ -168,7 +191,7 @@ public class CodeGen { // --------------------------- // Last check for bad programs - int _tTypeCheck; + public int _tTypeCheck; public CodeGen typeCheck() { // Demand phase Opto for cleaning up dead control flow at least, // required for the following GCM. @@ -188,17 +211,42 @@ public class CodeGen { return this; } + // --------------------------- + // Build the loop tree; break never-exit loops + public int _tLoopTree; + public CodeGen loopTree() { + assert _phase.ordinal() <= Phase.TypeCheck.ordinal(); + _phase = Phase.LoopTree; + long t0 = System.currentTimeMillis(); + // Build the loop tree, fix never-exit loops + _start.buildLoopTree(_stop); + _tLoopTree = (int)(System.currentTimeMillis() - t0); + return this; + } // --------------------------- // Code generation CPU target public Machine _mach; - // + // Chosen calling convention (usually either Win64 or SystemV) public String _callingConv; + // Callee save registers + public RegMask _callerSave; + + // All returns have the following inputs: + // 0 - ctrl + // 1 - memory + // 2 - varies with returning GPR,FPR + // 3 - RPC + // 4+- Caller save registers + public RegMask[] _retMasks; + // Return Program Counter + public RegMask _rpcMask; // Convert to target hardware nodes public int _tInsSel; - public CodeGen instSelect( String base, String cpu, String callingConv ) { - assert _phase.ordinal() <= Phase.TypeCheck.ordinal(); + public CodeGen instSelect( String cpu, String callingConv ) { return instSelect(cpu,callingConv,PORTS); } + public CodeGen instSelect( String cpu, String callingConv, String base ) { + assert _phase.ordinal() == Phase.LoopTree.ordinal(); _phase = Phase.InstSelect; _callingConv = callingConv; @@ -207,9 +255,30 @@ public class CodeGen { // Look for CPU in fixed named place: // com.seaofnodes.simple.node.cpus."cpu"."cpu.class" String clzFile = base+"."+cpu+"."+cpu; - try { _mach = ((Class) Class.forName( clzFile )).getDeclaredConstructor().newInstance(); } + try { _mach = ((Class) Class.forName( clzFile )).getDeclaredConstructor(new Class[]{CodeGen.class}).newInstance(this); } catch( Exception e ) { throw new RuntimeException(e); } + // Build global copies of common register masks. + long callerSave = _mach.callerSave(); + long neverSave = _mach. neverSave(); + int maxReg = Math.min(64,_mach.regs().length); + assert maxReg>=64 || (-1L << maxReg & callerSave)==0; // No stack slots in callerSave + _callerSave = new RegMask(callerSave); + + // Build a Return RegMask array. All returns have the following inputs: + // 0 - ctrl + // 1 - memory + // 2 - varies with returning GPR,FPR + // 3 - RPC + // 4+- Caller save registers + _retMasks = new RegMask[(maxReg-_callerSave.size()-Long.bitCount( neverSave ))+4]; + for( int reg=0, i=4; reg _cfg = new Ary<>(CFGNode.class); // Global schedule (code motion) nodes @@ -275,8 +344,6 @@ public class CodeGen { _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); @@ -285,7 +352,7 @@ public class CodeGen { // --------------------------- // Local (basic block) scheduler phase, a classic list scheduler - int _tLocal; + public int _tLocal; public CodeGen localSched() { assert _phase == Phase.Schedule; _phase = Phase.LocalSched; @@ -298,7 +365,7 @@ public class CodeGen { // --------------------------- // Register Allocation - int _tRegAlloc; + public int _tRegAlloc; public RegAlloc _regAlloc; public CodeGen regAlloc() { assert _phase == Phase.LocalSched; @@ -309,31 +376,46 @@ public class CodeGen { _tRegAlloc = (int)(System.currentTimeMillis() - t0); return this; } - public String reg(Node n) { + + // Human readable register name + public String reg(Node n) { return reg(n,null); } + public String reg(Node n, FunNode fun) { if( _phase.ordinal() >= Phase.RegAlloc.ordinal() ) { - String s = _regAlloc.reg(n); + String s = _regAlloc.reg(n,fun); if( s!=null ) return s; } return "N"+ n._nid; } + // --------------------------- // Encoding - int _tEncode; + public int _tEncode; + public Encoding _encoding; // Encoding object + public void preEncode() { } // overridden by alternative ports public CodeGen encode() { assert _phase == Phase.RegAlloc; _phase = Phase.Encoding; long t0 = System.currentTimeMillis(); + _encoding = new Encoding(this); + preEncode(); + _encoding.encode(); + _tEncode = (int)(System.currentTimeMillis() - t0); + return this; + } - - //_tEncode = (int)(System.currentTimeMillis() - t0); - //throw Utils.TODO(); + // --------------------------- + // Exporting to external formats + public CodeGen exportELF(String fname) throws IOException { + assert _phase == Phase.Encoding; + _phase = Phase.Export; + if( fname == null ) new LinkMem(this).link(); // In memory patching + else new ElfFile(this).export(fname); // External ELF file return this; } - // Encoded binary, no relocation info - public byte[] binary() { throw Utils.TODO(); } // --------------------------- + public boolean _asmLittle=true; SB asm(SB sb) { return ASMPrinter.print(sb,this); } public String asm() { return asm(new SB()).toString(); } @@ -352,4 +434,44 @@ public class CodeGen { // Debugging helper public Node f(int idx) { return _stop.find(idx); } + + + String printCFG() { + if( _cfg==null ) return "no CFG"; + SB sb = new SB(); + for( CFGNode cfg : _cfg ) + IRPrinter.printLine(cfg,sb); + return sb.toString(); + } + + public static void print_as_hex(Encoding enc) { + for (byte b : enc._bits.toByteArray()) { + System.out.print(String.format("%02X", b)); + } + System.out.println(); + } + + 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/src/main/java/com/seaofnodes/simple/codegen/ElfExpr.java b/src/main/java/com/seaofnodes/simple/codegen/ElfExpr.java new file mode 100644 index 00000000..9d9b8a1c --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/codegen/ElfExpr.java @@ -0,0 +1,87 @@ +package com.seaofnodes.simple.codegen; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class ElfExpr { + public static void main(String[] args) { + try (FileOutputStream fos = new FileOutputStream("hello.o")) { + ByteBuffer buffer = ByteBuffer.allocate(512); + buffer.order(ByteOrder.LITTLE_ENDIAN); + + // === ELF HEADER (64 bytes) === + buffer.put(new byte[]{ + 0x7F, 'E', 'L', 'F', // ELF Magic + 2, 1, 1, 0, // 64-bit, little-endian, version + 0, 0, 0, 0, 0, 0, 0, // ABI padding + 2, 0, // Type: Executable (ET_EXEC) + 0x3E, 0x00, // Machine: x86-64 + 1, 0, 0, 0, // ELF version + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Entry point + 0, 0, 0, 0, 0, 0, 0, 0, // Program header offset + 64, 0, 0, 0, 0, 0, 0, 0, // Section header offset + 0, 0, 0, 0, // Flags + 64, 0, // ELF header size + 0, 0, // Program header entry size + 0, 0, // Number of program header entries + 40, 0, // Section header entry size + 2, 0, // Number of section headers + 1, 0 // Section header string table index + }); + + // === .TEXT SECTION (Executable Code) === + byte[] textSection = { + // sys_write (write(1, message, 13)) + (byte) 0x48, (byte) 0xC7, (byte) 0xC0, 0x01, 0x00, 0x00, 0x00, // mov rax, 1 + (byte) 0x48, (byte) 0xC7, (byte) 0xC7, 0x01, 0x00, 0x00, 0x00, // mov rdi, 1 + (byte) 0x48, (byte) 0xBE, (byte)0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rsi, message + (byte) 0x48, (byte) 0xC7, (byte) 0xC2, 0x0D, 0x00, 0x00, 0x00, // mov rdx, 13 + (byte) 0x0F, (byte) 0x05, // syscall + + // sys_exit (exit(0)) + (byte) 0x48, (byte) 0xC7, (byte) 0xC0, 0x3C, 0x00, 0x00, 0x00, // mov rax, 60 + (byte) 0x48, (byte) 0x31, (byte) 0xFF, // xor rdi, rdi + (byte) 0x0F, (byte) 0x05 // syscall + }; + + // Append section data + buffer.position(256); + buffer.put(textSection); + + // === STRING MESSAGE (.data Section) === + byte[] message = "Hello, World!\n".getBytes(); + buffer.position(400); + buffer.put(message); + + // === SECTION HEADERS === + buffer.position(64); + + // Null Section (required as first entry) + buffer.put(new byte[40]); + + // .text Section + buffer.putInt(1); // Offset in section string table (".text") + buffer.putInt(1); // Type: SHT_PROGBITS + buffer.putLong(6); // Flags: Executable | Allocatable + buffer.putLong(0); // Virtual address + buffer.putLong(256); // Offset to section data + buffer.putLong(textSection.length); // Section size + buffer.putInt(0); // Link + buffer.putInt(0); // Info + buffer.putLong(4); // Address alignment + buffer.putLong(0); // No extra entries + + // === SECTION HEADER STRING TABLE === + buffer.position(500); + buffer.put(".text".getBytes()); + buffer.put((byte) 0x00); // Null terminator + + fos.write(buffer.array()); + System.out.println("ELF object file 'hello.o' created."); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java b/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java new file mode 100644 index 00000000..d1d20919 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java @@ -0,0 +1,382 @@ +package com.seaofnodes.simple.codegen; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.codegen.Encoding.BAOS; +import java.io.*; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.HashMap; +import java.util.Map; + +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; + + // bind 1011 + // 00001111 + // 10110000 + + 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 + // default visibility + 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(16); // 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) { + // Index 0 both designates the first entry in the table and serves as the undefined symbol index + for( int i = 0; i < SYMBOL_SIZE/4; i++ ) { + out.putInt(0); + } + // index is already set + for( Symbol s : _loc ) { + s.writeHeader(out); + } + for( Symbol s : _symbols ) { + 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; + } + + /* creates function and stores where it starts*/ + private final HashMap _funcs = new HashMap<>(); + private void encodeFunctions(SymbolSection symbols, DataSection text) { + for( int i=0; i<_code._cfg._len; i++ ) { + if( !(_code._cfg.at(i) instanceof FunNode fun) ) continue; + // skip until the function ends + while( !(_code._cfg.at(i) instanceof ReturnNode ret) ) + i++; + int end = _code._encoding._opStart[ret._nid] + _code._encoding._opLen[ret._nid]; + + if( fun._name==null ) continue; // Anonymous addresses are relocated within .text. + Symbol func = new Symbol(fun._name, text._index, SYM_BIND_GLOBAL, SYM_TYPE_FUNC); + func._value = _code._encoding._opStart[fun._nid]; + func._size = end - func._value; + symbols.push(func); + _funcs.put(fun.sig(), func); + } + } + + 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); + + // zero flag by default + // 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); + + // Build and write constant pool + BAOS cpool = new BAOS(); + Encoding enc = _code._encoding; + enc.writeConstantPool(cpool,false); + DataSection rdata = new DataSection(".rodata", 1 /* SHT_PROGBITS */, cpool); + rdata._flags = SHF_ALLOC; + pushSection(rdata); + + // populate function symbols + encodeFunctions(symbols, text); + + 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); + } + + // calculate local index + int num = 1; + for( Symbol s : symbols._loc ) + s._index = num++; + // extra space for .rela.text + int start_global = num+1; // Add one to skip the final .rela.text local symbol + for( Symbol a: symbols._symbols ) + a._index = start_global++; + int bigConIdx = start_global; + start_global += enc._bigCons.size(); + + // create .text relocations + DataSection text_rela = new DataSection(".rela.text", 4 /* SHT_RELA */); + for( Node n : enc._externals.keySet()) { + int nid = n._nid; + String extern = enc._externals.get(n); + + Symbol sym = new Symbol(extern, 0, SYM_BIND_GLOBAL, SYM_TYPE_NOTYPE); + sym._index = start_global++; + symbols.push(sym); + + int offset = enc._opStart[nid] + enc._opLen[nid] - 4; + + // u64 offset + write8(text_rela._contents, offset); + // u64 info + write8(text_rela._contents, ((long)sym._index << 32L) | 4L /* PLT32 */); + // i64 addend + write8(text_rela._contents, -4); + } + + // Write relocations for the constant pool + for( Encoding.Relo relo : enc._bigCons.values() ) { + Symbol glob = new Symbol("GLOB$"+bigConIdx, rdata._index, SYM_BIND_GLOBAL, SYM_TYPE_FUNC); + glob._value = relo._target; + glob._size = 1 << relo._t.log_size(); + glob._index = bigConIdx++; + symbols.push(glob); + write8(text_rela._contents, relo._opStart+relo._off); + write8(text_rela._contents, ((long)glob._index << 32L) | relo._elf ); + write8(text_rela._contents, -4); + } + + text_rela._flags = SHF_INFO_LINK; + text_rela._link = 2; + text_rela._info = text._index; + pushSection(text_rela); + + Symbol sym = new Symbol(text_rela._name, num++, SYM_BIND_LOCAL, SYM_TYPE_SECTION); + sym._name_pos = text_rela._name_pos; + sym._size = text_rela.size(); + symbols.push(sym); + + // 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 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; + + File file = new File(fname); + if( file.getParentFile()!=null ) + file.getParentFile().mkdirs(); + BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file)); + bos.write(out.array()); + bos.close(); + } + +} diff --git a/src/main/java/com/seaofnodes/simple/codegen/Encoding.java b/src/main/java/com/seaofnodes/simple/codegen/Encoding.java new file mode 100644 index 00000000..81f195b1 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/codegen/Encoding.java @@ -0,0 +1,475 @@ +package com.seaofnodes.simple.codegen; + +import com.seaofnodes.simple.Ary; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.print.IRPrinter; +import com.seaofnodes.simple.type.*; +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 implements com.seaofnodes.isa.CodeSink { + + // Top-level program graph structure + public 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 + + // Big Constant relocation info. + public static class Relo { + public final Node _op; + public final Type _t; // Constant type + public final int _align; // Log alignment, captured for diagnostic display + public final byte _off; // Offset from start of opcode + public final byte _elf; // ELF relocation type, e.g. 2/PC32 + public int _target; // Where constant is finally placede + public int _opStart; // Opcode start + Relo( Node op, Type t, byte off, byte elf ) { + _op=op; _t=t; _off=off; _elf=elf; + _align = t.log_size(); + } + } + public final HashMap _bigCons = new HashMap<>(); + + Encoding( CodeGen code ) { _code = code; } + + // Shortcut to the defining register + public short reg(Node n) { + return _code._regAlloc.regnum(n); + } + + public int read1(int idx) { + byte[] buf = _bits.buf(); + return (buf[idx]&0xFF); + } + public int read2(int idx) { + byte[] buf = _bits.buf(); + return (buf[idx]&0xFF) | (buf[idx+1]&0xFF) <<8; + } + public int read4(int idx) { + byte[] buf = _bits.buf(); + return (buf[idx]&0xFF) | (buf[idx+1]&0xFF) <<8 | (buf[idx+2]&0xFF)<<16 | (buf[idx+3]&0xFF)<<24; + } + public long read8(int idx) { + return (read4(idx) & 0xFFFFFFFFL) | read4(idx+4); + } + + // This buffer is invalid/moving until after all encodings are written + public byte[] bits() { return _bits.buf(); } + + // 4 byte little-endian write + public void patch4( int idx, int val ) { + byte[] buf = _bits.buf(); + buf[idx ] = (byte)(val ); + buf[idx+1] = (byte)(val>> 8); + buf[idx+2] = (byte)(val>>16); + buf[idx+3] = (byte)(val>>24); + } + + static void padN(int n, BAOS bits) { + while( (bits.size()+n-1 & -n) > bits.size() ) + bits.write(0); + } + + // Convenience for writing log-N + static void addN( int log, Type t, BAOS bits ) { + long x = t instanceof TypeInteger ti + ? ti.value() + : log==3 + ? Double.doubleToRawLongBits( ((TypeFloat)t).value()) + : Float.floatToRawIntBits((float)((TypeFloat)t).value()); + addN(log,x,bits); + } + static void addN( int log, long x, BAOS bits ) { + for( int i=0; i < 1<>= 8; + } + } + + public Encoding add1( int op ) { addN(0,op,_bits); return this; } + public Encoding add2( int op ) { addN(1,op,_bits); return this; } + public Encoding add4( int op ) { addN(2,op,_bits); return this; } + public Encoding add8(long op ) { addN(3,op,_bits); return this; } + + + // 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 ) { + TypeFunPtr tfp = (TypeFunPtr)con._con; + _internals.put(con,_code.link(tfp)); + return this; + } + public void jump( CFGNode jmp, CFGNode dst ) { + _internals.put(jmp,dst.uctrlSkipEmpty()); + } + + + 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 void largeConstant( Node relo, Type t, int off, int elf ) { + assert t.isConstant(); + assert (byte)off == off; + assert (byte)elf == elf; + _bigCons.put(relo,new Relo(relo,t,(byte)off,(byte)elf)); + } + + // -------------------------------------------------- + void encode() { + // Basic block layout: negate branches to keep blocks in-order; insert + // unconditional jumps. Attempt to keep backwards branches taken, + // forwards not-taken (this is the default prediction on most + // hardware). Layout is still Reverse Post Order but with more + // restrictions. + basicBlockLayout(); + + // Write encoding bits in order into a big byte array. + // Record opcode start and length. + writeEncodings(); + + // Short-form RIP-relative support: replace long encodings with short + // encodings and compact the code, changing all the offsets. + compactShortForm(); + + // Patch RIP-relative and local encodings now. + patchLocalRelocations(); + } + + // -------------------------------------------------- + // Basic block layout: negate branches to keep blocks in-order; insert + // unconditional jumps. Attempt to keep backwards branches taken, forwards + // not-taken (this is the default prediction on most hardware). Layout is + // still Reverse Post Order but with more restrictions. + private void basicBlockLayout() { + IdentityHashMap> rpos = new IdentityHashMap<>(); + Ary rpo = new Ary<>(CFGNode.class); + rpos.put(_code._start.loop(),rpo); + BitSet visit = _code.visit(); + //IdentityHashMap looptail = new IdentityHashMap<>(); + rpo.add(_code._stop); + for( Node n : _code._start._outputs ) + if( n instanceof FunNode fun ) { + int x = rpo._len; + //_rpo_cfg2(fun, visit, rpo, looptail); + _rpo_cfg(fun, visit, rpos ); + assert rpo.at(x) instanceof ReturnNode; + } + 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 + } + + + private void _rpo_cfg(CFGNode bb, BitSet visit, IdentityHashMap> rpos ) { + if( bb==null || visit.get(bb._nid) ) return; + visit.set(bb._nid); + CFGNode next = bb.uctrl(); + + // Loops run an inner "rpo_cfg", then append the entire loop body in + // place. This keeps loop bodies completely contained, although if + // Some Day Later we have real profile data we ought to arrange the + // branch orderings based on frequency. + if( bb instanceof LoopNode loop ) { + Ary body = new Ary<>(CFGNode.class); // Private RPO for the loop + rpos.put(loop.loop(),body); // Find it via loop tree + _rpo_cfg(next,visit,rpos); // RPO the loop + body.add(loop); // Include loop last (first in RPO) + Ary outer = rpos.get(loop.cfg(1).loop()); + outer.addAll(body); // Append to original CFG + return; + } + + // IfNodes visit 2 sides, and may choose to reorder them in the RPO + if( bb instanceof IfNode iff ) { + // Pick out the T/F projections + CProjNode t = iff.cproj(0); + CProjNode f = iff.cproj(1); + // Invert the branch or not + if( shouldInvert(t,f,iff.loopDepth()) ) { + iff.negate(); + t.invert(); + f.invert(); + CProjNode tmp=f; f=t; t=tmp; // Swap t/f + } + + // Whichever side is visited first becomes last in the RPO. With + // no loops, visit the False side last (so True side first) so that + // when the False RPO visit returns, the IF is immediately next. + // When the RPO is reversed, the fall-through path will always be + // following the IF. + _rpo_cfg(t,visit,rpos); // True side first + next = f; // False side last + } + + // If the *next* BB has already been visited, and we are not already a + // jump, we may need an unconditional forwards jump here + Ary rpo = rpos.get(bb.loop()); + if( next!=null && visit.get(next._nid) && !(bb instanceof IfNode) ) { + boolean needJump = next instanceof LoopNode + // Empty blocks from an IF will invert the IF and backwards + // branch to the loop head. If not empty, or not an IF + // will need a jump. + ? (bb.nOuts()>1 || !(bb.cfg0() instanceof IfNode) ) + // Forwards jump. If all the blocks, in RPO order, to our + // target are empty, we will fall in and not need a jump. + : !backwardsEmptyScan(rpo,next); + if( needJump ) { + CFGNode jmp = _code._mach.jump(); + jmp._ltree = bb._ltree; + jmp.setDefX(0,bb); + next.setDef(next._inputs.find(bb),jmp); + rpo.add(jmp); + } + } + + _rpo_cfg(next,visit,rpos); + rpo.add(bb); + } + + // Should this test be inverted? + private static boolean shouldInvert(CFGNode t, CFGNode f, int bld) { + int tld = t.loopDepth(), fld = f.loopDepth(); + // These next two are symmetric and can happen in any order; if `tld < + // bld` is true, the `fld < bld` must be false, or else both directions + // exit the loop... and the IF test would not be in the loop. + + // true to exit a loop usually (and false falls into Yet Another Loop + // Block), invert if false is taking an empty backwards branch. + if( tld < bld ) return forwardsEmptyScan(f,bld); + // false to exit a loop, normally invert to true (except empty back) + if( fld < bld ) return !forwardsEmptyScan(t,bld); + + // Not exiting the loop; staying at this depth (or going deeper loop). + + // Jump/true to an empty backwards block + if( forwardsEmptyScan(t,bld) ) return false; + if( forwardsEmptyScan(f,bld) ) return true ; + + // Fall/false into a full block, Jump/true to an empty block. + if( f.nOuts()>1 && t.nOuts()==1 ) return false; + if( t.nOuts()>1 && f.nOuts()==1 ) return true ; + // Everything else equal, use pre-order + return t._pre > f._pre; + } + + // Do we continue forwards from 'c' to the Loop back edge, without doing + // anything else? Then this branch can directly jump to the loop head. + private static boolean forwardsEmptyScan( CFGNode c, int bld ) { + if( c.nOuts()!=1 || c.loopDepth()!=bld ) return false; + return c.uctrl() instanceof RegionNode cfg && + ((cfg instanceof LoopNode && cfg._ltree==c._ltree) || forwardsEmptyScan(cfg,bld)); + } + + // Is the CFG from "next" to the end empty? This means jumping to "next" + // will naturally fall into the end. + private static boolean backwardsEmptyScan(Ary 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]); + } + } + } + } + + // -------------------------------------------------- + // 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 ) { // 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 + void patchLocalRelocations() { + // Walk the local code-address relocations + for( Node src : _internals.keySet() ) { + int start = _opStart[src._nid]; + Node dst = _internals.get(src); + int target = _opStart[dst._nid]; + ((RIPRelSize)src).patch(this, start, _opLen[src._nid], target - start); + } + } + + // -------------------------------------------------- + // Write the constant pool into the BAOS and optionally patch locally + void writeConstantPool( BAOS bits, boolean patch ) { + padN(16,bits); + + HashMap targets = new HashMap<>(); + + // By log size + for( int log = 3; log >= 0; log-- ) { + // Write the 8-byte constants + for( Node op : _bigCons.keySet() ) { + Relo relo = _bigCons.get(op); + if( relo._t.log_size()==log ) { + // Map from relo to constant start and patch + Integer target = targets.get(relo._t); + if( target==null ) { + targets.put(relo._t,target = bits.size()); + // Put constant into code space. + if( relo._t instanceof TypeTuple tt ) // Constant tuples put all entries + for( Type tx : tt._types ) + addN(log,tx,bits); + else + addN(log,relo._t,bits); + } + relo._target = target; + relo._opStart= _opStart[op._nid]; + // Go ahead and locally patch in-memory + if( patch ) + ((RIPRelSize)op).patch(this, relo._opStart, _opLen[op._nid], relo._target - relo._opStart); + } + } + } + } + + + // A series of libc/external calls that Simple can link against in a JIT. + // Since no runtime in the JVM process, using magic numbers for the CPU + // emulators to pick up on. + public static int SENTINAL_CALLOC = -4; + + void patchGlobalRelocations() { + for( Node src : _externals.keySet() ) { + int start = _opStart[src._nid]; + String dst = _externals.get(src); + int target = switch( dst ) { + case "calloc" -> SENTINAL_CALLOC; + default -> throw Utils.TODO(); + }; + ((RIPRelSize)src).patch(this, start, _opLen[src._nid], target - start); + } + } +} diff --git a/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java index 52906afb..447a6df1 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java +++ b/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java @@ -172,7 +172,7 @@ public abstract class GlobalCodeMotion { continue outer; // Nope, await all uses done // Loads need their memory inputs' uses also done - if( n instanceof MemOpNode ld && !ld.isMem() ) + if( n instanceof MemOpNode ld && ld._isLoad ) for( Node memuse : ld.antiDeps() ) if( late[memuse._nid]==null ) continue outer; @@ -205,9 +205,10 @@ public abstract class GlobalCodeMotion { lca = use_block(n,use, late).domLCA(lca,null); // Loads may need anti-dependencies, raising their LCA - if( n instanceof MemOpNode load && !load.isMem() ) + if( n instanceof MemOpNode load && load._isLoad ) lca = find_anti_dep(lca,load,early,late,anti); + // 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. @@ -250,7 +251,7 @@ public abstract class GlobalCodeMotion { if( visit.get(def._nid) ) return; visit.set(def._nid); for( Node out : def._outputs ) - if( out instanceof MemOpNode ld && !ld.isMem() && late[ld._nid]==null ) work.push(ld); + if( out instanceof MemOpNode ld && ld._isLoad && late[ld._nid]==null ) work.push(ld); if( def instanceof MemMergeNode ) for( int i=1; i TMP = new IdentityHashMap<>(); // Inteference Graph: Array of Bitsets - private static final Ary IFG = new Ary<>(BitSet.class); + static final Ary IFG = new Ary<>(BitSet.class); static void resetIFG() { for( BitSet bs : IFG ) if( bs!=null ) bs.clear(); @@ -80,6 +80,8 @@ abstract public class IFG { while( !WORK.isEmpty() ) do_block(round,alloc,WORK.pop()); + if( alloc.success() ) + convert2DAdjacency(alloc); return alloc.success(); } @@ -126,10 +128,15 @@ abstract public class IFG { TMP.remove(lrg); // Kill def } + // Phis use and define the same live range, i.e. these LRGs already + // marked conflicted, no need to mark again if( n instanceof PhiNode ) return; - // Instructions can clobber registers without defining an LRG. - if( n instanceof MachNode m ) kills(alloc,m); + + // Kill any killed registers; milli-code routines like New can kill + // will not being a CFG. + if( n instanceof MachNode m ) + kills(alloc,m); // Interfere n with all live if( lrg!=null ) { @@ -145,7 +152,8 @@ abstract public class IFG { // must fail. If *n* (a subset of lrg) needs the single // last tlrg register then only tlrg must fail. if( ((MachNode)n).outregmap().size1() ) { - if( !tlrg.clr(lrg._mask.firstReg()) ) alloc.fail(tlrg); + if( !tlrg.clr(lrg._mask.firstReg()) ) // Clear bit, no interference + alloc.fail(tlrg); // Clearing drives mask to empty } else addIFG(lrg,tlrg); // Add interference } } @@ -223,13 +231,11 @@ abstract public class IFG { CFGNode bb = priorbb.cfg(i); if( bb == null ) return; // Start has no prior while( !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<>() ); + IdentityHashMap lrgs = BBOUTS.computeIfAbsent( bb, k -> new IdentityHashMap<>() ); - for( LRG lrg : TMP.keySet() ) { + 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 ) { @@ -250,6 +256,24 @@ abstract public class IFG { } + static void convert2DAdjacency( RegAlloc alloc ) { + // Convert the 2-D array of bits (a 1-D array of BitSets) into an + // adjacency matrix. + int maxlrg = alloc._LRGS.length; + for( int i=1; i=0; lrg=ifg.nextSetBit(lrg+1) ) { + LRG lrg1 = alloc._LRGS[lrg]; + lrg0.addNeighbor(lrg1); + lrg1.addNeighbor(lrg0); + } + } + } + } + + // ------------------------------------------------------------------------ // Color the inference graph. @@ -266,22 +290,10 @@ abstract public class IFG { // If there's no spare color we'll have to spill this at-risk live range. public static boolean color(int round, RegAlloc alloc) { - - // Convert the 2-D array of bits (a 1-D array of BitSets) into an - // adjacency matrix. int maxlrg = alloc._LRGS.length, nlrgs=0; - for( int i=1; i=0; lrg=ifg.nextSetBit(lrg+1) ) { - LRG lrg1 = alloc._LRGS[lrg]; - lrg0.addNeighbor(lrg1); - lrg1.addNeighbor(lrg0); - } - } - } + for( int i=1; i _adj; + public int nadj() { return _adj==null ? 0 : _adj._len; } + void addNeighbor(LRG lrg) { if( _adj==null ) _adj = new Ary<>(LRG.class); _adj.push(lrg); @@ -72,7 +77,7 @@ public class LRG { } // More registers than neighbors - boolean lowDegree() { return (_adj==null ? 0 : _adj._len) < _mask.size(); } + boolean lowDegree() { return nadj() < _mask.size(); } LRG( short lrg ) { _lrg = lrg; _reg = -1; } @@ -116,17 +121,20 @@ public class LRG { if( _machDef==null ) { _machDef = lrg._machDef; } else if( lrg._machDef!=null ) { + if( _machDef != lrg._machDef ) _multiDef=true; if( _1regDefCnt==0 ) _machDef = lrg._machDef; else if( _machDef==lrg._machDef ) _1regDefCnt--; } _1regDefCnt += lrg._1regDefCnt; + _multiDef |= lrg._multiDef; if( _machUse==null ) { _machUse = lrg._machUse; _uidx = lrg._uidx; } else if( lrg._machUse!=null ) { + if( _machUse != lrg._machUse ) _multiUse=true; if( _1regUseCnt==0 ) { _machUse = lrg._machUse; _uidx = lrg._uidx; @@ -135,6 +143,7 @@ public class LRG { _1regUseCnt--; } _1regUseCnt += lrg._1regUseCnt; + _multiUse |= lrg._multiUse; // Fold deepest Split _splitDef = deepSplit(_splitDef,lrg._splitDef); @@ -142,7 +151,9 @@ public class LRG { // Fold together masks RegMask mask = _mask.and(lrg._mask); - _mask = mask==null ? _mask.copy().and(lrg._mask) : mask; + if( mask==null ) + mask = _mask.copy().and(lrg._mask); + _mask = mask; return this; } @@ -152,6 +163,8 @@ public class LRG { // 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 ) @@ -163,6 +176,8 @@ public class LRG { // 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 ) diff --git a/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java b/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java new file mode 100644 index 00000000..e1e849e6 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java @@ -0,0 +1,23 @@ +package com.seaofnodes.simple.codegen; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.node.Node; + +// In-memory linking +public class LinkMem { + final CodeGen _code; + LinkMem( CodeGen code ) { _code = code; } + + public CodeGen link() { + Encoding enc = _code._encoding; + + // Patch external calls internally + enc.patchGlobalRelocations(); + + // Write any large constants into a constant pool; they + // are accessed by RIP-relative addressing. + enc.writeConstantPool(enc._bits,true); + + return _code; + } +} diff --git a/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java b/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java index 1796ef7c..de4fadc4 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java +++ b/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java @@ -48,6 +48,9 @@ public abstract class ListScheduler { computeSingleRDef(bb,use); else computeSingleRDef(bb,n); + + if( n instanceof NewNode || n instanceof CallNode ) + bb.fun()._hasCalls = true; return this; } diff --git a/src/main/java/com/seaofnodes/simple/codegen/Machine.java b/src/main/java/com/seaofnodes/simple/codegen/Machine.java index 13d7abc1..2766414f 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/Machine.java +++ b/src/main/java/com/seaofnodes/simple/codegen/Machine.java @@ -1,22 +1,97 @@ package com.seaofnodes.simple.codegen; import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeFunPtr; abstract public class Machine { // Human readable machine name. Something like "x86-64" or "arm" or "risc5" public abstract String name(); + // Default opcode size for printing; 4 bytes for most 32-bit risc chips + public int defaultOpSize() { return 4; } // Human-readable name for a register number, e.g. "RAX" or "R0" - public abstract String reg( int reg ); + public abstract String[] regs(); // 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(); + // List of caller-save registers, as a 64bit mask + public abstract long callerSave(); + // List of never-save registers, e.g. RSP or a ZERO register if you have one + public abstract long neverSave(); + // Call Argument Mask. Passed in the function signature and argument + // number (2-based; 0 is for control and 1 for memory). Also passed in a 0 + // for the function itself, or for *outgoing* calls, the maximum stack slot + // given to the incoming function arguments (stack slots reserved for + // incoming arguments). + public abstract RegMask callArgMask(TypeFunPtr tfp, int arg, int maxArgSlot); + // Return register mask, based on signature (GPR vs FPR) + public abstract RegMask retMask(TypeFunPtr tfp); + // Return PC register + public abstract int rpc(); // Return a MachNode unconditional branch public abstract CFGNode jump(); - // Break an infinite loop - public abstract IfNode never( CFGNode ctrl ); // Instruction select from ideal nodes public abstract Node instSelect( Node n ); + + /** Stack Slot Numbering + +RA gives all registers a number, starting at 0. Stack slots continue this +numbering going up from the last register number. Common register numbers are +0-15 for GPRs, 16-31 for FPRs, 32 for flags, and stack slots starting at +register#33 going up. These numbers are machine-specific, YMMV, etc; e.g. RPC +is only in stack slot 0 on X86; other cpus start with the rpc in a register +which may spill into any generic spill slot. + +For ease of reading and printing, stack slots start again at slot#0 - but +during RA they are actually biased by CPU.MAX_REG. + +Stack Layout during Reg Alloc: + + Slot Value Description + ---- ------- ----------- +MAXREG+ R+1 argR maxArgSlot(calleR tfp), can be 0 + ... +MAXREG+ 1 arg0 + ---- -- Caller ------ +MAXREG+ 0 RPC +MAXREG+ R+E+N PAD optional alignment + +MAXREG+ + Spills Caller Frame Space; N spills +MAXREG+ R+E+1 Spills + +MAXREG+ R+E argE maxArgSlot(calleE tfp), can be 0 + ... +MAXREG+ R+2 arg0 + ---- -- Callee ------- + ... + +Slots defined by maxArgSlot do not have to contain the actual passed argument; +they can be "shadow slots". If they exist, they are property of the frame / +FunNode where the TFP comes from. + +For a given calleR, there are generally many calleE's and they all share the +low-range stack slots. + +Example:, Win64, main(int arg) { calloc(1,size); } + + Post-Warp: SP+56 shadow-main-arg +Pre-alloc Post-Alloc SP+48 RPC +--- ------- ---------- ------------- +40 spill#3 SP+40 SP+40 spill#3 +39 spill#2 SP+32 "" +38 spill#1 SP+24 "" +37 spill#0 SP+16 "" spill#0 +36 shadow-calloc#size SP+ 8 SP+ 8 shadow-calloc#size +35 shadow-calloc#1 SP+ 0 SP+ 0 shadow-calloc#1 +--- --- FRAME BASE ---------- ---- FRAME BASE ------- +34 shadow-main-arg SP+56 +33 RPC SP+48 + + + */ + + + // Maximum stack slot (or 0) for the args in this TFP. This will include + // shadow slots if defined in the ABI, even if all arguments are passed in + // registers. + public abstract short maxArgSlot(TypeFunPtr tfp); + } diff --git a/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java b/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java new file mode 100644 index 00000000..777182dc --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java @@ -0,0 +1,14 @@ +package com.seaofnodes.simple.codegen; + +// This Node rip-relative encodings has sizes that vary by delta +public interface RIPRelSize { + // delta is the distance from the opcode *start* to the target start. Each + // hardware target adjusts as needed, X86 measures from the opcode *end* + // and small jumps are 2 bytes, so they'll need to subtract 2. + byte encSize(int delta); + + // Patch full encoding in. Used for both short and long forms. delta + // again is from the opcode *start*. + void patch( Encoding enc, int opStart, int opLen, int delta ); + +} diff --git a/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java b/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java index af28cbb3..dd3d2acb 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java +++ b/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java @@ -5,6 +5,7 @@ import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.Type; import java.util.Arrays; +import java.util.BitSet; import java.util.IdentityHashMap; /** @@ -138,11 +139,18 @@ public class RegAlloc { } // Printable register number for node n - String reg( Node n ) { + String reg( Node n ) { return reg(n,null); } + String reg( Node n, FunNode fun ) { LRG lrg = _lrg(n); if( lrg==null ) return null; + // No register yet, use LRG if( lrg._reg == -1 ) return "V"+lrg._lrg; - return _code._mach.reg(lrg._reg); + // Chosen machine register unless stack-slot and past RA + String[] regs = _code._mach.regs(); + if( lrg._reg < regs.length || _code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() || fun==null ) + return RegMask.reg(regs,lrg._reg); + // Stack-slot past RA uses the frame layout logic + return "[rsp+"+fun.computeStackOffset(_code,lrg._reg)+"]"; } // ----------------------- @@ -150,9 +158,24 @@ public class RegAlloc { public void regAlloc() { // Insert callee-save registers + String[] regs = _code._mach.regs(); + long neverSave= _code._mach.neverSave(); for( CFGNode bb : _code._cfg ) - if( bb instanceof FunNode fun ) - insertCalleeSave(fun); + if( bb instanceof FunNode fun ) { + ReturnNode ret = fun.ret(); + int len = Math.min(regs.length,64); + for( int reg=0; reg= 7 ) // Really expect to be done soon + throw new IllegalStateException("Register allocation made no progress after eight rounds"); round++; } postColor(); // Remove no-op spills @@ -193,24 +217,12 @@ public class RegAlloc { 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) { @@ -220,12 +232,15 @@ public class RegAlloc { // independently... which generally requires a full pass over the // program for each failing live range. i.e., might be a lot of // passes. + + // Sort, to avoid non-deterministic HashMap ordering LRG[] splits = _failed.keySet().toArray(new LRG[0]); - Arrays.sort(splits, (x,y) -> x._lrg - y._lrg); - for( LRG lrg : splits ) split(round,lrg); + Arrays.sort(splits, (x,y) -> x._lrg - y._lrg ); + for( LRG lrg : splits ) + split(round,lrg); } - // Split this live range + // Split this live range, top level heuristic boolean split( byte round, LRG lrg ) { assert lrg.leader(); // Already rolled up @@ -234,7 +249,7 @@ public class RegAlloc { // Register mask when empty; split around defs and uses with limited // register masks. - if( lrg._mask.isEmpty() ) { + if( lrg._mask.isEmpty() && (!lrg._multiDef || lrg._1regUseCnt==1) ) { if( lrg._1regDefCnt <= 1 && lrg._1regUseCnt <= 1 && (lrg._1regDefCnt + lrg._1regUseCnt) > 0 && @@ -358,10 +373,10 @@ public class RegAlloc { (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, with no intervening clobber - !(n.nOuts()==1 && n.out(0) instanceof SplitNode split && sameBlockNoClobber(split)) ) + // Single user is already a split adjacent + !(n.nOuts()==1 && n.out(0) instanceof SplitNode split && sameBlockNoClobber(split) ) ) // Split after def in min loop nest - insertAfterAndReplace(makeSplit("def/loop",round,lrg),n,true); + insertAfterAndReplace( makeSplit("def/loop",round,lrg), n,true); } // PhiNodes check all CFG inputs @@ -372,17 +387,19 @@ public class RegAlloc { // splitting in inner loop or at loop border (min==max || phi.region().cfg(i).loopDepth() <= min) && // and not around the backedge of a loop (bad place to force a split, hard to remove) - !(phi.region() instanceof LoopNode && i==2 && phi.in(i) instanceof PhiNode pp && pp.region()==phi.region()) ) + !(phi.region() instanceof LoopNode && i==2 && (phi.in(i) instanceof PhiNode pp && pp.region()==phi.region())) ) // Split before phi-use in prior block insertBefore(phi,i, "use/loop/phi",round,lrg); } else { // Others check uses for( int i=1; i>32); int d = cfg.loopDepth(); + // if n will lower the min loop and is in the tail end of the loop + // header, splitting "around" the loop will not help. Treat n as being + // in the loop. + if( d < min ) { + if( cfg.uctrl() instanceof LoopNode loop && loop.entry()==cfg ) { + for( int i=cfg.nOuts()-2; i>=0; i-- ) { + Node out = cfg.out(i); + if( n==out ) + { d = loop.loopDepth(); break; } // Treat n as being "in the loop" + if( !((out instanceof MachNode mach && mach.isClone()) || out instanceof SplitNode ) ) + break; // Treat b as "normal", out of loop + } + } + } + + // lower min, raise max, and re-fold min = Math.min(min,d); max = Math.max(max,d); return ((long)max<<32) | min; @@ -428,7 +461,6 @@ public class RegAlloc { for( Node n : _ns ) assert !n.isDead(); } - void insertBefore(Node n, int i, String kind, byte round, LRG lrg) { insertBefore(n,i,kind,round,lrg,true); } void insertBefore(Node n, int i, String kind, byte round, LRG lrg, boolean skip) { Node def = n.in(i); // Phi splitting may visit the synthetic never-exit arm. @@ -449,6 +481,9 @@ public class RegAlloc { if( skip && def instanceof SplitNode && cfg==def.cfg0() ) n.in(i).setDefOrdered(1,def.in(1)); } + void insertBefore(Node n, int i, String kind, byte round, LRG lrg) { + insertBefore(n,i,kind,round,lrg,true); + } // Replace uses of `def` with `split`, and insert `split` immediately after // `def` in the basic block. @@ -486,18 +521,34 @@ public class RegAlloc { // ----------------------- // POST PASS: Remove empty spills that biased-coloring made private void postColor() { + int maxReg = -1; for( CFGNode bb : _code._cfg ) { // For all ops + if( bb instanceof FunNode fun ) + maxReg = -1; // Reset for new function + // Compute frame size, based on arguments and largest reg seen + if( bb instanceof ReturnNode ret ) + ret.fun().computeFrameAdjust(_code,maxReg); + // Raise frame size by max stack args passed, even if ignored + if( bb instanceof CallEndNode cend ) + maxReg = Math.max(maxReg,cend._xslot); + for( int j=0; j * 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 + * 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 @@ -32,6 +32,7 @@ public class RegMask { } public RegMask(long bits ) { _bits0 = bits; } public RegMask(long bits0, long bits1 ) { _bits0 = bits0; _bits1 = bits1; } + public RegMask(RegMask r) { _bits0 = r._bits0; _bits1 = r._bits1; } private RegMask() { _bits0 = _bits1 = 0; } // AND, with copy-on-write if changing @@ -81,7 +82,7 @@ public class RegMask { boolean isEmpty() { return _bits0==0 && _bits1==0; } - boolean test( int reg ) { + public boolean test( int reg ) { return ((reg<64 ? (_bits0 >> reg) : (_bits1 >> (reg-64))) & 1) != 0; } @@ -105,35 +106,17 @@ public class RegMask { Machine mach = CodeGen.CODE._mach; if( _bits0==0 && _bits1==0 ) return sb.p("[]"); sb.p("["); + String[] regs = mach.regs(); for( int i=0; i<64; i++ ) if( ((_bits0 >> i)&1) != 0 ) - sb.p(mach.reg(i)).p(","); + sb.p(reg(regs,i)).p(","); for( int i=0; i<64; i++ ) if( ((_bits1 >> i)&1) != 0 ) - sb.p(mach.reg(i+64)).p(","); + sb.p(reg(regs,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; + public static String reg(String[] regs, int i) { + return i 0; case MemMergeNode mmm -> missingAlias(mmm); diff --git a/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/src/main/java/com/seaofnodes/simple/node/CFGNode.java index 0e770659..47dfa311 100644 --- a/src/main/java/com/seaofnodes/simple/node/CFGNode.java +++ b/src/main/java/com/seaofnodes/simple/node/CFGNode.java @@ -1,10 +1,9 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.codegen.CodeGen; - import com.seaofnodes.simple.Ary; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.*; import java.util.BitSet; import java.util.HashMap; @@ -58,6 +57,13 @@ public abstract class CFGNode extends Node { return c; } + // Used by the encoding / final BB layout + public CFGNode uctrlSkipEmpty() { + CFGNode x = this, y; + while( x.nOuts() == 1 && (y=x.uctrl())!=null ) // Skip empty blocks + x = y; + return x; + } // ------------------------------------------------------------------------ /** @@ -91,8 +97,8 @@ public abstract class CFGNode extends Node { while( lhs != rhs ) { if( lhs==null || rhs==null ) return null; int comp = lhs.idepth() - rhs.idepth(); - if( comp >= 0 ) lhs = (dep==null ? lhs : (CFGNode)lhs.addDep(dep)).idom(); - if( comp <= 0 ) rhs = (dep==null ? rhs : (CFGNode)rhs.addDep(dep)).idom(); + if( comp >= 0 ) lhs = (dep==null ? lhs : dep.addDep(lhs)).idom(); + if( comp <= 0 ) rhs = (dep==null ? rhs : dep.addDep(rhs)).idom(); } return lhs; } @@ -110,9 +116,9 @@ public abstract class CFGNode extends Node { public LoopNode loop() { return _ltree._head; } public int loopDepth() { return _ltree==null ? 0 : _ltree.depth(); } - LoopTree _ltree; - int _pre; // Pre-order numbers for loop tree finding - private static class LoopTree { + public LoopTree _ltree; + public int _pre; // Pre-order numbers for loop tree finding + static class LoopTree { LoopTree _par; final LoopNode _head; int _depth; @@ -196,4 +202,9 @@ public abstract class CFGNode extends Node { (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/src/main/java/com/seaofnodes/simple/node/CProjNode.java b/src/main/java/com/seaofnodes/simple/node/CProjNode.java index bfdcb04b..4d3d8d0d 100644 --- a/src/main/java/com/seaofnodes/simple/node/CProjNode.java +++ b/src/main/java/com/seaofnodes/simple/node/CProjNode.java @@ -11,10 +11,10 @@ import com.seaofnodes.simple.type.TypeTuple; public class CProjNode extends CFGNode { // Which slice of the incoming multipart value - public final int _idx; + public int _idx; // Debugging label - public final String _label; + public String _label; public CProjNode(Node ctrl, int idx, String label) { super(ctrl); @@ -47,15 +47,21 @@ public class CProjNode extends CFGNode { } // Flip a negating if-test, to remove the not - if( ctrl() instanceof IfNode iff && iff.pred().addDep(this) instanceof NotNode not ) + if( ctrl() instanceof IfNode iff && addDep(iff.pred()) instanceof NotNode not ) return new CProjNode(new IfNode(iff.ctrl(),not.in(1)).peephole(),1-_idx,_idx==0 ? "False" : "True"); // Copy of some other input return ((MultiNode)ctrl()).pcopy(_idx); } + // Only called during basic-block layout, inverts a T/F CProj + public void invert() { + _label = _idx == 0 ? "False" : "True"; + _idx = 1-_idx; + } + @Override - boolean eq( Node n ) { return _idx == ((CProjNode)n)._idx; } + public boolean eq( Node n ) { return _idx == ((CProjNode)n)._idx; } @Override int hash() { return _idx; } diff --git a/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/src/main/java/com/seaofnodes/simple/node/CallEndNode.java index 0ee3d418..47e80ca9 100644 --- a/src/main/java/com/seaofnodes/simple/node/CallEndNode.java +++ b/src/main/java/com/seaofnodes/simple/node/CallEndNode.java @@ -3,7 +3,7 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.type.*; /** @@ -39,7 +39,7 @@ public class CallEndNode extends CFGNode implements MultiNode { return TypeTuple.RET.dual(); Type ret = Type.BOTTOM; TypeMem mem = TypeMem.BOT; - if( call.fptr().addDep(this)._type instanceof TypeFunPtr tfp ) { + if( addDep(call.fptr())._type instanceof TypeFunPtr tfp ) { ret = tfp.ret(); // Here, if I can figure out I've found *all* callers, then I can meet // across the linked returns and join with the function return type. @@ -71,8 +71,9 @@ public class CallEndNode extends CFGNode implements MultiNode { 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) ) + while( !(idom instanceof FunNode) ) idom = idom.idom(); + // Inline? if( idom != fun ) { // Trivial inline: rewrite _folding = true; @@ -82,14 +83,16 @@ public class CallEndNode extends CFGNode implements MultiNode { fun.setDef(2,call.ctrl()); // Bypass the Call; fun.ret().setDef(3,null); // Return is folding also CodeGen.CODE.addAll(fun._outputs); + // Inlining immediately blows all cache idepth fields past the inline point. + // Bump the global version number invalidating them en-masse. CodeGen.CODE.invalidateIDepthCaches(); return this; } } else { - fun.addDep(this); + addDep(fun); } } else { // Function ptr has multiple users (so maybe multiple call sites) - fptr.addDep(this); + addDep(fptr); } } @@ -103,7 +106,7 @@ public class CallEndNode extends CFGNode implements MultiNode { Node fptr = call.fptr(); if( !(fptr instanceof ConstantNode) || !(fptr._type instanceof TypeFunPtr tfp && tfp.notNull() && tfp.isConstant()) ) - { fptr.addDep(this); return false; } + { addDep(fptr); return false; } if( call.err()!=null ) return false; ReturnNode ret = (ReturnNode)in(1); FunNode fun = ret.fun(); @@ -164,13 +167,13 @@ public class CallEndNode extends CFGNode implements MultiNode { private int smallBody(FunNode fun) { ReturnNode ret = fun.ret(); for( Node in : ret._inputs ) - if( in!=null ) in.addDep(this); + if( in!=null ) addDep(in); if( ret.ctrl()!=fun ) return 0; TINYBODY[0] = fun; int len=1; for( int i=0; i>3) + : stackSlot - _maxArgSlot; + return slotRotate*8; + } + } diff --git a/src/main/java/com/seaofnodes/simple/node/IfNode.java b/src/main/java/com/seaofnodes/simple/node/IfNode.java index 2c4cad70..81fbe170 100644 --- a/src/main/java/com/seaofnodes/simple/node/IfNode.java +++ b/src/main/java/com/seaofnodes/simple/node/IfNode.java @@ -1,10 +1,10 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.print.ExprPrinter; +import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.type.*; -import java.util.HashSet; public class IfNode extends CFGNode implements MultiNode { @@ -17,7 +17,13 @@ public class IfNode extends CFGNode implements MultiNode { @Override public String label() { return "If"; } - @Override protected String format() { return "if( %1 )"; } + @Override + protected ExprPrinter _print1(ExprPrinter p) { + p.p("if( "); + if( in(1)==null ) p.p("never"); + else p.n(in(1)); + return p.p(" )"); + } public Node ctrl() { return in(0); } public Node pred() { return in(1); } @@ -53,11 +59,40 @@ public class IfNode extends CFGNode implements MultiNode { // test on either the true or false branch, that side wins. if( !pred()._type.isHighOrConst() ) for( CFGNode dom = idom(), prior=this; dom!=null; prior = dom, dom = dom.idom() ) - if( dom.addDep(this) instanceof IfNode iff && iff.pred().addDep(this)==pred() && prior instanceof CProjNode prj ) { + if( addDep(dom) instanceof IfNode iff && addDep(iff.pred())==pred() && prior instanceof CProjNode prj ) { setDef(1,con( prj._idx==0 ? 1 : 0 )); return this; } return null; } + // MachNode variants need to support this and negate the conditional test. + // The following CProjs will be inverted by the caller. + public void negate() { throw Utils.TODO(); } + + // Negate the sense of a test + public static String negate( String bop ) { + return switch( bop ) { + case "<" -> ">="; + case "<=" -> ">" ; + case "==" -> "!="; + case "!=" -> "=="; + case ">" -> "<="; + case ">=" -> "<" ; + default -> throw Utils.TODO(); + }; + } + + // Swap compare operands + public static String swap( String bop ) { + return switch( bop ) { + case "<" -> ">" ; + case "<=" -> ">="; + case "==" -> "!="; + case "!=" -> "=="; + case ">" -> "<" ; + case ">=" -> "<="; + default -> throw Utils.TODO(); + }; + } } diff --git a/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/src/main/java/com/seaofnodes/simple/node/LoadNode.java index b9905912..3084b4f7 100644 --- a/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -4,8 +4,8 @@ import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.type.*; +import java.util.BitSet; /** * Load represents extracting a value from inside a memory object, @@ -22,7 +22,7 @@ public class LoadNode extends MemOpNode { * @param off The offset inside the struct base */ public LoadNode(Parser.Lexer loc, String name, int alias, Type glb, Node mem, Node ptr, Node off) { - super(loc, name, alias, glb, mem, ptr, off); + super(loc, name, alias, true, glb, mem, ptr, off); } @Override boolean canDrop(MemOpNode other, Node dep) { @@ -33,10 +33,10 @@ public class LoadNode extends MemOpNode { // aggregates might lead to a clobber, so stop rather than search further. private boolean clobbered(Node dep) { Node mem = mem(); - mem.addDepForwards(dep); + dep.addDepForwards(mem); for( Node use : mem._outputs ) { if( use==null ) continue; - use.addDepForwards(dep); + dep.addDepForwards(use); if( use instanceof StoreNode || use instanceof PhiNode || use instanceof MemMergeNode || use instanceof NewNode || use instanceof CallNode ) return true; @@ -69,17 +69,8 @@ public class LoadNode extends MemOpNode { Node ptr = ptr(); Node mem = mem(); - // Simple Load-after-Store on same address. - if( mem() instanceof StoreNode st && - _alias==st._alias && 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(0) ) // Must check same object - return zero(nnn); // Load zero from new + Node win = find(mem,ptr,null,null); + if( win!=null ) return folded(win); // Uplift control to a prior dominating load. for( Node memuse : mem._outputs ) @@ -88,52 +79,7 @@ public class LoadNode extends MemOpNode { cfg0()!=null && cfg0().domLCA(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 ) { - mem.addDep(this); - switch( mem ) { - case MemMergeNode merge: - mem = merge.alias(_alias); - break; - case StoreNode st: - if( _alias==st._alias && ptr == st.ptr().addDep(this) && off() == st.off() ) - return extend(castRO(st.val())); // Proved equal - // Can we prove unequal? Offsets do not overlap? - if( _alias==st._alias && !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.mem();// Bypass unrelated New - break; - case StartNode start: break outer; - case CallEndNode cend: break outer; // TODO: Bypass no-alias call - default: throw Utils.TODO(); - } - break; - - default: - break outer; - } - } + if( ptr instanceof ReadOnlyNode ro ) ptr=ro.in(1); // Push a Load up through a Phi, as long as it collapses on at least // one arm. If at a Loop, the backedge MUST collapse - else we risk @@ -143,18 +89,26 @@ public class LoadNode extends MemOpNode { // else ptr.x = e1; : e1; // val = ptr.x; ptr.x = val; if( mem() instanceof MemPhiNode memphi && memphi.region()._type == Type.CONTROL && memphi.nIns()== 3 && + // Array access control must not be moved across the merge. + in(0)==null && // 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); + // Returning to this memory means no change only if the pointer + // is also loop invariant. A pointer Phi may select another object. + Node stop = memphi.region() instanceof LoopNode && + !(ptr instanceof PhiNode p && p.region()==memphi.region()) ? memphi : null; + if( find(memphi.in(2),ptr,stop,new BitSet())!=null || + (!(memphi.region() instanceof LoopNode) && find(memphi.in(1),ptr,null,new BitSet())!=null) ) { + PhiNode phi = new PhiNode(_name,_declaredType,memphi.region()).keep(); + phi.setType(_type); // Backedge searches may return this value Phi. + for( int i=1; i implements Cloneable { assert isDead(); // Really dead now } + // Preserve CFG use-ordering when killing + public void killOrdered() { + CFGNode cfg = cfg0(); + cfg._outputs.remove(cfg._outputs.find(this)); + _inputs.set(0,null); + kill(); + } + // Mostly used for asserts and printing. public boolean isDead() { return isUnused() && nIns()==0 && _type==null; } // Shortcuts to stop DCE mid-parse // Add bogus null use to keep node alive + @SuppressWarnings("unchecked") public N keep() { addUse(null); return (N)this; } // Remove bogus null. + @SuppressWarnings("unchecked") public N unkeep() { delUse(null); return (N)this; } // Test "keep" status public boolean iskeep() { return _outputs.find(null) != -1; } @@ -270,6 +280,8 @@ public abstract class Node extends BaseNode implements Cloneable { int i; if( use instanceof PhiNode phi ) { cfg = phi.region().cfg(uidx); + if( cfg instanceof CProjNode && cfg.in(0) instanceof NeverNode nvr ) + cfg = nvr.cfg0(); i = cfg.nOuts()-1; } else { i = cfg._outputs.find(use); @@ -281,7 +293,7 @@ public abstract class Node extends BaseNode implements Cloneable { use.setDefOrdered(uidx,this); } - public void setDefOrdered(int idx, Node def) { + 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 ) { @@ -294,13 +306,6 @@ public abstract class Node extends BaseNode implements Cloneable { setDef(idx,def); } - public void killOrdered() { - CFGNode cfg = cfg0(); - cfg._outputs.remove(cfg._outputs.find(this)); - _inputs.set(0,null); - kill(); - } - public void removeSplit() { CFGNode cfg = cfg0(); cfg._outputs.remove(cfg._outputs.find(this)); @@ -427,6 +432,7 @@ public abstract class Node extends BaseNode implements Cloneable { return old; } + @SuppressWarnings("unchecked") public N init() { _type = compute(); return (N)this; } /** @@ -490,27 +496,23 @@ public abstract class Node extends BaseNode implements Cloneable { * 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 ) { - return addDep(dep,false); - } + N addDep( N dep ) { return addDep(dep,false); } - // A bulk Phi inspects its users' partitions. Def-to-use propagation does - // not revisit that producer when an immediate user's partition changes. - Node addDepForwards(Node dep) { return addDep(dep,true); } + // Keep an explicit dependency when inspecting an immediate user's shape. + N addDepForwards(N dep) { return addDep(dep,true); } - private Node addDep(Node dep, boolean forwards) { + private N addDep(N dep, boolean forwards) { // Running peepholes during the big assert cannot have side effects // like adding dependencies. - if( CODE._midAssert ) return this; + if( CODE._midAssert ) return dep; var obs = CodeGen.CODE._midAssert ? null : CodeGen.CODE._obs; if( obs != null ) obs.dep(this, dep); - if( dep == null ) return this; - if( _deps==null ) _deps = new Ary<>(Node.class); - if( _deps .find(dep) != -1 ) return this; // Already on list - if( !forwards && _inputs .find(dep) != -1 ) return this; // No need for deps on immediate neighbors - if( !forwards && _outputs.find(dep) != -1 ) return this; - _deps.add(dep); - return this; + if( dep._deps==null ) dep._deps = new Ary<>(Node.class); + if( dep._deps .find(this) != -1 ) return dep; // Already on list + if( !forwards && dep._inputs .find(this) != -1 ) return dep; // No need for deps on immediate neighbors + if( !forwards && dep._outputs.find(this) != -1 ) return dep; + dep._deps.add(this); + return dep; } // Move the dependents onto a worklist, and clear for future dependents. @@ -535,7 +537,7 @@ public abstract class Node extends BaseNode implements Cloneable { } // Subclasses add extra checks (such as ConstantNodes have same constant), // and can assume "this!=n" and has the same Java class. - boolean eq( Node n ) { return true; } + public boolean eq( Node n ) { return true; } // Cached hash. If zero, then not computed AND this Node is NOT in the GVN // table - and can have its edges hacked (which will change his hash @@ -611,7 +613,7 @@ public abstract class Node extends BaseNode implements Cloneable { boolean allCons(Node dep) { for( int i=1; i implements Cloneable { // Does not need to be implemented in isCFG() nodes. Node copy(Node lhs, Node rhs) { throw Utils.TODO("Binary ops need to implement copy"); } + public Node copy() { return copyEmpty(); } + // Exact-class copy with fresh identity and no edges. Unlike machine // rematerialization copies, this never registers or copies input edges. public final Node copyEmpty() { diff --git a/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/src/main/java/com/seaofnodes/simple/node/PhiNode.java index a752977b..e075ea5c 100644 --- a/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -14,7 +14,7 @@ public class PhiNode extends Node { final Type _declaredType; public PhiNode(String label, Type declaredType, Node... inputs) { super(inputs); _label = label; assert declaredType!=null; _declaredType = declaredType; } - public PhiNode(PhiNode phi, String label, Type declaredType) { super(phi); _label = label; _declaredType = declaredType; } + public PhiNode(PhiNode phi, String label, Type declaredType) { super(phi); _label = label; _type = _declaredType = declaredType; } public PhiNode(PhiNode phi) { super(phi); _label = phi._label; _declaredType = phi._declaredType; } public PhiNode(RegionNode r, Node sample) { @@ -54,7 +54,7 @@ public class PhiNode extends Node { 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 != Type.XCONTROL ) + if( addDep(r.in(i))._type != Type.XCONTROL ) t = t.meet(in(i)._type); return t; } @@ -82,8 +82,8 @@ public class PhiNode extends Node { // If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove. if( nIns()==3 ) { - if( in(1) instanceof CastNode cast && cast.in(1).addDep(this)==in(2) ) return in(2); - if( in(2) instanceof CastNode cast && cast.in(1).addDep(this)==in(1) ) return in(1); + if( in(1) instanceof CastNode cast && addDep(cast.in(1))==in(2) ) return in(2); + if( in(2) instanceof CastNode cast && addDep(cast.in(1))==in(1) ) return in(1); } // If merging a null-checked null and the checked value, just use the value. // if( val ) ..; phi(Region,False=0/null,True=val); @@ -96,7 +96,7 @@ public class PhiNode extends Node { Node val = in(3-nullx); if( val instanceof CastNode cast ) val = cast.in(1); - if( r.idom(this).addDep(this) instanceof IfNode iff && iff.pred().addDep(this)==val ) { + if( addDep(r.idom(this)) instanceof IfNode iff && addDep(iff.pred())==val ) { // Must walk the idom on the null side to make sure we hit False. CFGNode idom = (CFGNode)r.in(nullx); while( idom != null && idom.nIns() > 0 && idom.in(0) != iff ) idom = idom.idom(); @@ -109,6 +109,8 @@ public class PhiNode extends Node { return null; } + // Same op on all Phi paths; all ops have only the Phi as a use. + // None have a control input. private boolean same_op() { Node op = in(1); if( op instanceof CFGNode || op instanceof ConstantNode || op instanceof PhiNode || @@ -121,22 +123,22 @@ public class PhiNode extends Node { if( op instanceof MemOpNode ) for( Node use : region()._outputs ) if( use instanceof BulkMemPhiNode ) { - use.addDepForwards(this); + addDepForwards(use); return false; } Node busy = null; for( int i=1; i1 ) { for( Node use : n._outputs ) - if( use!=null && use!=this ) use.addDepForwards(this); + if( use!=null && use!=this ) addDepForwards(use); if( n instanceof StoreNode || busy!=null ) return false; busy=n; } @@ -174,7 +176,7 @@ public class PhiNode extends Node { if( cp.compute().isa(compute()) ) return cp; for( int i=1; i2 && !hasMidUser(region) ) { + assert !region.inProgress(); + // Fold Phis + for( Node use : _outputs ) { + if( use instanceof PhiNode phi ) { + PhiNode phi2 = phi.in(i) instanceof PhiNode phi2x && phi2x.region()==region ? phi2x : null; + for( int j=1; j1 && in(nIns()-1) == null; } // Never equal if inProgress - @Override boolean eq( Node n ) { return !inProgress(); } + @Override public boolean eq( Node n ) { return !inProgress(); } } diff --git a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index e101ee09..e0ab863a 100644 --- a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -5,6 +5,9 @@ import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.Encoding; import com.seaofnodes.simple.type.*; /** Keeps all preceding memory effects and the return value alive: {ctrl, $mem, value, rpc}. */ @@ -22,7 +25,7 @@ public class ReturnNode extends CFGNode { 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 FunNode fun() { return _fun; } @Override public String label() { return "Return"; } @@ -118,4 +121,39 @@ public class ReturnNode extends CFGNode { if( tp || tn ) sb.p("reference and "); return Parser.error(sb.unchar(5).toString(),loc); } + + + // ------------ + // MachNode specifics, shared across all CPUs + public String op() { + return _fun._frameAdjust > 0 ? "addi" : "ret"; + } + // Correct Nodes outside the normal edges + public void postSelect(CodeGen code) { + FunNode fun = (FunNode)rpc().in(0); + _fun = fun; + fun.setRet(this); + } + public RegMask regmap(int i) { + return i==2 + ? CodeGen.CODE._mach.retMask(_fun.sig()) + : CodeGen.CODE._retMasks[i]; + } + public RegMask outregmap() { return null; } + public void encoding( Encoding enc ) { throw Utils.TODO(); } + public void asm(CodeGen code, SB sb) { + int frameAdjust = fun()._frameAdjust; + if( frameAdjust>0 ) + sb.p("rsp += #").p(frameAdjust).p("\nret"); + // Post code-gen, just print the "ret" + if( code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() ) + // Prints return reg (either RAX or XMM0), RPC and then the + // callee-save registers. + for( int i=2; i (x << i) + (c << i) - if( lhs instanceof AddNode add && add.addDep(this).in(2)._type instanceof TypeInteger c && c.isConstant() ) { + if( lhs instanceof AddNode add && addDep(add).in(2)._type instanceof TypeInteger c && c.isConstant() ) { long sum = c.value() << shl.value(); if( Integer.MIN_VALUE <= sum && sum <= Integer.MAX_VALUE ) return new AddNode(new ShlNode(_loc,add.in(1),rhs).peephole(), Parser.con(sum) ); diff --git a/src/main/java/com/seaofnodes/simple/node/SplitNode.java b/src/main/java/com/seaofnodes/simple/node/SplitNode.java index 07166a7c..ff18b292 100644 --- a/src/main/java/com/seaofnodes/simple/node/SplitNode.java +++ b/src/main/java/com/seaofnodes/simple/node/SplitNode.java @@ -13,7 +13,7 @@ public abstract class SplitNode extends MachConcreteNode { @Override public String op() { return "mov"; } @Override protected ExprPrinter _print1(ExprPrinter p) { p.p("mov("); - if( in(1)==null ) p.p("---"); + if( in(1) == null ) p.p("---"); else p.n(in(1)); return p.close(); } diff --git a/src/main/java/com/seaofnodes/simple/node/StopNode.java b/src/main/java/com/seaofnodes/simple/node/StopNode.java index 524c6a9e..c88dbf51 100644 --- a/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ b/src/main/java/com/seaofnodes/simple/node/StopNode.java @@ -27,7 +27,8 @@ public class StopNode extends CFGNode { if( ret()!=null ) return p.n(ret()); p.p("Stop[ "); for( Node ret : _inputs ) - p.n(ret).p(" "); + if( ret!=null ) + p.n(ret).p(" "); return p.p("]"); } diff --git a/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/src/main/java/com/seaofnodes/simple/node/StoreNode.java index 9e6689f0..a5b09663 100644 --- a/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -22,7 +22,7 @@ public class StoreNode extends MemOpNode { * @param value Value to be stored */ public StoreNode(Parser.Lexer loc, String name, int alias, Type glb, Node mem, Node ptr, Node off, Node value, boolean init) { - super(loc, name, alias, glb, mem, ptr, off, value); + super(loc, name, alias, false, glb, mem, ptr, off, value); _init = init; } @@ -85,6 +85,11 @@ public class StoreNode extends MemOpNode { } } + // Store of zero after alloc + if( mem() instanceof ProjNode prj && prj.in(0) instanceof NewNode && + prj.in(0)==ptr().in(0) && // Same NewNode memory & pointer + (val()._type==TypeInteger.ZERO || val()._type==Type.NIL ) ) + return mem(); return null; } @@ -93,9 +98,9 @@ public class StoreNode extends MemOpNode { // With no observers of the backedge store, only the last value matters. // The entry load preserves the field even when the loop takes zero trips. Node sink(MemPhiNode phi) { - addDepForwards(phi); + phi.addDepForwards(this); if( nOuts()!=1 ) return null; - ptr().addDep(phi); + phi.addDep(ptr()); // Start with a fixed field of an allocation dominating the loop. // In particular, do not sink stores through a loop-varying address. if( !(ptr() instanceof ProjNode p) || !(p.in(0) instanceof NewNode obj) || @@ -106,20 +111,20 @@ public class StoreNode extends MemOpNode { // before replacing this memory point with a Store. for( Node use : loop._outputs ) if( use instanceof BulkMemPhiNode ) { - use.addDepForwards(phi); + phi.addDepForwards(use); return null; } - obj.addDep(phi); + phi.addDep(obj); CFGNode ctrl = loop.entry(); while( ctrl!=null && ctrl!=obj.cfg0() ) { - ctrl.addDep(phi); + phi.addDep(ctrl); ctrl=ctrl.idom(phi); } if( ctrl==null ) return null; Node mem = phi.in(1); Node init = new LoadNode(_loc,_name,_alias,_declaredType,mem,ptr(),off()).peephole(); - Node value = new PhiNode(_name,_declaredType,loop,init,val()).peephole(); + Node value = CodeGen.CODE.add(new PhiNode(_name,_declaredType,loop,init,val()).peephole()); Node sink = new StoreNode(_loc,_name,_alias,_declaredType,mem,ptr(),off(),value,_init); // Break the memory cycle, killing the old store. Keep the Phi alive // even if that store was its last use, until our caller replaces it. @@ -136,7 +141,7 @@ public class StoreNode extends MemOpNode { // when the other uses go away we can retry. for( Node use : mem._outputs ) if( use != this ) - use.addDep(this); + addDep(use); return false; } @@ -149,7 +154,7 @@ public class StoreNode extends MemOpNode { TypeMemPtr tmp = (TypeMemPtr)ptr()._type; if( (tmp._ro || tmp._obj.field(_name)._final) && !_init ) return Parser.error("Cannot modify final field '"+_name+"'",_loc); - Type t = val()._type; + //Type t = val()._type; //return _init || t.isa(_declaredType) ? null : Parser.error("Cannot store "+t+" into field "+_declaredType+" "+_name,_loc); return null; } diff --git a/src/main/java/com/seaofnodes/simple/node/StructNode.java b/src/main/java/com/seaofnodes/simple/node/StructNode.java index b9801f63..3782d64a 100644 --- a/src/main/java/com/seaofnodes/simple/node/StructNode.java +++ b/src/main/java/com/seaofnodes/simple/node/StructNode.java @@ -39,7 +39,7 @@ public class StructNode extends Node { public Node idealize() { return null; } @Override - boolean eq(Node n) { return _ts == ((StructNode)n)._ts; } + public boolean eq(Node n) { return _ts == ((StructNode)n)._ts; } @Override int hash() { return _ts==null ? 0 : _ts.hashCode(); } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java index c4014571..76e7b9b5 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java @@ -1,34 +1,21 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; public class AddARM extends MachConcreteNode implements MachNode { + AddARM( Node add) { super(add); } + @Override public String op() { return "add"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } - AddARM( Node add) {super(add); } - - // Register mask allowed on input i. - @Override public RegMask regmap(int i) { - assert i== 1 || i == 2 || i == 3; - return arm.RMASK; - } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return arm.RMASK; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - + // ADD (shifted register) + @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,Arm64.OP_ADD); } // General form: "rd = rs1 + rs2" @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2))); } - @Override public String op() { - return "add";} } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java index 098ab8c1..9c2f64c0 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java @@ -1,31 +1,23 @@ package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; public class AddFARM extends MachConcreteNode implements MachNode { AddFARM(Node addf) { super(addf); } - - // Register mask allowed on input i. - @Override public RegMask regmap(int i) { assert i==1 || i==2; return arm.DMASK; } - // Register mask allowed as a result. 0 for no register. + @Override public String op() { return "addf"; } + @Override public RegMask regmap(int i) { return arm.DMASK; } @Override public RegMask outregmap() { return arm.DMASK; } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } + //FADD (scalar) + @Override public void encoding( Encoding enc ) { arm.f_scalar(enc,this,Arm64.OPF_OP_ADD);} // Default on double precision for now(64 bits) - // General form: "vadd.32 rd = src1 + src2 + // General form: "addf rd = src1 + src2 @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2))); } - @Override public String op() { return "addf"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java index c8f5f8dd..52cf36ea 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java @@ -1,51 +1,28 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; public class AddIARM extends MachConcreteNode implements MachNode { - final TypeInteger _ti; - - AddIARM(Node add, TypeInteger ti) { + final int _imm; + AddIARM(Node add, int imm) { super(add); _inputs.pop(); - _ti = ti; + _imm = imm; } - - // Register mask allowed on input i. - @Override - public RegMask regmap(int i) { - // assert i== i; - return arm.RMASK; + @Override public String op() { + return _imm == 1 ? "inc" : (_imm == -1 ? "dec" : "addi"); } - - // Register mask allowed as a result. 0 for no register. - @Override - public RegMask outregmap() { - return arm.RMASK; - } - - // Encoding is appended into the byte array; size is returned - @Override - public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + //ADD (immediate) + @Override public void encoding( Encoding enc ) { + arm.imm_inst(enc,this, in(1), Arm64.OPI_ADD,_imm); } - // General form: "addi rd = rs1 + imm" - @Override - public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + #"); - _ti.print(sb); + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + #").p(_imm); } - - @Override - public String op() { - return _ti.value() == 1 ? "inc" : (_ti.value() == -1 ? "dec" : "addi"); - - } - -} \ No newline at end of file +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java index 7655df40..40812d07 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java @@ -1,36 +1,20 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.MachConcreteNode; import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.node.Node; -import java.io.ByteArrayOutputStream; - public class AndARM extends MachConcreteNode implements MachNode { - AndARM(Node and) { - super(and); - } - - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { - assert i==1 || i==2; - return arm.RMASK; - } - - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return arm.RMASK; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form + AndARM(Node and) { super(and); } + @Override public String op() { return "and"; } + @Override public String glabel() { return "&"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + // AND (shifted register) + @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,Arm64.OP_AND); } // General form: #rd = rs1 & rs2 @Override public void asm(CodeGen code, SB sb){ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" & ").p(code.reg(in(2))); diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java index ee05654b..82d553aa 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java @@ -1,56 +1,25 @@ package com.seaofnodes.simple.node.cpus.arm; - +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; -import java.util.BitSet; -import java.lang.StringBuilder; - -public class AndIARM extends MachConcreteNode implements MachNode { - final TypeInteger _ti; - AndIARM(AndNode and, TypeInteger ti) { +public class AndIARM extends MachConcreteNode implements MachNode { + final int _imm; + AndIARM(Node and, int imm) { super(and); _inputs.pop(); - _ti = ti; - } - - // Register mask allowed on input i. - // This is the normal calling convention - @Override - public RegMask regmap(int i) { - assert i == 1 || i == 2; - return arm.RMASK; + _imm = imm; } - - // Register mask allowed as a result. 0 for no register. - @Override - public RegMask outregmap() { - return arm.RMASK; + @Override public String op() { return "andi"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public void encoding( Encoding enc ) { + arm.imm_inst_n(enc,this, in(1), Arm64.OPI_AND,_imm); } - - // Encoding is appended into the byte array; size is returned - @Override - public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form // General form: "andi rd = rs1 & imm" - @Override - public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" & #"); - _ti.print(sb); + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" & #").p(Arm64.decodeImm12(_imm)); } - - @Override - public String op() { - return "andi"; - } - } \ No newline at end of file diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java index a79de49b..cce868f9 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java @@ -1,40 +1,19 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; -import java.util.BitSet; -import java.lang.StringBuilder; // Arithmetic Shift Right (register) public class AsrARM extends MachConcreteNode implements MachNode { AsrARM(Node asr) {super(asr);} - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { - // assert i==1; - return arm.RMASK; } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return arm.RMASK; } - - // Output is same register as input#1 - @Override public int twoAddress() { return 1; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form - // General form: "asr rd, rs1, rs2" + @Override public String op() { return "sar"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.DMASK; } + @Override public void encoding( Encoding enc ) { arm.shift_reg(enc,this,Arm64.OP_ASR); } @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> ").p(code.reg(in(2))); } - @Override public String op() { return "asr"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java index 099c3699..2d50e967 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java @@ -1,41 +1,32 @@ package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.*; +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.SB; import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Encoding; import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; -import java.util.BitSet; -import java.lang.StringBuilder; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.Node; -// Arithmetic Shift Right (immediate) public class AsrIARM extends MachConcreteNode implements MachNode { - final TypeInteger _ti; - AsrIARM(Node asri, TypeInteger ti) {super(asri); _inputs.pop(); _ti = ti;} - - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { - // assert i==1; - return arm.RMASK; } - - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return arm.RMASK; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + final int _imm; + AsrIARM(Node asr, int imm) { + super(asr); + _inputs.pop(); + _imm = imm; } - - // General form - // General form: "asri rd, rs1, imm" + @Override public String op() { return "asri"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public void encoding( Encoding enc ) { + short rd = enc.reg(this); + short rn = enc.reg(in(1)); + assert _imm > 0; + enc.add4(Arm64.imm_shift(Arm64.OPI_ASR,_imm, 0b111111, rn, rd)); + } + // General form: "asri rd = rs1 >> imm" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> #"); - _ti.print(sb); + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> #").p(_imm); } - - @Override public String op() { return "asri"; } -} +} \ No newline at end of file diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java index 1a5108cf..5a660c90 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java @@ -1,25 +1,23 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import java.io.ByteArrayOutputStream; - - // Jump on flags, uses flags -public class BranchARM extends IfNode implements MachNode{ - final String _bop; +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() { + @Override public void postSelect(CodeGen code) { Node set = in(1); + if( set==null ) return; // Never-node cutout Node cmp = set.in(1); // Bypass an expected Set and just reference the cmp directly if( set instanceof SetARM) @@ -30,19 +28,50 @@ public class BranchARM extends IfNode implements MachNode{ @Override public RegMask regmap(int i) { assert i==1; return arm.FLAGS_MASK; } @Override public RegMask outregmap() { return null; } + @Override public void negate() { _bop = negate(_bop); } + + @Override public void encoding( Encoding enc ) { + // Assuming that condition flags are already set. These flags are set + // by comparison (or sub). No need for regs because it uses flags + if( in(1)!=null ) { + // B.cond + enc.add4( Arm64.b_cond(Arm64.OP_BRANCH, 0, Arm64.make_condition(_bop)) ); + } else { + if( _bop=="!=" ) return; // Inverted, no code + enc.add4(Arm64.b(Arm64.OP_UJMP, 0)); + } + enc.jump(this,cproj(0)); + } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { + // Delta is from opcode start + @Override public byte encSize(int delta) { + if( in(1)==null && _bop=="!=" ) return 0; // Inverted never-node, no code + if( -(1<<19) <= delta && delta < (1<<19) ) return 4; + // 2 word encoding needs a tmp register, must teach RA throw Utils.TODO(); } - @Override public void asm(CodeGen code, SB sb) { - String src = code.reg(in(1)); - if( src!="flags" ) sb.p(src); + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + assert !( in(1)==null && _bop=="!=" ); // Inverted never-node, no code no patch + if( opLen==4 ) { + enc.patch4(opStart,Arm64.b_cond(Arm64.OP_BRANCH, delta, Arm64.make_condition(_bop))); + } else { + throw Utils.TODO(); + } } - @Override public String op() { return "j"+_bop; } - @Override public String comment() { - return "L"+cproj(1)._nid+", L"+cproj(0)._nid; + @Override public void asm(CodeGen code, SB sb) { + if( in(1)!=null ) { // Never-node + String src = code.reg(in(1)); + if( src!="flags" ) sb.p(src).p(" "); + } else if( _bop=="!=" ) { + sb.p("never"); + return; + } + CFGNode prj = cproj(0).uctrlSkipEmpty(); + if( !prj.blockHead() ) prj = prj.cfg0(); + sb.p(label(prj)); } + @Override public String comment() { return "L"+cproj(1)._nid; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java index 0f883847..57d7ef7c 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java @@ -1,13 +1,12 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeFunPtr; -import java.io.ByteArrayOutputStream; -public class CallARM extends CallNode implements MachNode { +public class CallARM extends CallNode implements MachNode, RIPRelSize { final TypeFunPtr _tfp; final String _name; @@ -19,27 +18,31 @@ public class CallARM extends CallNode implements MachNode { _name = CodeGen.CODE.link(tfp)._name; } + @Override public String op() { return "call"; } @Override public String label() { return op(); } - @Override public RegMask regmap(int i) { - return arm.callInMask(_tfp,i); // Normal argument - } - @Override public RegMask outregmap() { return null; } - @Override public String name() { return _name; } @Override public TypeFunPtr tfp() { return _tfp; } + @Override public RegMask regmap(int i) { return arm.callInMask(_tfp,i,fun()._maxArgSlot); } + @Override public RegMask outregmap() { return null; } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + enc.relo(this); + // BL + enc.add4(Arm64.b(Arm64.OP_CALL,0)); // Target patched at link time + } + + // Delta is from opcode start, but X86 measures from the end of the 5-byte encoding + @Override public byte encSize(int delta) { return 4; } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + enc.patch4(opStart,Arm64.b(Arm64.OP_CALL,delta)); } @Override public void asm(CodeGen code, SB sb) { sb.p(_name).p(" "); for( int i=0; i= 1L<<12 ) throw Utils.TODO(); + enc.add4(Arm64.imm_inst(Arm64.OPI_SUB, sz, arm.RSP, arm.RSP)); } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java index b106ab8a..a7fa7a6a 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java @@ -1,29 +1,26 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; -// VCVT (between floating-point and integer) public class I2F8ARM extends MachConcreteNode implements MachNode { - I2F8ARM(Node i2f8) {super(i2f8);} - @Override public String op() { return "i2f8"; } - - // Register mask allowed on input i. - @Override public RegMask regmap(int i) { assert i==1; return arm.RMASK; } - // Register mask allowed as a result. 0 for no register. + I2F8ARM(Node i2f8) { super(i2f8); } + @Override public String op() { return "cvt"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } @Override public RegMask outregmap() { return arm.DMASK; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + // SCVTF + short self = (short)(enc.reg(this )-arm.D_OFFSET); + short reg1 = enc.reg(in(1)); + int body = Arm64.float_cast(Arm64.OP_FLOAT_C, 1, reg1, self); + enc.add4(body); } // General form: "i2f8 (flt)int_value" @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)).p(" = ").p("(flt)").p(code.reg(in(1))); } + } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java index 1f81300e..64d00d57 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java @@ -1,30 +1,28 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.ConstantNode; import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; // Integer constants public class IntARM extends ConstantNode implements MachNode { IntARM( ConstantNode con ) { super(con); } - @Override public String op() { return "ldi"; } - // Register mask allowed on input i. 0 for no register. + @Override public String op() { return "ldi"; } @Override public RegMask regmap(int i) { return null; } - // General int registers @Override public RegMask outregmap() { return arm.WMASK; } @Override public boolean isClone() { return true; } @Override public IntARM copy() { return new IntARM(this); } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + short self = enc.reg(this); + long x = _con==Type.NIL ? 0 : ((TypeInteger)_con).value(); + Arm64.constant(enc,self,x); } // Human-readable form appended to the SB. Things like the encoding, diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java index 0d59cc47..6fd4274a 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java @@ -1,37 +1,34 @@ package com.seaofnodes.simple.node.cpus.arm; - +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.LoadNode; import com.seaofnodes.simple.node.Node; - // 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) { +public class LoadARM extends MemOpARM { + LoadARM(LoadNode ld, Node base, Node idx, int off) { super(ld, base, idx, off, 0); } + @Override public String op() { return "ld"+_sz; } + @Override public RegMask outregmap() { return size() >= 4 ? arm.MEM_MASK : arm.WMASK; } - @Override public RegMask regmap(int i) { - return arm.RMASK; - } - - // Wide mask loads both ints and floats; encoding varies. - @Override public RegMask outregmap() { return arm.MEM_MASK; } - + private static final int[] OP_LOADS = new int[]{ Arm64.OP_LOAD_IMM_8, Arm64.OP_LOAD_IMM_16, Arm64.OP_LOAD_IMM_32, Arm64.OP_LOAD_IMM_64, }; - @Override public String op() { return "ld"+_sz; } + private static final int[] OP_LOAD_RS = new int[]{ Arm64.OP_LOAD_R_8 , Arm64.OP_LOAD_R_16 , Arm64.OP_LOAD_R_32 , Arm64.OP_LOAD_R_64, }; + // ldr(immediate - unsigned offset) | ldr(register) + @Override public void encoding( Encoding enc ) { + ldst_encode(enc, OP_LOADS[Integer.numberOfTrailingZeros(size())], OP_LOAD_RS[_declaredType.log_size()], this, size()); + } @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)).p(","); asm_address(code,sb); } - } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java index 6108c889..7624554e 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java @@ -1,41 +1,20 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; -import java.util.BitSet; -import java.lang.StringBuilder; - // Logical Shift Left (register) public class LslARM extends MachConcreteNode implements MachNode { LslARM(Node asr) {super(asr);} - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { - // assert i==1; - return arm.RMASK; } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return arm.RMASK; } - - // Output is same register as input#1 - @Override public int twoAddress() { return 1; } + @Override public String op() { return "shr"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form - // General form: "lsl rd, rs1, rs2" + @Override public void encoding( Encoding enc ) { arm.shift_reg(enc,this,Arm64.OP_LSL); } @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" << ").p(code.reg(in(2))); } - @Override public String op() { return "lsl"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java index 7a7014f4..bf394172 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java @@ -1,42 +1,34 @@ package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.*; +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.SB; import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Encoding; import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; -import java.util.BitSet; -import java.lang.StringBuilder; - +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.Node; -// Logical Shift Left (immediate) public class LslIARM extends MachConcreteNode implements MachNode { - final TypeInteger _ti; - LslIARM(Node lsli, TypeInteger ti) {super(lsli); _inputs.pop(); _ti = ti;} - - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { - // assert i==1; - return arm.RMASK; } - - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return arm.RMASK; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + final int _imm; + LslIARM(Node lsl, int imm) { + super(lsl); + _inputs.pop(); + _imm = imm; } - - // General form - // General form: "lsli rd, rs1, imm" + @Override public String op() { return "lsli"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public void encoding( Encoding enc ) { + short rd = enc.reg(this); + short rn = enc.reg(in(1)); + assert _imm > 0; + // UBFM , , #(- MOD 64), #(63-) + // immr must be (- MOD 64) = 64 - shift + enc.add4(Arm64.imm_shift(Arm64.OPI_LSL, 64 - _imm, (64 - _imm) - 1, rn, rd)); + } + // General form: "lsli rd = rs1 << imm" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" << #"); - _ti.print(sb); + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" << #").p(_imm); } - - @Override public String op() { return "lsli"; } -} +} \ No newline at end of file diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java index e6168dee..2d5fa52c 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java @@ -1,40 +1,20 @@ package com.seaofnodes.simple.node.cpus.arm; - +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; -import java.util.BitSet; -import java.lang.StringBuilder; // Logical Shift Right (register) public class LsrARM extends MachConcreteNode implements MachNode { LsrARM(Node asr) {super(asr);} - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { - // assert i==1; - return arm.RMASK; } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return arm.RMASK; } - - // Output is same register as input#1 - @Override public int twoAddress() { return 1; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } + @Override public String op() { return "shr"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } - // General form - // General form: "lsr rd, rs1, rs2" + @Override public void encoding( Encoding enc ) { arm.shift_reg(enc,this,Arm64.OP_LSR); } @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >>> ").p(code.reg(in(2))); } - @Override public String op() { return "lsr"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java index 8fb20cb2..6a12e4a5 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java @@ -1,41 +1,30 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; -import java.util.BitSet; -import java.lang.StringBuilder; -// Logical Shift Right (immediate) -public class LsrIARM extends MachConcreteNode implements MachNode{ - final TypeInteger _ti; - LsrIARM(Node lsri, TypeInteger ti) {super(lsri); _inputs.pop(); _ti = ti;} - - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { - // assert i==1; - return arm.RMASK; } - - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return arm.RMASK; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); +// 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; } - - // General form - // General form: "lsrl rd, rs1, imm" + @Override public String op() { return "lsri"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public void encoding( Encoding enc ) { + short rd = enc.reg(this); + short rn = enc.reg(in(1)); + assert _imm > 0; + enc.add4(Arm64.imm_shift(Arm64.OPI_LSR,_imm, 0b111111, rn,rd)); + } + // General form: "lsri rd = rs1 >>> imm" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >>> #"); - _ti.print(sb); + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >>> #").p(_imm); } - - @Override public String op() { return "lsrl"; } -} +} \ No newline at end of file diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java index c22e71f6..5e175b1e 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java @@ -1,60 +1,79 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; -import java.io.ByteArrayOutputStream; + import java.lang.StringBuilder; -public class MemOpARM extends MemOpNode implements MachNode { - final int _off; // Limit 32 bits - final int _imm; // Limit 32 bits +public abstract class MemOpARM extends MemOpNode implements MachNode { + final int _off; // Limit 9 bits sized, or (13 bits< _printMach(ExprPrinter p) { return p.p(".").p(_name); } - @Override public boolean isMem() { return _type instanceof TypeMem; } - - @Override public String label() { return op(); } @Override public Type compute() { throw Utils.TODO(); } @Override public Node idealize() { throw Utils.TODO(); } + int size() { return 1<<_declaredType.log_size(); } - - // Register mask allowed on input i. + // Wider mask to store both GPRs and FPRs @Override public RegMask regmap(int i) { - throw Utils.TODO(); + // 0 - ctrl + if( i==1 ) return null; // memory + if( i==2 ) return arm.RMASK; // ptr/base + if( i==3 ) return arm.RMASK; // off/index + if( i==4 ) return size() >= 4 ? arm.MEM_MASK : arm.RMASK; // value + return null; // Anti-dependence } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { throw Utils.TODO(); } - @Override public int encoding(ByteArrayOutputStream bytes) { throw Utils.TODO(); } + // Shared encoding for loads and stores(int and float) + public void ldst_encode( Encoding enc, int opcode_imm, int opcode_reg, Node xval, int size) { + short ptr = enc.reg(ptr()); + short off = enc.reg(off()); + short val = enc.reg(xval); + // The allocated register selects integer vs SIMD/FP encoding, even for + // bitwise copies. ARM numbers each register bank from zero in the opcode. + boolean fp = val >= arm.D0; + assert !fp || size >= 4; + if( fp ) { + opcode_imm |= 1<<4; + opcode_reg |= 1<<5; + val -= arm.D0; + } + int body = off() == null + ? Arm64.load_str_imm(opcode_imm, _off, ptr, val, size) + : Arm64.indr_adr(opcode_reg, off, Arm64.STORE_LOAD_OPTION.SXTX, 0, ptr, val); + enc.add4(body); + } SB asm_address(CodeGen code, SB sb) { sb.p("[").p(code.reg(ptr())).p("+"); - if( idx() != null ) sb.p(code.reg(idx())); + if( off() != null ) sb.p(code.reg(off())); else sb.p(_off); return sb.p("]"); } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java index 582b97d4..a0955565 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java @@ -1,34 +1,20 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; - // mulh signed multiply instruction(no-imm form) -public class MulARM extends MachConcreteNode implements MachNode{ +public class MulARM extends MachConcreteNode implements MachNode { MulARM(Node mul) {super(mul);} + @Override public String op() { return "mul"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } - // Register mask allowed on input i. - @Override public RegMask regmap(int i) { assert i==1 || i==2; return arm.RMASK; } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return arm.RMASK; } - // Output is same register as input#1 - @Override public int twoAddress() { return 1; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - + @Override public void encoding( Encoding enc ) { arm.madd(enc,this,Arm64.OP_MUL,31); } // General form: "rd = rs1 * rs2" @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2))); } - - @Override public String op() { return "mul"; } - } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java index b71b412d..33d32bd4 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java @@ -1,30 +1,19 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import java.io.ByteArrayOutputStream; public class MulFARM extends MachConcreteNode implements MachNode{ MulFARM(Node mulf) {super(mulf);} - - // Register mask allowed on input i. - @Override public RegMask regmap(int i) { assert i==1 || i==2; return arm.DMASK; } - // Register mask allowed as a result. 0 for no register. + @Override public String op() { return "mulf"; } + @Override public RegMask regmap(int i) { return arm.DMASK; } @Override public RegMask outregmap() { return arm.DMASK; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - + @Override public void encoding( Encoding enc ) { arm.f_scalar(enc,this,Arm64.OPF_MUL); } // Default on double precision for now(64 bits) // General form: "VMUL.f32 rd = src1 * src2 @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2))); } - - @Override public String op() { return "mulf"; } - } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulIARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulIARM.java deleted file mode 100644 index c118b00a..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulIARM.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; - -public class MulIARM extends MachConcreteNode implements MachNode{ - final TypeInteger _ti; - MulIARM(Node mul, TypeInteger ti) { - super(mul); - _inputs.pop(); - _ti = ti; - } - - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { - // assert i==1; - return arm.RMASK; } - - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return arm.RMASK; } - - // Output is same register as input#1 - @Override public int twoAddress() { return 1; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form - // General form: "muli dst * #imm" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * #"); - _ti.print(sb); - } - - @Override public String op() { return "muli"; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java new file mode 100644 index 00000000..b7c3c803 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java @@ -0,0 +1,25 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.type.TypeInteger; + +public class NegARM extends MachConcreteNode implements MachNode { + NegARM(Node sub) { + super(sub); + //_inputs.pop(); + } + @Override public String op() { return "neg"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public void encoding( Encoding enc ) { + // reverse subtract with immediate 0 + arm.imm_inst(enc,this, in(1), 0b00100110, 0); + } + // General form: "subi rd = rs1 - imm" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = -").p(code.reg(in(1))); + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java index c145f348..0dac086b 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java @@ -1,31 +1,37 @@ package com.seaofnodes.simple.node.cpus.arm; - +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import java.io.ByteArrayOutputStream; - -public class NewARM extends NewNode implements MachNode { +public class NewARM extends NewNode implements MachNode, RIPRelSize { // A pre-zeroed chunk of memory. NewARM(NewNode nnn) { super(nnn); } - // Size and pointer result in standard calling convention; null for all the - // memory and ordering edges - @Override public RegMask regmap(int i) { return i == 2 ? arm. X0_MASK : null; } - @Override public RegMask outregmap(int i) { return i == 0 ? arm. X0_MASK : null; } - @Override public RegMask outregmap() { return null; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + // BL(branch with link) + enc.external(this,"calloc"). + add4(Arm64.mov(Arm64.OP_MOVZ, 0, 1, 0)). // movz x0,#1 + add4(Arm64.b(Arm64.OP_CALL, 0)); } - // General form: "alloc #bytes" + // Patch is for running "new" in a JIT. + // Delta is from opcode start + @Override public byte encSize(int delta) { return 4*2; } + + // Patch is for running "new" in a JIT. + // Delta is from opcode start + @Override public void patch(Encoding enc, int opStart, int opLen, int delta ) { + // Negative patches are JIT emulator targets. + if( opStart+delta < 0 ) { + enc.patch4(opStart+4, Arm64.b_calloc(Arm64.OP_CALL, delta-4)); + } else { + throw Utils.TODO(); + } + } + // General form: "alloc #bytes PC" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(size())); + sb.p("ldi x0=#1\n"); + sb.p("call #calloc"); } - - @Override public String op() { return "alloc"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java index 28ebd60d..ec017032 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java @@ -1,26 +1,29 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.LRG; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; import com.seaofnodes.simple.node.MachConcreteNode; public class NotARM extends MachConcreteNode implements MachNode{ NotARM(NotNode not) {super(not);} + @Override public String op() { return "not"; } @Override public RegMask regmap(int i) { return arm.RMASK; } - @Override public RegMask outregmap() { return arm.RMASK; } - - @Override public int twoAddress( ) { return 0; } - - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public RegMask killmap() { return arm.FLAGS_MASK; } + @Override public void encoding( Encoding enc ) { + // subs xzr, rs, #0 + // cset rd, eq // Set rd to 1 if rs == 0 (equal), else 0 + // subtracting zero from rs will just yield rs, it sets the zero flag and then it's used in cset + short self = enc.reg(this ); + short reg1 = enc.reg(in(1)); + int subs = Arm64.imm_inst(Arm64.OP_SUBS, 0, reg1, 31); + enc.add4(subs); + int cset = Arm64.cset(Arm64.OP_CSET, 31, Arm64.COND.EQ, 31, self); + enc.add4(cset); } @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); } - @Override public String op() { return "not"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java index 12ea8265..ba3197c1 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java @@ -1,42 +1,21 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.MachConcreteNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.Node; - -import java.io.ByteArrayOutputStream; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; // Not using the ORRS variant. public class OrARM extends MachConcreteNode implements MachNode { - OrARM(Node and) { - super(and); - } - - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { - assert i==1 || i==2; - return arm.RMASK; - } - - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return arm.RMASK; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form + OrARM(Node and) { super(and); } + @Override public String op() { return "or"; } + @Override public String glabel() { return "|"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,Arm64.OP_OR); } // General form: #rd = rs1 & rs2 @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" | ").p(code.reg(in(2))); } - @Override public String op() { return "orr"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java index f257a867..9c7cb8cc 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java @@ -1,40 +1,25 @@ package com.seaofnodes.simple.node.cpus.arm; -import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.MachConcreteNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.Node; +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; - -public class OrIARM extends MachConcreteNode implements MachNode{ - final TypeInteger _ti; - OrIARM(Node or, TypeInteger ti) {super(or); _inputs.pop(); _ti = ti;} - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { - assert i==1 || i==2; - return arm.RMASK; +public class OrIARM extends MachConcreteNode implements MachNode { + final int _imm; + OrIARM(Node or, int imm) { + super(or); + _inputs.pop(); + _imm = imm; } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return arm.RMASK; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public String op() { return "ori"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public void encoding( Encoding enc ) { + arm.imm_inst_n(enc, this, in(1), Arm64.OPI_OR, _imm); } - - // General form - // General form: "rd = rs1 | imm" + // General form: "ori rd = rs1 | imm" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" | #"); - _ti.print(sb); + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" | #").p(_imm); } - - @Override public String op() { return "ori"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java index c3fa132c..b419fe0a 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java @@ -1,25 +1,17 @@ package com.seaofnodes.simple.node.cpus.arm; -import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Encoding; import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.node.ParmNode; -import java.io.ByteArrayOutputStream; public class ParmARM extends ParmNode implements MachNode { final RegMask _rmask; ParmARM(ParmNode parm) { super(parm); - // Assume int - _rmask = arm.callInMask(fun().sig(),_idx); + _rmask = arm.callInMask(fun().sig(),_idx,0); } - // Register mask allowed on input i. 0 for no register. @Override public RegMask regmap(int i) { return null; } - // Register mask allowed as a result. Calling convention register @Override public RegMask outregmap() { return _rmask; } - - // Encoding is appended into the byte array. Returns size - @Override public int encoding(ByteArrayOutputStream bytes) { return 0; } + @Override public void encoding( Encoding enc ) { } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java index b2add015..4b7d6f13 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java @@ -1,23 +1,15 @@ package com.seaofnodes.simple.node.cpus.arm; -import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.Encoding; import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.node.ProjNode; -import java.io.ByteArrayOutputStream; public class ProjARM extends ProjNode implements MachNode { ProjARM( ProjNode p ) { super(p); } - - // Register mask allowed on input i. 0 for no register. @Override public RegMask regmap(int i) { return null; } - // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return ((MachNode)in(0)).outregmap(_idx); } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } + @Override public void encoding( Encoding enc ) { } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java index c7d0dd4c..a7ed5073 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java @@ -1,49 +1,16 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.FunNode; -import com.seaofnodes.simple.node.ReturnNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.type.TypeFloat; - -import java.io.ByteArrayOutputStream; - +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; public class RetARM extends ReturnNode implements MachNode { RetARM(ReturnNode ret, FunNode fun) { super(ret, fun); fun.setRet(this); } - - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { return arm.retMask(_fun.sig(),i); } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return null; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - @Override public void asm(CodeGen code, SB sb) { - // 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 0 ) + enc.add4(Arm64.imm_inst(Arm64.OPI_ADD, frameAdjust, arm.RSP, arm.RSP)); + enc.add4(Arm64.ret(Arm64.OP_RET)); } - - // Correct Nodes outside the normal edges - @Override public void postSelect() { - FunNode fun = (FunNode)rpc().in(0); - _fun = fun; - fun.setRet(this); - } - - @Override public String op() { return "ret"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java index c3945d1a..6809bb1c 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java @@ -1,20 +1,14 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; - // conditional set? // e.g CSET, stores flag info into GPRS after compare - public class SetARM extends MachConcreteNode implements MachNode { final String _bop; // One of <,<=,== - SetARM(Node cmp, String bop) { super(cmp); _inputs.setLen(1); // Pop the cmp inputs @@ -22,13 +16,13 @@ public class SetARM extends MachConcreteNode implements MachNode { _inputs.push(cmp); _bop = bop; } - + @Override public String op() { return "set"+_bop; } @Override public RegMask regmap(int i) { assert i==1; return arm.FLAGS_MASK; } - @Override public RegMask outregmap() { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + int body = Arm64.cset(Arm64.OP_CSET,0b11111, Arm64.make_condition(_bop), 0b011111, enc.reg(this)); + enc.add4(body); } @Override public void asm(CodeGen code, SB sb) { @@ -36,6 +30,4 @@ public class SetARM extends MachConcreteNode implements MachNode { String src = code.reg(in(1)); if( src!="flags" ) sb.p(" = ").p(src); } - - @Override public String op() { return "set"+_bop; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java index 85771842..c0c60ea2 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java @@ -1,22 +1,67 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.FunNode; import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.node.SplitNode; -import java.io.ByteArrayOutputStream; +import com.seaofnodes.simple.node.cpus.riscv.riscv; public class SplitARM extends SplitNode { SplitARM(String kind, byte round) { super(kind,round,new Node[2]);} - - // Register mask allowed on input i. @Override public RegMask regmap(int i) { return arm.SPLIT_MASK; } - - // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return arm.SPLIT_MASK; } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + short dst = enc.reg(this ); + short src = enc.reg(in(1)); + + boolean dstX = dst >= arm.D_OFFSET; + boolean srcX = src >= arm.D_OFFSET; + + if (dst >= arm.MAX_REG) { + // store to sp + if(src >= arm.MAX_REG) { + throw Utils.TODO(); + } + int off = enc._fun.computeStackOffset(enc._code,dst); + if( srcX ) src -= arm.D_OFFSET; + enc.add4(Arm64.load_str_imm(Arm64.OP_STORE_IMM_64, off, arm.RSP, src, 8)); + return; + } + + if(src >= arm.MAX_REG) { + // Load from SP + int off = enc._fun.computeStackOffset(enc._code,src); + if( dstX ) dst -= arm.D_OFFSET; + enc.add4(Arm64.load_str_imm(Arm64.OP_LOAD_IMM_64, off, arm.RSP, dst, 8)); + return; + } + + // pick opcode based on regs + if(!dstX && !srcX) { + // GPR->GPR + enc.add4(Arm64.mov_reg(Arm64.OP_MOV, src, dst)); + } else if(dstX && srcX) { + // FPR->FPR + // fmov reg + enc.add4(Arm64.f_mov_reg(Arm64.OP_FMOV_REG, src - arm.D_OFFSET,dst - arm.D_OFFSET)); + } else if(!srcX && dstX) { + // GPR->FPR + // FMOV(general) 64 bits to DOUBLE-PRECISION + enc.add4(Arm64.f_mov_general(Arm64.OP_FMOV, 0b01, 0, 0b111, src, dst - arm.D_OFFSET)); + } else if( srcX && !dstX) { + //FPF->GPR + // FMOV(general) DOUBLE-PRECISION to 64 bits + enc.add4(Arm64.f_mov_general(Arm64.OP_FMOV, 0b01, 0, 0b110, src - arm.D_OFFSET, dst)); + } + } + + // General form: "mov dst = src" + @Override public void asm(CodeGen code, SB sb) { + FunNode fun = code._encoding==null ? null : code._encoding._fun; + sb.p(code.reg(this,fun)).p(" = ").p(code.reg(in(1),fun)); } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java index d78fdcad..cf858c36 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java @@ -1,49 +1,35 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.MachConcreteNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.StoreNode; -import java.io.ByteArrayOutputStream; -import java.util.BitSet; +import com.seaofnodes.simple.node.Node; // 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) +// base - base pointer, offset is added to base +// null - index never allowed (no [reg+reg] mode) +// off - offset added to base +// imm - immediate value to store, only if val is null +// val - value to store or null //e.g s.cs[0] = 67; // C // base = s.cs, off = 4, imm = 67, val = null public class StoreARM extends MemOpARM { - StoreARM(StoreNode st, Node base, Node idx, int off, int imm, Node val) { - super(st, base, idx, off, imm, val); + StoreARM(StoreNode st, Node base, Node idx, int off, Node val) { + super(st, base, idx, off, 0, val); } - @Override public String op() { return "st"+_sz; } - - // Wider mask to store both GPRs and FPRs - @Override public RegMask regmap(int i) { - if( i==1 ) return arm.MEM_MASK; - if( i==2 ) return arm.RMASK; - if( i==3 ) return arm.RMASK; - if( i==4 ) return arm.RMASK; - return null; // Trailing anti-dependence edges - } - // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return null; } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } + private static final int[] OP_STORES = new int[]{ Arm64.OP_STORE_IMM_8, Arm64.OP_STORE_IMM_16, Arm64.OP_STORE_IMM_32, Arm64.OP_STORE_IMM_64, }; + private static final int[] OP_STORE_RS = new int[]{ Arm64.OP_STORE_R_8, Arm64.OP_STORE_R_16, Arm64.OP_STORE_R_32, Arm64.OP_STORE_R_64, }; + + @Override public void encoding( Encoding enc ) { + ldst_encode(enc, OP_STORES[Integer.numberOfTrailingZeros(size())], OP_STORE_RS[_declaredType.log_size()], val(), size()); + } @Override public void asm(CodeGen code, SB sb) { asm_address(code,sb).p(","); if( val()==null ) sb.p("#").p(_imm); diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java index 27555b53..6024df68 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java @@ -1,30 +1,20 @@ package com.seaofnodes.simple.node.cpus.arm; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; - public class SubARM extends MachConcreteNode implements MachNode { - SubARM(Node sub) {super(sub);} - - // Register mask allowed on input i. - @Override public RegMask regmap(int i) { assert i==1 || i==2; return arm.RMASK; } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return arm.RMASK; } + SubARM(Node sub) { super(sub); } + @Override public String op() { return "sub"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } + @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,Arm64.OP_SUB); } // General form: "sub # rd = rs1 - rs2" @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - ").p(code.reg(in(2))); } - - @Override public String op() { return "sub"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java index 8ae05b40..dd147544 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java @@ -1,32 +1,19 @@ package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; -public class SubFARM extends MachConcreteNode implements MachNode{ +public class SubFARM extends MachConcreteNode implements MachNode { SubFARM(Node subf) {super(subf);} - - // Register mask allowed on input i. - @Override public RegMask regmap(int i) { assert i==1 || i==2; return arm.DMASK; } - // Register mask allowed as a result. 0 for no register. + @Override public String op() { return "subf"; } + @Override public RegMask regmap(int i) { return arm.DMASK; } @Override public RegMask outregmap() { return arm.DMASK; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - + @Override public void encoding( Encoding enc ) { arm.f_scalar(enc,this,Arm64.OPF_SUB); } // Default on double precision for now(64 bits) // General form: "vsub.f32 rd = src1 + src2 @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - ").p(code.reg(in(2))); } - - @Override public String op() { return "subf"; } - } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java index d19210cb..e45bfa63 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java @@ -1,42 +1,30 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.MachConcreteNode; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.LRG; -import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; public class SubIARM extends MachConcreteNode implements MachNode { - final TypeInteger _ti; - SubIARM(Node sub, TypeInteger ti) { + final int _imm; + SubIARM(Node sub, int imm) { super(sub); _inputs.pop(); - _ti = ti; + _imm = imm; } - - // Register mask allowed on input i. + @Override public String op() { return _imm == -1 ? "dec" : "subi"; } @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return arm.RMASK; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + arm.imm_inst(enc,this, in(1), Arm64.OPI_SUB,_imm); } // General form: "subi rd = rs1 - imm" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - #"); - _ti.print(sb); + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - #").p(_imm); } - - @Override public String op() { - return (_ti.value() == -1 ? "dec" : "subi"); - } - } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java index cdb2b6ec..1f2b6a37 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java @@ -1,37 +1,54 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.ConstantNode; import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.node.cpus.riscv.riscv; import com.seaofnodes.simple.type.TypeFunPtr; -import java.io.ByteArrayOutputStream; -public class TFPARM extends ConstantNode implements MachNode { +public class TFPARM extends ConstantNode implements MachNode, RIPRelSize { TFPARM( ConstantNode con ) { super(con); } @Override public String op() { return "ldx"; } - // Register mask allowed on input i. 0 for no register. @Override public RegMask regmap(int i) { return null; } - // General int registers @Override public RegMask outregmap() { return arm.WMASK; } - @Override public boolean isClone() { return true; } @Override public TFPARM copy() { return new TFPARM(this); } + @Override public void encoding( Encoding enc ) { + enc.relo(this); + short self = enc.reg(this); + // adrp x0, 0 + int adrp = Arm64.adrp(1,0, Arm64.OP_ADRP, 0,self); + // add x0, x0, 0 + enc.add4(adrp); + arm.imm_inst(enc,Arm64.OPI_ADD,0, 0); + } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { + @Override public byte encSize(int delta) { + if( -(1L<<12) <= delta && delta < (1L<<12) ) return 4; throw Utils.TODO(); } - // 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" + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + short rpc = enc.reg(this); + if(opLen == 8 ) { + // opstart of add + int next = opStart + opLen; + int adrp_delta = delta >> 12; + // patch upper 20 bits via adrp + enc.patch4(opStart, Arm64.adrp(1, adrp_delta & 0b11, 0b10000, adrp_delta >> 2, rpc)); + // low 12 bits via add + enc.patch4(next, Arm64.imm_inst_l(Arm64.OPI_ADD, delta & 0xfff, rpc)); + } else { + // should not happen as one instruction is 4 byte, and TFP arm encodes 2. + throw Utils.TODO(); + } + } + @Override public void asm(CodeGen code, SB sb) { String reg = code.reg(this); _con.print(sb.p(reg).p(" #")); } - } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java new file mode 100644 index 00000000..acb32ddb --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java @@ -0,0 +1,47 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; + +// unconditional jump +public class UJmpARM extends CFGNode implements MachNode, RIPRelSize { + UJmpARM() { } + @Override public String op() { return "jmp"; } + @Override public String label() { return op(); } + @Override protected String format() { return "jmp "; } + @Override public RegMask regmap(int i) {return null; } + @Override public RegMask outregmap() { return null; } + @Override public Type compute() { throw Utils.TODO(); } + @Override public Node idealize() { throw Utils.TODO(); } + @Override public void encoding( Encoding enc ) { + enc.jump(this,uctrl()); + int body = Arm64.b(Arm64.OP_UJMP, 0); + enc.add4(body); + } + + // Delta is from opcode start + @Override public byte encSize(int delta) { + if( -(1<<26) <= delta && delta < (1<<26) ) return 4; + // 2 word encoding needs a tmp register, must teach RA + throw Utils.TODO(); + } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + if( opLen==4 ) { + enc.patch4(opStart,Arm64.b(Arm64.OP_UJMP, delta)); + } else { + throw Utils.TODO(); + } + } + + @Override public void asm(CodeGen code, SB sb) { + CFGNode target = uctrl(); + //assert target.nOuts() > 1; // Should optimize jmp to empty targets + sb.p(label(target)); + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java index fd127b18..5b635303 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java @@ -1,41 +1,20 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.MachConcreteNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; public class XorARM extends MachConcreteNode implements MachNode{ - XorARM(Node xor) {super(xor); _inputs.pop();} - - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { - assert i==1 || i==2; - - return arm.RMASK; } - - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return arm.RMASK; } - - // Output is same register as input#1 - @Override public int twoAddress() { return 1; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form + XorARM(Node xor) {super(xor);} + @Override public String op() { return "xor"; } + @Override public String glabel() { return "^"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,Arm64.OP_XOR); } // General form: "rd = x1 ^ x2" @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ^ ").p(code.reg(in(2))); } - @Override public String op() { return "eor"; } -} \ No newline at end of file +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java index d230ea47..017815f3 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java @@ -1,43 +1,28 @@ package com.seaofnodes.simple.node.cpus.arm; -import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.node.MachConcreteNode; import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; -public class XorIARM extends MachConcreteNode implements MachNode{ - final TypeInteger _ti; - XorIARM(Node xor, TypeInteger ti) {super(xor); _inputs.pop(); _ti = ti;} +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { - assert i==1 || i==2; - - return arm.RMASK; } - - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return arm.RMASK; } - - // Output is same register as input#1 - @Override public int twoAddress() { return 1; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); +public class XorIARM extends MachConcreteNode implements MachNode { + final int _imm; + XorIARM(Node xor, int imm) { + super(xor); + _inputs.pop(); + _imm = imm; } - // General form - // General form: "rd = rs1 ^ imm" + @Override public String op() { return "xori"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + + // General form: "xori rd = rs1 ^ imm" + @Override public void encoding( Encoding enc ) { arm.imm_inst_n(enc,this, in(1), Arm64.OPI_XOR,_imm); } @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ^ #"); - _ti.print(sb); + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ^ #").p(_imm); } - - @Override public String op() { return "eor"; } -} \ No newline at end of file +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java index 72254310..66a59fde 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java @@ -1,30 +1,36 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; +import static com.seaofnodes.isa.Arm64.*; import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.LRG; -import com.seaofnodes.simple.codegen.Machine; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; public class arm extends Machine { + public arm( CodeGen code ) { + if( !"SystemV".equals(code._callingConv) ) + throw new IllegalArgumentException("Unknown calling convention "+code._callingConv); + } - // X86-64 V2. Includes e.g. SSE4.2 and POPCNT. + // 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; + public static final int X0 = 0, X1 = 1, X2 = 2, X3 = 3, X4 = 4, X5 = 5, X6 = 6, X7 = 7; + public static final int X8 = 8, X9 = 9, X10 = 10, X11 = 11, X12 = 12, X13 = 13, X14 = 14, X15 = 15; + public static final int X16 = 16, X17 = 17, X18 = 18, X19 = 19, X20 = 20, X21 = 21, X22 = 22, X23 = 23; + public static final int X24 = 24, X25 = 25, X26 = 26, X27 = 27, X28 = 28, X29 = 29, X30 = 30, RSP = 31; // Floating point registers - 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; + public static final int D0 = 32, D1 = 33, D2 = 34, D3 = 35, D4 = 36, D5 = 37, D6 = 38, D7 = 39; + public static final int D8 = 40, D9 = 41, D10 = 42, D11 = 43, D12 = 44, D13 = 45, D14 = 46, D15 = 47; + public static final int D16 = 48, D17 = 49, D18 = 50, D19 = 51, D20 = 52, D21 = 53, D22 = 54, D23 = 55; + public static final int D24 = 56, D25 = 57, D26 = 58, D27 = 59, D28 = 60, D29 = 61, D30 = 62, D31 = 63; static final int FLAGS = 64; + static final int MAX_REG = 65; + public static final int D_OFFSET = 32; static final String[] REGS = new String[] { "X0", "X1", "X2", "X3", "X4", "X5", "X6", "X7", @@ -37,9 +43,7 @@ public class arm extends Machine { "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)*4+"]"; - } + @Override public String[] regs() { return REGS; } // from (x0-x30) // General purpose register mask: pointers and ints, not floats @@ -55,11 +59,9 @@ public class arm extends Machine { // Load/store mask; both GPR and FPR static final RegMask MEM_MASK = new RegMask(WR_BITS | FP_BITS); - static final RegMask SPLIT_MASK = new RegMask(WR_BITS | FP_BITS, -1L); + static final RegMask SPLIT_MASK = new RegMask(WR_BITS | FP_BITS, -2L/*skip flags*/); static final RegMask FLAGS_MASK = new RegMask(FLAGS); - // x30 (LR): Procedure link register, used to return from subroutines. - static final RegMask RPC_MASK = new RegMask(1L << X30); // Arguments masks static final RegMask X0_MASK = new RegMask(X0); @@ -105,13 +107,126 @@ public class arm extends Machine { D7_MASK, }; - static RegMask callInMask( TypeFunPtr tfp, int idx ) { - if( idx==0 ) return RPC_MASK; + // ARM ENCODING + + // True if signed 9-bit immediate + private static boolean imm9(TypeInteger ti) { + // 55 = 64-9 + return ti.isConstant() && ((ti.value()<<55)>>55) == ti.value(); + } + // True if signed 12-bit immediate + private static boolean imm12(TypeInteger ti) { + // 52 = 64-12 + return ti.isConstant() && ((ti.value()<<52)>>52) == ti.value(); + } + + // Can we encode this in ARM's 12-bit LOGICAL immediate form? + // Some combination of shifted bit-masks. + private static int imm12Logical(TypeInteger ti) { + return ti.isConstant() ? Arm64.logicalImmediate(ti.value()) : -1; + } + + public static void imm_inst(Encoding enc, Node n,Node n2, int opcode, int imm12) { + short self = enc.reg(n); + short reg1 = enc.reg(n2); + + // ADD/SUB encode an unsigned magnitude; switch operation for a negative addend. + if( imm12<0 && (opcode==OPI_ADD || opcode==OPI_SUB) ) { + opcode ^= OPI_ADD ^ OPI_SUB; + imm12 = -imm12; + } + int body = Arm64.imm_inst(opcode, imm12&0xFFF, reg1, self); + enc.add4(body); + } + + public static void imm_inst_subs(Encoding enc, Node n,Node n2, int opcode, int imm12) { + short reg1 = enc.reg(n2); + // 31 = 11111 + int body = Arm64.imm_inst(opcode, imm12&0xFFF, reg1, 31); + enc.add4(body); + } + + // for cases where rs1 and dst are the same, eg add x0, x0, 1 + public static void imm_inst(Encoding enc, int opcode, int imm12, int self) { + int body = Arm64.imm_inst(opcode, imm12&0xFFF, self, self); + enc.add4(body); + } + + public static void imm_inst_n(Encoding enc, Node n, Node n2, int opcode, int imm13) { + short self = enc.reg(n); + short reg1 = enc.reg(n2); + int body = Arm64.imm_inst_n(opcode, imm13, reg1, self); + enc.add4(body); + } + + // for cases where rs1 and dst are the same, eg add x0, x0, 1 + public static int imm_inst_l(Encoding enc, Node n, int opcode, int imm12) { + short self = enc.reg(n); + short reg1 = enc.reg(n.in(1)); + int body = Arm64.imm_inst(opcode, imm12&0xFFF, reg1, self); + return body; + } + + // for normal add, reg1, reg2 cases (reg-to-reg) + // using shifted-reg form + + public static void r_reg(Encoding enc, Node n, int opcode) { + short self = enc.reg(n); + short reg1 = enc.reg(n.in(1)); + short reg2 = enc.reg(n.in(2)); + int body = Arm64.r_reg(opcode, 0, reg2, 0, reg1, self >= 32 ? reg1: self); + enc.add4(body); + } + + public static void r_reg_subs(Encoding enc, Node n, int opcode) { + short reg1 = enc.reg(n.in(1)); + short reg2 = enc.reg(n.in(2)); + // 31 = 11111 + int body = Arm64.r_reg(opcode, 0, reg2, 0, reg1, 31); + enc.add4(body); + } + + public static void shift_reg(Encoding enc, Node n, int op2) { + short self = enc.reg(n); + short reg1 = enc.reg(n.in(1)); + short reg2 = enc.reg(n.in(2)); + int body = Arm64.shift_reg(0b10011010110, reg2, op2, reg1, self); + enc.add4(body); + } + + // MUL can be considered an alias for MADD with the third operand Ra being set to 0 + + public static void madd(Encoding enc, Node n, int opcode, int ra) { + short self = enc.reg(n); + short reg1 = enc.reg(n.in(1)); + short reg2 = enc.reg(n.in(2)); + int body = Arm64.madd(opcode, reg2, ra, reg1, self); + enc.add4(body); + } + + public static void f_scalar(Encoding enc, Node n, int op ) { + short self = (short)(enc.reg(n) -D_OFFSET); + short reg1 = (short)(enc.reg(n.in(1))-D_OFFSET); + short reg2 = (short)(enc.reg(n.in(2))-D_OFFSET); + int body = Arm64.f_scalar(0b00011110, 1, reg2, op, reg1, self); + enc.add4(body); + } + + public static void f_cmp(Encoding enc, Node n) { + short reg1 = (short)(enc.reg(n.in(1))-D_OFFSET); + short reg2 = (short)(enc.reg(n.in(2))-D_OFFSET); + int body = Arm64.f_cmp(0b00011110, 3, reg2, reg1); + enc.add4(body); + } + + @Override public RegMask callArgMask(TypeFunPtr tfp, int idx, int maxArgSlot ) { return callInMask(tfp,idx,maxArgSlot); } + static RegMask callInMask(TypeFunPtr tfp, int idx, int maxArgSlot ) { + if( idx==0 ) return CodeGen.CODE._rpcMask; if( idx==1 ) return null; // Count floats in signature up to index int fcnt=0; for( int i=2; i 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 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 -> mul(mul); - case NewNode nnn -> new NewARM(nnn); - case OrNode or -> or(or); - case ParmNode parm -> new ParmARM(parm); + case AddFNode addf -> new AddFARM(addf); + case AddNode add -> add(add); + case AndNode and -> and(and); + case BoolNode bool -> cmp(bool); + case CallNode call -> call(call); + case CastNode cast -> new CastARM(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 MinusNode neg -> new NegARM(neg); + case MulFNode mulf -> new MulFARM(mulf); + case MulNode mul -> new MulARM(mul); + case NewNode nnn -> new NewARM(nnn); + case NotNode not -> new NotARM(not); + case OrNode or -> or(or); + case ParmNode parm -> new ParmARM(parm); case BulkMemPhiNode phi -> new BulkMemPhiNode(phi); case MemPhiNode phi -> new MemPhiNode(phi); - case PhiNode phi -> new PhiNode(phi); - case ProjNode prj -> prj(prj); + case 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 -> 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 SubFARM(subf); - case SubNode sub -> sub(sub); - case ToFloatNode tfn -> i2f8(tfn); + case ReturnNode ret -> new RetARM(ret,ret.fun()); + case SarNode sar -> asr(sar); + case ShlNode shl -> lsl(shl); + case ShrNode shr -> lsr(shr); + case StartNode start -> new StartNode(start); + case StopNode stop -> new StopNode(stop); + case StoreNode st -> st(st); + case SubFNode subf -> new SubFARM(subf); + case SubNode sub -> sub(sub); + case ToFloatNode tfn-> new I2F8ARM(tfn); case ToIntegerNode cvt -> new F8ToIARM(cvt); - case XorNode xor -> xor(xor); + case XorNode xor -> xor(xor); - case LoopNode loop -> new LoopNode(loop); - case RegionNode region-> new RegionNode(region); + case LoopNode loop -> new LoopNode(loop); + case RegionNode region-> new RegionNode(region); default -> throw Utils.TODO(); }; } - private Node mul(MulNode mul) { - Node rhs = mul.in(2); - if( rhs instanceof ConstantNode off && off._con instanceof TypeInteger toff ) { - return new MulIARM(mul, toff); - } - return new MulARM(mul); - } private Node cmp(BoolNode bool){ Node cmp = _cmp(bool); return new SetARM(cmp, bool.op()); } - - private Node i2f8(ToFloatNode tfn) { - assert tfn.in(1)._type instanceof TypeInteger ti; - return new I2F8ARM(tfn); + private Node _cmp(BoolNode bool) { + if( bool.isFloat() ) + return new CmpFARM(bool); + return bool.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti + ? new CmpIARM(bool, (int)ti.value()) + : new CmpARM(bool); } private Node ld(LoadNode ld) { @@ -253,129 +343,100 @@ public class arm extends Machine { // 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(TypeInteger.ZERO))); - return new BranchARM(iff, ((BoolNode)iff.in(1)).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 TypeInteger ti - ? new CmpIARM(bool, ti) - : new CmpARM(bool); - } - - - private Node addf(AddFNode addf) { - return new AddFARM(addf); + String op = "!="; + if( iff.in(1) instanceof BoolNode bool ) op = bool.op(); + else if( iff.in(1)==null ) op = "=="; // Never-node cutout + else iff.setDef(1, new BoolNode.NE(iff.in(1), new ConstantNode(TypeInteger.ZERO))); + return new BranchARM(iff, op); } private Node add(AddNode add) { - Node rhs = add.in(2); - if( rhs instanceof ConstantNode off && off._con instanceof TypeInteger toff ) { - return new AddIARM(add, toff); - } - return new AddARM(add); + return add.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && imm12(ti) + ? new AddIARM(add, (int)ti.value()) + : new AddARM(add); } private Node sub(SubNode sub) { - return sub.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti - ? new AddIARM(sub, TypeInteger.constant(-ti.value())) + return sub.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) + ? new SubIARM(sub, (int)(ti.value())) : new SubARM(sub); } - private Node and(AndNode and) { - if( and.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti ) - return new AndIARM(and, ti); - return new AndARM(and); - } - - private Node or(OrNode or) { - if(or.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) - return new OrIARM(or, ti); - - return new OrARM(or); - } - - private Node xor(XorNode xor) { - if(xor.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) - return new XorIARM(xor, ti); - return new XorARM(xor); - } - - private Node con(ConstantNode con) { + private Node con( ConstantNode con ) { if( !con._con.isConstant() ) return new ConstantNode( con ); // Default unknown caller inputs return switch( con._con ) { case TypeInteger ti -> new IntARM(con); case TypeFloat tf -> new FloatARM(con); case TypeFunPtr tfp -> new TFPARM(con); - case TypeMemPtr tmp -> new ConstantNode(con); - case TypeNil tn -> throw Utils.TODO(); + case TypeMemPtr tmp -> new ConstantNode(con); + case TypeNil tn -> throw Utils.TODO(); // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code. - case Type t -> new ConstantNode(con); + case Type t -> t==Type.NIL ? new IntARM(con) : new ConstantNode(con); }; } - private Node sar(SarNode sar){ - if( sar.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) - return new AsrIARM(sar, ti); - return new AsrARM(sar); + private Node call(CallNode call){ + return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp + ? new CallARM(call, tfp) + : new CallRRARM(call); } - private Node shl(ShlNode shl) { - if( shl.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) - return new LslIARM(shl, ti); - return new LslARM(shl); + private Node or(OrNode or) { + int imm12; + return or.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && (imm12 = imm12Logical(ti)) != -1 + ? new OrIARM(or, imm12) + : new OrARM(or); + } + private Node xor(XorNode xor) { + int imm12; + return xor.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && (imm12 = imm12Logical(ti)) != -1 + ? new XorIARM(xor, imm12) + : new XorARM(xor); } - private Node shr(ShrNode shr) { - if( shr.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) - return new LsrIARM(shr, ti); - return new LsrARM(shr); + private Node and(AndNode and) { + int imm12; + return and.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && (imm12 = imm12Logical(ti)) != -1 + ? new AndIARM(and, imm12) + : new AndARM(and); } - private Node call(CallNode call){ - if(call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp) - return new CallARM(call, tfp); - return new CallRRARM(call); + private Node asr(SarNode asr) { + return asr.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && ti.value() >= 0 && ti.value() < 63 + ? new AsrIARM(asr, (int)ti.value()) + : new AsrARM(asr); } - private Node prj(ProjNode prj) { - return new ProjARM(prj); + private Node lsl(ShlNode lsl) { + return lsl.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && ti.value() >= 0 && ti.value() < 63 + ? new LslIARM(lsl, (int)ti.value()) + : new LslARM(lsl); } + private Node lsr(ShrNode lsr) { + return lsr.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && ti.value() >= 0 && ti.value() < 63 + ? new LsrIARM(lsr, (int)ti.value()) + : new LsrARM(lsr); + } private static int off; private static Node idx; private Node st(StoreNode st) { - int imm=0; - Node xval = st.val(); - // e.g store this s.cs[0] = 67; // C - if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti ) { - xval = null; - imm = (int)ti.value(); - assert imm == ti.value(); // In 32-bit range - } - return new StoreARM(address(st),st.ptr(),idx,off,imm,xval); + return new StoreARM(address(st),st.ptr(),idx,off,st.val()); } // Gather addressing mode bits prior to constructing. This is a builder // pattern, but saving the bits in a *local* *global* here to keep mess // contained. - private N address( N mop ) { + 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 TypeMemPtr; // Base ptr always, not some derived - if( mop.off() instanceof ConstantNode con && con._con instanceof TypeInteger ti ) { + if( mop.off() instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm9(ti) ) { off = (int)ti.value(); assert off == ti.value(); // In 32-bit range } else { @@ -384,5 +445,4 @@ public class arm extends Machine { return mop; } - } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/i2f8.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/i2f8.java deleted file mode 100644 index e0792669..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/i2f8.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -public class i2f8 { -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java new file mode 100644 index 00000000..fc96cbe9 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java @@ -0,0 +1,42 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.isa.RiscV; +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.ConstantNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.type.Type; + +// Add upper 20bits to PC. Immediate comes from the relocation info. +public class AUIPC extends ConstantNode implements MachNode, RIPRelSize { + AUIPC( Type tfp ) { super(tfp); } + @Override public RegMask regmap(int i) { return null; } + @Override public RegMask outregmap() { return riscv.WMASK; } + @Override public boolean isClone() { return true; } + @Override public AUIPC copy() { return new AUIPC(_con); } + @Override public String op() { return "auipc"; } + @Override public void encoding( Encoding enc ) { + enc.relo(this); + short dst = enc.reg(this); + enc.add4(RiscV.u_type(RiscV.OP_AUIPC, dst, 0)); + } + + // Delta is from opcode start, but X86 measures from the end of the 5-byte encoding + @Override public byte encSize(int delta) { return 4; } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + //short rpc = enc.reg(this); + //// High half is where the TFP constant used to be, the last input + //short auipc = enc.reg(in(_inputs._len-1)); + //enc.patch4(opStart,RiscV.i_type(0x67, rpc, 0, auipc, delta)); + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { + String reg = code.reg(this); + sb.p(reg).p(" = PC+#"); + if( _con == null ) sb.p("---"); + else _con.print(sb); + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java index 2417aeb6..b0921fa0 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java @@ -1,30 +1,18 @@ package com.seaofnodes.simple.node.cpus.riscv; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.isa.RiscV; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; -public class AddFRISC extends MachConcreteNode implements MachNode{ +// fadd.d +public class AddFRISC extends MachConcreteNode implements MachNode { AddFRISC(Node addf) {super(addf);} - - // Register mask allowed on input i. + @Override public String op() { return "addf"; } @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; } - // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return riscv.FMASK; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // Default on double precision for now(64 bits) - // General form: "fadd.d rd = src1 + src2 + @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))); } - - @Override public String op() { return "addf"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java index e92a7b08..c3aa1276 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java @@ -1,11 +1,22 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.node.Node; public class AddIRISC extends ImmRISC { - AddIRISC( Node add, int imm ) { super(add,imm); } - @Override int opcode() { throw Utils.TODO(); } - @Override public String glabel() { return "+"; } + // Used to inst-selection as a direct match against an ideal Add/Sub + public AddIRISC( Node add, int imm12, boolean pop ) { super(add,imm12,pop); } @Override public String op() { return "addi"; } + @Override public String glabel() { return "+"; } + @Override int opcode() { return RiscV.OP_IMM; } + @Override int func3() {return 0;} + @Override public AddIRISC copy() { + // Clone the AddI, using the same inputs-only code used during inst select. + // Output edges are missing. + AddIRISC add = new AddIRISC(in(1),_imm12,false); + // Copys happen when output edges should be valid, so correct missing output edges. + in(1)._outputs.add(add); + return add; + } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java index 344c217d..df392a0e 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java @@ -1,29 +1,21 @@ package com.seaofnodes.simple.node.cpus.riscv; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; public class AddRISC extends MachConcreteNode implements MachNode { - AddRISC( Node add) {super(add); } + 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"; } - - // Register mask allowed on input i. @Override public RegMask regmap(int i) { return riscv.RMASK; } - // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return riscv.WMASK; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form: "rd = rs1 + rs2" + @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/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java index ddbf5aa4..7639ea94 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java @@ -1,11 +1,13 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.node.Node; public class AndIRISC extends ImmRISC { - AndIRISC( Node and, int imm) { super(and,imm); } - @Override int opcode() { throw Utils.TODO(); } - @Override public String glabel() { return "&"; } + public AndIRISC( Node and, int imm) { super(and,imm); } @Override public String op() { return "andi"; } + @Override public String glabel() { return "&"; } + @Override int opcode() { return RiscV.OP_IMM; } + @Override int func3() {return 7;} } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java index ca00bc98..93ef95c0 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java @@ -1,37 +1,15 @@ package com.seaofnodes.simple.node.cpus.riscv; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; -import java.util.BitSet; -import java.lang.StringBuilder; -public class AndRISC extends MachConcreteNode implements MachNode{ - AndRISC(Node and) { - super(and); - } - - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { - assert i==1 || i==2; - return riscv.RMASK; - } - - // Register mask allowed as a result. 0 for no register. +public class AndRISC extends MachConcreteNode implements MachNode { + public AndRISC(Node and) { super(and); } + @Override public String op() { return "and"; } + @Override public RegMask regmap(int i) { return riscv.RMASK; } @Override public RegMask outregmap() { return riscv.WMASK; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form - // General form: #rd = rs1 & rs2 + @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,0b111,0); } @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" & ").p(code.reg(in(2))); } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java index 30dfd7fe..b5ad5f59 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java @@ -1,48 +1,94 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import java.io.ByteArrayOutputStream; // Conditional branch such as: BEQ -public class BranchRISC extends IfNode implements MachNode { - final String _bop; +public class BranchRISC extends IfNode implements MachNode, RIPRelSize { + String _bop; // label is obtained implicitly - BranchRISC( IfNode iff, String bop, Node n1, Node n2 ) { + public BranchRISC( IfNode iff, String bop, Node n1, Node n2 ) { super(iff); _bop = bop; _inputs.setX(1,n1); _inputs.setX(2,n2); } + @Override public String op() { return "b"+_bop; } @Override public String label() { return op(); } - + @Override public String comment() { return "L"+cproj(1)._nid; } @Override public RegMask regmap(int i) { return riscv.RMASK; } @Override public RegMask outregmap() { return null; } + @Override public void negate() { + _bop = negate(_bop); + // Cannot encode ">" or "<=", so flip these + if( _bop.equals(">") || _bop.equals("<=") ) { + swap12(); // Cannot negate the test, so swap the operands + _bop = swap(_bop); // Swap test to match + } + } @Override protected ExprPrinter _print1(ExprPrinter p) { - p.p("if( ").n(in(1)).p(_bop); + p.p("if( "); + if( in(1)==null ) p.p("0"); + else p.n(in(1)); + p.p(_bop); if( in(2)==null ) p.p("0"); else p.n(in(2)); return p.p(" )"); } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + if( in(1)==null && _bop=="!=" ) return; // Inverted never-node, no code + enc.jump(this,cproj(0)); + if( in(1)==null ) // Never node + enc.add4(RiscV.j_type(RiscV.OP_JAL, 0, 0)); + else { + // Todo: relocs (for offset - immf) + short src1 = enc.reg(in(1)); + short src2 = in(2)==null ? (short)riscv.ZERO : enc.reg(in(2)); + enc.add4(RiscV.b_type(RiscV.OP_BRANCH, RiscV.jumpop(_bop), src1, src2, 0)); + } } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(in(1))).p(" ").p(_bop).p(" ").p(in(2)==null ? "#0" : code.reg(in(2))); + // Delta is from opcode start + @Override public byte encSize(int delta) { + if( in(1)==null && _bop=="!=" ) return 0; // Inverted never-node, no code + if( -4*1024 <= delta && delta < 4*1024 ) return 4; + // 2 word encoding needs a tmp register, must teach RA + throw Utils.TODO(); } - @Override public String op() { return "b"+_bop; } + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + assert !( in(1)==null && _bop=="!=" ); // Inverted never-node, no code no patch + if( in(1)==null ) { // Never node + enc.patch4(opStart,RiscV.j_type(RiscV.OP_JAL, 0, delta)); + } else { + short src1 = enc.reg(in(1)); + short src2 = in(2)==null ? (short)riscv.ZERO : enc.reg(in(2)); + if( opLen==4 ) { + enc.patch4(opStart,RiscV.b_type(RiscV.OP_BRANCH, RiscV.jumpop(_bop), src1, src2, delta)); + } else { + throw Utils.TODO(); + } + } + } - @Override public String comment() { - return "L"+cproj(1)._nid+", L"+cproj(0)._nid; + @Override public void asm(CodeGen code, SB sb) { + if( in(1)==null && _bop=="!=" ) { + sb.p("never"); + return; + } + String src1 = in(1)==null ? "#0" : code.reg(in(1)); + String src2 = in(2)==null ? "#0" : code.reg(in(2)); + sb.p(src1).p(" ").p(_bop).p(" ").p(src2).p(" "); + CFGNode prj = cproj(0).uctrlSkipEmpty(); + if( !prj.blockHead() ) prj = prj.cfg0(); + sb.p(label(prj)); } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallEndRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallEndRISC.java index fd1ba7c3..7df68e77 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallEndRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallEndRISC.java @@ -1,35 +1,8 @@ package com.seaofnodes.simple.node.cpus.riscv; -import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.CallEndNode; import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.type.TypeFloat; -import com.seaofnodes.simple.type.TypeFunPtr; -import java.io.ByteArrayOutputStream; public class CallEndRISC extends CallEndNode implements MachNode { - final TypeFunPtr _tfp; - CallEndRISC( CallEndNode cend ) { - super(cend); - _tfp = (TypeFunPtr)(cend.call().fptr()._type); - } - - @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(); } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - @Override public void asm(CodeGen code, SB sb) { } - - @Override public String op() { return "cend"; } - + CallEndRISC( CallEndNode cend ) { super(cend); } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java index 54f4fba9..2b3232c3 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java @@ -1,44 +1,59 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeFunPtr; -import java.io.ByteArrayOutputStream; -public class CallRISC extends CallNode implements MachNode{ +public class CallRISC extends CallNode implements MachNode, RIPRelSize { final TypeFunPtr _tfp; final String _name; - CallRISC( CallNode call, TypeFunPtr tfp ) { super(call); - _inputs.pop(); // Pop constant target 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) { - return riscv.callInMask(_tfp,i); // Normal argument + return riscv.callInMask(_tfp,i,fun()._maxArgSlot); } - @Override public RegMask outregmap() { return null; } - + @Override public RegMask outregmap() { return riscv.RPC_MASK; } @Override public String name() { return _name; } @Override public TypeFunPtr tfp() { return _tfp; } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { + @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); + enc.add4(RiscV.j_type(RiscV.OP_JAL, riscv.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 ) { + if( opLen==4 ) { + enc.patch4(opStart,RiscV.j_type(RiscV.OP_JAL, riscv.RPC, delta)); + } else { + throw Utils.TODO(); + } + } + @Override public void asm(CodeGen code, SB sb) { sb.p(_name).p(" "); for( int i=0; i>12)); + // Load dst,[dst+#low12_constant_pool] + enc.patch4(opStart+4, RiscV.i_type(RiscV.OP_LOADFP, dst, 0b11, tmp, delta & 0xFFF)); } - // Human-readable form appended to the SB. Things like the encoding, - // indentation, leading address or block labels not printed here. - // Just something like "ld4\tR17=[R18+12] // Load array base". - // General form: "op\tdst=src+src" @Override public void asm(CodeGen code, SB sb) { - _con.print(sb.p(code.reg(this)).p(" #")); + _con.print(sb.p(code.reg(this)).p(" = #")); } + } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java index 3b7d9a34..4ebdec15 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java @@ -1,21 +1,23 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; +import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.FunNode; import com.seaofnodes.simple.node.MachNode; -import java.io.ByteArrayOutputStream; public class FunRISC extends FunNode implements MachNode { FunRISC(FunNode fun) {super(fun);} - // Register mask allowed on input i. 0 for no register. - @Override public RegMask regmap(int i) { return null; } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return null; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void computeFrameAdjust(CodeGen code, int maxReg) { + super.computeFrameAdjust(code,maxReg); + if( _hasCalls ) // If non-leaf, pad to 16b + _frameAdjust = ((_frameAdjust+8) & -16); + } + @Override public void encoding( Encoding enc ) { + int sz = _frameAdjust; + if( sz == 0 ) return; // Skip if no frame adjust + if( sz >= 1L<<12 ) throw Utils.TODO(); + enc.add4(RiscV.i_type(RiscV.OP_IMM, riscv.RSP, 0, riscv.RSP, -sz & 0xFFF)); } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java index 560d1b38..c9b20939 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java @@ -1,30 +1,24 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; // fcvt.d.w +// Converts a 32-bit signed integer, in integer register rs1 into a double-precision floating-point number in floating-point register rd. public class I2F8RISC extends MachConcreteNode implements MachNode { I2F8RISC(Node i2f8) {super(i2f8);} - - // Register mask allowed on input i. + @Override public String op() { return "cvtf"; } @Override public RegMask regmap(int i) { assert i==1; return riscv.RMASK; } - // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return riscv.FMASK; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + short dst = (short)(enc.reg(this )-riscv.F_OFFSET); + short src1 = enc.reg(in(1)); + int body = RiscV.r_type(RiscV.OP_FP,dst,RiscV.RM.RNE.ordinal(),src1,0,0x69); + enc.add4(body); } - - // General form: "i2f8 (flt)int_value" @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)).p(" = ").p("(flt)").p(code.reg(in(1))); } - - @Override public String op() { return "i2f8"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java index 330de299..dd41589c 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java @@ -1,40 +1,56 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; -public abstract class ImmRISC extends MachConcreteNode implements MachNode { - final int _imm; - ImmRISC( Node add, int imm ) { +abstract public class ImmRISC extends MachConcreteNode implements MachNode { + public final int _imm12; + ImmRISC( Node add, int imm12, boolean pop ) { super(add); - _inputs.pop(); - _imm = imm; + _imm12 = imm12; + // If pop, we are moving from an ideal Node to MachNode; the ideal Node + // inputs got copied here, but we are absorbing the immediate into the + // MachNode and so do not want the constant input. + if( pop ) _inputs.pop(); + // if NOT pop, we are the 2nd-half of a 2-part expansion, reset the + // input edges to no-control + else { _inputs.set(0,null); _inputs.setX(1,add); } } - - // Register mask allowed on input i. + ImmRISC( Node add, int imm12 ) { this(add,imm12,true); } @Override public RegMask regmap(int i) { return riscv.RMASK; } - // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return riscv.WMASK; } + @Override public boolean isClone() { + // Allow cloning of the 2nd half of a 2-part constant; this might + // trigger cloning the first part also. + return in(1) instanceof LUI || in(1) instanceof AUIPC; + } @Override protected ExprPrinter _print1(ExprPrinter p) { - return p.p("( ").n(in(1)).p(String.format(" %s #%d )",glabel(),_imm)); + int imm12 = (_imm12<<20)>>20; // Sign extend 12 bits + p.p("( ").n(in(1)); + return p.p(String.format(" %s #%d )",glabel(),imm12)); } abstract int opcode(); + abstract int func3(); - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @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) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ").p(glabel()).p(" #").p(_imm); + int imm12 = (_imm12<<20)>>20; // Sign extend 12 bits + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ").p(glabel()).p(" #").p(imm12); + if( in(1) instanceof LUI lui ) + sb.p(" // #").hex4((int)(((TypeInteger)lui._con).value()) + imm12); } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java index e792b055..e4dd39dd 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java @@ -1,33 +1,29 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.ConstantNode; import com.seaofnodes.simple.node.MachNode; -import java.io.ByteArrayOutputStream; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; -// Integer constants +// 12-bit integer constant. Larger constants are made up in the instruction +// selection by adding with a LUI. public class IntRISC extends ConstantNode implements MachNode { - IntRISC(ConstantNode con) { super(con); } + public IntRISC(ConstantNode con) { super(con); } @Override public String op() { return "ldi"; } - // Register mask allowed on input i. 0 for no register. @Override public RegMask regmap(int i) { return null; } - // General int registers @Override public RegMask outregmap() { return riscv.WMASK; } - @Override public boolean isClone() { return true; } @Override public IntRISC copy() { return new IntRISC(this); } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + short dst = enc.reg(this); + int val = _con==Type.NIL ? 0 : (int)(((TypeInteger)_con).value() & 0xFFF); + // Explicit truncation of larger immediates; this will sign-extend on + // load and this is handled during instruction selection. + enc.add4(RiscV.i_type(RiscV.OP_IMM, dst, 0, riscv.ZERO, val)); } - - // 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/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java new file mode 100644 index 00000000..596e11cd --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java @@ -0,0 +1,33 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.isa.RiscV; +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.ConstantNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.type.TypeInteger; + +// Load upper 20bits. +public class LUI extends ConstantNode implements MachNode { + public LUI( int imm20 ) { + super(TypeInteger.constant(imm20)); + assert riscv.imm20Exact((TypeInteger)_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)((TypeInteger)_con).value()); } + @Override public String op() { return "lui"; } + @Override public void encoding( Encoding enc ) { + long x = ((TypeInteger)_con).value(); + int imm20 = (int)(x>>12) & 0xFFFFF; + short dst = enc.reg(this); + int lui = RiscV.u_type(RiscV.OP_LUI, dst, imm20); + enc.add4(lui); + } + + @Override public void asm(CodeGen code, SB sb) { + String reg = code.reg(this); + sb.p(reg).p(" = #").hex4((int)(((TypeInteger)_con).value())); + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java index 3fe8f44e..5e1845b8 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java @@ -1,40 +1,30 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; - // 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 - offset added to base - +// off = off - imm12 added to base public class LoadRISC extends MemOpRISC { - LoadRISC(LoadNode ld, Node base, Node idx, int off) { - super(ld, base, idx, off, 0); - } - - @Override public RegMask regmap(int i) { - return riscv.RMASK; - } - // Wide mask loads both ints and floats; encoding varies. + public LoadRISC(LoadNode ld, Node base, int off) { super(ld, base, off, null); } + @Override public String op() { return "ld" +_sz; } + @Override public RegMask regmap(int i) { return riscv.RMASK; } @Override public RegMask outregmap() { return riscv.MEM_MASK; } - - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + short dst = enc.reg(this ); + short ptr = enc.reg(ptr()); + int op = dst >= riscv.F_OFFSET ? RiscV.OP_LOADFP : RiscV.OP_LOAD; + if( dst >= riscv.F_OFFSET ) dst -= riscv.F_OFFSET; + enc.add4(RiscV.i_type(op, dst, func3(), ptr, _off)); } @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)).p(","); asm_address(code,sb); } - - @Override public String op() { return "ld" +_sz; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java index 19869285..41ab37be 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java @@ -2,59 +2,74 @@ package com.seaofnodes.simple.node.cpus.riscv; import com.seaofnodes.print.ExprPrinter; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; -import java.io.ByteArrayOutputStream; -import java.lang.StringBuilder; - -public class MemOpRISC extends MemOpNode implements MachNode { - final int _off; // Limit 32 bits - final int _imm; // Limit 32 bits - final char _sz = (char)('0'+(1<<_declaredType.log_size())); - MemOpRISC( MemOpNode mop, Node base, Node idx, int off, int imm) { +public abstract class MemOpRISC extends MemOpNode implements MachNode { + final int _off; // Limit 12 bits + final char _sz; + MemOpRISC(MemOpNode mop, Node base, int off, Node val) { super(mop,mop); - assert base._type instanceof TypeMemPtr && !(base instanceof AddNode); - assert ptr() == base; - _inputs.setX(3,idx); + assert base._type instanceof TypeMemPtr; + _inputs.setX(2, base ); // Base can be an Add, is no longer raw object base + _inputs.setX(3, null); // Never an index + _inputs.setX(4, val ); _off = off; - _imm = imm; - } - // Store-based flavors have a value edge - MemOpRISC( MemOpNode mop, Node base, Node idx, int off, int imm, Node val ) { - this(mop,base,idx,off,imm); - _inputs.setX(4,val); + _sz = (char)('0'+(1<<_declaredType.log_size())); } - Node idx() { return in(3); } - Node val() { return in(4); } // Only for stores, including op-to-memory + @Override public String label() { return op(); } + Node val() { return in(4); } // Only for stores @Override protected ExprPrinter _printMach(ExprPrinter p) { return p.p(".").p(_name); } - @Override public boolean isMem() { return _type instanceof TypeMem; } - - @Override public String label() { return op(); } @Override public Type compute() { throw Utils.TODO(); } @Override public Node idealize() { throw Utils.TODO(); } + // func3 is based on load/store size and extend + int func3() { + int func3 = -1; + // no unsigned flavour for store, so both signed and unsigned trigger the same + if(this instanceof StoreRISC) { + if( _declaredType == TypeInteger. I8 || _declaredType == TypeInteger.U8 || _declaredType == TypeInteger.BOOL) func3=0; // SB + if( _declaredType == TypeInteger.I16 || _declaredType == TypeInteger.U16 ) func3=1; // SH + if( _declaredType == TypeInteger.I32 || _declaredType == TypeInteger.U32 || _declaredType instanceof TypeMemPtr) func3=2; // SW + if( _declaredType == TypeInteger.BOT ) func3=3; // SD + if( func3 == -1 ) throw Utils.TODO(); + return func3; + } + if( _declaredType == TypeInteger. I8 ) func3=0; // LB + if( _declaredType == TypeInteger.I16 ) func3=1; // LH + if( _declaredType == TypeInteger.I32 ) func3=2; // LW + if( _declaredType == TypeInteger.BOT ) func3=3; // LD + if( _declaredType == TypeInteger. U8 ) func3=4; // LBU + if( _declaredType == TypeInteger.BOOL) func3=4; // LBU + if( _declaredType == TypeInteger.U16 ) func3=5; // LHU + if( _declaredType == TypeInteger.U32 ) func3=6; // LWU + + // float + if(_declaredType == TypeFloat.F32) func3 = 2; // fLW fSW + if(_declaredType == TypeFloat.F64) func3 = 3; // fLD fSD + if( _declaredType instanceof TypeMemPtr ) func3=3; // 8 byte pointers + if( func3 == -1 ) throw Utils.TODO(); + return func3; + } // Register mask allowed on input i. @Override public RegMask regmap(int i) { + // 0 - ctrl + // 1 - memory + if( i==2 ) return riscv.RMASK; // base + // 2 - index + if( i==4 ) return riscv.MEM_MASK; // value throw Utils.TODO(); } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { throw Utils.TODO(); } - - @Override public int encoding(ByteArrayOutputStream bytes) { throw Utils.TODO(); } - SB asm_address(CodeGen code, SB sb) { sb.p("[").p(code.reg(ptr())).p("+"); - if( idx() != null ) sb.p(code.reg(idx())); - else sb.p(_off); - return sb.p("]"); + return sb.p(_off).p("]"); } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java index 8a066ab1..40609f96 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java @@ -1,30 +1,18 @@ package com.seaofnodes.simple.node.cpus.riscv; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.isa.RiscV; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import java.io.ByteArrayOutputStream; -public class MulFRISC extends MachConcreteNode implements MachNode{ +// fmul.d +public class MulFRISC extends MachConcreteNode implements MachNode { MulFRISC(Node mulf) {super(mulf);} - - // Register mask allowed on input i. + @Override public String op() { return "mulf"; } @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; } - // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return riscv.FMASK; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // Default on double precision for now(64 bits) - // General form: "fmul.d rd = src1 * src2 + @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))); } - - @Override public String op() { return "mulf"; } - } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java index 0e2d35a3..a7961627 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java @@ -1,32 +1,17 @@ package com.seaofnodes.simple.node.cpus.riscv; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; - // mulh signed multiply instruction(no-imm form) public class MulRISC extends MachConcreteNode implements MachNode{ - MulRISC(Node mul) {super(mul);} - - // Register mask allowed on input i. + public MulRISC(Node mul) {super(mul);} + @Override public String op() { return "mul"; } @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.RMASK; } - // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return riscv.WMASK; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form: "rd = rs1 * rs2" + @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))); } - - @Override public String op() { return "mul"; } - } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java new file mode 100644 index 00000000..58340b32 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java @@ -0,0 +1,21 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.isa.RiscV; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; + +public class NegRISC extends MachConcreteNode implements MachNode { + public NegRISC( Node neg ) { super(neg); } + @Override public String op() { return "neg"; } + @Override public RegMask regmap(int i) { assert i==1; return riscv.RMASK; } + @Override public RegMask outregmap() { return riscv.WMASK; } + @Override public void encoding( Encoding enc ) { + short dst = enc.reg(this ); + short src = enc.reg(in(1)); + RiscV.r_type(RiscV.OP,dst,0,0,src,0x20); + } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = -").p(code.reg(in(1))); + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java index e8d32570..8e3daaba 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java @@ -1,29 +1,47 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import java.io.ByteArrayOutputStream; +import com.seaofnodes.simple.print.ASMPrinter; -public class NewRISC extends NewNode implements MachNode { +public class NewRISC extends NewNode implements MachNode, RIPRelSize { // A pre-zeroed chunk of memory. NewRISC( NewNode nnn ) { super(nnn); } - // Size and pointer result in standard calling convention; null for all the - // memory and ordering edges - @Override public RegMask regmap(int i) { return i == 2 ? riscv.A0_MASK : null; } - @Override public RegMask outregmap(int i) { return i == 0 ? riscv.A0_MASK : null; } - @Override public RegMask outregmap() { return null; } + @Override public void encoding( Encoding enc ) { + // Generic external encoding; 2 ops. + enc.external(this,"calloc"); + // A1 is a caller-save, allowed to crush building external address + // auipc + enc.add4(RiscV.i_type(RiscV.OP_IMM , _arg2Reg , 0, riscv.ZERO, 1)); + enc.add4(RiscV.u_type(RiscV.OP_AUIPC, riscv.A2 , 0)); + enc.add4(RiscV.i_type(RiscV.OP_JALR , riscv.RPC, 0, riscv.A2, 0)); + } + + // Patch is for running "new" in a JIT. + // Delta is from opcode start + @Override public byte encSize(int delta) { return 4*3; } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + // Patch is for running "new" in a JIT. + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + // Negative patches are JIT emulator targets. + if( opStart+delta < 0 ) { + delta -= 4; // Offset from the AUIPC start + int imm20 = delta>>12; + if( ((delta>>11)&1)==1 ) imm20++; // Correct accidental sign extension + int imm12 = delta&0xFFF; + enc.patch4(opStart+4,RiscV.u_type(RiscV.OP_AUIPC, riscv.A2 , imm20)); + enc.patch4(opStart+8,RiscV.i_type(RiscV.OP_JALR , riscv.RPC, 0, riscv.A2, imm12)); + } else + throw Utils.TODO(); } - // General form: "alloc #bytes" + // General form: "alloc #bytes PC" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(size())); + sb.p("ldi a1=#1\n"); + sb.p("auipc a2=#calloc\n"); + sb.p("call a2+#calloc, a0"); } - - @Override public String op() { return "alloc"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java index 6d30cd1c..6aefc54b 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java @@ -1,25 +1,22 @@ package com.seaofnodes.simple.node.cpus.riscv; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.LRG; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.*; +import com.seaofnodes.isa.RiscV; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.MachConcreteNode; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.NotNode; public class NotRISC extends MachConcreteNode implements MachNode { - NotRISC(NotNode not) {super(not);} + public NotRISC(NotNode not) { super(not); } + @Override public String op() { return "not"; } @Override public RegMask regmap(int i) { return riscv.RMASK; } - @Override public RegMask outregmap() { return riscv.RMASK; } - - @Override public int twoAddress( ) { return 0; } - - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public RegMask outregmap() { return riscv.WMASK; } + @Override public void encoding( Encoding enc ) { + short dst = enc.reg(this ); + short src = enc.reg(in(1)); + // SEQZ is unsigned: negative nonzero inputs must also produce false. + enc.add4(RiscV.i_type(RiscV.OP_IMM, dst, 3, src, 1)); } - @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); } - @Override public String op() { return "not"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java index f41d7162..53abcf0b 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java @@ -1,11 +1,13 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.node.Node; public class OrIRISC extends ImmRISC { - OrIRISC( Node and, int imm) { super(and,imm); } - @Override int opcode() { throw Utils.TODO(); } + public OrIRISC( Node and, int imm) { super(and,imm); } + @Override int opcode() { return RiscV.OP_IMM; } + @Override int func3() { return 6; } @Override public String glabel() { return "|"; } @Override public String op() { return "ori"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java index 7f808f90..103d4c7d 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java @@ -1,39 +1,16 @@ package com.seaofnodes.simple.node.cpus.riscv; import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.MachConcreteNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.Node; -import java.io.ByteArrayOutputStream; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; public class OrRISC extends MachConcreteNode implements MachNode { - OrRISC(Node or) { - super(or); - } - - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { - assert i==1 || i==2; - return riscv.RMASK; - } - - // Register mask allowed as a result. 0 for no register. + public OrRISC(Node or) { super(or); } + @Override public String op() { return "or"; } + @Override public RegMask regmap(int i) { return riscv.RMASK; } @Override public RegMask outregmap() { return riscv.WMASK; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form - // General form: # rd = rs1 | rs2 + @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,6,0); } @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" | ").p(code.reg(in(2))); } - - @Override public String op() { return "or"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java index 75674f37..0f8db4e5 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java @@ -1,26 +1,17 @@ package com.seaofnodes.simple.node.cpus.riscv; -import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Encoding; import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.node.ParmNode; -import java.io.ByteArrayOutputStream; public class ParmRISC extends ParmNode implements MachNode { final RegMask _rmask; ParmRISC(ParmNode parm) { super(parm); - // Assume int - _rmask = riscv.callInMask(fun().sig(),_idx); + _rmask = riscv.callInMask(fun().sig(),_idx,0); } - - // Register mask allowed on input i. 0 for no register. - @Override public RegMask regmap(int i) { return _rmask; } - // Register mask allowed as a result. Calling convention register + @Override public RegMask regmap(int i) { return null; } @Override public RegMask outregmap() { return _rmask; } - - // Encoding is appended into the byte array. Returns size - @Override public int encoding(ByteArrayOutputStream bytes) { return 0; } + @Override public void encoding( Encoding enc ) { } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java index 42d5dc9b..2f9ac469 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java @@ -1,25 +1,15 @@ package com.seaofnodes.simple.node.cpus.riscv; -import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Encoding; import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.node.ProjNode; -import java.io.ByteArrayOutputStream; public class ProjRISC extends ProjNode implements MachNode { ProjRISC(ProjNode p) { super(p); } - - // Register mask allowed on input i. 0 for no register. @Override public RegMask regmap(int i) { return null; } - // Register mask allowed as a result. @Override public RegMask outregmap() { return ((MachNode)in(0)).outregmap(_idx); } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } + @Override public void encoding( Encoding enc ) { } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java index c9fa7fac..d28311dc 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java @@ -1,48 +1,22 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.*; import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Encoding; import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.FunNode; import com.seaofnodes.simple.node.ReturnNode; import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.type.TypeFloat; -import java.io.ByteArrayOutputStream; - -public class RetRISC extends ReturnNode implements MachNode{ - RetRISC(ReturnNode ret, FunNode fun) { super(ret, fun); fun.setRet(this); } - - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { return riscv.retMask(_fun.sig(),i); } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return null; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); +public class RetRISC extends ReturnNode implements MachNode { + public RetRISC(ReturnNode ret, FunNode fun) { super(ret, fun); fun.setRet(this); } + @Override public void encoding( Encoding enc ) { + int sz = fun()._frameAdjust; + if( sz >= 1L<<12 ) throw Utils.TODO(); + if( sz != 0 ) + enc.add4(RiscV.i_type(RiscV.OP_IMM, riscv.RSP, 0, riscv.RSP, sz & 0xFFF)); + short rpc = enc.reg(rpc()); + enc.add4(RiscV.i_type(RiscV.OP_JALR, riscv.ZERO, 0, rpc, 0)); } - - @Override public void asm(CodeGen code, SB sb) { - // 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.computeStackOffset(enc._code,dst); + int op = srcX ? RiscV.OP_STOREFP : RiscV.OP_STORE; + if( srcX ) src -= riscv.F_OFFSET; + enc.add4(RiscV.s_type(op, 0b011, riscv.RSP, src, off)); + return; + } + if( src >= riscv.MAX_REG ) { + // Load from SP + int off = enc._fun.computeStackOffset(enc._code,src); + int op = dstX ? RiscV.OP_LOADFP : RiscV.OP_LOAD; + if( dstX ) dst -= riscv.F_OFFSET; + enc.add4(RiscV.i_type(op, dst, 0b011, riscv.RSP, off)); + return; + } + // pick opcode based on regs + if( !dstX && !srcX ) { + // GPR->GPR + enc.add4(RiscV.r_type(RiscV.OP,dst,0,src,riscv.ZERO,0)); + } else if( dstX && srcX ) { + // FPR->FPR + src -= riscv.F_OFFSET; + dst -= riscv.F_OFFSET; + enc.add4(RiscV.r_type(RiscV.OP_FP, dst, 0b000, src, src, 0b0010101)); // dst = FMIN(src,src) + } else if(!srcX && dstX) { + //GPR->FPR + // fmv.d.x + enc.add4(RiscV.r_type(RiscV.OP_FP, dst - riscv.F_OFFSET, 0, src, 0, 0b1111001)); + } else if(srcX && !dstX) { + //FPR->GPR + //fmv.x.d + enc.add4(RiscV.r_type(RiscV.OP_FP, dst, 0, src - riscv.F_OFFSET, 0, 0b1110001)); + } + } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + // General form: "mov dst = src" + @Override public void asm(CodeGen code, SB sb) { + FunNode fun = code._encoding==null ? null : code._encoding._fun; + sb.p(code.reg(this,fun)).p(" = ").p(code.reg(in(1),fun)); } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java index d5ab5e08..49b1cad9 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java @@ -1,11 +1,13 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.node.Node; public class SraIRISC extends ImmRISC { - SraIRISC( Node and, int imm) { super(and,imm); } - @Override int opcode() { throw Utils.TODO(); } + public SraIRISC( Node and, int imm) { super(and,imm); } + @Override int opcode() { return RiscV.OP_IMM; } + @Override int func3() { return 5;} @Override public String glabel() { return ">>"; } @Override public String op() { return "srai"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java index 0726e292..1e930e72 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java @@ -1,38 +1,17 @@ package com.seaofnodes.simple.node.cpus.riscv; import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.MachConcreteNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; // Right Shift Arithmetic public class SraRISC extends MachConcreteNode implements MachNode { - - SraRISC(Node sra) {super(sra);} - - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { - // assert i==1; - return riscv.RMASK; } - // Register mask allowed as a result. 0 for no register. + public SraRISC(Node sra) { super(sra); } + @Override public String op() { return "sar"; } + @Override public RegMask regmap(int i) { return riscv.RMASK; } @Override public RegMask outregmap() { return riscv.WMASK; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form - // General form: "sra rd, rs1, rs2" + @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,0b101,0b00100000); } @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> ").p(code.reg(in(2))); } - - @Override public String op() { return "sra"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java index a4eb99bc..985abe2c 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java @@ -1,11 +1,13 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.node.Node; public class SrlIRISC extends ImmRISC { - SrlIRISC( Node and, int imm) { super(and,imm); } - @Override int opcode() { throw Utils.TODO(); } + public SrlIRISC( Node and, int imm, boolean pop) { super(and,imm,pop); } + @Override int opcode() { return RiscV.OP_IMM; } + @Override int func3() { return 5; } @Override public String glabel() { return ">>>"; } @Override public String op() { return "srli"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java index d1435540..47712585 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java @@ -1,38 +1,17 @@ package com.seaofnodes.simple.node.cpus.riscv; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; -import java.util.BitSet; -import java.lang.StringBuilder; // Right Shift Logical public class SrlRISC extends MachConcreteNode implements MachNode { - SrlRISC(Node srli) {super(srli);} - - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { - // assert i==1; - return riscv.RMASK; } - // Register mask allowed as a result. 0 for no register. + public SrlRISC(Node srli) { super(srli); } + @Override public String op() { return "shr"; } + @Override public RegMask regmap(int i) { return riscv.RMASK; } @Override public RegMask outregmap() { return riscv.WMASK; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form - // General form: "srl rd, rs1, rs2" + @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))); } - - @Override public String op() { return "srl"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java index 22e443fd..3cb7cc2d 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java @@ -1,15 +1,11 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.MachConcreteNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.node.StoreNode; -import java.io.ByteArrayOutputStream; -import java.util.BitSet; +import com.seaofnodes.simple.node.*; // Store memory addressing on ARM // Support imm, reg(direct), or reg+off(indirect) addressing @@ -24,33 +20,29 @@ import java.util.BitSet; // sw rs2,offset(rs1) public class StoreRISC extends MemOpRISC { - StoreRISC( StoreNode st, Node base, Node idx, int off, int imm, Node val ) { - super(st, base, idx, off, imm, val); - } - - // Wider mask to store both GPRs and FPRs + 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) { - if( i==1 ) return riscv.MEM_MASK; - if( i==2 ) return riscv.RMASK; - if( i==3 ) return riscv.RMASK; - if( i==4 ) return riscv.RMASK; - return null; // Trailing anti-dependence edges + // 0 - ctrl + if( i==1 ) return null; // mem + if( i==2 ) return riscv.RMASK; // ptr + // 2 - index + if( i==4 ) return _sz >= '4' ? riscv.MEM_MASK : riscv.RMASK; // No byte/short FP stores + return null; // Anti-dependence } - - - // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return null; } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + short val = enc.reg(val()); + short ptr = enc.reg(ptr()); + int op = val >= riscv.F_OFFSET ? RiscV.OP_STOREFP : RiscV.OP_STORE; + if( val >= riscv.F_OFFSET ) val -= riscv.F_OFFSET; + enc.add4(RiscV.s_type(op, func3()&7, ptr, val == -1 ? riscv.ZERO : val, _off)); } @Override public void asm(CodeGen code, SB sb) { asm_address(code,sb).p(","); - if( val()==null ) sb.p("#").p(_imm); + if( val()==null ) sb.p("#").p("0"); else sb.p(code.reg(val())); } - - @Override public String op() { return "st"+_sz; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java index e6fe3012..caf751bb 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java @@ -1,30 +1,18 @@ package com.seaofnodes.simple.node.cpus.riscv; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.isa.RiscV; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; -public class SubFRISC extends MachConcreteNode implements MachNode{ +// fsub.d +public class SubFRISC extends MachConcreteNode implements MachNode { SubFRISC(Node subf) {super(subf);} - - // Register mask allowed on input i. + @Override public String op() { return "subf"; } @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; } - // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return riscv.FMASK; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // Default on double precision for now(64 bits) - // General form: "fsub.d rd = src1 - src2 + @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))); } - - @Override public String op() { return "subf"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java index c37aea98..7ab4f904 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java @@ -1,29 +1,16 @@ package com.seaofnodes.simple.node.cpus.riscv; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; public class SubRISC extends MachConcreteNode implements MachNode { - SubRISC( Node sub ) { super(sub); } - - // Register mask allowed on input i. + public SubRISC( Node sub ) { super(sub); } + @Override public String op() { return "sub"; } @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.RMASK; } - // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return riscv.WMASK; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form: "sub # rd = rs1 - rs2" + @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))); } - - @Override public String op() { return "sub"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java index 23fb6a74..1d3c5920 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java @@ -1,39 +1,49 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.ConstantNode; import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeFunPtr; -import java.io.ByteArrayOutputStream; -public class TFPRISC extends ConstantNode implements MachNode{ - TFPRISC(ConstantNode con) {super(con);} - @Override public String op() { return _con == Type.NIL ? "xor" : "ldx"; } - // Register mask allowed on input i. 0 for no register. +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; } - // General int registers @Override public RegMask outregmap() { return riscv.WMASK; } - @Override public boolean isClone() { return true; } @Override public TFPRISC copy() { return new TFPRISC(this); } + @Override public void encoding( Encoding enc ) { + enc.relo(this); + // TODO: 1 op encoding, plus a TODO if it does not fit + short dst = enc.reg(this); + TypeFunPtr tfp = (TypeFunPtr)_con; + // auipc t0,0 + int auipc = RiscV.u_type(RiscV.OP_AUIPC, dst, 0); + // addi t1,t0 + #0 + int addi = RiscV.i_type(RiscV.OP_IMM, dst, 0, dst, 0); + enc.add4(auipc); + enc.add4(addi); + } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public byte encSize(int delta) { return 8; } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + short rpc = enc.reg(this); + if( opLen==8 ) { + // ADDI sign-extends its low 12 bits, so round the AUIPC high part. + enc.patch4(opStart ,RiscV.u_type(RiscV.OP_AUIPC, rpc, (delta+0x800)>>12)); + enc.patch4(opStart+4,RiscV.i_type(RiscV.OP_IMM, rpc, 0, rpc, delta & 0xFFF)); + } else { + // should not happen as one instruction is 4 byte, and TFP arm encodes 2. + throw Utils.TODO(); + } } - // Human-readable form appended to the SB. Things like the encoding, - // indentation, leading address or block labels not printed here. - // Just something like "ld4\tR17=[R18+12] // Load array base". - // General form: "op\tdst=src+src" @Override public void asm(CodeGen code, SB sb) { - String reg = code.reg(this); - if( _con == Type.NIL ) - sb.p(reg).p(",").p(reg); - else - _con.print(sb.p(reg).p(" #")); + _con.print(sb.p(code.reg(this)).p(" #")); } + } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java new file mode 100644 index 00000000..cc47b7ed --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java @@ -0,0 +1,48 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.isa.RiscV; +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; + +// unconditional jump +public class UJmpRISC extends CFGNode implements MachNode, RIPRelSize { + @Override public String op() { return "jmp"; } + @Override public String label() { return op(); } + @Override protected ExprPrinter _print1(ExprPrinter p) { return p.p("jmp "); } + @Override public RegMask regmap(int i) {return null; } + @Override public RegMask outregmap() { return null; } + @Override public Type compute() { throw Utils.TODO(); } + @Override public Node idealize() { throw Utils.TODO(); } + @Override public void encoding( Encoding enc ) { + // Short form +/-4K: beq r0,r0,imm12 + // Long form: auipc rX,imm20/32; jal r0,[rX+imm12/32] + enc.jump(this,uctrl()); + enc.add4(RiscV.j_type(RiscV.OP_JAL, 0, 0)); + } + + // Delta is from opcode start + @Override public byte encSize(int delta) { + if( -(1L<<20) <= delta && delta < (1L<<20) ) return 4; + // 2 word encoding needs a tmp register, must teach RA + throw Utils.TODO(); + } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + if( opLen==4 ) { + enc.patch4(opStart,RiscV.j_type(RiscV.OP_JAL, 0, delta)); + } else { + throw Utils.TODO(); + } + } + + @Override public void asm(CodeGen code, SB sb) { + CFGNode target = uctrl(); + //assert target.nOuts() > 1; // Should optimize jmp to empty targets + sb.p(label(target)); + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java index a2e32f06..df96d10b 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java @@ -1,11 +1,13 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.node.Node; public class XorIRISC extends ImmRISC { - XorIRISC( Node and, int imm) { super(and,imm); } - @Override int opcode() { throw Utils.TODO(); } + public XorIRISC( Node and, int imm) { super(and,imm); } + @Override int opcode() { return RiscV.OP_IMM; } + @Override int func3() { return 4; } @Override public String glabel() { return "^"; } @Override public String op() { return "xori"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java index f4ecb430..725d2b78 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java @@ -1,41 +1,16 @@ package com.seaofnodes.simple.node.cpus.riscv; import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.MachConcreteNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; public class XorRISC extends MachConcreteNode implements MachNode { - - XorRISC(Node or) { - super(or); - } - - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { - assert i==1 || i==2; - return riscv.RMASK; - } - - // Register mask allowed as a result. 0 for no register. + public XorRISC(Node or) { super(or); } + @Override public String op() { return "xor"; } + @Override public RegMask regmap(int i) { return riscv.RMASK; } @Override public RegMask outregmap() { return riscv.WMASK; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form - // General form: # # rd = rs1 ^ rs2 + @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/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java index 0542085c..335347e9 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java @@ -1,49 +1,65 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; +import static com.seaofnodes.isa.RiscV.*; import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.LRG; -import com.seaofnodes.simple.codegen.Machine; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; public class riscv extends Machine { + + public riscv(CodeGen code) { + if( !"SystemV".equals(code._callingConv) ) + throw new IllegalArgumentException("Unknown calling convention "+code._callingConv); + } + @Override public String name() {return "riscv";} + //@Override public int maxReg() { return MAX_REG; } // Using ABI names instead of register names - static final int RPC= 1, SP = 2, GP = 3, TP = 4, T0 = 5, T1 = 6, T2 = 7; - static final int S0 = 8, S1 = 9, A0 = 10, A1 = 11, A2 = 12, A3 = 13, A4 = 14, A5 = 15; - static final int A6 = 16, A7 = 17, S2 = 18, S3 = 19, S4 = 20, S5 = 21, S6 = 22, S7 = 23; - static final int S8 = 24, S9 = 25, S10 = 26, S11 = 27, T3 = 28, T4 = 29, T5 = 30, T6 = 31; + public static int ZERO = 0, RPC= 1, RSP= 2, S12= 3, S13= 4, T0 = 5, T1 = 6, T2 = 7; + public static int S0 = 8, S1 = 9, A0 = 10, A1 = 11, A2 = 12, A3 = 13, A4 = 14, A5 = 15; + public static int A6 = 16, A7 = 17, S2 = 18, S3 = 19, S4 = 20, S5 = 21, S6 = 22, S7 = 23; + public static int S8 = 24, S9 = 25, S10= 26, S11= 27, T3 = 28, T4 = 29, T5 = 30, T6 = 31; // FP registers - static final int F0 = 32, F1 = 33, F2 = 34, F3 = 35, F4 = 36, F5 = 37, F6 = 38, F7 = 39; - static final int FS0 = 40, FS1 = 41, FA0 = 42, FA1 = 43, FA2 = 44, FA3 = 45, FA4 = 46, FA5 = 47; - static final int FA6 = 48, FA7 = 49, FS2 = 50, FS3 = 51, FS4 = 52, FS5 = 53, FS6 = 54, FS7 = 55; - static final int FS8 = 56, FS9 = 57, FS10 = 58, FS11 = 59, FT8 = 60, FT9 = 61, FT10 = 62, FT11 = 63; + public static int F0 = 32, F1 = 33, F2 = 34, F3 = 35, F4 = 36, F5 = 37, F6 = 38, F7 = 39; + public static int FS0 = 40, FS1 = 41, FA0 = 42, FA1 = 43, FA2 = 44, FA3 = 45, FA4 = 46, FA5 = 47; + public static int FA6 = 48, FA7 = 49, FS2 = 50, FS3 = 51, FS4 = 52, FS5 = 53, FS6 = 54, FS7 = 55; + public static int FS8 = 56, FS9 = 57, FS10 = 58, FS11 = 59, FT8 = 60, FT9 = 61, FT10 = 62, FT11 = 63; + + static final int MAX_REG = 64; - static final int MAX_REG = 61; + public static final int F_OFFSET = 32; static final String[] REGS = new String[] { - "zero", "r1" , "rsp" , "r3" , "r4" , "r5" , "r6" , "r7" , - "r8" , "r9" , "r10" , "r11" , "r12" , "r13" , "r14" , "r15" , - "r16" , "r17" , "r18" , "r19" , "r20" , "r21" , "r22" , "r23" , - "r24" , "r25" , "r26" , "r27" , "r28" , "r29" , "r30" , "r31" , + "zero","rpc" , "rsp" , "s12" , "s13" , "t0" , "t1" , "t2" , + "s0" , "s1" , "a0" , "a1" , "a2" , "a3" , "a4" , "a5" , + "a6" , "a7" , "s2" , "s3" , "s4" , "s5" , "s6" , "s7" , + "s8" , "s9" , "s10" , "s11" , "t3" , "t4" , "t5" , "t6" , "f0" , "f1" , "f2" , "f3" , "f4" , "f5" , "f6" , "f7" , "fs0" , "fs1" , "fa0" , "fa1" , "fa2" , "fa3" , "fa4" , "fa5" , "fa6" , "fa7" , "fs2" , "fs3" , "fs4" , "fs5" , "fs6" , "fs7" , "fs8" , "fs9" , "fs10", "fs11", "ft8" , "ft9" , "ft10", "ft11" + // Register-based naming hella easier to debug than "official" register + // names when single-stepping in debugger. + //"zero", "r1" , "rsp" , "r3" , "r4" , "r5" , "r6" , "r7" , + //"r8" , "r9" , "r10" , "r11", "r12", "r13", "r14", "r15", + //"r16" , "r17" , "r18" , "r19", "r20", "r21", "r22", "r23", + //"r24" , "r25" , "r26" , "r27", "r28", "r29", "r30", "r31", + //"f0" , "f1" , "f2" , "f3" , "f4" , "f5" , "f6" , "f7" , + //"fs0" , "fs1" , "fa0" , "fa1" , "fa2" , "fa3" , "fa4" , "fa5" , + //"fa6" , "fa7" , "fs2" , "fs3" , "fs4" , "fs5" , "fs6" , "fs7" , + //"fs8" , "fs9" , "fs10", "fs11", "ft8" , "ft9" , "ft10", "ft11" }; - @Override public String reg( int reg ) { - return reg < REGS.length ? REGS[reg] : "[rsp+"+(reg-REGS.length)*4+"]"; - } - + @Override public String[] regs() { return REGS; } // General purpose register mask: pointers and ints, not floats static final long RD_BITS = 0b11111111111111111111111111111111L; // All the GPRs static final RegMask RMASK = new RegMask(RD_BITS); - static final long WR_BITS = 0b11111111111111111111111111111010L; // All the GPRs, minus ZERO and SP + static final long WR_BITS = 0b11111111111111111111111111111010L; // All the GPRs, minus ZERO and RSP static final RegMask WMASK = new RegMask(WR_BITS); // Float mask from(f0-ft10). TODO: ft10,ft11 needs a larger RegMask static final long FP_BITS = 0b11111111111111111111111111111111L<0 ) throw Utils.TODO(); + return new RegMask(MAX_REG + 1 + (idx - 2)); } - // 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(TypeInteger ti) { + // shift left 32 to clear out the upper 32 bits. + // shift right SIGNED to sign-extend upper 32 bits; then shift 12 more to clear out lower 12 bits. + // shift left 12 to re-center the bits. + return ti.isConstant() && (((ti.value()<<32)>>>44)<<12) == ti.value(); + } @Override public Node instSelect( Node n ) { return switch (n) { - case AddFNode addf -> addf(addf); - case AddNode add -> add(add); - case AndNode and -> and(and); - case BoolNode bool -> cmp(bool); - case CallNode call -> call(call); - case CastNode cast -> new CastRISC(cast); + 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 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 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 -> new NewRISC(nnn); - case OrNode or -> or(or); - case ParmNode parm -> new ParmRISC(parm); + case MinusNode neg -> new NegRISC(neg); + case MulFNode mulf -> new MulFRISC(mulf); + case MulNode mul -> new MulRISC(mul); + case NewNode nnn -> nnn(nnn); + case NotNode not -> new NotRISC(not); + case OrNode or -> or(or); + case ParmNode parm -> new ParmRISC(parm); case BulkMemPhiNode phi -> new BulkMemPhiNode(phi); case MemPhiNode phi -> new MemPhiNode(phi); - case PhiNode phi -> new PhiNode(phi); - case ProjNode prj -> prj(prj); - case ReadOnlyNode read -> new 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 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 ToIntegerNode cvt -> new F8ToIRISC(cvt); - case XorNode xor -> xor(xor); + case XorNode xor -> xor(xor); case LoopNode loop -> new LoopNode(loop); case RegionNode region -> new RegionNode(region); @@ -235,36 +253,81 @@ public class riscv extends Machine { } private Node add(AddNode add) { - if( add.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && imm12(ti) ) - return new AddIRISC(add, (int)ti.value()); + if( add.in(2) instanceof ConstantNode off2 && off2._con instanceof TypeInteger ti && imm12(ti) ) + return new AddIRISC(add, (int)ti.value(),true); return new AddRISC(add); } private Node and(AndNode and) { - if( and.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) ) - return new AndIRISC(and, (int)ti.value()); + if( and.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti ) { + if( imm12(ti) ) + return new AndIRISC(and, (int)ti.value()); + // Could be any size low bit mask + if( ti.value() == 0xFFFFFFFFL ) + return new SrlIRISC(new SllIRISC(and,32),32,false); + } return new AndRISC(and); } private Node call(CallNode call) { - if( call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp ) - return new CallRISC(call, tfp); - return new CallRRISC(call); + return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp + ? new CallRISC(call, tfp) + : new CallRRISC(call); + } + private Node nnn(NewNode nnn) { + // TODO: pass in the TFP for alloc + return new NewRISC(nnn); } private Node cmp(BoolNode bool) { - if( bool.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger toff ) - return new SetIRISC(bool, (int)toff.value()); - // Float variant + // Float variant directly implemented in hardware if( bool.isFloat() ) return new SetFRISC(bool); - return new SetRISC(bool); + + // Only < and y - swap; y < x + // x >= y - swap and negate; !(x < y); `slt tmp=y,x;` then NOT. + // x != y - sub and vs0 == `sub tmp=x-y; sltu dst=tmp,#1` then NOT. + + // The ">", ">=" and "!=" in Simple include a NotNode, which can be + // implemented with a XOR. If one of the above is followed by a NOT + // we can remove the double XOR in the encodings. + + return switch( bool.op() ) { + case "<" -> bool.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) + ? new SetIRISC(bool, (int)ti.value(),false) + : new SetRISC(bool, false); + // x <= y - flip and negate; !(y < x); `slt tmp=y,x; xori dst=tmp,#1` + case "<=" -> new XorIRISC(new SetRISC(bool.swap12(), false),1); + // x == y - sub and vs0 == `sub tmp=x-y; sltu dst=tmp,#1` + case "==" -> new SetIRISC(new SubRISC(bool),1,true,false); + default -> throw Utils.TODO(); + }; } private Node con( ConstantNode con ) { if( !con._con.isConstant() ) return new ConstantNode( con ); // Default unknown caller inputs return switch( con._con ) { - case TypeInteger ti -> new IntRISC(con); + case TypeInteger ti -> { + if( imm12(ti) ) yield new IntRISC(con); + long x = ti.value(); + if( imm20Exact(ti) ) yield new LUI((int)x); + if( (x<<32)>>32 == x ) { // Signed lower 32-bit immediate + // Here, the low 12 bits get sign-extended, which means if + // bit11 is set, the value is negative and lowers the LUI + // value. Add a bit 12 to compensate + if( ((x>>11)&1)==1 ) x += 0x1000; + yield new AddIRISC(new LUI((int)(x & ~0xFFF)), (int)(x & 0xFFF),false); + } + // Need more complex sequence for larger constants... or a load + // from a constant pool, which does not need an extra register + throw Utils.TODO(); + } + // Load from constant pool case TypeFloat tf -> new FltRISC(con); case TypeFunPtr tfp -> new TFPRISC(con); case TypeMemPtr tmp -> throw Utils.TODO(); @@ -275,45 +338,50 @@ public class riscv extends Machine { } private Node jmp( IfNode iff ) { - if( iff.in(1) instanceof BoolNode bool && !bool.isFloat() ) - return new BranchRISC(iff, bool.op(), bool.in(1), bool.in(2)); + if( iff.in(1) instanceof BoolNode bool && !bool.isFloat() ) { + // if less than or equal switch inputs + String bop = bool.op(); + if( bop.equals("<=") || bop.equals(">") ) + return new BranchRISC(iff, IfNode.swap(bop), bool.in(2), bool.in(1)); + return new BranchRISC(iff, bop, bool.in(1), bool.in(2)); + } // Vs zero - return new BranchRISC(iff, "==", iff.in(1), null); + return new BranchRISC(iff, iff.in(1)==null ? "==" : "!=", iff.in(1), null); } private Node or(OrNode or) { - if( or.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) ) + if( or.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti)) return new OrIRISC(or, (int)ti.value()); return new OrRISC(or); } private Node xor(XorNode xor) { - if( xor.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) ) + if( xor.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti)) return new XorIRISC(xor, (int)ti.value()); return new XorRISC(xor); } private Node sra(SarNode sar) { - if( sar.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) ) + if( sar.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti)) return new SraIRISC(sar, (int)ti.value()); return new SraRISC(sar); } private Node srl(ShrNode shr) { - if( shr.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) ) - return new SrlIRISC(shr, (int)ti.value()); + if( shr.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti)) + return new SrlIRISC(shr, (int)ti.value(),true); return new SrlRISC(shr); } private Node sll(ShlNode sll) { - if( sll.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) ) + if( sll.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti)) return new SllIRISC(sll, (int)ti.value()); return new SllRISC(sll); } private Node sub(SubNode sub) { return sub.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) - ? new AddIRISC(sub, (int)(-ti.value())) + ? new AddIRISC(sub, (int)(-ti.value()),true) : new SubRISC(sub); } @@ -327,28 +395,20 @@ public class riscv extends Machine { } private Node ld(LoadNode ld) { - return new LoadRISC(address(ld),ld.ptr(),idx,off); + return new LoadRISC(ld,address(ld),off); } private Node st(StoreNode st) { - int imm=0; - Node xval = st.val(); - if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) ) { - xval = null; - imm = (int)ti.value(); - assert imm == ti.value(); // In 32-bit range - } - return new StoreRISC(address(st),st.ptr(),idx,off,imm,xval); + Node xval = st.val() instanceof ConstantNode con && con._con == TypeInteger.ZERO ? null : st.val(); + return new StoreRISC(st,address(st), off, xval); } // Gather addressing mode bits prior to constructing. This is a builder // pattern, but saving the bits in a *local* *global* here to keep mess // contained. private static int off; - private static Node idx; - private N address( N mop ) { + private Node address( MemOpNode 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); @@ -356,9 +416,10 @@ public class riscv extends Machine { if( mop.off() instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) ) { off = (int)ti.value(); } else { - idx = mop.off(); + base = new AddRISC(base,mop.off()); + base._type = mop.ptr()._type; } - return mop; + return base; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java index ffc6966e..76a2d3f7 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java @@ -1,16 +1,16 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; 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 @@ -18,10 +18,11 @@ public class AddFMemX86 extends MemOpX86 { if( i==4 ) return x86_64_v2.XMASK; // value throw Utils.TODO(); } - - // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return x86_64_v2.XMASK; } - + @Override public int twoAddress() { return 4; } + @Override public void encoding( Encoding enc ) { + X86.memory(enc,0xF2,0x0F58,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(ptr()),enc.reg(idx()),_off,_scale); + } // General form: "add dst = src + [base + idx<<2 + 12]" @Override public void asm(CodeGen code, SB sb) { @@ -29,5 +30,4 @@ public class AddFMemX86 extends MemOpX86 { sb.p(val()==null ? "#"+_imm : code.reg(val())).p(" + "); asm_address(code,sb); } - @Override public String op() { return "addf"+_sz; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java index d4ea9162..b3266cb0 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java @@ -1,33 +1,24 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; public class AddFX86 extends MachConcreteNode implements MachNode { AddFX86( Node addf ) { super(addf); } - - // Register mask allowed on input i. + @Override public String op() { return "addf"; } @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; } - // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return x86_64_v2.XMASK; } - // Output is same register as input#1 @Override public int twoAddress() { return 1; } - // Ok to swap arguments @Override public boolean commutes() { return true; } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + X86.sse(enc,0xF2,0x0F58,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET); } // General form: "addf dst += src" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2))); + sb.p(code.reg(this)).p(" += ").p(code.reg(in(2))); } - - @Override public String op() { return "addf"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java index c7f92ff1..569be754 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java @@ -1,41 +1,13 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; - -public class AddIX86 extends MachConcreteNode implements MachNode { - final TypeInteger _ti; - AddIX86( Node add, TypeInteger ti ) { - super(add); - _inputs.pop(); - _ti = ti; - } - - // Register mask allowed on input i. - @Override public RegMask regmap(int i) { - // assert i== i; - return x86_64_v2.WMASK; } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - // Output is same register as input#1 - @Override public int twoAddress() { return 1; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form: "addi dst += #imm" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + #"); - _ti.print(sb); - } +import com.seaofnodes.simple.node.Node; +public class AddIX86 extends ImmX86 { + AddIX86( Node add, int imm ) { super(add,imm); } @Override public String op() { - return _ti.value() == 1 ? "inc" : (_ti.value() == -1 ? "dec" : "addi"); + 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/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java index 3d4fac41..d2111d07 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java @@ -1,26 +1,24 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; 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; } - - // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - // Output is same register as val() @Override public int twoAddress() { return 4; } + @Override public void encoding( Encoding enc ) { + X86.memory(enc,0,0x03,true,enc.reg(this),enc.reg(ptr()),enc.reg(idx()),_off,_scale); + } - // General form: "add dst = src + [base + idx<<2 + 12]" + // General form: "add dst += [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(" + "); + sb.p(code.reg(this)).p(" += "); asm_address(code,sb); } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java index 9553d499..84543e8f 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java @@ -1,33 +1,11 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; +import com.seaofnodes.simple.node.Node; -public class AddX86 extends MachConcreteNode { +public class AddX86 extends RegX86 { AddX86( Node add ) { super(add); } - - // Register mask allowed on input i. - @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.RMASK; } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - // Output is same register as input#1 - @Override public int twoAddress() { return 1; } - // Ok to swap arguments - @Override public boolean commutes() { return true; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form: "add dst += src" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2))); - } - @Override public String op() { return "add"; } + @Override public String glabel() { return "+"; } + @Override int opcode() { return 0x03; } + @Override public boolean commutes() { return true; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java index a7f1801e..3c1b567c 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java @@ -1,44 +1,11 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; -import java.util.BitSet; -import java.lang.StringBuilder; - -public class AndIX86 extends MachConcreteNode implements MachNode { - final TypeInteger _ti; - AndIX86(Node and, TypeInteger ti) { - super(and); - _inputs.pop(); - _ti = ti; - } - - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.WMASK; } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - - - // Output is same register as input#1 - @Override public int twoAddress() { return 1; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form - // General form: "andi dst & #imm" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" & #"); - _ti.print(sb); - } +import com.seaofnodes.simple.node.Node; +public class AndIX86 extends ImmX86 { + AndIX86( Node add, int imm ) { super(add,imm); } @Override public String op() { return "andi"; } + @Override public String glabel() { return "&"; } + @Override int opcode() { return 0x81; } + @Override int mod() { return 4; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java index d7fb3613..dedbce45 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java @@ -1,34 +1,11 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.*; -import java.io.ByteArrayOutputStream; - -public class AndX86 extends MachConcreteNode implements MachNode{ - AndX86(Node and) { - super(and); - } - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.WMASK; } - - @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - - // Output is same register as input#1 - @Override public int twoAddress() { return 1; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form - // General form: "and 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)));; - } +import com.seaofnodes.simple.node.Node; +public class AndX86 extends RegX86 { + AndX86( Node add ) { super(add); } @Override public String op() { return "and"; } + @Override public String glabel() { return "&"; } + @Override int opcode() { return 0x23; } + @Override public boolean commutes() { return true; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallEndX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallEndX86.java index b9cc5659..ce8b993f 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallEndX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallEndX86.java @@ -1,35 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.CallEndNode; import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.type.TypeFloat; -import com.seaofnodes.simple.type.TypeFunPtr; -import java.io.ByteArrayOutputStream; public class CallEndX86 extends CallEndNode implements MachNode { - final TypeFunPtr _tfp; - CallEndX86( CallEndNode cend ) { - super(cend); - _tfp = (TypeFunPtr)(cend.call().fptr()._type); - } - - @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(); } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - @Override public void asm(CodeGen code, SB sb) { } - - @Override public String op() { return "cend"; } - + CallEndX86( CallEndNode cend ) { super(cend); } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java index ac35d17b..15b83bfc 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java @@ -1,32 +1,23 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.CallNode; import com.seaofnodes.simple.node.MachNode; -import java.io.ByteArrayOutputStream; 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 + : x86_64_v2.callInMask(tfp(),i,fun()._maxArgSlot); // Normal argument } @Override public RegMask outregmap() { return null; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - bytes.write(0xe8); - int beforeSize = bytes.size(); - // address - //TODO: relocs - bytes.write(0x00); - return bytes.size() - beforeSize; + @Override public void encoding( Encoding enc ) { + X86.callRegister(enc,enc.reg(fptr())); } @Override public void asm(CodeGen code, SB sb) { @@ -36,5 +27,4 @@ public class CallRX86 extends CallNode implements MachNode { sb.unchar(2); } - @Override public String op() { return "callr"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java index e2f68c1f..c97037e2 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java @@ -1,15 +1,12 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.CallNode; import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.type.TypeFunPtr; -import java.io.ByteArrayOutputStream; -public class CallX86 extends CallNode implements MachNode { +public class CallX86 extends CallNode implements MachNode, RIPRelSize { final TypeFunPtr _tfp; final String _name; CallX86( CallNode call, TypeFunPtr tfp ) { @@ -19,26 +16,24 @@ public class CallX86 extends CallNode implements MachNode { _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) { - return x86_64_v2.callInMask(_tfp,i); // Normal argument - } + @Override public String name() { return _name; } + @Override public TypeFunPtr tfp() { return _tfp; } + @Override public RegMask regmap(int i) { return x86_64_v2.callInMask(_tfp,i,fun()._maxArgSlot); } @Override public RegMask outregmap() { return null; } @Override public int nargs() { return nIns()-2; } // Minus control, memory, fptr - @Override public String name() { return _name; } - @Override public TypeFunPtr tfp() { return _tfp; } + @Override public void encoding( Encoding enc ) { + enc.relo(this).add1(0xe8).add4(0); + } + + // Delta is from opcode start, but X86 measures from the end of the 5-byte encoding + @Override public byte encSize(int delta) { return 5; } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - // linker will fix this up - bytes.write(0xe8); - int beforeSize = bytes.size(); - // TODO: relocs - // address - bytes.write(0x00); - return bytes.size() - beforeSize; + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + enc.patch4(opStart+1, delta-5); } @Override public void asm(CodeGen code, SB sb) { @@ -47,7 +42,4 @@ public class CallX86 extends CallNode implements MachNode { sb.p(code.reg(in(i+2))).p(" "); sb.unchar(2); } - - @Override public String op() { return "call"; } - } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CastX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CastX86.java index b620b89c..9537cfb3 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CastX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CastX86.java @@ -1,18 +1,16 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.CastNode; import com.seaofnodes.simple.node.MachNode; -import java.io.ByteArrayOutputStream; public class CastX86 extends CastNode implements MachNode { CastX86( CastNode cast ) { super(cast); } + @Override public String op() { return label(); } @Override public RegMask regmap(int i) { assert i==1; return RegMask.FULL; } @Override public RegMask outregmap() { return RegMask.FULL; } @Override public int twoAddress( ) { return 1; } - @Override public int encoding(ByteArrayOutputStream bytes) { return 0; /*no encoding*/ } + @Override public void encoding( Encoding enc ) { } @Override public void asm(CodeGen code, SB sb) { _t.print(sb.p(code.reg(in(1))).p(" isa ")); } - @Override public String op() { return label(); } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java index e9b0dacd..5a0c7a37 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java @@ -1,21 +1,18 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; public class CmpFX86 extends MachConcreteNode implements MachNode { CmpFX86( Node cmp ) { super(cmp); } - - @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; } + @Override public String op() { return "cmpf"; } + @Override public RegMask regmap(int i) { return x86_64_v2.XMASK; } @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + X86.sse(enc,0x66,0x0F2E,false,enc.reg(in(1))-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET); } // General form: "cmp src1,src2" @@ -24,6 +21,4 @@ public class CmpFX86 extends MachConcreteNode implements MachNode { if( dst!="flags" ) sb.p(dst).p(" = "); sb.p(code.reg(in(1))).p(", ").p(code.reg(in(2))); } - - @Override public String op() { return "cmpf"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java index fcba397d..2811a640 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java @@ -1,53 +1,38 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; // Compare immediate. Sets flags. public class CmpIX86 extends MachConcreteNode implements MachNode { final int _imm; - final String _bop; - CmpIX86( BoolNode bool, TypeInteger ti ) { - super(bool); - _inputs.pop(); // Toss ideal ConstantNode away, embedded into machine op - _bop = bool.op(); // One of <,<=,== - _imm = (int)ti.value(); - assert _imm == ti.value(); + CmpIX86( Node cmp, int imm, boolean swap ) { + super(cmp); + _inputs.del(swap ? 1 : 2); + _imm = imm; } - // Copy constructor does not set reverse edges in, because the basic block - // is changing from the original node - and it must be properly placed in a - // new block. CmpIX86( CmpIX86 cmp ) { - super(cmp); - _bop = cmp._bop; + super((Node)null); + addDef(null); + addDef(cmp.in(1)); _imm = cmp._imm; - // While in(0) will be handled by the caller, all other edges must be - // handled in the constructor. - cmp.in(1)._outputs.push(this); } - @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.RMASK; } + @Override public String op() { return _imm==0 ? "test" : "cmp"; } + @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; } @Override public boolean isClone() { return true; } @Override public Node copy() { return new CmpIX86(this); } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public final void encoding( Encoding enc ) { + X86.imm(enc,0x81,7,enc.reg(in(1)),_imm); } - @Override public void asm(CodeGen code, SB sb) { String dst = code.reg(this); if( dst!="flags" ) sb.p(dst).p(" = "); sb.p(code.reg(in(1))); if( _imm != 0 ) sb.p(", #").p(_imm); } - - @Override public String op() { return _imm==0 ? "test" : "cmp"; } - } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java index f9ace702..bf8f1631 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java @@ -1,33 +1,44 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; public class CmpMemX86 extends MemOpX86 { - final boolean _invert; // Op switched LHS, RHS - CmpMemX86( BoolNode bool, LoadNode ld , Node base, Node idx, int off, int scale, int imm, Node val, boolean invert ) { + final boolean _swap; // Op switched LHS, RHS + CmpMemX86( BoolNode bool, LoadNode ld, Node base, Node idx, int off, int scale, int imm, Node val, boolean swap ) { super(bool,ld, base, idx, off, scale, imm, val ); - _invert = invert; + _swap = swap; + assert !_swap || val() == null || bool.op()=="==" || bool.op()=="!="; // Cannot swap with immediate } - - // Register mask allowed as a result. 0 for no register. + @Override public String op() { return ((val()==null && _imm==0) ? "test" : "cmp") + _sz; } @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; } + @Override public void encoding( Encoding enc ) { + short ptr=enc.reg(ptr()), idx=enc.reg(idx()), src=enc.reg(val()); + if( src==-1 ) { + assert !_swap; + X86.cmpImm(enc,_declaredType.log_size(),_imm,ptr,idx,_off,_scale); + } else encVal(enc,_declaredType,ptr,idx,src,_off,_scale,_swap); + } + + static void encVal(Encoding enc, Type decl, short ptr, short idx, short src, int off, int scale, boolean _swap) { + assert !(decl instanceof TypeFloat); // Selected separately as CmpFX86. + X86.cmpMem(enc,decl.log_size(),src,ptr,idx,off,scale,_swap); + } // General form: "cmp dst = src, [base + idx<<2 + 12]" @Override public void asm(CodeGen code, SB sb) { String dst = code.reg(this); - if( dst!="FLAGS" ) sb.p(dst).p(" = "); - if( _invert ) asm_address(code,sb).p(","); + if( dst!="flags" ) sb.p(dst).p(" = "); + if( _swap ) asm_address(code,sb).p(","); if( val()==null ) { if( _imm!=0 ) sb.p("#").p(_imm); } else { sb.p(code.reg(val())); } - if( !_invert ) asm_address(code,sb.p(", ")); + if( !_swap ) asm_address(code,sb.p(", ")); } - - @Override public String op() { return ((val()==null && _imm==0) ? "test" : "cmp") + _sz; } - } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java index 41ce33d2..e3cbe75f 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java @@ -1,29 +1,22 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; public class CmpX86 extends MachConcreteNode implements MachNode { - CmpX86( Node cmp ) { super(cmp); } - - @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.RMASK; } + CmpX86( Node add ) { super(add); } + @Override public String op() { return "cmp"; } + @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + X86.reg(enc,0x3B,enc.reg(in(1)),enc.reg(in(2))); } - - // General form: "add dst += src" @Override public void asm(CodeGen code, SB sb) { String dst = code.reg(this); - if( dst!="FLAGS" ) sb.p(dst).p(" = "); + if( dst!="flags" ) sb.p(dst).p(" = "); sb.p(code.reg(in(1))).p(", ").p(code.reg(in(2))); } - - @Override public String op() { return "cmp"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java index f74b7348..962ec44e 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java @@ -1,35 +1,24 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; public class DivFX86 extends MachConcreteNode implements MachNode { DivFX86( Node divf) { super(divf); } - - // Register mask allowed on input i. - @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return x86_64_v2.XMASK; } - // Output is same register as input#1 + @Override public String op() { return "divf"; } + @Override public RegMask regmap(int i) { return x86_64_v2.XMASK; } + @Override public RegMask outregmap() { return x86_64_v2.XMASK; } @Override public int twoAddress() { return 1; } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + X86.sse(enc,0xF2,0x0F5E,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET); } // General form: "divf dst /= src" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2))); - } - - @Override public String op() { - // 8 bytes = 64 bits - return "divf"; + sb.p(code.reg(this)).p(" /= ").p(code.reg(in(2))); } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java index 54372629..e61f576c 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java @@ -1,35 +1,25 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; public class DivX86 extends MachConcreteNode implements MachNode { DivX86( Node div ) { super(div); } - - // Register mask allowed on input i. + @Override public String op() { return "div"; } @Override public RegMask regmap(int i) { return (i==1) ? x86_64_v2.RAX_MASK : x86_64_v2.RMASK; } - // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return x86_64_v2.RAX_MASK; } - - // DIV encodes a CQO which sign extends RAX into RDX, killing RDX. @Override public RegMask killmap() { return x86_64_v2.RDX_MASK; } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + X86.divide(enc,enc.reg(in(2))); } - // General form: "div dst /= src" @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2))); } - - @Override public String op() { return "div"; } @Override public String comment() { return "kill rdx"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java index 49d666b7..44bcbe7b 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java @@ -1,30 +1,22 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; public class F8ToIX86 extends MachConcreteNode implements MachNode { F8ToIX86(Node f8toi ) { super(f8toi); } - - // Register mask allowed on input i. + @Override public String op() { return "cvti"; } @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.XMASK; } - // Register mask allowed as a result. 0 for no register. @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; } @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + X86.floatToInteger(enc,enc.reg(this),enc.reg(in(1))-x86_64_v2.XMM_OFFSET); } - @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1))); } - - @Override public String op() { return "f8toi"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java index 47f7149d..b17ea305 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java @@ -1,35 +1,36 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.ConstantNode; -import com.seaofnodes.simple.node.MachNode; -import java.io.ByteArrayOutputStream; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; -public class FltX86 extends ConstantNode implements MachNode { +public class FltX86 extends ConstantNode implements MachNode, RIPRelSize{ + private byte _encSize; FltX86(ConstantNode con ) { super(con); } - @Override public String op() { return "fld"; } - - // Register mask allowed on input i. 0 for no register. + @Override public String op() { return "ld8"; } @Override public RegMask regmap(int i) { return null; } - // General int registers @Override public RegMask outregmap() { return x86_64_v2.XMASK; } @Override public boolean isClone() { return true; } - @Override public FltX86 copy() { return new FltX86(this); } + @Override public Node copy() { return new FltX86(this); } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + _encSize=X86.rip(enc,0xF2,0x0F10,false,enc.reg(this)-x86_64_v2.XMM_OFFSET); + enc.largeConstant(this,_con,_encSize-4,2/*ELF PC32*/); } - // Human-readable form appended to the SB. Things like the encoding, - // indentation, leading address or block labels not printed here. - // Just something like "ld4\tR17=[R18+12] // Load array base". - // General form: "op\tdst=src+src" - @Override public void asm(CodeGen code, SB sb) { - _con.print(sb.p(code.reg(this)).p(" #")); + // Delta is from opcode start + @Override public byte encSize(int delta) { + // TODO: 1-byte RIP offset? + return (byte)_encSize; + } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + enc.patch4(opStart+opLen-4,delta-opLen); } + @Override public void asm(CodeGen code, SB sb) { + _con.print(sb.p(code.reg(this)).p(" = #")); + } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java index 7346ae05..24c2a78b 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java @@ -1,22 +1,31 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.SB; import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.Encoding; import com.seaofnodes.simple.node.FunNode; import com.seaofnodes.simple.node.MachNode; -import java.io.ByteArrayOutputStream; public class FunX86 extends FunNode implements MachNode { FunX86( FunNode fun ) { super(fun); } + @Override public void postSelect(CodeGen code) { + super.postSelect(code); + // One more for RPC pre-pushed; X86 special + _maxArgSlot++; + } - // Register mask allowed on input i. 0 for no register. - @Override public RegMask regmap(int i) { return null; } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return null; } + @Override public void computeFrameAdjust(CodeGen code, int maxReg) { + super.computeFrameAdjust(code,maxReg); + // Alignment, but off by RPC + if( _hasCalls ) // If non-leaf, pad to 16b + _frameAdjust = ((_frameAdjust+8+8) & -16)-8; + } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - return 0; + @Override public void encoding( Encoding enc ) { + int sz = _frameAdjust; + if( sz == 0 ) return; // Skip if no frame adjust + X86.imm(enc,0x81,5,x86_64_v2.RSP,sz); } + } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java index d99c92a8..4a17b92b 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java @@ -1,30 +1,21 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; public class I2f8X86 extends MachConcreteNode implements MachNode { I2f8X86(Node i2f8 ) { super(i2f8); } - - // Register mask allowed on input i. + @Override public String op() { return "cvtf"; } @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.WMASK; } - // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return x86_64_v2.XMASK; } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + X86.sse(enc,0xF2,0x0F2A,true,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(1))); } - // General form: "i2f8 (flt)int_value" - @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)).p(" = ").p("(flt)").p(code.reg(in(1))); } - - @Override public String op() { return "i2f8"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java new file mode 100644 index 00000000..b87e7f47 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java @@ -0,0 +1,31 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; + +public abstract class ImmX86 extends MachConcreteNode implements MachNode { + final int _imm; + ImmX86( Node add, int imm ) { + super(add); + _inputs.pop(); // Pop constant input off + _imm = imm; + } + @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + @Override public int twoAddress() { return 1; } + + abstract int opcode(); + abstract int mod(); + + @Override public final void encoding( Encoding enc ) { + X86.imm(enc,opcode(),mod(),enc.reg(this),_imm); + } + + // General form: "addi dst += #imm" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm); + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java index fcd39e3f..29513d98 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java @@ -1,40 +1,29 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.ConstantNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; - // Integer constants public class IntX86 extends ConstantNode implements MachNode { IntX86( ConstantNode con ) { super(con); } - @Override public String op() { return _con == Type.NIL || _con == TypeInteger.ZERO ? "xor" : "ldi"; } - - // Register mask allowed on input i. 0 for no register. + @Override public boolean isClone() { return true; } + @Override public Node copy() { return new IntX86(this); } @Override public RegMask regmap(int i) { return null; } - // General int registers @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - // Zero-set uses XOR kills flags @Override public RegMask killmap() { return _con == Type.NIL || _con == TypeInteger.ZERO ? x86_64_v2.FLAGS_MASK : null; } - @Override public boolean isClone() { return true; } - @Override public Node copy() { return new IntX86(this); } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + X86.constant(enc,enc.reg(this),_con==Type.NIL ? 0 : ((TypeInteger)_con).value()); } // Human-readable form appended to the SB. Things like the encoding, diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java index 173f9ee7..92156c58 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java @@ -1,48 +1,64 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; // Jump on flags, uses flags -public class JmpX86 extends IfNode implements MachNode { - final String _bop; +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() { + @Override public void postSelect(CodeGen code) { Node set = in(1); + if( set==null ) return; // Never-node cutout Node cmp = set.in(1); // Bypass an expected Set and just reference the cmp directly - if( set instanceof SetX86 && (cmp instanceof CmpX86 || cmp instanceof CmpIX86 || cmp instanceof CmpMemX86 || cmp instanceof CmpFX86) ) - _inputs.set(1,cmp); - else + if( set instanceof SetX86 setx && (cmp instanceof CmpX86 || cmp instanceof CmpIX86 || cmp instanceof CmpMemX86 || cmp instanceof CmpFX86) ) { + _inputs.set( 1, cmp ); + _bop = setx._bop; + } else throw Utils.TODO(); } @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.FLAGS_MASK; } @Override public RegMask outregmap() { return null; } + @Override public void negate() { _bop = negate(_bop); } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + if( in(1)==null && _bop=="!=" ) return; + X86.branch(enc,in(1)==null ? -1 : x86_64_v2.jumpop(_bop)&15); + enc.jump(this,cproj(0)); } - @Override public void asm(CodeGen code, SB sb) { - String src = code.reg(in(1)); - if( src!="flags" ) sb.p(src); + // Delta is from opcode start, but X86 measures from the end of the 2-byte encoding + @Override public byte encSize(int delta) { + if( in(1)==null && _bop=="!=" ) return 0; + return X86.branchSize(delta,in(1)!=null); } - @Override public String op() { return "j"+_bop; } + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + assert !(in(1)==null && _bop=="!="); + X86.patchBranch(enc.bits(),opStart,opLen,delta,in(1)==null ? -1 : x86_64_v2.jumpop(_bop)&15); + } - @Override public String comment() { - return "L"+cproj(1)._nid+", L"+cproj(0)._nid; + @Override public void asm(CodeGen code, SB sb) { + if( in(1)!=null ) { // Never-node + String src = code.reg(in(1)); + if( src!="flags" ) sb.p(src).p(" "); + } else if( _bop=="!=" ) { + sb.p("never"); + return; + } + CFGNode prj = cproj(0).uctrlSkipEmpty(); // 0 is True is jump target + if( !prj.blockHead() ) prj = prj.cfg0(); + sb.p(label(prj)); } + + @Override public String comment() { return "L"+cproj(1)._nid; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java index 229c0151..e20fd1ec 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java @@ -1,18 +1,16 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; public class LeaX86 extends MachConcreteNode implements MachNode { final int _scale; - final long _offset; - LeaX86( Node add, Node base, Node idx, int scale, long offset ) { + final int _offset; + LeaX86( Node add, Node base, Node idx, int scale, int offset ) { super(add); - assert scale==1 || scale==2 || scale==4 || scale==8; + assert scale==0 || scale==1 || scale==2 || scale==3; _inputs.pop(); _inputs.pop(); _inputs.push(base); @@ -21,14 +19,12 @@ public class LeaX86 extends MachConcreteNode implements MachNode { _offset = offset; } - // Register mask allowed on input i. + @Override public String op() { return "lea"; } @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.RMASK; } - // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + X86.lea(enc,enc.reg(this),enc.reg(in(1)),enc.reg(in(2)),_offset,_scale); } // General form: "lea dst = base + 4*idx + 12" @@ -36,9 +32,7 @@ public class LeaX86 extends MachConcreteNode implements MachNode { 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); + sb.p(code.reg(in(2))).p("<<").p(_scale); if( _offset!=0 ) sb.p(" + #").p(_offset); } - - @Override public String op() { return "lea"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java index 893772a6..c5329e1d 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java @@ -1,20 +1,42 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.LoadNode; import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.type.TypeMemPtr; 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; } - - // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return x86_64_v2.MEM_MASK; } + @Override public void encoding( Encoding enc ) { + // REX.W + 8B /r MOV r64, r/m64 + // Zero extension for u8, u16 and u32 but sign extension i8, i16, i32 + // Use movsx and movzx + short dst = enc.reg(this ); + short ptr = enc.reg(ptr()); + short idx = enc.reg(idx()); + enc(enc, _declaredType, dst, ptr, idx, _off, _scale); + } + + static void enc( Encoding enc, Type decl, short dst, short ptr, short idx, int off, int scale ) { + // The chapter chooses the register bank and extension from its own type facts. + boolean xmm=dst>=x86_64_v2.XMM_OFFSET; + if( xmm && decl.log_size()==3 ) decl=TypeFloat.F64; + else if( !xmm && decl instanceof TypeFloat ) + decl=decl==TypeFloat.F32 ? TypeInteger.U32 : TypeInteger.BOT; + boolean signed=decl.isa(TypeInteger.I8) || decl==TypeInteger.I16 || decl==TypeInteger.I32; + X86.load(enc,decl.log_size(),signed,xmm, + xmm ? dst-x86_64_v2.XMM_OFFSET : dst,ptr,idx,off,scale); + } // General form: "ldN dst,[base + idx<<2 + 12]" diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java index 6d39a2b4..6c7ecbbc 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java @@ -1,19 +1,22 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; 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 ) { + X86.addMem(enc,enc.reg(val()),_imm,enc.reg(ptr()),enc.reg(idx()),_off,_scale); + } // General form: "add [base + idx<<2 + 12] += src" @Override public void asm(CodeGen code, SB sb) { asm_address(code,sb); @@ -21,8 +24,4 @@ public class MemAddX86 extends MemOpX86 { if( _imm != 1 && _imm != -1 ) sb.p(" += #").p(_imm); } else sb.p(" += ").p(code.reg(val())); } - - @Override public String op() { - return (_imm == 1 ? "inc" : (_imm == -1 ? "dec" : "add")) + _sz; - } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java index e74de677..fa3cdd13 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java @@ -3,11 +3,10 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; -import java.io.ByteArrayOutputStream; + import java.lang.StringBuilder; // Generic X86 memory operand base. @@ -16,7 +15,7 @@ import java.lang.StringBuilder; // 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 +// val/null - Value to store, as part of an op-to-mem or op-from-mem. Null for loads, or if an immediate is being used // Constants: // offset - offset added to base. Can be zero. // scale - scale on index; only 0,1,2,4,8 allowed, 0 is only when index is null @@ -25,13 +24,13 @@ 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())); + final char _sz; // Handy print name MemOpX86( Node op, MemOpNode mop, Node base, Node idx, int off, int scale, int imm ) { super(op,mop); assert base._type instanceof TypeMemPtr && !(base instanceof AddNode); assert (idx==null && scale==0) || (idx!=null && 0<= scale && scale<=3); - // Copy memory parts from eg the LoadNode over the opcode, e.g. an Add + // Copy memory parts from e.g. the LoadNode over the opcode, e.g. an Add if( op != mop ) { _inputs.set(0,mop.in(0)); // Control from mem op _inputs.set(1,mop.in(1)); // Memory from mem op @@ -43,6 +42,7 @@ public abstract class MemOpX86 extends MemOpNode implements MachNode { _off = off; _scale = scale; _imm = imm; + _sz = (char)('0'+(1<<_declaredType.log_size())); } // Store-based flavors have a value edge @@ -58,8 +58,6 @@ public abstract class MemOpX86 extends MemOpNode implements MachNode { return p.p(".").p(_name); } - @Override public boolean isMem() { return _type instanceof TypeMem; } - @Override public String label() { return op(); } @Override public Type compute() { throw Utils.TODO(); } @Override public Node idealize() { throw Utils.TODO(); } @@ -69,20 +67,19 @@ public abstract class MemOpX86 extends MemOpNode implements MachNode { if( i==1 ) return null; // Memory if( i==2 ) return x86_64_v2.RMASK; // base in GPR if( i==3 ) return x86_64_v2.RMASK; // index in GPR - if( i==4 ) return _sz >= '4' ? x86_64_v2.MEM_MASK : x86_64_v2.RMASK; // Narrow stores need GPRs - return null; // Trailing anti-dependence edges + if( i==4 ) return _sz >= '4' + ? x86_64_v2.MEM_MASK // value in GPR or XMM + : x86_64_v2.RMASK; // Bytes and shorts in GPR only + return null; // Anti-dependence } - @Override public int encoding(ByteArrayOutputStream bytes) { 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(_scale); + sb.p("*").p((1<<_scale)); } if( _off != 0 ) sb.p("+").p(_off); diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java index 9e996130..3371a8ba 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java @@ -1,34 +1,24 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeFloat; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; public class MulFX86 extends MachConcreteNode implements MachNode { MulFX86( Node mulf) { super(mulf); } - - // Register mask allowed on input i. + @Override public String op() { return "mulf"; } @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; } - // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return x86_64_v2.XMASK; } - // Output is same register as input#1 @Override public int twoAddress() { return 1; } - // Ok to swap arguments @Override public boolean commutes() { return true; } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + X86.sse(enc,0xF2,0x0F59,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET); } // General form: "mulf dst *= src" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2))); + sb.p(code.reg(this)).p(" *= ").p(code.reg(in(2))); } - - @Override public String op() { return "mulf"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java index b41a5720..2c1ce261 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java @@ -1,41 +1,30 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.MachConcreteNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.codegen.*; -public class MulIX86 extends MachConcreteNode implements MachNode { - final TypeInteger _ti; - MulIX86(Node mul, TypeInteger ti) {super(mul); _inputs.pop(); _ti = ti;} - - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { - // assert i==1; - return x86_64_v2.WMASK; } - // Register mask allowed as a result. 0 for no register. +public class MulIX86 extends MachConcreteNode implements MachNode { + final int _imm; + MulIX86( Node mul, int imm ) { + super(mul); + _inputs.pop(); // Pop the constant input + _imm = imm; // Record the constant + } + @Override public String op() { return "muli"; } + @Override public String glabel() { return "*"; } + @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - - - // Output is same register as input#1 + // Retain Chapter 21's allocation contract: output is also input #1. @Override public int twoAddress() { return 1; } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + // IMUL encodes a destination register, unlike ImmX86's opcode extension. + @Override public final void encoding( Encoding enc ) { + X86.imul(enc,enc.reg(this),enc.reg(in(1)),_imm); } - // General form - // General form: "muli dst * #imm" + // General form: "muli dst = src * #imm" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * #"); - _ti.print(sb); + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * #").p(_imm); } - - @Override public String op() { return "muli"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java index d880208e..cdc67ea8 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java @@ -1,33 +1,24 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; public class MulX86 extends MachConcreteNode implements MachNode { MulX86( Node mul ) { super(mul); } - - // Register mask allowed on input i. + @Override public String op() { return "mul"; } @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.RMASK; } - // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - // Output is same register as input#1 @Override public int twoAddress() { return 1; } - // Ok to swap arguments @Override public boolean commutes() { return true; } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + X86.reg(enc,0x0FAF,enc.reg(this),enc.reg(in(2))); } // General form: "mul dst *= src" @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2))); } - - @Override public String op() { return "mul"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java new file mode 100644 index 00000000..3d3798cd --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java @@ -0,0 +1,19 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; + +public class NegX86 extends MachConcreteNode implements MachNode { + NegX86(MinusNode not) { super(not); } + @Override public String op() { return "neg"; } + @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } + @Override public RegMask outregmap() { return x86_64_v2.RMASK; } + @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; } + + @Override public void encoding( Encoding enc ) { + X86.unary(enc,0xF7,3,enc.reg(this)); + } + @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java index f4803b2e..877ec224 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java @@ -1,29 +1,23 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import java.io.ByteArrayOutputStream; +import com.seaofnodes.simple.type.TypeFunPtr; public class NewX86 extends NewNode implements MachNode { // A pre-zeroed chunk of memory. NewX86( NewNode nnn ) { super(nnn); } - // Size and pointer result in standard calling convention; null for all the - // memory and ordering edges - @Override public RegMask regmap(int i) { return i == 2 ? x86_64_v2.RDI_MASK : null; } - @Override public RegMask outregmap(int i) { return i == 0 ? x86_64_v2.RAX_MASK : null; } - @Override public RegMask outregmap() { return null; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + enc.external(this,"calloc"); + X86.constant(enc,_arg2Reg,1); + X86.call(enc); } - - // General form: "alloc #bytes" + // General form: "alloc #bytes PC" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(size())); + sb.p("ldi rcx = #1\n"); + sb.p("call #calloc"); } - @Override public String op() { return "alloc"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java index a4b82de8..46227dd4 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java @@ -1,26 +1,21 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.LRG; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import java.io.ByteArrayOutputStream; public class NotX86 extends MachConcreteNode implements MachNode { - NotX86(NotNode not) {super(not);} + 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 int twoAddress( ) { return 0; } - - // NotNode(BoolNode) = Inverted BoolNode(1) - // NotNode(NotNode(BoolNode)) = BoolNode(2) - // anything else to test & setz(3) - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + assert !(in(1) instanceof NotNode); // Cleared out by peeps + assert !(in(1) instanceof BoolNode); + X86.not(enc,enc.reg(this),enc.reg(in(1))); } @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); } - @Override public String op() { return "not"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java index 949ac6c2..15f4824d 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java @@ -1,43 +1,11 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.MachConcreteNode; -import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; - -public class OrIX86 extends MachConcreteNode implements MachNode { - - final TypeInteger _ti; - OrIX86(Node or, TypeInteger ti) {super(or); _inputs.pop(); _ti = ti;} - - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { - //assert i==1; - return x86_64_v2.WMASK; } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - - - // Output is same register as input#1 - @Override public int twoAddress() { return 1; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form - // General form: "ori ori | #imm" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" | #"); - _ti.print(sb); - } +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/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java index 6d99fe3b..bc8f0ea8 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java @@ -1,35 +1,11 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.*; -import java.io.ByteArrayOutputStream; - -public class OrX86 extends MachConcreteNode implements MachNode{ - OrX86(Node or) { - super(or); - } - - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.WMASK; } - - @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - - // Output is same register as input#1 - @Override public int twoAddress() { return 1; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form - // General form: "or dst | src" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))); - } +import com.seaofnodes.simple.node.Node; +public class OrX86 extends RegX86 { + OrX86( Node add ) { super(add); } @Override public String op() { return "or"; } + @Override public String glabel() { return "|"; } + @Override int opcode() { return 0x0B; } + @Override public boolean commutes() { return true; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java index 69ebb74e..5cfdd804 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java @@ -1,27 +1,17 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Encoding; import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.node.ParmNode; -import java.io.ByteArrayOutputStream; public class ParmX86 extends ParmNode implements MachNode { final RegMask _rmask; ParmX86( ParmNode parm ) { super(parm); - _rmask = x86_64_v2.callInMask(fun().sig(),_idx); + _rmask = x86_64_v2.callInMask(fun().sig(),_idx,1/*RPC*/); } - - // Register mask allowed on input i. 0 for no register. - @Override public RegMask regmap(int i) { return _rmask; } - // Register mask allowed as a result. Calling convention register + @Override public RegMask regmap(int i) { return null; } @Override public RegMask outregmap() { return _rmask; } - - // Encoding is appended into the byte array. Returns size - @Override public int encoding(ByteArrayOutputStream bytes) { - return 0; - } + @Override public void encoding( Encoding enc ) { } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java index 7ac014e6..046c7b4c 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java @@ -1,23 +1,15 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.Encoding; import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.node.ProjNode; -import java.io.ByteArrayOutputStream; public class ProjX86 extends ProjNode implements MachNode { ProjX86( ProjNode p ) { super(p); } - - // Register mask allowed on input i. 0 for no register. @Override public RegMask regmap(int i) { return null; } - // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return ((MachNode)in(0)).outregmap(_idx); } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - return 0; - } + @Override public void encoding( Encoding enc ) {} } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java new file mode 100644 index 00000000..8dc749ee --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java @@ -0,0 +1,21 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; + +public 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 ) { + X86.reg(enc,opcode(),enc.reg(in(1)),enc.reg(in(2))); + } + // General form: "add dst += src" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" ").p(glabel()).p("= ").p(code.reg(in(2))); + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java index 433f64cc..e4467bb4 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java @@ -1,49 +1,15 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.FunNode; -import com.seaofnodes.simple.node.ReturnNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.type.TypeFloat; -import java.io.ByteArrayOutputStream; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; -// Return public class RetX86 extends ReturnNode implements MachNode { RetX86( ReturnNode ret, FunNode fun ) { super(ret, fun); fun.setRet(this); } - - // Correct Nodes outside the normal edges - @Override public void postSelect() { - FunNode fun = (FunNode)rpc().in(0); - _fun = fun; - fun.setRet(this); - } - - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { return x86_64_v2.retMask(_fun.sig(),i); } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return null; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - // Without imm form - // Near return - int beforeSize = bytes.size(); - bytes.write(0xC3); // opcode - - return bytes.size() - beforeSize; - } - - @Override public void asm(CodeGen code, SB sb) { - // 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>"; } + int opcode() { return 0xC1; } + int mod() { return 7; } - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { - // assert i==1; - return x86_64_v2.RMASK; } - // Register mask allowed as a result. 0 for no register. + @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - - // Output is same register as input#1 @Override public int twoAddress() { return 1; } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding(Encoding enc) { + X86.shift(enc,mod(),enc.reg(this),_imm); } - // General form - // General form: "sari dst << #imm" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> #"); - _ti.print(sb); + sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm); } - - @Override public String op() { return "sari"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java index ee7a2f56..50693034 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java @@ -1,41 +1,21 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; -import java.util.BitSet; -import java.lang.StringBuilder; +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.Node; - -public class SarX86 extends MachConcreteNode implements MachNode { - SarX86(Node sar) { super(sar); } - - // Register mask allowed on input i. - // This is the normal calling convention +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(); - } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - - // Output is same register as input#1 - @Override public int twoAddress() { return 1; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { + if (i == 1) return x86_64_v2.WMASK; + if (i == 2) return x86_64_v2.RCX_MASK; throw Utils.TODO(); } - - // General form - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" >> ").p(code.reg(in(1))); + @Override int opcode() { return 0xD3; } + @Override public void encoding( Encoding enc ) { + X86.unary(enc,opcode(),7,enc.reg(this)); } - - @Override public String op() { return "sar"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java index 53237538..591bf8da 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java @@ -1,33 +1,37 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; -// Compare immediate. Sets flags. // Corresponds to the x86 instruction "sete && setne". // Use result of comparison without jump. public class SetX86 extends MachConcreteNode implements MachNode { final String _bop; // One of <,<=,== + final boolean _unsigned; // Constructor expects input is an X86 and not an Ideal node. - SetX86( Node cmp, String bop ) { + SetX86( Node cmp, String bop, boolean unsigned ) { super(cmp); _inputs.setLen(1); // Pop the cmp inputs // Replace with the matched cmp _inputs.push(cmp); _bop = bop; + _unsigned = unsigned; } - + @Override public String op() { return "set"+_bop; } @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.FLAGS_MASK; } @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + int op=switch (_bop) { + case "==" -> 0x94; // SETE + case "<" -> _unsigned ? 0x92 : 0x9C; // SETB /SETL + case "<=" -> _unsigned ? 0x96 : 0x9E; // SETBE/SETLE + case ">=" -> _unsigned ? 0x93 : 0x9D; // SETAE/SETGE + default -> throw Utils.TODO(); + }; + X86.set(enc,op&15,enc.reg(this)); } @Override public void asm(CodeGen code, SB sb) { @@ -35,7 +39,4 @@ public class SetX86 extends MachConcreteNode implements MachNode { String src = code.reg(in(1)); if( src!="flags" ) sb.p(" = ").p(src); } - - @Override public String op() { return "set"+_bop; } - } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java index c18007cd..f31832e6 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java @@ -1,40 +1,33 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.node.Node; + import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; -import java.util.BitSet; -public class ShlIX86 extends MachConcreteNode implements MachNode{ - final TypeInteger _ti; - ShlIX86(Node shl, TypeInteger ti) {super(shl); _inputs.pop(); _ti = ti;} +public class ShlIX86 extends MachConcreteNode implements MachNode { + final int _imm; + ShlIX86( Node shli, int imm ) { + super(shli); + assert x86_64_v2.imm8(imm); + _inputs.pop(); // Pop constant input off + _imm = imm; + } + public int opcode() { return 0xC1; } + public int mod() { return 4; } - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { - //assert i==1; - return x86_64_v2.RMASK; } - // Register mask allowed as a result. 0 for no register. + @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - - // Output is same register as input#1 @Override public int twoAddress() { return 1; } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public String op() { return "shli"; } + @Override public String glabel() { return "<<"; } + @Override public void encoding(Encoding enc) { + X86.shift(enc,mod(),enc.reg(this),_imm); } - - // General form - // General form: "shli dst << #imm" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" << #"); - _ti.print(sb); + sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm); } - - @Override public String op() { return "shli"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java index a3de5347..08c4db25 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java @@ -1,39 +1,21 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.*; -import java.io.ByteArrayOutputStream; +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.Node; -public class ShlX86 extends MachConcreteNode implements MachNode { - ShlX86(Node shl) { super(shl); }; - - // Register mask allowed on input i. - // This is the normal calling convention - - // CL register must be used for input(2) +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; + if (i == 1) return x86_64_v2.WMASK; + if (i == 2) return x86_64_v2.RCX_MASK; throw Utils.TODO(); } - - @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - - // Output is same register as input#1 - @Override public int twoAddress() { return 1; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form - // General form: "shl dst << src" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" << ").p(code.reg(in(1))); + @Override int opcode() { return 0xD3; } + @Override public final void encoding( Encoding enc ) { + X86.unary(enc,opcode(),4,enc.reg(this)); } - - @Override public String op() { return "shl"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java index d17ee734..218542f3 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java @@ -1,41 +1,28 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.node.Node; + import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; -import java.util.BitSet; -import java.lang.StringBuilder; -public class ShrIX86 extends MachConcreteNode implements MachNode{ - final TypeInteger _ti; - ShrIX86(Node shr, TypeInteger ti) {super(shr); _inputs.pop(); _ti = ti;} +public class ShrIX86 extends MachConcreteNode implements MachNode { + final int _imm; + ShrIX86( Node shri, int imm ) { super(shri); assert x86_64_v2.imm8(imm); _inputs.pop(); _imm = imm; } + @Override public String op() { return "shri"; } + @Override public String glabel() { return ">>>"; } + int opcode() { return 0xC1; } + int mod() { return 5; } - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { - //assert i==1; - return x86_64_v2.RMASK; } - // Register mask allowed as a result. 0 for no register. + @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - - // Output is same register as input#1 @Override public int twoAddress() { return 1; } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding(Encoding enc) { + X86.shift(enc,mod(),enc.reg(this),_imm); } - - // General form - // General form: "shri dst >>> #imm" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >>> #"); - _ti.print(sb); + sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm); } - - @Override public String op() { return "shri"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java index 757d9b8e..c72f21e2 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java @@ -1,40 +1,21 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.*; -import java.io.ByteArrayOutputStream; +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.Node; -public class ShrX86 extends MachConcreteNode implements MachNode { - ShrX86(Node shr) { - super(shr); - }; - // Register mask allowed on input i. - // This is the normal calling convention - - // CL register must be used for input(2) +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 public RegMask outregmap() { return x86_64_v2.WMASK; } - - // Output is same register as input#1 - @Override public int twoAddress() { return 1; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { + if (i == 1) return x86_64_v2.WMASK; + if (i == 2) return x86_64_v2.RCX_MASK; throw Utils.TODO(); } - - // General form - // General form: "shr dst >>> src" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" >>> ").p(code.reg(in(1))); + @Override int opcode() { return 0xD3; } + @Override public final void encoding( Encoding enc ) { + X86.unary(enc,opcode(),5,enc.reg(this)); } - - @Override public String op() { return "shr"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java index ea283a87..5cf668d1 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java @@ -1,21 +1,54 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.FunNode; import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.node.SplitNode; -import java.io.ByteArrayOutputStream; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; +import com.seaofnodes.simple.type.TypeInteger; public class SplitX86 extends SplitNode { SplitX86( String kind, byte round ) { super(kind,round, new Node[2]); } - - // Register mask allowed on input i. + @Override public String op() { return "mov"; } @Override public RegMask regmap(int i) { return x86_64_v2.SPLIT_MASK; } - // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return x86_64_v2.SPLIT_MASK; } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + // 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 ) { + short dst=enc.reg(this), src=enc.reg(in(1)); + if( dst==x86_64_v2.FLAGS ) { X86.flags(enc,src,true); return; } + if( src==x86_64_v2.FLAGS ) { X86.flags(enc,dst,false); return; } + boolean dstX=dst>=x86_64_v2.XMM_OFFSET, srcX=src>=x86_64_v2.XMM_OFFSET; + // Stack spills + if( dst >= x86_64_v2.MAX_REG ) { + int off = enc._fun.computeStackOffset(enc._code,dst); + if( src >= x86_64_v2.MAX_REG ) { + // Rare stack-stack move. push [RSP+soff]; pop [RSP+doff] + int soff = enc._fun.computeStackOffset(enc._code,src); + X86.stackCopy(enc,soff,off); + return; + } + StoreX86.encVal(enc, srcX ? TypeFloat.F64 : TypeInteger.BOT, (short)x86_64_v2.RSP, (short)-1/*index*/, src, off, 0); + return; + } + if( src >= x86_64_v2.MAX_REG ) { + int off = enc._fun.computeStackOffset(enc._code,src); + LoadX86.enc(enc, dstX ? TypeFloat.F64 : TypeInteger.BOT, dst, (short)x86_64_v2.RSP, (short)-1, off, 0); + return; + } + + X86.move(enc,dstX,srcX,dstX ? dst-x86_64_v2.XMM_OFFSET : dst, + srcX ? src-x86_64_v2.XMM_OFFSET : src); + } + + // General form: "mov dst = src" + @Override public void asm(CodeGen code, SB sb) { + FunNode fun = code._encoding==null ? null : code._encoding._fun; + sb.p(code.reg(this,fun)).p(" = ").p(code.reg(in(1),fun)); } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java index 08d481fa..4796c0fe 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java @@ -1,20 +1,21 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.ConstantNode; import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.node.StoreNode; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; public class StoreX86 extends MemOpX86 { StoreX86( StoreNode st, Node base, Node idx, int off, int scale, int imm, Node val ) { super(st,st, base, idx, off, scale, imm, val); } - @Override public String op() { return "st"+_sz; } - @Override protected ExprPrinter _printMach(ExprPrinter p) { Node val = val(); p.p(".").p(_name).p("="); @@ -22,9 +23,21 @@ public class StoreX86 extends MemOpX86 { else p.n(val); return p.p(";"); } - // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return null; } + @Override public void encoding( Encoding enc ) { + short ptr=enc.reg(ptr()), idx=enc.reg(idx()), src=enc.reg(val()); + if( src==-1 ) X86.storeImm(enc,_declaredType.log_size(),_imm,ptr,idx,_off,_scale); + else encVal(enc,_declaredType,ptr,idx,src,_off,_scale); + } + + // Non-immediate encoding + static void encVal( Encoding enc, Type decl, short ptr, short idx, short src, int off, int scale ) { + boolean xmm=src>=x86_64_v2.XMM_OFFSET; + X86.store(enc,decl.log_size(),xmm, + xmm ? src-x86_64_v2.XMM_OFFSET : src,ptr,idx,off,scale); + } + // General form: "stN [base + idx<<2 + 12],val" @Override public void asm(CodeGen code, SB sb) { diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java index ceaf0636..6a008551 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java @@ -1,31 +1,23 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import java.io.ByteArrayOutputStream; public class SubFX86 extends MachConcreteNode implements MachNode { SubFX86( Node subf) { super(subf); } - - - // Register mask allowed on input i. + @Override public String op() { return "subf"; } @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; } - // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return x86_64_v2.XMASK; } - // Output is same register as input#1 @Override public int twoAddress() { return 1; } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + X86.sse(enc,0xF2,0x0F5C,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET); } // General form: "subf dst -= src" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - ").p(code.reg(in(2))); + sb.p(code.reg(this)).p(" -= ").p(code.reg(in(2))); } - - @Override public String op() { return "subf"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java index da44a838..8da34bf5 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java @@ -1,31 +1,10 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; - -public class SubX86 extends MachConcreteNode implements MachNode { - SubX86( Node sub ) { super(sub); } - - // Register mask allowed on input i. - @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.RMASK; } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - // Output is same register as input#1 - @Override public int twoAddress() { return 1; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form: "sub 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))); - } +import com.seaofnodes.simple.node.Node; +public class SubX86 extends RegX86 { + SubX86( Node add ) { super(add); } @Override public String op() { return "sub"; } + @Override public String glabel() { return "-"; } + @Override int opcode() { return 0x2B; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java index 2b4de2ca..d0c6157c 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java @@ -1,29 +1,41 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.ConstantNode; -import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.cpus.riscv.riscv; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeFunPtr; -import java.io.ByteArrayOutputStream; - // Function constants -public class TFPX86 extends ConstantNode implements MachNode { +public class TFPX86 extends ConstantNode implements MachNode, RIPRelSize { TFPX86( ConstantNode con ) { super(con); } - @Override public String op() { return _con == Type.NIL ? "xor" : "ldx"; } - // Register mask allowed on input i. 0 for no register. + @Override public String op() { + return _con == Type.NIL ? "xor" : "ldx"; + } + @Override public boolean isClone() { return true; } + @Override public Node copy() { return new TFPX86(this); } @Override public RegMask regmap(int i) { return null; } - // General int registers @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - @Override public boolean isClone() { return true; } - @Override public TFPX86 copy() { return new TFPX86(this); } + // Zero-set uses XOR kills flags + @Override public RegMask killmap() { + return _con == Type.NIL ? x86_64_v2.FLAGS_MASK : null; + } + + @Override public void encoding( Encoding enc ) { + if( _con==Type.NIL ) { X86.constant(enc,enc.reg(this),0); return; } + enc.relo(this); + X86.rip(enc,0,0x8D,true,enc.reg(this)); + } + + @Override public byte encSize(int delta) { + return 7; + } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + // 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, @@ -35,6 +47,6 @@ public class TFPX86 extends ConstantNode implements MachNode { if( _con == Type.NIL ) sb.p(reg).p(",").p(reg); else - _con.print(sb.p(reg).p(" #")); + _con.print(sb.p(reg).p(" = #")); } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java new file mode 100644 index 00000000..d991e40a --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java @@ -0,0 +1,40 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.isa.X86; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; + +// unconditional jump +public class UJmpX86 extends CFGNode implements MachNode, RIPRelSize { + UJmpX86() { } + @Override public String op() { return "jmp"; } + @Override public String label() { return op(); } + @Override protected String format() { return "jmp "; } + @Override public RegMask regmap(int i) {return null; } + @Override public RegMask outregmap() { return null; } + @Override public Type compute() { throw Utils.TODO(); } + @Override public Node idealize() { throw Utils.TODO(); } + @Override public void encoding( Encoding enc ) { + enc.jump(this,uctrl()); + X86.branch(enc,-1); + } + + // Delta is from opcode start, but X86 measures from the end of the 2-byte encoding + @Override public byte encSize(int delta) { + return X86.branchSize(delta,false); + } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + X86.patchBranch(enc.bits(),opStart,opLen,delta,-1); + } + + @Override public void asm(CodeGen code, SB sb) { + CFGNode target = uctrl(); + //assert target.nOuts() > 1; // Should optimize jmp to empty targets + sb.p(label(target)); + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java index 47c6be73..2d989a50 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java @@ -1,41 +1,11 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.MachConcreteNode; -import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; - -public class XorIX86 extends MachConcreteNode implements MachNode { - - final TypeInteger _ti; - XorIX86(Node xor, TypeInteger ti) {super(xor); _inputs.pop(); _ti = ti;} - - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.WMASK; } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - - - // Output is same register as input#1 - @Override public int twoAddress() { return 1; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form - // General form: "ori xori ^ #imm" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ^ #"); - _ti.print(sb); - } +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/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java index 3322d951..86703903 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java @@ -1,36 +1,11 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.LRG; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.MachConcreteNode; -import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; - -public class XorX86 extends MachConcreteNode implements MachNode{ - XorX86(Node xor) {super(xor);} - - @Override public RegMask regmap(int i) { return x86_64_v2.WMASK; } - @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - - // Output is same register as input#1 - @Override public int twoAddress() { return 1; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form - // General form: "xor ^ reg - @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))); - } +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/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java index c66aed9f..1b7cf775 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -1,26 +1,37 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.LRG; -import com.seaofnodes.simple.codegen.Machine; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; 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; } - static final int RAX = 0, RCX = 1, RDX = 2, RBX = 3, RSP = 4, RBP = 5, RSI = 6, RDI = 7; - static final int R08 = 8, R09 = 9, R10 = 10, R11 = 11, R12 = 12, R13 = 13, R14 = 14, R15 = 15; + 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; - static final int XMM0 = 16, XMM1 = 17, XMM2 = 18, XMM3 = 19, XMM4 = 20, XMM5 = 21, XMM6 = 22, XMM7 = 23; - static final int XMM8 = 24, XMM9 = 25, XMM10 = 26, XMM11 = 27, XMM12 = 28, XMM13 = 29, XMM14 = 30, XMM15 = 31; - static final int FLAGS = 32; - static final int MAX_REG = 33; - static final int RPC = 33; + public static final int XMM0 = 16, XMM1 = 17, XMM2 = 18, XMM3 = 19, XMM4 = 20, XMM5 = 21, XMM6 = 22, XMM7 = 23; + public static final int XMM8 = 24, XMM9 = 25, XMM10 = 26, XMM11 = 27, XMM12 = 28, XMM13 = 29, XMM14 = 30, XMM15 = 31; + public static final int FLAGS = 32; + public static final int MAX_REG = 33; + public static final int RPC = 33; + // Human-readable name for a register number, e.g. "RAX". + public static final String[] REGS = new String[]{ + "rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi", + "r8" , "r9" , "r10", "r11", "r12", "r13", "r14", "r15", + "xmm0", "xmm1", "xmm2" , "xmm3" , "xmm4" , "xmm5" , "xmm6" , "xmm7" , + "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15", + "flags", + }; + // Hard crash for bad register number, fix yer bugs! + @Override public String[] regs() { return REGS; } + + public static int XMM_OFFSET = 16; // General purpose register mask: pointers and ints, not floats static final long RD_BITS = 0b1111111111111111L; // All the GPRs static RegMask RMASK = new RegMask(RD_BITS); @@ -29,49 +40,138 @@ public class x86_64_v2 extends Machine { static RegMask WMASK = new RegMask(WR_BITS); // Xmm register mask static final long FP_BITS = 0b1111111111111111L << XMM0; // All the XMMs - static final RegMask XMASK = new RegMask(FP_BITS); - static final RegMask FLAGS_MASK = new RegMask(FLAGS); - static final RegMask RPC_MASK = new RegMask(RPC); + 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"); + }; + } - // Human-readable name for a register number, e.g. "RAX". - // Hard crash for bad register number, fix yer bugs! - 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","[rsp-4]", + // Limit of float args passed in registers + static RegMask[] XMMS8 = new RegMask[]{ + new RegMask(XMM0), new RegMask(XMM1), new RegMask(XMM2), new RegMask(XMM3), + new RegMask(XMM4), new RegMask(XMM5), new RegMask(XMM6), new RegMask(XMM7), }; - @Override public String reg( int reg ) { - return reg < REGS.length ? REGS[reg] : "[rsp+"+(reg-REGS.length)*4+"]"; + + // Map from function signature and argument index to register. + // Used to set input registers to CallNodes, and ParmNode outputs. + @Override public RegMask callArgMask( TypeFunPtr tfp, int idx, int maxArgSlot ) { return callInMask(tfp,idx,maxArgSlot); } + static RegMask callInMask( TypeFunPtr tfp, int idx, int maxArgSlot ) { + if( idx==0 ) return RPC_MASK; + if( idx==1 ) return null; + return switch( CodeGen.CODE._callingConv ) { + case "SystemV" -> callSys5 (tfp,idx,maxArgSlot); + case "Win64" -> callWin64(tfp,idx,maxArgSlot); + default -> throw Utils.TODO(); + }; } - // WIN64(param passing) - static RegMask[] CALLINMASK_WIN64 = new RegMask[] { + // Maximum stack args used by this signature + @Override public short maxArgSlot( TypeFunPtr tfp ) { + return switch( CodeGen.CODE._callingConv ) { + case "SystemV" -> maxArgSlotSys5 (tfp); + case "Win64" -> maxArgSlotWin64(tfp); + default -> throw Utils.TODO(); + }; + } + + // Win64 - max *4* args in registers of all kinds; after that on stack. + // Stack args land in increasing memory order with empty/mirror slots for + // every register. + // + // foo( int i0, flt f1, int i2, flt f3, int i4, flt f5, int i6, ... ) + // + // -- Prior Frame -- + // i6 + // f5 + // i4 + // empty mirror, XMM3= f3 + // empty mirror, R08 = i2 + // empty mirror, XMM1= f1 + // empty mirror, RCX = i0 + // -- Caller Frame; 16b align -- + // RPC + // PAD/ALIGN + // -- Callee Frame; 16b align -- + static RegMask[] WIN64_CALL = new RegMask[] { RCX_MASK, RDX_MASK, R08_MASK, R09_MASK, }; + static RegMask callWin64(TypeFunPtr tfp, int idx, int maxArgSlot ) { + // idx 2,3,4,5 passed in registers, with stack slot mirrors. + // idx >= 6 passed on stack, starting at slot#1 (#0 reserved for RPC). + if( idx >= 6 ) + return new RegMask(MAX_REG+maxArgSlot+(idx-2)); + return tfp.arg(idx-2) instanceof TypeFloat + ? XMMS8 [idx-2] + : WIN64_CALL[idx-2]; + } + static short maxArgSlotWin64(TypeFunPtr tfp) { + return (short)tfp.nargs(); + } + + // Sys5: max 6 GPRs and 8 FPRS filled first. Extra args land in increasing + // memory order as needed - no mirror space. + // + // foo( int i0, flt f1, int i2, flt f3, int i4, flt f5, int i6, ... ) + // RDI =i0, RSI =i2, RDX =i4, RCX =i6, R08 =i8, R09 =i10 + // XMM0=f1, XMM1=f3, XMM2=f5, XMM3=f7, XMM4=f9, XMM5=f11, XMM6=f13, XMM7=f15 + // + // -- Prior Frame -- + // f18 + // i18 + // f17 + // i16 + // i14 + // i12 + // -- Caller Frame; 16b align -- + // RPC + // PAD/ALIGN + // -- Callee Frame; 16b align -- // SystemV(param passing) - static RegMask[] CALLINMASK_SYSTEMV = new RegMask[] { + static RegMask[] SYS5_CALL = new RegMask[] { RDI_MASK, RSI_MASK, RDX_MASK, @@ -80,289 +180,245 @@ public class x86_64_v2 extends Machine { 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( TypeFunPtr 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]; + static RegMask callSys5(TypeFunPtr tfp, int idx, int maxArgSlot ) { + // First 6 integers passed in registers: rdi,rsi,rdx,rcx,r08,r09 + // First 8 floats passed in registers: xmm0-xmm7 + int icnt=0, fcnt=0; // Count of ints, floats + for( int i=2; i SYSTEM5_CALLER_SAVE_MASK; - case "Win64" -> WIN64_CALLER_SAVE_MASK; - default -> throw new IllegalArgumentException("Unknown calling convention: "+CodeGen.CODE._callingConv); + @Override public long callerSave() { + return switch (CodeGen.CODE._callingConv) { + case "SystemV" -> SYSTEM5_CALLER_SAVE; + case "Win64" -> WIN64_CALLER_SAVE; + default -> throw new IllegalArgumentException("Unknown calling convention: " + CodeGen.CODE._callingConv); }; } - @Override public 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); } + @Override public SplitNode split(String kind, byte round, LRG lrg) { + return new SplitX86(kind, round); + } // Return a MachNode unconditional branch @Override public CFGNode jump() { - throw Utils.TODO(); - } - - // Break an infinite loop - @Override public IfNode never( CFGNode ctrl ) { - throw Utils.TODO(); + return new UJmpX86(); } // Instruction selection - @Override public Node instSelect( Node n ) { - return switch( n ) { - case AddFNode addf -> addf(addf); - case AddNode add -> add(add); - case AndNode and -> and(and); - case BoolNode bool -> cmp(bool); - case 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 OrNode or -> or(or); - case ParmNode parm -> new ParmX86(parm); + @Override + public Node instSelect(Node n) { + return switch (n) { + case AddFNode addf -> addf(addf); + case AddNode add -> add(add); + case AndNode and -> and(and); + case BoolNode bool -> cmp(bool); + case CProjNode c -> new CProjNode(c); + case CallEndNode cend -> new CallEndX86(cend); + case CallNode call -> call(call); + case CastNode cast -> new CastX86(cast); + case ConstantNode con -> con(con); + case DivFNode divf -> new DivFX86(divf); + case DivNode div -> new DivX86(div); + case FunNode fun -> new FunX86(fun); + case IfNode iff -> jmp(iff); + case LoadNode ld -> ld(ld); + case MemMergeNode mem -> new MemMergeNode(mem); + case MinusNode neg -> new NegX86(neg); + case MulFNode mulf -> new MulFX86(mulf); + case MulNode mul -> mul(mul); + case NewNode nnn -> new NewX86(nnn); + case NotNode not -> new NotX86(not); + case OrNode or -> or(or); + case ParmNode parm -> new ParmX86(parm); case BulkMemPhiNode phi -> new BulkMemPhiNode(phi); case MemPhiNode phi -> new MemPhiNode(phi); - case PhiNode phi -> new PhiNode(phi); - case ProjNode prj -> prj(prj); - case ReadOnlyNode read -> new 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 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 ToIntegerNode cvt -> new F8ToIX86(cvt); - case XorNode xor -> xor(xor); + case XCtrlNode x -> new ConstantNode(Type.XCONTROL); + case XorNode xor -> xor(xor); - case LoopNode loop -> new LoopNode(loop); - case RegionNode region-> new RegionNode(region); + case LoopNode loop -> new LoopNode(loop); + case RegionNode region -> new RegionNode(region); default -> throw Utils.TODO(); }; } + public static boolean imm8( long imm ) { return -128 <= imm && imm <= 127; } + + public static boolean imm32( long imm ) { return (int)imm==imm; } // Attempt a full LEA-style break down. - private Node add( AddNode add ) { + private Node add(AddNode add) { Node lhs = add.in(1); Node rhs = add.in(2); - // The other addend must occupy the result register, even if constant. - if( lhs instanceof LoadNode ld && ld.nOuts()==1 ) - return new AddMemX86(add,address(ld),ld.ptr(),idx,off,scale, 0,rhs); + if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && ld._declaredType.log_size() >= 3) + return new AddMemX86(add, address(ld), ld.ptr(), idx, off, scale, 0, rhs); - if( rhs instanceof LoadNode ld && ld.nOuts()==1 ) + if( rhs instanceof LoadNode ld && ld.nOuts()==1 && ld._declaredType.log_size()>=3 ) return new AddMemX86(add,address(ld),ld.ptr(),idx,off,scale, 0,lhs); // Attempt a full LEA-style break down. // Returns one of AddX86, AddIX86, LeaX86, or LHS - if( rhs instanceof ConstantNode off && off._con instanceof TypeInteger toff ) { + if( rhs instanceof ConstantNode off2 && off2._con instanceof TypeInteger toff ) { + long imm = toff.value(); + assert imm!=0; // Folded in peeps + if( (int)imm != imm ) // Full 64bit immediate + return new AddX86(add); + // Now imm <= 32bits if( lhs instanceof AddNode ladd ) // ((base + (idx << scale)) + off) - return _lea(add,ladd.in(1),ladd.in(2),toff.value()); + 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,toff.value()); + return _lea(add, null, shift, (int)imm); // lhs + rhs1 - if( toff.value()==0 ) return add; - return new AddIX86(add, toff); + return new AddIX86(add, (int)imm); } - return _lea(add,lhs,rhs,0); + 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)); + 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 +// 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, long off ) { - int scale = 1; - if( base instanceof ShlNode && !(idx instanceof ShlNode) ) throw Utils.TODO(); // Bug in canonicalization, should on RHS + private Node _lea(Node add, Node base, Node idx, int off) { + int scale = 0; + if( base instanceof ShlNode && !(idx instanceof ShlNode) ) + throw Utils.TODO(); // Bug in canonicalization, should on RHS if( idx instanceof ShlNode shift && shift.in(2) instanceof ConstantNode shfcon && shfcon._con instanceof TypeInteger tscale && 0 <= tscale.value() && tscale.value() <= 3 ) { idx = shift.in(1); - scale = 1 << ((int)tscale.value()); + scale = ((int) tscale.value()); } // (base + idx) + off - return off==0 && scale==1 + return off == 0 && scale == 0 ? new AddX86(add) - : new LeaX86(add,base,idx,scale,off); + : new LeaX86(add, base, idx, scale, off); } private Node and(AndNode and) { - if( and.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti ) - return new AndIX86(and, ti); + if( and.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) ) + return new AndIX86(and, (int)ti.value()); return new AndX86(and); } private Node call(CallNode call) { - if( call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp ) - return new CallX86(call, tfp); - return new CallRX86( call ); + return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp + ? new CallX86(call, tfp) + : new CallRX86(call); } // Because X86 flags, a normal ideal Bool is 2 X86 ops: a "cmp" and at "setz". // Ideal If reading from a setz will skip it and use the "cmp" instead. + private static boolean swap, unsigned; private Node cmp( BoolNode bool ) { + swap = unsigned = false; Node cmp = _cmp(bool); - return new SetX86(cmp,bool.op()); + return new SetX86(cmp, swap ? IfNode.swap(bool.op()) : bool.op(), unsigned); } - private Node _cmp( BoolNode bool ) { + + private Node _cmp(BoolNode bool) { // Float variant - if( bool.isFloat() ) + if( bool.isFloat() ) { + unsigned = true; return new CmpFX86(bool); - + } Node lhs = bool.in(1); Node rhs = bool.in(2); - 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); + // Since cmp does not record a BOP, SetX/Jmp need to know if the CMP is swapped. + + // Vs memory + if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && rhs._type.isa(ld._declaredType) ) + return new CmpMemX86(bool, address(ld), ld.ptr(), idx, off, scale, imm(rhs), val, false); + + // Operands swap in the encoding directly, no need for Set/Jmp to swap `bop` + if( rhs instanceof LoadNode ld && ld.nOuts() == 1 && lhs._type.isa(ld._declaredType)) + return new CmpMemX86(bool, address(ld), ld.ptr(), idx, off, scale, imm(lhs), val, true ); // Vs immediate - if( rhs instanceof ConstantNode con && con._con instanceof TypeInteger ti ) - return new CmpIX86(bool, ti); + if( rhs instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) ) + return new CmpIX86(bool, (int)ti.value(), false); + + // Operand swap compare; Set and Jmp need to swap `bop` + if( lhs instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) ) + return new CmpIX86(bool, (int)ti.value(), swap=true); + // x vs y return new CmpX86(bool); } private Node con( ConstantNode con ) { - if( !con._con.isConstant() ) return new ConstantNode( con ); // Default unknown caller inputs - return switch( con._con ) { - case TypeInteger ti -> new IntX86(con); - case TypeFloat tf -> new FltX86(con); - case TypeFunPtr tfp -> new TFPX86(con); - case TypeMemPtr tmp -> throw Utils.TODO(); - case TypeNil tn -> throw Utils.TODO(); - // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code. - case Type t -> t==Type.NIL ? new IntX86(con) : new ConstantNode(con); + if(!con._con.isConstant()) return new ConstantNode(con); // Default unknown caller inputs + return switch (con._con) { + case TypeInteger ti -> new IntX86(con); + case TypeFloat tf -> new FltX86(con); + case TypeFunPtr tfp -> new TFPX86(con); + case TypeMemPtr tmp -> throw Utils.TODO(); + case TypeNil tn -> throw Utils.TODO(); + // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code. + case Type t -> t == Type.NIL ? new IntX86(con) : new ConstantNode(con); }; } @@ -371,28 +427,30 @@ public class x86_64_v2 extends Machine { return new I2f8X86(tfn); } - private Node jmp( IfNode iff ) { + 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(TypeInteger.ZERO))); - return new JmpX86(iff, ((BoolNode)iff.in(1)).op()); + String op = "!="; + if( iff.in(1) instanceof BoolNode bool ) op = swap ? IfNode.swap(bool.op()) : bool.op(); + else if( iff.in(1)==null ) op = "=="; // Never-node cutout + else iff.setDef(1, new BoolNode.NE(iff.in(1), new ConstantNode(TypeInteger.ZERO))); + return new JmpX86(iff, op); } - private Node ld( LoadNode ld ) { - return new LoadX86(address(ld),ld.ptr(),idx,off,scale); + private Node ld(LoadNode ld) { + return new LoadX86(address(ld), ld.ptr(), idx, off, scale); } private Node mul(MulNode mul) { - return mul.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti - ? new MulIX86(mul, ti) - : new MulX86(mul); + if( mul.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) ) + return new MulIX86(mul, (int)ti.value()); + return new MulX86(mul); } private Node or(OrNode or) { - if( or.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) - return new OrIX86(or, ti); + if( or.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) ) + return new OrIX86(or, (int)ti.value()); return new OrX86(or); } @@ -400,21 +458,21 @@ public class x86_64_v2 extends Machine { return new ProjX86(prj); } - private Node sar( SarNode sar ) { - if( sar.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) - return new SarIX86(sar, ti); + private Node sar(SarNode sar) { + if( sar.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm8(ti.value())) + return new SarIX86(sar, (int)(ti.value() & 0xff) ); return new SarX86(sar); } - private Node shl( ShlNode shl ) { - if( shl.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti ) - return new ShlIX86(shl, ti); + private Node shl(ShlNode shl) { + if( shl.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm8(ti.value())) + return new ShlIX86(shl, (int)(ti.value() & 0xff) ); return new ShlX86(shl); } - private Node shr(ShrNode shr) { - if( shr.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti ) - return new ShrIX86(shr, ti); + private Node shr( ShrNode shr ) { + if( shr.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm8(ti.value()) ) + return new ShrIX86(shr, (int)(ti.value() & 0xff) ); return new ShrX86(shr); } @@ -422,58 +480,58 @@ public class x86_64_v2 extends Machine { // 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(); - } + if( op.in(1) instanceof LoadNode ld && stld_match(st,ld) ) + return new MemAddX86(address(st), st.ptr(), idx, off, scale, imm(op.in(2)), val); + if( op.in(2) instanceof LoadNode ld && stld_match(st,ld) ) + return new MemAddX86(address(st), st.ptr(), idx, off, scale, imm(op.in(1)), val); } - - return new StoreX86(address(st),st.ptr(),idx,off,scale,imm(st.val()),val); + return new StoreX86(address(st), st.ptr(), idx, off, scale, imm(st.val()), val); } + private static boolean stld_match(StoreNode st, LoadNode ld ) { + return + ld.in(0) == st.in(0) && + ld.mem() == st.mem() && + ld.ptr() == st.ptr() && + ld.off() == st.off(); + } + - private Node sub( SubNode sub ) { + private Node sub (SubNode sub ){ return sub.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti - ? new AddIX86(sub, TypeInteger.constant(-ti.value())) + ? new AddIX86(sub, (int)-ti.value()) : new SubX86(sub); } - private Node xor(XorNode xor) { - if(xor.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) - return new XorIX86(xor, ti); - throw Utils.TODO(); + private Node xor (XorNode xor){ + if( xor.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) ) + return new XorIX86(xor, (int)ti.value()); + return new XorX86(xor); } - // Gather X86 addressing mode bits prior to constructing. This is a // builder pattern, but saving the bits in a *local* *global* here to keep // mess contained. private static int off, scale, imm; private static Node idx, val; - private N address( N mop ) { + 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); + if(base instanceof ReadOnlyNode read) base = read.in(1); assert !(base instanceof AddNode) && base._type instanceof TypeMemPtr; // Base ptr always, not some derived - if( mop.off() instanceof AddNode add && add.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti ) { - off = (int)ti.value(); + if(mop.off() instanceof AddNode add && add.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) { + off = (int) ti.value(); assert off == ti.value(); // In 32-bit range idx = add.in(1); - if( idx instanceof ShlNode shift && shift.in(2) instanceof ConstantNode shfcon && - shfcon._con instanceof TypeInteger tscale && 0 <= tscale.value() && tscale.value() <= 3 ) { + if(idx instanceof ShlNode shift && shift.in(2) instanceof ConstantNode shfcon && + shfcon._con instanceof TypeInteger tscale && 0 <= tscale.value() && tscale.value() <= 3) { idx = shift.in(1); - scale = (int)tscale.value(); + scale = (int) tscale.value(); } } else { - if( mop.off() instanceof ConstantNode con && con._con instanceof TypeInteger ti ) { - off = (int)ti.value(); + if(mop.off() instanceof ConstantNode con && con._con instanceof TypeInteger ti) { + off = (int) ti.value(); assert off == ti.value(); // In 32-bit range } else { idx = mop.off(); @@ -483,10 +541,10 @@ public class x86_64_v2 extends Machine { } private int imm( Node xval ) { - assert val==null && imm==0; - if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti ) { + assert val == null && imm == 0; + if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti) { val = null; - imm = (int)ti.value(); + imm = (int) ti.value(); assert imm == ti.value(); // In 32-bit range } else { val = xval; diff --git a/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java b/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java index a4f768c5..09ebd51b 100644 --- a/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java +++ b/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java @@ -8,13 +8,16 @@ import com.seaofnodes.simple.SB; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; import java.util.ArrayList; +import com.seaofnodes.simple.codegen.Encoding; /** Machine-specific facts; traversal, columns and byte display live in print/. */ public final class ASMPrinter extends AssemblyAdapter { private final CodeGen _code; + private final Encoding _enc; private ASMPrinter(CodeGen code) { super(new IRPrinter()); _code=code; + _enc=code._encoding; } public static SB print(SB sb, CodeGen code) { return sb.p(new com.seaofnodes.print.ASMPrinter<>(new ASMPrinter(code)).print()); @@ -50,4 +53,33 @@ public final class ASMPrinter extends AssemblyAdapter { return n.getClass()==ConstantNode.class; } @Override public boolean postAlloc() { return _code._phase.ordinal()>CodeGen.Phase.RegAlloc.ordinal(); } + @Override public boolean encoded() { return _enc!=null && _code._phase.ordinal()>=CodeGen.Phase.Encoding.ordinal(); } + @Override public int defaultSize() { return _code._mach==null ? 2 : _code._mach.defaultOpSize(); } + @Override public boolean littleEndian() { return _code._asmLittle; } + @Override public boolean prologue(Node n) { return ((FunNode)n)._frameAdjust!=0; } + @Override public byte[] bytes() { return encoded() ? _enc._bits.buf() : null; } + @Override public int offset(Node n) { + return encoded() && _enc._opStart!=null && n._nid<_enc._opStart.length ? _enc._opStart[n._nid] : -1; + } + @Override public int size(Node n) { + return encoded() && _enc._opLen!=null && n._nid<_enc._opLen.length ? _enc._opLen[n._nid]&255 : 0; + } + private static ArrayList entries(Iterable relos) { + var data=new ArrayList(); + for( Encoding.Relo r : relos ) { + int align=1< pools(int codeEnd) { + var pools=new ArrayList(); + if( !encoded() ) return pools; + int base=(codeEnd+15)&-16; + if( !_enc._bigCons.isEmpty() && base<_enc._bits.size() ) + pools.add(new Pool("Constant Pool",_enc._bits.buf(),base,base,entries(_enc._bigCons.values()))); + return pools; + } } diff --git a/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index 0357ff61..b9aabba3 100644 --- a/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -6,7 +6,7 @@ import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.type.TypeMem; -/** Chapter 20's view of the IR; browser, transport and layout are shared. */ +/** Chapter 21's view of the IR; browser, transport and layout are shared. */ public class SimpleGraphAdapter extends GraphAdapter { @Override protected String edgeName(Node n, int i) { diff --git a/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java index 9dce0b75..d8d0e216 100644 --- a/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java +++ b/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java @@ -7,7 +7,7 @@ import com.seaofnodes.simple.node.Node; import java.util.ArrayList; import java.io.IOException; -/** Chapter 20's compilation context; capture and event bookkeeping are shared. */ +/** Chapter 21's compilation context; capture and event bookkeeping are shared. */ public class SimpleGraphObserver extends GraphCapture { private CodeGen _code; public SimpleGraphObserver() { super(new SimpleGraphAdapter()); } diff --git a/src/main/java/com/seaofnodes/simple/type/Type.java b/src/main/java/com/seaofnodes/simple/type/Type.java index 1ce5dbd0..96b2c6ef 100644 --- a/src/main/java/com/seaofnodes/simple/type/Type.java +++ b/src/main/java/com/seaofnodes/simple/type/Type.java @@ -103,6 +103,7 @@ public class Type { // checks with every type compare, only during interning. // Factory method which interns "this" + @SuppressWarnings("unchecked") public T intern() { T nnn = (T)INTERN.get(this); if( nnn==null ) @@ -221,7 +222,7 @@ public class 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(); } + public int log_size() { return 3; } // ---------------------------------------------------------- // Useful in the debugger, which calls toString everywhere. diff --git a/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java b/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java index 609f2443..1bd73732 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java @@ -44,6 +44,7 @@ public class TypeFunPtr extends TypeNil { public static TypeFunPtr TEST = make((byte)2,TypeTuple.TEST,TypeInteger.BOT,1); public static TypeFunPtr TEST0 = make((byte)3,TypeTuple.TEST,TypeInteger.BOT,3); public static TypeFunPtr MAIN = make((byte)3,TypeTuple.MAIN,TypeInteger.BOT,-1); + public static TypeFunPtr CALLOC= make((byte)3,TypeTuple.CALLOC,TypeMemPtr.BOT,-1); public static void gather(ArrayList ts) { ts.add(TEST); ts.add(TEST0); ts.add(BOT); ts.add(MAIN); } @Override diff --git a/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java index 61b020cb..98e92bca 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java @@ -98,7 +98,7 @@ public class TypeMemPtr extends TypeNil { // Is forward-reference @Override public boolean isFRef() { return _obj.isFRef(); } - @Override public int log_size() { return 2; } // (1<<2)==4-byte pointers + @Override public int log_size() { return 3; } // (1<<3)==8-byte pointers @Override int hash() { return (_ro ? 8192 : 0) ^ _obj.hashCode() ^ super.hash(); } diff --git a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/src/main/java/com/seaofnodes/simple/type/TypeStruct.java index dc92e086..990c5fcf 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeStruct.java @@ -184,9 +184,12 @@ public class TypeStruct extends Type { public SB print(SB sb) { if( this==BOT ) return sb.p("StructBot"); if( this==TOP ) return sb.p("StructTop"); + if( _fields == null ) return sb.p(_name); // Forward reference struct, just print the name + if( isAry() ) { + if( !isFinal() ) sb.p("!"); + return sb.p(_name); // Skip printing generic array fields + } 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("; "); diff --git a/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/src/main/java/com/seaofnodes/simple/type/TypeTuple.java index 95e35e99..d3b19b52 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeTuple.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeTuple.java @@ -17,6 +17,7 @@ public class TypeTuple extends Type { public static final TypeTuple START= make(Type.CONTROL,TypeMem.TOP,TypeInteger.BOT); public static final TypeTuple MAIN = make(TypeInteger.BOT); public static final TypeTuple RET = make(Type.CONTROL,TypeMem.BOT,Type.BOTTOM); + public static final TypeTuple CALLOC = make(TypeInteger.BOT,TypeInteger.BOT); public static final TypeTuple IF_BOTH = make(new Type[]{Type. CONTROL,Type. CONTROL}); public static final TypeTuple IF_NEITHER = make(new Type[]{Type.XCONTROL,Type.XCONTROL}); @@ -55,6 +56,21 @@ public class TypeTuple extends Type { return make(ts); } + @Override public boolean isConstant() { + for( Type t : _types ) + if( !t.isConstant() ) + return false; + return true; + } + + @Override public int log_size() { + assert isConstant(); + int log_size = 0; + for( Type t : _types ) + log_size = Math.max(log_size,t.log_size()); + return log_size; + } + public Type ret() { assert _types.length==3; return _types[2]; } @Override public String str() { return print(new SB()).toString(); } diff --git a/src/test/java/com/seaofnodes/simple/BrainFuckTest.java b/src/test/java/com/seaofnodes/simple/BrainFuckTest.java index c23bb301..aa6fada0 100644 --- a/src/test/java/com/seaofnodes/simple/BrainFuckTest.java +++ b/src/test/java/com/seaofnodes/simple/BrainFuckTest.java @@ -1,71 +1,42 @@ package com.seaofnodes.simple; +import com.seaofnodes.isa.eval.EvalRisc5; + +import com.seaofnodes.isa.eval.EvalArm64; + import com.seaofnodes.simple.codegen.CodeGen; -import java.nio.charset.StandardCharsets; +import com.seaofnodes.simple.node.cpus.arm.arm; +import com.seaofnodes.simple.node.cpus.riscv.riscv; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; -import static com.seaofnodes.simple.Main.PORTS; public class BrainFuckTest { - @Test - public void testBrainfuck() { - var program = "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.".getBytes(StandardCharsets.UTF_8); - var encoded = new StringBuilder("u8[] !program = new u8[").append(program.length).append("];"); - for (int i = 0; i < program.length; i++) { - int value = program[i] & 0xFF; - encoded.append("program[").append(i).append("] = ").append(value).append(";"); - } + @Test public void testBrainFuck() throws IOException { + String brain_fuck = "Hello World!\n"; + TestC.run("brain_fuck", brain_fuck, 40); - String src = encoded + """ + EvalRisc5 R5 = TestRisc5.build("brain_fuck", 0, 146, false); + int trap = R5.step(100000); + assertEquals(0,trap); + int ptr = (int)R5.regs[riscv.A0]; + assertEquals(brain_fuck.length(),R5.ld4s(ptr)); + for( int i=0; i 0; ) { - command = program[++pc]; - if (command == 91) d++; - if (command == 93) d--; - } - } - } else if (command == 93) { - if (data[d]) { - for( int d = 1; d > 0; ) { - command = program[--pc]; - if (command == 93) d++; - if (command == 91) d--; - } - } - } -} -return output; -"""; - Chapter20Test.testCPU(src,"x86_64_v2", "SystemV",28,null); - Chapter20Test.testCPU(src,"riscv" , "SystemV",18,null); - Chapter20Test.testCPU(src,"arm" , "SystemV",12,null); - //assertEquals("Hello World!\n", Eval2.eval(code, 0, 10000)); + for( int i=0; i { if( n instanceof FunNode fun ) @@ -51,26 +51,18 @@ public class Chapter18Test { "val g = { int x -> x ? g(x-1)/305420988+x*305420988 : 1; }; "+ "return f(arg)+g(arg);"; CodeGen code = new CodeGen(src); - code.parse().opto().typeCheck(); - Node con = code._stop.walk(n -> n instanceof ConstantNode && - n._type==TypeInteger.constant(305420988) ? n : null); - assertNotNull(con); - Node[] uses = con._outputs.asAry(); - // Preserve a stacked Cast/Constant chain through scheduling. - Node cast = new CastNode(TypeInteger.BOT,code._start,con); - cast = new CastNode(TypeInteger.BOT,code._start,cast); - for( Node use : uses ) - for( int i=1; i(); code._stop.walk(n -> { - if( !(n instanceof CastNode) || !(n.in(1) instanceof CastNode) ) return null; + if( !(n instanceof com.seaofnodes.simple.node.cpus.riscv.AddIRISC add) || + !(add.in(1) instanceof com.seaofnodes.simple.node.cpus.riscv.LUI upper) || + ((TypeInteger)upper._con).value()+((add._imm12<<20)>>20)!=305420988 ) + return null; CFGNode fun = n.cfg0(); while( fun!=null && !(fun instanceof FunNode) ) fun = fun.idom(); assertTrue("Constant must belong to a function",fun instanceof FunNode); assertNull("Share one constant chain within each function",owners.put(fun,true)); - // Follow all parts of the constant chain, including shared inputs. + // Follow all parts of a machine constant, including shared inputs. var todo = new java.util.ArrayList(); var seen = new java.util.IdentityHashMap(); todo.add(n); @@ -130,7 +122,7 @@ public class Chapter18Test { """ return 0; """); - code.parse().opto().typeCheck().GCM().localSched(); + code.driver(Phase.LocalSched); assertEquals("return 0;", code._stop.toString()); assertEquals("0", Eval2.eval(code, 2)); } @@ -177,11 +169,10 @@ return x2; {int -> int}? sq = { int x -> x*x; }; -return sq; """); code.parse().opto(); - assertEquals("Stop[ return { sq}; return (Parm_x(sq,int)*x); ]", code._stop.toString()); - assertEquals("{ int -> int #1}", Eval2.eval(code, 3)); + assertEquals("return (Parm_x(sq,int)*x);", code._stop.toString()); + //assertEquals("{ int -> int #1}", Eval2.eval(code, 3)); } @Test @@ -193,7 +184,7 @@ var sq = { int x -> }; return sq(arg)+sq(3); """); - code.parse().opto().typeCheck().GCM().localSched(); + code.driver(Phase.LocalSched); assertEquals("Stop[ return (#2+#2); return (Parm_x(sq,int)*x); ]", code._stop.toString()); assertEquals("13", Eval2.eval(code, 2)); } @@ -291,7 +282,7 @@ for(;;) { i2i = id(x); } """); - code.parse().opto().typeCheck().GCM().localSched(); + code.driver(Phase.LocalSched); assertEquals("Stop[ return #2; return Parm_i(x,int); ]", code._stop.toString()); assertEquals("3", Eval2.eval(code, 0)); } @@ -307,9 +298,9 @@ for(;;) { arg = x(3); } """); - code.parse().opto().typeCheck().GCM().localSched(); + code.driver(Phase.LocalSched); assertEquals("return Top;", code._stop.toString()); - assertEquals(null, Eval2.eval(code, 0)); + assertNull(Eval2.eval(code,0)); // Infinite loop: i2i remains null. } @@ -332,7 +323,7 @@ ps[0] = new Person; ps[1] = new Person; fcn(ps,1); """); - code.parse().opto().typeCheck().GCM().localSched(); + code.driver(Phase.LocalSched); assertEquals("return 0;", code._stop.toString()); assertEquals("0", Eval2.eval(code, 0)); } @@ -462,7 +453,7 @@ if (arg) i2i = null; if (i2i) return i2i(arg); return f2f(o)(1); """); - code.parse().opto().typeCheck().GCM().localSched(); + code.driver(Phase.LocalSched); assertEquals("Stop[ return Phi(Region,#2,1); return Parm_i(o,int); ]", code._stop.toString()); assertEquals("1", Eval2.eval(code, 2)); } @@ -478,7 +469,7 @@ struct Person { p.coffee_count += 1; return p.coffee_count; """); - code.parse().opto().typeCheck().GCM().localSched(); + code.driver(Phase.LocalSched); assertEquals("return 1;", code._stop.toString()); assertEquals("1", Eval2.eval(code, 2)); } diff --git a/src/test/java/com/seaofnodes/simple/Chapter19Test.java b/src/test/java/com/seaofnodes/simple/Chapter19Test.java index 9bd75207..c53a8e11 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter19Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter19Test.java @@ -3,12 +3,14 @@ import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.codegen.CodeGen.Phase; import com.seaofnodes.simple.codegen.CodeGen; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import org.junit.Ignore; import org.junit.Test; -import static com.seaofnodes.simple.Main.PORTS; import static org.junit.Assert.*; -import static org.junit.Assert.fail; public class Chapter19Test { @Test public void testPrintingFunctionLookup() { @@ -44,143 +46,108 @@ return 0; @Test - public void testString() { - CodeGen code = new CodeGen( -""" -struct String { - u8[] !cs; - int _hashCode; -}; - -val equals = { !String !self, !String s -> - if( self == s ) return true; - if( self.cs# != s.cs# ) return false; - for( int i=0; i< self.cs#; i++ ) - if( self.cs[i] != s.cs[i] ) - return false; - return true; -}; - -val hashCode = { !String self -> - self._hashCode - ? self._hashCode - : (self._hashCode = _hashCodeString(self)); -}; - -val _hashCodeString = { !String self -> - int hash=0; - if( self.cs ) { - for( int i=0; i< self.cs#; i++ ) - hash = hash*31 + self.cs[i]; - } - if( !hash ) hash = 123456789; - return hash; -}; - -!String !s = new String { cs = new u8[17]; }; -s.cs[0] = 67; // C -s.cs[1] = 108; // l -hashCode(s)+hashCode(s);"""); - code.parse().opto().typeCheck().GCM().localSched(); - assertEquals("Stop[ return (#2+#2); return Phi(Region,1,0,0,1); return Phi(Region,._hashCode,Phi(Region,123456789,Phi(Loop,0,(.[]+((Phi_hash<<5)-Phi_hash))))); ]", code._stop.toString()); - assertEquals("-4898613127354160978", Eval2.eval(code, 2)); + public void testString() throws IOException { + String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/stringHash.smp")); + CodeGen code = new CodeGen(src).parse().opto().typeCheck().GCM().localSched(); + assertEquals("Stop[ return Phi(Region,._hashCode,Phi(Region,123456789,Phi(Loop,0,(.[]+((Phi_hash<<5)-Phi_hash))))); return Phi(Region,1,0,0,1); ]", code._stop.toString()); + //assertEquals("-4898613127354160978", Eval2.eval(code, 2)); } @Test public void testBasic0() { - CodeGen code = new CodeGen("return 0;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return 0;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return 0;", code._stop.toString()); } @Test public void testBasic1() { - CodeGen code = new CodeGen("return arg+1;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg+1;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return (inc,arg);", code._stop.toString()); } @Test public void testBasic2() { - CodeGen code = new CodeGen("return -17;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return -17;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return -17;", code._stop.toString()); } @Test public void testBasic3() { - CodeGen code = new CodeGen("return arg==1;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg==1;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return (set==,(cmp,arg));", code._stop.toString()); } @Test public void testBasic4() { - CodeGen code = new CodeGen("return arg<<1;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg<<1;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return (shli,arg);", code._stop.toString()); } @Test public void testBasic5() { - CodeGen code = new CodeGen("return arg >> 1;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg >> 1;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return (sari,arg);", code._stop.toString()); } @Test public void testBasic6() { - CodeGen code = new CodeGen("return arg >>> 1;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg >>> 1;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return (shri,arg);", code._stop.toString()); } @Test public void testBasic7() { - CodeGen code = new CodeGen("return arg / 2;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg / 2;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return (div,arg,2);", code._stop.toString()); } @Test public void testBasic8() { - CodeGen code = new CodeGen("return arg * 6;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg * 6;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return (muli,arg);", code._stop.toString()); } @Test public void testBasic9() { - CodeGen code = new CodeGen("return arg & 2;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg & 2;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return (andi,arg);", code._stop.toString()); } @Test public void testBasic10() { - CodeGen code = new CodeGen("return arg | 2;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg | 2;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return (ori,arg);", code._stop.toString()); } @Test public void testBasic11() { - CodeGen code = new CodeGen("return arg ^ 2;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg ^ 2;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return (xori,arg);", code._stop.toString()); } @Test public void testBasic12() { - CodeGen code = new CodeGen("return arg + 2.0;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); - assertEquals("return (addf,(i2f8,arg),2.0f);", code._stop.toString()); + CodeGen code = new CodeGen("return arg + 2.0;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); + assertEquals("return (addf,(cvtf,arg),2.0f);", code._stop.toString()); } @Test public void testBasic13() { - CodeGen code = new CodeGen("return arg - 2.0;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); - assertEquals("return (subf,(i2f8,arg),2.0f);", code._stop.toString()); + CodeGen code = new CodeGen("return arg - 2.0;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); + assertEquals("return (subf,(cvtf,arg),2.0f);", code._stop.toString()); } @Test public void testBasic14() { - CodeGen code = new CodeGen("return arg * 2.0;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); - assertEquals("return (mulf,(i2f8,arg),2.0f);", code._stop.toString()); + CodeGen code = new CodeGen("return arg * 2.0;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); + assertEquals("return (mulf,(cvtf,arg),2.0f);", code._stop.toString()); } @Test public void testBasic15() { - CodeGen code = new CodeGen("return arg / 2.0;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); - assertEquals("return (mulf,(i2f8,arg),0.5f);", code._stop.toString()); + CodeGen code = new CodeGen("return arg / 2.0;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); + assertEquals("return (mulf,(cvtf,arg),0.5f);", code._stop.toString()); } @Test @@ -189,7 +156,7 @@ hashCode(s)+hashCode(s);"""); """ int arg1 = arg + 1; return arg1 / arg;"""); - code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return (div,(inc,arg),arg);", code._stop.toString()); } @@ -200,18 +167,18 @@ return arg1 / arg;"""); int arg1 = arg + 1; return arg1 * arg; """); - code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return (mul,(inc,arg),arg);", code._stop.toString()); } @Test public void testToFloat() { CodeGen code = new CodeGen(""" - int a = arg; - return a + 2.0; - """ - ).parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); - assertEquals("return (addf,(i2f8,arg),2.0f);", code._stop.toString()); +int a = arg; +return a + 2.0; +""" + ).driver(Phase.LocalSched,"x86_64_v2", "SystemV"); + assertEquals("return (addf,(cvtf,arg),2.0f);", code._stop.toString()); } @Test @@ -225,7 +192,7 @@ else { a = arg-3; } return a;"""); - code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return Phi(Region,(addi,arg),(addi,arg));", code.print()); } @@ -240,7 +207,7 @@ if( arg==1 ) else a=b+1; return a+b;"""); - code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return (add,(add,Phi(Region,(shli,arg),arg),arg),Phi(Region,4,5));", code.print()); } @@ -252,7 +219,7 @@ int sum=0; for( int i=0; i - flt guess = x; - while( 1 ) { - flt next = (x/guess + guess)/2; - if( next == guess ) return guess; - guess = next; - } -}; -flt farg = arg; -return sqrt(farg); -"""); - code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); - assertEquals("return Phi(Loop,(i2f8,arg),(mulf,(addf,(divf,i2f8,Phi_guess),Phi_guess),0.5f));", code.print()); + public void testNewton() throws IOException { + String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/newtonFloat.smp")) + + "flt farg = arg; return test_sqrt(farg);"; + CodeGen code = new CodeGen(src).driver(Phase.LocalSched,"x86_64_v2", "SystemV"); + assertEquals("return Phi(Loop,(cvtf,arg),(mulf,(addf,(divf,cvtf,Phi_guess),Phi_guess),0.5f));", code.print()); }; @Test - public void sieveOfEratosthenes() { - CodeGen code = new CodeGen( -""" -var ary = new bool[arg], primes = new int[arg]; -var nprimes=0, p=0; -// Find primes while p^2 < arg -for( p=2; p*p < arg; p++ ) { - // skip marked non-primes - while( ary[p] ) p++; - // p is now a prime - primes[nprimes++] = p; - // Mark out the rest non-primes - for( int i = p + p; i < ary#; i += p ) - ary[i] = true; -} -// Now just collect the remaining primes, no more marking -for( ; p < arg; p++ ) - if( !ary[p] ) - primes[nprimes++] = p; -// Copy/shrink the result array -var !rez = new int[nprimes]; -for( int j=0; j x*x; } : { int x -> x+x; }; return fcn(2)*10 + fcn(3); """); - code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("Stop[ return (add,#2,(muli,#2)); return (mul,Parm_x($fun1,int),x); return (shli,Parm_x($fun2,int)); ]", code.print()); } @@ -417,7 +349,7 @@ return fcn(2)*10 + fcn(3); val sq = { int x -> x*x; }; return sq(arg) + sq(3); """); - code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("Stop[ return (add,#2,#2); return (mul,Parm_x(sq,int),x); ]", code.print()); } @@ -427,7 +359,7 @@ return sq(arg) + sq(3); Chapter18Test.CALL_MEMORY,Chapter18Test.RECURSIVE_MEMORY} ) { var code = new CodeGen(src).parse().opto().typeCheck(); var aliases = memoryPhis(code._stop); - code.instSelect(PORTS,cpu,"SystemV"); + code.loopTree().instSelect(cpu,"SystemV"); assertEquals(aliases,memoryPhis(code._stop)); code.GCM().localSched(); code._stop.walk(n -> { @@ -437,6 +369,7 @@ return sq(arg) + sq(3); assertEquals(2,((TypeTuple)nn._type)._types.length); assertTrue(nn.mem() instanceof MemMergeNode); assertNull(nn.mem().in(1)); // Partial allocation input. + nn.cacheRegs(code); MachNode mach = (MachNode)nn; assertNull(mach.regmap(1)); assertNotNull(mach.regmap(2)); @@ -480,7 +413,7 @@ return sq(arg) + sq(3); int before = p.x + arg; a.x=33; b.x=44; return before; - """).parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2","SystemV").GCM().localSched(); + """).parse().opto().typeCheck().loopTree().instSelect("x86_64_v2","SystemV").GCM().localSched(); var read = (MemOpNode)code._stop.walk(n -> n instanceof com.seaofnodes.simple.node.cpus.x86_64_v2.AddMemX86 ? n : null); assertNotNull(read); diff --git a/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/src/test/java/com/seaofnodes/simple/Chapter20Test.java index 104be01e..e63fb32e 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter20Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter20Test.java @@ -6,18 +6,47 @@ import com.seaofnodes.simple.print.ASMPrinter; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; -import static com.seaofnodes.simple.Main.PORTS; import static org.junit.Assert.*; public class Chapter20Test { @Test public void testAllocatorTopPhi() { com.seaofnodes.simple.codegen.RegAllocTestSupport.topPhi(); } + @Test public void testNeverReturnAllocation() { + String[] sources = { + // A real integer return merged with a synthetic exit. + "if(arg) while(1) {} return arg+1;", + // TOP must not widen a non-null pointer or constrain a float register. + "struct S { int x; }; !S !s=new S; if(arg) while(1) {} return s;", + "if(arg) while(1) {} return 1.5;", + // Several synthetic exits into the same return. + "if(arg==1) while(1) {} if(arg==2) while(1) {} return arg+3;", + // No real return, including a merge of only synthetic exits. + "while(1) { arg+=1; }", + "if(arg) while(1) {} while(1) {}", + // Loop-carried memory and scalar self-conflicts exercise spilling. + "struct S { int x; int y; }; !S !a=new S; if(arg) while(1) { a.x+=arg; a.y+=a.x; } return a.x+a.y;" + }; + for( String target : new String[]{"x86_64_v2","riscv","arm"} ) + for( String src : sources ) { + CodeGen code = new CodeGen(src).driver(CodeGen.Phase.RegAlloc,target,"SystemV"); + com.seaofnodes.simple.codegen.RegAllocTestSupport.checkRegisters(code); + assertNotNull(code._stop.walk(n -> { + if( n._type!=com.seaofnodes.simple.type.Type.TOP ) return null; + assertEquals(-1,code._regAlloc.regnum(n)); + for( var use : n.outs() ) + assertFalse(use instanceof com.seaofnodes.simple.node.SplitNode); + return n; + })); + code.encode(); + } + } + @Test public void testAllocatorFixedNeighbor() throws Exception { com.seaofnodes.simple.codegen.RegAllocTestSupport.uncoloredFixedNeighbor(); } @Test public void testNarrowStoreMasks() { for( String type : new String[]{"i8","u8","i16","u16"} ) { CodeGen code = new CodeGen("struct S { "+type+" x; }; !S !s=new S; s.x=arg; return 0;") - .parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2","SystemV").GCM().localSched().regAlloc(); + .driver(CodeGen.Phase.RegAlloc,"x86_64_v2","SystemV"); int stores=0; for( var bb : code._cfg ) for( var node : bb.outs() ) @@ -76,10 +105,10 @@ return f(s); static void testCPU(String src, String cpu, String os, int spills, String stop) { CodeGen code = new CodeGen(src); - code.parse().opto().typeCheck().instSelect(PORTS,cpu,os).GCM().localSched().regAlloc().encode(); + code.driver(CodeGen.Phase.RegAlloc,cpu,os); com.seaofnodes.simple.codegen.RegAllocTestSupport.checkRegisters(code); SpillStats.record(code,"Chapter20",cpu,os); - assertEquals("Expect spills: "+cpu,spills,code._regAlloc._spillScaled,Math.max(1,spills>>3)); + SpillStats.checkSpills(spills,code._regAlloc._spillScaled); if( stop!=null ) assertEquals(stop,code._stop.toString()); } @@ -90,7 +119,9 @@ return f(s); } @Test public void testAlloc0() { - testAllCPUs("return new u8[arg];", 1, "return [u8];"); + testTarget("return new u8[arg];","x86_64_v2","SystemV",4,"return [u8];"); + testTarget("return new u8[arg];","riscv","SystemV",5,"return [u8];"); + testTarget("return new u8[arg];","arm","SystemV",5,"return [u8];"); } @Test public void testBasic1() { @@ -115,9 +146,9 @@ val sqrt = { int x -> }; return sqrt(arg) + sqrt(arg+2); """; - testTarget(src,"x86_64_v2", "SystemV",26,null); - testTarget(src,"riscv" , "SystemV",19,null); - testTarget(src,"arm" , "SystemV",26,null); + testTarget(src,"x86_64_v2", "SystemV",23,null); + testTarget(src,"riscv" , "SystemV",17,null); + testTarget(src,"arm" , "SystemV",18,null); } @Test @@ -136,15 +167,17 @@ val sqrt = { flt x -> flt farg = arg; return sqrt(farg) + sqrt(farg+2.0); """; - testTarget(src,"x86_64_v2", "SystemV",25,null); - testTarget(src,"riscv" , "SystemV",21,null); + testTarget(src,"x86_64_v2", "SystemV",23,null); + testTarget(src,"riscv" , "SystemV",18,null); testTarget(src,"arm" , "SystemV",18,null); } @Test public void testAlloc2() { String src = "int[] !xs = new int[3]; xs[arg]=1; return xs[arg&1];"; - testAllCPUs(src,1,"return .[];"); + testTarget(src,"x86_64_v2","SystemV",4,"return .[];"); + testTarget(src,"riscv","SystemV",8,"return .[];"); + testTarget(src,"arm","SystemV",9,"return .[];"); } @Test @@ -160,9 +193,9 @@ for( int i=0; i 0; ) { + command = program[++pc]; + if (command == 91) d++; + if (command == 93) d--; + } + } + } else if (command == 93) { + if (data[d]) { + for( int d = 1; d > 0; ) { + command = program[--pc]; + if (command == 93) d++; + if (command == 91) d--; + } + } + } +} +return output; +"""; + testTarget(src,"x86_64_v2", "SystemV",40,null); + testTarget(src,"riscv" , "SystemV",146,null); + testTarget(src,"arm" , "SystemV",34,null); + //assertEquals("Hello World!\n", Eval2.eval(code, 0, 10000)); + } + // Original Chapter 20 allocation workload, kept fixed for cohort comparisons. + @Test + public void testMergeSort() { + String src = +""" +// based on the top-down version from https://en.wikipedia.org/wiki/Merge_sort + +val merge_sort = { int[] !a, int[] !b, int n -> + copy_array(a, 0, n, b); + split_merge(a, 0, n, b); +}; + +val split_merge = { int[] !b, int begin, int end, int[] a -> + if (end - begin <= 1) + return 0; + int middle = (end + begin) / 2; + split_merge(a, begin, middle, b); + split_merge(a, middle, end, b); + merge(b, begin, middle, end, a); + return 0; +}; + +val merge = { int[] !b, int begin, int middle, int end, int[] a -> + int i = begin, j = middle; + + for (int k = begin; k < end; k++) { + // && and || + bool cond = false; + if (i < middle) { + if (j >= end) cond = true; + else if (a[i] <= a[j]) cond = true; + } + if (cond) b[k] = a[i++]; + else b[k] = a[j++]; + } +}; + +val copy_array = { int[] !a, int begin, int end, int[] b -> + for (int k = begin; k < end; k++) + b[k] = a[k]; +}; + +int[] !a = new int[arg]; +int[] !b = new int[a#]; + +for (int i = 0; i < a#; i++) + a[i] = a# - i; + +merge_sort(a, b, a#); + +return a; +"""; + testTarget(src,"x86_64_v2", "SystemV",52,null); + testTarget(src,"riscv" , "SystemV",44,null); + testTarget(src,"arm" , "SystemV",41,null); +//assertEquals("int[ 1,2,3,4,5,6,7,8,9,10,11]", Eval2.eval(code, 11)); + } + @Test public void testMemoryAllocation() { for( String cpu : new String[]{"x86_64_v2","riscv","arm"} ) for( String src : new String[]{Chapter10Test.NESTED_MEMORY,Chapter16Test.CONSTRUCTOR_MEMORY, Chapter18Test.CALL_MEMORY,Chapter18Test.RECURSIVE_MEMORY, "int[] !a = new int[3]; a[arg]=arg; return a[0]+1;"} ) { - var code = new CodeGen(src).parse().opto().typeCheck() - .instSelect(PORTS,cpu,"SystemV").GCM().localSched().regAlloc(); + var code = new CodeGen(src).parse().opto().typeCheck().loopTree() + .instSelect(cpu,"SystemV").GCM().localSched().regAlloc(); com.seaofnodes.simple.codegen.RegAllocTestSupport.checkRegisters(code); code._stop.walk(n -> { // Whole memory, precise Phis, and memory projections need no register. diff --git a/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java b/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java new file mode 100644 index 00000000..bc300217 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java @@ -0,0 +1,55 @@ +package com.seaofnodes.simple; + +import java.io.IOException; +import java.nio.file.*; +import org.junit.Test; + +// Revised Chapter 20 examples introduced with native encoding. +public class Chapter21AllocTest { + @Test + public void testNewtonInteger() { + String src = +""" +// Newtons approximation to the square root +val sqrt = { int x -> + int guess = x; + while( 1 ) { + int next = (x/guess + guess)/2; + if( next == guess ) return guess; + guess = next; + } +}; +return sqrt(arg) + sqrt(arg+2); +"""; + Chapter21Test.testCPU(src,"x86_64_v2", "Win64" ,23,null); + Chapter21Test.testCPU(src,"riscv" , "SystemV",19,null); + Chapter21Test.testCPU(src,"arm" , "SystemV",19,null); + } + + @Test + public void testNewtonFloat() throws IOException { + String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/newtonFloat.smp")) + + "flt farg = arg; return test_sqrt(farg) + test_sqrt(farg+2.0);"; + Chapter21Test.testCPU(src,"x86_64_v2", "SystemV",39,null); + Chapter21Test.testCPU(src,"riscv" , "SystemV",17,null); + Chapter21Test.testCPU(src,"arm" , "SystemV",18,null); + } + + @Test + public void testArray1() throws IOException { + String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/array1.smp")); + Chapter21Test.testCPU(src,"x86_64_v2", "SystemV",-1,"return .[];"); + Chapter21Test.testCPU(src,"riscv" , "SystemV", 8,"return (add,.[],(mul,.[],1000));"); + Chapter21Test.testCPU(src,"arm" , "SystemV", 5,"return (add,.[],(mul,.[],1000));"); + } + + + @Test + public void testString() throws IOException { + String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/stringHash.smp")); + Chapter21Test.testCPU(src,"x86_64_v2", "SystemV", 9,null); + Chapter21Test.testCPU(src,"riscv" , "SystemV", 3,null); + Chapter21Test.testCPU(src,"arm" , "SystemV", 1,null); + } + +} diff --git a/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/src/test/java/com/seaofnodes/simple/Chapter21Test.java new file mode 100644 index 00000000..a51ae3bc --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/Chapter21Test.java @@ -0,0 +1,532 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.isa.eval.EvalRisc5; + +import com.seaofnodes.isa.eval.EvalArm64; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.node.cpus.arm.arm; +import com.seaofnodes.simple.node.cpus.riscv.riscv; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.Ignore; +import org.junit.Test; +import static org.junit.Assert.*; + +public class Chapter21Test { + + @Test public void testReadBeforeConditionalStore() { + var code = new CodeGen(""" + struct S { int x; }; !S !a=new S; !S !b=new S; + a.x=10; b.x=20; !S !p=a; if(arg&1) p=b; + int sum=0; + for(int i=0; i<6; i++) { + sum+=a.x; + if(i==2) p.x=30; + else if(i==4) b.x=40; + } + return sum; + """).parse().opto(); + // The i==2 read still sees 10: three reads of 10, then three of 30. + assertEquals("120",Eval2.eval(code,0)); + assertEquals("60",Eval2.eval(code,1)); + } + + + @Test public void testArmBitfieldExtracts() { + // Independent SXT/UXT instruction words, plus ASR, LSR and LSL aliases. + int[] ops = {0x93401c20,0xd3401c20,0x93403c20,0xd3403c20, + 0x93407c20,0xd3407c20,0x9344fc20,0xd344fc20,0xd37cec20}; + long value = 0xa5a5a5a580008080L; + long[] expected = {(byte)value,value&255,(short)value,value&65535, + (int)value,value&0xffffffffL,value>>4,value>>>4,value<<4}; + for( int i=0; i>2)&0x03FFFFFF)); // BL + assertEquals(0,cpu.step(1)); + assertEquals(128+delta,cpu._pc); + for( int i=0; i<32; i++ ) assertEquals(i==30 ? 132 : 1000+i,cpu.regs[i]); + } + for( int rn : new int[]{9,30} ) + for( boolean call : new boolean[]{false,true} ) { + cpu._pc=4; + cpu.regs[rn]=64; + cpu.st4(4,(call ? 0xD63F0000 : 0xD65F0000) | (rn<<5)); // BLR / RET + assertEquals(0,cpu.step(1)); + assertEquals(64,cpu._pc); + if( call ) assertEquals(8,cpu.regs[30]); + } + } + + @Test public void testArmImmediateArithmetic() { + EvalArm64 cpu = new EvalArm64(new byte[16],16); + // Unsigned imm12, including bit 11 and the optional 12-bit shift. + for( int[] test : new int[][]{{0x91200020,2148},{0xD1200020,-1948}, + {0x91400420,4196},{0xD1400420,-3996}} ) { + cpu._pc=0; cpu.regs[1]=100; + cpu.N=true; cpu.Z=false; cpu.C=true; cpu.V=true; + cpu.st4(0,test[0]); + assertEquals(0,cpu.step(1)); + assertEquals(test[1],cpu.regs[0]); + assertTrue(cpu.N); assertFalse(cpu.Z); assertTrue(cpu.C); assertTrue(cpu.V); + } + } + + @Test public void testArmCallsAndFrames() throws IOException { + String src = """ + val sum = { int n -> + if( n<=0 ) return 1; + return sum(n-1)+n; + }; + val run = { int n -> return sum(n)+sum(n+1); }; + """; + assertTrue("Ordinary calls must survive optimization",checkArmCalls(src,"run",4,27)>0); + } + + private static int checkArmCalls(String src, String entryName, int arg, int result) throws IOException { + CodeGen code = new CodeGen(src).driver("arm","SystemV",null); + byte[] image = new byte[1<<20]; + System.arraycopy(code._encoding.bits(),0,image,0,code._encoding.bits().length); + EvalArm64 cpu = new EvalArm64(image,1<<16); + boolean entry=false; + int frames=0, calls=0; + for( var bb : code._cfg ) { + if( bb instanceof com.seaofnodes.simple.node.FunNode fun ) { + int off = code._encoding._opStart[fun._nid]; + if( entryName.equals(fun._name) ) { cpu._pc=off; entry=true; } + int frame=fun._frameAdjust; + assertEquals(0,frame&15); + if( frame>0 ) { + frames++; + assertEquals(0xD10003FF | (frame<<10),cpu.ld4s(off)); // SUB SP,SP,#bytes + } + } + if( bb instanceof com.seaofnodes.simple.node.ReturnNode ret ) { + int off=code._encoding._opStart[ret._nid]; + int frame=ret.fun()._frameAdjust; + if( frame>0 ) { + assertEquals(0x910003FF | (frame<<10),cpu.ld4s(off)); // ADD SP,SP,#bytes + off+=4; + } + assertEquals(0xD65F03C0,cpu.ld4s(off)); // RET X30 + } + if( bb instanceof com.seaofnodes.simple.node.cpus.arm.CallARM ) calls++; + } + assertTrue("Must execute the program entry",entry); + assertTrue("Exercise stack frames",frames>0); + for( int i=19; i<=29; i++ ) cpu.regs[i]=1000+i; + cpu.regs[0]=arg; + assertEquals(0,cpu.step(10000)); + assertEquals("Must return to the harness",0,cpu._pc); + assertEquals(result,cpu.regs[0]); + assertEquals(1<<16,cpu.regs[31]); + for( int i=19; i<=29; i++ ) assertEquals(1000+i,cpu.regs[i]); + return calls; + } + + + @Test public void testArmSubtractRegisters() { + EvalArm64 cpu = new EvalArm64(new byte[16],16); + cpu.st4(0,0xCB020020); // SUB X0,X1,X2, no shift or flag update. + for( long[] pair : new long[][]{{7,3},{3,7},{Long.MIN_VALUE,1},{1L<<40,3}} ) { + cpu._pc = 0; + cpu.regs[1] = pair[0]; cpu.regs[2] = pair[1]; + cpu.N=true; cpu.Z=false; cpu.C=true; cpu.V=true; + assertEquals(0,cpu.step(1)); + assertEquals(pair[0]-pair[1],cpu.regs[0]); + assertTrue(cpu.N); assertFalse(cpu.Z); assertTrue(cpu.C); assertTrue(cpu.V); + } + } + + + @Test public void testCoalescing() { com.seaofnodes.simple.codegen.RegAllocTestSupport.coalescing(); } + @Test public void testCopyForward() throws Exception { com.seaofnodes.simple.codegen.RegAllocTestSupport.copyForward(); } + @Test public void testRisc64BitStore() { + byte[] mem = new byte[24]; + EvalRisc5 cpu = new EvalRisc5(mem,mem.length); + check64BitStore(mem,cpu::st8,cpu::ld8); + } + + @Test public void testArm64BitStore() { + byte[] mem = new byte[24]; + EvalArm64 cpu = new EvalArm64(mem,mem.length); + check64BitStore(mem,cpu::st8,cpu::ld8); + } + + // Check bytes independently of ld8, including overwrite and adjacent memory. + private static void check64BitStore(byte[] mem, + java.util.function.BiConsumer store, + java.util.function.IntToLongFunction load) { + java.util.Arrays.fill(mem,(byte)0xA5); + for( long value : new long[]{0x0123456789ABCDEFL,0xFEDCBA9876543210L, + -1L,0L,Long.MIN_VALUE,Long.MAX_VALUE} ) { + store.accept(8,value); + for( int i=0; i=8 && i<16 ? (int)(value >>> (8*(i-8)))&0xFF : 0xA5; + assertEquals("byte "+i,expected,mem[i]&0xFF); + } + assertEquals(value,load.applyAsLong(8)); + } + } + + + @Test @Ignore + public void testJig() throws IOException { + String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/jig.smp")); + testCPU(src,"x86_64_v2", "Win64" ,-1,null); + testCPU(src,"riscv" , "SystemV",-1,null); + testCPU(src,"arm" , "SystemV",-1,null); + } + + static void testCPU( String src, String cpu, String os, int spills, String stop ) { + CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Encoding,cpu,os); + com.seaofnodes.simple.codegen.RegAllocTestSupport.checkRegisters(code); + SpillStats.record(code,"Chapter21",cpu,os); + SpillStats.checkSpills(spills,code._regAlloc._spillScaled); + if( stop != null ) + assertEquals(stop, code._stop.toString()); + } + + + @Test public void testBasic1() { + String src = "return arg | 2;"; + testCPU(src,"x86_64_v2", "SystemV",1,"return (ori,mov(arg));"); + testCPU(src,"riscv" , "SystemV",0,"return ( arg | #2 );"); + testCPU(src,"arm" , "SystemV",0,"return (ori,arg);"); + } + + @Test public void testInfinite() { + String src = "struct S { int i; }; !S !s = new S; while(1) s.i++;"; + testCPU(src,"x86_64_v2", "SystemV",0,"return Top;"); + testCPU(src,"riscv" , "SystemV",0,"return Top;"); + testCPU(src,"arm" , "SystemV",0,"return Top;"); + } + + @Test + public void testArray1() throws IOException { + String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/array1.smp")); + testCPU(src,"x86_64_v2", "SystemV",-1,"return .[];"); + testCPU(src,"riscv" , "SystemV", 7,"return (add,.[],(mul,.[],1000));"); + testCPU(src,"arm" , "SystemV", 5,"return (add,.[],(mul,.[],1000));"); + } + + @Test + public void testAntiDeps1() throws IOException { + String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/antiDep1.smp")); + testCPU(src,"x86_64_v2", "SystemV", 7,"return mov(mov(S));"); + testCPU(src,"riscv" , "SystemV",10,"return mov(mov(S));"); + testCPU(src,"arm" , "SystemV",10,"return mov(mov(S));"); + } + + @Test + public void testString() throws IOException { + String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/stringHash.smp")); + testCPU(src,"x86_64_v2", "SystemV", 9,null); + testCPU(src,"riscv" , "SystemV", 3,null); + testCPU(src,"arm" , "SystemV", 1,null); + } + + @Test public void testStringExport() throws IOException { + TestC.run("stringHash", 9); + } + + @Test public void testLoop2() throws IOException { + String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/loop2.smp")); + testCPU(src,"x86_64_v2", "Win64" ,0,"return (inc,Phi(Loop,0,inc));"); + testCPU(src,"riscv" , "SystemV",0,"return ( Phi(Loop,0,addi) + #1 );"); + testCPU(src,"arm" , "SystemV",0,"return (inc,Phi(Loop,0,inc));"); + } + + @Test public void testNewtonExport() throws IOException { + String result = """ +0 0.000000 (0) +1 1.000000 (0) +2 1.414214 (2.22045e-16) +3 1.732051 (0) +4 2.000000 (0) +5 2.236068 (0) +6 2.449490 (0) +7 2.645751 (0) +8 2.828427 (4.44089e-16) +9 3.000000 (0) +"""; + TestC.run("newtonFloat",result,34); + + EvalRisc5 R5 = TestRisc5.build("newtonFloat", 0, 10, false); + R5.fregs[riscv.FA0 - riscv.F_OFFSET] = 3.0; + int trap_r5 = R5.step(1000); + assertEquals(0,trap_r5); + // Return register A0 holds fib(8)==55 + assertEquals(1.732051,R5.fregs[riscv.FA0 - riscv.F_OFFSET], 0.00001); + + // arm + EvalArm64 A5 = TestArm64.build("newtonFloat", 0, 10, false); + A5.fregs[arm.D0 - arm.D_OFFSET] = 3.0; + int trap_arm = A5.step(1000); + assertEquals(0,trap_arm); + assertEquals(1.732051, A5.fregs[arm.D0 - arm.D_OFFSET], 0.00001); + } + + + @Test public void testSieve() throws IOException { + // The primes + int[] primes = new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, }; + SB sb = new SB().p(primes.length).p("["); + for( int prime : primes ) + sb.p(prime).p(", "); + String sprimes = sb.p("]").toString(); + + // Compile, link against native C; expect the above string of primes to be printed out by C + TestC.run("sieve",sprimes, 185); + + // Evaluate on RISC5 emulator; expect return of an array of primes in + // the simulated heap. + EvalRisc5 R5 = TestRisc5.build("sieve", 100, 92, false); + int trap = R5.step(10000); + assertEquals(0,trap); + // Return register A0 holds sieve(100) + int ary = (int)R5.regs[riscv.A0]; + // Memory layout starting at ary(length,pad, prime1, primt2, prime3, prime4) + assertEquals(primes.length, R5.ld4s(ary)); + for( int i=0; i(); + if (USE_WSL) params.add("wsl.exe"); + params.add("gcc"); + params.add("-o"); + params.add("test"); + if (c != null) { + Files.writeString(Path.of("test.c"), c); + params.add("test.c"); + } + params.add("test.o"); + Process p = Runtime.getRuntime().exec(params.toArray(String[]::new)); + assertEquals(0, p.waitFor()); + return Runtime.getRuntime().exec(USE_WSL ? new String[]{"wsl.exe", "./test"}:new String[]{"./test"}); + } + + @Test + @Ignore + public void testCompile() throws IOException, InterruptedException { + var p = run(""" + val testMy = { flt x -> + flt guess = x; + while( 1 ) { + flt next = (x/guess + guess)/2; + if( next == guess ) return guess; + guess = next; + } + }; + """,""" + + #include + + extern double testMy(double); + + int main(int argc, char** argv) { + printf("%f\\n", testMy(2.0)); + return 0; + } + + """); + assertEquals(0, p.waitFor()); + assertEquals("1.414214\n", new String(p.getInputStream().readAllBytes(), StandardCharsets.UTF_8)); + } + +} diff --git a/src/test/java/com/seaofnodes/simple/Eval2.java b/src/test/java/com/seaofnodes/simple/Eval2.java index 3312ec6b..a855350e 100644 --- a/src/test/java/com/seaofnodes/simple/Eval2.java +++ b/src/test/java/com/seaofnodes/simple/Eval2.java @@ -117,6 +117,7 @@ public abstract class Eval2 { SB trace = null; // new SB(); // TRACE, set to null for off, new SB() for on // Force local scheduling phase if( code._phase.ordinal() < CodeGen.Phase.TypeCheck .ordinal() ) code.typeCheck(); + if( code._phase.ordinal() < CodeGen.Phase.LoopTree .ordinal() ) code.loopTree(); if( code._phase.ordinal() < CodeGen.Phase.Schedule .ordinal() ) code.GCM(); if( code._phase.ordinal() < CodeGen.Phase.LocalSched.ordinal() ) code.localSched(); // Set global, so don't have to pass everywhere @@ -159,6 +160,7 @@ public abstract class Eval2 { switch( prior ) { case StartNode start: F = new Frame(F); break; // Frame for the call to main, as-if Called case CallNode call : BB = call(call); break; + case NeverNode never: BB = never.cproj(0); break; // Synthetic exit is never taken. case IfNode iff : Object p = val(iff.pred()); BB = iff.cproj( p==null || (p instanceof Long x && x==0L) ? 1 : 0); break; case ReturnNode ret : if( clj(ret.rpc()).isStop() ) return exit(ret); else BB = ret(ret); break; case FunNode fun : if( loopCnt++ > timeout ) return null; break; // Timeout diff --git a/src/test/java/com/seaofnodes/simple/Main.java b/src/test/java/com/seaofnodes/simple/Main.java index f3a6784c..589c043b 100644 --- a/src/test/java/com/seaofnodes/simple/Main.java +++ b/src/test/java/com/seaofnodes/simple/Main.java @@ -5,7 +5,6 @@ import java.nio.file.Files; import java.nio.file.Path; public class Main { - public static final String PORTS = "com.seaofnodes.simple.node.cpus"; // Compile and run a simple program public static void main( String[] args ) throws Exception { // First arg is file, 2nd+ args are program args diff --git a/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java b/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java new file mode 100644 index 00000000..1ce2cd34 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java @@ -0,0 +1,180 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.isa.eval.EvalRisc5; + +import com.seaofnodes.isa.eval.EvalArm64; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegAllocTestSupport; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.cpus.arm.arm; +import com.seaofnodes.simple.node.cpus.riscv.riscv; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.Test; +import static org.junit.Assert.*; + +// Execution regressions are separate from the fixed spill-measurement cohorts. +public class MemoryEncodingTest { + @Test public void testLoopLoadSearch() { + String[] sources = { + """ + u8[] !program=new u8[106]; u8[] !out=new u8[0]; + int sum=0; + for(int i=0; i + n instanceof LoadNode ld && ld.ptr() instanceof ProjNode ptr && + ptr.in(0) instanceof NewNode nn && + nn.size()._type==com.seaofnodes.simple.type.TypeInteger.constant(110) ? ld : null)); + for( int arg=0; arg<7; arg++ ) { + long expected=switch(p) { + case 0 -> 106*arg+(arg>=2 ? 2 : 0); + case 1 -> (arg&1)==0 ? 140 : 60; + case 2 -> 130; + default -> 16; + }; + assertEquals(Long.toString(expected),Eval2.eval(code,arg)); + } + } + } + + private static final String[] SOURCES = { + Chapter16Test.CONSTRUCTOR_MEMORY, + Chapter18Test.CALL_MEMORY, + Chapter18Test.RECURSIVE_MEMORY, + Chapter18Test.INLINE_MEMORY, + "int[] !a = new int[7]; a[arg]=arg+3; return a[0]+1;", + """ + int[] !a = new int[7]; + for( int i=0; i<7; i++ ) a[i]=i+arg; + for( int i=0; i<6; i++ ) a[i+1]+=a[i]; + return a[6]; + """ + }; + + private static long expected(int program, long arg) { + return switch(program) { + case 0 -> arg==0 ? 111249 : 222351+arg; + case 1 -> arg==0 ? 10101325 : 20202322; + case 2 -> (10*(arg+1)+arg*(arg+1)/2)*100+10+arg; + case 3 -> 400944+arg*10001; + case 4 -> arg==0 ? 4 : 1; + case 5 -> 21+7*arg; + default -> throw new AssertionError(); + }; + } + + @Test public void testEmulatedMemory() throws IOException { + for( String target : new String[]{"riscv","arm"} ) + for( int p=0; p { + if( n instanceof MemMergeNode mem && + mem.in(0) instanceof CProjNode exit && exit.in(0) instanceof NeverNode ) { + assertTrue(mem.alias(2) instanceof MemPhiNode); + assertTrue(mem.alias(3) instanceof MemPhiNode); + return mem; + } + return null; + })); + code.instSelect(target,"SystemV").GCM().localSched().regAlloc(); + RegAllocTestSupport.checkRegisters(code); + code.encode(); + } + } + + @Test public void testNativeMemory() throws IOException { + Files.createDirectories(Path.of("build/objs")); + for( int p=0; p "+SOURCES[p]+" };", + TestC.CALL_CONVENTION,"",file+".c",file,"S","",-1); + } + } +} diff --git a/src/test/java/com/seaofnodes/simple/MergeSortTest.java b/src/test/java/com/seaofnodes/simple/MergeSortTest.java index 31fbe3a4..788bd9c2 100644 --- a/src/test/java/com/seaofnodes/simple/MergeSortTest.java +++ b/src/test/java/com/seaofnodes/simple/MergeSortTest.java @@ -1,67 +1,46 @@ package com.seaofnodes.simple; +import com.seaofnodes.isa.eval.EvalRisc5; + import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.node.cpus.riscv.riscv; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; public class MergeSortTest { - @Test - public void testMergeSort() { - String src = -""" -// based on the top-down version from https://en.wikipedia.org/wiki/Merge_sort - -val merge_sort = { int[] !a, int[] !b, int n -> - copy_array(a, 0, n, b); - split_merge(a, 0, n, b); -}; - -val split_merge = { int[] !b, int begin, int end, int[] a -> - if (end - begin <= 1) - return 0; - int middle = (end + begin) / 2; - split_merge(a, begin, middle, b); - split_merge(a, middle, end, b); - merge(b, begin, middle, end, a); - return 0; -}; - -val merge = { int[] !b, int begin, int middle, int end, int[] a -> - int i = begin, j = middle; - - for (int k = begin; k < end; k++) { - // && and || - bool cond = false; - if (i < middle) { - if (j >= end) cond = true; - else if (a[i] <= a[j]) cond = true; - } - if (cond) b[k] = a[i++]; - else b[k] = a[j++]; - } -}; - -val copy_array = { int[] !a, int begin, int end, int[] b -> - for (int k = begin; k < end; k++) - b[k] = a[k]; -}; - -int[] !a = new int[arg]; -int[] !b = new int[a#]; - -for (int i = 0; i < a#; i++) - a[i] = a# - i; - -merge_sort(a, b, a#); - -return a; -"""; - Chapter20Test.testCPU(src,"x86_64_v2", "SystemV",46,"Stop[ return mov(mov([int])); return 0; ]"); - Chapter20Test.testCPU(src,"riscv" , "SystemV",36,"Stop[ return mov(mov([int])); return 0; ]"); - Chapter20Test.testCPU(src,"arm" , "SystemV",37,"Stop[ return mov(mov([int])); return 0; ]"); -//assertEquals("int[ 1,2,3,4,5,6,7,8,9,10,11]", Eval2.eval(code, 11)); + @Test public void testMergeSort0() throws IOException { + int[] primes = new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, }; + SB sb = new SB().p(primes.length).p("["); + for( int prime : primes ) + sb.p(prime).p(", "); + String sprimes = sb.p("]").toString(); + TestC.run("sort", sprimes, 34); + + + EvalRisc5 R5 = TestRisc5.build("src/test/java/com/seaofnodes/simple/progs", "sort", "merge_sort", 0, 36, false); + // Allocate an array of primes, filled in reverse order + int ps = R5._heap; + R5._heap += (primes.length+1)*8; // 25 primes, and length + R5.st4(ps,primes.length); + for( int i=0; i [options] [--eval|--run ...] +Options: + --dump - dump final intermediate representation + --dump-after-parse - dump intermediate representation after parse + --dump-after-opto - dump intermediate representation after opto pass + --dump-after-type-check - dump intermediate representation after type check pass + --dump-after-instr-select - dump intermediate representation after instrution selection pass + --dump-after-gcm - dump intermediate representation after GCM pass + --dump-after-local-sched - dump intermediate representation after local scheduling pass + --dump-after-reg-alloc - dump intermediate representation after register allocation pass + --dump-after-encode - dump intermediate representation after encoding pass + --dump-after-all - dump intermediate representation after all passes + -S - dump generated assembler code + --eval ... - evaluate the compiled code in emulator + --run ... - run the compiled code natively + --dump-size - print the size of generated code + --dump-time - print compilation and execution times + --cpu - use specific CPU (x86_64_v2, riscv, arm) + --abi - use speific ABI variant (SystemV) + --target - print native CPU and ABI + --version + --help +""" +); + System.exit(0); + } + + static void bad_usage() { + System.err.println("ERROR: Invalid usage (use --help)"); + System.exit(1); + } + + static void target() { + if (system_cpu == null || system_abi == null) { + System.err.println("ERROR: Unknown target"); + System.exit(1); + } + System.out.print(system_cpu); + System.out.print("-"); + System.out.println(system_abi); + System.exit(0); + } + + static void dump(CodeGen code, int dump, int pass) { + if ((dump & pass) != 0) { + if ((dump & DUMP_PASS_NAME) != 0) { + System.err.println(switch (pass) { + case DUMP_AFTER_PARSE -> "After Parse:"; + case DUMP_AFTER_OPTO -> "After OPTO:"; + case DUMP_AFTER_TYPE_CHECK -> "After Type Check:"; + case DUMP_AFTER_LOOP_TREE -> "After Loop Tree:"; + case DUMP_AFTER_INSTR_SELECT -> "After Instruction Selection:"; + case DUMP_AFTER_GCM -> "After GCM:"; + case DUMP_AFTER_LOCAL_SCHED -> "After Local Scheduling:"; + case DUMP_AFTER_REG_ALLOC -> "After Register Allocation:"; + case DUMP_AFTER_ENCODE -> "After Instruction Encoding:"; + case DUMP_FINAL -> "Final:"; + default -> throw Utils.TODO(); + }); + } + + System.err.println(IRPrinter.prettyPrint(code._stop, 9999)); + + } + } + + static void print_compilation_times(CodeGen code) { + double t, total = 0; + + total += t = code._tParse / 1e3; + System.out.println(String.format("Parsing Time: %.3f sec", t)); + total += t = code._tOpto / 1e3; + System.out.println(String.format("Optimization Time: %.3f sec", t)); + total += t = code._tTypeCheck / 1e3; + System.out.println(String.format("Type Checking Time: %.3f sec", t)); + total += t = code._tLoopTree / 1e3; + System.out.println(String.format("Loop Tree Time: %.3f sec", t)); + total += t = code._tInsSel / 1e3; + System.out.println(String.format("Instruction Selection Time: %.3f sec", t)); + total += t = code._tGCM / 1e3; + System.out.println(String.format("GCM Time: %.3f sec", t)); + total += t = code._tLocal / 1e3; + System.out.println(String.format("Local Scheduling Time: %.3f sec", t)); + total += t = code._tRegAlloc / 1e3; + System.out.println(String.format("Register Allocation Time: %.3f sec", t)); + total += t = code._tEncode / 1e3; + System.out.println(String.format("Instruction Encoding Time: %.3f sec", t)); + System.out.println(String.format("TOTAL COMPILATION TIME: %.3f sec", total)); + } + + static String getInput() throws IOException { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) { + return reader.lines().collect(Collectors.joining("\n")); + } + } + + public static void main(String[] args) throws Exception { + String input_filename = null; + boolean use_stdin = false; + boolean do_eval = false; + boolean do_run = false; + boolean do_codegen = false; + boolean do_print_size = false; + boolean do_print_time = false; + int dump = 0; + int first_arg = 0; + String src = null; + String cpu = null; + String abi = null; + + // TODO: autodetect + system_cpu = "x86_64_v2"; + system_abi = "SystemV"; + + // Parse command line +loop: for (int i = 0; i < args.length; i++) { + String arg = args[i]; + if (arg.charAt(0) == '-') { + switch (arg) { + case "-": use_stdin = true; break; + case "--dump": dump |= DUMP_FINAL; break; + case "--dump-after-parse": dump |= DUMP_AFTER_PARSE; break; + case "--dump-after-opto": dump |= DUMP_AFTER_OPTO; break; + case "--dump-after-type-check": dump |= DUMP_AFTER_TYPE_CHECK; break; + case "--dump-after-loop-tree": dump |= DUMP_AFTER_LOOP_TREE; break; + case "--dump-after-instr-select": dump |= DUMP_AFTER_INSTR_SELECT; break; + case "--dump-after-gcm": dump |= DUMP_AFTER_GCM; break; + case "--dump-after-local-sched": dump |= DUMP_AFTER_LOCAL_SCHED; break; + case "--dump-after-reg-alloc": dump |= DUMP_AFTER_REG_ALLOC; break; + case "--dump-after-encode": dump |= DUMP_AFTER_ENCODE; break; + case "--dump-after-all": dump |= DUMP_AFTER_ALL; break; + case "-S": dump |= DUMP_DISASSEMBLE; break; + case "--eval": do_eval = true; first_arg = i + 1; break loop; + case "--run": do_run = true; first_arg = i + 1; break loop; + case "--dump-size": do_print_size = true; break; + case "--dump-time": do_print_time = true; break; + case "--cpu": if (cpu != null + || i + 1 >= args.length + || args[i + 1].charAt(0) == '-') bad_usage(); + cpu = args[i + 1]; + i++; + break; + case "--abi": if (abi != null + || i + 1 >= args.length + || args[i + 1].charAt(0) == '-') bad_usage(); + abi = args[i + 1]; + i++; + break; + case "--target": target(); + case "--version": throw Utils.TODO(); + case "--help": usage(); + default: bad_usage(); + } + } else { + if (input_filename != null || use_stdin) bad_usage(); + input_filename = arg; + } + } + + if (input_filename == null && !use_stdin) { + System.err.println("ERROR: no input file' (use --help)"); + System.exit(1); + } + + if (do_run || (dump & DUMP_DISASSEMBLE) != 0 || do_print_size) { + if (cpu == null) cpu = system_cpu; + if (abi == null) abi = system_abi; + if (cpu == null || abi == null) { + System.err.println("ERROR: Cannot compile for unknown target"); + System.exit(1); + } + do_codegen = true; + } + + // Read input file + try { + src = use_stdin ? getInput() : Files.readString(Path.of(input_filename)); + } catch(IOException e) { + System.err.println("ERROR: Cannot read input file"); + System.exit(1); + } + + // Compilation pipeline + CodeGen code = new CodeGen(src); + + code.parse(); + dump(code, dump, DUMP_AFTER_PARSE); + + code.opto(); + dump(code, dump, DUMP_AFTER_OPTO); + + code.typeCheck(); + dump(code, dump, DUMP_AFTER_TYPE_CHECK); + + code.loopTree(); + dump(code, dump, DUMP_AFTER_LOOP_TREE); + + if (do_codegen) { + code.instSelect(PORTS, cpu, abi); + dump(code, dump, DUMP_AFTER_INSTR_SELECT); + } + + code.GCM(); + dump(code, dump, DUMP_AFTER_GCM); + + code.localSched(); + dump(code, dump, DUMP_AFTER_LOCAL_SCHED); + + if (do_codegen) { + code.regAlloc(); + dump(code, dump, DUMP_AFTER_REG_ALLOC); + + code.encode(); + dump(code, dump, DUMP_AFTER_ENCODE); + } + + dump(code, dump, DUMP_FINAL); + + if (do_codegen && (dump & DUMP_DISASSEMBLE) != 0) { + System.out.println(code.asm()); + } + + if (do_print_size) { + System.out.println(String.format("Code Size: %d", code._encoding._bits.size())); + } + + if (do_print_time) { + print_compilation_times(code); + } + + if (do_eval) { + // TODO: Support for evaluation of functions with different argument numbers and types + long t = System.currentTimeMillis(); + long arg = (first_arg < args.length) ? Integer.valueOf(args[first_arg]) : 0; + System.out.println(Eval2.eval(code, arg, 100000)); + if (do_print_time) { + System.out.println(String.format("EXECUTION TIME: %.3f sec", + (System.currentTimeMillis() - t) / 1e3)); + } + } else if (do_run) { + if (cpu != system_cpu || abi != system_abi) { + System.err.println("ERROR: cannot run code on not native target"); + System.exit(1); + } + + throw Utils.TODO(); + } + } +} diff --git a/src/test/java/com/seaofnodes/simple/SpillStats.java b/src/test/java/com/seaofnodes/simple/SpillStats.java index 9b03da7b..318a6e33 100644 --- a/src/test/java/com/seaofnodes/simple/SpillStats.java +++ b/src/test/java/com/seaofnodes/simple/SpillStats.java @@ -11,6 +11,7 @@ import org.junit.runner.notification.*; public class SpillStats extends RunListener { private static SpillStats ACTIVE; private Description _test; + private int _qualityFailures; private final Map _totals = new TreeMap<>(); @Override public void testStarted(Description test) { _test = test; } @@ -25,8 +26,20 @@ public class SpillStats extends RunListener { code._regAlloc._spills+","+code._regAlloc._spillScaled); } + // Keep running every CPU and native result check when measuring quality. + // A changed golden still makes spill-stats fail at the end. + public static void checkSpills(int expected, int actual) { + if( expected==-1 ) return; + try { org.junit.Assert.assertEquals("Expect spills:",expected,actual,Math.max(1,expected>>3)); } + catch( AssertionError error ) { + if( ACTIVE==null ) throw error; + ACTIVE._qualityFailures++; + System.err.println(ACTIVE._test+": "+error.getMessage()); + } + } + public static void main(String[] args) throws ClassNotFoundException { - if( args.length==0 ) args = new String[]{"Chapter20Test","BrainFuckTest","MergeSortTest"}; + if( args.length==0 ) args = new String[]{"Chapter20Test","Chapter21Test","Chapter21AllocTest","BrainFuckTest","MergeSortTest"}; Class[] tests = new Class[args.length]; for( int i=0; i "x86_64_v2"; + default -> throw Utils.TODO("Map Yer CPU Port Here"); + }; + + public static void run( String file, int spills ) throws IOException { run(file,"",spills); } + + public static void run( String file, String expected, int spills ) throws IOException { + run("src/test/java/com/seaofnodes/simple/progs",file,expected,spills); + } + + // Compile and run a simple program + public static void run( String dir, String file, String expected, int spills ) throws IOException { + // Files + String cfile = dir+"/"+file+".c" ; + String sfile = dir+"/"+file+".smp"; + String efile = "build/objs/"+file; + + // Compile and export Simple + String src = Files.readString(Path.of(sfile)); + _run(src,CALL_CONVENTION,"",cfile,efile,"S",expected,spills); + } + + static void _run( String src, String simple_conv, String c_conv, String cfile, String efile, String xtn, String expected, int spills ) throws IOException { + String bin = efile+xtn; + String obj = bin+".o"; + // Compile simple, emit ELF + CodeGen code = new CodeGen(src).driver( CPU_PORT, simple_conv, obj); + com.seaofnodes.simple.codegen.RegAllocTestSupport.checkRegisters(code); + SpillStats.record(code,"Chapter21",CPU_PORT,simple_conv); + + // Compile the C program + var params = new String[] { + //if (USE_WSL) "wsl.exe"; + "gcc", + cfile, + obj, + "-lm", // Picks up 'sqrt' for newtonFloat tests to compare + "-g", + "-o", + bin, + "-D", + "CALL_CONV="+c_conv, + }; + Process gcc = new ProcessBuilder(params).redirectErrorStream(true).start(); + int error; + try { error = gcc.waitFor(); } catch( InterruptedException e ) { throw new IOException("interrupted"); } + String result = new String(gcc.getInputStream().readAllBytes()); + if( error!=0 ) { + System.err.println("gcc error code: "+error); + System.err.println(result); + } + assertEquals( 0, error ); + //assertTrue(result.isEmpty()); // No data in error stream + + // Execute results + Process smp = new ProcessBuilder(bin).redirectErrorStream(true).start(); + try { error = smp.waitFor(); } catch( InterruptedException e ) { throw new IOException("interrupted"); } + result = new String(smp.getInputStream().readAllBytes()); + if( error!=0 ) { + System.err.println("exec error code: "+error); + System.err.println(result); + } + assertEquals( 0, error ); + assertEquals(expected,result); + + // Allocation quality not degraded + SpillStats.checkSpills(spills,code._regAlloc._spillScaled); + + } +} diff --git a/src/test/java/com/seaofnodes/simple/TestRisc5.java b/src/test/java/com/seaofnodes/simple/TestRisc5.java new file mode 100644 index 00000000..1289575d --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/TestRisc5.java @@ -0,0 +1,51 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.isa.eval.EvalRisc5; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.node.FunNode; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.cpus.riscv.riscv; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import static org.junit.Assert.*; + +// Runs 32-bit R5 code in an emulator +public abstract class TestRisc5 { + + public static EvalRisc5 build( String file, int arg, int spills, boolean print ) throws IOException { + return build("src/test/java/com/seaofnodes/simple/progs",file, null, arg, spills, print); + } + + // Compile and run a simple program + public static EvalRisc5 build( String dir, String file, String main, int arg, int spills, boolean print ) throws IOException { + // Compile and export Simple + String src = Files.readString(Path.of(dir+"/"+file+".smp")); + CodeGen code = new CodeGen(src).driver("riscv", "SystemV",null); + com.seaofnodes.simple.codegen.RegAllocTestSupport.checkRegisters(code); + SpillStats.record(code,"Chapter21","riscv","SystemV"); + if( print ) { code.print_as_hex(); System.out.print(code.asm()); } + + // Allocation quality not degraded + SpillStats.checkSpills(spills,code._regAlloc._spillScaled); + + // Image + byte[] image = new byte[1<<20]; // A megabyte (1024*1024 bytes) + EvalRisc5 R5 = new EvalRisc5(image, 1<<16); + // Code at offset 0 + System.arraycopy(code._encoding.bits(), 0, image, 0, code._encoding.bits().length); + // Initial incoming int arg + R5.regs[riscv.A0] = arg; + // malloc memory starts at 64K and goes up + + // Look up starting PC or zero if not given + if( main!=null ) + for( Node use : code._start._outputs ) + if( use instanceof FunNode fun && main.equals(fun._name) ) + R5._pc = code._encoding._opStart[fun._nid]; + + return R5; + } + +} diff --git a/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java b/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java index 2966cb00..739eba3b 100644 --- a/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java +++ b/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java @@ -3,7 +3,6 @@ package com.seaofnodes.simple.codegen; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.node.cpus.x86_64_v2.x86_64_v2; import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; import static org.junit.Assert.*; // Small scheduled machine graphs isolate register constraints from peepholes. @@ -21,11 +20,11 @@ public class RegAllocTestSupport { @Override public RegMask killmap() { return _kill; } @Override public boolean isClone() { return _clone; } @Override public Node copy() { return new Op(_def,_use,_kill,_clone,(Node)null); } - @Override public int encoding(ByteArrayOutputStream bytes) { throw new AssertionError(); } + @Override public void encoding(Encoding enc) { throw new AssertionError(); } } private static CodeGen graph() { CodeGen code = new CodeGen(""); - code._mach = new x86_64_v2(); + code._mach = new x86_64_v2(code); code._cfg.add(code._start); return code; } @@ -192,4 +191,71 @@ public class RegAllocTestSupport { assertFalse("A fixed definition clobbers even before coloring",uncolored.sameBlockNoClobber(copy)); } + public static void copyForward() throws Exception { + // Forwarding, fixed operand, two-address tie, intervening clobber, + // another user, and a scheduling-only edge. + for( int kind=0; kind<6; kind++ ) { + CodeGen code = graph(); + RegAlloc alloc = new RegAlloc(code); + Op def = new Op(A,null,null,false,code._start); + alloc.newLRG(def)._reg=0; + SplitNode copy = code._mach.split("test",(byte)0,null); + copy.setDef(0,code._start); copy.setDef(1,def); + alloc.newLRG(copy)._reg=1; + if( kind==3 ) new Op(null,null,A,false,code._start); + int twoAddress = kind==2 ? 1 : 0; + RegMask mask = kind==1 ? B : kind==5 ? null : new RegMask(3L); + Op use = new Op(null,mask,null,false,code._start,copy) { + @Override public int twoAddress() { return twoAddress; } + }; + if( kind==4 ) new Op(null,B,null,false,code._start,copy); + var post = RegAlloc.class.getDeclaredMethod("postColor"); + post.setAccessible(true); post.invoke(alloc); + assertSame("copy case "+kind,kind==0 ? def : copy,use.in(1)); + assertEquals(kind==0 ? 0 : 1,alloc._spills); + } + } + + public static void coalescing() { + // Merge, incompatible masks, interference, capacity, and adjacency remapping. + for( int kind=0; kind<5; kind++ ) { + CodeGen code = graph(); + RegAlloc alloc = new RegAlloc(code); + Op def = new Op(new RegMask(3L),null,null,false,code._start); + SplitNode copy = code._mach.split("test",(byte)0,null); + copy.setDef(0,code._start); copy.setDef(1,def); + Op use = new Op(null,new RegMask(3L),null,false,code._start,copy); + LRG from = alloc.newLRG(def), to = alloc.newLRG(copy); + from._mask = kind==1 ? A : new RegMask(3L); + to._mask = kind==1 ? B : new RegMask(3L); + from.machDef(def,false); from.machUse(copy,(short)1,false); + to.machDef(copy,false); to.machUse(use,(short)1,false); + LRG left = new LRG((short)100), right = new LRG((short)101); + if( kind==2 ) { from.addNeighbor(to); to.addNeighbor(from); } + if( kind>=3 ) { + from.addNeighbor(left); left.addNeighbor(from); + if( kind==3 ) { to.addNeighbor(right); right.addNeighbor(to); } + else { to.addNeighbor(left); left.addNeighbor(to); } + } + int fcnt=from.nadj(), tcnt=to.nadj(); + assertTrue(Coalesce.coalesce(0,alloc)); + if( kind==0 || kind==4 ) { + assertSame(def,use.in(1)); + assertSame(alloc.lrg(def),alloc.lrg(copy)); + assertSame(use,alloc.lrg(def)._machUse); + assertEquals(1,alloc.lrg(def)._uidx); + if( kind==4 ) { + assertEquals(1,left.nadj()); + assertSame(alloc.lrg(def),left._adj.at(0)); + assertSame(left,alloc.lrg(def)._adj.at(0)); + } + } else { + assertSame(copy,use.in(1)); + assertNotSame(alloc.lrg(def),alloc.lrg(copy)); + assertEquals(fcnt,from.nadj()); + assertEquals(tcnt,to.nadj()); + } + } + } + } diff --git a/src/test/java/com/seaofnodes/simple/codegen/X86EncodingTest.java b/src/test/java/com/seaofnodes/simple/codegen/X86EncodingTest.java new file mode 100644 index 00000000..368c243b --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/codegen/X86EncodingTest.java @@ -0,0 +1,97 @@ +package com.seaofnodes.simple.codegen; + +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.cpus.x86_64_v2.CmpMemX86; +import com.seaofnodes.simple.node.cpus.x86_64_v2.NotX86; +import org.junit.Test; +import static org.junit.Assert.*; + +public class X86EncodingTest { + + @Test + public void testLogicalNotRegisters() { + CodeGen code = new CodeGen("return !arg;") + .driver(CodeGen.Phase.InstSelect, "x86_64_v2", "SystemV"); + var not = code._stop.walk(n -> n instanceof NotX86 ? (NotX86)n : null); + assertNotNull("Must exercise logical not", not); + int[][] registers = {{0,0}, {1,1}, {7,7}, {9,9}, {1,9}, {9,1}, {6,0}, {15,8}}; + // TEST src,src; SETZ dstb; MOVZX dst32,dstb. Include overlapping + // registers, low-byte registers requiring REX, and extended registers. + String[] expected = { + "4885c00f94c00fb6c0", "4885c90f94c10fb6c9", + "4885ff400f94c7400fb6ff", "4d85c9410f94c1450fb6c9", + "4d85c90f94c10fb6c9", "4885c9410f94c1450fb6c9", + "4885c0400f94c6400fb6f6", "4d85c0410f94c7450fb6ff" + }; + for (int i = 0; i < registers.length; i++) { + int dst = registers[i][0], src = registers[i][1]; + var enc = new FixedRegisterEncoding(code, not, dst, not.in(1), src); + not.encoding(enc); + assertArrayEquals("Logical not dst=" + dst + ", src=" + src, + java.util.HexFormat.of().parseHex(expected[i]), enc._bits.toByteArray()); + } + } + + @Test + public void testLogicalNotExecution() throws java.io.IOException { + var dir = java.nio.file.Path.of("build/objs"); + java.nio.file.Files.createDirectories(dir); + java.nio.file.Files.writeString(dir.resolve("x86LogicalNot.smp"), + "val logicalNot = { int x -> return !x; };"); + java.nio.file.Files.writeString(dir.resolve("x86LogicalNot.c"), """ + #include + extern long long logicalNot(long long); + int main(void) { + long long inputs[] = {0, 1, -1, LLONG_MIN, LLONG_MAX, 1LL << 32}; + for (unsigned i = 0; i < sizeof(inputs)/sizeof(inputs[0]); i++) + if (logicalNot(inputs[i]) != (inputs[i] == 0)) return 1; + return 0; + } + """); + com.seaofnodes.simple.TestC.run("build/objs", "x86LogicalNot", "", -1); + } + @Test + public void testMemoryCompare64Immediate32() { + for (int immediate : new int[]{123456789, -123456789}) { + String src = "int[] !a = new int[2]; a[0] = arg; return a[arg & 1] == " + immediate + ";"; + CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Encoding, "x86_64_v2", "SystemV"); + var cmp = code._stop.walk(n -> n instanceof CmpMemX86 ? n : null); + assertNotNull("Must exercise a memory comparison", cmp); + var enc = code._encoding; + int start = enc._opStart[cmp._nid]; + + // REX.W, 81 /7, SIB with scale 8, disp8, and a sign-extended imm32. + assertEquals(0x48, enc.read1(start) & 0xF8); + assertEquals(0x81, enc.read1(start + 1)); + assertEquals(0x7C, enc.read1(start + 2)); + assertEquals(0xC0, enc.read1(start + 3) & 0xC0); + assertEquals(8, enc.read1(start + 4)); + assertEquals(immediate, enc.read4(start + 5)); + assertEquals("CMP r/m64 uses four immediate bytes", 9, enc._opLen[cmp._nid]); + + // SETE must immediately follow the CPU's nine-byte CMP instruction. + assertEquals(0x0F, enc.read1(start + 9)); + assertEquals(0x94, enc.read1(start + 10)); + } + } + + /** Exercise instruction encoders with explicit registers, independent of allocator choices. */ + private static class FixedRegisterEncoding extends Encoding { + private final Node _dst, _src; + private final short _dstReg, _srcReg; + + FixedRegisterEncoding(CodeGen code, Node dst, int dstReg, Node src, int srcReg) { + super(code); + _dst = dst; + _src = src; + _dstReg = (short)dstReg; + _srcReg = (short)srcReg; + } + + @Override public short reg(Node n) { + if (n == _dst) return _dstReg; + if (n == _src) return _srcReg; + throw new AssertionError("Unexpected register lookup"); + } + } +} diff --git a/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java b/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java index 4bfa7628..7ba9272f 100644 --- a/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java +++ b/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java @@ -248,20 +248,20 @@ public class Scheduler { assert data.block != null; assert data.users == 0; - if (data.node instanceof LoadNode l) { + if (data.node instanceof MemOpNode l && l._isLoad) { assert isValid(data); // Handle anti-deps of load nodes. // At this point all anti-dep nodes are scheduled, // but they did not refine the placement // so do that now. - var mem = l.in(1); + var mem = data.node.in(1); for(var out : mem._outputs) { if (out instanceof PhiNode p) { var r = p.in(0); for (int i = 1; i < p.nIns(); i++) { if (p.in(i) == mem) optionalRefinePlacement(data, r.in(i)); } - } else if (!(out instanceof LoadNode)) { + } else if (!(out instanceof MemOpNode ld && ld._isLoad)) { optionalRefinePlacement(data, out); } } @@ -273,11 +273,11 @@ public class Scheduler { for(var in:data.node._inputs) { if (in!=null) update(d(in), data.block); } - if (data.node instanceof StoreNode s) { + if (data.node instanceof MemOpNode s && !s._isLoad) { // Store nodes have anti-deps to load nodes. // So decrease the uses of these loads when the store is placed. - for (var out: s.in(1)._outputs) { - if (out instanceof LoadNode) od(out).ifPresent(this::decUsers); + for (var out: ((Node)s).in(1)._outputs) { + if (out instanceof MemOpNode ld && ld._isLoad) od(out).ifPresent(this::decUsers); } } } @@ -471,14 +471,14 @@ public class Scheduler { } else { for (var in : node._inputs) if (in != null && !(in instanceof CFGNode)) markAlive(dataQueue, in, false); } - if (node instanceof StoreNode) mem.push(data); + if (node instanceof MemOpNode s && !s._isLoad) mem.push(data); } // Handle store nodes and increase load with an anti-dep to the store. while (!mem.isEmpty()) { var data = mem.pop(); node = data.node; for(var out:node.in(1)._outputs) { - if (out instanceof LoadNode) od(out).ifPresent(d->d.users++); + if (out instanceof MemOpNode ld && ld._isLoad) od(out).ifPresent(d->d.users++); } } } diff --git a/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java b/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java index a2137314..b2be5b5f 100644 --- a/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java +++ b/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java @@ -101,7 +101,7 @@ class FuzzerUtils { System.setErr(NULL_PRINT_STREAM); System.setOut(NULL_PRINT_STREAM); var code = new CodeGen(script, TypeInteger.BOT, workListSeed); - return code.parse().opto().typeCheck().GCM().localSched(); + return code.driver(CodeGen.Phase.LocalSched); } finally { System.setErr(err); System.setOut(out); diff --git a/src/test/java/com/seaofnodes/simple/progs/antiDep1.smp b/src/test/java/com/seaofnodes/simple/progs/antiDep1.smp new file mode 100644 index 00000000..52bfe29b --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/antiDep1.smp @@ -0,0 +1,11 @@ +// -*- mode: java; -*- +struct S { int f; }; +var v0 = new S; +!S? !v1; +if (arg) v1 = new S; +if (v1) { + v0.f = v1.f; +} else { + v0.f = 2; +} +return v0; diff --git a/src/test/java/com/seaofnodes/simple/progs/arg_count.c b/src/test/java/com/seaofnodes/simple/progs/arg_count.c new file mode 100644 index 00000000..e247e28a --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/arg_count.c @@ -0,0 +1,10 @@ +#include + +__attribute__ ((CALL_CONV)) +extern double addAll( long long i0, double f1, long long i2, double f3, long long i4, double f5, long long i6, double f7, long long i8, double f9, long long i10, double f11, long long i12, double f13, long long i14, double f15, long long i16, double f17, long long i18, double f19 ); + +int main() { + double result = addAll( 0, 1.1, 2, 3.1, 4, 5.1, 6, 7.1, 8, 9.1, 10, 11.1, 12, 13.1, 14, 15.1, 16, 17.1, 18, 19.1 ); + printf("%f\n", result); + return 0; +} diff --git a/src/test/java/com/seaofnodes/simple/progs/arg_count.smp b/src/test/java/com/seaofnodes/simple/progs/arg_count.smp new file mode 100644 index 00000000..6b6bc0ae --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/arg_count.smp @@ -0,0 +1,6 @@ +// -*- mode: java; -*- +val addAll = { int i0, flt f1, int i2, flt f3, int i4, flt f5, int i6, flt f7, int x8, flt f9, int i10, flt f11, int i12, flt f13, int i14, flt f15, int x16, flt f17 int x18, flt f19 -> + return + i0 + f1+ i2+ f3+ i4+ f5+ i6+ f7+ x8 +f9 + + i10+f11+i12+f13+i14+f15+x16+f17+x18+f19 ; +}; diff --git a/src/test/java/com/seaofnodes/simple/progs/array1.smp b/src/test/java/com/seaofnodes/simple/progs/array1.smp new file mode 100644 index 00000000..6b10f92c --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/array1.smp @@ -0,0 +1,9 @@ +// -*- mode: java; -*- +int[] !ary = new int[arg]; +// Fill [0,1,2,3,4,...] +for( int i=0; i +#include + +typedef struct AryInt { + unsigned int len; + unsigned char data[]; +} AryInt; + +__attribute__ ((CALL_CONV)) +extern AryInt* brain_fuck(); + +int main() { + AryInt* ps = brain_fuck(); + + printf("%.*s", ps->len, ps->data); + + return 0; +} diff --git a/src/test/java/com/seaofnodes/simple/progs/brain_fuck.smp b/src/test/java/com/seaofnodes/simple/progs/brain_fuck.smp new file mode 100644 index 00000000..ed90cb84 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/brain_fuck.smp @@ -0,0 +1,46 @@ +// -*- mode: java; -*- +val brain_fuck = { -> + u8[] !program = new u8[106];program[0] = 43;program[1] = 43;program[2] = 43;program[3] = 43;program[4] = 43;program[5] = 43;program[6] = 43;program[7] = 43;program[8] = 91;program[9] = 62;program[10] = 43;program[11] = 43;program[12] = 43;program[13] = 43;program[14] = 91;program[15] = 62;program[16] = 43;program[17] = 43;program[18] = 62;program[19] = 43;program[20] = 43;program[21] = 43;program[22] = 62;program[23] = 43;program[24] = 43;program[25] = 43;program[26] = 62;program[27] = 43;program[28] = 60;program[29] = 60;program[30] = 60;program[31] = 60;program[32] = 45;program[33] = 93;program[34] = 62;program[35] = 43;program[36] = 62;program[37] = 43;program[38] = 62;program[39] = 45;program[40] = 62;program[41] = 62;program[42] = 43;program[43] = 91;program[44] = 60;program[45] = 93;program[46] = 60;program[47] = 45;program[48] = 93;program[49] = 62;program[50] = 62;program[51] = 46;program[52] = 62;program[53] = 45;program[54] = 45;program[55] = 45;program[56] = 46;program[57] = 43;program[58] = 43;program[59] = 43;program[60] = 43;program[61] = 43;program[62] = 43;program[63] = 43;program[64] = 46;program[65] = 46;program[66] = 43;program[67] = 43;program[68] = 43;program[69] = 46;program[70] = 62;program[71] = 62;program[72] = 46;program[73] = 60;program[74] = 45;program[75] = 46;program[76] = 60;program[77] = 46;program[78] = 43;program[79] = 43;program[80] = 43;program[81] = 46;program[82] = 45;program[83] = 45;program[84] = 45;program[85] = 45;program[86] = 45;program[87] = 45;program[88] = 46;program[89] = 45;program[90] = 45;program[91] = 45;program[92] = 45;program[93] = 45;program[94] = 45;program[95] = 45;program[96] = 45;program[97] = 46;program[98] = 62;program[99] = 62;program[100] = 43;program[101] = 46;program[102] = 62;program[103] = 43;program[104] = 43;program[105] = 46; + int d = 0; + u8[] !output = new u8[0]; + u8[] !data = new u8[100]; + + for( int pc = 0; pc < program#; pc++ ) { + var command = program[pc]; + if (command == 62) { + d++; + } else if (command == 60) { + d--; + } else if (command == 43) { + data[d]++; + } else if (command == 45) { + data[d]--; + } else if (command == 46) { + // Output a byte; increase the output array size + var old = output; + output = new u8[output# + 1]; + for( int i = 0; i < old#; i++ ) + output[i] = old[i]; + output[old#] = data[d]; // Add the extra byte on the end + } else if (command == 44) { + data[d] = 42; + } else if (command == 91) { + if (data[d] == 0) { + for( int d = 1; d > 0; ) { + command = program[++pc]; + if (command == 91) d++; + if (command == 93) d--; + } + } + } else if (command == 93) { + if (data[d]) { + for( int d = 1; d > 0; ) { + command = program[--pc]; + if (command == 93) d++; + if (command == 91) d--; + } + } + } + } + return output; +}; diff --git a/src/test/java/com/seaofnodes/simple/progs/fcn.smp b/src/test/java/com/seaofnodes/simple/progs/fcn.smp deleted file mode 100644 index 43d398b3..00000000 --- a/src/test/java/com/seaofnodes/simple/progs/fcn.smp +++ /dev/null @@ -1,15 +0,0 @@ - -struct Person { - int age; -}; - -val fcn = { !Person?[] ps, int x -> - val tmp = ps[x]; - if( ps[x] ) - ps[x].age++; -}; - -!Person?[] !ps = new !Person?[2]; -ps[0] = new Person; -ps[1] = new Person; -fcn(ps,1); diff --git a/src/test/java/com/seaofnodes/simple/progs/fcn2.smp b/src/test/java/com/seaofnodes/simple/progs/fcn2.smp deleted file mode 100644 index 049132d9..00000000 --- a/src/test/java/com/seaofnodes/simple/progs/fcn2.smp +++ /dev/null @@ -1,5 +0,0 @@ - -val fcn = (arg&1) - ? { int x -> x*x; } - : { int x -> x+x; }; -fcn(arg); diff --git a/src/test/java/com/seaofnodes/simple/progs/fcn3.smp b/src/test/java/com/seaofnodes/simple/progs/fcn3.smp deleted file mode 100644 index 55b74455..00000000 --- a/src/test/java/com/seaofnodes/simple/progs/fcn3.smp +++ /dev/null @@ -1,42 +0,0 @@ - -//val fact = { int n -> n <= 1 ? 1 : fact(n-1)*n; } -struct LL { !LL? !next; int payload; }; - - -struct String { - - val _hashCodeString = { -> - int hash=0; - for( int i=0; i< cs#; i++ ) - hash = hash*31 + cs[i]; - if( !hash ) hash = 123456789; - return hash; - }; - - val equals = { !String s -> - if( self == s ) return true; - if( cs# != s.cs# ) return false; - for( int i=0; i< #; i++ ) - if( [i] != s.cs[i] ) - return false; - return true; - }; - - int _hashCode = hash(); - u8[] cs; -}; - - - -val hashCode= { !String self -> - int hash = self._hashCode - ? self._hashCode - : self._hashCode = _hashCodeString(self); - return hash; -}; - - -String s = new String { cs = new u8[17]; }; -s.cs[0] = 67; // C -s.cs[1] = 108; // l -hashCode(s); diff --git a/src/test/java/com/seaofnodes/simple/progs/fib.c b/src/test/java/com/seaofnodes/simple/progs/fib.c new file mode 100644 index 00000000..263d2f13 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/fib.c @@ -0,0 +1,16 @@ +#include +#include + + +__attribute__ ((CALL_CONV)) +extern int fib(int x); + +int main() { + printf("["); + for (int i = 0; i < 10; i++) { + printf("%d", fib(i)); + if (i < 9) printf(", "); + } + printf("]"); + return 0; +} \ No newline at end of file diff --git a/src/test/java/com/seaofnodes/simple/progs/fib.smp b/src/test/java/com/seaofnodes/simple/progs/fib.smp new file mode 100644 index 00000000..1512541b --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/fib.smp @@ -0,0 +1,11 @@ +// -*- mode: java; -*- +val fib = {int n -> + int f1=1; + int f2=1; + while( n-- > 1 ){ + int temp = f1+f2; + f1=f2; + f2=temp; + } + return f2; +}; \ No newline at end of file diff --git a/src/test/java/com/seaofnodes/simple/progs/jig.smp b/src/test/java/com/seaofnodes/simple/progs/jig.smp new file mode 100644 index 00000000..1daeee09 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/jig.smp @@ -0,0 +1,2 @@ +// -*- mode: java; -*- +return 0; diff --git a/src/test/java/com/seaofnodes/simple/progs/loop2.c b/src/test/java/com/seaofnodes/simple/progs/loop2.c new file mode 100644 index 00000000..934f2d89 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/loop2.c @@ -0,0 +1,7 @@ + +__attribute__ ((CALL_CONV)) +extern int loop2(); + +int main() { + return loop2(); +} diff --git a/src/test/java/com/seaofnodes/simple/progs/loop2.smp b/src/test/java/com/seaofnodes/simple/progs/loop2.smp new file mode 100644 index 00000000..01ec827e --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/loop2.smp @@ -0,0 +1,10 @@ +// -*- mode: java; -*- +int i = 0; +while(true) { + i += 1; + if( i==0 ) continue; + if( i==1 ) continue; + if( i==2 ) break; + continue; +} +return i; diff --git a/src/test/java/com/seaofnodes/simple/progs/newtonFloat.c b/src/test/java/com/seaofnodes/simple/progs/newtonFloat.c new file mode 100644 index 00000000..7e7e796d --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/newtonFloat.c @@ -0,0 +1,16 @@ +#include +#include +#include + +extern double test_sqrt(double); + +int main( int argc, char** argv ) { + for( int i=0; i<10; i++ ) { + double d = test_sqrt((double)i), expect = sqrt((double)i); + double delta = fabs(d-expect); + printf("%d %f (%g)\n",i,d,delta); + if( delta > 1e-15 ) + return 1; + } + return 0; +} diff --git a/src/test/java/com/seaofnodes/simple/progs/newtonFloat.smp b/src/test/java/com/seaofnodes/simple/progs/newtonFloat.smp new file mode 100644 index 00000000..be2dffec --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/newtonFloat.smp @@ -0,0 +1,12 @@ +// -*- mode: java; -*- +// Newtons approximation to the square root +val test_sqrt = { flt x -> + flt epsilon = 1e-15; + flt guess = x; + while( 1 ) { + flt next = (x/guess + guess)/2; + if( guess-epsilon <= next & next <= guess+epsilon ) return guess; + //if( guess==next ) return guess; + guess = next; + } +}; diff --git a/src/test/java/com/seaofnodes/simple/progs/no_stack_arg_count.smp b/src/test/java/com/seaofnodes/simple/progs/no_stack_arg_count.smp new file mode 100644 index 00000000..20a05445 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/no_stack_arg_count.smp @@ -0,0 +1,6 @@ +// -*- mode: java; -*- +val addAll = { int i0, flt f1, int i2, flt f3, int i4, flt f5, int i6, flt f7, int x8, flt f9, int i10, flt f11, int i12, flt f13, int i14, flt f15 -> + return + i0 + f1+ i2+ f3+ i4+ f5+ i6+ f7+ x8 +f9 + + i10+f11+i12+f13+i14+f15; +}; diff --git a/src/test/java/com/seaofnodes/simple/progs/person.c b/src/test/java/com/seaofnodes/simple/progs/person.c new file mode 100644 index 00000000..df95e1ca --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/person.c @@ -0,0 +1,34 @@ +#include +#include +#include + +typedef struct Person { + int age; +} Person; + +typedef struct PersonArray { + unsigned int length; + int pad; + Person* data[]; +} PersonArray; + +__attribute__ ((CALL_CONV)) +extern void fcn(PersonArray* people, int index); + +int main() { + Person base[5] = {{25},{1},{18},{45},{60}}; + unsigned char buf[8+8*5]; + PersonArray* people_array = (PersonArray*)buf; + people_array->length=5; + for (int i=0; ilength; i++) people_array->data[i] = &base[i]; + + fcn(people_array, 1); + fcn(people_array, 1); + fcn(people_array, 1); + fcn(people_array, 1); + fcn(people_array, 1); + + printf("%d\n", people_array->data[1]->age); // expected: 6 + + return 0; +} \ No newline at end of file diff --git a/src/test/java/com/seaofnodes/simple/progs/person.smp b/src/test/java/com/seaofnodes/simple/progs/person.smp new file mode 100644 index 00000000..b5e6ee90 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/person.smp @@ -0,0 +1,9 @@ +// -*-java-*- +struct Person { + int age; +}; + +val fcn = { !Person?[] ps, int x -> + if( ps[x] ) + ps[x].age++; +}; \ No newline at end of file diff --git a/src/test/java/com/seaofnodes/simple/progs/sieve.c b/src/test/java/com/seaofnodes/simple/progs/sieve.c new file mode 100644 index 00000000..cac4cfa3 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/sieve.c @@ -0,0 +1,19 @@ +#include +#include + +typedef struct AryInt { + unsigned int len; + unsigned int data[]; +} AryInt; + +__attribute__ ((CALL_CONV)) +extern AryInt *sieve(int N); + +int main() { + AryInt* ps = sieve(100); + printf("%d[",ps->len); + for( int i=0; ilen; i++ ) + printf("%d, ",ps->data[i]); + printf("]"); + return 0; +} diff --git a/src/test/java/com/seaofnodes/simple/progs/sieve.smp b/src/test/java/com/seaofnodes/simple/progs/sieve.smp index 0788e6c5..569dbb96 100644 --- a/src/test/java/com/seaofnodes/simple/progs/sieve.smp +++ b/src/test/java/com/seaofnodes/simple/progs/sieve.smp @@ -1,32 +1,31 @@ - -int[] !ary = new int[arg], !primes = new int[arg]; -int nprimes = 0, p=2; -// Find primes while p^2 < arg -while( p*p < arg ) { - // skip marked non-primes - while( ary[p]==1 ) p = p + 1; - // p is now a prime - primes[nprimes] = p; nprimes = nprimes + 1; - // Mark out the rest non-primes - int i = p + p; - while( i < ary# ) { - ary[i] = 1; - i = i + p; - } - p = p + 1; -} -// Now just collect the remaining primes, no more marking -while( p < arg ) { - if( ary[p] == 0 ) { - primes[nprimes] = p; nprimes = nprimes + 1; +// -*- mode: java; -*- +val sieve = { int N -> + // The main Sieve array + bool[] !ary = new bool[N]; + // The primes less than N + u32[] !primes = new u32[N>>1]; + // Number of primes so far, searching at index p + int nprimes = 0, p=2; + // Find primes while p^2 < N + while( p*p < N ) { + // skip marked non-primes + while( ary[p] ) p++; + // p is now a prime + primes[nprimes++] = p; + // Mark out the rest non-primes + for( int i = p + p; i < ary#; i+= p ) + ary[i] = true; + p++; } - p = p + 1; -} -// Copy/shrink the result array -int[] !rez = new int[nprimes]; -int j = 0; -while( j < nprimes ) { - rez[j] = primes[j]; - j = j + 1; -} -return rez; + + // Now just collect the remaining primes, no more marking + for( ; p < N; p++ ) + if( !ary[p] ) + primes[nprimes++] = p; + + // Copy/shrink the result array + u32[] !rez = new u32[nprimes]; + for( int j=0; j < nprimes; j++ ) + rez[j] = primes[j]; + return rez; +}; diff --git a/src/test/java/com/seaofnodes/simple/progs/sort.c b/src/test/java/com/seaofnodes/simple/progs/sort.c new file mode 100644 index 00000000..ad76cc7f --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/sort.c @@ -0,0 +1,28 @@ +#include +#include + +// Test wrapper to call `sort.smp` + +typedef struct AryInt { + unsigned int len; + long long data[]; +} AryInt; + +__attribute__ ((CALL_CONV)) +extern AryInt* merge_sort(AryInt*); + + +long long primes[] = { 25, 97, 89, 83, 79, 73, 71, 67, 61, 59, 53, 47, 43, 41, 37, 31, 29, 23, 19, 17, 13, 11, 7, 5, 3, 2 }; + +int main() { + AryInt *as = (AryInt*)primes; + + merge_sort(as); + + printf("%d[",as->len); + for( int i=0; ilen; i++ ) + printf("%d, ",as->data[i]); + printf("]"); + + return 0; +} \ No newline at end of file diff --git a/src/test/java/com/seaofnodes/simple/progs/sort.smp b/src/test/java/com/seaofnodes/simple/progs/sort.smp new file mode 100644 index 00000000..ae3f4f23 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/sort.smp @@ -0,0 +1,38 @@ +// -*- mode: java; -*- +// based on the top-down version from https://en.wikipedia.org/wiki/Merge_sort + +val merge_sort = { int[] a -> + int[] !b = new int[a#]; + copy_array (a, 0, a#, b); + split_merge(a, 0, a#, b); +}; + +val split_merge = { int[] b, int begin, int end, int[] a -> + if (end - begin <= 1) + return 0; + int middle = (end + begin) / 2; + split_merge(a, begin, middle, b); + split_merge(a, middle, end, b); + merge(b, begin, middle, end, a); + return 0; +}; + +val merge = { int[] b, int begin, int middle, int end, int[] a -> + int i = begin, j = middle; + + for (int k = begin; k < end; k++) { + // && and || + bool cond = false; + if (i < middle) { + if (j >= end) cond = true; + else if (a[i] <= a[j]) cond = true; + } + if (cond) b[k] = a[i++]; + else b[k] = a[j++]; + } +}; + +val copy_array = { int[] a, int begin, int end, int[] b -> + for (int k = begin; k < end; k++) + b[k] = a[k]; +}; diff --git a/src/test/java/com/seaofnodes/simple/progs/stringHash.c b/src/test/java/com/seaofnodes/simple/progs/stringHash.c new file mode 100644 index 00000000..2c6d0fa9 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/stringHash.c @@ -0,0 +1,30 @@ +#include +#include + +typedef struct ArrayU8 { + unsigned int length; + unsigned char data[]; +} ArrayU8; +typedef struct String { + ArrayU8 * cs; + long long _hashCode; +} String; + + +__attribute__ ((CALL_CONV)) +extern bool equals(String*st1, String*st2); + +int main() { + String test1 = {.cs=(ArrayU8*)"\x04\x00\x00\x00test"}; + String test2 = {.cs=(ArrayU8*)"\x05\x00\x00\x00test1"}; + + if( equals(&test1, &test2) ) { + printf("Expected test1 and test2 unequal"); + return 1; + } + if( !equals(&test2, &test2) ) { + printf("Expected test2 and test2 equal"); + return 1; + } + return 0; +} diff --git a/src/test/java/com/seaofnodes/simple/progs/stringHash.smp b/src/test/java/com/seaofnodes/simple/progs/stringHash.smp new file mode 100644 index 00000000..257f79f9 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/stringHash.smp @@ -0,0 +1,31 @@ +// -*- mode: java; -*- +struct String { + u8[] cs; + int _hashCode; +}; + +// Compare two Strings +val equals = { !String !self, !String s -> + if( self == s ) return true; + if( self.cs# != s.cs# ) return false; + for( int i=0; i< self.cs#; i++ ) + if( self.cs[i] != s.cs[i] ) + return false; + return true; +}; + +// Return the String hashCode (cached, and never 0) +val hashCode = { !String self -> + self._hashCode + ? self._hashCode + : (self._hashCode = _hashCodeString(self)); +}; + +val _hashCodeString = { !String self -> + int hash=0; + if( self.cs ) + for( int i=0; i< self.cs#; i++ ) + hash = hash*31 + self.cs[i]; + if( !hash ) hash = 123456789; + return hash; +}; -- cgit v1.2.3