diff options
| author | Cliff Click <cliffc@acm.org> | 2025-03-08 09:59:31 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-03-08 09:59:31 -0800 |
| commit | 2370fb29d4538479af9eb94fc666a5ce09fcb492 (patch) | |
| tree | f21ed94228e23c6786a175741b785ab85888fadb /chapter20 | |
| parent | 53452331d8e69dc3bcd40a56a4f00f9380140e73 (diff) | |
| download | Simple-2370fb29d4538479af9eb94fc666a5ce09fcb492.tar.gz Simple-2370fb29d4538479af9eb94fc666a5ce09fcb492.zip | |
Ch20 reg (#166)
* READMEs
* Update README.md
* Update README.md
* Move printing into print dir
* Move codegen into codegen
* Framework for Reg Alloc
* First cut BuildLRG
RegMask.EMPTY -> null
* Setup for first simple split
* First cut split code for mismatch regs
* 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.
* Next cut regalloc
Building IFG.
Coloring.
Some nested spilling
Optimize divf/con -> mul times inverse
* 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
* 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
* README updates
* Fix ch19 mis-merge?
* x86 call args, risc5 load/store regs
* Push RISC WMASK
avoid writing to RSP
* 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
* 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.
* 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
* remove jukn files
* minimal "make release"
* 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
* 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
* Push l,ast change, unbreak branch
* 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
* Bunch of minor arm, risc fixes
small constants not cloning
* 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
* 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
* Cleanup call ABI presentation
Everybody uses the same names, same code shapes
* Caller save regs
...not callee save yet
* Expose caller save regs
* 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
* 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
* 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
* yank MulIRISC
* fix arg flags reg
Arm split op can spill flags
bugfix RegAlloc 1st real >=64bit reg num
* Update Chapter20Test.java
* Turn off transpile tests
which are broken for some time now
* A lot more readme
* better flags
ListScheduler stalls cmp/jmp in the same block; avoids some flags spills.
Make CmpIX86/ARM rematieralize.
Bugfix: remove some include x86/risc/arm in other dirs.
Canonicalize boilerplate fcn order
* regalloc improves
Use FltRISC, not clonable requires a spill from int->flt regs.
new splits track origin LRG, so later splits in the same pass know what lrg a spilled input is.
* RA improvements & fixes
Bugfix on split loop-depth computation.
Track killmask kills, which are always capacity kills and require more spilling.
Rename the riscv registers canonically, so I can tell them part! Definitely no GP or TP in Simple.
Node insertAfter bypass a little more conservative.
* RA bugfix
remove unified failed live ranges
* easier access for remote ports
debug support
* Lots more comments
* bugfix
infinite peep cycle on (17 ^ 3.14), i.e. bad program
* Fix imm12 shift
---------
Co-authored-by: Tamás Hadházy <85063808+Hels15@users.noreply.github.com>
Diffstat (limited to 'chapter20')
224 files changed, 7235 insertions, 595 deletions
diff --git a/chapter20/Makefile b/chapter20/Makefile index 4c1e1f49..f96cfea3 100644 --- a/chapter20/Makefile +++ b/chapter20/Makefile @@ -53,7 +53,7 @@ endif default: $(default_targets) # Compile just the out-of-date files -$(main_classes): build/classes/main/%class: $(SRC)/%java +$(main_classes): $(CLZDIR)/main/%class: $(SRC)/%java @echo "compiling " $@ " because " $? @[ -d $(CLZDIR)/main ] || mkdir -p $(CLZDIR)/main @javac $(JAVAC_ARGS) -cp "$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(SRC) -d $(CLZDIR)/main $(main_javas) @@ -64,7 +64,7 @@ $(test_classes): $(CLZDIR)/test/%class: $(TST)/%java $(main_classes) @javac $(JAVAC_ARGS) -cp "$(CLZDIR)/test$(SEP)$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(TST) -d $(CLZDIR)/test $(test_javas) # Base launch line for JVM tests -JVM=nice java -ea -cp "build/classes/main${SEP}${jars}${SEP}$(CLZDIR)/test" +JVM=nice java -ea -cp "$(CLZDIR)/main${SEP}${jars}${SEP}$(CLZDIR)/test" tests: $(default_targets) @echo "testing " $(test_cp) @@ -75,6 +75,14 @@ fuzzer: $(default_targets) @echo "fuzzing " $(test_cp) @$(JVM) org.junit.runner.JUnitCore com.seaofnodes.simple.FuzzerWrap +# Build a Simple jar +release: build/release/simple.jar + +# Build a Simple jar +build/release/simple.jar: $(main_classes) $(test_classes) + @echo "jarring " $@ " because " $? + @[ -d $(dir $@) ] || mkdir -p $(dir $@) + @jar cf build/release/simple.jar -C $(CLZDIR)/main . -C $(CLZDIR)/test . -C $(SRC)/$(SIMPLE) . -C $(TST)/$(SIMPLE) . # Launch viewer view: $(main_classes) diff --git a/chapter20/README.md b/chapter20/README.md index d3d0fddd..ae2b9df0 100644 --- a/chapter20/README.md +++ b/chapter20/README.md @@ -1,9 +1,433 @@ # Chapter 20: Graph Coloring Register Allocation -This is a Briggs-Chaitan-Click allocator, and is very similar to the one used +## Some Reading Material + +This is a Briggs-Chaitin-Click allocator, and is very similar to the one used in HotSpot's C2 allocator for the past 25 years with great success. +Wikipedia has a nice overview of register allocation in general: https://en.wikipedia.org/wiki/Register_allocation + +Chaitin's original: https://en.wikipedia.org/wiki/Register_allocation#CITEREFChaitin1982 + +Briggs additions: https://dl.acm.org/doi/10.1145/177492.177575 + +There are several other useful link that can be found by searching for Briggs +or Chaitin in combination. Basically there was a spate of register allocation +improvements in this era that made it into many high-end mainstream compilers. + +I've included a very old unpublished paper on speeding up graph coloring +allocators: [Interference Graph Triming](docs/ifg_trim.pdf). + You can also read [this chapter](https://github.com/SeaOfNodes/Simple/tree/linear-chapter20) in a linear Git revision history on the [linear](https://github.com/SeaOfNodes/Simple/tree/linear) branch and [compare](https://github.com/SeaOfNodes/Simple/compare/linear-chapter19...linear-chapter20) it to the previous chapter. -## Registers + +## The Basic Theory + +The reading material includes several explainations of the basic concepts, but +at the risk of being redundant I'll present a short version again here. + +I assume you (the reader) know *why* register allocation is required: machine +registers are a high value finite resource, and as with all finite resources +they need to be allocated well. + +Graph Coloring is one such allocation method; machine registers are treated as +colors; a *live range interference graph* is built, with each *live range* +requiring its own register/color. Coloring the graph ensures every live range +gets a unique register, specificially when it overlaps or *interferes* with +another live range. + +A successful coloring ends the allocation and after some bookkeeping the +program is ready for e.g. code emission. A failed coloring requires spilling +or splitting conflicting live ranges; in this allocator we will be doing live +range *splitting*. Splitting live ranges makes them more colorable; they +become shorter with fewer interfering other live ranges. Generally large +functions might require a few rounds of splitting before becoming colorable. +Since the problem is NP-complete there are no guarantees here, the final +allocation quality becomes heavily dependent on splitting and coloring +heuristics. + +Hence, much of the "high theory" will be used to drive the core coloring +algorithm, but much of the "actual success" will depend on a large collection +of heuristics. + +### Coloring Rounds + +Coloring proceeds in rounds until we get a coloring: + +- Build the Live Ranges; live ranges require the same register for all + instructions in the live range. Phi functions will force unrelated def/use + edges to become part of the same live range and this can drive large + interconnected live ranges with a lot of conflicts. Some hardware ops + require the same register for both inputs and outputs (X86 is notorious here) + which also makes fewer larger live ranges. We'll be using the disjoint + Union-Find algorithm to rapidly build up live ranges in one forward pass over + the program. + +- Build the Inteference Graph: every live range which is alive at the same time + as any other, now *interferes* or conflicts. This is built using a LIVE-ness + pass, which is a backwards pass over the CFG, and can require more than one + pass according to loop nesting depth. The data structure here is generally a + 2-D bitset, triangulated to cut its size in half. As part of LIVE, we'll + also have a set of reaching defs per-block. + +- Color the interference graph. Nodes (live ranges) in the graph which are + strictly low degree (more available registers than neighbors) will guaranteed + get a color. These can be removed from the graph - since they will + guaranteed get a register (color) because they have so few conflicting + neighbors. Removing these trivial nodes makes more nodes go trivial, and + this process repeats until either we run out (and are guaranteed a coloring) + or we find a high-degree clique: a set of mutually interfering live ranges + where none of them are guaranteed a color. In this case we pull an "at risk" + live range out; this one might not color. Once the graph is emptied, we + reverse and re-insert the live ranges, coloring as we go. All the trivial + live ranges will color; the at-risk ones may or may not. + +- If every live range gets a color, then Register Allocation is done. If not, + we enter a round of splitting. Live ranges which did not color are now split + via a series of heuristics into lots of small live ranges. Splitting has a + tension: too much and you got a lot of spills (and a generally poor + allocation). Too little, and you remain uncolorable - and do not make + progress towards an allocation. Once we're done splitting, we start a new + round of coloring (Building the Live Ranges, then the Interference Graph, + then Coloring). + + +## Registers and Register Masks + +Registers are the thing that Register Allocation is all about! They are +represented as a small dense integer, starting up from 0, and the numbering +generally follows from the hardware encodings. So for an X86_64, RAX will be +register 0, RCX register 1 and so on up to the 16 GPRs. The XMM registers at +16 and go up to 32. Register numbers must be unique; this is how the register +allocator tracks them. + +#### Why a RegMask class and not a simple `long`? + +A collection of registers live in a Register Mask. 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. + +#### Stack Slots + +One of the Click extensions is this notion of treating "stack slots" are Just +Another Register. They get colored like other registers, which in turn leads +to very efficient use of the stack; C2 is known for having very small stack +frames. Another benefit is callee save registers will get split preferentially +(they have very long lifetime and only one def on entry and one use on +exit)... which the splits then end up coloring to the stack, building the call +prolog and epilog naturally. This allocator will not otherwise dedicate any +effort to stack frame management; it will "fall out in the wash". + +Many of the register masks are immutable; e.g. allowed registers for particular +opcodes, or describing registers for calling conventions. Other masks +represent mutable bitsets with the allocator routinely masking off bits when +accumulating a set of constraints. To help keep these uses apart, the +`RegMask` class includes mutable and immutable variants. + + +### Callee/Caller Save Registers + +In this allocator, callee- and caller-save registers get almost no special +treatment and no special prolog nor epilog is built to save and restore them. +At the start of the allocator, SoN graph edges are added between each `Return` +and a `CalleeSave` from each `Fun`. These edges are given register masks +pinning them to the callee-save set as defined by the normal ABI. + +Callee-save live ranges thus have a single def (a `CalleeSave` at function +start) and a single use (the `Return`), are cheap to spill and free a register +over a large area - they make ideal spill candidates. When the allocator needs +to spill something to get some more registers, these will be the first to +spill. A prolog and epilog will get built as a consequence of the normal live +range splitting process. + +`Calls` kill the caller-save registers (just the inverted callee-save mask), +and this is computed during the normal interference graph building process just +like any other operation that kills registers. Killing a bunch of registers at +a call typically removes all the free registers from whatever is currently +alive - and will thus force spilling... which will quickly end up spilling +callee-save registers as ideal candidates. + + +## Live Ranges + +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. + +Live ranges are held in `LRG` class instances. Most of the fields in this +class are for spill heuristics, but there are a few key ones that define what a +LRG is. `RegAlloc` has several functions dealing with LRGs, including a lookup +table from Nodes i.e., a mapping from `Node` to `LRG` (which is very similar to +having a dedicated LRG field in each Node... except that LRGs are only used +during RegAlloc and the field would be dead weight otherwise). + +LRGs have a unique dense integer `_lrg` number which names the LRG. New `_lrg` +numbers come from the `RegAlloc._lrg_num` counter. LRGs can be unioned +together - +[this is the Union-Find algorithm](https://en.wikipedia.org/wiki/Disjoint-set_data_structure) +- 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. + +Post-allocation the `LRG._reg` field holds the chosen register. During +allocation the `LRG._mask` field holds the set of available registers, +typically all the defaults, minus various conflicts. + +The `LRG._adj` holds a list of adjacent neighbors as part of the larger +Interference Graph, and is only used during the Coloring phase. +[See more about graphs here.](https://en.wikipedia.org/wiki/Graph_(abstract_data_type)) +This is a "adjacency list" form of a Graph description, and is built from the +IFG's 2-D collection of bits (itself a 1-D list of `BitSet`s). Graph coloring +register allocation is one of the few places where changing the layout of a +data structure mid-algorithm pays out. + +The rest of the fields are dedicated to various spilling heuristics: + +- `_machDef` `_machUse` `_uidx` - A sample def and use. +- `_splitDef` `_splitUse` - Sample splits involved with this LRG; biased + coloring attempts to align register choices to remove these. +- `_selfConflicts` - A partial set of *self-conflicting* nodes. These are + detected during the Interference Graph build phase, and require aggressive + splitting. +- `_1regDefCnt` `_1regUseCnt` - Count of defs and uses which are pinned to a + single register. These typically require a hard-split just before (use) or + after (def) to free up the register choices. +- `_killed` - LRG lost all registers due to an op killing its available + registers; commonly happens to LRGs which span a `Call` and generally + requires splitting some callee-save live range (spilling a callee-save + register) and move the LRG into the now available register. + + +## Live Ranges and Conflicts + +If the live range `_mask` field goes empty, you might have a *hard conflict* or +have gotten *killed*. Live ranges can self-interfere, making a +*self-conflict*. During coloring you might discover an uncolorable clique of +interfering live ranges, leading to a *capacity spill*. This leads to a set of +levels of conflicts within a live range (and thus a different heuristics for +handling them). + +### Hard-Conflicts + +A live range can have a *hard confict*: conflicting register requirements, +leading to no valid register choices. The obvious case is an incoming +parameter fixed in e.g. `rdx` but also required as an exit value in `rax`. You +can't pick both registers at once, so a split is required. During the build +live ranges phase, every register constraint from every def and use are AND'd +together; the `RegMask` might lose all valid registers, or remain with just a +handful of register choices. + +Hard-conflicts are usually found while build live ranges, and will trigger a +round of splitting before building the interference graph or coloring. They +can also be found during interference graph building, generally caused by a +1-register kill. + +### Avoiding interferences + +Once of the Click extensions to the Briggs-Chaitin allocator is to take +advantage of register constraints to lower the interference graph degree (which +otherwise becomes an O(n^2) edge collection). If a live range LR1 is reduced +to a single register (such as a call argument requiring `rdx`) and would +otherwise conflict with another live range LR2 which has more choices - we +remove `rdx` from the LR2's choices. Without having `rdx`, LR1 and LR2 have no +registers in common - and hence do not conflict. We just don't add the +interference graph edge between them. Call argument restrictions are very +common and this kind of edge-removal can dramatically shrink the O(n^2) edge +collection - and hence speedup allocation by a large constant factor. + +### Self-Conflicts + +A live-range can *self-conflict* be alive twice with 2 different values but +require the same register. This live range *must* split and thus picks up a +copy from the split. This happens with loop-carried dependencies as seen in +e.g. a simple `fib` program: + +```java + int x=1, y=1; + while( true ) { + tmp = x + y; + y = x; + x = tmp; + }; + +``` + +In SoN SSA form: +```java + int x0 = 1, y0 = 1; + while( true ) { + x1 = phi(x0,x2); + y1 = phi(y0,x1); + x2 = x1 + y1; + } +``` + +The variables `x0,x1,x2,y0,y1` all form part of the same live range, since they +are all joined by `Phis`. However, `x1` and `y1` hold different values from +different iterations of `fib`. The SSA form does not have a copy, but the +register-allocated form - just like the original code - DOES require a copy. + +Self-conflicts are found during building the interference graph, and will +trigger a round of splits before attempting the coloring phase. + + +## Coloring Heuristics + +Both register coloring and register splitting are full of heuristics, and the +quality of these heuristics goes a long way to deciding the quality of the +final allocation. Graph coloring is NP-complete; there is no efficient +algorithm for coloring and many programs are simply not colorable without +splitting in any case. + + +### Biased Coloring + +Biased coloring is a heuristic that attempts to "pick a better color" or "bias +the color choice", such that a Split is removed. A Split splits one live range +into two; sometimes some are inserted that are not required (i.e., over- +splitting has already happened). If one side (live range) of a split is +already colored and we can color the other side (live range) the same color - +then this split can be removed. If neither side is colored, but one or the +other side is recursively connected to a colored live range, we can still bias +the color in the hopes that a chain of splits will all get the same color and +thus all get removed. + +When the coloring process is reversing, and colors are being picked for live +ranges, we often have many register choices. A first choice is made +arbitrarily (lowest numbered register), and then we go looking for a better +color. Biased coloring inspects first a sample def (if there is only one, then +the One Def is inspected), and then a sample use. Inspection looks to see if +"the other side" of a split is already colored; if so, and that color is +available it is choosen. If there is a split, but it is not colored - we +recursively repeat the process on that split's other side. This is also done +for the other input of two-address ops and one of a Phi node inputs. + +In all cases, the goal is to pick a valid available color - that might also +eventually be pick-able for this Split's other side - and lead to this split +having the same color for both sides, and then being removed. + + +### Pick Risky + +Here we are forced to pick one of several *live ranges* that form a large +clique: they have too many mutual neighbors so that no one of them is +guaranteed a color. There is a conflicting tension here: + +Picking a live range with a large "area" (i.e. covering much of the program), +and low "cost" to spill (i.e. fewer defs and uses, and outside of loops) will +give a large win. A register will become free over a large part of the program +and allow coloring to success elsewhere. The normal ABI callee-save registers +are ideal in this case, defined on entry and used only on exit they cover the +entire program. Also, spilling means only a single split on entry and another +at exit. Spilling such live ranges basically builds a classic function +prolog/epilog sequence. Here though, the allocator is allowed to e.g. spill +outside a fast-path exit. + +``` + // No registers saved if the initial RDI is null + test rdi + jne BIG_AND_SLOW + xor rax,rax + ret // Fast path exit: null argument means null result +BIG_AND_SLOW: + add rsp,#12 + mov [rsp+0],rbx // begin spilling callee-save registers + mov [rsp+4],r12 // + // lots of code needing callee-save registers +``` + +The other side of the tension is to pick live ranges that are likely to color +more by chance. If a live range is "very nearly colorable" - say only one more +neighbor than available registers/colors then by chance (and Biased Coloring) +some neighbors might use the same color... freeing up a color and allowing this +risky live range to color after all. + + +## Splitting + +Here we failed to get a coloring - and need to split. Some specific live +ranges did not color and we will need to decide how to split. Again there are +tensions here: too much splitting leads to a bad allocation, to little leads to +no-progress bugs (manifested as too many rounds of splitting past some +arbitrary cutoff). + +We start by looking at each spilling live range in turn, and deciding +on a splitting strategy. + + +### Split Self Conflict + +Self-conflict live ranges are discovered during the "Build the Interference +Graph" stage. If this happens we don't attempt a coloring (its nonsensical +with self conflicts), but go straight to splitting. During the IFG building we +gathered a subset of the conflicting definitions for this live range. Now +we visit all those definition points and insert spills: + +- Before slot #1 on a Phi (which breaks loop-entry Phis from the loop-body) and + also after the Phi. +- Before a two-address instruction, breaking the live range before the op from + afterwards. +- Before any use that extends a live range. + +This set of splits is fairly aggressive... but test cases requiring a split in +each of the listed locations are including in Chapter 20's test cases. We +cannot even attempt a color while we have self conflicts, so its important to +break up these live ranges quickly. This tends to over-split and the allocator +leans on Biased Coloring to remove some of the extras. + + +### Split Hard Conflict + +Hard conflicts are discovered during the "Build the Live Ranges" pass for +direct def/use conflicts (e.g. defined in `rdi` and used in `rax`). They also +can be discovered during the "Build the Interference Graph" pass (e.g. incoming +argument in `rdi` used later in the program and killed by a `Call`). + +For simple hard-conflicts, we have the exact def and use kept in the Live Range +itself. We split once after the def and once before the use (and only on sides +with restricted registers). + +For more complex cases we use the capacity spilling/loop-nest technique. + + +### Split By Loop Nest + +Here we typically see capacity spills: we just need more registers. This +easily happens when a `Call` crushes all the temporary registers and we need to +carry some values past the call - but it can also happen any time we have a +larger function with lots of things going on at once. + +The heuristic here is to split *around* loops, attempting to keep some free +registers available so the hot loop body does not spill. Live ranges are split +into successively deeper loop nests in progressive rounds of splitting, and in +the final case will split once after each def and before each use, even in the +innermost loop. + +The heurstic starts by discovering the min and max loop depth for all defs and +uses. If these vary, we will split *around* the outermost loop, putting in +splits at the loop border and keeping an inner untouched live range that has no +constraints from outside. If this fails to color, on the next round of +splitting the outermost loop will have moved in one layer, and we will be +splitting around the next inner loop nest. This process repeats until we end +up splitting in the inner-most loops. + + +## Post Allocation + +After a successful coloring, we want to pull out any same-same register splits. +A quick pass over the splits and after checking registers, we pull out the +useless splits (and track the rest for "scoring" our allocation). We'll also +bypass some split-after-splits, which can remove some redundant copying. + +The registers remain available in the `RegAlloc` object via `alloc.regnum( Node n )` +and will be used by a following instruction encoding pass.
\ No newline at end of file diff --git a/chapter20/docs/ifg_trim.pdf b/chapter20/docs/ifg_trim.pdf Binary files differnew file mode 100644 index 00000000..d952fd4f --- /dev/null +++ b/chapter20/docs/ifg_trim.pdf diff --git a/chapter20/docs/pardoe.docx b/chapter20/docs/pardoe.docx Binary files differnew file mode 100644 index 00000000..24fde921 --- /dev/null +++ b/chapter20/docs/pardoe.docx diff --git a/chapter20/src/main/java/com/seaofnodes/simple/Ary.java b/chapter20/src/main/java/com/seaofnodes/simple/Ary.java index 73ada8e0..f43179ad 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/Ary.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/Ary.java @@ -29,6 +29,10 @@ public class Ary<E> extends AbstractList<E> implements List<E> { 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); } @@ -76,6 +80,17 @@ public class Ary<E> extends AbstractList<E> implements List<E> { 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; } @@ -158,10 +173,6 @@ public class Ary<E> extends AbstractList<E> implements List<E> { /** @return compact array version */ public E[] asAry() { return Arrays.copyOf(_es,_len); } - /** Sorts in-place - * @param c Comparator to sort by */ - public void sort_update(Comparator<? super E> c ) { Arrays.sort(_es, 0, _len, c); } - /** 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 diff --git a/chapter20/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter20/src/main/java/com/seaofnodes/simple/IterPeeps.java index 265e0b21..6208209f 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -1,7 +1,8 @@ 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; @@ -46,7 +47,7 @@ public class IterPeeps { private final WorkList<Node> _work; - IterPeeps( long seed ) { _work = new WorkList<>(seed); } + public IterPeeps( long seed ) { _work = new WorkList<>(seed); } public <N extends Node> N add( N n ) { return (N)_work.push(n); } @@ -136,7 +137,7 @@ public class IterPeeps { /* Useful stat - how many nodes are processed in the post parse iterative opt */ private long _totalWork = 0; - WorkList() { this(123); } + public WorkList() { this(123); } WorkList(long seed) { _es = new Node[1]; _len=0; @@ -177,7 +178,7 @@ public class IterPeeps { /** * Removes a random Node from the WorkList; null if WorkList is empty */ - E pop() { + public E pop() { if( _len == 0 ) return null; int idx = _R.nextInt(_len); E x = (E)_es[idx]; diff --git a/chapter20/src/main/java/com/seaofnodes/simple/Parser.java b/chapter20/src/main/java/com/seaofnodes/simple/Parser.java index 61a53d68..07f82625 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/Parser.java @@ -1,6 +1,8 @@ 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.*; @@ -1413,7 +1415,7 @@ public class Parser { mem.addDef(new ProjNode(cend,1,ScopeNode.MEM0).peephole()); _scope.mem(mem); // Call result - return new ProjNode(cend,2,null).peephole(); + return new ProjNode(cend,2,"#2").peephole(); } /** @@ -1724,6 +1726,6 @@ public class Parser { 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; assert loc!=null; } + ParseException( String msg, Lexer loc ) { super(msg); _loc = loc; } } } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/RegMask.java b/chapter20/src/main/java/com/seaofnodes/simple/RegMask.java deleted file mode 100644 index 20be7157..00000000 --- a/chapter20/src/main/java/com/seaofnodes/simple/RegMask.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.seaofnodes.simple; - -import java.util.BitSet; - -// A "register mask" - 1 bit set for each allowed register. In addition "stack -// slot" registers may be allowed, effectively making the set infinite. -public class RegMask { - - public static final RegMask EMPTY = new RegMask(); - - final BitSet _bits; - - public RegMask(long x ) { _bits = BitSet.valueOf(new long[]{x}); } - public RegMask(long[] xs) { _bits = BitSet.valueOf( xs); } - // Internal constructor - RegMask() { _bits = new BitSet(); } - RegMask(BitSet bs) { _bits = bs; } - - public boolean get(int r) { return _bits.get(r); } - - // Defensive writable copy - public RegMaskRW copy() { return new RegMaskRW( (BitSet)_bits.clone() ); } - - boolean size1() { return _bits.cardinality()==1; } -} - -class RegMaskRW extends RegMask { - public RegMaskRW() { super(); } - public RegMaskRW(long x) { super(new long[]{x}); } - public RegMaskRW(long[] xs) { super(xs); } - public RegMaskRW(BitSet bs) { super(bs); } - public void set(int r) { _bits.set(r); } - public void clr(int r) { _bits.clear(r); } - public void set(int r, boolean b) { _bits.set(r,b); } - public RegMaskRW and( RegMask r ) { - _bits.and(r._bits); - return this; - } -} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/SB.java b/chapter20/src/main/java/com/seaofnodes/simple/SB.java index 7d580c21..a45f0ddf 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/SB.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/SB.java @@ -40,7 +40,6 @@ public final class SB { } // Fixed width field public SB fix( int sz, String s ) { - assert sz >=0; for( int i=0; i<sz; i++ ) _sb.append( i < s.length() ? s.charAt(i) : ' '); return this; diff --git a/chapter20/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java b/chapter20/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java new file mode 100644 index 00000000..88b49863 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java @@ -0,0 +1,91 @@ +package com.seaofnodes.simple.codegen; + +import com.seaofnodes.simple.Utils; +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 mach ) 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); + if( !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/chapter20/src/main/java/com/seaofnodes/simple/CodeGen.java b/chapter20/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java index c48a4162..a1294890 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/CodeGen.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java @@ -1,7 +1,8 @@ - package com.seaofnodes.simple; +package com.seaofnodes.simple.codegen; +import com.seaofnodes.simple.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.node.cpus.x86_64_v2.MemOpX86; +import com.seaofnodes.simple.print.*; import com.seaofnodes.simple.type.*; import java.util.BitSet; import java.util.HashMap; @@ -16,14 +17,11 @@ public class CodeGen { Parse, // Parse ASCII text into Sea-of-Nodes IR Opto, // Run ideal optimizations TypeCheck, // Last check for bad programs - InstructionSelection, // Convert to target hardware nodes + InstSelect, // Convert to target hardware nodes Schedule, // Global schedule (code motion) nodes LocalSched, // Local schedule - RegAlloc; // Register allocation - } - public enum CallingConv { - Win64, - SystemV + RegAlloc, // Register allocation + Encoding, // Encoding } public Phase _phase; @@ -32,7 +30,6 @@ public class CodeGen { public final String _src; // Compile-time known initial argument type public final TypeInteger _arg; - public CallingConv _callingConv; // --------------------------- public CodeGen( String src ) { this(src, TypeInteger.BOT, 123L ); } @@ -54,9 +51,11 @@ public class CodeGen { * A counter, for unique node id generation. Starting with value 1, to * avoid bugs confusing node ID 0 with uninitialized values. */ - private int _uid = 1; + public int _uid = 1; public int UID() { return _uid; } - public int getUID() { return _uid++; } + public int getUID() { + return _uid++; + } // Next available memory alias number private int _alias = 2; // 0 is for control, 1 for memory @@ -108,11 +107,14 @@ public class CodeGen { 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; } @@ -130,12 +132,15 @@ public class CodeGen { 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 @@ -149,13 +154,16 @@ public class CodeGen { // --------------------------- // Last check for bad programs + 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; @@ -165,25 +173,26 @@ public class CodeGen { // --------------------------- // Code generation CPU target public Machine _mach; + // + public String _callingConv; // Convert to target hardware nodes - public CodeGen instSelect( String cpu, String callingConv ) { + public int _tInsSel; + public CodeGen instSelect( String base, String cpu, String callingConv ) { assert _phase.ordinal() <= Phase.TypeCheck.ordinal(); - _phase = Phase.InstructionSelection; + _phase = Phase.InstSelect; - _callingConv = switch(callingConv) { - case "SystemV" -> CallingConv.SystemV; - case "Win64" -> CallingConv.Win64; - default -> throw Utils.TODO(); // Unknown calling convention - }; + _callingConv = callingConv; + // Not timing the class load... // Look for CPU in fixed named place: // com.seaofnodes.simple.node.cpus."cpu"."cpu.class" - String clzFile = "com.seaofnodes.simple.node.cpus."+cpu+"."+cpu; + String clzFile = base+"."+cpu+"."+cpu; try { _mach = ((Class<Machine>) Class.forName( clzFile )).getDeclaredConstructor().newInstance(); } catch( Exception e ) { throw new RuntimeException(e); } // Convert to machine ops + long t0 = System.currentTimeMillis(); _uid = 1; // All new machine nodes reset numbering var map = new IdentityHashMap<Node,Node>(); _instSelect( _stop, map ); @@ -191,6 +200,7 @@ public class CodeGen { _start = (StartNode)map.get(_start); _instOuts(_stop,visit()); _visit.clear(); + _tInsSel = (int)(System.currentTimeMillis() - t0); return this; } @@ -201,10 +211,10 @@ public class CodeGen { Node x = map.get(n); if( x !=null ) return x; // Been there, done that - // If n is a MachConcrete, then its part of a multi-node expansion. + // 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 MachConcreteNode || n instanceof MemOpX86 ) { + if( n instanceof MachNode ) { for( int i=0; i < n.nIns(); i++ ) n._inputs.set(i, _instSelect(n.in(i),map) ); return n; @@ -237,17 +247,20 @@ public class CodeGen { // --------------------------- // Control Flow Graph in RPO order. - Ary<CFGNode> _cfg = new Ary<>(CFGNode.class); + 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.InstructionSelection.ordinal(); + assert _phase.ordinal() <= Phase.InstSelect.ordinal(); _phase = Phase.Schedule; + long t0 = System.currentTimeMillis(); // Build the loop tree, fix never-exit loops _start.buildLoopTree(_stop); GlobalCodeMotion.buildCFG(this); + _tGCM = (int)(System.currentTimeMillis() - t0); if( show ) System.out.println(new GraphVisualizer().generateDotOutput(_stop,null,null)); return this; @@ -255,33 +268,70 @@ public class CodeGen { // --------------------------- // Local (basic block) scheduler phase, a classic list scheduler + 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 + int _tRegAlloc; + public RegAlloc _regAlloc; + public CodeGen regAlloc() { + assert _phase == Phase.LocalSched; + _phase = Phase.RegAlloc; + long t0 = System.currentTimeMillis(); + _regAlloc = new RegAlloc(this); + _regAlloc.regAlloc(); + _tRegAlloc = (int)(System.currentTimeMillis() - t0); + return this; + } public String reg(Node n) { - if( _phase == Phase.RegAlloc ) throw Utils.TODO(); + if( _phase.ordinal() >= Phase.RegAlloc.ordinal() ) { + String s = _regAlloc.reg(n); + if( s!=null ) return s; + } return "N"+ n._nid; } + // --------------------------- + // Encoding + int _tEncode; + public CodeGen encode() { + assert _phase == Phase.RegAlloc; + _phase = Phase.Encoding; + long t0 = System.currentTimeMillis(); + + + //_tEncode = (int)(System.currentTimeMillis() - t0); + //throw Utils.TODO(); + return this; + } + // Encoded binary, no relocation info + public byte[] binary() { throw Utils.TODO(); } // --------------------------- SB asm(SB sb) { return ASMPrinter.print(sb,this); } - String asm() { return asm(new SB()).toString(); } + public String asm() { return asm(new SB()).toString(); } // Testing shortcuts - Node ctrl() { return _stop.ret().ctrl(); } - Node expr() { return _stop.ret().expr(); } - String print() { return _stop.print(); } + 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 _stop.p(9999); } + @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); } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java b/chapter20/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java index 12dd9243..11c2b061 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java @@ -1,6 +1,8 @@ - package com.seaofnodes.simple; +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.*; @@ -14,32 +16,70 @@ public abstract class GlobalCodeMotion { 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++ ) { - int j = rpo.size()-1-i; - CFGNode tmp = rpo.get(i); - rpo.set(i, rpo.get(j)); - rpo.set(j, tmp); - } + for( int i=0; i< rpo.size()>>1; i++ ) + rpo.swap(i,rpo.size()-1-i); // Set global CFG code._cfg = rpo; schedEarly(code); + + // Break up shared global constants by functions + breakUpGlobalConstants(code); + code._visit.clear(); schedLate (code); } // Post-Order of CFG private static void _rpo_cfg(CFGNode def, Node use, BitSet visit, Ary<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 CallNode && cfg instanceof FunNode ) - return; // Ignore linked function calls + if( def instanceof ReturnNode && use instanceof CallEndNode ) + return; + assert !( def instanceof CallNode && use instanceof FunNode ); visit.set(cfg._nid); for( Node useuse : cfg._outputs ) _rpo_cfg(cfg,useuse,visit,rpo); rpo.add(cfg); } + // Break up shared global constants by functions + private static void breakUpGlobalConstants( CodeGen code ) { + // For all global constants + for( int i=0; i< code._start.nOuts(); i++ ) { + Node con = code._start.out(i); + if( con instanceof MachNode mach && mach.isClone() ) { + // While constant has users in different functions + while( true ) { + // Find a function user, and another function + FunNode fun = null; + boolean done=true; + for( Node use : con.outs() ) { + FunNode fun2 = use.cfg0().fun(); + if( fun==null || fun==fun2 ) fun=fun2; + else { done=false; break; } + } + // Single function user, so this constant is not shared + if( done ) { i--; con.setDef(0,fun); break; } + // Move function users to a private constant + Node con2 = mach.copy(); // Private constant clone + con2._inputs.set(0,null); // Preserve edge invariants from clone + con2.setDef(0,fun); + // Move function users to this private constant + for( int j=0; j<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 @@ -50,30 +90,30 @@ public abstract class GlobalCodeMotion { for( CFGNode cfg : code._cfg ) { cfg.loopDepth(); for( Node n : cfg._inputs ) - _schedEarly(n,code._visit ); + _schedEarly(n,code ); if( cfg instanceof RegionNode ) for( Node phi : cfg._outputs ) if( phi instanceof PhiNode ) - _schedEarly(phi,code._visit ); + _schedEarly(phi,code ); } } - private static void _schedEarly(Node n, BitSet visit) { - if( n==null || visit.get(n._nid) ) return; // Been there, done that + 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); - visit.set(n._nid); + 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,visit); + _schedEarly(def,code); // If not-pinned (e.g. constants, projections, phi) and not-CFG if( !n.isPinned() ) { // Schedule at deepest input - CFGNode early = CodeGen.CODE._start; // Maximally early, lowest idepth + 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() ) diff --git a/chapter20/src/main/java/com/seaofnodes/simple/codegen/IFG.java b/chapter20/src/main/java/com/seaofnodes/simple/codegen/IFG.java new file mode 100644 index 00000000..1f3186ff --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/codegen/IFG.java @@ -0,0 +1,521 @@ +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 + private 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()); + + 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 + } + + if( n instanceof PhiNode ) + return; + // A copy does not define a new value, and the src and dst can use the + // same register. Remove the input from TMP/liveout set before + // interfering. + //if( n instanceof MachNode m && m.isSplit() ) + // TMP.remove(alloc.lrg(n.in(1))); // Kill spill-use + + // Interfere n with all live + if( lrg!=null ) { + if( n instanceof MachNode m ) + kills(alloc,m); + // 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( lrg.size1() && !tlrg.clr(lrg._mask.firstReg()) ) + alloc.fail(tlrg); + 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.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. + if( m.isClone() ) + alloc.fail(alloc.lrg((Node)m)); + else if( !tlrg.sub(killMask) ) { + tlrg._killed = true; // Failed by a kill-mask + 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); + } + } + } + + + // ------------------------------------------------------------------------ + // 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) { + + // Convert the 2-D array of bits (a 1-D array of BitSets) into an + // adjacency matrix. + int maxlrg = alloc._LRGS.length, nlrgs=0; + for( int i=1; i<maxlrg; i++ ) { + if( alloc._LRGS[i] != null ) nlrgs++; + 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); + } + } + } + + // 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 == 999999 ) 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 ) { + // Always pick callee-save registers as being very large area recovered + // and very cheap to spill. + if( lrg._machDef instanceof CalleeSaveNode ) + return 999998; + if( lrg._splitDef != null && lrg._splitDef.in(1) instanceof CalleeSaveNode && + lrg._splitUse != null && lrg._splitUse.out(0) instanceof ReturnNode ) + return 999999; + + // TODO: cost/benefit model. Perhaps counting loop-depth (freq) of def/use for cost + // and "area" for benefit + return 1000; + } + + private static short biasColor( RegAlloc alloc, LRG lrg, short reg, RegMask mask ) { + if( mask.size1() ) return reg; + // Check chain of splits up the def-chain. Take first allocated + // register, and if it's available in the mask, take it. + Node defSplit = lrg._splitDef, useSplit = lrg._splitUse; + int tidx, cnt=0; + + while( (tidx=biasable(defSplit)) != 0 || biasable(useSplit) != 0 ) { + if( cnt++ > 10 ) break; + + if( tidx != 0 ) { + short bias = biasColor( alloc, defSplit, mask ); + if( bias >= 0 ) return bias; // Good bias + if( bias == -2 ) defSplit = null; // Kill this side, no more searching + } else defSplit = null; + + if( biasable(useSplit) != 0 ) { + short bias = biasColor( alloc, useSplit, mask ); + if( bias >= 0 ) return bias; // Good bias + if( bias == -2 ) useSplit = null; // Kill this side, no more searching + } else useSplit = null; + + if( defSplit != null ) { + short bias = biasColorNeighbors( alloc, defSplit, mask ); + if( bias >= 0 ) return bias; + // Advance def side + defSplit = defSplit.in(tidx); + } + + if( useSplit != null ) { + short bias = biasColorNeighbors( alloc, useSplit, mask ); + if( bias >= 0 ) return bias; + useSplit = useSplit.out(0); + } + + } + return mask.firstReg(); + } + + private static int biasable(Node split) { + if( split instanceof SplitNode ) return 1; // Yes biasable, advance is slot 1 + if( split instanceof PhiNode ) return 1; // Yes biasable, advance is slot 1 + if( !(split instanceof MachNode mach) ) return 0; // Not biasable + return mach.twoAddress(); // Only biasable if 2-addr + } + + // 3-way return: + // - good bias reg, take it & exit + // - this path is cutoff; do not search here anymore + // - advance this side + private static short biasColor( RegAlloc alloc, Node split, RegMask mask ) { + short bias = alloc.lrg(split)._reg; + if( bias != -1 ) { + if( mask.test(bias) ) return bias; // Good bias + else return -2; // Kill this side + } else return -1; // Advance this side + } + + // Check if we can match "split" color, or else trim "mask" to colors + // "split" might get. + private static short biasColorNeighbors( RegAlloc alloc, Node split, RegMask mask ) { + LRG slrg = alloc.lrg(split); + if( slrg._adj == null ) return -1; // No trimming + + // Can I limit my own choices to valid neighbor choices? + for( LRG alrg : slrg._adj ) { + int reg = alrg._reg; + if( reg == -1 && alrg._mask.size1() ) + reg = alrg._mask.firstReg(); + if( reg != -1 ) { + mask.clr(alrg._reg); + if( mask.size1() ) + return mask.firstReg(); + } + } + return -1; // No obvious color choice, but mask got trimmed + } + + private static void swap( LRG[] ary, int x, int y ) { + LRG tmp = ary[x]; ary[x] = ary[y]; ary[y] = tmp; + } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/codegen/LRG.java b/chapter20/src/main/java/com/seaofnodes/simple/codegen/LRG.java new file mode 100644 index 00000000..394c4a01 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/codegen/LRG.java @@ -0,0 +1,220 @@ +package com.seaofnodes.simple.codegen; + +import com.seaofnodes.simple.Ary; +import com.seaofnodes.simple.SB; +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; + + // A sample MachNode def in the live range + public MachNode _machDef, _machUse; + short _uidx; // _machUse input + + // Mask set to empty via a kill-mask. This is usually a capacity kill, + // which means we need to split and spill into memory - which means even if + // the def-side has many registers, it MUST spill. + boolean _killed; + + // Some splits used in biased coloring + MachConcreteNode _splitDef, _splitUse; + + // All the self-conflicting defs for this live range + IdentityHashMap<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; + + void addNeighbor(LRG lrg) { + if( _adj==null ) _adj = new Ary<>(LRG.class); + _adj.push(lrg); + } + + // Remove and compress neighbor list; store ex-neighbor past + // end for unwinding Simplify. True if exactly going low-degree + boolean removeCompress( LRG lrg ) { + _adj.swap(_adj.find(lrg),_adj._len-1); + return _adj._len-- == _mask.size(); + } + + void reinsert( LRG lrg ) { + assert _adj._es[_adj._len]==lrg; + _adj._len++; + } + + // More registers than neighbors + boolean lowDegree() { return (_adj==null ? 0 : _adj._len) < _mask.size(); } + + LRG( short lrg ) { _lrg = lrg; _reg = -1; } + + boolean leader() { return _leader == null; } + + // Fast-path Find from the Union-Find algorithm + LRG find() { + if( _leader==null ) return this; // I am the leader + if( _leader._leader==null ) // I point to the leader + return _leader; + return _rollup(); + } + // Slow-path rollup of U-F + private LRG _rollup() { + LRG ldr = _leader._leader; + // Roll-up + while( ldr._leader!=null ) ldr = ldr._leader; + LRG l2 = this; + while( l2 != ldr ) { + LRG l3 = l2._leader; + l2._leader = ldr; + l2 = l3; + } + return ldr; + } + + // Union `this` and `lrg`, keeping the lower numbered _lrg. + // Includes a number of fast-path cutouts. + LRG union( LRG lrg ) { + assert leader(); + if( lrg==null ) return this; + lrg = lrg.find(); + if( lrg==this ) return this; + return _lrg < lrg._lrg ? _union(lrg) : lrg._union(this); + } + // Union `this` and `lrg`, folding together all stats. + private LRG _union( LRG lrg ) { + // Set U-F leader + lrg._leader = this; + // Fold together stats + if( _machDef==null ) { + _machDef = lrg._machDef; + } else if( lrg._machDef!=null ) { + if( _1regDefCnt==0 ) + _machDef = lrg._machDef; + else if( _machDef==lrg._machDef ) + _1regDefCnt--; + } + _1regDefCnt += lrg._1regDefCnt; + + if( _machUse==null ) { + _machUse = lrg._machUse; + } else if( lrg._machUse!=null ) { + if( _1regUseCnt==0 ) + _machUse = lrg._machUse; + else if( _machUse==lrg._machUse ) + _1regUseCnt--; + } + _1regUseCnt += lrg._1regUseCnt; + + // Fold deepest Split + _splitDef = deepSplit(_splitDef,lrg._splitDef); + _splitUse = deepSplit(_splitUse,lrg._splitUse); + + // Fold together masks + _mask = _mask.and(lrg._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 || 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 || 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/chapter20/src/main/java/com/seaofnodes/simple/ListScheduler.java b/chapter20/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java index 18055e19..1796ef7c 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/ListScheduler.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java @@ -1,6 +1,8 @@ - package com.seaofnodes.simple; +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.*; @@ -8,8 +10,17 @@ 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(); @@ -17,7 +28,7 @@ public abstract class ListScheduler { } void free() { _n = null; FREE.push(this); } - static XSched get(Node n) { return n==null ? null : XS.get(n); } + 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; @@ -29,11 +40,11 @@ public abstract class ListScheduler { for( Node def : n._inputs ) if( def != null && def != bb ) { if( def.cfg0()==bb ) _bcnt++; // Raise block-local input count - else _ruse = true; + else if( !def.isMem() ) _ruse = true; // Remote register user } // Count remote outputs and allowed registers if( n instanceof MultiNode ) - for( Node use : n._inputs ) + for( Node use : n._outputs ) computeSingleRDef(bb,use); else computeSingleRDef(bb,n); @@ -41,7 +52,14 @@ public abstract class ListScheduler { } private void computeSingleRDef(CFGNode bb, Node n) { - RegMaskRW rmask = n instanceof MachNode mach ? mach.outregmap().copy() : null; + // 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; @@ -52,7 +70,7 @@ public abstract class ListScheduler { rmask.and(mach.regmap(i)); } // Defines in a single register - _single = rmask!=null && rmask.size1(); + _single |= rmask!=null && rmask.size1(); } // If _bcnt==0, declare ready; move user bcnts into rcnts. @@ -82,13 +100,6 @@ public abstract class ListScheduler { _rcnt--; return isReady(); } - - 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 } @@ -97,11 +108,11 @@ public abstract class ListScheduler { XS.clear(); for( CFGNode bb : code._cfg ) if( bb.blockHead() ) - local(bb); + local(bb,false); } // Classic list scheduler - private static void local( CFGNode bb ) { + private static void local( CFGNode bb, boolean trace ) { assert XS.isEmpty(); int len = bb.nOuts(); @@ -126,7 +137,7 @@ public abstract class ListScheduler { int sched = 0; while( sched < len ) { assert sched < ready; - int pick = best(bb._outputs,sched,ready); // Pick best + 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. @@ -145,7 +156,7 @@ public abstract class ListScheduler { } private static int ready(CFGNode bb, Node use, int ready) { - if( use!=null && !(use instanceof PhiNode) && use.in(0)==bb && XSched.get(use).decIsReady() ) + 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; @@ -153,14 +164,19 @@ public abstract class ListScheduler { // Pick best between sched and ready. - private static int best( Ary<Node> blk, int sched, int ready ) { + private static int best( Ary<Node> blk, int sched, int ready, boolean trace ) { int pick = sched; - int score = score(blk.at(pick)); + 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++ ) { - int nscore = score(blk.at(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; } + { score = nscore; pick = i; p = n; } } + if( trace ) { System.out.printf("Pick: N%d %s\n\n",p._nid,p); } return pick; } @@ -180,9 +196,9 @@ public abstract class ListScheduler { // Register pressure local scheduling: avoid overlapping specialized // registers usages. - // Subtract 10 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 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. @@ -190,42 +206,53 @@ public abstract class ListScheduler { // 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 ) - singleUseNotReady( use, xn._single ); - } else if( n.nOuts() == 1 ) - singleUseNotReady( n, xn._single ); + 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 && xd._n.nOuts()==1 ) - CNT[xd._single || ((MachNode)n).regmap(i).size1() ? 2 : 1]++; + 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 - //if( n instanceof Pe ) return 500; // RISC want to go after PE - //if( n instanceof Risc ) return 400; // RISC want to go after PE + // 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. - // Return 0 for false, 1 if true, 10 if also single register - private static void singleUseNotReady( Node n, boolean single ) { - if( n.nOuts() != 1 ) return; - XSched xu = XSched.get(n.out(0)); - if( xu !=null && xu._bcnt==0 ) - return; - CNT[single ? 2 : 1]++; + // 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/chapter20/src/main/java/com/seaofnodes/simple/Machine.java b/chapter20/src/main/java/com/seaofnodes/simple/codegen/Machine.java index b29e9aba..13d7abc1 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/Machine.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/codegen/Machine.java @@ -1,9 +1,6 @@ -package com.seaofnodes.simple; +package com.seaofnodes.simple.codegen; -import com.seaofnodes.simple.RegMask; -import com.seaofnodes.simple.node.CFGNode; -import com.seaofnodes.simple.node.IfNode; -import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.*; abstract public class Machine { // Human readable machine name. Something like "x86-64" or "arm" or "risc5" @@ -11,7 +8,11 @@ abstract public class Machine { // Human-readable name for a register number, e.g. "RAX" or "R0" public abstract String reg( int reg ); // Create a split op; any register to any register, including stack slots - public abstract Node split(); + public abstract SplitNode split( String kind, byte round, LRG lrg); + // List of caller-save registers + public abstract RegMask callerSave(); + // List of callee-save registers + public abstract RegMask calleeSave(); // Return a MachNode unconditional branch public abstract CFGNode jump(); // Break an infinite loop diff --git a/chapter20/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java b/chapter20/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java new file mode 100644 index 00000000..9e607349 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java @@ -0,0 +1,502 @@ +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.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 int regnum( Node n ) { + LRG lrg = lrg(n); + return lrg==null ? -1 : lrg._reg; + } + + // Printable register number for node n + String reg( Node n ) { + LRG lrg = lrg(n); + if( lrg==null ) return null; + if( lrg._reg == -1 ) return "V"+lrg._lrg; + return _code._mach.reg(lrg._reg); + } + + // ----------------------- + RegAlloc( CodeGen code ) { _code = code; } + + public void regAlloc() { + // Insert callee-save registers + for( CFGNode bb : _code._cfg ) + if( bb instanceof FunNode fun ) + insertCalleeSave(fun); + + // Top driver: repeated rounds of coloring and splitting. + byte round=0; + while( !graphColor(round) ) { + split(round); + assert round < 7; // Really expect to be done soon + 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 + // Color attempt + IFG.color(round,this); // If colorable + } + + // Insert callee-save registers. Walk the callee-save RegMask ignoring any + // Parms, then insert a Parm and an edge from the Ret to the Parm with the + // callee-save register. + private void insertCalleeSave( FunNode fun ) { + RegMask saves = _code._mach.calleeSave(); + ReturnNode ret = fun.ret(); + + for( short reg = saves.firstReg(); reg != -1; reg = saves.nextReg(reg) ) { + ret.addDef(new CalleeSaveNode(fun,reg,_code._mach.reg(reg))); + assert ((MachNode)ret).regmap(ret.nIns()-1).firstReg()==reg; + } + } + + + // ----------------------- + // Split conflicted live ranges. + void split(byte round) { + + // In C2, all splits are handling in one pass over the program. Here, + // in the name of clarity, we'll handle each failing live range + // independently... which generally requires a full pass over the + // program for each failing live range. i.e., might be a lot of + // passes. + for( LRG lrg : _failed.keySet() ) + split(round,lrg); + } + + // Split this live range + boolean split( byte round, LRG lrg ) { + assert lrg.leader(); // Already rolled up + + if( lrg._selfConflicts != null ) + return splitSelfConflict(round,lrg); + + // Register mask when empty; split around defs and uses with limited + // register masks. + if( lrg._mask.isEmpty() ) { + if( lrg._1regDefCnt <= 1 && + lrg._1regUseCnt <= 1 && + (lrg._1regDefCnt + lrg._1regUseCnt) > 0 ) + return splitEmptyMaskSimple(round,lrg); + // Default to splitByLoop + //return splitEmptyMask(round,lrg); + } + + // Generic split-by-loop depth. + return splitByLoop(round,lrg); + } + + // Split live range with an empty mask. Specifically forces splits at + // single-register defs or uses and not elsewhere. + boolean splitEmptyMaskSimple( byte round, LRG lrg ) { + // Live range has a single-def single-register, and/or a single-use + // single-register. Split after the def and before the use. Does not + // require a full pass. + + // Split just after def + if( lrg._1regDefCnt==1 && !lrg._machDef.isClone() ) + // Force must-split, even if a prior split same block because register + // conflicts. Example: + // alloc + // V1/rax - forced by alloc + // alloc + // V2/rax - kills prior RAX + // st4 [V1],len - No good, must split around + makeSplit("def/empty1",round,lrg).insertAfter((Node)lrg._machDef, false/*true*/); + // Split just before use + if( lrg._1regUseCnt==1 ) + insertBefore((Node)lrg._machUse,lrg._uidx,"use/empty1",round,lrg); + return true; + } + + // Split live range with an empty mask. Specifically forces splits at + // single-register defs or uses everywhere. + boolean splitEmptyMask( byte round, LRG lrg ) { + findAllLRG(lrg); + // If no single-use or single-def, assume this is a complete register + // kill and force spilling everywhere. + boolean all = lrg._killed || (lrg._1regDefCnt + lrg._1regUseCnt)==0; + for( Node n : _ns ) { + if( !(n instanceof MachNode mach) ) continue; + // Find def of spilling live range; spilling everywhere, OR + // single-register DEF and not cloneable (since these will clone + // before every use) + if( lrg(n)==lrg && (all || (!mach.isClone() && mach.outregmap().size1() )) ) + makeSplit(n,"def/empty2",round,lrg).insertAfter(n,true); + // Find all uses + for( int i=1; i<n.nIns(); i++ ) { + Node def = n.in(i); + // Skip any new splits inserted just this pass + while( def instanceof SplitNode && lrg(def)==null ) + def = def.in(1); + // Main def (past splits) is of the spilling lrg, and spilling + // single-register USE (or everywhere) + if( lrg(def)==lrg && (all || mach.regmap(i).size1()) ) + insertBefore(n,i,"use/empty2",round,lrg); + } + } + 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 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); + split.insertAfter(def,false); + if( split.nOuts()==0 ) + split.kill(); + 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); + // 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 && phi.in(2)==def ) ) || + (use instanceof MachNode mach && mach.twoAddress()!=0 && use.in(mach.twoAddress())==def) ) + insertBefore( use, use._inputs.find(def), "use/self/use",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 + !(n.nOuts()==1 && n.out(0) instanceof SplitNode) ) + // Split after def in min loop nest + makeSplit("def/loop",round,lrg).insertAfter(n,false); + } + + // PhiNodes check all CFG inputs + if( n instanceof PhiNode phi && !(n instanceof ParmNode)) { + for( int i=1; i<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) ) + // 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++ ) { + if( lrgSame(n.in(i),lrg) && // This is a LRG use + // splitting in inner loop or at loop border + (min==max || n.cfg0().loopDepth() <= min) && + // Not a single-use split same block already + !(n.in(i) instanceof SplitNode && n.in(i).nOuts()==1 && n.cfg0()==n.in(i).cfg0()) ) + // Split before in this block + insertBefore( n, i, "use/loop/use", round,lrg ); + } + } + } + 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(); + 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) { + 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( 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( def instanceof SplitNode && cfg==def.cfg0() ) + n.in(i).setDefOrdered(1,def.in(1)); + } + + 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() { + for( CFGNode bb : _code._cfg ) { // For all ops + for( int j=0; j<bb.nOuts(); j++ ) { + Node n = bb.out(j); + if( !(n instanceof SplitNode lo) ) 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; + } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/codegen/RegMask.java b/chapter20/src/main/java/com/seaofnodes/simple/codegen/RegMask.java new file mode 100644 index 00000000..f28805b5 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/codegen/RegMask.java @@ -0,0 +1,142 @@ +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; } + 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() { + 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; } + + boolean test( int reg ) { + return ((reg<64 ? (_bits0 >> reg) : (_bits1 >> (reg-64))) & 1) != 0; + } + + // checks if the 2 masks have at least 1 bit in common + public boolean overlap( RegMask mask ) { return (_bits0 & mask._bits0)!=0 || (_bits1 & mask._bits1)!=0; } + + // Defensive writable copy + public RegMaskRW copy() { return new RegMaskRW( _bits0, _bits1 ); } + + // Has exactly 1 bit set + public boolean size1() { + return ((_bits0 & -_bits0)==_bits0 && _bits1==0) || + ((_bits1 & -_bits1)==_bits1 && _bits0==0); + } + + // Cardinality + public short size() { return (short)(Long.bitCount(_bits0)+Long.bitCount(_bits1)); } + + @Override public String toString() { return toString(new SB()).toString(); } + public SB toString(SB sb) { + Machine mach = CodeGen.CODE._mach; + if( _bits0==0 && _bits1==0 ) return sb.p("[]"); + sb.p("["); + for( int i=0; i<64; i++ ) + if( ((_bits0 >> i)&1) != 0 ) + sb.p(mach.reg(i)).p(","); + for( int i=0; i<64; i++ ) + if( ((_bits1 >> i)&1) != 0 ) + sb.p(mach.reg(i+64)).p(","); + return sb.unchar().p("]"); + } +} + +// Mutable regmask(writable) +class RegMaskRW extends RegMask { + public RegMaskRW(long x, long y) { super(x,y); } + // clears bit at position r. Returns true if the mask is still not empty. + public boolean clr(int r) { + if( r < 64 ) _bits0 &= ~(1L<<(r )); + else _bits1 &= ~(1L<<(r-64)); + return _bits0!=0 || _bits1!=0; + } + @Override RegMaskRW and( RegMask mask ) { + if( mask==null ) return this; + _bits0 &= mask._bits0; + _bits1 &= mask._bits1; + return this; + } + @Override RegMaskRW sub( RegMask mask ) { + if( mask==null ) return this; + _bits0 &= ~mask._bits0; + _bits1 &= ~mask._bits1; + return this; + } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/AddNode.java index 6b7f2418..bc65e79a 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.*; import java.util.BitSet; @@ -46,6 +47,8 @@ public class AddNode extends Node { 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 @@ -187,4 +190,10 @@ public class AddNode extends Node { @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/chapter20/src/main/java/com/seaofnodes/simple/node/AndNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/AndNode.java index 36e3a949..3cfcbda3 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/AndNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/AndNode.java @@ -36,14 +36,17 @@ public class AndNode extends LogicalNode { 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) - if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==-1 ) + // 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() ) + if( t1.isConstant() && !t2.isConstant() ) return swap12(); // Do we have ((x & (phi cons)) & con) ? diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/BoolNode.java index 93e5e4e9..e6b87ce8 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -49,6 +49,8 @@ abstract public class BoolNode extends Node { 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() { @@ -89,6 +91,7 @@ abstract public class BoolNode extends Node { Node copy(Node lhs, Node rhs) { return new EQ(lhs,rhs); } Node copyF() { return new EQF(null,null); } } + public static class LT extends BoolNode { public LT(Node lhs, Node rhs) { super(lhs,rhs); } public String op() { return "<" ; } @@ -101,6 +104,7 @@ abstract public class BoolNode extends Node { 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 "<="; } @@ -114,9 +118,9 @@ abstract public class BoolNode extends Node { 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 static class LTF extends LT { public LTF(Node lhs, Node rhs) { super(lhs,rhs); } boolean doOp(double lhs, double rhs) { return lhs < rhs; } } - 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 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 { diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/CFGNode.java index 174725f5..c5cd95f0 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/CFGNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/CFGNode.java @@ -18,15 +18,26 @@ import java.util.HashSet; * <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); - _idepth = cfg._idepth; - _ltree = cfg._ltree; - _pre = cfg._pre; + if( cfg != null ) { + _idepth = cfg._idepth; + _ltree = cfg._ltree; + _pre = cfg._pre; + } } public CFGNode cfg(int idx) { return (CFGNode)in(idx); } @@ -75,6 +86,14 @@ public abstract class CFGNode extends Node { // 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; } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/CProjNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/CProjNode.java index 690ef590..adc4e66f 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/CProjNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/CProjNode.java @@ -1,6 +1,7 @@ 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; @@ -25,7 +26,6 @@ public class CProjNode extends CFGNode { @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(_label); } - @Override public boolean isMultiTail() { return in(0).isMultiHead(); } @Override public boolean blockHead() { return true; } public CFGNode ctrl() { return cfg(0); } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/CallEndNode.java index 4e50c1fc..03a4462d 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/CallEndNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/CallEndNode.java @@ -1,6 +1,7 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.*; import java.util.BitSet; @@ -11,16 +12,15 @@ 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}); } - public CallEndNode(CallEndNode cend) { super(cend); } + 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 isMultiHead() { return true; } + @Override public String label() { return "CallEnd"; } @Override public boolean blockHead() { return true; } - CallNode call() { return (CallNode)in(0); } + public CallNode call() { return (CallNode)in(0); } @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { @@ -36,7 +36,17 @@ public class CallEndNode extends CFGNode implements MultiNode { public Type compute() { if( !(in(0) instanceof CallNode call) ) return TypeTuple.RET.dual(); - Type ret = call.fptr().addDep(this)._type instanceof TypeFunPtr tfp ? tfp.ret() : Type.BOTTOM; + Type ret = Type.BOTTOM; + TypeMem mem = TypeMem.BOT; + if( call.fptr().addDep(this)._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); } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/CallNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/CallNode.java index a9ca852c..c9bbcc52 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/CallNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/CallNode.java @@ -1,6 +1,6 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.CodeGen; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.Utils; @@ -19,8 +19,7 @@ public class CallNode extends CFGNode { 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 String label() { return "Call"; } @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { String fname = name(); @@ -43,11 +42,13 @@ public class CallNode extends CFGNode { Node ctrl() { return in(0); } Node mem () { return in(1); } // Args mapped 1-to-1 on inputs, so conceptually start at 2 - Node arg(int idx) { return in(idx); } + public Node arg(int idx) { return in(idx); } // Same arg accounting as TFPs, although the numbering starts at 2 - int nargs() { return nIns()-3; } // Minus control, memory, fptr + 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; } // Find the Call End from the Call public CallEndNode cend() { @@ -120,12 +121,28 @@ public class CallNode extends CFGNode { fun.addDef(this); for( Node use : fun._outputs ) if( use instanceof ParmNode parm ) - parm.addDef(parm._idx==0 ? cend() : arg(parm._idx)); + 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()); assert linked(fun); } + // Unlink all linked functions + public void unlink_all() { + for( int i=0; i<_outputs._len; i++ ) + if( out(i) instanceof FunNode fun ) { + 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) ) diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java new file mode 100644 index 00000000..d4a937bd --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java @@ -0,0 +1,25 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.type.Type; +import java.util.BitSet; +import java.io.ByteArrayOutputStream; + +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 int encoding(ByteArrayOutputStream bytes) { return 0; } + + @Override public Type compute() { return Type.BOTTOM; } + @Override public Node idealize() { return null; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/CastNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/CastNode.java index d8a968c1..5a92802c 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/CastNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/CastNode.java @@ -9,13 +9,19 @@ 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 { - private final Type _t; + 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 diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/ConstantNode.java index d788cff7..f7ebc006 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -1,6 +1,6 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.CodeGen; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.SB; import com.seaofnodes.simple.type.Type; @@ -26,7 +26,8 @@ public class ConstantNode extends Node { super(new Node[]{CodeGen.CODE._start}); _con = _type = type; } - public ConstantNode( ConstantNode con ) { super(con); _con = con._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(); @@ -42,7 +43,7 @@ public class ConstantNode extends Node { @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { - if( _con instanceof TypeFunPtr tfp ) { + if( _con instanceof TypeFunPtr tfp && tfp.isConstant() ) { FunNode fun = CodeGen.CODE.link(tfp); if( fun._name != null ) return sb.append("{ ").append(fun._name).append("}"); @@ -50,7 +51,6 @@ public class ConstantNode extends Node { return sb.append(_con.print(new SB())); } - @Override public boolean isMultiTail() { return true; } @Override public boolean isConst() { return true; } @Override diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/CtrlNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/CtrlNode.java index 0ea060bd..61d9c310 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/CtrlNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/CtrlNode.java @@ -1,6 +1,6 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.CodeGen; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.Type; import java.util.BitSet; diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/DivFNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/DivFNode.java index 36fd4524..a6e459c9 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/DivFNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/DivFNode.java @@ -31,14 +31,9 @@ public class DivFNode extends Node { @Override public Node idealize() { - Node lhs = in(1); - Node rhs = in(2); - Type t1 = lhs._type; - Type t2 = rhs._type; - - // Div of 1. - if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==1 ) - return lhs; + // 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; } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/FunNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/FunNode.java index 506e7758..38aa83a0 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/FunNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/FunNode.java @@ -3,8 +3,10 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.*; 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.CODE; +import static com.seaofnodes.simple.codegen.CodeGen.CODE; public class FunNode extends RegionNode { @@ -17,10 +19,19 @@ public class FunNode extends RegionNode { 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._loc ); _sig = fun.sig(); _name = fun._name; } + 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" : _name; } + 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. @@ -43,11 +54,11 @@ public class FunNode extends RegionNode { // 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; } - ReturnNode ret() { assert _ret!=null; return _ret; } + public ReturnNode ret() { assert _ret!=null; return _ret; } // Signature can improve over time public TypeFunPtr sig() { return _sig; } - void setSig( TypeFunPtr sig ) { + public void setSig( TypeFunPtr sig ) { assert sig.isa(_sig); if( _sig != sig ) { CODE.add(this); @@ -72,6 +83,10 @@ public class FunNode extends RegionNode { 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. diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/IfNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/IfNode.java index 9e6d2dff..e04522b0 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/IfNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/IfNode.java @@ -1,6 +1,6 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.CodeGen; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.type.*; import java.util.BitSet; @@ -18,13 +18,11 @@ public class IfNode extends CFGNode implements MultiNode { public String label() { return "If"; } @Override - StringBuilder _print1(StringBuilder sb, BitSet visited) { + public StringBuilder _print1(StringBuilder sb, BitSet visited) { sb.append("if( "); return in(1)._print0(sb, visited).append(" )"); } - @Override public boolean isMultiHead() { return true; } - public Node ctrl() { return in(0); } public Node pred() { return in(1); } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/LoadNode.java index 6c74042b..b70a938d 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -204,7 +204,7 @@ public class LoadNode extends MemOpNode { 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())); - throw Utils.TODO(); + Node shl = new ShlNode(null,val,shf.keep()).peephole(); + return new SarNode(null,shl,shf.unkeep()); } } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/LogicalNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/LogicalNode.java index ccc1f6b5..8c3452bf 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/LogicalNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/LogicalNode.java @@ -22,8 +22,8 @@ public abstract class LogicalNode extends Node { @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.glb(),_loc); - if( !(in(2)._type instanceof TypeInteger) ) return Parser.error("Cannot '"+op()+"' " + in(2)._type.glb(), _loc); + 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/chapter20/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java index 0c49fc64..b01b3308 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java @@ -7,7 +7,7 @@ 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 abstract class MachConcreteNode extends Node implements MachNode { public MachConcreteNode(Node node) { super(node); } public MachConcreteNode(Node[]nodes) { super(nodes); } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/MachNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/MachNode.java index 9f2fee85..ecdbc651 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/MachNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/MachNode.java @@ -1,6 +1,9 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.Machine; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import java.io.ByteArrayOutputStream; public interface MachNode { @@ -12,13 +15,16 @@ public interface MachNode { default void postSelect() { } // Register mask allowed on input i. 0 for no register. - abstract public RegMask regmap(int i); + RegMask regmap( int i ); // Register mask allowed as a result. 0 for no register. - abstract public RegMask outregmap(); + 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. @@ -32,11 +38,17 @@ public interface MachNode { // 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 into the byte array; size is returned - abstract public int encoding(ByteArrayOutputStream bytes); + int encoding(ByteArrayOutputStream bytes); // Human-readable form appended to the SB. Things like the encoding, // indentation, leading address or block labels not printed here. diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java index 5363bb6f..1495a703 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java @@ -1,6 +1,7 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.*; import java.util.*; diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/MemOpNode.java index d9ab6b89..cba4dc1b 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/MemOpNode.java @@ -33,14 +33,14 @@ public abstract class MemOpNode extends Node { } public MemOpNode( Node mach, MemOpNode mop ) { super(mach); - _name = mop._name; - _alias = mop._alias; - _declaredType = mop._declaredType; - _loc = mop._loc; + _name = mop==null ? null : mop._name; + _alias = mop==null ? 0 : mop._alias; + _loc = mop==null ? null : mop._loc; + _declaredType = mop==null ? Type.BOTTOM : mop._declaredType; } // - static String mlabel(String name) { return name.equals("[]") ? "ary" : (name.equals("#") ? "len" : name); } + static String mlabel(String name) { return "[]".equals(name) ? "ary" : ("#".equals(name) ? "len" : name); } String mlabel() { return mlabel(_name); } public Node mem() { return in(1); } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/MulNode.java index 4fdf987d..80f8c319 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -78,4 +78,10 @@ public class MulNode extends Node { } @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/chapter20/src/main/java/com/seaofnodes/simple/node/NeverNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/NeverNode.java index e77ed653..26198500 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/NeverNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/NeverNode.java @@ -13,7 +13,7 @@ public class NeverNode extends IfNode { @Override public String label() { return "Never"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append("Never"); } + @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append("Never"); } @Override public Type compute() { return TypeTuple.IF_BOTH; } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter20/src/main/java/com/seaofnodes/simple/node/Node.java index bbf1ebe9..b3d6c94e 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/Node.java @@ -1,12 +1,15 @@ 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.CODE; +import static com.seaofnodes.simple.codegen.CodeGen.CODE; /** * All Nodes in the Sea of Nodes IR inherit from the Node class. @@ -63,11 +66,11 @@ public abstract class Node { // 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 == CodeGen.Phase.InstructionSelection; + assert CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.InstSelect.ordinal(); _nid = CODE.getUID(); // allocate unique dense ID - _inputs = new Ary<>(n._inputs.asAry()); + _inputs = new Ary<>(n==null ? new Node[0] : n._inputs.asAry()); _outputs = new Ary<>(Node.class); - _type = n._type; + _type = n==null ? Type.BOTTOM : n._type; _deps = null; _hash = 0; } @@ -108,7 +111,7 @@ public abstract class Node { // This is the common print: check for repeats, check for DEAD and print // "DEAD" else call the per-Node print1. - final StringBuilder _print0(StringBuilder sb, BitSet visited) { + public final StringBuilder _print0(StringBuilder sb, BitSet visited) { if (visited.get(_nid) && !(this instanceof ConstantNode) ) return sb.append(label()); visited.set(_nid); @@ -121,9 +124,7 @@ public abstract class Node { public String p(int depth) { return IRPrinter.prettyPrint(this,depth); } - public boolean isMultiHead() { return false; } - public boolean isMultiTail() { return false; } - public boolean isConst () { return false; } + public boolean isConst() { return false; } // ------------------------------------------------------------------------ // Graph Node & Edge manipulation @@ -181,12 +182,16 @@ public abstract class Node { // 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. - Node delDef(int idx) { + public Node delDef(int idx) { unlock(); Node old_def = in(idx); _inputs.del(idx); @@ -295,6 +300,74 @@ public abstract class Node { kill(); } + // Replace uses of `def` with `this`, and insert `this` immediately after + // `def` in the basic block. + public void insertAfter( Node def, boolean must ) { + CFGNode cfg = def.cfg0(); + int i = cfg._outputs.find(def)+1; + if( cfg instanceof CallEndNode ) { + cfg = cfg.uctrl(); i=0; + } else if( def.in(0) instanceof MultiNode ) { + assert i==0; + i = cfg._outputs.find(def.in(0))+1; + } + + while( cfg.out(i) instanceof PhiNode || cfg.out(i) instanceof CalleeSaveNode ) i++; + cfg._outputs.insert(this,i); + _inputs.set(0,cfg); + for( int j=def.nOuts()-1; j>=0; j-- ) { + // Can we avoid a split of a split? 'this' split is used by + // another split in the same block. + if( !must && def.out(j) instanceof SplitNode split && def.out(j).cfg0()==cfg && + !split._kind.contains("self") ) + continue; + Node use = def._outputs.del(j); + use.unlock(); + int idx = use._inputs.find(def); + use._inputs.set(idx,this); + addUse(use); + } + if( nIns()>1 ) setDef(1,def); + } + + // Insert this in front of use.in(uidx) with this, and insert this + // immediately before use in the basic block. + public void insertBefore( Node use, int uidx ) { + CFGNode cfg = use.cfg0(); + int i; + if( use instanceof PhiNode phi ) { + cfg = phi.region().cfg(uidx); + i = cfg.nOuts()-1; + } else { + i = cfg._outputs.find(use); + } + cfg._outputs.insert(this,i); + _inputs.set(0,cfg); + if( _inputs._len > 1 && this instanceof SplitNode ) + setDefOrdered(1,use.in(uidx)); + use.setDefOrdered(uidx,this); + } + + public void setDefOrdered(int idx, Node def) { + // If old is dying, remove from CFG ordered + Node old = in(idx); + if( old!=null && old.nOuts()==1 ) { + CFGNode cfg = old.cfg0(); + if( cfg!=null ) { + cfg._outputs.remove(cfg._outputs.find(old)); + old._inputs.set(0,null); + } + } + setDef(idx,def); + } + + public void removeSplit() { + CFGNode cfg = cfg0(); + cfg._outputs.remove(cfg._outputs.find(this)); + _inputs.set(0,null); + if( _inputs._len > 1 ) subsume(in(1)); + } + // ------------------------------------------------------------------------ // Graph-based optimizations @@ -578,7 +651,7 @@ public abstract class Node { // Peephole utilities // Swap inputs without letting either input go dead during the swap. - Node swap12() { + public Node swap12() { unlock(); // Hash is order dependent _inputs.swap(1,2); return this; @@ -634,7 +707,14 @@ public abstract class Node { /** * Debugging utility to find a Node by index */ - public Node find(int nid) { - return walk( n -> n._nid==nid ? n : null ); + 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/chapter20/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/PhiNode.java index 17748afe..16f38d45 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -44,7 +44,6 @@ public class PhiNode extends Node { } public CFGNode region() { return (CFGNode)in(0); } - @Override public boolean isMultiTail() { return true; } @Override public boolean isMem() { return _declaredType instanceof TypeMem; } @Override public boolean isPinned() { return true; } @@ -101,6 +100,10 @@ public class PhiNode extends Node { // Test not running backwards, which can happen for e.g. And's if( down.compute().isa(compute()) ) return down; + in(1).in(1).addDep(this); + in(1).in(2).addDep(this); + in(2).in(1).addDep(this); + in(2).in(2).addDep(this); down.kill(); } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/ProjNode.java index ef7b17e4..3d9db4ed 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/ProjNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/ProjNode.java @@ -35,7 +35,6 @@ public class ProjNode extends Node { return in(0) instanceof CFGNode cfg ? cfg : in(0).cfg0(); } - @Override public boolean isMultiTail() { return in(0).isMultiHead(); } @Override public boolean isMem() { return _type instanceof TypeMem; } @Override public boolean isPinned() { return true; } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/RegionNode.java index 5d3db5c6..fb6539ae 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -1,6 +1,7 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.Type; import java.util.*; @@ -10,7 +11,7 @@ public class RegionNode extends CFGNode { 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); _loc = r._loc; } + public RegionNode(RegionNode r) { super(r); if( r!=null ) _loc = r._loc; } @Override public String label() { return "Region"; } @@ -20,7 +21,6 @@ public class RegionNode extends CFGNode { return sb.append(label()); } - @Override public boolean isMultiHead() { return true; } @Override public boolean blockHead() { return true; } @Override diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index 20b6c7b9..809dd1e1 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -37,7 +37,8 @@ public class ReturnNode extends CFGNode { @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { sb.append("return "); - expr()._print0(sb, visited); + if( expr()==null ) sb.append("----"); + else expr()._print0(sb, visited); return sb.append(";"); } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index 1208ff63..f193e55a 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -1,6 +1,7 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.*; import java.util.*; diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/SplitNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/SplitNode.java new file mode 100644 index 00000000..22cb9bb9 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/SplitNode.java @@ -0,0 +1,22 @@ +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) { + return in(1)._print0(sb.append("mov("),visited).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/chapter20/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/StartNode.java index 26e08535..22a1750d 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -1,6 +1,6 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.CodeGen; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.*; import java.util.Arrays; @@ -20,7 +20,7 @@ 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._arg; } + public StartNode(StartNode start) { super(start); _arg = start==null ? null : start._arg; } @Override public String label() { return "Start"; } @@ -29,7 +29,6 @@ public class StartNode extends LoopNode implements MultiNode { return sb.append(label()); } - @Override public boolean isMultiHead() { return true; } @Override public boolean blockHead() { return true; } @Override public CFGNode cfg0() { return null; } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/StopNode.java index 6aaba3b3..b4cb002b 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/StopNode.java @@ -13,7 +13,7 @@ public class StopNode extends CFGNode { _src = src; _type = compute(); } - public StopNode(StopNode stop) { super(stop); _src = stop._src; } + public StopNode(StopNode stop) { super(stop); _src = stop==null ? null : stop._src; } @Override public String label() { diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java index 9b4245c7..da021246 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java @@ -1,6 +1,6 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.CodeGen; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.Type; import java.util.BitSet; @@ -10,7 +10,6 @@ public class XCtrlNode extends CFGNode { @Override public String label() { return "Xctrl"; } @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append("Xctrl"); } @Override public boolean isConst() { return true; } - @Override public boolean isMultiTail() { return true; } @Override public Type compute() { return Type.XCONTROL; } @Override public Node idealize() { return null; } } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java new file mode 100644 index 00000000..c4014571 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java @@ -0,0 +1,34 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class AddARM extends MachConcreteNode implements MachNode { + + AddARM( Node add) {super(add); } + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { + assert i== 1 || i == 2 || i == 3; + return arm.RMASK; + } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form: "rd = rs1 + rs2" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2))); + } + + @Override public String op() { + return "add";} +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java new file mode 100644 index 00000000..098ab8c1 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java @@ -0,0 +1,31 @@ +package com.seaofnodes.simple.node.cpus.arm; + + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class AddFARM extends MachConcreteNode implements MachNode { + AddFARM(Node addf) { super(addf); } + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { assert i==1 || i==2; return arm.DMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.DMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // Default on double precision for now(64 bits) + // General form: "vadd.32 rd = src1 + src2 + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2))); + } + + @Override public String op() { return "addf"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java new file mode 100644 index 00000000..c8f5f8dd --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java @@ -0,0 +1,51 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class AddIARM extends MachConcreteNode implements MachNode { + final TypeInteger _ti; + + AddIARM(Node add, TypeInteger ti) { + super(add); + _inputs.pop(); + _ti = ti; + } + + // Register mask allowed on input i. + @Override + public RegMask regmap(int i) { + // assert i== i; + return arm.RMASK; + } + + // Register mask allowed as a result. 0 for no register. + @Override + public RegMask outregmap() { + return arm.RMASK; + } + + // Encoding is appended into the byte array; size is returned + @Override + public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form: "addi rd = rs1 + imm" + @Override + public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + #"); + _ti.print(sb); + } + + @Override + public String op() { + return _ti.value() == 1 ? "inc" : (_ti.value() == -1 ? "dec" : "addi"); + + } + +}
\ No newline at end of file diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java new file mode 100644 index 00000000..7655df40 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java @@ -0,0 +1,38 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.Node; + +import java.io.ByteArrayOutputStream; + +public class AndARM extends MachConcreteNode implements MachNode { + AndARM(Node and) { + super(and); + } + + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { + assert i==1 || i==2; + return arm.RMASK; + } + + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // 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/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java new file mode 100644 index 00000000..ee05654b --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java @@ -0,0 +1,56 @@ +package com.seaofnodes.simple.node.cpus.arm; + + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; +import java.util.BitSet; +import java.lang.StringBuilder; + +public class AndIARM extends MachConcreteNode implements MachNode { + final TypeInteger _ti; + + AndIARM(AndNode and, TypeInteger ti) { + super(and); + _inputs.pop(); + _ti = ti; + } + + // Register mask allowed on input i. + // This is the normal calling convention + @Override + public RegMask regmap(int i) { + assert i == 1 || i == 2; + return arm.RMASK; + } + + // Register mask allowed as a result. 0 for no register. + @Override + public RegMask outregmap() { + return arm.RMASK; + } + + // Encoding is appended into the byte array; size is returned + @Override + public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: "andi rd = rs1 & imm" + @Override + public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" & #"); + _ti.print(sb); + } + + @Override + public String op() { + return "andi"; + } + +}
\ No newline at end of file diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java new file mode 100644 index 00000000..a79de49b --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java @@ -0,0 +1,40 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; +import java.util.BitSet; +import java.lang.StringBuilder; + +// Arithmetic Shift Right (register) +public class AsrARM extends MachConcreteNode implements MachNode { + AsrARM(Node asr) {super(asr);} + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { + // assert i==1; + return arm.RMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } + + // Output is same register as input#1 + @Override public int twoAddress() { return 1; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: "asr rd, rs1, rs2" + @Override public void asm(CodeGen code, SB sb) { + + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> ").p(code.reg(in(2))); + } + + @Override public String op() { return "asr"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java new file mode 100644 index 00000000..099c3699 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java @@ -0,0 +1,41 @@ +package com.seaofnodes.simple.node.cpus.arm; + + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; +import java.util.BitSet; +import java.lang.StringBuilder; + +// Arithmetic Shift Right (immediate) +public class AsrIARM extends MachConcreteNode implements MachNode { + final TypeInteger _ti; + AsrIARM(Node asri, TypeInteger ti) {super(asri); _inputs.pop(); _ti = ti;} + + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { + // assert i==1; + return arm.RMASK; } + + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: "asri rd, rs1, imm" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> #"); + _ti.print(sb); + } + + @Override public String op() { return "asri"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java new file mode 100644 index 00000000..1a5108cf --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java @@ -0,0 +1,48 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; + +import java.io.ByteArrayOutputStream; + + +// Jump on flags, uses flags +public class BranchARM extends IfNode implements MachNode{ + final String _bop; + BranchARM(IfNode iff, String bop ) { + super(iff); + _bop = bop; + } + + @Override public String label() { return op(); } + + @Override public void postSelect() { + Node set = in(1); + Node cmp = set.in(1); + // Bypass an expected Set and just reference the cmp directly + if( set instanceof SetARM) + _inputs.set(1,cmp); + else + throw Utils.TODO(); + } + + @Override public RegMask regmap(int i) { assert i==1; return arm.FLAGS_MASK; } + @Override public RegMask outregmap() { return null; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { + String src = code.reg(in(1)); + if( src!="flags" ) sb.p(src); + } + + @Override public String op() { return "j"+_bop; } + @Override public String comment() { + return "L"+cproj(1)._nid+", L"+cproj(0)._nid; + } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java new file mode 100644 index 00000000..4359c045 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java @@ -0,0 +1,45 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeFunPtr; +import java.io.ByteArrayOutputStream; + +public class CallARM extends CallNode implements MachNode { + 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 label() { return op(); } + @Override public RegMask regmap(int i) { + return arm.callInMask(_tfp,i); // Normal argument + } + @Override public RegMask outregmap() { return null; } + + @Override public String name() { return _name; } + @Override public TypeFunPtr tfp() { return _tfp; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(_name); + for( int i=0; i<nargs(); i++ ) + sb.p(code.reg(arg(i))).p(" "); + sb.unchar(2); + } + + @Override public String op() { return "call"; } + +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallEndARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallEndARM.java new file mode 100644 index 00000000..6177c5ea --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallEndARM.java @@ -0,0 +1,35 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.CallEndNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.type.TypeFloat; +import com.seaofnodes.simple.type.TypeFunPtr; +import java.io.ByteArrayOutputStream; + +public class CallEndARM extends CallEndNode implements MachNode { + final TypeFunPtr _tfp; + CallEndARM( CallEndNode cend ) { + super(cend); + _tfp = (TypeFunPtr)(cend.call().fptr()._type); + } + + @Override public String label() { return op(); } + @Override public RegMask regmap(int i) { return null; } + @Override public RegMask outregmap() { return null; } + @Override public RegMask outregmap(int idx) { return idx == 2 ? arm.retMask(_tfp,2) : null; } + @Override public RegMask killmap() { return arm.armCallerSave(); } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { } + + @Override public String op() { return "cend"; } + +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java new file mode 100644 index 00000000..db85a9a9 --- /dev/null +++ b/chapter20/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.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import java.io.ByteArrayOutputStream; + +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) { + // Todo: float or int? + return i==_inputs._len + ? arm.RMASK // Function call target + : arm.callInMask(tfp(),i); // Normal argument + } + @Override public RegMask outregmap() { return null; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { + 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/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java new file mode 100644 index 00000000..f4f6242d --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java @@ -0,0 +1,30 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFunPtr; +import java.io.ByteArrayOutputStream; + +public class CmpARM extends MachConcreteNode implements MachNode { + CmpARM( Node cmp ) { super(cmp); } + @Override public String op() { return "cmp"; } + + + @Override public RegMask regmap(int i) { assert i==1 || i==2; return arm.RMASK; } + @Override public RegMask outregmap() { return arm.FLAGS_MASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // 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/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java new file mode 100644 index 00000000..9c287fcb --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java @@ -0,0 +1,31 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFunPtr; +import java.io.ByteArrayOutputStream; + +// compare instruction on float regs input(D-registers) +public class CmpFARM extends MachConcreteNode implements MachNode{ + CmpFARM(Node cmp) {super(cmp);} + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // 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))); + } + + @Override public RegMask regmap(int i) { assert i==1 || i==2; return arm.DMASK; } + @Override public RegMask outregmap() { return arm.FLAGS_MASK; } + + @Override public String op() { return "cmpf"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java new file mode 100644 index 00000000..a0b10ba4 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java @@ -0,0 +1,54 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +// Compare with immediate. +// Conditional compare (immediate)? e.g CCMP(immediate) +public class CmpIARM extends MachConcreteNode implements MachNode { + final int _imm; + final String _bop; + CmpIARM(BoolNode bool, TypeInteger ti) { + super(bool); + _inputs.pop(); + _bop = bool.op(); + _imm = (int)ti.value(); + assert _imm == ti.value(); + } + // Copy constructor does not set reverse edges in, because the basic block + // is changing from the original node - and it must be properly placed in a + // new block. + CmpIARM( CmpIARM cmp ) { + super(cmp); + _bop = cmp._bop; + _imm = cmp._imm; + // While in(0) will be handled by the caller, all other edges must be + // handled in the constructor. + cmp.in(1)._outputs.push(this); + } + + @Override public RegMask regmap(int i) { assert i==1; return arm.RMASK; } + @Override public RegMask outregmap() { return arm.FLAGS_MASK; } + @Override public boolean isClone() { return true; } + @Override public Node copy() { return new CmpIARM(this); } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // 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); + } + @Override public String op() { return _imm==0 ? "test" : "cmp"; } + + } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpMemARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpMemARM.java new file mode 100644 index 00000000..47f68ccc --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpMemARM.java @@ -0,0 +1,4 @@ +package com.seaofnodes.simple.node.cpus.arm; + +public class CmpMemARM { +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java new file mode 100644 index 00000000..7c67e2dd --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java @@ -0,0 +1,33 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class DivARM extends MachConcreteNode implements MachNode { + DivARM( Node div ) { super(div); } + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { + assert i==1 || i==2; + return arm.RMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } + // Output is same register as input#1 + @Override public int twoAddress() { return 1; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form: "div dst /= src" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2))); + } + + @Override public String op() { return "div"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java new file mode 100644 index 00000000..2137c01c --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java @@ -0,0 +1,30 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class DivFARM extends MachConcreteNode implements MachNode { + DivFARM( Node divf) { super(divf); } + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { assert i==1 || i==2; return arm.DMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.DMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + + // 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))); + } + + @Override public String op() { return "divf"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java new file mode 100644 index 00000000..7fafc31c --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java @@ -0,0 +1,36 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.ConstantNode; +import com.seaofnodes.simple.node.MachNode; +import java.io.ByteArrayOutputStream; + +//FMOV (scalar, immediate) +//Floating-point move immediate. +public class FloatARM extends ConstantNode implements MachNode { + FloatARM(ConstantNode con) { super(con); } + @Override public String op() { return "fld"; } + // Register mask allowed on input i. 0 for no register. + @Override public RegMask regmap(int i) { return null; } + // General int registers + @Override public RegMask outregmap() { return arm.DMASK; } + + @Override public boolean isClone() { return true; } + @Override public FloatARM copy() { return new FloatARM(this); } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // Human-readable form appended to the SB. Things like the encoding, + // indentation, leading address or block labels not printed here. + // Just something like "ld4\tR17=[R18+12] // Load array base". + // General form: "op\tdst=src+src" + @Override public void asm(CodeGen code, SB sb) { + _con.print(sb.p(code.reg(this)).p(" #")); + } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java new file mode 100644 index 00000000..0134911f --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java @@ -0,0 +1,21 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.node.FunNode; +import com.seaofnodes.simple.node.MachNode; +import java.io.ByteArrayOutputStream; + +public class FunARM extends FunNode implements MachNode{ + FunARM(FunNode fun) {super(fun);} + // Register mask allowed on input i. 0 for no register. + @Override public RegMask regmap(int i) { return null; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return null; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java new file mode 100644 index 00000000..b106ab8a --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java @@ -0,0 +1,29 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +// VCVT (between floating-point and integer) +public class I2F8ARM extends MachConcreteNode implements MachNode { + I2F8ARM(Node i2f8) {super(i2f8);} + @Override public String op() { return "i2f8"; } + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { assert i==1; return arm.RMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.DMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // 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/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java new file mode 100644 index 00000000..1f81300e --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java @@ -0,0 +1,38 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.ConstantNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +// Integer constants +public class IntARM extends ConstantNode implements MachNode { + IntARM( ConstantNode con ) { super(con); } + @Override public String op() { return "ldi"; } + + // Register mask allowed on input i. 0 for no register. + @Override public RegMask regmap(int i) { return null; } + // General int registers + @Override public RegMask outregmap() { return arm.WMASK; } + + @Override public boolean isClone() { return true; } + @Override public IntARM copy() { return new IntARM(this); } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // 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/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java new file mode 100644 index 00000000..0d59cc47 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java @@ -0,0 +1,37 @@ +package com.seaofnodes.simple.node.cpus.arm; + + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.LoadNode; +import com.seaofnodes.simple.node.Node; + + +// Load memory addressing on ARM +// Support imm, reg(direct), or reg+off(indirect) addressing +// Base = base - base pointer, offset is added to base +// idx = null +// off = off - offset added to base + +public class LoadARM extends MemOpARM{ + LoadARM(LoadNode ld,Node base, Node idx, int off) { + super(ld, base, idx, off, 0); + } + + @Override public RegMask regmap(int i) { + return arm.RMASK; + } + + // Wide mask loads both ints and floats; encoding varies. + @Override public RegMask outregmap() { return arm.MEM_MASK; } + + + @Override public String op() { return "ld"+_sz; } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(","); + asm_address(code,sb); + } + +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java new file mode 100644 index 00000000..6108c889 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java @@ -0,0 +1,41 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; +import java.util.BitSet; +import java.lang.StringBuilder; + + +// Logical Shift Left (register) +public class LslARM extends MachConcreteNode implements MachNode { + LslARM(Node asr) {super(asr);} + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { + // assert i==1; + return arm.RMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } + + // Output is same register as input#1 + @Override public int twoAddress() { return 1; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: "lsl rd, rs1, rs2" + @Override public void asm(CodeGen code, SB sb) { + + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" << ").p(code.reg(in(2))); + } + + @Override public String op() { return "lsl"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java new file mode 100644 index 00000000..7a7014f4 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java @@ -0,0 +1,42 @@ +package com.seaofnodes.simple.node.cpus.arm; + + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; +import java.util.BitSet; +import java.lang.StringBuilder; + + +// Logical Shift Left (immediate) +public class LslIARM extends MachConcreteNode implements MachNode { + final TypeInteger _ti; + LslIARM(Node lsli, TypeInteger ti) {super(lsli); _inputs.pop(); _ti = ti;} + + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { + // assert i==1; + return arm.RMASK; } + + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: "lsli rd, rs1, imm" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" << #"); + _ti.print(sb); + } + + @Override public String op() { return "lsli"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java new file mode 100644 index 00000000..e6168dee --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java @@ -0,0 +1,40 @@ +package com.seaofnodes.simple.node.cpus.arm; + + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; +import java.util.BitSet; +import java.lang.StringBuilder; + +// Logical Shift Right (register) +public class LsrARM extends MachConcreteNode implements MachNode { + LsrARM(Node asr) {super(asr);} + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { + // assert i==1; + return arm.RMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } + + // Output is same register as input#1 + @Override public int twoAddress() { return 1; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: "lsr rd, rs1, rs2" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >>> ").p(code.reg(in(2))); + } + + @Override public String op() { return "lsr"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java new file mode 100644 index 00000000..8fb20cb2 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java @@ -0,0 +1,41 @@ +package com.seaofnodes.simple.node.cpus.arm; + + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; +import java.util.BitSet; +import java.lang.StringBuilder; + +// Logical Shift Right (immediate) +public class LsrIARM extends MachConcreteNode implements MachNode{ + final TypeInteger _ti; + LsrIARM(Node lsri, TypeInteger ti) {super(lsri); _inputs.pop(); _ti = ti;} + + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { + // assert i==1; + return arm.RMASK; } + + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: "lsrl rd, rs1, imm" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >>> #"); + _ti.print(sb); + } + + @Override public String op() { return "lsrl"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java new file mode 100644 index 00000000..adffa501 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java @@ -0,0 +1,58 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.*; +import java.io.ByteArrayOutputStream; +import java.lang.StringBuilder; +import java.util.BitSet; + + +public class MemOpARM extends MemOpNode implements MachNode { + final int _off; // Limit 32 bits + final int _imm; // Limit 32 bits + final char _sz = (char)('0'+(1<<_declaredType.log_size())); + MemOpARM(MemOpNode mop, Node base, Node idx, int off, int imm) { + super(mop,mop); + assert base._type instanceof TypeMemPtr && !(base instanceof AddNode); + assert ptr() == base; + _inputs.setX(3,idx); + _off = off; + _imm = imm; + } + + // Store-based flavors have a value edge + MemOpARM( MemOpNode mop, Node base, Node idx, int off, int imm, Node val ) { + this(mop,base,idx,off,imm); + _inputs.setX(4,val); + } + + Node idx() { return in(3); } + Node val() { return in(4); } // Only for stores, includin + + @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) { + throw Utils.TODO(); + } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { throw Utils.TODO(); } + + @Override public int encoding(ByteArrayOutputStream bytes) { throw Utils.TODO(); } + + SB asm_address(CodeGen code, SB sb) { + sb.p("[").p(code.reg(ptr())).p("+"); + if( idx() != null ) sb.p(code.reg(idx())); + else sb.p(_off); + return sb.p("]"); + } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java new file mode 100644 index 00000000..582b97d4 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java @@ -0,0 +1,34 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + + +// mulh signed multiply instruction(no-imm form) +public class MulARM extends MachConcreteNode implements MachNode{ + MulARM(Node mul) {super(mul);} + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { assert i==1 || i==2; return arm.RMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } + // Output is same register as input#1 + @Override public int twoAddress() { return 1; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form: "rd = rs1 * rs2" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2))); + } + + @Override public String op() { return "mul"; } + +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java new file mode 100644 index 00000000..b71b412d --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java @@ -0,0 +1,30 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import java.io.ByteArrayOutputStream; + +public class MulFARM extends MachConcreteNode implements MachNode{ + MulFARM(Node mulf) {super(mulf);} + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { assert i==1 || i==2; return arm.DMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.DMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // Default on double precision for now(64 bits) + // General form: "VMUL.f32 rd = src1 * src2 + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2))); + } + + @Override public String op() { return "mulf"; } + +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulIARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulIARM.java new file mode 100644 index 00000000..c118b00a --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulIARM.java @@ -0,0 +1,43 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class MulIARM extends MachConcreteNode implements MachNode{ + final TypeInteger _ti; + MulIARM(Node mul, TypeInteger ti) { + super(mul); + _inputs.pop(); + _ti = ti; + } + + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { + // assert i==1; + return arm.RMASK; } + + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } + + // Output is same register as input#1 + @Override public int twoAddress() { return 1; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: "muli dst * #imm" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * #"); + _ti.print(sb); + } + + @Override public String op() { return "muli"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java new file mode 100644 index 00000000..bd88fa83 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java @@ -0,0 +1,31 @@ +package com.seaofnodes.simple.node.cpus.arm; + + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; + +import java.io.ByteArrayOutputStream; + +public class NewARM extends NewNode implements MachNode { + // A pre-zeroed chunk of memory. + NewARM(NewNode nnn) { super(nnn); } + // Size and pointer result in standard calling convention; null for all the + // memory aliases edges + @Override public RegMask regmap(int i) { return i == 1 ? arm. X0_MASK : null; } + @Override public RegMask outregmap(int i) { return i == 1 ? arm. X0_MASK : null; } + @Override public RegMask outregmap() { return null; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form: "alloc #bytes" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(size())); + } + + @Override public String op() { return "alloc"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java new file mode 100644 index 00000000..12ea8265 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java @@ -0,0 +1,42 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.Node; + +import java.io.ByteArrayOutputStream; + +// Not using the ORRS variant. +public class OrARM extends MachConcreteNode implements MachNode { + OrARM(Node and) { + super(and); + } + + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { + assert i==1 || i==2; + return arm.RMASK; + } + + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: #rd = rs1 & rs2 + @Override public void asm(CodeGen code, SB sb) { + + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" | ").p(code.reg(in(2))); + } + + @Override public String op() { return "orr"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java new file mode 100644 index 00000000..f257a867 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java @@ -0,0 +1,40 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.Node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class OrIARM extends MachConcreteNode implements MachNode{ + final TypeInteger _ti; + OrIARM(Node or, TypeInteger ti) {super(or); _inputs.pop(); _ti = ti;} + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { + assert i==1 || i==2; + return arm.RMASK; + } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: "rd = rs1 | imm" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" | #"); + _ti.print(sb); + } + + @Override public String op() { return "ori"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java new file mode 100644 index 00000000..c3fa132c --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java @@ -0,0 +1,25 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.ParmNode; +import java.io.ByteArrayOutputStream; + +public class ParmARM extends ParmNode implements MachNode { + final RegMask _rmask; + ParmARM(ParmNode parm) { + super(parm); + // Assume int + _rmask = arm.callInMask(fun().sig(),_idx); + } + // Register mask allowed on input i. 0 for no register. + @Override public RegMask regmap(int i) { return null; } + // Register mask allowed as a result. Calling convention register + @Override public RegMask outregmap() { return _rmask; } + + // Encoding is appended into the byte array. Returns size + @Override public int encoding(ByteArrayOutputStream bytes) { return 0; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java new file mode 100644 index 00000000..b2add015 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java @@ -0,0 +1,23 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.ProjNode; +import java.io.ByteArrayOutputStream; + +public class ProjARM extends ProjNode implements MachNode { + ProjARM( ProjNode p ) { super(p); } + + // Register mask allowed on input i. 0 for no register. + @Override public RegMask regmap(int i) { return null; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { + return ((MachNode)in(0)).outregmap(_idx); + } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java new file mode 100644 index 00000000..c7d0dd4c --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java @@ -0,0 +1,49 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.FunNode; +import com.seaofnodes.simple.node.ReturnNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.type.TypeFloat; + +import java.io.ByteArrayOutputStream; + + +public class RetARM extends ReturnNode implements MachNode { + RetARM(ReturnNode ret, FunNode fun) { super(ret, fun); fun.setRet(this); } + + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { return arm.retMask(_fun.sig(),i); } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return null; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { + // Post code-gen, just print the "ret" + if( code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() ) + // Prints return reg (either X0 or D0), RPC (always R30) and + // then the callee-save registers. + for( int i=2; i<nIns(); i++ ) + sb.p(code.reg(in(i))).p(" "); + + // Post-allocation, if we did not get the expected return register, print what we got + else if( code._regAlloc.regnum(in(3)) != arm.X30 ) + sb.p("[").p(code.reg(in(3))).p("]"); + } + + // Correct Nodes outside the normal edges + @Override public void postSelect() { + FunNode fun = (FunNode)rpc().in(0); + _fun = fun; + fun.setRet(this); + } + + @Override public String op() { return "ret"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java new file mode 100644 index 00000000..c3945d1a --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java @@ -0,0 +1,41 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + + +// conditional set? +// e.g CSET, stores flag info into GPRS after compare + +public class SetARM extends MachConcreteNode implements MachNode { + final String _bop; // One of <,<=,== + + SetARM(Node cmp, String bop) { + super(cmp); + _inputs.setLen(1); // Pop the cmp inputs + // Replace with the matched cmp + _inputs.push(cmp); + _bop = bop; + } + + @Override public RegMask regmap(int i) { assert i==1; return arm.FLAGS_MASK; } + @Override public RegMask outregmap() { return arm.RMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)); + String src = code.reg(in(1)); + if( src!="flags" ) sb.p(" = ").p(src); + } + + @Override public String op() { return "set"+_bop; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java new file mode 100644 index 00000000..85771842 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java @@ -0,0 +1,22 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.SplitNode; +import java.io.ByteArrayOutputStream; + +public class SplitARM extends SplitNode { + SplitARM(String kind, byte round) { super(kind,round,new Node[2]);} + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { return arm.SPLIT_MASK; } + + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.SPLIT_MASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java new file mode 100644 index 00000000..49045b28 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java @@ -0,0 +1,52 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.StoreNode; +import java.io.ByteArrayOutputStream; +import java.util.BitSet; + +// 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 +public class StoreARM extends MemOpARM { + StoreARM(StoreNode st, Node base, Node idx, int off, int imm, Node val) { + super(st, base, idx, off, imm, val); + } + + @Override public String op() { return "st"+_sz; } + + // Wider mask to store both GPRs and FPRs + @Override public RegMask regmap(int i) { + if( i==1 ) return arm.MEM_MASK; + if( i==2 ) return arm.RMASK; + if( i==3 ) return arm.RMASK; + if( i==4 ) return arm.RMASK; + throw Utils.TODO(); + } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return null; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { + asm_address(code,sb).p(","); + if( val()==null ) sb.p("#").p(_imm); + else sb.p(code.reg(val())); + } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java new file mode 100644 index 00000000..27555b53 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java @@ -0,0 +1,30 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + + +public class SubARM extends MachConcreteNode implements MachNode { + SubARM(Node sub) {super(sub);} + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { assert i==1 || i==2; return arm.RMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form: "sub # rd = rs1 - rs2" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - ").p(code.reg(in(2))); + } + + @Override public String op() { return "sub"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java new file mode 100644 index 00000000..8ae05b40 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java @@ -0,0 +1,32 @@ +package com.seaofnodes.simple.node.cpus.arm; + + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class SubFARM extends MachConcreteNode implements MachNode{ + SubFARM(Node subf) {super(subf);} + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { assert i==1 || i==2; return arm.DMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.DMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // Default on double precision for now(64 bits) + // General form: "vsub.f32 rd = src1 + src2 + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - ").p(code.reg(in(2))); + } + + @Override public String op() { return "subf"; } + +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java new file mode 100644 index 00000000..cdb2b6ec --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java @@ -0,0 +1,37 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.ConstantNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFunPtr; +import java.io.ByteArrayOutputStream; + +public class TFPARM extends ConstantNode implements MachNode { + TFPARM( ConstantNode con ) { super(con); } + @Override public String op() { return "ldx"; } + // Register mask allowed on input i. 0 for no register. + @Override public RegMask regmap(int i) { return null; } + // General int registers + @Override public RegMask outregmap() { return arm.WMASK; } + + @Override public boolean isClone() { return true; } + @Override public TFPARM copy() { return new TFPARM(this); } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // Human-readable form appended to the SB. Things like the encoding, + // indentation, leading address or block labels not printed here. + // Just something like "ld4\tR17=[R18+12] // Load array base". + // General form: "op\tdst=src+src" + @Override public void asm(CodeGen code, SB sb) { + String reg = code.reg(this); + _con.print(sb.p(reg).p(" #")); + } + +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java new file mode 100644 index 00000000..fd127b18 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java @@ -0,0 +1,41 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class XorARM extends MachConcreteNode implements MachNode{ + XorARM(Node xor) {super(xor); _inputs.pop();} + + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { + assert i==1 || i==2; + + return arm.RMASK; } + + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } + + // Output is same register as input#1 + @Override public int twoAddress() { return 1; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: "rd = x1 ^ x2" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ^ ").p(code.reg(in(2))); + } + + @Override public String op() { return "eor"; } +}
\ No newline at end of file diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java new file mode 100644 index 00000000..d230ea47 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java @@ -0,0 +1,43 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class XorIARM extends MachConcreteNode implements MachNode{ + final TypeInteger _ti; + XorIARM(Node xor, TypeInteger ti) {super(xor); _inputs.pop(); _ti = ti;} + + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { + assert i==1 || i==2; + + return arm.RMASK; } + + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } + + // Output is same register as input#1 + @Override public int twoAddress() { return 1; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: "rd = rs1 ^ imm" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ^ #"); + _ti.print(sb); + } + + @Override public String op() { return "eor"; } +}
\ No newline at end of file diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java new file mode 100644 index 00000000..fd207157 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java @@ -0,0 +1,385 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.LRG; +import com.seaofnodes.simple.codegen.Machine; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.*; + +public class arm extends Machine { + + // X86-64 V2. Includes e.g. SSE4.2 and POPCNT. + @Override public String name() { return "arm"; } + + // GPR(S) + static final int X0 = 0, X1 = 1, X2 = 2, X3 = 3, X4 = 4, X5 = 5, X6 = 6, X7 = 7; + static final int X8 = 8, X9 = 9, X10 = 10, X11 = 11, X12 = 12, X13 = 13, X14 = 14, X15 = 15; + static final int X16 = 16, X17 = 17, X18 = 18, X19 = 19, X20 = 20, X21 = 21, X22 = 22, X23 = 23; + static final int X24 = 24, X25 = 25, X26 = 26, X27 = 27, X28 = 28, X29 = 29, X30 = 30, RSP = 31; + + // Floating point registers + static final int D0 = 32, D1 = 33, D2 = 34, D3 = 35, D4 = 36, D5 = 37, D6 = 38, D7 = 39; + static final int D8 = 40, D9 = 41, D10 = 42, D11 = 43, D12 = 44, D13 = 45, D14 = 46, D15 = 47; + static final int D16 = 48, D17 = 49, D18 = 50, D19 = 51, D20 = 52, D21 = 53, D22 = 54, D23 = 55; + static final int D24 = 56, D25 = 57, D26 = 58, D27 = 59, D28 = 60, D29 = 61, D30 = 62, D31 = 63; + + static final int FLAGS = 64; + + static final String[] REGS = new String[] { + "X0", "X1", "X2", "X3", "X4", "X5", "X6", "X7", + "X8", "X9", "X10", "X11", "X12", "X13", "X14", "X15", + "X16", "X17", "X18", "X19", "X20", "X21", "X22", "X23", + "X24", "X25", "X26", "X27", "X28", "X29", "RPC", "RSP", + "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", + "D8", "D9", "D10", "D11", "D12", "D13", "D14", "D15", + "D16", "D17", "D18", "D19", "D20", "D21", "D22", "D23", + "D24", "D25", "D26", "D27", "D28", "D29", "D30", "D31", + "flags" + }; + @Override public String reg( int reg ) { + return reg < REGS.length ? REGS[reg] : "[rsp+"+(reg-REGS.length)*4+"]"; + } + + // 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, -1L); + + static final RegMask FLAGS_MASK = new RegMask(FLAGS); + // x30 (LR): Procedure link register, used to return from subroutines. + static final RegMask RPC_MASK = new RegMask(1L << X30); + + // Arguments masks + static final RegMask X0_MASK = new RegMask(X0); + static final RegMask X1_MASK = new RegMask(X1); + static final RegMask X2_MASK = new RegMask(X2); + static final RegMask X3_MASK = new RegMask(X3); + static final RegMask X4_MASK = new RegMask(X4); + static final RegMask X5_MASK = new RegMask(X5); + static final RegMask X6_MASK = new RegMask(X6); + static final RegMask X7_MASK = new RegMask(X7); + + // Arguments(float) masks + static final RegMask D0_MASK = new RegMask(D0); + static final RegMask D1_MASK = new RegMask(D1); + static final RegMask D2_MASK = new RegMask(D2); + static final RegMask D3_MASK = new RegMask(D3); + static final RegMask D4_MASK = new RegMask(D4); + static final RegMask D5_MASK = new RegMask(D5); + static final RegMask D6_MASK = new RegMask(D6); + static final RegMask D7_MASK = new RegMask(D7); + + // Calling convention; returns a machine-specific register + // for incoming argument idx. + // index 0 for control, 1 for memory, real args start at index 2 + static final RegMask[] CALLINMASK = new RegMask[] { + X0_MASK, + X1_MASK, + X2_MASK, + X3_MASK, + X4_MASK, + X5_MASK, + X6_MASK, + X7_MASK, + }; + static final RegMask[] XMMS = new RegMask[] { + D0_MASK, + D1_MASK, + D2_MASK, + D3_MASK, + D4_MASK, + D5_MASK, + D6_MASK, + D7_MASK, + }; + + 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]; + } + throw Utils.TODO(); // Pass on stack slot + } + + 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 RegMask CALLER_SAVE_MASK; + static { + long caller = ~CALLEE_SAVE; + caller &= ~(1L<<RSP); + CALLER_SAVE_MASK = new RegMask(caller,1L<<(FLAGS-64)); + } + static RegMask armCallerSave() { return CALLER_SAVE_MASK; } + @Override public RegMask callerSave() { return armCallerSave(); } + + static final RegMask CALLEE_SAVE_MASK = new RegMask(CALLEE_SAVE); + static RegMask armCalleeSave() { return CALLEE_SAVE_MASK; } + @Override public RegMask calleeSave() { return armCalleeSave(); } + + + // Return single int/ptr register. Used by CallEnd output and Return input. + static final RegMask[] RET_MASKS; + static { + int nSaves = CALLEE_SAVE_MASK.size(); + RET_MASKS = new RegMask[4 + nSaves]; + RET_MASKS[0] = null; + RET_MASKS[1] = null; // Memory + RET_MASKS[2] = null; // Varies, either XMM0 or RAX + RET_MASKS[3] = RMASK; // Expected R30 but could be any register + short reg = CALLEE_SAVE_MASK.firstReg(); + for( int i=0; i<nSaves; i++ ) { + RET_MASKS[i+4] = new RegMask(reg); + reg = CALLEE_SAVE_MASK.nextReg(reg); + } + } + static RegMask retMask( TypeFunPtr tfp, int i ) { + return i==2 + ? (tfp.ret() instanceof TypeFloat ? D0_MASK : X0_MASK) + : RET_MASKS[i]; + } + + // Create a split op; any register to any register, including stack slots + @Override public SplitNode split(String kind, byte round, LRG lrg) { return new SplitARM(kind,round); } + + // Return a MachNode unconditional branch + @Override public CFGNode jump() { + throw Utils.TODO(); + } + + // Break an infinite loop + @Override public IfNode never( CFGNode ctrl ) { + throw Utils.TODO(); + } + + // 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 CallNode call -> call(call); + case CastNode cast -> new CastNode(cast); + case CallEndNode cend -> new CallEndARM(cend); + case CProjNode c -> new CProjNode(c); + case ConstantNode con -> con(con); + case DivFNode divf -> new DivFARM(divf); + case DivNode div -> new DivARM(div); + case FunNode fun -> new FunARM(fun); + case IfNode iff -> jmp(iff); + case LoadNode ld -> ld(ld); + case MemMergeNode mem -> new MemMergeNode(mem); + case MulFNode mulf -> new MulFARM(mulf); + case MulNode mul -> mul(mul); + case NewNode nnn -> new NewARM(nnn); + case OrNode or -> or(or); + case ParmNode parm -> new ParmARM(parm); + case PhiNode phi -> new PhiNode(phi); + case ProjNode prj -> prj(prj); + case ReadOnlyNode read -> new ReadOnlyNode(read); + case ReturnNode ret -> new RetARM(ret,ret.fun()); + case SarNode sar -> sar(sar); + case ShlNode shl -> shl(shl); + case ShrNode shr -> shr(shr); + case StartNode start -> new StartNode(start); + case StopNode stop -> new StopNode(stop); + case StoreNode st -> st(st); + case SubFNode subf -> new SubFARM(subf); + case SubNode sub -> sub(sub); + case ToFloatNode tfn -> i2f8(tfn); + case XorNode xor -> xor(xor); + + case LoopNode loop -> new LoopNode(loop); + case RegionNode region-> new RegionNode(region); + default -> throw Utils.TODO(); + }; + } + + private Node mul(MulNode mul) { + Node rhs = mul.in(2); + if( rhs instanceof ConstantNode off && off._con instanceof TypeInteger toff ) { + return new MulIARM(mul, toff); + } + return new MulARM(mul); + } + private Node cmp(BoolNode bool){ + Node cmp = _cmp(bool); + return new SetARM(cmp, bool.op()); + } + + private Node i2f8(ToFloatNode tfn) { + assert tfn.in(1)._type instanceof TypeInteger ti; + return new I2F8ARM(tfn); + } + + private Node ld(LoadNode ld) { + return new LoadARM(address(ld), ld.ptr(), idx, off); + } + + private Node jmp(IfNode iff) { + // If/Bool combos will match to a Cmp/Set which sets flags. + // Most general arith ops will also set flags, which the Jmp needs directly. + // Loads do not set the flags, and will need an explicit TEST + if( !(iff.in(1) instanceof BoolNode) ) + iff.setDef(1,new BoolNode.EQ(iff.in(1),new ConstantNode(TypeInteger.ZERO))); + return new BranchARM(iff, ((BoolNode)iff.in(1)).op()); + } + + private Node _cmp(BoolNode bool) { + if( bool instanceof BoolNode.EQF || + bool instanceof BoolNode.LTF || + bool instanceof BoolNode.LEF ) + return new CmpFARM(bool); + + + return bool.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti + ? new CmpIARM(bool, ti) + : new CmpARM(bool); + } + + + private Node addf(AddFNode addf) { + return new AddFARM(addf); + } + + private Node add(AddNode add) { + Node rhs = add.in(2); + if( rhs instanceof ConstantNode off && off._con instanceof TypeInteger toff ) { + return new AddIARM(add, toff); + } + return new AddARM(add); + } + + private Node sub(SubNode sub) { + return sub.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti + ? new AddIARM(sub, TypeInteger.constant(-ti.value())) + : new SubARM(sub); + } + + private Node and(AndNode and) { + if( and.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti ) + return new AndIARM(and, ti); + return new AndARM(and); + } + + private Node or(OrNode or) { + if(or.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) + return new OrIARM(or, ti); + + return new OrARM(or); + } + + private Node xor(XorNode xor) { + if(xor.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) + return new XorIARM(xor, ti); + return new XorARM(xor); + } + + private Node con(ConstantNode con) { + 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 -> new ConstantNode(con); + }; + } + + private Node sar(SarNode sar){ + if( sar.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) + return new AsrIARM(sar, ti); + return new AsrARM(sar); + } + + private Node shl(ShlNode shl) { + if( shl.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) + return new LslIARM(shl, ti); + return new LslARM(shl); + + } + + private Node shr(ShrNode shr) { + if( shr.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) + return new LsrIARM(shr, ti); + return new LsrARM(shr); + } + + private Node call(CallNode call){ + if(call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp) + return new CallARM(call, tfp); + return new CallRRARM(call); + } + + private Node prj(ProjNode prj) { + return new ProjARM(prj); + } + + + private static int off; + private static Node idx; + private Node st(StoreNode st) { + int imm=0; + Node xval = st.val(); + // e.g store this s.cs[0] = 67; // C + if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti ) { + xval = null; + imm = (int)ti.value(); + assert imm == ti.value(); // In 32-bit range + } + return new StoreARM(address(st),st.ptr(),idx,off,imm,xval); + } + + // 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 ) { + off = (int)ti.value(); + assert off == ti.value(); // In 32-bit range + } else { + idx = mop.off(); + } + return mop; + } + + +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/i2f8.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/i2f8.java new file mode 100644 index 00000000..e0792669 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/i2f8.java @@ -0,0 +1,4 @@ +package com.seaofnodes.simple.node.cpus.arm; + +public class i2f8 { +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java new file mode 100644 index 00000000..2417aeb6 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java @@ -0,0 +1,30 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class AddFRISC extends MachConcreteNode implements MachNode{ + AddFRISC(Node addf) {super(addf);} + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return riscv.FMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // Default on double precision for now(64 bits) + // General form: "fadd.d rd = src1 + src2 + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2))); + } + + @Override public String op() { return "addf"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java new file mode 100644 index 00000000..e92a7b08 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java @@ -0,0 +1,11 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.node.Node; + +public class AddIRISC extends ImmRISC { + AddIRISC( Node add, int imm ) { super(add,imm); } + @Override int opcode() { throw Utils.TODO(); } + @Override public String glabel() { return "+"; } + @Override public String op() { return "addi"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java new file mode 100644 index 00000000..344c217d --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java @@ -0,0 +1,29 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class AddRISC extends MachConcreteNode implements MachNode { + AddRISC( Node add) {super(add); } + @Override public String op() { return "add"; } + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { return riscv.RMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return riscv.WMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form: "rd = rs1 + rs2" + @Override public void 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/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java new file mode 100644 index 00000000..ddbf5aa4 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java @@ -0,0 +1,11 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.node.Node; + +public class AndIRISC extends ImmRISC { + AndIRISC( Node and, int imm) { super(and,imm); } + @Override int opcode() { throw Utils.TODO(); } + @Override public String glabel() { return "&"; } + @Override public String op() { return "andi"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java new file mode 100644 index 00000000..ca00bc98 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java @@ -0,0 +1,38 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; +import java.util.BitSet; +import java.lang.StringBuilder; + +public class AndRISC extends MachConcreteNode implements MachNode{ + AndRISC(Node and) { + super(and); + } + + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { + assert i==1 || i==2; + return riscv.RMASK; + } + + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return riscv.WMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: #rd = rs1 & rs2 + @Override public void 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/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java new file mode 100644 index 00000000..6a1cf7dc --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java @@ -0,0 +1,47 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import java.io.ByteArrayOutputStream; +import java.util.BitSet; + +// Conditional branch such as: BEQ +public class BranchRISC extends IfNode implements MachNode { + final String _bop; + // label is obtained implicitly + BranchRISC( IfNode iff, String bop, Node n1, Node n2 ) { + super(iff); + _bop = bop; + _inputs.setX(1,n1); + _inputs.setX(2,n2); + } + + @Override public String label() { return op(); } + + @Override public RegMask regmap(int i) { return riscv.RMASK; } + @Override public RegMask outregmap() { return null; } + + @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { + in(1)._print0(sb.append("if( "),visited).append(_bop); + if( in(2)==null ) sb.append("0"); + else in(2)._print0(sb,visited); + return sb.append(" )"); + } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(in(1))).p(" ").p(_bop).p(" ").p(in(2)==null ? "#0" : code.reg(in(2))); + } + + @Override public String op() { return "b"+_bop; } + + @Override public String comment() { + return "L"+cproj(1)._nid+", L"+cproj(0)._nid; + } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallEndRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallEndRISC.java new file mode 100644 index 00000000..fd1ba7c3 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallEndRISC.java @@ -0,0 +1,35 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.CallEndNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.type.TypeFloat; +import com.seaofnodes.simple.type.TypeFunPtr; +import java.io.ByteArrayOutputStream; + +public class CallEndRISC extends CallEndNode implements MachNode { + final TypeFunPtr _tfp; + CallEndRISC( CallEndNode cend ) { + super(cend); + _tfp = (TypeFunPtr)(cend.call().fptr()._type); + } + + @Override public String label() { return op(); } + @Override public RegMask regmap(int i) { return null; } + @Override public RegMask outregmap() { return null; } + @Override public RegMask outregmap(int idx) { return idx == 2 ? riscv.retMask(_tfp,2) : null; } + @Override public RegMask killmap() { return riscv.riscCallerSave(); } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { } + + @Override public String op() { return "cend"; } + +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java new file mode 100644 index 00000000..54f4fba9 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java @@ -0,0 +1,44 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeFunPtr; +import java.io.ByteArrayOutputStream; + +public class CallRISC extends CallNode implements MachNode{ + final TypeFunPtr _tfp; + final String _name; + + CallRISC( 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 label() { return op(); } + @Override public RegMask regmap(int i) { + return riscv.callInMask(_tfp,i); // Normal argument + } + @Override public RegMask outregmap() { return null; } + + @Override public String name() { return _name; } + @Override public TypeFunPtr tfp() { return _tfp; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + 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))).p(" "); + sb.unchar(2); + } + + @Override public String op() { return "call"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java new file mode 100644 index 00000000..3bae656d --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java @@ -0,0 +1,35 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import java.io.ByteArrayOutputStream; + +public class CallRRISC extends CallNode implements MachNode{ + CallRRISC( CallNode call ) { super(call); } + + @Override public String label() { return op(); } + @Override public RegMask regmap(int i) { + // Todo: float or int? + return i==_inputs._len + ? riscv.RMASK // Function call target + : riscv.callInMask(tfp(),i); // Normal argument + } + @Override public RegMask outregmap() { return null; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(fptr())).p(" "); + for( int i=0; i<nargs(); i++ ) + sb.p(code.reg(arg(i))).p(" "); + sb.unchar(2); + } + + @Override public String op() { return "callr"; } + +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CastRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CastRISC.java new file mode 100644 index 00000000..a0d0df90 --- /dev/null +++ b/chapter20/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.RegMask; +import com.seaofnodes.simple.node.CastNode; +import com.seaofnodes.simple.node.MachNode; +import java.io.ByteArrayOutputStream; + +public class CastRISC extends CastNode implements MachNode { + CastRISC( CastNode cast ) { super(cast); } + @Override public RegMask regmap(int i) { assert i==1; return RegMask.FULL; } + @Override public RegMask outregmap() { return RegMask.FULL; } + @Override public int twoAddress( ) { return 1; } + @Override public int encoding(ByteArrayOutputStream bytes) { return 0; /*no encoding*/ } + @Override public void asm(CodeGen code, SB sb) { _t.print(sb.p(code.reg(in(1))).p(" isa ")); } + @Override public String op() { return label(); } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java new file mode 100644 index 00000000..d360bea6 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java @@ -0,0 +1,30 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import java.io.ByteArrayOutputStream; + +public class DivFRISC extends MachConcreteNode implements MachNode{ + DivFRISC(Node divf) {super(divf);} + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return riscv.FMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // Default on double precision for now(64 bits) + // General form: "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))); + } + + @Override public String op() { return "divf"; } + +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java new file mode 100644 index 00000000..ee49a883 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java @@ -0,0 +1,32 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class DivRISC extends MachConcreteNode implements MachNode{ + DivRISC(Node div) {super(div);} + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { + assert i==1 || i==2; + return riscv.RMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return riscv.WMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // Signed division + // General form: "rd = rs1 / rs" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2))); + } + + @Override public String op() { return "div"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java new file mode 100644 index 00000000..2832ac52 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java @@ -0,0 +1,31 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.ConstantNode; +import com.seaofnodes.simple.node.MachNode; +import java.io.ByteArrayOutputStream; + +public class FltRISC extends ConstantNode implements MachNode{ + FltRISC(ConstantNode con) { super(con); } + @Override public String op() { return "flw"; } + // Register mask allowed on input i. 0 for no register. + @Override public RegMask regmap(int i) { return null; } + // General int registers! Must-split into the floats + @Override public RegMask outregmap() { return riscv.RMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // Human-readable form appended to the SB. Things like the encoding, + // indentation, leading address or block labels not printed here. + // Just something like "ld4\tR17=[R18+12] // Load array base". + // General form: "op\tdst=src+src" + @Override public void asm(CodeGen code, SB sb) { + _con.print(sb.p(code.reg(this)).p(" #")); + } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java new file mode 100644 index 00000000..3b7d9a34 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java @@ -0,0 +1,21 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.FunNode; +import com.seaofnodes.simple.node.MachNode; +import java.io.ByteArrayOutputStream; + +public class FunRISC extends FunNode implements MachNode { + FunRISC(FunNode fun) {super(fun);} + // Register mask allowed on input i. 0 for no register. + @Override public RegMask regmap(int i) { return null; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return null; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java new file mode 100644 index 00000000..560d1b38 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java @@ -0,0 +1,30 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +// fcvt.d.w +public class I2F8RISC extends MachConcreteNode implements MachNode { + I2F8RISC(Node i2f8) {super(i2f8);} + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { assert i==1; return riscv.RMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return riscv.FMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form: "i2f8 (flt)int_value" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p("(flt)").p(code.reg(in(1))); + } + + @Override public String op() { return "i2f8"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java new file mode 100644 index 00000000..02191d05 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java @@ -0,0 +1,40 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.util.BitSet; +import java.io.ByteArrayOutputStream; + +public abstract class ImmRISC extends MachConcreteNode implements MachNode { + final int _imm; + ImmRISC( Node add, int imm ) { + super(add); + _inputs.pop(); + _imm = imm; + } + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { return riscv.RMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return riscv.WMASK; } + + @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { + in(1)._print0(sb.append("( "), visited); + return sb.append(String.format(" %s #%d )",glabel(),_imm)); + } + + abstract int opcode(); + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form: "addi rd = rs1 + imm" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ").p(glabel()).p(" #").p(_imm); + } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java new file mode 100644 index 00000000..e792b055 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java @@ -0,0 +1,35 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.ConstantNode; +import com.seaofnodes.simple.node.MachNode; +import java.io.ByteArrayOutputStream; + +// Integer constants +public class IntRISC extends ConstantNode implements MachNode { + IntRISC(ConstantNode con) { super(con); } + @Override public String op() { return "ldi"; } + // Register mask allowed on input i. 0 for no register. + @Override public RegMask regmap(int i) { return null; } + // General int registers + @Override public RegMask outregmap() { return riscv.WMASK; } + + @Override public boolean isClone() { return true; } + @Override public IntRISC copy() { return new IntRISC(this); } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // 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/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java new file mode 100644 index 00000000..3fe8f44e --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java @@ -0,0 +1,40 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + + +// Load memory addressing on RISC +// Support imm, reg(direct), or reg+off(indirect) addressing +// Base = base - base pointer, offset is added to base +// idx = null +// off = off - offset added to base + +public class LoadRISC extends MemOpRISC { + LoadRISC(LoadNode ld, Node base, Node idx, int off) { + super(ld, base, idx, off, 0); + } + + @Override public RegMask regmap(int i) { + return riscv.RMASK; + } + // Wide mask loads both ints and floats; encoding varies. + @Override public RegMask outregmap() { return riscv.MEM_MASK; } + + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(","); + asm_address(code,sb); + } + + @Override public String op() { return "ld" +_sz; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java new file mode 100644 index 00000000..bcd7924e --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java @@ -0,0 +1,57 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.*; +import java.io.ByteArrayOutputStream; +import java.lang.StringBuilder; +import java.util.BitSet; + + +public class MemOpRISC extends MemOpNode implements MachNode { + final int _off; // Limit 32 bits + final int _imm; // Limit 32 bits + final char _sz = (char)('0'+(1<<_declaredType.log_size())); + MemOpRISC( MemOpNode mop, Node base, Node idx, int off, int imm) { + super(mop,mop); + assert base._type instanceof TypeMemPtr && !(base instanceof AddNode); + assert ptr() == base; + _inputs.setX(3,idx); + _off = off; + _imm = imm; + } + // Store-based flavors have a value edge + MemOpRISC( MemOpNode mop, Node base, Node idx, int off, int imm, Node val ) { + this(mop,base,idx,off,imm); + _inputs.setX(4,val); + } + + 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) { + throw Utils.TODO(); + } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { throw Utils.TODO(); } + + @Override public int encoding(ByteArrayOutputStream bytes) { throw Utils.TODO(); } + + + SB asm_address(CodeGen code, SB sb) { + sb.p("[").p(code.reg(ptr())).p("+"); + if( idx() != null ) sb.p(code.reg(idx())); + else sb.p(_off); + return sb.p("]"); + } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java new file mode 100644 index 00000000..8a066ab1 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java @@ -0,0 +1,30 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import java.io.ByteArrayOutputStream; + +public class MulFRISC extends MachConcreteNode implements MachNode{ + MulFRISC(Node mulf) {super(mulf);} + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return riscv.FMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // Default on double precision for now(64 bits) + // General form: "fmul.d rd = src1 * src2 + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2))); + } + + @Override public String op() { return "mulf"; } + +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java new file mode 100644 index 00000000..0e2d35a3 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java @@ -0,0 +1,32 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + + +// mulh signed multiply instruction(no-imm form) +public class MulRISC extends MachConcreteNode implements MachNode{ + MulRISC(Node mul) {super(mul);} + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.RMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return riscv.WMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form: "rd = rs1 * rs2" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2))); + } + + @Override public String op() { return "mul"; } + +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java new file mode 100644 index 00000000..53ad0af2 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java @@ -0,0 +1,29 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import java.io.ByteArrayOutputStream; + +public class NewRISC extends NewNode implements MachNode { + // A pre-zeroed chunk of memory. + NewRISC( NewNode nnn ) { super(nnn); } + // Size and pointer result in standard calling convention; null for all the + // memory aliases edges + @Override public RegMask regmap(int i) { return i == 1 ? riscv.A0_MASK : null; } + @Override public RegMask outregmap(int i) { return i == 1 ? riscv.A0_MASK : null; } + @Override public RegMask outregmap() { return null; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form: "alloc #bytes" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(size())); + } + + @Override public String op() { return "alloc"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java new file mode 100644 index 00000000..f41d7162 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java @@ -0,0 +1,11 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.node.Node; + +public class OrIRISC extends ImmRISC { + OrIRISC( Node and, int imm) { super(and,imm); } + @Override int opcode() { throw Utils.TODO(); } + @Override public String glabel() { return "|"; } + @Override public String op() { return "ori"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java new file mode 100644 index 00000000..7f808f90 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java @@ -0,0 +1,39 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.Node; +import java.io.ByteArrayOutputStream; + +public class OrRISC extends MachConcreteNode implements MachNode { + OrRISC(Node or) { + super(or); + } + + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { + assert i==1 || i==2; + return riscv.RMASK; + } + + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return riscv.WMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: # rd = rs1 | rs2 + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" | ").p(code.reg(in(2))); + } + + @Override public String op() { return "or"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java new file mode 100644 index 00000000..75674f37 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java @@ -0,0 +1,26 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.ParmNode; +import java.io.ByteArrayOutputStream; + +public class ParmRISC extends ParmNode implements MachNode { + final RegMask _rmask; + ParmRISC(ParmNode parm) { + super(parm); + // Assume int + _rmask = riscv.callInMask(fun().sig(),_idx); + } + + // Register mask allowed on input i. 0 for no register. + @Override public RegMask regmap(int i) { return _rmask; } + // Register mask allowed as a result. Calling convention register + @Override public RegMask outregmap() { return _rmask; } + + // Encoding is appended into the byte array. Returns size + @Override public int encoding(ByteArrayOutputStream bytes) { return 0; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java new file mode 100644 index 00000000..42d5dc9b --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java @@ -0,0 +1,25 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.ProjNode; +import java.io.ByteArrayOutputStream; + +public class ProjRISC extends ProjNode implements MachNode { + ProjRISC(ProjNode p) { super(p); } + + // Register mask allowed on input i. 0 for no register. + @Override public RegMask regmap(int i) { return null; } + // Register mask allowed as a result. + @Override public RegMask outregmap() { + return ((MachNode)in(0)).outregmap(_idx); + } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java new file mode 100644 index 00000000..c9fa7fac --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java @@ -0,0 +1,48 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.FunNode; +import com.seaofnodes.simple.node.ReturnNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.type.TypeFloat; + +import java.io.ByteArrayOutputStream; + +public class RetRISC extends ReturnNode implements MachNode{ + RetRISC(ReturnNode ret, FunNode fun) { super(ret, fun); fun.setRet(this); } + + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { return riscv.retMask(_fun.sig(),i); } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return null; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { + // Post code-gen, just print the "ret" + if( code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() ) + // Prints return reg (either A0 or FA0), RPC (always R1) and then + // the callee-save registers. + for( int i=2; i<nIns(); i++ ) + sb.p(code.reg(in(i))).p(" "); + + // Post-allocation, if we did not get the expected return register, print what we got + else if( code._regAlloc.regnum(in(3)) != riscv.RPC ) + sb.p("[").p(code.reg(in(3))).p("]"); + } + + // Correct Nodes outside the normal edges + @Override public void postSelect() { + FunNode fun = (FunNode)rpc().in(0); + _fun = fun; + fun.setRet(this); + } + + @Override public String op() { return "ret"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java new file mode 100644 index 00000000..3f9fc192 --- /dev/null +++ b/chapter20/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.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.BoolNode; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; +import java.io.ByteArrayOutputStream; + +// corresponds to slt,sltu,slti,sltiu, seqz +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 RegMask regmap(int i) { return riscv.FMASK; } + @Override public RegMask outregmap() { return riscv.WMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(in(1))).p(" ").p(_bop).p(" ").p(code.reg(in(2))); + } + + @Override public String op() { return "set"+_bop; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java new file mode 100644 index 00000000..3c0f76ae --- /dev/null +++ b/chapter20/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.BoolNode; + +// corresponds to slt,sltu,slti,sltiu, seqz +public class SetIRISC extends ImmRISC { + final String _bop; // One of <,<=,== + SetIRISC( BoolNode bool, int imm ) { + super(bool,imm); + assert !bool.isFloat(); + _bop = bool.op(); + } + @Override int opcode() { throw Utils.TODO(); } + @Override public String glabel() { return _bop; } + @Override public String op() { return "set"+_bop; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java new file mode 100644 index 00000000..fb7650c7 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java @@ -0,0 +1,33 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.BoolNode; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; +import java.io.ByteArrayOutputStream; + +// corresponds to slt,sltu,slti,sltiu, seqz +public class SetRISC extends MachConcreteNode implements MachNode { + final String _bop; // One of <,<=,== + SetRISC( BoolNode bool ) { + super(bool); + assert !bool.isFloat(); + _bop = bool.op(); + } + @Override public RegMask regmap(int i) { throw Utils.TODO(); } + @Override public RegMask outregmap() { return riscv.WMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(in(1))).p(" ").p(_bop).p(" ").p(code.reg(in(2))); + } + + @Override public String op() { return "set"+_bop; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java new file mode 100644 index 00000000..b5202908 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java @@ -0,0 +1,11 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.node.Node; + +public class SllIRISC extends ImmRISC { + SllIRISC( Node and, int imm) { super(and,imm); } + @Override int opcode() { throw Utils.TODO(); } + @Override public String glabel() { return "<<"; } + @Override public String op() { return "slli"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java new file mode 100644 index 00000000..447f604a --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java @@ -0,0 +1,37 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; +import java.util.BitSet; + +public class SllRISC extends MachConcreteNode implements MachNode{ + SllRISC(Node sll) {super(sll);} + + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { + //assert i==1; + return riscv.RMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return riscv.WMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: "sll rd, rs1, rs2" + @Override public void asm(CodeGen code, SB sb) { + + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" << ").p(code.reg(in(2))); + + } + + @Override public String op() { return "sll"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java new file mode 100644 index 00000000..b5076ba0 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java @@ -0,0 +1,21 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.SplitNode; +import java.io.ByteArrayOutputStream; + +public class SplitRISC extends SplitNode { + SplitRISC( String kind, byte round ) { super(kind,round, new Node[2]); } + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { return riscv.SPLIT_MASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return riscv.SPLIT_MASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java new file mode 100644 index 00000000..d5ab5e08 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java @@ -0,0 +1,11 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.node.Node; + +public class SraIRISC extends ImmRISC { + SraIRISC( Node and, int imm) { super(and,imm); } + @Override int opcode() { throw Utils.TODO(); } + @Override public String glabel() { return ">>"; } + @Override public String op() { return "srai"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java new file mode 100644 index 00000000..0726e292 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java @@ -0,0 +1,38 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +// Right Shift Arithmetic +public class SraRISC extends MachConcreteNode implements MachNode { + + SraRISC(Node sra) {super(sra);} + + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { + // assert i==1; + return riscv.RMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return riscv.WMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: "sra rd, rs1, rs2" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> ").p(code.reg(in(2))); + } + + @Override public String op() { return "sra"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java new file mode 100644 index 00000000..a4eb99bc --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java @@ -0,0 +1,11 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.node.Node; + +public class SrlIRISC extends ImmRISC { + SrlIRISC( Node and, int imm) { super(and,imm); } + @Override int opcode() { throw Utils.TODO(); } + @Override public String glabel() { return ">>>"; } + @Override public String op() { return "srli"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java new file mode 100644 index 00000000..d1435540 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java @@ -0,0 +1,38 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; +import java.util.BitSet; +import java.lang.StringBuilder; + +// Right Shift Logical +public class SrlRISC extends MachConcreteNode implements MachNode { + SrlRISC(Node srli) {super(srli);} + + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { + // assert i==1; + return riscv.RMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return riscv.WMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: "srl rd, rs1, rs2" + @Override public void asm(CodeGen code, SB sb) { + + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >>> ").p(code.reg(in(2))); + } + + @Override public String op() { return "srl"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java new file mode 100644 index 00000000..c57cf11d --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java @@ -0,0 +1,56 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.StoreNode; +import java.io.ByteArrayOutputStream; +import java.util.BitSet; + +// 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, Node idx, int off, int imm, Node val ) { + super(st, base, idx, off, imm, val); + } + + // Wider mask to store both GPRs and FPRs + @Override public RegMask regmap(int i) { + if( i==1 ) return riscv.MEM_MASK; + if( i==2 ) return riscv.RMASK; + if( i==3 ) return riscv.RMASK; + if( i==4 ) return riscv.RMASK; + throw Utils.TODO(); + } + + + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return null; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { + asm_address(code,sb).p(","); + if( val()==null ) sb.p("#").p(_imm); + else sb.p(code.reg(val())); + } + + @Override public String op() { return "st"+_sz; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java new file mode 100644 index 00000000..e6fe3012 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java @@ -0,0 +1,30 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class SubFRISC extends MachConcreteNode implements MachNode{ + SubFRISC(Node subf) {super(subf);} + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return riscv.FMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // Default on double precision for now(64 bits) + // General form: "fsub.d rd = src1 - src2 + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2))); + } + + @Override public String op() { return "subf"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java new file mode 100644 index 00000000..c37aea98 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java @@ -0,0 +1,29 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class SubRISC extends MachConcreteNode implements MachNode { + SubRISC( Node sub ) { super(sub); } + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.RMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return riscv.WMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form: "sub # rd = rs1 - rs2" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - ").p(code.reg(in(2))); + } + + @Override public String op() { return "sub"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java new file mode 100644 index 00000000..23fb6a74 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java @@ -0,0 +1,39 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.ConstantNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFunPtr; +import java.io.ByteArrayOutputStream; + +public class TFPRISC extends ConstantNode implements MachNode{ + TFPRISC(ConstantNode con) {super(con);} + @Override public String op() { return _con == Type.NIL ? "xor" : "ldx"; } + // Register mask allowed on input i. 0 for no register. + @Override public RegMask regmap(int i) { return null; } + // General int registers + @Override public RegMask outregmap() { return riscv.WMASK; } + + @Override public boolean isClone() { return true; } + @Override public TFPRISC copy() { return new TFPRISC(this); } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // Human-readable form appended to the SB. Things like the encoding, + // indentation, leading address or block labels not printed here. + // Just something like "ld4\tR17=[R18+12] // Load array base". + // General form: "op\tdst=src+src" + @Override public void asm(CodeGen code, SB sb) { + String reg = code.reg(this); + if( _con == Type.NIL ) + sb.p(reg).p(",").p(reg); + else + _con.print(sb.p(reg).p(" #")); + } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java new file mode 100644 index 00000000..a2e32f06 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java @@ -0,0 +1,11 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.node.Node; + +public class XorIRISC extends ImmRISC { + XorIRISC( Node and, int imm) { super(and,imm); } + @Override int opcode() { throw Utils.TODO(); } + @Override public String glabel() { return "^"; } + @Override public String op() { return "xori"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java new file mode 100644 index 00000000..f4ecb430 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java @@ -0,0 +1,41 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class XorRISC extends MachConcreteNode implements MachNode { + + XorRISC(Node or) { + super(or); + } + + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { + assert i==1 || i==2; + return riscv.RMASK; + } + + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return riscv.WMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: # # rd = rs1 ^ rs2 + @Override public void 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/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java new file mode 100644 index 00000000..83706cbf --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java @@ -0,0 +1,361 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.LRG; +import com.seaofnodes.simple.codegen.Machine; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.*; + +public class riscv extends Machine { + @Override public String name() {return "riscv";} + + // Using ABI names instead of register names + static final int RPC= 1, SP = 2, GP = 3, TP = 4, T0 = 5, T1 = 6, T2 = 7; + static final int S0 = 8, S1 = 9, A0 = 10, A1 = 11, A2 = 12, A3 = 13, A4 = 14, A5 = 15; + static final int A6 = 16, A7 = 17, S2 = 18, S3 = 19, S4 = 20, S5 = 21, S6 = 22, S7 = 23; + static final int S8 = 24, S9 = 25, S10 = 26, S11 = 27, T3 = 28, T4 = 29, T5 = 30, T6 = 31; + + // FP registers + static final int F0 = 32, F1 = 33, F2 = 34, F3 = 35, F4 = 36, F5 = 37, F6 = 38, F7 = 39; + static final int FS0 = 40, FS1 = 41, FA0 = 42, FA1 = 43, FA2 = 44, FA3 = 45, FA4 = 46, FA5 = 47; + static final int FA6 = 48, FA7 = 49, FS2 = 50, FS3 = 51, FS4 = 52, FS5 = 53, FS6 = 54, FS7 = 55; + static final int FS8 = 56, FS9 = 57, FS10 = 58, FS11 = 59, FT8 = 60, FT9 = 61, FT10 = 62, FT11 = 63; + + static final int MAX_REG = 61; + + static final String[] REGS = new String[] { + "zero", "r1" , "rsp" , "r3" , "r4" , "r5" , "r6" , "r7" , + "r8" , "r9" , "r10" , "r11" , "r12" , "r13" , "r14" , "r15" , + "r16" , "r17" , "r18" , "r19" , "r20" , "r21" , "r22" , "r23" , + "r24" , "r25" , "r26" , "r27" , "r28" , "r29" , "r30" , "r31" , + "f0" , "f1" , "f2" , "f3" , "f4" , "f5" , "f6" , "f7" , + "fs0" , "fs1" , "fa0" , "fa1" , "fa2" , "fa3" , "fa4" , "fa5" , + "fa6" , "fa7" , "fs2" , "fs3" , "fs4" , "fs5" , "fs6" , "fs7" , + "fs8" , "fs9" , "fs10", "fs11", "ft8" , "ft9" , "ft10", "ft11" + }; + @Override public String reg( int reg ) { + return reg < REGS.length ? REGS[reg] : "[rsp+"+(reg-REGS.length)*4+"]"; + } + + + // General purpose register mask: pointers and ints, not floats + static final long RD_BITS = 0b11111111111111111111111111111111L; // All the GPRs + static final RegMask RMASK = new RegMask(RD_BITS); + + static final long WR_BITS = 0b11111111111111111111111111111010L; // All the GPRs, minus ZERO and SP + static final RegMask WMASK = new RegMask(WR_BITS); + // Float mask from(f0-ft10). TODO: ft10,ft11 needs a larger RegMask + static final long FP_BITS = 0b11111111111111111111111111111111L<<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 + static final RegMask A0_MASK = new RegMask(A0); + static final RegMask A1_MASK = new RegMask(A1); + static final RegMask A2_MASK = new RegMask(A2); + static final RegMask A3_MASK = new RegMask(A3); + static final RegMask A4_MASK = new RegMask(A4); + static final RegMask A5_MASK = new RegMask(A5); + static final RegMask A6_MASK = new RegMask(A6); + static final RegMask A7_MASK = new RegMask(A7); + + static final RegMask RPC_MASK = new RegMask(RPC); + + // Float arguments masks + static final RegMask FA0_MASK = new RegMask(FA0); + static final RegMask FA1_MASK = new RegMask(FA1); + static final RegMask FA2_MASK = new RegMask(FA2); + static final RegMask FA3_MASK = new RegMask(FA3); + static final RegMask FA4_MASK = new RegMask(FA4); + static final RegMask FA5_MASK = new RegMask(FA5); + static final RegMask FA6_MASK = new RegMask(FA6); + static final 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 + }; + + 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]; + } + throw Utils.TODO(); // Pass on stack slot + } + + // callee saved(riscv) + static final long CALLEE_SAVE = + (1L<< S0) | (1L<< S1) | (1L<< S2 ) | (1L<< S3 ) | + (1L<< S4) | (1L<< S5) | (1L<< S6 ) | (1L<< S7 ) | + (1L<< S8) | (1L<< S9) | (1L<< S10) | (1L<< S11) | + (1L<<FS0) | (1L<<FS1) | (1L<<FS2 ) | (1L<<FS3 ) | + (1L<<FS4) | (1L<<FS5) | (1L<<FS6 ) | (1L<<FS7 ) | + (1L<<FS8) | (1L<<FS9) | (1L<<FS10) | (1L<<FS11); + + static final RegMask CALLER_SAVE_MASK; + static { + long caller = ~CALLEE_SAVE; + caller &= ~(1L<<SP); + CALLER_SAVE_MASK = new RegMask(caller,0); + } + static RegMask riscCallerSave() { return CALLER_SAVE_MASK; } + @Override public RegMask callerSave() { return riscCallerSave(); } + + static final RegMask CALLEE_SAVE_MASK = new RegMask(CALLEE_SAVE); + static RegMask riscCalleeSave() { return CALLEE_SAVE_MASK; } + @Override public RegMask calleeSave() { return riscCalleeSave(); } + + // Return single int/ptr register. Used by CallEnd output and Return input. + static final RegMask[] RET_MASKS; + static { + int nSaves = CALLEE_SAVE_MASK.size(); + RET_MASKS = new RegMask[4 + nSaves]; + RET_MASKS[0] = null; + RET_MASKS[1] = null; // Memory + RET_MASKS[2] = null; // Varies, either XMM0 or RAX + RET_MASKS[3] = RMASK; // Expected R1 but could be any register really + short reg = CALLEE_SAVE_MASK.firstReg(); + for( int i=0; i<nSaves; i++ ) { + RET_MASKS[i+4] = new RegMask(reg); + reg = CALLEE_SAVE_MASK.nextReg(reg); + } + } + static RegMask retMask( TypeFunPtr tfp, int i ) { + return i==2 + ? (tfp.ret() instanceof TypeFloat ? FA0_MASK : A0_MASK) + : RET_MASKS[i]; + } + + + // Return a MachNode unconditional branch + @Override public CFGNode jump() { + throw Utils.TODO(); + } + + // 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); } + + // Break an infinite loop + @Override public IfNode never( CFGNode ctrl ) { + throw Utils.TODO(); + } + + // True if signed 12-bit immediate + private static boolean imm12(TypeInteger ti) { + // 64-12==52 + return ti.isConstant() && ((ti.value()<<52)>>52) == ti.value(); + } + + + @Override public Node instSelect( Node n ) { + return switch (n) { + case AddFNode addf -> addf(addf); + case AddNode add -> add(add); + case AndNode and -> and(and); + case BoolNode bool -> cmp(bool); + case CallNode call -> call(call); + case CastNode cast -> new CastRISC(cast); + case CallEndNode cend -> new CallEndRISC(cend); + case CProjNode c -> new CProjNode(c); + case ConstantNode con -> con(con); + case DivFNode divf -> new DivFRISC(divf); + case DivNode div -> new DivRISC(div); + case FunNode fun -> new FunRISC(fun); + case IfNode iff -> jmp(iff); + case LoadNode ld -> ld(ld); + case MemMergeNode mem -> new MemMergeNode(mem); + case MulFNode mulf -> new MulFRISC(mulf); + case MulNode mul -> new MulRISC(mul); + case NewNode nnn -> new NewRISC(nnn); + case OrNode or -> or(or); + case ParmNode parm -> new ParmRISC(parm); + case PhiNode phi -> new PhiNode(phi); + case ProjNode prj -> prj(prj); + case ReadOnlyNode read -> new ReadOnlyNode(read); + case ReturnNode ret -> new RetRISC(ret, ret.fun()); + case SarNode sar -> sra(sar); + case ShlNode shl -> sll(shl); + case ShrNode shr -> srl(shr); + case StartNode start -> new StartNode(start); + case StopNode stop -> new StopNode(stop); + case StoreNode st -> st(st); + case SubFNode subf -> new SubFRISC(subf); + case SubNode sub -> sub(sub); + case ToFloatNode tfn -> i2f8(tfn); + case XorNode xor -> xor(xor); + + case LoopNode loop -> new LoopNode(loop); + case RegionNode region -> new RegionNode(region); + default -> throw Utils.TODO(); + }; + } + + private Node addf(AddFNode addf) { + return new AddFRISC(addf); + } + + private Node add(AddNode add) { + if( add.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && imm12(ti) ) + return new AddIRISC(add, (int)ti.value()); + return new AddRISC(add); + } + + private Node and(AndNode and) { + if( and.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) ) + return new AndIRISC(and, (int)ti.value()); + return new AndRISC(and); + } + + private Node call(CallNode call) { + if( call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp ) + return new CallRISC(call, tfp); + return new CallRRISC(call); + } + + private Node cmp(BoolNode bool) { + if( bool.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger toff ) + return new SetIRISC(bool, (int)toff.value()); + // Float variant + if( bool.isFloat() ) + return new SetFRISC(bool); + return new SetRISC(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 IntRISC(con); + 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() ) + return new BranchRISC(iff, bool.op(), bool.in(1), bool.in(2)); + // Vs zero + return new BranchRISC(iff, "==", iff.in(1), null); + } + + private Node or(OrNode or) { + if( or.in(2) instanceof ConstantNode con && con._con instanceof 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()); + 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())) + : 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(address(ld),ld.ptr(),idx,off); + } + + private Node st(StoreNode st) { + int imm=0; + Node xval = st.val(); + if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) ) { + xval = null; + imm = (int)ti.value(); + assert imm == ti.value(); // In 32-bit range + } + return new StoreRISC(address(st),st.ptr(),idx,off,imm,xval); + } + + // Gather addressing mode bits prior to constructing. This is a builder + // pattern, but saving the bits in a *local* *global* here to keep mess + // contained. + private static int off; + private static Node idx; + private <N 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 && imm12(ti) ) { + off = (int)ti.value(); + } else { + idx = mop.off(); + } + return mop; + } + +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java index 4f75edcd..ffc6966e 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java @@ -1,9 +1,9 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.CodeGen; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.RegMask; import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; public class AddFMemX86 extends MemOpX86 { @@ -12,10 +12,10 @@ public class AddFMemX86 extends MemOpX86 { } @Override public RegMask regmap(int i) { - if( i==1 ) return RegMask.EMPTY; // 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 + 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(); } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java index a3f5c7b3..d4ea9162 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; @@ -14,6 +16,8 @@ public class AddFX86 extends MachConcreteNode implements MachNode { @Override public RegMask outregmap() { return x86_64_v2.XMASK; } // Output is same register as input#1 @Override public int twoAddress() { return 1; } + // Ok to swap arguments + @Override public boolean commutes() { return true; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java index 6a332ff5..c7f92ff1 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java index 7d45d757..3d4fac41 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java @@ -1,8 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.CodeGen; -import com.seaofnodes.simple.RegMask; import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; public class AddMemX86 extends MemOpX86 { @@ -14,7 +14,8 @@ public class AddMemX86 extends MemOpX86 { // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - + // Output is same register as val() + @Override public int twoAddress() { return 4; } // General form: "add dst = src + [base + idx<<2 + 12]" @Override public void asm(CodeGen code, SB sb) { diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java index 61aa5a9e..9553d499 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java @@ -1,11 +1,13 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; -public class AddX86 extends MachConcreteNode implements MachNode { +public class AddX86 extends MachConcreteNode { AddX86( Node add ) { super(add); } // Register mask allowed on input i. @@ -14,6 +16,8 @@ public class AddX86 extends MachConcreteNode implements MachNode { @Override public RegMask outregmap() { return x86_64_v2.WMASK; } // Output is same register as input#1 @Override public int twoAddress() { return 1; } + // Ok to swap arguments + @Override public boolean commutes() { return true; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java index f2ee1c66..a7f1801e 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java new file mode 100644 index 00000000..d7fb3613 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java @@ -0,0 +1,34 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import java.io.ByteArrayOutputStream; + +public class AndX86 extends MachConcreteNode implements MachNode{ + AndX86(Node and) { + super(and); + } + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.WMASK; } + + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + + // Output is same register as input#1 + @Override public int twoAddress() { return 1; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: "and dst & src" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" & ").p(code.reg(in(2)));; + } + + @Override public String op() { return "and"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallEndX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallEndX86.java new file mode 100644 index 00000000..b9cc5659 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallEndX86.java @@ -0,0 +1,35 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.CallEndNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.type.TypeFloat; +import com.seaofnodes.simple.type.TypeFunPtr; +import java.io.ByteArrayOutputStream; + +public class CallEndX86 extends CallEndNode implements MachNode { + final TypeFunPtr _tfp; + CallEndX86( CallEndNode cend ) { + super(cend); + _tfp = (TypeFunPtr)(cend.call().fptr()._type); + } + + @Override public String label() { return op(); } + @Override public RegMask regmap(int i) { return null; } + @Override public RegMask outregmap() { return null; } + @Override public RegMask outregmap(int idx) { return idx == 2 ? x86_64_v2.retMask(_tfp,2) : null; } + @Override public RegMask killmap() { return x86_64_v2.x86CallerSave(); } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { } + + @Override public String op() { return "cend"; } + +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java index 31d303ed..ec6f9993 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java @@ -1,9 +1,11 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFunPtr; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.CallNode; +import com.seaofnodes.simple.node.MachNode; import java.io.ByteArrayOutputStream; public class CallRX86 extends CallNode implements MachNode { @@ -13,9 +15,9 @@ public class CallRX86 extends CallNode implements MachNode { @Override public RegMask regmap(int i) { return i==_inputs._len ? x86_64_v2.WMASK // Function call target - : x86_64_v2.callInMask(i); // Normal argument + : x86_64_v2.callInMask(tfp(),i); // Normal argument } - @Override public RegMask outregmap() { return x86_64_v2.RET_MASK; } + @Override public RegMask outregmap() { return null; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { @@ -23,9 +25,11 @@ public class CallRX86 extends CallNode implements MachNode { } @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(fptr())); + sb.p(code.reg(fptr())).p(" "); + for( int i=0; i<nargs(); i++ ) + sb.p(code.reg(arg(i))).p(" "); + sb.unchar(2); } @Override public String op() { return "callr"; } - } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java index 9c246c24..959ec81e 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java @@ -1,11 +1,13 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.CallNode; +import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.type.TypeFunPtr; import java.io.ByteArrayOutputStream; -import java.util.BitSet; public class CallX86 extends CallNode implements MachNode { final TypeFunPtr _tfp; @@ -20,11 +22,13 @@ public class CallX86 extends CallNode implements MachNode { @Override public String label() { return op(); } @Override public RegMask regmap(int i) { - return x86_64_v2.callInMask(i); // Normal argument + return x86_64_v2.callInMask(_tfp,i); // Normal argument } - @Override public RegMask outregmap() { return x86_64_v2.RET_MASK; } + @Override public RegMask outregmap() { return null; } + @Override public int nargs() { return nIns()-2; } // Minus control, memory, fptr @Override public String name() { return _name; } + @Override public TypeFunPtr tfp() { return _tfp; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { @@ -32,7 +36,10 @@ public class CallX86 extends CallNode implements MachNode { } @Override public void asm(CodeGen code, SB sb) { - sb.p(_name); + sb.p(_name).p(" "); + for( int i=0; i<nargs(); i++ ) + sb.p(code.reg(in(i+2))).p(" "); + sb.unchar(2); } @Override public String op() { return "call"; } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CastX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CastX86.java new file mode 100644 index 00000000..b620b89c --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CastX86.java @@ -0,0 +1,18 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.CastNode; +import com.seaofnodes.simple.node.MachNode; +import java.io.ByteArrayOutputStream; + +public class CastX86 extends CastNode implements MachNode { + CastX86( CastNode cast ) { super(cast); } + @Override public RegMask regmap(int i) { assert i==1; return RegMask.FULL; } + @Override public RegMask outregmap() { return RegMask.FULL; } + @Override public int twoAddress( ) { return 1; } + @Override public int encoding(ByteArrayOutputStream bytes) { return 0; /*no encoding*/ } + @Override public void asm(CodeGen code, SB sb) { _t.print(sb.p(code.reg(in(1))).p(" isa ")); } + @Override public String op() { return label(); } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java new file mode 100644 index 00000000..e9b0dacd --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java @@ -0,0 +1,29 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class CmpFX86 extends MachConcreteNode implements MachNode { + CmpFX86( Node cmp ) { super(cmp); } + + @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; } + @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // 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))); + } + + @Override public String op() { return "cmpf"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java index 2fa7fbb1..fcba397d 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; @@ -17,14 +19,22 @@ public class CmpIX86 extends MachConcreteNode implements MachNode { _imm = (int)ti.value(); assert _imm == ti.value(); } - CmpIX86( Node cmp, double ignore ) { + // Copy constructor does not set reverse edges in, because the basic block + // is changing from the original node - and it must be properly placed in a + // new block. + CmpIX86( CmpIX86 cmp ) { super(cmp); - _bop = "=="; - _imm = 0; + _bop = cmp._bop; + _imm = cmp._imm; + // While in(0) will be handled by the caller, all other edges must be + // handled in the constructor. + cmp.in(1)._outputs.push(this); } @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.RMASK; } @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; } + @Override public boolean isClone() { return true; } + @Override public Node copy() { return new CmpIX86(this); } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { @@ -33,7 +43,7 @@ public class CmpIX86 extends MachConcreteNode implements MachNode { @Override public void asm(CodeGen code, SB sb) { String dst = code.reg(this); - if( dst!="FLAGS" ) sb.p(dst).p(" = "); + if( dst!="flags" ) sb.p(dst).p(" = "); sb.p(code.reg(in(1))); if( _imm != 0 ) sb.p(", #").p(_imm); } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java index 873ca9df..f9ace702 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java @@ -1,8 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.CodeGen; -import com.seaofnodes.simple.RegMask; import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; public class CmpMemX86 extends MemOpX86 { diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java index 6b502249..41ce33d2 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java index d73bfd5b..f74b7348 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java index 1dc20017..54372629 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; @@ -10,12 +12,13 @@ public class DivX86 extends MachConcreteNode implements MachNode { // Register mask allowed on input i. @Override public RegMask regmap(int i) { - assert i==1 || i==2; - return x86_64_v2.RMASK; } + return (i==1) ? x86_64_v2.RAX_MASK : x86_64_v2.RMASK; + } // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - // Output is same register as input#1 - @Override public int twoAddress() { return 1; } + @Override public RegMask outregmap() { return x86_64_v2.RAX_MASK; } + + // DIV encodes a CQO which sign extends RAX into RDX, killing RDX. + @Override public RegMask killmap() { return x86_64_v2.RDX_MASK; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { @@ -28,4 +31,5 @@ public class DivX86 extends MachConcreteNode implements MachNode { } @Override public String op() { return "div"; } + @Override public String comment() { return "kill rdx"; } } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java index 21529f7b..47f7149d 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java @@ -1,21 +1,23 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.CodeGen; -import com.seaofnodes.simple.RegMask; import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.ConstantNode; import com.seaofnodes.simple.node.MachNode; - import java.io.ByteArrayOutputStream; public class FltX86 extends ConstantNode implements MachNode { FltX86(ConstantNode con ) { super(con); } + @Override public String op() { return "fld"; } // Register mask allowed on input i. 0 for no register. - @Override public RegMask regmap(int i) { return RegMask.EMPTY; } + @Override public RegMask regmap(int i) { return null; } // General int registers @Override public RegMask outregmap() { return x86_64_v2.XMASK; } + @Override public boolean isClone() { return true; } + @Override public FltX86 copy() { return new FltX86(this); } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { @@ -30,7 +32,4 @@ public class FltX86 extends ConstantNode implements MachNode { _con.print(sb.p(code.reg(this)).p(" #")); } - @Override public String op() { - return "fld"; // Some fancier encoding - } } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java index f969fae4..5239670b 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java @@ -1,6 +1,7 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.RegMask; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.node.FunNode; import com.seaofnodes.simple.node.MachNode; @@ -10,9 +11,9 @@ public class FunX86 extends FunNode implements MachNode { FunX86( FunNode fun ) { super(fun); } // Register mask allowed on input i. 0 for no register. - @Override public RegMask regmap(int i) { return RegMask.EMPTY; } + @Override public RegMask regmap(int i) { return null; } // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return RegMask.EMPTY; } + @Override public RegMask outregmap() { return null; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java index 983f817d..d99c92a8 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java index 2cd27c14..fcd39e3f 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java @@ -1,8 +1,11 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.ConstantNode; import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; @@ -10,16 +13,25 @@ import java.io.ByteArrayOutputStream; // Integer constants public class IntX86 extends ConstantNode implements MachNode { + IntX86( ConstantNode con ) { super(con); } - IntX86( ConstantNode con ) { - super(con); + @Override public String op() { + return _con == Type.NIL || _con == TypeInteger.ZERO ? "xor" : "ldi"; } // Register mask allowed on input i. 0 for no register. - @Override public RegMask regmap(int i) { return RegMask.EMPTY; } + @Override public RegMask regmap(int i) { return null; } // General int registers @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + // Zero-set uses XOR kills flags + @Override public RegMask killmap() { + return _con == Type.NIL || _con == TypeInteger.ZERO ? x86_64_v2.FLAGS_MASK : null; + } + + @Override public boolean isClone() { return true; } + @Override public Node copy() { return new IntX86(this); } + // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { throw Utils.TODO(); @@ -34,12 +46,6 @@ public class IntX86 extends ConstantNode implements MachNode { if( _con == Type.NIL || _con == TypeInteger.ZERO ) sb.p(reg).p(",").p(reg); else - _con.print(sb.p(reg).p(" #")); - } - - @Override public String op() { - if( _con == Type.NIL || _con == TypeInteger.ZERO ) - return "xor"; - return "ldi"; // Some fancier encoding + _con.print(sb.p(reg).p(" = #")); } } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java index 7cf4444c..173f9ee7 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; @@ -20,13 +22,13 @@ public class JmpX86 extends IfNode implements MachNode { Node set = in(1); Node cmp = set.in(1); // Bypass an expected Set and just reference the cmp directly - if( set instanceof SetX86 && (cmp instanceof CmpX86 || cmp instanceof CmpIX86 || cmp instanceof CmpMemX86) ) + if( set instanceof SetX86 && (cmp instanceof CmpX86 || cmp instanceof CmpIX86 || cmp instanceof CmpMemX86 || cmp instanceof CmpFX86) ) _inputs.set(1,cmp); else throw Utils.TODO(); } @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.FLAGS_MASK; } - @Override public RegMask outregmap() { return RegMask.EMPTY; } + @Override public RegMask outregmap() { return null; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { @@ -35,7 +37,7 @@ public class JmpX86 extends IfNode implements MachNode { @Override public void asm(CodeGen code, SB sb) { String src = code.reg(in(1)); - if( src!="FLAGS" ) sb.p(src); + if( src!="flags" ) sb.p(src); } @Override public String op() { return "j"+_bop; } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java index a49b8d7f..229c0151 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java index 02a9a4fe..893772a6 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java @@ -1,8 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.CodeGen; -import com.seaofnodes.simple.RegMask; import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.LoadNode; import com.seaofnodes.simple.node.Node; @@ -14,7 +14,7 @@ public class LoadX86 extends MemOpX86 { @Override public String op() { return "ld"+_sz; } // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + @Override public RegMask outregmap() { return x86_64_v2.MEM_MASK; } // General form: "ldN dst,[base + idx<<2 + 12]" diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java index 418f63f3..6d39a2b4 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java @@ -1,8 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.CodeGen; -import com.seaofnodes.simple.RegMask; import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; public class MemAddX86 extends MemOpX86 { @@ -11,7 +11,7 @@ public class MemAddX86 extends MemOpX86 { } // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return RegMask.EMPTY; } + @Override public RegMask outregmap() { return null; } // General form: "add [base + idx<<2 + 12] += src" diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java index 1e678a0b..dee4afd4 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; import java.io.ByteArrayOutputStream; @@ -49,9 +51,11 @@ public abstract class MemOpX86 extends MemOpNode implements MachNode { } Node idx() { return in(3); } - Node val() { return in(4); } + 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 StringBuilder _printMach(StringBuilder sb, BitSet visited) { + return sb.append(".").append(_name); + } @Override public String label() { return op(); } @Override public Type compute() { throw Utils.TODO(); } @@ -59,14 +63,12 @@ public abstract class MemOpX86 extends MemOpNode implements MachNode { // Register mask allowed on input i. @Override public RegMask regmap(int i) { - if( i==1 ) return RegMask.EMPTY; // 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.RMASK; // value + if( i==1 ) return null; // Memory + if( i==2 ) return x86_64_v2.RMASK; // base in GPR + if( i==3 ) return x86_64_v2.RMASK; // index in GPR + if( i==4 ) return x86_64_v2.MEM_MASK; // value in GPR or XMM throw Utils.TODO(); } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { throw Utils.TODO(); } @Override public int encoding(ByteArrayOutputStream bytes) { throw Utils.TODO(); } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java index aef813e1..9e996130 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeFloat; import com.seaofnodes.simple.type.TypeInteger; @@ -15,6 +17,8 @@ public class MulFX86 extends MachConcreteNode implements MachNode { @Override public RegMask outregmap() { return x86_64_v2.XMASK; } // Output is same register as input#1 @Override public int twoAddress() { return 1; } + // Ok to swap arguments + @Override public boolean commutes() { return true; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java index 7508cf4c..b41a5720 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java @@ -1,7 +1,12 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java index 15fd9496..d880208e 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java @@ -1,12 +1,13 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; public class MulX86 extends MachConcreteNode implements MachNode { - MulX86( Node mul ) { super(mul); } // Register mask allowed on input i. @@ -15,6 +16,8 @@ public class MulX86 extends MachConcreteNode implements MachNode { @Override public RegMask outregmap() { return x86_64_v2.WMASK; } // Output is same register as input#1 @Override public int twoAddress() { return 1; } + // Ok to swap arguments + @Override public boolean commutes() { return true; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java index 4396a917..cd47c64b 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java @@ -1,30 +1,19 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import java.io.ByteArrayOutputStream; public class NewX86 extends NewNode implements MachNode { - // A pre-zeroed chunk of memory. - NewX86( NewNode nnn ) { super(nnn); } - - // Register mask allowed on input i, the size - @Override public RegMask regmap(int i) { - // Size - if( i==1 ) return x86_64_v2.RDI_MASK; - // All the memory alias edges - return RegMask.EMPTY; - } - @Override public RegMask outregmap() { throw Utils.TODO(); } - - // Register mask allowed as a result. Pointer result in standard calling - // convention. - @Override public RegMask outregmap(int i) { - if( i == 1 ) return x86_64_v2.RET_MASK; - // All the memory aliases edges - return RegMask.EMPTY; - } + NewX86( NewNode nnn ) { super(nnn); } + // Size and pointer result in standard calling convention; null for all the + // memory aliases edges + @Override public RegMask regmap(int i) { return i == 1 ? x86_64_v2.RDI_MASK : null; } + @Override public RegMask outregmap(int i) { return i == 1 ? x86_64_v2.RAX_MASK : null; } + @Override public RegMask outregmap() { return null; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java index 513962c2..949ac6c2 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java @@ -1,14 +1,13 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.CodeGen; -import com.seaofnodes.simple.RegMask; import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.MachConcreteNode; import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.type.TypeInteger; - import java.io.ByteArrayOutputStream; public class OrIX86 extends MachConcreteNode implements MachNode { diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java new file mode 100644 index 00000000..6d99fe3b --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java @@ -0,0 +1,35 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import java.io.ByteArrayOutputStream; + +public class OrX86 extends MachConcreteNode implements MachNode{ + OrX86(Node or) { + super(or); + } + + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.WMASK; } + + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + + // Output is same register as input#1 + @Override public int twoAddress() { return 1; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: "or dst | src" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))); + } + + @Override public String op() { return "or"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java index 8a439644..4d47f76d 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java @@ -1,21 +1,22 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.RegMask; import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.node.ParmNode; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.ParmNode; import java.io.ByteArrayOutputStream; public class ParmX86 extends ParmNode implements MachNode { final RegMask _rmask; ParmX86( ParmNode parm ) { super(parm); - _rmask = x86_64_v2.callInMask(_idx); + _rmask = x86_64_v2.callInMask(fun().sig(),_idx); } // Register mask allowed on input i. 0 for no register. - @Override public RegMask regmap(int i) { return RegMask.EMPTY; } + @Override public RegMask regmap(int i) { return _rmask; } // Register mask allowed as a result. Calling convention register @Override public RegMask outregmap() { return _rmask; } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java index 2849b232..40d8b6c2 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java @@ -1,20 +1,20 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.RegMask; -import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.node.ProjNode; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.ProjNode; import java.io.ByteArrayOutputStream; public class ProjX86 extends ProjNode implements MachNode { - ProjX86( ProjNode p ) { super(p); } // Register mask allowed on input i. 0 for no register. - @Override public RegMask regmap(int i) { return RegMask.EMPTY; } + @Override public RegMask regmap(int i) { return null; } // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return RegMask.EMPTY; } + @Override public RegMask outregmap() { + return ((MachNode)in(0)).outregmap(_idx); + } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java index e6219ff4..cdb20fd4 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java @@ -1,9 +1,12 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.FunNode; import com.seaofnodes.simple.node.ReturnNode; import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.type.TypeFloat; import java.io.ByteArrayOutputStream; // Return @@ -19,29 +22,22 @@ public class RetX86 extends ReturnNode implements MachNode { // Register mask allowed on input i. // This is the normal calling convention - @Override public RegMask regmap(int i) { - return switch( i ) { - case 0 -> RegMask.EMPTY; - case 1 -> RegMask.EMPTY; - case 2 -> x86_64_v2.RET_MASK; - case 3 -> RegMask.EMPTY; // RPC is always on stack - default -> throw Utils.TODO(); - }; - } + @Override public RegMask regmap(int i) { return x86_64_v2.retMask(_fun.sig(),i); } // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return RegMask.EMPTY; } + @Override public RegMask outregmap() { return null; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { throw Utils.TODO(); } - // Human-readable form appended to the SB. Things like the encoding, - // indentation, leading address or block labels not printed here. - // Just something like "ld4\tR17=[R18+12] // Load array base". - // General form: "op\tdst=src+src" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(in(2))); + // Post code-gen, just print the "ret" + if( code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() ) + // Prints return reg (either RAX or XMM0), RPC (always [rsp-4]) and + // then the callee-save registers. + for( int i=2; i<nIns(); i++ ) + sb.p(code.reg(in(i))).p(" "); } @Override public String op() { return "ret"; } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java new file mode 100644 index 00000000..9f942cc6 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java @@ -0,0 +1,39 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +// Arithmetic Right Shift +public class SarIX86 extends MachConcreteNode implements MachNode { + final TypeInteger _ti; + SarIX86(Node sar, TypeInteger ti) {super(sar); _inputs.pop(); _ti = ti;} + + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { + // assert i==1; + return x86_64_v2.RMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + + // Output is same register as input#1 + @Override public int twoAddress() { return 1; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: "sari dst << #imm" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> #"); + _ti.print(sb); + } + + @Override public String op() { return "sari"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java index 3d907eef..ee7a2f56 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; @@ -10,14 +12,15 @@ import java.lang.StringBuilder; public class SarX86 extends MachConcreteNode implements MachNode { - final TypeInteger _ti; - SarX86(Node sar, TypeInteger ti) {super(sar); _inputs.pop(); _ti = ti;} + SarX86(Node sar) { super(sar); } // Register mask allowed on input i. // This is the normal calling convention @Override public RegMask regmap(int i) { - // assert i==1; - return x86_64_v2.RMASK; } + if(i == 1) return x86_64_v2.WMASK; + if(i == 2) return x86_64_v2.RCX_MASK; + throw Utils.TODO(); + } // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return x86_64_v2.WMASK; } @@ -30,11 +33,9 @@ public class SarX86 extends MachConcreteNode implements MachNode { } // General form - // General form: "sari dst << #imm" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> #"); - _ti.print(sb); + sb.p(code.reg(this)).p(" >> ").p(code.reg(in(1))); } - @Override public String op() { return "sari"; } + @Override public String op() { return "sar"; } } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java index 25367e03..53237538 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; @@ -31,7 +33,7 @@ public class SetX86 extends MachConcreteNode implements MachNode { @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); + if( src!="flags" ) sb.p(" = ").p(src); } @Override public String op() { return "set"+_bop; } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java index 61e0d262..c18007cd 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java new file mode 100644 index 00000000..a3de5347 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java @@ -0,0 +1,39 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import java.io.ByteArrayOutputStream; + +public class ShlX86 extends MachConcreteNode implements MachNode { + ShlX86(Node shl) { super(shl); }; + + // Register mask allowed on input i. + // This is the normal calling convention + + // CL register must be used for input(2) + @Override public RegMask regmap(int i) { + if(i == 1) return x86_64_v2.WMASK; + if(i == 2) return x86_64_v2.RCX_MASK; + throw Utils.TODO(); + } + + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + + // Output is same register as input#1 + @Override public int twoAddress() { return 1; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: "shl dst << src" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" << ").p(code.reg(in(1))); + } + + @Override public String op() { return "shl"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java index 08cae93e..d17ee734 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java new file mode 100644 index 00000000..757d9b8e --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java @@ -0,0 +1,40 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import java.io.ByteArrayOutputStream; + +public class ShrX86 extends MachConcreteNode implements MachNode { + ShrX86(Node shr) { + super(shr); + }; + // Register mask allowed on input i. + // This is the normal calling convention + + // CL register must be used for input(2) + @Override public RegMask regmap(int i) { + if(i == 1) return x86_64_v2.WMASK; + if(i == 2) return x86_64_v2.RCX_MASK; + throw Utils.TODO(); + } + + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + + // Output is same register as input#1 + @Override public int twoAddress() { return 1; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: "shr dst >>> src" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" >>> ").p(code.reg(in(1))); + } + + @Override public String op() { return "shr"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java new file mode 100644 index 00000000..ea283a87 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java @@ -0,0 +1,21 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.SplitNode; +import java.io.ByteArrayOutputStream; + +public class SplitX86 extends SplitNode { + SplitX86( String kind, byte round ) { super(kind,round, new Node[2]); } + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { return x86_64_v2.SPLIT_MASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return x86_64_v2.SPLIT_MASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java index c9191b87..024ef904 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java @@ -1,8 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.CodeGen; -import com.seaofnodes.simple.RegMask; import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.node.StoreNode; import java.util.BitSet; @@ -15,11 +15,15 @@ public class StoreX86 extends MemOpX86 { @Override public String op() { return "st"+_sz; } @Override public StringBuilder _printMach(StringBuilder sb, BitSet visited) { - return sb.append(".").append(_name).append("=").append(val()==null ? _imm : val()).append(";"); + 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 RegMask.EMPTY; } + @Override public RegMask outregmap() { return null; } // General form: "stN [base + idx<<2 + 12],val" @Override public void asm(CodeGen code, SB sb) { diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java index ed19ad79..ceaf0636 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import java.io.ByteArrayOutputStream; diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java index cac59d3b..da44a838 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java index bd88011f..2b4de2ca 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.ConstantNode; import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.type.Type; @@ -11,10 +13,13 @@ import java.io.ByteArrayOutputStream; // Function constants public class TFPX86 extends ConstantNode implements MachNode { TFPX86( ConstantNode con ) { super(con); } + @Override public String op() { return _con == Type.NIL ? "xor" : "ldx"; } // Register mask allowed on input i. 0 for no register. - @Override public RegMask regmap(int i) { return RegMask.EMPTY; } + @Override public RegMask regmap(int i) { return null; } // General int registers @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + @Override public boolean isClone() { return true; } + @Override public TFPX86 copy() { return new TFPX86(this); } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { @@ -32,10 +37,4 @@ public class TFPX86 extends ConstantNode implements MachNode { else _con.print(sb.p(reg).p(" #")); } - - @Override public String op() { - if( _con == Type.NIL ) - return "xor"; - return "ldx"; // Some fancier encoding - } } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java index 795fa814..47c6be73 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java @@ -1,14 +1,13 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.CodeGen; -import com.seaofnodes.simple.RegMask; import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.MachConcreteNode; import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.type.TypeInteger; - import java.io.ByteArrayOutputStream; public class XorIX86 extends MachConcreteNode implements MachNode { diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java index 45ae19dc..aacd2352 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -1,9 +1,10 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.CodeGen; -import com.seaofnodes.simple.Machine; -import com.seaofnodes.simple.RegMask; import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.LRG; +import com.seaofnodes.simple.codegen.Machine; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; @@ -11,130 +12,195 @@ public class x86_64_v2 extends Machine { // X86-64 V2. Includes e.g. SSE4.2 and POPCNT. @Override public String name() { return "x86_64_v2"; } - public static int RAX = 0, RCX = 1, RDX = 2, RBX = 3, RSP = 4, RBP = 5, RSI = 6, RDI = 7; - public static int R08 = 8, R09 = 9, R10 = 10, R11 = 11, R12 = 12, R13 = 13, R14 = 14, R15 = 15; - public static int FLAGS = 32; + static final int RAX = 0, RCX = 1, RDX = 2, RBX = 3, RSP = 4, RBP = 5, RSI = 6, RDI = 7; + static final int R08 = 8, R09 = 9, R10 = 10, R11 = 11, R12 = 12, R13 = 13, R14 = 14, R15 = 15; - public static int XMM0 = 16, XMM1 = 17, XMM2 = 18, XMM3 = 19, XMM4 = 20, XMM5 = 21, XMM6 = 22, XMM7 = 23; - public static int XMM8 = 24, XMM9 = 25, XMM10 = 26, XMM11 = 27, XMM12 = 28, XMM13 = 29, XMM14 = 30, XMM15 = 31; + static final int XMM0 = 16, XMM1 = 17, XMM2 = 18, XMM3 = 19, XMM4 = 20, XMM5 = 21, XMM6 = 22, XMM7 = 23; + static final int XMM8 = 24, XMM9 = 25, XMM10 = 26, XMM11 = 27, XMM12 = 28, XMM13 = 29, XMM14 = 30, XMM15 = 31; + static final int FLAGS = 32; + static final int MAX_REG = 33; + static final int RPC = 33; // General purpose register mask: pointers and ints, not floats - public static RegMask RMASK = new RegMask(0b1111111111111111); + static final long RD_BITS = 0b1111111111111111L; // All the GPRs + static RegMask RMASK = new RegMask(RD_BITS); // No RSP in the *write* general set. - public static RegMask WMASK = new RegMask(0b1111111111101111); + static final long WR_BITS = 0b1111111111101111L; // All the GPRs minus RSP + static RegMask WMASK = new RegMask(WR_BITS); // Xmm register mask - public static RegMask XMASK = new RegMask( 0b1111111111111111L << XMM0); + static final long FP_BITS = 0b1111111111111111L << XMM0; // All the XMMs + static final RegMask XMASK = new RegMask(FP_BITS); + static final RegMask FLAGS_MASK = new RegMask(FLAGS); + static final RegMask RPC_MASK = new RegMask(RPC); - public static RegMask FLAGS_MASK = new RegMask(1L<<FLAGS); + static final long SPILLS = -(1L << MAX_REG); + static final RegMask SPLIT_MASK = new RegMask(WR_BITS | FP_BITS | (1L<<FLAGS) | SPILLS, -1L ); - // Return single int/ptr register - public static RegMask RET_MASK = new RegMask(RAX); + // Load/store mask; both GPR and FPR + static RegMask MEM_MASK = new RegMask(WR_BITS | FP_BITS); - public static RegMask RDI_MASK = new RegMask(1L<<RDI); - public static RegMask RCX_MASK = new RegMask(1L<<RCX); - public static RegMask RDX_MASK = new RegMask(1L<<RDX); - public static RegMask R08_MASK = new RegMask(1L<<R08); - public static RegMask R09_MASK = new RegMask(1L<<R09); - public static RegMask RSI_MASK = new RegMask(1L<<RSI); + static final RegMask RAX_MASK = new RegMask(RAX); + static final RegMask RCX_MASK = new RegMask(RCX); + static final RegMask RDX_MASK = new RegMask(RDX); + static final RegMask RDI_MASK = new RegMask(RDI); + static final RegMask R08_MASK = new RegMask(R08); + static final RegMask R09_MASK = new RegMask(R09); + static final RegMask RSI_MASK = new RegMask(RSI); - // Calling conv metadata - public int GPR_COUNT_CONV_WIN64 = 4; // RCX, RDX, R9, R9 - public int XMM_COUNT_CONV_WIN64 = 4; // XMM0L, XMM1L, XMM2L, XMM3L + static final RegMask XMM0_MASK = new RegMask(XMM0); - public int GPR_COUNT_CONV_SYSTEM_V = 6; // RDI, RSI, RDX, RCX, R8, R9 - public int XMM_COUNT_CONV_SYSTEM_V = 4; // XMM0, XMM1, XMM2, XMM3 .... // Human-readable name for a register number, e.g. "RAX". // Hard crash for bad register number, fix yer bugs! - public static final String[] REGS = new String[] { + static final String[] REGS = new String[] { "rax" , "rcx" , "rdx" , "rbx" , "rsp" , "rbp" , "rsi" , "rdi" , "r8" , "r9" , "r10" , "r11" , "r12" , "r13" , "r14" , "r15" , "xmm0", "xmm1", "xmm2" , "xmm3" , "xmm4" , "xmm5" , "xmm6" , "xmm7" , "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15", + "flags","[rsp-4]", }; - @Override public String reg( int reg ) { return REGS[reg]; } - - // 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 int callInArg( int idx ) { - return switch( CodeGen.CODE._callingConv ) { - case CodeGen.CallingConv.SystemV -> callInArgSystemV(idx); - case CodeGen.CallingConv.Win64 -> callInArgWin64 (idx); - }; + @Override public String reg( int reg ) { + return reg < REGS.length ? REGS[reg] : "[rsp+"+(reg-REGS.length)*4+"]"; } // WIN64(param passing) static RegMask[] CALLINMASK_WIN64 = new RegMask[] { - RegMask.EMPTY, - RegMask.EMPTY, RCX_MASK, RDX_MASK, R08_MASK, R09_MASK, - RegMask.EMPTY, - RegMask.EMPTY }; - static int[] CALLINARG_WIN64 = new int[] { - 0, // Control, no register - 0, // Memory, no register - RCX, // - RDX, - R08, - R09, - 0, - 0, - }; - static int callInArgWin64( int idx ) { return CALLINARG_WIN64[idx]; } - - // caller saved(win64) - public static final long WIN64_ABI_CALLER_SAVED = - (1L << RAX) | (1L << RCX) | (1L << RDX) | (1L << R08) | (1L << R09) | (1L << R10) | (1L << R11); - - // callee saved(win64) - public static final long WIN64_ABI_CALLEE_SAVED = ~WIN64_ABI_CALLER_SAVED; // SystemV(param passing) static RegMask[] CALLINMASK_SYSTEMV = new RegMask[] { - RegMask.EMPTY, - RegMask.EMPTY, RDI_MASK, RSI_MASK, RDX_MASK, RCX_MASK, R08_MASK, - R09_MASK + R09_MASK, }; - static int[] CALLINARG_SYSTEMV = new int[] { - 0, // Control, no register - 0, // Memory, no register - RDI, - RSI, - RDX, - RCX, - R08, - R09, + + // Limit of float args passed in registers + static RegMask[] XMMS4 = new RegMask[]{ + new RegMask(XMM0), new RegMask(XMM1), new RegMask(XMM2), new RegMask(XMM3), }; - static int callInArgSystemV( int idx ) { return CALLINARG_SYSTEMV[idx]; } + + // Map from function signature and argument index to register. + // Used to set input registers to CallNodes, and ParmNode outputs. + static RegMask callInMask( TypeFunPtr tfp, int idx ) { + if( idx==0 ) return RPC_MASK; + if( idx==1 ) return null; + // Count floats in signature up to index + int fcnt=0; + for( int i=2; i<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 < XMMS4.length ) + return XMMS4[fcnt]; + } else { + RegMask[] cargs = switch( CodeGen.CODE._callingConv ) { + case "SystemV" -> CALLINMASK_SYSTEMV; + case "Win64" -> CALLINMASK_WIN64; + default -> throw new IllegalArgumentException("Unknown calling convention: "+CodeGen.CODE._callingConv); + }; + if( idx-2-fcnt < cargs.length ) + return cargs[idx-2-fcnt]; + } + throw Utils.TODO(); // Pass on stack slot + } // caller saved(systemv) + static final long SYSTEM5_CALLER_SAVE = + (1L<< RAX) | (1L<< RCX) | (1L<< RDX) | + (1L<< RDI) | (1L<< RSI) | + (1L<< R08) | (1L<< R09) | (1L<< R10) | (1L<< R11) | + (1L<<FLAGS)| // Flags are killed + // All FP regs are killed + FP_BITS; + static final RegMask SYSTEM5_CALLER_SAVE_MASK = new RegMask(SYSTEM5_CALLER_SAVE); + // caller saved(win64) - public static final long SYSTEMV_ABI_CALLER_SAVED = - (1L << RAX) | (1L << RDI) | (1L << RSI) | (1L << RCX) | (1L << RDX) | (1L << R08) | (1L << R09) << (1L << R10) << (1L << R11); - // callee saved(systemv) - public static final long SYSTEMV_ABI_CALLE_SAVED = ~SYSTEMV_ABI_CALLER_SAVED; + 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); + static final RegMask WIN64_CALLER_SAVE_MASK = new RegMask(WIN64_CALLER_SAVE); + + static RegMask x86CallerSave() { + return switch( CodeGen.CODE._callingConv ) { + case "SystemV" -> SYSTEM5_CALLER_SAVE_MASK; + case "Win64" -> WIN64_CALLER_SAVE_MASK; + default -> throw new IllegalArgumentException("Unknown calling convention: "+CodeGen.CODE._callingConv); + }; + } + @Override public RegMask callerSave() { return x86CallerSave(); } + + static final RegMask SYSTEM5_CALLEE_SAVE_MASK; + static final RegMask WIN64_CALLEE_SAVE_MASK; + static { + long callee = ~SYSTEM5_CALLER_SAVE; + // Remove the spills + callee &= (1L<<MAX_REG)-1; + callee &= ~(1L<<FLAGS); + callee &= ~(1L<<RSP); + SYSTEM5_CALLEE_SAVE_MASK = new RegMask(callee); + + callee = ~WIN64_CALLER_SAVE; + // Remove the spills + callee &= (1L<<MAX_REG)-1; + callee &= ~(1L<<FLAGS); + callee &= ~(1L<<RSP); + WIN64_CALLEE_SAVE_MASK = new RegMask(callee); + } + static RegMask x86CalleeSave() { + return switch( CodeGen.CODE._callingConv ) { + case "SystemV" -> SYSTEM5_CALLEE_SAVE_MASK; + case "Win64" -> WIN64_CALLEE_SAVE_MASK; + default -> throw new IllegalArgumentException("Unknown calling convention: "+CodeGen.CODE._callingConv); + }; + } + @Override public RegMask calleeSave() { return x86CalleeSave(); } - static RegMask callInMask( int idx ) { - return switch( CodeGen.CODE._callingConv ) { - case CodeGen.CallingConv.SystemV -> CALLINMASK_SYSTEMV[idx]; - case CodeGen.CallingConv.Win64 -> CALLINMASK_WIN64 [idx]; + static final RegMask[] WIN64_RET_MASKS, SYS5_RET_MASKS; + static { + WIN64_RET_MASKS = makeRetMasks( WIN64_CALLEE_SAVE_MASK); + SYS5_RET_MASKS = makeRetMasks(SYSTEM5_CALLEE_SAVE_MASK); + } + private static RegMask[] makeRetMasks(RegMask mask) { + int nSaves = mask.size(); + RegMask[] masks = new RegMask[4 + nSaves]; + masks[0] = null; + masks[1] = null; // Memory + masks[2] = null; // Varies, either XMM0 or RAX + masks[3] = RPC_MASK; + short reg = mask.firstReg(); + for( int i=0; i<nSaves; i++ ) { + masks[i+4] = new RegMask(reg); + reg = mask.nextReg(reg); + } + return masks; + } + + // Return single int/ptr register. Used by CallEnd output and Return input. + static RegMask retMask( TypeFunPtr tfp, int i ) { + if( i==2 ) + return tfp.ret() instanceof TypeFloat ? XMM0_MASK : RAX_MASK; + RegMask[] masks = switch( CodeGen.CODE._callingConv ) { + case "SystemV" -> SYS5_RET_MASKS; + case "Win64" -> WIN64_RET_MASKS; + default -> throw new IllegalArgumentException("Unknown calling convention: "+CodeGen.CODE._callingConv); }; + return masks[i]; } // Create a split op; any register to any register, including stack slots - @Override public Node split() { - throw Utils.TODO(); - } + @Override public SplitNode split(String kind, byte round, LRG lrg) { return new SplitX86(kind,round); } // Return a MachNode unconditional branch @Override public CFGNode jump() { @@ -153,8 +219,9 @@ public class x86_64_v2 extends Machine { case AddNode add -> add(add); case AndNode and -> and(and); case BoolNode bool -> cmp(bool); - case CallEndNode cend -> new CallEndNode((CallNode)cend.in(0)); + case CallEndNode cend -> new CallEndX86(cend); case CallNode call -> call(call); + case CastNode cast -> new CastX86(cast); case CProjNode c -> new CProjNode(c); case ConstantNode con -> con(con); case DivFNode divf -> new DivFX86(divf); @@ -247,7 +314,7 @@ public class x86_64_v2 extends Machine { private Node and(AndNode and) { if( and.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti ) return new AndIX86(and, ti); - throw Utils.TODO(); + return new AndX86(and); } private Node call(CallNode call) { @@ -263,6 +330,10 @@ public class x86_64_v2 extends Machine { return new SetX86(cmp,bool.op()); } private Node _cmp( BoolNode bool ) { + // Float variant + if( bool.isFloat() ) + return new CmpFX86(bool); + Node lhs = bool.in(1); Node rhs = bool.in(2); if( lhs instanceof LoadNode ld && ld.nOuts()==1 ) @@ -271,20 +342,23 @@ public class x86_64_v2 extends Machine { if( rhs instanceof LoadNode ld && ld.nOuts()==1 ) return new CmpMemX86(bool,address(ld),ld.ptr(),idx,off,scale, imm(lhs),val,true); - return rhs instanceof ConstantNode con && con._con instanceof TypeInteger ti - ? new CmpIX86(bool, ti) - : new CmpX86(bool); + // Vs immediate + if( rhs instanceof ConstantNode con && con._con instanceof TypeInteger ti ) + return new CmpIX86(bool, ti); + // x vs y + return new CmpX86(bool); } private Node con( ConstantNode con ) { + if( !con._con.isConstant() ) return new ConstantNode( con ); // Default unknown caller inputs return switch( con._con ) { case TypeInteger ti -> new IntX86(con); case TypeFloat tf -> new FltX86(con); case TypeFunPtr tfp -> new TFPX86(con); - case TypeMemPtr tmp -> new ConstantNode(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 -> new ConstantNode(con); + case Type t -> t==Type.NIL ? new IntX86(con) : new ConstantNode(con); }; } @@ -315,7 +389,7 @@ public class x86_64_v2 extends Machine { private Node or(OrNode or) { if( or.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) return new OrIX86(or, ti); - throw Utils.TODO(); + return new OrX86(or); } private Node prj( ProjNode prj ) { @@ -324,20 +398,20 @@ public class x86_64_v2 extends Machine { private Node sar( SarNode sar ) { if( sar.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) - return new SarX86(sar, ti); - throw Utils.TODO(); + return new SarIX86(sar, ti); + return new SarX86(sar); } private Node shl( ShlNode shl ) { if( shl.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti ) return new ShlIX86(shl, ti); - throw Utils.TODO(); + return new ShlX86(shl); } private Node shr(ShrNode shr) { if( shr.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti ) return new ShrIX86(shr, ti); - throw Utils.TODO(); + return new ShrX86(shr); } private Node st( StoreNode st ) { @@ -403,6 +477,7 @@ public class x86_64_v2 extends Machine { } return mop; } + private int imm( Node xval ) { assert val==null && imm==0; if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti ) { diff --git a/chapter20/src/main/java/com/seaofnodes/simple/ASMPrinter.java b/chapter20/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java index 5cc13210..1a233aa2 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/ASMPrinter.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java @@ -1,7 +1,8 @@ -package com.seaofnodes.simple; +package com.seaofnodes.simple.print; -import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeFunPtr; import com.seaofnodes.simple.type.TypeMem; import com.seaofnodes.simple.type.TypeRPC; @@ -23,6 +24,7 @@ public abstract class ASMPrinter { private static int print(int iadr, SB sb, CodeGen code, FunNode fun, int cfgidx) { // Function header sb.nl().p("---"); + if( fun._name != null ) sb.p(fun._name).p(" "); fun.sig().print(sb); sb.p("---------------------------").nl(); @@ -37,39 +39,60 @@ public abstract class ASMPrinter { return iadr; } + static private final int encWidth = 8; + 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) { 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("L").p(bb._nid).p(":").nl(); + sb.p(bb instanceof LoopNode ? "LOOP" : "L").p(bb._nid).p(":").nl(); + if( bb instanceof CallNode ) return iadr; + final boolean postAlloc = code._phase.ordinal() > CodeGen.Phase.RegAlloc.ordinal(); + + // Count Phis + int nPhi=0; + for( ; nPhi<bb.nOuts(); nPhi++ ) + if( !(bb.out(nPhi) instanceof PhiNode) ) + break; + + if( nPhi>0 ) { + // Post-alloc phi prints all on one line + if( postAlloc ) { + sb.fix(4," ").p(" ").fix(encWidth,"").p(" "); + for( int i=0; i<nPhi; i++ ) { + PhiNode phi = (PhiNode)bb.out(i); + if( !(phi._type instanceof TypeMem || phi._type instanceof TypeRPC) ) // Nothing for the hidden ones + sb.p(phi._label).p(':').p(code.reg(phi)).p(','); + } + sb.unchar().nl(); + + } else { + for( int j=0; j<nPhi; j++ ) { + PhiNode phi = (PhiNode)bb.out(j); + if( phi._type instanceof TypeMem || phi._type instanceof TypeRPC ) continue; // Nothing for the hidden ones + sb.fix(4," ").p(" ").fix(encWidth,"").p(" ").fix(opWidth,phi._label).p(" "); + sb.p(code.reg(phi)); + if( !(phi instanceof ParmNode) ) { + 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( !(bb instanceof CallNode) ) - for( Node n : bb._outputs ) - iadr = doInst(iadr, sb,code,fun,bb,n); + // All the non-phis + for( int i=nPhi; i<bb.nOuts(); i++ ) + iadr = doInst(iadr, sb,code,fun,bb,bb.out(i),postAlloc ); return iadr; } - static int doInst(int iadr, SB sb, CodeGen code, FunNode fun, CFGNode bb, Node n) { - final int encWidth = 8; - final int opWidth = 5; - final int argWidth = 30; - - if( n instanceof CProjNode ) return iadr; - - // Phis are useful before (and during) reg alloc - if( n instanceof PhiNode phi ) { - if( phi._type instanceof TypeMem || phi._type instanceof TypeRPC ) return iadr; // Nothing for the hidden ones - sb.fix(4," ").p(" ").fix(encWidth,"").p(" ").fix(opWidth,phi._label).p(" "); - sb.p(code.reg(n)); - if( !(n instanceof ParmNode) ) { - sb.p(" = phi( "); - for( int i=1; i<n.nIns(); i++ ) - sb.p("N").p(n.in(i)._nid).p(","); - sb.unchar().p(" )"); - } - sb.nl(); - return iadr; - } + static int doInst(int iadr, SB sb, CodeGen code, FunNode fun, CFGNode bb, Node n, boolean postAlloc ) { + if( n instanceof CProjNode ) return iadr; + if( n instanceof CalleeSaveNode && postAlloc ) return iadr; // 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 @@ -131,7 +154,7 @@ public abstract class ASMPrinter { if( !(n instanceof CFGNode) && n instanceof MultiNode ) { for( Node proj : n._outputs ) { assert proj instanceof ProjNode; - doInst(iadr,sb,code,fun,bb,proj); + doInst(iadr,sb,code,fun,bb,proj,postAlloc); } } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter20/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java index 30e25916..e4cf2c31 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java @@ -1,9 +1,8 @@ -package com.seaofnodes.simple; +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; /** diff --git a/chapter20/src/main/java/com/seaofnodes/simple/IRPrinter.java b/chapter20/src/main/java/com/seaofnodes/simple/print/IRPrinter.java index 3cddacfd..c91f844b 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/IRPrinter.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/print/IRPrinter.java @@ -1,5 +1,9 @@ -package com.seaofnodes.simple; +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; @@ -53,19 +57,22 @@ public abstract class IRPrinter { boolean gap=false; for( int i=rpos.size()-1; i>=0; i-- ) { Node n = rpos.get(i); - if( n instanceof CFGNode || n.isMultiHead() ) { + 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.isMultiTail() ) { i++; break; } + if( !(t.in(0) instanceof MultiNode) ) { i++; break; } printLine( t, sb ); } if( n instanceof ReturnNode ret ) { FunNode fun = ret.fun(); - fun.sig().print(sb.p("--- ").p(fun._name==null ? "" : fun._name).p(" "),false).p("----------------------\n"); + 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 @@ -137,7 +144,7 @@ public abstract class IRPrinter { // Toss things past the limit except multi-heads while( idx < _bfs.size() ) { Node n = _bfs.get(idx); - if( n.isMultiHead() ) idx++; + if( n instanceof MultiNode ) idx++; else del(idx); } // Root set is any node with no inputs in the visited set @@ -170,6 +177,48 @@ public abstract class IRPrinter { } } + 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 ) + 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. diff --git a/chapter20/src/main/java/com/seaofnodes/simple/JSViewer.java b/chapter20/src/main/java/com/seaofnodes/simple/print/JSViewer.java index fcb0c28a..76c64c71 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/JSViewer.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/print/JSViewer.java @@ -1,12 +1,15 @@ -package com.seaofnodes.simple; +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.util.Stack; import java.nio.file.Paths; import java.util.Collection; -import static com.seaofnodes.simple.CodeGen.CODE; +import java.util.Stack; +import static com.seaofnodes.simple.codegen.CodeGen.CODE; public class JSViewer implements AutoCloseable { // Display programs in an endless loop diff --git a/chapter20/src/main/java/com/seaofnodes/simple/SimpleWebSocket.java b/chapter20/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java index 1ce766ff..7655213d 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/SimpleWebSocket.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java @@ -1,5 +1,6 @@ -package com.seaofnodes.simple; +package com.seaofnodes.simple.print; +import com.seaofnodes.simple.Utils; import java.io.*; import java.net.ServerSocket; import java.net.Socket; diff --git a/chapter20/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java b/chapter20/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java index be801c85..167a931c 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java @@ -43,7 +43,8 @@ public class TypeFunPtr extends TypeNil { 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 void gather(ArrayList<Type> ts) { ts.add(TEST); ts.add(TEST0); ts.add(BOT); } + public static TypeFunPtr MAIN = make((byte)3,TypeTuple.MAIN,TypeInteger.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) { @@ -68,6 +69,7 @@ public class TypeFunPtr extends TypeNil { 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()); } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/chapter20/src/main/java/com/seaofnodes/simple/type/TypeStruct.java index 6c5cb8d4..e981b5dc 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/type/TypeStruct.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/type/TypeStruct.java @@ -184,6 +184,7 @@ public class TypeStruct extends Type { 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("; "); diff --git a/chapter20/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/chapter20/src/main/java/com/seaofnodes/simple/type/TypeTuple.java index 6d3a2b69..95e35e99 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/type/TypeTuple.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/type/TypeTuple.java @@ -55,6 +55,8 @@ public class TypeTuple extends Type { return make(ts); } + 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) { diff --git a/chapter20/src/test/java/com/seaofnodes/simple/BrainFuckTest.java b/chapter20/src/test/java/com/seaofnodes/simple/BrainFuckTest.java index 8f09cb3d..c23bb301 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/BrainFuckTest.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/BrainFuckTest.java @@ -1,10 +1,11 @@ package com.seaofnodes.simple; -import com.seaofnodes.simple.evaluator.Evaluator; +import com.seaofnodes.simple.codegen.CodeGen; import java.nio.charset.StandardCharsets; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; +import static com.seaofnodes.simple.Main.PORTS; public class BrainFuckTest { @@ -17,8 +18,7 @@ public class BrainFuckTest { encoded.append("program[").append(i).append("] = ").append(value).append(";"); } - CodeGen code = new CodeGen( - encoded + """ + String src = encoded + """ int d = 0; u8[] !output = new u8[0]; @@ -62,8 +62,10 @@ for( int pc = 0; pc < program#; pc++ ) { } } return output; - """); - code.parse().opto().typeCheck()/*.instSelect("x86_64_v2", "SystemV")*/.GCM().localSched(); - assertEquals("Hello World!\n", Eval2.eval(code, 0, 10000)); +"""; + Chapter20Test.testCPU(src,"x86_64_v2", "SystemV",28,null); + Chapter20Test.testCPU(src,"riscv" , "SystemV",18,null); + Chapter20Test.testCPU(src,"arm" , "SystemV",12,null); + //assertEquals("Hello World!\n", Eval2.eval(code, 0, 10000)); } } diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter01Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter01Test.java index 934eda76..cddbdfcf 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter01Test.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter01Test.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import org.junit.Test; diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter02Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter02Test.java index d8607c32..631e1487 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter02Test.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter02Test.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import org.junit.Test; diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter03Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter03Test.java index 4f9b2490..c4e4c91e 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter03Test.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter03Test.java @@ -1,7 +1,7 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import org.junit.Test; - import static org.junit.Assert.*; public class Chapter03Test { diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter04Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter04Test.java index d09b1748..c6199193 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter04Test.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter04Test.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import org.junit.Test; diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter05Test.java index 86ec4136..a584ad5a 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter05Test.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter05Test.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.*; import org.junit.Test; diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter06Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter06Test.java index 117531f0..24d12921 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter06Test.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter06Test.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import org.junit.Test; diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter07Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter07Test.java index 6d6a3ce7..88554323 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter07Test.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter07Test.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.*; import org.junit.Test; diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter08Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter08Test.java index 350df18b..7e90d484 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter08Test.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter08Test.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.*; import org.junit.Assert; import org.junit.Test; diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter09Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter09Test.java index 10f4a78b..401f346f 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter09Test.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter09Test.java @@ -1,6 +1,6 @@ package com.seaofnodes.simple; -import com.seaofnodes.simple.evaluator.Evaluator; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; import org.junit.Assert; diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter10Test.java index 02029b3f..1253e4a6 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter11Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter11Test.java index 0b24bd5d..c2b9a86d 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter11Test.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter11Test.java @@ -1,8 +1,7 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import org.junit.Test; - - import static org.junit.Assert.assertEquals; public class Chapter11Test { diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter12Test.java index 21df8205..8069ddaa 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -1,8 +1,7 @@ package com.seaofnodes.simple; -import com.seaofnodes.simple.evaluator.Evaluator; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.StopNode; -import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -43,7 +42,7 @@ while( 1 ) { return guess; """); code.parse().opto(); - assertEquals("return Phi(Loop,(flt)arg,(((ToFloat/Phi_guess)+Phi_guess)/2.0f));", code.print()); + assertEquals("return Phi(Loop,(flt)arg,(((ToFloat/Phi_guess)+Phi_guess)*0.5f));", code.print()); assertEquals("3.0", Eval2.eval(code, 9)); assertEquals("1.414213562373095", Eval2.eval(code, 2)); } diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter13Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter13Test.java index d05fd7d8..977595cb 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter13Test.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter13Test.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -128,6 +129,19 @@ return n.i; } @Test + public void testNullRef4() { + CodeGen code = new CodeGen("-null-5/null-5;"); + try { code.parse().opto().typeCheck(); fail(); } + catch( Exception e ) { assertEquals("Cannot 'Add' null",e.getMessage()); } + } + + @Test public void testNullRef5() { + CodeGen code = new CodeGen("return null+42;"); + try { code.parse().opto().typeCheck(); fail(); } + catch( Exception e ) { assertEquals("Cannot 'Add' null",e.getMessage()); } + } + + @Test public void testEmpty() { CodeGen code = new CodeGen( """ diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter14Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter14Test.java index 17708c37..b83516e3 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter14Test.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter14Test.java @@ -1,8 +1,8 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.StopNode; import org.junit.Test; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -174,7 +174,7 @@ while(v1) break; return v1; """); code.parse().opto(); - assertEquals("return Phi(Loop,(((1<<arg)&1)&Shl),0);", code.print()); + assertEquals("return (Phi(Loop,((1<<arg)&1),0)&Phi(Loop,Shl,4294967295));", code.print()); assertEquals("1", Eval2.eval(code, 0)); } diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter15Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter15Test.java index 546eb0e9..7dd0710a 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter15Test.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter15Test.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import org.junit.Test; import static org.junit.Assert.*; @@ -300,18 +301,20 @@ return is[1]; @Test public void testBad3() { - CodeGen code = new CodeGen( -""" + CodeGen code = new CodeGen(""" int[] is = new int[arg]; return is[1]; """); code.parse().opto(); assertEquals("return 0;", code.print()); assertEquals("0", Eval2.eval(code, 4)); - try { Eval2.eval(code,0); } - catch( ArrayIndexOutOfBoundsException e ) { assertEquals("Array index 1 out of bounds for array length 0",e.getMessage()); } - try { Eval2.eval(code,-1); } + try { Eval2.eval(code,-1); fail(); } catch( NegativeArraySizeException e ) { assertEquals("-1",e.getMessage()); } + // This test will not pass (i.e., failed range-check) until we have range checks. + // Without the range check, the load `is[1]` optimizes against the `new int[]` + // and assumes it loads a zero then folds away, and the Eval2 never sees an OOB load. + //try { Eval2.eval(code,0); fail(); } + //catch( ArrayIndexOutOfBoundsException e ) { assertEquals("Array index 1 out of bounds for array length 0",e.getMessage()); } } @Test diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter16Test.java index ad3dc690..930624e5 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter16Test.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter16Test.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.evaluator.Evaluator; import com.seaofnodes.simple.node.StopNode; import org.junit.Test; diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter17Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter17Test.java index a1d0f8b9..6e43dbc8 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter17Test.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter17Test.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -10,11 +11,9 @@ public class Chapter17Test { public void testJig() { CodeGen code = new CodeGen( """ -int i = 0; -i=i=1; -return i; +return 1; """); - code.parse().opto(); + code.parse().opto().typeCheck(); assertEquals("return 1;", code.print()); assertEquals("1", Eval2.eval(code, 0)); } diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter18Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter18Test.java index db970f60..d0623d12 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter18Test.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter18Test.java @@ -1,9 +1,10 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; +import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.*; import static org.junit.Assert.fail; -import org.junit.Ignore; public class Chapter18Test { @@ -77,7 +78,7 @@ var sq = { int x -> return sq(arg)+sq(3); """); code.parse().opto().typeCheck().GCM().localSched(); - assertEquals("Stop[ return (sq( 3)+sq( arg)); return (Parm_x(sq,int,3,arg)*x); ]", code._stop.toString()); + assertEquals("Stop[ return (#2+#2); return (Parm_x(sq,int)*x); ]", code._stop.toString()); assertEquals("13", Eval2.eval(code, 2)); } @@ -115,7 +116,7 @@ var fcn = arg ? { int x -> x*x; } : { int x -> x+x; }; return fcn(3); """); code.parse().opto(); - assertEquals("Stop[ return Phi(Region,{ int -> int #1},{ int -> int #2})( 3); return (Parm_x($fun,int,3)*x); return (Parm_x($fun,int,3)<<1); ]", code._stop.toString()); + assertEquals("Stop[ return #2; return (Parm_x($fun1,int,3)*x); return (Parm_x($fun2,int,3)<<1); ]", code._stop.toString()); assertEquals("6", Eval2.eval(code, 0)); assertEquals("9", Eval2.eval(code, 1)); } @@ -125,7 +126,7 @@ return fcn(3); public void testFcn5() { CodeGen code = new CodeGen("val fact = { int x -> x <= 1 ? 1 : x*fact(x-1); }; return fact(arg);"); code.parse().opto().typeCheck(); - assertEquals("Stop[ return fact( arg); return Phi(Region,1,(Parm_x(fact,int,arg,(x-1))*fact( Sub))); ]", code._stop.toString()); + assertEquals("Stop[ return #2; return Phi(Region,1,(Parm_x(fact,int,arg,(x-1))*#2)); ]", code._stop.toString()); assertEquals( "1", Eval2.eval(code, 0)); assertEquals( "1", Eval2.eval(code, 1)); assertEquals( "2", Eval2.eval(code, 2)); @@ -175,7 +176,7 @@ for(;;) { } """); code.parse().opto().typeCheck().GCM().localSched(); - assertEquals("Stop[ return x( Phi(Loop,arg,x( 3))); return Parm_i(x,int,3,Phi_arg); ]", code._stop.toString()); + assertEquals("Stop[ return #2; return Parm_i(x,int); ]", code._stop.toString()); assertEquals("3", Eval2.eval(code, 0)); } @@ -346,7 +347,7 @@ if (i2i) return i2i(arg); return f2f(o)(1); """); code.parse().opto().typeCheck().GCM().localSched(); - assertEquals("Stop[ return Phi(Region,o( arg),o( 1)); return Parm_i(o,int,arg,1); ]", code._stop.toString()); + assertEquals("Stop[ return Phi(Region,#2,#2); return Parm_i(o,int); ]", code._stop.toString()); assertEquals("1", Eval2.eval(code, 2)); } diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter19Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter19Test.java index a4857a01..bb7e8716 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter19Test.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter19Test.java @@ -1,9 +1,11 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; +import org.junit.Ignore; import org.junit.Test; +import static com.seaofnodes.simple.Main.PORTS; import static org.junit.Assert.*; import static org.junit.Assert.fail; -import org.junit.Ignore; public class Chapter19Test { @@ -55,107 +57,107 @@ val _hashCodeString = { String self -> String !s = new String { cs = new u8[17]; }; s.cs[0] = 67; // C s.cs[1] = 108; // l -hashCode(s);"""); +hashCode(s)+hashCode(s);"""); code.parse().opto().typeCheck().GCM().localSched(); - assertEquals("Stop[ return Phi(Region,123456789,Phi(Loop,0,(.[]+((Phi_hash<<5)-Phi_hash)))); return Phi(Region,1,0,0,1); ]", code._stop.toString()); - assertEquals("-2449306563677080489", Eval2.eval(code, 2)); + assertEquals("Stop[ return (#2+#2); return Phi(Region,1,0,0,1); return Phi(Region,._hashCode,Phi(Region,123456789,Phi(Loop,0,(.[]+((Phi_hash<<5)-Phi_hash))))); ]", code._stop.toString()); + assertEquals("-4898613127354160978", Eval2.eval(code, 2)); } @Test public void testBasic0() { - CodeGen code = new CodeGen("return 0;").parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return 0;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return 0;", code._stop.toString()); } @Test public void testBasic1() { - CodeGen code = new CodeGen("return arg+1;").parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg+1;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (inc,arg);", code._stop.toString()); } @Test public void testBasic2() { - CodeGen code = new CodeGen("return -17;").parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return -17;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return -17;", code._stop.toString()); } @Test public void testBasic3() { - CodeGen code = new CodeGen("return arg==1;").parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg==1;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (set==,(cmp,arg));", code._stop.toString()); } @Test public void testBasic4() { - CodeGen code = new CodeGen("return arg<<1;").parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg<<1;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (shli,arg);", code._stop.toString()); } @Test public void testBasic5() { - CodeGen code = new CodeGen("return arg >> 1;").parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg >> 1;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (sari,arg);", code._stop.toString()); } @Test public void testBasic6() { - CodeGen code = new CodeGen("return arg >>> 1;").parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg >>> 1;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (shri,arg);", code._stop.toString()); } @Test public void testBasic7() { - CodeGen code = new CodeGen("return arg / 2;").parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg / 2;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (div,arg,2);", code._stop.toString()); } @Test public void testBasic8() { - CodeGen code = new CodeGen("return arg * 6;").parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg * 6;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (muli,arg);", code._stop.toString()); } @Test public void testBasic9() { - CodeGen code = new CodeGen("return arg & 2;").parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg & 2;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (andi,arg);", code._stop.toString()); } @Test public void testBasic10() { - CodeGen code = new CodeGen("return arg | 2;").parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg | 2;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (ori,arg);", code._stop.toString()); } @Test public void testBasic11() { - CodeGen code = new CodeGen("return arg ^ 2;").parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg ^ 2;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (xori,arg);", code._stop.toString()); } @Test public void testBasic12() { - CodeGen code = new CodeGen("return arg + 2.0;").parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg + 2.0;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (addf,(i2f8,arg),2.0f);", code._stop.toString()); } @Test public void testBasic13() { - CodeGen code = new CodeGen("return arg - 2.0;").parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg - 2.0;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (subf,(i2f8,arg),2.0f);", code._stop.toString()); } @Test public void testBasic14() { - CodeGen code = new CodeGen("return arg * 2.0;").parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg * 2.0;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (mulf,(i2f8,arg),2.0f);", code._stop.toString()); } @Test public void testBasic15() { - CodeGen code = new CodeGen("return arg / 2.0;").parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); - assertEquals("return (divf,(i2f8,arg),2.0f);", code._stop.toString()); + CodeGen code = new CodeGen("return arg / 2.0;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + assertEquals("return (mulf,(i2f8,arg),0.5f);", code._stop.toString()); } @Test @@ -164,7 +166,7 @@ hashCode(s);"""); """ int arg1 = arg + 1; return arg1 / arg;"""); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (div,(inc,arg),arg);", code._stop.toString()); } @@ -175,7 +177,7 @@ return arg1 / arg;"""); int arg1 = arg + 1; return arg1 * arg; """); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (mul,(inc,arg),arg);", code._stop.toString()); } @@ -185,7 +187,7 @@ return arg1 * arg; int a = arg; return a + 2.0; """ - ).parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + ).parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (addf,(i2f8,arg),2.0f);", code._stop.toString()); } @@ -200,7 +202,7 @@ else { a = arg-3; } return a;"""); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return Phi(Region,(addi,arg),(addi,arg));", code.print()); } @@ -215,7 +217,7 @@ if( arg==1 ) else a=b+1; return a+b;"""); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (add,(add,Phi(Region,(shli,arg),arg),arg),Phi(Region,4,5));", code.print()); } @@ -227,7 +229,7 @@ int sum=0; for( int i=0; i<arg; i++ ) sum += i; return sum;"""); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return Phi(Loop,0,(add,Phi_sum,Phi(Loop,0,(inc,Phi_i))));", code.print()); } @@ -237,42 +239,42 @@ return sum;"""); """ struct S { int a; S? c; }; return new S;"""); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return S;", code.print()); } @Test public void testLea1() { CodeGen code = new CodeGen("int x = arg/3; return arg+x+7;"); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (lea,arg,(div,arg,3));", code.print()); } @Test public void testLea2() { CodeGen code = new CodeGen("int x = arg/3; return arg+x*4+7;"); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (lea,arg,(div,arg,3));", code.print()); } @Test public void testLea3() { CodeGen code = new CodeGen("int x = arg/3; return x*4+arg;"); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (lea,arg,(div,arg,3));", code.print()); } @Test public void testAlloc2() { CodeGen code = new CodeGen("int[] !xs = new int[3]; xs[arg]=1; return xs[arg&1];"); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return .[];", code.print()); } @Test public void testAlloc3() { CodeGen code = new CodeGen("int[] !xs = new int[3]; xs[arg]=1; return xs[arg&1]+3;"); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return .[];", code.print()); } @@ -289,7 +291,7 @@ for( int i=0; i<ary#-1; i++ ) ary[i+1] += ary[i]; return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6 """); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return .[];", code.print()); } @@ -304,7 +306,7 @@ for( int i=0; i<A#; i++ ) for( int i=0; i<A#; i++ ) B[i] += A[i]; """); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return 0;", code.print()); } @@ -317,7 +319,7 @@ for( int i=0; i<A#; i++ ) A[i]++; return A[1]; """); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return .[];", code.print()); } @@ -337,8 +339,8 @@ val sqrt = { flt x -> flt farg = arg; return sqrt(farg); """); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); - assertEquals("return Phi(Loop,(i2f8,arg),(divf,(addf,(divf,i2f8,Phi_guess),Phi_guess),2.0f));", code.print()); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + assertEquals("return Phi(Loop,(i2f8,arg),(mulf,(addf,(divf,i2f8,Phi_guess),Phi_guess),0.5f));", code.print()); }; @@ -368,7 +370,7 @@ for( int j=0; j<nprimes; j++ ) rez[j] = primes[j]; return rez; """); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return [int];", code.print()); //assertEquals("int[ 2,3,5,7,11,13,17,19]",Eval2.eval(code, 20)); } @@ -381,8 +383,8 @@ return rez; val fcn = arg ? { int x -> x*x; } : { int x -> x+x; }; return fcn(2)*10 + fcn(3); """); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); - assertEquals("Stop[ return (add,Phi(Region,{ int -> int #1},{ int -> int #2})( 3),(muli,Phi_( 2))); return (mul,Parm_x($fun,int,3,2),x); return (shli,Parm_x($fun,int,3,2)); ]", code.print()); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + assertEquals("Stop[ return (add,#2,(muli,#2)); return (mul,Parm_x($fun1,int),x); return (shli,Parm_x($fun2,int)); ]", code.print()); } @Test @@ -392,7 +394,7 @@ return fcn(2)*10 + fcn(3); val sq = { int x -> x*x; }; return sq(arg) + sq(3); """); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); - assertEquals("Stop[ return (add,sq(),sq()); return (mul,Parm_x(sq,int,3,arg),x); ]", code.print()); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + assertEquals("Stop[ return (add,#2,#2); return (mul,Parm_x(sq,int),x); ]", code.print()); } } diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter20Test.java index 48458f95..008f5e0c 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter20Test.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter20Test.java @@ -1,17 +1,196 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.print.ASMPrinter; +import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; +import static com.seaofnodes.simple.Main.PORTS; import static org.junit.Assert.*; public class Chapter20Test { @Test public void testJig() { - CodeGen code = new CodeGen(""" -return 0; -"""); + CodeGen code = new CodeGen("return 0;"); code.parse().opto().typeCheck(); assertEquals("return 0;", code._stop.toString()); assertEquals("0", Eval2.eval(code, 2)); } + + static void testCPU( String src, String cpu, String os, int spills, String stop ) { + CodeGen code = new CodeGen(src); + code.parse().opto().typeCheck().instSelect(PORTS,cpu,os).GCM().localSched().regAlloc().encode(); + int delta = spills>>3; + if( delta==0 ) delta = 1; + assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta); + if( stop != null ) + assertEquals(stop, code._stop.toString()); + } + + private static void testAllCPUs( String src, int spills, String stop ) { + testCPU(src,"x86_64_v2", "SystemV",spills,stop); + testCPU(src,"riscv" , "SystemV",spills,stop); + testCPU(src,"arm" , "SystemV",spills,stop); + } + + @Test public void testAlloc0() { + testAllCPUs("return new u8[arg];", 1, "return [u8];"); + } + + @Test public void testBasic1() { + String src = "return arg | 2;"; + testCPU(src,"x86_64_v2", "SystemV",1,"return (ori,mov(arg));"); + testCPU(src,"riscv" , "SystemV",0,"return ( arg | #2 );"); + testCPU(src,"arm" , "SystemV",0,"return (ori,arg);"); + } + + @Test + public void testNewtonInteger() { + String src = +""" +// Newtons approximation to the square root +val sqrt = { int x -> + int guess = x; + while( 1 ) { + int next = (x/guess + guess)/2; + if( next == guess ) return guess; + guess = next; + } +}; +return sqrt(arg) + sqrt(arg+2); +"""; + testCPU(src,"x86_64_v2", "SystemV",26,null); + testCPU(src,"riscv" , "SystemV",19,null); + testCPU(src,"arm" , "SystemV",26,null); + } + + @Test + public void testNewtonFloat() { + String src = +""" +// Newtons approximation to the square root +val sqrt = { flt x -> + flt guess = x; + while( 1 ) { + flt next = (x/guess + guess)/2; + if( next == guess ) return guess; + guess = next; + } +}; +flt farg = arg; +return sqrt(farg) + sqrt(farg+2.0); +"""; + testCPU(src,"x86_64_v2", "SystemV",25,null); + testCPU(src,"riscv" , "SystemV",21,null); + testCPU(src,"arm" , "SystemV",18,null); + } + + @Test + public void testAlloc2() { + String src = "int[] !xs = new int[3]; xs[arg]=1; return xs[arg&1];"; + testAllCPUs(src,1,"return .[];"); + } + + @Test + public void testArray1() { + String src = +""" +int[] !ary = new int[arg]; +// Fill [0,1,2,3,4,...] +for( int i=0; i<ary#; i++ ) + ary[i] = i; +// Fill [0,1,3,6,10,...] +for( int i=0; i<ary#-1; i++ ) + ary[i+1] += ary[i]; +return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6 +"""; + testCPU(src,"x86_64_v2", "SystemV",3,"return .[];"); + testCPU(src,"riscv" , "SystemV",1,"return (add,.[],(mul,.[],1000));"); + testCPU(src,"arm" , "SystemV",1,"return (add,.[],(muli,.[]));"); + } + + @Test + public void testString() { + String src = """ +struct String { + u8[] cs; + int _hashCode; +}; + +val equals = { String self, String s -> + if( self == s ) return true; + if( self.cs# != s.cs# ) return false; + for( int i=0; i< self.cs#; i++ ) + if( self.cs[i] != s.cs[i] ) + return false; + return true; +}; + +val hashCode = { String self -> + self._hashCode + ? self._hashCode + : (self._hashCode = _hashCodeString(self)); +}; + +val _hashCodeString = { String self -> + int hash=0; + if( self.cs ) { + for( int i=0; i< self.cs#; i++ ) + hash = hash*31 + self.cs[i]; + } + if( !hash ) hash = 123456789; + return hash; +}; + +String !s = new String { cs = new u8[17]; }; +s.cs[0] = 67; // C +s.cs[1] = 108; // l +hashCode(s); +"""; + testCPU(src,"x86_64_v2", "SystemV",18,null); + testCPU(src,"riscv" , "SystemV", 3,null); + testCPU(src,"arm" , "SystemV", 3,null); + } + + @Test + public void testCast() { + String src = "struct Bar { int x; }; var b = arg ? new Bar; return b ? b.x++ + b.x++ : -1;"; + testAllCPUs(src,0,null); + } + + @Test + public void testFltArg() { + String src = "return {int i, flt f, int j->return i+f+j;};"; + testAllCPUs(src,0,null); + } + + @Test + public void testFlags1() { + String src = """ +bool b1 = arg == 1; +bool b2 = arg == 2; +if (b2) if (b1) return 1; +if (b1) return 2; +return 0; +"""; + testCPU(src,"x86_64_v2", "SystemV",0,"return Phi(Region,1,2,0);"); + testCPU(src,"riscv" , "SystemV",0,"return Phi(Region,1,2,0);"); + testCPU(src,"arm" , "SystemV",0,"return Phi(Region,1,2,0);"); + } + + @Test + public void testFlags2() { + String src = """ +bool b1 = arg == 1; +while (arg > 0) { + arg--; + if (b1) arg--; +} +return arg; +"""; + testCPU(src,"x86_64_v2", "SystemV",3,null); + testCPU(src,"riscv" , "SystemV",2,null); + testCPU(src,"arm" , "SystemV",2,null); + } } diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Eval2.java b/chapter20/src/test/java/com/seaofnodes/simple/Eval2.java index 6ae8427c..ecea97dd 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/Eval2.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/Eval2.java @@ -1,7 +1,9 @@ package com.seaofnodes.simple; -import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.print.IRPrinter; +import com.seaofnodes.simple.type.*; import java.util.*; public abstract class Eval2 { diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Main.java b/chapter20/src/test/java/com/seaofnodes/simple/Main.java index 523bd306..f3a6784c 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/Main.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/Main.java @@ -1,9 +1,11 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import java.nio.file.Files; import java.nio.file.Path; public class Main { + public static final String PORTS = "com.seaofnodes.simple.node.cpus"; // Compile and run a simple program public static void main( String[] args ) throws Exception { // First arg is file, 2nd+ args are program args diff --git a/chapter20/src/test/java/com/seaofnodes/simple/MergeSortTest.java b/chapter20/src/test/java/com/seaofnodes/simple/MergeSortTest.java index d48449d5..c83fe089 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/MergeSortTest.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/MergeSortTest.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -8,7 +9,7 @@ public class MergeSortTest { @Test public void testMergeSort() { - CodeGen code = new CodeGen( + String src = """ // based on the top-down version from https://en.wikipedia.org/wiki/Merge_sort @@ -56,10 +57,11 @@ for (int i = 0; i < a#; i++) merge_sort(a, b, a#); return a; -"""); - code.parse().opto(); - assertEquals("Stop[ return [int]; return 0; ]", code._stop.toString()); - assertEquals("int[ 1,2,3,4,5,6,7,8,9,10,11]", Eval2.eval(code, 11)); +"""; + Chapter20Test.testCPU(src,"x86_64_v2", "SystemV",46,"Stop[ return mov(mov([int])); return 0; ]"); + Chapter20Test.testCPU(src,"riscv" , "SystemV",36,"Stop[ return mov(mov([int])); return 0; ]"); + Chapter20Test.testCPU(src,"arm" , "SystemV",37,"Stop[ return mov(mov([int])); return 0; ]"); +//assertEquals("int[ 1,2,3,4,5,6,7,8,9,10,11]", Eval2.eval(code, 11)); } } diff --git a/chapter20/src/test/java/com/seaofnodes/simple/SchedulerTest.java b/chapter20/src/test/java/com/seaofnodes/simple/SchedulerTest.java index d50c302a..ca29bf84 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/SchedulerTest.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/SchedulerTest.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.evaluator.Evaluator; import com.seaofnodes.simple.node.StopNode; import org.junit.Assert; diff --git a/chapter20/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java b/chapter20/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java index f7e0c4ad..30a548ad 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java @@ -2,7 +2,7 @@ package com.seaofnodes.simple.fuzzer; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.Eval2; -import com.seaofnodes.simple.CodeGen; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.Node; import java.util.ArrayList; import java.util.Objects; diff --git a/chapter20/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java b/chapter20/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java index 0781ad67..669a1e6a 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java @@ -1,7 +1,7 @@ package com.seaofnodes.simple.fuzzer; -import com.seaofnodes.simple.CodeGen; import com.seaofnodes.simple.IterPeeps; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.node.StopNode; import com.seaofnodes.simple.type.TypeInteger; |
