summaryrefslogtreecommitdiff
path: root/chapter21/src/main/java/com
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2025-04-24 14:38:36 -0700
committerCliff Click <cliffc@acm.org>2025-04-24 14:38:36 -0700
commit5d026780154ddd9ca111303df4dce9eac1c2d1c2 (patch)
treeed6e78ca34eee5a22e4a2c3c31c063d30bbdc92d /chapter21/src/main/java/com
parent94f43b86a3240c0af1db2b0fd0429e6ef98d04b7 (diff)
downloadSimple-5d026780154ddd9ca111303df4dce9eac1c2d1c2.tar.gz
Simple-5d026780154ddd9ca111303df4dce9eac1c2d1c2.zip
Squashed commit of the following:
commit 37da12391989265ae9560e6b08d1d309ac961db5 Merge: 7357dd1a 94f43b86 Author: Cliff Click <cliffc@acm.org> Date: Thu Apr 24 14:37:32 2025 -0700 Merge branch 'main' into ch21 commit 7357dd1af9e7d4a2355d82a3e42a0123f465bd28 Author: Cliff Click <cliffc@acm.org> Date: Thu Apr 24 12:14:00 2025 -0700 minor deps elide commit 811f17eb70afa23ed58595518e1c9fb8d93a6d76 Author: Cliff Click <cliffc@acm.org> Date: Thu Apr 24 11:25:03 2025 -0700 inf loop tree fixes double-ended infinite loops slightly wrong loop tree commit 282d976283fc75f001b8446302f75d349c2a53d5 Author: Cliff Click <cliffc@acm.org> Date: Thu Apr 24 10:21:03 2025 -0700 Minor formatting. Replace if-trees with array lookups or expression switch. Same CALLOC sentinel for all commit a6b76731cd72fb1edef02b6b449ba7f10bb73167 Author: Tamás Hadházy <85063808+Hels15@users.noreply.github.com> Date: Thu Apr 24 17:36:30 2025 +0100 arm eval calloc sentinel (#210) * fixed main pr problems * regmask extra comments * Clean pass normal tests New fcn print upgrades several golden rule tests. Move the unlink-all from the walk::err call (which IS used during IterPeeps) to GCM. CallEnd upgrades return type based on constant call. Fix unlink to also unlink CallEnd Constant TFP print checks constant Tested an untested Load * Arm basics, minor changes in riscv (#160) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java * add riscv port to readme * arm init * work in progress * arm2 bs * work in progress * clearnup arm * fixed main pr problems * regmask extra comments --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Update Chapter20Test.java * Weaken range-check test since no range checks... test cannot fail (pass) * Fix issue #161 Error adding 'null' and '5' * bug fixs, cloning constants Fix bug missing Find. Fix bug setDef does unordered removal from basic blocks. Use cloned constants for spilling. * RA fixes SplitSelfConflict MUST split before uses and after defs. Split global constants across functions Missing find in union; Inserting split after new inserted wrong place Pre-remove null LRGs before color. Bias color hunts through Phis Shorten InstructionSelection. Rename !unified() to leader() AddMemX86 is 2-addr Add CastXXX to preserve register across cast. * encoding init * encoding init 2 * rex fix * indirect addressing modes * setop change opcode, rename FLOAT_OFFSET * mask sib and modrm with 3 bits, 4th bit goes into rex. * sign extend div with cqo * idiv input is fixed to rax and RMASK, grab input from second slot when encoding * encoding of newx86 * tfp and CallEndNode encoding * sib improvement * more cleanups * more cleanups * Allow a killMap to kill regs. Bugfix scheduler. Bugfix spiller - self-conflict order depended on hashtable iteration order, which depended on System.hashCode which would vary run to run. Improvements to coloring Improvements to spill quality testing * Added flags-kill for XOR * Missing add-deps from fuzzer * Update Chapter14Test.java * fixed order of operands * base rex fixup * more bug fix * remove jukn files * minimal "make release" * further fixes * minor fixes * minor fixes * minor fixes * base != -1 check, precedence fix * Add regkills in IFG building Fix bug LRG union stats Adjust self-conflict splits to be slightly less aggressive In every use-side split, dont split-after-split same block. NewNode is a multi, does not define a register * 64 bit encoding for gpr(s) load * RA improvements Attempt to commute to keep reg-masks compatible. Clones with kills (x86 XOR killing flags to zero) fail before the flag-def. Bugfix failed to color last lrg. More smarts on picking trivial LRGs during coloring Drop isSplit, add SplitNode Pass regalloc round to all splits for debugging Self-conflict no split on loop backedge. Handle split that gets made goes dead No-reg-mask spilling handles case with many def/uses. Add DivIX86 BrainFuck, MergeSort now allocate as testing * 8 bit imm support * conditional jump fix * Push l,ast change, unbreak branch * XOR remove extra opcode, imm size supports 8 bit. * Finally pass the `new [u8]` double-copy test * Fix several ARM reg setup problems Finish Split bypass check ARM: rsp is also a fixed zero, in R31. R0 is a valid register. CallArm prints args, passes TFP for arg selection. Does not return a register. All CallEndARM which DOES return a register. Parms missing tfp for float args. ProjARM after a New missing regs. RetARM missing RPC. Remove hopeful DivIX86 * 64 bit imm for alu ops encoded in extra reg then use it in Op as an operand * Bunch of minor arm, risc fixes small constants not cloning * jump invert encoding * warning comment * Much ARM fixage One More Go-Round with Better Biased Colors Fix bug picking deepest spills to bias. ARM: remove default 2-address. Fix regmask overflow. Rename some ops to be canonical. * Fixes to release Port does not have to be in a Simple named directory. Call convention is just a string, no enum Missed some tests running RISC, ARM. * Minor release change to allow another foreign port * better split encoding and fix CallInMask * notNode * fix callInMask * riscv init * riscv basic ALU ops and types of instructions * Allowing non-Simple repo ports Expanding public API slightly so alternative (non-Simple repo) ports can happen. RegMask supports single-bit-set and a long bitmask. Bugfix GCM splitting multiple global constants. Bugfix LCM grabbing a projection vs multi Bugfix RegAlloc when avoiding splitting a clonable Source code in release More timing in CodeGen * riscv indirect addressing and function calls * Cleanup call ABI presentation Everybody uses the same names, same code shapes * Caller save regs ...not callee save yet * Expose caller save regs * Store Risc adjustments, no imm in MemOpRISC * specific print encoding for cpus * merge fixup * More x86,risc,arm call convention fixes Collect callee and caller save masks (inverts of each other, except for spills). Reorg layout to be closer to each other; remove dead mask definitions; add final keyword * lui fix up with 32 bit immediates * Callee-save registers Callee-save registers and masks and inserting LRGs for same and spilling and picking good spills. Insert CalleeSaveNodes and edges to RetXXXs and RegMasks for those edges. Iterators for regmask. Better printing post-allocation * RegMask supports 128 bits All regs available arm, risc. MergeSort compiles & runs * added flags splitting * Fix risc5 compares No flags on risc5. Branch takes 2 inputs (one can be imm12). Float compare only sets a GPR to 0/1. Bool has a isFloat Common risc5 imm-form handling * extend test cases * imm encoding, flags fixed * grep for Todo: relocs * float riscv fixed * flags for set * lui fix for small bits [0, 12] * arm init * arm last touches * fmov still needs fix * minor fixes * minor fixes(2) * got rid of unnecessary stuff * create ch21 * remove encodings from chapter20 dir * 1/2 pass cleaning up ARM encoding API * ARM encoding compiles * Risc R_TYPE and I_TYPE * Progress RISC encodings Missing lots of stuff still * progress risc encoding * Bring back `make view` * risc encodings compile * Update README.md * Fold up redundant OP/OP-FP risc5 encoding * x86 mem opcodes first cut break out MemOp encodings into parts; change x86 indirectAdr to use Encoding class, remove redundant asserts & code * x86 mem op encodings * x86 reg+imm forms * most reg-reg forms * More x86 encodings * Last few encodings * switch to binary literals over decimals for opcodes * logical immediates fixed, bitwise ops imm forms * tfp and setArm fixed * PC relative float for riscv * readme init * Merged pr/169 * Fixed the array1 test encodings Clean up branch invert logic during BB layout Cleanup branch target printing for RISC,ARM ImmRISC ops mask imm12 to 12 bits Fix NewRISC; hardcode RPC as part of call Fix RISC inverting branchs >,>= * floatarm encoding * tfparm and intarm * uncontional jump encoding for arm and riscv * smarter IntARM, Intx86 does not always include rex. * REX after PREFIX * fixup load and store indirect * updated rex prefix, if its 0x40 then its not needed * Rex fix for floats, got rid of unnecessary stuff. * rename to camel case, better param name * add test export * merged * store opt * store and load optimised forms handled * tfp patch * Store/load float arm/riscv/x64 * got rid of debug print * splits for x86, riscv and arm * readme fixups * Create ASimpleReply.md * Update ASimpleReply.md * Update ASimpleReply.md * Update ASimpleReply.md * Update ASimpleReply.md * Update ASimpleReply.md * fix global symbol numbering for constants * ASimpleReply setup * fix TFP pathc on arm and riscv, keep symbols in good order. * fix opcode for store and loads, memOpRisc for Store and splitting * replace hard coded opcodes with major opcodes * Update README.md * replace hard coded opcodes with major opcodes * Fix some formatting (#184) * Fix title in chapter 19 * Wrap nseted scope images onto newlines in chapter 3 * add asserts and fix reg numbering * add asserts and fix reg numbering * minor updates * revert changes * remove constant pool from encoder TODO: add flag for JIT * extend test cases * Update ASimpleReply2.md * Update ASimpleReplyChain.md * minor fixes * minor fixes * update float test case * stringHash fix on x64 * remove conditional ConstantPool write * brainfuck minor fixes * minor arm fixes * split fix * Ignore unicorn * More simple replies * ASimpleReply3 * brainfuck and fib * brain fuck * Update DadalReply2.md * Update ASimpleReplyChain.md * arg_count, person * add ignore * More Simply Reply * Update ASimpleReply3.md * typo * riscv test sieve attempt * minor changes * riscv5 emulator additions * arm eval setup * Update riscv.java * arm eval basics * delta fixes * arm fixes * testfib arm * arg_count * small update for test newton * small update for test newton * testNewton runs on x64, riscv and arm * arm additions * arm pc eval fix * arm workings * debugging * arm sieve test * test person push * brainFuck arm --------- Co-authored-by: Cliff Click <cliffc@acm.org> Co-authored-by: godalming123 <68993177+godalming123@users.noreply.github.com> commit d80cf39073ce13c3ad6a707cff719852cbcdfab2 Author: Cliff Click <cliffc@acm.org> Date: Wed Apr 23 20:29:34 2025 -0700 Fix some arm encoding Fix some OBOB in R5 calloc encoding. Sort EvalArm by major opcodes. Add ASL. Remove leading switch. Fix OBOB in calloc. Fix shift in ld/st with reg+offset commit eb346519783e29a849bdd85e1d7622e33e0d8eaa Author: Cliff Click <cliffc@acm.org> Date: Wed Apr 23 16:57:51 2025 -0700 Progress sieve on ARM Most ops need WMASK as the output, to exclude RSP. calloc sentinal changes to multiple of 4 NewArm needs to set arg X0 to #1; arg X1 is length. Fix EvalARM calling calloc & not advancing PC. Fix EvalARM using ints vs longs. Fix EvalARM missing AndI encoding. commit 955e7816da5bcb39843230438a4a0e1e4dd7f9f8 Author: Cliff Click <cliffc@acm.org> Date: Tue Apr 22 09:59:23 2025 -0700 bring back more loop help from ch22 commit 6d80212003d2b03c2df06683efe696238f391cf7 Author: Cliff Click <cliffc@acm.org> Date: Mon Apr 21 14:11:27 2025 -0700 Update README.md commit 5fb03752b195e99dd251c9d497cccc93335bffe2 Author: Cliff Click <cliffc@acm.org> Date: Mon Apr 21 11:57:04 2025 -0700 Code quality on loop with many continues commit 79d6d7b2a882046da6be199da0a0651c6c13bb2b Author: Cliff Click <cliffc@acm.org> Date: Sun Apr 20 12:17:41 2025 -0700 Turn back on newton on ARM commit c0b6e387d7c40aec9e8d626976f4cfac27c54f90 Author: Cliff Click <cliffc@acm.org> Date: Sun Apr 20 12:14:22 2025 -0700 Fix bugs One more encoding invert-branch test version, with extensive comments and symmetry pointed out. Fixes missing back branch at loop end. Switch the arguments to `dep.addDep(this)` to become `this.addDep(dep)` shortened to `addDep(dep)`. One more missing dep in Region fusing. commit 0f3ece57429baf2014fd08a4fd167c6ad7b1315b Author: Cliff Click <cliffc@acm.org> Date: Sun Apr 20 10:46:50 2025 -0700 Correct test case ULP commit d761c267362e618e9e01de5b1fc0a405f5b466dc Author: Cliff Click <cliffc@acm.org> Date: Sun Apr 20 09:46:32 2025 -0700 C program silently fails on CI? `sqrt(2)` is returning a `2.0` not e.g. `1.715...` commit 6abe323a0cb8a3a6a52ab13ea7217a28654f55ca Author: Cliff Click <cliffc@acm.org> Date: Sun Apr 20 09:40:19 2025 -0700 Update newtonFloat.c commit 0cb35914c6a92be41108a7e3f16801b955967432 Author: Cliff Click <cliffc@acm.org> Date: Sat Apr 19 16:43:25 2025 -0700 Better block layout Keeps loops self-contained, handles more variants of infinite loops. Fold region-to-regions together, in an effort to have fewer junk jumps commit 4402980faff51393a4a8d35557f1ed8c06596f23 Author: Tamás Hadházy <85063808+Hels15@users.noreply.github.com> Date: Sat Apr 19 22:01:05 2025 +0100 TestNewton arm (#209) * x86 call args, risc5 load/store regs * arm2 bs * Push RISC WMASK avoid writing to RSP * work in progress * clearnup arm * Progress calling convention allocations Call unlinks before reg alloc. CallEnd tracks RPC CallX86 requires TFP for correct ABI Bias Color tracks 2-addr. RPC gets a mask/stack-slot Clean up call/fun ABI Fix mask RET, RET_F Fix print stack slot Remove isMultiHead, isMultiTail Better all-asm print * Beef up Newton test * fixed main pr problems * regmask extra comments * Clean pass normal tests New fcn print upgrades several golden rule tests. Move the unlink-all from the walk::err call (which IS used during IterPeeps) to GCM. CallEnd upgrades return type based on constant call. Fix unlink to also unlink CallEnd Constant TFP print checks constant Tested an untested Load * Arm basics, minor changes in riscv (#160) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java * add riscv port to readme * arm init * work in progress * arm2 bs * work in progress * clearnup arm * fixed main pr problems * regmask extra comments --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Update Chapter20Test.java * Weaken range-check test since no range checks... test cannot fail (pass) * Fix issue #161 Error adding 'null' and '5' * bug fixs, cloning constants Fix bug missing Find. Fix bug setDef does unordered removal from basic blocks. Use cloned constants for spilling. * RA fixes SplitSelfConflict MUST split before uses and after defs. Split global constants across functions Missing find in union; Inserting split after new inserted wrong place Pre-remove null LRGs before color. Bias color hunts through Phis Shorten InstructionSelection. Rename !unified() to leader() AddMemX86 is 2-addr Add CastXXX to preserve register across cast. * encoding init * encoding init 2 * rex fix * indirect addressing modes * setop change opcode, rename FLOAT_OFFSET * mask sib and modrm with 3 bits, 4th bit goes into rex. * sign extend div with cqo * idiv input is fixed to rax and RMASK, grab input from second slot when encoding * encoding of newx86 * tfp and CallEndNode encoding * sib improvement * more cleanups * more cleanups * Allow a killMap to kill regs. Bugfix scheduler. Bugfix spiller - self-conflict order depended on hashtable iteration order, which depended on System.hashCode which would vary run to run. Improvements to coloring Improvements to spill quality testing * Added flags-kill for XOR * Missing add-deps from fuzzer * Update Chapter14Test.java * fixed order of operands * base rex fixup * more bug fix * remove jukn files * minimal "make release" * further fixes * minor fixes * minor fixes * minor fixes * base != -1 check, precedence fix * Add regkills in IFG building Fix bug LRG union stats Adjust self-conflict splits to be slightly less aggressive In every use-side split, dont split-after-split same block. NewNode is a multi, does not define a register * 64 bit encoding for gpr(s) load * RA improvements Attempt to commute to keep reg-masks compatible. Clones with kills (x86 XOR killing flags to zero) fail before the flag-def. Bugfix failed to color last lrg. More smarts on picking trivial LRGs during coloring Drop isSplit, add SplitNode Pass regalloc round to all splits for debugging Self-conflict no split on loop backedge. Handle split that gets made goes dead No-reg-mask spilling handles case with many def/uses. Add DivIX86 BrainFuck, MergeSort now allocate as testing * 8 bit imm support * conditional jump fix * Push l,ast change, unbreak branch * XOR remove extra opcode, imm size supports 8 bit. * Finally pass the `new [u8]` double-copy test * Fix several ARM reg setup problems Finish Split bypass check ARM: rsp is also a fixed zero, in R31. R0 is a valid register. CallArm prints args, passes TFP for arg selection. Does not return a register. All CallEndARM which DOES return a register. Parms missing tfp for float args. ProjARM after a New missing regs. RetARM missing RPC. Remove hopeful DivIX86 * 64 bit imm for alu ops encoded in extra reg then use it in Op as an operand * Bunch of minor arm, risc fixes small constants not cloning * jump invert encoding * warning comment * Much ARM fixage One More Go-Round with Better Biased Colors Fix bug picking deepest spills to bias. ARM: remove default 2-address. Fix regmask overflow. Rename some ops to be canonical. * Fixes to release Port does not have to be in a Simple named directory. Call convention is just a string, no enum Missed some tests running RISC, ARM. * Minor release change to allow another foreign port * better split encoding and fix CallInMask * notNode * fix callInMask * riscv init * riscv basic ALU ops and types of instructions * Allowing non-Simple repo ports Expanding public API slightly so alternative (non-Simple repo) ports can happen. RegMask supports single-bit-set and a long bitmask. Bugfix GCM splitting multiple global constants. Bugfix LCM grabbing a projection vs multi Bugfix RegAlloc when avoiding splitting a clonable Source code in release More timing in CodeGen * riscv indirect addressing and function calls * Cleanup call ABI presentation Everybody uses the same names, same code shapes * Caller save regs ...not callee save yet * Expose caller save regs * Store Risc adjustments, no imm in MemOpRISC * specific print encoding for cpus * merge fixup * More x86,risc,arm call convention fixes Collect callee and caller save masks (inverts of each other, except for spills). Reorg layout to be closer to each other; remove dead mask definitions; add final keyword * lui fix up with 32 bit immediates * Callee-save registers Callee-save registers and masks and inserting LRGs for same and spilling and picking good spills. Insert CalleeSaveNodes and edges to RetXXXs and RegMasks for those edges. Iterators for regmask. Better printing post-allocation * RegMask supports 128 bits All regs available arm, risc. MergeSort compiles & runs * added flags splitting * Fix risc5 compares No flags on risc5. Branch takes 2 inputs (one can be imm12). Float compare only sets a GPR to 0/1. Bool has a isFloat Common risc5 imm-form handling * extend test cases * imm encoding, flags fixed * grep for Todo: relocs * float riscv fixed * flags for set * lui fix for small bits [0, 12] * arm init * arm last touches * fmov still needs fix * minor fixes * minor fixes(2) * got rid of unnecessary stuff * create ch21 * remove encodings from chapter20 dir * 1/2 pass cleaning up ARM encoding API * ARM encoding compiles * Risc R_TYPE and I_TYPE * Progress RISC encodings Missing lots of stuff still * progress risc encoding * Bring back `make view` * risc encodings compile * Update README.md * Fold up redundant OP/OP-FP risc5 encoding * x86 mem opcodes first cut break out MemOp encodings into parts; change x86 indirectAdr to use Encoding class, remove redundant asserts & code * x86 mem op encodings * x86 reg+imm forms * most reg-reg forms * More x86 encodings * Last few encodings * switch to binary literals over decimals for opcodes * logical immediates fixed, bitwise ops imm forms * tfp and setArm fixed * PC relative float for riscv * readme init * Merged pr/169 * Fixed the array1 test encodings Clean up branch invert logic during BB layout Cleanup branch target printing for RISC,ARM ImmRISC ops mask imm12 to 12 bits Fix NewRISC; hardcode RPC as part of call Fix RISC inverting branchs >,>= * floatarm encoding * tfparm and intarm * uncontional jump encoding for arm and riscv * smarter IntARM, Intx86 does not always include rex. * REX after PREFIX * fixup load and store indirect * updated rex prefix, if its 0x40 then its not needed * Rex fix for floats, got rid of unnecessary stuff. * rename to camel case, better param name * add test export * merged * store opt * store and load optimised forms handled * tfp patch * Store/load float arm/riscv/x64 * got rid of debug print * splits for x86, riscv and arm * readme fixups * Create ASimpleReply.md * Update ASimpleReply.md * Update ASimpleReply.md * Update ASimpleReply.md * Update ASimpleReply.md * Update ASimpleReply.md * fix global symbol numbering for constants * ASimpleReply setup * fix TFP pathc on arm and riscv, keep symbols in good order. * fix opcode for store and loads, memOpRisc for Store and splitting * replace hard coded opcodes with major opcodes * Update README.md * replace hard coded opcodes with major opcodes * Fix some formatting (#184) * Fix title in chapter 19 * Wrap nseted scope images onto newlines in chapter 3 * add asserts and fix reg numbering * add asserts and fix reg numbering * minor updates * revert changes * remove constant pool from encoder TODO: add flag for JIT * extend test cases * Update ASimpleReply2.md * Update ASimpleReplyChain.md * minor fixes * minor fixes * update float test case * stringHash fix on x64 * remove conditional ConstantPool write * brainfuck minor fixes * minor arm fixes * split fix * Ignore unicorn * More simple replies * ASimpleReply3 * brainfuck and fib * brain fuck * Update DadalReply2.md * Update ASimpleReplyChain.md * arg_count, person * add ignore * More Simply Reply * Update ASimpleReply3.md * typo * riscv test sieve attempt * minor changes * riscv5 emulator additions * arm eval setup * Update riscv.java * arm eval basics * delta fixes * arm fixes * testfib arm * arg_count * small update for test newton * small update for test newton * testNewton runs on x64, riscv and arm --------- Co-authored-by: Cliff Click <cliffc@acm.org> Co-authored-by: godalming123 <68993177+godalming123@users.noreply.github.com> commit 2c8f9a01f5ccdac7e145f171fa95330923521f41 Author: Cliff Click <cliffc@acm.org> Date: Thu Apr 17 10:17:32 2025 -0700 Fix arm D_OFFSET commit e8e8335258b85f95f24ac506f770fbdd08f4a083 Author: Cliff Click <cliffc@acm.org> Date: Thu Apr 17 10:00:59 2025 -0700 win64 spills more than sys5 commit 71835b1cc3d597234e7fb50e7ddddf2b20cf10e2 Author: Tamás Hadházy <85063808+Hels15@users.noreply.github.com> Date: Thu Apr 17 17:54:03 2025 +0100 Arm eval (#208) * README updates * Fix ch19 mis-merge? * x86 call args, risc5 load/store regs * arm2 bs * Push RISC WMASK avoid writing to RSP * work in progress * clearnup arm * Progress calling convention allocations Call unlinks before reg alloc. CallEnd tracks RPC CallX86 requires TFP for correct ABI Bias Color tracks 2-addr. RPC gets a mask/stack-slot Clean up call/fun ABI Fix mask RET, RET_F Fix print stack slot Remove isMultiHead, isMultiTail Better all-asm print * Beef up Newton test * fixed main pr problems * regmask extra comments * Clean pass normal tests New fcn print upgrades several golden rule tests. Move the unlink-all from the walk::err call (which IS used during IterPeeps) to GCM. CallEnd upgrades return type based on constant call. Fix unlink to also unlink CallEnd Constant TFP print checks constant Tested an untested Load * Arm basics, minor changes in riscv (#160) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java * add riscv port to readme * arm init * work in progress * arm2 bs * work in progress * clearnup arm * fixed main pr problems * regmask extra comments --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Update Chapter20Test.java * Weaken range-check test since no range checks... test cannot fail (pass) * Fix issue #161 Error adding 'null' and '5' * bug fixs, cloning constants Fix bug missing Find. Fix bug setDef does unordered removal from basic blocks. Use cloned constants for spilling. * RA fixes SplitSelfConflict MUST split before uses and after defs. Split global constants across functions Missing find in union; Inserting split after new inserted wrong place Pre-remove null LRGs before color. Bias color hunts through Phis Shorten InstructionSelection. Rename !unified() to leader() AddMemX86 is 2-addr Add CastXXX to preserve register across cast. * encoding init * encoding init 2 * rex fix * indirect addressing modes * setop change opcode, rename FLOAT_OFFSET * mask sib and modrm with 3 bits, 4th bit goes into rex. * sign extend div with cqo * idiv input is fixed to rax and RMASK, grab input from second slot when encoding * encoding of newx86 * tfp and CallEndNode encoding * sib improvement * more cleanups * more cleanups * Allow a killMap to kill regs. Bugfix scheduler. Bugfix spiller - self-conflict order depended on hashtable iteration order, which depended on System.hashCode which would vary run to run. Improvements to coloring Improvements to spill quality testing * Added flags-kill for XOR * Missing add-deps from fuzzer * Update Chapter14Test.java * fixed order of operands * base rex fixup * more bug fix * remove jukn files * minimal "make release" * further fixes * minor fixes * minor fixes * minor fixes * base != -1 check, precedence fix * Add regkills in IFG building Fix bug LRG union stats Adjust self-conflict splits to be slightly less aggressive In every use-side split, dont split-after-split same block. NewNode is a multi, does not define a register * 64 bit encoding for gpr(s) load * RA improvements Attempt to commute to keep reg-masks compatible. Clones with kills (x86 XOR killing flags to zero) fail before the flag-def. Bugfix failed to color last lrg. More smarts on picking trivial LRGs during coloring Drop isSplit, add SplitNode Pass regalloc round to all splits for debugging Self-conflict no split on loop backedge. Handle split that gets made goes dead No-reg-mask spilling handles case with many def/uses. Add DivIX86 BrainFuck, MergeSort now allocate as testing * 8 bit imm support * conditional jump fix * Push l,ast change, unbreak branch * XOR remove extra opcode, imm size supports 8 bit. * Finally pass the `new [u8]` double-copy test * Fix several ARM reg setup problems Finish Split bypass check ARM: rsp is also a fixed zero, in R31. R0 is a valid register. CallArm prints args, passes TFP for arg selection. Does not return a register. All CallEndARM which DOES return a register. Parms missing tfp for float args. ProjARM after a New missing regs. RetARM missing RPC. Remove hopeful DivIX86 * 64 bit imm for alu ops encoded in extra reg then use it in Op as an operand * Bunch of minor arm, risc fixes small constants not cloning * jump invert encoding * warning comment * Much ARM fixage One More Go-Round with Better Biased Colors Fix bug picking deepest spills to bias. ARM: remove default 2-address. Fix regmask overflow. Rename some ops to be canonical. * Fixes to release Port does not have to be in a Simple named directory. Call convention is just a string, no enum Missed some tests running RISC, ARM. * Minor release change to allow another foreign port * better split encoding and fix CallInMask * notNode * fix callInMask * riscv init * riscv basic ALU ops and types of instructions * Allowing non-Simple repo ports Expanding public API slightly so alternative (non-Simple repo) ports can happen. RegMask supports single-bit-set and a long bitmask. Bugfix GCM splitting multiple global constants. Bugfix LCM grabbing a projection vs multi Bugfix RegAlloc when avoiding splitting a clonable Source code in release More timing in CodeGen * riscv indirect addressing and function calls * Cleanup call ABI presentation Everybody uses the same names, same code shapes * Caller save regs ...not callee save yet * Expose caller save regs * Store Risc adjustments, no imm in MemOpRISC * specific print encoding for cpus * merge fixup * More x86,risc,arm call convention fixes Collect callee and caller save masks (inverts of each other, except for spills). Reorg layout to be closer to each other; remove dead mask definitions; add final keyword * lui fix up with 32 bit immediates * Callee-save registers Callee-save registers and masks and inserting LRGs for same and spilling and picking good spills. Insert CalleeSaveNodes and edges to RetXXXs and RegMasks for those edges. Iterators for regmask. Better printing post-allocation * RegMask supports 128 bits All regs available arm, risc. MergeSort compiles & runs * added flags splitting * Fix risc5 compares No flags on risc5. Branch takes 2 inputs (one can be imm12). Float compare only sets a GPR to 0/1. Bool has a isFloat Common risc5 imm-form handling * extend test cases * imm encoding, flags fixed * grep for Todo: relocs * float riscv fixed * flags for set * lui fix for small bits [0, 12] * arm init * arm last touches * fmov still needs fix * minor fixes * minor fixes(2) * got rid of unnecessary stuff * create ch21 * remove encodings from chapter20 dir * 1/2 pass cleaning up ARM encoding API * ARM encoding compiles * Risc R_TYPE and I_TYPE * Progress RISC encodings Missing lots of stuff still * progress risc encoding * Bring back `make view` * risc encodings compile * Update README.md * Fold up redundant OP/OP-FP risc5 encoding * x86 mem opcodes first cut break out MemOp encodings into parts; change x86 indirectAdr to use Encoding class, remove redundant asserts & code * x86 mem op encodings * x86 reg+imm forms * most reg-reg forms * More x86 encodings * Last few encodings * switch to binary literals over decimals for opcodes * logical immediates fixed, bitwise ops imm forms * tfp and setArm fixed * PC relative float for riscv * readme init * Merged pr/169 * Fixed the array1 test encodings Clean up branch invert logic during BB layout Cleanup branch target printing for RISC,ARM ImmRISC ops mask imm12 to 12 bits Fix NewRISC; hardcode RPC as part of call Fix RISC inverting branchs >,>= * floatarm encoding * tfparm and intarm * uncontional jump encoding for arm and riscv * smarter IntARM, Intx86 does not always include rex. * REX after PREFIX * fixup load and store indirect * updated rex prefix, if its 0x40 then its not needed * Rex fix for floats, got rid of unnecessary stuff. * rename to camel case, better param name * add test export * merged * store opt * store and load optimised forms handled * tfp patch * Store/load float arm/riscv/x64 * got rid of debug print * splits for x86, riscv and arm * readme fixups * Create ASimpleReply.md * Update ASimpleReply.md * Update ASimpleReply.md * Update ASimpleReply.md * Update ASimpleReply.md * Update ASimpleReply.md * fix global symbol numbering for constants * ASimpleReply setup * fix TFP pathc on arm and riscv, keep symbols in good order. * fix opcode for store and loads, memOpRisc for Store and splitting * replace hard coded opcodes with major opcodes * Update README.md * replace hard coded opcodes with major opcodes * Fix some formatting (#184) * Fix title in chapter 19 * Wrap nseted scope images onto newlines in chapter 3 * add asserts and fix reg numbering * add asserts and fix reg numbering * minor updates * revert changes * remove constant pool from encoder TODO: add flag for JIT * extend test cases * Update ASimpleReply2.md * Update ASimpleReplyChain.md * minor fixes * minor fixes * update float test case * stringHash fix on x64 * remove conditional ConstantPool write * brainfuck minor fixes * minor arm fixes * split fix * Ignore unicorn * More simple replies * ASimpleReply3 * brainfuck and fib * brain fuck * Update DadalReply2.md * Update ASimpleReplyChain.md * arg_count, person * add ignore * More Simply Reply * Update ASimpleReply3.md * typo * riscv test sieve attempt * minor changes * riscv5 emulator additions * arm eval setup * Update riscv.java * arm eval basics * delta fixes * arm fixes * testfib arm * arg_count * small update for test newton * small update for test newton --------- Co-authored-by: Cliff Click <cliffc@acm.org> Co-authored-by: godalming123 <68993177+godalming123@users.noreply.github.com> commit 38fd3c17c672c42fc768b0f31680b5d9679cac6d Author: Cliff Click <cliffc@acm.org> Date: Thu Apr 17 09:42:52 2025 -0700 Fix CmpIX86/Jmp swap args bug Some x86 cmp need to swap args, then Set/Jmp both need to swap `bop`. Fix missing padding in ASM printer. MergeSort test like brainfuck - runs on arm. commit bd0f7963b117d3760cd0206ddfa54573c3e9b149 Author: Cliff Click <cliffc@acm.org> Date: Wed Apr 16 14:17:17 2025 -0700 Remove redundant BrainFuck test Move all flavors into BrainFuck,java. Check x86 by linking with C & running. Check risc5 by running in EvalRisc5. Check arm by encoding, but no sim (yet). commit d3bc904d9d44bd9ad1e1b6e72c5f9c1a8c428f0b Author: Cliff Click <cliffc@acm.org> Date: Wed Apr 16 13:56:16 2025 -0700 Fix risc5 branch invert Had conditions on forced invert mixed up. Sometimes need both a negate and a swap. rename invert as negate. commit 3ee7184fb99f8f389e1ff7178ae58e3e90571b07 Author: Cliff Click <cliffc@acm.org> Date: Wed Apr 16 09:18:34 2025 -0700 Missed cutout test commit 2a3b2d4d4bc6099ab9fead00cfaa3f0a8e6aeb9f Author: Cliff Click <cliffc@acm.org> Date: Tue Apr 15 19:41:55 2025 -0700 R5 fcmp, flt load from pool exportELF(null) now patches the cpool entries as well as globals. Repeated cpool entries folded. Fixed FltRISC, FCMP encoding. commit 2fb0ec312d76f22534331023c86e68b7a62b98e6 Author: Tamás Hadházy <85063808+Hels15@users.noreply.github.com> Date: Tue Apr 15 22:46:50 2025 +0100 riscv emulator (#206) * First cut regalloc working Biased coloring; tracking splits around a LRG. Separate split for self-conflicts. postColor to remove junk splits. Some minor riscv fixes. * Compile some riscv codes If SplitEmptyMask does not apply, fall back to normal split. Missing CmpFRISC. Renamed riscv flags. RetRISC handls FPR remove extra RegMask constructurs * add riscv port to readme * arm init * Progress reg alloc Handle multi-node projections. A little smarter about 2-addr scheduling RegAlloc must-have single registers removes from other masks instead of interfering; had the mask removal backwards in a few cases. LRG/RegMask COW expand masks before mutating. Clone small constants instead of spilling. Alloc & ProjX86 get output reg masks. Full print large reg masks with sp+offset * UF LRGs can be null during coloring * Finish UF find * work in progress * README updates * Fix ch19 mis-merge? * x86 call args, risc5 load/store regs * arm2 bs * Push RISC WMASK avoid writing to RSP * work in progress * clearnup arm * Progress calling convention allocations Call unlinks before reg alloc. CallEnd tracks RPC CallX86 requires TFP for correct ABI Bias Color tracks 2-addr. RPC gets a mask/stack-slot Clean up call/fun ABI Fix mask RET, RET_F Fix print stack slot Remove isMultiHead, isMultiTail Better all-asm print * Beef up Newton test * fixed main pr problems * regmask extra comments * Clean pass normal tests New fcn print upgrades several golden rule tests. Move the unlink-all from the walk::err call (which IS used during IterPeeps) to GCM. CallEnd upgrades return type based on constant call. Fix unlink to also unlink CallEnd Constant TFP print checks constant Tested an untested Load * Arm basics, minor changes in riscv (#160) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java * add riscv port to readme * arm init * work in progress * arm2 bs * work in progress * clearnup arm * fixed main pr problems * regmask extra comments --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Update Chapter20Test.java * Weaken range-check test since no range checks... test cannot fail (pass) * Fix issue #161 Error adding 'null' and '5' * bug fixs, cloning constants Fix bug missing Find. Fix bug setDef does unordered removal from basic blocks. Use cloned constants for spilling. * RA fixes SplitSelfConflict MUST split before uses and after defs. Split global constants across functions Missing find in union; Inserting split after new inserted wrong place Pre-remove null LRGs before color. Bias color hunts through Phis Shorten InstructionSelection. Rename !unified() to leader() AddMemX86 is 2-addr Add CastXXX to preserve register across cast. * encoding init * encoding init 2 * rex fix * indirect addressing modes * setop change opcode, rename FLOAT_OFFSET * mask sib and modrm with 3 bits, 4th bit goes into rex. * sign extend div with cqo * idiv input is fixed to rax and RMASK, grab input from second slot when encoding * encoding of newx86 * tfp and CallEndNode encoding * sib improvement * more cleanups * more cleanups * Allow a killMap to kill regs. Bugfix scheduler. Bugfix spiller - self-conflict order depended on hashtable iteration order, which depended on System.hashCode which would vary run to run. Improvements to coloring Improvements to spill quality testing * Added flags-kill for XOR * Missing add-deps from fuzzer * Update Chapter14Test.java * fixed order of operands * base rex fixup * more bug fix * remove jukn files * minimal "make release" * further fixes * minor fixes * minor fixes * minor fixes * base != -1 check, precedence fix * Add regkills in IFG building Fix bug LRG union stats Adjust self-conflict splits to be slightly less aggressive In every use-side split, dont split-after-split same block. NewNode is a multi, does not define a register * 64 bit encoding for gpr(s) load * RA improvements Attempt to commute to keep reg-masks compatible. Clones with kills (x86 XOR killing flags to zero) fail before the flag-def. Bugfix failed to color last lrg. More smarts on picking trivial LRGs during coloring Drop isSplit, add SplitNode Pass regalloc round to all splits for debugging Self-conflict no split on loop backedge. Handle split that gets made goes dead No-reg-mask spilling handles case with many def/uses. Add DivIX86 BrainFuck, MergeSort now allocate as testing * 8 bit imm support * conditional jump fix * Push l,ast change, unbreak branch * XOR remove extra opcode, imm size supports 8 bit. * Finally pass the `new [u8]` double-copy test * Fix several ARM reg setup problems Finish Split bypass check ARM: rsp is also a fixed zero, in R31. R0 is a valid register. CallArm prints args, passes TFP for arg selection. Does not return a register. All CallEndARM which DOES return a register. Parms missing tfp for float args. ProjARM after a New missing regs. RetARM missing RPC. Remove hopeful DivIX86 * 64 bit imm for alu ops encoded in extra reg then use it in Op as an operand * Bunch of minor arm, risc fixes small constants not cloning * jump invert encoding * warning comment * Much ARM fixage One More Go-Round with Better Biased Colors Fix bug picking deepest spills to bias. ARM: remove default 2-address. Fix regmask overflow. Rename some ops to be canonical. * Fixes to release Port does not have to be in a Simple named directory. Call convention is just a string, no enum Missed some tests running RISC, ARM. * Minor release change to allow another foreign port * better split encoding and fix CallInMask * notNode * fix callInMask * riscv init * riscv basic ALU ops and types of instructions * Allowing non-Simple repo ports Expanding public API slightly so alternative (non-Simple repo) ports can happen. RegMask supports single-bit-set and a long bitmask. Bugfix GCM splitting multiple global constants. Bugfix LCM grabbing a projection vs multi Bugfix RegAlloc when avoiding splitting a clonable Source code in release More timing in CodeGen * riscv indirect addressing and function calls * Cleanup call ABI presentation Everybody uses the same names, same code shapes * Caller save regs ...not callee save yet * Expose caller save regs * Store Risc adjustments, no imm in MemOpRISC * specific print encoding for cpus * merge fixup * More x86,risc,arm call convention fixes Collect callee and caller save masks (inverts of each other, except for spills). Reorg layout to be closer to each other; remove dead mask definitions; add final keyword * lui fix up with 32 bit immediates * Callee-save registers Callee-save registers and masks and inserting LRGs for same and spilling and picking good spills. Insert CalleeSaveNodes and edges to RetXXXs and RegMasks for those edges. Iterators for regmask. Better printing post-allocation * RegMask supports 128 bits All regs available arm, risc. MergeSort compiles & runs * added flags splitting * Fix risc5 compares No flags on risc5. Branch takes 2 inputs (one can be imm12). Float compare only sets a GPR to 0/1. Bool has a isFloat Common risc5 imm-form handling * extend test cases * imm encoding, flags fixed * grep for Todo: relocs * float riscv fixed * flags for set * lui fix for small bits [0, 12] * arm init * arm last touches * fmov still needs fix * minor fixes * minor fixes(2) * got rid of unnecessary stuff * create ch21 * remove encodings from chapter20 dir * 1/2 pass cleaning up ARM encoding API * ARM encoding compiles * Risc R_TYPE and I_TYPE * Progress RISC encodings Missing lots of stuff still * progress risc encoding * Bring back `make view` * risc encodings compile * Update README.md * Fold up redundant OP/OP-FP risc5 encoding * x86 mem opcodes first cut break out MemOp encodings into parts; change x86 indirectAdr to use Encoding class, remove redundant asserts & code * x86 mem op encodings * x86 reg+imm forms * most reg-reg forms * More x86 encodings * Last few encodings * switch to binary literals over decimals for opcodes * logical immediates fixed, bitwise ops imm forms * tfp and setArm fixed * PC relative float for riscv * readme init * Merged pr/169 * Fixed the array1 test encodings Clean up branch invert logic during BB layout Cleanup branch target printing for RISC,ARM ImmRISC ops mask imm12 to 12 bits Fix NewRISC; hardcode RPC as part of call Fix RISC inverting branchs >,>= * floatarm encoding * tfparm and intarm * uncontional jump encoding for arm and riscv * smarter IntARM, Intx86 does not always include rex. * REX after PREFIX * fixup load and store indirect * updated rex prefix, if its 0x40 then its not needed * Rex fix for floats, got rid of unnecessary stuff. * rename to camel case, better param name * add test export * merged * store opt * store and load optimised forms handled * tfp patch * Store/load float arm/riscv/x64 * got rid of debug print * splits for x86, riscv and arm * readme fixups * Create ASimpleReply.md * Update ASimpleReply.md * Update ASimpleReply.md * Update ASimpleReply.md * Update ASimpleReply.md * Update ASimpleReply.md * fix global symbol numbering for constants * ASimpleReply setup * fix TFP pathc on arm and riscv, keep symbols in good order. * fix opcode for store and loads, memOpRisc for Store and splitting * replace hard coded opcodes with major opcodes * Update README.md * replace hard coded opcodes with major opcodes * Fix some formatting (#184) * Fix title in chapter 19 * Wrap nseted scope images onto newlines in chapter 3 * add asserts and fix reg numbering * add asserts and fix reg numbering * minor updates * revert changes * remove constant pool from encoder TODO: add flag for JIT * extend test cases * Update ASimpleReply2.md * Update ASimpleReplyChain.md * minor fixes * minor fixes * update float test case * stringHash fix on x64 * remove conditional ConstantPool write * brainfuck minor fixes * minor arm fixes * split fix * Ignore unicorn * More simple replies * ASimpleReply3 * brainfuck and fib * brain fuck * Update DadalReply2.md * Update ASimpleReplyChain.md * arg_count, person * add ignore * More Simply Reply * Update ASimpleReply3.md * typo * riscv test sieve attempt * minor changes * riscv5 emulator additions * arm eval setup * Update riscv.java --------- Co-authored-by: Cliff Click <cliffc@acm.org> Co-authored-by: godalming123 <68993177+godalming123@users.noreply.github.com> commit b74f35618c087e4e6634707c6e03ca669315bcc9 Author: Darrick Wiebe <darrickw@gmail.com> Date: Tue Apr 15 17:39:27 2025 -0400 Remove incorrect assert (#205) commit d4d894407b9cbf83217da3bde1ecbd6d783297e4 Author: Cliff Click <cliffc@acm.org> Date: Tue Apr 15 12:58:13 2025 -0700 Revamp calling convention math Handles win64 shadow stack space correctly. Unifies most of the caller-save mask, return mask, arg mask computations. Unifies most of the frame-size math across all chips. Large documentation comment in Machine about how stack math is computed. commit 5e7223504d356cbb48c397bed0cd0012eb075935 Author: Cliff Click <cliffc@acm.org> Date: Mon Apr 14 10:42:50 2025 -0700 win64 shadow stack killed by calls commit e1351e0f4788779e9f37a77f0abb936f2869cbfc Author: Cliff Click <cliffc@acm.org> Date: Mon Apr 14 10:37:53 2025 -0700 NewXXX honors declared CC commit faf6bae55523f12c32b0e21b3c39445459c50f71 Author: Cliff Click <cliffc@acm.org> Date: Mon Apr 14 10:08:33 2025 -0700 Missing hasCalls for Newxxx commit 40a6f7b90e9e5d42bdacabb1c9e93fab5d27f63d Author: Cliff Click <cliffc@acm.org> Date: Sun Apr 13 19:51:04 2025 -0700 EvalRISC5 handles calloc Patch calls to calloc with a sentinel; emulator spots call to sentinel and mimics calloc on the internal heap. commit 450dd5169ebbfd8664d073b0d8baea201dae0ed2 Author: Cliff Click <cliffc@acm.org> Date: Sun Apr 13 19:49:31 2025 -0700 Cleanup recent phi xform Missing deps commit 2308671cd2a95aec8ab42b4eec5cb6f5cba5302c Author: Cliff Click <cliffc@acm.org> Date: Sun Apr 13 11:30:47 2025 -0700 negate on x86,risc,arm commit 1e28b27ec2d1129d4a219a57ec0c661c74720493 Author: Cliff Click <cliffc@acm.org> Date: Sun Apr 13 10:23:18 2025 -0700 Handle infinite loops Loop tree building (and breaking infinite loops) needs to happen *before* instruction selection, which is optional. Best done as a new compiler phase... which now wants a driver. Add a compiler driver; adjust tests to use. Add "never-branch" handling to the 3 CPU port main branch ops. commit 0ac2721f28c729c06c28fce14f8f863f8c57e41b Merge: a42aa8e3 53bb3207 Author: Cliff Click <cliffc@acm.org> Date: Sat Apr 12 14:08:59 2025 -0700 Merge branch 'ch21' of https://github.com/SeaOfNodes/Simple into ch21 commit a42aa8e31488f34315717743cf075e4d7b6e3677 Author: Cliff Click <cliffc@acm.org> Date: Sat Apr 12 14:08:57 2025 -0700 Phi generic pull down is stronger commit 53bb3207c85bc438b9cc28f84bce458e5068ee45 Author: Darrick Wiebe <darrickw@gmail.com> Date: Sat Apr 12 16:33:57 2025 -0400 Add a constructor to MemOpNode for M2 (#203) commit d08925c1ff212e2157356eacbd748e679b80954d Author: Cliff Click <cliffc@acm.org> Date: Sat Apr 12 11:41:58 2025 -0700 2 more encoding bugs Missing constant NIL, arm CAST commit 798bd959410eb6c2e0b9f7ae1222181ac2b295d4 Author: Darrick Wiebe <darrickw@gmail.com> Date: Sat Apr 12 13:40:20 2025 -0400 MemStore interface (#201) * Hook for pre-encoding work needed by M2 * Add MemStore interface * Add both MemLoad and MemStore interfaces * Fix NPE printing StopNode * Attach merged mem to return when forcing a loop exit * Made requested changes. This breaks a few tests, possibly because... ...I am not sure whether some X86 ops are loads or stores. * Mach ops need anti-deps too MemOp subclasses are either "Load-like" or "Store-like" based on a flag, and not on subclassing LoadNode nor StoreNode. Mach ops pick up anti-deps, no reg mask for those. x86 bug: do not allow "dst = [mem] + #imm", no such instruction Empty merge-mem folds away. x86 add-to-mem works both ways --------- Co-authored-by: Cliff Click <cliffc@acm.org> commit 47711b81ea915eeeb46b2ad8ebb291fb21cbaf6e Author: Darrick Wiebe <darrickw@gmail.com> Date: Sat Apr 12 11:46:53 2025 -0400 Don't print ante-deps on multinodes when generating asm dumps (#202) commit 08421921cbb885fdfb785b896f21b59937b99f39 Author: Darrick Wiebe <darrickw@gmail.com> Date: Sat Apr 12 00:25:08 2025 -0400 Hook for pre-encoding work needed by M2 (#200) commit 6ab61f8c6ae0fb26802cfff0f4d007e8a55e817e Author: Cliff Click <cliffc@acm.org> Date: Fri Apr 11 21:08:03 2025 -0700 Update Chapter20Test.java commit 404fb8dadfef10d0954abf6e84901b7d6a469f14 Author: Cliff Click <cliffc@acm.org> Date: Fri Apr 11 21:05:51 2025 -0700 Remove debug prints commit a2614064a6d86d146af07d0db50c2e76a2c37274 Author: Cliff Click <cliffc@acm.org> Date: Fri Apr 11 21:01:53 2025 -0700 Larger frame encodings commit 4e467263058c015ec0838487d1523b51fb42fcfd Author: Cliff Click <cliffc@acm.org> Date: Fri Apr 11 20:52:50 2025 -0700 Encoding bugs NewX86 using wrong killmap. Split: moving from xmm to gpr inverted REX bits commit 3f97f091c40ab92b8ea0def108e1db870b5a6f09 Author: Cliff Click <cliffc@acm.org> Date: Fri Apr 11 19:54:36 2025 -0700 Update TestC.java commit 81985bd73393c95b34334b5b57613873ffad95ae Author: Cliff Click <cliffc@acm.org> Date: Fri Apr 11 19:35:53 2025 -0700 One more try for CI commit 62f94ffd6020a6cb45ea7ee0bdf1f5295977f1b1 Author: Cliff Click <cliffc@acm.org> Date: Fri Apr 11 19:23:22 2025 -0700 Update README.md commit 752bc3e5df6e72e8b639e01a860f80c9d9b5db80 Author: Cliff Click <cliffc@acm.org> Date: Thu Apr 10 17:34:15 2025 -0700 Update NewX86.java commit d89e11f08591f5ff05395b5dc552b630aee5d5b8 Author: Cliff Click <cliffc@acm.org> Date: Thu Apr 10 17:26:55 2025 -0700 Update NewX86.java commit 58141543b8365fe5246fec107a9a68f26c5a7edf Author: Cliff Click <cliffc@acm.org> Date: Thu Apr 10 17:25:38 2025 -0700 Update NewX86.java commit 006c2351ce7eed1209296b8811f64f3f72f64045 Author: Cliff Click <cliffc@acm.org> Date: Thu Apr 10 17:23:04 2025 -0700 better ci error commit b6765473a3ce3db943355abba8e8ff765a3f8a3b Author: Cliff Click <cliffc@acm.org> Date: Thu Apr 10 17:18:53 2025 -0700 C tests check both Win64 qnd Sys5 NewX86 links against OS-specific `calloc`, uses OS-specific calling convention, and not the declared convention. Encoding bug, byte-store of SIL, DIL, BPL needs a blank REX. OBOB errors in Sys5 large-arg-count calling. commit b68dafa3e6e231bd0d0b0e613986c7e3181349b9 Author: Cliff Click <cliffc@acm.org> Date: Thu Apr 10 13:47:06 2025 -0700 More README updates commit 0b8c694910ccf5e615b8fb68049cffd47be2c3b9 Author: Cliff Click <cliffc@acm.org> Date: Thu Apr 10 13:33:08 2025 -0700 Update Makefile commit f13661bd35bedb211075b8d1ac48bbbbf394001e Author: Cliff Click <cliffc@acm.org> Date: Thu Apr 10 10:16:32 2025 -0700 Update README.md commit 7d22a8e805befd5d733d079cbbbce634bb6ae623 Author: Cliff Click <cliffc@acm.org> Date: Thu Apr 10 09:38:18 2025 -0700 README commit 074f718a104dbbb8b1b0a2205c176659a27dfd9f Author: Cliff Click <cliffc@acm.org> Date: Wed Apr 9 17:12:56 2025 -0700 Handle large arg count function 2 print modes for stack slots - slot# or stack offset, only available after encoding. Compute maxArgSlot in one place on all archs. X86 yank flags from Splits - lest we split a XMM into the flags??? commit b32c6a879c0be7aebad25fabae6f0a24d5670366 Author: Darrick Wiebe <darrickw@gmail.com> Date: Tue Apr 8 21:52:12 2025 -0400 Make eq() public (#199) commit 938c09baca29b0647dcd56e9581540d7a8142fb4 Author: Cliff Click <cliffc@acm.org> Date: Tue Apr 8 14:28:51 2025 -0700 Fix risc5 bugs from emulator commit 06872d180720c376d3fdcc0bd0a2a973bf9341c4 Author: Tamás Hadházy <85063808+Hels15@users.noreply.github.com> Date: Tue Apr 8 21:40:41 2025 +0100 arg_count, person (#198) * First cut BuildLRG RegMask.EMPTY -> null * fix minor bug * Setup for first simple split * First cut split code for mismatch regs * delete non existing matches * replace RegMask.Empty with null * Live Ranges U-F BuildLRG for Phi Add CmpFX86 * FPR return CallEndX86 needed to tell fp return from gpr ret Call has no output regs. * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * Next cut regalloc Building IFG. Coloring. Some nested spilling Optimize divf/con -> mul times inverse * turn off ch20 tests * Update Chapter20Test.java * RISC-V basic isel (#156) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Bring pr/154 to ch 20 RISC5 changes. Some missing x86 ops. Still needs some calling convention love. * First cut regalloc working Biased coloring; tracking splits around a LRG. Separate split for self-conflicts. postColor to remove junk splits. Some minor riscv fixes. * Compile some riscv codes If SplitEmptyMask does not apply, fall back to normal split. Missing CmpFRISC. Renamed riscv flags. RetRISC handls FPR remove extra RegMask constructurs * add riscv port to readme * arm init * Progress reg alloc Handle multi-node projections. A little smarter about 2-addr scheduling RegAlloc must-have single registers removes from other masks instead of interfering; had the mask removal backwards in a few cases. LRG/RegMask COW expand masks before mutating. Clone small constants instead of spilling. Alloc & ProjX86 get output reg masks. Full print large reg masks with sp+offset * UF LRGs can be null during coloring * Finish UF find * work in progress * README updates * Fix ch19 mis-merge? * x86 call args, risc5 load/store regs * arm2 bs * Push RISC WMASK avoid writing to RSP * work in progress * clearnup arm * Progress calling convention allocations Call unlinks before reg alloc. CallEnd tracks RPC CallX86 requires TFP for correct ABI Bias Color tracks 2-addr. RPC gets a mask/stack-slot Clean up call/fun ABI Fix mask RET, RET_F Fix print stack slot Remove isMultiHead, isMultiTail Better all-asm print * Beef up Newton test * fixed main pr problems * regmask extra comments * Clean pass normal tests New fcn print upgrades several golden rule tests. Move the unlink-all from the walk::err call (which IS used during IterPeeps) to GCM. CallEnd upgrades return type based on constant call. Fix unlink to also unlink CallEnd Constant TFP print checks constant Tested an untested Load * Arm basics, minor changes in riscv (#160) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java * add riscv port to readme * arm init * work in progress * arm2 bs * work in progress * clearnup arm * fixed main pr problems * regmask extra comments --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Update Chapter20Test.java * Weaken range-check test since no range checks... test cannot fail (pass) * Fix issue #161 Error adding 'null' and '5' * bug fixs, cloning constants Fix bug missing Find. Fix bug setDef does unordered removal from basic blocks. Use cloned constants for spilling. * RA fixes SplitSelfConflict MUST split before uses and after defs. Split global constants across functions Missing find in union; Inserting split after new inserted wrong place Pre-remove null LRGs before color. Bias color hunts through Phis Shorten InstructionSelection. Rename !unified() to leader() AddMemX86 is 2-addr Add CastXXX to preserve register across cast. * encoding init * encoding init 2 * rex fix * indirect addressing modes * setop change opcode, rename FLOAT_OFFSET * mask sib and modrm with 3 bits, 4th bit goes into rex. * sign extend div with cqo * idiv input is fixed to rax and RMASK, grab input from second slot when encoding * encoding of newx86 * tfp and CallEndNode encoding * sib improvement * more cleanups * more cleanups * Allow a killMap to kill regs. Bugfix scheduler. Bugfix spiller - self-conflict order depended on hashtable iteration order, which depended on System.hashCode which would vary run to run. Improvements to coloring Improvements to spill quality testing * Added flags-kill for XOR * Missing add-deps from fuzzer * Update Chapter14Test.java * fixed order of operands * base rex fixup * more bug fix * remove jukn files * minimal "make release" * further fixes * minor fixes * minor fixes * minor fixes * base != -1 check, precedence fix * Add regkills in IFG building Fix bug LRG union stats Adjust self-conflict splits to be slightly less aggressive In every use-side split, dont split-after-split same block. NewNode is a multi, does not define a register * 64 bit encoding for gpr(s) load * RA improvements Attempt to commute to keep reg-masks compatible. Clones with kills (x86 XOR killing flags to zero) fail before the flag-def. Bugfix failed to color last lrg. More smarts on picking trivial LRGs during coloring Drop isSplit, add SplitNode Pass regalloc round to all splits for debugging Self-conflict no split on loop backedge. Handle split that gets made goes dead No-reg-mask spilling handles case with many def/uses. Add DivIX86 BrainFuck, MergeSort now allocate as testing * 8 bit imm support * conditional jump fix * Push l,ast change, unbreak branch * XOR remove extra opcode, imm size supports 8 bit. * Finally pass the `new [u8]` double-copy test * Fix several ARM reg setup problems Finish Split bypass check ARM: rsp is also a fixed zero, in R31. R0 is a valid register. CallArm prints args, passes TFP for arg selection. Does not return a register. All CallEndARM which DOES return a register. Parms missing tfp for float args. ProjARM after a New missing regs. RetARM missing RPC. Remove hopeful DivIX86 * 64 bit imm for alu ops encoded in extra reg then use it in Op as an operand * Bunch of minor arm, risc fixes small constants not cloning * jump invert encoding * warning comment * Much ARM fixage One More Go-Round with Better Biased Colors Fix bug picking deepest spills to bias. ARM: remove default 2-address. Fix regmask overflow. Rename some ops to be canonical. * Fixes to release Port does not have to be in a Simple named directory. Call convention is just a string, no enum Missed some tests running RISC, ARM. * Minor release change to allow another foreign port * better split encoding and fix CallInMask * notNode * fix callInMask * riscv init * riscv basic ALU ops and types of instructions * Allowing non-Simple repo ports Expanding public API slightly so alternative (non-Simple repo) ports can happen. RegMask supports single-bit-set and a long bitmask. Bugfix GCM splitting multiple global constants. Bugfix LCM grabbing a projection vs multi Bugfix RegAlloc when avoiding splitting a clonable Source code in release More timing in CodeGen * riscv indirect addressing and function calls * Cleanup call ABI presentation Everybody uses the same names, same code shapes * Caller save regs ...not callee save yet * Expose caller save regs * Store Risc adjustments, no imm in MemOpRISC * specific print encoding for cpus * merge fixup * More x86,risc,arm call convention fixes Collect callee and caller save masks (inverts of each other, except for spills). Reorg layout to be closer to each other; remove dead mask definitions; add final keyword * lui fix up with 32 bit immediates * Callee-save registers Callee-save registers and masks and inserting LRGs for same and spilling and picking good spills. Insert CalleeSaveNodes and edges to RetXXXs and RegMasks for those edges. Iterators for regmask. Better printing post-allocation * RegMask supports 128 bits All regs available arm, risc. MergeSort compiles & runs * added flags splitting * Fix risc5 compares No flags on risc5. Branch takes 2 inputs (one can be imm12). Float compare only sets a GPR to 0/1. Bool has a isFloat Common risc5 imm-form handling * extend test cases * imm encoding, flags fixed * grep for Todo: relocs * float riscv fixed * flags for set * lui fix for small bits [0, 12] * arm init * arm last touches * fmov still needs fix * minor fixes * minor fixes(2) * got rid of unnecessary stuff * create ch21 * remove encodings from chapter20 dir * 1/2 pass cleaning up ARM encoding API * ARM encoding compiles * Risc R_TYPE and I_TYPE * Progress RISC encodings Missing lots of stuff still * progress risc encoding * Bring back `make view` * risc encodings compile * Update README.md * Fold up redundant OP/OP-FP risc5 encoding * x86 mem opcodes first cut break out MemOp encodings into parts; change x86 indirectAdr to use Encoding class, remove redundant asserts & code * x86 mem op encodings * x86 reg+imm forms * most reg-reg forms * More x86 encodings * Last few encodings * switch to binary literals over decimals for opcodes * logical immediates fixed, bitwise ops imm forms * tfp and setArm fixed * PC relative float for riscv * readme init * Merged pr/169 * Fixed the array1 test encodings Clean up branch invert logic during BB layout Cleanup branch target printing for RISC,ARM ImmRISC ops mask imm12 to 12 bits Fix NewRISC; hardcode RPC as part of call Fix RISC inverting branchs >,>= * floatarm encoding * tfparm and intarm * uncontional jump encoding for arm and riscv * smarter IntARM, Intx86 does not always include rex. * REX after PREFIX * fixup load and store indirect * updated rex prefix, if its 0x40 then its not needed * Rex fix for floats, got rid of unnecessary stuff. * rename to camel case, better param name * add test export * merged * store opt * store and load optimised forms handled * tfp patch * Store/load float arm/riscv/x64 * got rid of debug print * splits for x86, riscv and arm * readme fixups * Create ASimpleReply.md * Update ASimpleReply.md * Update ASimpleReply.md * Update ASimpleReply.md * Update ASimpleReply.md * Update ASimpleReply.md * fix global symbol numbering for constants * ASimpleReply setup * fix TFP pathc on arm and riscv, keep symbols in good order. * fix opcode for store and loads, memOpRisc for Store and splitting * replace hard coded opcodes with major opcodes * Update README.md * replace hard coded opcodes with major opcodes * Fix some formatting (#184) * Fix title in chapter 19 * Wrap nseted scope images onto newlines in chapter 3 * add asserts and fix reg numbering * add asserts and fix reg numbering * minor updates * revert changes * remove constant pool from encoder TODO: add flag for JIT * extend test cases * minor fixes * minor fixes * update float test case * stringHash fix on x64 * remove conditional ConstantPool write * brainfuck minor fixes * minor arm fixes * split fix * brainfuck and fib * brain fuck * arg_count, person * add ignore --------- Co-authored-by: Cliff Click <cliffc@acm.org> Co-authored-by: godalming123 <68993177+godalming123@users.noreply.github.com> commit 22cb2b4555b6b539a5204c2e2b60748c8138d8dc Author: Cliff Click <cliffc@acm.org> Date: Tue Apr 8 11:55:35 2025 -0700 Add 32b risc5 emulator commit a0f70ef9b432fdad06879c333c59e3f3a4dbfe54 Author: Cliff Click <cliffc@acm.org> Date: Mon Apr 7 14:55:19 2025 -0700 Clean tests commit b7603e71510b4d0e82681b9639a56cc77bab40d6 Author: Cliff Click <cliffc@acm.org> Date: Mon Apr 7 14:50:30 2025 -0700 Update Encoding.java commit 840bfd14b3658f49429cbf84fbc39980477d5f6e Author: Cliff Click <cliffc@acm.org> Date: Mon Apr 7 14:50:24 2025 -0700 x86 shift imm are 2-addr commit 635ef128b8e3c5a993e8551454f986098dd5092e Author: Tamás Hadházy <85063808+Hels15@users.noreply.github.com> Date: Mon Apr 7 22:00:29 2025 +0100 Minor arm fixes (#197) * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * First cut BuildLRG RegMask.EMPTY -> null * fix minor bug * Setup for first simple split * First cut split code for mismatch regs * delete non existing matches * replace RegMask.Empty with null * Live Ranges U-F BuildLRG for Phi Add CmpFX86 * FPR return CallEndX86 needed to tell fp return from gpr ret Call has no output regs. * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * Next cut regalloc Building IFG. Coloring. Some nested spilling Optimize divf/con -> mul times inverse * turn off ch20 tests * Update Chapter20Test.java * RISC-V basic isel (#156) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Bring pr/154 to ch 20 RISC5 changes. Some missing x86 ops. Still needs some calling convention love. * First cut regalloc working Biased coloring; tracking splits around a LRG. Separate split for self-conflicts. postColor to remove junk splits. Some minor riscv fixes. * Compile some riscv codes If SplitEmptyMask does not apply, fall back to normal split. Missing CmpFRISC. Renamed riscv flags. RetRISC handls FPR remove extra RegMask constructurs * add riscv port to readme * arm init * Progress reg alloc Handle multi-node projections. A little smarter about 2-addr scheduling RegAlloc must-have single registers removes from other masks instead of interfering; had the mask removal backwards in a few cases. LRG/RegMask COW expand masks before mutating. Clone small constants instead of spilling. Alloc & ProjX86 get output reg masks. Full print large reg masks with sp+offset * UF LRGs can be null during coloring * Finish UF find * work in progress * README updates * Fix ch19 mis-merge? * x86 call args, risc5 load/store regs * arm2 bs * Push RISC WMASK avoid writing to RSP * work in progress * clearnup arm * Progress calling convention allocations Call unlinks before reg alloc. CallEnd tracks RPC CallX86 requires TFP for correct ABI Bias Color tracks 2-addr. RPC gets a mask/stack-slot Clean up call/fun ABI Fix mask RET, RET_F Fix print stack slot Remove isMultiHead, isMultiTail Better all-asm print * Beef up Newton test * fixed main pr problems * regmask extra comments * Clean pass normal tests New fcn print upgrades several golden rule tests. Move the unlink-all from the walk::err call (which IS used during IterPeeps) to GCM. CallEnd upgrades return type based on constant call. Fix unlink to also unlink CallEnd Constant TFP print checks constant Tested an untested Load * Arm basics, minor changes in riscv (#160) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java * add riscv port to readme * arm init * work in progress * arm2 bs * work in progress * clearnup arm * fixed main pr problems * regmask extra comments --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Update Chapter20Test.java * Weaken range-check test since no range checks... test cannot fail (pass) * Fix issue #161 Error adding 'null' and '5' * bug fixs, cloning constants Fix bug missing Find. Fix bug setDef does unordered removal from basic blocks. Use cloned constants for spilling. * RA fixes SplitSelfConflict MUST split before uses and after defs. Split global constants across functions Missing find in union; Inserting split after new inserted wrong place Pre-remove null LRGs before color. Bias color hunts through Phis Shorten InstructionSelection. Rename !unified() to leader() AddMemX86 is 2-addr Add CastXXX to preserve register across cast. * encoding init * encoding init 2 * rex fix * indirect addressing modes * setop change opcode, rename FLOAT_OFFSET * mask sib and modrm with 3 bits, 4th bit goes into rex. * sign extend div with cqo * idiv input is fixed to rax and RMASK, grab input from second slot when encoding * encoding of newx86 * tfp and CallEndNode encoding * sib improvement * more cleanups * more cleanups * Allow a killMap to kill regs. Bugfix scheduler. Bugfix spiller - self-conflict order depended on hashtable iteration order, which depended on System.hashCode which would vary run to run. Improvements to coloring Improvements to spill quality testing * Added flags-kill for XOR * Missing add-deps from fuzzer * Update Chapter14Test.java * fixed order of operands * base rex fixup * more bug fix * remove jukn files * minimal "make release" * further fixes * minor fixes * minor fixes * minor fixes * base != -1 check, precedence fix * Add regkills in IFG building Fix bug LRG union stats Adjust self-conflict splits to be slightly less aggressive In every use-side split, dont split-after-split same block. NewNode is a multi, does not define a register * 64 bit encoding for gpr(s) load * RA improvements Attempt to commute to keep reg-masks compatible. Clones with kills (x86 XOR killing flags to zero) fail before the flag-def. Bugfix failed to color last lrg. More smarts on picking trivial LRGs during coloring Drop isSplit, add SplitNode Pass regalloc round to all splits for debugging Self-conflict no split on loop backedge. Handle split that gets made goes dead No-reg-mask spilling handles case with many def/uses. Add DivIX86 BrainFuck, MergeSort now allocate as testing * 8 bit imm support * conditional jump fix * Push l,ast change, unbreak branch * XOR remove extra opcode, imm size supports 8 bit. * Finally pass the `new [u8]` double-copy test * Fix several ARM reg setup problems Finish Split bypass check ARM: rsp is also a fixed zero, in R31. R0 is a valid register. CallArm prints args, passes TFP for arg selection. Does not return a register. All CallEndARM which DOES return a register. Parms missing tfp for float args. ProjARM after a New missing regs. RetARM missing RPC. Remove hopeful DivIX86 * 64 bit imm for alu ops encoded in extra reg then use it in Op as an operand * Bunch of minor arm, risc fixes small constants not cloning * jump invert encoding * warning comment * Much ARM fixage One More Go-Round with Better Biased Colors Fix bug picking deepest spills to bias. ARM: remove default 2-address. Fix regmask overflow. Rename some ops to be canonical. * Fixes to release Port does not have to be in a Simple named directory. Call convention is just a string, no enum Missed some tests running RISC, ARM. * Minor release change to allow another foreign port * better split encoding and fix CallInMask * notNode * fix callInMask * riscv init * riscv basic ALU ops and types of instructions * Allowing non-Simple repo ports Expanding public API slightly so alternative (non-Simple repo) ports can happen. RegMask supports single-bit-set and a long bitmask. Bugfix GCM splitting multiple global constants. Bugfix LCM grabbing a projection vs multi Bugfix RegAlloc when avoiding splitting a clonable Source code in release More timing in CodeGen * riscv indirect addressing and function calls * Cleanup call ABI presentation Everybody uses the same names, same code shapes * Caller save regs ...not callee save yet * Expose caller save regs * Store Risc adjustments, no imm in MemOpRISC * specific print encoding for cpus * merge fixup * More x86,risc,arm call convention fixes Collect callee and caller save masks (inverts of each other, except for spills). Reorg layout to be closer to each other; remove dead mask definitions; add final keyword * lui fix up with 32 bit immediates * Callee-save registers Callee-save registers and masks and inserting LRGs for same and spilling and picking good spills. Insert CalleeSaveNodes and edges to RetXXXs and RegMasks for those edges. Iterators for regmask. Better printing post-allocation * RegMask supports 128 bits All regs available arm, risc. MergeSort compiles & runs * added flags splitting * Fix risc5 compares No flags on risc5. Branch takes 2 inputs (one can be imm12). Float compare only sets a GPR to 0/1. Bool has a isFloat Common risc5 imm-form handling * extend test cases * imm encoding, flags fixed * grep for Todo: relocs * float riscv fixed * flags for set * lui fix for small bits [0, 12] * arm init * arm last touches * fmov still needs fix * minor fixes * minor fixes(2) * got rid of unnecessary stuff * create ch21 * remove encodings from chapter20 dir * 1/2 pass cleaning up ARM encoding API * ARM encoding compiles * Risc R_TYPE and I_TYPE * Progress RISC encodings Missing lots of stuff still * progress risc encoding * Bring back `make view` * risc encodings compile * Update README.md * Fold up redundant OP/OP-FP risc5 encoding * x86 mem opcodes first cut break out MemOp encodings into parts; change x86 indirectAdr to use Encoding class, remove redundant asserts & code * x86 mem op encodings * x86 reg+imm forms * most reg-reg forms * More x86 encodings * Last few encodings * switch to binary literals over decimals for opcodes * logical immediates fixed, bitwise ops imm forms * tfp and setArm fixed * PC relative float for riscv * readme init * Merged pr/169 * Fixed the array1 test encodings Clean up branch invert logic during BB layout Cleanup branch target printing for RISC,ARM ImmRISC ops mask imm12 to 12 bits Fix NewRISC; hardcode RPC as part of call Fix RISC inverting branchs >,>= * floatarm encoding * tfparm and intarm * uncontional jump encoding for arm and riscv * smarter IntARM, Intx86 does not always include rex. * REX after PREFIX * fixup load and store indirect * updated rex prefix, if its 0x40 then its not needed * Rex fix for floats, got rid of unnecessary stuff. * rename to camel case, better param name * add test export * merged * store opt * store and load optimised forms handled * tfp patch * Store/load float arm/riscv/x64 * got rid of debug print * splits for x86, riscv and arm * readme fixups * Create ASimpleReply.md * Update ASimpleReply.md * Update ASimpleReply.md * Update ASimpleReply.md * Update ASimpleReply.md * Update ASimpleReply.md * fix global symbol numbering for constants * ASimpleReply setup * fix TFP pathc on arm and riscv, keep symbols in good order. * fix opcode for store and loads, memOpRisc for Store and splitting * replace hard coded opcodes with major opcodes * Update README.md * replace hard coded opcodes with major opcodes * Fix some formatting (#184) * Fix title in chapter 19 * Wrap nseted scope images onto newlines in chapter 3 * add asserts and fix reg numbering * add asserts and fix reg numbering * minor updates * revert changes * remove constant pool from encoder TODO: add flag for JIT * extend test cases * minor fixes * minor fixes * update float test case * stringHash fix on x64 * remove conditional ConstantPool write * brainfuck minor fixes * minor arm fixes * split fix * brainfuck and fib * brain fuck --------- Co-authored-by: Cliff Click <cliffc@acm.org> Co-authored-by: godalming123 <68993177+godalming123@users.noreply.github.com> commit cac92d551e16e13530f0254548476ab122d30b8b Author: Cliff Click <cliffc@acm.org> Date: Sun Apr 6 19:23:47 2025 -0700 Math lib by default for CI commit 07530e8c5ec3e2ab98be92769d6abcb9024cfbea Author: Cliff Click <cliffc@acm.org> Date: Sun Apr 6 19:19:49 2025 -0700 ignore gcc warning during CI commit 7030243523999bd647938cd39013f47d9dfc6e88 Author: Cliff Click <cliffc@acm.org> Date: Sun Apr 6 19:09:10 2025 -0700 Debug printing for CI commit a81794df00369f6adf4a06e2f0247d309a196e27 Author: Cliff Click <cliffc@acm.org> Date: Sun Apr 6 16:22:38 2025 -0700 Fix sieve RA returns maxSlot+1 FunX86 prints as `subi`, uses Sub; Ret uses Add X86 knows that always RPC is passed on stack "as if" a stack arg. sieve uses unsigned ints. Driver expects an array pointer commit 4c4778f8cc3dd4fd354832bfb0ba1a8c03ee744d Author: Cliff Click <cliffc@acm.org> Date: Sun Apr 6 14:43:39 2025 -0700 Fix store ref/0x66 prefix commit 819963deb617ea622a27918101867aab3c522c3c Author: Cliff Click <cliffc@acm.org> Date: Sun Apr 6 11:42:55 2025 -0700 More encoding bugs Stack adjust needs REX. Boolean load/store same as U8 Calling calloc needs size of 1 in RCX, bytes in RDX commit abd2fc675d0fc29d5b525cdbe5b949e1c85b1e7e Merge: e30990ad 8db8f750 Author: Cliff Click <cliffc@acm.org> Date: Sun Apr 6 10:37:17 2025 -0700 Merge branch 'ch21' of https://github.com/SeaOfNodes/Simple into ch21 commit e30990ad8929084cf433894843adc04b577d6e9f Author: Cliff Click <cliffc@acm.org> Date: Sun Apr 6 10:37:16 2025 -0700 End-to-end testing of x86 encoding commit 8db8f75006a1daf522976ee97048aa4f06780c8e Author: Tamás Hadházy <85063808+Hels15@users.noreply.github.com> Date: Sun Apr 6 17:11:18 2025 +0100 StringHash x64 fixed (#195) * One more missed case * READMEs * Update README.md * Update README.md * Move printing into print dir * Move codegen into codegen * Framework for Reg Alloc * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * First cut BuildLRG RegMask.EMPTY -> null * fix minor bug * Setup for first simple split * First cut split code for mismatch regs * delete non existing matches * replace RegMask.Empty with null * Live Ranges U-F BuildLRG for Phi Add CmpFX86 * FPR return CallEndX86 needed to tell fp return from gpr ret Call has no output regs. * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * Next cut regalloc Building IFG. Coloring. Some nested spilling Optimize divf/con -> mul times inverse * turn off ch20 tests * Update Chapter20Test.java * RISC-V basic isel (#156) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Bring pr/154 to ch 20 RISC5 changes. Some missing x86 ops. Still needs some calling convention love. * First cut regalloc working Biased coloring; tracking splits around a LRG. Separate split for self-conflicts. postColor to remove junk splits. Some minor riscv fixes. * Compile some riscv codes If SplitEmptyMask does not apply, fall back to normal split. Missing CmpFRISC. Renamed riscv flags. RetRISC handls FPR remove extra RegMask constructurs * add riscv port to readme * arm init * Progress reg alloc Handle multi-node projections. A little smarter about 2-addr scheduling RegAlloc must-have single registers removes from other masks instead of interfering; had the mask removal backwards in a few cases. LRG/RegMask COW expand masks before mutating. Clone small constants instead of spilling. Alloc & ProjX86 get output reg masks. Full print large reg masks with sp+offset * UF LRGs can be null during coloring * Finish UF find * work in progress * README updates * Fix ch19 mis-merge? * x86 call args, risc5 load/store regs * arm2 bs * Push RISC WMASK avoid writing to RSP * work in progress * clearnup arm * Progress calling convention allocations Call unlinks before reg alloc. CallEnd tracks RPC CallX86 requires TFP for correct ABI Bias Color tracks 2-addr. RPC gets a mask/stack-slot Clean up call/fun ABI Fix mask RET, RET_F Fix print stack slot Remove isMultiHead, isMultiTail Better all-asm print * Beef up Newton test * fixed main pr problems * regmask extra comments * Clean pass normal tests New fcn print upgrades several golden rule tests. Move the unlink-all from the walk::err call (which IS used during IterPeeps) to GCM. CallEnd upgrades return type based on constant call. Fix unlink to also unlink CallEnd Constant TFP print checks constant Tested an untested Load * Arm basics, minor changes in riscv (#160) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java * add riscv port to readme * arm init * work in progress * arm2 bs * work in progress * clearnup arm * fixed main pr problems * regmask extra comments --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Update Chapter20Test.java * Weaken range-check test since no range checks... test cannot fail (pass) * Fix issue #161 Error adding 'null' and '5' * bug fixs, cloning constants Fix bug missing Find. Fix bug setDef does unordered removal from basic blocks. Use cloned constants for spilling. * RA fixes SplitSelfConflict MUST split before uses and after defs. Split global constants across functions Missing find in union; Inserting split after new inserted wrong place Pre-remove null LRGs before color. Bias color hunts through Phis Shorten InstructionSelection. Rename !unified() to leader() AddMemX86 is 2-addr Add CastXXX to preserve register across cast. * encoding init * encoding init 2 * rex fix * indirect addressing modes * setop change opcode, rename FLOAT_OFFSET * mask sib and modrm with 3 bits, 4th bit goes into rex. * sign extend div with cqo * idiv input is fixed to rax and RMASK, grab input from second slot when encoding * encoding of newx86 * tfp and CallEndNode encoding * sib improvement * more cleanups * more cleanups * Allow a killMap to kill regs. Bugfix scheduler. Bugfix spiller - self-conflict order depended on hashtable iteration order, which depended on System.hashCode which would vary run to run. Improvements to coloring Improvements to spill quality testing * Added flags-kill for XOR * Missing add-deps from fuzzer * Update Chapter14Test.java * fixed order of operands * base rex fixup * more bug fix * remove jukn files * minimal "make release" * further fixes * minor fixes * minor fixes * minor fixes * base != -1 check, precedence fix * Add regkills in IFG building Fix bug LRG union stats Adjust self-conflict splits to be slightly less aggressive In every use-side split, dont split-after-split same block. NewNode is a multi, does not define a register * 64 bit encoding for gpr(s) load * RA improvements Attempt to commute to keep reg-masks compatible. Clones with kills (x86 XOR killing flags to zero) fail before the flag-def. Bugfix failed to color last lrg. More smarts on picking trivial LRGs during coloring Drop isSplit, add SplitNode Pass regalloc round to all splits for debugging Self-conflict no split on loop backedge. Handle split that gets made goes dead No-reg-mask spilling handles case with many def/uses. Add DivIX86 BrainFuck, MergeSort now allocate as testing * 8 bit imm support * conditional jump fix * Push l,ast change, unbreak branch * XOR remove extra opcode, imm size supports 8 bit. * Finally pass the `new [u8]` double-copy test * Fix several ARM reg setup problems Finish Split bypass check ARM: rsp is also a fixed zero, in R31. R0 is a valid register. CallArm prints args, passes TFP for arg selection. Does not return a register. All CallEndARM which DOES return a register. Parms missing tfp for float args. ProjARM after a New missing regs. RetARM missing RPC. Remove hopeful DivIX86 * 64 bit imm for alu ops encoded in extra reg then use it in Op as an operand * Bunch of minor arm, risc fixes small constants not cloning * jump invert encoding * warning comment * Much ARM fixage One More Go-Round with Better Biased Colors Fix bug picking deepest spills to bias. ARM: remove default 2-address. Fix regmask overflow. Rename some ops to be canonical. * Fixes to release Port does not have to be in a Simple named directory. Call convention is just a string, no enum Missed some tests running RISC, ARM. * Minor release change to allow another foreign port * better split encoding and fix CallInMask * notNode * fix callInMask * riscv init * riscv basic ALU ops and types of instructions * Allowing non-Simple repo ports Expanding public API slightly so alternative (non-Simple repo) ports can happen. RegMask supports single-bit-set and a long bitmask. Bugfix GCM splitting multiple global constants. Bugfix LCM grabbing a projection vs multi Bugfix RegAlloc when avoiding splitting a clonable Source code in release More timing in CodeGen * riscv indirect addressing and function calls * Cleanup call ABI presentation Everybody uses the same names, same code shapes * Caller save regs ...not callee save yet * Expose caller save regs * Store Risc adjustments, no imm in MemOpRISC * specific print encoding for cpus * merge fixup * More x86,risc,arm call convention fixes Collect callee and caller save masks (inverts of each other, except for spills). Reorg layout to be closer to each other; remove dead mask definitions; add final keyword * lui fix up with 32 bit immediates * Callee-save registers Callee-save registers and masks and inserting LRGs for same and spilling and picking good spills. Insert CalleeSaveNodes and edges to RetXXXs and RegMasks for those edges. Iterators for regmask. Better printing post-allocation * RegMask supports 128 bits All regs available arm, risc. MergeSort compiles & runs * added flags splitting * Fix risc5 compares No flags on risc5. Branch takes 2 inputs (one can be imm12). Float compare only sets a GPR to 0/1. Bool has a isFloat Common risc5 imm-form handling * extend test cases * imm encoding, flags fixed * grep for Todo: relocs * float riscv fixed * flags for set * lui fix for small bits [0, 12] * arm init * arm last touches * fmov still needs fix * minor fixes * minor fixes(2) * got rid of unnecessary stuff * create ch21 * remove encodings from chapter20 dir * 1/2 pass cleaning up ARM encoding API * ARM encoding compiles * Risc R_TYPE and I_TYPE * Progress RISC encodings Missing lots of stuff still * progress risc encoding * Bring back `make view` * risc encodings compile * Update README.md * Fold up redundant OP/OP-FP risc5 encoding * x86 mem opcodes first cut break out MemOp encodings into parts; change x86 indirectAdr to use Encoding class, remove redundant asserts & code * x86 mem op encodings * x86 reg+imm forms * most reg-reg forms * More x86 encodings * Last few encodings * switch to binary literals over decimals for opcodes * logical immediates fixed, bitwise ops imm forms * tfp and setArm fixed * PC relative float for riscv * readme init * Merged pr/169 * Fixed the array1 test encodings Clean up branch invert logic during BB layout Cleanup branch target printing for RISC,ARM ImmRISC ops mask imm12 to 12 bits Fix NewRISC; hardcode RPC as part of call Fix RISC inverting branchs >,>= * floatarm encoding * tfparm and intarm * uncontional jump encoding for arm and riscv * smarter IntARM, Intx86 does not always include rex. * REX after PREFIX * fixup load and store indirect * updated rex prefix, if its 0x40 then its not needed * Rex fix for floats, got rid of unnecessary stuff. * rename to camel case, better param name * add test export * merged * store opt * store and load optimised forms handled * tfp patch * Store/load float arm/riscv/x64 * got rid of debug print * splits for x86, riscv and arm * readme fixups * Create ASimpleReply.md * Update ASimpleReply.md * Update ASimpleReply.md * Update ASimpleReply.md * Update ASimpleReply.md * Update ASimpleReply.md * fix global symbol numbering for constants * ASimpleReply setup * fix TFP pathc on arm and riscv, keep symbols in good order. * fix opcode for store and loads, memOpRisc for Store and splitting * replace hard coded opcodes with major opcodes * Update README.md * replace hard coded opcodes with major opcodes * Fix some formatting (#184) * Fix title in chapter 19 * Wrap nseted scope images onto newlines in chapter 3 * add asserts and fix reg numbering * add asserts and fix reg numbering * minor updates * revert changes * remove constant pool from encoder TODO: add flag for JIT * extend test cases * minor fixes * minor fixes * update float test case * stringHash fix on x64 * remove conditional ConstantPool write --------- Co-authored-by: Cliff Click <cliffc@acm.org> Co-authored-by: godalming123 <68993177+godalming123@users.noreply.github.com> commit bb97129daa1504d67a8e3a76f16d6db15f7e5e7b Author: Cliff Click <cliffc@acm.org> Date: Sat Apr 5 15:55:48 2025 -0700 Float uses unsigned compare commit 532b34d34fde93b2841f4c9544c673a0d89434ea Author: Cliff Click <cliffc@acm.org> Date: Sat Apr 5 15:53:10 2025 -0700 Fix vs0 bugs Use a BoolNode.NE instead of inverting outputs; the outputs are already matched and cannot be inverted. Use a `swap` not an `invert` when switching sides in a compare. commit 7277ab049ab9cae5fa64cd5ea958f69278f773a4 Author: Cliff Click <cliffc@acm.org> Date: Sat Apr 5 13:58:07 2025 -0700 Fix elf constants Record more relocation info in the machine-specific encodings. Use it when building ELF. Switch to simpler Newtons; some encoding bug in the complex one. commit fc7d2bce649854982a3c16de164bbaafec11bc27 Author: Tamás Hadházy <85063808+Hels15@users.noreply.github.com> Date: Sat Apr 5 19:38:40 2025 +0100 Ch20 encoding (#194) * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * READMEs * Update README.md * Update README.md * Move printing into print dir * Move codegen into codegen * Framework for Reg Alloc * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * First cut BuildLRG RegMask.EMPTY -> null * fix minor bug * Setup for first simple split * First cut split code for mismatch regs * delete non existing matches * replace RegMask.Empty with null * Live Ranges U-F BuildLRG for Phi Add CmpFX86 * FPR return CallEndX86 needed to tell fp return from gpr ret Call has no output regs. * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * Next cut regalloc Building IFG. Coloring. Some nested spilling Optimize divf/con -> mul times inverse * turn off ch20 tests * Update Chapter20Test.java * RISC-V basic isel (#156) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Bring pr/154 to ch 20 RISC5 changes. Some missing x86 ops. Still needs some calling convention love. * First cut regalloc working Biased coloring; tracking splits around a LRG. Separate split for self-conflicts. postColor to remove junk splits. Some minor riscv fixes. * Compile some riscv codes If SplitEmptyMask does not apply, fall back to normal split. Missing CmpFRISC. Renamed riscv flags. RetRISC handls FPR remove extra RegMask constructurs * add riscv port to readme * arm init * Progress reg alloc Handle multi-node projections. A little smarter about 2-addr scheduling RegAlloc must-have single registers removes from other masks instead of interfering; had the mask removal backwards in a few cases. LRG/RegMask COW expand masks before mutating. Clone small constants instead of spilling. Alloc & ProjX86 get output reg masks. Full print large reg masks with sp+offset * UF LRGs can be null during coloring * Finish UF find * work in progress * README updates * Fix ch19 mis-merge? * x86 call args, risc5 load/store regs * arm2 bs * Push RISC WMASK avoid writing to RSP * work in progress * clearnup arm * Progress calling convention allocations Call unlinks before reg alloc. CallEnd tracks RPC CallX86 requires TFP for correct ABI Bias Color tracks 2-addr. RPC gets a mask/stack-slot Clean up call/fun ABI Fix mask RET, RET_F Fix print stack slot Remove isMultiHead, isMultiTail Better all-asm print * Beef up Newton test * fixed main pr problems * regmask extra comments * Clean pass normal tests New fcn print upgrades several golden rule tests. Move the unlink-all from the walk::err call (which IS used during IterPeeps) to GCM. CallEnd upgrades return type based on constant call. Fix unlink to also unlink CallEnd Constant TFP print checks constant Tested an untested Load * Arm basics, minor changes in riscv (#160) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java * add riscv port to readme * arm init * work in progress * arm2 bs * work in progress * clearnup arm * fixed main pr problems * regmask extra comments --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Update Chapter20Test.java * Weaken range-check test since no range checks... test cannot fail (pass) * Fix issue #161 Error adding 'null' and '5' * bug fixs, cloning constants Fix bug missing Find. Fix bug setDef does unordered removal from basic blocks. Use cloned constants for spilling. * RA fixes SplitSelfConflict MUST split before uses and after defs. Split global constants across functions Missing find in union; Inserting split after new inserted wrong place Pre-remove null LRGs before color. Bias color hunts through Phis Shorten InstructionSelection. Rename !unified() to leader() AddMemX86 is 2-addr Add CastXXX to preserve register across cast. * encoding init * encoding init 2 * rex fix * indirect addressing modes * setop change opcode, rename FLOAT_OFFSET * mask sib and modrm with 3 bits, 4th bit goes into rex. * sign extend div with cqo * idiv input is fixed to rax and RMASK, grab input from second slot when encoding * encoding of newx86 * tfp and CallEndNode encoding * sib improvement * more cleanups * more cleanups * Allow a killMap to kill regs. Bugfix scheduler. Bugfix spiller - self-conflict order depended on hashtable iteration order, which depended on System.hashCode which would vary run to run. Improvements to coloring Improvements to spill quality testing * Added flags-kill for XOR * Missing add-deps from fuzzer * Update Chapter14Test.java * fixed order of operands * base rex fixup * more bug fix * remove jukn files * minimal "make release" * further fixes * minor fixes * minor fixes * minor fixes * base != -1 check, precedence fix * Add regkills in IFG building Fix bug LRG union stats Adjust self-conflict splits to be slightly less aggressive In every use-side split, dont split-after-split same block. NewNode is a multi, does not define a register * 64 bit encoding for gpr(s) load * RA improvements Attempt to commute to keep reg-masks compatible. Clones with kills (x86 XOR killing flags to zero) fail before the flag-def. Bugfix failed to color last lrg. More smarts on picking trivial LRGs during coloring Drop isSplit, add SplitNode Pass regalloc round to all splits for debugging Self-conflict no split on loop backedge. Handle split that gets made goes dead No-reg-mask spilling handles case with many def/uses. Add DivIX86 BrainFuck, MergeSort now allocate as testing * 8 bit imm support * conditional jump fix * Push l,ast change, unbreak branch * XOR remove extra opcode, imm size supports 8 bit. * Finally pass the `new [u8]` double-copy test * Fix several ARM reg setup problems Finish Split bypass check ARM: rsp is also a fixed zero, in R31. R0 is a valid register. CallArm prints args, passes TFP for arg selection. Does not return a register. All CallEndARM which DOES return a register. Parms missing tfp for float args. ProjARM after a New missing regs. RetARM missing RPC. Remove hopeful DivIX86 * 64 bit imm for alu ops encoded in extra reg then use it in Op as an operand * Bunch of minor arm, risc fixes small constants not cloning * jump invert encoding * warning comment * Much ARM fixage One More Go-Round with Better Biased Colors Fix bug picking deepest spills to bias. ARM: remove default 2-address. Fix regmask overflow. Rename some ops to be canonical. * Fixes to release Port does not have to be in a Simple named directory. Call convention is just a string, no enum Missed some tests running RISC, ARM. * Minor release change to allow another foreign port * better split encoding and fix CallInMask * notNode * fix callInMask * riscv init * riscv basic ALU ops and types of instructions * Allowing non-Simple repo ports Expanding public API slightly so alternative (non-Simple repo) ports can happen. RegMask supports single-bit-set and a long bitmask. Bugfix GCM splitting multiple global constants. Bugfix LCM grabbing a projection vs multi Bugfix RegAlloc when avoiding splitting a clonable Source code in release More timing in CodeGen * riscv indirect addressing and function calls * Cleanup call ABI presentation Everybody uses the same names, same code shapes * Caller save regs ...not callee save yet * Expose caller save regs * Store Risc adjustments, no imm in MemOpRISC * specific print encoding for cpus * merge fixup * More x86,risc,arm call convention fixes Collect callee and caller save masks (inverts of each other, except for spills). Reorg layout to be closer to each other; remove dead mask definitions; add final keyword * lui fix up with 32 bit immediates * Callee-save registers Callee-save registers and masks and inserting LRGs for same and spilling and picking good spills. Insert CalleeSaveNodes and edges to RetXXXs and RegMasks for those edges. Iterators for regmask. Better printing post-allocation * RegMask supports 128 bits All regs available arm, risc. MergeSort compiles & runs * added flags splitting * Fix risc5 compares No flags on risc5. Branch takes 2 inputs (one can be imm12). Float compare only sets a GPR to 0/1. Bool has a isFloat Common risc5 imm-form handling * extend test cases * imm encoding, flags fixed * grep for Todo: relocs * float riscv fixed * flags for set * lui fix for small bits [0, 12] * arm init * arm last touches * fmov still needs fix * minor fixes * minor fixes(2) * got rid of unnecessary stuff * create ch21 * remove encodings from chapter20 dir * 1/2 pass cleaning up ARM encoding API * ARM encoding compiles * Risc R_TYPE and I_TYPE * Progress RISC encodings Missing lots of stuff still * progress risc encoding * Bring back `make view` * risc encodings compile * Update README.md * Fold up redundant OP/OP-FP risc5 encoding * x86 mem opcodes first cut break out MemOp encodings into parts; change x86 indirectAdr to use Encoding class, remove redundant asserts & code * x86 mem op encodings * x86 reg+imm forms * most reg-reg forms * More x86 encodings * Last few encodings * switch to binary literals over decimals for opcodes * logical immediates fixed, bitwise ops imm forms * tfp and setArm fixed * PC relative float for riscv * readme init * Merged pr/169 * Fixed the array1 test encodings Clean up branch invert logic during BB layout Cleanup branch target printing for RISC,ARM ImmRISC ops mask imm12 to 12 bits Fix NewRISC; hardcode RPC as part of call Fix RISC inverting branchs >,>= * floatarm encoding * tfparm and intarm * uncontional jump encoding for arm and riscv * smarter IntARM, Intx86 does not always include rex. * REX after PREFIX * fixup load and store indirect * updated rex prefix, if its 0x40 then its not needed * Rex fix for floats, got rid of unnecessary stuff. * rename to camel case, better param name * add test export * merged * store opt * store and load optimised forms handled * tfp patch * Store/load float arm/riscv/x64 * got rid of debug print * splits for x86, riscv and arm * readme fixups * Create ASimpleReply.md * Update ASimpleReply.md * Update ASimpleReply.md * Update ASimpleReply.md * Update ASimpleReply.md * Update ASimpleReply.md * fix global symbol numbering for constants * ASimpleReply setup * fix TFP pathc on arm and riscv, keep symbols in good order. * fix opcode for store and loads, memOpRisc for Store and splitting * replace hard coded opcodes with major opcodes * Update README.md * replace hard coded opcodes with major opcodes * Fix some formatting (#184) * Fix title in chapter 19 * Wrap nseted scope images onto newlines in chapter 3 * add asserts and fix reg numbering * add asserts and fix reg numbering * minor updates * revert changes * remove constant pool from encoder TODO: add flag for JIT * extend test cases * minor fixes * minor fixes * update float test case --------- Co-authored-by: Cliff Click <cliffc@acm.org> Co-authored-by: godalming123 <68993177+godalming123@users.noreply.github.com> commit 1741cb3039555b1446a1b0c1c3768f3763a0d9e6 Author: Cliff Click <cliffc@acm.org> Date: Sat Apr 5 11:30:35 2025 -0700 Add in-memory linker Encoding supports writing cpool to either Elf or in memory ElfFile writes constant pool commit 631b699a4cf65f6c91a24df49329bb285aec09fc Author: Cliff Click <cliffc@acm.org> Date: Sat Apr 5 10:04:42 2025 -0700 Move PORTS into CodeGen commit 3016c9fcf8361cb1a0c494dbb7b997df33df5728 Author: Cliff Click <cliffc@acm.org> Date: Fri Apr 4 16:32:25 2025 -0700 Support constant tuples commit 887f3d9caf68b051c630bab6c8895778be8f044f Author: Cliff Click <cliffc@acm.org> Date: Fri Apr 4 12:29:46 2025 -0700 Partial progress cpool in ELF Align functions to 16b commit cc32b0cf801620f1d289cb6ae183e079111540fc Author: Cliff Click <cliffc@acm.org> Date: Thu Apr 3 11:31:06 2025 -0700 Fix RA parallel phi assign Force spills when phi uses phi same loop header. This is effectively a parallel assignment, generally shifting values from one iteration to the next. commit 37b46799c03244241d589085e24582bef8a59efb Author: Cliff Click <cliffc@acm.org> Date: Thu Apr 3 09:32:33 2025 -0700 Allow stacked cloneables For RISC 2-part immediates, both parts can be treated as constants and moved/cloned commit 35057d50ae21e143bf89158a65e2db139fcb76fa Author: Cliff Click <cliffc@acm.org> Date: Wed Apr 2 16:39:17 2025 -0700 Bug Fix RegAlloc RA, self-conflict around backedge of loop predates loop, must still split. Eager splitting here leads to bad allocations, but never splitting is broken. Move building adj lists into IFG from Coalesce. testNewtonFloat not so fragile. Hack around bug SetArm commit fa846b232a9eca4a7691ab59b3ea92e53c6b85a6 Merge: b73a1146 abdb59f9 Author: Cliff Click <cliffc@acm.org> Date: Wed Apr 2 14:55:51 2025 -0700 Merge branch 'ch21' of https://github.com/SeaOfNodes/Simple into ch21 commit b73a1146a1af2eabe6af1ba043fc6ff5ef8731ab Author: Cliff Click <cliffc@acm.org> Date: Wed Apr 2 14:55:48 2025 -0700 parse negative floats commit abdb59f94008f8620d8c654980c8850ce94a15ad Author: Darrick Wiebe <darrickw@gmail.com> Date: Wed Apr 2 14:45:45 2025 -0400 Make eq public on ConstantNode (#193) commit 9452d347d07ff38f6897be0ff8e9a50b7b43ddc3 Author: Cliff Click <cliffc@acm.org> Date: Wed Apr 2 11:32:42 2025 -0700 Stronger inf loop assert Handle returning value after inf loop. Loop nesting check does not use depth until after loop tree is built. ASMPrint can be called no machine. commit 4cd6517df6f9fbf77215525cf0b736ab4f90b143 Author: Cliff Click <cliffc@acm.org> Date: Tue Apr 1 19:59:02 2025 -0700 Fix bug encoded inf loops Assert functions laid out end to end. Fix bug with layout on inf loops. Swap default Never jump order. commit f4542326141578053122f8e3c11fd3a1932c6866 Author: Tamás Hadházy <85063808+Hels15@users.noreply.github.com> Date: Wed Apr 2 03:37:23 2025 +0100 Minor bug fixes (#190) * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * READMEs * Update README.md * Update README.md * Move printing into print dir * Move codegen into codegen * Framework for Reg Alloc * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * First cut BuildLRG RegMask.EMPTY -> null * fix minor bug * Setup for first simple split * First cut split code for mismatch regs * delete non existing matches * replace RegMask.Empty with null * Live Ranges U-F BuildLRG for Phi Add CmpFX86 * FPR return CallEndX86 needed to tell fp return from gpr ret Call has no output regs. * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * Next cut regalloc Building IFG. Coloring. Some nested spilling Optimize divf/con -> mul times inverse * turn off ch20 tests * Update Chapter20Test.java * RISC-V basic isel (#156) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Bring pr/154 to ch 20 RISC5 changes. Some missing x86 ops. Still needs some calling convention love. * First cut regalloc working Biased coloring; tracking splits around a LRG. Separate split for self-conflicts. postColor to remove junk splits. Some minor riscv fixes. * Compile some riscv codes If SplitEmptyMask does not apply, fall back to normal split. Missing CmpFRISC. Renamed riscv flags. RetRISC handls FPR remove extra RegMask constructurs * add riscv port to readme * arm init * Progress reg alloc Handle multi-node projections. A little smarter about 2-addr scheduling RegAlloc must-have single registers removes from other masks instead of interfering; had the mask removal backwards in a few cases. LRG/RegMask COW expand masks before mutating. Clone small constants instead of spilling. Alloc & ProjX86 get output reg masks. Full print large reg masks with sp+offset * UF LRGs can be null during coloring * Finish UF find * work in progress * README updates * Fix ch19 mis-merge? * x86 call args, risc5 load/store regs * arm2 bs * Push RISC WMASK avoid writing to RSP * work in progress * clearnup arm * Progress calling convention allocations Call unlinks before reg alloc. CallEnd tracks RPC CallX86 requires TFP for correct ABI Bias Color tracks 2-addr. RPC gets a mask/stack-slot Clean up call/fun ABI Fix mask RET, RET_F Fix print stack slot Remove isMultiHead, isMultiTail Better all-asm print * Beef up Newton test * fixed main pr problems * regmask extra comments * Clean pass normal tests New fcn print upgrades several golden rule tests. Move the unlink-all from the walk::err call (which IS used during IterPeeps) to GCM. CallEnd upgrades return type based on constant call. Fix unlink to also unlink CallEnd Constant TFP print checks constant Tested an untested Load * Arm basics, minor changes in riscv (#160) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java * add riscv port to readme * arm init * work in progress * arm2 bs * work in progress * clearnup arm * fixed main pr problems * regmask extra comments --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Update Chapter20Test.java * Weaken range-check test since no range checks... test cannot fail (pass) * Fix issue #161 Error adding 'null' and '5' * bug fixs, cloning constants Fix bug missing Find. Fix bug setDef does unordered removal from basic blocks. Use cloned constants for spilling. * RA fixes SplitSelfConflict MUST split before uses and after defs. Split global constants across functions Missing find in union; Inserting split after new inserted wrong place Pre-remove null LRGs before color. Bias color hunts through Phis Shorten InstructionSelection. Rename !unified() to leader() AddMemX86 is 2-addr Add CastXXX to preserve register across cast. * encoding init * encoding init 2 * rex fix * indirect addressing modes * setop change opcode, rename FLOAT_OFFSET * mask sib and modrm with 3 bits, 4th bit goes into rex. * sign extend div with cqo * idiv input is fixed to rax and RMASK, grab input from second slot when encoding * encoding of newx86 * tfp and CallEndNode encoding * sib improvement * more cleanups * more cleanups * Allow a killMap to kill regs. Bugfix scheduler. Bugfix spiller - self-conflict order depended on hashtable iteration order, which depended on System.hashCode which would vary run to run. Improvements to coloring Improvements to spill quality testing * Added flags-kill for XOR * Missing add-deps from fuzzer * Update Chapter14Test.java * fixed order of operands * base rex fixup * more bug fix * remove jukn files * minimal "make release" * further fixes * minor fixes * minor fixes * minor fixes * base != -1 check, precedence fix * Add regkills in IFG building Fix bug LRG union stats Adjust self-conflict splits to be slightly less aggressive In every use-side split, dont split-after-split same block. NewNode is a multi, does not define a register * 64 bit encoding for gpr(s) load * RA improvements Attempt to commute to keep reg-masks compatible. Clones with kills (x86 XOR killing flags to zero) fail before the flag-def. Bugfix failed to color last lrg. More smarts on picking trivial LRGs during coloring Drop isSplit, add SplitNode Pass regalloc round to all splits for debugging Self-conflict no split on loop backedge. Handle split that gets made goes dead No-reg-mask spilling handles case with many def/uses. Add DivIX86 BrainFuck, MergeSort now allocate as testing * 8 bit imm support * conditional jump fix * Push l,ast change, unbreak branch * XOR remove extra opcode, imm size supports 8 bit. * Finally pass the `new [u8]` double-copy test * Fix several ARM reg setup problems Finish Split bypass check ARM: rsp is also a fixed zero, in R31. R0 is a valid register. CallArm prints args, passes TFP for arg selection. Does not return a register. All CallEndARM which DOES return a register. Parms missing tfp for float args. ProjARM after a New missing regs. RetARM missing RPC. Remove hopeful DivIX86 * 64 bit imm for alu ops encoded in extra reg then use it in Op as an operand * Bunch of minor arm, risc fixes small constants not cloning * jump invert encoding * warning comment * Much ARM fixage One More Go-Round with Better Biased Colors Fix bug picking deepest spills to bias. ARM: remove default 2-address. Fix regmask overflow. Rename some ops to be canonical. * Fixes to release Port does not have to be in a Simple named directory. Call convention is just a string, no enum Missed some tests running RISC, ARM. * Minor release change to allow another foreign port * better split encoding and fix CallInMask * notNode * fix callInMask * riscv init * riscv basic ALU ops and types of instructions * Allowing non-Simple repo ports Expanding public API slightly so alternative (non-Simple repo) ports can happen. RegMask supports single-bit-set and a long bitmask. Bugfix GCM splitting multiple global constants. Bugfix LCM grabbing a projection vs multi Bugfix RegAlloc when avoiding splitting a clonable Source code in release More timing in CodeGen * riscv indirect addressing and function calls * Cleanup call ABI presentation Everybody uses the same names, same code shapes * Caller save regs ...not callee save yet * Expose caller save regs * Store Risc adjustments, no imm in MemOpRISC * specific print encoding for cpus * merge fixup * More x86,risc,arm call convention fixes Collect callee and caller save masks (inverts of each other, except for spills). Reorg layout to be closer to each other; remove dead mask definitions; add final keyword * lui fix up with 32 bit immediates * Callee-save registers Callee-save registers and masks and inserting LRGs for same and spilling and picking good spills. Insert CalleeSaveNodes and edges to RetXXXs and RegMasks for those edges. Iterators for regmask. Better printing post-allocation * RegMask supports 128 bits All regs available arm, risc. MergeSort compiles & runs * added flags splitting * Fix risc5 compares No flags on risc5. Branch takes 2 inputs (one can be imm12). Float compare only sets a GPR to 0/1. Bool has a isFloat Common risc5 imm-form handling * extend test cases * imm encoding, flags fixed * grep for Todo: relocs * float riscv fixed * flags for set * lui fix for small bits [0, 12] * arm init * arm last touches * fmov still needs fix * minor fixes * minor fixes(2) * got rid of unnecessary stuff * create ch21 * remove encodings from chapter20 dir * 1/2 pass cleaning up ARM encoding API * ARM encoding compiles * Risc R_TYPE and I_TYPE * Progress RISC encodings Missing lots of stuff still * progress risc encoding * risc encodings compile * Update README.md * Fold up redundant OP/OP-FP risc5 encoding * x86 mem opcodes first cut break out MemOp encodings into parts; change x86 indirectAdr to use Encoding class, remove redundant asserts & code * x86 mem op encodings * x86 reg+imm forms * most reg-reg forms * More x86 encodings * Last few encodings * switch to binary literals over decimals for opcodes * logical immediates fixed, bitwise ops imm forms * tfp and setArm fixed * PC relative float for riscv * readme init * Merged pr/169 * Fixed the array1 test encodings Clean up branch invert logic during BB layout Cleanup branch target printing for RISC,ARM ImmRISC ops mask imm12 to 12 bits Fix NewRISC; hardcode RPC as part of call Fix RISC inverting branchs >,>= * floatarm encoding * tfparm and intarm * uncontional jump encoding for arm and riscv * smarter IntARM, Intx86 does not always include rex. * REX after PREFIX * fixup load and store indirect * updated rex prefix, if its 0x40 then its not needed * Rex fix for floats, got rid of unnecessary stuff. * rename to camel case, better param name * add test export * merged * store opt * store and load optimised forms handled * tfp patch * Store/load float arm/riscv/x64 * got rid of debug print * splits for x86, riscv and arm * readme fixups * fix global symbol numbering for constants * fix TFP pathc on arm and riscv, keep symbols in good order. * fix opcode for store and loads, memOpRisc for Store and splitting * replace hard coded opcodes with major opcodes * replace hard coded opcodes with major opcodes * add asserts and fix reg numbering * add asserts and fix reg numbering * minor updates * revert changes --------- Co-authored-by: Cliff Click <cliffc@acm.org> commit fc783af92ecab0288b94c7b1d6ce1b1e2cab5d42 Author: Cliff Click <cliffc@acm.org> Date: Tue Apr 1 15:42:52 2025 -0700 bugfix commit f6a047a93971574f64a779cf59dc41ff52cdfe27 Author: Cliff Click <cliffc@acm.org> Date: Tue Apr 1 15:05:31 2025 -0700 More infinite loop encodings commit f691d2de7217b04844da51c2c6ecc0fd9137c7d6 Author: Cliff Click <cliffc@acm.org> Date: Tue Apr 1 14:26:46 2025 -0700 RA tighter def of being "in" or "out" of loop commit d8a39c7063ecaa26a3bb962b730837a678ed4d93 Author: Cliff Click <cliffc@acm.org> Date: Tue Apr 1 12:07:36 2025 -0700 Encode infinite loops for RISC commit 40e0111061e174810eb75c898034fc5255b025d8 Author: Cliff Click <cliffc@acm.org> Date: Tue Apr 1 09:23:31 2025 -0700 remove debug print commit ea737ed0a49b091478e954e125a58a1842a89e5f Author: Darrick Wiebe <darrickw@gmail.com> Date: Tue Apr 1 01:53:31 2025 -0400 Get SLT[IU]* working (#192) commit 28a873ea522190733e89602b1d50f042c41d20bc Author: Cliff Click <cliffc@acm.org> Date: Mon Mar 31 16:57:26 2025 -0700 RA uses killOrdered commit 950d57cc74a36655a770daea7c28cee03b60e707 Author: Cliff Click <cliffc@acm.org> Date: Mon Mar 31 15:40:03 2025 -0700 Create intermediate dirs commit 6d50b541a7a5c6757ee507da24a995bc02354270 Author: Darrick Wiebe <darrickw@gmail.com> Date: Mon Mar 31 18:30:45 2025 -0400 Make risc op constructors public so that I can subclass them. (#191) commit 22318a9654f4a6c3f26e6ad6800a875c16f472a2 Author: Cliff Click <cliffc@acm.org> Date: Mon Mar 31 15:24:16 2025 -0700 Fix encoding FIB Use common utility for walking CFG chains of empty blocks commit 760e2b4f57ceac91017ad018f2e9234af351bf5b Author: Cliff Click <cliffc@acm.org> Date: Mon Mar 31 11:41:51 2025 -0700 X86 float splits commit d98a5c8cdf7a249f3c227fc00f014d53452af858 Author: Cliff Click <cliffc@acm.org> Date: Mon Mar 31 10:15:36 2025 -0700 Move insertAfterAndReplace to RA Fix Split printing commit 3a7a9bde8ee95b4e2f4c08588428c59125d727e4 Author: Darrick Wiebe <darrickw@gmail.com> Date: Mon Mar 31 12:38:12 2025 -0400 Fix sameBlockNoClobber and refine insertAfterAndReplace (#189) Also fix firstReg when empty commit ec9a8a7f49f7a8b22897ca96cf2aff8c47e475f8 Author: Cliff Click <cliffc@acm.org> Date: Sun Mar 30 16:46:18 2025 -0700 Fix bug j_type encoding Switch I_TYPE to OP_IMM to match the other major opcodes commit a7a9069f29b112a355596756bc53cff73e597a3f Author: Cliff Click <cliffc@acm.org> Date: Sun Mar 30 16:12:49 2025 -0700 Pass new reg asserts Handle -1 reg in X86 in several places. SplitARM Float spills still wrong, but less wrong. Cleanup RISC load/store handling. commit aea468c0b09891f89a8346de7bb37982f7767c2b Author: Cliff Click <cliffc@acm.org> Date: Sun Mar 30 14:54:16 2025 -0700 Update x86_64_v2.java commit 150bbf6d1f71411c9cdffdc0081013a5dd0f4f4e Author: Cliff Click <cliffc@acm.org> Date: Sun Mar 30 14:54:08 2025 -0700 Remove leftover prints commit 2ce631b2086f2491e34728fae3ff397b3a35ee73 Author: Cliff Click <cliffc@acm.org> Date: Sun Mar 30 14:53:50 2025 -0700 Fix some RISC encodings around FP spills commit f9710b82df0eb51090514c56e64e743d32d06b12 Author: Tamás Hadházy <85063808+Hels15@users.noreply.github.com> Date: Sun Mar 30 21:33:48 2025 +0100 Extra assert for reg numbering (#188) * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * READMEs * Update README.md * Update README.md * Move printing into print dir * Move codegen into codegen * Framework for Reg Alloc * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * First cut BuildLRG RegMask.EMPTY -> null * fix minor bug * Setup for first simple split * First cut split code for mismatch regs * delete non existing matches * replace RegMask.Empty with null * Live Ranges U-F BuildLRG for Phi Add CmpFX86 * FPR return CallEndX86 needed to tell fp return from gpr ret Call has no output regs. * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * Next cut regalloc Building IFG. Coloring. Some nested spilling Optimize divf/con -> mul times inverse * turn off ch20 tests * Update Chapter20Test.java * RISC-V basic isel (#156) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Bring pr/154 to ch 20 RISC5 changes. Some missing x86 ops. Still needs some calling convention love. * First cut regalloc working Biased coloring; tracking splits around a LRG. Separate split for self-conflicts. postColor to remove junk splits. Some minor riscv fixes. * Compile some riscv codes If SplitEmptyMask does not apply, fall back to normal split. Missing CmpFRISC. Renamed riscv flags. RetRISC handls FPR remove extra RegMask constructurs * add riscv port to readme * arm init * Progress reg alloc Handle multi-node projections. A little smarter about 2-addr scheduling RegAlloc must-have single registers removes from other masks instead of interfering; had the mask removal backwards in a few cases. LRG/RegMask COW expand masks before mutating. Clone small constants instead of spilling. Alloc & ProjX86 get output reg masks. Full print large reg masks with sp+offset * UF LRGs can be null during coloring * Finish UF find * work in progress * README updates * Fix ch19 mis-merge? * x86 call args, risc5 load/store regs * arm2 bs * Push RISC WMASK avoid writing to RSP * work in progress * clearnup arm * Progress calling convention allocations Call unlinks before reg alloc. CallEnd tracks RPC CallX86 requires TFP for correct ABI Bias Color tracks 2-addr. RPC gets a mask/stack-slot Clean up call/fun ABI Fix mask RET, RET_F Fix print stack slot Remove isMultiHead, isMultiTail Better all-asm print * Beef up Newton test * fixed main pr problems * regmask extra comments * Clean pass normal tests New fcn print upgrades several golden rule tests. Move the unlink-all from the walk::err call (which IS used during IterPeeps) to GCM. CallEnd upgrades return type based on constant call. Fix unlink to also unlink CallEnd Constant TFP print checks constant Tested an untested Load * Arm basics, minor changes in riscv (#160) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java * add riscv port to readme * arm init * work in progress * arm2 bs * work in progress * clearnup arm * fixed main pr problems * regmask extra comments --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Update Chapter20Test.java * Weaken range-check test since no range checks... test cannot fail (pass) * Fix issue #161 Error adding 'null' and '5' * bug fixs, cloning constants Fix bug missing Find. Fix bug setDef does unordered removal from basic blocks. Use cloned constants for spilling. * RA fixes SplitSelfConflict MUST split before uses and after defs. Split global constants across functions Missing find in union; Inserting split after new inserted wrong place Pre-remove null LRGs before color. Bias color hunts through Phis Shorten InstructionSelection. Rename !unified() to leader() AddMemX86 is 2-addr Add CastXXX to preserve register across cast. * encoding init * encoding init 2 * rex fix * indirect addressing modes * setop change opcode, rename FLOAT_OFFSET * mask sib and modrm with 3 bits, 4th bit goes into rex. * sign extend div with cqo * idiv input is fixed to rax and RMASK, grab input from second slot when encoding * encoding of newx86 * tfp and CallEndNode encoding * sib improvement * more cleanups * more cleanups * Allow a killMap to kill regs. Bugfix scheduler. Bugfix spiller - self-conflict order depended on hashtable iteration order, which depended on System.hashCode which would vary run to run. Improvements to coloring Improvements to spill quality testing * Added flags-kill for XOR * Missing add-deps from fuzzer * Update Chapter14Test.java * fixed order of operands * base rex fixup * more bug fix * remove jukn files * minimal "make release" * further fixes * minor fixes * minor fixes * minor fixes * base != -1 check, precedence fix * Add regkills in IFG building Fix bug LRG union stats Adjust self-conflict splits to be slightly less aggressive In every use-side split, dont split-after-split same block. NewNode is a multi, does not define a register * 64 bit encoding for gpr(s) load * RA improvements Attempt to commute to keep reg-masks compatible. Clones with kills (x86 XOR killing flags to zero) fail before the flag-def. Bugfix failed to color last lrg. More smarts on picking trivial LRGs during coloring Drop isSplit, add SplitNode Pass regalloc round to all splits for debugging Self-conflict no split on loop backedge. Handle split that gets made goes dead No-reg-mask spilling handles case with many def/uses. Add DivIX86 BrainFuck, MergeSort now allocate as testing * 8 bit imm support * conditional jump fix * Push l,ast change, unbreak branch * XOR remove extra opcode, imm size supports 8 bit. * Finally pass the `new [u8]` double-copy test * Fix several ARM reg setup problems Finish Split bypass check ARM: rsp is also a fixed zero, in R31. R0 is a valid register. CallArm prints args, passes TFP for arg selection. Does not return a register. All CallEndARM which DOES return a register. Parms missing tfp for float args. ProjARM after a New missing regs. RetARM missing RPC. Remove hopeful DivIX86 * 64 bit imm for alu ops encoded in extra reg then use it in Op as an operand * Bunch of minor arm, risc fixes small constants not cloning * jump invert encoding * warning comment * Much ARM fixage One More Go-Round with Better Biased Colors Fix bug picking deepest spills to bias. ARM: remove default 2-address. Fix regmask overflow. Rename some ops to be canonical. * Fixes to release Port does not have to be in a Simple named directory. Call convention is just a string, no enum Missed some tests running RISC, ARM. * Minor release change to allow another foreign port * better split encoding and fix CallInMask * notNode * fix callInMask * riscv init * riscv basic ALU ops and types of instructions * Allowing non-Simple repo ports Expanding public API slightly so alternative (non-Simple repo) ports can happen. RegMask supports single-bit-set and a long bitmask. Bugfix GCM splitting multiple global constants. Bugfix LCM grabbing a projection vs multi Bugfix RegAlloc when avoiding splitting a clonable Source code in release More timing in CodeGen * riscv indirect addressing and function calls * Cleanup call ABI presentation Everybody uses the same names, same code shapes * Caller save regs ...not callee save yet * Expose caller save regs * Store Risc adjustments, no imm in MemOpRISC * specific print encoding for cpus * merge fixup * More x86,risc,arm call convention fixes Collect callee and caller save masks (inverts of each other, except for spills). Reorg layout to be closer to each other; remove dead mask definitions; add final keyword * lui fix up with 32 bit immediates * Callee-save registers Callee-save registers and masks and inserting LRGs for same and spilling and picking good spills. Insert CalleeSaveNodes and edges to RetXXXs and RegMasks for those edges. Iterators for regmask. Better printing post-allocation * RegMask supports 128 bits All regs available arm, risc. MergeSort compiles & runs * added flags splitting * Fix risc5 compares No flags on risc5. Branch takes 2 inputs (one can be imm12). Float compare only sets a GPR to 0/1. Bool has a isFloat Common risc5 imm-form handling * extend test cases * imm encoding, flags fixed * grep for Todo: relocs * float riscv fixed * flags for set * lui fix for small bits [0, 12] * arm init * arm last touches * fmov still needs fix * minor fixes * minor fixes(2) * got rid of unnecessary stuff * create ch21 * remove encodings from chapter20 dir * 1/2 pass cleaning up ARM encoding API * ARM encoding compiles * Risc R_TYPE and I_TYPE * Progress RISC encodings Missing lots of stuff still * progress risc encoding * risc encodings compile * Update README.md * Fold up redundant OP/OP-FP risc5 encoding * x86 mem opcodes first cut break out MemOp encodings into parts; change x86 indirectAdr to use Encoding class, remove redundant asserts & code * x86 mem op encodings * x86 reg+imm forms * most reg-reg forms * More x86 encodings * Last few encodings * switch to binary literals over decimals for opcodes * logical immediates fixed, bitwise ops imm forms * tfp and setArm fixed * PC relative float for riscv * readme init * Merged pr/169 * Fixed the array1 test encodings Clean up branch invert logic during BB layout Cleanup branch target printing for RISC,ARM ImmRISC ops mask imm12 to 12 bits Fix NewRISC; hardcode RPC as part of call Fix RISC inverting branchs >,>= * floatarm encoding * tfparm and intarm * uncontional jump encoding for arm and riscv * smarter IntARM, Intx86 does not always include rex. * REX after PREFIX * fixup load and store indirect * updated rex prefix, if its 0x40 then its not needed * Rex fix for floats, got rid of unnecessary stuff. * rename to camel case, better param name * add test export * merged * store opt * store and load optimised forms handled * tfp patch * Store/load float arm/riscv/x64 * got rid of debug print * splits for x86, riscv and arm * readme fixups * fix global symbol numbering for constants * fix TFP pathc on arm and riscv, keep symbols in good order. * fix opcode for store and loads, memOpRisc for Store and splitting * replace hard coded opcodes with major opcodes * replace hard coded opcodes with major opcodes * add asserts and fix reg numbering * add asserts and fix reg numbering --------- Co-authored-by: Cliff Click <cliffc@acm.org> commit fe08995cca0196850855c0f81619d264cf66b716 Author: Darrick Wiebe <darrickw@gmail.com> Date: Sun Mar 30 15:33:43 2025 -0400 Make RegMaskRW public and add mutating or; Fix coelesce bug (#186) * Make RegMaskRW public and add union to it. * Rename union to or * Split popular non-constant * Fix coelesce bug --------- Co-authored-by: Cliff Click <cliffc@acm.org> commit 2fd3cd57934ef168b090e3b0ec7da7b41fa595b3 Author: Cliff Click <cliffc@acm.org> Date: Sun Mar 30 11:22:27 2025 -0700 fix indent commit 79263e424709885da08a0cada2ace6cd744158d7 Author: Cliff Click <cliffc@acm.org> Date: Sun Mar 30 11:19:14 2025 -0700 Encode ptr loads ptrs become 64b Fix Cmp encoding commit 8701c671c8e6d6eedc01436ea38bde01fba634f0 Author: Cliff Click <cliffc@acm.org> Date: Sun Mar 30 10:54:39 2025 -0700 Fix BB layout bug Fix split of popular value bug. commit 7d60f55d2c97677e03a6b01869b4832111a12267 Author: Cliff Click <cliffc@acm.org> Date: Sat Mar 29 16:48:49 2025 -0700 Force split during a parallel assignment Was not inserting same-block split, but actually required if doing parallel assignment from fixed regs to fixed regs commit 8ea9c74afef87cb562604bf94f803853babe2d33 Author: Darrick Wiebe <darrickw@gmail.com> Date: Sat Mar 29 18:22:54 2025 -0400 Enable asm printing to be flipped to big-endian (#185) * Suppress unchecked operation warnings * Selectable asm dump endianness * Fix a crash in coalesce when v2._adj is null, so has no adjacent nodes anyway. commit 99d3fe964eb556370d0f23327b29c26cee9b3edb Author: Cliff Click <cliffc@acm.org> Date: Sat Mar 29 14:47:33 2025 -0700 Fix non-deterministic bug commit b09370af9db2be3b763eb72a61a34aecb743dcd9 Author: Cliff Click <cliffc@acm.org> Date: Sat Mar 29 10:48:05 2025 -0700 Split popular constants commit 7c0a57d1693b2d714f5871f4615b17c29d15ad46 Author: Tamás Hadházy <85063808+Hels15@users.noreply.github.com> Date: Sat Mar 29 16:39:00 2025 +0000 Arm major opcodes, riscv load-store fixes (#183) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * READMEs * Update README.md * Update README.md * Move printing into print dir * Move codegen into codegen * Framework for Reg Alloc * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * First cut BuildLRG RegMask.EMPTY -> null * fix minor bug * Setup for first simple split * First cut split code for mismatch regs * delete non existing matches * replace RegMask.Empty with null * Live Ranges U-F BuildLRG for Phi Add CmpFX86 * FPR return CallEndX86 needed to tell fp return from gpr ret Call has no output regs. * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * Next cut regalloc Building IFG. Coloring. Some nested spilling Optimize divf/con -> mul times inverse * turn off ch20 tests * Update Chapter20Test.java * RISC-V basic isel (#156) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Bring pr/154 to ch 20 RISC5 changes. Some missing x86 ops. Still needs some calling convention love. * First cut regalloc working Biased coloring; tracking splits around a LRG. Separate split for self-conflicts. postColor to remove junk splits. Some minor riscv fixes. * Compile some riscv codes If SplitEmptyMask does not apply, fall back to normal split. Missing CmpFRISC. Renamed riscv flags. RetRISC handls FPR remove extra RegMask constructurs * add riscv port to readme * arm init * Progress reg alloc Handle multi-node projections. A little smarter about 2-addr scheduling RegAlloc must-have single registers removes from other masks instead of interfering; had the mask removal backwards in a few cases. LRG/RegMask COW expand masks before mutating. Clone small constants instead of spilling. Alloc & ProjX86 get output reg masks. Full print large reg masks with sp+offset * UF LRGs can be null during coloring * Finish UF find * work in progress * README updates * Fix ch19 mis-merge? * x86 call args, risc5 load/store regs * arm2 bs * Push RISC WMASK avoid writing to RSP * work in progress * clearnup arm * Progress calling convention allocations Call unlinks before reg alloc. CallEnd tracks RPC CallX86 requires TFP for correct ABI Bias Color tracks 2-addr. RPC gets a mask/stack-slot Clean up call/fun ABI Fix mask RET, RET_F Fix print stack slot Remove isMultiHead, isMultiTail Better all-asm print * Beef up Newton test * fixed main pr problems * regmask extra comments * Clean pass normal tests New fcn print upgrades several golden rule tests. Move the unlink-all from the walk::err call (which IS used during IterPeeps) to GCM. CallEnd upgrades return type based on constant call. Fix unlink to also unlink CallEnd Constant TFP print checks constant Tested an untested Load * Arm basics, minor changes in riscv (#160) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java * add riscv port to readme * arm init * work in progress * arm2 bs * work in progress * clearnup arm * fixed main pr problems * regmask extra comments --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Update Chapter20Test.java * Weaken range-check test since no range checks... test cannot fail (pass) * Fix issue #161 Error adding 'null' and '5' * bug fixs, cloning constants Fix bug missing Find. Fix bug setDef does unordered removal from basic blocks. Use cloned constants for spilling. * RA fixes SplitSelfConflict MUST split before uses and after defs. Split global constants across functions Missing find in union; Inserting split after new inserted wrong place Pre-remove null LRGs before color. Bias color hunts through Phis Shorten InstructionSelection. Rename !unified() to leader() AddMemX86 is 2-addr Add CastXXX to preserve register across cast. * encoding init * encoding init 2 * rex fix * indirect addressing modes * setop change opcode, rename FLOAT_OFFSET * mask sib and modrm with 3 bits, 4th bit goes into rex. * sign extend div with cqo * idiv input is fixed to rax and RMASK, grab input from second slot when encoding * encoding of newx86 * tfp and CallEndNode encoding * sib improvement * more cleanups * more cleanups * Allow a killMap to kill regs. Bugfix scheduler. Bugfix spiller - self-conflict order depended on hashtable iteration order, which depended on System.hashCode which would vary run to run. Improvements to coloring Improvements to spill quality testing * Added flags-kill for XOR * Missing add-deps from fuzzer * Update Chapter14Test.java * fixed order of operands * base rex fixup * more bug fix * remove jukn files * minimal "make release" * further fixes * minor fixes * minor fixes * minor fixes * base != -1 check, precedence fix * Add regkills in IFG building Fix bug LRG union stats Adjust self-conflict splits to be slightly less aggressive In every use-side split, dont split-after-split same block. NewNode is a multi, does not define a register * 64 bit encoding for gpr(s) load * RA improvements Attempt to commute to keep reg-masks compatible. Clones with kills (x86 XOR killing flags to zero) fail before the flag-def. Bugfix failed to color last lrg. More smarts on picking trivial LRGs during coloring Drop isSplit, add SplitNode Pass regalloc round to all splits for debugging Self-conflict no split on loop backedge. Handle split that gets made goes dead No-reg-mask spilling handles case with many def/uses. Add DivIX86 BrainFuck, MergeSort now allocate as testing * 8 bit imm support * conditional jump fix * Push l,ast change, unbreak branch * XOR remove extra opcode, imm size supports 8 bit. * Finally pass the `new [u8]` double-copy test * Fix several ARM reg setup problems Finish Split bypass check ARM: rsp is also a fixed zero, in R31. R0 is a valid register. CallArm prints args, passes TFP for arg selection. Does not return a register. All CallEndARM which DOES return a register. Parms missing tfp for float args. ProjARM after a New missing regs. RetARM missing RPC. Remove hopeful DivIX86 * 64 bit imm for alu ops encoded in extra reg then use it in Op as an operand * Bunch of minor arm, risc fixes small constants not cloning * jump invert encoding * warning comment * Much ARM fixage One More Go-Round with Better Biased Colors Fix bug picking deepest spills to bias. ARM: remove default 2-address. Fix regmask overflow. Rename some ops to be canonical. * Fixes to release Port does not have to be in a Simple named directory. Call convention is just a string, no enum Missed some tests running RISC, ARM. * Minor release change to allow another foreign port * better split encoding and fix CallInMask * notNode * fix callInMask * riscv init * riscv basic ALU ops and types of instructions * Allowing non-Simple repo ports Expanding public API slightly so alternative (non-Simple repo) ports can happen. RegMask supports single-bit-set and a long bitmask. Bugfix GCM splitting multiple global constants. Bugfix LCM grabbing a projection vs multi Bugfix RegAlloc when avoiding splitting a clonable Source code in release More timing in CodeGen * riscv indirect addressing and function calls * Cleanup call ABI presentation Everybody uses the same names, same code shapes * Caller save regs ...not callee save yet * Expose caller save regs * Store Risc adjustments, no imm in MemOpRISC * specific print encoding for cpus * merge fixup * More x86,risc,arm call convention fixes Collect callee and caller save masks (inverts of each other, except for spills). Reorg layout to be closer to each other; remove dead mask definitions; add final keyword * lui fix up with 32 bit immediates * Callee-save registers Callee-save registers and masks and inserting LRGs for same and spilling and picking good spills. Insert CalleeSaveNodes and edges to RetXXXs and RegMasks for those edges. Iterators for regmask. Better printing post-allocation * RegMask supports 128 bits All regs available arm, risc. MergeSort compiles & runs * added flags splitting * Fix risc5 compares No flags on risc5. Branch takes 2 inputs (one can be imm12). Float compare only sets a GPR to 0/1. Bool has a isFloat Common risc5 imm-form handling * extend test cases * imm encoding, flags fixed * grep for Todo: relocs * float riscv fixed * flags for set * lui fix for small bits [0, 12] * arm init * arm last touches * fmov still needs fix * minor fixes * minor fixes(2) * got rid of unnecessary stuff * create ch21 * remove encodings from chapter20 dir * 1/2 pass cleaning up ARM encoding API * ARM encoding compiles * Risc R_TYPE and I_TYPE * Progress RISC encodings Missing lots of stuff still * progress risc encoding * risc encodings compile * Update README.md * Fold up redundant OP/OP-FP risc5 encoding * x86 mem opcodes first cut break out MemOp encodings into parts; change x86 indirectAdr to use Encoding class, remove redundant asserts & code * x86 mem op encodings * x86 reg+imm forms * most reg-reg forms * More x86 encodings * Last few encodings * switch to binary literals over decimals for opcodes * logical immediates fixed, bitwise ops imm forms * tfp and setArm fixed * PC relative float for riscv * readme init * Merged pr/169 * Fixed the array1 test encodings Clean up branch invert logic during BB layout Cleanup branch target printing for RISC,ARM ImmRISC ops mask imm12 to 12 bits Fix NewRISC; hardcode RPC as part of call Fix RISC inverting branchs >,>= * floatarm encoding * tfparm and intarm * uncontional jump encoding for arm and riscv * smarter IntARM, Intx86 does not always include rex. * REX after PREFIX * fixup load and store indirect * updated rex prefix, if its 0x40 then its not needed * Rex fix for floats, got rid of unnecessary stuff. * rename to camel case, better param name * add test export * merged * store opt * store and load optimised forms handled * tfp patch * Store/load float arm/riscv/x64 * got rid of debug print * splits for x86, riscv and arm * readme fixups * fix global symbol numbering for constants * fix TFP pathc on arm and riscv, keep symbols in good order. * fix opcode for store and loads, memOpRisc for Store and splitting * replace hard coded opcodes with major opcodes * replace hard coded opcodes with major opcodes --------- Co-authored-by: Cliff Click <cliffc@acm.org> commit 7a22f7be12dadf9936a5102757b048a354a40bf5 Author: Tamás Hadházy <85063808+Hels15@users.noreply.github.com> Date: Sat Mar 29 02:45:44 2025 +0000 Ch20 encoding (#181) * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * READMEs * Update README.md * Update README.md * Move printing into print dir * Move codegen into codegen * Framework for Reg Alloc * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * First cut BuildLRG RegMask.EMPTY -> null * fix minor bug * Setup for first simple split * First cut split code for mismatch regs * delete non existing matches * replace RegMask.Empty with null * Live Ranges U-F BuildLRG for Phi Add CmpFX86 * FPR return CallEndX86 needed to tell fp return from gpr ret Call has no output regs. * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * Next cut regalloc Building IFG. Coloring. Some nested spilling Optimize divf/con -> mul times inverse * turn off ch20 tests * Update Chapter20Test.java * RISC-V basic isel (#156) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Bring pr/154 to ch 20 RISC5 changes. Some missing x86 ops. Still needs some calling convention love. * First cut regalloc working Biased coloring; tracking splits around a LRG. Separate split for self-conflicts. postColor to remove junk splits. Some minor riscv fixes. * Compile some riscv codes If SplitEmptyMask does not apply, fall back to normal split. Missing CmpFRISC. Renamed riscv flags. RetRISC handls FPR remove extra RegMask constructurs * add riscv port to readme * arm init * Progress reg alloc Handle multi-node projections. A little smarter about 2-addr scheduling RegAlloc must-have single registers removes from other masks instead of interfering; had the mask removal backwards in a few cases. LRG/RegMask COW expand masks before mutating. Clone small constants instead of spilling. Alloc & ProjX86 get output reg masks. Full print large reg masks with sp+offset * UF LRGs can be null during coloring * Finish UF find * work in progress * README updates * Fix ch19 mis-merge? * x86 call args, risc5 load/store regs * arm2 bs * Push RISC WMASK avoid writing to RSP * work in progress * clearnup arm * Progress calling convention allocations Call unlinks before reg alloc. CallEnd tracks RPC CallX86 requires TFP for correct ABI Bias Color tracks 2-addr. RPC gets a mask/stack-slot Clean up call/fun ABI Fix mask RET, RET_F Fix print stack slot Remove isMultiHead, isMultiTail Better all-asm print * Beef up Newton test * fixed main pr problems * regmask extra comments * Clean pass normal tests New fcn print upgrades several golden rule tests. Move the unlink-all from the walk::err call (which IS used during IterPeeps) to GCM. CallEnd upgrades return type based on constant call. Fix unlink to also unlink CallEnd Constant TFP print checks constant Tested an untested Load * Arm basics, minor changes in riscv (#160) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java * add riscv port to readme * arm init * work in progress * arm2 bs * work in progress * clearnup arm * fixed main pr problems * regmask extra comments --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Update Chapter20Test.java * Weaken range-check test since no range checks... test cannot fail (pass) * Fix issue #161 Error adding 'null' and '5' * bug fixs, cloning constants Fix bug missing Find. Fix bug setDef does unordered removal from basic blocks. Use cloned constants for spilling. * RA fixes SplitSelfConflict MUST split before uses and after defs. Split global constants across functions Missing find in union; Inserting split after new inserted wrong place Pre-remove null LRGs before color. Bias color hunts through Phis Shorten InstructionSelection. Rename !unified() to leader() AddMemX86 is 2-addr Add CastXXX to preserve register across cast. * encoding init * encoding init 2 * rex fix * indirect addressing modes * setop change opcode, rename FLOAT_OFFSET * mask sib and modrm with 3 bits, 4th bit goes into rex. * sign extend div with cqo * idiv input is fixed to rax and RMASK, grab input from second slot when encoding * encoding of newx86 * tfp and CallEndNode encoding * sib improvement * more cleanups * more cleanups * Allow a killMap to kill regs. Bugfix scheduler. Bugfix spiller - self-conflict order depended on hashtable iteration order, which depended on System.hashCode which would vary run to run. Improvements to coloring Improvements to spill quality testing * Added flags-kill for XOR * Missing add-deps from fuzzer * Update Chapter14Test.java * fixed order of operands * base rex fixup * more bug fix * remove jukn files * minimal "make release" * further fixes * minor fixes * minor fixes * minor fixes * base != -1 check, precedence fix * Add regkills in IFG building Fix bug LRG union stats Adjust self-conflict splits to be slightly less aggressive In every use-side split, dont split-after-split same block. NewNode is a multi, does not define a register * 64 bit encoding for gpr(s) load * RA improvements Attempt to commute to keep reg-masks compatible. Clones with kills (x86 XOR killing flags to zero) fail before the flag-def. Bugfix failed to color last lrg. More smarts on picking trivial LRGs during coloring Drop isSplit, add SplitNode Pass regalloc round to all splits for debugging Self-conflict no split on loop backedge. Handle split that gets made goes dead No-reg-mask spilling handles case with many def/uses. Add DivIX86 BrainFuck, MergeSort now allocate as testing * 8 bit imm support * conditional jump fix * Push l,ast change, unbreak branch * XOR remove extra opcode, imm size supports 8 bit. * Finally pass the `new [u8]` double-copy test * Fix several ARM reg setup problems Finish Split bypass check ARM: rsp is also a fixed zero, in R31. R0 is a valid register. CallArm prints args, passes TFP for arg selection. Does not return a register. All CallEndARM which DOES return a register. Parms missing tfp for float args. ProjARM after a New missing regs. RetARM missing RPC. Remove hopeful DivIX86 * 64 bit imm for alu ops encoded in extra reg then use it in Op as an operand * Bunch of minor arm, risc fixes small constants not cloning * jump invert encoding * warning comment * Much ARM fixage One More Go-Round with Better Biased Colors Fix bug picking deepest spills to bias. ARM: remove default 2-address. Fix regmask overflow. Rename some ops to be canonical. * Fixes to release Port does not have to be in a Simple named directory. Call convention is just a string, no enum Missed some tests running RISC, ARM. * Minor release change to allow another foreign port * better split encoding and fix CallInMask * notNode * fix callInMask * riscv init * riscv basic ALU ops and types of instructions * Allowing non-Simple repo ports Expanding public API slightly so alternative (non-Simple repo) ports can happen. RegMask supports single-bit-set and a long bitmask. Bugfix GCM splitting multiple global constants. Bugfix LCM grabbing a projection vs multi Bugfix RegAlloc when avoiding splitting a clonable Source code in release More timing in CodeGen * riscv indirect addressing and function calls * Cleanup call ABI presentation Everybody uses the same names, same code shapes * Caller save regs ...not callee save yet * Expose caller save regs * Store Risc adjustments, no imm in MemOpRISC * specific print encoding for cpus * merge fixup * More x86,risc,arm call convention fixes Collect callee and caller save masks (inverts of each other, except for spills). Reorg layout to be closer to each other; remove dead mask definitions; add final keyword * lui fix up with 32 bit immediates * Callee-save registers Callee-save registers and masks and inserting LRGs for same and spilling and picking good spills. Insert CalleeSaveNodes and edges to RetXXXs and RegMasks for those edges. Iterators for regmask. Better printing post-allocation * RegMask supports 128 bits All regs available arm, risc. MergeSort compiles & runs * added flags splitting * Fix risc5 compares No flags on risc5. Branch takes 2 inputs (one can be imm12). Float compare only sets a GPR to 0/1. Bool has a isFloat Common risc5 imm-form handling * extend test cases * imm encoding, flags fixed * grep for Todo: relocs * float riscv fixed * flags for set * lui fix for small bits [0, 12] * arm init * arm last touches * fmov still needs fix * minor fixes * minor fixes(2) * got rid of unnecessary stuff * create ch21 * remove encodings from chapter20 dir * 1/2 pass cleaning up ARM encoding API * ARM encoding compiles * Risc R_TYPE and I_TYPE * Progress RISC encodings Missing lots of stuff still * progress risc encoding * risc encodings compile * Update README.md * Fold up redundant OP/OP-FP risc5 encoding * x86 mem opcodes first cut break out MemOp encodings into parts; change x86 indirectAdr to use Encoding class, remove redundant asserts & code * x86 mem op encodings * x86 reg+imm forms * most reg-reg forms * More x86 encodings * Last few encodings * switch to binary literals over decimals for opcodes * logical immediates fixed, bitwise ops imm forms * tfp and setArm fixed * PC relative float for riscv * readme init * Merged pr/169 * Fixed the array1 test encodings Clean up branch invert logic during BB layout Cleanup branch target printing for RISC,ARM ImmRISC ops mask imm12 to 12 bits Fix NewRISC; hardcode RPC as part of call Fix RISC inverting branchs >,>= * floatarm encoding * tfparm and intarm * uncontional jump encoding for arm and riscv * smarter IntARM, Intx86 does not always include rex. * REX after PREFIX * fixup load and store indirect * updated rex prefix, if its 0x40 then its not needed * Rex fix for floats, got rid of unnecessary stuff. * rename to camel case, better param name * add test export * merged * store opt * store and load optimised forms handled * tfp patch * Store/load float arm/riscv/x64 * got rid of debug print * splits for x86, riscv and arm * readme fixups * fix global symbol numbering for constants * fix TFP pathc on arm and riscv, keep symbols in good order. --------- Co-authored-by: Cliff Click <cliffc@acm.org> commit 82b4b2187d51f96f3217b10febfbf18071ec61fc Author: Cliff Click <cliffc@acm.org> Date: Fri Mar 28 15:58:58 2025 -0700 risc5 encoding bug commit b3c108731794fed83015ba92a651af13a3a1477d Author: Cliff Click <cliffc@acm.org> Date: Fri Mar 28 14:05:49 2025 -0700 More custom op names commit e5f156803488ade996c04b5135ac87942c354245 Author: Cliff Click <cliffc@acm.org> Date: Fri Mar 28 09:29:39 2025 -0700 Update LoadRISC.java commit d18505d598fd7c96d152d57f6f5d1f34124defc3 Author: Cliff Click <cliffc@acm.org> Date: Thu Mar 27 11:55:31 2025 -0700 R5 encoding bug fixes Remove extra branch-to-next commit e16cf5d94bce81a27f5080be6f90da448544ee2a Author: Cliff Click <cliffc@acm.org> Date: Wed Mar 26 16:53:49 2025 -0700 Update LUI.java commit c2dbd396024d4ecf21747c16f410fa31a1dfdf91 Author: Cliff Click <cliffc@acm.org> Date: Wed Mar 26 14:42:40 2025 -0700 more portable commit d90baa2a8c0a8ce9a84befec756b39ec49360b8e Author: Cliff Click <cliffc@acm.org> Date: Wed Mar 26 12:16:15 2025 -0700 Minor improves to portability commit 37150e852041029792176f9d559eb714a2a6726b Author: Cliff Click <cliffc@acm.org> Date: Tue Mar 25 19:56:36 2025 -0700 few more publics commit 7225eb931c2cefa105b17f393fe242c00b02a185 Author: Cliff Click <cliffc@acm.org> Date: Tue Mar 25 12:11:42 2025 -0700 RISC5 branch encoding bug commit 4bbfd2f561f4a28fc749dd36bbb417ce03de5f78 Author: Cliff Click <cliffc@acm.org> Date: Tue Mar 25 11:21:35 2025 -0700 Minor cleanups Custom op for risc5, a few more things public for external port commit 5fd3508bd3c45ed83757b47caa7a24361bec71cb Author: XmiliaH <45106915+XmiliaH@users.noreply.github.com> Date: Tue Mar 25 18:44:41 2025 +0100 Call Simple function from C (#179) * Call simple function from c * Small fix * Use different test values commit 1f87fba4ee6b874eed6c3652e3e8a25b5b0726aa Author: Cliff Click <cliffc@acm.org> Date: Tue Mar 25 08:53:55 2025 -0700 Update UJmpRISC.java commit 321193056bf13d525399885afed16ffece3c2e5a Author: Cliff Click <cliffc@acm.org> Date: Mon Mar 24 16:35:48 2025 -0700 Make more public Allows custom risc5 to override commit ac3855c4313d6cab8e2bb778b5bfa679415cb519 Author: Dmitry Stogov <dmitrystogov@gmail.com> Date: Mon Mar 24 18:56:05 2025 +0300 Driver related fixes and improvements (#175) * Fix simple.sh (pass arguments) * Consistently replace "_" with "-" in command-line options * Add "--dump-time" option to print compilation statistics * Add "--dump-size" option to print generated code size commit 3db126e018388facfab368ecb2c8f8bc566afe1a Author: XmiliaH <45106915+XmiliaH@users.noreply.github.com> Date: Mon Mar 24 00:09:33 2025 +0100 First run as elf binary (#178) commit 8d6de0e10358fc197490cdacb10cbcedc18bb4f6 Author: Cliff Click <cliffc@acm.org> Date: Sun Mar 23 15:59:23 2025 -0700 Coalesacing Fix bug IFG adding interference to from 1 reg live range, even if success in clearing reg Fix bug LRG merging when both masks have already been trimmed. Missing ARM stack adjust commit 0afb4876e602faa75319bb1faf9adc7f6f7476c2 Author: Cliff Click <cliffc@acm.org> Date: Sun Mar 23 11:26:16 2025 -0700 Correct golden spill counts Encoding: bad test cutout required JMP AddIRISC bad edges during spill. commit 3e17e811c4429cda194ccdb892ddb2bd10fa71ea Author: Tamás Hadházy <85063808+Hels15@users.noreply.github.com> Date: Sun Mar 23 17:10:41 2025 +0000 Ch20 encoding (#177) * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * READMEs * Update README.md * Update README.md * Move printing into print dir * Move codegen into codegen * Framework for Reg Alloc * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * First cut BuildLRG RegMask.EMPTY -> null * fix minor bug * Setup for first simple split * First cut split code for mismatch regs * delete non existing matches * replace RegMask.Empty with null * Live Ranges U-F BuildLRG for Phi Add CmpFX86 * FPR return CallEndX86 needed to tell fp return from gpr ret Call has no output regs. * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * Next cut regalloc Building IFG. Coloring. Some nested spilling Optimize divf/con -> mul times inverse * turn off ch20 tests * Update Chapter20Test.java * RISC-V basic isel (#156) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Bring pr/154 to ch 20 RISC5 changes. Some missing x86 ops. Still needs some calling convention love. * First cut regalloc working Biased coloring; tracking splits around a LRG. Separate split for self-conflicts. postColor to remove junk splits. Some minor riscv fixes. * Compile some riscv codes If SplitEmptyMask does not apply, fall back to normal split. Missing CmpFRISC. Renamed riscv flags. RetRISC handls FPR remove extra RegMask constructurs * add riscv port to readme * arm init * Progress reg alloc Handle multi-node projections. A little smarter about 2-addr scheduling RegAlloc must-have single registers removes from other masks instead of interfering; had the mask removal backwards in a few cases. LRG/RegMask COW expand masks before mutating. Clone small constants instead of spilling. Alloc & ProjX86 get output reg masks. Full print large reg masks with sp+offset * UF LRGs can be null during coloring * Finish UF find * work in progress * README updates * Fix ch19 mis-merge? * x86 call args, risc5 load/store regs * arm2 bs * Push RISC WMASK avoid writing to RSP * work in progress * clearnup arm * Progress calling convention allocations Call unlinks before reg alloc. CallEnd tracks RPC CallX86 requires TFP for correct ABI Bias Color tracks 2-addr. RPC gets a mask/stack-slot Clean up call/fun ABI Fix mask RET, RET_F Fix print stack slot Remove isMultiHead, isMultiTail Better all-asm print * Beef up Newton test * fixed main pr problems * regmask extra comments * Clean pass normal tests New fcn print upgrades several golden rule tests. Move the unlink-all from the walk::err call (which IS used during IterPeeps) to GCM. CallEnd upgrades return type based on constant call. Fix unlink to also unlink CallEnd Constant TFP print checks constant Tested an untested Load * Arm basics, minor changes in riscv (#160) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java * add riscv port to readme * arm init * work in progress * arm2 bs * work in progress * clearnup arm * fixed main pr problems * regmask extra comments --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Update Chapter20Test.java * Weaken range-check test since no range checks... test cannot fail (pass) * Fix issue #161 Error adding 'null' and '5' * bug fixs, cloning constants Fix bug missing Find. Fix bug setDef does unordered removal from basic blocks. Use cloned constants for spilling. * RA fixes SplitSelfConflict MUST split before uses and after defs. Split global constants across functions Missing find in union; Inserting split after new inserted wrong place Pre-remove null LRGs before color. Bias color hunts through Phis Shorten InstructionSelection. Rename !unified() to leader() AddMemX86 is 2-addr Add CastXXX to preserve register across cast. * encoding init * encoding init 2 * rex fix * indirect addressing modes * setop change opcode, rename FLOAT_OFFSET * mask sib and modrm with 3 bits, 4th bit goes into rex. * sign extend div with cqo * idiv input is fixed to rax and RMASK, grab input from second slot when encoding * encoding of newx86 * tfp and CallEndNode encoding * sib improvement * more cleanups * more cleanups * Allow a killMap to kill regs. Bugfix scheduler. Bugfix spiller - self-conflict order depended on hashtable iteration order, which depended on System.hashCode which would vary run to run. Improvements to coloring Improvements to spill quality testing * Added flags-kill for XOR * Missing add-deps from fuzzer * Update Chapter14Test.java * fixed order of operands * base rex fixup * more bug fix * remove jukn files * minimal "make release" * further fixes * minor fixes * minor fixes * minor fixes * base != -1 check, precedence fix * Add regkills in IFG building Fix bug LRG union stats Adjust self-conflict splits to be slightly less aggressive In every use-side split, dont split-after-split same block. NewNode is a multi, does not define a register * 64 bit encoding for gpr(s) load * RA improvements Attempt to commute to keep reg-masks compatible. Clones with kills (x86 XOR killing flags to zero) fail before the flag-def. Bugfix failed to color last lrg. More smarts on picking trivial LRGs during coloring Drop isSplit, add SplitNode Pass regalloc round to all splits for debugging Self-conflict no split on loop backedge. Handle split that gets made goes dead No-reg-mask spilling handles case with many def/uses. Add DivIX86 BrainFuck, MergeSort now allocate as testing * 8 bit imm support * conditional jump fix * Push l,ast change, unbreak branch * XOR remove extra opcode, imm size supports 8 bit. * Finally pass the `new [u8]` double-copy test * Fix several ARM reg setup problems Finish Split bypass check ARM: rsp is also a fixed zero, in R31. R0 is a valid register. CallArm prints args, passes TFP for arg selection. Does not return a register. All CallEndARM which DOES return a register. Parms missing tfp for float args. ProjARM after a New missing regs. RetARM missing RPC. Remove hopeful DivIX86 * 64 bit imm for alu ops encoded in extra reg then use it in Op as an operand * Bunch of minor arm, risc fixes small constants not cloning * jump invert encoding * warning comment * Much ARM fixage One More Go-Round with Better Biased Colors Fix bug picking deepest spills to bias. ARM: remove default 2-address. Fix regmask overflow. Rename some ops to be canonical. * Fixes to release Port does not have to be in a Simple named directory. Call convention is just a string, no enum Missed some tests running RISC, ARM. * Minor release change to allow another foreign port * better split encoding and fix CallInMask * notNode * fix callInMask * riscv init * riscv basic ALU ops and types of instructions * Allowing non-Simple repo ports Expanding public API slightly so alternative (non-Simple repo) ports can happen. RegMask supports single-bit-set and a long bitmask. Bugfix GCM splitting multiple global constants. Bugfix LCM grabbing a projection vs multi Bugfix RegAlloc when avoiding splitting a clonable Source code in release More timing in CodeGen * riscv indirect addressing and function calls * Cleanup call ABI presentation Everybody uses the same names, same code shapes * Caller save regs ...not callee save yet * Expose caller save regs * Store Risc adjustments, no imm in MemOpRISC * specific print encoding for cpus * merge fixup * More x86,risc,arm call convention fixes Collect callee and caller save masks (inverts of each other, except for spills). Reorg layout to be closer to each other; remove dead mask definitions; add final keyword * lui fix up with 32 bit immediates * Callee-save registers Callee-save registers and masks and inserting LRGs for same and spilling and picking good spills. Insert CalleeSaveNodes and edges to RetXXXs and RegMasks for those edges. Iterators for regmask. Better printing post-allocation * RegMask supports 128 bits All regs available arm, risc. MergeSort compiles & runs * added flags splitting * Fix risc5 compares No flags on risc5. Branch takes 2 inputs (one can be imm12). Float compare only sets a GPR to 0/1. Bool has a isFloat Common risc5 imm-form handling * extend test cases * imm encoding, flags fixed * grep for Todo: relocs * float riscv fixed * flags for set * lui fix for small bits [0, 12] * arm init * arm last touches * fmov still needs fix * minor fixes * minor fixes(2) * got rid of unnecessary stuff * create ch21 * remove encodings from chapter20 dir * 1/2 pass cleaning up ARM encoding API * ARM encoding compiles * Risc R_TYPE and I_TYPE * Progress RISC encodings Missing lots of stuff still * progress risc encoding * risc encodings compile * Update README.md * Fold up redundant OP/OP-FP risc5 encoding * x86 mem opcodes first cut break out MemOp encodings into parts; change x86 indirectAdr to use Encoding class, remove redundant asserts & code * x86 mem op encodings * x86 reg+imm forms * most reg-reg forms * More x86 encodings * Last few encodings * switch to binary literals over decimals for opcodes * logical immediates fixed, bitwise ops imm forms * tfp and setArm fixed * PC relative float for riscv * readme init * Merged pr/169 * Fixed the array1 test encodings Clean up branch invert logic during BB layout Cleanup branch target printing for RISC,ARM ImmRISC ops mask imm12 to 12 bits Fix NewRISC; hardcode RPC as part of call Fix RISC inverting branchs >,>= * floatarm encoding * tfparm and intarm * uncontional jump encoding for arm and riscv * smarter IntARM, Intx86 does not always include rex. * REX after PREFIX * fixup load and store indirect * updated rex prefix, if its 0x40 then its not needed * Rex fix for floats, got rid of unnecessary stuff. * rename to camel case, better param name * add test export * merged * store opt * store and load optimised forms handled * tfp patch * Store/load float arm/riscv/x64 * got rid of debug print * splits for x86, riscv and arm --------- Co-authored-by: Cliff Click <cliffc@acm.org> commit 880329e6b4ba209ea35ca647bb14157c1b4234fb Author: Cliff Click <cliffc@acm.org> Date: Sun Mar 23 10:06:43 2025 -0700 More RegAlloc Forcing kills on allocations broke a bunch of things. Color: pickRisky single-Def cloneables that are not next to their uses. I.e., a X86 flags-producer far away from a required jump: Pick as Risky, fail to color, and spill (clone) next to jump. Remove a bunch of premature split fold-thrus commit 05925aa5a73c48f4c380ec919583cb56057383ea Author: Cliff Click <cliffc@acm.org> Date: Sun Mar 23 10:02:41 2025 -0700 x86 test op Invert "Bool(#0,x); If " to "CmpI(x,#0); Jmp!XX" commit e1b6befcfb865e0a8e34d895a0ddc82bf31d6d4b Author: Cliff Click <cliffc@acm.org> Date: Sun Mar 23 10:01:32 2025 -0700 Robust pre-encoding commit d333a9001c4f1db159dca8395b5e59c2d2aa92d7 Author: Cliff Click <cliffc@acm.org> Date: Sun Mar 23 10:01:20 2025 -0700 Share a bunch of larger common tests Move them to `*.smp` files commit 71d882bae6c88ce20353b787cb3699860d52b3d5 Author: Cliff Click <cliffc@acm.org> Date: Sun Mar 23 10:00:33 2025 -0700 riscv load/store does not do [reg+reg] riscv load/store does not do [reg+reg], so the base might not be object base. commit 94ea5ac5871b74273537d6124a1aeb64bba495d9 Author: Tamás Hadházy <85063808+Hels15@users.noreply.github.com> Date: Fri Mar 21 22:49:50 2025 +0000 Ch20 encoding (#176) * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * READMEs * Update README.md * Update README.md * Move printing into print dir * Move codegen into codegen * Framework for Reg Alloc * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * First cut BuildLRG RegMask.EMPTY -> null * fix minor bug * Setup for first simple split * First cut split code for mismatch regs * delete non existing matches * replace RegMask.Empty with null * Live Ranges U-F BuildLRG for Phi Add CmpFX86 * FPR return CallEndX86 needed to tell fp return from gpr ret Call has no output regs. * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * Next cut regalloc Building IFG. Coloring. Some nested spilling Optimize divf/con -> mul times inverse * turn off ch20 tests * Update Chapter20Test.java * RISC-V basic isel (#156) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Bring pr/154 to ch 20 RISC5 changes. Some missing x86 ops. Still needs some calling convention love. * First cut regalloc working Biased coloring; tracking splits around a LRG. Separate split for self-conflicts. postColor to remove junk splits. Some minor riscv fixes. * Compile some riscv codes If SplitEmptyMask does not apply, fall back to normal split. Missing CmpFRISC. Renamed riscv flags. RetRISC handls FPR remove extra RegMask constructurs * add riscv port to readme * arm init * Progress reg alloc Handle multi-node projections. A little smarter about 2-addr scheduling RegAlloc must-have single registers removes from other masks instead of interfering; had the mask removal backwards in a few cases. LRG/RegMask COW expand masks before mutating. Clone small constants instead of spilling. Alloc & ProjX86 get output reg masks. Full print large reg masks with sp+offset * UF LRGs can be null during coloring * Finish UF find * work in progress * README updates * Fix ch19 mis-merge? * x86 call args, risc5 load/store regs * arm2 bs * Push RISC WMASK avoid writing to RSP * work in progress * clearnup arm * Progress calling convention allocations Call unlinks before reg alloc. CallEnd tracks RPC CallX86 requires TFP for correct ABI Bias Color tracks 2-addr. RPC gets a mask/stack-slot Clean up call/fun ABI Fix mask RET, RET_F Fix print stack slot Remove isMultiHead, isMultiTail Better all-asm print * Beef up Newton test * fixed main pr problems * regmask extra comments * Clean pass normal tests New fcn print upgrades several golden rule tests. Move the unlink-all from the walk::err call (which IS used during IterPeeps) to GCM. CallEnd upgrades return type based on constant call. Fix unlink to also unlink CallEnd Constant TFP print checks constant Tested an untested Load * Arm basics, minor changes in riscv (#160) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java * add riscv port to readme * arm init * work in progress * arm2 bs * work in progress * clearnup arm * fixed main pr problems * regmask extra comments --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Update Chapter20Test.java * Weaken range-check test since no range checks... test cannot fail (pass) * Fix issue #161 Error adding 'null' and '5' * bug fixs, cloning constants Fix bug missing Find. Fix bug setDef does unordered removal from basic blocks. Use cloned constants for spilling. * RA fixes SplitSelfConflict MUST split before uses and after defs. Split global constants across functions Missing find in union; Inserting split after new inserted wrong place Pre-remove null LRGs before color. Bias color hunts through Phis Shorten InstructionSelection. Rename !unified() to leader() AddMemX86 is 2-addr Add CastXXX to preserve register across cast. * encoding init * encoding init 2 * rex fix * indirect addressing modes * setop change opcode, rename FLOAT_OFFSET * mask sib and modrm with 3 bits, 4th bit goes into rex. * sign extend div with cqo * idiv input is fixed to rax and RMASK, grab input from second slot when encoding * encoding of newx86 * tfp and CallEndNode encoding * sib improvement * more cleanups * more cleanups * Allow a killMap to kill regs. Bugfix scheduler. Bugfix spiller - self-conflict order depended on hashtable iteration order, which depended on System.hashCode which would vary run to run. Improvements to coloring Improvements to spill quality testing * Added flags-kill for XOR * Missing add-deps from fuzzer * Update Chapter14Test.java * fixed order of operands * base rex fixup * more bug fix * remove jukn files * minimal "make release" * further fixes * minor fixes * minor fixes * minor fixes * base != -1 check, precedence fix * Add regkills in IFG building Fix bug LRG union stats Adjust self-conflict splits to be slightly less aggressive In every use-side split, dont split-after-split same block. NewNode is a multi, does not define a register * 64 bit encoding for gpr(s) load * RA improvements Attempt to commute to keep reg-masks compatible. Clones with kills (x86 XOR killing flags to zero) fail before the flag-def. Bugfix failed to color last lrg. More smarts on picking trivial LRGs during coloring Drop isSplit, add SplitNode Pass regalloc round to all splits for debugging Self-conflict no split on loop backedge. Handle split that gets made goes dead No-reg-mask spilling handles case with many def/uses. Add DivIX86 BrainFuck, MergeSort now allocate as testing * 8 bit imm support * conditional jump fix * Push l,ast change, unbreak branch * XOR remove extra opcode, imm size supports 8 bit. * Finally pass the `new [u8]` double-copy test * Fix several ARM reg setup problems Finish Split bypass check ARM: rsp is also a fixed zero, in R31. R0 is a valid register. CallArm prints args, passes TFP for arg selection. Does not return a register. All CallEndARM which DOES return a register. Parms missing tfp for float args. ProjARM after a New missing regs. RetARM missing RPC. Remove hopeful DivIX86 * 64 bit imm for alu ops encoded in extra reg then use it in Op as an operand * Bunch of minor arm, risc fixes small constants not cloning * jump invert encoding * warning comment * Much ARM fixage One More Go-Round with Better Biased Colors Fix bug picking deepest spills to bias. ARM: remove default 2-address. Fix regmask overflow. Rename some ops to be canonical. * Fixes to release Port does not have to be in a Simple named directory. Call convention is just a string, no enum Missed some tests running RISC, ARM. * Minor release change to allow another foreign port * better split encoding and fix CallInMask * notNode * fix callInMask * riscv init * riscv basic ALU ops and types of instructions * Allowing non-Simple repo ports Expanding public API slightly so alternative (non-Simple repo) ports can happen. RegMask supports single-bit-set and a long bitmask. Bugfix GCM splitting multiple global constants. Bugfix LCM grabbing a projection vs multi Bugfix RegAlloc when avoiding splitting a clonable Source code in release More timing in CodeGen * riscv indirect addressing and function calls * Cleanup call ABI presentation Everybody uses the same names, same code shapes * Caller save regs ...not callee save yet * Expose caller save regs * Store Risc adjustments, no imm in MemOpRISC * specific print encoding for cpus * merge fixup * More x86,risc,arm call convention fixes Collect callee and caller save masks (inverts of each other, except for spills). Reorg layout to be closer to each other; remove dead mask definitions; add final keyword * lui fix up with 32 bit immediates * Callee-save registers Callee-save registers and masks and inserting LRGs for same and spilling and picking good spills. Insert CalleeSaveNodes and edges to RetXXXs and RegMasks for those edges. Iterators for regmask. Better printing post-allocation * RegMask supports 128 bits All regs available arm, risc. MergeSort compiles & runs * added flags splitting * Fix risc5 compares No flags on risc5. Branch takes 2 inputs (one can be imm12). Float compare only sets a GPR to 0/1. Bool has a isFloat Common risc5 imm-form handling * extend test cases * imm encoding, flags fixed * grep for Todo: relocs * float riscv fixed * flags for set * lui fix for small bits [0, 12] * arm init * arm last touches * fmov still needs fix * minor fixes * minor fixes(2) * got rid of unnecessary stuff * create ch21 * remove encodings from chapter20 dir * 1/2 pass cleaning up ARM encoding API * ARM encoding compiles * Risc R_TYPE and I_TYPE * Progress RISC encodings Missing lots of stuff still * progress risc encoding * risc encodings compile * Update README.md * Fold up redundant OP/OP-FP risc5 encoding * x86 mem opcodes first cut break out MemOp encodings into parts; change x86 indirectAdr to use Encoding class, remove redundant asserts & code * x86 mem op encodings * x86 reg+imm forms * most reg-reg forms * More x86 encodings * Last few encodings * switch to binary literals over decimals for opcodes * logical immediates fixed, bitwise ops imm forms * tfp and setArm fixed * PC relative float for riscv * readme init * Merged pr/169 * Fixed the array1 test encodings Clean up branch invert logic during BB layout Cleanup branch target printing for RISC,ARM ImmRISC ops mask imm12 to 12 bits Fix NewRISC; hardcode RPC as part of call Fix RISC inverting branchs >,>= * floatarm encoding * tfparm and intarm * uncontional jump encoding for arm and riscv * smarter IntARM, Intx86 does not always include rex. * REX after PREFIX * fixup load and store indirect * updated rex prefix, if its 0x40 then its not needed * Rex fix for floats, got rid of unnecessary stuff. * rename to camel case, better param name * add test export * merged * store opt * store and load optimised forms handled * tfp patch --------- Co-authored-by: Cliff Click <cliffc@acm.org> commit de2d08cfaa659d1c22f2cbbfa84748bd5c307ac4 Author: Cliff Click <cliffc@acm.org> Date: Fri Mar 21 15:12:05 2025 -0700 Stack slot updates Spill code work commit 442b5060a7f5857cfdedcc69384d90fd5fed9036 Author: Cliff Click <cliffc@acm.org> Date: Wed Mar 19 09:54:45 2025 -0700 constant pool commit 352c6927252137a252ab942c83695890fb332004 Author: Cliff Click <cliffc@acm.org> Date: Wed Mar 19 09:54:38 2025 -0700 ELF exporting takes a target file commit 0328057dc9e5a7b4bcee4b9fe9260c91a59d5b5e Author: Cliff Click <cliffc@acm.org> Date: Tue Mar 18 15:13:43 2025 -0700 Update Chapter21Test.java commit fbe1e0a84e154b17e09055b184f33cdb08a0138c Author: Cliff Click <cliffc@acm.org> Date: Tue Mar 18 15:13:30 2025 -0700 bugz Fix RA bug using Win64 and not full spilling in a single block. riscv/arm demand SystemV calling convention Cleanup ch21 tests; add 1 export test commit 0b107134d7c4081a4529d9cbb15897bfcdc83910 Author: Cliff Click <cliffc@acm.org> Date: Tue Mar 18 14:28:19 2025 -0700 Missing address size on risc commit 59173187cc8c01506d1bc25447bd7a7f5fee8604 Author: Cliff Click <cliffc@acm.org> Date: Tue Mar 18 14:25:49 2025 -0700 Fix missed peep only missing when running without asserts. commit 3bf52024aee9021521bfb85f7e894a3b343e6c85 Author: Cliff Click <cliffc@acm.org> Date: Tue Mar 18 13:57:02 2025 -0700 better printing commit 129f7a9ce2b9b170f35b07367d80179744776732 Author: Cliff Click <cliffc@acm.org> Date: Tue Mar 18 13:56:54 2025 -0700 add sh and bat drivers commit 3848ef1cab34690dea7c9fba0ff08fe14b7a13e5 Author: Cliff Click <cliffc@acm.org> Date: Tue Mar 18 13:50:39 2025 -0700 Missed merge piece commit 0f5ebdf4563f4d71d349a32f511137cfad37d828 Author: Dmitry Stogov <dmitrystogov@gmail.com> Date: Tue Mar 18 22:00:24 2025 +0300 Create a better driver programn (#168) This allows: - read "Simple" programs from files or stdin - selectivley dump sea-of-nodes after earch optimization pass - save *.dot files - print generated assembler - evaluate progrmas with given arguments (Eval2::eval() needs improvement) - select CPU and ABI for code-generation commit 5cab242acdb68045e5993b75fc67b54901002072 Author: Cliff Click <cliffc@acm.org> Date: Tue Mar 18 11:37:26 2025 -0700 Fix comment printing Minor encoding bugs for large- x86 jumps and arm external call commit e625a921d8fc9540a277e5becc8fe49b557c49fc Author: Cliff Click <cliffc@acm.org> Date: Tue Mar 18 11:22:07 2025 -0700 changed relo ABI Missing patch/merge from ARM ldst-encoding Explicit external calls. Better printing for multi-op template encodings commit efc53c6d4b8d11cb08ba84800d85bc725b2f2d5b Author: Cliff Click <cliffc@acm.org> Date: Tue Mar 18 09:02:20 2025 -0700 Update CallRISC.java commit 5faaadb794ad864ce3e26ed6a5fc4457a8aa2f15 Author: Cliff Click <cliffc@acm.org> Date: Tue Mar 18 00:34:34 2025 -0700 progress on patching calls commit 9929bca5617c2b0134ae16fb174df8d247443af8 Author: Yasser Arguelles Snape <yasserarguelles2506@gmail.com> Date: Mon Mar 17 14:59:33 2025 -0700 ELF exporting (but in Chapter 21) (#173) * Updated ELF exporting code, added function relocations * added big constants (only floats currently) commit 65ff94cc06005208d454b6759503b8604135e607 Author: Tamás Hadházy <85063808+Hels15@users.noreply.github.com> Date: Mon Mar 17 21:40:44 2025 +0000 Ch20 encoding (#174) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * READMEs * Update README.md * Update README.md * Move printing into print dir * Move codegen into codegen * Framework for Reg Alloc * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * First cut BuildLRG RegMask.EMPTY -> null * fix minor bug * Setup for first simple split * First cut split code for mismatch regs * delete non existing matches * replace RegMask.Empty with null * Live Ranges U-F BuildLRG for Phi Add CmpFX86 * FPR return CallEndX86 needed to tell fp return from gpr ret Call has no output regs. * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * Next cut regalloc Building IFG. Coloring. Some nested spilling Optimize divf/con -> mul times inverse * turn off ch20 tests * Update Chapter20Test.java * RISC-V basic isel (#156) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Bring pr/154 to ch 20 RISC5 changes. Some missing x86 ops. Still needs some calling convention love. * First cut regalloc working Biased coloring; tracking splits around a LRG. Separate split for self-conflicts. postColor to remove junk splits. Some minor riscv fixes. * Compile some riscv codes If SplitEmptyMask does not apply, fall back to normal split. Missing CmpFRISC. Renamed riscv flags. RetRISC handls FPR remove extra RegMask constructurs * add riscv port to readme * arm init * Progress reg alloc Handle multi-node projections. A little smarter about 2-addr scheduling RegAlloc must-have single registers removes from other masks instead of interfering; had the mask removal backwards in a few cases. LRG/RegMask COW expand masks before mutating. Clone small constants instead of spilling. Alloc & ProjX86 get output reg masks. Full print large reg masks with sp+offset * UF LRGs can be null during coloring * Finish UF find * work in progress * README updates * Fix ch19 mis-merge? * x86 call args, risc5 load/store regs * arm2 bs * Push RISC WMASK avoid writing to RSP * work in progress * clearnup arm * Progress calling convention allocations Call unlinks before reg alloc. CallEnd tracks RPC CallX86 requires TFP for correct ABI Bias Color tracks 2-addr. RPC gets a mask/stack-slot Clean up call/fun ABI Fix mask RET, RET_F Fix print stack slot Remove isMultiHead, isMultiTail Better all-asm print * Beef up Newton test * fixed main pr problems * regmask extra comments * Clean pass normal tests New fcn print upgrades several golden rule tests. Move the unlink-all from the walk::err call (which IS used during IterPeeps) to GCM. CallEnd upgrades return type based on constant call. Fix unlink to also unlink CallEnd Constant TFP print checks constant Tested an untested Load * Arm basics, minor changes in riscv (#160) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java * add riscv port to readme * arm init * work in progress * arm2 bs * work in progress * clearnup arm * fixed main pr problems * regmask extra comments --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Update Chapter20Test.java * Weaken range-check test since no range checks... test cannot fail (pass) * Fix issue #161 Error adding 'null' and '5' * bug fixs, cloning constants Fix bug missing Find. Fix bug setDef does unordered removal from basic blocks. Use cloned constants for spilling. * RA fixes SplitSelfConflict MUST split before uses and after defs. Split global constants across functions Missing find in union; Inserting split after new inserted wrong place Pre-remove null LRGs before color. Bias color hunts through Phis Shorten InstructionSelection. Rename !unified() to leader() AddMemX86 is 2-addr Add CastXXX to preserve register across cast. * encoding init * encoding init 2 * rex fix * indirect addressing modes * setop change opcode, rename FLOAT_OFFSET * mask sib and modrm with 3 bits, 4th bit goes into rex. * sign extend div with cqo * idiv input is fixed to rax and RMASK, grab input from second slot when encoding * encoding of newx86 * tfp and CallEndNode encoding * sib improvement * more cleanups * more cleanups * Allow a killMap to kill regs. Bugfix scheduler. Bugfix spiller - self-conflict order depended on hashtable iteration order, which depended on System.hashCode which would vary run to run. Improvements to coloring Improvements to spill quality testing * Added flags-kill for XOR * Missing add-deps from fuzzer * Update Chapter14Test.java * fixed order of operands * base rex fixup * more bug fix * remove jukn files * minimal "make release" * further fixes * minor fixes * minor fixes * minor fixes * base != -1 check, precedence fix * Add regkills in IFG building Fix bug LRG union stats Adjust self-conflict splits to be slightly less aggressive In every use-side split, dont split-after-split same block. NewNode is a multi, does not define a register * 64 bit encoding for gpr(s) load * RA improvements Attempt to commute to keep reg-masks compatible. Clones with kills (x86 XOR killing flags to zero) fail before the flag-def. Bugfix failed to color last lrg. More smarts on picking trivial LRGs during coloring Drop isSplit, add SplitNode Pass regalloc round to all splits for debugging Self-conflict no split on loop backedge. Handle split that gets made goes dead No-reg-mask spilling handles case with many def/uses. Add DivIX86 BrainFuck, MergeSort now allocate as testing * 8 bit imm support * conditional jump fix * Push l,ast change, unbreak branch * XOR remove extra opcode, imm size supports 8 bit. * Finally pass the `new [u8]` double-copy test * Fix several ARM reg setup problems Finish Split bypass check ARM: rsp is also a fixed zero, in R31. R0 is a valid register. CallArm prints args, passes TFP for arg selection. Does not return a register. All CallEndARM which DOES return a register. Parms missing tfp for float args. ProjARM after a New missing regs. RetARM missing RPC. Remove hopeful DivIX86 * 64 bit imm for alu ops encoded in extra reg then use it in Op as an operand * Bunch of minor arm, risc fixes small constants not cloning * jump invert encoding * warning comment * Much ARM fixage One More Go-Round with Better Biased Colors Fix bug picking deepest spills to bias. ARM: remove default 2-address. Fix regmask overflow. Rename some ops to be canonical. * Fixes to release Port does not have to be in a Simple named directory. Call convention is just a string, no enum Missed some tests running RISC, ARM. * Minor release change to allow another foreign port * better split encoding and fix CallInMask * notNode * fix callInMask * riscv init * riscv basic ALU ops and types of instructions * Allowing non-Simple repo ports Expanding public API slightly so alternative (non-Simple repo) ports can happen. RegMask supports single-bit-set and a long bitmask. Bugfix GCM splitting multiple global constants. Bugfix LCM grabbing a projection vs multi Bugfix RegAlloc when avoiding splitting a clonable Source code in release More timing in CodeGen * riscv indirect addressing and function calls * Cleanup call ABI presentation Everybody uses the same names, same code shapes * Caller save regs ...not callee save yet * Expose caller save regs * Store Risc adjustments, no imm in MemOpRISC * specific print encoding for cpus * merge fixup * More x86,risc,arm call convention fixes Collect callee and caller save masks (inverts of each other, except for spills). Reorg layout to be closer to each other; remove dead mask definitions; add final keyword * lui fix up with 32 bit immediates * Callee-save registers Callee-save registers and masks and inserting LRGs for same and spilling and picking good spills. Insert CalleeSaveNodes and edges to RetXXXs and RegMasks for those edges. Iterators for regmask. Better printing post-allocation * RegMask supports 128 bits All regs available arm, risc. MergeSort compiles & runs * added flags splitting * Fix risc5 compares No flags on risc5. Branch takes 2 inputs (one can be imm12). Float compare only sets a GPR to 0/1. Bool has a isFloat Common risc5 imm-form handling * extend test cases * imm encoding, flags fixed * grep for Todo: relocs * float riscv fixed * flags for set * lui fix for small bits [0, 12] * arm init * arm last touches * fmov still needs fix * minor fixes * minor fixes(2) * got rid of unnecessary stuff * create ch21 * remove encodings from chapter20 dir * 1/2 pass cleaning up ARM encoding API * ARM encoding compiles * Risc R_TYPE and I_TYPE * Progress RISC encodings Missing lots of stuff still * progress risc encoding * risc encodings compile * Update README.md * Fold up redundant OP/OP-FP risc5 encoding * x86 mem opcodes first cut break out MemOp encodings into parts; change x86 indirectAdr to use Encoding class, remove redundant asserts & code * x86 mem op encodings * x86 reg+imm forms * most reg-reg forms * More x86 encodings * Last few encodings * switch to binary literals over decimals for opcodes * logical immediates fixed, bitwise ops imm forms * tfp and setArm fixed * PC relative float for riscv * readme init * Merged pr/169 * Fixed the array1 test encodings Clean up branch invert logic during BB layout Cleanup branch target printing for RISC,ARM ImmRISC ops mask imm12 to 12 bits Fix NewRISC; hardcode RPC as part of call Fix RISC inverting branchs >,>= * floatarm encoding * tfparm and intarm * uncontional jump encoding for arm and riscv * smarter IntARM, Intx86 does not always include rex. * REX after PREFIX * fixup load and store indirect * updated rex prefix, if its 0x40 then its not needed * Rex fix for floats, got rid of unnecessary stuff. * rename to camel case, better param name --------- Co-authored-by: Cliff Click <cliffc@acm.org> commit d5b91a513b53c0e69a82f66f350b4e91a86f615d Author: Cliff Click <cliffc@acm.org> Date: Mon Mar 17 12:22:33 2025 -0700 patch branch offsets commit 1a60b525475c3ad6e548af2e9417e4639aee210c Author: Cliff Click <cliffc@acm.org> Date: Sun Mar 16 22:25:45 2025 -0700 Update LeaX86.java commit f727c10fe7ec84de7e8fefe16e9774bdae8eee6b Author: Tamás Hadházy <85063808+Hels15@users.noreply.github.com> Date: Mon Mar 17 05:07:11 2025 +0000 Ch20 encoding (#172) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * READMEs * Update README.md * Update README.md * Move printing into print dir * Move codegen into codegen * Framework for Reg Alloc * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * First cut BuildLRG RegMask.EMPTY -> null * fix minor bug * Setup for first simple split * First cut split code for mismatch regs * delete non existing matches * replace RegMask.Empty with null * Live Ranges U-F BuildLRG for Phi Add CmpFX86 * FPR return CallEndX86 needed to tell fp return from gpr ret Call has no output regs. * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * Next cut regalloc Building IFG. Coloring. Some nested spilling Optimize divf/con -> mul times inverse * turn off ch20 tests * Update Chapter20Test.java * RISC-V basic isel (#156) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Bring pr/154 to ch 20 RISC5 changes. Some missing x86 ops. Still needs some calling convention love. * First cut regalloc working Biased coloring; tracking splits around a LRG. Separate split for self-conflicts. postColor to remove junk splits. Some minor riscv fixes. * Compile some riscv codes If SplitEmptyMask does not apply, fall back to normal split. Missing CmpFRISC. Renamed riscv flags. RetRISC handls FPR remove extra RegMask constructurs * add riscv port to readme * arm init * Progress reg alloc Handle multi-node projections. A little smarter about 2-addr scheduling RegAlloc must-have single registers removes from other masks instead of interfering; had the mask removal backwards in a few cases. LRG/RegMask COW expand masks before mutating. Clone small constants instead of spilling. Alloc & ProjX86 get output reg masks. Full print large reg masks with sp+offset * UF LRGs can be null during coloring * Finish UF find * work in progress * README updates * Fix ch19 mis-merge? * x86 call args, risc5 load/store regs * arm2 bs * Push RISC WMASK avoid writing to RSP * work in progress * clearnup arm * Progress calling convention allocations Call unlinks before reg alloc. CallEnd tracks RPC CallX86 requires TFP for correct ABI Bias Color tracks 2-addr. RPC gets a mask/stack-slot Clean up call/fun ABI Fix mask RET, RET_F Fix print stack slot Remove isMultiHead, isMultiTail Better all-asm print * Beef up Newton test * fixed main pr problems * regmask extra comments * Clean pass normal tests New fcn print upgrades several golden rule tests. Move the unlink-all from the walk::err call (which IS used during IterPeeps) to GCM. CallEnd upgrades return type based on constant call. Fix unlink to also unlink CallEnd Constant TFP print checks constant Tested an untested Load * Arm basics, minor changes in riscv (#160) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java * add riscv port to readme * arm init * work in progress * arm2 bs * work in progress * clearnup arm * fixed main pr problems * regmask extra comments --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Update Chapter20Test.java * Weaken range-check test since no range checks... test cannot fail (pass) * Fix issue #161 Error adding 'null' and '5' * bug fixs, cloning constants Fix bug missing Find. Fix bug setDef does unordered removal from basic blocks. Use cloned constants for spilling. * RA fixes SplitSelfConflict MUST split before uses and after defs. Split global constants across functions Missing find in union; Inserting split after new inserted wrong place Pre-remove null LRGs before color. Bias color hunts through Phis Shorten InstructionSelection. Rename !unified() to leader() AddMemX86 is 2-addr Add CastXXX to preserve register across cast. * encoding init * encoding init 2 * rex fix * indirect addressing modes * setop change opcode, rename FLOAT_OFFSET * mask sib and modrm with 3 bits, 4th bit goes into rex. * sign extend div with cqo * idiv input is fixed to rax and RMASK, grab input from second slot when encoding * encoding of newx86 * tfp and CallEndNode encoding * sib improvement * more cleanups * more cleanups * Allow a killMap to kill regs. Bugfix scheduler. Bugfix spiller - self-conflict order depended on hashtable iteration order, which depended on System.hashCode which would vary run to run. Improvements to coloring Improvements to spill quality testing * Added flags-kill for XOR * Missing add-deps from fuzzer * Update Chapter14Test.java * fixed order of operands * base rex fixup * more bug fix * remove jukn files * minimal "make release" * further fixes * minor fixes * minor fixes * minor fixes * base != -1 check, precedence fix * Add regkills in IFG building Fix bug LRG union stats Adjust self-conflict splits to be slightly less aggressive In every use-side split, dont split-after-split same block. NewNode is a multi, does not define a register * 64 bit encoding for gpr(s) load * RA improvements Attempt to commute to keep reg-masks compatible. Clones with kills (x86 XOR killing flags to zero) fail before the flag-def. Bugfix failed to color last lrg. More smarts on picking trivial LRGs during coloring Drop isSplit, add SplitNode Pass regalloc round to all splits for debugging Self-conflict no split on loop backedge. Handle split that gets made goes dead No-reg-mask spilling handles case with many def/uses. Add DivIX86 BrainFuck, MergeSort now allocate as testing * 8 bit imm support * conditional jump fix * Push l,ast change, unbreak branch * XOR remove extra opcode, imm size supports 8 bit. * Finally pass the `new [u8]` double-copy test * Fix several ARM reg setup problems Finish Split bypass check ARM: rsp is also a fixed zero, in R31. R0 is a valid register. CallArm prints args, passes TFP for arg selection. Does not return a register. All CallEndARM which DOES return a register. Parms missing tfp for float args. ProjARM after a New missing regs. RetARM missing RPC. Remove hopeful DivIX86 * 64 bit imm for alu ops encoded in extra reg then use it in Op as an operand * Bunch of minor arm, risc fixes small constants not cloning * jump invert encoding * warning comment * Much ARM fixage One More Go-Round with Better Biased Colors Fix bug picking deepest spills to bias. ARM: remove default 2-address. Fix regmask overflow. Rename some ops to be canonical. * Fixes to release Port does not have to be in a Simple named directory. Call convention is just a string, no enum Missed some tests running RISC, ARM. * Minor release change to allow another foreign port * better split encoding and fix CallInMask * notNode * fix callInMask * riscv init * riscv basic ALU ops and types of instructions * Allowing non-Simple repo ports Expanding public API slightly so alternative (non-Simple repo) ports can happen. RegMask supports single-bit-set and a long bitmask. Bugfix GCM splitting multiple global constants. Bugfix LCM grabbing a projection vs multi Bugfix RegAlloc when avoiding splitting a clonable Source code in release More timing in CodeGen * riscv indirect addressing and function calls * Cleanup call ABI presentation Everybody uses the same names, same code shapes * Caller save regs ...not callee save yet * Expose caller save regs * Store Risc adjustments, no imm in MemOpRISC * specific print encoding for cpus * merge fixup * More x86,risc,arm call convention fixes Collect callee and caller save masks (inverts of each other, except for spills). Reorg layout to be closer to each other; remove dead mask definitions; add final keyword * lui fix up with 32 bit immediates * Callee-save registers Callee-save registers and masks and inserting LRGs for same and spilling and picking good spills. Insert CalleeSaveNodes and edges to RetXXXs and RegMasks for those edges. Iterators for regmask. Better printing post-allocation * RegMask supports 128 bits All regs available arm, risc. MergeSort compiles & runs * added flags splitting * Fix risc5 compares No flags on risc5. Branch takes 2 inputs (one can be imm12). Float compare only sets a GPR to 0/1. Bool has a isFloat Common risc5 imm-form handling * extend test cases * imm encoding, flags fixed * grep for Todo: relocs * float riscv fixed * flags for set * lui fix for small bits [0, 12] * arm init * arm last touches * fmov still needs fix * minor fixes * minor fixes(2) * got rid of unnecessary stuff * create ch21 * remove encodings from chapter20 dir * 1/2 pass cleaning up ARM encoding API * ARM encoding compiles * Risc R_TYPE and I_TYPE * Progress RISC encodings Missing lots of stuff still * progress risc encoding * risc encodings compile * Update README.md * Fold up redundant OP/OP-FP risc5 encoding * x86 mem opcodes first cut break out MemOp encodings into parts; change x86 indirectAdr to use Encoding class, remove redundant asserts & code * x86 mem op encodings * x86 reg+imm forms * most reg-reg forms * More x86 encodings * Last few encodings * switch to binary literals over decimals for opcodes * logical immediates fixed, bitwise ops imm forms * tfp and setArm fixed * PC relative float for riscv * readme init * Merged pr/169 * Fixed the array1 test encodings Clean up branch invert logic during BB layout Cleanup branch target printing for RISC,ARM ImmRISC ops mask imm12 to 12 bits Fix NewRISC; hardcode RPC as part of call Fix RISC inverting branchs >,>= * floatarm encoding * tfparm and intarm * uncontional jump encoding for arm and riscv * smarter IntARM, Intx86 does not always include rex. --------- Co-authored-by: Cliff Click <cliffc@acm.org> commit 309da044c29f7f5e92f00aca911e69c8bfad7caf Author: Cliff Click <cliffc@acm.org> Date: Sun Mar 16 22:03:51 2025 -0700 short-form x86 encodings commit 248ac2317de092ea5f302266962fdbec17045d42 Author: Cliff Click <cliffc@acm.org> Date: Sun Mar 16 10:50:13 2025 -0700 Add unconditional arm,risc commit d15e43400bb7c7d0d27257d4c907206257c912b3 Author: Cliff Click <cliffc@acm.org> Date: Sun Mar 16 10:41:20 2025 -0700 Unconditional jumps commit 30cea7d810dd9460fa7bd40d1dfb1393c71a0ee9 Author: Tamás Hadházy <85063808+Hels15@users.noreply.github.com> Date: Sun Mar 16 00:03:20 2025 +0000 floatarm (#170) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * READMEs * Update README.md * Update README.md * Move printing into print dir * Move codegen into codegen * Framework for Reg Alloc * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * First cut BuildLRG RegMask.EMPTY -> null * fix minor bug * Setup for first simple split * First cut split code for mismatch regs * delete non existing matches * replace RegMask.Empty with null * Live Ranges U-F BuildLRG for Phi Add CmpFX86 * FPR return CallEndX86 needed to tell fp return from gpr ret Call has no output regs. * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * Next cut regalloc Building IFG. Coloring. Some nested spilling Optimize divf/con -> mul times inverse * turn off ch20 tests * Update Chapter20Test.java * RISC-V basic isel (#156) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Bring pr/154 to ch 20 RISC5 changes. Some missing x86 ops. Still needs some calling convention love. * First cut regalloc working Biased coloring; tracking splits around a LRG. Separate split for self-conflicts. postColor to remove junk splits. Some minor riscv fixes. * Compile some riscv codes If SplitEmptyMask does not apply, fall back to normal split. Missing CmpFRISC. Renamed riscv flags. RetRISC handls FPR remove extra RegMask constructurs * add riscv port to readme * arm init * Progress reg alloc Handle multi-node projections. A little smarter about 2-addr scheduling RegAlloc must-have single registers removes from other masks instead of interfering; had the mask removal backwards in a few cases. LRG/RegMask COW expand masks before mutating. Clone small constants instead of spilling. Alloc & ProjX86 get output reg masks. Full print large reg masks with sp+offset * UF LRGs can be null during coloring * Finish UF find * work in progress * README updates * Fix ch19 mis-merge? * x86 call args, risc5 load/store regs * arm2 bs * Push RISC WMASK avoid writing to RSP * work in progress * clearnup arm * Progress calling convention allocations Call unlinks before reg alloc. CallEnd tracks RPC CallX86 requires TFP for correct ABI Bias Color tracks 2-addr. RPC gets a mask/stack-slot Clean up call/fun ABI Fix mask RET, RET_F Fix print stack slot Remove isMultiHead, isMultiTail Better all-asm print * Beef up Newton test * fixed main pr problems * regmask extra comments * Clean pass normal tests New fcn print upgrades several golden rule tests. Move the unlink-all from the walk::err call (which IS used during IterPeeps) to GCM. CallEnd upgrades return type based on constant call. Fix unlink to also unlink CallEnd Constant TFP print checks constant Tested an untested Load * Arm basics, minor changes in riscv (#160) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java * add riscv port to readme * arm init * work in progress * arm2 bs * work in progress * clearnup arm * fixed main pr problems * regmask extra comments --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Update Chapter20Test.java * Weaken range-check test since no range checks... test cannot fail (pass) * Fix issue #161 Error adding 'null' and '5' * bug fixs, cloning constants Fix bug missing Find. Fix bug setDef does unordered removal from basic blocks. Use cloned constants for spilling. * RA fixes SplitSelfConflict MUST split before uses and after defs. Split global constants across functions Missing find in union; Inserting split after new inserted wrong place Pre-remove null LRGs before color. Bias color hunts through Phis Shorten InstructionSelection. Rename !unified() to leader() AddMemX86 is 2-addr Add CastXXX to preserve register across cast. * encoding init * encoding init 2 * rex fix * indirect addressing modes * setop change opcode, rename FLOAT_OFFSET * mask sib and modrm with 3 bits, 4th bit goes into rex. * sign extend div with cqo * idiv input is fixed to rax and RMASK, grab input from second slot when encoding * encoding of newx86 * tfp and CallEndNode encoding * sib improvement * more cleanups * more cleanups * Allow a killMap to kill regs. Bugfix scheduler. Bugfix spiller - self-conflict order depended on hashtable iteration order, which depended on System.hashCode which would vary run to run. Improvements to coloring Improvements to spill quality testing * Added flags-kill for XOR * Missing add-deps from fuzzer * Update Chapter14Test.java * fixed order of operands * base rex fixup * more bug fix * remove jukn files * minimal "make release" * further fixes * minor fixes * minor fixes * minor fixes * base != -1 check, precedence fix * Add regkills in IFG building Fix bug LRG union stats Adjust self-conflict splits to be slightly less aggressive In every use-side split, dont split-after-split same block. NewNode is a multi, does not define a register * 64 bit encoding for gpr(s) load * RA improvements Attempt to commute to keep reg-masks compatible. Clones with kills (x86 XOR killing flags to zero) fail before the flag-def. Bugfix failed to color last lrg. More smarts on picking trivial LRGs during coloring Drop isSplit, add SplitNode Pass regalloc round to all splits for debugging Self-conflict no split on loop backedge. Handle split that gets made goes dead No-reg-mask spilling handles case with many def/uses. Add DivIX86 BrainFuck, MergeSort now allocate as testing * 8 bit imm support * conditional jump fix * Push l,ast change, unbreak branch * XOR remove extra opcode, imm size supports 8 bit. * Finally pass the `new [u8]` double-copy test * Fix several ARM reg setup problems Finish Split bypass check ARM: rsp is also a fixed zero, in R31. R0 is a valid register. CallArm prints args, passes TFP for arg selection. Does not return a register. All CallEndARM which DOES return a register. Parms missing tfp for float args. ProjARM after a New missing regs. RetARM missing RPC. Remove hopeful DivIX86 * 64 bit imm for alu ops encoded in extra reg then use it in Op as an operand * Bunch of minor arm, risc fixes small constants not cloning * jump invert encoding * warning comment * Much ARM fixage One More Go-Round with Better Biased Colors Fix bug picking deepest spills to bias. ARM: remove default 2-address. Fix regmask overflow. Rename some ops to be canonical. * Fixes to release Port does not have to be in a Simple named directory. Call convention is just a string, no enum Missed some tests running RISC, ARM. * Minor release change to allow another foreign port * better split encoding and fix CallInMask * notNode * fix callInMask * riscv init * riscv basic ALU ops and types of instructions * Allowing non-Simple repo ports Expanding public API slightly so alternative (non-Simple repo) ports can happen. RegMask supports single-bit-set and a long bitmask. Bugfix GCM splitting multiple global constants. Bugfix LCM grabbing a projection vs multi Bugfix RegAlloc when avoiding splitting a clonable Source code in release More timing in CodeGen * riscv indirect addressing and function calls * Cleanup call ABI presentation Everybody uses the same names, same code shapes * Caller save regs ...not callee save yet * Expose caller save regs * Store Risc adjustments, no imm in MemOpRISC * specific print encoding for cpus * merge fixup * More x86,risc,arm call convention fixes Collect callee and caller save masks (inverts of each other, except for spills). Reorg layout to be closer to each other; remove dead mask definitions; add final keyword * lui fix up with 32 bit immediates * Callee-save registers Callee-save registers and masks and inserting LRGs for same and spilling and picking good spills. Insert CalleeSaveNodes and edges to RetXXXs and RegMasks for those edges. Iterators for regmask. Better printing post-allocation * RegMask supports 128 bits All regs available arm, risc. MergeSort compiles & runs * added flags splitting * Fix risc5 compares No flags on risc5. Branch takes 2 inputs (one can be imm12). Float compare only sets a GPR to 0/1. Bool has a isFloat Common risc5 imm-form handling * extend test cases * imm encoding, flags fixed * grep for Todo: relocs * float riscv fixed * flags for set * lui fix for small bits [0, 12] * arm init * arm last touches * fmov still needs fix * minor fixes * minor fixes(2) * got rid of unnecessary stuff * create ch21 * remove encodings from chapter20 dir * 1/2 pass cleaning up ARM encoding API * ARM encoding compiles * Risc R_TYPE and I_TYPE * Progress RISC encodings Missing lots of stuff still * progress risc encoding * risc encodings compile * Update README.md * Fold up redundant OP/OP-FP risc5 encoding * x86 mem opcodes first cut break out MemOp encodings into parts; change x86 indirectAdr to use Encoding class, remove redundant asserts & code * x86 mem op encodings * x86 reg+imm forms * most reg-reg forms * More x86 encodings * Last few encodings * switch to binary literals over decimals for opcodes * logical immediates fixed, bitwise ops imm forms * tfp and setArm fixed * PC relative float for riscv * readme init * Merged pr/169 * Fixed the array1 test encodings Clean up branch invert logic during BB layout Cleanup branch target printing for RISC,ARM ImmRISC ops mask imm12 to 12 bits Fix NewRISC; hardcode RPC as part of call Fix RISC inverting branchs >,>= * floatarm encoding --------- Co-authored-by: Cliff Click <cliffc@acm.org> commit 251622b0111f9f49e04528a11e4ea4f19376b3ef Author: Cliff Click <cliffc@acm.org> Date: Sat Mar 15 16:42:13 2025 -0700 Fixed the array1 test encodings Clean up branch invert logic during BB layout Cleanup branch target printing for RISC,ARM ImmRISC ops mask imm12 to 12 bits Fix NewRISC; hardcode RPC as part of call Fix RISC inverting branchs >,>= commit d4c1895977755a94602d9cbbe8ee743e6bdeb894 Author: Cliff Click <cliffc@acm.org> Date: Sat Mar 15 16:11:27 2025 -0700 Merged pr/169 commit dc3a84cea6ad0b99c05ff7f6ab98edb905af0c4f Author: Tamás Hadházy <85063808+Hels15@users.noreply.github.com> Date: Sat Mar 15 23:09:53 2025 +0000 Ch21 fixups (#169) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * READMEs * Update README.md * Update README.md * Move printing into print dir * Move codegen into codegen * Framework for Reg Alloc * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * First cut BuildLRG RegMask.EMPTY -> null * fix minor bug * Setup for first simple split * First cut split code for mismatch regs * delete non existing matches * replace RegMask.Empty with null * Live Ranges U-F BuildLRG for Phi Add CmpFX86 * FPR return CallEndX86 needed to tell fp return from gpr ret Call has no output regs. * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * Next cut regalloc Building IFG. Coloring. Some nested spilling Optimize divf/con -> mul times inverse * turn off ch20 tests * Update Chapter20Test.java * RISC-V basic isel (#156) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Bring pr/154 to ch 20 RISC5 changes. Some missing x86 ops. Still needs some calling convention love. * First cut regalloc working Biased coloring; tracking splits around a LRG. Separate split for self-conflicts. postColor to remove junk splits. Some minor riscv fixes. * Compile some riscv codes If SplitEmptyMask does not apply, fall back to normal split. Missing CmpFRISC. Renamed riscv flags. RetRISC handls FPR remove extra RegMask constructurs * add riscv port to readme * arm init * Progress reg alloc Handle multi-node projections. A little smarter about 2-addr scheduling RegAlloc must-have single registers removes from other masks instead of interfering; had the mask removal backwards in a few cases. LRG/RegMask COW expand masks before mutating. Clone small constants instead of spilling. Alloc & ProjX86 get output reg masks. Full print large reg masks with sp+offset * UF LRGs can be null during coloring * Finish UF find * work in progress * README updates * Fix ch19 mis-merge? * x86 call args, risc5 load/store regs * arm2 bs * Push RISC WMASK avoid writing to RSP * work in progress * clearnup arm * Progress calling convention allocations Call unlinks before reg alloc. CallEnd tracks RPC CallX86 requires TFP for correct ABI Bias Color tracks 2-addr. RPC gets a mask/stack-slot Clean up call/fun ABI Fix mask RET, RET_F Fix print stack slot Remove isMultiHead, isMultiTail Better all-asm print * Beef up Newton test * fixed main pr problems * regmask extra comments * Clean pass normal tests New fcn print upgrades several golden rule tests. Move the unlink-all from the walk::err call (which IS used during IterPeeps) to GCM. CallEnd upgrades return type based on constant call. Fix unlink to also unlink CallEnd Constant TFP print checks constant Tested an untested Load * Arm basics, minor changes in riscv (#160) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java * add riscv port to readme * arm init * work in progress * arm2 bs * work in progress * clearnup arm * fixed main pr problems * regmask extra comments --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Update Chapter20Test.java * Weaken range-check test since no range checks... test cannot fail (pass) * Fix issue #161 Error adding 'null' and '5' * bug fixs, cloning constants Fix bug missing Find. Fix bug setDef does unordered removal from basic blocks. Use cloned constants for spilling. * RA fixes SplitSelfConflict MUST split before uses and after defs. Split global constants across functions Missing find in union; Inserting split after new inserted wrong place Pre-remove null LRGs before color. Bias color hunts through Phis Shorten InstructionSelection. Rename !unified() to leader() AddMemX86 is 2-addr Add CastXXX to preserve register across cast. * encoding init * encoding init 2 * rex fix * indirect addressing modes * setop change opcode, rename FLOAT_OFFSET * mask sib and modrm with 3 bits, 4th bit goes into rex. * sign extend div with cqo * idiv input is fixed to rax and RMASK, grab input from second slot when encoding * encoding of newx86 * tfp and CallEndNode encoding * sib improvement * more cleanups * more cleanups * Allow a killMap to kill regs. Bugfix scheduler. Bugfix spiller - self-conflict order depended on hashtable iteration order, which depended on System.hashCode which would vary run to run. Improvements to coloring Improvements to spill quality testing * Added flags-kill for XOR * Missing add-deps from fuzzer * Update Chapter14Test.java * fixed order of operands * base rex fixup * more bug fix * remove jukn files * minimal "make release" * further fixes * minor fixes * minor fixes * minor fixes * base != -1 check, precedence fix * Add regkills in IFG building Fix bug LRG union stats Adjust self-conflict splits to be slightly less aggressive In every use-side split, dont split-after-split same block. NewNode is a multi, does not define a register * 64 bit encoding for gpr(s) load * RA improvements Attempt to commute to keep reg-masks compatible. Clones with kills (x86 XOR killing flags to zero) fail before the flag-def. Bugfix failed to color last lrg. More smarts on picking trivial LRGs during coloring Drop isSplit, add SplitNode Pass regalloc round to all splits for debugging Self-conflict no split on loop backedge. Handle split that gets made goes dead No-reg-mask spilling handles case with many def/uses. Add DivIX86 BrainFuck, MergeSort now allocate as testing * 8 bit imm support * conditional jump fix * Push l,ast change, unbreak branch * XOR remove extra opcode, imm size supports 8 bit. * Finally pass the `new [u8]` double-copy test * Fix several ARM reg setup problems Finish Split bypass check ARM: rsp is also a fixed zero, in R31. R0 is a valid register. CallArm prints args, passes TFP for arg selection. Does not return a register. All CallEndARM which DOES return a register. Parms missing tfp for float args. ProjARM after a New missing regs. RetARM missing RPC. Remove hopeful DivIX86 * 64 bit imm for alu ops encoded in extra reg then use it in Op as an operand * Bunch of minor arm, risc fixes small constants not cloning * jump invert encoding * warning comment * Much ARM fixage One More Go-Round with Better Biased Colors Fix bug picking deepest spills to bias. ARM: remove default 2-address. Fix regmask overflow. Rename some ops to be canonical. * Fixes to release Port does not have to be in a Simple named directory. Call convention is just a string, no enum Missed some tests running RISC, ARM. * Minor release change to allow another foreign port * better split encoding and fix CallInMask * notNode * fix callInMask * riscv init * riscv basic ALU ops and types of instructions * Allowing non-Simple repo ports Expanding public API slightly so alternative (non-Simple repo) ports can happen. RegMask supports single-bit-set and a long bitmask. Bugfix GCM splitting multiple global constants. Bugfix LCM grabbing a projection vs multi Bugfix RegAlloc when avoiding splitting a clonable Source code in release More timing in CodeGen * riscv indirect addressing and function calls * Cleanup call ABI presentation Everybody uses the same names, same code shapes * Caller save regs ...not callee save yet * Expose caller save regs * Store Risc adjustments, no imm in MemOpRISC * specific print encoding for cpus * merge fixup * More x86,risc,arm call convention fixes Collect callee and caller save masks (inverts of each other, except for spills). Reorg layout to be closer to each other; remove dead mask definitions; add final keyword * lui fix up with 32 bit immediates * Callee-save registers Callee-save registers and masks and inserting LRGs for same and spilling and picking good spills. Insert CalleeSaveNodes and edges to RetXXXs and RegMasks for those edges. Iterators for regmask. Better printing post-allocation * RegMask supports 128 bits All regs available arm, risc. MergeSort compiles & runs * added flags splitting * Fix risc5 compares No flags on risc5. Branch takes 2 inputs (one can be imm12). Float compare only sets a GPR to 0/1. Bool has a isFloat Common risc5 imm-form handling * extend test cases * imm encoding, flags fixed * grep for Todo: relocs * float riscv fixed * flags for set * lui fix for small bits [0, 12] * arm init * arm last touches * fmov still needs fix * minor fixes * minor fixes(2) * got rid of unnecessary stuff * create ch21 * remove encodings from chapter20 dir * 1/2 pass cleaning up ARM encoding API * ARM encoding compiles * Risc R_TYPE and I_TYPE * Progress RISC encodings Missing lots of stuff still * progress risc encoding * risc encodings compile * Update README.md * Fold up redundant OP/OP-FP risc5 encoding * x86 mem opcodes first cut break out MemOp encodings into parts; change x86 indirectAdr to use Encoding class, remove redundant asserts & code * x86 mem op encodings * x86 reg+imm forms * most reg-reg forms * More x86 encodings * Last few encodings * switch to binary literals over decimals for opcodes * logical immediates fixed, bitwise ops imm forms * tfp and setArm fixed * PC relative float for riscv --------- Co-authored-by: Cliff Click <cliffc@acm.org> commit 5097e9f08dacb25e4c4aca20b48aa7d523067628 Author: Cliff Click <cliffc@acm.org> Date: Sat Mar 15 16:05:04 2025 -0700 Encoding fixes commit fe27a3326becaf1a59286fa03d9aaa22504ec4ee Author: Cliff Click <cliffc@acm.org> Date: Sat Mar 15 12:55:57 2025 -0700 Tests all pass commit b6cbd3d7b7698922e60e2c3eadadab51a430efe1 Author: Cliff Click <cliffc@acm.org> Date: Sat Mar 15 12:06:11 2025 -0700 Progress towards no crashes Multi-part constant expansion was buggy when splitting down into functions. Improved printing for RISC multi-part constants. commit a36369843133a170718006600890d12ceb66a553 Author: Cliff Click <cliffc@acm.org> Date: Fri Mar 14 16:48:07 2025 -0700 Buncha encoding related fixes. RA cloning `CmpIxxx` stretches the input live range, new for cloning... and was making broken graph. Several cloning related bugs in RISC & ARM. Invariant around RA cloning vs InstSelect is too complex commit 3fa376db5d05d738b6e1b6d01680ca5bfd79962d Author: Cliff Click <cliffc@acm.org> Date: Fri Mar 14 15:17:49 2025 -0700 Progress on not-encoding tests commit 49a9c088087918f2018a2a6173805f044fd6a70c Author: Cliff Click <cliffc@acm.org> Date: Fri Mar 14 14:33:03 2025 -0700 Push a driver for ARM logical 12-bit immediates commit 8784736848d846f40c90c41edeb9162c00d0bdbd Author: Cliff Click <cliffc@acm.org> Date: Fri Mar 14 13:38:11 2025 -0700 ASM printer includes encodings commit de7fbdcf3f2002903d03c7737f573fa1e84cb306 Author: Cliff Click <cliffc@acm.org> Date: Fri Mar 14 12:27:21 2025 -0700 Call encode commit f3ca559f9fe58d221e2d465845932f92278a7928 Author: Cliff Click <cliffc@acm.org> Date: Fri Mar 14 12:24:17 2025 -0700 Encoding driver ch21 commit fd0123589d21c5055e4314b8bdf7e883f3d6d956 Author: Tamás Hadházy <85063808+Hels15@users.noreply.github.com> Date: Fri Mar 14 16:56:58 2025 +0000 Ch20 encoding (#167) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * READMEs * Update README.md * Update README.md * Move printing into print dir * Move codegen into codegen * Framework for Reg Alloc * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * First cut BuildLRG RegMask.EMPTY -> null * fix minor bug * Setup for first simple split * First cut split code for mismatch regs * delete non existing matches * replace RegMask.Empty with null * Live Ranges U-F BuildLRG for Phi Add CmpFX86 * FPR return CallEndX86 needed to tell fp return from gpr ret Call has no output regs. * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * Next cut regalloc Building IFG. Coloring. Some nested spilling Optimize divf/con -> mul times inverse * turn off ch20 tests * Update Chapter20Test.java * RISC-V basic isel (#156) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Bring pr/154 to ch 20 RISC5 changes. Some missing x86 ops. Still needs some calling convention love. * First cut regalloc working Biased coloring; tracking splits around a LRG. Separate split for self-conflicts. postColor to remove junk splits. Some minor riscv fixes. * Compile some riscv codes If SplitEmptyMask does not apply, fall back to normal split. Missing CmpFRISC. Renamed riscv flags. RetRISC handls FPR remove extra RegMask constructurs * add riscv port to readme * arm init * Progress reg alloc Handle multi-node projections. A little smarter about 2-addr scheduling RegAlloc must-have single registers removes from other masks instead of interfering; had the mask removal backwards in a few cases. LRG/RegMask COW expand masks before mutating. Clone small constants instead of spilling. Alloc & ProjX86 get output reg masks. Full print large reg masks with sp+offset * UF LRGs can be null during coloring * Finish UF find * work in progress * README updates * Fix ch19 mis-merge? * x86 call args, risc5 load/store regs * arm2 bs * Push RISC WMASK avoid writing to RSP * work in progress * clearnup arm * Progress calling convention allocations Call unlinks before reg alloc. CallEnd tracks RPC CallX86 requires TFP for correct ABI Bias Color tracks 2-addr. RPC gets a mask/stack-slot Clean up call/fun ABI Fix mask RET, RET_F Fix print stack slot Remove isMultiHead, isMultiTail Better all-asm print * Beef up Newton test * fixed main pr problems * regmask extra comments * Clean pass normal tests New fcn print upgrades several golden rule tests. Move the unlink-all from the walk::err call (which IS used during IterPeeps) to GCM. CallEnd upgrades return type based on constant call. Fix unlink to also unlink CallEnd Constant TFP print checks constant Tested an untested Load * Arm basics, minor changes in riscv (#160) * Initial push Major CodeGen cleanup; most static globals move into the CodeGen object. * Minor progress bug with mutual recursion * Add test from pr#148 * First cut x86 "port" Only "return 0;", no registers, no encoding, but yah gotta start somewhere * Update README.md * Update README.md * Update ListScheduler.java * command line launcher for simple Parser bug fref in args Some new test cases. * Add basic RegMask for more than 64 bits of register mask. Add calling convention basics to X86; con+ret RegMasks. GCM computes CFG for all users. Drop unused MultiUse. Pick up reg-pressure aware ListScheduler. Re-layout CodeGen file to get code & data chunks nearer each other. minor Ary extension * ASM Printer InstSel handles folding 2+ ideal ops into 1 machine op * handle instSel for some control flow Bool,If,CProj,Region,Phi Handle 2-op expansions Shuffle instSel graph walk back again. * Basic if-block inst selects and asm prints * Add mul-by-coinstant to shift * left shift codegen basic * exclude RSP from *write* mask * 2-arg add (not immediate) mul-by-small constant opt. Some utilities. * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * inst select for new struct allocation * Float, bitwise and arithmetic operands codegen x86-64 (#150) * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * Handle first loop Change inst sel walk again to pre-order, to set an early visit bit to stop cycles. CFGNode copies dom/loop info. Cmp not-immediate form. Ret/Fun lazy updates * float ops without imm values * merge * merge * inst select for new struct allocation * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * Remove non-exsting FP+imm ops * left shift codegen basic * sar codegen basic * merge * some basic ops * addf * divf, subf, mulf * rsp allowed in bitwise input, not output * formatting * float ops without imm values * merge * merge * Rebased on ch 19 * Update tests --------- Co-authored-by: Cliff Click <cliffc@acm.org> * cleanup after merge alpha-sort helper fcns implicit test vs zero/null * Add LEA op asm print works on ideal nodes for all tests * Minor cleanup lea * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * x86 addressing modes during inst select Drop New taking inits; just follow with initializing stores. Simplifies inst selelection which otherwise needs to undo this optimization and emit following init stores. Basic load/store for now, op-to-mem comes later add same becomes Shl by 1. Drop DivF-immiedate * call setup * extended ch13, 14,15,16 (#153) * merge2 * merge3 * Fix a few minor fuzzer bugs * Force array layout ld/st get size (but not signed/unsigned) Add unsigned LT for later range checks. * Fix a bunch of float issues was deleting required inputs * simpler invariant defensive copy inputs for simpler invariant in complex sharing patterns inc/dec form (just the opcode now). * Call & CallR X86 instructions * merge2 * Add-from-memory op Remove FP immediate forms. * Remove name from TFP Never shoulda been there (but was convenient for awhile). Moved into FunNode, found by checking the linker table with a TFP. * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI * New (#154) * merge2 * merge3 * GCM for float ops fixed, other cleanup - small extensions * call setup * merge2 * remove debug prints add check for bad call convention fix 3 tests (bad merge?) * cleanup call convention abi Needs more love at some point... * one more cleanup ABI --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Add CmpMem form Common addressing mode print LEA can skip a base * Add a AddFMemX86 A bunch of mem op patterns are missing, hopefully they are just cut-n-paste from the existing patterns. * Docs * XMM reg mask fix Minor README updates * Allow inverted cmp/mem narrowing stores can bypass an AndMask Array length loads do not need control Some missing print info * Support "*ptr op= val" at least for MemAdd * missed golden rule update * Array len is u32 load-after-store zero/sign-extends if the store is truncating * One more missed case * riscv init * added not imm form bitwise shifts and ops plus test cases * addressing modes(high chance it'll get deleted) * fix minor bug * delete non existing matches * replace RegMask.Empty with null * riscv handle store and load, fltRisch uses risc reg now * load float into GPR and then later in RA do hard split. * minor changes * turn off ch20 tests * Update Chapter20Test.java * add riscv port to readme * arm init * work in progress * arm2 bs * work in progress * clearnup arm * fixed main pr problems * regmask extra comments --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Update Chapter20Test.java * Weaken range-check test since no range checks... test cannot fail (pass) * Fix issue #161 Error adding 'null' and '5' * bug fixs, cloning constants Fix bug missing Find. Fix bug setDef does unordered removal from basic blocks. Use cloned constants for spilling. * RA fixes SplitSelfConflict MUST split before uses and after defs. Split global constants across functions Missing find in union; Inserting split after new inserted wrong place Pre-remove null LRGs before color. Bias color hunts through Phis Shorten InstructionSelection. Rename !unified() to leader() AddMemX86 is 2-addr Add CastXXX to preserve register across cast. * encoding init * encoding init 2 * rex fix * indirect addressing modes * setop change opcode, rename FLOAT_OFFSET * mask sib and modrm with 3 bits, 4th bit goes into rex. * sign extend div with cqo * idiv input is fixed to rax and RMASK, grab input from second slot when encoding * encoding of newx86 * tfp and CallEndNode encoding * sib improvement * more cleanups * more cleanups * Allow a killMap to kill regs. Bugfix scheduler. Bugfix spiller - self-conflict order depended on hashtable iteration order, which depended on System.hashCode which would vary run to run. Improvements to coloring Improvements to spill quality testing * Added flags-kill for XOR * Missing add-deps from fuzzer * Update Chapter14Test.java * fixed order of operands * base rex fixup * more bug fix * remove jukn files * minimal "make release" * further fixes * minor fixes * minor fixes * minor fixes * base != -1 check, precedence fix * Add regkills in IFG building Fix bug LRG union stats Adjust self-conflict splits to be slightly less aggressive In every use-side split, dont split-after-split same block. NewNode is a multi, does not define a register * 64 bit encoding for gpr(s) load * RA improvements Attempt to commute to keep reg-masks compatible. Clones with kills (x86 XOR killing flags to zero) fail before the flag-def. Bugfix failed to color last lrg. More smarts on picking trivial LRGs during coloring Drop isSplit, add SplitNode Pass regalloc round to all splits for debugging Self-conflict no split on loop backedge. Handle split that gets made goes dead No-reg-mask spilling handles case with many def/uses. Add DivIX86 BrainFuck, MergeSort now allocate as testing * 8 bit imm support * conditional jump fix * Push l,ast change, unbreak branch * XOR remove extra opcode, imm size supports 8 bit. * Finally pass the `new [u8]` double-copy test * Fix several ARM reg setup problems Finish Split bypass check ARM: rsp is also a fixed zero, in R31. R0 is a valid register. CallArm prints args, passes TFP for arg selection. Does not return a register. All CallEndARM which DOES return a register. Parms missing tfp for float args. ProjARM after a New missing regs. RetARM missing RPC. Remove hopeful DivIX86 * 64 bit imm for alu ops encoded in extra reg then use it in Op as an operand * Bunch of minor arm, risc fixes small constants not cloning * jump invert encoding * warning comment * Much ARM fixage One More Go-Round with Better Biased Colors Fix bug picking deepest spills to bias. ARM: remove default 2-address. Fix regmask overflow. Rename some ops to be canonical. * Fixes to release Port does not have to be in a Simple named directory. Call convention is just a string, no enum Missed some tests running RISC, ARM. * Minor release change to allow another foreign port * better split encoding and fix CallInMask * notNode * fix callInMask * riscv init * riscv basic ALU ops and types of instructions * Allowing non-Simple repo ports Expanding public API slightly so alternative (non-Simple repo) ports can happen. RegMask supports single-bit-set and a long bitmask. Bugfix GCM splitting multiple global constants. Bugfix LCM grabbing a projection vs multi Bugfix RegAlloc when avoiding splitting a clonable Source code in release More timing in CodeGen * riscv indirect addressing and function calls * Cleanup call ABI presentation Everybody uses the same names, same code shapes * Caller save regs ...not callee save yet * Expose caller save regs * Store Risc adjustments, no imm in MemOpRISC * specific print encoding for cpus * merge fixup * More x86,risc,arm call convention fixes Collect callee and caller save masks (inverts of each other, except for spills). Reorg layout to be closer to each other; remove dead mask definitions; add final keyword * lui fix up with 32 bit immediates * Callee-save registers Callee-save registers and masks and inserting LRGs for same and spilling and picking good spills. Insert CalleeSaveNodes and edges to RetXXXs and RegMasks for those edges. Iterators for regmask. Better printing post-allocation * RegMask supports 128 bits All regs available arm, risc. MergeSort compiles & runs * added flags splitting * Fix risc5 compares No flags on risc5. Branch takes 2 inputs (one can be imm12). Float compare only sets a GPR to 0/1. Bool has a isFloat Common risc5 imm-form handling * extend test cases * imm encoding, flags fixed * grep for Todo: relocs * float riscv fixed * flags for set * lui fix for small bits [0, 12] * arm init * arm last touches * fmov still needs fix * minor fixes * minor fixes(2) * got rid of unnecessary stuff * create ch21 * remove encodings from chapter20 dir * 1/2 pass cleaning up ARM encoding API * ARM encoding compiles * Risc R_TYPE and I_TYPE * Progress RISC encodings Missing lots of stuff still * progress risc encoding * risc encodings compile * Update README.md * Fold up redundant OP/OP-FP risc5 encoding * x86 mem opcodes first cut break out MemOp encodings into parts; change x86 indirectAdr to use Encoding class, remove redundant asserts & code * x86 mem op encodings * x86 reg+imm forms * most reg-reg forms * More x86 encodings * Last few encodings --------- Co-authored-by: Cliff Click <cliffc@acm.org> commit d8e9a8152d1d3122b5d0da6d1c7a12203011a0f7 Author: Cliff Click <cliffc@acm.org> Date: Mon Mar 10 12:55:06 2025 -0700 First cut BB layout commit 10013969971a6c2f3a8b0966323ae35e9a3fb1d8 Author: Cliff Click <cliffc@acm.org> Date: Mon Mar 10 12:54:46 2025 -0700 Update README.md commit 4d5538c4b9df61d6c2524868bd005030123b3189 Author: Cliff Click <cliffc@acm.org> Date: Sat Mar 8 15:17:22 2025 -0800 first cut encoding driver ASM printer skips jmps to next-blocks commit 1fd6f46cca3541861d56f3710728d34791beedf8 Author: Cliff Click <cliffc@acm.org> Date: Sat Mar 8 11:01:01 2025 -0800 chapter 21 encodings first push
Diffstat (limited to 'chapter21/src/main/java/com')
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/Ary.java228
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/IterPeeps.java199
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/Parser.java1751
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/SB.java99
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/Utils.java53
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/Var.java55
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java90
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java105
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java463
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/codegen/ElfExpr.java87
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java381
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/codegen/Encoding.java475
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java285
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/codegen/IFG.java551
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/codegen/LRG.java234
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java23
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java261
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/codegen/Machine.java93
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java14
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java625
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/codegen/RegMask.java126
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/codegen/RegMaskRW.java36
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/AddFNode.java44
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/AddNode.java199
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/AndNode.java61
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/BoolNode.java149
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/CFGNode.java192
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/CProjNode.java68
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/CallEndNode.java130
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/CallNode.java168
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java23
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/CastNode.java64
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/ConstantNode.java70
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/CtrlNode.java15
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/DivFNode.java41
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/DivNode.java46
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/FRefNode.java35
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/FunNode.java216
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/IfNode.java97
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/LoadNode.java210
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/LogicalNode.java29
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/LoopNode.java101
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java27
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/MachNode.java58
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java162
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/MemOpNode.java110
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/MinusFNode.java36
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/MinusNode.java40
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/MulFNode.java51
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/MulNode.java87
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/MultiNode.java29
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/NeverNode.java22
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/NewNode.java119
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/Node.java731
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/NotNode.java32
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/OrNode.java55
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/ParmNode.java48
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/PhiNode.java221
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/ProjNode.java54
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java30
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/RegionNode.java167
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/ReturnNode.java168
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java38
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/SarNode.java50
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/ScopeNode.java429
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/ShlNode.java53
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/ShrNode.java46
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/SplitNode.java25
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/StartNode.java57
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/StopNode.java65
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/StoreNode.java112
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/StructNode.java46
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/SubFNode.java44
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/SubNode.java59
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java34
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java15
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/XorNode.java54
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java20
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java22
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java27
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java21
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java24
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java18
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java31
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java76
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java47
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallEndARM.java8
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java32
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/CastARM.java18
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java22
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java22
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java40
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java19
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java19
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java45
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java22
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java25
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java67
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java39
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java19
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java33
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java19
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java29
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java69
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java19
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java18
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java25
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java36
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java28
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java20
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java24
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java17
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java15
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java15
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java32
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java66
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java43
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java19
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java18
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java29
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java53
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java48
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java19
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java27
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java815
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java41
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java17
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java21
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java21
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java12
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java16
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java92
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallEndRISC.java8
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java60
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java31
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CastRISC.java18
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java19
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java17
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java47
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java22
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java23
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java54
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java30
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java32
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java29
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java74
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java17
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java17
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java20
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java46
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java20
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java12
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java16
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java17
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java15
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java21
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java33
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java17
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java20
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java12
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java17
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java66
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java12
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java17
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java12
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java17
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java47
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java17
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java16
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java55
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java46
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java12
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java16
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java542
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java44
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java33
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java13
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java34
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java11
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java11
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java11
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallEndX86.java8
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java33
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java45
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CastX86.java16
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java32
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java44
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java78
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java26
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java34
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java33
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java47
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java34
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java31
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java37
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java76
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java80
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java55
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java60
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java39
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java88
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java32
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java11
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java29
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java21
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java25
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java39
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java11
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java11
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java17
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java15
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java26
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java19
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java40
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java24
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java53
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java40
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java24
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java35
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java24
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java100
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java85
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java31
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java10
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java66
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java53
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java11
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java11
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java644
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java286
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java287
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/print/IRPrinter.java331
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/print/JSViewer.java405
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java149
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/type/Field.java74
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/type/Type.java236
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/type/TypeFloat.java95
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java107
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/type/TypeInteger.java128
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/type/TypeMem.java74
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java105
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/type/TypeNil.java62
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/type/TypePtr.java53
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/type/TypeRPC.java100
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/type/TypeStruct.java249
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/type/TypeTuple.java115
246 files changed, 21060 insertions, 0 deletions
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/Ary.java b/chapter21/src/main/java/com/seaofnodes/simple/Ary.java
new file mode 100644
index 00000000..f43179ad
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/Ary.java
@@ -0,0 +1,228 @@
+package com.seaofnodes.simple;
+
+import java.lang.StringBuilder;
+import java.lang.reflect.Array;
+import java.util.*;
+
+// ArrayList with saner syntax
+public class Ary<E> extends AbstractList<E> implements List<E> {
+ public E[] _es;
+ public int _len;
+
+ public Ary(E[] es) { this(es,es.length); }
+ public Ary(E[] es, int len) { if( es.length==0 ) es=Arrays.copyOf(es,1); _es=es; _len=len; }
+ @SuppressWarnings("unchecked")
+ public Ary(Class<E> clazz) { this((E[]) Array.newInstance(clazz, 1),0); }
+
+ /** @return list is empty */
+ public boolean isEmpty() { return _len==0; }
+ /** @return active list length */
+ public int len() { return _len; }
+ public int size() { return len(); }
+
+ /** @param i element index
+ * @return element being returned; throws if OOB
+ * @exception AIOOBE if !(0 <= i < _len)
+ */
+ public E get( int i ) { return at(i); }
+ public E at( int i ) {
+ range_check(i);
+ return _es[i];
+ }
+ public E atX( int i ) {
+ if( i >= _len ) return null;
+ return _es[i];
+ }
+
+ /** @return last element */
+ public E last( ) { return at(_len-1); }
+
+ /** Add element in amortized constant time
+ * @param e Element to add at end of list
+ **/
+ public E push( E e ) {
+ if( _len >= _es.length ) _es = Arrays.copyOf(_es,Math.max(1,_es.length<<1));
+ return (_es[_len++] = e);
+ }
+ /** @return remove and return last element */
+ public E pop( ) {
+ return _len==0 ? null : _es[--_len];
+ }
+ @Override public E removeLast() { return pop(); }
+
+
+ /** Add element in amortized constant time
+ * @param e Element to add at end of list
+ * @return true */
+ public boolean add( E e ) {
+ if( _len >= _es.length ) _es = Arrays.copyOf(_es,Math.max(1,_es.length<<1));
+ _es[_len++] = e;
+ return true;
+ }
+
+ /** Fast, constant-time, element removal. Does not preserve order
+ * @param i element to be removed
+ * @return element removed */
+ public E del( int i ) {
+ range_check(i);
+ E tmp = _es[i];
+ _es[i]=_es[--_len];
+ return tmp;
+ }
+
+ /** Slow, linear-time, element removal. Preserves order.
+ * @param i element to be removed
+ * @return element removed */
+ @Override public E remove( int i ) {
+ range_check(i);
+ E e = _es[i];
+ System.arraycopy(_es,i+1,_es,i,(--_len)-i);
+ return e;
+ }
+
+ /** Slow, linear-time, element insertion. Preserves order.
+ * @param i where to insert
+ * @return element inserted */
+ public E insert( E e, int i ) {
+ setLen(_len+1);
+ System.arraycopy(_es,i,_es,i+1,_len-i);
+ _es[i] = e;
+ return e;
+ }
+
+
+ /** Remove all elements */
+ public void clear( ) { _len=0; }
+
+ /** Set existing element
+ * @param i element to set
+ * @param e value to set
+ * @return old value
+ * @exception AIOOBE if !(0 <= i < _len)
+ */
+ public E set( int i, E e ) {
+ range_check(i);
+ E old = _es[i];
+ _es[i] = e;
+ return old;
+ }
+
+ /** Set existing element
+ * @param i element to set
+ * @param e value to set
+ * @return old value
+ * @exception AIOOBE if !(0 <= i < _len)
+ */
+ public E setX( int i, E e ) {
+ if( i >= _len ) {
+ while( i >= _es.length ) _es = Arrays.copyOf( _es, _es.length << 1 );
+ _len = i+1;
+ }
+ E old = _es[i];
+ _es[i] = e;
+ return old;
+ }
+
+ public Ary<E> setLen( int len ) {
+ if( len > _len )
+ while( len>= _es.length ) _es = Arrays.copyOf(_es,_es.length<<1);
+ _len = len;
+ while( _es.length > (len<<1) ) // Shrink if hugely too large
+ _es = Arrays.copyOf(_es,_es.length>>1);
+ Arrays.fill(_es,len,_es.length,null);
+ return this;
+ }
+
+ public E swap( int i, int j ) {
+ range_check(i);
+ range_check(j);
+ E tmp = _es[i];
+ if( i==j ) return tmp;
+ _es[i] = _es[j];
+ _es[j] = tmp;
+ return tmp;
+ }
+
+
+ /** @param c Collection to be added */
+ public boolean addAll( Collection<? extends E> c ) {
+ if( c==null ) return false;
+ for( E e : c ) add(e);
+ return true;
+ }
+ public Ary<E> addAll( Iterable<? extends E> it ) { for( E e : it ) add(e); return this; }
+
+ /** @param es Array to be added */
+ public <F extends E> Ary<E> addAll( F[] es ) {
+ if( es==null || es.length==0 ) return this;
+ while( _len+es.length > _es.length ) _es = Arrays.copyOf(_es,_es.length<<1);
+ System.arraycopy(es,0,_es,_len,es.length);
+ _len += es.length;
+ return this;
+ }
+
+ /** @param c Collection to be added */
+ public Ary<E> addAll( Ary<? extends E> c ) {
+ if( c==null || c._len==0 ) return this;
+ while( _len+c._len > _es.length ) _es = Arrays.copyOf(_es,_es.length<<1);
+ System.arraycopy(c._es,0,_es,_len,c._len);
+ _len += c._len;
+ return this;
+ }
+
+ /** @return compact array version */
+ public E[] asAry() { return Arrays.copyOf(_es,_len); }
+
+ /** Find the first matching element using ==, or -1 if none. Note that
+ * most del calls shuffle the list, so the first element might be random.
+ * @param e Element to find
+ * @return index of first matching element, or -1 if none */
+ public int find( E e ) {
+ for( int i=0; i<_len; i++ ) if( _es[i]==e ) return i;
+ return -1;
+ }
+
+
+ /** @return an iterator */
+ @Override public Iterator<E> iterator() { return new Iter(); }
+ private class Iter implements Iterator<E> {
+ int _i=0;
+ @Override public boolean hasNext() { return _i<_len; }
+ @Override public E next() { return _es[_i++]; }
+ }
+
+ @Override public String toString() {
+ StringBuilder sb = new StringBuilder().append('{');
+ for( int i=0; i<_len; i++ ) {
+ if( i>0 ) sb.append(',');
+ if( _es[i] != null ) sb.append(_es[i].toString());
+ }
+ return sb.append('}').toString();
+ }
+
+ private void range_check( int i ) {
+ if( i < 0 || i>=_len )
+ throw new ArrayIndexOutOfBoundsException(""+i+" >= "+_len);
+ }
+
+ // Note that the hashCode() and equals() are not invariant to changes in the
+ // underlying array. If the hashCode() is used (e.g., inserting into a
+ // HashMap) and the then the array changes, the hashCode() will change also.
+ @Override public boolean equals( Object o ) {
+ if( this==o ) return true;
+ if( !(o instanceof Ary ary) ) return false;
+ if( _len != ary._len ) return false;
+ if( _es == ary._es ) return true;
+ for( int i=0; i<_len; i++ )
+ if( !(_es[i]==null ? (ary._es[i] == null) : _es[i].equals(ary._es[i])) )
+ return false;
+ return true;
+ }
+ @Override public int hashCode( ) {
+ int sum=_len;
+ for( int i=0; i<_len; i++ )
+ sum += _es[i]==null ? 0 : _es[i].hashCode();
+ return sum;
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter21/src/main/java/com/seaofnodes/simple/IterPeeps.java
new file mode 100644
index 00000000..66e8a4a8
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -0,0 +1,199 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.print.JSViewer;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Random;
+
+/**
+ * The IterPeeps runs after parsing. It iterates the peepholes to a fixed point
+ * so that no more peepholes apply. This should be linear because peepholes rarely
+ * (never?) increase code size. The graph should monotonically reduce in some
+ * dimension, which is usually size. It might also reduce in e.g. number of
+ * MulNodes or Load/Store nodes, swapping out more "expensive" Nodes for cheaper
+ * ones.
+ * <br>
+ * The theoretical overall worklist is mindless just grabbing the next thing and
+ * doing it. If the graph changes, put the neighbors on the worklist.
+ * <br>
+ * Lather, Rinse, Repeat until the worklist runs dry.
+ * <p>
+ * The main issues we have to deal with:
+ *
+ * <ul>
+ * <li>Nodes have uses; replacing some set of Nodes with another requires more graph
+ * reworking. Not rocket science, but it can be fiddly. Its helpful to have a
+ * small set of graph munging utilities, and the strong invariant that the graph
+ * is stable and correct between peepholes. In our case `Node.subsume` does
+ * most of the munging, building on our prior stable Node utilities.</li>
+ *
+ * <li>Changing a Node also changes the graph "neighborhood". The neighbors need to
+ * be checked to see if THEY can also peephole, and so on. After any peephole
+ * or graph update we put a Nodes uses and defs on the worklist.</li>
+ *
+ * <li>Our strong invariant is that for all Nodes, either they are on the worklist
+ * OR no peephole applies. This invariant is easy to check, although expensive.
+ * Basically the normal "iterate peepholes to a fixed point" is linear, and this
+ * check is linear at each peephole step... so quadratic overall. Its a useful
+ * assert, but one we can disable once the overall algorithm is stable - and
+ * then turn it back on again when some new set of peepholes is misbehaving.
+ * The code for this is turned on in `IterPeeps.iterate` as `assert
+ * progressOnList(stop);`</li>
+ * </ul>
+ */
+public class IterPeeps {
+
+ private final WorkList<Node> _work;
+
+ public IterPeeps( long seed ) { _work = new WorkList<>(seed); }
+
+ @SuppressWarnings("unchecked")
+ public <N extends Node> N add( N n ) { return (N)_work.push(n); }
+
+ public void addAll( Ary<Node> ary ) { _work.addAll(ary); }
+
+ /**
+ * Iterate peepholes to a fixed point
+ */
+ public void iterate( CodeGen code ) {
+ assert progressOnList(code);
+ int cnt=0;
+
+ Node n;
+ while( (n=_work.pop()) != null ) {
+ if( n.isDead() ) continue;
+ cnt++; // Useful for debugging, searching which peephole broke things
+ Node x = n.peepholeOpt();
+ if( x != null ) {
+ if( x.isDead() ) continue;
+ // peepholeOpt can return brand-new nodes, needing an initial type set
+ if( x._type==null ) x.setType(x.compute());
+ // Changes require neighbors onto the worklist
+ if( x != n || !(x instanceof ConstantNode) ) {
+ // All outputs of n (changing node) not x (prior existing node).
+ for( Node z : n._outputs ) _work.push(z);
+ // Everybody gets a free "go again" in case they didn't get
+ // made in their final form.
+ _work.push(x);
+ // If the result is not self, revisit all inputs (because
+ // there's a new user), and replace in the graph.
+ if( x != n ) {
+ for( Node z : n. _inputs ) _work.push(z);
+ n.subsume(x);
+ }
+ }
+ // If there are distant neighbors, move to worklist
+ n.moveDepsToWorklist();
+ JSViewer.show(); // Show again
+ assert progressOnList(code); // Very expensive assert
+ }
+ if( n.isUnused() && !(n instanceof StopNode) )
+ n.kill(); // Just plain dead
+ }
+
+ }
+
+ // Visit ALL nodes and confirm the invariant:
+ // Either you are on the _work worklist OR running `iter()` makes no progress.
+
+ // This invariant ensures that no progress is missed, i.e., when the
+ // worklist is empty we have indeed done all that can be done. To help
+ // with debugging, the {@code assert} is broken out in a place where it is easy to
+ // stop if a change is found.
+
+ // Also, the normal usage of `iter()` may attempt peepholes with distance
+ // neighbors and these should fail, but will then try to add dependencies
+ // {@link #Node.addDep} which is a side effect in an assert. The {@link
+ // #midAssert} is used to stop this side effect.
+ private boolean progressOnList(CodeGen code) {
+ code._midAssert = true;
+ Node changed = code._stop.walk( n -> {
+ Node m = n;
+ if( n.compute().isa(n._type) && (!n.iskeep() || n._nid<=6) ) { // Types must be forwards, even if on worklist
+ if( _work.on(n) ) return null;
+ m = n.peepholeOpt();
+ if( m==null ) return null;
+ }
+ System.err.println("BREAK HERE FOR BUG");
+ return m;
+ });
+ code._midAssert = false;
+ return changed==null;
+ }
+
+ /**
+ * Classic WorkList, with a fast add/remove, dup removal, random pull.
+ * The Node's nid is used to check membership in the worklist.
+ */
+ @SuppressWarnings("unchecked")
+ public static class WorkList<E extends Node> {
+
+ private Node[] _es;
+ private int _len;
+ private final BitSet _on; // Bit set if Node._nid is on WorkList
+ private final Random _R; // For randomizing pull from the WorkList
+ private final long _seed;
+
+ /* Useful stat - how many nodes are processed in the post parse iterative opt */
+ private long _totalWork = 0;
+
+ public WorkList() { this(123); }
+ WorkList(long seed) {
+ _es = new Node[1];
+ _len=0;
+ _on = new BitSet();
+ _seed = seed;
+ _R = new Random();
+ _R.setSeed(_seed);
+ }
+
+ /**
+ * Pushes a Node on the WorkList, ensuring no duplicates
+ * If Node is null it will not be added.
+ */
+ public E push( E x ) {
+ if( x==null ) return null;
+ int idx = x._nid;
+ if( !_on.get(idx) ) {
+ _on.set(idx);
+ if( _len==_es.length )
+ _es = Arrays.copyOf(_es,_len<<1);
+ _es[_len++] = x;
+ _totalWork++;
+ }
+ return x;
+ }
+
+ public void addAll( Ary<E> ary ) {
+ for( E n : ary )
+ push(n);
+ }
+
+ /**
+ * True if Node is on the WorkList
+ */
+ boolean on( E x ) { return _on.get(x._nid); }
+ boolean isEmpty() { return _len==0; }
+
+ /**
+ * Removes a random Node from the WorkList; null if WorkList is empty
+ */
+ public E pop() {
+ if( _len == 0 ) return null;
+ int idx = _R.nextInt(_len);
+ E x = (E)_es[idx];
+ _es[idx] = _es[--_len]; // Compress array
+ _on.clear(x._nid);
+ return x;
+ }
+
+ public void clear() {
+ _len = 0;
+ _on.clear();
+ _R.setSeed(_seed);
+ _totalWork = 0;
+ }
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/Parser.java b/chapter21/src/main/java/com/seaofnodes/simple/Parser.java
new file mode 100644
index 00000000..219c3c44
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/Parser.java
@@ -0,0 +1,1751 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.print.GraphVisualizer;
+import com.seaofnodes.simple.type.*;
+import java.text.ParseException;
+import java.util.*;
+
+/**
+ * The Parser converts a Simple source program to the Sea of Nodes intermediate
+ * representation directly in one pass. There is no intermediate Abstract
+ * Syntax Tree structure.
+ * <p>
+ * This is a simple recursive descent parser. All lexical analysis is done here as well.
+ */
+public class Parser {
+
+ public static ConstantNode ZERO; // Very common node, cached here
+ public static ConstantNode NIL; // Very common node, cached here
+ public static XCtrlNode XCTRL; // Very common node, cached here
+
+ // Compile driver
+ public final CodeGen _code;
+
+ // The Lexer. Thin wrapper over a byte[] buffer with a cursor.
+ private final Lexer _lexer;
+
+ /**
+ * Current ScopeNode - ScopeNodes change as we parse code, but at any point of time
+ * there is one current ScopeNode. The reason the current ScopeNode can change is to do with how
+ * we handle branching. See {@link #parseIf()}.
+ * <p>
+ * Each ScopeNode contains a stack of lexical scopes, each scope is a symbol table that binds
+ * variable names to Nodes. The top of this stack represents current scope.
+ * <p>
+ * We keep a list of all ScopeNodes so that we can show them in graphs.
+ * @see #parseIf()
+ * @see #_xScopes
+ */
+ public ScopeNode _scope;
+
+ /**
+ * List of keywords disallowed as identifiers
+ */
+ public static final HashSet<String> KEYWORDS = new HashSet<>(){{
+ add("bool");
+ add("break");
+ add("byte");
+ add("continue");
+ add("else");
+ add("f32");
+ add("f64");
+ add("false");
+ add("flt");
+ add("i16");
+ add("i32");
+ add("i64");
+ add("i8");
+ add("if");
+ add("int");
+ add("new");
+ add("null");
+ add("return");
+ add("struct");
+ add("true");
+ add("u1");
+ add("u16");
+ add("u32");
+ add("u8");
+ add("while");
+ }};
+
+ /**
+ * We clone ScopeNodes when control flows branch; it is useful to have
+ * a list of all active ScopeNodes for purposes of visualization of the SoN graph
+ */
+ public final Stack<ScopeNode> _xScopes = new Stack<>();
+
+ ScopeNode _continueScope;
+ ScopeNode _breakScope; // Merge all the while-breaks here
+ FunNode _fun; // Current function being parsed
+
+ // Mapping from a type name to a Type. The string name matches
+ // `type.str()` call. No TypeMemPtrs are in here, because Simple does not
+ // have C-style '*ptr' references.
+ public static HashMap<String, Type> TYPES;
+
+ // Mapping from a type name to the constructor for a Type.
+ public final HashMap<String, StructNode> INITS;
+
+
+ public Parser(CodeGen code, TypeInteger arg) {
+ _code = code;
+ _lexer = new Lexer(code._src);
+ _scope = new ScopeNode();
+ _continueScope = _breakScope = null;
+ ZERO = con(TypeInteger.ZERO).keep();
+ NIL = con(Type.NIL).keep();
+ XCTRL= new XCtrlNode().peephole().keep();
+ TYPES = defaultTypes();
+ INITS = new HashMap<>();
+ }
+
+ @Override
+ public String toString() { return _lexer.toString(); }
+
+ public static HashMap<String, Type> defaultTypes() {
+ return new HashMap<>() {{
+ put("bool",TypeInteger.U1 );
+ put("byte",TypeInteger.U8 );
+ put("f32" ,TypeFloat .F32);
+ put("f64" ,TypeFloat .F64);
+ put("flt" ,TypeFloat .F64);
+ put("i16" ,TypeInteger.I16);
+ put("i32" ,TypeInteger.I32);
+ put("i64" ,TypeInteger.BOT);
+ put("i8" ,TypeInteger.I8 );
+ put("int" ,TypeInteger.BOT);
+ put("u1" ,TypeInteger.U1 );
+ put("u16" ,TypeInteger.U16);
+ put("u32" ,TypeInteger.U32);
+ put("u8" ,TypeInteger.U8 );
+ put("val" ,Type.TOP); // Marker type, indicates type inference
+ put("var" ,Type.BOTTOM); // Marker type, indicates type inference
+ }};
+ }
+
+ // Debugging utility to find a Node by index
+ public Node f(int nid) { return _code.f(nid); }
+
+ private Node ctrl() { return _scope.ctrl(); }
+ private <N extends Node> N ctrl(N n) { return _scope.ctrl(n); }
+
+ public void parse() { parse(false); }
+ public void parse(boolean show) {
+ _xScopes.push(_scope);
+ _scope.define(ScopeNode.CTRL, Type.CONTROL , false, null, _lexer);
+ _scope.define(ScopeNode.MEM0, TypeMem.BOT , false, null, _lexer);
+ _scope.define(ScopeNode.ARG0, TypeInteger.BOT, false, null, _lexer);
+
+ ctrl(XCTRL);
+ _scope.mem(new MemMergeNode(false));
+
+ // Parse whole program, as-if function header "{ int arg -> body }"
+ parseFunctionBody(_code._main,loc(),"arg");
+
+ // 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");
+
+ // Clean up and reset
+ _xScopes.pop();
+ _scope.kill();
+ for( StructNode init : INITS.values() )
+ init.unkeep().kill();
+ INITS.clear();
+ stop.peephole();
+ if( show ) showGraph();
+ }
+
+ /**
+ * Parses a function body, assuming the header is parsed.
+ */
+ private ReturnNode parseFunctionBody(TypeFunPtr sig, Lexer loc, String... ids) {
+ // Stack parser state on the local Java stack, and unstack it later
+ Node oldctrl = ctrl().keep();
+ Node oldmem = _scope.mem().keep();
+ FunNode oldfun = _fun;
+ ScopeNode breakScope = _breakScope; _breakScope = null;
+ ScopeNode continueScope = _continueScope; _continueScope = null;
+
+ FunNode fun = _fun = (FunNode)peep(new FunNode(loc(),sig,null,_code._start));
+ // Once the function header is available, install in linker table -
+ // allowing recursive functions. Linker matches on declared args and
+ // exact fidx, and ignores the return (because the fidx will only match
+ // the exact single function).
+ _code.link(fun);
+
+ Node rpc = new ParmNode("$rpc",0,TypeRPC.BOT,fun,con(TypeRPC.BOT)).peephole();
+
+ // Build a multi-exit return point for all function returns
+ RegionNode r = new RegionNode((Lexer)null,null,null).init();
+ assert r.inProgress();
+ PhiNode rmem = new PhiNode(ScopeNode.MEM0,TypeMem.BOT,r,null).init();
+ PhiNode rrez = new PhiNode(ScopeNode.ARG0,Type.BOTTOM,r,null).init();
+ ReturnNode ret = new ReturnNode(r, rmem, rrez, rpc, fun).init();
+ fun.setRet(ret);
+ assert ret.inProgress();
+ _code._stop.addDef(ret);
+
+ // Pre-call the function from Start, with worse-case arguments. This
+ // represents all the future, yet-to-be-parsed functions calls and
+ // external calls.
+ _scope.push(ScopeNode.Kind.Function);
+ ctrl(fun); // Scope control from function
+ // Private mem alias tracking per function
+ MemMergeNode mem = new MemMergeNode(true);
+ mem.addDef(null); // Alias#0
+ mem.addDef(new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,con(TypeMem.BOT)).peephole()); // All aliases
+ _scope.mem(mem);
+ // All args, "as-if" called externally
+ for( int i=0; i<ids.length; i++ ) {
+ Type t = sig.arg(i);
+ _scope.define(ids[i], t, false, new ParmNode(ids[i],i+2,t,fun,con(t)).peephole(), loc);
+ }
+
+ // Parse the body
+ Node last=ZERO;
+ while (!peek('}') && !_lexer.isEOF())
+ last = parseStatement();
+
+ // Last expression is the return
+ if( ctrl()._type==Type.CONTROL )
+ fun.addReturn(ctrl(), _scope.mem().merge(), last);
+
+ // Pop off the inProgress node on the multi-exit Region merge
+ assert r.inProgress();
+ r ._inputs.pop();
+ rmem._inputs.pop();
+ rrez._inputs.pop();
+ r._loc = loc(); // Final position
+ assert !r.inProgress();
+
+ // Force peeps, which have been avoided due to inProgress
+ ret.setDef(1,rmem.peephole());
+ ret.setDef(2,rrez.peephole());
+ ret.setDef(0,r.peephole());
+ ret = (ReturnNode)ret.peephole();
+
+ // Function scope ends
+ _scope.pop();
+ _fun = oldfun;
+ _breakScope = breakScope;
+ _continueScope = continueScope;
+ // Reset control and memory to pre-function parsing days
+ ctrl(oldctrl.unkeep());
+ _scope.mem(oldmem.unkeep());
+
+ return ret;
+ }
+
+ /**
+ * Parses a block
+ *
+ * <pre>
+ * '{' statements '}'
+ * </pre>
+ * Does not parse the opening or closing '{}'
+ * @return a {@link Node} or {@code null}
+ */
+ private Node parseBlock(ScopeNode.Kind kind) {
+ // Enter a new scope
+ _scope.push(kind);
+ Node last = ZERO;
+ while (!peek('}') && !_lexer.isEOF())
+ last = parseStatement();
+ // Exit scope
+ last.keep();
+ _scope.pop();
+ return last.unkeep();
+ }
+
+ /**
+ * Parses a statement
+ *
+ * <pre>
+ * returnStatement | declStatement | blockStatement | ifStatement | expressionStatement
+ * </pre>
+ * @return a {@link Node} or {@code null}
+ */
+ private Node parseStatement() {
+ if( false ) return null;
+ else if (matchx("return") ) return parseReturn();
+ else if (matchx("if") ) return parseIf();
+ else if (matchx("while") ) return parseWhile();
+ else if (matchx("for") ) return parseFor();
+ else if (matchx("break") ) return parseBreak();
+ else if (matchx("continue")) return parseContinue();
+ else if (matchx("struct") ) return parseStruct();
+ else if (matchx("#showGraph")) return require(showGraph(),";");
+ else if (matchx(";") ) return null; // Empty statement
+ // Break ambiguity around leading function types and starting a block
+ else if (peek('{') && !isTypeFun() ) {
+ match("{");
+ return require(parseBlock(ScopeNode.Kind.Block),"}");
+ }
+ // Declaration or normal assignment/expression
+ else return parseDeclarationStatement();
+ }
+
+ /**
+ * Parses a while statement
+ *
+ * <pre>
+ * while ( expression ) statement
+ * </pre>
+ * @return a {@link Node}, never {@code null}
+ */
+ private Node parseWhile() {
+ require("(");
+ return parseLooping(false);
+ }
+
+
+ /**
+ * Parses a for statement
+ *
+ * <pre>
+ * for( var x=init; test; incr ) body
+ * </pre>
+ * @return a {@link Node}, never {@code null}
+ */
+ private Node parseFor() {
+ // { var x=init,y=init,...;
+ // while( test ) {
+ // body;
+ // next;
+ // }
+ // }
+ require("(");
+ _scope.push(ScopeNode.Kind.Block); // Scope for the index variables
+ if( !match(";") ) // Can be empty init "for(;test;next) body"
+ parseDeclarationStatement(); // Non-empty init
+ Node rez = parseLooping(true);
+ _scope.pop(); // Exit index variable scope
+ return rez;
+ }
+
+ // Shared by `for` and `while`
+ private Node parseLooping( boolean doFor ) {
+
+ var savedContinueScope = _continueScope;
+ var savedBreakScope = _breakScope;
+
+ // Loop region has two control inputs, the first is the entry
+ // point, and second is back edge that is set after loop is parsed
+ // (see end_loop() call below). Note that the absence of back edge is
+ // used as an indicator to switch off peepholes of the region and
+ // associated phis; see {@code inProgress()}.
+
+ ctrl(new LoopNode(loc(),ctrl()).peephole()); // Note we set back edge to null here
+
+ // At loop head, we clone the current Scope (this includes all
+ // names in every nesting level within the Scope).
+ // We create phis eagerly for all the names we find, see dup().
+
+ // Save the current scope as the loop head
+ ScopeNode head = _scope.keep();
+ // Clone the head Scope to create a new Scope for the body.
+ // Create phis eagerly as part of cloning
+ _xScopes.push(_scope = _scope.dup(true)); // The true argument triggers creating phis
+
+ // Parse predicate
+ var pred = peek(';') ? con(1) : parseAsgn();
+ require( doFor ? ";" : ")" );
+
+ // IfNode takes current control and predicate
+ Node ifNode = new IfNode(ctrl(), pred.keep()).peephole();
+ // Setup projection nodes
+ Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep();
+ Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole();
+
+ // for( ;;next ) body
+ int nextPos = -1, nextEnd = -1;
+ if( doFor ) {
+ // Skip the next expression and parse it later
+ nextPos = pos();
+ skipAsgn();
+ nextEnd = pos();
+ require(")");
+ }
+
+ // Clone the body Scope to create the break/exit Scope which accounts for any
+ // side effects in the predicate. The break/exit Scope will be the final
+ // scope after the loop, and its control input is the False branch of
+ // the loop predicate. Note that body Scope is still our current scope.
+ ctrl(ifF);
+ _xScopes.push(_breakScope = _scope.dup());
+ _breakScope.addGuards(ifF,pred,true); // Up-cast predicate
+
+ // No continues yet
+ _continueScope = null;
+
+ // Parse the true side, which corresponds to loop body
+ // Our current scope is the body Scope
+ ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection
+ _scope.addGuards(ifT,pred.unkeep(),false); // Up-cast predicate
+ parseStatement(); // Parse loop body
+ _scope.removeGuards(ifT);
+
+ // Merge the loop bottom into other continue statements
+ if (_continueScope != null) {
+ _continueScope = jumpTo(_continueScope);
+ _scope.kill();
+ _scope = _continueScope;
+ }
+
+ // Now append the next code onto the body code
+ if( doFor ) {
+ int old = pos(nextPos);
+ if( !peek(')') )
+ parseAsgn();
+ if( pos() != nextEnd )
+ throw errorSyntax( "Unexpected code after expression" );
+ pos(old);
+ }
+
+ // The true branch loops back, so whatever is current _scope.ctrl gets
+ // added to head loop as input. endLoop() updates the head scope, and
+ // goes through all the phis that were created earlier. For each phi,
+ // it sets the second input to the corresponding input from the back
+ // edge. If the phi is redundant, it is replaced by its sole input.
+ var exit = _breakScope;
+ head.endLoop(_scope, exit);
+ head.unkeep().kill();
+
+ _xScopes.pop(); // Cleanup
+ _xScopes.pop(); // Cleanup
+
+ _continueScope = savedContinueScope;
+ _breakScope = savedBreakScope;
+
+ // At exit the false control is the current control, and
+ // the scope is the exit scope after the exit test.
+ _xScopes.pop();
+ _xScopes.push(exit);
+ _scope = exit;
+ return ZERO;
+ }
+
+ private ScopeNode jumpTo(ScopeNode toScope) {
+ ScopeNode cur = _scope.dup();
+ ctrl(XCTRL); // Kill current scope
+ // Prune nested lexical scopes that have depth > than the loop head
+ // We use _breakScope as a proxy for the loop head scope to obtain the depth
+ while( cur._lexSize.size() > _breakScope._lexSize.size() )
+ cur.pop();
+ // If this is a continue then first time the target is null
+ // So we just use the pruned current scope as the base for the
+ // "continue"
+ if( toScope == null )
+ return cur;
+ // toScope is either the break scope, or a scope that was created here
+ assert toScope._lexSize.size() <= _breakScope._lexSize.size();
+ toScope.ctrl(toScope.mergeScopes(cur,loc()).peephole());
+ return toScope;
+ }
+
+ private void checkLoopActive() { if (_breakScope == null) throw error("No active loop for a break or continue"); }
+
+ private Node parseContinue() { checkLoopActive(); _continueScope = require(jumpTo( _continueScope ),";"); return ZERO; }
+ private Node parseBreak () {
+ checkLoopActive();
+ // At the time of the break, and loop-exit conditions are only valid if
+ // they are ALSO valid at the break. It is the intersection of
+ // conditions here, not the union.
+ _breakScope.removeGuards(_breakScope.ctrl());
+ _breakScope = require(jumpTo(_breakScope ),";");
+ _breakScope.addGuards(_breakScope.ctrl(), null, false);
+ return ZERO;
+ }
+
+ // Look for an unbalanced `)`, skipping balanced
+ private void skipAsgn() {
+ int paren=0;
+ while( true )
+ // Next X char handles skipping complex comments
+ switch( _lexer.nextXChar() ) {
+ case Character.MAX_VALUE:
+ throw Utils.TODO();
+ case ')':
+ if( --paren<0 ) {
+ posT( pos() - 1 );
+ return; // Leave the `)` behind
+ }
+ break;
+ case '(': paren ++; break;
+ default: break;
+ }
+ }
+
+
+ /**
+ * Parses a statement
+ *
+ * <pre>
+ * if ( expression ) statement [else statement]
+ * </pre>
+ * @return a {@link Node}, never {@code null}
+ */
+ private Node parseIf() {
+ // Parse predicate
+ require("(");
+ var pred = require(parseAsgn(), ")");
+ return parseTrinary(pred,true,"else");
+ }
+
+ // Parse a conditional expression, merging results.
+ private Node parseTrinary( Node pred, boolean stmt, String fside ) {
+ pred.keep();
+
+ // IfNode takes current control and predicate
+ Node ifNode = new IfNode(ctrl(), pred).peephole();
+ // Setup projection nodes
+ Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep();
+ Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole().keep();
+ // In if true branch, the ifT proj node becomes the ctrl
+ // But first clone the scope and set it as current
+ ScopeNode fScope = _scope.dup(); // Duplicate current scope
+ _xScopes.push(fScope); // For graph visualization we need all scopes
+
+ // Parse the true side
+ ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection
+ _scope.addGuards(ifT,pred,false); // Up-cast predicate
+ Node lhs = (stmt ? parseStatement() : parseAsgn()).keep(); // Parse true-side
+ _scope.removeGuards(ifT);
+
+ // See if a one-sided def was made: "if(pred) int x = 1;" and throw.
+ // See if any forward-refs were made, and copy them to the other side:
+ // "pred ? n*fact(n-1) : 1"
+ fScope.balanceIf(_scope);
+
+ ScopeNode tScope = _scope;
+
+ // Parse the false side
+ _scope = fScope; // Restore scope, then parse else block if any
+ ctrl(ifF.unkeep()); // Ctrl token is now set to ifFalse projection
+ // Up-cast predicate, even if not else clause, because predicate can
+ // remain true if the true clause exits: `if( !ptr ) return 0; return ptr.fld;`
+ _scope.addGuards(ifF,pred,true);
+ boolean doRHS = match(fside);
+ Node rhs = (doRHS
+ ? (stmt ? parseStatement() : parseAsgn())
+ : con(lhs._type.makeZero())).keep();
+ _scope.removeGuards(ifF);
+ if( doRHS )
+ fScope = _scope;
+ pred.unkeep();
+
+ // Check the trinary widening int/flt
+ if( !stmt ) {
+ rhs = widenInt( rhs.unkeep(), lhs._type ).keep();
+ lhs = widenInt( lhs.unkeep(), rhs._type ).keep();
+ }
+
+ _scope = tScope;
+ _xScopes.pop(); // Discard pushed from graph display
+ // See if a one-sided def was made: "if(pred) int x = 1;" and throw.
+ // See if any forward-refs were made, and copy them to the other side:
+ // "pred ? n*fact(n-1) : 1"
+ tScope.balanceIf(fScope);
+
+ // Merge results
+ RegionNode r = ctrl(tScope.mergeScopes(fScope,loc()));
+ Node ret = peep(new PhiNode("",lhs._type.meet(rhs._type),r,lhs.unkeep(),rhs.unkeep()));
+ // Immediately fail e.g. `arg ? 7 : ptr`
+ ParseException err;
+ if( !stmt && (err=ret.err()) !=null ) throw err;
+ r.peephole();
+ return ret;
+ }
+
+ /**
+ * Parses a return statement; "return" already parsed.
+ * The $ctrl edge is killed.
+ *
+ * <pre>
+ * 'return' expr ;
+ * </pre>
+ * @return an expression {@link Node}, never {@code null}
+ */
+ private Node parseReturn() {
+ var expr = require(parseAsgn(), ";");
+ // Need default memory, since it can be lazy, need to force
+ // a non-lazy Phi
+ _fun.addReturn(ctrl(), _scope.mem().merge(), expr);
+ ctrl(XCTRL); // Kill control
+ return expr;
+ }
+
+ /**
+ * Dumps out the node graph
+ * @return {@code null}
+ */
+ Node showGraph() {
+ System.out.println(new GraphVisualizer().generateDotOutput(_code._stop,_scope,_xScopes));
+ return null;
+ }
+
+ /** Parse: [name '='] expr
+ */
+ private Node parseAsgn() {
+ int old = pos();
+ String name = _lexer.matchId();
+ // Just a plain expression, no assignment.
+ // Distinguish `var==expr` from `var=expr`
+ if( name==null || KEYWORDS.contains(name) || !matchOpx('=','=') )
+ { pos(old); return parseExpression(); }
+
+ // Parse assignment expression
+ Node expr = parseAsgn();
+
+ // Final variable to update
+ Var def = _scope.lookup(name);
+ if( def==null )
+ throw error("Undefined name '" + name + "'");
+
+ // TOP fields are for late-initialized fields; these have never
+ // been written to, and this must be the final write. Other writes
+ // outside the constructor need to check the final bit.
+ if( _scope.in(def._idx)._type!=Type.TOP && def._final &&
+ // Inside a constructor, final assign is OK, outside nope
+ !(_scope.inCon() && def._idx >= _scope._lexSize.last()) )
+ throw error("Cannot reassign final '"+name+"'");
+
+ // Lift expression, based on type
+ Node lift = liftExpr(expr.keep(), def.type(), def._final);
+ // Update
+ _scope.update(name,lift);
+ // Return un-lifted expr
+ return expr.unkeep();
+ }
+
+ // Make finals deep; widen ints to floats; narrow wide int types.
+ // Early error if types do not match variable.
+ private Node liftExpr( Node expr, Type t, boolean xfinal ) {
+ assert !(expr._type instanceof TypeMemPtr tmp) || !tmp.isFRef();
+ // Final is deep on ptrs
+ if( xfinal && t instanceof TypeMemPtr tmp ) {
+ t = tmp.makeRO();
+ expr = peep(new ReadOnlyNode(expr));
+ }
+ // Auto-widen int to float
+ expr = widenInt( expr, t );
+ // Auto-narrow wide ints to narrow ints
+ expr = zsMask(expr,t);
+ // Type is sane
+ if( expr._type!=Type.BOTTOM && !expr._type.shallowISA(t) )
+ throw error("Type " + expr._type.str() + " is not of declared type " + t.str());
+ return expr;
+ }
+
+ private Node widenInt( Node expr, Type t ) {
+ return (expr._type instanceof TypeInteger || expr._type==Type.NIL) && t instanceof TypeFloat
+ ? peep(new ToFloatNode(expr)) : expr;
+ }
+
+ /**
+ * Parse declaration or expression statement
+ * declStmt = type var['=' exprAsgn][, var['=' exprAsgn]]* ';' | exprAsgn ';'
+ * <p>
+ * exprAsgn = var '=' exprAsgn | expr
+ */
+ private Node parseDeclarationStatement() {
+ Type t = type();
+ if( t == null )
+ return require(parseAsgn(),";");
+
+ // now parse var['=' asgnexpr] in a loop
+ Node n = parseDeclaration(t);
+ while( match(",") )
+ n = parseDeclaration(t);
+ return require(n,";");
+ }
+
+ /** Parse final: [!]var['=' asgn]
+ */
+ private Node parseDeclaration(Type t) {
+ assert t!=null;
+ // Has var/val instead of a user-declared type
+ boolean inferType = t==Type.TOP || t==Type.BOTTOM;
+ boolean hasBang = match("!");
+ Lexer loc = loc();
+ String name = requireId();
+ // Optional initializing expression follows
+ boolean xfinal = false;
+ Node expr;
+ if( match("=") ) {
+ expr = parseAsgn();
+ // TOP means val and val is always final
+ xfinal = (t==Type.TOP) ||
+ // BOTTOM is var and var is always not-final
+ (t!=Type.BOTTOM &&
+ // no Bang AND
+ !hasBang &&
+ // not-null (expecting null to be set to not-null)
+ expr._type != Type.NIL &&
+ // Pointers are final by default; int/flt are not-final by default.
+ (t instanceof TypeNil));
+ // var/val, then type comes from expression
+ if( inferType ) {
+ if( expr._type==Type.NIL )
+ throw error("a not-null/non-zero expression");
+ t = expr._type;
+ if( !xfinal ) t = t.glb(); // Widen if not final
+ }
+ if( t instanceof TypeFunPtr && expr._type instanceof TypeFunPtr tfp && tfp.isConstant() )
+ _code.link(tfp)._name = name;
+
+ } else {
+ // Need an expression to infer the type.
+ // Also, if not-null then need an initializing expression.
+ // Allowed in a class def, because type/init will happen in the constructor.
+ if( (inferType || (t instanceof TypeNil tn && !tn.nullable() )) && !_scope.inCon() )
+ throw errorSyntax("=expression");
+ // Initial value for uninitialized struct fields.
+ expr = switch( t ) {
+ // Nullable pointers get a NIL; not-null get a TOP which
+ // signals that they *must* be initialized in the constructor.
+ case TypeNil tn -> tn.nullable() ? NIL : con(Type.TOP);
+ case TypeInteger ti -> ZERO;
+ case TypeFloat tf -> con(TypeFloat.FZERO);
+ // Bottom signals type inference: they must be initialized in
+ // the constructor and that's when we'll discover the type.
+ case Type tt -> { assert tt==Type.BOTTOM; yield con(tt); }
+ };
+ }
+
+ // Lift expression, based on type
+ Node lift = liftExpr(expr, t, xfinal);
+
+ if( xfinal && t instanceof TypeMemPtr tmp )
+ t = tmp.makeRO();
+
+ // Lift type to the declaration. This will report as an error later if
+ // we cannot lift the type.
+ if( !lift._type.isa(t) )
+ lift = peep(new CastNode(t,null,lift));
+ // Define a new name
+ if( !_scope.define(name,t,xfinal,lift, loc) )
+ throw error("Redefining name '" + name + "'", loc);
+ return lift;
+ }
+
+
+
+ /**
+ * Parse a struct declaration, and return the following statement.
+ * Only allowed in top level scope.
+ * Structs cannot be redefined.
+ *
+ * @return The statement following the struct
+ */
+ private Node parseStruct() {
+ if (_xScopes.size() > 1) throw error("struct declarations can only appear in top level scope");
+ String typeName = requireId();
+ Type t = TYPES.get(typeName);
+ if( t!=null && !(t instanceof TypeMemPtr tmp && tmp.isFRef() ) )
+ throw error("struct '" + typeName + "' cannot be redefined");
+
+ // A Block scope parse, and inspect the scope afterward for fields.
+ _scope.push(ScopeNode.Kind.Constructor);
+ require("{");
+ while (!peek('}') && !_lexer.isEOF())
+ parseStatement();
+
+ // Grab the declarations and build fields and a Struct
+ int lexlen = _scope._lexSize.last();
+ int varlen = _scope._vars._len;
+ StructNode s = new StructNode();
+ Ary<Field> fs = new Ary<>(Field.class);
+ for( int i=lexlen; i<varlen; i++ ) {
+ s.addDef(_scope.in(i));
+ Var v = _scope._vars.at(i);
+ if( !v.isFRef() ) // Promote to outer scope, not defined here
+ fs.push(Field.make(v._name,v.type(),_code.getALIAS(),v._final));
+ }
+ TypeStruct ts = s._ts = TypeStruct.make(typeName, fs.asAry());
+ TYPES.put(typeName, TypeMemPtr.make(ts));
+ INITS.put(typeName,s.peephole().keep());
+ // Done with struct/block scope
+ require("}");
+ require(";");
+ _scope.pop();
+ return ZERO;
+ }
+
+
+ // Parse and return a type or null. Valid types always are followed by an
+ // 'id' which the caller must parse. This lets us distinguish forward ref
+ // types (which ARE valid here) from local vars in an (optional) forward
+ // ref type position.
+
+ // t = int|i8|i16|i32|i64|u8|u16|u32|u64|byte|bool | flt|f32|f64 | val | var | struct[?]
+ private Type type() {
+ int old1 = pos();
+ // Only type with a leading `{` is a function pointer...
+ if( peek('{') ) return typeFunPtr();
+
+ // Otherwise you get a type name
+ String tname = _lexer.matchId();
+ if( tname==null ) return null;
+
+ // Convert the type name to a type.
+ Type t0 = TYPES.get(tname);
+ // No new types as keywords
+ if( t0 == null && KEYWORDS.contains(tname) )
+ return posT(old1);
+ if( t0 == Type.BOTTOM || t0 == Type.TOP ) return t0; // var/val type inference
+ Type t1 = t0 == null ? TypeMemPtr.make(TypeStruct.makeFRef(tname)) :t0; // Null: assume a forward ref type
+ // Nest arrays and '?' as needed
+ Type t2 = t1;
+ while( true ) {
+ if( match("?") ) {
+ if( !(t2 instanceof TypeMemPtr tmp) )
+ throw error("Type "+t0+" cannot be null");
+ if( tmp.nullable() ) throw error("Type "+t2+" already allows null");
+ t2 = tmp.makeNullable();
+ } else if( match("[]") ) {
+ t2 = typeAry(t2);
+ } else
+ break;
+ }
+
+ // Check no forward ref
+ if( t0 != null ) return t2;
+ // Check valid forward ref, after parsing all the type extra bits.
+ // Cannot check earlier, because cannot find required 'id' until after "[]?" syntax
+ int old2 = pos();
+ match("!");
+ String id = _lexer.matchId();
+ if( !(peek(',') || peek(';') || match("->")) )
+ return posT(old1);
+ pos(old2); // Reset lexer to reparse
+ if( id==null )
+ return posT(old1); // Reset lexer to reparse
+ // Yes a forward ref, so declare it
+ TYPES.put(tname,t1);
+ return t2;
+ }
+
+ // Make an array type of t
+ private TypeMemPtr typeAry( Type t ) {
+ if( t instanceof TypeMemPtr tmp && tmp.notNull() )
+ throw error("Arrays of reference types must always be nullable");
+ String tname = "["+t.str()+"]";
+ Type ta = TYPES.get(tname);
+ if( ta != null ) return (TypeMemPtr)ta;
+ // Need make an array type.
+ TypeStruct ts = TypeStruct.makeAry(TypeInteger.U32,_code.getALIAS(),t,_code.getALIAS());
+ assert ts.str().equals(tname);
+ TypeMemPtr tary = TypeMemPtr.make(ts);
+ TYPES.put(tname,tary);
+ return tary;
+ }
+
+ // A function type is `{ type... -> type }` or `{ type }`.
+ private Type typeFunPtr() {
+ int old = pos(); // Record lexer position
+ match("{"); // Skip already-peeked '{'
+ Type t0 = type(); // Either return or first arg
+ if( t0==null ) return posT(old); // Not a function
+ if( match("}") ) // No-arg function { -> type }
+ return TypeFunPtr.make(match("?"),TypeTuple.BOT,t0);
+ Ary<Type> ts = new Ary<>(Type.class);
+ ts.push(t0); // First argument
+ while( true ) {
+ if( match("->") ) { // End of arguments, parse return
+ Type ret = type();
+ if( ret==null || !match("}") )
+ return posT(old); // Not a function
+ return TypeFunPtr.make(match("?"),TypeTuple.make(ts.asAry()),ret);
+ }
+ Type t1 = type();
+ if( t1==null ) return posT(old); // Not a function
+ ts.push(t1);
+ }
+ }
+
+ // True if a TypeFunPtr, without advancing parser
+ private boolean isTypeFun() {
+ int old = pos();
+ if( typeFunPtr()==null ) return false;
+ pos(old);
+ return true;
+ }
+
+ /**
+ * Parse an expression of the form:
+ *
+ * <pre>
+ * expr : bitwise [? expr [: expr]]
+ * </pre>
+ * @return an expression {@link Node}, never {@code null}
+ */
+ private Node parseExpression() {
+ Node expr = parseBitwise();
+ return match("?") ? parseTrinary(expr,false,":") : expr;
+ }
+
+ /**
+ * Parse an bitwise expression
+ *
+ * <pre>
+ * bitwise : compareExpr (('&' | '|' | '^') compareExpr)*
+ * </pre>
+ * @return a bitwise expression {@link Node}, never {@code null}
+ */
+ private Node parseBitwise() {
+ Node lhs = parseComparison();
+ while( true ) {
+ if( false ) ;
+ else if( match("&") ) lhs = new AndNode(loc(),lhs,null);
+ else if( match("|") ) lhs = new OrNode(loc(),lhs,null);
+ else if( match("^") ) lhs = new XorNode(loc(),lhs,null);
+ else break;
+ lhs.setDef(2,parseComparison());
+ lhs = peep(lhs);
+ }
+ return lhs;
+ }
+
+ /**
+ * Parse an expression of the form:
+ *
+ * <pre>
+ * expr : additiveExpr op additiveExpr
+ * </pre>
+ * @return an comparator expression {@link Node}, never {@code null}
+ */
+ private Node parseComparison() {
+ var lhs = parseShift();
+ while( true ) {
+ int idx=0; boolean negate=false;
+ // Test for any local nodes made, and "keep" lhs during peepholes
+ if( false ) ;
+ else if( match("==") ) { idx=2; lhs = new BoolNode.EQ(lhs, null); }
+ else if( match("!=") ) { idx=2; lhs = new BoolNode.EQ(lhs, null); negate=true; }
+ else if( match("<=") ) { idx=2; lhs = new BoolNode.LE(lhs, null); }
+ else if( match("<" ) ) { idx=2; lhs = new BoolNode.LT(lhs, null); }
+ else if( match(">=") ) { idx=1; lhs = new BoolNode.LE(null, lhs); }
+ else if( match(">" ) ) { idx=1; lhs = new BoolNode.LT(null, lhs); }
+ else break;
+ // Peepholes can fire, but lhs is already "hooked", kept alive
+ lhs.setDef(idx,parseShift());
+ lhs = peep(lhs.widen());
+ if( negate ) // Extra negate for !=
+ lhs = peep(new NotNode(lhs));
+ }
+ return lhs;
+ }
+
+ /**
+ * Parse an additive expression
+ *
+ * <pre>
+ * shiftExpr : additiveExpr (('<<' | '>>' | '>>>') additiveExpr)*
+ * </pre>
+ * @return a shift expression {@link Node}, never {@code null}
+ */
+ private Node parseShift() {
+ Node lhs = parseAddition();
+ while( true ) {
+ if( false ) ;
+ else if( match("<<") ) lhs = new ShlNode(loc(),lhs,null);
+ else if( match(">>>")) lhs = new ShrNode(loc(),lhs,null);
+ else if( match(">>") ) lhs = new SarNode(loc(),lhs,null);
+ else break;
+ lhs.setDef(2,parseAddition());
+ ParseException err;
+ if( (err=lhs.err()) != null )
+ throw err;
+ lhs = peep(lhs.widen());
+ }
+ return lhs;
+ }
+
+ /**
+ * Parse an additive expression
+ *
+ * <pre>
+ * additiveExpr : multiplicativeExpr (('+' | '-') multiplicativeExpr)*
+ * </pre>
+ * @return an add expression {@link Node}, never {@code null}
+ */
+ private Node parseAddition() {
+ Node lhs = parseMultiplication();
+ while( true ) {
+ if( false ) ;
+ else if( match("+") ) lhs = new AddNode(lhs,null);
+ else if( match("-") ) lhs = new SubNode(lhs,null);
+ else break;
+ lhs.setDef(2,parseMultiplication());
+ lhs = peep(lhs.widen());
+ }
+ return lhs;
+ }
+
+ /**
+ * Parse an multiplicativeExpr expression
+ *
+ * <pre>
+ * multiplicativeExpr : unaryExpr (('*' | '/') unaryExpr)*
+ * </pre>
+ * @return a multiply expression {@link Node}, never {@code null}
+ */
+ private Node parseMultiplication() {
+ var lhs = parseUnary();
+ while( true ) {
+ if( false ) ;
+ else if( match("*") ) lhs = new MulNode(lhs,null);
+ else if( match("/") ) lhs = new DivNode(lhs,null);
+ else break;
+ lhs.setDef(2,parseUnary());
+ lhs = peep(lhs.widen());
+ }
+ return lhs;
+ }
+
+ /**
+ * Parse a unary minus expression.
+ *
+ * <pre>
+ * unaryExpr : ('-') unaryExpr | '!') unaryExpr | postfixExpr | primaryExpr | '--' Id | '++' Id
+ * </pre>
+ * @return a unary expression {@link Node}, never {@code null}
+ */
+ private Node parseUnary() {
+ // Pre-dec/pre-inc
+ int old = pos();
+ if( match("--") || match("++") ) {
+ int delta = _lexer.peek(-1)=='+' ? 1 : -1; // Pre vs post
+ String name = _lexer.matchId();
+ if( name!=null ) {
+ Var n = _scope.lookup(name);
+ if( n != null && !(n.type() instanceof TypeMemPtr) ) {
+ if( n._final )
+ throw error("Cannot reassign final '"+n._name+"'");
+ Node expr = n.type() instanceof TypeFloat
+ ? peep(new AddFNode(_scope.in(n),con(TypeFloat.constant(delta))))
+ : zsMask(peep(new AddNode(_scope.in(n),con(delta))),n.type());
+ _scope.update(n,expr);
+ return expr;
+ }
+ }
+ // Reset, try again
+ pos(old);
+ }
+ if (match("-")) return peep(new MinusNode(parseUnary()).widen());
+ if (match("!")) return peep(new NotNode(parseUnary()));
+ return parsePostfix(parsePrimary());
+ }
+
+ /**
+ * Parse a primary expression:
+ *
+ * <pre>
+ * primaryExpr : integerLiteral | true | false | null | new Type | '(' expression ')' | Id['++','--']
+ * </pre>
+ * @return a primary {@link Node}, never {@code null}
+ */
+ private Node parsePrimary() {
+ if( _lexer.isNumber(_lexer.peek()) ) return parseLiteral();
+ if( matchx("true" ) ) return con(1);
+ if( matchx("false") ) return ZERO;
+ if( matchx("null" ) ) return NIL;
+ if( match ("(" ) ) return require(parseAsgn(), ")");
+ if( matchx("new" ) ) return alloc();
+ if( match ("{" ) ) return require(func(),"}");
+ // Expect an identifier now
+ Var n = requireLookupId();
+ Node rvalue = _scope.in(n);
+ if( rvalue._type == Type.BOTTOM )
+ if( rvalue instanceof FRefNode ) return rvalue;
+ else throw error("Cannot read uninitialized field '"+n._name+"'");
+ // Check for assign-update, x += e0;
+ char ch = _lexer.matchOperAssign();
+ if( ch==0 ) return rvalue;
+ Node op = opAssign(ch,rvalue,n._final,n.type(),n._name);
+ _scope.update(n,op);
+ return postfix(ch) ? rvalue.unkeep() : op;
+ }
+
+ // Check for assign-update, "x += e0".
+ // Returns "x + e0"; the caller assigns value.
+ // if ch is postfix, then lhs is kept and caller will unkeep and return it.
+ private Node opAssign(char ch, Node lhs, boolean xfinal, Type t, String name ) {
+ if( xfinal )
+ throw error("Cannot reassign final '"+name+"'");
+ // RHS of the update.
+ lhs.keep(); // Alive across parseAsgn
+ Node rhs =
+ (byte)ch == 1 ? con( 1) : // var++
+ (byte)ch == -1 ? con(-1) : // var--
+ parseAsgn(); // var op= rhs
+ if( !postfix(ch) ) lhs.unkeep(); // Allow to die in next peep
+ // 4 cases:
+ // int + int ==>> narrow int
+ // int + flt ==>> error, caller must fail assigning flt into int
+ // flt + int ==>> use float op, wrap toFloat()
+ // flt + flt ==>> use float op
+ Node op = switch(ch) {
+ case 1, (char)-1,
+ '+' -> new AddNode(lhs,rhs);
+ case '-' -> new SubNode(lhs,rhs);
+ case '*' -> new MulNode(lhs,rhs);
+ case '/' -> new DivNode(lhs,rhs);
+ default -> throw Utils.TODO();
+ };
+ // Convert to float ops, or narrow int types; error if not declared type.
+ // Also, if postfix LHS is still keep()
+ return liftExpr(peep(op.widen()),t,false);
+ }
+
+
+ // Expect an ID here. If not found, assume a forward reference function
+ Var requireLookupId() {
+ String id = _lexer.matchId();
+ if( id == null || KEYWORDS.contains(id) )
+ throw errorSyntax("an identifier or expression");
+ Var n = _scope.lookup(id);
+ // If missing, assume a forward reference
+ if( n==null ) {
+ _scope.define(id, FRefNode.FREF_TYPE, true, XCTRL, loc());
+ n = _scope.lookup(id);
+ } else {
+ // Lookup worked on an out-of-function value.
+ if( _scope.outOfFunction(n) ) {
+ // No closures, so this has to be a final constant (which
+ // includes forward refs)
+ Node def = _scope.in(n._idx);
+ if( !(def instanceof FRefNode) && !(n._final && def._type.isConstant()) )
+ throw error("Variable '"+n._name+"' is out of function scope and must be a final constant");
+ }
+ }
+ return n;
+ }
+
+ /**
+ Parse an allocation
+ */
+ private Node alloc() {
+ Type t = type();
+ if( t==null ) throw error("Expected a type");
+ // Parse ary[ length_expr ]
+ if( match("[") ) {
+ if( !t.makeZero().isa(t) )
+ throw error("Cannot allocate a non-nullable, since arrays always zero/null fill");
+ Node len = parseAsgn();
+ if( !(len._type instanceof TypeInteger) )
+ throw error("Cannot allocate an array with length "+len._type);
+ require("]");
+ TypeMemPtr tmp = typeAry(t);
+ return newArray(tmp._obj,len);
+ }
+
+ if( !(t instanceof TypeMemPtr tmp) )
+ throw error("Cannot allocate a "+t.str());
+
+ // Parse new struct { default_initialization }
+ StructNode s = INITS.get(tmp._obj._name);
+ if( s==null ) throw error("Unknown struct type '" + tmp._obj._name + "'");
+
+ Field[] fs = s._ts._fields;
+ // if the object is fully initialized, we can skip a block here.
+ // Check for constructor block:
+ boolean hasConstructor = match("{");
+ Ary<Node> init=s._inputs; int idx=0;
+ if( hasConstructor ) {
+ idx = _scope.nIns();
+ // Push a scope, and pre-assign all struct fields.
+ _scope.push(ScopeNode.Kind.Block);
+ Lexer loc = loc();
+ for( int i=0; i<fs.length; i++ )
+ _scope.define(fs[i]._fname, fs[i]._type, fs[i]._final, s.in(i)._type==Type.TOP ? con(Type.BOTTOM) : s.in(i), loc);
+ // Parse the constructor body
+ require(parseBlock(ScopeNode.Kind.Constructor),"}");
+ init = _scope._inputs;
+ }
+ // Check that all fields are initialized
+ for( int i=idx; i<init.size(); i++ )
+ if( init.at(i)._type == Type.TOP || init.at(i)._type == Type.BOTTOM )
+ throw error("'"+tmp._obj._name+"' is not fully initialized, field '" + fs[i-idx]._fname + "' needs to be set in a constructor");
+ Node ptr = newStruct(tmp._obj, con(tmp._obj.offset(fs.length)), idx, init );
+ if( hasConstructor )
+ _scope.pop();
+ return ptr;
+ }
+
+
+ /**
+ * Return a NewNode initialized memory.
+ * @param obj is the declared type, with GLB fields
+ * @param init is a collection of initialized fields
+ */
+ private Node newStruct( TypeStruct obj, Node size, int idx, Ary<Node> init ) {
+ Field[] fs = obj._fields;
+ if( fs==null )
+ throw error("Unknown struct type '" + obj._name + "'");
+ int len = fs.length;
+ Node[] ns = new Node[2+len];
+ ns[0] = ctrl(); // Control in slot 0
+ // Total allocated length in bytes
+ ns[1] = size;
+ // Memory aliases for every field
+ for( int i = 0; i < len; i++ )
+ ns[2+i] = memAlias(fs[i]._alias);
+ Node nnn = new NewNode(TypeMemPtr.make(obj), ns).peephole().keep();
+ for( int i = 0; i < len; i++ )
+ memAlias(fs[i]._alias, new ProjNode(nnn,i+2,memName(fs[i]._alias)).peephole());
+ Node ptr = new ProjNode(nnn.unkeep(),1,obj._name).peephole().keep();
+
+ // Initial nonzero values for every field
+ for( int i = 0; i < len; i++ ) {
+ Node val = init.get( i + idx );
+ if( val._type != val._type.makeZero() ) {
+ Node mem = memAlias(fs[i]._alias);
+ Node st = new StoreNode(loc(),fs[i]._fname,fs[i]._alias,fs[i]._type,mem,ptr,con(obj.offset(i)),val,true).peephole();
+ memAlias(fs[i]._alias,st);
+ }
+ }
+
+ return ptr.unkeep();
+ }
+
+ private static final Ary<Node> ALTMP = new Ary<>(Node.class);
+ private Node newArray(TypeStruct ary, Node len) {
+ int base = ary.aryBase ();
+ int scale= ary.aryScale();
+ Node size = peep(new AddNode(con(base),peep(new ShlNode(null,len.keep(),con(scale)))));
+ ALTMP.clear(); ALTMP.add(len.unkeep()); ALTMP.add(con(ary._fields[1]._type.makeZero()));
+ return newStruct(ary,size,0,ALTMP);
+ }
+
+ // We set up memory aliases by inserting special vars in the scope these
+ // variables are prefixed by $ so they cannot be referenced in Simple code.
+ // Using vars has the benefit that all the existing machinery of scoping
+ // and phis work as expected
+ private Node memAlias(int alias ) { return _scope.mem(alias ); }
+ private void memAlias(int alias, Node st) { _scope.mem(alias, st); }
+ public static String memName(int alias) { return ("$"+alias).intern(); }
+
+ /**
+ * Parse postfix expression; this can be a field expression, an array
+ * lookup or a postfix operator like '#'
+ *
+ * <pre>
+ * expr ('.' FIELD)* [ = expr ] // Field reference
+ * expr '#' // Postfix unary read operator
+ * expr ['++' | '--' ] // Postfix unary write operator
+ * expr ('[' expr ']')* = [ = expr ] // Array reference
+ * expr '(' [args,]* ')' // Function call
+ * </pre>
+ */
+ private Node parsePostfix(Node expr) {
+ String name;
+ if( match(".") ) name = requireId();
+ else if( match("#") ) name = "#";
+ else if( match("[") ) name = "[]";
+ else if( match("(") ) return parsePostfix(require(functionCall(expr),")"));
+ else return expr; // No postfix
+
+ if( expr._type==Type.NIL )
+ throw error("Accessing unknown field '" + name + "' from 'null'");
+
+ // Sanity check expr for being a reference
+ if( !(expr._type instanceof TypeMemPtr ptr) ) {
+ throw error( "Expected "+(name=="#" || name=="[]" ? "array" : "reference")+" but found " + expr._type.str() );
+ }
+
+ // Sanity check field name for existing
+ TypeMemPtr tmp = (TypeMemPtr)TYPES.get(ptr._obj._name);
+ if( tmp == null ) throw error("Accessing unknown field '" + name + "' from '" + ptr + "'");
+ TypeStruct base = tmp._obj;
+ int fidx = base.find(name);
+ if( fidx == -1 ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'");
+
+ // Get field type and layout offset from base type and field index fidx
+ Field f = base._fields[fidx]; // Field from field index
+ Type tf = f._type;
+ if( tf instanceof TypeMemPtr ftmp && ftmp.isFRef() )
+ tf = ftmp.makeFrom(((TypeMemPtr)(TYPES.get(ftmp._obj._name)))._obj);
+
+ // Field offset; fixed for structs, computed for arrays
+ Node off = (name.equals("[]") // If field is an array body
+ // Array index math
+ ? peep(new AddNode(con(base.aryBase()),peep(new ShlNode(null,require(parseAsgn(),"]"),con(base.aryScale())))))
+ // Struct field offsets are hardwired
+ : con(base.offset(fidx))).keep();
+
+ // Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment
+ if( matchOpx('=','=') ) {
+ Node val = parseAsgn().keep();
+ Node lift = liftExpr( val, tf, f._final );
+
+ Node st = new StoreNode(loc(), name, f._alias, tf, memAlias(f._alias), expr, off.unkeep(), lift, false);
+ // Arrays include control, as a proxy for a safety range check.
+ // Structs don't need this; they only need a NPE check which is
+ // done via the type system.
+ if( base.isAry() ) st.setDef(0,ctrl());
+ memAlias(f._alias, st.peephole());
+ return val.unkeep(); // "obj.a = expr" returns the expression while updating memory
+ }
+
+ Node load = new LoadNode(loc(),name, f._alias, tf, memAlias(f._alias), expr.keep(), off);
+ // Arrays include control, as a proxy for a safety range check
+ // Structs don't need this; they only need a NPE check which is
+ // done via the type system.
+ if( base.isAry() && !name.equals("#") ) load.setDef(0,ctrl());
+ load = peep(load);
+
+ // Check for assign-update, "ptr.fld += expr" or "ary[idx]++"
+ char ch = _lexer.matchOperAssign();
+ if( ch!=0 ) {
+ Node op = opAssign(ch,load,f._final,tf,name);
+ Node st = new StoreNode(loc(), name, f._alias, tf, memAlias(f._alias), expr.unkeep(), off, op, false);
+ // Arrays include control, as a proxy for a safety range check.
+ // Structs don't need this; they only need a NPE check which is
+ // done via the type system.
+ if( base.isAry() ) st.setDef(0,ctrl());
+ memAlias(f._alias, peep(st));
+ load = postfix(ch) ? load.unkeep() : op;
+ // And use the original loaded value as the result
+ } else expr.unkill();
+ off.unkill();
+
+ return parsePostfix(load);
+ }
+
+
+ // zero/sign extend. "i" is limited to either classic unsigned (min==0) or
+ // classic signed (min=minus-power-of-2); max=power-of-2-minus-1.
+ private Node zsMask(Node val, Type t ) {
+ if( !(val._type instanceof TypeInteger tval && t instanceof TypeInteger t0 && !tval.isa(t0)) ) {
+ if( !(val._type instanceof TypeFloat tval && t instanceof TypeFloat t0 && !tval.isa(t0)) )
+ return val;
+ // Float rounding
+ return peep(new RoundF32Node(val));
+ }
+ if( t0._min==0 ) // Unsigned
+ return peep(new AndNode(null,val,con(t0._max)));
+ // Signed extension
+ int shift = Long.numberOfLeadingZeros(t0._max)-1;
+ Node shf = con(shift);
+ if( shf._type==TypeInteger.ZERO )
+ return val;
+ return peep(new SarNode(null,peep(new ShlNode(null,val,shf.keep())),shf.unkeep()));
+ }
+
+ /**
+ * Parse a function body; the caller will parse the surrounding "{}"
+ *
+ * <pre>
+ * { [type arg,]* -> expr }
+ * { expr } // The no-argument function
+ * </pre>
+ */
+ private Node func() {
+ Ary<Type> ts = new Ary<>(Type.class);
+ Ary<String> ids = new Ary<>(String.class);
+ _lexer.skipWhiteSpace();
+ Lexer loc = loc(); // First argument location
+ while( true ) {
+ Type t = type(); // Arg type
+ if( t==null ) break;
+ String id = requireId();
+ ts .push(t ); // Push type/arg pairs
+ ids.push(id);
+ match(",");
+ }
+ require("->");
+ // Make a concrete function type, with a fidx
+ TypeFunPtr tfp = _code.makeFun(TypeTuple.make(ts.asAry()),Type.BOTTOM);
+ ReturnNode ret = parseFunctionBody(tfp,loc,ids.asAry());
+ return con(ret._fun.sig());
+ }
+
+ /**
+ * Parse function call arguments; caller will parse the surrounding "()"
+ * <pre>
+ * ( arg* )
+ * </pre>
+ */
+ private Node functionCall(Node expr) {
+ if( expr._type == Type.NIL )
+ throw error("Calling a null function pointer");
+ if( !(expr instanceof FRefNode) && !expr._type.isa(TypeFunPtr.BOT) )
+ throw error("Expected a function but got "+expr._type.glb().str());
+ expr.keep(); // Keep while parsing args
+
+ Ary<Node> args = new Ary<Node>(Node.class);
+ args.push(null); // Space for ctrl,mem
+ args.push(null);
+ while( !peek(')') ) {
+ Node arg = parseAsgn();
+ if( arg==null ) break;
+ args.push(arg.keep());
+ if( !match(",") ) break;
+ }
+ // Control & memory after parsing args
+ args.set(0,ctrl().keep());
+ args.set(1,_scope.mem().merge().keep());
+ args.push(expr); // Function pointer
+ // Unkeep them all
+ for( Node arg : args )
+ arg.unkeep();
+ // Dead into the call? Skip all the node gen
+ if( ctrl()._type == Type.XCONTROL ) {
+ for( Node arg : args )
+ if( arg.isUnused() )
+ arg.kill();
+ return con(Type.TOP);
+ }
+
+ // Into the call
+ CallNode call = (CallNode)new CallNode(loc(), args.asAry()).peephole();
+
+ // Post-call setup
+ CallEndNode cend = (CallEndNode)new CallEndNode(call).peephole();
+ call.peephole(); // Rerun peeps after CallEnd, allows early inlining
+ // Control from CallEnd
+ ctrl(new CProjNode(cend,0,ScopeNode.CTRL).peephole());
+ // Memory from CallEnd
+ MemMergeNode mem = new MemMergeNode(true);
+ mem.addDef(null); // Alias#0
+ mem.addDef(new ProjNode(cend,1,ScopeNode.MEM0).peephole());
+ _scope.mem(mem);
+ // Call result
+ return new ProjNode(cend,2,"#2").peephole();
+ }
+
+ /**
+ * Parse integer literal
+ *
+ * <pre>
+ * integerLiteral: [1-9][0-9]* | [0]
+ * floatLiteral: [digits].[digits]?[e [digits]]?
+ * </pre>
+ */
+ private ConstantNode parseLiteral() { return con(_lexer.parseNumber()); }
+ public static Node con( long con ) { return con==0 ? ZERO : con(TypeInteger.constant(con)); }
+ public static ConstantNode con( Type t ) { return (ConstantNode)new ConstantNode(t).peephole(); }
+ public Node peep( Node n ) {
+ // Peephole, then improve with lexically scoped guards
+ return _scope.upcastGuard(n.peephole());
+ }
+
+ //////////////////////////////////
+ // Utilities for lexical analysis
+
+ // Return true and skip if "syntax" is next in the stream.
+ private boolean match (String syntax) { return _lexer.match (syntax); }
+ // Match must be "exact", not be followed by more id letters
+ private boolean matchx(String syntax) { return _lexer.matchx(syntax); }
+ private boolean matchOpx(char c0, char c1) { return _lexer.matchOpx(c0,c1); }
+ // Return true and do NOT skip if 'ch' is next
+ private boolean peek(char ch) { return _lexer.peek(ch); }
+ private boolean peekIsId() { return _lexer.peekIsId(); }
+
+ // ch is +/- 1, means oper++ or oper-- means postfix
+ private static boolean postfix(char ch) {
+ return (byte)ch== 1 || (byte)ch== -1;
+ }
+
+ public int pos() { return _lexer._position; }
+ private int pos(int pos) {
+ int old = _lexer._position;
+ _lexer._position = pos;
+ return old;
+ }
+ private Type posT(int pos) { _lexer._position = pos; return null; }
+ // Source code location
+ Lexer loc() { return new Lexer(_lexer); }
+
+
+ // Require and return an identifier
+ private String requireId() {
+ String id = _lexer.matchId();
+ if (id != null && !KEYWORDS.contains(id) ) return id.intern();
+ throw error("Expected an identifier, found '"+id+"'");
+ }
+
+ private String matchId() {
+ int old = pos();
+ String id = _lexer.matchId();
+ if( id==null ) return null;
+ if( !KEYWORDS.contains(id) ) return id;
+ pos(old);
+ return null;
+ }
+
+ // Require an exact match
+ private Parser require(String syntax) { require(null, syntax); return this; }
+ private <N> N require(N n, String syntax) {
+ if (match(syntax)) return n;
+ throw errorSyntax(syntax);
+ }
+
+ ////////////////////////////////////
+ // Lexer components
+
+ // Lexer provides low level access to the raw file bytes, peeks and matches
+ // short strings, parses numbers, skips comments and whitespace, tracks
+ // line numbers, allows the parse position to be saved and restored, and
+ // serves as a location indicator for errors.
+
+ public static class Lexer {
+
+ // Input buffer; an array of text bytes read from a file or a string
+ private final byte[] _input;
+ // Tracks current position in input buffer
+ private int _position = 0;
+
+ //
+ private int _line_number = 1;
+ // Start of current line
+ private int _line_start = 0;
+
+ /**
+ * Record the source text for lexing
+ */
+ public Lexer(String source) {
+ this(source.getBytes());
+ }
+
+ /**
+ * Direct from disk file source
+ */
+ public Lexer(byte[] buf) {
+ _input = buf;
+ }
+
+ /**
+ * Copy a lexer from a lexer
+ */
+ private Lexer(Lexer l) {
+ _input = l._input;
+ _position = l._position;
+ _line_number = l._line_number;
+ _line_start = l._line_start;
+ }
+
+ // Very handy in the debugger, shows the unparsed program
+ @Override
+ public String toString() {
+ return new String(_input, _position, _input.length - _position);
+ }
+
+ // True if at EOF
+ private boolean isEOF() {
+ return _position >= _input.length;
+ }
+
+ // Peek next character, or report EOF
+ private char peek() {
+ return isEOF() ? Character.MAX_VALUE // Special value that causes parsing to terminate
+ : (char) _input[_position];
+ }
+ // Just crash if misused
+ public byte peek(int off) { return _input[_position+off]; }
+
+ private void inc() {
+ if( _position++ < _input.length && _input[_position-1] == '\n' ) {
+ _line_number++;
+ _line_start = _position;
+ }
+ }
+ // Does not honor LF, so caller must roll back position on a LF
+ private char nextChar() {
+ char ch = peek();
+ inc();
+ return ch;
+ }
+
+ // True if a white space
+ private boolean isWhiteSpace() {
+ return peek() <= ' '; // Includes all the use space, tab, newline, CR
+ }
+
+ /**
+ * Return the next non-white-space character
+ */
+ private void skipWhiteSpace() {
+ while( true ) {
+ if( isWhiteSpace() ) inc();
+ // Skip // to end of line
+ else if( _position+2 < _input.length &&
+ _input[_position ] == '/' &&
+ _input[_position+1] == '/') {
+ inc(); inc();
+ while( !isEOF() && _input[_position] != '\n' ) inc();
+ } else break;
+ }
+ }
+
+ // Next non-white-space character, or EOF
+ public char nextXChar() { skipWhiteSpace(); return nextChar(); }
+
+ // Return true, if we find "syntax" after skipping white space; also
+ // then advance the cursor past syntax.
+ // Return false otherwise, and do not advance the cursor.
+ boolean match(String syntax) {
+ assert syntax.indexOf('\n')==-1; // No newlines in match
+ skipWhiteSpace();
+ int len = syntax.length();
+ if (_position + len > _input.length) return false;
+ for (int i = 0; i < len; i++)
+ if ((char) _input[_position + i] != syntax.charAt(i))
+ return false;
+ _position += len;
+ return true;
+ }
+
+ // Match must be exact and not followed by more ID characters.
+ // Prevents identifier "ifxy" from matching an "if" statement.
+ boolean matchx(String syntax) {
+ if( !match(syntax) ) return false;
+ if( !isIdLetter(peek()) ) return true;
+ _position -= syntax.length();
+ return false;
+ }
+ boolean matchOpx(char c0, char c1) {
+ skipWhiteSpace();
+ if( _position+1 >= _input.length || _input[_position]!=c0 || _input[_position+1]==c1 )
+ return false;
+ inc();
+ return true;
+ }
+
+ private boolean peek(char ch) {
+ skipWhiteSpace();
+ return peek()==ch;
+ }
+
+ boolean peekIsId() {
+ skipWhiteSpace();
+ return isIdStart(peek());
+ }
+
+ // Return an identifier or null
+ String matchId() {
+ return peekIsId() ? parseId() : null;
+ }
+
+ // Used for errors
+ String getAnyNextToken() {
+ if (isEOF()) return "";
+ if (isIdStart(peek())) return parseId();
+ if (isNumber(peek())) return parseNumberString();
+ if (isPunctuation(peek())) return parsePunctuation();
+ return String.valueOf(peek());
+ }
+
+
+ boolean isNumber(char ch) {return Character.isDigit(ch);}
+
+ // Return a constant Type, either TypeInteger or TypeFloat
+ private Type parseNumber() {
+ int old = _position;
+ int len = isLongOrDouble();
+ if( len > 0 ) {
+ if( len > 1 && _input[old]=='0' )
+ throw error("Syntax error: integer values cannot start with '0'",this);
+ long i = Long.parseLong(new String(_input,old,len));
+ return TypeInteger.constant(i);
+ }
+ return TypeFloat.constant(Double.parseDouble(new String(_input,old,-len)));
+ }
+ private String parseNumberString() {
+ int old = _position;
+ int len = Math.abs(isLongOrDouble());
+ return new String(_input,old,len);
+ }
+
+ // Return +len that ends a long
+ // Return -len that ends a double
+ private int isLongOrDouble() {
+ int old = _position;
+ char c;
+ while( Character.isDigit(c=nextChar()) ) ;
+ if( !(c=='e' || c=='.') )
+ return --_position - old;
+ if( peek() == '-' ) nextChar();
+ while( Character.isDigit(c=nextChar()) || c=='e' || c=='.' ) ;
+ return -(--_position - old);
+ }
+
+ // First letter of an identifier
+ private boolean isIdStart(char ch) {
+ return Character.isAlphabetic(ch) || ch == '_';
+ }
+
+ // All characters of an identifier, e.g. "_x123"
+ private boolean isIdLetter(char ch) {
+ return Character.isLetterOrDigit(ch) || ch == '_';
+ }
+
+ private String parseId() {
+ int start = _position;
+ while (isIdLetter(nextChar())) ;
+ return new String(_input, start, --_position - start);
+ }
+
+ //
+ private boolean isPunctuation(char ch) {
+ return "=;[]<>()+-/*&|^".indexOf(ch) != -1;
+ }
+
+ private String parsePunctuation() {
+ int start = _position;
+ return new String(_input, start, 1);
+ }
+
+ // Next oper= character, or 0.
+ // As a convenience, mark "++" as a char 1 and "--" as char -1 (65535)
+ public char matchOperAssign() {
+ skipWhiteSpace();
+ if( _position+2 >= _input.length ) return 0;
+ char ch0 = (char)_input[_position];
+ if( "+-/*&|^".indexOf(ch0) == -1 ) return 0;
+ char ch1 = (char)_input[_position+1];
+ if( ch1 == '=' ) { _position += 2; return ch0; }
+ if( isIdLetter((char)_input[_position+2]) ) return 0;
+ if( ch0 == '+' && ch1 == '+' ) { _position += 2; return (char) 1; }
+ if( ch0 == '-' && ch1 == '-' ) { _position += 2; return (char)-1; }
+ return 0;
+ }
+ }
+
+ ParseException errorSyntax(String syntax) { return _errorSyntax("expected "+syntax); }
+ private ParseException _errorSyntax(String msg) {
+ return error("Syntax error, "+msg+": " + _lexer.getAnyNextToken());
+ }
+ ParseException error(String msg) { return error(msg,_lexer); }
+ public static ParseException error(String msg, Lexer loc) { return new ParseException(msg,loc); }
+
+ public static class ParseException extends RuntimeException {
+ public final Lexer _loc;
+ // file:line:charoff err
+ //String msg = "src:"+_line_number+":"+(_position-_line_start)+" "+errorMessage;
+ ParseException( String msg, Lexer loc ) { super(msg); _loc = loc; }
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/SB.java b/chapter21/src/main/java/com/seaofnodes/simple/SB.java
new file mode 100644
index 00000000..978fbed9
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/SB.java
@@ -0,0 +1,99 @@
+package com.seaofnodes.simple;
+
+/** Tight/tiny StringBuilder wrapper.
+ * Short short names on purpose; so they don't obscure the printing.
+ * Can't believe this wasn't done long, long ago. */
+public final class SB {
+ private final StringBuilder _sb;
+ private int _indent = 0;
+ public SB( ) { _sb = new StringBuilder( ); }
+ public SB(String s) { _sb = new StringBuilder(s); }
+ public SB p( String s ) { _sb.append(s); return this; }
+ public SB p( float s ) {
+ if( Float.isNaN(s) )
+ _sb.append( "Float.NaN");
+ else if( Float.isInfinite(s) ) {
+ _sb.append(s > 0 ? "Float.POSITIVE_INFINITY" : "Float.NEGATIVE_INFINITY");
+ } else _sb.append(s);
+ return this;
+ }
+ public SB p( double s ) {
+ if( Double.isNaN(s) )
+ _sb.append("Double.NaN");
+ else if( Double.isInfinite(s) ) {
+ _sb.append(s > 0 ? "Double.POSITIVE_INFINITY" : "Double.NEGATIVE_INFINITY");
+ } else _sb.append(s);
+ return this;
+ }
+ public SB p( char s ) { _sb.append(s); return this; }
+ public SB p( int s ) { _sb.append(s); return this; }
+ public SB p( long s ) { _sb.append(s); return this; }
+ public SB p( boolean s) { _sb.append(s); return this; }
+ // 1 byte, 2 hex digits, 8 bits
+ public SB hex1(int s) {
+ int digit = (s>>4) & 0xf;
+ _sb.append((char)((digit <= 9 ? '0' : ('A'-10))+digit));
+ digit = s & 0xf;
+ _sb.append((char)((digit <= 9 ? '0' : ('A'-10))+digit));
+ return this;
+ }
+ // 2 bytes, 4 hex digits, 16 bits, Big Endian
+ public SB hex2(int s) { return hex1(s>> 8).hex1(s); }
+ // 4 bytes, 8 hex digits, 32 bits, Big Endian
+ public SB hex4(int s) { return hex2(s>>16).hex2(s); }
+ // 8 bytes, 16 hex digits, 64 bits, Big Endian
+ public SB hex8(long s) { return hex4((int)(s>>32)).hex4((int)s); }
+
+ // Fixed width field
+ public SB fix( int sz, String s ) {
+ for( int i=0; i<sz; i++ )
+ _sb.append( i < s.length() ? s.charAt(i) : ' ');
+ return this;
+ }
+ public char at(int idx ) { return _sb.charAt(idx); }
+
+ // Not spelled "p" on purpose: too easy to accidentally say "p(1.0)" and
+ // suddenly call the autoboxed version.
+ public SB pobj( Object s ) { _sb.append(s.toString()); return this; }
+ public SB i( int d ) { for( int i=0; i<d+_indent; i++ ) p(" "); return this; }
+ public SB i( ) { return i(0); }
+ public SB ip(String s) { return i().p(s); }
+ public SB s() { _sb.append(' '); return this; }
+
+ // Increase indentation
+ public SB ii( int i) { _indent += i; return this; }
+ public SB ii() { return ii(1); }
+ // Decrease indentation
+ public SB di( int i) { _indent -= i; return this; }
+ public SB di() { return di(1); }
+
+ public SB nl( ) { return p(System.lineSeparator()); }
+ // Last printed a nl
+ public boolean was_nl() {
+ int len = _sb.length();
+ String nl = System.lineSeparator();
+ int nlen = nl.length();
+ if( len < nlen ) return false;
+ for( int i=0; i<nlen; i++ )
+ if( _sb.charAt(len-nlen+i)!=nl.charAt(i) )
+ return false;
+ return true;
+ }
+
+ // Delete last char. Useful when doing string-joins and JSON printing and an
+ // extra separater char needs to be removed:
+ //
+ // sb.p('[');
+ // for( Foo foo : foos )
+ // sb.p(foo).p(',');
+ // sb.unchar().p(']'); // remove extra trailing comma
+ //
+ public SB unchar() { return unchar(1); }
+ public SB unchar(int x) { _sb.setLength(_sb.length()-x); return this; }
+ public SB setLen(int len) { _sb.setLength(len); return this; }
+ public String subString(int start, int end ) { return _sb.substring(start,end); }
+
+ public SB clear() { _sb.setLength(0); return this; }
+ public int len() { return _sb.length(); }
+ @Override public String toString() { return _sb.toString(); }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/Utils.java b/chapter21/src/main/java/com/seaofnodes/simple/Utils.java
new file mode 100644
index 00000000..1346c3ea
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/Utils.java
@@ -0,0 +1,53 @@
+package com.seaofnodes.simple;
+
+import java.util.ArrayList;
+
+public class Utils {
+ public static RuntimeException TODO() { return TODO("Not yet implemented"); }
+ public static RuntimeException TODO(String msg) { return new RuntimeException(msg); }
+
+ /**
+ * Fast, constant-time, element removal. Does not preserve order
+ *
+ * @param array ArrayList to modify
+ * @param i element to be removed
+ * @return element removed
+ */
+ public static <E> void del(ArrayList<E> array, int i) {
+ E last = array.removeLast();
+ if (i < array.size()) array.set(i, last);
+ }
+
+ /**
+ * Search a list for an element by reference
+ *
+ * @param ary List to search in
+ * @param x Object to be searched
+ * @return >= 0 on success, -1 on failure
+ */
+ public static <E> int find( ArrayList<E> ary, E x ) {
+ for( int i=0; i<ary.size(); i++ )
+ if( ary.get(i)==x )
+ return i;
+ return -1;
+ }
+ /**
+ * Search a list for an element by reference
+ *
+ * @param ary List to search in
+ * @param x Object to be searched
+ * @return >= 0 on success, -1 on failure
+ */
+ public static <E> int find( E[] ary, E x ) {
+ for( int i=0; i<ary.length; i++ )
+ if( ary[i]==x )
+ return i;
+ return -1;
+ }
+
+ // Rotate a long, nice for hashes
+ public static long rot( long x, int n ) { return (x<<n) | (x>>>n); }
+
+ // Fold a 64bit hash into 32 bits
+ public static int fold( long x ) { return (int)((x>>32) ^ x); }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/Var.java b/chapter21/src/main/java/com/seaofnodes/simple/Var.java
new file mode 100644
index 00000000..e71d7ef2
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/Var.java
@@ -0,0 +1,55 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.type.*;
+
+
+/**
+ * The tracked fields are now complex enough to deserve a array-of-structs layout
+ */
+public class Var {
+
+ public final String _name; // Declared name
+ public int _idx; // index in containing scope
+ private Type _type; // Declared type
+ public boolean _final; // Final field
+ public Parser.Lexer _loc; // Source location
+ public boolean _fref; // Forward ref
+
+ public Var(int idx, String name, Type type, boolean xfinal, Parser.Lexer loc ) {
+ this(idx,name,type,xfinal,loc,false);
+ }
+ public Var(int idx, String name, Type type, boolean xfinal, Parser.Lexer loc, boolean fref) {
+ _idx = idx;
+ _name = name;
+ _type = type;
+ _final = xfinal;
+ _loc = loc;
+ _fref = fref;
+ }
+ public Type type() {
+ if( !_type.isFRef() ) return _type;
+ // Update self to no longer use the forward ref type
+ Type def = Parser.TYPES.get(((TypeMemPtr)_type)._obj._name);
+ return (_type=_type.meet(def));
+ }
+ public Type lazyGLB() {
+ Type t = type();
+ return t instanceof TypeMemPtr ? t : t.glb();
+ }
+
+ // Forward reference variables (not types) must be BOTTOM and
+ // distinct from inferred variables
+ public boolean isFRef() { return _fref; }
+
+ public void defFRef( Type type, boolean xfinal, Parser.Lexer loc ) {
+ assert isFRef() && xfinal;
+ _type = type;
+ _final = true;
+ _loc = loc;
+ _fref = false;
+ }
+
+ @Override public String toString() {
+ return _type.toString()+(_final ? " ": " !")+_name;
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java b/chapter21/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java
new file mode 100644
index 00000000..a2bb21ed
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java
@@ -0,0 +1,90 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeMem;
+
+abstract public class BuildLRG {
+ // Compute live ranges in a single forwards pass. Every def is a new live
+ // range except live ranges are joined at Phi. Also, gather the
+ // intersection of register masks in each live range. Example: "free
+ // always zero" register forces register zero, and calling conventions
+ // typically force registers.
+
+ // Sets FAILED to the set of hard-conflicts (means no need for an IFG,
+ // since it will not color, just split the conflicted ranges now). Returns
+ // true if no hard-conflicts, although we still might not color.
+ public static boolean run(int round, RegAlloc alloc) {
+ for( Node bb : alloc._code._cfg )
+ for( Node n : bb.outs() ) {
+ if( n instanceof PhiNode phi && !(phi._type instanceof TypeMem) ) {
+ // All Phi inputs end up with the same LRG.
+ // Pass 1: find any pre-existing LRG, to avoid make-then-Union a LRG
+ LRG lrg = alloc.lrg(phi);
+ if( lrg == null )
+ for( int i=1; i<phi.nIns(); i++ )
+ if( (lrg = alloc.lrg(phi.in(i))) != null )
+ break;
+ // If none, make one.
+ if( lrg==null ) lrg = alloc.newLRG(n);
+ if( phi instanceof MachNode ) defLRG(alloc,n);
+ // Pass 2: everybody uses the same LRG
+ lrg=alloc.union(lrg,phi);
+ for( int i=phi instanceof ParmNode ? 2 : 1; i<n.nIns(); i++ )
+ lrg=alloc.union(lrg,n.in(i));
+ if( lrg._mask!=null && lrg._mask.isEmpty() )
+ alloc.fail(lrg);
+
+ } else if( n instanceof MachNode mach ) {
+ // Attempt to commute ops to keep live ranges compatible.
+ if( mach.commutes() && n.nOuts()==1 ) {
+ int uidx = n.out(0)._inputs.find(n);
+ RegMask mask1 = n.in (1) instanceof MachNode machx ? machx.outregmap() : alloc.lrg(n.in(1))._mask;
+ RegMask mask2 = n.in (2) instanceof MachNode machx ? machx.outregmap() : alloc.lrg(n.in(2))._mask;
+ RegMask masko = n.out(0) instanceof MachNode machx ? machx.regmap(uidx): alloc.lrg(n )._mask;
+ if( !mask1.overlap(masko) && mask2.overlap(masko) )
+ n.swap12();
+ }
+
+ // Define live range
+ defLRG(alloc,n);
+
+ // Now, look in the opposite direction. How are incoming
+ // LRGs affected by this node: For all uses, make live lrgs
+ for( int i=1; i<n.nIns(); i++ )
+ if( n.in(i)!=null ) {
+ LRG lrg2 = alloc.lrg(n.in(i));
+ if( lrg2 != null ) { // Anti-dep or other, no LRG
+ RegMask use_mask = mach.regmap(i); // use_mask is also null for anti-dep
+ if( use_mask!=null && !lrg2.machUse(mach,(short)i,use_mask.size1()).and(use_mask) )
+ alloc.fail(lrg2); // Empty register mask, must split
+ }
+ }
+
+ }
+
+ // MultiNodes have projections which set registers
+ if( n instanceof MultiNode )
+ for( Node proj : n.outs() )
+ if( proj instanceof MachNode )
+ defLRG(alloc,proj);
+ }
+
+ // Collect live ranges
+ alloc.unify();
+
+ return alloc.success();
+ }
+
+ private static void defLRG( RegAlloc alloc, Node n ) {
+ MachNode mach = (MachNode)n;
+ RegMask def_mask = mach.outregmap();
+ if( def_mask == null ) return;
+ LRG lrg = mach.twoAddress() == 0
+ ? alloc.newLRG(n) // Define a new LRG for N
+ : alloc.lrg2(n,mach.twoAddress()); // Use the matching 2-adr input
+ // Record mask and mach
+ if( !lrg.machDef(mach,def_mask.size1()).and(def_mask) )
+ alloc.fail(lrg); // Empty register mask, must split
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java b/chapter21/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java
new file mode 100644
index 00000000..e2fac779
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java
@@ -0,0 +1,105 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.Ary;
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.node.*;
+import java.util.BitSet;
+import java.util.IdentityHashMap;
+
+// Coalesce copies
+abstract public class Coalesce {
+
+ public static boolean coalesce( int round, RegAlloc alloc ) {
+
+ // Walk all the splits, looking for coalesce chances
+ boolean progress = false;
+ for( CFGNode bb : alloc._code._cfg ) {
+ outer:
+ for( int j=0; j<bb.nOuts(); j++ ) {
+ Node n = bb.out(j);
+ if( !(n instanceof SplitNode) ) continue;
+ LRG v1 = alloc.lrg(n);
+ LRG v2 = alloc.lrg(n.in(1));
+ if( v1 != v2 ) {
+ LRG ov1 = v1, ov2 = v2;
+ // Get the smaller neighbor count in v1
+ if( v1.nadj() > v2.nadj() )
+ { v1 = v2; v2 = alloc.lrg(n); }
+ int v2len = v2._adj==null ? 0 : v2._adj._len;
+ // See that they do not conflict (coalescing would make a self-conflict)
+ if( v1._adj!=null && v2._adj!=null && v1._mask.overlap(v2._mask) ) {
+ // Walk the smaller neighbors, and add to the larger.
+ // Check for direct conflicts along the way.
+ for( LRG v3 : v1._adj ) {
+ if( v3==v2 ) {
+ // Direct conflict, skip this LRG pair
+ v2._adj.setLen(v2len);
+ continue outer;
+ }
+ // Add missing LRGs from V1 to V2
+ if( v2._adj.find(v3)== -1 )
+ v2._adj.push(v3);
+ }
+ }
+ // Most constrained mask
+ RegMask mask = v1._mask==v2._mask ? v1._mask : v1._mask.copy().and(v2._mask);
+ // Check for capacity
+ if( v2.nadj() >= mask.size() ) {
+ // Fails capacity, will not be trivial colorable
+ if( v2._adj!=null ) v2._adj.setLen(v2len);
+ continue;
+ }
+
+ // Union lrgs.
+ progress = true;
+
+ // Since `n` is dying, remove from the LRGS BEFORE doing
+ // the union; this will let the union code keep the
+ // matching bits from the other side, preserving some
+ // info for biased coloring
+ if( ov1. _machDef==n ) ov1. _machDef = null;
+ if( ov1._splitDef==n ) ov1._splitDef = null;
+ if( ov2. _machUse==n ) ov2. _machUse = null;
+ if( ov2._splitUse==n ) ov2._splitUse = null;
+ // Keep larger adjacency list.
+ Ary<LRG> larger = v2._adj;
+ v2._adj = null;
+
+ // Union lrgs. Swaps again, based on small lrg
+ LRG vWin = v1.union(v2);
+ vWin._adj = larger;
+ LRG vLose = v1.leader() ? v2 : v1;
+
+ // LRGs below v2len were always in v2, but maybe not in v1.
+ // LRGs above v2len are in v1 but were not in v2.
+
+ // Walk v2's neighbors, replacing vLose with vWin.
+ if( larger != null )
+ for( LRG vn : larger ) {
+ int idx = vn._adj.find(vLose);
+ if( idx != -1 )
+ vn._adj.del(idx);
+ if( vn._adj.find(vWin) == -1 )
+ vn._adj.add(vWin);
+ }
+ } else {
+
+ // Since `n` is dying, remove from the LRGS
+ if( v1. _machDef==n ) v1. _machDef=null;
+ if( v1. _machUse==n ) v1. _machUse=null;
+ if( v1._splitDef==n ) v1._splitDef=null;
+ if( v1._splitUse==n ) v1._splitUse=null;
+ }
+
+ // Remove junk split
+ n.removeSplit();
+ j--;
+ }
+ }
+
+ if( progress )
+ alloc.unify();
+
+ return true;
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/chapter21/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
new file mode 100644
index 00000000..86cbfe97
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
@@ -0,0 +1,463 @@
+package com.seaofnodes.simple.codegen;
+
+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 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;
+
+ public enum Phase {
+ 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;
+
+ // ---------------------------
+ // Compilation source code
+ 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 ) {
+ CODE = this;
+ _phase = null;
+ _callingConv = null;
+ _start = new StartNode(arg);
+ _stop = new StopNode(src);
+ _src = src;
+ _arg = arg;
+ _iter = new IterPeeps(workListSeed);
+ P = new Parser(this,arg);
+ }
+
+
+ // 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
+ * avoid bugs confusing node ID 0 with uninitialized values.
+ */
+ public int _uid = 1;
+ public int UID() { return _uid; }
+ public int getUID() {
+ return _uid++;
+ }
+
+ // Next available memory alias number
+ private int _alias = 2; // 0 is for control, 1 for memory
+ public int getALIAS() { return _alias++; }
+
+
+ // Popular visit bitset, declared here, so it gets reused all over
+ public final BitSet _visit = new BitSet();
+ public BitSet visit() { assert _visit.isEmpty(); return _visit; }
+
+ // Start and stop; end points of the generated IR
+ public StartNode _start;
+ public StopNode _stop;
+
+ // Global Value Numbering. Hash over opcode and inputs; hits in this table
+ // are structurally equal.
+ public final HashMap<Node,Node> _gvn = new HashMap<>();
+
+ // Compute "function indices": FIDX.
+ // Each new request at the same signature gets a new FIDX.
+ private final HashMap<TypeTuple,Integer> FIDXS = new HashMap<>();
+ public TypeFunPtr makeFun( TypeTuple sig, Type ret ) {
+ Integer i = FIDXS.get(sig);
+ int fidx = i==null ? 0 : i;
+ FIDXS.put(sig,fidx+1); // Track count per sig
+ assert fidx<64; // TODO: need a larger FIDX space
+ return TypeFunPtr.make((byte)2,sig,ret, 1L<<fidx );
+ }
+ // Signature for MAIN
+ public TypeFunPtr _main = makeFun(TypeTuple.MAIN,Type.BOTTOM);
+ // Reverse from a constant function pointer to the IR function being called
+ public FunNode link( TypeFunPtr tfp ) {
+ assert tfp.isConstant();
+ return _linker.get(tfp.makeFrom(Type.BOTTOM));
+ }
+
+ // Insert linker mapping from constant function signature to the function
+ // being called.
+ public void link(FunNode fun) {
+ _linker.put(fun.sig().makeFrom(Type.BOTTOM),fun);
+ }
+
+ // "Linker" mapping from constant TypeFunPtrs to heads of function. These
+ // TFPs all have exact single fidxs and their return is wiped to BOTTOM (so
+ // the return is not part of the match).
+ private final HashMap<TypeFunPtr,FunNode> _linker = new HashMap<>();
+
+ // Parser object
+ public final Parser P;
+
+ // Parse ASCII text into Sea-of-Nodes IR
+ public int _tParse;
+ public CodeGen parse() {
+ assert _phase == null;
+ _phase = Phase.Parse;
+ long t0 = System.currentTimeMillis();
+
+ P.parse();
+ _tParse = (int)(System.currentTimeMillis() - t0);
+ JSViewer.show();
+ return this;
+ }
+
+
+ // ---------------------------
+ // Iterator peepholes.
+ public final IterPeeps _iter;
+ // Stop tracking deps while assertin
+ public boolean _midAssert;
+ // Statistics on peepholes
+ public int _iter_cnt, _iter_nop_cnt;
+ // Stat gathering
+ public void iterCnt() { if( !_midAssert ) _iter_cnt++; }
+ public void iterNop() { if( !_midAssert ) _iter_nop_cnt++; }
+
+ // Run ideal optimizations
+ public int _tOpto;
+ public CodeGen opto() {
+ assert _phase == Phase.Parse;
+ _phase = Phase.Opto;
+ long t0 = System.currentTimeMillis();
+
+ // Pessimistic peephole optimization on a worklist
+ _iter.iterate(this);
+ _tOpto = (int)(System.currentTimeMillis() - t0);
+
+ // TODO:
+ // Optimistic
+ // TODO:
+ // loop unroll, peel, RCE, etc
+ return this;
+ }
+ public <N extends Node> N add( N n ) { return (N)_iter.add(n); }
+ public void addAll( Ary<Node> ary ) { _iter.addAll(ary); }
+
+
+ // ---------------------------
+ // Last check for bad programs
+ public int _tTypeCheck;
+ public CodeGen typeCheck() {
+ // Demand phase Opto for cleaning up dead control flow at least,
+ // required for the following GCM.
+ assert _phase.ordinal() <= Phase.Opto.ordinal();
+ _phase = Phase.TypeCheck;
+ long t0 = System.currentTimeMillis();
+
+ Parser.ParseException err = _stop.walk( Node::err );
+ _tTypeCheck = (int)(System.currentTimeMillis() - t0);
+ if( err != null )
+ throw err;
+ 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 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;
+
+ // Not timing the class load...
+ // Look for CPU in fixed named place:
+ // com.seaofnodes.simple.node.cpus."cpu"."cpu.class"
+ String clzFile = base+"."+cpu+"."+cpu;
+ try { _mach = ((Class<Machine>) Class.forName( clzFile )).getDeclaredConstructor(new Class[]{CodeGen.class}).newInstance(this); }
+ catch( Exception e ) { throw new RuntimeException(e); }
+
+ // 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<maxReg; reg++ )
+ if( !_callerSave.test(reg) && ((1L<<reg)&neverSave)==0 )
+ _retMasks[i++] = new RegMask(reg);
+ _rpcMask = new RegMask(_mach.rpc());
+ _retMasks[3] = _rpcMask;
+
+
+ // Convert to machine ops
+ long t0 = System.currentTimeMillis();
+ _uid = 1; // All new machine nodes reset numbering
+ var map = new IdentityHashMap<Node,Node>();
+ _instSelect( _stop, map );
+ _stop = ( StopNode)map.get(_stop );
+ _start = (StartNode)map.get(_start);
+ _instOuts(_stop,visit());
+ _visit.clear();
+ _tInsSel = (int)(System.currentTimeMillis() - t0);
+ return this;
+ }
+
+ // Walk all ideal nodes, recursively mapping ideal to machine nodes, then
+ // make a machine node for "this".
+ private Node _instSelect( Node n, IdentityHashMap<Node,Node> map ) {
+ if( n==null ) return null;
+ Node x = map.get(n);
+ if( x !=null ) return x; // Been there, done that
+
+ // If n is a MachNode already, then its part of a multi-node expansion.
+ // It does not need instruction selection (already selected!)
+ // but it does need its inputs walked.
+ if( n instanceof MachNode ) {
+ for( int i=0; i < n.nIns(); i++ )
+ n._inputs.set(i, _instSelect(n.in(i),map) );
+ return n;
+ }
+
+ // Produce a machine node from n; map it to flag as done so stops cycles.
+ map.put(n, x=_mach.instSelect(n) );
+ // Walk machine op and replace inputs with mapped inputs
+ for( int i=0; i < x.nIns(); i++ )
+ x._inputs.set(i, _instSelect(x.in(i),map) );
+ if( x instanceof MachNode mach )
+ mach.postSelect(this); // Post selection action
+
+ // Updates forward edges only.
+ n._outputs.clear();
+ return x;
+ }
+
+ // Walk all machine Nodes, and set their output edges
+ private void _instOuts( Node n, BitSet visit ) {
+ if( visit.get(n._nid) ) return;
+ visit.set(n._nid);
+ for( Node in : n._inputs )
+ if( in!=null ) {
+ in._outputs.push(n);
+ _instOuts(in,visit);
+ }
+ }
+
+
+ // ---------------------------
+ // Control Flow Graph in Reverse Post Order.
+ public int _tGCM;
+ public Ary<CFGNode> _cfg = new Ary<>(CFGNode.class);
+
+ // Global schedule (code motion) nodes
+ public CodeGen GCM() { return GCM(false); }
+ public CodeGen GCM( boolean show) {
+ assert _phase.ordinal() <= Phase.InstSelect.ordinal();
+ _phase = Phase.Schedule;
+ long t0 = System.currentTimeMillis();
+
+ GlobalCodeMotion.buildCFG(this);
+ _tGCM = (int)(System.currentTimeMillis() - t0);
+ if( show )
+ System.out.println(new GraphVisualizer().generateDotOutput(_stop,null,null));
+ return this;
+ }
+
+ // ---------------------------
+ // Local (basic block) scheduler phase, a classic list scheduler
+ public int _tLocal;
+ public CodeGen localSched() {
+ assert _phase == Phase.Schedule;
+ _phase = Phase.LocalSched;
+ long t0 = System.currentTimeMillis();
+ ListScheduler.sched(this);
+ _tLocal = (int)(System.currentTimeMillis() - t0);
+ return this;
+ }
+
+
+ // ---------------------------
+ // Register Allocation
+ public int _tRegAlloc;
+ public RegAlloc _regAlloc;
+ public CodeGen regAlloc() {
+ assert _phase == Phase.LocalSched;
+ _phase = Phase.RegAlloc;
+ long t0 = System.currentTimeMillis();
+ _regAlloc = new RegAlloc(this);
+ _regAlloc.regAlloc();
+ _tRegAlloc = (int)(System.currentTimeMillis() - t0);
+ return this;
+ }
+
+ // 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,fun);
+ if( s!=null ) return s;
+ }
+ return "N"+ n._nid;
+ }
+
+
+ // ---------------------------
+ // Encoding
+ 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;
+ }
+
+ // ---------------------------
+ // 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;
+ }
+
+ // ---------------------------
+ public boolean _asmLittle=true;
+ SB asm(SB sb) { return ASMPrinter.print(sb,this); }
+ public String asm() { return asm(new SB()).toString(); }
+
+
+ // Testing shortcuts
+ public Node ctrl() { return _stop.ret().ctrl(); }
+ public Node expr() { return _stop.ret().expr(); }
+ public String print() { return _stop.print(); }
+
+ // Debugging helper
+ @Override public String toString() {
+ return _phase.ordinal() > Phase.Schedule.ordinal()
+ ? IRPrinter._prettyPrint( this )
+ : _stop.p(9999);
+ }
+
+ // Debugging helper
+ public Node f(int idx) { return _stop.find(idx); }
+
+
+ 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/chapter21/src/main/java/com/seaofnodes/simple/codegen/ElfExpr.java b/chapter21/src/main/java/com/seaofnodes/simple/codegen/ElfExpr.java
new file mode 100644
index 00000000..9d9b8a1c
--- /dev/null
+++ b/chapter21/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/chapter21/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java b/chapter21/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java
new file mode 100644
index 00000000..746ecfed
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java
@@ -0,0 +1,381 @@
+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<Section> _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<Symbol> _symbols = new Ary<>(Symbol.class);
+ Ary<Symbol> _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<TypeFunPtr,Symbol> _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];
+
+ 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/chapter21/src/main/java/com/seaofnodes/simple/codegen/Encoding.java b/chapter21/src/main/java/com/seaofnodes/simple/codegen/Encoding.java
new file mode 100644
index 00000000..d7057839
--- /dev/null
+++ b/chapter21/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
+ * <p>
+ * This class holds the encoding bits, plus the relocation information needed
+ * to move the code to a non-zero offset, or write to an external file (ELF).
+ * <p>
+ * There are also a bunch of generic utilities for managing bits and bytes
+ * common in all encodings.
+ */
+public class Encoding {
+
+ // Top-level program graph structure
+ public final CodeGen _code;
+
+ // 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 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;
+ }
+ }
+ public final HashMap<Node,Relo> _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<<log; i++ ) {
+ bits.write((int)x);
+ x >>= 8;
+ }
+ }
+
+ public Encoding add1( int op ) { addN(0,op,_bits); return this; }
+ public Encoding add2( int op ) { addN(1,op,_bits); return this; }
+ public Encoding add4( int op ) { addN(2,op,_bits); return this; }
+ public Encoding add8(long op ) { addN(3,op,_bits); return this; }
+
+
+ // Nodes need "relocation" patching; things done after code is placed.
+ // Record src and dst Nodes.
+ private final HashMap<Node,CFGNode> _internals = new HashMap<>();
+ // Source is a Call, destination in the Fun.
+ public Encoding relo( CallNode call ) {
+ _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<Node,String> _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<LoopNode,Ary<CFGNode>> rpos = new IdentityHashMap<>();
+ Ary<CFGNode> rpo = new Ary<>(CFGNode.class);
+ rpos.put(_code._start.loop(),rpo);
+ BitSet visit = _code.visit();
+ //IdentityHashMap<CFGNode,LoopNode> 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<LoopNode,Ary<CFGNode>> rpos ) {
+ if( bb==null || visit.get(bb._nid) ) return;
+ visit.set(bb._nid);
+ CFGNode next = bb.uctrl();
+
+ // Loops run an inner "rpo_cfg", then append the entire loop body in
+ // place. This keeps loop bodies completely contained, although if
+ // Some Day Later we have real profile data we ought to arrange the
+ // branch orderings based on frequency.
+ if( bb instanceof LoopNode loop ) {
+ Ary<CFGNode> body = new Ary<>(CFGNode.class); // Private RPO for the loop
+ rpos.put(loop.loop(),body); // Find it via loop tree
+ _rpo_cfg(next,visit,rpos); // RPO the loop
+ body.add(loop); // Include loop last (first in RPO)
+ Ary<CFGNode> outer = rpos.get(loop.cfg(1).loop());
+ outer.addAll(body); // Append to original CFG
+ return;
+ }
+
+ // IfNodes visit 2 sides, and may choose to reorder them in the RPO
+ if( bb instanceof IfNode iff ) {
+ // Pick out the T/F projections
+ CProjNode t = iff.cproj(0);
+ CProjNode f = iff.cproj(1);
+ // Invert the branch or not
+ if( shouldInvert(t,f,iff.loopDepth()) ) {
+ iff.negate();
+ t.invert();
+ f.invert();
+ CProjNode tmp=f; f=t; t=tmp; // Swap t/f
+ }
+
+ // Whichever side is visited first becomes last in the RPO. With
+ // no loops, visit the False side last (so True side first) so that
+ // when the False RPO visit returns, the IF is immediately next.
+ // When the RPO is reversed, the fall-through path will always be
+ // following the IF.
+ _rpo_cfg(t,visit,rpos); // True side first
+ next = f; // False side last
+ }
+
+ // If the *next* BB has already been visited, and we are not already a
+ // jump, we may need an unconditional forwards jump here
+ Ary<CFGNode> rpo = rpos.get(bb.loop());
+ if( next!=null && visit.get(next._nid) && !(bb instanceof IfNode) ) {
+ boolean needJump = next instanceof LoopNode
+ // Empty blocks from an IF will invert the IF and backwards
+ // branch to the loop head. If not empty, or not an IF
+ // will need a jump.
+ ? (bb.nOuts()>1 || !(bb.cfg0() instanceof IfNode) )
+ // Forwards jump. If all the blocks, in RPO order, to our
+ // target are empty, we will fall in and not need a jump.
+ : !backwardsEmptyScan(rpo,next);
+ if( needJump ) {
+ CFGNode jmp = _code._mach.jump();
+ jmp._ltree = bb._ltree;
+ jmp.setDefX(0,bb);
+ next.setDef(next._inputs.find(bb),jmp);
+ rpo.add(jmp);
+ }
+ }
+
+ _rpo_cfg(next,visit,rpos);
+ rpo.add(bb);
+ }
+
+ // Should this test be inverted?
+ private static boolean shouldInvert(CFGNode t, CFGNode f, int bld) {
+ int tld = t.loopDepth(), fld = f.loopDepth();
+ // These next two are symmetric and can happen in any order; if `tld <
+ // bld` is true, 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 || forwardsEmptyScan(cfg,bld));
+ }
+
+ // Is the CFG from "next" to the end empty? This means jumping to "next"
+ // will naturally fall into the end.
+ private static boolean backwardsEmptyScan(Ary<CFGNode> rpo, CFGNode next) {
+ for( int i=rpo._len-1; rpo.at(i)!=next; i-- )
+ if( rpo.at(i).nOuts()!=1 )
+ return false;
+ return true;
+ }
+
+ // --------------------------------------------------
+ // Write encoding bits in order into a big byte array.
+ // Record opcode start and length.
+ 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 ) {
+ padN(16,_bits);
+ _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]);
+ }
+ }
+ }
+ padN(16,_bits);
+ }
+
+ // --------------------------------------------------
+ // 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<len; i++ )
+ oldStarts[i] = _opStart[_code._cfg.at(i)._nid];
+
+ // TODO: Rewrite this algo to use the small "_jmps" list of just the
+ // jumps instead of walking all blocks.
+ // TODO2: Allow short encodings on non-jumps. This algo doesn't
+ // really care jumps vs other rip-relative things.
+
+ // Check the size of short jumps, adjust the block starts for a whole
+ // pass over all blocks. If some jumps go long, they might stretch the
+ // distance for other jumps, so another pass is needed.
+ int slide = -1;
+ while( slide != 0) { // While no fails
+ slide = 0;
+ for( int i=0; i<len; i++ ) {
+ CFGNode bb = _code._cfg.at(i);
+ // Functions pad to align 16
+ if( bb instanceof FunNode ) {
+ int newStart = _opStart[bb._nid]+slide;
+ slide += (newStart+15 & -16)-newStart;
+ }
+ _opStart[bb._nid] += slide;
+ // Slide down all other (non-CFG) ops in the block
+ for( Node n : bb._outputs )
+ if( n instanceof MachNode && !(n instanceof CFGNode) )
+ _opStart[n._nid] += slide;
+ if( bb instanceof RIPRelSize riprel ) {
+ CFGNode target = (bb instanceof IfNode iff ? iff.cproj(0) : (CFGNode)bb.out(0)).uctrlSkipEmpty();
+ // Delta is from opStart to opStart. X86 at least counts
+ // the delta from the opEnd, but we don't have the end until
+ // we decide the size - so the encSize has to deal
+ int delta = _opStart[target._nid] - _opStart[bb._nid];
+ byte opLen = riprel.encSize(delta);
+ // Recorded size is smaller than the current size?
+ if( _opLen[bb._nid] < opLen ) {
+ // Start sliding the code down; record slide amount and new size
+ slide += opLen - _opLen[bb._nid];
+ _opLen[bb._nid] = opLen;
+ }
+ }
+ }
+ }
+
+
+ // Copy/slide the bits to make space for all the longer branches
+ int grow = _opStart[_code._cfg.at(len-1)._nid] - oldStarts[len-1];
+ if( grow > 0 ) { // If no short-form ops, nothing to do here
+ int end = _bits.size();
+ byte[] bits = new byte[end+grow];
+ for( int i=len-1; i>=0; i-- ) {
+ int start = oldStarts[i];
+ if( start==0 && i>1 ) continue;
+ int oldStart = oldStarts[i];
+ int newStart = _opStart[_code._cfg.at(i)._nid];
+ System.arraycopy(_bits.buf(),oldStart,bits,newStart,end-start);
+ end = start;
+ }
+ _bits.set(bits,bits.length);
+ }
+ }
+
+
+ // --------------------------------------------------
+ // Patch local encodings now
+ 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<Type,Integer> 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/chapter21/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/chapter21/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
new file mode 100644
index 00000000..e904dc66
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
@@ -0,0 +1,285 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.Ary;
+import com.seaofnodes.simple.IterPeeps.WorkList;
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import java.util.*;
+
+public abstract class GlobalCodeMotion {
+
+ // Arrange that the existing isCFG() Nodes form a valid CFG. The
+ // Node.use(0) is always a block tail (either IfNode or head of the
+ // following block). There are no unreachable infinite loops.
+ public static void buildCFG( CodeGen code ) {
+ Ary<CFGNode> rpo = new Ary<>(CFGNode.class);
+ _rpo_cfg(null, code._start, code.visit(), rpo);
+ // Reverse in-place
+ for( int i=0; i< rpo.size()>>1; i++ )
+ rpo.swap(i,rpo.size()-1-i);
+ // Set global CFG
+ code._cfg = rpo;
+
+ schedEarly(code);
+
+ // Break up shared global constants by functions
+ breakUpGlobalConstants(code._start);
+
+ code._visit.clear();
+ schedLate (code);
+ }
+
+ // Post-Order of CFG
+ private static void _rpo_cfg(CFGNode def, Node use, BitSet visit, Ary<CFGNode> rpo) {
+ if( use instanceof CallNode call ) call.unlink_all();
+ if( !(use instanceof CFGNode cfg) || visit.get(cfg._nid) )
+ return; // Been there, done that
+ if( def instanceof ReturnNode && use instanceof CallEndNode )
+ return;
+ assert !( def instanceof CallNode && use instanceof FunNode );
+ visit.set(cfg._nid);
+ for( Node useuse : cfg._outputs )
+ _rpo_cfg(cfg,useuse,visit,rpo);
+ rpo.add(cfg);
+ }
+
+ // Break up shared global constants by functions
+ private static void breakUpGlobalConstants( Node start ) {
+ // For all global constants
+ for( int i=0; i< start.nOuts(); i++ ) {
+ Node con = start.out(i);
+ if( con instanceof MachNode mach && mach.isClone() ) {
+ breakUpGlobalConstants(con);
+ // While constant has users in different functions
+ while( true ) {
+ // Find a function user, and another function
+ FunNode fun = null;
+ boolean done=true;
+ for( Node use : con.outs() ) {
+ FunNode fun2 = use instanceof ReturnNode ret ? ret.fun() : use.cfg0().fun();
+ if( fun==null || fun==fun2 ) fun=fun2;
+ else { done=false; break; }
+ }
+ // Single function user, so this constant is not shared
+ if( done ) {
+ if( con.in(0)==start ) i--;
+ con.setDef(0,fun);
+ break;
+ }
+ // Move function users to a private constant
+ Node con2 = mach.copy(); // Private constant clone
+ con2._inputs.set(0,null); // Preserve edge invariants from clone
+ con2.setDef(0,fun);
+ // Move function users to this private constant
+ for( int j=0; j<con._outputs._len; j++ ) {
+ Node use = con.out(j);
+ FunNode fun2 = use.cfg0().fun();
+ if( fun2==fun ) {
+ use.setDef(use._inputs.find(con),con2);
+ j--;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Visit all nodes in CFG Reverse Post-Order, essentially defs before uses
+ // (except at loops). Since defs are visited first - and hoisted as early
+ // as possible, when we come to a use we place it just after its deepest
+ // input.
+ private static void schedEarly(CodeGen code) {
+ // Reverse Post-Order on CFG
+ for( CFGNode cfg : code._cfg ) {
+ cfg.loopDepth();
+ for( Node n : cfg._inputs )
+ _schedEarly(n,code );
+ if( cfg instanceof RegionNode )
+ for( Node phi : cfg._outputs )
+ if( phi instanceof PhiNode )
+ _schedEarly(phi,code );
+ }
+ }
+
+ private static void _schedEarly(Node n, CodeGen code) {
+ if( n==null || code._visit.get(n._nid) ) return; // Been there, done that
+ assert !(n instanceof CFGNode);
+ code._visit.set(n._nid);
+ // Schedule not-pinned not-CFG inputs before self. Since skipping
+ // Pinned, this never walks the backedge of Phis (and thus spins around
+ // a data-only loop), eventually attempting relying on some pre-visited-
+ // not-post-visited data op with no scheduled control.
+ for( Node def : n._inputs )
+ if( def!=null && !(def instanceof PhiNode) )
+ _schedEarly(def,code);
+
+ // If not-pinned (e.g. constants, projections, phi) and not-CFG
+ if( !n.isPinned() ) {
+ // Schedule at deepest input
+ CFGNode early = code._start; // Maximally early, lowest idepth
+ if( n.in(0) instanceof CFGNode cfg ) early = cfg;
+ for( int i=1; i<n.nIns(); i++ )
+ if( n.in(i)!=null && n.in(i).cfg0().idepth() > early.idepth() )
+ early = n.in(i).cfg0(); // Latest/deepest input
+ n.setDef(0,early);
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ private static void schedLate( CodeGen code ) {
+ CFGNode[] late = new CFGNode[code.UID()];
+ Node[] ns = new Node[code.UID()];
+ // Breadth-first scheduling
+ breadth(code._stop,ns,late);
+
+ // Copy the best placement choice into the control slot
+ for( int i=0; i<late.length; i++ )
+ if( ns[i] != null && !(ns[i] instanceof ProjNode) )
+ ns[i].setDef(0,late[i]);
+ }
+
+ private static void breadth(Node stop, Node[] ns, CFGNode[] late) {
+ // Things on the worklist have some (but perhaps not all) uses done.
+ WorkList<Node> work = new WorkList<>();
+ work.push(stop);
+ Node n;
+ outer:
+ while( (n = work.pop()) != null ) {
+ assert late[n._nid]==null; // No double visit
+ // These I know the late schedule of, and need to set early for loops
+ if( n instanceof CFGNode cfg ) late[n._nid] = cfg.blockHead() ? cfg : cfg.cfg(0);
+ else if( n instanceof PhiNode phi ) late[n._nid] = phi.region();
+ else if( n instanceof ProjNode && n.in(0) instanceof CFGNode cfg ) late[n._nid] = cfg;
+ else {
+
+ // All uses done?
+ for( Node use : n._outputs )
+ if( use!=null && late[use._nid]==null )
+ continue outer; // Nope, await all uses done
+
+ // Loads need their memory inputs' uses also done
+ if( n instanceof MemOpNode ld && ld._isLoad )
+ for( Node memuse : ld.mem()._outputs )
+ if( late[memuse._nid]==null &&
+ // New makes new memory, never crushes load memory
+ !(memuse instanceof NewNode) &&
+ // Load-use directly defines memory
+ (memuse._type instanceof TypeMem ||
+ // Load-use indirectly defines memory
+ (memuse._type instanceof TypeTuple tt && tt._types[ld._alias] instanceof TypeMem)) )
+ continue outer;
+
+ // All uses done, schedule
+ _doSchedLate(n,ns,late);
+ }
+
+ // Walk all inputs and put on worklist, as their last-use might now be done
+ for( Node def : n._inputs )
+ if( def!=null && late[def._nid]==null ) {
+ work.push(def);
+ // if the def has a load use, maybe the load can fire
+ for( Node out : def._outputs )
+ if( out instanceof MemOpNode ld && ld._isLoad && late[ld._nid]==null )
+ work.push(ld);
+ }
+ }
+ }
+
+ private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late) {
+ // Walk uses, gathering the LCA (Least Common Ancestor) of uses
+ CFGNode early = n.in(0) instanceof CFGNode cfg ? cfg : n.in(0).cfg0();
+ assert early != null;
+ CFGNode lca = null;
+ for( Node use : n._outputs )
+ if( use != null )
+ lca = use_block(n,use, late)._idom(lca,null);
+
+ // Loads may need anti-dependencies, raising their LCA
+ if( n instanceof MemOpNode load && load._isLoad )
+ lca = find_anti_dep(lca,load,early,late);
+
+ // Walk up from the LCA to the early, looking for best place. This is
+ // the lowest execution frequency, approximated by least loop depth and
+ // deepest control flow.
+ CFGNode best = lca;
+ lca = lca.idom(); // Already found best for starting LCA
+ for( ; lca != early.idom(); lca = lca.idom() )
+ if( better(lca,best) )
+ best = lca;
+ assert !(best instanceof IfNode);
+ ns [n._nid] = n;
+ late[n._nid] = best;
+ }
+
+ // Block of use. Normally from late[] schedule, except for Phis, which go
+ // to the matching Region input.
+ private static CFGNode use_block(Node n, Node use, CFGNode[] late) {
+ if( !(use instanceof PhiNode phi) )
+ return late[use._nid];
+ CFGNode found=null;
+ for( int i=1; i<phi.nIns(); i++ )
+ if( phi.in(i)==n )
+ found = phi.region().cfg(i)._idom(found,null);
+
+ assert found!=null;
+ return found;
+ }
+
+
+ // Least loop depth first, then largest idepth
+ private static boolean better( CFGNode lca, CFGNode best ) {
+ return lca.loopDepth() < best.loopDepth() ||
+ lca instanceof NeverNode ||
+ (lca.idepth() > best.idepth() || best instanceof IfNode);
+ }
+
+ private static CFGNode find_anti_dep(CFGNode lca, MemOpNode load, CFGNode early, CFGNode[] late) {
+ // We could skip final-field loads here.
+ // Walk LCA->early, flagging Load's block location choices
+ for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ cfg._anti = load._nid;
+ // Walk load->mem uses, looking for Stores causing an anti-dep
+ for( Node mem : load.mem()._outputs ) {
+ switch( mem ) {
+ case MemOpNode st:
+ if( !st._isLoad ) {
+ assert late[mem._nid] != null;
+ lca = anti_dep( load, late[mem._nid], mem.cfg0(), lca, st );
+ }
+ break; // Loads do not cause anti-deps on other loads
+ case CallNode st:
+ assert late[st._nid]!=null;
+ lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st);
+ break;
+ case PhiNode phi:
+ // Repeat anti-dep for matching Phi inputs.
+ // No anti-dep edges but may raise the LCA.
+ for( int i=1; i<phi.nIns(); i++ )
+ if( phi.in(i)==load.mem() )
+ lca = anti_dep(load,phi.region().cfg(i),load.mem().cfg0(),lca,null);
+ break;
+ case NewNode st: break;
+ case ReturnNode ret: break; // Load must already be ahead of Return
+ case MemMergeNode ret: break; // Mem uses now on ScopeMin
+ case NeverNode never: break;
+ default: throw Utils.TODO();
+ }
+ }
+ return lca;
+ }
+
+ //
+ private static CFGNode anti_dep( MemOpNode load, CFGNode stblk, CFGNode defblk, CFGNode lca, Node st ) {
+ // Store and Load overlap, need anti-dependence
+ if( stblk._anti==load._nid ) {
+ lca = stblk._idom(lca,null); // Raise Loads LCA
+ if( lca == stblk && st != null && st._inputs.find(load) == -1 ) // And if something moved,
+ st.addDef(load); // Add anti-dep as well
+ return lca; // Cap this stores' anti-dep to here
+ }
+ return lca;
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/codegen/IFG.java b/chapter21/src/main/java/com/seaofnodes/simple/codegen/IFG.java
new file mode 100644
index 00000000..81955779
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/codegen/IFG.java
@@ -0,0 +1,551 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.Ary;
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.node.*;
+import java.util.BitSet;
+import java.util.IdentityHashMap;
+
+// Interference Graph
+abstract public class IFG {
+ // Interference Graph Support
+
+ // Map from a Basic Block to Live-Out: {a map from a Live Range to a Def}
+ private static final IdentityHashMap<CFGNode,IdentityHashMap<LRG,Node>> BBOUTS = new IdentityHashMap<>();
+ static void resetBBLiveOut() {
+ for( IdentityHashMap<LRG,Node> bbout : BBOUTS.values() )
+ bbout.clear();
+ }
+ static final IdentityHashMap<LRG,Node> TMP = new IdentityHashMap<>();
+
+ // Inteference Graph: Array of Bitsets
+ static final Ary<BitSet> IFG = new Ary<>(BitSet.class);
+ static void resetIFG() {
+ for( BitSet bs : IFG )
+ if( bs!=null ) bs.clear();
+ }
+
+ // Set matching bit
+ private static void addIFG( LRG lrg0, LRG lrg1 ) {
+ short x0 = lrg0._lrg, x1 = lrg1._lrg;
+ // Triangulate
+ if( x0 < x1 ) _addIFG(x0,x1);
+ else _addIFG(x1,x0);
+ }
+ // Add lrg1 to lrg0's conflict set
+ private static void _addIFG( short x0, short x1 ) {
+ BitSet ifg = IFG.atX(x0);
+ if( ifg==null )
+ IFG.setX(x0,ifg = new BitSet());
+ ifg.set(x1);
+ }
+
+
+ private static final Ary<CFGNode> WORK = new Ary<>(CFGNode.class);
+ static void pushWork(CFGNode bb) {
+ if( WORK.find(bb) == -1 )
+ WORK.push(bb);
+ }
+
+ // ------------------------------------------------------------------------
+ // Visit all blocks, using the live-out LRGs per-block and doing a backwards
+ // walk over each block. At the end of the walk, push the live-out sets to
+ // prior blocks. Due to loops, no single visitation order suffices. This
+ // problem can be fairly efficiently solved with a proper LIVE calculation
+ // and bitsets. In the interests of simplicity (and assumption of smaller
+ // programs) I am skipping LIVE and directly computing it during the IFG
+ // building.
+
+ // Start by setting the live-out of the exit block (to empty), and putting
+ // it on a worklist.
+
+ // - Pull from worklist a block who's live-out has changed
+ // - Walk backwards, adding interferences and computing live.
+ // - At block head, "push" the new live-out to prior blocks.
+ // - - If they pick up new live-outs, put them on the worklist.
+
+ public static boolean build( int round, RegAlloc alloc ) {
+ // Reset all to empty
+ resetBBLiveOut();
+ resetIFG();
+
+ // Last block has nothing live out
+ assert WORK.isEmpty();
+ for( CFGNode bb : alloc._code._cfg )
+ if( bb.blockHead())
+ WORK.push( bb );
+
+ // Process blocks until no more changes
+ while( !WORK.isEmpty() )
+ do_block(round,alloc,WORK.pop());
+
+ if( alloc.success() )
+ convert2DAdjacency(alloc);
+ return alloc.success();
+ }
+
+ // Walk one block backwards, compute live-in from live-out, and build IFG
+ static void do_block( int round, RegAlloc alloc, CFGNode bb ) {
+ assert bb.blockHead();
+ IdentityHashMap<LRG,Node> live_out = BBOUTS.get(bb);
+ TMP.clear();
+ if( live_out != null )
+ TMP.putAll(live_out); // Copy bits to temp
+
+ // A backwards walk over instructions in the basic block
+ for( int inum = bb.nOuts()-1; inum >= 0; inum-- ) {
+ Node n = bb.out(inum);
+ if( n.in(0) != bb ) continue;
+ // In a backwards walk, proj users come before the node itself
+ if( n instanceof MultiNode )
+ for( Node proj : n.outs() )
+ if( proj instanceof ProjNode )
+ do_node(alloc,proj);
+ if( n instanceof MachNode )
+ do_node(alloc,n);
+ }
+
+ // The bb head kills register, e.g. a CallEnd and caller-save registers
+ if( bb instanceof MachNode mach )
+ kills(alloc,mach);
+
+ // Push live-sets backwards to priors in CFG.
+ if( bb instanceof RegionNode )
+ for( int i=1; i<bb.nIns(); i++ )
+ mergeLiveOut(alloc,bb,i);
+ else
+ mergeLiveOut(alloc,bb,0);
+ }
+
+ private static void do_node(RegAlloc alloc, Node n) {
+ // Defining means killing live LRG
+ LRG lrg = alloc.lrg(n);
+ if( lrg != null ) {
+ // Check for def-side self-conflict live ranges. These must split,
+ // and only happens during the first round a particular LRG splits.
+ selfConflict(alloc,n,lrg);
+ TMP.remove(lrg); // Kill def
+ }
+
+ // Phis use and define the same live range, i.e. these LRGs already
+ // marked conflicted, no need to mark again
+ if( n instanceof PhiNode )
+ return;
+
+ // Kill any killed registers; milli-code routines like New can kill
+ // will not being a CFG.
+ if( n instanceof MachNode m )
+ kills(alloc,m);
+
+ // Interfere n with all live
+ if( lrg!=null ) {
+ // Interfere n with all live
+ for( LRG tlrg : TMP.keySet() ) {
+ assert tlrg.leader();
+ // Skip self
+ if( lrg != tlrg &&
+ // And register sets overlap
+ lrg._mask.overlap(tlrg._mask) )
+ // Then tlrg and lrg interfere. If lrg needs the single
+ // last tlrg register at some point, either tlrg or lrg
+ // must fail. If *n* (a subset of lrg) needs the single
+ // last tlrg register then only tlrg must fail.
+ if( ((MachNode)n).outregmap().size1() ) {
+ if( !tlrg.clr(lrg._mask.firstReg()) ) // Clear bit, no interference
+ alloc.fail(tlrg); // Clearing drives mask to empty
+ } else addIFG(lrg,tlrg); // Add interference
+ }
+ }
+
+
+ // Check for self-conflict live ranges. These must split, and only
+ // happens during the first round when a particular LRG splits.
+ // Also record use-side spills for biased coloring.
+ // Then make all inputs live.
+ for( int i=1; i<n.nIns(); i++ ) {
+ Node def = n.in(i);
+ if( def==null ) continue;
+ LRG lrg1 = alloc.lrg(def);
+ if( lrg1==null ) continue; // Anti-dependence, etc, no register
+
+ // Check use-side for self-conflict; if so we MUST split
+ selfConflict(alloc,def,lrg1);
+
+ // Look for a must-use single register conflicting with some other must-def.
+ if( n instanceof MachNode m ) {
+ RegMask ni_mask = m.regmap(i);
+ if( ni_mask!=null && ni_mask.size1() ) { // Must-use single register
+ // Search all current live
+ for( LRG tlrg : TMP.keySet() ) {
+ assert tlrg.leader();
+ Node live = TMP.get(tlrg);
+ if( live != def && live instanceof MachNode lmach && lmach.outregmap().overlap(ni_mask) ) {
+ // Deny the register, since it absolutely must be used here
+ if( !tlrg.clr(ni_mask.firstReg()) )
+ // Then direct reg-reg conflict between use here (at n.in(i)) and def (of tlrg) there.
+ // Fail the older live range, it must move its register.
+ alloc.fail( tlrg );
+ }
+ }
+ }
+ }
+
+ // All inputs live - except in self conflicts, where we keep the prior def alive
+ // until it goes.
+ TMP.put(lrg1,def);
+ }
+ }
+
+ // Single-register defines *must* have their register; other live ranges
+ // *must* avoid this register, so instead of interfering we remove the
+ // single register from the other rmask.
+ private static void kills( RegAlloc alloc, MachNode m ) {
+ RegMask killMask = m.killmap();
+ if( killMask==null ) return;
+ // Kill registers with all live
+ for( LRG tlrg : TMP.keySet() ) {
+ assert tlrg.leader();
+ // Always, tlrg cannot use kills
+ if( tlrg._mask.overlap(killMask) ) {
+ // Disallow clone-ables from killing registers. Just fail
+ // them and re-clone closer to target... so no kill.
+ // Special case for Intel XOR used to zero.
+ Node n = (Node)m;
+ CFGNode effUseBlk = n.out(0) instanceof PhiNode phi ? phi.region().cfg(phi._inputs.find(n)) : n.out(0).cfg0();
+ if( m.isClone() && // Must be clonable
+ (n.nOuts()>1 || // Has many users OR
+ // Only 1 user but effective use is remote block
+ effUseBlk != n.cfg0() ))
+ // Then fail the clonable; it should split or move
+ alloc.fail(alloc.lrg((Node)m));
+ // Else clonable cannot move
+ else if( !tlrg.sub(killMask) )
+ alloc.fail(tlrg);
+ }
+ }
+ }
+
+ // Check for self-conflict live ranges. These must split, and only happens
+ // during the first round a particular LRG splits.
+ private static void selfConflict(RegAlloc alloc, Node n, LRG lrg) {
+ selfConflict(alloc,n,lrg,TMP.get(lrg));
+ }
+ private static void selfConflict(RegAlloc alloc, Node n, LRG lrg, Node prior) {
+ if( prior!=null && prior != n ) {
+ lrg.selfConflict(prior);
+ lrg.selfConflict(n);
+ alloc.fail(lrg); // 2 unrelated values live at once same live range; self-conflict
+ }
+ }
+
+ // Merge TMP into bb's live-out set; if changes put bb on WORK
+ private static void mergeLiveOut( RegAlloc alloc, CFGNode priorbb, int i ) {
+ CFGNode bb = priorbb.cfg(i);
+ if( bb == null ) return; // Start has no prior
+ if( !bb.blockHead() ) bb = bb.cfg0();
+ //if( i==0 && !(bb instanceof StartNode) ) bb = bb.cfg0();
+ assert bb.blockHead();
+
+ // Lazy get live-out set for bb
+ IdentityHashMap<LRG, Node> lrgs = BBOUTS.computeIfAbsent( bb, k -> new IdentityHashMap<>() );
+
+ for( LRG lrg : TMP.keySet() ) {
+ Node def = TMP.get(lrg);
+ // Effective def comes from phi input from prior block
+ if( def instanceof PhiNode phi && phi.cfg0()==priorbb ) {
+ assert i!=0;
+ def = phi.in( i );
+ }
+ Node def_bb = lrgs.get(lrg);
+ if( def_bb==null ) {
+ lrgs.put(lrg,def);
+ pushWork(bb);
+ } else {
+ // Alive twice with different definitions; self-conflict
+ selfConflict(alloc,def,lrg,def_bb);
+ }
+ }
+ }
+
+
+ static void convert2DAdjacency( RegAlloc alloc ) {
+ // Convert the 2-D array of bits (a 1-D array of BitSets) into an
+ // adjacency matrix.
+ int maxlrg = alloc._LRGS.length;
+ for( int i=1; i<maxlrg; i++ ) {
+ BitSet ifg = IFG.atX(i);
+ if( ifg != null ) {
+ LRG lrg0 = alloc._LRGS[i];
+ for( int lrg = ifg.nextSetBit(0); lrg>=0; lrg=ifg.nextSetBit(lrg+1) ) {
+ LRG lrg1 = alloc._LRGS[lrg];
+ lrg0.addNeighbor(lrg1);
+ lrg1.addNeighbor(lrg0);
+ }
+ }
+ }
+ }
+
+
+ // ------------------------------------------------------------------------
+ // Color the inference graph.
+
+ // Coloring works by removing "trivial" live ranges from the IFG - those
+ // live ranges with fewer neighbors than available colors (registers).
+ // These are trivial because even if every neighbor takes a unique color,
+ // there's at least one more available to color this live range.
+
+ // If we hit a clique of non-trivial live ranges, we pick one to be "at
+ // risk" of not-coloring. Good choices include live ranges with a large
+ // area and few hot uses.
+
+ // Then we reverse and put-back live ranges - picking a color as we go.
+ // If there's no spare color we'll have to spill this at-risk live range.
+
+ public static boolean color(int round, RegAlloc alloc) {
+ int maxlrg = alloc._LRGS.length, nlrgs=0;
+ for( int i=1; i<maxlrg; i++ )
+ if( alloc._LRGS[i] != null )
+ nlrgs++;
+
+ // Simplify
+
+ // Walk all the LRGS looking for some trivial ones to start with.
+ // During this pass all LRGS are broken into 3 disjoint sets:
+ // - trivial, removed from IFG; color_stack[0 to sptr]
+ // - trivial, not (yet) removed from IFG; color_stack[sptr to swork]
+ // - unknown; color_stack[work to maxlrg]
+
+ // Gather all not-unified (not-null); separate trivial and non-trivial set.
+ int sptr = 0, swork = 0;
+ LRG[] color_stack = new LRG[nlrgs];
+ for( int i=0, j=0; i<maxlrg; i++ ) {
+ LRG lrg = alloc._LRGS[i];
+ if( lrg==null ) continue; // Unified lrgs are null here
+ color_stack[j++] = lrg;
+ if( lrg.lowDegree() ) swap(color_stack, swork++, j-1);
+ }
+
+ // Pull all lrgs from IFG, in trivial order if possible
+ while( sptr < color_stack.length ) {
+ // Swap best color up front
+ pickColor(color_stack,sptr,swork);
+
+ // Pick a trivial lrg, and (temporarily) remove from the IFG.
+ LRG lrg = color_stack[sptr++];
+ // If sptr was swork, then pulled an at-risk lrg
+ if( sptr > swork )
+ swork = sptr;
+
+ // Walk all neighbors and remove
+ if( lrg._adj != null ) {
+ for( LRG nlrg : lrg._adj ) {
+ // Remove and compress out neighbor
+ if( nlrg.removeCompress(lrg) ) {
+ // Neighbor is just exactly going trivial as 'lrg' is removed from IFG
+ // Find "j" position in the color_stack
+ int jx = swork; while( color_stack[jx] != nlrg ) jx++;
+ // Add trivial neighbor to trivial list. Pull lrg j out of
+ // unknown set, since its now in the trivial set
+ swap(color_stack, swork++, jx);
+ }
+ }
+ }
+ }
+
+ // Reverse simplify (unstack the color stack), and set colors (registers) for live ranges
+ while( sptr > 0 ) {
+ LRG lrg = color_stack[--sptr];
+ if( lrg==null ) continue;
+ RegMaskRW rmask = lrg._mask.copy();
+ // Walk neighbors and remove adjacent colors
+ if( lrg._adj!=null ) {
+ for( LRG nlrg : lrg._adj ) {
+ nlrg.reinsert(lrg);
+ int reg = nlrg._reg;
+ if( reg!= -1 ) // Failed neighbors do not count
+ rmask.clr(reg); // Remove neighbor from my choices
+ }
+ }
+ // At-risk live-range did not color?
+ if( rmask.isEmpty() ) {
+ alloc.fail(lrg);
+ lrg._reg = -1;
+ } else {
+ // Pick first available register
+ short reg = rmask.firstReg();
+ // Pick a "good" color from the choices. Typically, biased-coloring
+ // removes some over-spilling.
+ if( rmask.size() > 1 ) reg = biasColor(alloc,lrg,reg,rmask);
+ lrg._reg = reg; // Assign the register
+ }
+ }
+
+ return alloc.success();
+ }
+
+ // Pick LRG from color stack
+ private static void pickColor(LRG[] color_stack, int sptr, int swork) {
+ // Out of trivial colorable, pick an at-risk to pull
+ if( sptr==swork )
+ swap(color_stack,sptr,pickRisky(color_stack,sptr));
+ // When coloring, we'd like to give more choices; so when coloring we'd
+ // like to see the single-def first (since no choices anyway), then
+ // non-split related (so more live ranges get colored), then
+ // split-related last, so they have more colors to bias towards.
+
+ // Working in reverse, pick first split-related with many regs, then
+ // those with some regs, then single-def.
+ int bidx=sptr;
+ LRG best=color_stack[bidx];
+ for( int idx = sptr+1; idx < swork; idx++ )
+ if( betterLRG(best,color_stack[idx]) )
+ best = color_stack[bidx=idx];
+ if( bidx != sptr )
+ swap(color_stack,sptr,bidx); // Pick best at sptr
+ }
+
+ private static boolean betterLRG( LRG best, LRG lrg ) {
+ // If single-def varies, keep the not-single-def
+ if( best.size1() != lrg.size1() )
+ return best.size1();
+ // If hasSplit varies, keep the hasSplit
+ if( best.hasSplit() != lrg.hasSplit() )
+ return lrg.hasSplit();
+ // Keep large register count
+ return best.size() < lrg.size();
+ }
+
+ // Pick a live range that hasn't already spilled, or has a single-def-
+ // single-use that are not adjacent.
+ private static int pickRisky( LRG[] color_stack, int sptr ) {
+ int best=sptr;
+ int bestScore = pickRiskyScore(color_stack[best]);
+ for( int i=sptr+1; i<color_stack.length; i++ ) {
+ if( bestScore == 1000000 ) return best; // Already max score
+ int iScore = pickRiskyScore(color_stack[i]);
+ if( iScore > bestScore )
+ { best = i; bestScore = iScore; }
+ }
+ return best;
+ }
+
+ // Pick a live range to pull, that might not color.
+ //
+ // Picking a live range with a very large span, with defs and uses outside
+ // loops means spilling a relative cheap live range and getting that
+ // register over a large area.
+ //
+ // Picking a live range that is very close to coloring might allow it to
+ // color despite being risky.
+ private static int pickRiskyScore( LRG lrg ) {
+ // Pick single-def clonables that are not right next to their single-use.
+ // Failing to color these will clone them closer to their uses.
+ if( !lrg._multiDef && lrg._machDef.isClone() ) {
+ Node def = ((Node)lrg._machDef);
+ Node use = ((Node)lrg._machUse);
+ CFGNode cfg = def.cfg0();
+ if( cfg != use.cfg0() || // Different blocks OR
+ // Same block, but not close
+ cfg._outputs.find(def) < cfg._outputs.find(use)+1 )
+ return 1000000;
+ }
+
+ // Always pick callee-save registers as being very large area recovered
+ // and very cheap to spill.
+ if( lrg._machDef instanceof CalleeSaveNode )
+ return 1000000-2-lrg._mask.firstReg();
+ if( lrg._splitDef != null && lrg._splitDef. in(1) instanceof CalleeSaveNode &&
+ lrg._splitUse != null && lrg._splitUse.out(0) instanceof ReturnNode )
+ return 1000000-1;
+
+ // TODO: cost/benefit model. Perhaps counting loop-depth (freq) of def/use for cost
+ // and "area" for benefit
+ return 1000;
+ }
+
+ private static short biasColor( RegAlloc alloc, LRG lrg, short reg, RegMask mask ) {
+ if( mask.size1() ) return reg;
+ // Check chain of splits up the def-chain. Take first allocated
+ // register, and if it's available in the mask, take it.
+ Node def = lrg._splitDef, use = lrg._splitUse;
+ int tidx=0, cnt=0;
+
+ while( def != null || use != null ) {
+ if( cnt++ > 10 ) break;
+
+ if( def != null ) {
+ short bias = biasColor( alloc, def, mask );
+ if( bias >= 0 ) return bias; // Good bias
+ if( bias == -2 ) def = null; // Kill this side, no more searching
+ else if( (tidx=biasable(def)) == 0 ) def = null;
+ }
+
+ if( use != null ) {
+ short bias = biasColor( alloc, use, mask );
+ if( bias >= 0 ) return bias; // Good bias
+ if( bias == -2 ) use = null; // Kill this side, no more searching
+ else if( biasable(use)==0 ) use = null;
+ }
+
+ if( def != null ) {
+ short bias = biasColorNeighbors( alloc, def, mask );
+ if( bias >= 0 ) return bias;
+ // Advance def side
+ def = def.in(tidx);
+ if( alloc.lrg(def)==null ) def=null;
+ }
+
+ if( use != null ) {
+ short bias = biasColorNeighbors( alloc, use, mask );
+ if( bias >= 0 ) return bias;
+ use = use.out(0);
+ if( biasable(use)==0 ) use=null;
+ }
+
+ }
+ return mask.firstReg();
+ }
+
+ private static int biasable(Node split) {
+ if( split instanceof SplitNode ) return 1; // Yes biasable, advance is slot 1
+ if( split instanceof PhiNode phi ) return phi.region() instanceof LoopNode ? 2 : 1; // Yes biasable, advance is slot 1
+ if( !(split instanceof MachNode mach) ) return 0; // Not biasable
+ return mach.twoAddress(); // Only biasable if 2-addr
+ }
+
+ // 3-way return:
+ // - good bias reg, take it & exit
+ // - this path is cutoff; do not search here anymore
+ // - advance this side
+ private static short biasColor( RegAlloc alloc, Node split, RegMask mask ) {
+ short bias = alloc.lrg(split)._reg;
+ if( bias != -1 ) {
+ if( mask.test(bias) ) return bias; // Good bias
+ else return -2; // Kill this side
+ } else return -1; // Advance this side
+ }
+
+ // Check if we can match "split" color, or else trim "mask" to colors
+ // "split" might get.
+ private static short biasColorNeighbors( RegAlloc alloc, Node split, RegMask mask ) {
+ LRG slrg = alloc.lrg(split);
+ if( slrg._adj == null ) return -1; // No trimming
+
+ // Can I limit my own choices to valid neighbor choices?
+ for( LRG alrg : slrg._adj ) {
+ short reg = alrg._reg;
+ if( reg == -1 && alrg._mask.size1() )
+ reg = alrg._mask.firstReg();
+ if( reg != -1 ) {
+ mask.clr(alrg._reg);
+ if( mask.size1() )
+ return mask.firstReg();
+ }
+ }
+ return -1; // No obvious color choice, but mask got trimmed
+ }
+
+ private static void swap( LRG[] ary, int x, int y ) {
+ LRG tmp = ary[x]; ary[x] = ary[y]; ary[y] = tmp;
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/codegen/LRG.java b/chapter21/src/main/java/com/seaofnodes/simple/codegen/LRG.java
new file mode 100644
index 00000000..ec254a2e
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/codegen/LRG.java
@@ -0,0 +1,234 @@
+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 java.util.IdentityHashMap;
+
+/** A Live Range
+ * <p>
+ * A live range is a set of nodes and edges which must get the same register.
+ * Live ranges form an interconnected web with almost no limits on their
+ * shape. Live ranges also gather the set of register constraints from all
+ * their parts.
+ * <p>
+ * Most of the fields in this class are for spill heuristics, but there are a
+ * few key ones that define what a LRG is. LRGs have a unique dense integer
+ * `_lrg` number which names the LRG. New `_lrg` numbers come from the
+ * `RegAlloc._lrg_num` counter. LRGs can be unioned together -
+ * (<a href="https://en.wikipedia.org/wiki/Disjoint-set_data_structure">this is the Union-Find algorithm</a>)
+ * - and when this happens the lower numbered `_lrg` wins. Unioning only
+ * happens during `BuildLRG` and happens because either a `Phi` is forcing all
+ * its inputs and outputs into the same register, or because of a 2-address
+ * instruction. LRGs have matching `union` and `find` calls, and a set
+ * `_leader` field.
+ */
+public class LRG {
+
+ // Dense live range numbers
+ final short _lrg;
+
+ // U-F leader; null if leader
+ LRG _leader;
+
+ // Choosen register
+ short _reg;
+
+ // Count of single-register defs and uses
+ short _1regDefCnt, _1regUseCnt;
+ // Count of all defs, uses. Mostly interested 1 vs many
+ boolean _multiDef, _multiUse;
+
+ // A sample MachNode def in the live range
+ public MachNode _machDef, _machUse;
+ short _uidx; // _machUse input
+
+ // Some splits used in biased coloring
+ MachConcreteNode _splitDef, _splitUse;
+
+ // All the self-conflicting defs for this live range
+ IdentityHashMap<Node,String> _selfConflicts;
+
+ // AND of all masks involved; null if none have been applied yet
+ RegMask _mask;
+
+ // Adjacent Live Range neighbors. Only valid during coloring
+ Ary<LRG> _adj;
+
+ public int nadj() { return _adj==null ? 0 : _adj._len; }
+
+ void addNeighbor(LRG lrg) {
+ if( _adj==null ) _adj = new Ary<>(LRG.class);
+ _adj.push(lrg);
+ }
+
+ // Remove and compress neighbor list; store ex-neighbor past
+ // end for unwinding Simplify. True if exactly going low-degree
+ boolean removeCompress( LRG lrg ) {
+ _adj.swap(_adj.find(lrg),_adj._len-1);
+ return _adj._len-- == _mask.size();
+ }
+
+ void reinsert( LRG lrg ) {
+ assert _adj._es[_adj._len]==lrg;
+ _adj._len++;
+ }
+
+ // More registers than neighbors
+ boolean lowDegree() { return nadj() < _mask.size(); }
+
+ LRG( short lrg ) { _lrg = lrg; _reg = -1; }
+
+ boolean leader() { return _leader == null; }
+
+ // Fast-path Find from the Union-Find algorithm
+ LRG find() {
+ if( _leader==null ) return this; // I am the leader
+ if( _leader._leader==null ) // I point to the leader
+ return _leader;
+ return _rollup();
+ }
+ // Slow-path rollup of U-F
+ private LRG _rollup() {
+ LRG ldr = _leader._leader;
+ // Roll-up
+ while( ldr._leader!=null ) ldr = ldr._leader;
+ LRG l2 = this;
+ while( l2 != ldr ) {
+ LRG l3 = l2._leader;
+ l2._leader = ldr;
+ l2 = l3;
+ }
+ return ldr;
+ }
+
+ // Union `this` and `lrg`, keeping the lower numbered _lrg.
+ // Includes a number of fast-path cutouts.
+ LRG union( LRG lrg ) {
+ assert leader();
+ if( lrg==null ) return this;
+ lrg = lrg.find();
+ if( lrg==this ) return this;
+ return _lrg < lrg._lrg ? _union(lrg) : lrg._union(this);
+ }
+ // Union `this` and `lrg`, folding together all stats.
+ private LRG _union( LRG lrg ) {
+ // Set U-F leader
+ lrg._leader = this;
+ // Fold together stats
+ if( _machDef==null ) {
+ _machDef = lrg._machDef;
+ } else if( lrg._machDef!=null ) {
+ if( _machDef != lrg._machDef ) _multiDef=true;
+ if( _1regDefCnt==0 )
+ _machDef = lrg._machDef;
+ else if( _machDef==lrg._machDef )
+ _1regDefCnt--;
+ }
+ _1regDefCnt += lrg._1regDefCnt;
+ _multiDef |= lrg._multiDef;
+
+ if( _machUse==null ) {
+ _machUse = lrg._machUse;
+ _uidx = lrg._uidx;
+ } else if( lrg._machUse!=null ) {
+ if( _machUse != lrg._machUse ) _multiUse=true;
+ if( _1regUseCnt==0 ) {
+ _machUse = lrg._machUse;
+ _uidx = lrg._uidx;
+ }
+ else if( _machUse==lrg._machUse )
+ _1regUseCnt--;
+ }
+ _1regUseCnt += lrg._1regUseCnt;
+ _multiUse |= lrg._multiUse;
+
+ // Fold deepest Split
+ _splitDef = deepSplit(_splitDef,lrg._splitDef);
+ _splitUse = deepSplit(_splitUse,lrg._splitUse);
+
+ // Fold together masks
+ RegMask mask = _mask.and(lrg._mask);
+ if( mask==null )
+ mask = _mask.copy().and(lrg._mask);
+ _mask = mask;
+ return this;
+ }
+
+ private static MachConcreteNode deepSplit( MachConcreteNode s0, MachConcreteNode s1 ) {
+ return s0==null || (s1!=null && s0.cfg0().loopDepth() < s1.cfg0().loopDepth()) ? s1 : s0;
+ }
+
+ // Record any Mach def for spilling heuristics
+ LRG machDef( MachNode def, boolean size1 ) {
+ if( _machDef!=null && _machDef!=def )
+ _multiDef = true;
+ if( _machDef==null || size1 )
+ _machDef = def;
+ if( size1 )
+ _1regDefCnt++;
+ if( def instanceof SplitNode split )
+ _splitDef = deepSplit(_splitDef,split);
+ return this;
+ }
+
+ // Record any Mach use for spilling heuristics
+ LRG machUse( MachNode use, short uidx, boolean size1 ) {
+ if( _machUse!=null && _machUse!=use )
+ _multiUse = true;
+ if( _machUse==null || size1 )
+ { _machUse = use; _uidx = uidx; }
+ if( size1 )
+ _1regUseCnt++;
+ if( use instanceof SplitNode split )
+ _splitUse = deepSplit(_splitUse,split);
+ return this;
+ }
+
+ boolean hasSplit() { return _splitDef != null || _splitUse != null; }
+ short size() { return _mask.size(); }
+ boolean size1() { return _mask.size1(); }
+
+
+ void selfConflict( Node def ) {
+ if( _selfConflicts == null ) _selfConflicts = new IdentityHashMap<>();
+ _selfConflicts.put(def,"");
+ }
+
+
+ // Record intersection of all register masks.
+ // True if still has registers
+ boolean and( RegMask mask ) {
+ RegMask mask2 = mask.and(_mask);
+ if( mask2==null )
+ mask2 = _mask.copy().and(mask);
+ _mask = mask2;
+ return !_mask.isEmpty();
+ }
+ // Remove this singular register
+ // True if still has registers
+ boolean clr( int reg ) {
+ if( _mask.clr(reg) ) return true;
+ _mask = _mask.copy(); // Need a mutable copy
+ return _mask.clr(reg);
+ }
+ // Subtract mask (AND complement)
+ // True if still has registers
+ boolean sub( RegMask mask ) {
+ RegMask mask2 = _mask.sub(mask);
+ if( mask2==null )
+ mask2 = _mask.copy().sub(mask);
+ _mask = mask2;
+ return !_mask.isEmpty();
+ }
+
+ @Override public String toString() { return toString(new SB()).toString(); }
+
+ public SB toString( SB sb ) {
+ sb.p("V").p(_lrg);
+ if( _mask!=null ) _mask.toString(sb);
+ return sb;
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java b/chapter21/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java
new file mode 100644
index 00000000..e1e849e6
--- /dev/null
+++ b/chapter21/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/chapter21/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java b/chapter21/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java
new file mode 100644
index 00000000..de4fadc4
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java
@@ -0,0 +1,261 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.Ary;
+import com.seaofnodes.simple.IterPeeps.WorkList;
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import java.util.*;
+
+public abstract class ListScheduler {
+
+ // eXtra per-node stuff for scheduling.
+ // Nodes are all block-local, and projections fold into their parent.
+ private static final IdentityHashMap<Node,XSched> XS = new IdentityHashMap<>();
+ private static class XSched {
+
+ Node _n; // Node this extra is for
+ int _bcnt; // Not-ready not-scheduled inputs
+ int _rcnt; // Ready not-scheduled inputs
+ boolean _ruse; // Node IS a "remote use", uses a other-block value
+ boolean _rdef; // Node IS a "remote def" of a value used in other block
+ boolean _single; // Defines a single register, likely to conflict
+
+ static final Ary<XSched> FREE = new Ary<>(XSched.class);
+ static void alloc(CFGNode bb, Node n) {
+ XSched x = FREE.pop();
+ XS.put(n, (x==null ? new XSched() : x).init(bb,n));
+ }
+ void free() { _n = null; FREE.push(this); }
+
+ static XSched get(Node n) { return n==null ? null : XS.get(n instanceof ProjNode && !(n.in(0) instanceof CallEndNode) ? n.in(0) : n); }
+
+ private XSched init(CFGNode bb, Node n) {
+ _n = n;
+ _bcnt = 0;
+ _ruse = _rdef = _single = false;
+
+ // Count block-locals and remote inputs
+ if( !(n instanceof PhiNode) )
+ for( Node def : n._inputs )
+ if( def != null && def != bb ) {
+ if( def.cfg0()==bb ) _bcnt++; // Raise block-local input count
+ else if( !def.isMem() ) _ruse = true; // Remote register user
+ }
+ // Count remote outputs and allowed registers
+ if( n instanceof MultiNode )
+ for( Node use : n._outputs )
+ computeSingleRDef(bb,use);
+ else
+ computeSingleRDef(bb,n);
+
+ if( n instanceof NewNode || n instanceof CallNode )
+ bb.fun()._hasCalls = true;
+ return this;
+ }
+
+ private void computeSingleRDef(CFGNode bb, Node n) {
+ // See if this is 2-input, and that input is single-def
+ if( n instanceof MachNode mach && mach.twoAddress() != 0 ) {
+ XSched xs = XS.get(n.in(mach.twoAddress()));
+ if( xs != null )
+ _single = xs._single;
+ }
+ // Visit all outputs
+ RegMaskRW rmask = !_single && n instanceof MachNode mach && mach.outregmap() != null ? mach.outregmap().copy() : null;
+ for( Node use : n._outputs ) {
+ // Remote use, so this is a remote def
+ if( use!=null && use.cfg0()!=bb ) _rdef = true;
+ // And mask between n's def mask and all n's users masks'
+ if( use instanceof MachNode mach && rmask != null )
+ for( int i=1; i<use.nIns(); i++ )
+ if( use.in(i)==n )
+ rmask.and(mach.regmap(i));
+ }
+ // Defines in a single register
+ _single |= rmask!=null && rmask.size1();
+ }
+
+ // If _bcnt==0, declare ready; move user bcnts into rcnts.
+ boolean isReady() {
+ if( _bcnt > 0 || _rcnt > 0 ) return false;
+ if( _n instanceof MultiNode )
+ for( Node use : _n._outputs )
+ if( use instanceof ProjNode ) readyUp(use);
+ // anti-dep from a multireg directly
+ else _readyUp(use);
+ else
+ readyUp(_n);
+ return true;
+ }
+ private static void readyUp(Node n) {
+ for( Node use : n._outputs )
+ _readyUp(use);
+ }
+ private static void _readyUp(Node use) {
+ XSched xs = get(use);
+ if( xs!=null && !(use instanceof PhiNode) )
+ { assert xs._bcnt>0; xs._bcnt--; xs._rcnt++; }
+ }
+
+ boolean decIsReady() {
+ assert _rcnt > 0;
+ _rcnt--;
+ return isReady();
+ }
+ }
+
+
+ // List schedule every block
+ public static void sched( CodeGen code ) {
+ XS.clear();
+ for( CFGNode bb : code._cfg )
+ if( bb.blockHead() )
+ local(bb,false);
+ }
+
+ // Classic list scheduler
+ private static void local( CFGNode bb, boolean trace ) {
+ assert XS.isEmpty();
+ int len = bb.nOuts();
+
+ // Count block-locals, remote inputs and outputs
+ for( int i=0; i<len; i++ )
+ XSched.alloc(bb,bb.out(i));
+
+ // Nodes are ready if they are not used by other nodes in this block.
+ // Move ready up front.
+ int ready = 0;
+ for( int i=0; i<len; i++ )
+ if( XSched.get(bb.out(i)).isReady() ) // No block-local inputs?
+ bb._outputs.swap(ready++,i); // Move into ready set
+ assert len==0 || ready > 0;
+
+ // Classic list scheduler. Behind sched is scheduled. Between sched
+ // and ready have zero counts and are ready to schedule. Ahead of
+ // ready is undefined.
+ //
+ // As nodes are scheduled, the nodes they use decrement their bcnts[]. Once
+ // that gets to 0, all the nodes using it are scheduled, so it's ready.
+ int sched = 0;
+ while( sched < len ) {
+ assert sched < ready;
+ int pick = best(bb._outputs,sched,ready,trace); // Pick best
+ Node best = bb._outputs.swap(pick,sched++); // Swap best to head of ready and move into the scheduled set
+ // Lower ready count of users, and since projections are ready as
+ // soon as their multi is ready, lower those now too.
+ for( Node use : best._outputs )
+ if( use instanceof ProjNode )
+ for( Node useuse : use._outputs )
+ ready = ready(bb,useuse,ready);
+ else
+ ready = ready(bb,use,ready);
+ }
+
+ // Reset for next block
+ for( XSched x : XS.values() )
+ x.free();
+ XS.clear();
+ }
+
+ private static int ready(CFGNode bb, Node use, int ready) {
+ if( use!=null && use.in(0)==bb && !(use instanceof PhiNode) && XSched.get(use).decIsReady() )
+ bb._outputs.set(ready++, use); // Became ready, move into ready set
+ //bb._outputs.swap(ready++,use); // Became ready, move into ready set
+ return ready;
+ }
+
+
+ // Pick best between sched and ready.
+ private static int best( Ary<Node> blk, int sched, int ready, boolean trace ) {
+ int pick = sched;
+ Node p = blk.at(pick);
+ int score = score(p);
+ if( trace ) { System.out.printf("%4d N%d ",score,p._nid); System.out.println(p); }
+ for( int i=sched+1; i<ready; i++ ) {
+ Node n = blk.at(i);
+ int nscore = score(n);
+ if( trace ) { System.out.printf("%4d N%d ",nscore,n._nid); System.out.println(n); }
+ if( nscore > score )
+ { score = nscore; pick = i; p = n; }
+ }
+ if( trace ) { System.out.printf("Pick: N%d %s\n\n",p._nid,p); }
+ return pick;
+ }
+
+ // Highest score wins. Max at 1000, min at 10, except specials.
+ private static final int[] CNT = new int[3];
+ static int score( Node n ) {
+ if( n instanceof ProjNode ) return 1001; // Pinned at block entry
+ if( n instanceof CProjNode ) return 1001; // Pinned at block entry
+ if( n instanceof PhiNode ) return 1000;
+ if( n instanceof CFGNode ) return 1; // Pinned at block exit
+
+ int score = 500;
+ // Ideal nodes ignore register pressure scheduling
+ if( !(n instanceof MachNode) )
+ return score;
+
+ // Register pressure local scheduling: avoid overlapping specialized
+ // registers usages.
+
+ // Subtract 10 (delay) if this op forces a live range to exist that
+ // cannot be resolved immediately; i.e., live count goes up. Scale to
+ // 20 if multi-def.
+
+ // Subtract 100 if this op forces a single-def live range to exist
+ // which might conflict on the same register with other live ranges.
+ // Defines a single register based on def & uses, and the output is not
+ // ready. Scale to 200 for multi-def.
+ CNT[1]=CNT[2]=0;
+ XSched xn = XSched.get(n);
+ // If defining a remote value, just generically stall alot. Value is
+ // used in a later block, can we delay until the end of this block?
+ if( xn._rdef ) score = 200; // If defining a remote value, just generically stall alot
+ boolean flags = false;
+ if( n instanceof MultiNode ) {
+ for( Node use : n._outputs )
+ flags |= singleUseNotReady( use, xn._single );
+ } else
+ flags |= singleUseNotReady( n, xn._single );
+ score += -10 * Math.min( CNT[1], 2 );
+ score += -100 * Math.min( CNT[2], 2 );
+ if( flags ) return 10;
+
+ // Add 10 if this op ends a single-def live range, add 20 for 2 or more.
+ // Scale to 100,200 if the single-def is also single-register.
+ CNT[1]=CNT[2]=0;
+ for( int i=1; i<n.nIns(); i++ ) {
+ XSched xd = XSched.get( n.in(i) );
+ if( xd != null && n.in(i).nOuts()==1 )
+ CNT[xd._single || ((MachNode)n).regmap(i)!=null && ((MachNode)n).regmap(i).size1() ? 2 : 1]++;
+ }
+ score += 10 * Math.min( CNT[1], 2 );
+ score += 100 * Math.min( CNT[2], 2 );
+
+ // Nothing special
+ assert 10 <= score && score <= 990;
+ return score;
+ }
+
+ // Single use, and that use is not ready to fire once this one does. If
+ // true, stalling 'n' might reduce 'n's lifetime. Stall by 1 (2 if single)
+ // per not-ready.
+
+ // If CFG user in this block, it must be last, so definitely stall
+ // single-def (which stalls flags on x86,arm until the jmp).
+ private static boolean singleUseNotReady( Node n, boolean single ) {
+ // If n can rematerialize, assume allocator will split into private uses
+ // as needed. No impact on local scheduling.
+ if( n.nOuts()>1 && n instanceof MachNode mach && mach.isClone() )
+ return false;
+ for( Node use : n.outs() ) {
+ XSched xu = XSched.get(use);
+ if( xu != null && xu._n instanceof CFGNode ) return true;
+ if( xu != null && xu._bcnt > 0 )
+ CNT[single ? 2 : 1]++; // Since bcnt>0 or CFG, stall until user is more ready
+ }
+ return false;
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/codegen/Machine.java b/chapter21/src/main/java/com/seaofnodes/simple/codegen/Machine.java
new file mode 100644
index 00000000..1c12bfcb
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/codegen/Machine.java
@@ -0,0 +1,93 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeFunPtr;
+
+abstract public class Machine {
+ // Human readable machine name. Something like "x86-64" or "arm" or "risc5"
+ public abstract String name();
+ // Default opcode size for printing; 4 bytes for most 32-bit risc chips
+ public int defaultOpSize() { return 4; }
+ // Human-readable name for a register number, e.g. "RAX" or "R0"
+ public abstract String[] regs();
+ // Create a split op; any register to any register, including stack slots
+ public abstract SplitNode split( String kind, byte round, LRG lrg);
+ // 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
+ public abstract RegMask callArgMask(TypeFunPtr tfp, int arg);
+ // Return register mask, based on signature (GPR vs FPR)
+ public abstract RegMask retMask(TypeFunPtr tfp);
+ // Return PC register
+ public abstract int rpc();
+ // Return a MachNode unconditional branch
+ public abstract CFGNode jump();
+ // Instruction select from ideal nodes
+ public abstract Node instSelect( Node n );
+
+ /** Stack Slot Numbering
+
+RA gives all registers a number, starting at 0. Stack slots continue this
+numbering going up from the last register number. Common register numbers are
+0-15 for GPRs, 16-31 for FPRs, 32 for flags, and stack slots starting at
+register#33 going up. These numbers are machine-specific, YMMV, etc; e.g. RPC
+is only in stack slot 0 on X86; other cpus start with the rpc in a register
+which may spill into any generic spill slot.
+
+For ease of reading and printing, stack slots start again at slot#0 - but
+during RA they are actually biased by CPU.MAX_REG.
+
+Stack Layout during Reg Alloc:
+
+ Slot Value Description
+ ---- ------- -----------
+MAXREG+ R+1 argR maxArgSlot(calleR tfp), can be 0
+ ...
+MAXREG+ 1 arg0
+ ---- -- Caller ------
+MAXREG+ 0 RPC
+MAXREG+ R+E+N PAD optional alignment
+
+MAXREG+ + Spills Caller Frame Space; N spills
+MAXREG+ R+E+1 Spills
+
+MAXREG+ R+E argE maxArgSlot(calleE tfp), can be 0
+ ...
+MAXREG+ R+2 arg0
+ ---- -- Callee -------
+ ...
+
+Slots defined by maxArgSlot do not have to contain the actual passed argument;
+they can be "shadow slots". If they exist, they are property of the frame /
+FunNode where the TFP comes from.
+
+For a given calleR, there are generally many calleE's and they all share the
+low-range stack slots.
+
+Example:, Win64, main(int arg) { calloc(1,size); }
+
+ Post-Warp: SP+56 shadow-main-arg
+Pre-alloc Post-Alloc SP+48 RPC
+--- ------- ---------- -------------
+40 spill#3 SP+40 SP+40 spill#3
+39 spill#2 SP+32 ""
+38 spill#1 SP+24 ""
+37 spill#0 SP+16 "" spill#0
+36 shadow-calloc#size SP+ 8 SP+ 8 shadow-calloc#size
+35 shadow-calloc#1 SP+ 0 SP+ 0 shadow-calloc#1
+--- --- FRAME BASE ---------- ---- FRAME BASE -------
+34 shadow-main-arg SP+56
+33 RPC SP+48
+
+
+ */
+
+
+ // Maximum stack slot (or 0) for the args in this TFP. This will include
+ // shadow slots if defined in the ABI, even if all arguments are passed in
+ // registers.
+ public abstract short maxArgSlot(TypeFunPtr tfp);
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java b/chapter21/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java
new file mode 100644
index 00000000..777182dc
--- /dev/null
+++ b/chapter21/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/chapter21/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java b/chapter21/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java
new file mode 100644
index 00000000..e1051fc6
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java
@@ -0,0 +1,625 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.Ary;
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.node.*;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.IdentityHashMap;
+
+/**
+ * "Briggs/Chaitin/Click".
+ * Graph coloring.
+ * <p>
+ * Every def and every use have a bit-set of allowed registers.
+ * Fully general to bizarre chips.
+ * Multiple outputs fully supported, e.g. add-with-carry or combined div/rem or "xor rax,rax" setting both rax and flags.
+ * <p>
+ * Intel 2-address accumulator-style ops fully supported.
+ * Op-to-stack-spill supported.
+ * All addressing modes supported.
+ * Register pairs could be supported with some grief.
+ * <p>
+ * Splitting instead of spilling (simpler implementation).
+ * Stack slots are "just another register" (tiny stack frames, simpler implementation).
+ * Stack/unstack during coloring with a marvelous trick (simpler implementation).
+ * <p>
+ * Both bitset and adjacency list formats for the interference graph; one of
+ * the few times it's faster to change data structures mid-flight rather than
+ * just wrap one of the two.
+ * <p>
+ * Liveness computation and interference graph built in the same one pass (one
+ * fewer passes per round of coloring).
+ * <p>
+ * Single-register def or use live ranges deny neighbors their required
+ * register and thus do not interfere, vs interfering and denying the color
+ * and coloring time. Basically all function calls do this, but many oddball
+ * registers also, e.g. older div/mod/mul ops. (5x smaller IFG, the only
+ * O(n^2) part of this operation).
+ */
+
+public class RegAlloc {
+ // Main Coloring Algorithm:
+ // Repeat until colored:
+ // Build Live Ranges (LRGs)
+ // - Intersect allowed registers
+ // If hard conflicts (LRGs with no allowed registers)
+ // - Pre-split conflicted LRGs, and repeat
+ // Build Interference Graph (IFG)
+ // - Self conflicts split now
+ // Color (remove trivial first then any until empty; reverse assign colors
+ // - If color fails:
+ // - - Split uncolorable LRGs
+
+ // Top-level program graph structure
+ final CodeGen _code;
+
+ public int _spills, _spillScaled;
+
+ // -----------------------
+ // Live ranges with self-conflicts or no allowed registers
+ private final IdentityHashMap<LRG,String> _failed = new IdentityHashMap<>();
+ void fail( LRG lrg ) {
+ assert lrg.leader();
+ _failed.put(lrg,"");
+ }
+ boolean success() { return _failed.isEmpty(); }
+
+
+ // -----------------------
+ // Map from Nodes to Live Ranges
+ private final IdentityHashMap<Node,LRG> _lrgs = new IdentityHashMap<>();
+ short _lrg_num;
+
+ // Define a new LRG, and assign n
+ LRG newLRG( Node n ) {
+ LRG lrg = lrg(n);
+ if( lrg!=null ) return lrg;
+ lrg = new LRG(_lrg_num++);
+ LRG old = _lrgs.put(n,lrg); assert old==null;
+ return lrg;
+ }
+
+ // LRG for n
+ LRG lrg( Node n ) {
+ LRG lrg = _lrgs.get(n);
+ if( lrg==null ) return null;
+ LRG lrg2 = lrg.find();
+ if( lrg != lrg2 )
+ _lrgs.put(n,lrg2);
+ return lrg2;
+ }
+
+ // Find LRG for n.in(idx), and also map n to it
+ LRG lrg2( Node n, int idx ) {
+ LRG lrg = lrg(n.in(idx));
+ return union(lrg,n);
+ }
+
+ // Union any lrg for n with lrg and map to the union
+ LRG union( LRG lrg, Node n ) {
+ LRG lrgn = _lrgs.get(n);
+ LRG lrg3 = lrg.union(lrgn);
+ _lrgs.put(n,lrg3);
+ return lrg3;
+ }
+
+ // Force all unified to roll up; collect live ranges
+ LRG[] _LRGS;
+ void unify() {
+ Ary<LRG> lrgs = new Ary<>(LRG.class);
+ for( Node n : _lrgs.keySet() ) {
+ LRG lrg = lrg(n);
+ lrgs.setX(lrg._lrg,lrg);
+ }
+ _LRGS = lrgs.asAry();
+ // Remove unified lrgs from failed set also
+ for( LRG lrg : _failed.keySet().toArray(new LRG[0]) ) {
+ if( !lrg.leader() ) {
+ LRG lrg2 = lrg.find();
+ _failed.remove(lrg);
+ _failed.put(lrg2,"");
+ }
+ }
+ }
+
+
+ public short regnum( Node n ) {
+ LRG lrg = lrg(n);
+ return lrg==null ? -1 : lrg._reg;
+ }
+
+ // Printable register number for node n
+ String reg( Node n ) { 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;
+ // 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)+"]";
+ }
+
+ // -----------------------
+ RegAlloc( CodeGen code ) { _code = code; }
+
+ public void regAlloc() {
+ // Insert callee-save registers
+ String[] regs = _code._mach.regs();
+ long neverSave= _code._mach.neverSave();
+ for( CFGNode bb : _code._cfg )
+ if( bb instanceof FunNode fun ) {
+ ReturnNode ret = fun.ret();
+ int len = Math.min(regs.length,64);
+ for( int reg=0; reg<len; reg++ )
+ if( !_code._callerSave.test(reg) && ((1L<<reg)&neverSave)==0 ) {
+ ret.addDef(new CalleeSaveNode(fun,reg,regs[reg]));
+ assert ret.regmap(ret.nIns()-1).firstReg()==reg;
+ }
+ }
+ // Cache reg masks for New and Call
+ for( CFGNode bb : _code._cfg ) {
+ if( bb instanceof CallEndNode cend ) cend.cacheRegs(_code);
+ for( Node n : bb._outputs )
+ if( n instanceof NewNode nnn ) nnn.cacheRegs(_code);
+ }
+
+ // Top driver: repeated rounds of coloring and splitting.
+ byte round=0;
+ while( !graphColor(round) ) {
+ split(round);
+ if( round >= 7 ) // Really expect to be done soon
+ throw Utils.TODO("Allocator taking too long");
+ round++;
+ }
+ postColor(); // Remove no-op spills
+ }
+
+ private boolean graphColor(byte round) {
+ _failed.clear();
+ _lrgs.clear();
+ _lrg_num = 1;
+ _LRGS=null;
+
+ return
+ // Build Live Ranges
+ BuildLRG.run(round,this) && // if no hard register conflicts
+ // Build Interference Graph
+ IFG.build(round,this) && // If no self conflicts or uncolorable
+ // Conservative coalesce copies
+ Coalesce.coalesce(round,this) &&
+ // Color attempt
+ IFG.color(round,this); // If colorable
+ }
+
+ // -----------------------
+ // Split conflicted live ranges.
+ void split(byte round) {
+
+ // In C2, all splits are handling in one pass over the program. Here,
+ // in the name of clarity, we'll handle each failing live range
+ // independently... which generally requires a full pass over the
+ // program for each failing live range. i.e., might be a lot of
+ // passes.
+
+ // Sort, to avoid non-deterministic HashMap ordering
+ LRG[] splits = _failed.keySet().toArray(new LRG[0]);
+ Arrays.sort(splits, (x,y) -> x._lrg - y._lrg );
+ for( LRG lrg : splits )
+ split(round,lrg);
+ }
+
+ // Split this live range, top level heuristic
+ boolean split( byte round, LRG lrg ) {
+ assert lrg.leader(); // Already rolled up
+
+ if( lrg._selfConflicts != null )
+ return splitSelfConflict(round,lrg);
+
+ // Register mask when empty; split around defs and uses with limited
+ // register masks.
+ if( lrg._mask.isEmpty() && (!lrg._multiDef || lrg._1regUseCnt==1) ) {
+ if( lrg._1regDefCnt <= 1 &&
+ lrg._1regUseCnt <= 1 &&
+ (lrg._1regDefCnt + lrg._1regUseCnt) > 0 )
+ return splitEmptyMaskSimple(round,lrg);
+ // Repeated single-reg uses from a single def. Special for archs
+ // with more fixed regs.
+ if( !lrg._multiDef && lrg._1regDefCnt <= 1 && lrg._1regUseCnt > 2 )
+ if( splitEmptyMaskByUse(round,lrg) )
+ return true;
+ }
+
+ // Generic split-by-loop depth.
+ return splitByLoop(round,lrg);
+ }
+
+ // Split live range with an empty mask. Specifically forces splits at
+ // single-register defs or uses and not elsewhere.
+ boolean splitEmptyMaskSimple( byte round, LRG lrg ) {
+ // Live range has a single-def single-register, and/or a single-use
+ // single-register. Split after the def and before the use. Does not
+ // require a full pass.
+
+ // Split just after def
+ if( lrg._1regDefCnt==1 && !lrg._machDef.isClone() )
+ // Force must-split, even if a prior split same block because register
+ // conflicts. Example:
+ // alloc
+ // V1/rax - forced by alloc
+ // alloc
+ // V2/rax - kills prior RAX
+ // st4 [V1],len - No good, must split around
+ insertAfterAndReplace( makeSplit("def/empty1",round,lrg), (Node)lrg._machDef, false/*true*/);
+ // Split just before use
+ if( lrg._1regUseCnt==1 || (lrg._1regDefCnt==1 && ((Node)lrg._machDef).nOuts()==1) )
+ insertBefore((Node)lrg._machUse,lrg._uidx,"use/empty1",round,lrg);
+ return true;
+ }
+
+ // Single-def live range with an empty mask. There are many single-reg
+ // uses. Theory is there's many repeats if the same reg amongst the uses.
+ // In of splitting once per use, start by splitting into groups based on
+ // required input register.
+ boolean splitEmptyMaskByUse( byte round, LRG lrg ) {
+ Node def = (Node)lrg._machDef;
+
+ // Look at each use, and break into non-overlapping register classes.
+ Ary<RegMask> rclass = new Ary<>(RegMask.class);
+ boolean done=false;
+ while( !done ) {
+ done = true;
+ for( Node use : def._outputs )
+ if( use instanceof MachNode mach )
+ for( int i=1; i<use.nIns(); i++ )
+ if( use.in(i)==def )
+ done = putIntoRegClass( rclass, mach.regmap(i) );
+ }
+
+ // See how many register classes we split into
+ if( rclass._len <= 1 ) return false;
+
+ // Split by class
+ for( RegMask rmask : rclass ) {
+ Node split = makeSplit(def,"popular",round,lrg);
+ split.insertAfter( def );
+ if( split.nIns()>1 ) split.setDef(1,def);
+ // all uses by class to split
+ for( int j=0; j < def._outputs._len; j++ ) {
+ Node use = def._outputs.at(j);
+ if( use instanceof MachNode mach && use!=split ) {
+ // Check all use inputs for n, in case there's several
+ for( int i = 1; i < use.nIns(); i++ )
+ // Find a def input, and check register class
+ if( use.in( i ) == def && mach.regmap( i ).overlap( rmask ) )
+ // Modify use to use the split version specialized to this rclass
+ { use.setDef( i, split ); j--; break; }
+ }
+ }
+ }
+ return true;
+ }
+
+
+ // Put use into a register class, perhaps adding a class or perhaps
+ // narrowing a class (and causing a repeat)
+ private static boolean putIntoRegClass( Ary<RegMask> rclass, RegMask rmask ) {
+ for( int i=0; i<rclass._len; i++ ) {
+ RegMask omask = rclass.at(i);
+ if( omask.and(rmask) == omask ) return true; // Within the same register class
+ if( omask.overlap(rmask) ) {
+ rclass.set(i,new RegMask(omask.copy().and(rmask)));
+ return false; // Need go again
+ }
+ }
+ // Add a new class, no need to go again
+ rclass.push(rmask);
+ return true;
+ }
+
+ // Self conflicts require Phis (or two-address).
+ // Insert a split after every def.
+ boolean splitSelfConflict( byte round, LRG lrg ) {
+ // Sort conflict set, so we're deterministic
+ Node[] conflicts = lrg._selfConflicts.keySet().toArray(new Node[0]);
+ Arrays.sort(conflicts, (x,y) -> x._nid - y._nid );
+
+ // For all conflicts
+ for( Node def : conflicts ) {
+ assert lrg(def)==lrg; // Might be conflict use-side
+ // Split before each use that extends the live range; i.e. is a
+ // Phi or two-address
+ for( int i=0; i<def._outputs._len; i++ ) {
+ Node use = def.out(i);
+ if( (use instanceof PhiNode phi &&
+ !(phi.region() instanceof LoopNode loop && phi.in(2)==def && def.cfg0().idepth() > loop.idepth() ) ) ||
+ (use instanceof MachNode mach && mach.twoAddress()!=0 && use.in(mach.twoAddress())==def) )
+ insertBefore( use, use._inputs.find(def), "use/self/use",round,lrg );
+ }
+ // Split after the Phi which extends the LRG. Split also before
+ // Phi slot 1 (and not all inputs), because Phis extend the live range.
+ // TODO: split before all inputs (except the last; at least 1 split here must be extra)
+ if( def instanceof PhiNode phi && !(def instanceof ParmNode) ) {
+ SplitNode split = makeSplit("def/self",round,lrg);
+ insertAfterAndReplace(split,def,false);
+ if( split.nOuts()==0 )
+ split.killOrdered();
+ insertBefore(phi,1,"use/self/phi",round,lrg);
+ }
+ // Split before two-address ops which extend the live range
+ if( def instanceof MachNode mach && mach.twoAddress()!= 0 )
+ insertBefore(def,mach.twoAddress(),"use/self/two",round,lrg);
+ }
+ return true;
+ }
+
+
+ // Generic: split around the outermost loop with non-split def/uses. This
+ // covers both self-conflicts (once we split deep enough) and register
+ // pressure issues.
+ boolean splitByLoop( byte round, LRG lrg ) {
+ findAllLRG(lrg);
+
+ // Find min loop depth for all non-split defs and uses.
+ long ld = (-1L<<32) | 9999;
+ for( Node n : _ns ) {
+ if( lrg(n)==lrg ) // This is a LRG def
+ ld = ldepth(ld,n,n.cfg0());
+ // PhiNodes check all CFG inputs
+ if( n instanceof PhiNode phi ) {
+ for( int i=1; i<n.nIns(); i++ )
+ ld = ldepth(ld, phi.in(i), phi.region().cfg(i));
+ } else {
+ // Others check uses
+ for( int i=1; i<n.nIns(); i++ )
+ if( lrgSame(n.in(i),lrg) ) // This is a LRG use
+ ld = ldepth(ld,n,n.cfg0());
+ }
+ }
+ int min = (int)ld;
+ int max = (int)(ld>>32);
+
+
+ // If the minLoopDepth is less than the maxLoopDepth: for-all defs and
+ // uses, if at minLoopDepth or lower, split after def and before use.
+ for( Node n : _ns ) {
+ if( n instanceof SplitNode ) continue; // Ignoring splits; since spilling need to split in a deeper loop
+ if( n.isDead() ) continue; // Some Clonable went dead by other spill changes
+ // If this is a 2-address commutable op (e.g. AddX86, MulX86) and the rhs has only a single user,
+ // commute the inputs... which chops the LHS live ranges' upper bound to just the RHS.
+ if( n instanceof MachNode mach && lrg(n)==lrg && mach.twoAddress()==1 && mach.commutes() && n.in(2).nOuts()==1 )
+ n.swap12();
+
+ if( lrg(n)==lrg && // This is a LRG def
+ // At loop boundary, or splitting in inner loop
+ (min==max || n.cfg0().loopDepth() <= min) ) {
+ // Cloneable constants will be cloned at uses, not after def
+ if( !(n instanceof MachNode mach && mach.isClone()) &&
+ // Single user is already a split adjacent
+ !(n.nOuts()==1 && n.out(0) instanceof SplitNode split && sameBlockNoClobber(split) ) )
+ // Split after def in min loop nest
+ insertAfterAndReplace( makeSplit("def/loop",round,lrg), n,true);
+ }
+
+ // PhiNodes check all CFG inputs
+ if( n instanceof PhiNode phi && !(n instanceof ParmNode)) {
+ for( int i=1; i<n.nIns(); i++ )
+ // No split in front of a split
+ if( !(n.in(i) instanceof SplitNode) &&
+ // splitting in inner loop or at loop border
+ (min==max || phi.region().cfg(i).loopDepth() <= min) &&
+ // and not around the backedge of a loop (bad place to force a split, hard to remove)
+ !(phi.region() instanceof LoopNode && i==2 && (phi.in(i) instanceof PhiNode pp && pp.region()==phi.region())) )
+ // Split before phi-use in prior block
+ insertBefore(phi,i, "use/loop/phi",round,lrg);
+
+ } else {
+ // Others check uses
+ for( int i=1; i<n.nIns(); i++ ) {
+ // This is a LRG use
+ // splitting in inner loop or at loop border
+ if( lrgSame( n.in( i ), lrg ) &&
+ (min == max || (n.in(i) instanceof MachNode mach && mach.isClone()) || n.cfg0().loopDepth() <= min) )
+ // Split before in this block
+ insertBefore( n, i, "use/loop/use", round,lrg, false );
+ }
+ }
+ }
+ return true;
+ }
+
+ private boolean lrgSame(Node x, LRG lrg) {
+ LRG xlrg = lrg(x);
+ while( xlrg==null && x instanceof SplitNode )
+ xlrg=lrg(x = x.in(1));
+ return xlrg==lrg;
+ }
+
+ private static long ldepth( long ld, Node n, CFGNode cfg ) {
+ // Do not count splits
+ if( n instanceof SplitNode ) return ld;
+ // Collect min/max loop depth
+ int min = (int)ld;
+ int max = (int)(ld>>32);
+ int d = cfg.loopDepth();
+ // if n will lower the min loop and is in the tail end of the loop
+ // header, splitting "around" the loop will not help. Treat n as being
+ // in the loop.
+ if( d < min ) {
+ if( cfg.uctrl() instanceof LoopNode loop && loop.entry()==cfg ) {
+ for( int i=cfg.nOuts()-2; i>=0; i-- ) {
+ Node out = cfg.out(i);
+ if( n==out )
+ { d = loop.loopDepth(); break; } // Treat n as being "in the loop"
+ if( !((out instanceof MachNode mach && mach.isClone()) || out instanceof SplitNode ) )
+ break; // Treat b as "normal", out of loop
+ }
+ }
+ }
+
+ // lower min, raise max, and re-fold
+ min = Math.min(min,d);
+ max = Math.max(max,d);
+ return ((long)max<<32) | min;
+ }
+
+ // Find all members of a live range, both defs and uses
+ private final Ary<Node> _ns = new Ary<>(Node.class);
+ void findAllLRG( LRG lrg ) {
+ _ns.clear();
+ int wd = 0;
+ _ns.push((Node)lrg._machDef);
+ _ns.push((Node)lrg._machUse);
+ while( wd < _ns._len ) {
+ Node n = _ns.at(wd++);
+ if( lrg(n)!=lrg ) continue;
+ for( Node def : n._inputs )
+ if( lrg(def)==lrg && _ns.find(def)== -1 )
+ _ns.push(def);
+ for( Node use : n._outputs )
+ if( _ns.find(use)== -1 )
+ _ns.push(use);
+ }
+ for( Node n : _ns ) assert !n.isDead();
+ }
+
+ void insertBefore(Node n, int i, String kind, byte round, LRG lrg, boolean skip) {
+ Node def = n.in(i);
+ // Effective block for use
+ CFGNode cfg = n instanceof PhiNode phi ? phi.region().cfg(i) : n.cfg0();
+ // Def is a split ?
+ if( skip && def instanceof SplitNode ) {
+ boolean singleReg = n instanceof MachNode mach && mach.regmap(i).size1();
+ // Same block, multiple registers, split is only used by n,
+ // assume this is good enough and do not split again.
+ if( cfg==def.cfg0() && def.nOuts()==1 && !singleReg )
+ return;
+ }
+ makeSplit(def,kind,round,lrg).insertBefore(n, i);
+ // Skip split-of-split same block
+ if( skip && def instanceof SplitNode && cfg==def.cfg0() )
+ n.in(i).setDefOrdered(1,def.in(1));
+ }
+ void insertBefore(Node n, int i, String kind, byte round, LRG lrg) {
+ insertBefore(n,i,kind,round,lrg,true);
+ }
+
+ // Replace uses of `def` with `split`, and insert `split` immediately after
+ // `def` in the basic block.
+ public void insertAfterAndReplace( Node split, Node def, boolean must ) {
+ split.insertAfter(def);
+ if( split.nIns()>1 ) split.setDef(1,def);
+ for( int j=def.nOuts()-1; j>=0; j-- ) {
+ Node use = def.out(j);
+ if( use==split ) continue; // Skip self
+ // Can we avoid a split of a split? 'this' split is used by
+ // another split in the same block.
+ if( !must && use instanceof SplitNode split2 && sameBlockNoClobber(split2) )
+ continue;
+ int idx = use._inputs.find(def);
+ use.setDefOrdered(idx,split);
+ if( j < def.nOuts() ) j++;
+ }
+ }
+
+ private Node makeSplit( Node def, String kind, byte round, LRG lrg ) {
+ Node split = def instanceof MachNode mach && mach.isClone()
+ ? mach.copy()
+ : _code._mach.split(kind,round,lrg);
+ _lrgs.put(split,lrg);
+ return split;
+ }
+ private SplitNode makeSplit( String kind, byte round, LRG lrg ) {
+ SplitNode split = _code._mach.split(kind,round,lrg);
+ _lrgs.put(split,lrg);
+ return split;
+ }
+
+
+ // -----------------------
+ // POST PASS: Remove empty spills that biased-coloring made
+ private void postColor() {
+ int 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<bb.nOuts(); j++ ) {
+ Node n = bb.out(j);
+ if( lrg(n)!=null )
+ maxReg = Math.max(maxReg,lrg(n)._reg+1);
+ // Raise frame size by max stack args passed to New
+ if( n instanceof NewNode nnn )
+ maxReg = Math.max(maxReg,nnn._xslot);
+
+ if( !(n instanceof SplitNode ) ) continue;
+ int defreg = lrg(n )._reg;
+ int usereg = lrg(n.in(1))._reg;
+ // Attempt to bypass split
+ if( defreg != usereg && splitBypass(bb,j,n,defreg) )
+ usereg = lrg(n.in(1))._reg;
+
+ // Split has same reg? Useless! Can remove it!
+ if( defreg == usereg ) {
+ n.removeSplit();
+ j--;
+ continue;
+ }
+ // Split is kept; count against split score
+ _spills++;
+ _spillScaled += (1<<bb.loopDepth()*3);
+ assert _spillScaled >= 0;
+ }
+ }
+ }
+
+ private boolean splitBypass( CFGNode bb, int j, Node lo, int defreg ) {
+ // Attempt to bypass split
+ Node hi = lo.in(1);
+ while( true ) {
+ if( !(hi instanceof SplitNode && lo.cfg0() == hi.cfg0()) )
+ return false;
+ if( lrg(hi.in(1))._reg==defreg )
+ break;
+ hi = hi.in(1);
+ }
+ // Check no clobbers
+ for( int idx = j-1; bb.out(idx) != hi; idx++) {
+ Node n = bb.out(idx);
+ if( lrg(n)!=null && lrg(n)._reg == defreg )
+ return false; // Clobbered
+ }
+ lo.setDefOrdered(1,hi.in(1));
+ return true;
+ }
+
+
+ public boolean sameBlockNoClobber( SplitNode split ) {
+ Node def = split.in(1);
+ CFGNode cfg = def.cfg0();
+ if( cfg != split.cfg0() ) return false; // Not same block
+ // Get multinode head
+ Node def0 = def instanceof ProjNode ? def.in(0) : def;
+ int defreg = lrg(def)._reg;
+ if( defreg == -1 ) defreg = lrg(def)._mask.firstReg();
+ if( defreg == -1 ) return false; // no allowed registers -> clobbered
+ for( int idx = cfg._outputs.find(split) -1; idx >= 0; idx-- ) {
+ Node n = cfg.out(idx);
+ if( n==def0 ) return true; // No clobbers
+ if( lrg(n) == lrg(def) ) return false; // Self conflict
+ if( lrg(n)!=null && lrg(n)._reg == defreg )
+ return false; // Clobbered
+ }
+ throw Utils.TODO();
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/codegen/RegMask.java b/chapter21/src/main/java/com/seaofnodes/simple/codegen/RegMask.java
new file mode 100644
index 00000000..79de3fbf
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/codegen/RegMask.java
@@ -0,0 +1,126 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.SB;
+
+/** RegMask
+ * A "register mask" - 1 bit set for each allowed register. In addition
+ * "stack slot" registers may be allowed, effectively making the set infinite.
+ * <p>
+ * For smaller and simpler machines it suffices to make such masks an i64 or
+ * i128 (64 or 128 bit integers), and this presentation is by far the better
+ * way to go... if all register allocations can fit in this bit limitation.
+ * The allocator will need bits for stack-based parameters and for splits
+ * which cannot get a register. For a 32-register machine like the X86, add 1
+ * for flags - gives 33 registers. Using a Java `long` has 64 bits, leaving
+ * 31 for spills and stack passing. This is adequate for nearly all
+ * allocations; only the largest allocations will run this out. However, if
+ * we move to a chip with 64 registers we'll immediately run out, and need at
+ * least a 128 bit mask. Since you cannot *return* a 128 bit value directly
+ * in Java, Simple will pick up a `RegMask` class object.
+*/
+public class RegMask {
+
+ long _bits0, _bits1;
+
+ private static final RegMask EMPTY = new RegMask(0L);
+ public static final RegMask FULL = new RegMask(-1L);
+
+ public RegMask(int bit) {
+ if( bit < 64 ) _bits0 = 1L<<bit;
+ else _bits1 = 1L<<(bit=64);
+ }
+ public RegMask(long bits ) { _bits0 = bits; }
+ public RegMask(long bits0, long bits1 ) { _bits0 = bits0; _bits1 = bits1; }
+ public RegMask(RegMask r) { _bits0 = r._bits0; _bits1 = r._bits1; }
+ private RegMask() { _bits0 = _bits1 = 0; }
+
+ // AND, with copy-on-write if changing
+ RegMask and( RegMask mask ) {
+ if( mask==null ) return this;
+ long bits0 = _bits0 & mask._bits0;
+ long bits1 = _bits1 & mask._bits1;
+ if( bits0==_bits0 && bits1==_bits1 ) return this;
+ if( bits0==mask._bits0 && bits1==mask._bits1 ) return mask;
+ if( bits0==0 && bits1==0 ) return EMPTY;
+ // Update-in-place a mutable mask, or make a defensive copy
+ return null;
+ }
+ RegMask sub( RegMask mask ) {
+ if( mask==null ) return this;
+ long bits0 = _bits0 & ~mask._bits0;
+ long bits1 = _bits1 & ~mask._bits1;
+ if( bits0==_bits0 && bits1==_bits1 ) return this;
+ if( bits0==mask._bits0 && bits1==mask._bits1 ) return mask;
+ if( bits0==0 && bits1==0 ) return EMPTY;
+ // Update-in-place a mutable mask, or make a defensive copy
+ return null;
+ }
+
+ // Fails if bit is set, because this is immutable
+ public boolean clr( int reg ) {
+ if( reg < 64 ) return ((_bits0 >> reg)&1)==0;
+ if( reg < 128 ) return ((_bits1 >> reg)&1)==0;
+ return true;
+ }
+
+
+ public short firstReg() {
+ if( isEmpty() ) return -1;
+ return (short)(_bits0 != 0
+ ? Long.numberOfTrailingZeros(_bits0)
+ : Long.numberOfTrailingZeros(_bits1)+64);
+ }
+ public short nextReg(short reg) {
+ if( reg<64 ) {
+ long bits0 = _bits0 >> (reg+1);
+ if( bits0!=0 )
+ return (short)(reg+1+Long.numberOfTrailingZeros(bits0));
+ reg=64;
+ }
+ long bits1 = _bits1 >> (reg-64+1);
+ if( bits1!=0 )
+ return (short)(reg+1+Long.numberOfTrailingZeros(bits1));
+ return -1;
+ }
+
+ boolean isEmpty() { return _bits0==0 && _bits1==0; }
+
+ public boolean test( int reg ) {
+ return ((reg<64 ? (_bits0 >> reg) : (_bits1 >> (reg-64))) & 1) != 0;
+ }
+
+ // checks if the 2 masks have at least 1 bit in common
+ public boolean overlap( RegMask mask ) { return (_bits0 & mask._bits0)!=0 || (_bits1 & mask._bits1)!=0; }
+
+ // Defensive writable copy
+ public RegMaskRW copy() { return new RegMaskRW( _bits0, _bits1 ); }
+
+ // Has exactly 1 bit set
+ public boolean size1() {
+ return ((_bits0 & -_bits0)==_bits0 && _bits1==0) ||
+ ((_bits1 & -_bits1)==_bits1 && _bits0==0);
+ }
+
+ // Cardinality
+ public short size() { return (short)(Long.bitCount(_bits0)+Long.bitCount(_bits1)); }
+
+ @Override public String toString() { return toString(new SB()).toString(); }
+ public SB toString(SB sb) {
+ Machine mach = CodeGen.CODE._mach;
+ if( _bits0==0 && _bits1==0 ) return sb.p("[]");
+ sb.p("[");
+ String[] regs = mach.regs();
+ for( int i=0; i<64; i++ )
+ if( ((_bits0 >> i)&1) != 0 )
+ sb.p(reg(regs,i)).p(",");
+ for( int i=0; i<64; i++ )
+ if( ((_bits1 >> i)&1) != 0 )
+ sb.p(reg(regs,i+64)).p(",");
+ return sb.unchar().p("]");
+ }
+
+ public static String reg(String[] regs, int i) {
+ return i<regs.length ? regs[i] : "[stk#"+i+"]";
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/codegen/RegMaskRW.java b/chapter21/src/main/java/com/seaofnodes/simple/codegen/RegMaskRW.java
new file mode 100644
index 00000000..1da9a3b1
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/codegen/RegMaskRW.java
@@ -0,0 +1,36 @@
+package com.seaofnodes.simple.codegen;
+
+// Mutable regmask(writable)
+public class RegMaskRW extends RegMask {
+ public RegMaskRW(long x, long y) { super(x,y); }
+ // clears bit at position r. Returns true if the mask is still not empty.
+ public boolean clr(int r) {
+ if( r < 64 ) _bits0 &= ~(1L<<(r ));
+ else _bits1 &= ~(1L<<(r-64));
+ return _bits0!=0 || _bits1!=0;
+ }
+ // sets bit at position r.
+ public void set(int r) {
+ if( r < 64 ) _bits0 |= (1L<<(r ));
+ else _bits1 |= (1L<<(r-64));
+ }
+ @Override RegMaskRW and( RegMask mask ) {
+ if( mask==null ) return this;
+ _bits0 &= mask._bits0;
+ _bits1 &= mask._bits1;
+ return this;
+ }
+ @Override RegMaskRW sub( RegMask mask ) {
+ if( mask==null ) return this;
+ _bits0 &= ~mask._bits0;
+ _bits1 &= ~mask._bits1;
+ return this;
+ }
+ public RegMaskRW or( RegMask mask ) {
+ if( mask!=null ) {
+ _bits0 |= mask._bits0;
+ _bits1 |= mask._bits1;
+ }
+ return this;
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/AddFNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/AddFNode.java
new file mode 100644
index 00000000..792da62e
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/AddFNode.java
@@ -0,0 +1,44 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFloat;
+
+import java.util.BitSet;
+
+public class AddFNode extends Node {
+ public AddFNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+
+ @Override public String label() { return "AddF"; }
+
+ @Override public String glabel() { return "+"; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("("), visited);
+ in(2)._print0(sb.append("+"), visited);
+ return sb.append(")");
+ }
+
+ @Override
+ public Type compute() {
+ if (in(1)._type instanceof TypeFloat i0 &&
+ in(2)._type instanceof TypeFloat i1) {
+ if (i0.isConstant() && i1.isConstant())
+ return TypeFloat.constant(i0.value()+i1.value());
+ }
+ return in(1)._type.meet(in(2)._type);
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Type t2 = in(2)._type;
+
+ // Add of 0.
+ if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==0 )
+ return lhs;
+
+ return null;
+ }
+ @Override Node copy(Node lhs, Node rhs) { return new AddFNode(lhs,rhs); }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/AddNode.java
new file mode 100644
index 00000000..dd128e76
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/AddNode.java
@@ -0,0 +1,199 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.*;
+
+import java.util.BitSet;
+
+import static com.seaofnodes.simple.Parser.con;
+
+public class AddNode extends Node {
+ public AddNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+
+ @Override public String label() { return "Add"; }
+
+ @Override public String glabel() { return "+"; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("("), visited);
+ in(2)._print0(sb.append("+"), visited);
+ return sb.append(")");
+ }
+
+
+ @Override
+ public Type compute() {
+ Type t1 = in(1)._type, t2 = in(2)._type;
+ if( t1.isHigh() || t2.isHigh() )
+ return TypeInteger.TOP;
+ if( t1 instanceof TypeInteger i1 &&
+ t2 instanceof TypeInteger i2 ) {
+ if (i1.isConstant() && i2.isConstant())
+ return TypeInteger.constant(i1.value()+i2.value());
+ // Fold ranges like {0-1} + {2-3} into {2-4}.
+ if( !overflow(i1._min,i2._min) &&
+ !overflow(i1._max,i2._max) )
+ return TypeInteger.make(i1._min+i2._min,i1._max+i2._max);
+ }
+ return TypeInteger.BOT;
+ }
+
+ static boolean overflow( long x, long y ) {
+ if( (x ^ y ) < 0 ) return false; // unequal signs, never overflow
+ return (x ^ (x + y)) < 0; // sum has unequal signs, so overflow
+ }
+ @Override
+ public Node idealize () {
+ Node lhs = in(1);
+ Node rhs = in(2);
+ if( rhs instanceof AddNode add && add.err()!=null )
+ return null;
+ Type t2 = rhs._type;
+
+ // Add of 0. We do not check for (0+x) because this will already
+ // canonicalize to (x+0)
+ if( t2 == TypeInteger.ZERO )
+ return lhs;
+
+ // Add of same to a multiply by 2
+ if( lhs==rhs )
+ return new ShlNode(null,lhs,con(1));
+
+ // Goal: a left-spine set of adds, with constants on the rhs (which then fold).
+
+ // Move non-adds to RHS
+ if( !(lhs instanceof AddNode) && rhs instanceof AddNode )
+ return swap12();
+
+ // x+(-y) becomes x-y
+ if( rhs instanceof MinusNode minus )
+ return new SubNode(lhs,minus.in(1));
+
+ // Now we might see (add add non) or (add non non) or (add add add) but never (add non add)
+
+ // Do we have x + (y + z) ?
+ // Swap to (x + y) + z
+ // Rotate (add add add) to remove the add on RHS
+ if( rhs instanceof AddNode add )
+ return new AddNode(new AddNode(lhs,add.in(1)).peephole(), add.in(2));
+
+ // Now we might see (add add non) or (add non non) but never (add non add) nor (add add add)
+ if( !(lhs instanceof AddNode) )
+ // Rotate; look for (add (phi cons) con/(phi cons))
+ return spine_cmp(lhs,rhs,this) ? swap12() : phiCon(this,true);
+
+ // Now we only see (add add non)
+
+ // Dead data cycle; comes about from dead infinite loops. Do nothing,
+ // the loop will peep as dead after a bit.
+ if( lhs.in(1) == lhs )
+ return null;
+
+ // Do we have (x + con1) + con2?
+ // Replace with (x + (con1+con2) which then fold the constants
+ // lhs.in(2) is con1 here
+ // If lhs.in(2) is not a constant, we add ourselves as a dependency
+ // because if it later became a constant then we could make this
+ // transformation.
+ if( addDep(lhs.in(2))._type.isConstant() && rhs._type.isConstant() )
+ return new AddNode(lhs.in(1),new AddNode(lhs.in(2),rhs).peephole());
+
+
+ // Do we have ((x + (phi cons)) + con) ?
+ // Do we have ((x + (phi cons)) + (phi cons)) ?
+ // Push constant up through the phi: x + (phi con0+con0 con1+con1...)
+ Node phicon = phiCon(this,true);
+ if( phicon!=null ) return phicon;
+
+ // Now we sort along the spine via rotates, to gather similar things together.
+
+ // Do we rotate (x + y) + z
+ // into (x + z) + y ?
+ if( spine_cmp(lhs.in(2),rhs,this) )
+ return new AddNode(new AddNode(lhs.in(1),rhs).peephole(),lhs.in(2));
+
+ return null;
+ }
+
+ // Rotation is only valid for associative ops, e.g. Add, Mul, And, Or, Xor.
+ // Do we have ((phi cons)|(x + (phi cons)) + con|(phi cons)) ?
+ // Push constant up through the phi: x + (phi con0+con0 con1+con1...)
+ static Node phiCon(Node op, boolean rotate) {
+ Node lhs = op.in(1);
+ Node rhs = op.in(2);
+ if( rhs._type==TypeInteger.TOP ) return null;
+ // LHS is either a Phi of constants, or another op with Phi of constants
+ PhiNode lphi = pcon(lhs,op);
+ if( rotate && lphi==null && lhs.nIns() > 2 ) {
+ // Only valid to rotate constants if both are same associative ops
+ if( lhs.getClass() != op.getClass() ) return null;
+ lphi = pcon(lhs.in(2),op); // Will rotate with the Phi push
+ }
+ if( lphi==null ) return null;
+ if( lphi.region().nIns() <=2 ) return null; // Phi is collapsing
+
+ // RHS is a constant or a Phi of constants
+ if( !(rhs instanceof ConstantNode) && pcon(rhs,op)==null )
+ return null;
+
+ // If both are Phis, must be same Region
+ if( rhs instanceof PhiNode && lphi.in(0) != rhs.in(0) )
+ return null;
+
+ // Note that this is the exact reverse of Phi pulling a common op down
+ // to reduce total op-count. We don't get in an endless push-up
+ // push-down peephole cycle because the constants all fold first.
+ Node[] ns = new Node[lphi.nIns()];
+ ns[0] = lphi.in(0);
+ // Push constant up through the phi: x + (phi con0+con0 con1+con1...)
+ for( int i=1; i<ns.length; i++ )
+ ns[i] = op.copy(lphi.in(i), rhs instanceof PhiNode ? rhs.in(i) : rhs).peephole();
+ String label = lphi._label + (rhs instanceof PhiNode rphi ? rphi._label : "");
+ Node phi = new PhiNode(label,lphi._declaredType,ns).peephole();
+ // Rotate needs another op, otherwise just the phi
+ return lhs==lphi ? phi : op.copy(lhs.in(1),phi);
+ }
+
+ /**
+ * Tests if the op is a phi and has all constant inputs.
+ * If not, returns null.
+ * If op is a phi, but its inputs are not all constants, then dep is added as
+ * a dependency to the phi's non-const input, because if later the input turn into a constant
+ * dep can make progress.
+ */
+ static PhiNode pcon(Node op, Node dep) {
+ return op instanceof PhiNode phi && phi.allCons(dep) ? phi : null;
+ }
+
+ // Compare two off-spine nodes and decide what order they should be in.
+ // Do we rotate ((x + hi) + lo) into ((x + lo) + hi) ?
+ // Generally constants always go right, then Phi-of-constants, then muls, then others.
+ // Ties with in a category sort by node ID.
+ // TRUE if swapping hi and lo.
+ static boolean spine_cmp( Node hi, Node lo, Node dep ) {
+ if( lo._type.isConstant() ) return false;
+ if( hi._type.isConstant() ) return true ;
+
+ if( lo instanceof PhiNode lphi && lphi.region()._type==Type.XCONTROL ) return false;
+ if( hi instanceof PhiNode hphi && hphi.region()._type==Type.XCONTROL ) return false;
+
+ if( lo instanceof PhiNode && lo.allCons(dep) ) return false;
+ if( hi instanceof PhiNode && hi.allCons(dep) ) return true ;
+
+ if( lo instanceof PhiNode && !(hi instanceof PhiNode) ) return true;
+ if( hi instanceof PhiNode && !(lo instanceof PhiNode) ) return false;
+
+ // Same category of "others"
+ return lo._nid > hi._nid;
+ }
+
+ @Override Node copy(Node lhs, Node rhs) { return new AddNode(lhs,rhs); }
+ @Override Node copyF() { return new AddFNode(null,null); }
+ @Override public Parser.ParseException err() {
+ if( in(1)._type.isHigh() || in(2)._type.isHigh() ) return null;
+ if( !(in(1)._type instanceof TypeInteger) ) return Parser.error("Cannot '"+label()+"' " + in(1)._type,null);
+ if( !(in(2)._type instanceof TypeInteger) ) return Parser.error("Cannot '"+label()+"' " + in(2)._type,null);
+ return null;
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/AndNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/AndNode.java
new file mode 100644
index 00000000..3cfcbda3
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/AndNode.java
@@ -0,0 +1,61 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+
+import java.util.BitSet;
+
+public class AndNode extends LogicalNode {
+ public AndNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); }
+
+ @Override public String label() { return "And"; }
+ @Override public String op() { return "&"; }
+
+ @Override public String glabel() { return "&"; }
+
+ @Override
+ public Type compute() {
+ Type t1 = in(1)._type, t2 = in(2)._type;
+ if( t1.isHigh() || t2.isHigh() )
+ return TypeInteger.TOP;
+ if( t1 instanceof TypeInteger i0 &&
+ t2 instanceof TypeInteger i1 ) {
+ if( i0.isConstant() && i1.isConstant() )
+ return TypeInteger.constant(i0.value()&i1.value());
+ // Sharpen allowed bits if either value is narrowed
+ long mask = i0.mask() & i1.mask();
+ return mask < 0 ? TypeInteger.BOT : TypeInteger.make(0,mask);
+ }
+ return TypeInteger.BOT;
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Node rhs = in(2);
+ Type t1 = lhs._type;
+ Type t2 = rhs._type;
+ if( !(t1 instanceof TypeInteger && t2 instanceof TypeInteger t2i) )
+ return null; // Malformed, e.g. (17 & 3.14)
+
+ // And of -1. We do not check for (-1&x) because this will already
+ // canonicalize to (x&-1). We do not check for zero, because
+ // the compute() call will return a zero already.
+ if( t2i.isConstant() && t2i.value()==-1 )
+ return lhs;
+
+ // Move constants to RHS: con*arg becomes arg*con
+ if( t1.isConstant() && !t2.isConstant() )
+ return swap12();
+
+ // Do we have ((x & (phi cons)) & con) ?
+ // Do we have ((x & (phi cons)) & (phi cons)) ?
+ // Push constant up through the phi: x & (phi con0&con0 con1&con1...)
+ Node phicon = AddNode.phiCon(this,true);
+ if( phicon!=null ) return phicon;
+
+ return null;
+ }
+ @Override Node copy(Node lhs, Node rhs) { return new AndNode(_loc,lhs,rhs); }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/BoolNode.java
new file mode 100644
index 00000000..fd6448a2
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/BoolNode.java
@@ -0,0 +1,149 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.type.*;
+import java.util.BitSet;
+import static com.seaofnodes.simple.type.TypeInteger.*;
+
+abstract public class BoolNode extends Node {
+
+ public BoolNode(Node lhs, Node rhs) {
+ super(null, lhs, rhs);
+ }
+
+ abstract public String op(); // String opcode name
+
+ @Override
+ public String label() { return getClass().getSimpleName(); }
+
+ @Override
+ public String glabel() { return op(); }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("("), visited);
+ in(2)._print0(sb.append(op()), visited);
+ return sb.append(")");
+ }
+
+ @Override
+ public TypeInteger compute() {
+ Type t1 = in(1)._type;
+ Type t2 = in(2)._type;
+ // Exactly equals?
+ if( t1.isHigh() || t2.isHigh() )
+ return BOOL.dual();
+ if( in(1)==in(2) )
+ // LT fails, both EQ and LE succeed
+ return this instanceof LT ? FALSE : TRUE;
+ if( t1 instanceof TypeInteger i1 &&
+ t2 instanceof TypeInteger i2 )
+ return doOp(i1,i2);
+ if( t1 instanceof TypeFloat f1 &&
+ t2 instanceof TypeFloat f2 &&
+ f1.isConstant() && f2.isConstant() )
+ return doOp(f1.value(), f2.value()) ? TRUE : FALSE;
+ return BOOL;
+ }
+
+ TypeInteger doOp(TypeInteger t1, TypeInteger t2) { throw Utils.TODO(); }
+ boolean doOp(double lhs, double rhs) { throw Utils.TODO(); }
+ Node copyF(Node lhs, Node rhs) { return null; }
+ public boolean isFloat() { return false; }
+
+ @Override
+ public Node idealize() {
+ // Compare of same
+ if( in(1)==in(2) )
+ return this instanceof LT ? Parser.ZERO : new ConstantNode(TRUE);
+
+ // Equals pushes constant to the right; 5==X becomes X==5.
+ if( this instanceof EQ ) {
+ if( !(in(2) instanceof ConstantNode) ) {
+ // con==noncon becomes noncon==con
+ if( in(1) instanceof ConstantNode || in(1)._nid > in(2)._nid )
+ // Equals sorts by NID otherwise: non.high == non.low becomes non.low == non.high
+ return in(1)._type instanceof TypeFloat ? new EQF(in(2),in(1)) : new EQ(in(2),in(1));
+ }
+ // Equals X==0 becomes a !X
+ if( (in(2)._type == ZERO || in(2)._type == Type.NIL) )
+ return new NotNode(in(1));
+ }
+
+ // Do we have ((x * (phi cons)) * con) ?
+ // Do we have ((x * (phi cons)) * (phi cons)) ?
+ // Push constant up through the phi: x * (phi con0*con0 con1*con1...)
+ Node phicon = AddNode.phiCon(this,this instanceof EQ);
+ if( phicon!=null ) return phicon;
+
+ return null;
+ }
+
+ public static class EQ extends BoolNode {
+ public EQ(Node lhs, Node rhs) { super(lhs,rhs); }
+ public String op() { return "=="; }
+ TypeInteger doOp(TypeInteger i1, TypeInteger i2) {
+ if( i1==i2 && i1.isConstant() ) return TRUE;
+ if( i1._max < i2._min || i1._min > i2._max ) return FALSE;
+ return BOOL;
+ }
+ Node copy(Node lhs, Node rhs) { return new EQ(lhs,rhs); }
+ Node copyF() { return new EQF(null,null); }
+ }
+
+ public static class NE extends BoolNode {
+ public NE(Node lhs, Node rhs) { super(lhs,rhs); }
+ public String op() { return "!="; }
+ TypeInteger doOp(TypeInteger i1, TypeInteger i2) {
+ if( i1==i2 && i1.isConstant() ) return TRUE;
+ if( i1._max < i2._min || i1._min > i2._max ) return FALSE;
+ return BOOL;
+ }
+ Node copy(Node lhs, Node rhs) { return new NE(lhs,rhs); }
+ Node copyF() { throw Utils.TODO(); }
+ }
+
+ public static class LT extends BoolNode {
+ public LT(Node lhs, Node rhs) { super(lhs,rhs); }
+ public String op() { return "<" ; }
+ public String glabel() { return "&lt;"; }
+ TypeInteger doOp(TypeInteger i1, TypeInteger i2) {
+ if( i1._max < i2._min ) return TRUE;
+ if( i1._min >= i2._max ) return FALSE;
+ return BOOL;
+ }
+ Node copy(Node lhs, Node rhs) { return new LT(lhs,rhs); }
+ Node copyF() { return new LTF(null,null); }
+ }
+
+ public static class LE extends BoolNode {
+ public LE(Node lhs, Node rhs) { super(lhs,rhs); }
+ public String op() { return "<="; }
+ public String glabel() { return "&lt;="; }
+ TypeInteger doOp(TypeInteger i1, TypeInteger i2) {
+ if( i1._max <= i2._min ) return TRUE;
+ if( i1._min > i2._max ) return FALSE;
+ return BOOL;
+ }
+ Node copy(Node lhs, Node rhs) { return new LE(lhs,rhs); }
+ Node copyF() { return new LEF(null,null); }
+ }
+
+ public static class EQF extends EQ { public EQF(Node lhs, Node rhs) { super(lhs,rhs); } boolean doOp(double lhs, double rhs) { return lhs == rhs; } public boolean isFloat() { return true; } }
+ public static class LTF extends LT { public LTF(Node lhs, Node rhs) { super(lhs,rhs); } boolean doOp(double lhs, double rhs) { return lhs < rhs; } public boolean isFloat() { return true; } }
+ public static class LEF extends LE { public LEF(Node lhs, Node rhs) { super(lhs,rhs); } boolean doOp(double lhs, double rhs) { return lhs <= rhs; } public boolean isFloat() { return true; } }
+
+ // Unsigned less that, for range checks. Not directly user writable.
+ public static class ULT extends BoolNode {
+ public ULT(Node lhs, Node rhs) { super(lhs,rhs); }
+ public String op() { return "u<" ; }
+ public String glabel() { return "u&lt;"; }
+ TypeInteger doOp(TypeInteger i1, TypeInteger i2) {
+ if( Long.compareUnsigned(i1._max,i2._min) < 0 ) return TRUE;
+ if( Long.compareUnsigned(i1._min,i2._max) >= 0 ) return FALSE;
+ return BOOL;
+ }
+ Node copy(Node lhs, Node rhs) { return new ULT(lhs,rhs); }
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/CFGNode.java
new file mode 100644
index 00000000..56a33a26
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/CFGNode.java
@@ -0,0 +1,192 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Ary;
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.type.*;
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.HashSet;
+
+/** Control Flow Graph Nodes
+ * <p>
+ * CFG nodes have a immediate dominator depth (idepth) and a loop nesting
+ * depth(loop_depth).
+ * <p>
+ * idepth is computed lazily upon first request, and is valid even in the
+ * Parser, and is used by peepholes during parsing and afterward.
+ * <p>
+ * loop_depth is computed after optimization as part of scheduling.
+ *
+ * Start - Block head; has constants and trailing Funs
+ * Region - Block head; has Phis. Includes Fun and Parms.
+ * CallEnd - Block head; followed by CProj and Proj; also linked RPC Parms
+ * CProj - Block head
+ *
+ * If - Block tail; followed by CProj same block
+ * Call - Block tail; followed by CallEnd and linked Funs
+ * New/intrinsic - If followed by CProj: block tail else mid-block; followed by Proj
+ *
+ */
+public abstract class CFGNode extends Node {
+
+ public CFGNode(Node... nodes) { super(nodes); }
+ public CFGNode(CFGNode cfg) {
+ super(cfg);
+ if( cfg != null ) {
+ _idepth = cfg._idepth;
+ _ltree = cfg._ltree;
+ _pre = cfg._pre;
+ }
+ }
+
+ public CFGNode cfg(int idx) { return (CFGNode)in(idx); }
+
+ // Block head is Start, Region, CProj, but not e.g. If, Return, Stop
+ public boolean blockHead() { return false; }
+
+ // Get the one control following; error to call with more than one e.g. an
+ // IfNode or other multi-way branch.
+ public CFGNode uctrl() {
+ CFGNode c = null;
+ for( Node n : _outputs )
+ if( n instanceof CFGNode cfg )
+ { assert c==null; c = cfg; }
+ return c;
+ }
+
+ // Used by the encoding / final BB layout
+ public CFGNode uctrlSkipEmpty() {
+ CFGNode x = this, y;
+ while( x.nOuts() == 1 && (y=x.uctrl())!=null ) // Skip empty blocks
+ x = y;
+ return x;
+ }
+
+ // ------------------------------------------------------------------------
+ /**
+ * Immediate dominator tree depth, used to approximate a real IDOM during
+ * parsing where we do not have the whole program, and also peepholes
+ * change the CFG incrementally.
+ * <p>
+ * See {@link <a href="https://en.wikipedia.org/wiki/Dominator_(graph_theory)">...</a>}
+ */
+ public int _idepth;
+ public int idepth() { return _idepth==0 ? (_idepth=idom().idepth()+1) : _idepth; }
+
+ // Return the immediate dominator of this Node and compute dom tree depth.
+ public CFGNode idom(Node dep) { return cfg(0); }
+ public final CFGNode idom() { return idom(null); }
+
+ // Return the LCA of two idoms
+ public CFGNode _idom(CFGNode rhs, Node dep) {
+ if( rhs==null ) return this;
+ CFGNode lhs = this;
+ while( lhs != rhs ) {
+ var comp = lhs.idepth() - rhs.idepth();
+ if( comp >= 0 ) lhs = (dep==null ? lhs : dep.addDep(lhs)).idom();
+ if( comp <= 0 ) rhs = (dep==null ? rhs : dep.addDep(rhs)).idom();
+ }
+ return lhs;
+ }
+
+ // Anti-dependence field support
+ public int _anti; // Per-CFG field to help find anti-deps
+
+ // Find nearest enclosing FunNode
+ public FunNode fun() {
+ CFGNode cfg = this;
+ while( !(cfg instanceof FunNode fun) )
+ cfg = cfg.idom();
+ return fun;
+ }
+
+ // ------------------------------------------------------------------------
+ // Loop nesting
+ public LoopNode loop() { return _ltree._head; }
+ public int loopDepth() { return _ltree==null ? 0 : _ltree.depth(); }
+
+ public LoopTree _ltree;
+ public int _pre; // Pre-order numbers for loop tree finding
+ static class LoopTree {
+ LoopTree _par;
+ final LoopNode _head;
+ int _depth;
+ LoopTree(LoopNode head) { _head = head; }
+ @Override public String toString() { return "LOOP"+_head._nid; }
+ int depth() {
+ return _depth==0 ? (_par==null ? 0 : (_depth = _par.depth()+1)) : _depth;
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Tag all CFG Nodes with their containing LoopNode; LoopNodes themselves
+ // also refer to *their* containing LoopNode, as well as have their depth.
+ // Start is a LoopNode which contains all at depth 1.
+ public void buildLoopTree(StopNode stop) {
+ _ltree = stop._ltree = Parser.XCTRL._ltree = new LoopTree((StartNode)this);
+ _bltWalk(2,null,stop, new BitSet());
+ }
+ int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post ) {
+ // Pre-walked?
+ if( _pre!=0 ) return pre;
+ _pre = pre++;
+ // Pre-walk
+ for( Node use : _outputs )
+ if( use instanceof CFGNode usecfg && !skip( usecfg ) )
+ pre = usecfg._bltWalk( pre, use instanceof FunNode fuse ? fuse : fun, stop, post );
+
+ // Post-order work: find innermost loop
+ LoopTree inner = null, ltree;
+ for( Node use : _outputs ) {
+ if( !(use instanceof CFGNode usecfg) ) continue;
+ if( skip(usecfg) ) continue;
+ if( usecfg._type == Type.XCONTROL || // Do not walk dead control
+ usecfg._type == TypeTuple.IF_NEITHER ) // Nor dead IFs
+ continue;
+ // Child visited but not post-visited?
+ if( !post.get(usecfg._nid) ) {
+ // Must be a backedge to a LoopNode then
+ ltree = usecfg._ltree = new LoopTree((LoopNode)usecfg);
+ } else {
+ // Take child's loop choice, which must exist
+ ltree = usecfg._ltree;
+ // If falling into a loop, use the target loop's parent instead
+ if( ltree._head == usecfg ) {
+ if( ltree._par == null )
+ // This loop never had an If test choose to take its
+ // exit, i.e. it is a no-exit infinite loop.
+ ltree._par = ltree._head.forceExit(fun,stop)._ltree;
+ ltree = ltree._par;
+ }
+ }
+ // Sort inner loops. The decision point is some branch far removed
+ // from either loop head OR either backedge so requires pre-order
+ // numbers to figure out innermost.
+ if( inner == null ) { inner = ltree; continue; }
+ if( inner == ltree ) continue; // No change
+ LoopTree outer = ltree._head._pre > inner._head._pre ? inner : ltree;
+ inner = ltree._head._pre > inner._head._pre ? ltree : inner;
+ inner._par = outer;
+ }
+ // Set selected loop
+ if( inner!=null )
+ _ltree = inner;
+ // Tag as post-walked
+ post.set(_nid);
+ return pre;
+ }
+
+ private boolean skip(CFGNode usecfg) {
+ // Only walk control users that are alive.
+ // Do not walk from a Call to linked Fun's.
+ return usecfg instanceof XCtrlNode ||
+ (this instanceof CallNode && usecfg instanceof FunNode) ||
+ (this instanceof ReturnNode && usecfg instanceof CallEndNode);
+ }
+
+
+ public String label( CFGNode target ) {
+ return (target instanceof LoopNode ? "LOOP" : "L")+target._nid;
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/CProjNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/CProjNode.java
new file mode 100644
index 00000000..94770f16
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/CProjNode.java
@@ -0,0 +1,68 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeMem;
+import com.seaofnodes.simple.type.TypeTuple;
+import java.util.BitSet;
+
+public class CProjNode extends CFGNode {
+
+ // Which slice of the incoming multipart value
+ public int _idx;
+
+ // Debugging label
+ public String _label;
+
+ public CProjNode(Node ctrl, int idx, String label) {
+ super(ctrl);
+ _idx = idx;
+ _label = label;
+ }
+ public CProjNode(CProjNode c) { super(c); _idx = c._idx; _label = c._label; }
+
+ @Override public String label() { return _label; }
+
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(_label); }
+
+ @Override public boolean blockHead() { return true; }
+
+ public CFGNode ctrl() { return cfg(0); }
+
+ @Override
+ public Type compute() {
+ Type t = ctrl()._type;
+ return t instanceof TypeTuple tt ? tt._types[_idx] : Type.BOTTOM;
+ }
+
+ @Override
+ public Node idealize() {
+ if( ctrl()._type instanceof TypeTuple tt ) {
+ if( tt._types[_idx]==Type.XCONTROL )
+ return Parser.XCTRL; // We are dead
+ if( ctrl() instanceof IfNode && tt._types[1-_idx]==Type.XCONTROL ) // Only true for IfNodes
+ return ctrl().in(0); // We become our input control
+ }
+
+ // Flip a negating if-test, to remove the not
+ if( ctrl() instanceof IfNode iff && addDep(iff.pred()) instanceof NotNode not )
+ return new CProjNode(new IfNode(iff.ctrl(),not.in(1)).peephole(),1-_idx,_idx==0 ? "False" : "True");
+
+ // Copy of some other input
+ return ((MultiNode)ctrl()).pcopy(_idx);
+ }
+
+ // Only called during basic-block layout, inverts a T/F CProj
+ public void invert() {
+ _label = _idx == 0 ? "False" : "True";
+ _idx = 1-_idx;
+ }
+
+ @Override
+ public boolean eq( Node n ) { return _idx == ((CProjNode)n)._idx; }
+
+ @Override
+ int hash() { return _idx; }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
new file mode 100644
index 00000000..47975833
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
@@ -0,0 +1,130 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.type.*;
+import java.util.BitSet;
+
+/**
+ * CallEnd
+ */
+public class CallEndNode extends CFGNode implements MultiNode {
+
+ // When set true, this Call/CallEnd/Fun/Return is being trivially inlined
+ private boolean _folding;
+ public final TypeRPC _rpc;
+
+ public CallEndNode(CallNode call) { super(new Node[]{call}); _rpc = TypeRPC.constant(_nid); }
+ public CallEndNode(CallEndNode cend) { super(cend); _rpc = cend._rpc; }
+
+ @Override public String label() { return "CallEnd"; }
+ @Override public boolean blockHead() { return true; }
+
+ public CallNode call() { return (CallNode)in(0); }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("cend( ");
+ sb.append( in(0) instanceof CallNode ? "Call, " : "----, ");
+ for( int i=1; i<nIns()-1; i++ )
+ in(i)._print0(sb,visited).append(",");
+ sb.setLength(sb.length()-1);
+ return sb.append(")");
+ }
+
+ @Override
+ public Type compute() {
+ if( !(in(0) instanceof CallNode call) )
+ return TypeTuple.RET.dual();
+ Type ret = Type.BOTTOM;
+ TypeMem mem = TypeMem.BOT;
+ 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.
+ if( tfp.isConstant() && nIns()>1 ) {
+ assert nIns()==2; // Linked exactly once for a constant
+ ret = ((TypeTuple)in(1)._type).ret(); // Return type
+ }
+ }
+ return TypeTuple.make(call._type,TypeMem.BOT,ret);
+ }
+
+ @Override
+ public Node idealize() {
+
+ // Trivial inlining: call site calls a single function; single function
+ // is only called by this call site.
+ if( !_folding && nIns()==2 && in(0) instanceof CallNode call ) {
+ Node fptr = call.fptr();
+ if( fptr.nOuts() == 1 && // Only user is this call
+ fptr instanceof ConstantNode && // We have an immediate call
+ // Function is being called, and its not-null
+ fptr._type instanceof TypeFunPtr tfp && tfp.notNull() &&
+ // Arguments are correct
+ call.err()==null ) {
+ ReturnNode ret = (ReturnNode)in(1);
+ FunNode fun = ret.fun();
+ // Expecting Start, and the Call
+ if( fun.nIns()==3 ) {
+ assert fun.in(1) instanceof StartNode && fun.in(2)==call;
+ // Disallow self-recursive inlining (loop unrolling by another name)
+ CFGNode idom = call;
+ while( !(idom instanceof FunNode fun2) )
+ idom = idom.idom();
+ if( idom != fun ) {
+ // Trivial inline: rewrite
+ _folding = true;
+ // Rewrite Fun so the normal RegionNode ideal collapses
+ fun._folding = true;
+ fun.setDef(1,Parser.XCTRL); // No default/unknown StartNode caller
+ fun.setDef(2,call.ctrl()); // Bypass the Call;
+ fun.ret().setDef(3,null); // Return is folding also
+ CodeGen.CODE.addAll(fun._outputs);
+ return this;
+ }
+ } else {
+ addDep(fun);
+ }
+ } else { // Function ptr has multiple users (so maybe multiple call sites)
+ addDep(fptr);
+ }
+ }
+
+ return null;
+ }
+
+ @Override public Node pcopy(int idx) {
+ return _folding ? in(1).in(idx) : null;
+ }
+
+ // ------------
+ // MachNode specifics, shared across all CPUs
+ public int _xslot;
+ private RegMask _retMask;
+ private RegMask _kills;
+ public void cacheRegs(CodeGen code) {
+ // Return mask depends on TFP (either GPR or FPR)
+ _retMask = code._mach.retMask(call().tfp());
+ // Kill mask is all caller-saves, and any mirror stack slots for args
+ // in registers.
+ RegMaskRW kills = code._callerSave.copy();
+ // Start of stack slots
+ int maxReg = code._mach.regs().length;
+ // Incoming function arg slots, all low numbered in the RA
+ int fslot = fun()._maxArgSlot;
+ // Killed slots for this calls outgoing args
+ int xslot = code._mach.maxArgSlot(call().tfp());
+ _xslot = (maxReg+fslot)+xslot;
+ for( int i=0; i<xslot; i++ )
+ kills.set((maxReg+fslot)+i);
+ _kills = kills;
+ }
+ public String op() { return "cend"; }
+ public RegMask regmap(int i) { return null; }
+ public RegMask outregmap() { return null; }
+ public RegMask outregmap(int idx) { return idx==2 ? _retMask : null; }
+ public RegMask killmap() { return _kills; }
+ public void encoding( Encoding enc ) { }
+ public void asm(CodeGen code, SB sb) { }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/CallNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/CallNode.java
new file mode 100644
index 00000000..773716f7
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/CallNode.java
@@ -0,0 +1,168 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.IterPeeps;
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFunPtr;
+import java.util.BitSet;
+
+/**
+ * Call
+ */
+public class CallNode extends CFGNode {
+
+ // Source location for late reported errors
+ public final Parser.Lexer _loc;
+
+ public CallNode(Parser.Lexer loc, Node... nodes) { super(nodes); _loc = loc; }
+ public CallNode(CallNode call) { super(call); _loc = call._loc; }
+
+ @Override public String label() { return "Call"; }
+
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ String fname = name();
+ if( fname == null ) fptr()._print0(sb,visited);
+ else sb.append(fname);
+ sb.append("( ");
+ for( int i=2; i<nIns()-1; i++ )
+ in(i)._print0(sb,visited).append(",");
+ sb.setLength(sb.length()-1);
+ return sb.append(")");
+ }
+ public String name() {
+ if( fptr()._type instanceof TypeFunPtr tfp && tfp.isConstant() )
+ return CodeGen.CODE.link(tfp)._name;
+ return null;
+ }
+
+
+
+ Node ctrl() { return in(0); }
+ Node mem () { return in(1); }
+ // Args mapped 1-to-1 on inputs, so conceptually start at 2
+ public Node arg(int idx) { return in(idx); }
+ // Same arg accounting as TFPs, although the numbering starts at 2
+ public int nargs() { return nIns()-3; } // Minus control, memory, fptr
+ // args from input 2 to last; last is function input
+ public Node fptr() { return _inputs.last(); }
+ // Error if not a TFP
+ public TypeFunPtr tfp() { return (TypeFunPtr)fptr()._type; }
+
+ // Call is to an externally supplied code
+ public boolean external() { return false; }
+
+
+ // Find the Call End from the Call
+ public CallEndNode cend() {
+ // Always in use slot 0
+ if( nOuts()>0 && out(0) instanceof CallEndNode cend ) {
+ assert _cend()==cend;
+ return cend;
+ } else {
+ assert _cend()==null;
+ return null;
+ }
+ }
+ private CallEndNode _cend() {
+ CallEndNode cend=null;
+ for( Node n : _outputs )
+ if( n instanceof CallEndNode cend0 )
+ { assert cend == null; cend = cend0; }
+ return cend;
+ }
+
+ // Get the one control following; error to call with more than one e.g. an
+ // IfNode or other multi-way branch.
+ @Override public CFGNode uctrl() { return cend(); }
+
+
+ @Override
+ public Type compute() {
+ return ctrl()._type;
+ }
+
+ @Override
+ public Node idealize() {
+ CallEndNode cend = cend();
+ if( cend==null ) return null; // Still building
+
+ // Link: call calls target function. Linking makes the target FunNode
+ // point to this Call, and all his Parms point to the call arguments;
+ // also the CallEnd points to the Return.
+ Node progress = null;
+ if( fptr()._type instanceof TypeFunPtr tfp && tfp.nargs() == nargs() ) {
+ // If fidxs is negative, then infinite unknown functions
+ long fidxs = tfp.fidxs();
+ if( fidxs > 0 ) {
+ // Wipe out the return which matching in the linker table
+ // Walk the (63 max) bits and link
+ for( ; fidxs!=0; fidxs = TypeFunPtr.nextFIDX(fidxs) ) {
+ int fidx = Long.numberOfTrailingZeros(fidxs);
+ TypeFunPtr tfp0 = tfp.makeFrom(fidx);
+ FunNode fun = CodeGen.CODE.link(tfp0);
+ if( fun!=null && !fun._folding && !linked(fun) )
+ progress = link(fun);
+ }
+ }
+ }
+
+ return progress;
+ }
+
+ // True if Fun is linked to this Call
+ boolean linked( FunNode fun ) {
+ for( Node n : fun._inputs )
+ if( n == this )
+ return true;
+ return false;
+ }
+
+
+ // Link so this calls fun
+ private Node link( FunNode fun ) {
+ assert !linked(fun);
+ fun.addDef(this);
+ for( Node use : fun._outputs )
+ if( use instanceof ParmNode parm )
+ parm.addDef(parm._idx==0 ? new ConstantNode(cend()._rpc).peephole() : arg(parm._idx));
+ // Call end points to function return
+ CodeGen.CODE.add(cend()).addDef(fun.ret());
+ return this;
+ }
+
+ // Unlink all linked functions
+ public void unlink_all() {
+ for( int i=0; i<_outputs._len; i++ )
+ if( out(i) instanceof FunNode fun ) {
+ assert linked(fun);
+ int idx = fun._inputs.find(this);
+ for( Node use : fun._outputs )
+ if( use instanceof ParmNode parm )
+ use.delDef(idx);
+ fun.delDef(idx);
+ cend().delDef(cend()._inputs.find(fun.ret()));
+ assert !linked(fun);
+ i--;
+ }
+ }
+
+ @Override
+ public Parser.ParseException err() {
+ if( !(fptr()._type instanceof TypeFunPtr tfp) )
+ throw Utils.TODO();
+ if( !tfp.notNull() )
+ return Parser.error( "Might be null calling "+tfp, _loc);
+ if( nargs() != tfp.nargs() )
+ return Parser.error( "Expecting "+tfp.nargs()+" arguments, but found "+nargs(), _loc);
+
+ // Check for args
+ for( int i=0; i<tfp.nargs(); i++ )
+ if( !arg(i+2)._type.isa(tfp.arg(i)) )
+ return Parser.error( "Argument #"+i+" isa "+arg(i+2)._type+", but must be a "+tfp.arg(i), _loc);
+
+ return null;
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java
new file mode 100644
index 00000000..0afc5827
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java
@@ -0,0 +1,23 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.type.Type;
+
+public class CalleeSaveNode extends ProjNode implements MachNode {
+ final RegMask _mask;
+ public CalleeSaveNode(FunNode fun, int reg, String label) {
+ super(fun,reg,label);
+ fun._outputs.pop();
+ int i=0;
+ while( fun.out(i) instanceof PhiNode ) i++;
+ fun._outputs.insert(this,i);
+ _mask = new RegMask(reg);
+ }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return _mask; }
+ @Override public void encoding( Encoding enc ) { }
+
+ @Override public Type compute() { return Type.BOTTOM; }
+ @Override public Node idealize() { return null; }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/CastNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/CastNode.java
new file mode 100644
index 00000000..0947bcf0
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/CastNode.java
@@ -0,0 +1,64 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import java.util.BitSet;
+
+// Upcast (join) the input to a t. Used after guard test to lift an input.
+// Can also be used to make a type-assertion if ctrl is null.
+public class CastNode extends Node {
+ public Type _t;
+ public CastNode(Type t, Node ctrl, Node in) {
+ super(ctrl, in);
+ _t = t;
+ setType(compute());
+ }
+
+ public CastNode(CastNode c) {
+ super(c.in(0), c.in(1)); // Call parent constructor
+ this._t = c._t; // Copy the Type field
+ setType(compute()); // Ensure type is recomputed
+ }
+
+ @Override public String label() { return "("+_t.str()+")"; }
+
+ @Override
+ public String uniqueName() { return "Cast_" + _nid; }
+
+ @Override public boolean isPinned() { return true; }
+ @Override public boolean isConst() { return true; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return in(1)._print0(sb.append(label()), visited);
+ }
+
+ @Override
+ public Type compute() {
+ return in(1)._type.join(_t);
+ }
+
+ @Override
+ public Node idealize() {
+ return in(1)._type.isa(_t) ? in(1) : null;
+ }
+
+ @Override
+ public boolean eq(Node n) {
+ CastNode cast = (CastNode)n; // Contract
+ return _t==cast._t;
+ }
+
+ @Override
+ int hash() { return _t.hashCode(); }
+
+ @Override
+ public Parser.ParseException err() {
+ // Has a condition to test, so OK
+ if( in(0) != null ) return null;
+ // No condition to test, so this must optimize away
+ throw Utils.TODO();
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
new file mode 100644
index 00000000..79f301c1
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
@@ -0,0 +1,70 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFunPtr;
+import java.util.BitSet;
+
+/**
+ * A Constant node represents a constant value. At present, the only constants
+ * that we allow are integer literals; therefore Constants contain an integer
+ * value. As we add other types of constants, we will refactor how we represent
+ * Constants.
+ * <p>
+ * Constants have no semantic inputs. However, we set Start as an input to
+ * Constants to enable a forward graph walk. This edge carries no semantic
+ * meaning, and it is present <em>solely</em> to allow visitation.
+ * <p>
+ * The Constant's value is the value stored in it.
+ */
+
+public class ConstantNode extends Node {
+ public final Type _con;
+ public ConstantNode( Type type ) {
+ super(new Node[]{CodeGen.CODE._start});
+ _con = _type = type;
+ }
+ public ConstantNode( Node con, Type t ) { super(con); _con = t; }
+ public ConstantNode( ConstantNode con ) { this(con,con._type); }
+
+ public static Node make( Type type ) {
+ if( type==Type. CONTROL ) return new CtrlNode();
+ if( type==Type.XCONTROL ) return new XCtrlNode();
+ return new ConstantNode(type);
+ }
+
+ @Override public String label() { return "#"+_con; }
+ @Override public String glabel() { return _con.gprint(new SB().p("#")).toString(); }
+
+ @Override
+ public String uniqueName() { return "Con_" + _nid; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ if( _con instanceof TypeFunPtr tfp && tfp.isConstant() ) {
+ FunNode fun = CodeGen.CODE.link(tfp);
+ if( fun!=null && fun._name != null )
+ return sb.append("{ ").append(fun._name).append("}");
+ }
+ return sb.append(_con==null ? "---" : _con.print(new SB()));
+ }
+
+ @Override public boolean isConst() { return true; }
+
+ @Override
+ public Type compute() { return _con; }
+
+ @Override
+ public Node idealize() { return null; }
+
+ @Override
+ public boolean eq(Node n) {
+ ConstantNode con = (ConstantNode)n; // Contract
+ return _con==con._con;
+ }
+
+ @Override
+ int hash() { return _con.hashCode(); }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/CtrlNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/CtrlNode.java
new file mode 100644
index 00000000..eda7d684
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/CtrlNode.java
@@ -0,0 +1,15 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.Type;
+import java.util.BitSet;
+
+public class CtrlNode extends CFGNode {
+ public CtrlNode() { super(CodeGen.CODE._start); }
+ @Override public String label() { return "Ctrl"; }
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append("Cctrl"); }
+ @Override public boolean isConst() { return true; }
+ @Override public Type compute() { return Type.CONTROL; }
+ @Override public Node idealize() { return null; }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/DivFNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/DivFNode.java
new file mode 100644
index 00000000..633f3224
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/DivFNode.java
@@ -0,0 +1,41 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFloat;
+
+import java.util.BitSet;
+
+public class DivFNode extends Node {
+ public DivFNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+
+ @Override public String label() { return "DivF"; }
+
+ @Override public String glabel() { return "/"; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("("), visited);
+ in(2)._print0(sb.append("/"), visited);
+ return sb.append(")");
+ }
+
+ @Override
+ public Type compute() {
+ if (in(1)._type instanceof TypeFloat i0 &&
+ in(2)._type instanceof TypeFloat i1) {
+ if (i0.isConstant() && i1.isConstant())
+ return TypeFloat.constant(i0.value()/i1.value());
+ }
+ return in(1)._type.meet(in(2)._type);
+ }
+
+ @Override
+ public Node idealize() {
+ // Div of constant
+ if( in(2)._type instanceof TypeFloat f && f.isConstant() )
+ return new MulFNode(in(1),new ConstantNode(TypeFloat.constant(1.0/f.value())).peephole());
+
+ return null;
+ }
+ @Override Node copy(Node lhs, Node rhs) { return new DivFNode(lhs,rhs); }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/DivNode.java
new file mode 100644
index 00000000..41518311
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/DivNode.java
@@ -0,0 +1,46 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import java.util.BitSet;
+
+public class DivNode extends Node {
+ public DivNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+
+ @Override public String label() { return "Div"; }
+
+ @Override public String glabel() { return "//"; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("("), visited);
+ in(2)._print0(sb.append("/"), visited);
+ return sb.append(")");
+ }
+
+ @Override
+ public Type compute() {
+ Type t1 = in(1)._type, t2 = in(2)._type;
+ if( t1.isHigh() || t2.isHigh() )
+ return TypeInteger.TOP;
+ if( t1 instanceof TypeInteger i1 &&
+ t2 instanceof TypeInteger i2 ) {
+ if (i1.isConstant() && i2.isConstant())
+ return i2.value() == 0
+ ? TypeInteger.ZERO
+ : TypeInteger.constant(i1.value()/i2.value());
+ }
+ return TypeInteger.BOT;
+ }
+
+ @Override
+ public Node idealize() {
+ // Div of 1.
+ if( in(2)._type == TypeInteger.TRUE )
+ return in(1);
+ return null;
+ }
+
+ @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs); }
+ @Override Node copyF() { return new DivFNode(null,null); }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/FRefNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/FRefNode.java
new file mode 100644
index 00000000..6093b8a6
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/FRefNode.java
@@ -0,0 +1,35 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.Var;
+import com.seaofnodes.simple.type.Type;
+import java.util.BitSet;
+
+/**
+ * A Forward Reference. Its any final constant, including functions. When
+ * the Def finally appears its plugged into the forward reference, which then
+ * peepholes to the Def.
+ */
+public class FRefNode extends ConstantNode {
+ public static final Type FREF_TYPE = Type.BOTTOM;
+ public final Var _n;
+ public FRefNode( Var n ) { super(FREF_TYPE); _n = n; }
+
+ @Override public String label() { return "FRef"+_n; }
+
+ @Override public String uniqueName() { return "FRef_" + _nid; }
+
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return sb.append("FRef_").append(_n);
+ }
+
+ @Override public Node idealize() {
+ // When FRef finds its definition, idealize to it
+ return nIns()==1 ? null : in(1);
+ }
+
+ public Parser.ParseException err() { return Parser.error("Undefined name '"+_n._name+"'",_n._loc); }
+
+ @Override public boolean eq(Node n) { return this==n; }
+ @Override int hash() { return _n._idx; }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/FunNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/FunNode.java
new file mode 100644
index 00000000..c477e125
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/FunNode.java
@@ -0,0 +1,216 @@
+package com.seaofnodes.simple.node;
+
+
+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.type.Type;
+import com.seaofnodes.simple.type.TypeFunPtr;
+import com.seaofnodes.simple.type.TypeTuple;
+import java.util.BitSet;
+import static com.seaofnodes.simple.codegen.CodeGen.CODE;
+
+public class FunNode extends RegionNode {
+
+ // When set true, this Call/CallEnd/Fun/Return is being trivially inlined
+ boolean _folding;
+
+ private TypeFunPtr _sig; // Initial signature
+ private ReturnNode _ret; // Return pointer
+
+ public String _name; // Debug name
+
+ public FunNode( Parser.Lexer loc, TypeFunPtr sig, Node... nodes ) { super(loc,nodes); _sig = sig; }
+ public FunNode( FunNode fun ) {
+ super( fun, fun==null ? null : fun._loc );
+ if( fun!=null ) {
+ _sig = fun.sig();
+ _name = fun._name;
+ } else {
+ _sig = TypeFunPtr.BOT;
+ _name = "";
+ }
+ }
+
+ @Override
+ public String label() { return _name == null ? "$fun"+_sig.fidx() : _name; }
+
+ // Find the one CFG user from Fun. It's not always the Return, but always
+ // the Return *is* a CFG user of Fun.
+ @Override public CFGNode uctrl() {
+ for( Node n : _outputs )
+ if( n instanceof CFGNode cfg &&
+ (cfg instanceof RegionNode || cfg.cfg0()==this) )
+ return cfg;
+ return null;
+ }
+
+ public ParmNode rpc() {
+ ParmNode rpc = null;
+ for( Node n : _outputs )
+ if( n instanceof ParmNode parm && parm._idx==0 )
+ { assert rpc==null; rpc=parm; }
+ return rpc;
+ }
+
+ // Cannot create the Return and Fun at the same time; one has to be first.
+ // So setting the return requires a second step.
+ public void setRet(ReturnNode ret) { _ret=ret; }
+ public ReturnNode ret() { assert _ret!=null; return _ret; }
+
+ // Signature can improve over time
+ public TypeFunPtr sig() { return _sig; }
+ public void setSig( TypeFunPtr sig ) {
+ assert sig.isa(_sig);
+ if( _sig != sig ) {
+ CODE.add(this);
+ _sig = sig;
+ }
+ }
+
+ @Override
+ public Type compute() {
+ // Only dead if no callers after SCCP
+ return Type.CONTROL;
+ }
+
+ @Override
+ public Node idealize() {
+
+ // Some linked path dies
+ Node progress = deadPath();
+ if( progress!=null ) {
+ if( nIns()==3 && in(2) instanceof CallNode call )
+ CODE.add(call.cend()); // If Start and one call, check for inline
+ return progress;
+ }
+
+ // Upgrade inferred or user-written return type to actual
+ if( _ret!=null && _ret._type instanceof TypeTuple tt && tt.ret() != _sig.ret() )
+ throw Utils.TODO();
+
+ // When can we assume no callers? Or no other callers (except main)?
+ // In a partial compilation, we assume Start gets access to any/all
+ // top-level public structures and recursively what they point to.
+ // This in turn is valid arguments to every callable function.
+ //
+ // In a total compilation, we can start from Start and keep things
+ // more contained.
+
+ // If no default/unknown caller, use the normal RegionNode ideal rules
+ // to collapse
+ if( unknownCallers() ) return null;
+
+ // If down to a single input, become that input
+ if( nIns()==2 && !hasPhi() ) {
+ CODE.add( CODE._stop ); // Stop will remove dead path
+ CODE.add( _ret ); // Return will compute to TOP control
+ return in(1); // Collapse if no Phis; 1-input Phis will collapse on their own
+ }
+
+ return null;
+ }
+
+ // Bypass Region idom, always assume depth == 1, one more than Start
+ @Override public int idepth() { return (_idepth=1); }
+ // Bypass Region idom, always assume idom is Start
+ @Override public CFGNode idom(Node dep) { return cfg(1); }
+
+ // Always in-progress until we run out of unknown callers
+ public boolean unknownCallers() { return in(1) instanceof StartNode; }
+
+ @Override public boolean inProgress() { return unknownCallers(); }
+
+ // Add a new function exit point.
+ public void addReturn(Node ctrl, Node mem, Node rez) { _ret.addReturn(ctrl,mem,rez); }
+
+ // Build the function body
+ public BitSet body() {
+
+ // Reverse up (stop to start) CFG only, collect bitmap.
+ BitSet cfgs = new BitSet();
+ cfgs.set(_nid);
+ walkUp(ret(),cfgs );
+
+ // Top down (start to stop) all flavors. CFG limit to bitmap.
+ // If data use bottoms out in wrong CFG, returns false - but tries all outputs.
+ // If any output hits an in-CFG use (e.g. phi), then keep node.
+ BitSet body = new BitSet();
+ walkDown(this, cfgs, body, new BitSet());
+ return body;
+ }
+
+ private static void walkUp(CFGNode n, BitSet cfgs) {
+ if( cfgs.get(n._nid) ) return;
+ cfgs.set(n._nid);
+ if( n instanceof RegionNode r )
+ for( int i=1; i<n.nIns(); i++ )
+ walkUp(n.cfg(i),cfgs);
+ else walkUp(n.cfg0(),cfgs);
+ }
+
+ private static boolean walkDown( Node n, BitSet cfgs, BitSet body, BitSet visit ) {
+ if( visit.get(n._nid) ) return body.get(n._nid);
+ visit.set(n._nid);
+
+ if( n instanceof CFGNode && !cfgs.get(n._nid) )
+ return false;
+ if( n.in(0)!=null && !cfgs.get(n.in(0)._nid) )
+ return false;
+ boolean in = n.in(0)!=null || n instanceof CFGNode;
+ for( Node use : n._outputs )
+ in |= walkDown(use,cfgs,body,visit);
+ if( in ) body.set(n._nid);
+ return in;
+ }
+
+
+ // ------------
+ // MachNode specifics, shared across all CPUs
+
+ // Function may call other functions? X86 requires 16b aligned SP on
+ // outbound calls, but not leaf. Set during an unrelated RA scan.
+ public boolean _hasCalls;
+
+ // Maximum incoming argument slot, based on function type
+ public int _maxArgSlot;
+
+ // Frame adjust, including padding, set during early Encoding
+ public int _frameAdjust;
+
+ public String op() { return _frameAdjust==0 ? "entry" : "subi"; }
+ public void postSelect(CodeGen code) {
+ code.link(this);
+ // Max slot seen past space reserved for incoming arguments
+ _maxArgSlot = code._mach.maxArgSlot(sig());
+ }
+ public RegMask regmap(int i) { return null; }
+ public RegMask outregmap() { return null; }
+ public void encoding( Encoding enc ) { throw Utils.TODO(); }
+ public void asm(CodeGen code, SB sb) {
+ if( _frameAdjust!=0 )
+ sb.p("rsp -= #").p(_frameAdjust);
+ }
+
+ public void computeFrameAdjust(CodeGen code, int maxReg) {
+ // Max slot seen, or 0.
+ int maxSlot = Math.max(maxReg - code._mach.regs().length,_maxArgSlot);
+ // Frame adjust before rounding, can be negative for no spills
+ int size = maxSlot - _maxArgSlot;
+ _frameAdjust = size*8;
+ }
+
+ // Given a high register number and during encoding compute the stack
+ // pointer offset. Pre-RA we lack the fun._frameAdjust. We also need a
+ // FunNode.
+ public int computeStackOffset(CodeGen code, int reg) {
+ String[] regs = code._mach.regs();
+ int stackSlot = reg - regs.length;
+ int slotRotate = stackSlot < _maxArgSlot
+ ? stackSlot + (_frameAdjust>>3)
+ : stackSlot - _maxArgSlot;
+ return slotRotate*8;
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/IfNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/IfNode.java
new file mode 100644
index 00000000..5d90449e
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/IfNode.java
@@ -0,0 +1,97 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.*;
+import java.util.BitSet;
+
+public class IfNode extends CFGNode implements MultiNode {
+
+ public IfNode(Node ctrl, Node pred) {
+ super(ctrl, pred);
+ CodeGen.CODE.add(this); // Because idoms are complex, just add it
+ }
+ public IfNode(IfNode iff) { super(iff); }
+
+ @Override
+ public String label() { return "If"; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("if( ");
+ if( in(1)==null ) sb.append("never");
+ else in(1)._print0(sb, visited);
+ return sb.append(" )");
+ }
+
+ public Node ctrl() { return in(0); }
+ public Node pred() { return in(1); }
+
+ // No one unique control follows
+ @Override public CFGNode uctrl() { return null; }
+
+ @Override
+ public Type compute() {
+ // If the If node is not reachable then neither is any following Proj
+ if (ctrl()._type != Type.CONTROL && ctrl()._type != Type.BOTTOM )
+ return TypeTuple.IF_NEITHER;
+ Node pred = pred();
+ Type t = pred._type;
+ // High types mean NEITHER side is reachable.
+ // Wait until the type falls to decide which way to go.
+ if( t.isHigh() )
+ return TypeTuple.IF_NEITHER;
+ // If constant is 0 then false branch is reachable
+ // Else true branch is reachable
+ if( t.isConstant() )
+ return (t==Type.NIL || t==TypeInteger.ZERO || (t instanceof TypeFunPtr tfp && tfp._fidxs==0) ) ? TypeTuple.IF_FALSE : TypeTuple.IF_TRUE;
+ // If adding a zero makes a difference, the predicate must not have a zero/null
+ if( !t.makeZero().isa(t) )
+ return TypeTuple.IF_TRUE;
+
+ return TypeTuple.IF_BOTH;
+ }
+
+ @Override
+ public Node idealize() {
+ // Hunt up the immediate dominator tree. If we find an identical if
+ // test on either the true or false branch, that side wins.
+ if( !pred()._type.isHighOrConst() )
+ for( CFGNode dom = idom(), prior=this; dom!=null; prior = dom, dom = dom.idom() )
+ if( addDep(dom) instanceof IfNode iff && addDep(iff.pred())==pred() && prior instanceof CProjNode prj ) {
+ setDef(1,con( prj._idx==0 ? 1 : 0 ));
+ return this;
+ }
+ 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/chapter21/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/LoadNode.java
new file mode 100644
index 00000000..6147de81
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -0,0 +1,210 @@
+package com.seaofnodes.simple.node;
+
+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,
+ * in chapter 10 this means Struct fields.
+ */
+public class LoadNode extends MemOpNode {
+
+ /**
+ * Load a value from a ptr.field.
+ *
+ * @param name The field we are loading
+ * @param mem The memory alias node - this is updated after a Store
+ * @param ptr The ptr to the struct base from where we load a field
+ * @param off The offset inside the struct base
+ */
+ public LoadNode(Parser.Lexer loc, String name, int alias, Type glb, Node mem, Node ptr, Node off) {
+ super(loc, name, alias, true, glb, mem, ptr, off);
+ }
+
+ // GraphVis DOT code (must be valid Java identifiers) and debugger labels
+ @Override public String label() { return "ld_"+mlabel(); }
+ // GraphVis node-internal labels
+ @Override public String glabel() { return "." +_name; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); }
+
+ @Override
+ public Type compute() {
+ if( mem()._type instanceof TypeMem mem ) {
+ // Update declared forward ref to the actual
+ if( _declaredType.isFRef() && mem._t instanceof TypeMemPtr tmp && !tmp.isFRef() )
+ _declaredType = tmp;
+ // No lifting if ptr might null-check
+ if( err()==null )
+ return _declaredType.join(mem._t);
+ }
+ return _declaredType;
+ }
+
+ @Override
+ public Node idealize() {
+ Node ptr = ptr();
+ Node mem = mem();
+
+ // Simple Load-after-Store on same address.
+ if( mem instanceof StoreNode st &&
+ ptr == st.ptr() && off() == st.off() ) { // Must check same object
+ assert _name.equals(st._name); // Equiv class aliasing is perfect
+ return extend(st.val());
+ }
+
+ // Simple Load-after-New on same address.
+ if( mem instanceof ProjNode p && p.in(0) instanceof NewNode nnn &&
+ ptr == nnn.proj(1) ) // Must check same object
+ return zero(nnn); // Load zero from new
+
+ // Uplift control to a prior dominating load.
+ for( Node memuse : mem._outputs )
+ // Find a prior load, has same mem,ptr,off but higher ctrl
+ if( memuse != this && memuse instanceof LoadNode ld && ptr==ld.ptr() && off()==ld.off() &&
+ cfg0()!=null && cfg0()._idom(ld.cfg0(),this) == ld.cfg0() ) // Higher control means load is legal earlier
+ return ld;
+
+ // Load-after-Store on same address, but bypassing provably unrelated
+ // stores. This is a more complex superset of the above two peeps.
+ // "Provably unrelated" is really weak.
+ if( ptr instanceof ReadOnlyNode ro )
+ ptr = ro.in(1);
+ outer:
+ while( true ) {
+ switch( mem ) {
+ case StoreNode st:
+ if( ptr == addDep(st.ptr()) && off() == st.off() )
+ return extend(castRO(st.val())); // Proved equal
+ // Can we prove unequal? Offsets do not overlap?
+ if( !off()._type.join(st.off()._type).isHigh() && // Offsets overlap
+ !neverAlias(ptr,st.ptr()) ) // And might alias
+ break outer; // Cannot tell, stop trying
+ // Pointers cannot overlap
+ mem = st.mem(); // Proved never equal
+ break;
+ case PhiNode phi:
+ // Assume related
+ addDep(phi);
+ break outer;
+ case ConstantNode top: break outer; // Assume shortly dead
+ case ProjNode mproj: // Memory projection
+ switch( mproj.in(0) ) {
+ case NewNode nnn1:
+ if( ptr instanceof ProjNode pproj && pproj.in(0) == mproj.in(0) )
+ return zero(nnn1);
+ if( !(ptr instanceof ProjNode pproj && pproj.in(0) instanceof NewNode) )
+ break outer; // Cannot tell, ptr not related to New
+ mem = nnn1.in(nnn1.findAlias(_alias));// Bypass unrelated New
+ break;
+ case StartNode start: break outer;
+ case CallEndNode cend: break outer; // TODO: Bypass no-alias call
+ default: throw Utils.TODO();
+ }
+ break;
+ case MemMergeNode merge: mem = merge.alias(_alias); break;
+
+ default:
+ throw Utils.TODO();
+ }
+ }
+
+ // Push a Load up through a Phi, as long as it collapses on at least
+ // one arm. If at a Loop, the backedge MUST collapse - else we risk
+ // spinning the same transform around the loop indefinitely.
+ // BEFORE (2 Sts, 1 Ld): AFTER (1 St, 0 Ld):
+ // if( pred ) ptr.x = e0; val = pred ? e0
+ // else ptr.x = e1; : e1;
+ // val = ptr.x; ptr.x = val;
+ if( mem() instanceof PhiNode memphi && memphi.region()._type == Type.CONTROL && memphi.nIns()== 3 &&
+ // Offset can be hoisted
+ off() instanceof ConstantNode &&
+ // Pointer can be hoisted
+ hoistPtr(ptr,memphi) ) {
+
+ // Profit on RHS/Loop backedge
+ if( profit(memphi,2) ||
+ // Else must not be a loop to count profit on LHS.
+ (!(memphi.region() instanceof LoopNode) && profit(memphi,1)) ) {
+ Node ld1 = ld(1);
+ Node ld2 = ld(2);
+ return new PhiNode(_name,_declaredType,memphi.region(),ld1,ld2);
+ }
+ }
+
+ return null;
+ }
+
+ // Load a flavored zero from a New
+ private Node zero(NewNode nnn) {
+ TypeStruct ts = nnn._ptr._obj;
+ Type zero = ts._fields[ts.findAlias(_alias)]._type.makeZero();
+ return castRO(new ConstantNode(zero).peephole());
+ }
+
+ private Node ld( int idx ) {
+ Node mem = mem(), ptr = ptr();
+ return new LoadNode(_loc,_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr, off()).peephole();
+ }
+
+ private static boolean neverAlias( Node ptr1, Node ptr2 ) {
+ return ptr1.in(0) != ptr2.in(0) &&
+ // Unrelated allocations
+ ptr1 instanceof ProjNode && ptr1.in(0) instanceof NewNode &&
+ ptr2 instanceof ProjNode && ptr2.in(0) instanceof NewNode;
+ }
+
+ private static boolean hoistPtr(Node ptr, PhiNode memphi ) {
+ // Can I hoist ptr above the Region?
+ if( !(memphi.region() instanceof RegionNode r) )
+ return false; // Dead or dying Region/Phi
+ // If ptr from same Region, then yes, just use hoisted split pointers
+ if( ptr instanceof PhiNode pphi && pphi.region() == r )
+ return true;
+
+ // No, so can we lift this ptr?
+ CFGNode cptr = ptr.cfg0();
+ if( cptr != null )
+ // Pointer is controlled high
+ // TODO: Really needs to be the LCA of all inputs is high
+ return cptr.idepth() <= r.idepth();
+
+ // Dunno without a longer walk
+ return false;
+ }
+
+ // Profitable if we find a matching Store on this Phi arm.
+ private boolean profit(PhiNode phi, int idx) {
+ Node px = phi.in(idx);
+ if( px==null ) return false;
+ if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() ) return true;
+ if( px instanceof StoreNode st1 && ptr()==st1.ptr() && off()==st1.off() ) return true;
+ addDep(px);
+ return false;
+ }
+
+ // Read-Only is a deep property, and cannot be cast-away
+ private Node castRO(Node rez) {
+ if( ptr()._type.isFinal() && !rez._type.isFinal() )
+ return new ReadOnlyNode(rez).peephole();
+ return rez;
+ }
+
+ // When a load bypasses a store, the store might truncate bits - and the
+ // load will need to zero/sign-extend.
+ private Node extend(Node val) {
+ if( !(_declaredType instanceof TypeInteger ti) ) return val;
+ if( ti._min==0 ) // Unsigned
+ return new AndNode(null,val,con(ti._max));
+ // Signed extension
+ int shift = Long.numberOfLeadingZeros(ti._max)-1;
+ Node shf = con(shift);
+ if( shf._type==TypeInteger.ZERO )
+ return val;
+ Node shl = new ShlNode(null,val,shf.keep()).peephole();
+ return new SarNode(null,shl,shf.unkeep());
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/LogicalNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/LogicalNode.java
new file mode 100644
index 00000000..e8552789
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/LogicalNode.java
@@ -0,0 +1,29 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import java.util.BitSet;
+
+public abstract class LogicalNode extends Node {
+ // Source location for late reported errors
+ Parser.Lexer _loc;
+
+ public LogicalNode(Parser.Lexer loc, Node lhs, Node rhs) { super(null, lhs, rhs); _loc = loc; }
+ abstract String op();
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("("), visited);
+ in(2)._print0(sb.append(op()), visited);
+ return sb.append(")");
+ }
+
+
+ @Override public Parser.ParseException err() {
+ if( in(1)._type.isHigh() || in(2)._type.isHigh() ) return null;
+ if( !(in(1)._type instanceof TypeInteger) ) return Parser.error("Cannot '"+op()+"' " + in(1)._type,_loc);
+ if( !(in(2)._type instanceof TypeInteger) ) return Parser.error("Cannot '"+op()+"' " + in(2)._type,_loc);
+ return null;
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/LoopNode.java
new file mode 100644
index 00000000..cacf9c99
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/LoopNode.java
@@ -0,0 +1,101 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.type.TypeMem;
+import java.util.BitSet;
+import java.util.HashSet;
+
+public class LoopNode extends RegionNode {
+ public LoopNode( Parser.Lexer loc, Node entry ) { super(loc,null,entry,null); }
+ public LoopNode( LoopNode loop ) { super(loop); }
+
+ public CFGNode entry() { return cfg(1); }
+ public CFGNode back () { return cfg(2); }
+
+ @Override
+ public String label() { return "Loop"; }
+
+ @Override
+ public Type compute() {
+ if( inProgress() ) return Type.CONTROL;
+ return entry()._type;
+ }
+
+ // Bypass Region idom, same as the default idom() using use in(1) instead of in(0)
+ @Override public int idepth() { return _idepth==0 ? (_idepth=idom().idepth()+1) : _idepth; }
+ // Bypass Region idom, same as the default idom() using use in(1) instead of in(0)
+ @Override public CFGNode idom(Node dep) { return entry(); }
+
+ // If this is an unreachable loop, it may not have an exit. If it does not
+ // (i.e., infinite loop), force an exit to make it reachable.
+ public StopNode forceExit( FunNode fun, StopNode stop ) {
+ // Walk the backedge, then immediate dominator tree util we hit this
+ // Loop again. If we ever hit a CProj from an If (as opposed to
+ // directly on the If) we found our exit.
+ CFGNode x = back();
+ while( x != this ) {
+ if( x instanceof CProjNode exit && exit.in(0) instanceof IfNode iff ) {
+ CFGNode other = iff.cproj(1-exit._idx);
+ if( other!=null && other._ltree != _ltree && nested(_ltree,other._ltree) )
+ return stop; // Found an exit, not an infinite loop
+ }
+ x = x.idom();
+ }
+ // Found a no-exit loop. Insert an exit
+ NeverNode iff = new NeverNode(back()); // Ideal never-branch
+ CProjNode t = new CProjNode(iff,0,"True" ).init();
+ CProjNode f = new CProjNode(iff,1,"False").init();
+ setDef(2,t); // True continues loop, False (never) exits loop
+ ReturnNode ret = fun.ret();
+ iff._ltree = t._ltree = _ltree;
+ ret._ltree = f._ltree = stop._ltree;
+
+ // Now fold control into the exit. Might have 1 valid exit, or an
+ // XCtrl or a bunch of prior NeverNode exits.
+ Node top = new ConstantNode(Type.TOP).peephole();
+ Node memout = new MemMergeNode(false);
+ memout.addDef(f); // placeholder for control
+ for( Node u : _outputs )
+ if( u instanceof PhiNode phi && phi._type.isa(TypeMem.BOT) )
+ memout.addDef(phi);
+
+ Node ctrl = ret.ctrl(), mem = ret.mem(), expr = ret.expr();
+ if( ctrl!=null && ctrl._type != Type.XCONTROL ) {
+ // Perfect aligned exit?
+ if( !(ctrl instanceof RegionNode r &&
+ mem instanceof PhiNode pmem && pmem.region()==r &&
+ expr instanceof PhiNode prez && prez.region()==r ) ) {
+ // Nope, insert an aligned exit layer
+ RegionNode r = new RegionNode(_loc,null,ctrl).init();
+ ctrl = r; r._ltree = stop._ltree;
+ mem = new PhiNode(r,mem ).init();
+ expr = new PhiNode(r,expr).init();
+ }
+ // Append new Never exit
+ ctrl.addDef(f );
+ mem .addDef(memout);
+ expr.addDef(top);
+ } else {
+ ctrl = f;
+ mem = memout;
+ expr = top;
+ }
+ ret.setDef(0,ctrl);
+ ret.setDef(1,mem );
+ ret.setDef(2,expr);
+
+ return stop;
+ }
+
+ private static boolean nested(LoopTree inner, LoopTree outer) {
+ for( ; inner!=null; inner = inner._par )
+ if( inner == outer )
+ return true;
+ return false;
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java
new file mode 100644
index 00000000..b01b3308
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java
@@ -0,0 +1,27 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.Utils;
+import java.io.ByteArrayOutputStream;
+import java.util.BitSet;
+
+// Generic machine-specific class, has a few Node implementations that have to
+// exist (abstract) but are not useful past the optimizer.
+public abstract class MachConcreteNode extends Node implements MachNode {
+
+ public MachConcreteNode(Node node) { super(node); }
+ public MachConcreteNode(Node[]nodes) { super(nodes); }
+
+ @Override public String label() { return op(); }
+ @Override public Type compute () { throw Utils.TODO(); }
+ @Override public Node idealize() { throw Utils.TODO(); }
+
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("(").append(op()).append(",");
+ for( int i=1; i<nIns(); i++ )
+ (in(i)==null ? sb.append(" ---") : in(i)._print0(sb, visited)).append(",");
+ sb.setLength(sb.length()-1);
+ return sb.append(")");
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/MachNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/MachNode.java
new file mode 100644
index 00000000..f63b3651
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/MachNode.java
@@ -0,0 +1,58 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+
+public interface MachNode {
+
+ // Easy access to the abstract Machine
+ default Machine machine() { return CodeGen.CODE._mach; }
+
+ // Run a post-instruction-selection action
+ default void postSelect( CodeGen code ) { }
+
+ // Register mask allowed on input i. 0 for no register.
+ RegMask regmap( int i );
+ // Register mask allowed as a result. 0 for no register.
+ RegMask outregmap();
+ // Multi-reg-defining machine op; idx comes from Proj.
+ // Sometimes these Projs have no uses, and just exist to kill a register.
+ default RegMask outregmap(int idx) { throw Utils.TODO(); }
+
+ // Set of killed registers
+ default RegMask killmap() { return null; }
+
+ // If this op updates the same register as input 'idx', e.g. many X86 "R1 +=
+ // R2" style ops. This forces the output live range to equal the input live
+ // range, so they both get the same register.
+
+ // This can hold true for projections, if the instruction updates many
+ // registers that it also takes in. Note that ops also use the same
+ // registers for both inputs and outputs but these are pinned and without any
+ // choices (e.g. always RCX for both an input and output) and are better
+ // served with just declaring as a fixed register.
+
+ // Returns either 0 for not-two-adr or the updated input; for projections
+ // this is their Multi's updated input.
+ default int twoAddress( ) { return 0; }
+ // Ok to switch arguments
+ default boolean commutes() { return false; }
+
+ // Instructions cheaper to recreate than to spill, such as loading small constants
+ default boolean isClone() { return false; }
+
+ // Make a clone of a cheap instruction
+ default Node copy() { return null; }
+
+ // Encoding is appended, relocations gathered.
+ void encoding( Encoding enc );
+
+ // Human-readable form appended to the SB. Things like the encoding,
+ // indentation, leading address or block labels not printed here.
+ // Just something like "R17=[R18+12] // Load array base".
+ // General form: "dst=src+src".
+ default void asm(CodeGen code, SB sb) { throw Utils.TODO(); }
+
+ // Human readable opcode; e.g. "ld4" or "call".
+ default String op() { throw Utils.TODO(); }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
new file mode 100644
index 00000000..32c25a8b
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
@@ -0,0 +1,162 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.*;
+import java.util.*;
+
+/**
+ * Memory Merge - a merge of many aliases into a "fat memory". All aliases
+ * are here, but most will be lazy - take the default fat memory.
+ */
+public class MemMergeNode extends Node {
+
+ /*
+ * In-Progress means this is being used by the Parser to track memory
+ * aliases. No optimizations are allowed. When no longer "in progress"
+ * normal peeps work.
+ */
+ public final boolean _inProgress;
+
+ public MemMergeNode( boolean inProgress) { _type = TypeMem.BOT; _inProgress = inProgress; }
+ public MemMergeNode(MemMergeNode mem) { super(mem); _inProgress = false; }
+
+
+ // If being used by a Scope, this is "in progress" from the Parser.
+ // Otherwise, it's a memory merge
+ boolean inProgress() { return _inProgress; }
+
+ @Override public String label() { return "ALLMEM"; }
+ @Override public boolean isMem() { return true; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append(_inProgress ? "Merge[" : "MEM[ ");
+ for( int j=2; j<nIns(); j++ ) {
+ sb.append(j);
+ sb.append(":");
+ Node n = in(j);
+ while( n instanceof ScopeNode loop ) {
+ sb.append("Lazy_");
+ n = loop.mem(j);
+ }
+ if( n==null ) sb.append("___ ");
+ else n._print0(sb, visited).append(" ");
+ }
+ sb.setLength(sb.length()-1);
+ return sb.append("]");
+ }
+
+ // Make a memory merge: no longer a Scope really, tracking memory state but
+ // not related to the parser in any way.
+ public Node merge() {
+ // Force default memory to not be lazy
+ MemMergeNode merge = new MemMergeNode(false);
+ for( Node n : _inputs )
+ merge.addDef(n);
+ merge._mem(1,null);
+ return merge.peephole();
+ }
+
+
+ @Override public Type compute() { return TypeMem.BOT; }
+
+ @Override public Node idealize() {
+ if( inProgress() ) return null;
+
+ // If not merging any memory (all memory is just the default)
+ if( allDefault() )
+ return in(1); // Become default memory
+
+ return null;
+ }
+ private boolean allDefault() {
+ for( int i=2; i<nIns(); i++ )
+ if( in(1) != in(i) )
+ return false;
+ return true;
+ }
+
+
+ public Node in( Var v ) { return in(v._idx); }
+
+ public Node alias( int alias ) {
+ return in(alias<nIns() && in(alias)!=null ? alias : 1);
+ }
+
+ Node alias( int alias, Node st ) {
+ while( alias >= nIns() ) addDef(null);
+ return setDef(alias,st);
+ }
+
+ // Read or update from memory.
+ // A shared implementation allows us to create lazy phis both during
+ // lookups and updates; the lazy phi creation is part of chapter 8.
+ Node _mem( int alias, Node st ) {
+ // Memory projections are made lazily; if one does not exist
+ // then it must be START.proj(1)
+ Node old = alias(alias);
+ if( old instanceof ScopeNode loop ) {
+ MemMergeNode loopmem = loop.mem();
+ Node memdef = loopmem.alias(alias);
+ // Lazy phi!
+ old = memdef instanceof PhiNode phi && loop.ctrl()==phi.region()
+ // Loop already has a real Phi, use it
+ ? memdef
+ // Set real Phi in the loop head
+ // The phi takes its one input (no backedge yet) from a recursive
+ // lookup, which might have insert a Phi in every loop nest.
+ : loopmem.alias(alias, new PhiNode(Parser.memName(alias), TypeMem.BOT,loop.ctrl(),loopmem._mem(alias,null),null).peephole() );
+ alias(alias,old);
+ }
+ // Memory projections are made lazily; expand as needed
+ return st==null ? old : alias(alias,st); // Not lazy, so this is the answer
+ }
+
+
+ void _merge( MemMergeNode that, RegionNode r) {
+ int len = Math.max(nIns(),that.nIns());
+ for( int i = 2; i < len; i++)
+ if( alias(i) != that.alias(i) ) { // No need for redundant Phis
+ // If we are in lazy phi mode we need to a lookup
+ // by alias as it will trigger a phi creation
+ Node lhs = this._mem(i,null);
+ Node rhs = that._mem(i,null);
+ alias(i, new PhiNode(Parser.memName(i), lhs._type.glb().meet(rhs._type.glb()), r, lhs, rhs).peephole());
+ }
+ }
+
+ // Fill in the backedge of any inserted Phis
+ void _endLoopMem( ScopeNode scope, MemMergeNode back, MemMergeNode exit ) {
+ Node exit_def = exit.alias(1);
+ for( int i=1; i<nIns(); i++ ) {
+ if( in(i) instanceof PhiNode phi && phi.region()==scope.ctrl() ) {
+ assert phi.in(2)==null;
+ phi.setDef(2,back.in(i)==scope ? phi : back.in(i)); // Fill backedge
+ }
+ if( exit_def == scope ) // Replace a lazy-phi on the exit path also
+ exit.alias(i,in(i));
+ }
+ }
+
+ // Now one-time do a useless-phi removal
+ void _useless( ) {
+ for( int i=2; i<nIns(); i++ ) {
+ if( in(i) instanceof PhiNode phi ) {
+ // Do an eager useless-phi removal
+ Node in = phi.peephole();
+ CodeGen.CODE.addAll(phi._outputs);
+ phi.moveDepsToWorklist();
+ if( in != phi ) {
+ if( !phi.iskeep() ) // Keeping phi around for parser elsewhere
+ phi.subsume(in);
+ setDef(i,in); // Set the update back into Scope
+ }
+ }
+ }
+ }
+
+ @Override public boolean eq( Node n ) {
+ return this==n || !_inProgress;
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
new file mode 100644
index 00000000..058c86fa
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
@@ -0,0 +1,110 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeMemPtr;
+import java.lang.StringBuilder;
+import java.util.BitSet;
+
+/**
+ * Convenience common base for Load and Store.
+ *
+ * 0 - Control
+ * 1 - Memory
+ * 2 - Base of object, OOP
+ * 3 - Offset, integer types
+ * 4 - Value, for Stores only
+ */
+public abstract class MemOpNode extends Node {
+
+ // The equivalence alias class
+ public final int _alias;
+
+ // True if load-like, false if store-like.
+ //
+ // Stores produce memory (maybe as part of a tuple with other things),
+ // loads do not.
+ //
+ // Loads might pick up anti-dependences on prior Stores, and never cause an
+ // anti-dependence themselves.
+ //
+ // Stores must maximally sink to the least dominator use. Loads can be
+ // opportunistically hoisted.
+ public final boolean _isLoad;
+
+ // Declared type; not final because it might be a forward-reference
+ // which will be lazily improved when the reference is declared.
+ public Type _declaredType;
+
+ // A debug name, no semantic meaning
+ public final String _name;
+ // Source location for late reported errors
+ public final Parser.Lexer _loc;
+
+ public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type glb, Node mem, Node ptr, Node off) {
+ super(null, mem, ptr, off);
+ _name = name;
+ _alias = alias;
+ _declaredType = glb;
+ _loc = loc;
+ _isLoad = isLoad;
+ }
+ public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type glb, Node mem, Node ptr, Node off, Node value) {
+ this(loc,name, alias, isLoad, glb, mem, ptr, off);
+ addDef(value);
+ }
+ public MemOpNode( Node mach, MemOpNode mop ) {
+ super(mach);
+ _name = mop==null ? null : mop._name;
+ _alias = mop==null ? 0 : mop._alias;
+ _loc = mop==null ? null : mop._loc;
+ _isLoad= mop==null ? true : mop._isLoad;
+ _declaredType = mop==null ? Type.BOTTOM : mop._declaredType;
+ if( mop==null )
+ throw Utils.TODO("Load or not");
+ }
+
+ // Used by M2 when translating graph to Simple
+ public MemOpNode( boolean isLoad ) {
+ super((Node)null);
+ _name = null;
+ _alias = 0;
+ _loc = null;
+ _isLoad = isLoad;
+ _declaredType = Type.BOTTOM;
+ }
+
+ //
+ static String mlabel(String name) { return "[]".equals(name) ? "ary" : ("#".equals(name) ? "len" : name); }
+ String mlabel() { return mlabel(_name); }
+
+ public Node mem() { return in(1); }
+ public Node ptr() { return in(2); }
+ public Node off() { return in(3); }
+
+ @Override public StringBuilder _print1( StringBuilder sb, BitSet visited ) { return _printMach(sb,visited); }
+ public StringBuilder _printMach( StringBuilder sb, BitSet visited ) { throw Utils.TODO(); }
+
+
+ @Override
+ public boolean eq(Node n) {
+ MemOpNode mem = (MemOpNode)n; // Invariant
+ return _alias==mem._alias; // When comparing types error to use "equals"; always use "=="
+ }
+
+ @Override
+ int hash() { return _alias; }
+
+ @Override
+ public Parser.ParseException err() {
+ Type ptr = ptr()._type;
+ // Already an error, but better error messages come from elsewhere
+ if( ptr == Type.BOTTOM ) return null;
+ if( ptr.isHigh() ) return null; // Assume it will fall to not-null
+ // Better be a not-nil TMP
+ if( ptr instanceof TypeMemPtr tmp && tmp.notNull() )
+ return null;
+ return Parser.error( "Might be null accessing '" + _name + "'",_loc);
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/MinusFNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/MinusFNode.java
new file mode 100644
index 00000000..99fc3144
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/MinusFNode.java
@@ -0,0 +1,36 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFloat;
+
+import java.util.BitSet;
+
+public class MinusFNode extends Node {
+ public MinusFNode(Node in) { super(null, in); }
+
+ @Override public String label() { return "MinusF"; }
+
+ @Override public String glabel() { return "-"; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("(-"), visited);
+ return sb.append(")");
+ }
+
+ @Override
+ public Type compute() {
+ if (in(1)._type instanceof TypeFloat i0)
+ return i0.isConstant() ? TypeFloat.constant(-i0.value()) : i0;
+ return TypeFloat.F64;
+ }
+
+ @Override
+ public Node idealize() {
+ // -(-x) is x
+ if( in(1) instanceof MinusFNode minus )
+ return minus.in(1);
+
+ return null;
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/MinusNode.java
new file mode 100644
index 00000000..e06e0c69
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/MinusNode.java
@@ -0,0 +1,40 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.Utils;
+
+import java.util.BitSet;
+
+public class MinusNode extends Node {
+ public MinusNode(Node in) { super(null, in); }
+
+ @Override public String label() { return "Minus"; }
+
+ @Override public String glabel() { return "-"; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("(-"), visited);
+ return sb.append(")");
+ }
+
+ @Override
+ public Type compute() {
+ if( in(1)._type.isHigh() ) return TypeInteger.TOP;
+ if( in(1)._type instanceof TypeInteger i0 &&
+ !(i0==TypeInteger.BOT || i0._min == Long.MIN_VALUE || i0._max == Long.MIN_VALUE) )
+ return TypeInteger.make(-i0._max,-i0._min);
+ return TypeInteger.BOT;
+ }
+
+ @Override
+ public Node idealize() {
+ // -(-x) is x
+ if( in(1) instanceof MinusNode minus )
+ return minus.in(1);
+
+ return null;
+ }
+ @Override Node copyF() { return new MinusFNode(null); }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/MulFNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/MulFNode.java
new file mode 100644
index 00000000..6fbf4bbb
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/MulFNode.java
@@ -0,0 +1,51 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFloat;
+
+import java.util.BitSet;
+
+public class MulFNode extends Node {
+ public MulFNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+
+ @Override public String label() { return "MulF"; }
+
+ @Override public String glabel() { return "*"; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("("), visited);
+ in(2)._print0(sb.append("*"), visited);
+ return sb.append(")");
+ }
+
+ @Override
+ public Type compute() {
+ if (in(1)._type instanceof TypeFloat i0 &&
+ in(2)._type instanceof TypeFloat i1) {
+ if (i0.isConstant() && i1.isConstant())
+ return TypeFloat.constant(i0.value()*i1.value());
+ }
+ return in(1)._type.meet(in(2)._type);
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Node rhs = in(2);
+ Type t1 = lhs._type;
+ Type t2 = rhs._type;
+
+ // Mul of 1. We do not check for (1*x) because this will already
+ // canonicalize to (x*1)
+ if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==1 )
+ return lhs;
+
+ // Move constants to RHS: con*arg becomes arg*con
+ if ( t1.isConstant() && !t2.isConstant() )
+ return swap12();
+
+ return null;
+ }
+ @Override Node copy(Node lhs, Node rhs) { return new MulFNode(lhs,rhs); }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/MulNode.java
new file mode 100644
index 00000000..384dbace
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/MulNode.java
@@ -0,0 +1,87 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import java.util.BitSet;
+
+public class MulNode extends Node {
+ public MulNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+
+ @Override public String label() { return "Mul"; }
+
+ @Override public String glabel() { return "*"; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("("), visited);
+ in(2)._print0(sb.append("*"), visited);
+ return sb.append(")");
+ }
+
+ @Override
+ public Type compute() {
+ Type t1 = in(1)._type, t2 = in(2)._type;
+ if( t1.isHigh() || t2.isHigh() )
+ return TypeInteger.TOP;
+ if( t1 instanceof TypeInteger i1 &&
+ t2 instanceof TypeInteger i2 ) {
+ if( i1==TypeInteger.ZERO || i2==TypeInteger.ZERO)
+ return TypeInteger.ZERO;
+ if (i1.isConstant() && i2.isConstant())
+ return TypeInteger.constant(i1.value()*i2.value());
+ }
+ return TypeInteger.BOT;
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Node rhs = in(2);
+ Type t1 = lhs._type;
+ Type t2 = rhs._type;
+
+ // Move constants to RHS: con*arg becomes arg*con
+ if ( t1.isConstant() && !t2.isConstant() )
+ return swap12();
+
+ // Multiply by constant
+ if ( t2.isConstant() && t2 instanceof TypeInteger i ) {
+ // Mul of 1. We do not check for (1*x) because this will already
+ // canonicalize to (x*1)
+ long c = i.value();
+ if( c==1 ) return lhs;
+ if( c==0 ) return Parser.ZERO;
+ // Mul by a power of 2, +/-1. Bit patterns more complex than this
+ // are unlikely to win on an X86 vs the normal "imul", and so
+ // become machine-specific.
+ if( (c & (c-1)) == 0 )
+ return new ShlNode(null,lhs,con(Long.numberOfTrailingZeros(c)));
+ // 2**n + 1, e.g. 9
+ long d = c-1;
+ if( (d & (d-1)) == 0 )
+ return new AddNode(new ShlNode(null,lhs,con(Long.numberOfTrailingZeros(d))).peephole(),lhs);
+ // 2**n - 1, e.g. 31
+ long e = c+1;
+ if( (e & (e-1)) == 0 )
+ return new SubNode(new ShlNode(null,lhs,con(Long.numberOfTrailingZeros(e))).peephole(),lhs);
+
+ }
+
+ // Do we have ((x * (phi cons)) * con) ?
+ // Do we have ((x * (phi cons)) * (phi cons)) ?
+ // Push constant up through the phi: x * (phi con0*con0 con1*con1...)
+ Node phicon = AddNode.phiCon(this,true);
+ if( phicon!=null ) return phicon;
+
+ return null;
+ }
+ @Override Node copy(Node lhs, Node rhs) { return new MulNode(lhs,rhs); }
+ @Override Node copyF() { return new MulFNode(null,null); }
+ @Override public Parser.ParseException err() {
+ if( in(1)._type.isHigh() || in(2)._type.isHigh() ) return null;
+ if( !(in(1)._type instanceof TypeInteger) ) return Parser.error("Cannot '"+label()+"' " + in(1)._type.glb(),null);
+ if( !(in(2)._type instanceof TypeInteger) ) return Parser.error("Cannot '"+label()+"' " + in(2)._type.glb(),null);
+ return null;
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/MultiNode.java
new file mode 100644
index 00000000..17d00881
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/MultiNode.java
@@ -0,0 +1,29 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Ary;
+
+
+public interface MultiNode {
+
+ abstract Ary<Node> outs();
+
+ // Find a projection by index
+ default ProjNode proj( int idx ) {
+ for( Node out : outs() )
+ if( out instanceof ProjNode prj && prj._idx==idx )
+ return prj;
+ return null;
+ }
+
+ // Find a projection by index
+ default CProjNode cproj( int idx ) {
+ for( Node out : outs() )
+ if( out instanceof CProjNode prj && prj._idx==idx )
+ return prj;
+ return null;
+ }
+
+ // Return not-null if this projection index is a ideal copy.
+ // Called by ProjNode ideal and used to collapse Multis.
+ default Node pcopy(int idx) { return null; }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/NeverNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/NeverNode.java
new file mode 100644
index 00000000..516f218b
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/NeverNode.java
@@ -0,0 +1,22 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeTuple;
+
+import java.util.BitSet;
+
+// "Never true" for infinite loop exits
+public class NeverNode extends IfNode {
+ public NeverNode(NeverNode ctrl) { super(ctrl); }
+ public NeverNode(Node ctrl) { super(ctrl,null); }
+
+ @Override public String label() { return "Never"; }
+
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append("Never"); }
+
+ @Override public Type compute() { return TypeTuple.IF_BOTH; }
+
+ @Override public Node idealize() { return null; }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/NewNode.java
new file mode 100644
index 00000000..3d4bad5e
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/NewNode.java
@@ -0,0 +1,119 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.type.*;
+import java.util.BitSet;
+
+/**
+ * Allocation! Allocate a chunk of memory, and pre-zero it.
+ * The inputs include control and size, and ALL aliases being set.
+ * The output is large tuple, one for every alias plus the created pointer.
+ * New is expected to be followed by projections for every alias.
+ */
+public class NewNode extends Node implements MultiNode {
+
+ public final TypeMemPtr _ptr;
+ public final int _len;
+
+ public NewNode(TypeMemPtr ptr, Node... nodes) {
+ super(nodes);
+ assert !ptr.nullable();
+ _ptr = ptr;
+ _len = ptr._obj._fields.length;
+ // Control in slot 0
+ assert nodes[0]._type==Type.CONTROL || nodes[0]._type == Type.XCONTROL;
+ // Malloc-length in slot 1
+ assert nodes[1]._type instanceof TypeInteger || nodes[1]._type==Type.NIL;
+ for( int i=0; i<_len; i++ )
+ // Memory slices for all fields.
+ assert nodes[2+i]._type.isa(TypeMem.BOT);
+ }
+
+ public NewNode(NewNode nnn) { super(nnn); _ptr = nnn._ptr; _len = nnn._len; }
+
+ public Node mem (int idx) { return in(idx+2); }
+
+ @Override public String label() { return "new_"+glabel(); }
+ @Override public String glabel() {
+ return _ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._type.str() : _ptr._obj.str();
+ }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("new ");
+ return sb.append(_ptr._obj.str());
+ }
+
+ int findAlias(int alias) {
+ int idx = _ptr._obj.findAlias(alias);
+ assert idx!= -1; // Error, caller should be calling
+ return idx+2; // Skip ctrl, size
+ }
+
+ // 0 - ctrl
+ // 1 - byte size
+ // 2-len+2 - aliases, one per field
+ // len+2 - 2*len+2 - initial values, one per field
+ public Node size() { return in(1); }
+
+ @Override
+ public TypeTuple compute() {
+ Field[] fs = _ptr._obj._fields;
+ Type[] ts = new Type[fs.length+2];
+ ts[0] = Type.CONTROL;
+ ts[1] = _ptr;
+ for( int i=0; i<fs.length; i++ ) {
+ Type mt = in(i+2)._type;
+ TypeMem mem = mt==Type.TOP ? TypeMem.TOP : (TypeMem)mt;
+ Type tfld = mem._t.meet(mem._t.makeZero());
+ Type tfld2 = tfld.join(fs[i]._type);
+ ts[i+2] = TypeMem.make(fs[i]._alias,tfld2);
+ }
+ return TypeTuple.make(ts);
+ }
+
+ @Override
+ public Node idealize() { return null; }
+
+ @Override
+ public boolean eq(Node n) { return this == n; }
+
+ @Override
+ int hash() { return _ptr.hashCode(); }
+
+ // ------------
+ // MachNode specifics, shared across all CPUs
+ public int _arg2Reg, _xslot;
+ private RegMask _arg3Mask;
+ private RegMask _retMask;
+ private RegMask _kills;
+ public void cacheRegs(CodeGen code) {
+ _arg2Reg = code._mach.callArgMask(TypeFunPtr.CALLOC,2).firstReg();
+ _arg3Mask = code._mach.callArgMask(TypeFunPtr.CALLOC,3);
+ // Return mask depends on TFP (either GPR or FPR)
+ _retMask = code._mach.retMask(TypeFunPtr.CALLOC);
+ // Kill mask is all caller-saves, and any mirror stack slots for args
+ // in registers.
+ RegMaskRW kills = code._callerSave.copy();
+ // Start of stack slots
+ int maxReg = code._mach.regs().length;
+ // Incoming function arg slots, all low numbered in the RA
+ int fslot = cfg0().fun()._maxArgSlot;
+ // Killed slots for this calls outgoing args
+ int xslot = code._mach.maxArgSlot(TypeFunPtr.CALLOC);
+ _xslot = (maxReg+fslot)+xslot;
+ for( int i=0; i<xslot; i++ )
+ kills.set((maxReg+fslot)+i);
+ _kills = kills;
+ }
+ public String op() { return "alloc"; }
+ public RegMask regmap(int i) { return i==1 ? _arg3Mask : null; }
+ public RegMask outregmap() { return null; }
+ public RegMask outregmap(int idx) { return idx==1 ? _retMask : null; }
+ public RegMask killmap() { return _kills; }
+ public void asm(CodeGen code, SB sb) {
+ sb.p("#calloc, ").p(code.reg(size()));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter21/src/main/java/com/seaofnodes/simple/node/Node.java
new file mode 100644
index 00000000..c0c2e606
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/Node.java
@@ -0,0 +1,731 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.print.IRPrinter;
+import com.seaofnodes.simple.print.JSViewer;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFloat;
+import com.seaofnodes.simple.type.TypeInteger;
+import java.util.*;
+import java.util.function.Function;
+import static com.seaofnodes.simple.codegen.CodeGen.CODE;
+
+/**
+ * All Nodes in the Sea of Nodes IR inherit from the Node class.
+ * The Node class provides common functionality used by all subtypes.
+ * Subtypes of Node specialize by overriding methods.
+ */
+public abstract class Node implements Cloneable {
+
+ /**
+ * Each node has a unique dense Node ID within a compilation context
+ * The ID is useful for debugging, for using as an offset in a bitvector,
+ * as well as for computing equality of nodes (to be implemented later).
+ */
+ public int _nid;
+
+ /**
+ * Inputs to the node. These are use-def references to Nodes.
+ * <p>
+ * Generally fixed length, ordered, nulls allowed, no unused trailing space.
+ * Ordering is required because e.g. "a/b" is different from "b/a".
+ * The first input (offset 0) is often a {@link CFGNode} node.
+ */
+ public Ary<Node> _inputs;
+
+ /**
+ * Outputs reference Nodes that are not null and have this Node as an
+ * input. These nodes are users of this node, thus these are def-use
+ * references to Nodes.
+ * <p>
+ * Outputs directly match inputs, making a directed graph that can be
+ * walked in either direction. These outputs are typically used for
+ * efficient optimizations but otherwise have no semantics meaning.
+ */
+ public Ary<Node> _outputs;
+
+
+ /**
+ * Current computed type for this Node. This value changes as the graph
+ * changes and more knowledge is gained about the program.
+ */
+ public Type _type;
+
+
+ Node(Node... inputs) {
+ _nid = CODE.getUID(); // allocate unique dense ID
+ _inputs = new Ary<>(Node.class);
+ Collections.addAll(_inputs,inputs);
+ _outputs = new Ary<>(Node.class);
+ for( Node n : _inputs )
+ if( n != null )
+ n.addUse( this );
+ }
+
+ // Make a Node using the existing arrays of nodes.
+ // Used by any pass rewriting all Node classes but not the edges.
+ Node( Node n ) {
+ assert CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.InstSelect.ordinal();
+ _nid = CODE.getUID(); // allocate unique dense ID
+ _inputs = new Ary<>(n==null ? new Node[0] : n._inputs.asAry());
+ _outputs = new Ary<>(Node.class);
+ _type = n==null ? Type.BOTTOM : n._type;
+ _deps = null;
+ _hash = 0;
+ }
+
+ // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ"
+ public abstract String label();
+
+ // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99"
+ public String uniqueName() {
+ // Get rid of $ as graphviz doesn't like it
+ String label = label().replaceAll("\\$", "");
+ return label + _nid;
+ }
+
+ // Graphical label, e.g. "+" or "Region" or "=="
+ public String glabel() { return label(); }
+
+ // Extra fun stuff, for assembly printing. Jump labels, parser locations,
+ // variable types, etc.
+ public String comment() { return null; }
+
+ // ------------------------------------------------------------------------
+
+ // Debugger Printing.
+
+ // {@code toString} is what you get in the debugger. It has to print 1
+ // line (because this is what a debugger typically displays by default) and
+ // has to be robust with broken graph/nodes.
+ @Override
+ public final String toString() { return print(); }
+
+ // This is a *deep* print. We print with a mutually recursive tik-tok
+ // style; the common _print0 calls the per-Node _print1, which calls back
+ // to _print0;
+ public final String print() {
+ return _print0(new StringBuilder(), new BitSet()).toString();
+ }
+
+ // This is the common print: check for repeats, check for DEAD and print
+ // "DEAD" else call the per-Node print1.
+ public final StringBuilder _print0(StringBuilder sb, BitSet visited) {
+ if (visited.get(_nid) && !(this instanceof ConstantNode) )
+ return sb.append(label());
+ visited.set(_nid);
+ return isDead()
+ ? sb.append(uniqueName()).append(":DEAD")
+ : _print1(sb, visited);
+ }
+ // Every Node implements this; a partial-line recursive print
+ abstract public StringBuilder _print1(StringBuilder sb, BitSet visited);
+
+ public String p(int depth) { return IRPrinter.prettyPrint(this,depth); }
+
+ public boolean isConst() { return false; }
+
+ // ------------------------------------------------------------------------
+ // Graph Node & Edge manipulation
+
+ /**
+ * Gets the ith input node
+ * @param i Offset of the input node
+ * @return Input node or null
+ */
+ public Node in(int i) { return _inputs.get(i); }
+ public Node out(int i) { return _outputs.get(i); }
+ public final Ary<Node> outs() { return _outputs; }
+
+ public int nIns() { return _inputs.size(); }
+
+ public int nOuts() { return _outputs.size(); }
+
+ public boolean isUnused() { return nOuts() == 0; }
+
+ public CFGNode cfg0() { return (CFGNode)in(0); }
+
+ /**
+ * Change a <em>def</em> into a Node. Keeps the edges correct, by removing
+ * the corresponding <em>use->def</em> edge. This may make the original
+ * <em>def</em> go dead. This function is co-recursive with {@link #kill}.
+ * <p>
+
+ * This method is the normal path for altering a Node, because it does the
+ * proper default edge maintenance. It also <em>immediately</em> kills
+ * Nodes that lose their last use; at times care must be taken to avoid
+ * killing Nodes that are being used without having an output Node. This
+ * definitely happens in the middle of recursive {@link #peephole} calls.
+ *
+ * @param idx which def to set
+ * @param new_def the new definition
+ * @return new_def for flow coding
+ */
+ public <N extends Node> N setDef(int idx, N new_def ) {
+ unlock();
+ Node old_def = in(idx);
+ if( old_def == new_def ) return new_def; // No change
+ // If new def is not null, add the corresponding def->use edge
+ // This needs to happen before removing the old node's def->use edge as
+ // the new_def might get killed if the old node kills it recursively.
+ if( new_def != null )
+ new_def.addUse(this);
+ // Set the new_def over the old (killed) edge
+ _inputs.set(idx,new_def);
+ if( old_def != null ) { // If the old def exists, remove a def->use edge
+ if( old_def.delUse(this) ) // If we removed the last use, the old def is now dead
+ old_def.kill(); // Kill old def
+ else CODE.add(old_def); // Else old lost a use, so onto worklist
+ }
+ moveDepsToWorklist();
+ // Return new_def for easy flow-coding
+ return new_def;
+ }
+ public <N extends Node> N setDefX(int idx, N new_def ) {
+ while( nIns() <= idx ) addDef(null);
+ return setDef(idx,new_def);
+ }
+
+ // Remove the numbered input, compressing the inputs in-place. This
+ // shuffles the order deterministically - which is suitable for Region and
+ // Phi, but not for every Node. If the def goes dead, it is recursively
+ // killed, which may include 'this' Node.
+ public Node delDef(int idx) {
+ unlock();
+ Node old_def = in(idx);
+ _inputs.del(idx);
+ if( old_def.delUse(this) ) // If we removed the last use, the old def is now dead
+ old_def.kill(); // Kill old def
+ old_def.moveDepsToWorklist();
+ return this;
+ }
+
+ // Insert the numbered input, sliding other inputs to the right
+ Node insertDef(int idx, Node new_def) {
+ _inputs.add(idx,null);
+ return setDef(idx,new_def);
+ }
+
+
+ /**
+ * Add a new def to an existing Node. Keep the edges correct by
+ * adding the corresponding <em>def->use</em> edge.
+ *
+ * @param new_def the new definition, appended to the end of existing definitions
+ * @return new_def for flow coding
+ */
+ public Node addDef(Node new_def) {
+ unlock();
+ // Add use->def edge
+ _inputs.add(new_def);
+ // If new def is not null, add the corresponding def->use edge
+ if( new_def != null )
+ new_def.addUse(this);
+ return new_def;
+ }
+
+ // Breaks the edge invariants, used temporarily
+ @SuppressWarnings("unchecked")
+ protected <N extends Node> N addUse(Node n) { _outputs.add(n); return (N)this; }
+
+ // Remove node 'use' from 'def's (i.e. our) output list, by compressing the list in-place.
+ // Return true if the output list is empty afterward.
+ // Error is 'use' does not exist; ok for 'use' to be null.
+ protected boolean delUse( Node use ) {
+ _outputs.del(_outputs.find(use));
+ return _outputs.isEmpty();
+ }
+
+ // Shortcut for "popping" until n nodes. A "pop" is basically a
+ // setDef(last,null) followed by lowering the nIns() count.
+ void popUntil(int n) {
+ unlock();
+ while( nIns() > n ) {
+ Node old_def = _inputs.pop();
+ if( old_def != null && // If it exists and
+ old_def.delUse(this) ) // If we removed the last use, the old def is now dead
+ old_def.kill(); // Kill old def
+ }
+ }
+
+ /**
+ * Kill a Node with no <em>uses</em>, by setting all of its <em>defs</em>
+ * to null. This may recursively kill more Nodes, and is basically dead
+ * code elimination.
+ */
+ public void kill( ) {
+ unlock();
+ moveDepsToWorklist();
+ assert isUnused(); // Has no uses, so it is dead
+ _type=null; // Flag as dead
+ while( nIns()>0 ) { // Set all inputs to null, recursively killing unused Nodes
+ Node old_def = _inputs.removeLast();
+ // Revisit neighbor because removed use
+ if( old_def != null && CODE.add(old_def).delUse(this) )
+ old_def.kill(); // If we removed the last use, the old def is now dead
+ }
+ assert isDead(); // Really dead now
+ }
+
+ // Preserve CFG use-ordering when killing
+ public void killOrdered() {
+ CFGNode cfg = cfg0();
+ cfg._outputs.remove(cfg._outputs.find(this));
+ _inputs.set(0,null);
+ kill();
+ }
+
+
+ // Mostly used for asserts and printing.
+ public boolean isDead() { return isUnused() && nIns()==0 && _type==null; }
+
+ // Shortcuts to stop DCE mid-parse
+ // Add bogus null use to keep node alive
+ public <N extends Node> N keep() { return addUse(null); }
+ // Remove bogus null.
+ @SuppressWarnings("unchecked")
+ public <N extends Node> N unkeep() {
+ delUse(null);
+ return (N)this;
+ }
+ // Test "keep" status
+ public boolean iskeep() { return _outputs.find(null) != -1; }
+ public void unkill() {
+ if( unkeep().isUnused() )
+ kill();
+ }
+
+
+ // Replace self with nnn in the graph, making 'this' go dead
+ public void subsume( Node nnn ) {
+ assert nnn!=this;
+ while( nOuts() > 0 ) {
+ Node n = _outputs.removeLast();
+ n.unlock();
+ int idx = n._inputs.find(this);
+ n._inputs.set(idx,nnn);
+ nnn.addUse(n);
+ CODE.addAll(n._outputs);
+ }
+ kill();
+ }
+
+ // insert `this` immediately after `def` in the same basic block.
+ public void insertAfter( Node def ) {
+ CFGNode cfg = def.cfg0();
+ int i = cfg._outputs.find(def)+1;
+ if( cfg instanceof CallEndNode ) {
+ cfg = cfg.uctrl(); i=0;
+ } else if( def.in(0) instanceof MultiNode ) {
+ assert i==0;
+ i = cfg._outputs.find(def.in(0))+1;
+ }
+
+ while( cfg.out(i) instanceof PhiNode || cfg.out(i) instanceof CalleeSaveNode ) i++;
+ cfg._outputs.insert(this,i);
+ _inputs.set(0,cfg);
+ }
+
+ // Insert this in front of use.in(uidx) with this, and insert this
+ // immediately before use in the basic block.
+ public void insertBefore( Node use, int uidx ) {
+ CFGNode cfg = use.cfg0();
+ int i;
+ if( use instanceof PhiNode phi ) {
+ cfg = phi.region().cfg(uidx);
+ if( cfg instanceof CProjNode && cfg.in(0) instanceof NeverNode nvr )
+ cfg = nvr.cfg0();
+ i = cfg.nOuts()-1;
+ } else {
+ i = cfg._outputs.find(use);
+ }
+ cfg._outputs.insert(this,i);
+ _inputs.set(0,cfg);
+ if( _inputs._len > 1 && this instanceof SplitNode )
+ setDefOrdered(1,use.in(uidx));
+ use.setDefOrdered(uidx,this);
+ }
+
+ public void setDefOrdered( int idx, Node def) {
+ // If old is dying, remove from CFG ordered
+ Node old = in(idx);
+ if( old!=null && old.nOuts()==1 ) {
+ CFGNode cfg = old.cfg0();
+ if( cfg!=null ) {
+ cfg._outputs.remove(cfg._outputs.find(old));
+ old._inputs.set(0,null);
+ }
+ }
+ setDef(idx,def);
+ }
+
+ public void removeSplit() {
+ CFGNode cfg = cfg0();
+ cfg._outputs.remove(cfg._outputs.find(this));
+ _inputs.set(0,null);
+ if( _inputs._len > 1 ) subsume(in(1));
+ }
+
+ // ------------------------------------------------------------------------
+ // Graph-based optimizations
+
+ /**
+ * Try to peephole at this node and return a better replacement Node.
+ * Always returns some not-null Node (often this).
+ */
+ public final Node peephole( ) {
+ if( _type==null ) // Brand-new node, never peeped before
+ JSViewer.show();
+ Node n = peepholeOpt();
+ if( n!=null ) // Made progress?
+ JSViewer.show(); // Show again
+ return n==null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n); // Cannot return null for no-progress
+ }
+
+ /**
+ * Try to peephole at this node and return a better replacement Node if
+ * possible. We compute a {@link Type} and then check and replace:
+ * <ul>
+ * <li>if the Type {@link Type#isConstant}, we replace with a {@link ConstantNode}</li>
+ * <li>in a future chapter we will look for a
+ * <a href="https://en.wikipedia.org/wiki/Common_subexpression_elimination">Common Subexpression</a>
+ * to eliminate.</li>
+ * <li>we ask the Node for a better replacement. The "better replacement"
+ * is things like {@code (1+2)} becomes {@code 3} and {@code (1+(x+2))} becomes
+ * {@code (x+(1+2))}. By canonicalizing expressions we fold common addressing
+ * math constants, remove algebraic identities and generally simplify the
+ * code. </li>
+ *
+ * Unlike peephole above, this explicitly returns null for no-change, or not-null
+ * for a better replacement (which can be this).
+ * </ul>
+ */
+ public final Node peepholeOpt( ) {
+ CODE.iterCnt();
+ // Compute initial or improved Type
+ Type old = setType(compute());
+
+ // Replace constant computations from non-constants with a constant
+ // node. If peeps are disabled, still allow high Phis to collapse;
+ // they typically come from dead Regions, and we want the Region to
+ // collapse, which requires the Phis to die first.
+ if( _type.isHighOrConst() && !isConst() )
+ return ConstantNode.make(_type).peephole();
+
+ // Global Value Numbering
+ if( _hash==0 ) {
+ Node n = CODE._gvn.get(this); // Will set _hash as a side effect
+ if( n==null )
+ CODE._gvn.put(this,this); // Put in table now
+ else {
+ // Because of random worklist ordering, the two equal nodes
+ // might have different types. Because of monotonicity, both
+ // types are valid. To preserve monotonicity, the resulting
+ // shared Node has to have the best of both types.
+ n.setType(n._type.join(_type));
+ _hash = 0; // Clear, since it never went in the table
+ return deadCodeElim(n);// Return previous; does Common Subexpression Elimination
+ }
+ }
+
+ // Ask each node for a better replacement
+ Node n = idealize();
+ if( n != null ) // Something changed
+ return n; // Report progress
+
+ if( old!=_type ) return this; // Report progress;
+ CODE.iterNop();
+ return null; // No progress
+ }
+
+ // m is the new Node, self is the old.
+ // Return 'm', which may have zero uses but is alive nonetheless.
+ // If self has zero uses (and is not 'm'), {@link #kill} self.
+ private Node deadCodeElim(Node m) {
+ // If self is going dead and not being returned here (Nodes returned
+ // from peephole commonly have no uses (yet)), then kill self.
+ if( m != this && isUnused() && !isDead() ) {
+ // Killing self - and since self recursively kills self's inputs we
+ // might end up killing 'm', which we are returning as a live Node.
+ // So we add a bogus extra null output edge to stop kill().
+ m.keep(); // Keep m alive
+ kill(); // Kill self because replacing with 'm'
+ m.unkeep(); // Okay to peephole m
+ }
+ return m;
+ }
+
+ /**
+ * This function needs to be
+ * <a href="https://en.wikipedia.org/wiki/Monotonic_function">Monotonic</a>
+ * as it is part of a Monotone Analysis Framework.
+ * <a href="https://www.cse.psu.edu/~gxt29/teaching/cse597s21/slides/08monotoneFramework.pdf">See for example this set of slides</a>.
+ * <p>
+ * For Chapter 2, all our Types are really integer constants, and so all
+ * the needed properties are trivially true, and we can ignore the high
+ * theory. Much later on, this will become important and allow us to do
+ * many fancy complex optimizations trivially... because theory.
+ * <p>
+ * {@link #compute()} needs to be stand-alone, and cannot recursively call compute
+ * on its inputs, because programs are cyclic (have loops!) and this will just
+ * infinitely recurse until stack overflow. Instead, compute typically
+ * computes a new type from the {@link #_type} field of its inputs.
+ */
+ public abstract Type compute();
+
+ // Set the type. Assert monotonic progress.
+ // If changing, add users to worklist.
+ public Type setType(Type type) {
+ Type old = _type;
+ assert old==null || type.isa(old); // Since _type not set, can just re-run this in assert in the debugger
+ if( old == type ) return old;
+ _type = type; // Set _type late for easier assert debugging
+ CODE.addAll(_outputs);
+ moveDepsToWorklist();
+ return old;
+ }
+
+ @SuppressWarnings("unchecked")
+ public <N extends Node> N init() { _type = compute(); return (N)this; }
+
+ /**
+ * This function rewrites the current Node into a more "idealized" form.
+ * This is the bulk of our peephole rewrite rules, and we use this to
+ * e.g. turn arbitrary collections of adds and multiplies with mixed
+ * constants into a normal form that's easy for hardware to implement.
+ * Example: An array addressing expression:
+ * <pre> ary[idx+1]</pre>
+ * might turn into Sea-of-Nodes IR:
+ * <pre> (ary+12)+((idx+1) * 4)</pre>
+ * This expression can then be idealized into:
+ * <pre> ary + ((idx*4) + (12 + (1*4)))</pre>
+ * And more folding:
+ * <pre> ary + ((idx<<2) + 16)</pre>
+ * And during code-gen:
+ * <pre> MOV4 Rary,[Ridx<<2 +16] // or some such hardware-specific notation </pre>
+ * <p>
+ * {@link #idealize} has a very specific calling convention:
+ * <ul>
+ * <li>If NO change is made, return {@code null}
+ * <li>If ANY change is made, return not-null; this can be {@code this}
+ * <li>The returned Node does NOT call {@link #peephole} on itself; the {@link #peephole} call will recursively peephole it.
+ * <li>Any NEW nodes that are not directly returned DO call {@link #peephole}.
+ * </ul>
+ * <p>
+ * Examples:
+ * <table border="3">
+ * <tr><th> before </th><th> after </th><th>return </th><th>comment </th></tr>
+ * <tr><td>{@code (x+5) }</td><td>{@code (x+5) }</td><td>{@code null }</td><td>No change</td></tr>
+ * <tr><td>{@code (5+x) }</td><td>{@code (x+5) }</td><td>{@code this }</td><td>Swapped arguments</td></tr>
+ * <tr><td>{@code ((x+1)+2)}</td><td>{@code (x+(1+2))}</td><td>{@code (x+_) }</td><td>Returns 2 new Nodes</td></tr>
+ * </table>
+ *
+ * The last entry deserves more discussion. The new Node {@code (1+2)}
+ * created in {@link #idealize} calls {@link #peephole} (which then folds
+ * into a constant). The other new Node {@code (x+3)} does not call
+ * peephole, because it is returned and peephole itself will recursively
+ * call peephole.
+ * <p>
+ * Since idealize calls peephole and peephole calls idealize, you must be
+ * careful that all idealizations are <em>monotonic</em>: all transforms
+ * remove some feature, so that the set of available transforms always
+ * shrinks. If you don't, you risk an infinite peephole loop!
+ *
+ * @return Either a new or changed node, or null for no changes.
+ */
+ public abstract Node idealize();
+
+
+ // Some of the peephole rules get complex, and search further afield than
+ // just the nearest neighbor. These peepholes can fail the pattern match
+ // on a node some distance away, and if that node ever changes we should
+ // retry the peephole. Track a set of Nodes dependent on `this`, and
+ // revisit them if `this` changes.
+ Ary<Node> _deps;
+
+ /**
+ * Add a node to the list of dependencies. Only add it if its not an input
+ * or output of this node, that is, it is at least one step away. The node
+ * being added must benefit from this node being peepholed.
+ */
+ <N extends Node> N addDep( N dep ) {
+ // Running peepholes during the big assert cannot have side effects
+ // like adding dependencies.
+ if( CODE._midAssert ) return dep;
+ if( dep._deps==null ) dep._deps = new Ary<>(Node.class);
+ if( dep._deps .find(this) != -1 ) return dep; // Already on list
+ if( dep._inputs .find(this) != -1 ) return dep; // No need for deps on immediate neighbors
+ if( dep._outputs.find(this) != -1 ) return dep;
+ dep._deps.add(this);
+ return dep;
+ }
+
+ // Move the dependents onto a worklist, and clear for future dependents.
+ public void moveDepsToWorklist( ) {
+ if( _deps==null ) return;
+ CODE.addAll(_deps);
+ _deps.clear();
+ }
+
+
+ // Two nodes are equal if they have the same inputs and the same "opcode"
+ // which means the same Java class, plus same internal parts.
+ @Override public final boolean equals( Object o ) {
+ if( o==this ) return true;
+ if( o.getClass() != getClass() ) return false;
+ Node n = (Node)o;
+ int len = _inputs.size();
+ if( len != n._inputs.size() ) return false;
+ for( int i=0; i<len; i++ )
+ if( in(i) != n.in(i) )
+ return false;
+ return eq(n);
+ }
+ // Subclasses add extra checks (such as ConstantNodes have same constant),
+ // and can assume "this!=n" and has the same Java class.
+ public boolean eq( Node n ) { return true; }
+
+
+ // Cached hash. If zero, then not computed AND this Node is NOT in the GVN
+ // table - and can have its edges hacked (which will change his hash
+ // anyway). If Non-Zero then this Node is IN the GVN table, or is being
+ // probed to see if it can be inserted. His edges are "locked", because
+ // hacking his edges will change his hash.
+ int _hash;
+
+ // If the _hash is set, then the Node is in the GVN table; remove it.
+ void unlock() {
+ if( _hash==0 ) return;
+ Node old = CODE._gvn.remove(this); // Pull from table
+ assert old==this;
+ _hash=0; // Out of table now
+ }
+
+
+ // Hash of opcode and inputs
+ @Override public final int hashCode() {
+ if( _hash != 0 ) return _hash;
+ int hash = hash();
+ for( Node n : _inputs )
+ if( n != null )
+ hash = hash ^ (hash<<17) ^ (hash>>13) ^ n._nid;
+ if( hash==0 ) hash = 0xDEADBEEF; // Bad hash, so use some junky thing
+ return (_hash=hash);
+ }
+ // Subclasses add extra hash info (such as ConstantNodes constant)
+ int hash() { return 0; }
+
+ // ------------------------------------------------------------------------
+ //
+
+ /** Is this Node Memory related */
+ public boolean isMem() { return false; }
+
+ /** Pinned in the schedule; these are data nodes whose input#0 is not allowed to change */
+ public boolean isPinned() { return false; }
+
+ // Semantic change to the graph (so NOT a peephole), used by the Parser.
+ // If any input is a float, flip to a float-flavored opcode and widen any
+ // non-float input.
+ public final Node widen() {
+ if( !hasFloatInput() ) return this;
+ Node flt = copyF();
+ if( flt==null ) return this;
+ for( int i=1; i<nIns(); i++ )
+ flt.setDef(i, in(i)._type instanceof TypeFloat ? in(i) : new ToFloatNode(in(i)).peephole());
+ kill();
+ return flt;
+ }
+ private boolean hasFloatInput() {
+ for( int i=1; i<nIns(); i++ )
+ if( in(i)._type instanceof TypeFloat )
+ return true;
+ return false;
+ }
+ Node copyF() { return null; }
+
+
+ // ------------------------------------------------------------------------
+ // Peephole utilities
+
+ // Swap inputs without letting either input go dead during the swap.
+ public Node swap12() {
+ unlock(); // Hash is order dependent
+ _inputs.swap(1,2);
+ return this;
+ }
+
+ /**
+ * Does this node contain all constants? Ignores in(0), as is usually
+ * control. In an input is not a constant, we add dep as a dependency to
+ * it because dep can make progress if the input becomes a constant later.
+ * It is sufficient for one of the non-const inputs to have the dependency,
+ * so we don't bother checking the rest.
+ */
+ boolean allCons(Node dep) {
+ for( int i=1; i<nIns(); i++ )
+ if( !(in(i)._type.isConstant()) ) {
+ dep.addDep(in(i)); // If in(i) becomes a constant later, will trigger some peephole
+ return false;
+ }
+ return true;
+ }
+
+
+ // Make a shallow copy (same class) of this Node, with given inputs and
+ // empty outputs and a new Node ID. The original inputs are ignored.
+ // Does not need to be implemented in isCFG() nodes.
+ Node copy(Node lhs, Node rhs) { throw Utils.TODO("Binary ops need to implement copy"); }
+
+ public Node copy() {
+ Node n;
+ try { n = (Node)clone(); }
+ catch( Exception e ) { throw new RuntimeException(e); }
+ n._nid = CODE.getUID(); // allocate unique dense ID
+ n._inputs = new Ary<>(Node.class);
+ n._outputs = new Ary<>(Node.class);
+ n._deps = null;
+ n._hash = 0;
+ return n;
+ }
+
+ // Report any post-optimize errors
+ public Parser.ParseException err() { return null; }
+
+ // Common integer constants
+ public static ConstantNode con(long x) { return (ConstantNode)(new ConstantNode(TypeInteger.constant(x)).peephole()); }
+
+ // Utility to walk the entire graph applying a function; return the first
+ // not-null result.
+ final public <E> E walk( Function<Node,E> pred ) {
+ assert CODE._visit.isEmpty();
+ E rez = _walk(pred);
+ CODE._visit.clear();
+ return rez;
+ }
+
+ private <E> E _walk( Function<Node,E> pred ) {
+ if( CODE._visit.get(_nid) ) return null; // Been there, done that
+ CODE._visit.set(_nid);
+ E x = pred.apply(this);
+ if( x != null ) return x;
+ for( Node def : _inputs ) if( def != null && (x = def._walk(pred)) != null ) return x;
+ for( Node use : _outputs ) if( use != null && (x = use._walk(pred)) != null ) return x;
+ return null;
+ }
+
+ /**
+ * Debugging utility to find a Node by index
+ */
+ public Node find(int nid) { return _find(nid, new BitSet()); }
+ private Node _find(int nid, BitSet bs) {
+ if( bs.get(_nid) ) return null; // Been there, done that
+ bs.set(_nid);
+ if( _nid==nid ) return this;
+ Node x;
+ for( Node def : _inputs ) if( def != null && (x = def._find(nid,bs)) != null ) return x;
+ for( Node use : _outputs ) if( use != null && (x = use._find(nid,bs)) != null ) return x;
+ return null;
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/NotNode.java
new file mode 100644
index 00000000..0314841d
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/NotNode.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.type.*;
+import java.util.BitSet;
+
+public class NotNode extends Node {
+ public NotNode(Node in) { super(null, in); }
+
+ @Override public String label() { return "Not"; }
+
+ @Override public String glabel() { return "!"; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("(!"), visited);
+ return sb.append(")");
+ }
+
+ @Override
+ public TypeInteger compute() {
+ Type t0 = in(1)._type;
+ if( t0.isHigh() ) return TypeInteger.BOOL.dual();
+ if( t0 == Type.NIL || t0 == TypeInteger.ZERO ) return TypeInteger.TRUE;
+ if( t0 instanceof TypeNil tn && tn.notNull() ) return TypeInteger.FALSE;
+ if( t0 instanceof TypeInteger i && (i._min > 0 || i._max < 0) ) return TypeInteger.FALSE;
+ return TypeInteger.BOOL;
+ }
+
+ @Override
+ public Node idealize() { return null; }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/OrNode.java
new file mode 100644
index 00000000..9b03dc5d
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/OrNode.java
@@ -0,0 +1,55 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+
+import java.util.BitSet;
+
+public class OrNode extends LogicalNode {
+ public OrNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); }
+
+ @Override public String label() { return "Or"; }
+ @Override public String op() { return "|"; }
+
+ @Override public String glabel() { return "|"; }
+
+ @Override
+ public Type compute() {
+ Type t1 = in(1)._type, t2 = in(2)._type;
+ if( t1.isHigh() || t2.isHigh() )
+ return TypeInteger.TOP;
+ if( t1 instanceof TypeInteger i0 &&
+ t2 instanceof TypeInteger i1 ) {
+ if( i0.isConstant() && i1.isConstant() )
+ return TypeInteger.constant(i0.value()|i1.value());
+ }
+ return TypeInteger.BOT;
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Node rhs = in(2);
+ Type t1 = lhs._type;
+ Type t2 = rhs._type;
+
+ // Or of 0. We do not check for (0|x) because this will already
+ // canonicalize to (x|0)
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
+ return lhs;
+
+ // Move constants to RHS: con*arg becomes arg*con
+ if ( t1.isConstant() && !t2.isConstant() )
+ return swap12();
+
+ // Do we have ((x | (phi cons)) | con) ?
+ // Do we have ((x | (phi cons)) | (phi cons)) ?
+ // Push constant up through the phi: x | (phi con0|con0 con1|con1...)
+ Node phicon = AddNode.phiCon(this,true);
+ if( phicon!=null ) return phicon;
+
+ return null;
+ }
+ @Override Node copy(Node lhs, Node rhs) { return new OrNode(_loc,lhs,rhs); }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/ParmNode.java
new file mode 100644
index 00000000..9e11a129
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/ParmNode.java
@@ -0,0 +1,48 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.type.Type;
+import java.util.BitSet;
+
+public class ParmNode extends PhiNode {
+
+ // Argument indices are mapped one-to-one on CallNode inputs
+ public final int _idx; // Argument index
+
+ public ParmNode(String label, int idx, Type declaredType, Node... inputs) {
+ super(label,declaredType,inputs);
+ _idx = idx;
+ }
+ public ParmNode(ParmNode parm) { super(parm, parm._label, parm._declaredType); _idx = parm._idx; }
+
+ @Override public String label() { return MemOpNode.mlabel(_label); }
+
+ @Override public String glabel() { return _label; }
+
+ public FunNode fun() { return (FunNode)in(0); }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ if( "main".equals(fun()._name) && _label.equals("arg") )
+ return sb.append("arg");
+ sb.append("Parm_").append(_label).append("(");
+ for( Node in : _inputs ) {
+ if (in == null) sb.append("____");
+ else in._print0(sb, visited);
+ sb.append(",");
+ }
+ sb.setLength(sb.length()-1);
+ sb.append(")");
+ return sb;
+ }
+
+
+ @Override
+ public Node idealize() {
+ if( inProgress() ) return null;
+ return super.idealize();
+ }
+
+ // Always in-progress until we run out of unknown callers
+ @Override public boolean inProgress() { return fun().inProgress(); }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/PhiNode.java
new file mode 100644
index 00000000..0579ecd0
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/PhiNode.java
@@ -0,0 +1,221 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.type.*;
+import java.util.BitSet;
+
+public class PhiNode extends Node {
+
+ public final String _label;
+
+ // The Phi type we compute must stay within the domain of the Phi.
+ // Example Int stays Int, Ptr stays Ptr, Control stays Control, Mem stays Mem.
+ final 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; _type = _declaredType = declaredType; }
+ public PhiNode(PhiNode phi) { super(phi); _label = phi._label; _declaredType = phi._declaredType; }
+
+ public PhiNode(RegionNode r, Node sample) {
+ super(new Node[]{r});
+ _label = "";
+ _declaredType = sample._type;
+ while( nIns() < r.nIns() )
+ addDef(sample);
+ }
+
+ @Override public String label() { return "Phi_"+MemOpNode.mlabel(_label); }
+
+ @Override public String glabel() { return "&phi;_"+_label; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ if( !(region() instanceof RegionNode r) || r.inProgress() )
+ sb.append("Z");
+ sb.append("Phi(");
+ for( Node in : _inputs ) {
+ if (in == null) sb.append("____");
+ else in._print0(sb, visited);
+ sb.append(",");
+ }
+ sb.setLength(sb.length()-1);
+ sb.append(")");
+ return sb;
+ }
+
+ public CFGNode region() { return (CFGNode)in(0); }
+ @Override public boolean isMem() { return _declaredType instanceof TypeMem; }
+ @Override public boolean isPinned() { return true; }
+
+ @Override
+ public Type compute() {
+ if( !(region() instanceof RegionNode r) )
+ return region()._type==Type.XCONTROL ? (_type instanceof TypeMem ? TypeMem.TOP : Type.TOP) : _type;
+ // During parsing Phis have to be computed type pessimistically.
+ if( r.inProgress() ) return _declaredType;
+ // Set type to local top of the starting type
+ Type t = _declaredType.glb().dual();//Type.TOP;
+ for (int i = 1; i < nIns(); i++)
+ // If the region's control input is live, add this as a dependency
+ // to the control because we can be peeped should it become dead.
+ if( addDep(r.in(i))._type != Type.XCONTROL )
+ t = t.meet(in(i)._type);
+ return t;
+ }
+
+ @Override
+ public Node idealize() {
+ if( !(region() instanceof RegionNode r ) )
+ return in(1); // Input has collapse to e.g. starting control.
+ if( r.inProgress() || r.nIns()<=1 )
+ return null; // Input is in-progress
+
+ // If we have only a single unique input, become it.
+ Node live = singleUniqueInput();
+ if (live != null)
+ return live;
+
+ // No bother if region is going to fold dead paths soon
+ for( int i=1; i<nIns(); i++ )
+ if( r.in(i)._type == Type.XCONTROL )
+ return null;
+
+ // Generic "pull down op"
+ Node progress;
+ if( same_op() && (progress = drop_same_op()) != null )
+ return progress;
+
+ // If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove.
+ if( nIns()==3 ) {
+ if( in(1) instanceof CastNode cast && addDep(cast.in(1))==in(2) ) return in(2);
+ if( in(2) instanceof CastNode cast && addDep(cast.in(1))==in(1) ) return in(1);
+ }
+ // If merging a null-checked null and the checked value, just use the value.
+ // if( val ) ..; phi(Region,False=0/null,True=val);
+ // then replace with plain val.
+ if( nIns()==3 ) {
+ int nullx = -1;
+ if( in(1)._type == in(1)._type.makeZero() ) nullx = 1;
+ if( in(2)._type == in(2)._type.makeZero() ) nullx = 2;
+ if( nullx != -1 ) {
+ Node val = in(3-nullx);
+ if( val instanceof CastNode cast )
+ val = cast.in(1);
+ 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();
+ if( idom instanceof CProjNode proj && proj._idx==1 )
+ return val;
+ }
+ }
+ }
+
+ 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() {
+ for( int i=1; i<nIns(); i++ ) {
+ Node op = in(i);
+ if( in(1).getClass() != op.getClass() || op.in(0)!=null || in(1).nIns() != op.nIns() )
+ return false; // Wrong class or CFG bound or mismatched inputs
+ if( op.nOuts() > 1 ) { // Too many users, but addDep in case lose users
+ for( Node out : op._outputs )
+ if( out!=null && out!=this )
+ addDep(out);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private Node drop_same_op() {
+ assert !(in(1) instanceof CFGNode);
+ Node op = in(1);
+ Node cp = op.copy();
+ cp._type = null; // Fresh type
+ cp.addDef(null); // No control
+
+ for( int j=1; j<op.nIns(); j++ ) {
+ boolean needsPhi = false;
+ Node x = op.in(j); // Jth input from sample #1
+ for( int i=2; i<nIns(); i++ )
+ if( in(i).in(j) != x )
+ { needsPhi=true; break; }
+ if( needsPhi ) {
+ x = new PhiNode(_label,op.in(j)._type.glb());
+ x.addDef(region());
+ for( int i=1; i<nIns(); i++ )
+ x.addDef(in(i).in(j));
+ x = x.peephole();
+ }
+ cp.addDef(x);
+ }
+ // Test not running backwards, which can happen for e.g. And's
+ if( cp.compute().isa(compute()) )
+ return cp;
+ cp.kill();
+ return null;
+ }
+
+ /**
+ * If only single unique input, return it
+ */
+ private Node singleUniqueInput() {
+ if( region() instanceof LoopNode loop && loop.entry()._type == Type.XCONTROL )
+ return null; // Dead entry loops just ignore and let the loop collapse
+ Node live = null;
+ for( int i=1; i<nIns(); i++ ) {
+ // If the region's control input is live, add this as a dependency
+ // to the control because we can be peeped should it become dead.
+ if( addDep(region().in(i))._type != Type.XCONTROL && in(i) != this )
+ if( live == null || live == in(i) ) live = in(i);
+ else return null;
+ }
+ return live;
+ }
+
+ @Override
+ boolean allCons(Node dep) {
+ if( !(region() instanceof RegionNode r) ) return false;
+ // When the region completes (is no longer in progress) the Phi can
+ // become a "all constants" Phi, and the "dep" might make progress.
+ dep.addDep(this);
+ if( r.inProgress() ) return false;
+ return super.allCons(dep);
+ }
+
+ // True if last input is null
+ public boolean inProgress() {
+ return in(nIns()-1) == null;
+ }
+
+ // Never equal if inProgress
+ @Override public boolean eq( Node n ) {
+ return !inProgress();
+ }
+
+ @Override
+ public Parser.ParseException err() {
+ if( _type != Type.BOTTOM ) return null;
+
+ // BOTTOM means we mixed e.g. int and ptr
+ for( int i=1; i<nIns(); i++ )
+ // Already an error, but better error messages come from elsewhere
+ if( in(i)._type == Type.BOTTOM )
+ return null;
+
+ // Gather a minimal set of types that "cover" all the rest
+ boolean ti=false, tf=false, tp=false, tn=false;
+ for( int i=1; i<nIns(); i++ ) {
+ Type t = in(i)._type;
+ ti |= t instanceof TypeInteger x;
+ tf |= t instanceof TypeFloat x;
+ tp |= t instanceof TypeMemPtr x;
+ tn |= t==Type.NIL;
+ }
+ return ReturnNode.mixerr(ti,tf,tp,tn, ((RegionNode)region())._loc);
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/ProjNode.java
new file mode 100644
index 00000000..a0394e5b
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/ProjNode.java
@@ -0,0 +1,54 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeMem;
+import com.seaofnodes.simple.type.TypeTuple;
+
+import java.util.BitSet;
+
+public class ProjNode extends Node {
+
+ // Which slice of the incoming multipart value
+ public final int _idx;
+
+ // Debugging label
+ public final String _label;
+
+ public ProjNode(Node ctrl, int idx, String label) {
+ super(new Node[]{ctrl});
+ _idx = idx;
+ _label = label;
+ }
+ public ProjNode(ProjNode p) { super(p); _idx = p._idx; _label = p._label; }
+
+ @Override public String label() { return _label; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ if( _label != null ) return sb.append(_label);
+ if( in(0) instanceof CallEndNode cend && cend.call()!=null )
+ return cend.call()._print0(sb,visited);
+ return sb.append("LONELY PROJ");
+ }
+
+ @Override public CFGNode cfg0() {
+ return in(0) instanceof CFGNode cfg ? cfg : in(0).cfg0();
+ }
+
+ @Override public boolean isMem() { return _type instanceof TypeMem; }
+ @Override public boolean isPinned() { return true; }
+
+ @Override
+ public Type compute() {
+ Type t = in(0)._type;
+ return t instanceof TypeTuple tt ? tt._types[_idx] : Type.BOTTOM;
+ }
+
+ @Override public Node idealize() { return ((MultiNode)in(0)).pcopy(_idx); }
+
+ @Override
+ public boolean eq( Node n ) { return _idx == ((ProjNode)n)._idx; }
+
+ @Override
+ int hash() { return _idx; }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java
new file mode 100644
index 00000000..e9c25f47
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java
@@ -0,0 +1,30 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeMemPtr;
+
+import java.util.BitSet;
+
+/**
+ * Cast a pointer to read-only
+ */
+public class ReadOnlyNode extends Node {
+ public ReadOnlyNode( Node n ) { super(null,n); }
+ public ReadOnlyNode( ReadOnlyNode n ) { super(n); }
+ @Override public String label() { return "ReadOnly"; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return in(1)._print0(sb.append("(const)"),visited);
+ }
+ @Override public Type compute() {
+ Type t = in(1)._type;
+ return t instanceof TypeMemPtr tmp ? tmp.makeRO() : t;
+ }
+
+ @Override public Node idealize() {
+ if( in(1)._type instanceof TypeMemPtr tmp && tmp.isFinal() )
+ return in(1);
+ return null;
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/RegionNode.java
new file mode 100644
index 00000000..8c56feff
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/RegionNode.java
@@ -0,0 +1,167 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.Type;
+import java.util.*;
+
+public class RegionNode extends CFGNode {
+ // Source location for late discovered errors
+ public Parser.Lexer _loc;
+
+ public RegionNode(Parser.Lexer loc, Node... nodes) { super(nodes); _loc = loc; }
+ public RegionNode(RegionNode r, Parser.Lexer loc) { super(r); _loc = loc; }
+ public RegionNode(RegionNode r) { super(r); if( r!=null ) _loc = r._loc; }
+
+ @Override
+ public String label() { return "Region"; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return sb.append(label());
+ }
+
+ @Override public boolean blockHead() { return true; }
+
+ @Override
+ public Type compute() {
+ if( inProgress() ) return Type.CONTROL;
+ Type t = Type.XCONTROL;
+ for (int i = 1; i < nIns(); i++)
+ t = t.meet(in(i)._type);
+ return t;
+ }
+
+ @Override
+ public Node idealize() {
+ if( inProgress() ) return null;
+
+ Node progress = deadPath();
+ if( progress!=null ) return progress;
+
+ // If down to a single input, become that input
+ if( nIns()==2 && !hasPhi() )
+ return in(1); // Collapse if no Phis; 1-input Phis will collapse on their own
+
+ // If a CFG diamond with no merging, delete: "if( pred ) {} else {};"
+ if( !hasPhi() && // No Phi users, just a control user
+ in(1) instanceof CProjNode p1 &&
+ in(2) instanceof CProjNode p2 &&
+ addDep(p1.in(0))==addDep(p2.in(0)) &&
+ p1.in(0) instanceof IfNode iff ) {
+ // Replace with the iff.ctrl directly
+ if( nIns()==3 ) return iff.ctrl();
+ // Just delete the path for fat Regions
+ setDef(1,iff.ctrl());
+ return delDef(2);
+ }
+
+ // Flatten stack regions (no loops involved)
+ if( getClass() == RegionNode.class ) {
+ for( int i=1; i<nIns(); i++ )
+ if( cfg(i) instanceof RegionNode region && region.getClass() == RegionNode.class && region.nIns()>2 && !hasMidUser(region) ) {
+ assert !region.inProgress();
+ // Fold Phis
+ for( Node use : _outputs ) {
+ if( use instanceof PhiNode phi ) {
+ PhiNode phi2 = phi.in(i) instanceof PhiNode phi2x && phi2x.region()==region ? phi2x : null;
+ for( int j=1; j<region.nIns(); j++ )
+ phi.addDef(phi2==null ? phi.in(i) : phi2.in(j));
+ CodeGen.CODE.add(phi);
+ }
+ }
+
+ // Fold Region
+ for( int j=1; j<region.nIns(); j++ )
+ addDef(region.in(j));
+ setDef(i,Parser.XCTRL);
+ return this;
+ }
+ }
+
+ return null;
+ }
+
+ Node deadPath() {
+ // Delete dead paths into a Region
+ int path = findDeadInput();
+ if( path==0 ) return null;
+ // Do not delete the entry path of a loop (ok to remove the back edge
+ // and make the loop a single-entry Region which folds away the Loop).
+ // Folding the entry path confused the loop structure, moving the
+ // backedge to the entry point.
+ if( this instanceof LoopNode loop && loop.entry()==in(path) )
+ return null;
+ // Cannot use the obvious output iterator here, because a Phi deleting
+ // an input might recursively delete *itself*. This shuffles the
+ // output array, and we might miss iterating an unrelated Phi. So on
+ // rare occasions we repeat the loop to get all the Phis.
+ int nouts = 0;
+ while( nouts != nOuts() ) {
+ nouts = nOuts();
+ for( int i=0; i<nOuts(); i++ )
+ if( out(i) instanceof PhiNode phi && phi.nIns()==nIns() )
+ CodeGen.CODE.addAll(phi.delDef(path)._outputs);
+ }
+ return isDead() ? Parser.XCTRL : delDef(path);
+ }
+
+
+ private int findDeadInput() {
+ for( int i=1; i<nIns(); i++ )
+ if( in(i)._type==Type.XCONTROL )
+ return i;
+ return 0; // All inputs alive
+ }
+
+ boolean hasPhi() {
+ for( Node phi : _outputs )
+ if( phi instanceof PhiNode )
+ return true;
+ return false;
+ }
+
+ // Is there a value in-between 2 Regions? Difficult to correctly fuse
+ // without extensive testing, so just ignore.
+ boolean hasMidUser(RegionNode r) {
+ for( Node use : r._outputs ) {
+ if( use==this ) continue;
+ if( use instanceof PhiNode ) {
+ for( Node data : use._outputs ) {
+ if( !(data instanceof PhiNode phi2) || phi2.region()!=this )
+ { addDep(use); addDep(data); return true; }
+ }
+ } else
+ { addDep(use); return true; } // Control user
+ }
+ return false;
+ }
+
+
+ // Immediate dominator of Region is a little more complicated.
+ @Override public int idepth() {
+ if( _idepth!=0 ) return _idepth;
+ int d=0;
+ for( Node n : _inputs )
+ if( n!=null )
+ d = Math.max(d,((CFGNode)n).idepth()+1);
+ return _idepth=d;
+ }
+
+ @Override public CFGNode idom(Node dep) {
+ CFGNode lca = null;
+ // Walk the LHS & RHS idom trees in parallel until they match, or either fails.
+ // Because this does not cache, it can be linear in the size of the program.
+ for( int i=1; i<nIns(); i++ )
+ lca = cfg(i)._idom(lca,dep);
+ return lca;
+ }
+
+ // True if last input is null
+ public boolean inProgress() { return nIns()>1 && in(nIns()-1) == null; }
+
+ // Never equal if inProgress
+ @Override public boolean eq( Node n ) { return !inProgress(); }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
new file mode 100644
index 00000000..317f4ce8
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
@@ -0,0 +1,168 @@
+package com.seaofnodes.simple.node;
+
+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.*;
+import java.util.BitSet;
+
+/**
+ * The Return node has two inputs. The first input is a control node and the
+ * second is the data node that supplies the return value.
+ * <p>
+ * In this presentation, Return functions as a Stop node, since multiple <code>return</code> statements are not possible.
+ * The Stop node will be introduced in Chapter 6 when we implement <code>if</code> statements.
+ * <p>
+ * The Return's output is the value from the data node.
+ */
+public class ReturnNode extends CFGNode {
+
+ public FunNode _fun;
+
+ public ReturnNode(Node ctrl, Node mem, Node data, Node rpc, FunNode fun ) {
+ super(ctrl, mem, data, rpc);
+ _fun = fun;
+ }
+ public ReturnNode( ReturnNode ret, FunNode fun ) { super(ret); _fun = fun; }
+
+ public Node ctrl() { return in(0); }
+ public Node mem () { return in(1); }
+ public Node expr() { return in(2); }
+ public Node rpc () { return in(3); }
+ @Override public FunNode fun() { return _fun; }
+
+ @Override
+ public String label() { return "Return"; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("return ");
+ if( expr()==null ) sb.append("----");
+ else expr()._print0(sb, visited);
+ return sb.append(";");
+ }
+
+ // No one unique control follows; can be many call end sites
+ @Override public CFGNode uctrl() { return null; }
+
+ @Override
+ public Type compute() {
+ if( inProgress () ) return TypeTuple.RET; // In progress
+ if( _fun.isDead() ) return TypeTuple.RET.dual(); // Dead another way
+ return TypeTuple.make(ctrl()._type,mem()._type,expr()._type);
+ }
+
+ @Override public Node idealize() {
+ if( inProgress () ) return null;
+ if( _fun.isDead() ) return null;
+
+ // Upgrade signature based on return type
+ Type ret = expr()._type;
+ TypeFunPtr fcn = _fun.sig();
+ assert ret.isa(fcn.ret());
+ if( ret != fcn.ret() )
+ _fun.setSig(fcn.makeFrom(ret));
+
+ // If dead (cant be reached; infinite loop), kill the exit values
+ if( ctrl()._type==Type.XCONTROL &&
+ !(mem() instanceof ConstantNode && expr() instanceof ConstantNode) ) {
+ Node top = new ConstantNode(Type.TOP).peephole();
+ setDef(1,top);
+ setDef(2,top);
+ return this;
+ }
+
+ return null;
+ }
+
+ public boolean inProgress() {
+ return ctrl().getClass() == RegionNode.class && ((RegionNode)ctrl()).inProgress();
+ }
+
+ // Gather parse-time return types for error reporting
+ private Type mt = Type.TOP;
+ private boolean ti=false, tf=false, tp=false, tn=false;
+
+ // Add a return exit to the current parsing function
+ void addReturn( Node ctrl, Node rmem, Node expr ) {
+ assert inProgress();
+
+ // Gather parse-time return types for error reporting
+ Type t = expr._type;
+ mt = mt.meet(t);
+ ti |= t instanceof TypeInteger x;
+ tf |= t instanceof TypeFloat x;
+ tp |= t instanceof TypeMemPtr x;
+ tn |= t==Type.NIL;
+
+ // Merge path into the One True Return
+ RegionNode r = (RegionNode)ctrl();
+ // Assert that the Phis are in particular outputs; not reordered or shuffled
+ PhiNode mem = (PhiNode)r.out(0); assert mem._declaredType == TypeMem.BOT;
+ PhiNode rez = (PhiNode)r.out(1); assert rez._declaredType == Type.BOTTOM;
+ // Pop "inProgress" null off
+ r ._inputs.pop();
+ mem._inputs.pop();
+ rez._inputs.pop();
+ // Add new return point
+ r .addDef(ctrl);
+ mem.addDef(rmem);
+ rez.addDef(expr);
+ // Back to being inProgress
+ r .addDef(null);
+ mem.addDef(null);
+ rez.addDef(null);
+ }
+
+ @Override public Parser.ParseException err() {
+ return expr()._type/*mt*/==Type.BOTTOM ? mixerr(ti,tf,tp,tn,_fun._loc) : null;
+ }
+
+ static Parser.ParseException mixerr( boolean ti, boolean tf, boolean tp, boolean tn, Parser.Lexer loc ) {
+ if( !ti && !tf && !tp && !tn )
+ return Parser.error("No defined return type", loc);
+ SB sb = new SB().p("No common type amongst ");
+ if( ti ) sb.p("int and ");
+ if( tf ) sb.p("f64 and ");
+ if( tp || tn ) sb.p("reference and ");
+ return Parser.error(sb.unchar(5).toString(),loc);
+ }
+
+
+ // ------------
+ // MachNode specifics, shared across all CPUs
+ public String op() {
+ return _fun._frameAdjust > 0 ? "addi" : "ret";
+ }
+ // Correct Nodes outside the normal edges
+ public void postSelect(CodeGen code) {
+ FunNode fun = (FunNode)rpc().in(0);
+ _fun = fun;
+ fun.setRet(this);
+ }
+ public RegMask regmap(int i) {
+ return i==2
+ ? CodeGen.CODE._mach.retMask(_fun.sig())
+ : CodeGen.CODE._retMasks[i];
+ }
+ public RegMask outregmap() { return null; }
+ public void encoding( Encoding enc ) { throw Utils.TODO(); }
+ public void asm(CodeGen code, SB sb) {
+ int frameAdjust = fun()._frameAdjust;
+ if( frameAdjust>0 )
+ sb.p("rsp += #").p(frameAdjust).p("\nret");
+ // Post code-gen, just print the "ret"
+ if( code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() )
+ // Prints return reg (either RAX or XMM0), RPC and then the
+ // callee-save registers.
+ for( int i=2; i<nIns(); i++ )
+ sb.p(code.reg(in(i),fun())).p(" ");
+ // If we did not get the expected rpc, print which one we got
+ else if( code._regAlloc.regnum(rpc()) != code._mach.rpc() )
+ sb.p("[").p(code.reg(rpc())).p("]");
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java b/chapter21/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java
new file mode 100644
index 00000000..7936086d
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java
@@ -0,0 +1,38 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFloat;
+
+import java.util.BitSet;
+
+public class RoundF32Node extends Node {
+ public RoundF32Node(Node lhs) { super(null, lhs); }
+
+ @Override public String label() { return "RoundF32"; }
+
+ @Override public String glabel() { return "(f32)"; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return in(1)._print0(sb.append("((f32)"), visited).append(")");
+ }
+
+ @Override
+ public Type compute() {
+ if (in(1)._type instanceof TypeFloat i0 && i0.isConstant() )
+ return TypeFloat.constant((float)i0.value());
+ return in(1)._type;
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Type t1 = lhs._type;
+
+ // RoundF32 of float
+ if( t1 instanceof TypeFloat tf && tf._sz==32 )
+ return lhs;
+
+ return null;
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/SarNode.java
new file mode 100644
index 00000000..4a71ec06
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/SarNode.java
@@ -0,0 +1,50 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import java.util.BitSet;
+
+public class SarNode extends LogicalNode {
+ public SarNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); }
+
+ @Override public String label() { return "Sar"; }
+ @Override public String op() { return ">>"; }
+
+ @Override public String glabel() { return "&gt;&gt;"; }
+
+ @Override
+ public Type compute() {
+ Type t1 = in(1)._type, t2 = in(2)._type;
+ if( t1.isHigh() || t2.isHigh() )
+ return TypeInteger.TOP;
+ if (t1 instanceof TypeInteger i0 &&
+ t2 instanceof TypeInteger i1) {
+ if( i0 == TypeInteger.ZERO )
+ return TypeInteger.ZERO;
+ if( i0.isConstant() && i1.isConstant() )
+ return TypeInteger.constant(i0.value()>>i1.value());
+ if( i1.isConstant() ) {
+ int log = (int)i1.value();
+ return TypeInteger.make(-1L<<(63-log),(1L<<(63-log))-1);
+ }
+ }
+ return TypeInteger.BOT;
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Node rhs = in(2);
+ Type t2 = rhs._type;
+
+ // Sar of 0.
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 )
+ return lhs;
+
+ // TODO: x >> 3 >> (y ? 1 : 2) ==> x >> (y ? 4 : 5)
+
+ return null;
+ }
+ @Override Node copy(Node lhs, Node rhs) { return new SarNode(_loc,lhs,rhs); }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
new file mode 100644
index 00000000..38a5d968
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
@@ -0,0 +1,429 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.*;
+import java.util.*;
+
+/**
+ * The Scope node is purely a parser helper - it tracks names to nodes with a
+ * stack of variables.
+ */
+public class ScopeNode extends MemMergeNode {
+
+ /**
+ * The control is a name that binds to the currently active control
+ * node in the graph
+ */
+ public static final String CTRL = "$ctrl";
+ public static final String ARG0 = "arg";
+ public static final String MEM0 = "$mem";
+
+ // All active/live variables in all nested scopes, all run together
+ public final Ary<Var> _vars;
+
+ // Size of each nested lexical scope
+ public final Ary<Integer> _lexSize;
+
+ // Lexical scope is one of normal Block, constructor or function
+ public enum Kind { Block, Constructor, Function };
+ public final Ary<Kind> _kinds;
+
+ // Extra guards; tested predicates and casted results
+ private final Ary<Node> _guards;
+
+ // A new ScopeNode
+ public ScopeNode() {
+ super(true);
+ _vars = new Ary<>(Var .class);
+ _lexSize= new Ary<>(Integer.class);
+ _kinds = new Ary<>(Kind .class);
+ _guards = new Ary<>(Node .class);
+ }
+
+ @Override public String label() { return "Scope"; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("Scope[ ");
+ int j=1;
+ for( int i=0; i<nIns(); i++ ) {
+ if( j < _lexSize._len && i == _lexSize.at(j) ) { sb.append("| "); j++; }
+ Var v = _vars.get(i);
+ sb.append(v.type().print(new SB()));
+ sb.append(" ");
+ if( v._final ) sb.append("!");
+ sb.append(v._name);
+ sb.append("=");
+ Node n = in(i);
+ while( n instanceof ScopeNode loop ) {
+ sb.append("Lazy_");
+ n = loop.in(i);
+ }
+ if( n==null ) sb.append("___");
+ else n._print0(sb, visited);
+ sb.append(", ");
+ }
+ sb.setLength(sb.length()-2);
+ return sb.append("]");
+ }
+
+
+ public Node ctrl() { return in(0); }
+ public MemMergeNode mem() { return (MemMergeNode)in(1); }
+
+ /**
+ * The ctrl of a ScopeNode is always bound to the currently active
+ * control node in the graph, via a special name '$ctrl' that is not
+ * a valid identifier in the language grammar and hence cannot be
+ * referenced in Simple code.
+ *
+ * @param n The node to be bound to '$ctrl'
+ *
+ * @return Node that was bound
+ */
+ public <N extends Node> N ctrl(N n) { return setDef(0,n); }
+ public Node mem(Node n) { return setDef(1,n); }
+
+ public void push(Kind kind) {
+ assert _lexSize._len==_kinds._len;
+ _lexSize.push(_vars.size());
+ _kinds .push(kind);
+ }
+
+ // Pop a lexical scope
+ public void pop() {
+ assert _lexSize._len==_kinds._len;
+ promote();
+ int n = _lexSize.pop();
+ _kinds.pop();
+ popUntil(n);
+ _vars.setLen(n);
+ }
+
+
+ // Look for forward references in the last lexical scope and promote to the
+ // next outer lexical scope. At the last scope declare them an error.
+ public void promote() {
+ int n = _lexSize.last();
+ for( int i=n; i<nIns(); i++ ) {
+ Var v = _vars.at(i);
+ if( !v.isFRef() ) continue;
+ if( _lexSize._len==1 )
+ throw Parser.error("Undefined name '" + v._name + "'",v._loc);
+ _vars.swap(n,i);
+ _inputs.swap(n,i);
+ v._idx = n;
+ n++;
+ _lexSize.set(_lexSize._len-1,n);
+ }
+ }
+
+
+ public boolean inCon() { return _kinds.last() == Kind.Constructor; }
+
+ // Is v outside any current function scope?
+ public boolean outOfFunction( Var v ) {
+ for( int i=_lexSize._len-1; i>=0 && v._idx<_lexSize.at(i); i-- )
+ if( _kinds.at(i)==Kind.Function )
+ return true;
+ return false;
+ }
+
+
+ // Find name in reverse, return an index into _vars or -1. Linear scan
+ // instead of hashtable, but probably doesn't matter until the scan
+ // typically hits many dozens of variables.
+ int find( String name ) {
+ for( int i=_vars.size()-1; i>=0; i-- )
+ if( _vars.at(i)._name.equals(name) )
+ return i;
+ return -1;
+ }
+
+ /**
+ * Create a new variable name in the current scope
+ */
+ public boolean define( String name, Type declaredType, boolean xfinal, Node init, Parser.Lexer loc ) {
+ assert _lexSize.isEmpty() || name.charAt(0)!='$' ; // Later scopes do not define memory
+ if( _lexSize._len > 0 )
+ for( int i=_vars.size()-1; i>=_lexSize.last(); i-- ) {
+ Var n = _vars.at(i);
+ if( n._name.equals(name) ) {
+ if( !n.isFRef() ) return false; // Double define
+ FRefNode fref = (FRefNode)in(n._idx); // Get forward ref
+ if( !xfinal || !declaredType.isConstant() ) throw fref.err(); // Must be a final constant
+ n.defFRef(declaredType,xfinal,loc); // Declare full correct type, final, source location
+ setDef(n._idx,fref.addDef(init)); // Set FRef to defined; tell parser also
+ }
+ }
+ Var v = new Var(nIns(),name,declaredType,xfinal,loc,init==Parser.XCTRL);
+ _vars.add(v);
+ // Creating a forward reference
+ if( init==Parser.XCTRL )
+ init = new FRefNode(v).init();
+ addDef(init);
+ return true;
+ }
+
+ // Read from memory
+ public Node mem( int alias ) { return mem()._mem(alias,null); }
+ // Write to memory
+ public void mem( int alias, Node st ) { mem()._mem(alias,st); }
+
+
+ /**
+ * Lookup a name in all scopes starting from most deeply nested.
+ *
+ * @param name Name to be looked up
+ * @return null if not found, or the implementing Node
+ */
+ public Var lookup( String name ) {
+ int idx = find(name);
+ // -1 is missed in all scopes, not found
+ return idx == -1 ? null : update(_vars.at(idx),null);
+ }
+
+ /**
+ * If the name is present in any scope, then redefine else null
+ *
+ * @param name Name being redefined
+ * @param n The node to bind to the name
+ */
+ public void update( String name, Node n ) {
+ int idx = find(name);
+ assert idx>=0;
+ update(_vars.at(idx),n);
+ }
+
+ public Var update( Var v, Node st ) {
+ Node old = in(v._idx);
+ if( old instanceof ScopeNode loop ) {
+ // Lazy Phi!
+ Node def = loop.in(v._idx);
+ old = def instanceof PhiNode phi && loop.ctrl()==phi.region()
+ // Loop already has a real Phi, use it
+ ? def
+ // Set real Phi in the loop head
+ // The phi takes its one input (no backedge yet) from a recursive
+ // lookup, which might have insert a Phi in every loop nest.
+ : loop.setDef(v._idx,new PhiNode(v._name, v.lazyGLB(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).peephole());
+ setDef(v._idx,old);
+ }
+ assert !v._final || st==null;
+ if( st!=null ) setDef(v._idx,st); // Set new value
+ return v;
+ }
+
+ /**
+ * Duplicate a ScopeNode; including all levels, up to Nodes. So this is
+ * neither shallow (would dup the Scope but not the internal HashMap
+ * tables), nor deep (would dup the Scope, the HashMap tables, but then
+ * also the program Nodes).
+ * <p>
+ * If the {@code loop} flag is set, the edges are filled in as the original
+ * Scope, as an indication of Lazy Phis at loop heads. The goal here is to
+ * not make Phis at loop heads for variables which are never touched in the
+ * loop body.
+ * <p>
+ * The new Scope is a full-fledged Node with proper use<->def edges.
+ */
+ public ScopeNode dup() { return dup(false); }
+ public ScopeNode dup(boolean loop) {
+ ScopeNode dup = new ScopeNode();
+ // Our goals are:
+ // 1) duplicate the name bindings of the ScopeNode across all stack levels
+ // 2) Make the new ScopeNode a user of all the nodes bound
+ // 3) Ensure that the order of defs is the same to allow easy merging
+ dup._vars .addAll(_vars );
+ dup._lexSize.addAll(_lexSize);
+ dup._kinds .addAll(_kinds );
+ dup._guards .addAll(_guards );
+ // The dup'd guards all need dup'd keepers, to keep proper accounting
+ // when later removing all guards
+ for( Node n : _guards )
+ if( !(n instanceof CFGNode) )
+ n.keep();
+ dup.addDef(ctrl()); // Control input is just copied
+
+ // Memory input is a shallow copy
+ MemMergeNode memdup = new MemMergeNode(true), mem = mem();
+ memdup.addDef(null);
+ memdup.addDef(loop ? this : mem.in(1));
+ for( int i=2; i<mem.nIns(); i++ )
+ // For lazy phis on loops we use a sentinel
+ // that will trigger phi creation on update
+ memdup.addDef(loop ? this : mem.in(i));
+ dup.addDef(memdup);
+
+ // Copy of other inputs
+ for( int i=2; i<nIns(); i++ )
+ // For lazy phis on loops we use a sentinel
+ // that will trigger phi creation on update
+ dup.addDef(loop && !_vars.at(i)._final ? this : in(i));
+ return dup;
+ }
+
+ /**
+ * Merges the names whose node bindings differ, by creating Phi node for such names
+ * The names could occur at all stack levels, but a given name can only differ in the
+ * innermost stack level where the name is bound.
+ *
+ * @param that The ScopeNode to be merged into this
+ * @return A new node representing the merge point
+ */
+ public RegionNode mergeScopes(ScopeNode that, Parser.Lexer loc) {
+ RegionNode r = ctrl(new RegionNode(loc,null,ctrl(), that.ctrl()).keep());
+ mem()._merge(that.mem(),r);
+ this ._merge(that ,r);
+ that.kill(); // Kill merged scope
+ CodeGen.CODE.add(r);
+ return r.unkeep();
+ }
+
+ private void _merge(ScopeNode that, RegionNode r) {
+ for( int i = 2; i < nIns(); i++)
+ if( in(i) != that.in(i) ) { // No need for redundant Phis
+ // If we are in lazy phi mode we need to a lookup
+ // by name as it will trigger a phi creation
+ Var v = _vars.at(i);
+ Node lhs = this.in(this.update(v,null));
+ Node rhs = that.in(that.update(v,null));
+ setDef(i, new PhiNode(v._name, v.type(), r, lhs, rhs).peephole());
+ }
+ }
+
+ // Balance arms of an IF. Extra lonely defs are thrown: "if(pred) int x;".
+ // Forward refs are copied to the other side, "as if" they were there all along.
+ public void balanceIf( ScopeNode scope ) {
+ for( int i = nIns(); i < scope.nIns(); i++ ) {
+ Var n = scope._vars.at(i);
+ if( n.isFRef() ) { // RHS has forward refs
+ _vars.add(n); // Copy to LHS
+ addDef(scope.in(i));
+ } else
+ throw Parser.error("Cannot define a '"+n._name+"' on one arm of an if",n._loc);
+ }
+ }
+
+
+ // peephole the backedge scope into this loop head scope
+ // We set the second input to the phi from the back edge (i.e. loop body)
+ public void endLoop(ScopeNode back, ScopeNode exit ) {
+ Node ctrl = ctrl();
+ assert ctrl instanceof LoopNode loop && loop.inProgress();
+ ctrl.setDef(2,back.ctrl());
+
+ mem()._endLoopMem( this, back.mem(), exit.mem() );
+ this ._endLoop ( this, back , exit );
+ back.kill(); // Loop backedge is dead
+ // Now one-time do a useless-phi removal
+ mem()._useless();
+ this ._useless();
+
+ // The exit mem's lazy default value had been the loop top,
+ // now it goes back to predating the loop.
+ exit.mem().setDef(1,mem().in(1));
+ }
+
+ // Fill in the backedge of any inserted Phis
+ void _endLoop( ScopeNode scope, Node back, Node exit ) {
+ for( int i=2; i<nIns(); i++ ) {
+ if( _vars.at(i)._final ) continue; // Final vars did not get modified in the loop
+ if( _vars.at(i).type().isHighOrConst() ) continue; // Cannot lift higher than a constant, so no Phi
+ if( back.in(i) != scope ) {
+ PhiNode phi = (PhiNode)in(i);
+ assert phi.region()==scope.ctrl() && phi.in(2)==null;
+ phi.setDef(2,back.in(i)); // Fill backedge
+ }
+ if( exit.in(i) == scope ) // Replace a lazy-phi on the exit path also
+ exit.setDef(i,in(i));
+ }
+ }
+
+
+ // Up-casting: using the results of an If to improve a value.
+ // E.g. "if( ptr ) ptr.field;" is legal because ptr is known not-null.
+ public void addGuards( Node ctrl, Node pred, boolean invert ) {
+ assert ctrl instanceof CFGNode;
+ _guards.add(ctrl); // Marker to distinguish 0,1,2 guards
+ // add pred & its cast to the normal input list, with special Vars
+ if( pred==null || pred.isDead() )
+ return; // Dead, do not add any guards
+ // Invert the If conditional
+ if( invert )
+ pred = pred instanceof NotNode not ? not.in(1) : CodeGen.CODE.add(new NotNode(pred).peephole());
+ // This is a zero/null test.
+ // Compute the positive test type.
+ Type tnz = pred._type.nonZero();
+ if( tnz!=null )
+ _addGuard(tnz,ctrl,pred);
+
+ // Compute the negative test type.
+ if( pred instanceof NotNode not ) {
+ Node npred = not.in(1);
+ Type tzero = npred._type.makeZero();
+ _addGuard(tzero,ctrl,npred);
+ }
+ }
+
+ private void _addGuard(Type guard, Node ctrl, Node pred) {
+ Type tcast = guard.join(pred._type);
+ if( tcast != pred._type && !tcast.isHigh() ) {
+ Node cast = new CastNode(tcast,ctrl,pred.keep()).peephole().keep();
+ _guards.add(pred);
+ _guards.add(cast);
+ replace(pred,cast);
+ }
+ }
+
+
+ // Remove matching pred/cast pairs from this guarded region.
+ public ScopeNode removeGuards( Node ctrl ) {
+ assert ctrl instanceof CFGNode;
+ // 0,1 or 2 guards
+ while( true ) {
+ Node g = _guards.pop();
+ if( g == ctrl ) break;
+ if( g instanceof CFGNode ) continue;
+ g .unkill(); // Pop/kill cast
+ _guards.pop().unkill(); // Pop/kill pred
+ }
+ return this;
+ }
+
+ // If we find a guarded instance of pred, replace with the upcasted version
+ public Node upcastGuard( Node pred ) {
+ // If finding an instanceof pred, replace with cast.
+ // Otherwise, just pred itself.
+ int i = _guards._len;
+ while( i > 0 ) {
+ Node cast = _guards.at(--i);
+ if( cast instanceof CFGNode ) continue; // Marker between guard sets
+ Node xpred = _guards.at(--i);
+ if( xpred == pred )
+ return cast;
+ }
+ return pred;
+ }
+
+ // Kill guards also
+ @Override public void kill() {
+ for( Node n : _guards )
+ if( !(n instanceof CFGNode) )
+ n.unkill();
+ _guards.clear();
+ // Can have lazy uses remaining
+ if( isUnused() )
+ super.kill();
+ }
+
+
+ private void replace( Node old, Node cast ) {
+ assert old!=null && old!=cast;
+ for( int i=0; i<nIns(); i++ )
+ if( in(i)==old )
+ setDef(i,cast);
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/ShlNode.java
new file mode 100644
index 00000000..82e01841
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/ShlNode.java
@@ -0,0 +1,53 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import java.util.BitSet;
+
+public class ShlNode extends LogicalNode {
+ public ShlNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); }
+
+ @Override public String label() { return "Shl"; }
+ @Override public String op() { return "<<"; }
+
+ @Override public String glabel() { return "&lt;&lt;"; }
+
+ @Override
+ public Type compute() {
+ Type t1 = in(1)._type, t2 = in(2)._type;
+ if( t1.isHigh() || t2.isHigh() )
+ return TypeInteger.TOP;
+ if (t1 instanceof TypeInteger i0 &&
+ t2 instanceof TypeInteger i1 ) {
+ if( i0 == TypeInteger.ZERO )
+ return TypeInteger.ZERO;
+ if( i0.isConstant() && i1.isConstant() )
+ return TypeInteger.constant(i0.value()<<i1.value());
+ }
+ return TypeInteger.BOT;
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Node rhs = in(2);
+
+ if( rhs._type instanceof TypeInteger shl && shl.isConstant() ) {
+ // Shl of 0.
+ if( (shl.value()&63)==0 )
+ return lhs;
+ // (x + c) << i => (x << i) + (c << i)
+ if( lhs instanceof AddNode add && addDep(add).in(2)._type instanceof TypeInteger c && c.isConstant() ) {
+ long sum = c.value() << shl.value();
+ if( Integer.MIN_VALUE <= sum && sum <= Integer.MAX_VALUE )
+ return new AddNode(new ShlNode(_loc,add.in(1),rhs).peephole(), Parser.con(sum) );
+ }
+ }
+
+ // TODO: x << 3 << (y ? 1 : 2) ==> x << (y ? 4 : 5)
+
+ return null;
+ }
+ @Override Node copy(Node lhs, Node rhs) { return new ShlNode(_loc,lhs,rhs); }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/ShrNode.java
new file mode 100644
index 00000000..789fb8a8
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/ShrNode.java
@@ -0,0 +1,46 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import java.util.BitSet;
+
+public class ShrNode extends LogicalNode {
+ public ShrNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); }
+
+ @Override public String label() { return "Shr"; }
+ @Override public String op() { return ">>>"; }
+
+ @Override public String glabel() { return "&gt;&gt;&gt;"; }
+
+ @Override
+ public Type compute() {
+ Type t1 = in(1)._type, t2 = in(2)._type;
+ if( t1.isHigh() || t2.isHigh() )
+ return TypeInteger.TOP;
+ if (t1 instanceof TypeInteger i0 &&
+ t2 instanceof TypeInteger i1 ) {
+ if( i0 == TypeInteger.ZERO )
+ return TypeInteger.ZERO;
+ if( i0.isConstant() && i1.isConstant() )
+ return TypeInteger.constant(i0.value()>>>i1.value());
+ }
+ return TypeInteger.BOT;
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Node rhs = in(2);
+ Type t2 = rhs._type;
+
+ // Shr of 0.
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 )
+ return lhs;
+
+ // TODO: x >>> 3 >>> (y ? 1 : 2) ==> x >>> (y ? 4 : 5)
+
+ return null;
+ }
+ @Override Node copy(Node lhs, Node rhs) { return new ShrNode(_loc,lhs,rhs); }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/SplitNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/SplitNode.java
new file mode 100644
index 00000000..3a1ed011
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/SplitNode.java
@@ -0,0 +1,25 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.Type;
+import java.util.BitSet;
+
+public abstract class SplitNode extends MachConcreteNode {
+ public final String _kind; // Kind of split
+ public final byte _round;
+ public SplitNode(String kind, byte round, Node[] nodes) { super(nodes); _kind = kind; _round = round; }
+ @Override public String op() { return "mov"; }
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("mov(");
+ if( in(1) == null ) sb.append("---");
+ else in(1)._print0(sb,visited);
+ return sb.append(")");
+ }
+ @Override public Type compute() { return in(0)._type; }
+ @Override public Node idealize() { return null; }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1)));
+ }
+ @Override public String comment() { return _kind + " #"+ _round; }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/StartNode.java
new file mode 100644
index 00000000..4bd76f50
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/StartNode.java
@@ -0,0 +1,57 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.*;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.HashSet;
+import static com.seaofnodes.simple.Utils.TODO;
+
+/**
+ * The Start node represents the start of the function.
+ *
+ * Start initially has 1 input (arg) from outside and the initial control.
+ * In ch10 we also add mem aliases as structs get defined; each field in struct
+ * adds a distinct alias to Start's tuple.
+ */
+public class StartNode extends LoopNode implements MultiNode {
+
+ final Type _arg;
+
+ public StartNode(Type arg) { super((Parser.Lexer)null,null); _arg = arg; _type = compute(); }
+ public StartNode(StartNode start) { super(start); _arg = start==null ? null : start._arg; }
+
+ @Override public String label() { return "Start"; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return sb.append(label());
+ }
+
+ @Override public boolean blockHead() { return true; }
+ @Override public CFGNode cfg0() { return null; }
+
+ // Get the one control following; error to call with more than one e.g. an
+ // IfNode or other multi-way branch. For Start, its "main"
+ @Override public CFGNode uctrl() {
+ // Find "main", its the start.
+ CFGNode C = null;
+ for( Node use : _outputs )
+ if( use instanceof FunNode fun && fun.sig().isa(CodeGen.CODE._main) )
+ { assert C==null; C = fun; }
+ return C;
+ }
+
+
+ @Override public TypeTuple compute() {
+ return TypeTuple.make(Type.CONTROL,TypeMem.TOP,_arg);
+ }
+
+ @Override public Node idealize() { return null; }
+
+ // No immediate dominator, and idepth==0
+ @Override public int idepth() { return 0; }
+ @Override public CFGNode idom(Node dep) { return null; }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/StopNode.java
new file mode 100644
index 00000000..9be08ad9
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/StopNode.java
@@ -0,0 +1,65 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.type.Type;
+import java.util.BitSet;
+
+public class StopNode extends CFGNode {
+
+ public final String _src;
+
+ public StopNode(String src) {
+ super();
+ _src = src;
+ _type = compute();
+ }
+ public StopNode(StopNode stop) { super(stop); _src = stop==null ? null : stop._src; }
+
+ @Override
+ public String label() {
+ return "Stop";
+ }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ // For the sake of many old tests, and single value prints as "return val"
+ if( ret()!=null ) return ret()._print0(sb,visited);
+ sb.append("Stop[ ");
+ for( Node ret : _inputs )
+ if( ret!=null )
+ ret._print0(sb, visited).append(" ");
+ return sb.append("]");
+ }
+
+ @Override public boolean blockHead() { return true; }
+
+
+ // If a single Return, return it.
+ // Otherwise, null because ambiguous.
+ public ReturnNode ret() {
+ return nIns()==1 && in(0) instanceof ReturnNode ret ? ret : null;
+ }
+
+ @Override
+ public Type compute() {
+ return Type.BOTTOM;
+ }
+
+ @Override
+ public Node idealize() {
+ int len = nIns();
+ for( int i=0; i<nIns(); i++ )
+ if( ((ReturnNode)in(i)).fun().isDead() )
+ delDef(i--);
+ if( len != nIns() ) return this;
+ return null;
+ }
+
+ @Override public int idepth() {
+ if( _idepth!=0 ) return _idepth;
+ int d=0;
+ for( Node ret : _inputs )
+ d = Math.max(d,((ReturnNode)ret).idepth()+1);
+ return _idepth=d;
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/StoreNode.java
new file mode 100644
index 00000000..b9f99f4e
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/StoreNode.java
@@ -0,0 +1,112 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.type.*;
+import java.util.BitSet;
+
+/**
+ * Store represents setting a value to a memory based object, in chapter 10
+ * this means a field inside a struct.
+ */
+public class StoreNode extends MemOpNode {
+
+ private final boolean _init; // Initializing writes are allowed to write null
+
+ /**
+ * @param name The struct field we are assigning to
+ * @param mem The memory alias node - this is updated after a Store
+ * @param ptr The ptr to the struct base where we will store a value
+ * @param off The offset inside the struct base
+ * @param value Value to be stored
+ */
+ public StoreNode(Parser.Lexer loc, String name, int alias, Type glb, Node mem, Node ptr, Node off, Node value, boolean init) {
+ super(loc, name, alias, false, glb, mem, ptr, off, value);
+ _init = init;
+ }
+
+ // GraphVis DOT code and debugger labels
+ @Override public String label() { return "st_"+mlabel(); }
+ // GraphVis node-internal labels
+ @Override public String glabel() { return "." +_name+"="; }
+ @Override public boolean isMem() { return true; }
+
+ public Node val() { return in(4); }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return sb.append(".").append(_name).append("=").append( val()).append(";");
+ }
+
+ @Override
+ public Type compute() {
+ Type val = val()._type;
+ TypeMem mem = (TypeMem)mem()._type; // Invariant
+ if( mem == TypeMem.TOP ) return TypeMem.TOP;
+ Type t = Type.BOTTOM; // No idea on field contents
+ // Same alias, lift val to the declared type and then meet into other fields
+ if( mem._alias == _alias ) {
+ // Update declared forward ref to the actual
+ if( _declaredType.isFRef() && val instanceof TypeMemPtr tmp && !tmp.isFRef() )
+ _declaredType = tmp;
+ val = val.join(_declaredType);
+ t = val.meet(mem._t);
+ }
+ return TypeMem.make(_alias,t);
+ }
+
+ @Override
+ public Node idealize() {
+
+ // Simple store-after-store on same address. Should pick up the
+ // required init-store being stomped by a first user store.
+ if( mem() instanceof StoreNode st &&
+ ptr()==st.ptr() && // Must check same object
+ off()==st.off() && // And same offset (could be "same alias" but this handles arrays to same index)
+ ptr()._type instanceof TypeMemPtr && // No bother if weird dead pointers
+ // Must have exactly one use of "this" or you get weird
+ // non-serializable memory effects in the worse case.
+ checkOnlyUse(st) ) {
+ assert _name.equals(st._name); // Equiv class aliasing is perfect
+ setDef(1,st.mem());
+ return this;
+ }
+
+ // Value is automatically truncated by narrow store
+ if( val() instanceof AndNode and && and.in(2)._type.isConstant() ) {
+ int log = _declaredType.log_size();
+ if( log<3 ) { // And-mask vs narrow store
+ long mask = ((TypeInteger)and.in(2)._type).value();
+ long bits = (1L<<(8<<log))-1;
+ // Mask does not mask any of the stored bits
+ if( (bits&mask)==bits )
+ // So and-mask is already covered by the store
+ { setDef(4,and.in(1)); return this; }
+ }
+ }
+
+ return null;
+ }
+
+ // Check that "mem" has no uses except "this"
+ private boolean checkOnlyUse(Node mem) {
+ if( mem.nOuts()==1 ) return true;
+ // Add deps on the other uses (can be e.g. ScopeNode mid-parse) so that
+ // when the other uses go away we can retry.
+ for( Node use : mem._outputs )
+ if( use != this )
+ addDep(use);
+ return false;
+ }
+
+ @Override
+ public Parser.ParseException err() {
+ Parser.ParseException err = super.err();
+ if( err != null ) return err;
+ TypeMemPtr tmp = (TypeMemPtr)ptr()._type;
+ if( tmp._obj.field(_name)._final && !_init )
+ return Parser.error("Cannot modify final field '"+_name+"'",_loc);
+ 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/chapter21/src/main/java/com/seaofnodes/simple/node/StructNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/StructNode.java
new file mode 100644
index 00000000..4efc0de9
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/StructNode.java
@@ -0,0 +1,46 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.type.*;
+import java.util.BitSet;
+
+/**
+ * Build a compound object
+ */
+public class StructNode extends Node {
+
+ public TypeStruct _ts;
+
+ @Override public String label() { return _ts==null ? "STRUCT?" : _ts.str(); }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ if( _ts==null ) return sb.append("STRUCT?");
+ sb.append(_ts._name).append(" {");
+ for( int i=0; i<nIns(); i++ ) {
+ sb.append(_ts._fields[i]._fname).append(":");
+ sb.append((in(i)==null ? Type.BOTTOM : in(i)._type).print(new SB()));
+ sb.append("; ");
+ }
+ sb.setLength(sb.length()-2);
+ return sb.append("}");
+ }
+
+ @Override
+ public TypeStruct compute() {
+ if( _ts==null ) return TypeStruct.BOT;
+ Field[] fs = new Field[_ts._fields.length];
+ for( int i=0; i<fs.length; i++ )
+ fs[i] = _ts._fields[i].makeFrom(in(i)==null ? Type.TOP : in(i)._type);
+ return TypeStruct.make(_ts._name,fs);
+ }
+
+ @Override
+ public Node idealize() { return null; }
+
+ @Override
+ public boolean eq(Node n) { return _ts == ((StructNode)n)._ts; }
+
+ @Override
+ int hash() { return _ts==null ? 0 : _ts.hashCode(); }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/SubFNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/SubFNode.java
new file mode 100644
index 00000000..5c7e969f
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/SubFNode.java
@@ -0,0 +1,44 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFloat;
+
+import java.util.BitSet;
+
+public class SubFNode extends Node {
+ public SubFNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+
+ @Override public String label() { return "SubF"; }
+
+ @Override public String glabel() { return "-"; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("("), visited);
+ in(2)._print0(sb.append("-"), visited);
+ return sb.append(")");
+ }
+
+ @Override
+ public Type compute() {
+ if (in(1)._type instanceof TypeFloat i0 &&
+ in(2)._type instanceof TypeFloat i1) {
+ if (i0.isConstant() && i1.isConstant())
+ return TypeFloat.constant(i0.value()-i1.value());
+ }
+ return in(1)._type.meet(in(2)._type);
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Type t2 = in(2)._type;
+
+ // Sub of 0.
+ if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==0 )
+ return lhs;
+
+ return null;
+ }
+ @Override Node copy(Node lhs, Node rhs) { return new SubFNode(lhs,rhs); }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/SubNode.java
new file mode 100644
index 00000000..4290ff9b
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -0,0 +1,59 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import java.util.BitSet;
+
+public class SubNode extends Node {
+ public SubNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+
+ @Override public String label() { return "Sub"; }
+
+ @Override public String glabel() { return "-"; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("("), visited);
+ in(2)._print0(sb.append("-"), visited);
+ return sb.append(")");
+ }
+
+ @Override
+ public Type compute() {
+ Type t1 = in(1)._type, t2 = in(2)._type;
+ if( t1.isHigh() || t2.isHigh() )
+ return TypeInteger.TOP;
+ // Sub of same is 0
+ if( in(1)==in(2) )
+ return TypeInteger.ZERO;
+ if( t1 instanceof TypeInteger i1 &&
+ t2 instanceof TypeInteger i2 ) {
+ if (i1.isConstant() && i2.isConstant())
+ return TypeInteger.constant(i1.value()-i2.value());
+ // Fold ranges like {2-3} - {0-1} into {1-3}.
+ if( !AddNode.overflow(i1._min,-i2._max) &&
+ !AddNode.overflow(i1._max,-i2._min) &&
+ i2._min != Long.MIN_VALUE )
+ return TypeInteger.make(i1._min-i2._max,i1._max-i2._min);
+ }
+
+ return TypeInteger.BOT;
+ }
+
+ @Override
+ public Node idealize() {
+ // x - (-y) is x+y
+ if( in(2) instanceof MinusNode minus )
+ return new AddNode(in(1),minus.in(1));
+
+ // (-x) - y is -(x+y)
+ if( in(1) instanceof MinusNode minus )
+ return new MinusNode(new AddNode(minus.in(1),in(2)).peephole());
+
+ return null;
+ }
+
+ @Override Node copy(Node lhs, Node rhs) { return new SubNode(lhs,rhs); }
+ @Override Node copyF() { return new SubFNode(null,null); }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java
new file mode 100644
index 00000000..c098ad99
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java
@@ -0,0 +1,34 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFloat;
+import com.seaofnodes.simple.type.TypeInteger;
+
+import java.util.BitSet;
+
+public class ToFloatNode extends Node {
+ public ToFloatNode(Node lhs) { super(null, lhs); }
+
+ @Override public String label() { return "ToFloat"; }
+
+ @Override public String glabel() { return "(flt)"; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return in(1)._print0(sb.append("(flt)"), visited);
+ }
+
+ @Override
+ public Type compute() {
+ if( in(1)._type == Type.NIL ) return TypeFloat.FZERO;
+ if (in(1)._type instanceof TypeInteger i0 ) {
+ if( i0.isHigh() ) return TypeFloat.F64.dual();
+ if( i0.isConstant() )
+ return TypeFloat.constant(i0.value());
+ }
+ return TypeFloat.F64;
+ }
+
+ @Override public Node idealize() { return null; }
+ @Override Node copy(Node lhs, Node rhs) { return new ToFloatNode(lhs); }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java
new file mode 100644
index 00000000..8c620a04
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java
@@ -0,0 +1,15 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.Type;
+import java.util.BitSet;
+
+public class XCtrlNode extends CFGNode {
+ public XCtrlNode() { super(new Node[]{CodeGen.CODE._start}); }
+ @Override public String label() { return "Xctrl"; }
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append("Xctrl"); }
+ @Override public boolean isConst() { return true; }
+ @Override public Type compute() { return Type.XCONTROL; }
+ @Override public Node idealize() { return null; }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/XorNode.java
new file mode 100644
index 00000000..37d7147d
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/XorNode.java
@@ -0,0 +1,54 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import java.util.BitSet;
+
+public class XorNode extends LogicalNode {
+ public XorNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); }
+
+ @Override public String label() { return "Xor"; }
+ @Override public String op() { return "^"; }
+
+ @Override public String glabel() { return "^"; }
+
+ @Override
+ public Type compute() {
+ Type t1 = in(1)._type, t2 = in(2)._type;
+ if( t1.isHigh() || t2.isHigh() )
+ return TypeInteger.TOP;
+ if( t1 instanceof TypeInteger i0 &&
+ t2 instanceof TypeInteger i1 ) {
+ if( i0.isConstant() && i1.isConstant() )
+ return TypeInteger.constant(i0.value()^i1.value());
+ }
+ return TypeInteger.BOT;
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Node rhs = in(2);
+ Type t1 = lhs._type;
+ Type t2 = rhs._type;
+
+ // Xor of 0. We do not check for (0^x) because this will already
+ // canonicalize to (x^0)
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
+ return lhs;
+
+ // Move constants to RHS: con*arg becomes arg*con
+ if ( t1.isConstant() && !t2.isConstant() )
+ return swap12();
+
+ // Do we have ((x ^ (phi cons)) ^ con) ?
+ // Do we have ((x ^ (phi cons)) ^ (phi cons)) ?
+ // Push constant up through the phi: x ^ (phi con0^con0 con1^con1...)
+ Node phicon = AddNode.phiCon(this,true);
+ if( phicon!=null ) return phicon;
+
+ return null;
+ }
+ @Override Node copy(Node lhs, Node rhs) { return new XorNode(_loc,lhs,rhs); }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java
new file mode 100644
index 00000000..f37738bf
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java
@@ -0,0 +1,20 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class AddARM extends MachConcreteNode implements MachNode {
+ AddARM( Node add) { super(add); }
+ @Override public String op() { return "add"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+
+ // ADD (shifted register)
+ @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,arm.OP_ADD); }
+ // General form: "rd = rs1 + rs2"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2)));
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java
new file mode 100644
index 00000000..209f24f4
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java
@@ -0,0 +1,22 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class AddFARM extends MachConcreteNode implements MachNode {
+ AddFARM(Node addf) { super(addf); }
+ @Override public String op() { return "addf"; }
+ @Override public RegMask regmap(int i) { return arm.DMASK; }
+ @Override public RegMask outregmap() { return arm.DMASK; }
+
+ //FADD (scalar)
+ @Override public void encoding( Encoding enc ) { arm.f_scalar(enc,this,arm.OPF_OP_ADD);}
+
+ // Default on double precision for now(64 bits)
+ // General form: "addf rd = src1 + src2
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2)));
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java
new file mode 100644
index 00000000..a1cd8306
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java
@@ -0,0 +1,27 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class AddIARM extends MachConcreteNode implements MachNode {
+ final int _imm;
+ AddIARM(Node add, int imm) {
+ super(add);
+ _inputs.pop();
+ _imm = imm;
+ }
+ @Override public String op() {
+ return _imm == 1 ? "inc" : (_imm == -1 ? "dec" : "addi");
+ }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ //ADD (immediate)
+ @Override public void encoding( Encoding enc ) {
+ arm.imm_inst(enc,this, in(1), arm.OPI_ADD,_imm);
+ }
+ // General form: "addi rd = rs1 + imm"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + #").p(_imm);
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java
new file mode 100644
index 00000000..ad76ca4c
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.MachConcreteNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.Node;
+
+public class AndARM extends MachConcreteNode implements MachNode {
+ AndARM(Node and) { super(and); }
+ @Override public String op() { return "and"; }
+ @Override public String glabel() { return "&"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ // AND (shifted register)
+ @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,arm.OP_AND); }
+ // General form: #rd = rs1 & rs2
+ @Override public void asm(CodeGen code, SB sb){
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" & ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java
new file mode 100644
index 00000000..1f12ef13
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java
@@ -0,0 +1,24 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class AndIARM extends MachConcreteNode implements MachNode {
+ final int _imm;
+ AndIARM(Node and, int imm) {
+ super(and);
+ _inputs.pop();
+ _imm = imm;
+ }
+ @Override public String op() { return "andi"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ arm.imm_inst_n(enc,this, in(1), arm.OPI_AND,_imm);
+ }
+ // General form: "andi rd = rs1 & imm"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" & #").p(arm.decodeImm12(_imm));
+ }
+} \ No newline at end of file
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java
new file mode 100644
index 00000000..342b7519
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java
@@ -0,0 +1,18 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+// Arithmetic Shift Right (register)
+public class AsrARM extends MachConcreteNode implements MachNode {
+ AsrARM(Node asr) {super(asr);}
+ @Override public String op() { return "sar"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.DMASK; }
+ @Override public void encoding( Encoding enc ) { arm.shift_reg(enc,this,arm.OP_ASR); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> ").p(code.reg(in(2)));
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java
new file mode 100644
index 00000000..a1775dde
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java
@@ -0,0 +1,31 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.node.MachConcreteNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.Node;
+
+public class AsrIARM extends MachConcreteNode implements MachNode {
+ final int _imm;
+ AsrIARM(Node asr, int imm) {
+ super(asr);
+ _inputs.pop();
+ _imm = imm;
+ }
+ @Override public String op() { return "asri"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ short rd = enc.reg(this);
+ short rn = enc.reg(in(1));
+ assert _imm > 0;
+ enc.add4(arm.imm_shift(arm.OPI_ASR,_imm, 0b111111, rn, rd));
+ }
+ // General form: "asri rd = rs1 >> imm"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> #").p(_imm);
+ }
+} \ No newline at end of file
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java
new file mode 100644
index 00000000..b36a395b
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java
@@ -0,0 +1,76 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+// Jump on flags, uses flags
+public class BranchARM extends IfNode implements MachNode, RIPRelSize {
+ String _bop;
+ BranchARM(IfNode iff, String bop ) {
+ super(iff);
+ _bop = bop;
+ }
+ @Override public String op() { return "j"+_bop; }
+ @Override public String label() { return op(); }
+
+ @Override public void postSelect(CodeGen code) {
+ Node set = in(1);
+ if( set==null ) return; // Never-node cutout
+ Node cmp = set.in(1);
+ // Bypass an expected Set and just reference the cmp directly
+ if( set instanceof SetARM)
+ _inputs.set(1,cmp);
+ else
+ throw Utils.TODO();
+ }
+
+ @Override public RegMask regmap(int i) { assert i==1; return arm.FLAGS_MASK; }
+ @Override public RegMask outregmap() { return null; }
+ @Override public void negate() { _bop = negate(_bop); }
+
+ @Override public void encoding( Encoding enc ) {
+ // Assuming that condition flags are already set. These flags are set
+ // by comparison (or sub). No need for regs because it uses flags
+ if( in(1)!=null ) {
+ // B.cond
+ enc.add4( arm.b_cond(arm.OP_BRANCH, 0, arm.make_condition(_bop)) );
+ } else {
+ if( _bop=="!=" ) return; // Inverted, no code
+ enc.add4(arm.b(arm.OP_UJMP, 0));
+ }
+ enc.jump(this,cproj(0));
+ }
+
+ // Delta is from opcode start
+ @Override public byte encSize(int delta) {
+ if( in(1)==null && _bop=="!=" ) return 0; // Inverted never-node, no code
+ if( -(1<<19) <= delta && delta < (1<<19) ) return 4;
+ // 2 word encoding needs a tmp register, must teach RA
+ throw Utils.TODO();
+ }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ assert !( in(1)==null && _bop=="!=" ); // Inverted never-node, no code no patch
+ if( opLen==4 ) {
+ enc.patch4(opStart,arm.b_cond(arm.OP_BRANCH, delta, arm.make_condition(_bop)));
+ } else {
+ throw Utils.TODO();
+ }
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ if( in(1)!=null ) { // Never-node
+ String src = code.reg(in(1));
+ if( src!="flags" ) sb.p(src).p(" ");
+ } else if( _bop=="!=" ) {
+ sb.p("never");
+ return;
+ }
+ CFGNode prj = cproj(0).uctrlSkipEmpty();
+ if( !prj.blockHead() ) prj = prj.cfg0();
+ sb.p(label(prj));
+ }
+ @Override public String comment() { return "L"+cproj(1)._nid; }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java
new file mode 100644
index 00000000..63d418ff
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java
@@ -0,0 +1,47 @@
+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.type.TypeFunPtr;
+
+public class CallARM extends CallNode implements MachNode, RIPRelSize {
+ final TypeFunPtr _tfp;
+ final String _name;
+
+ CallARM(CallNode call, TypeFunPtr tfp) {
+ super(call);
+ _inputs.pop(); // Pop constant target
+ assert tfp.isConstant();
+ _tfp = tfp;
+ _name = CodeGen.CODE.link(tfp)._name;
+ }
+
+ @Override public String op() { return "call"; }
+ @Override public String label() { return op(); }
+ @Override public String name() { return _name; }
+ @Override public TypeFunPtr tfp() { return _tfp; }
+ @Override public RegMask regmap(int i) { return arm.callInMask(_tfp,i); }
+ @Override public RegMask outregmap() { return null; }
+
+ @Override public void encoding( Encoding enc ) {
+ enc.relo(this);
+ // BL
+ enc.add4(arm.b(arm.OP_CALL,0)); // Target patched at link time
+ }
+
+ // Delta is from opcode start, but X86 measures from the end of the 5-byte encoding
+ @Override public byte encSize(int delta) { return 4; }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ enc.patch4(opStart,arm.b(arm.OP_CALL,delta));
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(_name);
+ for( int i=0; i<nargs(); i++ )
+ sb.p(code.reg(arg(i+2))).p(" ");
+ sb.unchar(2);
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallEndARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallEndARM.java
new file mode 100644
index 00000000..b3c2f6c2
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallEndARM.java
@@ -0,0 +1,8 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.node.CallEndNode;
+import com.seaofnodes.simple.node.MachNode;
+
+public class CallEndARM extends CallEndNode implements MachNode {
+ CallEndARM( CallEndNode cend ) { super(cend); }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java
new file mode 100644
index 00000000..b3f55659
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class CallRRARM extends CallNode implements MachNode {
+ CallRRARM(CallNode call) { super(call); }
+ @Override public String op() { return "callr"; }
+ @Override public String label() { return op(); }
+ @Override public RegMask regmap(int i) {
+ return i==_inputs._len
+ ? arm.RMASK // Function call target
+ : arm.callInMask(tfp(),i); // Normal argument
+ }
+ @Override public RegMask outregmap() { return null; }
+
+ @Override public void encoding( Encoding enc ) {
+ // Needs a register, typically a jump-and-link-register opcode
+ // blr
+ short self = enc.reg(this);
+ enc.add4(arm.blr(arm.OP_CALLRARM, self));
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(fptr())).p(" ");
+ for( int i=0; i<nargs(); i++ )
+ sb.p(code.reg(arg(i))).p(" ");
+ sb.unchar(2);
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/CastARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/CastARM.java
new file mode 100644
index 00000000..a5a4e112
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/CastARM.java
@@ -0,0 +1,18 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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.CastNode;
+import com.seaofnodes.simple.node.MachNode;
+
+public class CastARM extends CastNode implements MachNode {
+ public CastARM( 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 void encoding( Encoding enc ) { }
+ @Override public void asm(CodeGen code, SB sb) { _t.print(sb.p(code.reg(in(1))).p(" isa ")); }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java
new file mode 100644
index 00000000..dbc86f62
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java
@@ -0,0 +1,22 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class CmpARM extends MachConcreteNode implements MachNode {
+ CmpARM( Node cmp ) { super(cmp); }
+ @Override public String op() { return "cmp"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.FLAGS_MASK; }
+
+ // SUBS (shifted register)
+ @Override public void encoding( Encoding enc ) { arm.r_reg_subs(enc,this,arm.OP_CMP); }
+
+ // General form: "cmp rs1, rs2"
+ @Override public void asm(CodeGen code, SB sb) {
+ String dst = code.reg(this);
+ if( dst!="flags" ) sb.p(dst).p(" = ");
+ sb.p(code.reg(in(1))).p(", ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java
new file mode 100644
index 00000000..e82a2ee3
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java
@@ -0,0 +1,22 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+// compare instruction on float regs input(D-registers)
+public class CmpFARM extends MachConcreteNode implements MachNode{
+ CmpFARM(Node cmp) {super(cmp);}
+ @Override public String op() { return "cmpf"; }
+ @Override public RegMask regmap(int i) { return arm.DMASK; }
+ @Override public RegMask outregmap() { return arm.FLAGS_MASK; }
+
+ // FCMP
+ @Override public void encoding( Encoding enc ) { arm.f_cmp(enc,this); }
+ // General form: "cmp d0, d1"
+ @Override public void asm(CodeGen code, SB sb) {
+ String dst = code.reg(this);
+ if( dst!="flags" ) sb.p(dst).p(" = ");
+ sb.p(code.reg(in(1))).p(", ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java
new file mode 100644
index 00000000..8a4f242a
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java
@@ -0,0 +1,40 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+// Compare with immediate.
+// CMP (immediate) - SUBS
+public class CmpIARM extends MachConcreteNode implements MachNode {
+ final int _imm;
+ final String _bop;
+ CmpIARM(BoolNode bool, int imm) {
+ super(bool);
+ _inputs.pop();
+ _bop = bool.op();
+ _imm = imm;
+ }
+ CmpIARM( CmpIARM cmp ) {
+ super((Node)null);
+ addDef(null);
+ addDef(cmp.in(1));
+ _bop = cmp._bop;
+ _imm = cmp._imm;
+ }
+ @Override public String op() { return _imm==0 ? "test" : "cmp"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.FLAGS_MASK; }
+ @Override public boolean isClone() { return true; }
+ @Override public Node copy() { return new CmpIARM(this); }
+
+ @Override public void encoding( Encoding enc ) { arm.imm_inst_subs(enc,in(1),in(1), arm.OPI_CMP,_imm); }
+
+ // General form: "cmp rs1, 1"
+ @Override public void asm(CodeGen code, SB sb) {
+ String dst = code.reg(this);
+ if( dst!="flags" ) sb.p(dst).p(" = ");
+ sb.p(code.reg(in(1)));
+ if( _imm != 0 ) sb.p(", #").p(_imm);
+ }
+ }
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java
new file mode 100644
index 00000000..40bd8387
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java
@@ -0,0 +1,19 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class DivARM extends MachConcreteNode implements MachNode {
+ DivARM( Node div ) { super(div); }
+ @Override public String op() { return "div"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ // SDIV
+ @Override public void encoding( Encoding enc ) { arm.madd(enc,this,arm.OP_DIV,3); }
+ // General form: "div dst /= src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2)));
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java
new file mode 100644
index 00000000..72b76444
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java
@@ -0,0 +1,19 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class DivFARM extends MachConcreteNode implements MachNode {
+ DivFARM( Node divf) { super(divf); }
+ @Override public String op() { return "divf"; }
+ @Override public RegMask regmap(int i) { return arm.DMASK; }
+ @Override public RegMask outregmap() { return arm.DMASK; }
+
+ // FDIV (scalar)
+ @Override public void encoding( Encoding enc ) { arm.f_scalar(enc,this,arm.OPF_DIV); }
+ // General form: "VDIF = dst /= src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java
new file mode 100644
index 00000000..6de65b03
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java
@@ -0,0 +1,45 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.ConstantNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.type.TypeFloat;
+
+//FMOV (scalar, immediate)
+//Floating-point move immediate.
+public class FloatARM extends ConstantNode implements MachNode, RIPRelSize {
+ FloatARM(ConstantNode con) { super(con); }
+ @Override public String op() { return "ld8"; }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return arm.DMASK; }
+ @Override public boolean isClone() { return true; }
+ @Override public FloatARM copy() { return new FloatARM(this); }
+
+ @Override public void encoding( Encoding enc ) {
+ enc.largeConstant(this,_con,0,-1/*TODO: ARM-style ELF patching*/);
+ short dst = (short)(enc.reg(this) - arm.D_OFFSET);
+ double d = ((TypeFloat)_con).value();
+ long x = Double.doubleToRawLongBits(d);
+ enc.add4(arm.load_pc(arm.OPF_ARM, 0, dst));
+ }
+
+ // Delta is from opcode start.
+ @Override public byte encSize(int delta) { return 4; }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ short dst = (short)(enc.reg(this) - arm.D_OFFSET);
+ enc.patch4(opStart, arm.load_pc(arm.OPF_ARM, delta, dst));
+ }
+
+ // Human-readable form appended to the SB. Things like the encoding,
+ // indentation, leading address or block labels not printed here.
+ // Just something like "ld4\tR17=[R18+12] // Load array base".
+ // General form: "op\tdst=src+src"
+ @Override public void asm(CodeGen code, SB sb) {
+ _con.print(sb.p(code.reg(this)).p(" = #"));
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java
new file mode 100644
index 00000000..32777f03
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java
@@ -0,0 +1,22 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.FunNode;
+import com.seaofnodes.simple.node.MachNode;
+
+public class FunARM extends FunNode implements MachNode {
+ FunARM(FunNode fun) { super(fun); }
+ @Override public void computeFrameAdjust(CodeGen code, int maxReg) {
+ super.computeFrameAdjust(code,maxReg);
+ if( _hasCalls ) // If non-leaf, pad to 16b
+ _frameAdjust = ((_frameAdjust+8) & -16);
+ }
+ @Override public void encoding( Encoding enc ) {
+ int sz = _frameAdjust;
+ if( sz == 0 ) return; // Skip if no frame adjust
+ if( sz >= 1L<<12 ) throw Utils.TODO();
+ enc.add4(arm.imm_inst(arm.OPI_ADD, -sz&0xFFF, arm.RSP, arm.RSP));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java
new file mode 100644
index 00000000..5ecc98b6
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java
@@ -0,0 +1,25 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class I2F8ARM extends MachConcreteNode implements MachNode {
+ I2F8ARM(Node i2f8) { super(i2f8); }
+ @Override public String op() { return "cvt"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.DMASK; }
+ @Override public void encoding( Encoding enc ) {
+ // SCVTF
+ short self = (short)(enc.reg(this )-arm.D_OFFSET);
+ short reg1 = enc.reg(in(1));
+ int body = arm.float_cast(arm.OP_FLOAT_C, 1, reg1, self);
+ enc.add4(body);
+ }
+
+ // General form: "i2f8 (flt)int_value"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p("(flt)").p(code.reg(in(1)));
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java
new file mode 100644
index 00000000..0a07b07a
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java
@@ -0,0 +1,67 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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;
+import com.seaofnodes.simple.type.TypeInteger;
+
+// Integer constants
+public class IntARM extends ConstantNode implements MachNode {
+ IntARM( ConstantNode con ) { super(con); }
+
+ @Override public String op() { return "ldi"; }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+
+ @Override public boolean isClone() { return true; }
+ @Override public IntARM copy() { return new IntARM(this); }
+
+ @Override public void encoding( Encoding enc ) {
+ short self = enc.reg(this);
+ long x = _con==Type.NIL ? 0 : ((TypeInteger)_con).value();
+ int nb0 = 0;
+ int nb1 = 0;
+ // Count number of 0000 and FFFF blocks
+ for (int i=0; i<64; i+=16) {
+ int block = (int)(x >> i) & 0xFFFF;
+ if (block == 0) nb0++;
+ if (block == 0xFFFF) nb1++;
+ }
+ int pattern;
+ int op;
+ if(nb0 >= nb1) {
+ // More 0 blocks then F blocks, use movz
+ pattern = 0;
+ op = arm.OP_MOVZ;
+ } else {
+ // More F blocks then 0 blocks, use movn
+ pattern = 0xFFFF;
+ op = arm.OP_MOVN;
+ }
+ int invert = pattern;
+ for (int i=0; i<4; i++) {
+ int block = (int)x & 0xFFFF;
+ x >>= 16;
+ if (block != pattern) {
+ enc.add4(arm.mov(op, i, block ^ invert, self));
+ op = arm.OP_MOVK;
+ invert = 0;
+ }
+ }
+ if (op != arm.OP_MOVK) {
+ // All blocks are the same, special case
+ enc.add4(arm.mov(op, 0, 0, self));
+ }
+ }
+
+ // Human-readable form appended to the SB. Things like the encoding,
+ // indentation, leading address or block labels not printed here.
+ // Just something like "ld4\tR17=[R18+12] // Load array base".
+ // General form: "op\tdst=src+src"
+ @Override public void asm(CodeGen code, SB sb) {
+ String reg = code.reg(this);
+ _con.print(sb.p(reg).p(" = #"));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
new file mode 100644
index 00000000..44c13321
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
@@ -0,0 +1,39 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.LoadNode;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.type.TypeFloat;
+
+// Load memory addressing on ARM
+// Support imm, reg(direct), or reg+off(indirect) addressing
+// Base = base - base pointer, offset is added to base
+// idx = null
+// off = off - offset added to base
+
+public class LoadARM extends MemOpARM {
+ LoadARM(LoadNode ld, Node base, Node idx, int off) {
+ super(ld, base, idx, off, 0);
+ }
+ @Override public String op() { return "ld"+_sz; }
+ @Override public RegMask outregmap() { return arm.MEM_MASK; }
+
+ private static final int[] OP_LOADS = new int[]{ arm.OP_LOAD_IMM_8, arm.OP_LOAD_IMM_16, arm.OP_LOAD_IMM_32, arm.OP_LOAD_IMM_64, };
+ private int imm_op() {
+ return _declaredType == TypeFloat.F32 ? arm.OPF_LOAD_IMM_32
+ : _declaredType == TypeFloat.F64 ? arm.OPF_LOAD_IMM_64
+ : OP_LOADS[_declaredType.log_size()];
+ }
+
+ private static final int[] OP_LOAD_RS = new int[]{ arm.OP_LOAD_R_8 , arm.OP_LOAD_R_16 , arm.OP_LOAD_R_32 , arm.OP_LOAD_R_64, };
+
+ // ldr(immediate - unsigned offset) | ldr(register)
+ @Override public void encoding( Encoding enc ) {
+ ldst_encode(enc, imm_op(), OP_LOAD_RS[_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/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java
new file mode 100644
index 00000000..055d6dcc
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java
@@ -0,0 +1,19 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+// Logical Shift Left (register)
+public class LslARM extends MachConcreteNode implements MachNode {
+ LslARM(Node asr) {super(asr);}
+ @Override public String op() { return "shr"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+
+ @Override public void encoding( Encoding enc ) { arm.shift_reg(enc,this,arm.OP_LSL); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" << ").p(code.reg(in(2)));
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java
new file mode 100644
index 00000000..94d3e25b
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java
@@ -0,0 +1,33 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.node.MachConcreteNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.Node;
+
+public class LslIARM extends MachConcreteNode implements MachNode {
+ final int _imm;
+ LslIARM(Node lsl, int imm) {
+ super(lsl);
+ _inputs.pop();
+ _imm = imm;
+ }
+ @Override public String op() { return "lsli"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ short rd = enc.reg(this);
+ short rn = enc.reg(in(1));
+ assert _imm > 0;
+ // UBFM <Xd>, <Xn>, #(-<shift> MOD 64), #(63-<shift>)
+ // immr must be (-<shift> MOD 64) = 64 - shift
+ enc.add4(arm.imm_shift(arm.OPI_LSL, 64 - _imm, (64 - _imm) - 1, rn, rd));
+ }
+ // General form: "lsli rd = rs1 << imm"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" << #").p(_imm);
+ }
+} \ No newline at end of file
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java
new file mode 100644
index 00000000..a691e0fe
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java
@@ -0,0 +1,19 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+// Logical Shift Right (register)
+public class LsrARM extends MachConcreteNode implements MachNode {
+ LsrARM(Node asr) {super(asr);}
+ @Override public String op() { return "shr"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+
+ @Override public void encoding( Encoding enc ) { arm.shift_reg(enc,this,arm.OP_LSR); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >>> ").p(code.reg(in(2)));
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java
new file mode 100644
index 00000000..094cd30f
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java
@@ -0,0 +1,29 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+// logical right shift immediate
+public class LsrIARM extends MachConcreteNode implements MachNode {
+ final int _imm;
+ LsrIARM(Node lsr, int imm) {
+ super(lsr);
+ _inputs.pop();
+ _imm = imm;
+ }
+ @Override public String op() { return "lsri"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ short rd = enc.reg(this);
+ short rn = enc.reg(in(1));
+ assert _imm > 0;
+ enc.add4(arm.imm_shift(arm.OPI_LSR,_imm, 0b111111, rn,rd));
+ }
+ // General form: "lsri rd = rs1 >>> imm"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >>> #").p(_imm);
+ }
+} \ No newline at end of file
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java
new file mode 100644
index 00000000..6d8344a2
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java
@@ -0,0 +1,69 @@
+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.type.*;
+
+import java.lang.StringBuilder;
+import java.util.BitSet;
+
+
+public abstract class MemOpARM extends MemOpNode implements MachNode {
+ final int _off; // Limit 9 bits sized, or (13 bits<<logsize) unsigned
+ final int _imm; // Limit ? bits
+ final char _sz = (char)('0'+(1<<_declaredType.log_size()));
+ MemOpARM(MemOpNode mop, Node ptr, Node idx, int off, int imm) {
+ super(mop,mop);
+ assert ptr._type instanceof TypeMemPtr && !(ptr instanceof AddNode);
+ assert ptr() == ptr;
+ _inputs.setX(2, ptr);
+ _inputs.setX(3, idx);
+ _off = off;
+ _imm = imm;
+ }
+
+ // Store-based flavors have a value edge
+ MemOpARM( MemOpNode mop, Node ptr, Node idx, int off, int imm, Node val ) {
+ this(mop,ptr,idx,off,imm);
+ _inputs.setX(4,val);
+ }
+
+ @Override public String label() { return op();}
+ Node val() { return in(4); } // Only for stores
+
+ @Override public StringBuilder _printMach(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); }
+
+ @Override public Type compute() { throw Utils.TODO(); }
+ @Override public Node idealize() { throw Utils.TODO(); }
+
+ int size() { return 1<<_declaredType.log_size(); }
+
+ // Wider mask to store both GPRs and FPRs
+ @Override public RegMask regmap(int i) {
+ // 0 - ctrl
+ if( i==1 ) return null; // memory
+ if( i==2 ) return arm.RMASK; // ptr/base
+ if( i==3 ) return arm.RMASK; // off/index
+ if( i==4 ) return arm.RMASK; // value
+ return null; // Anti-dependence
+ }
+
+ // Shared encoding for loads and stores(int and float)
+ public void ldst_encode( Encoding enc, int opcode_imm, int opcode_reg, Node xval, int size) {
+ short ptr = enc.reg(ptr());
+ short off = enc.reg(off());
+ short val = enc.reg(xval);
+ int body = off() == null
+ ? arm.load_str_imm(opcode_imm, _off, ptr, val, size)
+ : arm.indr_adr(opcode_reg, off, arm.STORE_LOAD_OPTION.SXTX, 0, ptr, val);
+ enc.add4(body);
+ }
+
+ SB asm_address(CodeGen code, SB sb) {
+ sb.p("[").p(code.reg(ptr())).p("+");
+ if( off() != null ) sb.p(code.reg(off()));
+ else sb.p(_off);
+ return sb.p("]");
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java
new file mode 100644
index 00000000..3514db9b
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java
@@ -0,0 +1,19 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+// mulh signed multiply instruction(no-imm form)
+public class MulARM extends MachConcreteNode implements MachNode {
+ MulARM(Node mul) {super(mul);}
+ @Override public String op() { return "mul"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+
+ @Override public void encoding( Encoding enc ) { arm.madd(enc,this,arm.OP_MUL,31); }
+ // General form: "rd = rs1 * rs2"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java
new file mode 100644
index 00000000..89dba172
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java
@@ -0,0 +1,18 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class MulFARM extends MachConcreteNode implements MachNode{
+ MulFARM(Node mulf) {super(mulf);}
+ @Override public String op() { return "mulf"; }
+ @Override public RegMask regmap(int i) { return arm.DMASK; }
+ @Override public RegMask outregmap() { return arm.DMASK; }
+ @Override public void encoding( Encoding enc ) { arm.f_scalar(enc,this,arm.OPF_MUL); }
+ // Default on double precision for now(64 bits)
+ // General form: "VMUL.f32 rd = src1 * src2
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java
new file mode 100644
index 00000000..b7c3c803
--- /dev/null
+++ b/chapter21/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/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java
new file mode 100644
index 00000000..cb51eea2
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java
@@ -0,0 +1,36 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class NewARM extends NewNode implements MachNode, RIPRelSize {
+ // A pre-zeroed chunk of memory.
+ NewARM(NewNode nnn) { super(nnn); }
+ @Override public void encoding( Encoding enc ) {
+ // BL(branch with link)
+ enc.external(this,"calloc").
+ add4(arm.mov(arm.OP_MOVZ, 0, 1, 0)). // movz x0,#1
+ add4(arm.b(arm.OP_CALL, 0));
+ }
+
+ // Patch is for running "new" in a JIT.
+ // Delta is from opcode start
+ @Override public byte encSize(int delta) { return 4*2; }
+
+ // Patch is for running "new" in a JIT.
+ // Delta is from opcode start
+ @Override public void patch(Encoding enc, int opStart, int opLen, int delta ) {
+ // Negative patches are JIT emulator targets.
+ if( opStart+delta < 0 ) {
+ enc.patch4(opStart+4, arm.b_calloc(arm.OP_CALL, delta-4));
+ } else {
+ throw Utils.TODO();
+ }
+ }
+ // General form: "alloc #bytes PC"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p("ldi x0=#1\n");
+ sb.p("call #calloc");
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java
new file mode 100644
index 00000000..5a8d882d
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java
@@ -0,0 +1,28 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+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.WMASK; }
+ @Override public RegMask killmap() { return arm.FLAGS_MASK; }
+ @Override public void encoding( Encoding enc ) {
+ // subs xzr, rs, #0
+ // cset rd, eq // Set rd to 1 if rs == 0 (equal), else 0
+ // subtracting zero from rs will just yield rs, it sets the zero flag and then it's used in cset
+ short self = enc.reg(this );
+ short reg1 = enc.reg(in(1));
+ int subs = arm.imm_inst(arm.OP_SUBS, 0, reg1, self);
+ enc.add4(subs);
+ int cset = arm.cond_set(arm.OP_CSET, 31, arm.COND.EQ, 63, reg1);
+ enc.add4(cset);
+ }
+
+ @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java
new file mode 100644
index 00000000..b5cfa8cf
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java
@@ -0,0 +1,20 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.SB;
+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); }
+ @Override public String op() { return "or"; }
+ @Override public String glabel() { return "|"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,arm.OP_OR); }
+ // General form: #rd = rs1 & rs2
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" | ").p(code.reg(in(2)));
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java
new file mode 100644
index 00000000..2a2d199a
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java
@@ -0,0 +1,24 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class OrIARM extends MachConcreteNode implements MachNode {
+ final int _imm;
+ OrIARM(Node or, int imm) {
+ super(or);
+ _inputs.pop();
+ _imm = imm;
+ }
+ @Override public String op() { return "ori"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ arm.imm_inst_n(enc, this, in(1), arm.OPI_OR, _imm);
+ }
+ // General form: "ori rd = rs1 | imm"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" | #").p(_imm);
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java
new file mode 100644
index 00000000..4d0f95ab
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.ParmNode;
+
+public class ParmARM extends ParmNode implements MachNode {
+ final RegMask _rmask;
+ ParmARM(ParmNode parm) {
+ super(parm);
+ _rmask = arm.callInMask(fun().sig(),_idx);
+ }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return _rmask; }
+ @Override public void encoding( Encoding enc ) { }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java
new file mode 100644
index 00000000..4b7d6f13
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java
@@ -0,0 +1,15 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.ProjNode;
+
+public class ProjARM extends ProjNode implements MachNode {
+ ProjARM( ProjNode p ) { super(p); }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() {
+ return ((MachNode)in(0)).outregmap(_idx);
+ }
+ @Override public void encoding( Encoding enc ) { }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java
new file mode 100644
index 00000000..2136ebf5
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java
@@ -0,0 +1,15 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class RetARM extends ReturnNode implements MachNode {
+ RetARM(ReturnNode ret, FunNode fun) { super(ret, fun); fun.setRet(this); }
+ @Override public void encoding( Encoding enc ) {
+ int frameAdjust = ((FunARM)fun())._frameAdjust;
+ if( frameAdjust > 0 )
+ enc.add4(arm.imm_inst(arm.OPI_ADD, (frameAdjust*-8)&0xFFF, arm.RSP, arm.RSP));
+ enc.add4(arm.ret(arm.OP_RET));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java
new file mode 100644
index 00000000..246e482a
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+// conditional set?
+// e.g CSET, stores flag info into GPRS after compare
+public class SetARM extends MachConcreteNode implements MachNode {
+ final String _bop; // One of <,<=,==
+ SetARM(Node cmp, String bop) {
+ super(cmp);
+ _inputs.setLen(1); // Pop the cmp inputs
+ // Replace with the matched cmp
+ _inputs.push(cmp);
+ _bop = bop;
+ }
+ @Override public String op() { return "set"+_bop; }
+ @Override public RegMask regmap(int i) { assert i==1; return arm.FLAGS_MASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+
+ @Override public void encoding( Encoding enc ) {
+ int body = arm.cset(arm.OP_CSET,0b11111, arm.make_condition(_bop), 0b011111, enc.reg(this));
+ enc.add4(body);
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this));
+ String src = code.reg(in(1));
+ if( src!="flags" ) sb.p(" = ").p(src);
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java
new file mode 100644
index 00000000..62bb75aa
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java
@@ -0,0 +1,66 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.FunNode;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.node.SplitNode;
+import com.seaofnodes.simple.node.cpus.riscv.riscv;
+
+public class SplitARM extends SplitNode {
+ SplitARM(String kind, byte round) { super(kind,round,new Node[2]);}
+ @Override public RegMask regmap(int i) { return arm.SPLIT_MASK; }
+ @Override public RegMask outregmap() { return arm.SPLIT_MASK; }
+
+ @Override public void encoding( Encoding enc ) {
+ short dst = enc.reg(this );
+ short src = enc.reg(in(1));
+
+ boolean dstX = dst >= arm.D_OFFSET;
+ boolean srcX = src >= arm.D_OFFSET;
+
+ if (dst >= arm.MAX_REG) {
+ // store to sp
+ if(src >= arm.MAX_REG) {
+ throw Utils.TODO();
+ }
+ int off = enc._fun.computeStackOffset(enc._code,dst);
+ if( srcX ) src -= arm.D_OFFSET;
+ enc.add4(arm.load_str_imm(arm.OP_STORE_IMM_64, off, arm.RSP, src, 8));
+ return;
+ }
+
+ if(src >= arm.MAX_REG) {
+ // Load from SP
+ int off = enc._fun.computeStackOffset(enc._code,src);
+ if( dstX ) dst -= arm.D_OFFSET;
+ enc.add4(arm.load_str_imm(arm.OP_LOAD_IMM_64, off, arm.RSP, dst, 8));
+ return;
+ }
+
+ // pick opcode based on regs
+ if(!dstX && !srcX) {
+ // GPR->GPR
+ enc.add4(arm.mov_reg(arm.OP_MOV, src, dst));
+ } else if(dstX && srcX) {
+ // FPR->FPR
+ // fmov reg
+ enc.add4(arm.f_mov_reg(arm.OP_FMOV_REG, src - arm.D_OFFSET,dst - arm.D_OFFSET));
+ } else if(!srcX && dstX) {
+ // GPR->FPR
+ // FMOV(general) 64 bits to DOUBLE-PRECISION
+ enc.add4(arm.f_mov_general(arm.OP_FMOV, 0b01, 0, 0b111, src, dst - arm.D_OFFSET));
+ } else if( srcX && !dstX) {
+ //FPF->GPR
+ // FMOV(general) DOUBLE-PRECISION to 64 bits
+ enc.add4(arm.f_mov_general(arm.OP_FMOV, 0b01, 0, 0b110, src - arm.D_OFFSET, dst));
+ }
+ }
+
+ // 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/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java
new file mode 100644
index 00000000..688d8731
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java
@@ -0,0 +1,43 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.StoreNode;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.type.TypeFloat;
+
+// Store memory addressing on ARM
+// Support imm, reg(direct), or reg+off(indirect) addressing
+// base - base pointer, offset is added to base
+// null - index never allowed (no [reg+reg] mode)
+// off - offset added to base
+// imm - immediate value to store, only if val is null
+// val - value to store or null
+
+//e.g s.cs[0] = 67; // C
+// base = s.cs, off = 4, imm = 67, val = null
+public class StoreARM extends MemOpARM {
+ StoreARM(StoreNode st, Node base, Node idx, int off, Node val) {
+ super(st, base, idx, off, 0, val);
+ }
+ @Override public String op() { return "st"+_sz; }
+ @Override public RegMask outregmap() { return null; }
+
+ private static final int[] OP_STORES = new int[]{ arm.OP_STORE_IMM_8, arm.OP_STORE_IMM_16, arm.OP_STORE_IMM_32, arm.OP_STORE_IMM_64, };
+ private int imm_op() {
+ return _declaredType == TypeFloat.F32 ? arm.OPF_STORE_IMM_32
+ : _declaredType == TypeFloat.F64 ? arm.OPF_STORE_IMM_64
+ : OP_STORES[_declaredType.log_size()];
+ }
+
+ private static final int[] OP_STORE_RS = new int[]{ arm.OP_STORE_R_8, arm.OP_STORE_R_16, arm.OP_STORE_R_32, arm.OP_STORE_R_64, };
+
+ @Override public void encoding( Encoding enc ) {
+ ldst_encode(enc, imm_op(), OP_STORE_RS[_declaredType.log_size()], val(), size());
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ asm_address(code,sb).p(",");
+ if( val()==null ) sb.p("#").p(_imm);
+ else sb.p(code.reg(val()));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java
new file mode 100644
index 00000000..552b46df
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java
@@ -0,0 +1,19 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class SubARM extends MachConcreteNode implements MachNode {
+ SubARM(Node sub) { super(sub); }
+ @Override public String op() { return "sub"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+
+ @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,arm.OP_SUB); }
+
+ // General form: "sub # rd = rs1 - rs2"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java
new file mode 100644
index 00000000..0d8d3c10
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java
@@ -0,0 +1,18 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class SubFARM extends MachConcreteNode implements MachNode {
+ SubFARM(Node subf) {super(subf);}
+ @Override public String op() { return "subf"; }
+ @Override public RegMask regmap(int i) { return arm.DMASK; }
+ @Override public RegMask outregmap() { return arm.DMASK; }
+ @Override public void encoding( Encoding enc ) { arm.f_scalar(enc,this,arm.OPF_SUB); }
+ // Default on double precision for now(64 bits)
+ // General form: "vsub.f32 rd = src1 + src2
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java
new file mode 100644
index 00000000..b882c5a4
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java
@@ -0,0 +1,29 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.MachConcreteNode;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+
+public class SubIARM extends MachConcreteNode implements MachNode {
+ final int _imm;
+ SubIARM(Node sub, int imm) {
+ super(sub);
+ _inputs.pop();
+ _imm = imm;
+ }
+ @Override public String op() { return _imm == -1 ? "dec" : "subi"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+
+ @Override public void encoding( Encoding enc ) {
+ arm.imm_inst(enc,this, in(1), arm.OPI_SUB,_imm);
+ }
+
+ // General form: "subi rd = rs1 - imm"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - #").p(_imm);
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java
new file mode 100644
index 00000000..16b56553
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java
@@ -0,0 +1,53 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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.node.cpus.riscv.riscv;
+import com.seaofnodes.simple.type.TypeFunPtr;
+
+public class TFPARM extends ConstantNode implements MachNode, RIPRelSize {
+ TFPARM( ConstantNode con ) { super(con); }
+ @Override public String op() { return "ldx"; }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public boolean isClone() { return true; }
+ @Override public TFPARM copy() { return new TFPARM(this); }
+ @Override public void encoding( Encoding enc ) {
+ enc.relo(this);
+ short self = enc.reg(this);
+ // adrp x0, 0
+ int adrp = arm.adrp(1,0, arm.OP_ADRP, 0,self);
+ // add x0, x0, 0
+ enc.add4(adrp);
+ arm.imm_inst(enc,arm.OPI_ADD,0, 0);
+ }
+
+ @Override public byte encSize(int delta) {
+ if( -(1L<<12) <= delta && delta < (1L<<12) ) return 4;
+ throw Utils.TODO();
+ }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ short rpc = enc.reg(this);
+ if(opLen == 8 ) {
+ // opstart of add
+ int next = opStart + opLen;
+ int adrp_delta = delta >> 12;
+ // patch upper 20 bits via adrp
+ enc.patch4(opStart, arm.adrp(1, adrp_delta & 0b11, 0b10000, adrp_delta >> 2, rpc));
+ // low 12 bits via add
+ enc.patch4(next, arm.imm_inst_l(arm.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/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java
new file mode 100644
index 00000000..a69c3c7c
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java
@@ -0,0 +1,48 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.Type;
+import java.util.BitSet;
+
+// unconditional jump
+public class UJmpARM extends CFGNode implements MachNode, RIPRelSize {
+ UJmpARM() { }
+ @Override public String op() { return "jmp"; }
+ @Override public String label() { return op(); }
+ @Override public StringBuilder _print1( StringBuilder sb, BitSet visited ) {
+ return sb.append("jmp ");
+ }
+ @Override public RegMask regmap(int i) {return null; }
+ @Override public RegMask outregmap() { return null; }
+ @Override public Type compute() { throw Utils.TODO(); }
+ @Override public Node idealize() { throw Utils.TODO(); }
+ @Override public void encoding( Encoding enc ) {
+ enc.jump(this,uctrl());
+ int body = arm.b(arm.OP_UJMP, 0);
+ enc.add4(body);
+ }
+
+ // Delta is from opcode start
+ @Override public byte encSize(int delta) {
+ if( -(1<<26) <= delta && delta < (1<<26) ) return 4;
+ // 2 word encoding needs a tmp register, must teach RA
+ throw Utils.TODO();
+ }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ if( opLen==4 ) {
+ enc.patch4(opStart,arm.b(arm.OP_UJMP, delta));
+ } else {
+ throw Utils.TODO();
+ }
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ CFGNode target = uctrl();
+ //assert target.nOuts() > 1; // Should optimize jmp to empty targets
+ sb.p(label(target));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java
new file mode 100644
index 00000000..ec86cfa5
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java
@@ -0,0 +1,19 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class XorARM extends MachConcreteNode implements MachNode{
+ XorARM(Node xor) {super(xor);}
+ @Override public String op() { return "xor"; }
+ @Override public String glabel() { return "^"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,arm.OP_XOR); }
+ // General form: "rd = x1 ^ x2"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ^ ").p(code.reg(in(2)));
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java
new file mode 100644
index 00000000..e2582d22
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java
@@ -0,0 +1,27 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.node.MachConcreteNode;
+import com.seaofnodes.simple.node.MachNode;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class XorIARM extends MachConcreteNode implements MachNode {
+ final int _imm;
+ XorIARM(Node xor, int imm) {
+ super(xor);
+ _inputs.pop();
+ _imm = imm;
+ }
+
+ @Override public String op() { return "xori"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+
+ // General form: "xori rd = rs1 ^ imm"
+ @Override public void encoding( Encoding enc ) { arm.imm_inst_n(enc,this, in(1), arm.OPI_XOR,_imm); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ^ #").p(_imm);
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
new file mode 100644
index 00000000..104929f4
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
@@ -0,0 +1,815 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.Utils;
+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);
+ }
+
+ // ARM64
+ @Override public String name() { return "arm"; }
+
+ // GPR(S)
+ public static final int X0 = 0, X1 = 1, X2 = 2, X3 = 3, X4 = 4, X5 = 5, X6 = 6, X7 = 7;
+ public static final int X8 = 8, X9 = 9, X10 = 10, X11 = 11, X12 = 12, X13 = 13, X14 = 14, X15 = 15;
+ public static final int X16 = 16, X17 = 17, X18 = 18, X19 = 19, X20 = 20, X21 = 21, X22 = 22, X23 = 23;
+ public static final int X24 = 24, X25 = 25, X26 = 26, X27 = 27, X28 = 28, X29 = 29, X30 = 30, RSP = 31;
+
+ // Floating point registers
+ public static final int D0 = 32, D1 = 33, D2 = 34, D3 = 35, D4 = 36, D5 = 37, D6 = 38, D7 = 39;
+ public static final int D8 = 40, D9 = 41, D10 = 42, D11 = 43, D12 = 44, D13 = 45, D14 = 46, D15 = 47;
+ public static final int D16 = 48, D17 = 49, D18 = 50, D19 = 51, D20 = 52, D21 = 53, D22 = 54, D23 = 55;
+ public static final int D24 = 56, D25 = 57, D26 = 58, D27 = 59, D28 = 60, D29 = 61, D30 = 62, D31 = 63;
+
+ static final int FLAGS = 64;
+ static final int MAX_REG = 65;
+ public static final int D_OFFSET = 32;
+
+ static final String[] REGS = new String[] {
+ "X0", "X1", "X2", "X3", "X4", "X5", "X6", "X7",
+ "X8", "X9", "X10", "X11", "X12", "X13", "X14", "X15",
+ "X16", "X17", "X18", "X19", "X20", "X21", "X22", "X23",
+ "X24", "X25", "X26", "X27", "X28", "X29", "RPC", "RSP",
+ "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7",
+ "D8", "D9", "D10", "D11", "D12", "D13", "D14", "D15",
+ "D16", "D17", "D18", "D19", "D20", "D21", "D22", "D23",
+ "D24", "D25", "D26", "D27", "D28", "D29", "D30", "D31",
+ "flags"
+ };
+ @Override public String[] regs() { return REGS; }
+
+ // from (x0-x30)
+ // General purpose register mask: pointers and ints, not floats
+ static final long RD_BITS = 0xFFFFFFFFL;
+ static final RegMask RMASK = new RegMask(RD_BITS);
+ static final long WR_BITS = 0x7FFFFFFFL; // All the GPRs, not RSP
+ static final RegMask WMASK = new RegMask(WR_BITS);
+
+ // Float mask from(d0–d31)
+ static final long FP_BITS = 0xFFFFFFFFL<<D0;
+ static final RegMask DMASK = new RegMask(FP_BITS);
+
+ // Load/store mask; both GPR and FPR
+ static final RegMask MEM_MASK = new RegMask(WR_BITS | FP_BITS);
+
+ static final RegMask SPLIT_MASK = new RegMask(WR_BITS | FP_BITS, -2L/*skip flags*/);
+
+ static final RegMask FLAGS_MASK = new RegMask(FLAGS);
+
+ // Arguments masks
+ static final RegMask X0_MASK = new RegMask(X0);
+ static final RegMask X1_MASK = new RegMask(X1);
+ static final RegMask X2_MASK = new RegMask(X2);
+ static final RegMask X3_MASK = new RegMask(X3);
+ static final RegMask X4_MASK = new RegMask(X4);
+ static final RegMask X5_MASK = new RegMask(X5);
+ static final RegMask X6_MASK = new RegMask(X6);
+ static final RegMask X7_MASK = new RegMask(X7);
+
+ // Arguments(float) masks
+ static final RegMask D0_MASK = new RegMask(D0);
+ static final RegMask D1_MASK = new RegMask(D1);
+ static final RegMask D2_MASK = new RegMask(D2);
+ static final RegMask D3_MASK = new RegMask(D3);
+ static final RegMask D4_MASK = new RegMask(D4);
+ static final RegMask D5_MASK = new RegMask(D5);
+ static final RegMask D6_MASK = new RegMask(D6);
+ static final RegMask D7_MASK = new RegMask(D7);
+
+ // major opcode: OP
+ public static int OP_ADD = 0b10_001_011;
+ public static int OPF_ADD = 0b00_011_110;
+ public static int OPF_OP_ADD = 0b00_101_0;
+ public static int OPI_ADD = 0b10_010_00100;
+
+ public static int OPI_SUB = 0b11_010_00100;
+ public static int OP_UJMP = 0b000101;
+
+ public static int OP_ADRP = 0b10000;
+
+ public static int OP_ASR = 0b1010;
+ public static int OPI_ASR = 0b10_0100_1101;
+
+ public static int OP_LSL = 0b1000;
+ public static int OPI_LSL =0b1101001101;
+
+ public static int OP_LSR = 0b1001;
+ public static int OPI_LSR = 0b1101001101;
+
+ public static int OP_BRANCH = 0b01_0101_00;
+ public static int OP_CALL = 0b10_010_1;
+
+ public static int OP_CALLRARM = 0b1101011000111111000000;
+
+ public static int OP_SUBS = 0b1111000100;
+ public static int OP_CSET = 0b10011010100;
+ public static int OP_CMP = 0b11101011;
+ public static int OPI_CMP = 0b1111000100;
+
+ public static int OP_DIV = 0b10011010110;
+ public static int OPF_DIV = 0b000110;
+
+ public static int OP_MUL = 0b10011011000;
+ public static int OPF_MUL = 0b10;
+
+ public static int OPF_ARM = 0b01011100;
+
+ public static int OP_FLOAT_C = 0b10011110;
+
+ public static int OP_XOR = 0b11001010;
+ public static int OPI_XOR = 0b110100100;
+
+ public static int OP_AND = 0b10_0010_10;
+ public static int OPI_AND = 0b10_0100_100;
+
+ public static int OP_OR = 0b10101010;
+ public static int OPI_OR = 0b101100100;
+
+ public static int OP_SUB = 0b11001011;
+ public static int OPF_SUB = 0b1110;
+
+ public static int OP_MOVK = 0b111100101;
+ public static int OP_MOVN = 0b100100101;
+ public static int OP_MOVZ = 0b110100101;
+
+ public static int OP_RET = 0b1101011001011111000000;
+ // Store/load opcodes
+ public static int OP_LOAD_R_64 = 0b11111000011;
+ public static int OP_LOAD_R_32 = 0b10111000011;
+ public static int OP_LOAD_R_16 = 0b01111000010;
+ public static int OP_LOAD_R_8 = 0b00111000011;
+
+ public static int OP_LOAD_IMM_64 = 0b1111100101;
+ public static int OP_LOAD_IMM_32 = 0b1011100101;
+ public static int OP_LOAD_IMM_16 = 0b0111100101;
+ public static int OP_LOAD_IMM_8 = 0b0011100101;
+
+ // load
+ public static int OPF_LOAD_R_64 = 0b11111100011;
+ public static int OPF_LOAD_R_32 = 0b10111100011;
+
+ public static int OPF_LOAD_IMM_64 = 0b1111110101;
+ public static int OPF_LOAD_IMM_32 = 0b1011110101;
+
+ public static int OP_STORE_R_64 = 0b11111000001;
+ public static int OP_STORE_R_32 = 0b10111000001;
+ public static int OP_STORE_R_16 = 0b01111000001;
+ public static int OP_STORE_R_8 = 0b00111000001;
+
+ public static int OP_STORE_IMM_64 = 0b1111100100;
+ public static int OP_STORE_IMM_32 = 0b1011100100;
+ public static int OP_STORE_IMM_16 = 0b0111100100;
+ public static int OP_STORE_IMM_8 = 0b0011100100;
+
+ public static int OPF_STORE_R_64 = 0b11111100001;
+ public static int OPF_STORE_R_32 = 0b10111100001;
+
+ public static int OPF_STORE_IMM_32 = 0b1111110100;
+ public static int OPF_STORE_IMM_64 = 0b1111110100;
+
+
+ public static int OP_FMOV = 0b10011110;
+ public static int OP_FMOV_REG = 0b00011110;
+ // https://docsmirror.github.io/A64/2023-06/mov_orr_log_shift.html
+ public static int OP_MOV = 0b10101010000;
+ // Calling convention; returns a machine-specific register
+ // for incoming argument idx.
+ // index 0 for control, 1 for memory, real args start at index 2
+ static final RegMask[] CALLINMASK = new RegMask[] {
+ X0_MASK,
+ X1_MASK,
+ X2_MASK,
+ X3_MASK,
+ X4_MASK,
+ X5_MASK,
+ X6_MASK,
+ X7_MASK,
+ };
+ static final RegMask[] XMMS = new RegMask[] {
+ D0_MASK,
+ D1_MASK,
+ D2_MASK,
+ D3_MASK,
+ D4_MASK,
+ D5_MASK,
+ D6_MASK,
+ D7_MASK,
+ };
+
+ // ARM ENCODING
+ public enum OPTION {
+ UXTB,
+ UXTH,
+ UXTX,
+ SXTB,
+ SXTH,
+ SXTW,
+ SXTX,
+ }
+
+ static public int cset(int opcode, int rm, COND cond, int rn, int rd) {
+ assert 0 <= rm && rm < 32;
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rd && rd < 32;
+ return (opcode << 21) | (rm << 15) | (cond.ordinal() << 12) | (rn << 5) | rd;
+ }
+
+ static public int cset(int opcode, COND cond, int rn, int rd) {
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rd && rd < 32;
+ return (opcode << 16) | (cond.ordinal() << 12) | (rn << 5) | rd;
+ }
+
+ public enum STORE_LOAD_OPTION {
+ UXTW, // base+ u32 index [<< logsize]
+ UXTX, // base+ u64 index [<< logsize]
+ SXTW, // base+ s32 index [<< logsize]
+ SXTX, // base+ s64 index [<< logsize]
+ }
+
+ public enum COND {
+ EQ,
+ NE,
+ CS,
+ CC,
+ MI,
+ PL,
+ VS,
+ VC,
+ HI,
+ LS,
+ GE,
+ LT,
+ GT,
+ LE,
+ AL,
+ NV
+ }
+
+ // True if signed 9-bit immediate
+ private static boolean imm9(TypeInteger ti) {
+ // 55 = 64-9
+ return ti.isConstant() && ((ti.value()<<55)>>55) == ti.value();
+ }
+ // True if signed 12-bit immediate
+ private static boolean imm12(TypeInteger ti) {
+ // 52 = 64-12
+ return ti.isConstant() && ((ti.value()<<52)>>52) == ti.value();
+ }
+
+
+ // Can we encode this in ARM's 12-bit LOGICAL immediate form?
+ // Some combination of shifted bit-masks.
+ private static int imm12Logical(TypeInteger ti) {
+ if( !ti.isConstant() ) return -1;
+ if( !ti.isConstant() ) return -1;
+ long val = ti.value();
+ if (val == 0 || val == -1) return -1; // Special cases are not allowed
+ int immr = 0;
+ // Rotate until we have 0[...]1
+
+ // Rotate until:
+ // The number is negative (MSB is 1)
+ // Or the LSB is not 1
+ while (val < 0 || (val & 1)==0) {
+ // circular rotation
+ val = (val >>> 63) | (val << 1);
+ immr++;
+ }
+ // Start by assuming that val might be made of two 32-bit chunks that repeat.
+ int size = 32;
+ long pattern = val;
+ // Is upper half of pattern the same as the lower?
+ while ((pattern & ((1L<<size)-1)) == (pattern >> size)) {
+ // Then only take one half
+ pattern >>= size;
+ size >>= 1;
+ }
+ size <<= 1;
+ int imms = Long.bitCount(pattern);
+ // Pattern should now be zeros followed by ones 0000011111
+ if (pattern != (1L<<imms)-1) return -1;
+ imms--;
+ if (size == 64) return 0x1000 | immr << 6 | imms;
+ return (32-size)<<1 | immr << 6 | imms;
+ }
+
+ // sh is encoded in opcode
+ public static int imm_inst(int opcode, int imm12, int rn, int rd) {
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rd && rd < 32;
+ assert opcode >=0 && imm12 >= 0; // Caller zeros high order bits
+ return (opcode << 22) | (imm12 << 10) | (rn << 5) | rd;
+ }
+
+ public static int imm_shift(int opcode, int imm, int imms, int rn, int rd) {
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rd && rd < 32;
+ return (opcode << 22) | (1 << 22) | (imm << 16) | (imms << 10) | (rn << 5) | rd;
+ }
+
+ public static void imm_inst(Encoding enc, Node n,Node n2, int opcode, int imm12) {
+ short self = enc.reg(n);
+ short reg1 = enc.reg(n2);
+
+ int body = imm_inst(opcode, imm12&0xFFF, reg1, self);
+ enc.add4(body);
+ }
+
+ public static void imm_inst_subs(Encoding enc, Node n,Node n2, int opcode, int imm12) {
+ short reg1 = enc.reg(n2);
+ // 31 = 11111
+ int body = imm_inst(opcode, imm12&0xFFF, reg1, 31);
+ enc.add4(body);
+ }
+
+ // for cases where rs1 and dst are the same, eg add x0, x0, 1
+ public static void imm_inst(Encoding enc, int opcode, int imm12, int self) {
+ int body = imm_inst(opcode, imm12&0xFFF, self, self);
+ enc.add4(body);
+ }
+
+ public static void imm_inst_n(Encoding enc, Node n, Node n2, int opcode, int imm13) {
+ short self = enc.reg(n);
+ short reg1 = enc.reg(n2);
+ int body = imm_inst_n(opcode, imm13, reg1, self);
+ enc.add4(body);
+ }
+
+ // nth bit comes from immediate and not opcode
+ public static int imm_inst_n(int opcode, int imm13, int rn, int rd) {
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rd && rd < 32;
+ assert 0 <= imm13 && imm13 <= 0x1FFF;
+ return (opcode << 23) | (imm13 << 10) | (rn << 5) | rd;
+ }
+
+ public static int imm_inst_l(int opcode, int imm12, int self) {
+ int body = imm_inst(opcode, imm12&0xFFF, self, self);
+ return body;
+ }
+
+ // for cases where rs1 and dst are the same, eg add x0, x0, 1
+ public static int imm_inst_l(Encoding enc, Node n, int opcode, int imm12) {
+ short self = enc.reg(n);
+ short reg1 = enc.reg(n.in(1));
+ int body = imm_inst(opcode, imm12&0xFFF, reg1, self);
+ return body;
+ }
+
+ // for normal add, reg1, reg2 cases (reg-to-reg)
+ // using shifted-reg form
+ public static int r_reg(int opcode, int shift, int rm, int imm6, int rn, int rd) {
+ assert 0 <= rm && rm < 32;
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rd && rd < 32;
+ return (opcode << 24) | (shift << 21) | (rm << 16) | (imm6 << 10) | (rn << 5) | rd;
+ }
+ public static void r_reg(Encoding enc, Node n, int opcode) {
+ short self = enc.reg(n);
+ short reg1 = enc.reg(n.in(1));
+ short reg2 = enc.reg(n.in(2));
+ int body = r_reg(opcode, 0, reg2, 0, reg1, self >= 32 ? reg1: self);
+ enc.add4(body);
+ }
+
+ public static void r_reg_subs(Encoding enc, Node n, int opcode) {
+ short reg1 = enc.reg(n.in(1));
+ short reg2 = enc.reg(n.in(2));
+ // 31 = 11111
+ int body = r_reg(opcode, 0, reg2, 0, reg1, 31);
+ enc.add4(body);
+ }
+
+ public static int shift_reg(int opcode, int rm, int op2, int rn, int rd) {
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rm && rm < 32;
+ assert 0 <= rd && rd < 32;
+ return (opcode << 21) | (rm << 16) | (op2 << 10) | (rn << 5) | rd;
+ }
+ public static void shift_reg(Encoding enc, Node n, int op2) {
+ short self = enc.reg(n);
+ short reg1 = enc.reg(n.in(1));
+ short reg2 = enc.reg(n.in(2));
+ int body = shift_reg(0b10011010110, reg2, op2, reg1, self);
+ enc.add4(body);
+ }
+
+ // MUL can be considered an alias for MADD with the third operand Ra being set to 0
+ public static int madd(int opcode, int rm, int ra, int rn, int rd) {
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rd && rd < 32;
+ assert 0 <= rm && rm < 32;
+ return (opcode << 21) | (rm << 16) | (ra << 10) | (rn << 5) | rd;
+ }
+ public static void madd(Encoding enc, Node n, int opcode, int ra) {
+ short self = enc.reg(n);
+ short reg1 = enc.reg(n.in(1));
+ short reg2 = enc.reg(n.in(2));
+ int body = madd(opcode, reg2, ra, reg1, self);
+ enc.add4(body);
+ }
+
+ // encodes movk, movn, and movz
+ public static int mov(int opcode, int shift, int imm16, int rd) {
+ assert 0 <= rd && rd < 32;
+ return (opcode << 23) | (shift << 21) | (imm16 << 5) | rd;
+ }
+
+ public static int mov_reg(int opcode, int src, int rd) {
+ assert 0 <= rd && rd < 32;
+ return (opcode << 21) | (src << 16) | 0b11111 << 5 | rd;
+ }
+
+ public static int ret(int opcode) {
+ return (opcode << 10);
+ }
+
+ // FMOV (scalar, immediate)
+ public static int f_scalar(int opcode, int ftype, int rm, int op, int rn, int rd) {
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rd && rd < 32;
+ assert 0 <= rm && rm < 32;
+ return (opcode << 24) | (ftype << 22) | (1 << 21) | (rm << 16) | (op << 10) | (rn << 5) | rd;
+ }
+
+ public static int f_mov_reg(int opcode, int rn, int rd) {
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rd && rd < 32;
+ return (opcode << 24) | (0b01100000010000 << 10) | (rn << 5) | rd;
+ }
+ public static int f_mov_general(int opcode, int ftype, int rmode, int opcode1, int rn, int rd) {
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rd && rd < 32;
+ return (opcode << 24) |(ftype << 22) | (1 << 21) | (rmode << 19) | (opcode1 << 16) | (rn << 5) | rd;
+ }
+ public static void f_scalar(Encoding enc, Node n, int op ) {
+ short self = (short)(enc.reg(n) -D_OFFSET);
+ short reg1 = (short)(enc.reg(n.in(1))-D_OFFSET);
+ short reg2 = (short)(enc.reg(n.in(2))-D_OFFSET);
+ int body = f_scalar(0b00011110, 1, reg2, op, reg1, self);
+ enc.add4(body);
+ }
+
+ // share same encoding with LDR (literal, SIMD&FP) flavour
+ public static int load_pc(int opcode, int offset, int rt) {
+ offset>>=2;
+ return (opcode << 24) | (offset << 5) | rt;
+ }
+ // int l
+ public static int adrp(int op, int imlo,int opcode, int imhi, int rd) {
+ assert 0 <= rd && rd < 32;
+ return (op << 31) | (imlo << 29) |(opcode << 24) | (imhi << 5) | rd;
+ }
+
+ // [Rptr+Roff]
+ public static int indr_adr(int opcode, int off, STORE_LOAD_OPTION option, int s, int ptr, int rt) {
+ assert 0 <= ptr && ptr < 32;
+ assert 0 <= rt && rt < 32;
+ return (opcode << 21) | (off << 16) | (option.ordinal() << 13) | (s << 12) | (2 << 10) | (ptr << 5) | rt;
+ }
+ // [Rptr+imm9]
+ public static int load_str_imm(int opcode, int imm12, int ptr, int rt, int size) {
+ assert 0 <= ptr && ptr < 32;
+ assert 0 <= rt && rt < 32;
+
+ if(size == 8) imm12 = imm12 >> 3;
+ if(size == 4) imm12 = imm12 >> 2;
+ if(size == 2) imm12 = imm12 >> 1;
+ // size == 1 imm12 = imm12
+ return (opcode << 22) | ((imm12) << 10) | (ptr << 5) | rt;
+ }
+
+ // encoding for vcvt, size is encoded in operand
+ // <Qd>, <Qm>
+ // F32.S32
+ //encoded as op = 0b00, size = 0b10.
+ // VCVT<c>.<Td>.<Tm> <Dd>, <Dm>
+ // opcode is broken down into 4 pieces
+ public static int f_convert(int opcode_1, int opcode_2, int opcode_3, int opcode_4, int vd, int vm) {
+ return (opcode_1 << 28) | (opcode_2 << 24) | (opcode_3 << 20) | (opcode_4 << 16) |
+ (vd << 12) | (0x01100010 << 4) | vm;
+ }
+ public static int float_cast(int opcode, int ftype, int rn, int rd) {
+ assert 0 <= rd && rd < 32;
+ assert 0 <= rn && rn < 32;
+ return (opcode << 24) | (ftype << 22) | (2176 << 10) | (rn << 5) | rd;
+ }
+
+
+ // ftype = 3
+ public static int f_cmp(int opcode, int ftype, int rm, int rn) {
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rm && rm < 32;
+ // Todo: |8 is not needed
+ return (opcode << 24) | (ftype << 21) | (rm << 16) | (8 << 10) | (rn << 5);
+ }
+ public static void f_cmp(Encoding enc, Node n) {
+ short reg1 = (short)(enc.reg(n.in(1))-D_OFFSET);
+ short reg2 = (short)(enc.reg(n.in(2))-D_OFFSET);
+ int body = f_cmp(0b00011110, 3, reg1, reg2);
+ enc.add4(body);
+ }
+
+ public static COND make_condition(String bop) {
+ return switch (bop) {
+ case "==" -> COND.EQ;
+ case "!=" -> COND.NE;
+ case "<" -> COND.LT;
+ case "<=" -> COND.LE;
+ case ">=" -> COND.GE;
+ case ">" -> COND.GT;
+ default -> throw Utils.TODO();
+ };
+ }
+
+ public static int b_cond(int opcode, int delta, COND cond) {
+ // 24-5 == 19bits offset range
+ assert -(1<<19) <= delta && delta < (1<<19);
+ assert (delta&3)==0;
+ delta>>=2;
+ delta &= (1L<<19)-1; // Zero extend
+ return (opcode << 24) | ((delta)<< 5) | cond.ordinal();
+ }
+
+ public static int cond_set(int opcode, int rm, COND cond, int rn, int rd) {
+ assert 0 <= rd && rd < 32;
+ assert 0 <= rn && rn < 32;
+ return (opcode << 21) | (rm << 16) | (cond.ordinal() << 12) | (rn << 5) | rd;
+ }
+
+ // Branch with Link to Register calls a subroutine at an address in a register, setting register X30 to PC+4.
+ public static int blr(int opcode, int rd) {
+ assert 0 <= rd && rd < 32;
+ return opcode << 10 | rd << 5;
+ }
+ public static int b(int opcode, int delta) {
+ assert -(1<<26) <= delta && delta < (1<<26);
+ assert (delta&3)==0;
+ delta>>=2;
+ delta &= (1L<<26)-1; // Zero extend
+ return (opcode << 26) | delta;
+ }
+ // no aligned assert(assert (delta&3)==0;)
+ public static int b_calloc(int opcode, int delta) {
+ assert -(1<<26) <= delta && delta < (1<<26);
+ delta>>=2;
+ delta &= (1L<<26)-1; // Zero extend
+ return (opcode << 26) | delta;
+ }
+
+ @Override public RegMask callArgMask(TypeFunPtr tfp, int idx ) { return callInMask(tfp,idx); }
+ static RegMask callInMask(TypeFunPtr tfp, int idx ) {
+ 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<idx; i++ )
+ if( tfp.arg(i-2) instanceof TypeFloat)
+ fcnt++;
+ // Floats up to XMMS in XMM registers
+ if( tfp.arg(idx-2) instanceof TypeFloat ) {
+ if( fcnt < XMMS.length )
+ return XMMS[fcnt];
+ } else {
+ RegMask[] cargs = CALLINMASK;
+ if( idx-2-fcnt < cargs.length )
+ return cargs[idx-2-fcnt];
+ }
+ throw Utils.TODO(); // Pass on stack slot
+ }
+
+ public static long decodeImm12(int imm12) {
+ int immr = (imm12 >> 6) & 0x3F;
+ int imms = imm12 & 0x3F;
+ int size;
+ if ((imm12 & 0x1000) != 0) {
+ size = 64;
+ } else {
+ size = 31-(imms >> 1);
+ size |= size >> 1;
+ size |= size >> 2;
+ size |= size >> 4;
+ size++;
+ imms &= ~((32-size) << 1);
+ }
+ long val = (2L << imms)-1;
+ while (size < 64) {
+ val |= val << size;
+ size <<= 1;
+ }
+ val = (val >>> immr) | val << (64-immr);
+ return val;
+ }
+
+ // Return the max stack slot used by this signature, or 0
+ @Override public short maxArgSlot( TypeFunPtr tfp ) {
+ int icnt=0, fcnt=0; // Count of ints, floats
+ for( int i=0; i<tfp.nargs(); i++ ) {
+ if( tfp.arg(i) instanceof TypeFloat ) fcnt++;
+ else icnt++;
+ }
+ int nstk = Math.max(icnt-8,0)+Math.max(fcnt-8,0);
+ return (short)nstk;
+ }
+
+ private static final long CALLEE_SAVE =
+ 1L<<X19 |
+ 1L<<X20 | 1L<<X21 | 1L<<X22 | 1L<<X23 |
+ 1L<<X24 | 1L<<X25 | 1L<<X26 | 1L<<X27 |
+ 1L<<X28 |
+ 1L<<D9 | 1L<<D10 | 1L<<D11 |
+ 1L<<D12 | 1L<<D13 | 1L<<D14 | 1L<<D15;
+ static final long CALLER_SAVE = ~CALLEE_SAVE & ~(1L<<RSP);
+ @Override public long callerSave() { return CALLER_SAVE; }
+ @Override public long neverSave() { return 1L<<RSP; }
+ @Override public RegMask retMask( TypeFunPtr tfp ) {
+ return tfp.ret() instanceof TypeFloat ? D0_MASK : X0_MASK;
+ }
+ @Override public int rpc() { return X30; }
+
+ // Create a split op; any register to any register, including stack slots
+ @Override public SplitNode split(String kind, byte round, LRG lrg) { return new SplitARM(kind,round); }
+
+ // Return a MachNode unconditional branch
+ @Override public CFGNode jump() {
+ return new UJmpARM();
+ }
+
+ // Instruction selection
+ @Override public Node instSelect(Node n ) {
+ return switch( n ) {
+ case AddFNode addf -> new AddFARM(addf);
+ case AddNode add -> add(add);
+ case AndNode and -> and(and);
+ case BoolNode bool -> cmp(bool);
+ case CallNode call -> call(call);
+ case CastNode cast -> new 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 PhiNode phi -> new PhiNode(phi);
+ case ProjNode prj -> new ProjARM(prj);
+ case ReadOnlyNode read -> new ReadOnlyNode(read);
+ case ReturnNode ret -> new RetARM(ret,ret.fun());
+ case SarNode sar -> asr(sar);
+ case ShlNode shl -> lsl(shl);
+ case ShrNode shr -> lsr(shr);
+ case StartNode start -> new StartNode(start);
+ case StopNode stop -> new StopNode(stop);
+ case StoreNode st -> st(st);
+ case SubFNode subf -> new SubFARM(subf);
+ case SubNode sub -> sub(sub);
+ case ToFloatNode tfn-> new I2F8ARM(tfn);
+ case XorNode xor -> xor(xor);
+
+ case LoopNode loop -> new LoopNode(loop);
+ case RegionNode region-> new RegionNode(region);
+ default -> throw Utils.TODO();
+ };
+ }
+
+ private Node cmp(BoolNode bool){
+ Node cmp = _cmp(bool);
+ return new SetARM(cmp, IfNode.negate(bool.op()));
+ }
+ private Node _cmp(BoolNode bool) {
+ if( bool.isFloat() )
+ return new CmpFARM(bool);
+ return bool.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti
+ ? new CmpIARM(bool, (int)ti.value())
+ : new CmpARM(bool);
+ }
+
+ private Node ld(LoadNode ld) {
+ return new LoadARM(address(ld), ld.ptr(), idx, off);
+ }
+
+ private Node jmp(IfNode iff) {
+ // If/Bool combos will match to a Cmp/Set which sets flags.
+ // Most general arith ops will also set flags, which the Jmp needs directly.
+ // Loads do not set the flags, and will need an explicit TEST
+ String op = "!=";
+ if( iff.in(1) instanceof BoolNode bool ) op = bool.op();
+ else if( iff.in(1)==null ) op = "=="; // Never-node cutout
+ else iff.setDef(1, new BoolNode.NE(iff.in(1), new ConstantNode(TypeInteger.ZERO)));
+ return new BranchARM(iff, op);
+ }
+
+ private Node add(AddNode add) {
+ return add.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && imm12(ti)
+ ? new AddIARM(add, (int)ti.value())
+ : new AddARM(add);
+ }
+
+ private Node sub(SubNode sub) {
+ return sub.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti)
+ ? new SubIARM(sub, (int)(ti.value()))
+ : new SubARM(sub);
+ }
+
+ private Node con( ConstantNode con ) {
+ if( !con._con.isConstant() ) return new ConstantNode( con ); // Default unknown caller inputs
+ return switch( con._con ) {
+ case TypeInteger ti -> new IntARM(con);
+ case TypeFloat tf -> new FloatARM(con);
+ case TypeFunPtr tfp -> new TFPARM(con);
+ case TypeMemPtr tmp -> new ConstantNode(con);
+ case TypeNil tn -> throw Utils.TODO();
+ // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code.
+ case Type t -> t==Type.NIL ? new IntARM(con) : new ConstantNode(con);
+ };
+ }
+
+ private Node call(CallNode call){
+ return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp
+ ? new CallARM(call, tfp)
+ : new CallRRARM(call);
+ }
+
+ private Node or(OrNode or) {
+ int imm12;
+ return or.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && (imm12 = imm12Logical(ti)) != -1
+ ? new OrIARM(or, imm12)
+ : new OrARM(or);
+ }
+
+ private Node xor(XorNode xor) {
+ int imm12;
+ return xor.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && (imm12 = imm12Logical(ti)) != -1
+ ? new XorIARM(xor, imm12)
+ : new XorARM(xor);
+ }
+
+ private Node and(AndNode and) {
+ int imm12;
+ return and.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && (imm12 = imm12Logical(ti)) != -1
+ ? new AndIARM(and, imm12)
+ : new AndARM(and);
+ }
+
+ private Node asr(SarNode asr) {
+ return asr.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && ti.value() >= 0 && ti.value() < 63
+ ? new AsrIARM(asr, (int)ti.value())
+ : new AsrARM(asr);
+ }
+
+ private Node lsl(ShlNode lsl) {
+ return lsl.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && ti.value() >= 0 && ti.value() < 63
+ ? new LslIARM(lsl, (int)ti.value())
+ : new LslARM(lsl);
+ }
+
+ private Node lsr(ShrNode lsr) {
+ return lsr.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && ti.value() >= 0 && ti.value() < 63
+ ? new LsrIARM(lsr, (int)ti.value())
+ : new LsrARM(lsr);
+ }
+
+
+ private static int off;
+ private static Node idx;
+ private Node st(StoreNode st) {
+ Node xval = st.val() instanceof ConstantNode con && con._con == TypeInteger.ZERO ? null : st.val();
+ return new StoreARM(address(st),st.ptr(),idx,off,xval);
+ }
+
+ // Gather addressing mode bits prior to constructing. This is a builder
+ // pattern, but saving the bits in a *local* *global* here to keep mess
+ // contained.
+ private <N extends MemOpNode> N address(N mop ) {
+ off = 0; // Reset
+ idx = null;
+ Node base = mop.ptr();
+ // Skip/throw-away a ReadOnly, only used to typecheck
+ if( base instanceof ReadOnlyNode read ) base = read.in(1);
+ assert !(base instanceof AddNode) && base._type instanceof TypeMemPtr; // Base ptr always, not some derived
+ if( mop.off() instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm9(ti) ) {
+ off = (int)ti.value();
+ assert off == ti.value(); // In 32-bit range
+ } else {
+ idx = mop.off();
+ }
+ return mop;
+ }
+
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java
new file mode 100644
index 00000000..37383608
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java
@@ -0,0 +1,41 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.*;
+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/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java
new file mode 100644
index 00000000..4d2ac916
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+// fadd.d
+public class AddFRISC extends MachConcreteNode implements MachNode {
+ AddFRISC(Node addf) {super(addf);}
+ @Override public String op() { return "addf"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; }
+ @Override public RegMask outregmap() { return riscv.FMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.rf_type(enc,this,riscv.RM.RNE,1); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java
new file mode 100644
index 00000000..a85ce5d0
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.node.Node;
+
+public class AddIRISC extends ImmRISC {
+ // Used to inst-selection as a direct match against an ideal Add/Sub
+ public AddIRISC( Node add, int imm12, boolean pop ) { super(add,imm12,pop); }
+ @Override public String op() { return "addi"; }
+ @Override public String glabel() { return "+"; }
+ @Override int opcode() { return riscv.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/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java
new file mode 100644
index 00000000..df392a0e
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class AddRISC extends MachConcreteNode implements MachNode {
+ public AddRISC( Node add ) { super(add); }
+ AddRISC( Node base, Node off ) {
+ super(new Node[3]);
+ _inputs.set(1,base);
+ _inputs.set(2,off );
+ }
+ @Override public String op() { return "add"; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,0,0); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java
new file mode 100644
index 00000000..ba88bb48
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java
@@ -0,0 +1,12 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.node.Node;
+
+public class AndIRISC extends ImmRISC {
+ public AndIRISC( Node and, int imm) { super(and,imm); }
+ @Override public String op() { return "andi"; }
+ @Override public String glabel() { return "&"; }
+ @Override int opcode() { return riscv.OP_IMM; }
+ @Override int func3() {return 7;}
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java
new file mode 100644
index 00000000..93ef95c0
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java
@@ -0,0 +1,16 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class AndRISC extends MachConcreteNode implements MachNode {
+ public AndRISC(Node and) { super(and); }
+ @Override public String op() { return "and"; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,0b111,0); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" & ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java
new file mode 100644
index 00000000..44ff4e8b
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java
@@ -0,0 +1,92 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import java.util.BitSet;
+
+// Conditional branch such as: BEQ
+public class BranchRISC extends IfNode implements MachNode, RIPRelSize {
+ String _bop;
+ // label is obtained implicitly
+ public BranchRISC( IfNode iff, String bop, Node n1, Node n2 ) {
+ super(iff);
+ _bop = bop;
+ _inputs.setX(1,n1);
+ _inputs.setX(2,n2);
+ }
+
+ @Override public String op() { return "b"+_bop; }
+ @Override public String label() { return op(); }
+ @Override public String comment() { return "L"+cproj(1)._nid; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return null; }
+ @Override public void negate() {
+ _bop = negate(_bop);
+ // Cannot encode ">" or "<=", so flip these
+ if( _bop.equals(">") || _bop.equals("<=") ) {
+ swap12(); // Cannot negate the test, so swap the operands
+ _bop = swap(_bop); // Swap test to match
+ }
+ }
+
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("if( ");
+ if( in(1)==null ) sb.append("0");
+ else in(1)._print0(sb,visited);
+ sb.append(_bop);
+ if( in(2)==null ) sb.append("0");
+ else in(2)._print0(sb,visited);
+ return sb.append(" )");
+ }
+
+ @Override public void encoding( Encoding enc ) {
+ if( in(1)==null && _bop=="!=" ) return; // Inverted never-node, no code
+ enc.jump(this,cproj(0));
+ if( in(1)==null ) // Never node
+ enc.add4(riscv.j_type(riscv.OP_JAL, 0, 0));
+ else {
+ // Todo: relocs (for offset - immf)
+ short src1 = enc.reg(in(1));
+ short src2 = in(2)==null ? (short)riscv.ZERO : enc.reg(in(2));
+ enc.add4(riscv.b_type(riscv.OP_BRANCH, riscv.jumpop(_bop), src1, src2, 0));
+ }
+ }
+
+ // Delta is from opcode start
+ @Override public byte encSize(int delta) {
+ if( in(1)==null && _bop=="!=" ) return 0; // Inverted never-node, no code
+ if( -4*1024 <= delta && delta < 4*1024 ) return 4;
+ // 2 word encoding needs a tmp register, must teach RA
+ throw Utils.TODO();
+ }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ assert !( in(1)==null && _bop=="!=" ); // Inverted never-node, no code no patch
+ if( in(1)==null ) { // Never node
+ enc.patch4(opStart,riscv.j_type(riscv.OP_JAL, 0, delta));
+ } else {
+ short src1 = enc.reg(in(1));
+ short src2 = in(2)==null ? (short)riscv.ZERO : enc.reg(in(2));
+ if( opLen==4 ) {
+ enc.patch4(opStart,riscv.b_type(riscv.OP_BRANCH, riscv.jumpop(_bop), src1, src2, delta));
+ } else {
+ throw Utils.TODO();
+ }
+ }
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ if( in(1)==null && _bop=="!=" ) {
+ sb.p("never");
+ return;
+ }
+ String src1 = in(1)==null ? "#0" : code.reg(in(1));
+ String src2 = in(2)==null ? "#0" : code.reg(in(2));
+ sb.p(src1).p(" ").p(_bop).p(" ").p(src2).p(" ");
+ CFGNode prj = cproj(0).uctrlSkipEmpty();
+ if( !prj.blockHead() ) prj = prj.cfg0();
+ sb.p(label(prj));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallEndRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallEndRISC.java
new file mode 100644
index 00000000..7df68e77
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallEndRISC.java
@@ -0,0 +1,8 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.node.CallEndNode;
+import com.seaofnodes.simple.node.MachNode;
+
+public class CallEndRISC extends CallEndNode implements MachNode {
+ CallEndRISC( CallEndNode cend ) { super(cend); }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java
new file mode 100644
index 00000000..5aff94d3
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java
@@ -0,0 +1,60 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeFunPtr;
+
+public class CallRISC extends CallNode implements MachNode, RIPRelSize {
+ final TypeFunPtr _tfp;
+ final String _name;
+ CallRISC( CallNode call, TypeFunPtr tfp ) {
+ super(call);
+ assert tfp.isConstant();
+ _inputs.pop(); // Pop constant target
+ _tfp = tfp;
+ _name = CodeGen.CODE.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);
+ }
+ @Override public RegMask outregmap() { return riscv.RPC_MASK; }
+ @Override public String name() { return _name; }
+ @Override public TypeFunPtr tfp() { return _tfp; }
+
+ @Override public void encoding( Encoding enc ) {
+ // Short form +/-4K: beq r0,r0,imm12
+ // Long form: auipc rX,imm20/32; jal r0,[rX+imm12/32]
+ enc.relo(this);
+ short rpc = enc.reg(this);
+ enc.add4(riscv.j_type(riscv.OP_JAL, rpc, 0));
+ }
+
+ // Delta is from opcode start
+ @Override public byte encSize(int delta) {
+ if( -(1L<<20) <= delta && delta < (1L<<20) ) return 4;
+ // 2 word encoding needs a tmp register, must teach RA
+ throw Utils.TODO();
+ }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ short rpc = enc.reg(this);
+ if( opLen==4 ) {
+ enc.patch4(opStart,riscv.j_type(riscv.OP_JAL, rpc, delta));
+ } else {
+ throw Utils.TODO();
+ }
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(_name).p(" ");
+ for( int i=0; i<nargs(); i++ )
+ sb.p(code.reg(arg(i+2))).p(", ");
+ sb.unchar(2).p(" ").p(code.reg(fptr()));
+
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java
new file mode 100644
index 00000000..2afbbfa9
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java
@@ -0,0 +1,31 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class CallRRISC extends CallNode implements MachNode {
+ CallRRISC( CallNode call ) { super(call); }
+ @Override public String op() { return "callr"; }
+ @Override public String label() { return op(); }
+ @Override public RegMask regmap(int i) {
+ return i==_inputs._len-1
+ ? riscv.RMASK // Function call target
+ : riscv.callInMask(tfp(),i); // Normal argument
+ }
+ @Override public RegMask outregmap() { return null; }
+
+ @Override public void encoding( Encoding enc ) {
+ short rpc = enc.reg(this);
+ short src = enc.reg(in(_inputs._len-1));
+ int body = riscv.i_type(riscv.OP_JALR, rpc, 0, src, 0);
+ enc.add4(body);
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(fptr())).p(" ");
+ for( int i=0; i<nargs(); i++ )
+ sb.p(code.reg(arg(i))).p(" ");
+ sb.unchar(2);
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CastRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CastRISC.java
new file mode 100644
index 00000000..c404aaa5
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CastRISC.java
@@ -0,0 +1,18 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+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.CastNode;
+import com.seaofnodes.simple.node.MachNode;
+
+public class CastRISC extends CastNode implements MachNode {
+ public CastRISC( 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 void encoding( Encoding enc ) { }
+ @Override public void asm(CodeGen code, SB sb) { _t.print(sb.p(code.reg(in(1))).p(" isa ")); }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java
new file mode 100644
index 00000000..c67d7b3d
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java
@@ -0,0 +1,19 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+// fdiv.d
+public class DivFRISC extends MachConcreteNode implements MachNode{
+ DivFRISC(Node divf) {super(divf);}
+ @Override public String op() { return "divf"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; }
+ @Override public RegMask outregmap() { return riscv.FMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.rf_type(enc,this,riscv.RM.RUP,13); }
+ // Default on double precision for now(64 bits)
+ // General form: "fdiv.d rd = src1 / src2
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java
new file mode 100644
index 00000000..8367ee68
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class DivRISC extends MachConcreteNode implements MachNode{
+ public DivRISC(Node div) {super(div);}
+ @Override public String op() { return "div"; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,4,1); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2)));
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java
new file mode 100644
index 00000000..35de7e04
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java
@@ -0,0 +1,47 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.ConstantNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.type.TypeFloat;
+
+public class FltRISC extends ConstantNode implements MachNode, RIPRelSize {
+ FltRISC(ConstantNode con) { super(con); }
+ @Override public String op() { return "ld8"; }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return riscv.FMASK; }
+ @Override public boolean isClone() { return true; }
+ @Override public FltRISC copy() { return new FltRISC(this); }
+
+ @Override public void encoding( Encoding enc ) {
+ enc.largeConstant(this,_con, 0, -1/*TODO: RISC5 style patching*/);
+ short dst = (short)(enc.reg(this) - riscv.F_OFFSET);
+ short tmp = (short)riscv.T6;
+ // AUIPC dst,#hi20_constant_pool
+ enc.add4(riscv.u_type(riscv.OP_AUIPC, tmp, 0));
+ // Load dst,[dst+#low12_constant_pool]
+ enc.add4(riscv.i_type(riscv.OP_LOADFP, dst, 0b11, tmp, 0));
+ }
+ @Override public RegMask killmap() { return new RegMask(riscv.T6); }
+
+ // Delta is from opcode start.
+ // TODO: always size 8?
+ @Override public byte encSize(int delta) { return 8; }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ short dst = (short)(enc.reg(this) - riscv.F_OFFSET);
+ short tmp = (short)riscv.T6;
+ // AUIPC dst,#hi20_constant_pool
+ enc.patch4(opStart , riscv.u_type(riscv.OP_AUIPC, tmp, delta>>12));
+ // Load dst,[dst+#low12_constant_pool]
+ enc.patch4(opStart+4, riscv.i_type(riscv.OP_LOADFP, dst, 0b11, tmp, delta & 0xFFF));
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ _con.print(sb.p(code.reg(this)).p(" = #"));
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java
new file mode 100644
index 00000000..84f0f4c7
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java
@@ -0,0 +1,22 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.FunNode;
+import com.seaofnodes.simple.node.MachNode;
+
+public class FunRISC extends FunNode implements MachNode {
+ FunRISC(FunNode fun) {super(fun);}
+ @Override public void computeFrameAdjust(CodeGen code, int maxReg) {
+ super.computeFrameAdjust(code,maxReg);
+ if( _hasCalls ) // If non-leaf, pad to 16b
+ _frameAdjust = ((_frameAdjust+8) & -16);
+ }
+ @Override public void encoding( Encoding enc ) {
+ int sz = _frameAdjust;
+ if( sz == 0 ) return; // Skip if no frame adjust
+ if( sz >= 1L<<12 ) throw Utils.TODO();
+ enc.add4(riscv.i_type(riscv.OP_IMM, riscv.RSP, 0, riscv.RSP, -sz & 0xFFF));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java
new file mode 100644
index 00000000..5519fe5b
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java
@@ -0,0 +1,23 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+// fcvt.d.w
+// Converts a 32-bit signed integer, in integer register rs1 into a double-precision floating-point number in floating-point register rd.
+public class I2F8RISC extends MachConcreteNode implements MachNode {
+ I2F8RISC(Node i2f8) {super(i2f8);}
+ @Override public String op() { return "cvtf"; }
+ @Override public RegMask regmap(int i) { assert i==1; return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.FMASK; }
+ @Override public void encoding( Encoding enc ) {
+ short dst = (short)(enc.reg(this )-riscv.F_OFFSET);
+ short src1 = enc.reg(in(1));
+ int body = riscv.r_type(riscv.OP_FP,dst,riscv.RM.RNE.ordinal(),src1,0,0x69);
+ enc.add4(body);
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p("(flt)").p(code.reg(in(1)));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java
new file mode 100644
index 00000000..9303e566
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java
@@ -0,0 +1,54 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import java.util.BitSet;
+
+abstract public class ImmRISC extends MachConcreteNode implements MachNode {
+ public final int _imm12;
+ ImmRISC( Node add, int imm12, boolean pop ) {
+ super(add);
+ _imm12 = imm12;
+ // If pop, we are moving from an ideal Node to MachNode; the ideal Node
+ // inputs got copied here, but we are absorbing the immediate into the
+ // MachNode and so do not want the constant input.
+ if( pop ) _inputs.pop();
+ // if NOT pop, we are the 2nd-half of a 2-part expansion, reset the
+ // input edges to no-control
+ else { _inputs.set(0,null); _inputs.setX(1,add); }
+ }
+ ImmRISC( Node add, int imm12 ) { this(add,imm12,true); }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public boolean isClone() {
+ // Allow cloning of the 2nd half of a 2-part constant; this might
+ // trigger cloning the first part also.
+ return in(1) instanceof LUI || in(1) instanceof AUIPC;
+ }
+
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ int imm12 = (_imm12<<20)>>20; // Sign extend 12 bits
+ in(1)._print0(sb.append("( "), visited);
+ return sb.append(String.format(" %s #%d )",glabel(),imm12));
+ }
+
+ abstract int opcode();
+ abstract int func3();
+
+ @Override public void encoding( Encoding enc ) {
+ short dst = enc.reg(this );
+ short src = enc.reg(in(1));
+ int body = riscv.i_type(opcode(), dst, func3(), src, _imm12 & 0xFFF);
+ enc.add4(body);
+ }
+
+ // General form: "addi rd = rs1 + imm"
+ @Override public void asm(CodeGen code, SB sb) {
+ int imm12 = (_imm12<<20)>>20; // Sign extend 12 bits
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ").p(glabel()).p(" #").p(imm12);
+ if( in(1) instanceof LUI lui )
+ sb.p(" // #").hex4((int)(((TypeInteger)lui._con).value()) + imm12);
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java
new file mode 100644
index 00000000..70d5c18f
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java
@@ -0,0 +1,30 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.*;
+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;
+
+// 12-bit integer constant. Larger constants are made up in the instruction
+// selection by adding with a LUI.
+public class IntRISC extends ConstantNode implements MachNode {
+ public IntRISC(ConstantNode con) { super(con); }
+ @Override public String op() { return "ldi"; }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public boolean isClone() { return true; }
+ @Override public IntRISC copy() { return new IntRISC(this); }
+ @Override public void encoding( Encoding enc ) {
+ short dst = enc.reg(this);
+ int val = _con==Type.NIL ? 0 : (int)(((TypeInteger)_con).value() & 0xFFF);
+ // Explicit truncation of larger immediates; this will sign-extend on
+ // load and this is handled during instruction selection.
+ enc.add4(riscv.i_type(riscv.OP_IMM, dst, 0, riscv.ZERO, val));
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ String reg = code.reg(this);
+ _con.print(sb.p(reg).p(" = #"));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java
new file mode 100644
index 00000000..75ad6496
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.*;
+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/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java
new file mode 100644
index 00000000..7b0e9138
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java
@@ -0,0 +1,29 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+// Load memory addressing on RISC
+// Support imm, reg(direct), or reg+off(indirect) addressing
+// Base = base - base pointer, offset is added to base
+// idx = null
+// off = off - imm12 added to base
+public class LoadRISC extends MemOpRISC {
+ public LoadRISC(LoadNode ld, Node base, int off) { super(ld, base, off, null); }
+ @Override public String op() { return "ld" +_sz; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.MEM_MASK; }
+ @Override public void encoding( Encoding enc ) {
+ short dst = enc.reg(this );
+ short ptr = enc.reg(ptr());
+ int op = dst >= riscv.F_OFFSET ? riscv.OP_LOADFP : riscv.OP_LOAD;
+ if( dst >= riscv.F_OFFSET ) dst -= riscv.F_OFFSET;
+ enc.add4(riscv.i_type(op, dst, func3(), ptr, _off));
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(",");
+ asm_address(code,sb);
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java
new file mode 100644
index 00000000..b8eb7644
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java
@@ -0,0 +1,74 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+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.util.BitSet;
+
+public abstract class MemOpRISC extends MemOpNode implements MachNode {
+ final int _off; // Limit 12 bits
+ final char _sz;
+ MemOpRISC(MemOpNode mop, Node base, int off, Node val) {
+ super(mop,mop);
+ assert base._type instanceof TypeMemPtr;
+ _inputs.setX(2, base ); // Base can be an Add, is no longer raw object base
+ _inputs.setX(3, null); // Never an index
+ _inputs.setX(4, val );
+ _off = off;
+ _sz = (char)('0'+(1<<_declaredType.log_size()));
+ }
+
+ @Override public String label() { return op(); }
+ Node val() { return in(4); } // Only for stores
+
+ @Override public StringBuilder _printMach(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); }
+
+ @Override public Type compute() { throw Utils.TODO(); }
+ @Override public Node idealize() { throw Utils.TODO(); }
+
+ // func3 is based on load/store size and extend
+ 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();
+ }
+
+ SB asm_address(CodeGen code, SB sb) {
+ sb.p("[").p(code.reg(ptr())).p("+");
+ return sb.p(_off).p("]");
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java
new file mode 100644
index 00000000..c9917b4f
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+// fmul.d
+public class MulFRISC extends MachConcreteNode implements MachNode {
+ MulFRISC(Node mulf) {super(mulf);}
+ @Override public String op() { return "mulf"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; }
+ @Override public RegMask outregmap() { return riscv.FMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.rf_type(enc,this,riscv.RM.RNE,9); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java
new file mode 100644
index 00000000..a7961627
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+// mulh signed multiply instruction(no-imm form)
+public class MulRISC extends MachConcreteNode implements MachNode{
+ public MulRISC(Node mul) {super(mul);}
+ @Override public String op() { return "mul"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,0,1); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java
new file mode 100644
index 00000000..77f4a497
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java
@@ -0,0 +1,20 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.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/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java
new file mode 100644
index 00000000..e395a811
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java
@@ -0,0 +1,46 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.print.ASMPrinter;
+
+public class NewRISC extends NewNode implements MachNode, RIPRelSize {
+ // A pre-zeroed chunk of memory.
+ NewRISC( NewNode nnn ) { super(nnn); }
+ @Override public void encoding( Encoding enc ) {
+ // Generic external encoding; 2 ops.
+ enc.external(this,"calloc");
+ // A1 is a caller-save, allowed to crush building external address
+ // auipc
+ enc.add4(riscv.i_type(riscv.OP_IMM , _arg2Reg , 0, riscv.ZERO, 1));
+ enc.add4(riscv.u_type(riscv.OP_AUIPC, riscv.A2 , 0));
+ enc.add4(riscv.i_type(riscv.OP_JALR , riscv.RPC, 0, riscv.A2, 0));
+ }
+
+ // Patch is for running "new" in a JIT.
+ // Delta is from opcode start
+ @Override public byte encSize(int delta) { return 4*3; }
+
+ // Patch is for running "new" in a JIT.
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ // Negative patches are JIT emulator targets.
+ if( opStart+delta < 0 ) {
+ delta -= 4; // Offset from the AUIPC start
+ int imm20 = delta>>12;
+ if( ((delta>>11)&1)==1 ) imm20++; // Correct accidental sign extension
+ int imm12 = delta&0xFFF;
+ enc.patch4(opStart+4,riscv.u_type(riscv.OP_AUIPC, riscv.A2 , imm20));
+ enc.patch4(opStart+8,riscv.i_type(riscv.OP_JALR , riscv.RPC, 0, riscv.A2, imm12));
+ } else
+ throw Utils.TODO();
+ }
+
+ // General form: "alloc #bytes PC"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p("ldi a1=#1\n");
+ sb.p("auipc a2=#calloc\n");
+ sb.p("call a2+#calloc, a0");
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java
new file mode 100644
index 00000000..9cbd5025
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java
@@ -0,0 +1,20 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.MachConcreteNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.NotNode;
+
+public class NotRISC extends MachConcreteNode implements MachNode {
+ public NotRISC(NotNode not) { super(not); }
+ @Override public String op() { return "not"; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ short dst = enc.reg(this );
+ short src = enc.reg(in(1));
+ enc.add4(riscv.i_type(riscv.OP_IMM, dst, 2, src, 1));
+ }
+ @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java
new file mode 100644
index 00000000..f6a888d9
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java
@@ -0,0 +1,12 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.node.Node;
+
+public class OrIRISC extends ImmRISC {
+ public OrIRISC( Node and, int imm) { super(and,imm); }
+ @Override int opcode() { return riscv.OP_IMM; }
+ @Override int func3() { return 6; }
+ @Override public String glabel() { return "|"; }
+ @Override public String op() { return "ori"; }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java
new file mode 100644
index 00000000..103d4c7d
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java
@@ -0,0 +1,16 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class OrRISC extends MachConcreteNode implements MachNode {
+ public OrRISC(Node or) { super(or); }
+ @Override public String op() { return "or"; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,6,0); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" | ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java
new file mode 100644
index 00000000..02d53560
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.ParmNode;
+
+public class ParmRISC extends ParmNode implements MachNode {
+ final RegMask _rmask;
+ ParmRISC(ParmNode parm) {
+ super(parm);
+ _rmask = riscv.callInMask(fun().sig(),_idx);
+ }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return _rmask; }
+ @Override public void encoding( Encoding enc ) { }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java
new file mode 100644
index 00000000..2f9ac469
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java
@@ -0,0 +1,15 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.ProjNode;
+
+public class ProjRISC extends ProjNode implements MachNode {
+ ProjRISC(ProjNode p) { super(p); }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() {
+ return ((MachNode)in(0)).outregmap(_idx);
+ }
+ @Override public void encoding( Encoding enc ) { }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java
new file mode 100644
index 00000000..8a8186cd
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.*;
+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;
+
+public class RetRISC extends ReturnNode implements MachNode {
+ public RetRISC(ReturnNode ret, FunNode fun) { super(ret, fun); fun.setRet(this); }
+ @Override public void encoding( Encoding enc ) {
+ int sz = fun()._frameAdjust;
+ if( sz >= 1L<<12 ) throw Utils.TODO();
+ if( sz != 0 )
+ enc.add4(riscv.i_type(riscv.OP_IMM, riscv.RSP, 0, riscv.RSP, sz & 0xFFF));
+ short rpc = enc.reg(rpc());
+ enc.add4(riscv.i_type(riscv.OP_JALR, riscv.ZERO, 0, rpc, 0));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java
new file mode 100644
index 00000000..9ee19e42
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java
@@ -0,0 +1,33 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.BoolNode;
+import com.seaofnodes.simple.node.MachConcreteNode;
+import com.seaofnodes.simple.node.MachNode;
+
+// corresponds to feq.d = equal
+// fle.d = less than or equal
+// flt.d = less than
+public class SetFRISC extends MachConcreteNode implements MachNode {
+ final String _bop; // One of <,<=,==
+ SetFRISC( BoolNode bool ) {
+ super(bool);
+ assert bool.isFloat();
+ _bop = bool.op();
+ }
+ @Override public String op() { return "set"+_bop; }
+ @Override public RegMask regmap(int i) { return riscv.FMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ short dst = enc.reg(this );
+ short src1 = (short)(enc.reg(in(1))-riscv.F_OFFSET);
+ short src2 = (short)(enc.reg(in(2))-riscv.F_OFFSET);
+ int body = riscv.r_type(riscv.OP_FP,dst,riscv.fsetop(_bop),src1,src2,0b1010001/*FCMP*/);
+ enc.add4(body);
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ").p(_bop).p(" ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java
new file mode 100644
index 00000000..f270bf12
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.node.Node;
+
+// corresponds to slti,sltiu
+public class SetIRISC extends ImmRISC {
+ final boolean _unsigned; // slti vs sltiu
+ public SetIRISC( Node src, int imm12, boolean unsigned ) {
+ super(src,imm12);
+ _unsigned = unsigned;
+ }
+ @Override public String op() { return "slt" + (_unsigned ? "u":"") + "i"; }
+ @Override public String glabel() { return "<"+ (_unsigned ? "u":""); }
+ @Override int opcode() { return 0b0010011; }
+ @Override int func3() { return _unsigned ? 0b011 : 0b010; }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java
new file mode 100644
index 00000000..8d4a2f71
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java
@@ -0,0 +1,20 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+// corresponds to slt sltu
+public class SetRISC extends MachConcreteNode implements MachNode {
+ final boolean _unsigned;
+ public SetRISC( Node cmp, boolean unsigned ) { super(cmp); _unsigned=unsigned; }
+ @Override public String op() { return "slt" + (_unsigned ? "u":""); }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ riscv.r_type(enc,this,_unsigned ? 0b011 : 0b010,0);
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" < ").p(code.reg(in(2)));
+ }
+} \ No newline at end of file
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java
new file mode 100644
index 00000000..4edecc04
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java
@@ -0,0 +1,12 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.node.Node;
+
+public class SllIRISC extends ImmRISC {
+ public SllIRISC( Node and, int imm) { super(and,imm); }
+ @Override int opcode() { return riscv.OP_IMM; }
+ @Override int func3() { return 1; }
+ @Override public String glabel() { return "<<"; }
+ @Override public String op() { return "slli"; }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java
new file mode 100644
index 00000000..87e6693e
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+// Shift left logical
+public class SllRISC extends MachConcreteNode implements MachNode {
+ public SllRISC(Node sll) { super(sll); }
+ @Override public String op() { return "shl"; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,1,0); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" << ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java
new file mode 100644
index 00000000..569fa2e0
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java
@@ -0,0 +1,66 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.FunNode;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.node.SplitNode;
+
+public class SplitRISC extends SplitNode {
+ SplitRISC( String kind, byte round ) { super(kind,round, new Node[2]); }
+ @Override public RegMask regmap(int i) { return riscv.SPLIT_MASK; }
+ @Override public RegMask outregmap() { return riscv.SPLIT_MASK; }
+ @Override public void encoding( Encoding enc ) {
+ short dst = enc.reg(this );
+ short src = enc.reg(in(1));
+
+ // Flag for being either XMM or stack
+ boolean dstX = dst >= riscv.F_OFFSET;
+ boolean srcX = src >= riscv.F_OFFSET;
+
+ if( dst >= riscv.MAX_REG ) {
+ // Store to SP
+ if( src >= riscv.MAX_REG ) {
+ throw Utils.TODO(); // Very rare stack-stack move
+ }
+ int off = enc._fun.computeStackOffset(enc._code,dst);
+ int op = srcX ? riscv.OP_STOREFP : riscv.OP_STORE;
+ if( srcX ) src -= riscv.F_OFFSET;
+ enc.add4(riscv.s_type(op, 0b011, riscv.RSP, src, off));
+ return;
+ }
+ if( src >= riscv.MAX_REG ) {
+ // Load from SP
+ int off = enc._fun.computeStackOffset(enc._code,src);
+ int op = dstX ? riscv.OP_LOADFP : riscv.OP_LOAD;
+ if( dstX ) dst -= riscv.F_OFFSET;
+ enc.add4(riscv.i_type(op, dst, 0b011, riscv.RSP, off));
+ return;
+ }
+ // pick opcode based on regs
+ if( !dstX && !srcX ) {
+ // GPR->GPR
+ enc.add4(riscv.r_type(riscv.OP,dst,0,src,riscv.ZERO,0));
+ } else if( dstX && srcX ) {
+ // FPR->FPR
+ src -= riscv.F_OFFSET;
+ dst -= riscv.F_OFFSET;
+ enc.add4(riscv.r_type(riscv.OP_FP, dst, 0b000, src, src, 0b0010101)); // dst = FMIN(src,src)
+ } else if(!srcX && dstX) {
+ //GPR->FPR
+ // fmv.d.x
+ enc.add4(riscv.r_type(riscv.OP_FP, dst - riscv.F_OFFSET, 0, src, 0, 0b1111001));
+ } else if(srcX && !dstX) {
+ //FPR->GPR
+ //fmv.x.d
+ enc.add4(riscv.r_type(riscv.OP_FP, dst, 0, src - riscv.F_OFFSET, 0, 0b1110001));
+ }
+ }
+
+ // General form: "mov dst = src"
+ @Override public void asm(CodeGen code, SB sb) {
+ FunNode fun = code._encoding==null ? null : code._encoding._fun;
+ sb.p(code.reg(this,fun)).p(" = ").p(code.reg(in(1),fun));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java
new file mode 100644
index 00000000..26404e95
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java
@@ -0,0 +1,12 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.node.Node;
+
+public class SraIRISC extends ImmRISC {
+ 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/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java
new file mode 100644
index 00000000..1e930e72
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+// Right Shift Arithmetic
+public class SraRISC extends MachConcreteNode implements MachNode {
+ public SraRISC(Node sra) { super(sra); }
+ @Override public String op() { return "sar"; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,0b101,0b00100000); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java
new file mode 100644
index 00000000..ed697678
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java
@@ -0,0 +1,12 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.node.Node;
+
+public class SrlIRISC extends ImmRISC {
+ public SrlIRISC( Node and, int imm, boolean pop) { super(and,imm,pop); }
+ @Override int opcode() { return riscv.OP_IMM; }
+ @Override int func3() { return 5; }
+ @Override public String glabel() { return ">>>"; }
+ @Override public String op() { return "srli"; }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java
new file mode 100644
index 00000000..47712585
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+// Right Shift Logical
+public class SrlRISC extends MachConcreteNode implements MachNode {
+ public SrlRISC(Node srli) { super(srli); }
+ @Override public String op() { return "shr"; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,7,0); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >>> ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java
new file mode 100644
index 00000000..d34d94fc
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java
@@ -0,0 +1,47 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.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)
+
+//e.g s.cs[0] = 67; // C
+// base = s.cs, off = 4, imm = 67, val = null
+
+// sw rs2,offset(rs1)
+public class StoreRISC extends MemOpRISC {
+ StoreRISC( StoreNode st, Node base, int off, Node val ) { super(st, base, off, val); }
+ @Override public String op() { return "st"+_sz; }
+ @Override public RegMask regmap(int i) {
+ // 0 - ctrl
+ if( i==1 ) return null; // mem
+ if( i==2 ) return riscv.RMASK; // ptr
+ // 2 - index
+ if( i==4 ) return riscv.MEM_MASK; // Wide mask to store GPR and FPR
+ return null; // Anti-dependence
+ }
+ @Override public RegMask outregmap() { return null; }
+
+ @Override public void encoding( Encoding enc ) {
+ short val = enc.reg(val());
+ short ptr = enc.reg(ptr());
+ int op = val >= riscv.F_OFFSET ? riscv.OP_STOREFP : riscv.OP_STORE;
+ if( val >= riscv.F_OFFSET ) val -= riscv.F_OFFSET;
+ enc.add4(riscv.s_type(op, func3()&7, ptr, val, _off));
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ asm_address(code,sb).p(",");
+ if( val()==null ) sb.p("#").p("0");
+ else sb.p(code.reg(val()));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java
new file mode 100644
index 00000000..1e1b43c7
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+// fsub.d
+public class SubFRISC extends MachConcreteNode implements MachNode {
+ SubFRISC(Node subf) {super(subf);}
+ @Override public String op() { return "subf"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; }
+ @Override public RegMask outregmap() { return riscv.FMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.rf_type(enc,this,riscv.RM.RNE,5); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java
new file mode 100644
index 00000000..7ab4f904
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java
@@ -0,0 +1,16 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class SubRISC extends MachConcreteNode implements MachNode {
+ public SubRISC( Node sub ) { super(sub); }
+ @Override public String op() { return "sub"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,0,0x20); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java
new file mode 100644
index 00000000..17613009
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java
@@ -0,0 +1,55 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.ConstantNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.type.TypeFunPtr;
+
+public class TFPRISC extends ConstantNode implements MachNode, RIPRelSize {
+ TFPRISC(ConstantNode con) { super(con); }
+ @Override public String op() { return "ldx"; }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public boolean isClone() { return true; }
+ @Override public TFPRISC copy() { return new TFPRISC(this); }
+ @Override public void encoding( Encoding enc ) {
+ enc.relo(this);
+ // TODO: 1 op encoding, plus a TODO if it does not fit
+ short dst = enc.reg(this);
+ TypeFunPtr tfp = (TypeFunPtr)_con;
+ // auipc t0,0
+ 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);
+ }
+
+ @Override public byte encSize(int delta) {
+ if( -(1L<<11) <= delta && delta < (1L<<11) ) return 4;
+ throw Utils.TODO();
+ }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ short rpc = enc.reg(this);
+ if( opLen==8 ) {
+ // AUIPC (upper 20 bits)
+ // opstart of add
+ int next = opStart + opLen;
+ enc.patch4(opStart,riscv.u_type(riscv.OP_AUIPC, rpc, delta));
+ // addi(low 12 bits)
+ enc.patch4(next,riscv.i_type(riscv.OP_IMM, rpc, 0, rpc, delta & 0xFFF));
+ // addi
+ } else {
+ // should not happen as one instruction is 4 byte, and TFP arm encodes 2.
+ throw Utils.TODO();
+ }
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ _con.print(sb.p(code.reg(this)).p(" #"));
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java
new file mode 100644
index 00000000..6473d81c
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java
@@ -0,0 +1,46 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.Type;
+import java.util.BitSet;
+
+// unconditional jump
+public class UJmpRISC extends CFGNode implements MachNode, RIPRelSize {
+ @Override public String op() { return "jmp"; }
+ @Override public String label() { return op(); }
+ @Override public StringBuilder _print1( StringBuilder sb, BitSet visited ) { return sb.append("jmp "); }
+ @Override public RegMask regmap(int i) {return null; }
+ @Override public RegMask outregmap() { return null; }
+ @Override public Type compute() { throw Utils.TODO(); }
+ @Override public Node idealize() { throw Utils.TODO(); }
+ @Override public void encoding( Encoding enc ) {
+ // Short form +/-4K: beq r0,r0,imm12
+ // Long form: auipc rX,imm20/32; jal r0,[rX+imm12/32]
+ enc.jump(this,uctrl());
+ enc.add4(riscv.j_type(riscv.OP_JAL, 0, 0));
+ }
+
+ // Delta is from opcode start
+ @Override public byte encSize(int delta) {
+ if( -(1L<<20) <= delta && delta < (1L<<20) ) return 4;
+ // 2 word encoding needs a tmp register, must teach RA
+ throw Utils.TODO();
+ }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ if( opLen==4 ) {
+ enc.patch4(opStart,riscv.j_type(riscv.OP_JAL, 0, delta));
+ } else {
+ throw Utils.TODO();
+ }
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ CFGNode target = uctrl();
+ //assert target.nOuts() > 1; // Should optimize jmp to empty targets
+ sb.p(label(target));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java
new file mode 100644
index 00000000..61f25be2
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java
@@ -0,0 +1,12 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.node.Node;
+
+public class XorIRISC extends ImmRISC {
+ 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/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java
new file mode 100644
index 00000000..725d2b78
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java
@@ -0,0 +1,16 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class XorRISC extends MachConcreteNode implements MachNode {
+ public XorRISC(Node or) { super(or); }
+ @Override public String op() { return "xor"; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,4,0); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ^ ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
new file mode 100644
index 00000000..47d1db75
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
@@ -0,0 +1,542 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.Utils;
+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
+ public static int ZERO = 0, RPC= 1, RSP= 2, S12= 3, S13= 4, T0 = 5, T1 = 6, T2 = 7;
+ public static int S0 = 8, S1 = 9, A0 = 10, A1 = 11, A2 = 12, A3 = 13, A4 = 14, A5 = 15;
+ public static int A6 = 16, A7 = 17, S2 = 18, S3 = 19, S4 = 20, S5 = 21, S6 = 22, S7 = 23;
+ public static int S8 = 24, S9 = 25, S10= 26, S11= 27, T3 = 28, T4 = 29, T5 = 30, T6 = 31;
+
+ // FP registers
+ public static int F0 = 32, F1 = 33, F2 = 34, F3 = 35, F4 = 36, F5 = 37, F6 = 38, F7 = 39;
+ public static int FS0 = 40, FS1 = 41, FA0 = 42, FA1 = 43, FA2 = 44, FA3 = 45, FA4 = 46, FA5 = 47;
+ public static int FA6 = 48, FA7 = 49, FS2 = 50, FS3 = 51, FS4 = 52, FS5 = 53, FS6 = 54, FS7 = 55;
+ public static int FS8 = 56, FS9 = 57, FS10 = 58, FS11 = 59, FT8 = 60, FT9 = 61, FT10 = 62, FT11 = 63;
+
+ static final int MAX_REG = 64;
+
+ public static final int F_OFFSET = 32;
+
+ static final String[] REGS = new String[] {
+ "zero","rpc" , "rsp" , "s12" , "s13" , "t0" , "t1" , "t2" ,
+ "s0" , "s1" , "a0" , "a1" , "a2" , "a3" , "a4" , "a5" ,
+ "a6" , "a7" , "s2" , "s3" , "s4" , "s5" , "s6" , "s7" ,
+ "s8" , "s9" , "s10" , "s11" , "t3" , "t4" , "t5" , "t6" ,
+ "f0" , "f1" , "f2" , "f3" , "f4" , "f5" , "f6" , "f7" ,
+ "fs0" , "fs1" , "fa0" , "fa1" , "fa2" , "fa3" , "fa4" , "fa5" ,
+ "fa6" , "fa7" , "fs2" , "fs3" , "fs4" , "fs5" , "fs6" , "fs7" ,
+ "fs8" , "fs9" , "fs10", "fs11", "ft8" , "ft9" , "ft10", "ft11"
+ // Register-based naming hella easier to debug than "official" register
+ // names when single-stepping in debugger.
+ //"zero", "r1" , "rsp" , "r3" , "r4" , "r5" , "r6" , "r7" ,
+ //"r8" , "r9" , "r10" , "r11", "r12", "r13", "r14", "r15",
+ //"r16" , "r17" , "r18" , "r19", "r20", "r21", "r22", "r23",
+ //"r24" , "r25" , "r26" , "r27", "r28", "r29", "r30", "r31",
+ //"f0" , "f1" , "f2" , "f3" , "f4" , "f5" , "f6" , "f7" ,
+ //"fs0" , "fs1" , "fa0" , "fa1" , "fa2" , "fa3" , "fa4" , "fa5" ,
+ //"fa6" , "fa7" , "fs2" , "fs3" , "fs4" , "fs5" , "fs6" , "fs7" ,
+ //"fs8" , "fs9" , "fs10", "fs11", "ft8" , "ft9" , "ft10", "ft11"
+ };
+ @Override public String[] regs() { return REGS; }
+
+ // General purpose register mask: pointers and ints, not floats
+ static final long RD_BITS = 0b11111111111111111111111111111111L; // All the GPRs
+ static final RegMask RMASK = new RegMask(RD_BITS);
+
+ static final long WR_BITS = 0b11111111111111111111111111111010L; // All the GPRs, minus ZERO and RSP
+ static final RegMask WMASK = new RegMask(WR_BITS);
+ // Float mask from(f0-ft10). TODO: ft10,ft11 needs a larger RegMask
+ static final long FP_BITS = 0b11111111111111111111111111111111L<<F0;
+ static final RegMask FMASK = new RegMask(FP_BITS);
+
+ // Load/store mask; both GPR and FPR
+ static final RegMask MEM_MASK = new RegMask(WR_BITS | FP_BITS);
+
+ static final RegMask SPLIT_MASK = new RegMask( WR_BITS | FP_BITS, -1L );
+
+
+ // Arguments masks
+ public static RegMask A0_MASK = new RegMask(A0);
+ public static RegMask A1_MASK = new RegMask(A1);
+ public static RegMask A2_MASK = new RegMask(A2);
+ public static RegMask A3_MASK = new RegMask(A3);
+ public static RegMask A4_MASK = new RegMask(A4);
+ public static RegMask A5_MASK = new RegMask(A5);
+ public static RegMask A6_MASK = new RegMask(A6);
+ public static RegMask A7_MASK = new RegMask(A7);
+
+ public static final RegMask RPC_MASK = new RegMask(RPC);
+
+ // Float arguments masks
+ public static RegMask FA0_MASK = new RegMask(FA0);
+ public static RegMask FA1_MASK = new RegMask(FA1);
+ public static RegMask FA2_MASK = new RegMask(FA2);
+ public static RegMask FA3_MASK = new RegMask(FA3);
+ public static RegMask FA4_MASK = new RegMask(FA4);
+ public static RegMask FA5_MASK = new RegMask(FA5);
+ public static RegMask FA6_MASK = new RegMask(FA6);
+ public static RegMask FA7_MASK = new RegMask(FA7);
+
+ // Int arguments calling conv
+ static RegMask[] CALLINMASK = new RegMask[] {
+ A0_MASK,
+ A1_MASK,
+ A2_MASK,
+ A3_MASK,
+ A4_MASK,
+ A5_MASK,
+ A6_MASK,
+ A7_MASK
+ };
+
+ static RegMask[] XMMS = new RegMask[] {
+ FA0_MASK,
+ FA1_MASK,
+ FA2_MASK,
+ FA3_MASK,
+ FA4_MASK,
+ FA5_MASK,
+ FA6_MASK,
+ FA7_MASK
+ };
+
+ // major opcode: OP
+ public static int OP_LOAD = 0b00_000_11;
+ public static int OP_LOADFP = 0b00_001_11;
+ public static int OP_CUSTOM0 = 0b00_010_11;
+ public static int OP_IMM = 0b00_100_11;
+ public static int OP_AUIPC = 0b00_101_11;
+
+ public static int OP_STORE = 0b01_000_11;
+ public static int OP_STOREFP = 0b01_001_11;
+ public static int OP_CUSTOM1 = 0b01_010_11;
+ public static int OP = 0b01_100_11;
+ public static int OP_LUI = 0b01_101_11;
+
+ public static int OP_CUSTOM2 = 0b10_010_11;
+ public static int OP_FP = 0b10_100_11;
+
+ public static int OP_BRANCH = 0b11_000_11;
+ public static int OP_JALR = 0b11_001_11;
+ public static int OP_RESERVED= 0b11_010_11;
+ public static int OP_JAL = 0b11_011_11;
+
+
+ // Since riscv instructions are fixed we can just or them together
+ public static int r_type(int opcode, int rd, int func3, int rs1, int rs2, int func7) {
+ assert 0 <= rs1 && rs1 < 32;
+ assert 0 <= rs2 && rs2 < 32;
+ assert 0 <= rd && rd < 32;
+ return (func7 << 25) | (rs2 << 20) | (rs1 << 15) | (func3 << 12) | (rd << 7) | opcode;
+ }
+ public static void r_type(Encoding enc, Node n, int func3, int func7) {
+ short dst = enc.reg(n);
+ short src1 = enc.reg(n.in(1));
+ short src2 = enc.reg(n.in(2));
+ int body = r_type(OP,dst,func3,src1,src2,func7);
+ enc.add4(body);
+ }
+
+ public static void rf_type(Encoding enc, Node n, RM func3, int func7) {
+ short dst = (short)(enc.reg(n )-F_OFFSET);
+ short src1 = (short)(enc.reg(n.in(1))-F_OFFSET);
+ short src2 = (short)(enc.reg(n.in(2))-F_OFFSET);
+ int body = r_type(OP_FP,dst,func3.ordinal(),src1,src2,func7);
+ enc.add4(body);
+ }
+
+
+ public static int u_type(int opcode, int rd, int imm20) {
+ assert 0 <= rd && rd < 32;
+ return (imm20 << 12) | (rd << 7) | opcode;
+ }
+
+ public static int j_type(int opcode, int rd, int delta) {
+ assert -(1L<<20) <= delta && delta < (1L<<20);
+ assert 0 <= rd && rd < 32;
+ // Messy branch offset encoding
+ // 31 30-21 20 19-12 11-7 6-0
+ // 20 10- 1 11 19-12 rpc JAL
+ assert (delta&1)==0; // Low bit is always zero, not encoded
+ int imm10_01 = (delta>> 1) & 0x3FF;
+ int imm11 = (delta>>11) & 1;
+ int imm12_19 = (delta>>12) & 0xFF;
+ int imm20 = (delta>>19) & 1;
+ int bits = imm20<<19 | imm10_01 << 9 | imm11 << 8 | imm12_19;
+ return bits << 12 | rd << 7 | opcode;
+ }
+
+
+ public static int i_type(int opcode, int rd, int func3, int rs1, int imm12) {
+ assert 0 <= rd && rd < 32;
+ assert 0 <= rs1 && rs1 < 32;
+ assert opcode >= 0 && func3 >=0 && imm12 >= 0; // Zero-extend by caller
+ return (imm12 << 20) | (rs1 << 15) | (func3 << 12) | (rd << 7) | opcode;
+ }
+
+
+ // S-type instructions(store)
+ public static int s_type(int opcode, int func3, int rs1, int rs2, int imm12) {
+ assert 0 <= rs1 && rs1 < 32;
+ assert 0 <= rs2 && rs2 < 32;
+ assert 0 <= func3;
+
+ assert imm12 >= 0; // Masked to high zero bits by caller
+ int imm_lo = imm12 & 0x1F;
+ int imm_hi = imm12 >> 5;
+ return (imm_hi << 25) | (rs2 << 20) | (rs1 << 15) | (func3 << 12) | (imm_lo << 7) | opcode;
+ }
+
+ // BRANCH
+ public static int b_type(int opcode, int func3, short rs1, short rs2, int delta) {
+ assert 0 <= rs1 && rs1 < 32;
+ assert 0 <= rs2 && rs2 < 32;
+ assert -4*1024 <= delta && delta < 4*1024;
+ assert (delta&1)==0; // Low bit is always zero, not encoded
+ // Messy branch offset encoding
+ // 31 30 29 28 27 26 25 24-20 19-15 14-12 11 10 9 8 7 6-0
+ // 12 10 9 8 7 6 5 SRC2 SRC1 FUNC3 4 3 2 1 11 OP
+ int imm4_1 = (delta>> 1) & 0xF;
+ int imm10_5= (delta>> 5) &0x3F;
+ int imm11 = (delta>>11) & 1;
+ int imm12 = (delta>>12) & 1;
+ int imm5 = imm4_1<<1 | imm11;
+ int imm7 = imm12<<6 | imm10_5;
+ return (imm7 << 25 ) | (rs2 << 20) | (rs1 << 15) | (func3 << 12) | (imm5 << 7) | opcode;
+ }
+
+ public enum RM {
+ RNE, // Round to Nearest, ties to Even
+ RTZ, // Round towards Zero
+ RDN, // Round Down
+ RUP, // Round Up
+ DIRECT, // Round to Nearest, ties to Max Magnitude
+ RESERVED1, // Reserved for futue use
+ RESERVED2, // Reserved for future use
+ DYN, // In instruction’s rm field, selects dynamic rounding mode; In Rounding Mode register, reserved
+ }
+
+ // Since opcode is the same just return back func3
+ static public int jumpop(String op) {
+ return switch(op) {
+ case "==" -> 0x0;
+ case "!=" -> 0x1;
+ case "<" -> 0x4;
+ case ">=" -> 0x5;
+ case "u<" -> 0x6;
+ case "u<=" -> 0x7;
+ default -> throw Utils.TODO();
+ };
+ }
+
+ // Since opcode is the same just return back func3
+ static public int fsetop(String op) {
+ return switch(op) {
+ case "<" -> 1;
+ case "<=" -> 0;
+ case "==" -> 2;
+ default -> throw Utils.TODO();
+ };
+ }
+
+ @Override public RegMask callArgMask( TypeFunPtr tfp, int idx ) { return callInMask(tfp,idx); }
+ 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<idx; i++ )
+ if( tfp.arg(i-2) instanceof TypeFloat )
+ fcnt++;
+ // Floats up to XMMS in XMM registers
+ if( tfp.arg(idx-2) instanceof TypeFloat ) {
+ if( fcnt < XMMS.length )
+ return XMMS[fcnt];
+ } else {
+ RegMask[] cargs = CALLINMASK;
+ if( idx-2-fcnt < cargs.length )
+ return cargs[idx-2-fcnt];
+ }
+ // Pass on stack slot(8 and higher)
+ return new RegMask(MAX_REG + 1 + (idx - 2));
+ }
+
+ @Override public short maxArgSlot( TypeFunPtr tfp ) {
+ int icnt=0, fcnt=0; // Count of ints, floats
+ for( int i=0; i<tfp.nargs(); i++ ) {
+ if( tfp.arg(i) instanceof TypeFloat ) fcnt++;
+ else icnt++;
+ }
+ int nstk = Math.max(icnt-8,0)+Math.max(fcnt-8,0);
+ return (short)nstk;
+ }
+
+ static final long CALLER_SAVE =
+ (1L<<RPC) |
+ (1L<<A0 ) | (1L<<A1 ) | (1L<<A2 ) | (1L<<A3 ) | (1L<<A4 ) | (1L<<A5 ) | (1L<<A6 ) | (1L<<A7 ) |
+ (1L<<T0 ) | (1L<<T1 ) | (1L<<T2 ) | (1L<<T3 ) | (1L<<T4 ) | (1L<<T5 ) | (1L<<T6 ) |
+ (1L<<FA0) | (1L<<FA1) | (1L<<FA2) | (1L<<FA3) | (1L<<FA4) | (1L<<FA5) | (1L<<FA6) | (1L<<FA7) |
+ (1L<<F0 ) | (1L<<F1 ) | (1L<<F2 ) | (1L<<F3 ) | (1L<<F4 ) | (1L<<F5 ) | (1L<<F6 ) | (1L<<F7 ) |
+ (1L<<FT8) | (1L<<FT9) | (1L<<FT10)| (1L<<FT11);
+
+ @Override public long callerSave() { return CALLER_SAVE; }
+ @Override public long neverSave() { return (1L<<RSP) | (1L<<ZERO); }
+ @Override public RegMask retMask( TypeFunPtr tfp ) {
+ return tfp.ret() instanceof TypeFloat ? FA0_MASK : A0_MASK;
+ }
+ @Override public int rpc() { return RPC; }
+
+
+ // Return a MachNode unconditional branch
+ @Override public CFGNode jump() { return new UJmpRISC(); }
+
+ // Create a split op; any register to any register, including stack slots
+ @Override public SplitNode split(String kind, byte round, LRG lrg) { return new SplitRISC(kind,round); }
+
+ // True if signed 12-bit immediate
+ public static boolean imm12(TypeInteger ti) {
+ // 52 = 64-12
+ return ti.isConstant() && ((ti.value()<<52)>>52) == ti.value();
+ }
+ // True if HIGH 20-bit signed immediate, with all zeros low.
+ public static boolean imm20Exact(TypeInteger ti) {
+ // shift left 32 to clear out the upper 32 bits.
+ // shift right SIGNED to sign-extend upper 32 bits; then shift 12 more to clear out lower 12 bits.
+ // shift left 12 to re-center the bits.
+ return ti.isConstant() && (((ti.value()<<32)>>>44)<<12) == ti.value();
+ }
+
+ @Override public Node instSelect( Node n ) {
+ return switch (n) {
+ case AddFNode addf -> addf(addf);
+ case AddNode add -> add(add);
+ case AndNode and -> and(and);
+ case BoolNode bool -> cmp(bool);
+ case CallNode call -> call(call);
+ case CastNode cast -> new CastRISC(cast);
+ case CallEndNode cend -> new CallEndRISC(cend);
+ case CProjNode c -> new CProjNode(c);
+ case ConstantNode con -> con(con);
+ case DivFNode divf -> new DivFRISC(divf);
+ case DivNode div -> new DivRISC(div);
+ case FunNode fun -> new FunRISC(fun);
+ case IfNode iff -> jmp(iff);
+ case LoadNode ld -> ld(ld);
+ case MemMergeNode mem -> new MemMergeNode(mem);
+ case 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 PhiNode phi -> new PhiNode(phi);
+ case ProjNode prj -> prj(prj);
+ case ReadOnlyNode read-> new ReadOnlyNode(read);
+ case ReturnNode ret -> new RetRISC(ret, ret.fun());
+ case SarNode sar -> sra(sar);
+ case ShlNode shl -> sll(shl);
+ case ShrNode shr -> srl(shr);
+ case StartNode start -> new StartNode(start);
+ case StopNode stop -> new StopNode(stop);
+ case StoreNode st -> st(st);
+ case SubFNode subf -> new SubFRISC(subf);
+ case SubNode sub -> sub(sub);
+ case ToFloatNode tfn -> i2f8(tfn);
+ case XorNode xor -> xor(xor);
+
+ case LoopNode loop -> new LoopNode(loop);
+ case RegionNode region -> new RegionNode(region);
+ default -> throw Utils.TODO();
+ };
+ }
+
+ private Node addf(AddFNode addf) {
+ return new AddFRISC(addf);
+ }
+
+ private Node add(AddNode add) {
+ if( add.in(2) instanceof ConstantNode off2 && off2._con instanceof TypeInteger ti && imm12(ti) )
+ return new AddIRISC(add, (int)ti.value(),true);
+ return new AddRISC(add);
+ }
+
+ private Node and(AndNode and) {
+ if( and.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti ) {
+ if( imm12(ti) )
+ return new AndIRISC(and, (int)ti.value());
+ // Could be any size low bit mask
+ if( ti.value() == 0xFFFFFFFFL )
+ return new SrlIRISC(new SllIRISC(and,32),32,false);
+ }
+ return new AndRISC(and);
+ }
+
+ private Node call(CallNode call) {
+ return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp
+ ? new CallRISC(call, tfp)
+ : new CallRRISC(call);
+ }
+ private Node nnn(NewNode nnn) {
+ // TODO: pass in the TFP for alloc
+ return new NewRISC(nnn);
+ }
+
+ private Node cmp(BoolNode bool) {
+ // Float variant directly implemented in hardware
+ if( bool.isFloat() )
+ return new SetFRISC(bool);
+
+ // Only < and <u are implemented in hardware.
+ // x < y - as-is
+ // x <= y - flip and negate; !(y < x); `slt tmp=y,x; xori dst=tmp,#1`
+ // x == y - sub and vs0 == `sub tmp=x-y; sltu dst=tmp,#1`
+
+ // x > y - swap; y < x
+ // x >= y - swap and negate; !(x < y); `slt tmp=y,x;` then NOT.
+ // x != y - sub and vs0 == `sub tmp=x-y; sltu dst=tmp,#1` then NOT.
+
+ // The ">", ">=" and "!=" in Simple include a NotNode, which can be
+ // implemented with a XOR. If one of the above is followed by a NOT
+ // we can remove the double XOR in the encodings.
+
+ return switch( bool.op() ) {
+ case "<" -> bool.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti)
+ ? new SetIRISC(bool, (int)ti.value(),false)
+ : new SetRISC(bool, false);
+ // x <= y - flip and negate; !(y < x); `slt tmp=y,x; xori dst=tmp,#1`
+ case "<=" -> new XorIRISC(new SetRISC(bool.swap12(), false),1);
+ // x == y - sub and vs0 == `sub tmp=x-y; sltu dst=tmp,#1`
+ case "==" -> new SetIRISC(new SubRISC(bool),1,true);
+ default -> throw Utils.TODO();
+ };
+ }
+
+ private Node con( ConstantNode con ) {
+ if( !con._con.isConstant() ) return new ConstantNode( con ); // Default unknown caller inputs
+ return switch( con._con ) {
+ case 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();
+ case TypeNil tn -> throw Utils.TODO();
+ // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code.
+ case Type t -> t==Type.NIL ? new IntRISC(con) : new ConstantNode(con);
+ };
+ }
+
+ private Node jmp( IfNode iff ) {
+ if( iff.in(1) instanceof BoolNode bool && !bool.isFloat() ) {
+ // if less than or equal switch inputs
+ String bop = bool.op();
+ if( bop.equals("<=") || bop.equals(">") )
+ return new BranchRISC(iff, IfNode.swap(bop), bool.in(2), bool.in(1));
+ return new BranchRISC(iff, bop, bool.in(1), bool.in(2));
+ }
+ // Vs zero
+ return new BranchRISC(iff, iff.in(1)==null ? "==" : "!=", iff.in(1), null);
+ }
+
+ private Node or(OrNode or) {
+ if( or.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti))
+ return new OrIRISC(or, (int)ti.value());
+ return new OrRISC(or);
+ }
+
+ private Node xor(XorNode xor) {
+ if( xor.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti))
+ return new XorIRISC(xor, (int)ti.value());
+ return new XorRISC(xor);
+ }
+
+ private Node sra(SarNode sar) {
+ if( sar.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti))
+ return new SraIRISC(sar, (int)ti.value());
+ return new SraRISC(sar);
+ }
+
+ private Node srl(ShrNode shr) {
+ if( shr.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti))
+ return new SrlIRISC(shr, (int)ti.value(),true);
+ return new SrlRISC(shr);
+ }
+
+ private Node sll(ShlNode sll) {
+ if( sll.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti))
+ return new SllIRISC(sll, (int)ti.value());
+ return new SllRISC(sll);
+ }
+
+ private Node sub(SubNode sub) {
+ return sub.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti)
+ ? new AddIRISC(sub, (int)(-ti.value()),true)
+ : new SubRISC(sub);
+ }
+
+ private Node i2f8(ToFloatNode tfn) {
+ assert tfn.in(1)._type instanceof TypeInteger ti;
+ return new I2F8RISC(tfn);
+ }
+
+ private Node prj(ProjNode prj) {
+ return new ProjRISC(prj);
+ }
+
+ private Node ld(LoadNode ld) {
+ return new LoadRISC(ld,address(ld),off);
+ }
+
+ private Node st(StoreNode st) {
+ Node xval = st.val() instanceof ConstantNode con && con._con == TypeInteger.ZERO ? null : st.val();
+ return new StoreRISC(st,address(st), off, xval);
+ }
+
+ // Gather addressing mode bits prior to constructing. This is a builder
+ // pattern, but saving the bits in a *local* *global* here to keep mess
+ // contained.
+ private static int off;
+ private Node address( MemOpNode mop ) {
+ off = 0; // Reset
+ Node base = mop.ptr();
+ // Skip/throw-away a ReadOnly, only used to typecheck
+ if( base instanceof ReadOnlyNode read ) base = read.in(1);
+ assert !(base instanceof AddNode) && base._type instanceof TypeMemPtr; // Base ptr always, not some derived
+ if( mop.off() instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) ) {
+ off = (int)ti.value();
+ } else {
+ base = new AddRISC(base,mop.off());
+ base._type = mop.ptr()._type;
+ }
+ return base;
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java
new file mode 100644
index 00000000..d54c7c31
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java
@@ -0,0 +1,44 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.Utils;
+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
+ if( i==3 ) return x86_64_v2.RMASK; // index
+ if( i==4 ) return x86_64_v2.XMASK; // value
+ throw Utils.TODO();
+ }
+ @Override public RegMask outregmap() { return x86_64_v2.XMASK; }
+ @Override public int twoAddress() { return 4; }
+ @Override public void encoding( Encoding enc ) {
+ // addsd xmm0, DWORD PTR [rdi+0xc]
+ short dst = enc.reg(this );
+ short ptr = enc.reg(ptr());
+ short idx = enc.reg(idx());
+ // F opcode
+ enc.add1(0xF2);
+ // rex prefix must come next (REX.W is not set)
+ x86_64_v2.rexF(dst, ptr, idx, false, enc);
+
+ // FP ADD
+ enc.add1(0x0F).add1(0x58);
+
+ x86_64_v2.indirectAdr(_scale, idx, ptr, _off, dst, enc);
+ }
+
+ // General form: "add dst = src + [base + idx<<2 + 12]"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ");
+ sb.p(val()==null ? "#"+_imm : code.reg(val())).p(" + ");
+ asm_address(code,sb);
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java
new file mode 100644
index 00000000..a4166bc7
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java
@@ -0,0 +1,33 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class AddFX86 extends MachConcreteNode implements MachNode {
+ AddFX86( Node addf ) { super(addf); }
+ @Override public String op() { return "addf"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.XMASK; }
+ @Override public int twoAddress() { return 1; }
+ @Override public boolean commutes() { return true; }
+
+ @Override public void encoding( Encoding enc ) {
+ // F2 0F 58 /r ADDSD xmm1, xmm2/m64
+ short dst = (short)(enc.reg(this ) - x86_64_v2.XMM_OFFSET);
+ short src = (short)(enc.reg(in(2)) - x86_64_v2.XMM_OFFSET);
+ // Fopcode
+ enc.add1(0xF2);
+ // rex prefix must come next (REX.W is not set)
+ x86_64_v2.rexF(dst, src, 0, false, enc);
+
+ enc.add1(0x0F).add1(0x58);
+
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src));
+ }
+
+ // General form: "addf dst += src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" += ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java
new file mode 100644
index 00000000..569be754
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java
@@ -0,0 +1,13 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.node.Node;
+
+public class AddIX86 extends ImmX86 {
+ AddIX86( Node add, int imm ) { super(add,imm); }
+ @Override public String op() {
+ return _imm == 1 ? "inc" : (_imm == -1 ? "dec" : "addi");
+ }
+ @Override public String glabel() { return "+"; }
+ @Override int opcode() { return 0x81; }
+ @Override int mod() { return 0; }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java
new file mode 100644
index 00000000..f97a7cb8
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java
@@ -0,0 +1,34 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.SB;
+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; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ @Override public int twoAddress() { return 4; }
+ @Override public void encoding( Encoding enc ) {
+ // add something to register from memory
+ // add eax,DWORD PTR [rdi+0xc]
+ // REX.W + 03 /r ADD r64, r/m64
+ short dst = enc.reg(this );
+ short ptr = enc.reg(ptr());
+ short idx = enc.reg(idx());
+
+ enc.add1(x86_64_v2.rex(dst, ptr, idx));
+ // opcode
+ enc.add1(0x03);
+
+ x86_64_v2.indirectAdr(_scale, idx, ptr, _off, dst, enc);
+ }
+
+ // General form: "add dst += [base + idx<<2 + 12]"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" += ");
+ asm_address(code,sb);
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java
new file mode 100644
index 00000000..84543e8f
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java
@@ -0,0 +1,11 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.node.Node;
+
+public class AddX86 extends RegX86 {
+ AddX86( Node add ) { super(add); }
+ @Override public String op() { return "add"; }
+ @Override public String glabel() { return "+"; }
+ @Override int opcode() { return 0x03; }
+ @Override public boolean commutes() { return true; }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java
new file mode 100644
index 00000000..3c1b567c
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java
@@ -0,0 +1,11 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.node.Node;
+
+public class AndIX86 extends ImmX86 {
+ AndIX86( Node add, int imm ) { super(add,imm); }
+ @Override public String op() { return "andi"; }
+ @Override public String glabel() { return "&"; }
+ @Override int opcode() { return 0x81; }
+ @Override int mod() { return 4; }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java
new file mode 100644
index 00000000..dedbce45
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java
@@ -0,0 +1,11 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.node.Node;
+
+public class AndX86 extends RegX86 {
+ AndX86( Node add ) { super(add); }
+ @Override public String op() { return "and"; }
+ @Override public String glabel() { return "&"; }
+ @Override int opcode() { return 0x23; }
+ @Override public boolean commutes() { return true; }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallEndX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallEndX86.java
new file mode 100644
index 00000000..ce8b993f
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallEndX86.java
@@ -0,0 +1,8 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.node.CallEndNode;
+import com.seaofnodes.simple.node.MachNode;
+
+public class CallEndX86 extends CallEndNode implements MachNode {
+ CallEndX86( CallEndNode cend ) { super(cend); }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java
new file mode 100644
index 00000000..b9d4fa8d
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java
@@ -0,0 +1,33 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.CallNode;
+import com.seaofnodes.simple.node.MachNode;
+
+public class CallRX86 extends CallNode implements MachNode {
+ CallRX86( CallNode call ) { super(call); }
+ @Override public String op() { return "callr"; }
+ @Override public String label() { return op(); }
+ @Override public RegMask regmap(int i) {
+ return i==_inputs._len
+ ? x86_64_v2.WMASK // Function call target
+ : x86_64_v2.callInMask(tfp(),i); // Normal argument
+ }
+ @Override public RegMask outregmap() { return null; }
+ @Override public void encoding( Encoding enc ) {
+ // FF /2 CALL r/m64
+ // calls the function in the register
+ short src = enc.reg(fptr());
+ enc.add1(0xFF);
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.INDIRECT,2,src));
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(fptr())).p(" ");
+ for( int i=0; i<nargs(); i++ )
+ sb.p(code.reg(arg(i))).p(" ");
+ sb.unchar(2);
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java
new file mode 100644
index 00000000..f6e67fb4
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java
@@ -0,0 +1,45 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.CallNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.type.TypeFunPtr;
+
+public class CallX86 extends CallNode implements MachNode, RIPRelSize {
+ final TypeFunPtr _tfp;
+ final String _name;
+ CallX86( CallNode call, TypeFunPtr tfp ) {
+ super(call);
+ _inputs.pop(); // Pop constant target
+ assert tfp.isConstant();
+ _tfp = tfp;
+ _name = CodeGen.CODE.link(tfp)._name;
+ }
+ @Override public String op() { return "call"; }
+ @Override public String label() { return op(); }
+ @Override public String name() { return _name; }
+ @Override public TypeFunPtr tfp() { return _tfp; }
+ @Override public RegMask regmap(int i) { return x86_64_v2.callInMask(_tfp,i); }
+ @Override public RegMask outregmap() { return null; }
+ @Override public int nargs() { return nIns()-2; } // Minus control, memory, fptr
+
+ @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; }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ enc.patch4(opStart+1, delta-5);
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(_name).p(" ");
+ for( int i=0; i<nargs(); i++ )
+ sb.p(code.reg(in(i+2))).p(" ");
+ sb.unchar(2);
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CastX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CastX86.java
new file mode 100644
index 00000000..9537cfb3
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CastX86.java
@@ -0,0 +1,16 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.CastNode;
+import com.seaofnodes.simple.node.MachNode;
+
+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 void encoding( Encoding enc ) { }
+ @Override public void asm(CodeGen code, SB sb) { _t.print(sb.p(code.reg(in(1))).p(" isa ")); }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java
new file mode 100644
index 00000000..35b92170
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class CmpFX86 extends MachConcreteNode implements MachNode {
+ CmpFX86( Node cmp ) { super(cmp); }
+ @Override public String op() { return "cmpf"; }
+ @Override public RegMask regmap(int i) { return x86_64_v2.XMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; }
+
+ @Override public void encoding( Encoding enc ) {
+ // UCOMISD
+ short src1 = (short)(enc.reg(in(1)) - x86_64_v2.XMM_OFFSET);
+ short src2 = (short)(enc.reg(in(2)) - x86_64_v2.XMM_OFFSET);
+ // float opcode
+ enc.add1(0x66);
+ // rex prefix must come next (REX.W is not set)
+ x86_64_v2.rexF(src1, src2, 0, false, enc);
+
+ enc.add1(0x0F).add1(0x2E);
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, src1, src2));
+ }
+
+ // General form: "cmp src1,src2"
+ @Override public void asm(CodeGen code, SB sb) {
+ String dst = code.reg(this);
+ if( dst!="flags" ) sb.p(dst).p(" = ");
+ sb.p(code.reg(in(1))).p(", ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java
new file mode 100644
index 00000000..91b6d43c
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java
@@ -0,0 +1,44 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+// Compare immediate. Sets flags.
+public class CmpIX86 extends MachConcreteNode implements MachNode {
+ final int _imm;
+ CmpIX86( Node cmp, int imm, boolean swap ) {
+ super(cmp);
+ _inputs.del(swap ? 1 : 2);
+ _imm = imm;
+ }
+ CmpIX86( CmpIX86 cmp ) {
+ super((Node)null);
+ addDef(null);
+ addDef(cmp.in(1));
+ _imm = cmp._imm;
+ }
+
+ @Override public String op() { return _imm==0 ? "test" : "cmp"; }
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; }
+ @Override public boolean isClone() { return true; }
+ @Override public Node copy() { return new CmpIX86(this); }
+
+ @Override public final void encoding( Encoding enc ) {
+ short dst = enc.reg(in(1)); // Also src1
+ enc.add1(x86_64_v2.rex(0, dst, 0));
+ // opcode; 0x81 or 0x83; 0x69 or 0x6B
+ enc.add1( 0x81 + (x86_64_v2.imm8(_imm) ? 2 : 0) );
+ enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 7, dst) );
+ // immediate(4 bytes) 32 bits or (1 byte)8 bits
+ if( x86_64_v2.imm8(_imm) ) enc.add1(_imm);
+ else enc.add4(_imm);
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ String dst = code.reg(this);
+ if( dst!="flags" ) sb.p(dst).p(" = ");
+ sb.p(code.reg(in(1)));
+ if( _imm != 0 ) sb.p(", #").p(_imm);
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java
new file mode 100644
index 00000000..489853eb
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java
@@ -0,0 +1,78 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.SB;
+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 _swap; // Op switched LHS, RHS
+ CmpMemX86( BoolNode bool, LoadNode ld, Node base, Node idx, int off, int scale, int imm, Node val, boolean swap ) {
+ super(bool,ld, base, idx, off, scale, imm, val );
+ _swap = swap;
+ assert !_swap || val() == null || bool.op()=="==" || bool.op()=="!="; // Cannot swap with immediate
+ }
+ @Override public String op() { return ((val()==null && _imm==0) ? "test" : "cmp") + _sz; }
+ @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; }
+ @Override public void encoding( Encoding enc ) {
+ // REX.W + 81 /7 id CMP r/m64, imm32 | REX.W + 39 /r CMP r/m64,r64
+ // CMP [mem], imm32
+
+ short ptr = enc.reg(ptr());
+ short idx = enc.reg(idx());
+ short src = enc.reg(val());
+
+ if(src == -1) {
+ assert !_swap;
+ int log = _declaredType.log_size();
+ x86_64_v2.rexF(-1, ptr, idx, log==3, enc);
+ switch(log) {
+ case 0: enc.add1(0x80); break;
+ case 1: enc.add1(0x66).add1(0x81); break;
+ case 2: enc.add1(0x81); break;
+ case 3: enc.add1(0x81); break;
+ }
+ x86_64_v2.indirectAdr(_scale, idx, ptr, _off, 7, enc);
+ switch(log) {
+ case 0: enc.add1(_imm); break;
+ case 1: enc.add2(_imm); break;
+ case 2: enc.add4(_imm); break;
+ case 3: enc.add8(_imm); break;
+ }
+ } else {
+ encVal(enc, _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) {
+ if(decl instanceof TypeFloat) {
+ src -= (short)x86_64_v2.XMM_OFFSET;
+ enc.add1(decl==TypeFloat.F32 ? 0xF3 : 0xF2).add1(0x0F).add1(0xC2);
+ } else {
+ int log = decl.log_size();
+ x86_64_v2.rexF(src, ptr, idx, log == 3, enc);
+ int swap = _swap ? 2 : 0;
+ switch(log) {
+ case 0: enc.add1(0x38+swap); break;
+ case 1: enc.add1(0x66).add1(0x39+swap); break;
+ case 2: enc.add1(0x39+swap); break;
+ case 3: enc.add1(0x39+swap); break;
+ }
+ }
+ x86_64_v2.indirectAdr(scale, idx, ptr, off, src, enc);
+ }
+
+ // General form: "cmp dst = src, [base + idx<<2 + 12]"
+ @Override public void asm(CodeGen code, SB sb) {
+ String dst = code.reg(this);
+ if( dst!="flags" ) sb.p(dst).p(" = ");
+ if( _swap ) asm_address(code,sb).p(",");
+ if( val()==null ) {
+ if( _imm!=0 ) sb.p("#").p(_imm);
+ } else {
+ sb.p(code.reg(val()));
+ }
+ if( !_swap ) asm_address(code,sb.p(", "));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java
new file mode 100644
index 00000000..06a36d99
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java
@@ -0,0 +1,26 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class CmpX86 extends MachConcreteNode implements MachNode {
+ CmpX86( Node add ) { super(add); }
+ @Override public String op() { return "cmp"; }
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; }
+
+ @Override public void encoding( Encoding enc ) {
+ short dst = enc.reg(in(1));
+ short src = enc.reg(in(2));
+
+ enc.add1(x86_64_v2.rex(dst, src, 0));
+ enc.add1(0x3B);
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src));
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ String dst = code.reg(this);
+ if( dst!="flags" ) sb.p(dst).p(" = ");
+ sb.p(code.reg(in(1))).p(", ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java
new file mode 100644
index 00000000..9509a1f5
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java
@@ -0,0 +1,34 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class DivFX86 extends MachConcreteNode implements MachNode {
+ DivFX86( Node divf) { super(divf); }
+ @Override public String op() { return "divf"; }
+ @Override public RegMask regmap(int i) { return x86_64_v2.XMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.XMASK; }
+ @Override public int twoAddress() { return 1; }
+
+ @Override public void encoding( Encoding enc ) {
+ // F2 0F 5E /r DIVSD xmm1, xmm2/m64
+ short dst = (short)(enc.reg(this ) - x86_64_v2.XMM_OFFSET);
+ short src = (short)(enc.reg(in(2)) - x86_64_v2.XMM_OFFSET);
+
+ // Fopcode
+ enc.add1(0xF2);
+ // rex prefix must come next (REX.W is not set)
+ x86_64_v2.rexF(dst, src, 0, false, enc);
+
+ enc.add1(0x0F).add1(0x5E);
+
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src));
+ }
+
+
+ // General form: "divf dst /= src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" /= ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java
new file mode 100644
index 00000000..87b8b0c3
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java
@@ -0,0 +1,33 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class DivX86 extends MachConcreteNode implements MachNode {
+ DivX86( Node div ) { super(div); }
+ @Override public String op() { return "div"; }
+ @Override public RegMask regmap(int i) {
+ return (i==1) ? x86_64_v2.RAX_MASK : x86_64_v2.RMASK;
+ }
+ @Override public RegMask outregmap() { return x86_64_v2.RAX_MASK; }
+ @Override public RegMask killmap() { return x86_64_v2.RDX_MASK; }
+
+ @Override public void encoding( Encoding enc ) {
+ // REX.W + F7 /7 IDIV r/m64
+ short src = enc.reg(in(2));
+ // sign extend rax before 128/64 bits division
+ // sign extend rax to rdx:rax
+ enc.add1(x86_64_v2.REX_W);
+ enc.add1(0x99);
+ enc.add1(x86_64_v2.rex(0, src, 0));
+ // divide
+ enc.add1(0xF7); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 0x07, src));
+ }
+ // General form: "div dst /= src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2)));
+ }
+ @Override public String comment() { return "kill rdx"; }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java
new file mode 100644
index 00000000..e3b20003
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java
@@ -0,0 +1,47 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class FltX86 extends ConstantNode implements MachNode, RIPRelSize{
+ private byte _encSize;
+ FltX86(ConstantNode con ) { super(con); }
+ @Override public String op() { return "ld8"; }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return x86_64_v2.XMASK; }
+ @Override public boolean isClone() { return true; }
+ @Override public Node copy() { return new FltX86(this); }
+
+ @Override public void encoding( Encoding enc ) {
+ // Load the constant into a FPR
+ // movsd xmm, [rip + 0]
+ // F2 0F 10 /r MOVSD xmm1, m64
+ _encSize = 1/*Fop*/+0/*REX*/+2/*op*/+1/*MODRM*/+4/*offset*/;
+ short dst = (short)(enc.reg(this ) - x86_64_v2.XMM_OFFSET);
+ // Fopcode
+ enc.add1(0xF2);
+ // rex prefix must come next (REX.W is not set)
+ _encSize += x86_64_v2.rexF(dst, 0, 0, false, enc);
+ enc.add1(0x0F).add1(0x10);
+ // hard-code rip here
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.INDIRECT, dst, 0x05));
+ enc.add4(0);
+ enc.largeConstant(this,_con,_encSize-4,2/*ELF encoding PC32*/);
+ }
+
+ // Delta is from opcode start
+ @Override public byte encSize(int delta) {
+ // TODO: 1-byte RIP offset?
+ return (byte)_encSize;
+ }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ enc.patch4(opStart+opLen-4,delta-opLen);
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ _con.print(sb.p(code.reg(this)).p(" = #"));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java
new file mode 100644
index 00000000..07418eb0
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java
@@ -0,0 +1,34 @@
+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.Encoding;
+import com.seaofnodes.simple.node.FunNode;
+import com.seaofnodes.simple.node.MachNode;
+
+public class FunX86 extends FunNode implements MachNode {
+ FunX86( FunNode fun ) { super(fun); }
+ @Override public void postSelect(CodeGen code) {
+ super.postSelect(code);
+ // One more for RPC pre-pushed; X86 special
+ _maxArgSlot++;
+ }
+
+ @Override public void computeFrameAdjust(CodeGen code, int maxReg) {
+ super.computeFrameAdjust(code,maxReg);
+ // Alignment, but off by RPC
+ if( _hasCalls ) // If non-leaf, pad to 16b
+ _frameAdjust = ((_frameAdjust+8+8) & -16)-8;
+ }
+
+ @Override public void encoding( Encoding enc ) {
+ int sz = _frameAdjust;
+ if( sz == 0 ) return; // Skip if no frame adjust
+ // opcode: 0x83, addi rsp with immediate 8
+ enc.add1( x86_64_v2.REX_W ).add1( x86_64_v2.imm8(sz) ? 0x83 : 0x81 );
+ enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 0b101, x86_64_v2.RSP) );
+ if( x86_64_v2.imm8(sz) ) enc.add1(sz);
+ else enc.add4(sz);
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java
new file mode 100644
index 00000000..d3213ae4
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java
@@ -0,0 +1,31 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class I2f8X86 extends MachConcreteNode implements MachNode {
+ I2f8X86(Node i2f8 ) { super(i2f8); }
+ @Override public String op() { return "cvtf"; }
+ @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.WMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.XMASK; }
+
+ @Override public void encoding( Encoding enc ) {
+ // F2 0F 2A /r CVTSI2SD xmm1, r32/m32
+ short dst = (short)(enc.reg(this ) - x86_64_v2.XMM_OFFSET);
+ short src = enc.reg(in(1));
+
+ // Fopcode
+ enc.add1(0xF2);
+ // rex prefix must come next (REX.W is not set)
+ x86_64_v2.rexF(dst, src, 0, true, enc);
+
+ enc.add1(0x0F).add1(0x2A);
+
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src));
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p("(flt)").p(code.reg(in(1)));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java
new file mode 100644
index 00000000..760d6805
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java
@@ -0,0 +1,37 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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 ) {
+ short dst = enc.reg(this); // Also src1
+ enc.add1(x86_64_v2.rex(0, dst, 0));
+ // opcode; 0x81 or 0x83; 0x69 or 0x6B
+ enc.add1( opcode() + (x86_64_v2.imm8(_imm) ? 2 : 0) );
+ enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, mod(), dst) );
+ // immediate(4 bytes) 32 bits or (1 byte)8 bits
+ if( x86_64_v2.imm8(_imm) ) enc.add1(_imm);
+ else enc.add4(_imm);
+ }
+
+ // General form: "addi dst += #imm"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm);
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java
new file mode 100644
index 00000000..6d9110f2
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java
@@ -0,0 +1,76 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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;
+
+// Integer constants
+public class IntX86 extends ConstantNode implements MachNode {
+ IntX86( ConstantNode con ) { super(con); }
+ @Override public String op() {
+ return _con == Type.NIL || _con == TypeInteger.ZERO ? "xor" : "ldi";
+ }
+ @Override public boolean isClone() { return true; }
+ @Override public Node copy() { return new IntX86(this); }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ // Zero-set uses XOR kills flags
+ @Override public RegMask killmap() {
+ return _con == Type.NIL || _con == TypeInteger.ZERO ? x86_64_v2.FLAGS_MASK : null;
+ }
+
+ @Override public void encoding( Encoding enc ) {
+ short dst = enc.reg(this);
+ // Short form for zero
+ if( _con==Type.NIL || _con==TypeInteger.ZERO ) {
+ // XOR dst,dst. Can skip REX is dst is low 8, makes this a 32b
+ // xor, which will also zero the high bits.
+ if( dst >= 8 ) enc.add1(x86_64_v2.rex(dst, dst, 0));
+ enc.add1(0x33); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, dst));
+ return;
+ }
+
+ // Simply move the constant into a GPR
+ // Conditional encoding based on 64 or 32 bits
+ //REX.W + C7 /0 id MOV r/m64, imm32
+
+ long imm = ((TypeInteger)_con).value();
+ if (Integer.MIN_VALUE <= imm && imm < 0) {
+ // We need sign extension, so use imm32 into 64 bit register
+ // REX.W + C7 /0 id MOV r/m64, imm32
+ enc.add1(x86_64_v2.rex(0, dst, 0));
+ enc.add1(0xC7); // 32 bits encoding
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 0x00, dst));
+ enc.add4((int)imm);
+ } else if (0 <= imm && imm <= 0xFFFFFFFFL) {
+ // We want zero extension into 64 bit register
+ // so move 32 bit into 32 bit register which zeros
+ // the upper bits in the 64 bit register.
+ // B8+ rd id MOV r32, imm32
+ if (dst >= 8) enc.add1(0x41);
+ enc.add1(0xB8 + (dst & 0x07));
+ enc.add4((int)imm);
+ } else {
+ // Just write the full 64 bit constant
+ // REX.W + B8+ rd io MOV r64, imm64
+ enc.add1(x86_64_v2.rex(0, dst, 0));
+ enc.add1(0xB8 + (dst & 0x07));
+ enc.add8(imm); // 64 bits encoding
+ }
+ }
+
+ // Human-readable form appended to the SB. Things like the encoding,
+ // indentation, leading address or block labels not printed here.
+ // Just something like "ld4\tR17=[R18+12] // Load array base".
+ // General form: "op\tdst=src+src"
+ @Override public void asm(CodeGen code, SB sb) {
+ String reg = code.reg(this);
+ if( _con == Type.NIL || _con == TypeInteger.ZERO )
+ sb.p(reg).p(",").p(reg);
+ else
+ _con.print(sb.p(reg).p(" = #"));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java
new file mode 100644
index 00000000..d7acf8b8
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java
@@ -0,0 +1,80 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+// Jump on flags, uses flags
+public class JmpX86 extends IfNode implements MachNode, RIPRelSize {
+ String _bop;
+ JmpX86( IfNode iff, String bop ) {
+ super(iff);
+ _bop = bop;
+ }
+ @Override public String op() { return "j"+_bop; }
+ @Override public String label() { return op(); }
+ @Override public void postSelect(CodeGen code) {
+ Node set = in(1);
+ if( set==null ) return; // Never-node cutout
+ Node cmp = set.in(1);
+ // Bypass an expected Set and just reference the cmp directly
+ if( set instanceof SetX86 setx && (cmp instanceof CmpX86 || cmp instanceof CmpIX86 || cmp instanceof CmpMemX86 || cmp instanceof CmpFX86) ) {
+ _inputs.set( 1, cmp );
+ _bop = setx._bop;
+ } else
+ throw Utils.TODO();
+ }
+ @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.FLAGS_MASK; }
+ @Override public RegMask outregmap() { return null; }
+ @Override public void negate() { _bop = negate(_bop); }
+
+ @Override public void encoding( Encoding enc ) {
+ if( in(1)!=null ) {
+ int op = x86_64_v2.jumpop(_bop);
+ enc.add1(op-16).add1(0); // Short form jump
+ } else {
+ if( _bop=="!=" ) return; // Inverted, no code
+ enc.add1(0xEB).add1(0); // Never-node
+ }
+ enc.jump(this,cproj(0));
+ }
+
+ // Delta is from opcode start, but X86 measures from the end of the 2-byte encoding
+ @Override public byte encSize(int delta) {
+ if( in(1)==null && _bop=="!=" ) return 0; // Inverted never-node, no code
+ return (byte)(x86_64_v2.imm8(delta-2) ? 2 : 6);
+ }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ assert !( in(1)==null && _bop=="!=" ); // Inverted never-node, no code no patch
+ byte[] bits = enc.bits();
+ if( opLen==2 ) {
+ assert in(1)==null || bits[opStart] == x86_64_v2.jumpop(_bop)-16;
+ delta -= 2; // Offset from opcode END
+ assert (byte)delta==delta;
+ bits[opStart+1] = (byte)delta;
+ } else {
+ assert in(1)==null || bits[opStart] == x86_64_v2.jumpop(_bop)-16;
+ delta -= 6; // Offset from opcode END
+ bits[opStart] = 0x0F;
+ bits[opStart+1] = (byte)(in(1)==null ? 0xE9 : x86_64_v2.jumpop(_bop));
+ enc.patch4(opStart+2,delta);
+ }
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ if( in(1)!=null ) { // Never-node
+ String src = code.reg(in(1));
+ if( src!="flags" ) sb.p(src).p(" ");
+ } else if( _bop=="!=" ) {
+ sb.p("never");
+ return;
+ }
+ CFGNode prj = cproj(0).uctrlSkipEmpty(); // 0 is True is jump target
+ if( !prj.blockHead() ) prj = prj.cfg0();
+ sb.p(label(prj));
+ }
+
+ @Override public String comment() { return "L"+cproj(1)._nid; }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java
new file mode 100644
index 00000000..dce8a49a
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java
@@ -0,0 +1,55 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class LeaX86 extends MachConcreteNode implements MachNode {
+ final int _scale;
+ final int _offset;
+ LeaX86( Node add, Node base, Node idx, int scale, int offset ) {
+ super(add);
+ assert scale==0 || scale==1 || scale==2 || scale==3;
+ _inputs.pop();
+ _inputs.pop();
+ _inputs.push(base);
+ _inputs.push(idx);
+ _scale = scale;
+ _offset = offset;
+ }
+
+ @Override public String op() { return "lea"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+
+ @Override public void encoding( Encoding enc ) {
+ // REX.W + 8D /r LEA r64,m
+ short dst = enc.reg(this);
+ short ptr = enc.reg(in(1));
+ short idx = enc.reg(in(2));
+ // ptr is null
+ // just do: [(index * s) + disp32]
+ if( ptr == -1 ) {
+ enc.add1(x86_64_v2.rex(dst, 0, idx));
+ enc.add1(0x8D); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.INDIRECT, dst, 0x04));
+ enc.add1(x86_64_v2.sib(_scale, idx, x86_64_v2.RBP));
+ enc.add4(_offset);
+ return;
+ }
+
+ enc.add1(x86_64_v2.rex(dst, ptr, idx));
+ enc.add1(0x8D); // opcode
+ // rsp is hard-coded here(0x04)
+ x86_64_v2.indirectAdr(_scale, idx, ptr, _offset, dst, enc);
+ }
+
+ // General form: "lea dst = base + 4*idx + 12"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ");
+ if( in(1) != null )
+ sb.p(code.reg(in(1))).p(" + ");
+ sb.p(code.reg(in(2))).p("<<").p(_scale);
+ if( _offset!=0 ) sb.p(" + #").p(_offset);
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java
new file mode 100644
index 00000000..6e72a540
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java
@@ -0,0 +1,60 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.SB;
+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; }
+ @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 ) {
+ if( decl == TypeFloat.F32) enc.add1(0xF3);
+ if( decl == TypeFloat.F64) enc.add1(0xF2);
+
+ if( decl.isa(TypeFloat.F64) )
+ dst -= (short)x86_64_v2.XMM_OFFSET;
+
+ x86_64_v2.rexF(dst, ptr, idx, decl != TypeInteger.U32 && decl != TypeFloat.F32 && decl != TypeFloat.F64, enc);
+
+ if( false ) ;
+ else if( decl == TypeFloat.F32 ) enc.add1(0x0F).add1(0x10); // F3 0F 10 /r MOVSS xmm1, m32
+ else if( decl == TypeFloat.F64 ) enc.add1(0x0F).add1(0x10); // F2 0F 10 /r MOVSD xmm1, m64
+ else if( decl.isa(TypeInteger.I8)) enc.add1(0x0F).add1(0xBE); // sign extend: REX.W + 0F BE /r MOVSX r64, r/m8
+ else if( decl == TypeInteger.I16 ) enc.add1(0x0F).add1(0xBF); // sign extend: REX.W + 0F BF /r MOVSX r64, r/m16
+ else if( decl == TypeInteger.I32 ) enc.add1(0x63); // sign extend: REX.W + 63 /r MOVSXD r64, r/m32
+ else if( decl == TypeInteger.U8 ) enc.add1(0x0F).add1(0xB6); // zero extend: REX.W + 0F B6 /r MOVZX r64, r/m8
+ else if( decl == TypeInteger.U16 ) enc.add1(0x0F).add1(0xB7); // zero extend: REX.W + 0F B7 /r MOVZX r64, r/m16
+ // Covers U32, I64/BOT, TMP
+ else if( decl.log_size()>=2 ) enc.add1(0x8B); // zero extend: 8B /r MOV r32, r/m32
+ else throw Utils.TODO();
+
+ // includes modrm internally
+ x86_64_v2.indirectAdr(scale, idx, ptr, off, dst, enc);
+ }
+
+
+ // General form: "ldN dst,[base + idx<<2 + 12]"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(",");
+ asm_address(code,sb);
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java
new file mode 100644
index 00000000..a97f3d9f
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java
@@ -0,0 +1,39 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.SB;
+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 ) {
+ // add something to memory
+ // REX.W + 01 /r | REX.W + 81 /0 id
+ // ADD [mem], imm32/reg
+ short ptr = enc.reg(ptr());
+ short idx = enc.reg(idx());
+ short src = enc.reg(val());
+
+ enc.add1(x86_64_v2.rex(src, ptr, idx));
+ // opcode
+ enc.add1( src == -1 ? 0x81: 0x01);
+
+ // includes modrm
+ x86_64_v2.indirectAdr(_scale, idx, ptr, _off, src == -1 ? 0 : src, enc);
+ if( src == -1 ) enc.add4(_imm);
+ }
+ // General form: "add [base + idx<<2 + 12] += src"
+ @Override public void asm(CodeGen code, SB sb) {
+ asm_address(code,sb);
+ if( val()==null ) {
+ if( _imm != 1 && _imm != -1 ) sb.p(" += #").p(_imm);
+ } else sb.p(" += ").p(code.reg(val()));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java
new file mode 100644
index 00000000..97586d87
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java
@@ -0,0 +1,88 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+
+import java.lang.StringBuilder;
+import java.util.BitSet;
+
+// Generic X86 memory operand base.
+// inputs:
+// ctrl - will appear for possibly RCE (i.e. array ops)
+// mem - memory dependence edge
+// base - Basic object pointer
+// idx/null - Scaled index offset, or null if none
+// val/null - Value to store, as part of an op-to-mem or op-from-mem. Null for loads, or if an immediate is being used
+// Constants:
+// offset - offset added to base. Can be zero.
+// scale - scale on index; only 0,1,2,4,8 allowed, 0 is only when index is null
+// imm - immediate value to store or op-to-mem, and only when val is null
+public abstract class MemOpX86 extends MemOpNode implements MachNode {
+ final int _off; // Limit 32 bits
+ final int _scale; // Limit 0,1,2,3
+ final int _imm; // Limit 32 bits
+ final char _sz; // Handy print name
+ MemOpX86( Node op, MemOpNode mop, Node base, Node idx, int off, int scale, int imm ) {
+ super(op,mop);
+ assert base._type instanceof TypeMemPtr && !(base instanceof AddNode);
+ assert (idx==null && scale==0) || (idx!=null && 0<= scale && scale<=3);
+
+ // Copy memory parts from e.g. the LoadNode over the opcode, e.g. an Add
+ if( op != mop ) {
+ _inputs.set(0,mop.in(0)); // Control from mem op
+ _inputs.set(1,mop.in(1)); // Memory from mem op
+ _inputs.set(2,base); // Base handed in
+ }
+
+ assert ptr() == base;
+ _inputs.setX(3,idx);
+ _off = off;
+ _scale = scale;
+ _imm = imm;
+ _sz = (char)('0'+(1<<_declaredType.log_size()));
+ }
+
+ // Store-based flavors have a value edge
+ MemOpX86( Node op, MemOpNode mop, Node base, Node idx, int off, int scale, int imm, Node val ) {
+ this(op,mop,base,idx,off,scale,imm);
+ _inputs.setX(4,val);
+ }
+
+ Node idx() { return in(3); }
+ Node val() { return in(4); } // Only for stores, including op-to-memory
+
+ @Override public StringBuilder _printMach(StringBuilder sb, BitSet visited) {
+ return sb.append(".").append(_name);
+ }
+
+ @Override public String label() { return op(); }
+ @Override public Type compute() { throw Utils.TODO(); }
+ @Override public Node idealize() { throw Utils.TODO(); }
+
+ // Register mask allowed on input i.
+ @Override public RegMask regmap(int i) {
+ if( i==1 ) return null; // Memory
+ if( i==2 ) return x86_64_v2.RMASK; // base in GPR
+ if( i==3 ) return x86_64_v2.RMASK; // index in GPR
+ if( i==4 ) return _sz >= 2
+ ? x86_64_v2.MEM_MASK // value in GPR or XMM
+ : x86_64_v2.RMASK; // Bytes and shorts in GPR only
+ return null; // Anti-dependence
+ }
+
+ // "[base + idx<<2 + 12]"
+ SB asm_address(CodeGen code, SB sb) {
+ sb.p("[").p(code.reg(ptr()));
+ if( idx() != null ) {
+ sb.p("+").p(code.reg(idx()));
+ if( _scale != 0 )
+ sb.p("*").p((1<<_scale));
+ }
+ if( _off != 0 )
+ sb.p("+").p(_off);
+ return sb.p("]");
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java
new file mode 100644
index 00000000..c1edb082
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class MulFX86 extends MachConcreteNode implements MachNode {
+ MulFX86( Node mulf) { super(mulf); }
+ @Override public String op() { return "mulf"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.XMASK; }
+ @Override public int twoAddress() { return 1; }
+ @Override public boolean commutes() { return true; }
+
+ @Override public void encoding( Encoding enc ) {
+ // F2 0F 59 /r MULSD xmm1,xmm2/m64
+ short dst = (short)(enc.reg(this ) - x86_64_v2.XMM_OFFSET);
+ short src = (short)(enc.reg(in(2)) - x86_64_v2.XMM_OFFSET);
+
+ // Fopcode
+ enc.add1(0xF2);
+ // rex prefix must come next (REX.W is not set)
+ x86_64_v2.rexF(dst, src, 0, false, enc);
+
+ enc.add1(0x0F).add1(0x59).add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src));
+ }
+
+ // General form: "mulf dst *= src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" *= ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java
new file mode 100644
index 00000000..a9a83fb7
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java
@@ -0,0 +1,11 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.node.Node;
+
+public class MulIX86 extends ImmX86 {
+ MulIX86( Node add, int imm ) { super(add,imm); }
+ @Override public String op() { return "muli"; }
+ @Override public String glabel() { return "*"; }
+ @Override int opcode() { return 0x69; }
+ @Override int mod() { return 0; }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java
new file mode 100644
index 00000000..3cd1f795
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java
@@ -0,0 +1,29 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class MulX86 extends MachConcreteNode implements MachNode {
+ MulX86( Node mul ) { super(mul); }
+ @Override public String op() { return "mul"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ @Override public int twoAddress() { return 1; }
+ @Override public boolean commutes() { return true; }
+
+ @Override public void encoding( Encoding enc ) {
+ // REX.W + 0F AF /r IMUL r64, r/m64
+ short dst = enc.reg(this ); // src1
+ short src = enc.reg(in(2)); // src2
+ enc.add1(x86_64_v2.rex(dst, src, 0));
+ enc.add1(0x0F); // opcode
+ enc.add1(0xAF); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src));
+ }
+
+ // General form: "mul dst *= src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java
new file mode 100644
index 00000000..b8b83cb0
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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 ) {
+ short dst = enc.reg(this );
+ enc.add1(x86_64_v2.rex(0, dst, 0));
+ enc.add1(0xF7); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 3, dst));
+ }
+ @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java
new file mode 100644
index 00000000..0439adfd
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java
@@ -0,0 +1,25 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeFunPtr;
+
+public class NewX86 extends NewNode implements MachNode {
+ // A pre-zeroed chunk of memory.
+ NewX86( NewNode nnn ) { super(nnn); }
+ @Override public void encoding( Encoding enc ) {
+ enc.external(this,"calloc");
+ // ldi rcx,#1 // number of elements to calloc
+ enc.add1(0xB8 + _arg2Reg).add4(1);
+ // E8 cd CALL rel32;
+ enc.add1(0xE8);
+ enc.add4(0); // offset
+ }
+ // General form: "alloc #bytes PC"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p("ldi rcx = #1\n");
+ sb.p("call #calloc");
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java
new file mode 100644
index 00000000..b23249b8
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java
@@ -0,0 +1,39 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class NotX86 extends MachConcreteNode implements MachNode {
+ NotX86(NotNode not) { super(not); }
+ @Override public String op() { return "not"; }
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.RMASK; }
+ @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; }
+
+ @Override public void encoding( Encoding enc ) {
+ assert !(in(1) instanceof NotNode); // Cleared out by peeps
+ assert !(in(1) instanceof BoolNode); // Cleared out by peeps
+ short dst = enc.reg(this );
+ short src = enc.reg(in(1));
+
+ // Pre-zero using XOR dst,dst; since zero'd will not have a
+ // byte-dependency from the setz. Can skip REX is dst is low 8, makes
+ // this a 32b xor, which will also zero the high bits.
+ if( dst >= 8 ) enc.add1(x86_64_v2.rex(dst, dst, 0));
+ enc.add1(0x33); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, dst));
+
+ // test rdi,rdi
+ enc.add1(x86_64_v2.rex(src, src, 0));
+ enc.add1(0x85);
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, src, src));
+
+ // setz (sete dil)
+ enc.add1(x86_64_v2.rex(dst, 0, 0));
+ enc.add1(0x0F);
+ enc.add1(0x94);
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, 0));
+ }
+ @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java
new file mode 100644
index 00000000..15f4824d
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java
@@ -0,0 +1,11 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.node.Node;
+
+public class OrIX86 extends ImmX86 {
+ OrIX86( Node add, int imm ) { super(add,imm); }
+ @Override public String op() { return "ori"; }
+ @Override public String glabel() { return "|"; }
+ @Override int opcode() { return 0x81; }
+ @Override int mod() { return 1; }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java
new file mode 100644
index 00000000..bc8f0ea8
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java
@@ -0,0 +1,11 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.node.Node;
+
+public class OrX86 extends RegX86 {
+ OrX86( Node add ) { super(add); }
+ @Override public String op() { return "or"; }
+ @Override public String glabel() { return "|"; }
+ @Override int opcode() { return 0x0B; }
+ @Override public boolean commutes() { return true; }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java
new file mode 100644
index 00000000..7a6dbb13
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.ParmNode;
+
+public class ParmX86 extends ParmNode implements MachNode {
+ final RegMask _rmask;
+ ParmX86( ParmNode parm ) {
+ super(parm);
+ _rmask = x86_64_v2.callInMask(fun().sig(),_idx);
+ }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return _rmask; }
+ @Override public void encoding( Encoding enc ) { }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java
new file mode 100644
index 00000000..046c7b4c
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java
@@ -0,0 +1,15 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.ProjNode;
+
+public class ProjX86 extends ProjNode implements MachNode {
+ ProjX86( ProjNode p ) { super(p); }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() {
+ return ((MachNode)in(0)).outregmap(_idx);
+ }
+ @Override public void encoding( Encoding enc ) {}
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java
new file mode 100644
index 00000000..0997644d
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java
@@ -0,0 +1,26 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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 ) {
+ // REX.W + 01 /r
+ short dst = enc.reg(in(1)); // src1
+ short src = enc.reg(in(2)); // src2
+
+ enc.add1(x86_64_v2.rex(dst, src, 0));
+ enc.add1(opcode()); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src));
+ }
+ // General form: "add dst += src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" ").p(glabel()).p("= ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java
new file mode 100644
index 00000000..a35d43a2
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java
@@ -0,0 +1,19 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class RetX86 extends ReturnNode implements MachNode {
+ RetX86( ReturnNode ret, FunNode fun ) { super(ret, fun); fun.setRet(this); }
+ @Override public void encoding( Encoding enc ) {
+ int sz = fun()._frameAdjust;
+ if( sz != 0 ) {
+ enc.add1( x86_64_v2.REX_W ).add1( x86_64_v2.imm8(sz) ? 0x83 : 0x81 );
+ enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 0b000, x86_64_v2.RSP) );
+ if( x86_64_v2.imm8(sz) ) enc.add1(sz);
+ else enc.add4(sz);
+ }
+ enc.add1(0xC3);
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java
new file mode 100644
index 00000000..07256604
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java
@@ -0,0 +1,40 @@
+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.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.Node;
+
+import com.seaofnodes.simple.node.*;
+
+// Arithmetic Right Shift
+public class SarIX86 extends MachConcreteNode implements MachNode {
+ final int _imm;
+ SarIX86( Node sari, int imm ) { super(sari); assert x86_64_v2.imm8(imm); _inputs.pop(); _imm = imm; }
+ @Override public String op() { return "sari"; }
+ @Override public String glabel() { return ">>"; }
+ int opcode() { return 0xC1; }
+ int mod() { return 7; }
+
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ @Override public int twoAddress() { return 1; }
+
+ @Override public void encoding(Encoding enc) {
+ short dst = enc.reg(this); // Also src1
+ enc.add1(x86_64_v2.rex(0, dst, 0));
+ enc.add1( opcode());
+
+ enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, mod(), dst) );
+
+ // immediate(4 bytes) 32 bits or (1 byte)8 bits
+ if( x86_64_v2.imm8(_imm) ) enc.add1(_imm);
+ else enc.add4(_imm);
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm);
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java
new file mode 100644
index 00000000..b1be643c
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java
@@ -0,0 +1,24 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.Node;
+
+public class SarX86 extends RegX86 {
+ SarX86( Node add ) { super(add); }
+ @Override public String op() { return "sar"; }
+ @Override public String glabel() { return ">>"; }
+ @Override public RegMask regmap(int i) {
+ if (i == 1) return x86_64_v2.WMASK;
+ if (i == 2) return x86_64_v2.RCX_MASK;
+ throw Utils.TODO();
+ }
+ @Override int opcode() { return 0xD3; }
+ @Override public void encoding( Encoding enc ) {
+ short dst = enc.reg(this ); // src1
+ short src = enc.reg(in(2)); // src2
+ enc.add1(x86_64_v2.rex(0, dst, 0));
+ enc.add1(opcode()); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 7, dst));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java
new file mode 100644
index 00000000..b2902086
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java
@@ -0,0 +1,53 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+// Corresponds to the x86 instruction "sete && setne".
+// Use result of comparison without jump.
+public class SetX86 extends MachConcreteNode implements MachNode {
+ final String _bop; // One of <,<=,==
+ final boolean _unsigned;
+ // Constructor expects input is an X86 and not an Ideal node.
+ SetX86( Node cmp, String bop, boolean unsigned ) {
+ super(cmp);
+ _inputs.setLen(1); // Pop the cmp inputs
+ // Replace with the matched cmp
+ _inputs.push(cmp);
+ _bop = bop;
+ _unsigned = unsigned;
+ }
+ @Override public String op() { return "set"+_bop; }
+ @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.FLAGS_MASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+
+ @Override public void encoding( Encoding enc ) {
+ // REX + 0F 94
+ short dst = enc.reg(this );
+
+ // Optional rex, for dst
+ if( dst >= 8 ) enc.add1(x86_64_v2.rex(0, dst, 0));
+ enc.add1(0x0F); // opcode
+ enc.add1(switch (_bop) {
+ case "==" -> 0x94; // SETE
+ case "<" -> _unsigned ? 0x92 : 0x9C; // SETB /SETL
+ case "<=" -> _unsigned ? 0x96 : 0x9E; // SETBE/SETLE
+ case ">=" -> _unsigned ? 0x93 : 0x9D; // SETAE/SETGE
+ default -> throw Utils.TODO();
+ });
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 0, dst));
+
+ // low 8 bites are set, now zero extend for next instruction
+ if( dst >= 8 ) enc.add1(x86_64_v2.rex(0, dst, 0));
+ enc.add1(0x0F); // opcode
+ enc.add1(0xB6); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, dst));
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this));
+ String src = code.reg(in(1));
+ if( src!="flags" ) sb.p(" = ").p(src);
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java
new file mode 100644
index 00000000..0b75b456
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java
@@ -0,0 +1,40 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.node.Node;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class ShlIX86 extends MachConcreteNode implements MachNode {
+ final int _imm;
+ ShlIX86( Node shli, int imm ) {
+ super(shli);
+ assert x86_64_v2.imm8(imm);
+ _inputs.pop(); // Pop constant input off
+ _imm = imm;
+ }
+ public int opcode() { return 0xC1; }
+ public int mod() { return 4; }
+
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ @Override public int twoAddress() { return 1; }
+
+ @Override public String op() { return "shli"; }
+ @Override public String glabel() { return "<<"; }
+ @Override public void encoding(Encoding enc) {
+ short dst = enc.reg(this); // Also src1
+ enc.add1(x86_64_v2.rex(0, dst, 0));
+ enc.add1( opcode());
+
+ enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, mod(), dst) );
+
+ // immediate(4 bytes) 32 bits or (1 byte)8 bits
+ if( x86_64_v2.imm8(_imm) ) enc.add1(_imm);
+ else enc.add4(_imm);
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm);
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java
new file mode 100644
index 00000000..9f358575
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java
@@ -0,0 +1,24 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.Node;
+
+public class ShlX86 extends RegX86 {
+ ShlX86( Node add ) { super(add); }
+ @Override public String op() { return "shl"; }
+ @Override public String glabel() { return "<<"; }
+ @Override public RegMask regmap(int i) {
+ if (i == 1) return x86_64_v2.WMASK;
+ if (i == 2) return x86_64_v2.RCX_MASK;
+ throw Utils.TODO();
+ }
+ @Override int opcode() { return 0xD3; }
+ @Override public final void encoding( Encoding enc ) {
+ short dst = enc.reg(this ); // src1
+ short src = enc.reg(in(2)); // src2
+ enc.add1(x86_64_v2.rex(0, dst, 0));
+ enc.add1(opcode()); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 4, dst));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java
new file mode 100644
index 00000000..893eb23d
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java
@@ -0,0 +1,35 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.node.Node;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class ShrIX86 extends MachConcreteNode implements MachNode {
+ final int _imm;
+ ShrIX86( Node shri, int imm ) { super(shri); assert x86_64_v2.imm8(imm); _inputs.pop(); _imm = imm; }
+ @Override public String op() { return "shri"; }
+ @Override public String glabel() { return ">>>"; }
+ int opcode() { return 0xC1; }
+ int mod() { return 5; }
+
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ @Override public int twoAddress() { return 1; }
+
+ @Override public void encoding(Encoding enc) {
+ short dst = enc.reg(this); // Also src1
+ enc.add1(x86_64_v2.rex(0, dst, 0));
+ enc.add1( opcode());
+
+ enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, mod(), dst) );
+
+ // immediate(4 bytes) 32 bits or (1 byte)8 bits
+ if( x86_64_v2.imm8(_imm) ) enc.add1(_imm);
+ else enc.add4(_imm);
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm);
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java
new file mode 100644
index 00000000..d869260f
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java
@@ -0,0 +1,24 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.Node;
+
+public class ShrX86 extends RegX86 {
+ ShrX86( Node add ) { super(add); }
+ @Override public String op() { return "shr"; }
+ @Override public String glabel() { return ">>>"; }
+ @Override public RegMask regmap(int i) {
+ if (i == 1) return x86_64_v2.WMASK;
+ if (i == 2) return x86_64_v2.RCX_MASK;
+ throw Utils.TODO();
+ }
+ @Override int opcode() { return 0xD3; }
+ @Override public final void encoding( Encoding enc ) {
+ short dst = enc.reg(this ); // src1
+ short src = enc.reg(in(2)); // src2
+ enc.add1(x86_64_v2.rex(0, dst, 0));
+ enc.add1(opcode()); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 5, dst));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java
new file mode 100644
index 00000000..f965157b
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java
@@ -0,0 +1,100 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.FunNode;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.node.SplitNode;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFloat;
+import com.seaofnodes.simple.type.TypeInteger;
+
+public class SplitX86 extends SplitNode {
+ SplitX86( String kind, byte round ) { super(kind,round, new Node[2]); }
+ @Override public String op() { return "mov"; }
+ @Override public RegMask regmap(int i) { return x86_64_v2.SPLIT_MASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.SPLIT_MASK; }
+
+ // Need to handle 8 cases: . reg->reg, reg->xmm, reg->flags, xmm->reg, xmm->xmm, xmm->flags, flags->reg, flags->xmm,
+ // flags->flags.
+ @Override public void encoding( Encoding enc ) {
+ // REX.W + 8B /r MOV r64, r/m64
+ short dst = enc.reg(this );
+ short src = enc.reg(in(1));
+ if( dst == x86_64_v2.FLAGS ) {
+ // mov reg, flags
+ // push rcx
+ // popf (Pop the top of the stack into the FLAGS register)
+ // 50+rd PUSH r64
+ x86_64_v2.rexF(0,src,0,false,enc);
+ enc.add1(0x50 + src);
+ // popf
+ enc.add1(0x9D);
+ return;
+ }
+ if( src == x86_64_v2.FLAGS ) {
+ // mov flags, reg
+ // pushf; pop reg
+ enc.add1(0x9C);
+ // 58+ rd POP r64
+ x86_64_v2.rexF(0,dst,0,false, enc);
+ enc.add1(0x58 + dst);
+ return;
+ }
+
+ // Flag for being either XMM or stack
+ boolean dstX = dst >= x86_64_v2.XMM_OFFSET;
+ boolean srcX = src >= x86_64_v2.XMM_OFFSET;
+
+ // Stack spills
+ if( dst >= x86_64_v2.MAX_REG ) {
+ if( src >= x86_64_v2.MAX_REG )
+ throw Utils.TODO(); // Very rare stack-stack move
+ int off = enc._fun.computeStackOffset(enc._code,dst);
+ 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;
+ }
+
+ // reg-reg move. Adjust numbering for GPR vs FPR reg set
+ if( dstX ) dst -= (short) x86_64_v2.XMM_OFFSET;
+ if( srcX ) src -= (short) x86_64_v2.XMM_OFFSET;
+
+ // 0x66 if moving between register classes
+ if( dstX ^ srcX ) enc.add1(0x66);
+ if( !dstX && srcX )
+ { short tmp=src; src=dst; dst=tmp; }
+ enc.add1(x86_64_v2.rex(dst, src, 0));
+
+ // pick opcode based on regs
+ if( !dstX && !srcX ) {
+ // reg->reg (MOV r64, r/m64)
+ enc.add1(0x8B);
+ } else if( dstX && srcX ) {
+ // xmm->xmm (NP 0F 28 /r MOVAPS xmm1, xmm2/m128)
+ enc.add1(0x0F);
+ enc.add1(0x28);
+ } else if( dstX && !srcX ) {
+ // reg->xmm (66 REX.W 0F 6E /r MOVQ xmm, r/m64)
+ enc.add1(0x0F);
+ enc.add1(0x6E);
+ } else if( !dstX && srcX ) {
+ // xmm->reg(66 0F 7E /r MOVQ r/m64, xmm)
+ enc.add1(0x0F);
+ enc.add1(0x7E);
+ }
+
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src));
+ }
+
+ // 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/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java
new file mode 100644
index 00000000..c906c3f2
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java
@@ -0,0 +1,85 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.ConstantNode;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.node.StoreNode;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFloat;
+import java.util.BitSet;
+
+public class StoreX86 extends MemOpX86 {
+ StoreX86( StoreNode st, Node base, Node idx, int off, int scale, int imm, Node val ) {
+ super(st,st, base, idx, off, scale, imm, val);
+ }
+ @Override public String op() { return "st"+_sz; }
+ @Override public StringBuilder _printMach(StringBuilder sb, BitSet visited) {
+ Node val = val();
+ sb.append(".").append(_name).append("=");
+ if( val==null ) sb.append(_imm);
+ else val._print0(sb,visited);
+ return sb.append(";");
+ }
+ // Register mask allowed as a result. 0 for no register.
+ @Override public RegMask outregmap() { return null; }
+ @Override public void encoding( Encoding enc ) {
+ // REX.W + C7 /0 id MOV r/m64, imm32 |
+ // REX.W + 89 /r MOV r/m64, r64
+ short ptr = enc.reg(ptr());
+ short idx = enc.reg(idx());
+ short src = enc.reg(val());
+
+ if( src == -1 ) {
+ int log = _declaredType.log_size();
+ x86_64_v2.rexF(-1, ptr, idx, log==3, enc);
+ switch( log ) {
+ case 0: enc .add1(0xC6); break;
+ case 1: enc.add1(0x66).add1(0xC7); break;
+ case 2: enc .add1(0xC7); break;
+ case 3: enc .add1(0xC7); break;
+ }
+ x86_64_v2.indirectAdr(_scale, idx, ptr, _off, src, enc);
+ switch( log ) {
+ case 0: enc.add1(_imm); break;
+ case 1: enc.add2(_imm); break;
+ case 2: enc.add4(_imm); break;
+ case 3: enc.add8(_imm); break;
+ }
+ } 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 ) {
+ int log = decl.log_size();
+ // Float reg being stored
+ if( src >= x86_64_v2.XMM_OFFSET ) {
+ src -= (short)x86_64_v2.XMM_OFFSET;
+ assert log == 2 || log == 3;
+ enc.add1( log==2 ? 0xF3 : 0xF2 );
+ x86_64_v2.rexF(src,ptr,idx,false,enc);
+ enc.add1(0x0F).add1(0x11);
+ } else {
+ // byte stores from sil, dil, bpl, spl need a rex
+ if( log == 0 && src >= x86_64_v2.RSP ) enc.add1(x86_64_v2.rex(src,ptr,idx,false));
+ else x86_64_v2.rexF(src,ptr,idx,log==3,enc);
+ switch( log ) {
+ case 0: enc .add1(0x88); break;
+ case 1: enc.add1(0x66).add1(0x89); break;
+ case 2: enc .add1(0x89); break;
+ case 3: enc .add1(0x89); break;
+ }
+ }
+ x86_64_v2.indirectAdr(scale, idx, ptr, off, src, enc);
+ }
+
+
+ // General form: "stN [base + idx<<2 + 12],val"
+ @Override public void asm(CodeGen code, SB sb) {
+ asm_address(code,sb).p(",");
+ if( val()==null ) sb.p("#").p(_imm);
+ else sb.p(code.reg(val()));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java
new file mode 100644
index 00000000..0a169a22
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java
@@ -0,0 +1,31 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class SubFX86 extends MachConcreteNode implements MachNode {
+ SubFX86( Node subf) { super(subf); }
+ @Override public String op() { return "subf"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.XMASK; }
+ @Override public int twoAddress() { return 1; }
+
+ @Override public void encoding( Encoding enc ) {
+ // F2 0F 5C /r SUBSD xmm1, xmm2/m64
+ short dst = (short)(enc.reg(this ) - x86_64_v2.XMM_OFFSET);
+ short src = (short)(enc.reg(in(2)) - x86_64_v2.XMM_OFFSET);
+ // Fopcode
+ enc.add1(0xF2);
+ // rex prefix must come next (REX.W is not set)
+ x86_64_v2.rexF(dst, src, 0, false, enc);
+
+ enc.add1(0x0F).add1(0x5C);
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src));
+ }
+
+ // General form: "subf dst -= src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" -= ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java
new file mode 100644
index 00000000..8da34bf5
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java
@@ -0,0 +1,10 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.node.Node;
+
+public class SubX86 extends RegX86 {
+ SubX86( Node add ) { super(add); }
+ @Override public String op() { return "sub"; }
+ @Override public String glabel() { return "-"; }
+ @Override int opcode() { return 0x2B; }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java
new file mode 100644
index 00000000..be4d1a83
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java
@@ -0,0 +1,66 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.*;
+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;
+
+// Function constants
+public class TFPX86 extends ConstantNode implements MachNode, RIPRelSize {
+ TFPX86( ConstantNode con ) { super(con); }
+ @Override public String op() {
+ return _con == Type.NIL ? "xor" : "ldx";
+ }
+ @Override public boolean isClone() { return true; }
+ @Override public Node copy() { return new TFPX86(this); }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ // Zero-set uses XOR kills flags
+ @Override public RegMask killmap() {
+ return _con == Type.NIL ? x86_64_v2.FLAGS_MASK : null;
+ }
+
+ @Override public void encoding( Encoding enc ) {
+ short dst = enc.reg(this);
+ // Short form for zero
+ if( _con==Type.NIL ) {
+ // XOR dst,dst. Can skip REX is dst is low 8, makes this a 32b
+ // xor, which will also zero the high bits.
+ if( dst >= 8 ) enc.add1(x86_64_v2.rex(dst, dst, 0));
+ enc.add1(0x33); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, dst));
+ return;
+ }
+
+ // lea to load function pointer address
+ // lea rax, [rip+disp32]
+ enc.relo(this);
+ enc.add1(x86_64_v2.rex(dst, 0, 0));
+ enc.add1(0x8D); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.INDIRECT, dst, 0b101));
+ enc.add4(0);
+ }
+
+ @Override public byte encSize(int delta) {
+ return 7;
+ }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ enc.patch4(opStart + 3, delta - 7);
+ }
+
+ // Human-readable form appended to the SB. Things like the encoding,
+ // indentation, leading address or block labels not printed here.
+ // Just something like "ld4\tR17=[R18+12] // Load array base".
+ // General form: "op\tdst=src+src"
+ @Override public void asm(CodeGen code, SB sb) {
+ String reg = code.reg(this);
+ if( _con == Type.NIL )
+ sb.p(reg).p(",").p(reg);
+ else
+ _con.print(sb.p(reg).p(" = #"));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java
new file mode 100644
index 00000000..ec4eeb3b
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java
@@ -0,0 +1,53 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.Type;
+import java.util.BitSet;
+
+// unconditional jump
+public class UJmpX86 extends CFGNode implements MachNode, RIPRelSize {
+ UJmpX86() { }
+ @Override public String op() { return "jmp"; }
+ @Override public String label() { return op(); }
+ @Override public StringBuilder _print1( StringBuilder sb, BitSet visited ) {
+ return sb.append("jmp ");
+ }
+ @Override public RegMask regmap(int i) {return null; }
+ @Override public RegMask outregmap() { return null; }
+ @Override public Type compute() { throw Utils.TODO(); }
+ @Override public Node idealize() { throw Utils.TODO(); }
+ @Override public void encoding( Encoding enc ) {
+ enc.jump(this,uctrl());
+ // Short-form jump
+ enc.add1(0xEB).add1(0);
+ //// E9 cd JMP rel32
+ //enc.add1(0xE9).add4(0);
+ }
+
+ // Delta is from opcode start, but X86 measures from the end of the 2-byte encoding
+ @Override public byte encSize(int delta) { return (byte)(x86_64_v2.imm8(delta-2) ? 2 : 5); }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ byte[] bits = enc.bits();
+ if( opLen==2 ) {
+ assert bits[opStart] == (byte)0xEB;
+ delta -= 2; // Offset from opcode END
+ assert (byte)delta==delta;
+ bits[opStart+1] = (byte)delta;
+ } else {
+ assert bits[opStart] == (byte)0xEB;
+ bits[opStart] = (byte)0xE9; // Long form
+ delta -= 5; // Offset from opcode END
+ enc.patch4(opStart+1,delta);
+ }
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ CFGNode target = uctrl();
+ //assert target.nOuts() > 1; // Should optimize jmp to empty targets
+ sb.p(label(target));
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java
new file mode 100644
index 00000000..2d989a50
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java
@@ -0,0 +1,11 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.node.Node;
+
+public class XorIX86 extends ImmX86 {
+ XorIX86( Node add, int imm ) { super(add,imm); }
+ @Override public String op() { return "xori"; }
+ @Override public String glabel() { return "^"; }
+ @Override int opcode() { return 0x81; }
+ @Override int mod() { return 6; }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java
new file mode 100644
index 00000000..86703903
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java
@@ -0,0 +1,11 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.node.Node;
+
+public class XorX86 extends RegX86 {
+ XorX86( Node add ) { super(add); }
+ @Override public String op() { return "xor"; }
+ @Override public String glabel() { return "^"; }
+ @Override int opcode() { return 0x33; }
+ @Override public boolean commutes() { return true; }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
new file mode 100644
index 00000000..23c12ae7
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
@@ -0,0 +1,644 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.Utils;
+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; }
+
+ public static final int RAX = 0, RCX = 1, RDX = 2, RBX = 3, RSP = 4, RBP = 5, RSI = 6, RDI = 7;
+ public static final int R08 = 8, R09 = 9, R10 = 10, R11 = 11, R12 = 12, R13 = 13, R14 = 14, R15 = 15;
+
+ public static final int XMM0 = 16, XMM1 = 17, XMM2 = 18, XMM3 = 19, XMM4 = 20, XMM5 = 21, XMM6 = 22, XMM7 = 23;
+ public static final int XMM8 = 24, XMM9 = 25, XMM10 = 26, XMM11 = 27, XMM12 = 28, XMM13 = 29, XMM14 = 30, XMM15 = 31;
+ public static final int FLAGS = 32;
+ public static final int MAX_REG = 33;
+ public static final int RPC = 33;
+
+ // Human-readable name for a register number, e.g. "RAX".
+ public static final String[] REGS = new String[]{
+ "rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi",
+ "r8" , "r9" , "r10", "r11", "r12", "r13", "r14", "r15",
+ "xmm0", "xmm1", "xmm2" , "xmm3" , "xmm4" , "xmm5" , "xmm6" , "xmm7" ,
+ "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15",
+ "flags",
+ };
+ // Hard crash for bad register number, fix yer bugs!
+ @Override public String[] regs() { return REGS; }
+
+ public static int XMM_OFFSET = 16;
+ // General purpose register mask: pointers and ints, not floats
+ static final long RD_BITS = 0b1111111111111111L; // All the GPRs
+ static RegMask RMASK = new RegMask(RD_BITS);
+ // No RSP in the *write* general set.
+ static final long WR_BITS = 0b1111111111101111L; // All the GPRs minus RSP
+ static RegMask WMASK = new RegMask(WR_BITS);
+ // Xmm register mask
+ static final long FP_BITS = 0b1111111111111111L << XMM0; // All the XMMs
+ static RegMask XMASK = new RegMask(FP_BITS);
+ static RegMask FLAGS_MASK = new RegMask(FLAGS);
+ static RegMask RPC_MASK = new RegMask(RPC);
+
+ static final long SPILLS = -(1L << MAX_REG);
+ static final RegMask SPLIT_MASK = new RegMask(WR_BITS | FP_BITS /* | (1L<<FLAGS)*/ | SPILLS, -1L );
+
+ // Load/store mask; both GPR and FPR
+ static RegMask MEM_MASK = new RegMask(WR_BITS | FP_BITS);
+
+ public static RegMask RAX_MASK = new RegMask(RAX);
+ public static RegMask RCX_MASK = new RegMask(RCX);
+ public static RegMask RDX_MASK = new RegMask(RDX);
+ public static RegMask RDI_MASK = new RegMask(RDI);
+ public static RegMask R08_MASK = new RegMask(R08);
+ public static RegMask R09_MASK = new RegMask(R09);
+ public static RegMask RSI_MASK = new RegMask(RSI);
+
+ public static RegMask XMM0_MASK = new RegMask(XMM0);
+
+ // Encoding
+ public static int REX = 0x40;
+ public static int REX_W = 0x48;
+ public static int REX_WR = 0x4C;
+ public static int REX_WRB= 0x4D;
+ public static int REX_WB = 0x49;
+
+ public enum MOD {
+ INDIRECT, // [mem]
+ INDIRECT_disp8, // [mem + 0x12]
+ INDIRECT_disp32, // [mem + 0x12345678]
+ DIRECT, // mem
+ };
+
+ // opcode included here
+ // 0F 84 cd JE rel32
+ // 0F 85 cd JNE rel32
+ // 0F 8F cd JG rel32
+ // 0F 8C cd JL rel32
+ // 0F 8E cd JLE rel32
+ // 0F 8D cd JGE rel32
+ static public int jumpop(String op) {
+ return switch (op) {
+ case "==" -> 0x84;
+ case "!=" -> 0x85;
+ case ">" -> 0x8F;
+ case "<" -> 0x8C;
+ case "<=" -> 0x8E;
+ case ">=" -> 0x8D;
+ default -> throw new IllegalArgumentException("Too many arguments");
+ };
+ }
+
+ public static int modrm(MOD mod, int reg, int m_r) {
+ if( reg == -1 ) reg=0; // Missing reg in this flavor
+ // combine all the bits
+ assert 0 <= reg && reg < 16;
+ assert 0 <= m_r && m_r < 16;
+ return (mod.ordinal() << 6) | ((reg & 0x07) << 3) | m_r & 0x07;
+ }
+
+ // 00 000 000
+ // same bit-layout as modrm
+ public static int sib(int scale, int index, int base) {
+ assert 0 <= base && base < 16;
+ assert 0 <= index && index < 16;
+ return (scale << 6) | ((index & 0x07) << 3) | base & 0x07;
+ }
+
+ // reg1 is reg(R)
+ // reg2 is r/mem(B)
+ // reg3 is X(index)
+ // reg4 is X(base)
+
+ // 0 denotes no direct register
+ public static int rex(int reg, int ptr, int idx, boolean wide) {
+ // assuming 64 bit by default so: 0100 1000
+ assert -1 <= reg && reg < 16;
+ assert -1 <= ptr && ptr < 16;
+ assert -1 <= idx && idx < 16;
+
+ int rex = wide ? REX_W : REX;
+ if( 8 <= reg ) rex |= 0b00000100; // REX.R
+ if( 8 <= ptr ) rex |= 0b00000001; // REX.B
+ if( 8 <= idx ) rex |= 0b00000010; // REX.X
+ return rex;
+ }
+
+ public static int rex(int reg, int ptr, int idx) {
+ return rex(reg, ptr, idx, true);
+ }
+
+ // rex for floats. Return size (0 or 1)
+ // don't need to use REX if 0x40.
+ public static byte rexF(int reg, int ptr, int idx, boolean wide, Encoding enc) {
+ int rex = rex(reg, ptr, idx, wide);
+ if (rex == REX) return 0;
+ enc.add1(rex);
+ return 1;
+ }
+ // Function used for encoding indirect memory addresses
+ // Does not always generate SIB byte e.g index == -1.
+ // -1 denotes empty value, not set - note 0 is different from -1 as it can represent rax.
+ // Looks for best mod locally
+ public static void indirectAdr( int scale, short index, short base, int offset, int reg, Encoding enc ) {
+ // Assume indirect
+ assert 0 <= base && base < 16;
+ assert -1 <= index && index < 16 && index != RSP;
+
+ MOD mod = MOD.INDIRECT;
+ // is 1 byte enough or need more?
+ if( offset != 0 )
+ mod = imm8(offset)
+ ? MOD.INDIRECT_disp8
+ : MOD.INDIRECT_disp32;
+
+ if( mod == MOD.INDIRECT && (base == RBP || base == R13) ) {
+ mod = MOD.INDIRECT_disp8;
+ } else if( index == -1 && (base == RSP || base == R12) ) {
+ index = RSP;
+ }
+
+ if( index == -1 ) {
+ enc.add1(modrm(mod, reg, base));
+ } else {
+ enc.add1(modrm(mod, reg, x86_64_v2.RSP));
+ enc.add1(sib(scale, index, base));
+ }
+
+ if( mod == MOD.INDIRECT_disp8 ) {
+ enc.add1(offset);
+ } else if( mod == MOD.INDIRECT_disp32 ) {
+ enc.add4(offset);
+ }
+ }
+
+
+ // Limit of float args passed in registers
+ static RegMask[] XMMS8 = new RegMask[]{
+ new RegMask(XMM0), new RegMask(XMM1), new RegMask(XMM2), new RegMask(XMM3),
+ new RegMask(XMM4), new RegMask(XMM5), new RegMask(XMM6), new RegMask(XMM7),
+ };
+
+ // Map from function signature and argument index to register.
+ // Used to set input registers to CallNodes, and ParmNode outputs.
+ @Override public RegMask callArgMask( TypeFunPtr tfp, int idx ) { return callInMask(tfp,idx); }
+ static RegMask callInMask( TypeFunPtr tfp, int idx ) {
+ if( idx==0 ) return RPC_MASK;
+ if( idx==1 ) return null;
+ return switch( CodeGen.CODE._callingConv ) {
+ case "SystemV" -> callSys5 (tfp,idx);
+ case "Win64" -> callWin64(tfp,idx);
+ default -> throw Utils.TODO();
+ };
+ }
+
+ // Maximum stack args used by this signature
+ @Override public short maxArgSlot( TypeFunPtr tfp ) {
+ return switch( CodeGen.CODE._callingConv ) {
+ case "SystemV" -> maxArgSlotSys5 (tfp);
+ case "Win64" -> maxArgSlotWin64(tfp);
+ default -> throw Utils.TODO();
+ };
+ }
+
+ // Win64 - max *4* args in registers of all kinds; after that on stack.
+ // Stack args land in increasing memory order with empty/mirror slots for
+ // every register.
+ //
+ // foo( int i0, flt f1, int i2, flt f3, int i4, flt f5, int i6, ... )
+ //
+ // -- Prior Frame --
+ // i6
+ // f5
+ // i4
+ // empty mirror, XMM3= f3
+ // empty mirror, R08 = i2
+ // empty mirror, XMM1= f1
+ // empty mirror, RCX = i0
+ // -- Caller Frame; 16b align --
+ // RPC
+ // PAD/ALIGN
+ // -- Callee Frame; 16b align --
+ static RegMask[] WIN64_CALL = new RegMask[] {
+ RCX_MASK,
+ RDX_MASK,
+ R08_MASK,
+ R09_MASK,
+ };
+
+ static RegMask callWin64(TypeFunPtr tfp, int idx ) {
+ // 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+1/*RPC*/+(idx-2));
+ return tfp.arg(idx-2) instanceof TypeFloat
+ ? XMMS8 [idx-2]
+ : WIN64_CALL[idx-2];
+ }
+ static short maxArgSlotWin64(TypeFunPtr tfp) {
+ return (short)tfp.nargs();
+ }
+
+ // Sys5: max 6 GPRs and 8 FPRS filled first. Extra args land in increasing
+ // memory order as needed - no mirror space.
+ //
+ // foo( int i0, flt f1, int i2, flt f3, int i4, flt f5, int i6, ... )
+ // RDI =i0, RSI =i2, RDX =i4, RCX =i6, R08 =i8, R09 =i10
+ // XMM0=f1, XMM1=f3, XMM2=f5, XMM3=f7, XMM4=f9, XMM5=f11, XMM6=f13, XMM7=f15
+ //
+ // -- Prior Frame --
+ // f18
+ // i18
+ // f17
+ // i16
+ // i14
+ // i12
+ // -- Caller Frame; 16b align --
+ // RPC
+ // PAD/ALIGN
+ // -- Callee Frame; 16b align --
+ // SystemV(param passing)
+ static RegMask[] SYS5_CALL = new RegMask[] {
+ RDI_MASK,
+ RSI_MASK,
+ RDX_MASK,
+ RCX_MASK,
+ R08_MASK,
+ R09_MASK,
+ };
+
+ static RegMask callSys5(TypeFunPtr tfp, int idx ) {
+ // First 6 integers passed in registers: rdi,rsi,rdx,rcx,r08,r09
+ // First 8 floats passed in registers: xmm0-xmm7
+ int icnt=0, fcnt=0; // Count of ints, floats
+ for( int i=2; i<idx; i++ ) {
+ if( tfp.arg(i-2) instanceof TypeFloat ) fcnt++;
+ else icnt++;
+ }
+ int nstk = Math.max(icnt-6,0)+Math.max(fcnt-8,0);
+ return tfp.arg(idx-2) instanceof TypeFloat
+ ? fcnt<8 ? XMMS8 [fcnt] : new RegMask(MAX_REG+1/*RPC*/+nstk)
+ : icnt<6 ? SYS5_CALL[icnt] : new RegMask(MAX_REG+1/*RPC*/+nstk);
+ }
+ static short maxArgSlotSys5(TypeFunPtr tfp) {
+ int icnt=0, fcnt=0; // Count of ints, floats
+ for( int i=0; i<tfp.nargs(); i++ ) {
+ if( tfp.arg(i) instanceof TypeFloat ) fcnt++;
+ else icnt++;
+ }
+ int nstk = Math.max(icnt-6,0)+Math.max(fcnt-8,0);
+ return (short)nstk;
+ }
+
+ static final long SYSTEM5_CALLER_SAVE =
+ (1L << RAX) | (1L << RCX) | (1L << RDX) |
+ (1L << RDI) | (1L << RSI) |
+ (1L << R08) | (1L << R09) | (1L << R10) | (1L << R11) |
+ (1L << FLAGS) | // Flags are killed
+ // All FP regs are killed
+ FP_BITS;
+
+ static final long WIN64_CALLER_SAVE =
+ (1L<< RAX) | (1L<< RCX) | (1L<< RDX) |
+ (1L<< R08) | (1L<< R09) | (1L<< R10) | (1L<< R11) |
+ (1L<<FLAGS)| // Flags are killed
+ // Only XMM0-XMM5 are killed; XMM6-XMM15 are preserved
+ (1L<<XMM0) | (1L<<XMM1) | (1L<<XMM2) | (1L<<XMM3) |
+ (1L<<XMM4) | (1L<<XMM5) ;
+
+ @Override public long callerSave() {
+ return switch (CodeGen.CODE._callingConv) {
+ case "SystemV" -> SYSTEM5_CALLER_SAVE;
+ case "Win64" -> WIN64_CALLER_SAVE;
+ default -> throw new IllegalArgumentException("Unknown calling convention: " + CodeGen.CODE._callingConv);
+ };
+ }
+ @Override public long neverSave() { return 1L<<RSP; }
+ @Override public RegMask retMask( TypeFunPtr tfp ) {
+ return tfp.ret() instanceof TypeFloat ? XMM0_MASK : RAX_MASK;
+ }
+ @Override public int rpc() { return RPC; }
+
+
+ // Create a split op; any register to any register, including stack slots
+ @Override public SplitNode split(String kind, byte round, LRG lrg) {
+ return new SplitX86(kind, round);
+ }
+
+ // Return a MachNode unconditional branch
+ @Override public CFGNode jump() {
+ return new UJmpX86();
+ }
+
+ // Instruction selection
+ @Override
+ public Node instSelect(Node n) {
+ return switch (n) {
+ case AddFNode addf -> addf(addf);
+ case AddNode add -> add(add);
+ case AndNode and -> and(and);
+ case BoolNode bool -> cmp(bool);
+ case CProjNode c -> new CProjNode(c);
+ case CallEndNode cend -> new 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 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 XCtrlNode x -> new ConstantNode(Type.XCONTROL);
+ case XorNode xor -> xor(xor);
+
+ case LoopNode loop -> new LoopNode(loop);
+ case RegionNode region -> new RegionNode(region);
+ default -> throw Utils.TODO();
+ };
+ }
+
+ public static boolean imm8( long imm ) { return -128 <= imm && imm <= 127; }
+
+ public static boolean imm32( long imm ) { return (int)imm==imm; }
+
+ // Attempt a full LEA-style break down.
+ private Node add(AddNode add) {
+ Node lhs = add.in(1);
+ Node rhs = add.in(2);
+ if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && ld._declaredType.log_size() >= 3)
+ return new AddMemX86(add, address(ld), ld.ptr(), idx, off, scale, 0, rhs);
+
+// if(rhs instanceof LoadNode ld && ld.nOuts() == 1 && ld._declaredType.log_size() >= 3) {
+// throw Utils.TODO(); // Swap load sides
+// }
+
+ // Attempt a full LEA-style break down.
+ // Returns one of AddX86, AddIX86, LeaX86, or LHS
+ if( rhs instanceof ConstantNode off2 && off2._con instanceof TypeInteger toff ) {
+ long imm = toff.value();
+ assert imm!=0; // Folded in peeps
+ if( (int)imm != imm ) // Full 64bit immediate
+ return new AddX86(add);
+ // Now imm <= 32bits
+ if( lhs instanceof AddNode ladd )
+ // ((base + (idx << scale)) + off)
+ return _lea(add, ladd.in(1), ladd.in(2), (int)imm);
+ if( lhs instanceof ShlNode shift )
+ // (idx << scale) + off; no base
+ return _lea(add, null, shift, (int)imm);
+
+ // lhs + rhs1
+ return new AddIX86(add, (int)imm);
+ }
+ return _lea(add, lhs, rhs, 0);
+ }
+
+
+ private Node addf(AddFNode addf) {
+ if(addf.in(1) instanceof LoadNode ld && ld.nOuts() == 1)
+ return new AddFMemX86(addf, address(ld), ld.ptr(), idx, off, scale, addf.in(2));
+
+// if(addf.in(2) instanceof LoadNode ld && ld.nOuts() == 1)
+// throw Utils.TODO(); // Swap load sides
+
+ return new AddFX86(addf);
+ }
+
+
+ private Node _lea(Node add, Node base, Node idx, int off) {
+ int scale = 0;
+ if( base instanceof ShlNode && !(idx instanceof ShlNode) )
+ throw Utils.TODO(); // Bug in canonicalization, should on RHS
+ if( idx instanceof ShlNode shift && shift.in(2) instanceof ConstantNode shfcon &&
+ shfcon._con instanceof TypeInteger tscale && 0 <= tscale.value() && tscale.value() <= 3 ) {
+ idx = shift.in(1);
+ scale = ((int) tscale.value());
+ }
+ // (base + idx) + off
+ return off == 0 && scale == 0
+ ? new AddX86(add)
+ : new LeaX86(add, base, idx, scale, off);
+ }
+
+
+ private Node and(AndNode and) {
+ if( and.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) )
+ return new AndIX86(and, (int)ti.value());
+ return new AndX86(and);
+ }
+
+ private Node call(CallNode call) {
+ return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp
+ ? new CallX86(call, tfp)
+ : new CallRX86(call);
+ }
+
+ // Because X86 flags, a normal ideal Bool is 2 X86 ops: a "cmp" and at "setz".
+ // Ideal If reading from a setz will skip it and use the "cmp" instead.
+ private static boolean swap, unsigned;
+ private Node cmp( BoolNode bool ) {
+ swap = unsigned = false;
+ Node cmp = _cmp(bool);
+ return new SetX86(cmp, swap ? IfNode.swap(bool.op()) : bool.op(), unsigned);
+ }
+
+ private Node _cmp(BoolNode bool) {
+ // Float variant
+ if( bool.isFloat() ) {
+ unsigned = true;
+ return new CmpFX86(bool);
+ }
+ Node lhs = bool.in(1);
+ Node rhs = bool.in(2);
+
+ // Since cmp does not record a BOP, SetX/Jmp need to know if the CMP is swapped.
+
+ // Vs memory
+ if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && rhs._type.isa(ld._declaredType) )
+ return new CmpMemX86(bool, address(ld), ld.ptr(), idx, off, scale, imm(rhs), val, false);
+
+ // Operands swap in the encoding directly, no need for Set/Jmp to swap `bop`
+ if( rhs instanceof LoadNode ld && ld.nOuts() == 1 && lhs._type.isa(ld._declaredType))
+ return new CmpMemX86(bool, address(ld), ld.ptr(), idx, off, scale, imm(lhs), val, true );
+
+ // Vs immediate
+ if( rhs instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) )
+ return new CmpIX86(bool, (int)ti.value(), false);
+
+ // Operand swap compare; Set and Jmp need to swap `bop`
+ if( lhs instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) )
+ return new CmpIX86(bool, (int)ti.value(), swap=true);
+
+ // x vs y
+ return new CmpX86(bool);
+ }
+
+ private Node con( ConstantNode con ) {
+ if(!con._con.isConstant()) return new ConstantNode(con); // Default unknown caller inputs
+ return switch (con._con) {
+ case TypeInteger ti -> new IntX86(con);
+ case TypeFloat tf -> new FltX86(con);
+ case TypeFunPtr tfp -> new TFPX86(con);
+ case TypeMemPtr tmp -> 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);
+ };
+ }
+
+ private Node i2f8(ToFloatNode tfn) {
+ assert tfn.in(1)._type instanceof TypeInteger ti;
+ return new I2f8X86(tfn);
+ }
+
+ private Node jmp(IfNode iff) {
+ // If/Bool combos will match to a Cmp/Set which sets flags.
+ // Most general arith ops will also set flags, which the Jmp needs directly.
+ // Loads do not set the flags, and will need an explicit TEST
+ String op = "!=";
+ if( iff.in(1) instanceof BoolNode bool ) op = swap ? IfNode.swap(bool.op()) : bool.op();
+ else if( iff.in(1)==null ) op = "=="; // Never-node cutout
+ else iff.setDef(1, new BoolNode.NE(iff.in(1), new ConstantNode(TypeInteger.ZERO)));
+ return new JmpX86(iff, op);
+ }
+
+ private Node ld(LoadNode ld) {
+ return new LoadX86(address(ld), ld.ptr(), idx, off, scale);
+ }
+
+ private Node mul(MulNode mul) {
+ if( mul.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) )
+ return new MulIX86(mul, (int)ti.value());
+ return new MulX86(mul);
+ }
+
+ private Node or(OrNode or) {
+ if( or.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) )
+ return new OrIX86(or, (int)ti.value());
+ return new OrX86(or);
+ }
+
+ private Node prj( ProjNode prj ) {
+ return new ProjX86(prj);
+ }
+
+ private Node sar(SarNode sar) {
+ if( sar.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm8(ti.value()))
+ return new SarIX86(sar, (int)(ti.value() & 0xff) );
+ return new SarX86(sar);
+ }
+
+ private Node shl(ShlNode shl) {
+ if( shl.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm8(ti.value()))
+ return new ShlIX86(shl, (int)(ti.value() & 0xff) );
+ return new ShlX86(shl);
+ }
+
+ private Node shr( ShrNode shr ) {
+ if( shr.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm8(ti.value()) )
+ return new ShrIX86(shr, (int)(ti.value() & 0xff) );
+ return new ShrX86(shr);
+ }
+
+ private Node st( StoreNode st ) {
+ // Look for "*ptr op= val"
+ Node op = st.val();
+ if( op instanceof AddNode ) {
+ if( op.in(1) instanceof LoadNode ld && stld_match(st,ld) )
+ return new MemAddX86(address(st), st.ptr(), idx, off, scale, imm(op.in(2)), val);
+ if( op.in(2) instanceof LoadNode ld && stld_match(st,ld) )
+ return new MemAddX86(address(st), st.ptr(), idx, off, scale, imm(op.in(1)), val);
+ }
+ return new StoreX86(address(st), st.ptr(), idx, off, scale, imm(st.val()), val);
+ }
+ private static boolean stld_match(StoreNode st, LoadNode ld ) {
+ return
+ ld.in(0) == st.in(0) &&
+ ld.mem() == st.mem() &&
+ ld.ptr() == st.ptr() &&
+ ld.off() == st.off();
+ }
+
+
+ private Node sub (SubNode sub ){
+ return sub.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti
+ ? new AddIX86(sub, (int)-ti.value())
+ : new SubX86(sub);
+ }
+
+ private Node xor (XorNode xor){
+ if( xor.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) )
+ return new XorIX86(xor, (int)ti.value());
+ return new XorX86(xor);
+ }
+
+ // Gather X86 addressing mode bits prior to constructing. This is a
+ // builder pattern, but saving the bits in a *local* *global* here to keep
+ // mess contained.
+ private static int off, scale, imm;
+ private static Node idx, val;
+ private <N extends MemOpNode > N address(N mop) {
+ off = scale = imm = 0; // Reset
+ idx = val = null;
+ Node base = mop.ptr();
+ // Skip/throw-away a ReadOnly, only used to typecheck
+ if(base instanceof ReadOnlyNode read) base = read.in(1);
+ assert !(base instanceof AddNode) && base._type instanceof TypeMemPtr; // Base ptr always, not some derived
+ if(mop.off() instanceof AddNode add && add.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) {
+ off = (int) ti.value();
+ assert off == ti.value(); // In 32-bit range
+ idx = add.in(1);
+ if(idx instanceof ShlNode shift && shift.in(2) instanceof ConstantNode shfcon &&
+ shfcon._con instanceof TypeInteger tscale && 0 <= tscale.value() && tscale.value() <= 3) {
+ idx = shift.in(1);
+ scale = (int) tscale.value();
+ }
+ } else {
+ if(mop.off() instanceof ConstantNode con && con._con instanceof TypeInteger ti) {
+ off = (int) ti.value();
+ assert off == ti.value(); // In 32-bit range
+ } else {
+ idx = mop.off();
+ }
+ }
+ return mop;
+ }
+
+ private int imm( Node xval ) {
+ assert val == null && imm == 0;
+ if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti) {
+ val = null;
+ imm = (int) ti.value();
+ assert imm == ti.value(); // In 32-bit range
+ } else {
+ val = xval;
+ }
+ return imm;
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java b/chapter21/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java
new file mode 100644
index 00000000..da269d31
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java
@@ -0,0 +1,286 @@
+package com.seaofnodes.simple.print;
+
+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.node.*;
+import com.seaofnodes.simple.type.*;
+import java.util.HashSet;
+
+public abstract class ASMPrinter {
+
+ public static SB print(SB sb, CodeGen code) {
+ if( code._cfg==null )
+ return sb.p("Need _cfg set, run after GCM");
+
+ // instruction address
+ int iadr = 0;
+ for( int i=0; i<code._cfg._len; i++ )
+ if( code._cfg.at(i) instanceof FunNode fun )
+ iadr = print(iadr,sb,code,fun,i);
+
+ // Skip padding
+ while( ((iadr+7) & -8) > iadr )
+ iadr++;
+
+ // constant pool
+ Encoding enc = code._encoding;
+ if( enc!=null && !enc._bigCons.isEmpty() ) {
+ iadr = (iadr+15)&-16; // pad to 16
+ HashSet<Type> targets = new HashSet<>();
+ sb.p("--- Constant Pool ------").nl();
+ // By log size
+ for( int log = 3; log >= 0; log-- ) {
+ for( Node op : enc._bigCons.keySet() ) {
+ Encoding.Relo relo = enc._bigCons.get(op);
+ if( targets.contains(relo._t) ) continue;
+ targets.add(relo._t);
+ if( relo._t.log_size()==log ) {
+ sb.hex2(iadr).p(" ");
+ if( relo._t instanceof TypeTuple tt ) {
+ for( Type tx : tt._types ) {
+ switch( log ) {
+ case 0: sb.hex1(enc.read1(iadr)); break;
+ case 1: sb.hex2(enc.read2(iadr)); break;
+ case 2: sb.hex4(enc.read4(iadr)); break;
+ case 3: sb.hex8(enc.read8(iadr)); break;
+ }
+ iadr += (1<<log);
+ sb.p(" ");
+ }
+ } else {
+ switch( log ) {
+ case 0: sb.hex1(enc.read1(iadr)).fix(9-1,""); break;
+ case 1: sb.hex2(enc.read2(iadr)).fix(9-2,""); break;
+ case 2: sb.hex4(enc.read4(iadr)).fix(9-4,""); break;
+ case 3: sb.hex8(enc.read8(iadr)).p(" "); break;
+ }
+ iadr += (1<<log);
+ }
+ relo._t.print(sb).nl();
+ }
+ }
+ }
+ }
+
+
+ return sb;
+ }
+
+ private static int print(int iadr, SB sb, CodeGen code, FunNode fun, int cfgidx) {
+ FunNode old=null;
+ if( code._encoding!=null ) {
+ old = code._encoding._fun;
+ code._encoding._fun = fun; // Useful printing after RA
+ }
+ // Function header
+ sb.nl().p("---");
+ if( fun._name != null ) sb.p(fun._name).p(" ");
+ fun.sig().print(sb);
+ sb.p("---------------------------").nl();
+ iadr = (iadr + 15)&-16; // All function entries padded to 16 align
+
+ if( fun._frameAdjust != 0 )
+ iadr = doInst(iadr,sb,code,fun,cfgidx,fun,true,true);
+ while( !(code._cfg.at(cfgidx) instanceof ReturnNode) )
+ iadr = doBlock(iadr,sb,code,fun,cfgidx++);
+
+ // Function separator
+ sb.p("---");
+ fun.sig().print(sb);
+ sb.p("---------------------------").nl();
+ if( code._encoding != null )
+ code._encoding._fun = old;
+ return iadr;
+ }
+
+ static private final int opWidth = 5;
+ static private final int argWidth = 30;
+ static int doBlock(int iadr, SB sb, CodeGen code, FunNode fun, int cfgidx) {
+ final int encWidth = code._mach==null ? 2 : code._mach.defaultOpSize()*2;
+ CFGNode bb = code._cfg.at(cfgidx);
+ if( bb != fun && !(bb instanceof IfNode) && !(bb instanceof CallEndNode) && !(bb instanceof CallNode) && !(bb instanceof CProjNode && bb.in(0) instanceof CallEndNode ))
+ sb.p(label(bb)).p(":").nl();
+ if( bb instanceof CallNode ) return iadr;
+ final boolean postAlloc = code._phase.ordinal() > CodeGen.Phase.RegAlloc.ordinal();
+ final boolean postEncode= code._phase.ordinal() >=CodeGen.Phase.Encoding.ordinal();
+
+ boolean once=false;
+ for( Node n : bb.outs() ) {
+ if( !(n instanceof PhiNode phi) ) continue;
+ if( phi._type instanceof TypeMem || phi._type instanceof TypeRPC ) continue; // Nothing for the hidden ones
+ // Post-RegAlloc phi prints all on one line
+ if( postAlloc ) {
+ if( !once ) { once=true; sb.fix(4," ").p(" ").fix(encWidth,"").p(" "); }
+ sb.p(phi._label).p(':').p(code.reg(phi,fun)).p(',');
+ } else {
+ // Pre-RegAlloc phi prints one line per
+ sb.fix(4," ").p(" ").fix(encWidth,"").p(" ").fix(opWidth,phi._label).p(" ").p(code.reg(phi,fun));
+ if( phi.getClass() == PhiNode.class ) {
+ sb.p(" = phi( ");
+ for( int i=1; i<phi.nIns(); i++ )
+ sb.p("N").p(phi.in(i)._nid).p(",");
+ sb.unchar().p(" )");
+ }
+ sb.nl();
+ }
+ }
+ if( once ) sb.unchar().nl();
+
+ // All the non-phis
+ for( int i=0; i<bb.nOuts(); i++ )
+ if( !(bb.out(i) instanceof PhiNode) )
+ iadr = doInst(iadr, sb,code, fun, cfgidx, bb.out(i),postAlloc, postEncode );
+
+ return iadr;
+ }
+
+ static int doInst( int iadr, SB sb, CodeGen code, FunNode fun, int cfgidx, Node n, boolean postAlloc, boolean postEncode ) {
+ if( n==null || n instanceof CProjNode ) return iadr;
+ if( postAlloc && n instanceof CalleeSaveNode ) return iadr;
+ if( postEncode && n instanceof ProjNode ) return iadr;
+ if( n instanceof MemMergeNode ) return iadr;
+ if( n.getClass() == ConstantNode.class ) return iadr; // Default placeholders
+ final int dopz = code._mach==null ? 2 : code._mach.defaultOpSize();
+ final int encWidth = dopz*2;
+
+ // All blocks ending in a Region will need to either fall into or jump
+ // to this block. Until the post-reg-alloc block layout cleanup, we
+ // need to assume a jump. There's no real hardware op here, yet.
+ if( n instanceof RegionNode cfg && !(n instanceof FunNode) ) {
+ if( postEncode ) return iadr; // All jumps inserted already
+ while( cfgidx < code._cfg._len-1 ) {
+ CFGNode next = code._cfg.at(++cfgidx);
+ if( next == n ) return iadr; // Fall-through, no branch
+ if( next.nOuts()>1 )
+ break; // Has code in the block, need to jump around
+ // No code in the block, can fall through it
+ }
+ sb.hex2(iadr++).p(" ").fix(encWidth,"??").p(" ").fix(opWidth,"JMP").p(" ").fix(argWidth,label(cfg)).nl();
+ return iadr;
+ }
+
+ // ProjNodes following a multi (e.g. Call or New results),
+ // get indent slightly and just print their index & node#
+ if( n instanceof ProjNode proj ) {
+ if( proj._type instanceof TypeMem ) return iadr; // Nothing for the hidden ones
+ sb.fix(4," ").p(" ").fix(encWidth,"").p(" ").fix(opWidth,proj._label==null ? "---" : proj._label).p(" ").p(code.reg(n,fun)).nl();
+ return iadr;
+ }
+
+ // ADDR ENCODING Op--- dst = src op src // Comment
+ // 1234 abcdefgh ld4 RAX = [Rbase + off] // Comment
+ sb.hex2(iadr).p(" ");
+
+ // Encoding
+ int fatEncoding = 0;
+ if( code._encoding != null ) {
+ int size = code._encoding._opLen[n._nid];
+ if( code._asmLittle )
+ for( int i=0; i<Math.min(size,dopz); i++ )
+ sb.hex1(code._encoding._bits.buf()[iadr++]);
+ else {
+ iadr += Math.min(size,dopz);
+ for( int i=0; i<Math.min(size,dopz); i++ )
+ sb.hex1(code._encoding._bits.buf()[iadr-i-1]);
+ }
+ for( int i=size*2; i<encWidth; i++ )
+ sb.p(" ");
+ fatEncoding = size - (encWidth>>1); // Not-printed parts of encoding
+ } else
+ sb.fix(encWidth,"");
+ sb.p(" ");
+
+ // Op; generally "ld4" or "call"
+ sb.fix(opWidth, n instanceof MachNode mach ? mach.op() : n.label()).p(" ");
+
+ // General asm args
+ String isMultiOp = null;
+ if( n instanceof MachNode mach ) {
+ int old = sb.len();
+ mach.asm(code,sb);
+ int len = sb.len();
+ isMultiOp = isMultiOp(sb,old,len);
+ sb.fix(argWidth-(len-old),""); // Pad out
+
+ } else if( !(n._type instanceof TypeMem) ) {
+ // Room for some inputs
+ sb.fix(5, n._nid+":" );
+ int off = 0;
+ for( int i=0; i<n.nIns(); i++ ) {
+ sb.fix(5, n.in(i) == null ? "___" : String.valueOf(n.in(i)._nid));
+ off += 5;
+ if( off >= argWidth ) break;
+ }
+ if( off < argWidth ) sb.fix(argWidth-off,"");
+ }
+ sb.p(" ");
+
+ // Comment
+ String comment = n.comment();
+ if( comment!=null ) sb.p("// ").p(comment);
+
+ sb.nl();
+
+ // Printing more op bits than fit
+ if( isMultiOp != null && code._encoding != null ) {
+ // Multiple ops, template style, no RA, no scheduling. Print out
+ // one-line-per-newline, with encoding bits up front.
+ int size = code._encoding._opLen[n._nid];
+ int off = Math.min(size,dopz);
+ while( isMultiOp!=null ) {
+ sb.hex2(iadr).p(" ");
+ int len = Math.min(size-off,dopz);
+ for( int i=0; i<len; i++ )
+ sb.hex1(code._encoding._bits.buf()[iadr++]);
+ off += len;
+ for( int i=len; i<dopz; i++ ) sb.p(" ");
+ sb.p(" ");
+ int x = isMultiOp.indexOf('\n');
+ if( x== -1 ) { // Last line
+ sb.p(isMultiOp).nl();
+ isMultiOp = null;
+ } else {
+ sb.p(isMultiOp.substring(0,x+1)); // Includes the newline
+ isMultiOp = isMultiOp.substring(x+1);
+ }
+
+ }
+
+ } else if( fatEncoding > 0 ) {
+ // Extra bytes past the default encoding width, all put on a line by
+ // themselves. X86 special for super long encodings
+ sb.hex2(iadr).p(" ");
+ for( int i=0; i<fatEncoding; i++ )
+ sb.hex1(code._encoding._bits.buf()[iadr++]);
+ sb.nl();
+ }
+
+
+ // MultiNodes are immediately followed by projection(s)
+ if( !(n instanceof CFGNode) && n instanceof MultiNode ) {
+ for( Node proj : n._outputs ) {
+ if( proj instanceof ProjNode ) // it could also be an ante-dep
+ doInst(iadr,sb,code,fun, cfgidx, proj,postAlloc,postEncode);
+ }
+ }
+
+ return iadr;
+ }
+
+ private static String label( CFGNode bb ) {
+ return (bb instanceof LoopNode ? "LOOP" : "L")+bb._nid;
+ }
+
+ private static String isMultiOp(SB sb, int old, int len) {
+ for( int i=old; i<len; i++ )
+ if( sb.at(i)=='\n' ) {
+ String s = sb.subString(i+1,len);
+ sb.setLen(i);
+ return s;
+ }
+ return null;
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java b/chapter21/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java
new file mode 100644
index 00000000..e4cf2c31
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java
@@ -0,0 +1,287 @@
+package com.seaofnodes.simple.print;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.node.*;
+import java.util.*;
+import static com.seaofnodes.simple.Utils.TODO;
+
+/**
+ * Simple visualizer that outputs GraphViz dot format.
+ * The dot output must be saved to a file and run manually via dot to generate the SVG output.
+ * Currently, this is done manually.
+ */
+public class GraphVisualizer {
+
+ /**
+ * If set to true we put the control nodes in a separate cluster from
+ * data nodes.
+ */
+ boolean _separateControlCluster = false;
+
+ public GraphVisualizer(boolean separateControlCluster) { this._separateControlCluster = separateControlCluster; }
+ public GraphVisualizer() { this(false); }
+
+ public String generateDotOutput(Parser parse) { return generateDotOutput(parse._code._stop,parse._scope,parse._xScopes); }
+ public String generateDotOutput(StopNode stop, Node scope, Stack<ScopeNode> xScopes) {
+
+ // Since the graph has cycles, we need to create a flat list of all the
+ // nodes in the graph.
+ Collection<Node> all = findAll(xScopes, stop, scope);
+ StringBuilder sb = new StringBuilder();
+ sb.append("digraph chapter18 {\n");
+ sb.append("/*\n");
+ sb.append(stop._src);
+ sb.append("\n*/\n");
+
+ // To keep the Scopes below the graph and pointing up into the graph we
+ // need to group the Nodes in a subgraph cluster, and the scopes into a
+ // different subgraph cluster. THEN we can draw edges between the
+ // scopes and nodes. If we try to cross subgraph cluster borders while
+ // still making the subgraphs DOT gets confused.
+ sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes
+
+ // CNC - turned off Apr/8/2024, gives more flex in the layout and
+ // removes some of the more ludicrous layout choices.
+ // Preserve node input order
+ //sb.append("\tordering=\"in\";\n");
+
+ // Merge multiple edges hitting the same node. Makes common shared
+ // nodes much prettier to look at.
+ sb.append("\tconcentrate=\"true\";\n");
+
+ // Force nested scopes to order
+ sb.append("\tcompound=\"true\";\n");
+
+ // Just the Nodes first, in a cluster no edges
+ nodes(sb, all);
+
+ // Now the scopes, in a cluster no edges
+ if( xScopes != null )
+ for( ScopeNode xscope : xScopes )
+ if( !xscope.isDead() )
+ scope( sb, xscope );
+
+ // Walk the Node edges
+ nodeEdges(sb, all);
+
+ // Walk the active Scope edges
+ if( xScopes != null )
+ for( ScopeNode xscope : xScopes )
+ if( !xscope.isDead() )
+ scopeEdges( sb, xscope );
+
+ sb.append("}\n");
+ return sb.toString();
+ }
+
+ private void nodesByCluster(StringBuilder sb, boolean doCtrl, Collection<Node> all) {
+ if (!_separateControlCluster && doCtrl) // all nodes in 1 cluster
+ return;
+ // Just the Nodes first, in a cluster no edges
+ sb.append(doCtrl ? "\tsubgraph cluster_Controls {\n" : "\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name
+ for (Node n : all) {
+ if (n instanceof ProjNode || n instanceof CProjNode || n instanceof MemMergeNode || n==Parser.XCTRL)
+ continue; // Do not emit, rolled into MultiNode or Scope cluster already
+ if( _separateControlCluster && doCtrl && !(n instanceof CFGNode) ) continue;
+ if( _separateControlCluster && !doCtrl && (n instanceof CFGNode) ) continue;
+ sb.append("\t\t").append(n.uniqueName()).append(" [ ");
+ String lab = n.glabel();
+ if (n instanceof MultiNode) {
+ // Make a box with the MultiNode on top, and all the projections on the bottom
+ sb.append("shape=plaintext label=<\n");
+ sb.append("\t\t\t<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\" CELLPADDING=\"4\">\n");
+ sb.append("\t\t\t<TR><TD BGCOLOR=\"yellow\">").append(lab).append("</TD></TR>\n");
+ sb.append("\t\t\t<TR>");
+ boolean doProjTable = false;
+ n._outputs.sort((x,y) -> x instanceof ProjNode xp && y instanceof ProjNode yp ? (xp._idx - yp._idx) : (x._nid - y._nid));
+ for (Node use : n._outputs) {
+ if (use instanceof ProjNode proj) {
+ if (!doProjTable) {
+ doProjTable = true;
+ sb.append("<TD>").append("\n");
+ sb.append("\t\t\t\t<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">").append("\n");
+ sb.append("\t\t\t\t<TR>");
+ }
+ sb.append("<TD PORT=\"p").append(proj._idx).append("\">").append(proj.glabel()).append("</TD>");
+ }
+ if (use instanceof CProjNode proj) {
+ if (!doProjTable) {
+ doProjTable = true;
+ sb.append("<TD>").append("\n");
+ sb.append("\t\t\t\t<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">").append("\n");
+ sb.append("\t\t\t\t<TR>");
+ }
+ sb.append("<TD PORT=\"p").append(proj._idx).append("\" BGCOLOR=\"yellow\">").append(proj.glabel()).append("</TD>");
+ }
+ }
+ if (doProjTable) {
+ sb.append("</TR>").append("\n");
+ sb.append("\t\t\t\t</TABLE>").append("\n");
+ sb.append("\t\t\t</TD>");
+ }
+ sb.append("</TR>\n");
+ sb.append("\t\t\t</TABLE>>\n\t\t");
+
+ } else {
+ // control nodes have box shape
+ // other nodes are ellipses, i.e. default shape
+ if( n instanceof CFGNode ) sb.append("shape=box style=filled fillcolor=yellow ");
+ else if (n instanceof PhiNode) sb.append("style=filled fillcolor=lightyellow ");
+ sb.append("label=\"").append(lab).append("\" ");
+ }
+ sb.append("];\n");
+ }
+ if (!_separateControlCluster) {
+ // Force Region & Phis to line up
+ for (Node n : all) {
+ if (n instanceof RegionNode region) {
+ sb.append("\t\t{ rank=same; ");
+ sb.append(region.uniqueName()).append(";");
+ for (Node phi : region._outputs)
+ if (phi instanceof PhiNode) sb.append(phi.uniqueName()).append(";");
+ sb.append("}\n");
+ }
+ }
+ }
+ sb.append("\t}\n"); // End Node cluster
+ }
+
+ private void nodes(StringBuilder sb, Collection<Node> all) {
+ nodesByCluster(sb, true, all);
+ nodesByCluster(sb, false, all);
+ }
+
+ // Build a nested scope display, walking the _prev edge
+ private void scope( StringBuilder sb, ScopeNode scope ) {
+ sb.append("\tnode [shape=plaintext];\n");
+ int last = scope.nIns();
+ int max = scope._lexSize.size();
+ for( int i = 0; i < max; i++ ) {
+ int level = max-i-1;
+ String scopeName = makeScopeName(scope, level);
+ sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name
+ // Special for memory ScopeMinNode
+ if( level==0 ) {
+ MemMergeNode n = scope.mem();
+ sb.append("\t\t").append(n.uniqueName()).append(" [label=\"").append(n.glabel()).append("\"];\n");
+ }
+ sb.append("\t\t").append(scopeName).append(" [label=<\n");
+ sb.append("\t\t\t<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n");
+ // Add the scope level
+ sb.append("\t\t\t<TR><TD BGCOLOR=\"cyan\">").append(level).append("</TD>");
+ int lexStart=scope._lexSize.at(level);
+ for( int j=lexStart; j<last; j++ ) {
+ var v = scope._vars.at(j);
+ sb.append("<TD PORT=\"").append(makePortName(scopeName, v._name)).append("\">").append(v._name).append("</TD>");
+ }
+ last = lexStart;
+ sb.append("</TR>\n");
+ sb.append("\t\t\t</TABLE>>];\n");
+ }
+ // Scope clusters nest, so the graphics shows the nested scopes, so
+ // they are not closed as they are printed; so they just keep nesting.
+ // We close them all at once here.
+ sb.append( "\t}\n".repeat( max ) ); // End all Scope clusters
+ }
+
+ private String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; }
+ private String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; }
+
+ // Walk the node edges
+ private void nodeEdges(StringBuilder sb, Collection<Node> all) {
+ // All them edge labels
+ sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n");
+ for( Node n : all ) {
+ // Do not display the Constant->Start edge;
+ // ProjNodes handled by Multi;
+ // ScopeNodes are done separately
+ if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeNode || n==Parser.XCTRL )
+ continue;
+ for( int i=0; i<n.nIns(); i++ ) {
+ Node def = n.in(i);
+ if( n instanceof PhiNode && def instanceof RegionNode ) {
+ // Draw a dotted use->def edge from Phi to Region.
+ sb.append('\t').append(n.uniqueName());
+ sb.append(" -> ");
+ sb.append(def.uniqueName());
+ sb.append(" [style=dotted taillabel=").append(i).append("];\n");
+ } else if( def != null ) {
+ // Most edges land here use->def
+ sb.append('\t').append(n.uniqueName()).append(" -> ");
+ if( def instanceof CProjNode proj ) {
+ String mname = proj.ctrl().uniqueName();
+ sb.append(mname).append(":p").append(proj._idx);
+ } else if( def instanceof ProjNode proj ) {
+ String mname = proj.in(0).uniqueName();
+ sb.append(mname).append(":p").append(proj._idx);
+ } else sb.append(def.uniqueName());
+ // Number edges, so we can see how they track
+ sb.append("[taillabel=").append(i);
+ // The edge from New to ctrl is just for anchoring the New
+ if ( n instanceof NewNode )
+ sb.append(" color=green");
+ // control edges are colored red
+ else if( def instanceof CFGNode )
+ sb.append(" color=red");
+ else if( def.isMem() )
+ sb.append(" color=blue");
+ // Backedges do not add a ranking constraint
+ if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) )
+ sb.append(" constraint=false");
+ sb.append("];\n");
+ }
+ }
+ }
+ }
+
+ // Walk the scope edges
+ private void scopeEdges( StringBuilder sb, ScopeNode scope ) {
+ sb.append("\tedge [style=dashed color=cornflowerblue];\n");
+ int level=0;
+ for( var v : scope._vars ) {
+ Node def = scope.in(v._idx);
+ while( def instanceof ScopeNode lazy )
+ def = lazy.in(v._idx);
+ if( def==null ) continue;
+ while( level < scope._lexSize.size() && v._idx >= scope._lexSize.at(level) )
+ level++;
+ String scopeName = makeScopeName(scope, level-1);
+ sb.append("\t")
+ .append(scopeName).append(":")
+ .append('"').append(makePortName(scopeName, v._name)).append('"') // wrap port name with quotes because $ctrl is not valid unquoted
+ .append(" -> ");
+ if( def instanceof CProjNode proj ) {
+ sb.append(def.in(0).uniqueName()).append(":p").append(proj._idx);
+ } else if( def instanceof ProjNode proj ) {
+ sb.append(def.in(0).uniqueName()).append(":p").append(proj._idx);
+ } else sb.append(def.uniqueName());
+ sb.append(";\n");
+ }
+ }
+
+ /**
+ * Finds all nodes in the graph.
+ */
+ public static Collection<Node> findAll(Stack<ScopeNode> ss, Node... ns) {
+ final HashMap<Integer, Node> all = new HashMap<>();
+ for( Node n : ns )
+ walk(all, n);
+ if( ss != null )
+ for( Node n : ss )
+ walk(all, n);
+ return all.values();
+ }
+
+ /**
+ * Walk a subgraph and populate distinct nodes in the all list.
+ */
+ private static void walk(HashMap<Integer,Node> all, Node n) {
+ if(n == null ) return;
+ if (all.get(n._nid) != null) return; // Been there, done that
+ all.put(n._nid, n);
+ for (Node c : n._inputs)
+ walk(all, c);
+ for( Node c : n._outputs )
+ walk(all, c);
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/print/IRPrinter.java b/chapter21/src/main/java/com/seaofnodes/simple/print/IRPrinter.java
new file mode 100644
index 00000000..c6e478c5
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/print/IRPrinter.java
@@ -0,0 +1,331 @@
+package com.seaofnodes.simple.print;
+
+import com.seaofnodes.simple.Ary;
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeFunPtr;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.HashMap;
+
+public abstract class IRPrinter {
+
+ // Print a node on 1 line, columnar aligned, as:
+ // NNID NNAME DDEF DDEF [[ UUSE UUSE ]] TYPE
+ // 1234 sssss 1234 1234 1234 1234 1234 1234 tttttt
+ public static SB printLine( Node n, SB sb ) {
+ if( n==null ) return sb;
+ sb.p("%4d %-7.7s ".formatted(n._nid,n.label()));
+ if( n._inputs==null )
+ return sb.p("DEAD\n");
+ for( Node def : n._inputs )
+ sb.p(def==null ? "____" : "%4d".formatted(def._nid))
+ // Lazy Phi indicator
+ .p(n instanceof MemMergeNode && def instanceof MemMergeNode ? "^" : " ");
+ for( int i = n._inputs.size(); i<4; i++ )
+ sb.p(" ");
+ sb.p(" [[ ");
+ for( Node use : n._outputs )
+ sb.p(use==null ? "____ " : "%4d ".formatted(use._nid));
+ int lim = 6 - Math.max(n._inputs.size(),4);
+ for( int i = n._outputs.size(); i<lim; i++ )
+ sb.p(" ");
+ sb.p(" ]] ");
+ if( n._type!= null ) sb.p(n._type.str());
+ return sb.p("\n");
+ }
+
+
+ public static String prettyPrint(Node node, int depth) {
+ return CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Schedule.ordinal()
+ ? _prettyPrintScheduled( node, depth )
+ : _prettyPrint( node, depth );
+ }
+
+ // Another bulk pretty-printer. Makes more effort at basic-block grouping.
+ private static String _prettyPrint( Node node, int depth ) {
+ // First, a Breadth First Search at a fixed depth.
+ BFS bfs = new BFS(node,depth);
+ // Convert just that set to a post-order
+ ArrayList<Node> rpos = new ArrayList<>();
+ BitSet visit = new BitSet();
+ for( int i=bfs._lim; i< bfs._bfs.size(); i++ )
+ postOrd( bfs._bfs.get(i), null, rpos, visit, bfs._bs);
+ // Reverse the post-order walk
+ SB sb = new SB();
+ boolean gap=false;
+ for( int i=rpos.size()-1; i>=0; i-- ) {
+ Node n = rpos.get(i);
+ if( n instanceof CFGNode || n instanceof MultiNode ) {
+ if( !gap ) sb.p("\n"); // Blank before multihead
+ if( n instanceof FunNode fun )
+ fun.sig().print(sb.p("--- ").p(fun._name==null ? "" : fun._name).p(" "),false).p("----------------------\n");
+ printLine( n, sb ); // Print head
+ while( --i >= 0 ) {
+ Node t = rpos.get(i);
+ if( !(t.in(0) instanceof MultiNode) ) { i++; break; }
+ printLine( t, sb );
+ }
+ if( n instanceof ReturnNode ret ) {
+ FunNode fun = ret.fun();
+ sb.p("--- ");
+ if( fun != null )
+ fun.sig().print(sb.p(fun._name==null ? "" : fun._name).p(" "),false);
+ sb.p("----------------------\n");
+ }
+ if( !(n instanceof CallNode) ) {
+ sb.p("\n"); // Blank after multitail
+ gap = true;
+ }
+ } else {
+ printLine( n, sb );
+ gap = false;
+ }
+ }
+ return sb.toString();
+ }
+
+ private static void postOrd(Node n, Node prior, ArrayList<Node> rpos, BitSet visit, BitSet bfs) {
+ if( !bfs.get(n._nid) )
+ return; // Not in the BFS visit
+ if( n instanceof FunNode && !(prior instanceof StartNode) )
+ return; // Only visit Fun from Start
+ if( visit.get(n._nid) ) return; // Already post-order walked
+ visit.set(n._nid);
+ // First walk the CFG, then everything
+ if( n instanceof CFGNode ) {
+ for( Node use : n._outputs )
+ // Follow CFG, not across call/function borders, and not around backedges
+ if( use instanceof CFGNode && !(n instanceof CallNode && use instanceof FunNode) &&
+ use.nOuts() >= 1 && !(use._outputs.get(0) instanceof LoopNode) )
+ postOrd(use, n, rpos,visit,bfs);
+ for( Node use : n._outputs )
+ // Follow CFG, not across call/function borders
+ if( use instanceof CFGNode && !(n instanceof CallNode && use instanceof FunNode) )
+ postOrd(use,n,rpos,visit,bfs);
+ }
+ // Follow all outputs
+ for( Node use : n._outputs )
+ if( use != null &&
+ !(n instanceof CallNode && use instanceof FunNode) &&
+ (n instanceof FunNode || !(use instanceof ParmNode)) )
+ postOrd(use, n, rpos,visit,bfs);
+ // Post-order
+ rpos.add(n);
+ }
+
+ // Breadth-first search, broken out in a class to keep in more independent.
+ // Maintains a root-set of Nodes at the limit (or past by 1 if MultiHead).
+ public static class BFS {
+ // A breadth first search, plus MultiHeads for any MultiTails
+ public final ArrayList<Node> _bfs;
+ public final BitSet _bs; // Visited members by node id
+ public final int _depth; // Depth limit
+ public final int _lim; // From here to _bfs._len can be roots for a reverse search
+ public BFS( Node base, int d ) {
+ _depth = d;
+ _bfs = new ArrayList<>();
+ _bs = new BitSet();
+
+ add(base); // Prime the pump
+ int idx=0, lim=1; // Limit is where depth counter changes
+ while( idx < _bfs.size() ) { // Ran out of nodes below depth
+ Node n = _bfs.get(idx++);
+ for( Node def : n._inputs )
+ if( def!=null && !_bs.get(def._nid) )
+ add(def);
+ if( idx==lim ) { // Depth counter changes at limit
+ if( --d < 0 )
+ break; // Ran out of depth
+ lim = _bfs.size(); // New depth limit
+ }
+ }
+ // Toss things past the limit except multi-heads
+ while( idx < _bfs.size() ) {
+ Node n = _bfs.get(idx);
+ if( n instanceof MultiNode ) idx++;
+ else del(idx);
+ }
+ // Root set is any node with no inputs in the visited set
+ lim = _bfs.size();
+ for( int i=_bfs.size()-1; i>=0; i-- )
+ if( !any_visited(_bfs.get(i)) )
+ swap( i,--lim);
+ _lim = lim;
+ }
+ void swap( int x, int y ) {
+ if( x==y ) return;
+ Node tx = _bfs.get(x);
+ Node ty = _bfs.get(y);
+ _bfs.set(x,ty);
+ _bfs.set(y,tx);
+ }
+ void add(Node n) {
+ _bfs.add(n);
+ _bs.set(n._nid);
+ }
+ void del(int idx) {
+ _bs.clear(_bfs.get(idx)._nid);
+ Utils.del(_bfs, idx);
+ }
+ boolean any_visited( Node n ) {
+ for( Node def : n._inputs )
+ if( def!=null && _bs.get(def._nid) )
+ return true;
+ return false;
+ }
+ }
+
+ public static String _prettyPrint( CodeGen code ) {
+ SB sb = new SB();
+ // Print the Start "block"
+ printLine(code._start,sb);
+ for( Node n : code._start._outputs )
+ printLine(n,sb);
+ sb.nl();
+
+ // Skip start, stop
+ for( int i=1; i<code._cfg._len-1; i++ ) {
+ CFGNode blk = code._cfg.at(i);
+ if( blk.blockHead() && (!(blk instanceof CProjNode) || (blk.cfg0() instanceof IfNode )) ) {
+ if( blk instanceof FunNode fun )
+ sb.p("--- ").p(fun.label()).p(" ---------------------------").nl();
+ // Print block header
+ sb.p("%-13.13s".formatted(label(blk)+":"));
+ sb.p( " ".repeat(4) ).p(" [[ ");
+ if( blk instanceof RegionNode )
+ for( int j=1; j<blk.nIns(); j++ )
+ label(sb,blk.cfg(j));
+ else
+ label(sb,blk.cfg(0));
+ sb.p(" ]] \n");
+ }
+ printLine(blk,sb);
+ if( blk instanceof ReturnNode ret )
+ sb.p("--- ").p(ret.fun().label()).p(" ---------------------------").nl();
+
+ // Block contents
+ for( Node n : blk._outputs ) {
+ if( n instanceof CFGNode cfg ) continue;
+ printLine(n,sb);
+ if( !(n instanceof CFGNode) && n instanceof MultiNode )
+ for( Node use : n._outputs )
+ if( use instanceof ProjNode )
+ printLine(use,sb);
+ }
+ }
+
+ printLine(code._stop,sb);
+ return sb.toString();
+ }
+
+ // Bulk pretty printer, knowing scheduling information is available
+ private static String _prettyPrintScheduled( Node node, int depth ) {
+ // Backwards DFS walk to depth.
+ HashMap<Integer,Integer> ds = new HashMap<>();
+ Ary<Node> ns = new Ary<>(Node.class);
+ _walk(ds,ns,node,depth);
+ // Remove data projections, these are force-printed behind their multinode head
+ for( int i=0; i<ns.size(); i++ ) {
+ if( ns.get(i) instanceof ProjNode proj && !(proj.in(0) instanceof CFGNode) ) {
+ ns.del(i--);
+ ds.remove(proj._nid);
+ }
+ }
+ // Print by block with least idepth
+ SB sb = new SB();
+ Ary<Node> bns = new Ary<>(Node.class);
+ while( !ds.isEmpty() ) {
+ CFGNode blk = null;
+ for( Node n : ns ) {
+ CFGNode cfg = n instanceof CFGNode cfg0 && cfg0.blockHead() ? cfg0 : n.cfg0();
+ if( blk==null || cfg.idepth() < blk.idepth() )
+ blk = cfg;
+ }
+ Integer d = ds.remove(blk._nid);
+ ns.del(ns.find(blk));
+
+ // Print block header
+ sb.p("%-13.13s".formatted(label(blk)+":"));
+ sb.p( " ".repeat(4) ).p(" [[ ");
+ if( blk instanceof StartNode ) ;
+ else if( blk instanceof RegionNode || blk instanceof StopNode )
+ for( int i=(blk instanceof StopNode ? 3 : 1); i<blk.nIns(); i++ )
+ label(sb,blk.cfg(i));
+ else
+ label(sb,blk.cfg(0));
+ sb.p(" ]] \n");
+ printLine(blk,sb);
+
+ // Collect block contents that are in the depth limit
+ bns.clear();
+ int xd = Integer.MAX_VALUE;
+ for( Node use : blk._outputs ) {
+ Integer i = ds.get(use._nid);
+ if( i!=null && !(use instanceof CFGNode cfg && cfg.blockHead()) ) {
+ if( bns.find(use)==-1 )
+ bns.add(use);
+ xd = Math.min(xd,i);
+ }
+ }
+ // Print Phis up front, if any
+ for( int i=0; i<bns.size(); i++ )
+ if( bns.get(i) instanceof PhiNode phi )
+ printLine( phi, sb,bns,i--,ds,ns);
+
+ // Print block contents in depth order, bumping depth until whole block printed
+ for( ; !bns.isEmpty(); xd++ )
+ for( int i=0; i<bns.size(); i++ ) {
+ Node n = bns.get(i);
+ if( ds.get(n._nid)==xd ) {
+ printLine( n, sb, bns, i--, ds,ns );
+ if( n instanceof MultiNode && !(n instanceof CFGNode) ) {
+ for( Node use : n._outputs ) {
+ if( use instanceof ProjNode )
+ printLine(use,sb,bns,bns.indexOf(use),ds,ns);
+ }
+ }
+ }
+ }
+ sb.p("\n");
+ }
+ return sb.toString();
+ }
+
+ private static void _walk( HashMap<Integer,Integer> ds, Ary<Node> ns, Node node, int d ) {
+ Integer nd = ds.get(node._nid);
+ if( nd!=null && d <= nd ) return; // Been there, done that
+ Integer old = ds.put(node._nid,d) ;
+ if( old == null )
+ ns.add(node);
+ if( d == 0 ) return; // Depth cutoff
+ for( Node def : node._inputs )
+ if( def != null &&
+ !(node instanceof LoopNode loop && loop.back()==def) &&
+ // Don't walk into or out of functions
+ !(node instanceof CallEndNode && def instanceof ReturnNode) &&
+ !(node instanceof FunNode && def instanceof CallNode) &&
+ !(node instanceof ParmNode && !(def instanceof FunNode))
+ )
+ _walk(ds,ns,def,d-1);
+ }
+
+ static String label( CFGNode blk ) {
+ if( blk instanceof StartNode ) return "START";
+ return (blk instanceof LoopNode ? "LOOP" : "L")+blk._nid;
+ }
+ static void label( SB sb, CFGNode blk ) {
+ if( !blk.blockHead() ) blk = blk.cfg(0);
+ sb.p( "%-9.9s ".formatted( label( blk ) ) );
+ }
+ static void printLine( Node n, SB sb, Ary<Node> bns, int i, HashMap<Integer,Integer> ds, Ary<Node> ns ) {
+ printLine( n, sb );
+ if( i != -1 ) bns.del(i);
+ ds.remove(n._nid);
+ int idx = ns.find(n);
+ if( idx!=-1 ) ns.del(idx);
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/print/JSViewer.java b/chapter21/src/main/java/com/seaofnodes/simple/print/JSViewer.java
new file mode 100644
index 00000000..76c64c71
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/print/JSViewer.java
@@ -0,0 +1,405 @@
+package com.seaofnodes.simple.print;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.Collection;
+import java.util.Stack;
+import static com.seaofnodes.simple.codegen.CodeGen.CODE;
+
+public class JSViewer implements AutoCloseable {
+ // Display programs in an endless loop
+ public static void main( String[] args ) throws Exception {
+ try(var js = new JSViewer()) {
+ js.run();
+ }
+ }
+
+ static boolean SHOW;
+
+ // WebSocket to the browser for display
+ static SimpleWebSocket SERVER;
+
+ static int N; // Dot frames
+
+ JSViewer() throws Exception {
+ // Launch server; handshake
+ SERVER = new SimpleWebSocket(Paths.get("docs/index.html").toUri(),12345) ;
+ }
+
+ void run( ) throws Exception {
+ SERVER.put("!");
+ while( true ) {
+ String src = SERVER.get();
+ switch( src ) {
+ case null: return;
+ case "null": return;
+ case "+": break; // Client requests more frames, but we send them all anyway
+ default:
+ System.out.println(src);
+ try {
+ N=0;
+ // Use parser scope, xscope when building views
+ CodeGen code = new CodeGen(src);
+ SHOW = true;
+ show();
+ // Parse program, generating views at every parse point
+ code.parse();
+ // No longer user parse internal state when building views
+ code.opto();
+
+ // Catch and ignore Parser errors
+ } catch(RuntimeException re) {
+ System.err.println(re);
+ } finally {
+ SHOW = false;
+ SERVER.put("#"); // Final frame
+ }
+ break;
+ }
+ }
+ }
+
+ @Override public void close() throws IOException {
+ SERVER.close();
+ SERVER=null;
+ }
+
+ public static void show() { if( SERVER!=null && SHOW ) _show(); }
+ private static void _show() {
+ // Skip util we at least get the Parse object made
+ if( CODE._phase==null || CODE._phase.ordinal() < CodeGen.Phase.Parse.ordinal() )
+ return;
+ boolean midParse = CODE._phase == CodeGen.Phase.Parse;
+ Stack<ScopeNode> xScopes = midParse ? CODE.P._xScopes : null;
+
+ Collection<Node> all = GraphVisualizer.findAll(xScopes, CODE._stop, midParse ? CODE.P._scope: null);
+ SB sb = new SB();
+ sb.p("digraph view_").p(N++).p(" {\n").ii();
+ if( midParse )
+ sb.i().p("// POS: ").p(CODE.P.pos()).nl();
+
+ // To keep the Scopes below the graph and pointing up into the graph we
+ // need to group the Nodes in a subgraph cluster, and the scopes into a
+ // different subgraph cluster. THEN we can draw edges between the
+ // scopes and nodes. If we try to cross subgraph cluster borders while
+ // still making the subgraphs DOT gets confused.
+ sb.i().p("rankdir=BT;\n"); // Force Nodes before Scopes
+
+ // CNC - turned off Apr/8/2024, gives more flex in the layout and
+ // removes some of the more ludicrous layout choices.
+ // Preserve node input order
+ //sb.append("ordering=\"in\";\n");
+
+ // Merge multiple edges hitting the same node. Makes common shared
+ // nodes much prettier to look at.
+ sb.i().p("concentrate=\"true\";\n");
+
+ // Force nested scopes to order
+ sb.i().p("compound=\"true\";\n");
+
+ // Just the Nodes first, in a cluster no edges
+ nodes(sb, all);
+
+ // Now the scopes, in a cluster no edges
+ if( midParse )
+ for( ScopeNode xscope : xScopes )
+ if( !xscope.isDead() )
+ scope( sb, xscope );
+
+ // Walk the Node edges
+ nodeEdges(sb, midParse && !xScopes.empty() ? xScopes.peek() : null, all);
+
+ // Walk the active Scope edges
+ if( midParse )
+ for( ScopeNode xscope : xScopes )
+ if( !xscope.isDead() )
+ scopeEdges( sb, xscope );
+
+
+ sb.p("}\n").di();
+ // Tell client another DOT frame
+ String dot = sb.toString();
+ System.out.println(dot);
+ try {
+ SERVER.put(dot);
+ } catch( IOException ioe ) {
+ try { SERVER.close();} catch( IOException ignored ){}
+ }
+ }
+
+
+ private static void nodes(SB sb, Collection<Node> all) {
+ // Just the Nodes first, in a cluster no edges
+ sb.i().p("subgraph cluster_Nodes {\n").ii(); // Magic "cluster_" in the subgraph name
+ for( Node n : all ) {
+ if( n instanceof ProjNode || n instanceof CProjNode || n instanceof MemMergeNode )
+ continue; // Do not emit, rolled into MultiNode or Scope cluster already
+ sb.i().p(n.uniqueName()).p(" [ ");
+ if( n instanceof MultiNode && !(n instanceof StartNode) ) {
+ // Make a box with the MultiNode on top, and all the projections on the bottom
+ sb. p("shape=plaintext label=<\n").ii();
+ sb.i().p("<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\" CELLPADDING=\"4\">\n");
+ // Row over single cell for node label
+ cell(sb.i().p("<TR>"), n.glabel(), n, null).p("</TR>\n");
+ // Row over single cell, for nested table
+ sb.i().p("<TR><TD>\n").ii();
+ sb.i().p("<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">").p("\n");
+ sb.i().p("<TR>");
+ n._outputs.sort((x,y) -> x instanceof ProjNode xp && y instanceof ProjNode yp ? (xp._idx - yp._idx) : ((x==null ? 99999 : x._nid) - (y==null ? 99999 : y._nid)));
+ boolean empty_row=true;
+ for( Node use : n._outputs ) {
+ int idx = idx(n);
+ if( idx != -1 ) {
+ cell(sb,use.glabel(),use,"p"+idx);
+ empty_row=false;
+ }
+ }
+ // At least one cell on row
+ if( empty_row ) sb.p("<TD></TD>");
+ sb. p("</TR>").p("\n");
+ sb.i().p("</TABLE>").p("\n").di();
+ sb.i().p("</TD></TR>\n");
+ sb.i().p("</TABLE>>\n").di();
+ sb.i().p("];\n");
+
+ } else {
+ // control nodes have box shape
+ // other nodes are ellipses, i.e. default shape
+ //else if (n instanceof PhiNode) sb.p("style=filled fillcolor=lightyellow ");
+ node(sb,n.glabel(),n);
+ sb.p("];\n");
+ }
+ }
+ // Force Region & Phis to line up
+ for (Node n : all) {
+ if (n instanceof RegionNode region) {
+ sb.i().p("{ rank=same; ");
+ sb.p(region.uniqueName()).p(";");
+ for (Node phi : region._outputs)
+ if (phi instanceof PhiNode) sb.p(phi.uniqueName()).p(";");
+ sb.p("}\n");
+ }
+ }
+ sb.di().i().p("}").nl(); // End Node cluster
+ }
+
+ // Build a nested scope display, walking the _prev edge
+ private static void scope( SB sb, ScopeNode scope ) {
+ sb.i().p("node [shape=plaintext];\n");
+ int last = scope.nIns();
+ int max = scope._lexSize.size()+1; // One more than lexsize
+ for( int i = 0; i < max; i++ ) {
+ int level = max-i-1;
+ String scopeName = makeScopeName(scope, level);
+ sb.i().p("subgraph cluster_").p(scopeName).p(" {\n").ii(); // Magic "cluster_" in the subgraph name
+ sb.i().p(scopeName).p(" [label=<\n").ii();
+ sb.i().p("<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n");
+ int lexStart = level==0 ? 0 : scope._lexSize.at(level-1);
+
+ // Special for memory ScopeMinNode
+ MemMergeNode n = scope.nIns()>1 ? scope.mem() : null;
+ if( level==0 && n!=null && n.nIns()>2 ) {
+ sb.i().p("<TR>");
+ for( int m=2; m<n.nIns(); m++ )
+ cell(sb,"#"+m,n.alias(m),"m"+m);
+ sb.p("</TR>\n"); // End scope level
+ }
+
+ // Scope variables, empty if none
+ if( lexStart<last ) {
+ sb.i().p("<TR>\n");
+ for( int j=lexStart; j<last; j++ ) {
+ if( scope.in(j)==null ) continue;
+ var v = scope._vars.at(j);
+ cell(sb.i(),v._name,scope.in(j),makePortName(scopeName, v._name)).nl();
+ }
+ last = lexStart;
+ sb.i().p("</TR>\n");
+ }
+ sb.i().p("</TABLE>>];\n").di();
+ // Scope label
+ sb.i().p(scopeName).p("A [label=\"").p(level).p("\" shape=doublecircle style=filled fillcolor=aqua fontsize=10 margin=0 width=0.2 ];\n");
+ }
+ // Scope clusters nest, so the graphics shows the nested scopes, so
+ // they are not closed as they are printed; so they just keep nesting.
+ // We close them all at once here.
+ for( int i=0; i<max; i++ )
+ sb.di().i().p("}\n");
+ }
+
+ // Append a cell, with color
+ private static SB cell(SB sb, String text, Node n, String port) {
+ sb.p("<TD");
+ String color = color(n);
+ sb.p(" BGCOLOR=\"").p(color).p("\"");
+ if( port!=null ) sb.p(" PORT=\"").p(port).p("\"");
+ return colorcell(sb.p(">"),text,n,dark(color)).p("</TD>");
+ }
+ private static SB node(SB sb, String text, Node n) {
+ String color = color(n);
+ sb.p("style=filled fillcolor=").p(color).p(" ");
+ String shape = switch( n ) {
+ case FunNode fun -> "shape=box peripheries=2";
+ case CFGNode cfg -> "shape=box";
+ default -> "";
+ };
+ sb.p(shape).p(" ");
+ return colorcell(sb.p("label=<"),text,n,dark(color)).p(">");
+ }
+
+ private static String color(Node n) {
+ return switch( n ) {
+ case CFGNode cfg -> "yellow";
+ case NewNode nnn -> "lightgreen";
+ case StructNode s -> "lightgreen";
+ default -> typeColor(n._type);
+ };
+ }
+ private static String typeColor(Type t) {
+ return t==null ? "white" : switch( t ) {
+ case TypeMem mem -> "blue";
+ case TypeMemPtr tmp -> "green";
+ case TypeFunPtr tfp -> "orange";
+ case TypeRPC rpc -> "antiquewhite";
+ case TypeInteger ti -> "lightblue";
+ case TypeFloat tf -> "aqua";
+ case Type tn -> tn==Type.NIL ? "lightgray" : "red";
+ };
+ }
+ private static final String[] DARKS = new String[]{"blue","green","black"};
+ private static boolean dark(String color) { return Utils.find(DARKS,color) != -1; }
+
+
+
+ // Called with an open div
+ private static SB colorcell(SB sb, String text, Node n, boolean dark) {
+ if( dark ) sb.p("<font color=\"white\">");
+ sb.p(text);
+ if( n._type!=null ) {
+ sb.p("<br /><font point-size=\"10\">");
+ n._type.gprint(sb);
+ sb.p("</font>");
+ }
+ if( dark ) sb.p("</font>");
+ return sb;
+ }
+
+
+ private static String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; }
+ private static String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; }
+
+ // Walk the node edges
+ private static void nodeEdges(SB sb, ScopeNode scope, Collection<Node> all) {
+ // All them edge labels
+ String scopeName = scope!=null ? makeScopeName(scope, 0) : null;
+ sb.i().p("edge [ fontname=Helvetica, fontsize=8 ];\n");
+ for( Node n : all ) {
+ // Do not display the Constant->Start edge;
+ if( n instanceof ConstantNode ||
+ n instanceof XCtrlNode ||
+ // ProjNodes handled by Multi;
+ n instanceof ProjNode ||
+ n instanceof CProjNode ||
+ // ScopeNodes are done separately
+ n instanceof ScopeNode ||
+ n instanceof MemMergeNode
+ )
+ continue;
+ if( n.isDead() )
+ throw Utils.TODO();
+ // Normal edges
+ for( int i=0; i<n.nIns(); i++ ) {
+ Node def = n.in(i);
+ if( def==null ) continue;
+ sb.i().p(n.uniqueName()).p(" -> ");
+ if( n instanceof PhiNode && def instanceof RegionNode ) {
+ // Draw a dotted use->def edge from Phi to Region.
+ sb.p(def.uniqueName());
+ sb.p(" [style=dotted taillabel=").p(i).p("];\n");
+ } else {
+ // Most edges land here use->def
+ if( def instanceof CProjNode proj ) {
+ String mname = proj.ctrl().uniqueName();
+ sb.p(mname).p(":p").p(proj._idx);
+ } else if( def instanceof ProjNode proj ) {
+ String mname = proj.in(0).uniqueName();
+ sb.p(mname).p(":p").p(proj._idx);
+ } else sb.p(def.uniqueName());
+ // Number edges, so we can see how they track
+ sb.p("[taillabel=").p(i);
+ // Color the edge
+ nodeEdgeColor(sb,def._type);
+ // Backedges do not add a ranking constraint
+ if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) )
+ sb.p(" constraint=false");
+ sb.p("];\n");
+ }
+ }
+
+ // Bonus edge if hooked by parser
+ if( (n.iskeep() || n.isUnused()) && scopeName != null ) {
+ sb.i().p(scopeName).p(" -> ").p(n.uniqueName()).p(" [ style=dashed color=grey];\n");
+ }
+ // Force functions above return
+ if( n instanceof ReturnNode ret && ret.inProgress() && !ret.fun().isDead() )
+ sb.i().p(ret.ctrl().uniqueName()).p(" -> ").p(ret.fun().uniqueName()).p("[style=invis]\n");
+ }
+ }
+
+ private static void nodeEdgeColor(SB sb, Type t) {
+ sb.p(" color=").p(typeColor(t));
+ }
+
+ // Walk the scope edges
+ private static void scopeEdges( SB sb, ScopeNode scope ) {
+ sb.i().p("edge [style=dashed color=cornflowerblue];\n");
+ int level=0;
+ for( var v : scope._vars ) {
+ if( v._idx==1 ) continue; // Memory done separately
+ Node def = scope.in(v._idx);
+ while( def instanceof ScopeNode lazy )
+ def = lazy.in(v._idx);
+ if( def==null ) continue;
+ while( level < scope._lexSize.size() && v._idx >= scope._lexSize.at(level) )
+ level++;
+ String scopeName = makeScopeName(scope, level);
+ sb.i()
+ .p(scopeName).p(":")
+ .p('"').p(makePortName(scopeName, v._name)).p('"') // wrap port name with quotes because $ctrl is not valid unquoted
+ .p(" -> ");
+ defPort(sb,def).p(";\n");
+ }
+
+ // Memory
+ if( scope.nIns()>1 ) {
+ MemMergeNode n = scope.mem();
+ for( int i=2; i<n.nIns(); i++ ) {
+ Node def = n.in(i);
+ while( def instanceof ScopeNode lazy )
+ def = lazy.in(i);
+ if( def==null ) continue;
+ String scopeName = makeScopeName(scope, 0);
+ sb.i().p(scopeName).p(":m").p(i).p(" -> ");
+ defPort(sb,def).p(";\n");
+ }
+ }
+
+ }
+
+ // (C)Projection index or -1
+ private static int idx(Node n) {
+ return n instanceof ProjNode p ? p._idx : (n instanceof CProjNode cp ? cp._idx : -1);
+ }
+
+ private static SB defPort(SB sb, Node def) {
+ int idx = idx(def);
+ return idx != -1
+ ? sb.p(def.in(0).uniqueName()).p(":p").p(idx)
+ : sb.p(def.uniqueName());
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java b/chapter21/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java
new file mode 100644
index 00000000..7655213d
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java
@@ -0,0 +1,149 @@
+package com.seaofnodes.simple.print;
+
+import com.seaofnodes.simple.Utils;
+import java.io.*;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Base64;
+
+// Why-Oh-Why is this needed?
+
+// Pass in a URI and a port.
+// Opens the client; client has to open a websocket at the given port.
+// Connects, handshakes.
+// Thereafter you can call get/put to swap frames with client.
+
+class SimpleWebSocket extends ServerSocket {
+ final InputStream _in;
+ final OutputStream _out;
+
+ SimpleWebSocket(URI uri, int port) throws IOException, NoSuchAlgorithmException {
+ super(port);
+ // Launch client
+ java.awt.Desktop.getDesktop().browse(uri);
+ // Look for client; get in/out streams
+ Socket sock = accept();
+ _in = sock. getInputStream();
+ _out = sock.getOutputStream();
+
+
+ // Establish web socket with handshake; easier with a buffered reader
+ BufferedReader br = new BufferedReader(new InputStreamReader(_in));
+ String key = null;
+ while( br.ready() ) {
+ String line = br.readLine();
+ if( line.startsWith("Sec-WebSocket-Key: ") )
+ key = line.substring(19);
+ }
+ assert key!=null;
+ // Magic handshake
+ byte[] response = ("HTTP/1.1 101 Switching Protocols\r\n"
+ + "Connection: Upgrade\r\n"
+ + "Upgrade: websocket\r\n"
+ + "Sec-WebSocket-Accept: "
+ + Base64.getEncoder().encodeToString(MessageDigest.getInstance("SHA-1").digest((key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes( StandardCharsets.UTF_8 )))
+ + "\r\n\r\n").getBytes( StandardCharsets.UTF_8 );
+ _out.write(response,0,response.length);
+ }
+
+ final byte[] _ins = new byte[1024];
+ final byte[] _str = new byte[1024];
+ final byte[] _key = new byte[4];
+ int _x, _len;
+ private int rawget() { assert _x<_len; return _ins[_x++]&0xFF; }
+
+ // TODO: Flip to Direct Byte Buffer, this is just a poor-mans version
+ private void flipRead() throws IOException {
+ // Move _x to _len to offset 0
+ System.arraycopy(_ins,_x,_ins,0,(_len-_x));
+ _len = _len-_x;
+ _x=0;
+ // Read more
+ int len = _in.read(_ins,_len,_ins.length-_len);
+ if( len == -1 )
+ throw Utils.TODO(); // EOF
+ _len += len;
+ }
+ // Block until you read at least len
+ private void atLeast(int len) throws IOException {
+ if( _x+len <= _len ) return;
+ flipRead();
+ }
+
+
+ // Classic read from browser client. Encoded, so decoded here
+ public String get() throws IOException {
+ atLeast(1); // Need 1 byte for opcode
+ int op = rawget();
+ if( (op&0x80)!=0x80 ) // FIN bit not set?
+ throw Utils.TODO( "Multi-part from client: " + op ); // Multipart message
+ op &= 0x7F; // Strip FIN bit
+ switch( op&15) {
+ case 1: {
+ atLeast(5); // Min message length
+ // Message length
+ int len = rawget()-128;
+ if( len > 125 ) {
+ if( len > 126 )
+ throw Utils.TODO(); // Multi-byte length
+ len = (rawget()<<8) | rawget();
+ }
+ atLeast(len+4); // Rest of message
+ // Encoding key
+ _key[0] = (byte)rawget();
+ _key[1] = (byte)rawget();
+ _key[2] = (byte)rawget();
+ _key[3] = (byte)rawget();
+
+ // Decode bytes
+ for( int i=0; i<len; i++ )
+ _str[i] = (byte)(rawget() ^ _key[i & 3]);
+
+ return new String(_str,0,len);
+ }
+ // Client closes
+ case 8: return null;
+ // Ping
+ case 9:
+ // Send a PONG back. TODO: echo PING data in the PONG
+ _out.write(0x8A); // FIN + PONG
+ _out.write(0x00); // LENGTH==0
+ return get();
+ // Not (yet) implemented: continuation frames, ping/pong
+ default:
+ throw Utils.TODO("Unexpected op from client: "+op); // Multi-part message
+ }
+ }
+
+ // Classic put to browser client. No encoding.
+ public void put(String msg) throws IOException {
+ int len = msg.length();
+ if( len <= 125 ) {
+ _out.write(129); // FIN, opcode 1 - whole text message
+ _out.write(len);
+ for( int i=0; i<msg.length(); i++ )
+ _out.write(msg.charAt(i));
+ return;
+ }
+ if( len > 65535 )
+ throw Utils.TODO("MOAR LENGTH");
+ _out.write(129); // FIN, opcode 1 - whole text message
+ _out.write(126); // Length is 16b
+ _out.write(len>>>8);
+ _out.write(len&255);
+ for( int i=0; i<msg.length(); i++ )
+ _out.write(msg.charAt(i));
+ }
+
+ @Override public void close() throws IOException {
+ if( _out != null ) {
+ _out.write(0b10001000);
+ _out.flush();
+ }
+ super.close();
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/type/Field.java b/chapter21/src/main/java/com/seaofnodes/simple/type/Field.java
new file mode 100644
index 00000000..ffa9d2a6
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/type/Field.java
@@ -0,0 +1,74 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.Utils;
+import java.util.ArrayList;
+
+/**
+ * Represents a field in a struct. This is not a Type in the type system.
+ */
+public class Field extends Type {
+
+ // The pair {fieldName,type} uniquely identifies a field.
+
+ // Field name
+ public final String _fname;
+ // Type of the field
+ public final Type _type;
+ // Unique memory alias, not sensibly part of a "type" but very convenient here.
+ public final int _alias;
+ // Field must be written to exactly once, no more, no less
+ public final boolean _final;
+
+ private Field(String fname, Type type, int alias, boolean xfinal ) {
+ super(TFLD);
+ _fname = fname;
+ _type = type;
+ _alias = alias;
+ _final = xfinal;
+ }
+ // Make with existing alias
+ public static Field make( String fname, Type type, int alias, boolean xfinal ) {
+ return new Field(fname,type,alias,xfinal).intern();
+ }
+ public Field makeFrom( Type type ) {
+ return type == _type ? this : new Field(_fname,type,_alias,_final).intern();
+ }
+ @Override public Field makeRO() { return _final ? this : make(_fname,_type.makeRO(),_alias,true); }
+ @Override public boolean isFinal() { return _final && _type.isFinal(); }
+
+ public static final Field TEST = make("test",Type.NIL,-2,false);
+ public static final Field TEST2= make("test",Type.NIL,-2,true);
+ public static void gather(ArrayList<Type> ts) { ts.add(TEST); ts.add(TEST2); }
+
+ @Override Field xmeet( Type that ) {
+ Field fld = (Field)that; // Invariant
+ assert _fname.equals(fld._fname);
+ assert _alias==fld._alias;
+ return make(_fname,_type.meet(fld._type),_alias,_final | fld._final);
+ }
+
+ @Override
+ public Field dual() { return make(_fname,_type.dual(),_alias,!_final); }
+
+ @Override public Field glb() {
+ Type glb = _type.glb();
+ return (glb==_type && _final) ? this : make(_fname,glb,_alias,true);
+ }
+
+ // Override in subclasses
+ int hash() { return _fname.hashCode() ^ _type.hashCode() ^ _alias ^ (_final ? 1024 : 0); }
+
+ boolean eq(Type t) {
+ Field f = (Field)t;
+ return _fname.equals(f._fname) && _type==f._type && _alias==f._alias && _final==f._final;
+ }
+
+
+ @Override
+ public SB print( SB sb ) {
+ return _type.print(sb.p(_final?"":"!").p(_fname).p(":").p(_alias).p(" : "));
+ }
+
+ @Override public String str() { return _fname; }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter21/src/main/java/com/seaofnodes/simple/type/Type.java
new file mode 100644
index 00000000..6df4e7ab
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/type/Type.java
@@ -0,0 +1,236 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.Utils;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * These types are part of a Monotone Analysis Framework,
+ * @see <a href="https://www.cse.psu.edu/~gxt29/teaching/cse597s21/slides/08monotoneFramework.pdf">see for example this set of slides</a>.
+ * <p>
+ * The types form a lattice; @see <a href="https://en.wikipedia.org/wiki/Lattice_(order)">a symmetric complete bounded (ranked) lattice.</a>
+ * <p>
+ * This wild lattice theory will be needed later to allow us to easily beef up
+ * the analysis and optimization of the Simple compiler... but we don't need it
+ * now, just know that it is coming along in a later Chapter.
+ * <p>g
+ * One of the fun things here is that while the theory is deep and subtle, the
+ * actual implementation is darn near trivial and is generally really obvious
+ * what we're doing with it. Right now, it's just simple integer math to do
+ * simple constant folding e.g. 1+2 == 3 stuff.
+ */
+
+public class Type {
+ static final HashMap<Type,Type> INTERN = new HashMap<>();
+
+ // ----------------------------------------------------------
+ // Simple types are implemented fully here. "Simple" means: the code and
+ // type hierarchy are simple, not that the Type is conceptually simple.
+ static final byte TBOT = 0; // Bottom (ALL)
+ static final byte TTOP = 1; // Top (ANY)
+ static final byte TCTRL = 2; // Ctrl flow bottom
+ static final byte TXCTRL = 3; // Ctrl flow top (mini-lattice: any-xctrl-ctrl-all)
+ static final byte TNIL = 4; // low null of all flavors
+ static final byte TXNIL = 5; // high or choice null
+ static final byte TSIMPLE = 6; // End of the Simple Types
+
+ static final byte TPTR = 7; // All nil-able scalar values
+ static final byte TINT = 8; // All Integers; see TypeInteger
+ static final byte TFLT = 9; // All Floats ; see TypeFloat
+ static final byte TMEMPTR =10; // Memory pointer to a struct type
+ static final byte TFUNPTR =11; // Function pointer; unique signature and code address (just a bit)
+ static final byte TTUPLE =12; // Tuples; finite collections of unrelated Types, kept in parallel
+ static final byte TMEM =13; // All memory (alias 0) or A slice of memory - with specific alias
+ static final byte TSTRUCT =14; // Structs; tuples with named fields
+ static final byte TFLD =15; // Named fields in structs
+ static final byte TRPC =16; // Return Program Control (Return PC or RPC)
+
+ public final byte _type;
+
+ public boolean is_simple() { return _type < TSIMPLE; }
+ private static final String[] STRS = new String[]{"Bot","Top","Ctrl","~Ctrl","null","~nil"};
+ protected Type(byte type) { _type = type; }
+
+ public static final Type BOTTOM = new Type( TBOT ).intern(); // ALL
+ public static final Type TOP = new Type( TTOP ).intern(); // ANY
+ public static final Type CONTROL = new Type( TCTRL ).intern(); // Ctrl
+ public static final Type XCONTROL = new Type( TXCTRL ).intern(); // ~Ctrl
+ public static final Type NIL = new Type( TNIL ).intern(); // low null of all flavors
+ public static final Type XNIL = new Type( TXNIL ).intern(); // high or choice null
+ public static Type[] gather() {
+ ArrayList<Type> ts = new ArrayList<>();
+ ts.add(BOTTOM);
+ ts.add(CONTROL);
+ ts.add(NIL);
+ ts.add(XNIL);
+ TypeNil.gather(ts);
+ TypePtr.gather(ts);
+ TypeInteger.gather(ts);
+ TypeFloat.gather(ts);
+ TypeMemPtr.gather(ts);
+ TypeFunPtr.gather(ts);
+ TypeMem.gather(ts);
+ Field.gather(ts);
+ TypeStruct.gather(ts);
+ TypeTuple.gather(ts);
+ TypeRPC.gather(ts);
+ int sz = ts.size();
+ for( int i = 0; i < sz; i++ )
+ ts.add(ts.get(i).dual());
+ return ts.toArray(new Type[ts.size()]);
+ }
+
+ // Is high or on the lattice centerline.
+ public boolean isHigh () { return _type==TTOP || _type==TXCTRL || _type==TXNIL; }
+ public boolean isHighOrConst() { return isHigh() || isConstant(); }
+
+ // Strict constant values, things on the lattice centerline.
+ // Excludes both high and low values
+ public boolean isConstant() { return _type==TNIL; }
+
+ // ----------------------------------------------------------
+
+ // Notes on Type interning: At the moment it is not easy to reset the
+ // interned types because we hold static references to several types and
+ // these are scattered around. This means the INTERN cache will retain all
+ // types from every run of the Parser. For this to work correctly types
+ // must be rigorous about defining when they are the same. Also types need
+ // to be immutable once defined. The rationale for interning is
+ // *correctness* with cyclic type definitions. Simple structural recursive
+ // checks go exponential with merely sharing, but with cycles they will
+ // stack overflow and crash. Interning means we do not need to have sharing
+ // checks with every type compare, only during interning.
+
+ // Factory method which interns "this"
+ @SuppressWarnings("unchecked")
+ public <T extends Type> T intern() {
+ T nnn = (T)INTERN.get(this);
+ if( nnn==null )
+ INTERN.put(nnn=(T)this,this);
+ return nnn;
+ }
+
+ private int _hash; // Hash cache; not-zero when set.
+ @Override
+ public final int hashCode() {
+ if( _hash!=0 ) return _hash;
+ _hash = hash();
+ if( _hash==0 ) _hash = 0xDEADBEEF; // Bad hash from subclass; use some junk thing
+ return _hash;
+ }
+ // Override in subclasses
+ int hash() { return _type; }
+
+ @Override
+ public final boolean equals( Object o ) {
+ if( o==this ) return true;
+ if( !(o instanceof Type t)) return false;
+ if( _type != t._type ) return false;
+ return eq(t);
+ }
+ // Overridden in subclasses; subclass can assume "this!=t" and java classes are same
+ boolean eq(Type t) { return true; }
+
+
+ // ----------------------------------------------------------
+ public final Type meet(Type t) {
+ // Shortcut for the self case
+ if( t == this ) return this;
+ // Same-type is always safe in the subclasses
+ if( _type==t._type ) return xmeet(t);
+ // TypeNil vs TypeNil meet
+ if( this instanceof TypeNil ptr0 && t instanceof TypeNil ptr1 )
+ return ptr0.nmeet(ptr1);
+ // Reverse; xmeet 2nd arg is never "is_simple" and never equal to "this".
+ if( is_simple() ) return this.xmeet(t );
+ if( t.is_simple() ) return t .xmeet(this);
+ return Type.BOTTOM; // Mixing 2 unrelated types
+ }
+
+ // Compute meet right now. Overridden in subclasses.
+ // Handle cases where 'this.is_simple()' and unequal to 't'.
+ // Subclassed xmeet calls can assert that '!t.is_simple()'.
+ Type xmeet(Type t) {
+ assert is_simple(); // Should be overridden in subclass
+ // ANY meet anything is thing; thing meet ALL is ALL
+ if( _type==TBOT || t._type==TTOP ) return this;
+ if( _type==TTOP || t._type==TBOT ) return t;
+
+ // RHS TypeNil vs NIL/XNIL
+ if( _type== TNIL ) return t instanceof TypeNil ptr ? ptr.meet0() : (t._type==TXNIL ? TypePtr.PTR : BOTTOM);
+ if( _type== TXNIL ) return t instanceof TypeNil ptr ? ptr.meetX() : (t._type== TNIL ? TypePtr.PTR : BOTTOM);
+ // 'this' is only {TCTRL,TXCTRL}
+ // Other non-simple RHS things bottom out
+ if( !t.is_simple() ) return BOTTOM;
+ // If RHS is NIL/XNIL
+ if( t._type==TNIL || t._type==TXNIL ) return BOTTOM;
+ // Both are {TCTRL,TXCTRL} and unequal to each other
+ return _type==TCTRL || t._type==TCTRL ? CONTROL : XCONTROL;
+ }
+
+ public Type dual() {
+ return switch( _type ) {
+ case TBOT -> TOP;
+ case TTOP -> BOTTOM;
+ case TCTRL ->XCONTROL;
+ case TXCTRL-> CONTROL;
+ case TNIL ->XNIL;
+ case TXNIL -> NIL;
+ default -> throw Utils.TODO(); // Should not reach here
+ };
+ }
+
+ // ----------------------------------------------------------
+ // Our lattice is defined with a MEET and a DUAL.
+ // JOIN is dual of meet of both duals.
+ public final Type join(Type t) {
+ if( this==t ) return this;
+ return dual().meet(t.dual()).dual();
+ }
+
+ // True if this "isa" t; e.g. 17 isa TypeInteger.BOT
+ public boolean isa( Type t ) { return meet(t)==t; }
+
+ // True if this "isa" t up to named structures
+ public boolean shallowISA( Type t ) { return isa(t); }
+
+ public Type nonZero() { return TypePtr.NPTR; }
+
+ // Make a zero version of this type, 0 for integers and null for pointers.
+ public Type makeZero() { return Type.NIL; }
+
+ /** Compute greatest lower bound in the lattice */
+ public Type glb() { return Type.BOTTOM; }
+
+ // Is forward-reference
+ public boolean isFRef() { return false; }
+
+ // All reachable struct Fields are final
+ public boolean isFinal() { return true; }
+
+ // Make all reachable struct Fields final
+ public Type makeRO() { return this; }
+
+ // ----------------------------------------------------------
+
+ // Size in bits to hold an instance of this type.
+ // Sizes are expected to be between 1 and 64 bits.
+ // Size 0 means this either takes no space (such as a known-zero field)
+ // or isn't a scalar to be stored in memory.
+ public int log_size() { return 3; }
+
+ // ----------------------------------------------------------
+ // Useful in the debugger, which calls toString everywhere.
+ // This is a more verbose dev-friendly print.
+ @Override
+ public final String toString() {
+ return print(new SB()).toString();
+ }
+
+ public SB print(SB sb) { return sb.p(str()); }
+ public SB gprint(SB sb) { return print(sb); }
+
+ // This is used by error messages, and is a shorted print.
+ public String str() { return STRS[_type]; }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/type/TypeFloat.java b/chapter21/src/main/java/com/seaofnodes/simple/type/TypeFloat.java
new file mode 100644
index 00000000..aa205149
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/type/TypeFloat.java
@@ -0,0 +1,95 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.Utils;
+import java.util.ArrayList;
+
+/**
+ * Float Type
+ */
+public class TypeFloat extends Type {
+
+ public final static TypeFloat ONE = constant(1.0);
+ public final static TypeFloat FZERO = constant(0.0);
+ public final static TypeFloat F32 = make(32, 0);
+ public final static TypeFloat F64 = make(64, 0);
+
+ // - high -64, high -32, con 0, low +32, low +64
+ public final byte _sz;
+
+ /**
+ * The constant value or 0
+ */
+ public final double _con;
+
+ private TypeFloat(byte sz, double con) {
+ super(TFLT);
+ _sz = sz;
+ _con = con;
+ }
+ private static TypeFloat make(int sz, double con) {
+ return new TypeFloat((byte)sz,con).intern();
+ }
+
+ public static TypeFloat constant(double con) { return make(0, con); }
+
+ public static void gather(ArrayList<Type> ts) { ts.add(F64); ts.add(F32); ts.add(constant(3.141592653589793)); }
+
+ @Override public String str() {
+ return switch( _sz ) {
+ case -64 -> "~flt";
+ case -32 -> "~f32";
+ case 0 -> ""+_con+((float)_con==_con ? "f" : "");
+ case 32 -> "f32";
+ case 64 -> "flt";
+ default -> throw Utils.TODO();
+ };
+ }
+ private boolean isF32() { return ((float)_con)==_con; }
+
+ @Override public boolean isHigh () { return _sz< 0; }
+ @Override public boolean isConstant() { return _sz==0; }
+ @Override public int log_size() { return _sz==32 || _sz==-32 ? 2 : 3; }
+
+ public double value() { return _con; }
+
+ @Override
+ public TypeFloat xmeet(Type other) {
+ TypeFloat f = (TypeFloat)other;
+ // Invariant from caller: 'this' != 'other' and same class (TypeFloat).
+ TypeFloat i = (TypeFloat)other; // Contract
+ // Larger size in i1, smaller in i0
+ TypeFloat i0 = _sz < i._sz ? this : i;
+ TypeFloat i1 = _sz < i._sz ? i : this;
+
+ if( i1._sz== 64 ) return F64;
+ if( i0._sz==-64 ) return i1;
+ if( i1._sz== 32 )
+ return i0._sz==0 && !i0.isF32() ? F64 : F32;
+ if( i1._sz!= 0 ) return i1;
+ // i1 is a constant
+ if( i0._sz==-32 )
+ return i1.isF32() ? i1 : F64;
+ // Since both are constants, and are never equals (contract) unequals
+ // constants fall to bottom
+ return i0.isF32() && i1.isF32() ? F32 : F64;
+ }
+
+ @Override
+ public Type dual() {
+ return isConstant() ? this : make(-_sz,0); // Constants are a self-dual
+ }
+
+ @Override public Type makeZero() { return FZERO; }
+ @Override public Type glb() { return F64; }
+
+ @Override
+ int hash() { return (int)(Double.hashCode(_con) ^ _sz ^ (1<<17)); }
+ @Override
+ public boolean eq( Type t ) {
+ TypeFloat i = (TypeFloat)t; // Contract
+ // Allow NaN to check for equality
+ return _sz==i._sz && Double.doubleToLongBits(_con)==Double.doubleToLongBits(i._con);
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java b/chapter21/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java
new file mode 100644
index 00000000..bd64bd81
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java
@@ -0,0 +1,107 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.Utils;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * Represents a Pointer to a function.
+ * <p></p>
+ * Functions have argument types and a return type, making a function
+ * signature. Within a signature there can be many instances of functions, and
+ * each function is labeled with a small integer constant. A TFP can represent
+ * a single signature and a set of functions; the set might contain only 1
+ * function or the zeroth function (the null ptr).
+ * <p></p>
+ * Functions with different signatures cannot be mixed, and will result in a
+ * bottom function type, which can only be null-checked.
+ */
+public class TypeFunPtr extends TypeNil {
+ // A TypeFunPtr is Signature and a set of functions.
+ public final TypeTuple _sig;
+ public final Type _ret;
+ // Cheesy easy implementation for a small set; 1 bit per unique function
+ // within the TypeTuple. Can be upgraded to a BitSet for larger classes of
+ // functions. Negative means "these 63 concrete bits plus infinite unknown more"
+ public final long _fidxs; // 63 unique functions per signature
+
+ private TypeFunPtr(byte nil, TypeTuple sig, Type ret, long fidxs) {
+ super(TFUNPTR,nil);
+ assert sig != null;
+ _sig = sig;
+ _ret = ret;
+ _fidxs = fidxs;
+ }
+
+ public static TypeFunPtr make( byte nil, TypeTuple sig, Type ret, long fidxs ) { return new TypeFunPtr(nil,sig,ret,fidxs).intern(); }
+ public static TypeFunPtr make( boolean nil, TypeTuple sig, Type ret ) { return make((byte)(nil ? 3 : 2),sig,ret,-1); }
+ @Override TypeFunPtr makeFrom( byte nil ) { return nil ==_nil ? this : make( nil,_sig,_ret, _fidxs); }
+ public TypeFunPtr makeFrom( Type ret ) { return ret ==_ret ? this : make( _nil,_sig, ret, _fidxs); }
+ public TypeFunPtr makeFrom( int fidx ) { return make((byte)2, _sig,_ret,1L<<fidx ); }
+
+ public static TypeFunPtr BOT = make((byte)3,TypeTuple.BOT,Type.BOTTOM,-1);
+ 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<Type> ts) { ts.add(TEST); ts.add(TEST0); ts.add(BOT); ts.add(MAIN); }
+
+ @Override
+ Type xmeet(Type t) {
+ TypeFunPtr that = (TypeFunPtr) t;
+ return TypeFunPtr.make(xmeet0(that),(TypeTuple)_sig.meet(that._sig), _ret.meet(that._ret), _fidxs | that._fidxs);
+ }
+
+ @Override
+ public TypeFunPtr dual() { return TypeFunPtr.make(dual0(), _sig.dual(), _ret.dual(), ~_fidxs); }
+
+ // RHS is NIL; do not deep-dual when crossing the centerline
+ @Override public Type meet0() { return _nil==3 ? this : make((byte)3,_sig,_ret,_fidxs); }
+
+ @Override public TypeFunPtr glb() { return make((byte)3,_sig,_ret,-1L); }
+
+ @Override public boolean isHigh () { return _nil <= 1 || (_nil==2 && _fidxs==0); }
+ @Override public boolean isConstant() { return (_nil==2 && Long.bitCount(_fidxs)==1) || (_nil==3 && _fidxs==0); }
+
+ @Override public int log_size() { return 2; } // (1<<2)==4-byte pointers
+
+ public Type arg(int i) { return _sig._types[i]; }
+ public long fidxs() { return _fidxs; }
+ public Type ret() { return _ret; }
+ public int nargs() { return _sig._types.length; }
+ public int fidx() { assert Long.bitCount(_fidxs)==1; return Long.numberOfTrailingZeros(_fidxs); }
+
+ @Override
+ int hash() { return Utils.fold(_sig.hashCode() ^ _ret.hashCode() ^ _fidxs ^ super.hash()); }
+
+ @Override
+ boolean eq(Type t) {
+ TypeFunPtr ptr = (TypeFunPtr)t; // Invariant
+ return _sig == ptr._sig && _ret == ptr._ret && _fidxs == ptr._fidxs && super.eq(ptr);
+ }
+
+ @Override public String str() { return print(new SB()).toString(); }
+ @Override public SB print(SB sb) { return _print(sb,false,true); }
+ public SB print(SB sb, boolean n) { return _print(sb,false,n); }
+ @Override public SB gprint(SB sb) { return _print(sb,true ,true); }
+ private static SB _print(SB sb, boolean g, Type t) { return g ? t.gprint(sb) : t.print(sb); }
+ private SB _print(SB sb, boolean g, boolean n) {
+ sb.p(x()).p("{ ");
+ if( _sig._types!=null )
+ for( Type t : _sig._types )
+ _print(sb,g,t).p(" ");
+ _print(sb.p(g ? "&rarr; " : "-> "),g,_ret).p(" #");
+ if( isHigh() ) sb.p("~");
+ long fidxs = isHigh() ? ~_fidxs : _fidxs;
+ String fidx = fidxs==0 ? ""
+ : Long.bitCount(fidxs) == 1 ? ""+Long.numberOfTrailingZeros(fidxs)
+ : fidxs == -1 ? "ALL"
+ : "b"+Long.toBinaryString(fidxs); // Just some function bits
+ return sb.p(fidx).p("}").p(q());
+ }
+
+ // Usage: for( long fidxs=fidxs(); fidxs!=0; fidxs=nextFIDX(fidxs) { int fidxs = Long.numberOfTrailingZeros(fidxs); ... }
+ public static long nextFIDX(long fidxs) { return fidxs & (fidxs-1); }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter21/src/main/java/com/seaofnodes/simple/type/TypeInteger.java
new file mode 100644
index 00000000..cccb85a5
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/type/TypeInteger.java
@@ -0,0 +1,128 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.Utils;
+import java.util.ArrayList;
+
+/**
+ * Integer Type
+ */
+public class TypeInteger extends Type {
+
+ public final static TypeInteger ZERO= make(0,0);
+ public final static TypeInteger FALSE=ZERO;
+ public final static TypeInteger TRUE= make(1,1);
+
+ public final static TypeInteger I1 = make(-1,0);
+ public final static TypeInteger I8 = make(-128,127);
+ public final static TypeInteger I16 = make(-32768,32767);
+ public final static TypeInteger I32 = make(-1L<<31,(1L<<31)-1);
+ public final static TypeInteger BOT = make(Long.MIN_VALUE,Long.MAX_VALUE);
+ public final static TypeInteger TOP = BOT.dual();
+
+ public final static TypeInteger U1 = make(0,1);
+ public final static TypeInteger BOOL= U1;
+ public final static TypeInteger U8 = make(0,255);
+ public final static TypeInteger U16 = make(0,65535);
+ public final static TypeInteger U32 = make(0,(1L<<32)-1);
+
+ /**
+ * Describes an integer *range* - everything from min to max; both min and
+ * max are inclusive. If min==max, this is a constant.
+ * <br>
+ * If min <= max, this is a below center (towards bottom).
+ * If min > max, this is an above center (towards top).
+ */
+ public final long _min, _max;
+
+ private TypeInteger(long min, long max) {
+ super(TINT);
+ _min = min;
+ _max = max;
+ }
+
+ // Strict non-zero contract
+ public static TypeInteger make(long lo, long hi) { return new TypeInteger(lo,hi).intern(); }
+
+ public static TypeInteger constant(long con) { return make(con, con); }
+
+ public static void gather(ArrayList<Type> ts) { ts.add(I32); ts.add(BOT); ts.add(U1); ts.add(I1); ts.add(U8); }
+
+ @Override public String str() {
+ if( isConstant() ) return ""+_min;
+ long lo = _min, hi = _max;
+ String x = "";
+ if( hi < lo ) {
+ lo = _max; hi = _min;
+ x = "~";
+ }
+ return x+_str(lo,hi);
+ }
+ private static String _str(long lo, long hi) {
+ if( lo==Long.MIN_VALUE && hi==Long.MAX_VALUE ) return "int";
+ if( lo== 0 && hi== 1 ) return "bool";
+ if( lo== -1 && hi== 0 ) return "i1";
+ if( lo== -128 && hi== 127 ) return "i8";
+ if( lo== -32768 && hi== 32767 ) return "i16";
+ if( lo== -1L<<31 && hi==(1L<<31)-1 ) return "i32";
+ if( lo== 0 && hi== 255 ) return "u8";
+ if( lo== 0 && hi== 65535 ) return "u16";
+ if( lo== 0 && hi==(1L<<32)-1 ) return "u32";
+ return "["+lo+"-"+hi+"]";
+ }
+
+ @Override public boolean isHigh () { return _min > _max; }
+ @Override public boolean isConstant() { return _min == _max; }
+
+ @Override public int log_size() {
+ if( this==I8 || this==U8 || this==BOOL ) return 0; // 1<<0 == 1 bytes
+ if( this==I16 || this==U16 ) return 1; // 1<<1 == 2 bytes
+ if( this==I32 || this==U32 ) return 2; // 1<<2 == 4 bytes
+ if( this==BOT ) return 3; // 1<<3 == 8 bytes
+ if( isHighOrConst() ) return 0;
+ throw Utils.TODO();
+ }
+
+ public long value() { assert isConstant(); return _min; }
+
+ // AND-mask of forced zeros. e.g. unsigned types will return their mask;
+ // u8 will return 0xFF. But also a range of 16-18 (0x10-0x12) will return
+ // 0x13 - no value in the range {16,17,18} will allow bit 0x04 to be set.
+ public long mask() {
+ if( isHigh() ) return 0;
+ if( isConstant() ) return _min;
+ // Those bit positions which differ min to max
+ long x = _min ^ _max;
+ // Highest '1' bit in the differ set. Since the range is from min to
+ // max, all values below here are possible.
+ long ff1 = Long.highestOneBit(x);
+ // Make a all-1's mask from ff1, and set over the same bits (either min
+ // or max is ok).
+ long mask = _min | (ff1-1) | ff1;
+ return mask;
+ }
+
+ @Override
+ public Type xmeet(Type other) {
+ // Invariant from caller: 'this' != 'other' and same class (TypeInteger)
+ TypeInteger i = (TypeInteger)other; // Contract
+ return make(Math.min(_min,i._min), Math.max(_max,i._max));
+ }
+
+ @Override public TypeInteger dual() { return make(_max,_min); }
+
+ @Override public TypeInteger nonZero() {
+ if( isHigh() ) return this;
+ if( this==ZERO ) return null; // No sane answer
+ if( _min==0 ) return make(1,Math.max(_max,1)); // specifically good on BOOL
+ if( _max==0 ) return make(_min,-1);
+ return this;
+ }
+ @Override public Type makeZero() { return ZERO; }
+ @Override public TypeInteger glb() { return BOT; }
+ @Override int hash() { return Utils.fold(_min) * Utils.fold(_max); }
+ @Override public boolean eq( Type t ) {
+ TypeInteger i = (TypeInteger)t; // Contract
+ return _min==i._min && _max==i._max;
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/type/TypeMem.java b/chapter21/src/main/java/com/seaofnodes/simple/type/TypeMem.java
new file mode 100644
index 00000000..9ed4c7a8
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/type/TypeMem.java
@@ -0,0 +1,74 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.Utils;
+import java.lang.Long;
+import java.util.BitSet;
+import java.util.ArrayList;
+
+
+/**
+ * Represents a slice of memory corresponding to a set of aliases
+ */
+public class TypeMem extends Type {
+
+ // Which slice of memory?
+ // 0 means TOP, no slice.
+ // 0 means BOT, all memory.
+ // N means slice#N.
+ public final int _alias;
+ public final Type _t; // Memory contents, some scalar type
+
+ private TypeMem(int alias, Type t) {
+ super(TMEM);
+ assert alias!=0 || (t==Type.TOP || t==Type.BOTTOM);
+ _alias = alias;
+ _t = t;
+ }
+
+ public static TypeMem make(int alias, Type t) { return new TypeMem(alias,t).intern(); }
+ public static final TypeMem TOP = make(0, Type.TOP );
+ public static final TypeMem BOT = make(0, Type.BOTTOM);
+
+ public static void gather(ArrayList<Type> ts) { ts.add(make(1,Type.NIL)); ts.add(make(1,TypeInteger.ZERO)); ts.add(BOT); }
+
+ @Override
+ TypeMem xmeet(Type t) {
+ TypeMem that = (TypeMem) t; // Invariant: TypeMem and unequal
+ if( this==TOP ) return that;
+ if( that==TOP ) return this;
+ if( this==BOT ) return BOT;
+ if( that==BOT ) return BOT;
+ int alias = _alias==that._alias ? _alias : 0;
+ Type mt = _t.meet(that._t);
+ return make(alias,mt);
+ }
+
+ @Override
+ public Type dual() {
+ return make(_alias,_t.dual());
+ }
+
+ @Override public boolean isHigh() { return _t.isHigh(); }
+ @Override public Type glb() { return make(_alias,_t.glb()); }
+
+ @Override int hash() { return 9876543 + _alias + _t.hashCode(); }
+
+ @Override boolean eq(Type t) {
+ TypeMem that = (TypeMem) t; // Invariant
+ return _alias == that._alias && _t == that._t;
+ }
+
+ @Override public SB print(SB sb) {
+ sb.p("#");
+ if( _alias==0 ) return sb.p(_t._type==TTOP ? "TOP" : "BOT");
+ return _t.print(sb.p(_alias).p(":"));
+ }
+ @Override public SB gprint(SB sb) {
+ sb.p("#");
+ if( _alias==0 ) return sb.p(_t._type==TTOP ? "TOP" : "BOT");
+ return _t.gprint(sb.p(_alias).p(":"));
+ }
+
+ @Override public String str() { return toString(); }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/chapter21/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
new file mode 100644
index 00000000..284bf581
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
@@ -0,0 +1,105 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.Utils;
+import java.util.ArrayList;
+
+/**
+ * Represents a Pointer to memory.
+ *
+ * Null is generic pointer to non-existent memory.
+ * *void is a non-Null pointer to all possible refs, both structs and arrays.
+ * Pointers can be to specific struct and array types, or a union with Null.
+ * The distinguished *$BOT ptr represents union of *void and Null.
+ * The distinguished *$TOP ptr represents the dual of *$BOT.
+ */
+public class TypeMemPtr extends TypeNil {
+ // A TypeMemPtr is pair (obj,nil)
+ // where obj is a TypeStruct, possibly TypeStruct.BOT/TOP
+ // where nil is an explicit null is allowed or not
+
+ // Examples:
+ // (Person,false) - a not-nil Person
+ // (Person,true ) - a Person or a nil
+ // (BOT ,false) - a not-nil void* (unspecified struct)
+ // (TOP ,true ) - a nil
+
+ public final TypeStruct _obj;
+
+ private TypeMemPtr(byte nil, TypeStruct obj) {
+ super(TMEMPTR,nil);
+ assert obj!=null;
+ _obj = obj;
+ }
+ static TypeMemPtr make(byte nil, TypeStruct obj) { return new TypeMemPtr(nil, obj).intern(); }
+ public static TypeMemPtr makeNullable(TypeStruct obj) { return make((byte)3, obj); }
+ public static TypeMemPtr make(TypeStruct obj) { return new TypeMemPtr((byte)2, obj).intern(); }
+ public TypeMemPtr makeFrom(TypeStruct obj) { return obj==_obj ? this : make(_nil, obj); }
+ public TypeMemPtr makeNullable() { return makeFrom((byte)3); }
+ @Override TypeMemPtr makeFrom(byte nil) { return nil==_nil ? this : make(nil,_obj); }
+ @Override public TypeMemPtr makeRO() { return makeFrom(_obj.makeRO()); }
+ @Override public boolean isFinal() { return _obj.isFinal(); }
+
+ // An abstract pointer, pointing to either a Struct or an Array.
+ // Can also be null or not, so 4 choices {TOP,BOT} x {nil,not}
+ public static final TypeMemPtr BOT = make((byte)3, TypeStruct.BOT);
+ public static final TypeMemPtr TOP = BOT.dual();
+ public static final TypeMemPtr NOTBOT = make((byte)2,TypeStruct.BOT);
+
+ public static final TypeMemPtr TEST= make((byte)2, TypeStruct.TEST);
+ public static void gather(ArrayList<Type> ts) { ts.add(NOTBOT); ts.add(BOT); ts.add(TEST); }
+
+ @Override
+ public TypeNil xmeet(Type t) {
+ TypeMemPtr that = (TypeMemPtr) t;
+ return TypeMemPtr.make(xmeet0(that), (TypeStruct)_obj.meet(that._obj));
+ }
+
+ @Override
+ public TypeMemPtr dual() { return TypeMemPtr.make( dual0(), _obj.dual()); }
+
+ // RHS is NIL; do not deep-dual when crossing the centerline
+ @Override Type meet0() { return _nil==3 ? this : make((byte)3,_obj); }
+
+
+ // True if this "isa" t up to named structures
+ @Override public boolean shallowISA( Type t ) {
+ if( !(t instanceof TypeMemPtr that) ) return false;
+ if( this==that ) return true;
+ if( xmeet0(that)!=that._nil ) return false;
+ if( _obj==that._obj ) return true;
+ if( _obj._name.equals(that._obj._name) )
+ return true; // Shallow, do not follow matching names, just assume ok
+ throw Utils.TODO(); // return _obj.shallowISA(that._obj);
+ }
+
+ @Override public TypeMemPtr glb() { return make((byte)3,_obj.glb()); }
+ // Is forward-reference
+ @Override public boolean isFRef() { return _obj.isFRef(); }
+
+ @Override public int log_size() { return 3; } // (1<<3)==8-byte pointers
+
+ @Override int hash() { return _obj.hashCode() ^ super.hash(); }
+
+ @Override boolean eq(Type t) {
+ TypeMemPtr ptr = (TypeMemPtr)t; // Invariant
+ return _obj == ptr._obj && super.eq(ptr);
+ }
+
+ @Override public String str() {
+ if( this== NOTBOT) return "*void";
+ if( this== BOT) return "*void?";
+ return x()+"*"+_obj.str()+q();
+ }
+
+ @Override public SB print(SB sb) {
+ if( this== NOTBOT) return sb.p("*void");
+ if( this== BOT) return sb.p("*void?");
+ return _obj.print(sb.p(x()).p("*")).p(q());
+ }
+ @Override public SB gprint(SB sb) {
+ if( this== NOTBOT) return sb.p("*void");
+ if( this== BOT) return sb.p("*void?");
+ return _obj.gprint(sb.p(x()).p("*")).p(q());
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/type/TypeNil.java b/chapter21/src/main/java/com/seaofnodes/simple/type/TypeNil.java
new file mode 100644
index 00000000..aee623ba
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/type/TypeNil.java
@@ -0,0 +1,62 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.Utils;
+import java.util.ArrayList;
+
+/**
+ * Nil-able Scalar types
+ */
+public abstract class TypeNil extends Type {
+ // 0 = high-subclass choice nil
+ // 1 = high-subclass no nil
+ // 2 = low -subclass no nil
+ // 3 = low -subclass also nil
+ final byte _nil;
+
+ TypeNil(byte t, byte nil ) { super(t); _nil = nil; }
+
+ public static void gather(ArrayList<Type> ts) { }
+
+ abstract TypeNil makeFrom(byte nil);
+
+ byte xmeet0(TypeNil that) { return (byte)Math.max(_nil,that._nil); }
+
+ byte dual0() { return (byte)(3-_nil); }
+
+ // RHS is NIL
+ abstract Type meet0();
+
+ // RHS is XNIL
+ Type meetX() {
+ return _nil==0 ? XNIL : (_nil<=2 ? TypePtr.NPTR : TypePtr.PTR);
+ }
+
+ Type nmeet(TypeNil tn) {
+ // Invariants: both are TypeNil subclasses and unequal classes.
+ // If this is TypePtr, we went to TypePtr.nmeet and not here.
+ // If that is TypePtr, this is not (invariant); reverse and go again.
+ if( tn instanceof TypePtr ts ) return ts.nmeet(this);
+
+ // Two mismatched TypeNil, no Scalar.
+ if( _nil==0 && tn._nil==0 ) return XNIL;
+ if( _nil<=2 && tn._nil<=2 ) return TypePtr.NPTR;
+ return TypePtr.PTR;
+ }
+
+ @Override public boolean isHigh () { return _nil <= 1; }
+ @Override public boolean isConstant () { return false; }
+ @Override public boolean isHighOrConst() { return isHigh() || isConstant(); }
+
+ @Override public Type glb() { return Type.NIL; }
+
+ public boolean notNull() { return _nil==1 || _nil==2; }
+ public boolean nullable() { return _nil==3; }
+
+ final String q() { return _nil == 1 || _nil == 2 ? "" : "?"; }
+ final String x() { return isHigh() ? "~" : ""; }
+
+ int hash() { return _nil<<17; }
+
+ boolean eq(TypeNil ptr) { return _nil == ptr._nil; }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/type/TypePtr.java b/chapter21/src/main/java/com/seaofnodes/simple/type/TypePtr.java
new file mode 100644
index 00000000..e9091c25
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/type/TypePtr.java
@@ -0,0 +1,53 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.Utils;
+import java.util.ArrayList;
+
+/**
+ * Represents a Scalar; a single register-sized value.
+ */
+public class TypePtr extends TypeNil {
+
+ private TypePtr(byte nil) { super(TPTR,nil); }
+
+ // An abstract pointer, pointing to either a Struct or an Array.
+ // Can also be null or not, so 4 choices {TOP,BOT} x {nil,not}
+ public static TypePtr XPTR = new TypePtr((byte)0).intern();
+ public static TypePtr XNPTR= new TypePtr((byte)1).intern();
+ public static TypePtr NPTR = new TypePtr((byte)2).intern();
+ public static TypePtr PTR = new TypePtr((byte)3).intern();
+ private static final TypePtr[] PTRS = new TypePtr[]{XPTR,XNPTR,NPTR,PTR};
+
+ public static void gather(ArrayList<Type> ts) { ts.add(PTR); ts.add(NPTR); }
+
+ TypeNil makeFrom(byte nil) { throw Utils.TODO(); }
+
+ @Override public TypeNil xmeet(Type t) {
+ TypePtr that = (TypePtr) t;
+ return PTRS[xmeet0(that)];
+ }
+
+ @Override public TypePtr dual() { return PTRS[dual0()]; }
+
+ // High scalar loses, low scalar wins
+ @Override TypeNil nmeet(TypeNil tn) {
+ if( _nil==0 ) return tn; // High scalar loses
+ if( _nil==1 ) return tn.makeFrom(xmeet0(tn)); // High scalar loses
+ if( _nil==2 ) return tn._nil==3 ? PTR : NPTR; // Low scalar wins
+ return PTR; // this
+ }
+
+
+ // RHS is NIL
+ @Override Type meet0() { return isHigh() ? NIL : PTR; }
+ // RHS is XNIL
+ // 0->xscalar, 1->nscalar, 2->nscalar, 3->scalar
+ @Override Type meetX() { return _nil==0 ? XNIL : (_nil==3 ? PTR : NPTR); }
+
+ @Override public TypePtr glb() { return PTR; }
+
+ private static final String[] STRS = new String[]{"~ptr","~nptr","nptr","ptr"};
+ @Override public String str() { return STRS[_nil]; }
+ @Override public SB print(SB sb) { return sb.p(str()); }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/type/TypeRPC.java b/chapter21/src/main/java/com/seaofnodes/simple/type/TypeRPC.java
new file mode 100644
index 00000000..ec2f5f0a
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/type/TypeRPC.java
@@ -0,0 +1,100 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.Utils;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.HashSet;
+
+/**
+ * Return Program Control or Return PC or RPC
+ */
+public class TypeRPC extends Type {
+
+ // A set of CallEndNode IDs (or StopNode); commonly just one.
+ // Basically a sparse bit set
+ final HashSet<Integer> _rpcs;
+
+ // If true, invert the meaning of the bits
+ final boolean _any;
+
+ private TypeRPC(boolean any, HashSet<Integer> rpcs) {
+ super(TRPC);
+ _any = any;
+ _rpcs = rpcs;
+ }
+ private static TypeRPC make(boolean any, HashSet<Integer> rpcs) {
+ return new TypeRPC(any,rpcs).intern();
+ }
+
+ public static TypeRPC constant(int cend) {
+ HashSet<Integer> rpcs = new HashSet<>();
+ rpcs.add(cend);
+ return make(false,rpcs);
+ }
+
+ public static final TypeRPC BOT = make(true,new HashSet<>());
+ private static final TypeRPC TEST2 = constant(2);
+ private static final TypeRPC TEST3 = constant(2);
+
+ public static void gather(ArrayList<Type> ts) { ts.add(BOT); ts.add(TEST2); ts.add(TEST3); }
+
+ @Override public String str() {
+ if( _rpcs.isEmpty() )
+ return _any ? "$[ALL]" : "$[]";
+ if( _rpcs.size()==1 )
+ for( Integer rpc : _rpcs )
+ return _any ? "$[-"+rpc+"]" : "$["+rpc+"]";
+ return "$["+(_any ? "-" : "")+_rpcs+"]";
+ }
+
+ @Override public boolean isConstant() {
+ return !_any && _rpcs.size()==1;
+ }
+
+ @Override
+ public TypeRPC xmeet(Type other) {
+ TypeRPC rpc = (TypeRPC)other;
+ // If the two sets are equal, the _any must be unequal (invariant),
+ // so they cancel and all bits are set.
+ if( _rpcs.equals(rpc._rpcs) )
+ return BOT;
+ // Classic union of bit sets (which might be infinite).
+ HashSet<Integer> lhs = _rpcs, rhs = rpc._rpcs;
+ // Smaller on left
+ if( lhs.size() > rhs.size() ) { lhs = rpc._rpcs; rhs = _rpcs; }
+
+ HashSet<Integer> rpcs = new HashSet<>();
+ boolean any = true;
+ // If both sets are infinite, intersect.
+ if( _any && rpc._any ) {
+ for( Integer i : lhs ) if( rhs.contains(i) ) rpcs.add(i);
+
+ } else if( !_any && !rpc._any ) {
+ // if neither set is infinite, union.
+ rpcs.addAll( lhs );
+ rpcs.addAll( rhs );
+ any = false;
+ } else {
+ // If one is infinite, subtract the other from it.
+ HashSet<Integer> sub = _any ? rpc._rpcs : _rpcs;
+ HashSet<Integer> inf = _any ? _rpcs : rpc._rpcs;
+ for( Integer i : inf )
+ if( inf.contains(i) && !sub.contains(i) )
+ rpcs.add(i);
+ }
+ return make(any,rpcs);
+ }
+
+ @Override
+ public Type dual() { return make(!_any,_rpcs); }
+
+ @Override
+ int hash() { return _rpcs.hashCode() ^ (_any ? -1 : 0) ; }
+ @Override
+ public boolean eq( Type t ) {
+ TypeRPC rpc = (TypeRPC)t; // Contract
+ return _any==rpc._any && _rpcs.equals(rpc._rpcs);
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/chapter21/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
new file mode 100644
index 00000000..e981b5dc
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
@@ -0,0 +1,249 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.Utils;
+import java.util.ArrayList;
+
+/**
+ * Represents a struct type.
+ */
+public class TypeStruct extends Type {
+
+ // A Struct has a name and a set of fields; the fields themselves have
+ // names, types and aliases. The name has no semantic meaning, but is
+ // useful for debugging. Briefly during parsing a Struct can be a
+ // forward-ref; in this case the _fields array is null.
+
+ // Its illegal to attempt to load a field from a forward-ref struct.
+
+ // Example: "int rez = new S.x; struct S { int x; } return rez;" // Error, S not defined
+ // Rewrite: "struct S { int x; } int rez = new S.x; return rez;" // Ok, no forward ref
+ //
+ // During the normal optimization run, struct types "bottom out" at further
+ // struct references, so we don't have to handle e.g. cyclic types. The
+ // "bottom out" is again the forward-ref struct.
+ public final String _name;
+ public final Field[] _fields;
+
+ TypeStruct(String name, Field[] fields) {
+ super(TSTRUCT);
+ _name = name;
+ _fields = fields;
+ }
+
+ // All fields directly listed
+ public static TypeStruct make(String name, Field... fields) { return new TypeStruct(name, fields).intern(); }
+ public static final TypeStruct TOP = make("$TOP",new Field[0]);
+ public static final TypeStruct BOT = make("$BOT",new Field[0]);
+ public static final TypeStruct TEST = make("test",new Field[]{Field.TEST});
+ // Forward-ref version
+ public static TypeStruct makeFRef(String name) { return make(name, (Field[])null); }
+ // Make a read-only version
+ @Override public TypeStruct makeRO() {
+ if( isFinal() ) return this;
+ Field[] flds = new Field[_fields.length];
+ for( int i=0; i<flds.length; i++ )
+ flds[i] = _fields[i].makeRO();
+ return make(_name,flds);
+ }
+
+ // Array
+ public static TypeStruct makeAry(TypeInteger len, int lenAlias, Type body, int bodyAlias) {
+ assert body instanceof TypeInteger || body instanceof TypeFloat || (body instanceof TypeNil tn && tn.nullable());
+ assert len.isa(TypeInteger.U32);
+ return make("[" + body.str() + "]",
+ Field.make("#" ,len , lenAlias,true ),
+ Field.make("[]",body,bodyAlias,false));
+ }
+
+ // A pair of self-cyclic types
+ private static final TypeStruct S1F = makeFRef("S1");
+ private static final TypeStruct S2F = makeFRef("S2");
+ public static final TypeStruct S1 = make("S1", Field.make("a", TypeInteger.BOT, -1, false), Field.make("s2",TypeMemPtr.make((byte)2,S2F),-2, false) );
+ private static final TypeStruct S2 = make("S2", Field.make("b", TypeFloat .F64, -3, false), Field.make("s1",TypeMemPtr.make((byte)2,S1F),-4, false) );
+
+ private static final TypeStruct ARY = makeAry(TypeInteger.U32,-1,TypeInteger.BOT,-2);
+
+ public static void gather(ArrayList<Type> ts) { ts.add(TEST); ts.add(BOT); ts.add(S1); ts.add(S2); ts.add(ARY); }
+
+ // Find field index by name
+ public int find(String fname) {
+ for( int i=0; i<_fields.length; i++ )
+ if( _fields[i]._fname.equals(fname) )
+ return i;
+ return -1;
+ }
+ public Field field(String fname) {
+ int idx = find(fname);
+ return idx== -1 ? null : _fields[idx];
+ }
+
+ // Find field index by alias
+ public int findAlias( int alias ) {
+ for( int i=0; i<_fields.length; i++ )
+ if( _fields[i]._alias==alias )
+ return i;
+ return -1;
+ }
+
+
+ @Override
+ Type xmeet(Type t) {
+ TypeStruct that = (TypeStruct) t;
+ if( this==TOP ) return that;
+ if( that==TOP ) return this;
+ if( this==BOT ) return BOT;
+ if( that==BOT ) return BOT;
+
+ // Within the same compilation unit, struct names are unique. If the
+ // names differ, its different structs. Across many compilation units,
+ // structs with the same name but different field layouts can be
+ // interned... which begs the question:
+ // "What is the meet of structs from two different compilation units?"
+ // And the answer is: "don't ask".
+ if( !_name.equals(that._name) )
+ return BOT; // It's a struct; that's about all we know
+ if( this._fields==null ) return that;
+ if( that._fields==null ) return this;
+ if( _fields.length != that._fields.length ) return BOT;
+ // Just do field meets
+ Field[] flds = new Field[_fields.length];
+ for( int i=0; i<_fields.length; i++ ) {
+ Field f0 = _fields[i], f1 = that._fields[i];
+ if( !f0._fname.equals(f1._fname) || f0._alias != f1._alias )
+ return BOT;
+ flds[i] = (Field)f0.meet(f1);
+ }
+ return make(_name,flds);
+ }
+
+ @Override
+ public TypeStruct dual() {
+ if( this==TOP ) return BOT;
+ if( this==BOT ) return TOP;
+ if( _fields == null ) return this;
+ Field[] flds = new Field[_fields.length];
+ for( int i=0; i<_fields.length; i++ )
+ flds[i] = _fields[i].dual();
+ return make(_name,flds);
+ }
+
+ // Keeps the same struct, but lower-bounds all fields.
+ @Override public TypeStruct glb() {
+ if( _glb() ) return this;
+ // Need to glb each field
+ Field[] flds = new Field[_fields.length];
+ for( int i=0; i<_fields.length; i++ )
+ flds[i] = _fields[i].glb();
+ return make(_name,flds);
+ }
+ private boolean _glb() {
+ if( _fields!=null )
+ for( Field f : _fields )
+ if( f.glb() != f )
+ return false;
+ return true;
+ }
+
+ // Is forward-reference
+ @Override public boolean isFRef() { return _fields==null; }
+ // All fields are final
+ @Override public boolean isFinal() {
+ if( _fields==null ) return true;
+ for( Field fld : _fields )
+ if( !fld.isFinal() )
+ return false;
+ return true;
+ }
+
+ @Override
+ boolean eq(Type t) {
+ TypeStruct ts = (TypeStruct)t; // Invariant
+ if( !_name.equals(ts._name) )
+ return false;
+ if( _fields == ts._fields ) return true;
+ if( _fields==null || ts._fields==null ) return false;
+ if( _fields.length!=ts._fields.length )
+ return false;
+ for( int i = 0; i < _fields.length; i++ )
+ if( _fields[i] != ts._fields[i] )
+ return false;
+ return true;
+ }
+
+ @Override
+ int hash() {
+ long hash = _name.hashCode();
+ if( _fields != null )
+ for( Field f : _fields )
+ hash = Utils.rot(hash,13) ^ f.hashCode();
+ return Utils.fold(hash);
+ }
+
+ @Override
+ public SB print(SB sb) {
+ sb.p(_name);
+ if( _fields == null ) return sb; // Forward reference struct, just print the name
+ if( isAry() ) return sb; // Skip printing generic array fields
+ sb.p(" {");
+ for( Field f : _fields )
+ f._type.print(sb).p(f._final ? " " : " !").p(f._fname).p("; ");
+ return sb.p("}");
+ }
+ @Override public SB gprint( SB sb ) { return sb.p(_name); }
+
+ @Override public String str() { return _name; }
+
+
+ public boolean isAry() { return _fields.length==2 && _fields[1]._fname.equals("[]"); }
+
+ public int aryBase() {
+ assert isAry();
+ if( _offs==null ) _offs = offsets();
+ return _offs[1];
+ }
+ public int aryScale() {
+ assert isAry();
+ return _fields[1]._type.log_size();
+ }
+
+
+ // Field offsets as packed byte offsets
+ private int[] _offs; // Lazily computed and not part of the type semantics
+ public int offset(int idx) {
+ if( _offs==null ) _offs = offsets();
+ return _offs[idx];
+ }
+ private int[] offsets() { // Field byte offsets
+ // Compute a layout for a collection of fields
+ assert _fields != null; // No forward refs
+
+ // Array layout is different: len,[pad],body...
+ if( isAry() )
+ return _offs = new int[]{ 0, _fields[1]._type.log_size() < 3 ? 4 : 8 };
+
+ // Compute a layout
+ int[] cnts = new int[4]; // Count of fields at log field size
+ for( Field f : _fields )
+ cnts[f._type.log_size()]++; // Log size is 0(byte), 1(i16/u16), 2(i32/f32), 3(i64/dbl)
+ int off = 0, idx = 0; // Base common struct fields go here, e.g. Mark/Klass
+ // Compute offsets to the start of each power-of-2 aligned fields.
+ int[] offs = new int[4];
+ for( int i=3; i>=0; i-- ) {
+ offs[i] = off;
+ off += cnts[i]<<i;
+ }
+ // Assign offsets to all fields.
+ // Really a hidden radix sort.
+ _offs = new int[_fields.length+1];
+ for( Field f : _fields ) {
+ int log = f._type.log_size();
+ _offs[idx++] = offs[log]; // Field offset
+ offs[log] += 1<<log; // Next field offset at same alignment
+ cnts[log]--; // Count down, should be all zero at end
+ }
+ _offs[_fields.length] = (off+7)& ~7; // Round out to max alignment
+ return _offs;
+ }
+
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/chapter21/src/main/java/com/seaofnodes/simple/type/TypeTuple.java
new file mode 100644
index 00000000..d3b19b52
--- /dev/null
+++ b/chapter21/src/main/java/com/seaofnodes/simple/type/TypeTuple.java
@@ -0,0 +1,115 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.SB;
+import java.util.ArrayList;
+
+public class TypeTuple extends Type {
+
+ public final Type[] _types;
+
+ private TypeTuple(Type[] types) { super(TTUPLE); _types = types; }
+ public static TypeTuple make(Type... types) { return new TypeTuple(types).intern(); }
+
+ public static final TypeTuple BOT = new TypeTuple(new Type[0]).intern();
+ public static final TypeTuple TOP = new TypeTuple(null).intern();
+
+ public static final TypeTuple TEST = make(TypeInteger.BOT,TypeMemPtr.TEST);
+ 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});
+ public static final TypeTuple IF_TRUE = make(new Type[]{Type. CONTROL,Type.XCONTROL});
+ public static final TypeTuple IF_FALSE = make(new Type[]{Type.XCONTROL,Type. CONTROL});
+
+ public static void gather(ArrayList<Type> ts) { ts.add(BOT); ts.add(TEST); ts.add(START); ts.add(MAIN); ts.add(IF_TRUE); }
+
+ @Override
+ Type xmeet(Type other) {
+ TypeTuple tt = (TypeTuple)other; // contract from xmeet
+ if( this==TOP ) return other;
+ if( tt ==TOP ) return this ;
+ if( _types.length != tt._types.length )
+ return BOT;
+ Type[] ts = new Type[_types.length];
+ for( int i=0; i<_types.length; i++ )
+ ts[i] = _types[i].meet(tt._types[i]);
+ return make(ts);
+ }
+
+ @Override public TypeTuple dual() {
+ if( this==TOP ) return BOT;
+ if( this==BOT ) return TOP;
+ Type[] ts = new Type[_types.length];
+ for( int i=0; i<_types.length; i++ )
+ ts[i] = _types[i].dual();
+ return make(ts);
+ }
+
+ @Override
+ public Type glb() {
+ Type[] ts = new Type[_types.length];
+ for( int i=0; i<_types.length; i++ )
+ ts[i] = _types[i].glb();
+ 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(); }
+
+ @Override public SB print(SB sb) {
+ if( this==TOP ) return sb.p("[TOP]");
+ if( this==BOT ) return sb.p("[BOT]");
+ sb.p("[ ");
+ for( Type t : _types )
+ t.print(sb).p(", ");
+ return sb.unchar(2).p("]");
+ }
+ @Override public SB gprint(SB sb) {
+ if( this==TOP ) return sb.p("[TOP]");
+ if( this==BOT ) return sb.p("[BOT]");
+ sb.p("[ ");
+ for( Type t : _types )
+ t.gprint(sb).p(", ");
+ return sb.unchar(2).p("]");
+ }
+
+ @Override
+ int hash() {
+ int sum = 0;
+ if( _types!=null ) for( Type type : _types ) sum ^= type.hashCode();
+ return sum;
+ }
+
+ @Override
+ boolean eq( Type t ) {
+ TypeTuple tt = (TypeTuple)t; // Contract
+ if( _types==null && tt._types==null ) return true;
+ if( _types==null || tt._types==null ) return false;
+ if( _types.length != tt._types.length ) return false;
+ for( int i=0; i<_types.length; i++ )
+ if( _types[i]!=tt._types[i] )
+ return false;
+ return true;
+ }
+
+
+}