diff options
18 files changed, 313 insertions, 57 deletions
diff --git a/ASimpleReply.md b/ASimpleReply.md new file mode 100644 index 00000000..b208658f --- /dev/null +++ b/ASimpleReply.md @@ -0,0 +1,70 @@ + +A Simple Reply to https://www.reddit.com/r/Compilers/comments/1jjldhu/land_ahoy_leaving_the_sea_of_nodes/ + +Because reddit crashes when I try to post this. + +## prolog +I kept quiet for awhile on this one, as any response I make is going to sound either defensive or mis-aligned, and in some sense that's right. Also chat/email/IRC are terrible places to hold in-depth tech discussions. But I've had quite a number of people rise up to the defense of SoN both here and in other forums, so I finally decided to join the fray. :-P +Looking at the design choices, it seems clear to me that V8 hobbled itself from the start. V8 made a number of choices leading to a number of problems which in my mind have obvious fixes. + +## Effects +*Equivalence class aliasing* is a powerful tool, and a key part of SoN. It appears to me that V8 threw this away from the start, and then spent the next several years dealing with its lack. + +#### "Too many nodes on the effects chain" +Equivalence class aliasing comes for free, literally free, in any strongly typed language. It comes directly out of the parser (be it JVM bytecodes, Simple, C), and need not be ever touched again. With it, Java/Simple/C have very sparse and clean effects chains. Definitely not "too many". + +JS is *not* strongly typed, so there's an engineering tradeoff being made. I was under the belief that V8 (and other JS engines) used *specialization* aggressively to bring back strong typing in code regions; in those areas again ECA should just work directly. + +#### "Managing the effect and control chains manually is hard" +Equivalence class aliasing is *directly* represented in SoN, as just normal nodes and edges. There's no special handling for it anywhere, the normal graph maintenance functions manipulate those nodes and edges just the same as they do anything else. Two nodes that might alias directly have connecting edges, nodes that do not - do not, and hence *cannot* be influenced about what happens in other alias classes. +As result of those two observations (free to obtain from strong typing, normal node/edge maintenance) this form of effect handling is been vastly simpler to build and maintain, and vastly faster to use than any other compiled I've ever worked on - and that's quite a few. 25yrs of C2 on Java shows that it can be very effective for performance and plenty fast in a JIT. + +Me thinks V8 did itself no favors here. + +## Control on Effect nodes +Guard tests for correctness: either null checks on fields or range checks on arrays. Perhaps for JS (and certainly C2/Java) also some type/specialization checks. C2 also adds a control edge to guard effect nodes. These then get optimized away (controlling edge removed) in one of two generic ways: + +(1) *Lattice*: For null and type checks there is a Node `Type` which includes things like null-ness or sub-classing. Not-null pointers are known not-null based on the type; null checks against them constant-fold, etc. (The `Types` form a *lattice* with strong properties like symmetric, complete, bounded (ranked), and *meet* is commutative and associative.) + +After a guarding test we can lift the type - using a Cast tied to the guard test, where the Cast does a lattice *join* (not *meet*). Effect nodes that need that guard pick up the Cast on the base pointer - not the control edge. If, later, we prove the Cast's *join* is useless e.g. `_t.join(in(1)._type)==_t`, the Cast constant folds away, and the effect Node can now float more freely. This analysis (comes as part of the normal c-prop) removes > 90% of Java's casting needs. + +(2) The other generic test is range-checks, which start life as a direct control edge dependency. These generally do not trivially fold away - something with all constant sizes and array indices might, but mostly no. Then C2 runs *Range Check Elimination*, a special pass just for Java safe arrays, and removes dynamically nearly all of them. I would expect to see the same come from any serious safe-language optimization. + +## Debugging +Yeah, don't use graphs to debug. Use normal ASCII dumps like the ones being shown in this article. That's what *I* do anyways. Certainly I didn't have access to fast easy graph layouts back when C2 started, I just debugged it "the normal way". +Graphs are nice for intuition but they don't scale well. That being said I've seen more recent successes in getting better graphs-for-debug scenarios (don't use D3, sucks on cycles, dump to a file; file watcher picks up and refreshes window, keep graph window visible aside of editor, etc). Maybe I'll revisit this, but for now, for me, ASCII dumps rule. + +## Complex schedule +The Click thesis scheduler missed out on anti-dependencies. C2 - and then the easier to read Simple Chapter 10 corrected this. The algorithm is fast and simple enough, no more complex than e.g. the natural loop finding version of SCC. Source code is available from Simple. + +### Late arriving control flow from expanding small "diamonds" +Yup, same solution for C2; I kept things like `min` or `max` as single pure nodes. At some late point in the game they expanded. For things as trivial as `min` which does NOT need any internal registers - I expanded them in the encoding phase post RA. For more complex things, I expanded them just after Global Code Motion brought me back to an official CFG. + +In any case, this problem (and its solution) seem... trivial to me. Doesn't carry much weight on the IR choice argument; each IR has its issues all will need dozens of such "fixes". + +## Visitation order + +#### "Finding a good visitation order is hard" +Yes, so don't bother. Use a worklist! Hard guaranteed linear time to completion, fast and simple. Back-edges are needed and they are well worth the cost. I start C2 without them, couldn't get closure with a few passes, added backedges and everything got lots *faster*. Edge maintenance code deals with the work, so I don't have to think about it, its all cache-resident L1 hits. Fast, fast, fast. + +## Other things? + +#### "Dead code elimination" +free with backedges; basically when the backedge/ref-count drops to zero, recursively delete nodes. There's maybe 10 lines dedicated to this in Simple. + +#### "Hard to introduce new control flow" +Hello compiler writers? Sorry, can't hardly believe this one; editing a CFG is morally the same as editing the SoN, what's the issue? In the specific `min` case, the goal is to pick a place in the CFG to expand... which means a place needs to be picked, which for SoN usually means after Global Code Motion when "places" (e.g. classic CFG) is available again. + +#### "Hard to figure out what's inside a loop" +Again, Hello compiler writers? C2 certainly does lots of aggressive loop optimizations - which start by running a standard SCC, finding loop headers, walking the graph area constrained by the loop, producing a loop body for further manipulations... yada yada. Basically, I build loops and a loop tree via the Olde Fashioned method of running SCC. + +#### "Compiling is slow" +Compiling is fast. He-said-She-said. Neen-neener. Data? Perf discussions with facts? Apples to apples comparisons? No? So how about "compilation speed vs code quality didn't meet our goals"? + +#### "Cache-unfriendly" +Same (lack of) argument. Certainly when I did C2 I was highly focused on cache-friendly and based on the vast compile speedup I obtained over all competitions (at that time every AOT compiler, GCC, LLVM) I was largely successful. C2 compiles take far less footprint (both cache and memory) that my "competitors". Mostly C2 compiles, despite aggressive inlining, fit easily in L2, nearly always in L1. Getting this right is important, so I suspect other things happened to make it miss for V8. But also... no numbers from me either, so neen-neener again. + +## postlog +I'll point out most of the above discussion is things that surely made V8's life harder - and slower - and are things I never would have done. OTOH I've not done a JS compiler, so I have no facts here also. + +Cliff diff --git a/ASimpleReply/ASimpleReply2.md b/ASimpleReply/ASimpleReply2.md new file mode 100644 index 00000000..62f1a5ba --- /dev/null +++ b/ASimpleReply/ASimpleReply2.md @@ -0,0 +1,29 @@ +# A Simple Reply (2) + +Hi Dadals, we got to stop meeting like this. + +The whole reply chain: + +https://github.com/SeaOfNodes/Simple/blob/main/ASimpleReply/ASimpleReplyChain.md + +I find long, involved, multi-part discussions difficult to follow because +basically we're really having 8 unrelated technical discussions. I broke up my +replies so each part is easier to consider on its own. + + +# Reply - Using t effect chains based on equivalence classes + +# Reply - "Guard tests for correctness" + +# Reply - "Graphs are nice for intuition but they don't scale well." + +# Reply - "Use a worklist!" +I think we agree, at least in theory, here. + +# Reply - Late arriving control flow from expanding small "diamonds" + +# Reply - "Data? Perf discussions with facts? Apples to apples comparisons? No?" + +# Reply - Hard to introduce new control flow + +# Reply - It's easy in Simple diff --git a/ASimpleReply/ASimpleReplyChain.md b/ASimpleReply/ASimpleReplyChain.md new file mode 100644 index 00000000..2d53a8ee --- /dev/null +++ b/ASimpleReply/ASimpleReplyChain.md @@ -0,0 +1,31 @@ +# A Simple Reply ... Chain + +Hi Dadals, we got to stop meeting like this. + +### Original message: + +https://v8.dev/blog/leaving-the-sea-of-nodes +https://www.reddit.com/r/Compilers/comments/1jjldhu/land_ahoy_leaving_the_sea_of_nodes/ + +### Cliff's Reply: + +https://github.com/SeaOfNodes/Simple/blob/main/ASimpleReply.md + +### Dadal's Reply: + +https://gist.github.com/DadaIsCrazy/8ce07d409f8f74bad7b36e74b77a3129 + +### Cliff's Reply(2): + +(first, this reply-chain to gather all the parts) + +https://github.com/SeaOfNodes/Simple/blob/main/ASimpleReply/ASimpleReply2.md + + + +[room for more conversations] + +No conversations here, its just the chain. + +Cliff + diff --git a/chapter03/README.md b/chapter03/README.md index 35075e7e..ddb9fafa 100644 --- a/chapter03/README.md +++ b/chapter03/README.md @@ -167,10 +167,12 @@ scope and deeper indices representing more nested scopes. Popping returns the current scope and moves to the outer one. There is a direct one-to-one mapping between each index and its corresponding scope. -The first nested scope(04-08) +The first nested scope(04-08): +  -The second nested scope(09-13) +The second nested scope(09-13): +  - In both cases the index which represents the deepness of the scope is 1. @@ -192,4 +194,4 @@ The graph for above, is displayed below.  The return node has the constant `8` as its data value! When the parsing ends -and the scope at level 0 is removed, only the `return 8` remains.
\ No newline at end of file +and the scope at level 0 is removed, only the `return 8` remains. diff --git a/chapter19/README.md b/chapter19/README.md index 87a4c3a6..a866876d 100644 --- a/chapter19/README.md +++ b/chapter19/README.md @@ -1,4 +1,4 @@ -Ch# Chapter 19: Instruction Selection and Portable Compilation +# Chapter 19: Instruction Selection and Portable Compilation You can also read [this chapter](https://github.com/SeaOfNodes/Simple/tree/linear-chapter19) 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-chapter18...linear-chapter19) it to the previous chapter. diff --git a/chapter20/Makefile b/chapter20/Makefile index f96cfea3..c5d8fa71 100644 --- a/chapter20/Makefile +++ b/chapter20/Makefile @@ -87,7 +87,7 @@ build/release/simple.jar: $(main_classes) $(test_classes) # Launch viewer view: $(main_classes) @echo "viewing " - @$(JVM) com.seaofnodes.simple.JSViewer + @$(JVM) com.seaofnodes.simple.print.JSViewer .PHONY: clean clean: diff --git a/chapter20/src/main/java/com/seaofnodes/simple/print/JSViewer.java b/chapter20/src/main/java/com/seaofnodes/simple/print/JSViewer.java index 76c64c71..a2b674c9 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/print/JSViewer.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/print/JSViewer.java @@ -72,7 +72,7 @@ public class JSViewer implements AutoCloseable { public static void show() { if( SERVER!=null && SHOW ) _show(); } private static void _show() { // Skip util we at least get the Parse object made - if( CODE._phase==null || CODE._phase.ordinal() < CodeGen.Phase.Parse.ordinal() ) + if( CODE._phase == null ) return; boolean midParse = CODE._phase == CodeGen.Phase.Parse; Stack<ScopeNode> xScopes = midParse ? CODE.P._xScopes : null; diff --git a/chapter21/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/chapter21/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java index 64ea8201..f5d32dd2 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java @@ -308,16 +308,21 @@ public class CodeGen { // --------------------------- // Encoding public int _tEncode; + public boolean _JIT; public Encoding _encoding; - public CodeGen encode() { + public CodeGen encode(boolean jit) { assert _phase == Phase.RegAlloc; _phase = Phase.Encoding; long t0 = System.currentTimeMillis(); _encoding = new Encoding(this); + _JIT = jit; _encoding.encode(); _tEncode = (int)(System.currentTimeMillis() - t0); return this; } + public CodeGen encode() { + return encode(false); + } // Encoded binary, no relocation info public byte[] binary() { return _encoding.bits(); } diff --git a/chapter21/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java b/chapter21/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java index 223911ba..62bcfcde 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java @@ -228,6 +228,30 @@ public class ElfFile { } } + public final HashMap<Type, Symbol> _bigCons = new HashMap<>(); + private void encodeConstants(SymbolSection symbols, DataSection rdata) { + int cnt = 0; + for (Map.Entry<Node,Type> e : _code._encoding._bigCons.entrySet()) { + if (_bigCons.get(e.getValue()) != null) { + continue; + } + + Symbol glob = new Symbol("GLOB$"+cnt, rdata._index, SYM_BIND_GLOBAL, SYM_TYPE_FUNC); + glob._value = rdata._contents.size(); + symbols.push(glob); + + Type t = e.getValue(); + if ( t instanceof TypeFloat tf ) { + write8(rdata._contents, Double.doubleToLongBits(tf._con)); + } else { + throw Utils.TODO(); + } + + glob._size = rdata._contents.size() - glob._value; + _bigCons.put(e.getValue(), glob); + cnt++; + } + } public void export(String fname) throws IOException { DataSection strtab = new DataSection(".strtab", 3 /* SHT_SYMTAB */); // first byte is reserved for an empty string @@ -253,6 +277,8 @@ public class ElfFile { // populate function symbols encodeFunctions(symbols, text); + // populate big constants + encodeConstants(symbols, rdata); int idx = 1; for( Section s : _sections ) { @@ -267,8 +293,9 @@ public class ElfFile { int num = 1; for( Symbol s : symbols._loc ) s._index = num++; + // extra space for .rela.text int start_global = symbols._loc.size(); - for(Symbol a: symbols._symbols) + for(Symbol a: symbols._symbols) { a._index = start_global++; // create .text relocations @@ -276,16 +303,31 @@ public class ElfFile { for( Node n : _code._encoding._externals.keySet()) { int nid = n._nid; String extern = _code._encoding._externals.get(n); - if(!extern.isEmpty()) throw Utils.TODO(); - // Todo: fix this up later when external functions will be in TU - //int sym_id = _funcs.get(extern)._index; - int sym_id = 0; + + Symbol sym = new Symbol(extern, 0, SYM_BIND_GLOBAL, SYM_TYPE_NOTYPE); + sym._index = start_global++; + symbols.push(sym); + + int offset = _code._encoding._opStart[nid] + _code._encoding._opLen[nid] - 4; + + // u64 offset + write8(text_rela._contents, offset); + // u64 info + write8(text_rela._contents, ((long)sym._index << 32L) | 4L /* PLT32 */); + // i64 addend + write8(text_rela._contents, -4); + } + + // relocations to constants + for (Map.Entry<Node,Type> e : _code._encoding._bigCons.entrySet()) { + int nid = e.getKey()._nid; + int sym_id = _bigCons.get(e.getValue())._index; int offset = _code._encoding._opStart[nid] + _code._encoding._opLen[nid] - 4; // u64 offset write8(text_rela._contents, offset); // u64 info - write8(text_rela._contents, ((long)sym_id << 32L) | 4L /* PLT32 */); + write8(text_rela._contents, ((long)sym_id << 32L) | 2L /* PC32 */); // i64 addend write8(text_rela._contents, -4); } @@ -295,7 +337,7 @@ public class ElfFile { text_rela._info = text._index; pushSection(text_rela); - Symbol sym = new Symbol(text_rela._name, idx++, SYM_BIND_LOCAL, SYM_TYPE_SECTION); + Symbol sym = new Symbol(text_rela._name, num, SYM_BIND_LOCAL, SYM_TYPE_SECTION); sym._name_pos = text_rela._name_pos; sym._size = text_rela.size(); symbols.push(sym); diff --git a/chapter21/src/main/java/com/seaofnodes/simple/codegen/Encoding.java b/chapter21/src/main/java/com/seaofnodes/simple/codegen/Encoding.java index 0f9aac89..f90ce96a 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/codegen/Encoding.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/codegen/Encoding.java @@ -151,9 +151,14 @@ public class Encoding { // Record opcode start and length. writeEncodings(); + // ELF handles big constants, they + // are accessed by RIP-relative addressing. + + if(_code._JIT) writeConstantPool(true); // Short-form RIP-relative support: replace long encodings with short // encodings and compact the code, changing all the offsets. compactShortForm(); + } // Basic block layout: invert branches to keep blocks in-order; insert diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java index 636c5a5f..8d4a2f71 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java @@ -4,7 +4,7 @@ import com.seaofnodes.simple.SB; import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -// corresponds to slt (and NOT sltu, slti, sltiu) +// corresponds to slt sltu public class SetRISC extends MachConcreteNode implements MachNode { final boolean _unsigned; public SetRISC( Node cmp, boolean unsigned ) { super(cmp); _unsigned=unsigned; } @@ -12,7 +12,7 @@ public class SetRISC extends MachConcreteNode implements MachNode { @Override public RegMask regmap(int i) { return riscv.RMASK; } @Override public RegMask outregmap() { return riscv.WMASK; } @Override public void encoding( Encoding enc ) { - riscv.r_type(enc,this,_unsigned ? 0b011 : 0x010,0); + riscv.r_type(enc,this,_unsigned ? 0b011 : 0b010,0); } @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" < ").p(code.reg(in(2))); diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java index f6f3474f..921fa336 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java @@ -15,13 +15,14 @@ public class CmpMemX86 extends MemOpX86 { @Override public void encoding( Encoding enc ) { // REX.W + 81 /7 id CMP r/m64, imm32 | REX.W + 39 /r CMP r/m64,r64 // CMP [mem], imm32 + short ptr = enc.reg(ptr()); short idx = enc.reg(idx()); short src = enc.reg(val()); enc.add1(x86_64_v2.rex(src, ptr, idx)); // opcode varies by immediate - enc.add1( src == -1 ? 0x39 : 0x81 ); + enc.add1( src == -1 ? 0x81 : 0x39); // includes modrm x86_64_v2.indirectAdr(_scale, idx, ptr, _off, src == -1 ? 7 : src, enc); diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java index 708269bd..c9df7b99 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java @@ -4,7 +4,7 @@ import com.seaofnodes.simple.SB; import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -public class FltX86 extends ConstantNode implements MachNode, RIPRelSize { +public class FltX86 extends ConstantNode implements MachNode, RIPRelSize{ private byte _encSize; FltX86(ConstantNode con ) { super(con); } @Override public String op() { return "ld8"; } diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java index d6c977a5..6d6aa89c 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java @@ -1,12 +1,39 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Encoding; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.*; + // Arithmetic Right Shift -public class SarIX86 extends ImmX86 { - SarIX86( Node add, int imm ) { super(add,imm); assert x86_64_v2.imm8(imm); } +public class SarIX86 extends MachConcreteNode implements MachNode { + final int _imm; + SarIX86( Node sari, int imm ) { super(sari); assert x86_64_v2.imm8(imm); _inputs.pop(); _imm = imm; } @Override public String op() { return "sari"; } @Override public String glabel() { return ">>"; } - @Override int opcode() { return 0xC1; } - @Override int mod() { return 7; } + int opcode() { return 0xC1; } + int mod() { return 7; } + + @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + + @Override public void encoding(Encoding enc) { + short dst = enc.reg(this); // Also src1 + enc.add1(x86_64_v2.rex(0, dst, 0)); + enc.add1( opcode()); + + enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, mod(), dst) ); + + // immediate(4 bytes) 32 bits or (1 byte)8 bits + if( x86_64_v2.imm8(_imm) ) enc.add1(_imm); + else enc.add4(_imm); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm); + } } diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java index 35818a91..c53e0ea9 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java @@ -2,10 +2,38 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.node.Node; -public class ShlIX86 extends ImmX86 { - ShlIX86( Node add, int imm ) { super(add,imm); } +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; + +public class ShlIX86 extends MachConcreteNode implements MachNode { + final int _imm; + ShlIX86( Node shli, int imm ) { + super(shli); + assert x86_64_v2.imm8(imm); + _inputs.pop(); // Pop constant input off + _imm = imm; + } + public int opcode() { return 0xC1; } + public int mod() { return 4; } + + @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + @Override public String op() { return "shli"; } @Override public String glabel() { return "<<"; } - @Override int opcode() { return 0xC1; } - @Override int mod() { return 4; } + @Override public void encoding(Encoding enc) { + short dst = enc.reg(this); // Also src1 + enc.add1(x86_64_v2.rex(0, dst, 0)); + enc.add1( opcode()); + + enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, mod(), dst) ); + + // immediate(4 bytes) 32 bits or (1 byte)8 bits + if( x86_64_v2.imm8(_imm) ) enc.add1(_imm); + else enc.add4(_imm); + } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm); + } } diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java index 0e96962b..c79b8817 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java @@ -2,10 +2,33 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.node.Node; -public class ShrIX86 extends ImmX86 { - ShrIX86( Node add, int imm ) { super(add,imm); } +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; + +public class ShrIX86 extends MachConcreteNode implements MachNode { + final int _imm; + ShrIX86( Node shri, int imm ) { super(shri); assert x86_64_v2.imm8(imm); _inputs.pop(); _imm = imm; } @Override public String op() { return "shri"; } @Override public String glabel() { return ">>>"; } - @Override int opcode() { return 0xC1; } - @Override int mod() { return 5; } + int opcode() { return 0xC1; } + int mod() { return 5; } + + @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + + @Override public void encoding(Encoding enc) { + short dst = enc.reg(this); // Also src1 + enc.add1(x86_64_v2.rex(0, dst, 0)); + enc.add1( opcode()); + + enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, mod(), dst) ); + + // immediate(4 bytes) 32 bits or (1 byte)8 bits + if( x86_64_v2.imm8(_imm) ) enc.add1(_imm); + else enc.add4(_imm); + } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm); + } } diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java index 39048f2d..bf8f92ad 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -146,31 +146,24 @@ public class x86_64_v2 extends Machine { public static void indirectAdr( int scale, short index, short base, int offset, int reg, Encoding enc ) { // Assume indirect assert 0 <= base && base < 16; - assert index != RSP; + assert -1 <= index && index < 16 && index != RSP; MOD mod = MOD.INDIRECT; // is 1 byte enough or need more? if( offset != 0 ) mod = imm8(offset) - ? MOD.INDIRECT_disp8 - : MOD.INDIRECT_disp32; + ? MOD.INDIRECT_disp8 + : MOD.INDIRECT_disp32; - // needs to pick optimal displacement mod if we want to encode base - // when we only have base = RSP then we still need displacement added so ti becomes: [RSP+RSP*1+0x0] - // RSP as index in this case is special and refers to: [base + disp8] - if( mod == MOD.INDIRECT && (base == RBP || base == R13) ) + if( mod == MOD.INDIRECT && (base == RBP || base == R13) ) { mod = MOD.INDIRECT_disp8; + } else if( index == -1 && (base == RSP || base == R12) ) { + index = RSP; + } - // special encoding for [base +offset] if( index == -1 ) { - // Case for mov reg, [disp] (load) - enc.add1(modrm(offset == 0 ? MOD.INDIRECT_disp8 : mod, reg, x86_64_v2.RSP)); - enc.add1(sib(scale, x86_64_v2.RSP, base)); - // special case - // still add zero it is part of SIB byte - if(offset == 0) enc.add1(offset); + enc.add1(modrm(mod, reg, base)); } else { - // rsp is hard-coded here(0x04) enc.add1(modrm(mod, reg, x86_64_v2.RSP)); enc.add1(sib(scale, index, base)); } @@ -592,20 +585,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 SarIX86(sar, (int)(ti.value() & 0x03f) ); + if( sar.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm8(ti.value())) + return new SarIX86(sar, (int)(ti.value() & 0xff) ); return new SarX86(sar); } private Node shl(ShlNode shl) { - if( shl.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti ) - return new ShlIX86(shl, (int)(ti.value() & 0x03f) ); + if( shl.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm8(ti.value())) + return new ShlIX86(shl, (int)(ti.value() & 0xff) ); return new ShlX86(shl); } private Node shr( ShrNode shr ) { - if( shr.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti ) - return new ShrIX86(shr, (int)(ti.value() & 0x03f) ); + if( shr.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm8(ti.value()) ) + return new ShrIX86(shr, (int)(ti.value() & 0xff) ); return new ShrX86(shr); } diff --git a/chapter21/src/test/java/com/seaofnodes/simple/progs/newtonFloat.smp b/chapter21/src/test/java/com/seaofnodes/simple/progs/newtonFloat.smp index b41fc26e..50419959 100644 --- a/chapter21/src/test/java/com/seaofnodes/simple/progs/newtonFloat.smp +++ b/chapter21/src/test/java/com/seaofnodes/simple/progs/newtonFloat.smp @@ -1,11 +1,11 @@ // -*- mode: java; -*- // Newtons approximation to the square root -val sqrt = { flt x -> - flt epsilon = 1e-57; - flt guess = x; - while( 1 ) { +val test_sqrt = { flt x -> + flt epsilon = 1e-57; + flt guess = x; + while( 1 ) { flt next = (x/guess + guess)/2; if( guess-epsilon <= next & next <= guess+epsilon ) return guess; guess = next; - } -}; + } +};
\ No newline at end of file |
