diff options
| author | Cliff Click <cliffc@acm.org> | 2025-06-13 15:42:08 -0700 |
|---|---|---|
| committer | Cliff Click <cliffc@acm.org> | 2025-07-08 20:29:09 -0700 |
| commit | fcce1e13814a02d8358eae9a40e33a7e6f2b5400 (patch) | |
| tree | b9623369d88e22a0be21dbba472eebfa476d02ab /src/main/java/com | |
| parent | 664761624f4b0e3d6d1593b99e576854beec5949 (diff) | |
| download | Simple-linear-chapter24.tar.gz Simple-linear-chapter24.zip | |
Unifying Structs and Arrayslinear-chapter24
Diffstat (limited to 'src/main/java/com')
254 files changed, 0 insertions, 23108 deletions
diff --git a/src/main/java/com/seaofnodes/simple/IterPeeps.java b/src/main/java/com/seaofnodes/simple/IterPeeps.java deleted file mode 100644 index 9ae4231e..00000000 --- a/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ /dev/null @@ -1,201 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.print.JSViewer; -import java.util.Arrays; -import java.util.BitSet; -import java.util.Random; - -/** - * The IterPeeps runs after parsing. It iterates the peepholes to a fixed point - * so that no more peepholes apply. This should be linear because peepholes rarely - * (never?) increase code size. The graph should monotonically reduce in some - * dimension, which is usually size. It might also reduce in e.g. number of - * MulNodes or Load/Store nodes, swapping out more "expensive" Nodes for cheaper - * ones. - * <br> - * The theoretical overall worklist is mindless just grabbing the next thing and - * doing it. If the graph changes, put the neighbors on the worklist. - * <br> - * Lather, Rinse, Repeat until the worklist runs dry. - * <p> - * The main issues we have to deal with: - * - * <ul> - * <li>Nodes have uses; replacing some set of Nodes with another requires more graph - * reworking. Not rocket science, but it can be fiddly. Its helpful to have a - * small set of graph munging utilities, and the strong invariant that the graph - * is stable and correct between peepholes. In our case `Node.subsume` does - * most of the munging, building on our prior stable Node utilities.</li> - * - * <li>Changing a Node also changes the graph "neighborhood". The neighbors need to - * be checked to see if THEY can also peephole, and so on. After any peephole - * or graph update we put a Nodes uses and defs on the worklist.</li> - * - * <li>Our strong invariant is that for all Nodes, either they are on the worklist - * OR no peephole applies. This invariant is easy to check, although expensive. - * Basically the normal "iterate peepholes to a fixed point" is linear, and this - * check is linear at each peephole step... so quadratic overall. Its a useful - * assert, but one we can disable once the overall algorithm is stable - and - * then turn it back on again when some new set of peepholes is misbehaving. - * The code for this is turned on in `IterPeeps.iterate` as `assert - * progressOnList(stop);`</li> - * </ul> - */ -public class IterPeeps { - - private final WorkList<Node> _work; - - public IterPeeps( long seed ) { _work = new WorkList<>(seed); } - - @SuppressWarnings("unchecked") - public <N extends Node> N add( N n ) { return (N)_work.push(n); } - - public void addAll( Ary<Node> ary ) { _work.addAll(ary); } - - /** - * Iterate peepholes to a fixed point - */ - public void iterate( CodeGen code ) { - assert progressOnList(code); - int cnt=0; - - Node n; - while( (n=_work.pop()) != null ) { - if( n.isDead() ) continue; - cnt++; // Useful for debugging, searching which peephole broke things - Node x = n.peepholeOpt(); - if( x != null ) { - if( x.isDead() ) continue; - // peepholeOpt can return brand-new nodes, needing an initial type set - if( x._type==null ) x.setType(x.compute()); - // Changes require neighbors onto the worklist - if( x != n || !(x instanceof ConstantNode) ) { - // All outputs of n (changing node) not x (prior existing node). - for( Node z : n._outputs ) _work.push(z); - // Everybody gets a free "go again" in case they didn't get - // made in their final form. - _work.push(x); - // If the result is not self, revisit all inputs (because - // there's a new user), and replace in the graph. - if( x != n ) { - for( Node z : n. _inputs ) _work.push(z); - for( Node z : x._outputs ) _work.push(z); - n.subsume(x); - } - } - // If there are distant neighbors, move to worklist - n.moveDepsToWorklist(); - JSViewer.show(); // Show again - assert progressOnList(code); // Very expensive assert - } - if( n.isUnused() && !(n instanceof StopNode) ) - n.kill(); // Just plain dead - } - - } - - // Visit ALL nodes and confirm the invariant: - // Either you are on the _work worklist OR running `iter()` makes no progress. - - // This invariant ensures that no progress is missed, i.e., when the - // worklist is empty we have indeed done all that can be done. To help - // with debugging, the {@code assert} is broken out in a place where it is easy to - // stop if a change is found. - - // Also, the normal usage of `iter()` may attempt peepholes with distance - // neighbors and these should fail, but will then try to add dependencies - // {@link #Node.addDep} which is a side effect in an assert. The {@link - // #midAssert} is used to stop this side effect. - private boolean progressOnList(CodeGen code) { - code._midAssert = true; - Node changed = code._stop.walk( n -> { - Node m = n; - if( n.compute().isa(n._type) && (!n.iskeep() || n._nid<=6) ) { // Types must be forwards, even if on worklist - if( _work.on(n) ) return null; - m = n.peepholeOpt(); - if( m==null ) return null; - } - System.err.println("BREAK HERE FOR BUG"); - return m; - }); - code._midAssert = false; - return changed==null; - } - - /** - * Classic WorkList, with a fast add/remove, dup removal, random pull. - * The Node's nid is used to check membership in the worklist. - */ - @SuppressWarnings("unchecked") - public static class WorkList<E extends Node> { - - private Node[] _es; - private int _len; - private final BitSet _on; // Bit set if Node._nid is on WorkList - private final Random _R; // For randomizing pull from the WorkList - private final long _seed; - - /* Useful stat - how many nodes are processed in the post parse iterative opt */ - private long _totalWork = 0; - - public WorkList() { this(123); } - WorkList(long seed) { - _es = new Node[1]; - _len=0; - _on = new BitSet(); - _seed = seed; - _R = new Random(); - _R.setSeed(_seed); - } - - /** - * Pushes a Node on the WorkList, ensuring no duplicates - * If Node is null it will not be added. - */ - public E push( E x ) { - if( x==null ) return null; - int idx = x._nid; - if( !_on.get(idx) ) { - _on.set(idx); - if( _len==_es.length ) - _es = Arrays.copyOf(_es,_len<<1); - _es[_len++] = x; - _totalWork++; - } - return x; - } - - public void addAll( Ary<E> ary ) { - for( E n : ary ) - push(n); - } - - /** - * True if Node is on the WorkList - */ - boolean on( E x ) { return _on.get(x._nid); } - boolean isEmpty() { return _len==0; } - - /** - * Removes a random Node from the WorkList; null if WorkList is empty - */ - public E pop() { - if( _len == 0 ) return null; - int idx = _R.nextInt(_len); - E x = (E)_es[idx]; - _es[idx] = _es[--_len]; // Compress array - _on.clear(x._nid); - return x; - } - - public void clear() { - _len = 0; - _on.clear(); - _R.setSeed(_seed); - _totalWork = 0; - } - } -} diff --git a/src/main/java/com/seaofnodes/simple/Parser.java b/src/main/java/com/seaofnodes/simple/Parser.java deleted file mode 100644 index 1db873e0..00000000 --- a/src/main/java/com/seaofnodes/simple/Parser.java +++ /dev/null @@ -1,2002 +0,0 @@ -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 com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.Utils; -import com.seaofnodes.simple.node.ScopeNode.Kind; -import java.util.*; - -/** - * The Parser converts a Simple source program to the Sea of Nodes intermediate - * representation directly in one pass. There is no intermediate Abstract - * Syntax Tree structure. - * <p> - * This is a simple recursive descent parser. All lexical analysis is done here as well. - */ -public class Parser { - - public static ConstantNode ZERO; // Very common node, cached here - public static ConstantNode NIL; // Very common node, cached here - public static XCtrlNode XCTRL; // Very common node, cached here - - // Compile driver - public final CodeGen _code; - - // The Lexer. Thin wrapper over a byte[] buffer with a cursor. - private Lexer _lexer; - - /** - * Current ScopeNode - ScopeNodes change as we parse code, but at any point of time - * there is one current ScopeNode. The reason the current ScopeNode can change is to do with how - * we handle branching. See {@link #parseIf()}. - * <p> - * Each ScopeNode contains a stack of lexical scopes, each scope is a symbol table that binds - * variable names to Nodes. The top of this stack represents current scope. - * <p> - * We keep a list of all ScopeNodes so that we can show them in graphs. - * @see #parseIf() - * @see #_xScopes - */ - public ScopeNode _scope; - - /** - * List of keywords disallowed as identifiers - */ - public static final HashSet<String> KEYWORDS = new HashSet<>(){{ - add("bool"); - add("break"); - add("byte"); - add("continue"); - add("else"); - add("f32"); - add("f64"); - add("false"); - add("flt"); - add("i16"); - add("i32"); - add("i64"); - add("i8"); - add("if"); - add("int"); - add("new"); - add("null"); - add("return"); - add("struct"); - add("true"); - add("u1"); - add("u16"); - add("u32"); - add("u8"); - add("while"); - }}; - - /** - * We clone ScopeNodes when control flows branch; it is useful to have - * a list of all active ScopeNodes for purposes of visualization of the SoN graph - */ - public final Stack<ScopeNode> _xScopes = new Stack<>(); - - ScopeNode _continueScope; - ScopeNode _breakScope; // Merge all the while-breaks here - FunNode _fun; // Current function being parsed - - // Mapping from a type name to a Type. The string name matches - // `type.str()` call. - public static HashMap<String, Type> TYPES = defaultTypes(); - - // Mapping from a type name to the constructor for a Type. - public final HashMap<String, StructNode> INITS; - - - public Parser(CodeGen code, TypeInteger arg) { - _code = code; - _scope = new ScopeNode(); - _continueScope = _breakScope = null; - ZERO = con(TypeInteger.ZERO).keep(); - NIL = con(Type.NIL).keep(); - XCTRL= new XCtrlNode().peephole().keep(); - TYPES = defaultTypes(); - INITS = new HashMap<>(); - } - - @Override - public String toString() { return _lexer.toString(); } - - public static HashMap<String, Type> defaultTypes() { - return new HashMap<>() {{ - put("bool",TypeInteger.U1 ); - put("byte",TypeInteger.U8 ); - put("f32" ,TypeFloat .F32); - put("f64" ,TypeFloat .F64); - put("flt" ,TypeFloat .F64); - put("i16" ,TypeInteger.I16); - put("i32" ,TypeInteger.I32); - put("i64" ,TypeInteger.BOT); - put("i8" ,TypeInteger.I8 ); - put("int" ,TypeInteger.BOT); - put("u1" ,TypeInteger.U1 ); - put("u16" ,TypeInteger.U16); - put("u32" ,TypeInteger.U32); - put("u8" ,TypeInteger.U8 ); - put("val" ,Type.TOP); // Marker type, indicates type inference - put("var" ,Type.BOTTOM); // Marker type, indicates type inference - }}; - } - - // Debugging utility to find a Node by index - public Node f(int nid) { return _code.f(nid); } - - private Node ctrl() { return _scope.ctrl(); } - private <N extends Node> N ctrl(N n) { return _scope.ctrl(n); } - - public void parse() { - - _scope.define(ScopeNode.CTRL, Type.CONTROL , false, null, _lexer); - _scope.define(ScopeNode.MEM0, TypeMem.BOT , false, null, _lexer); - _scope.define(ScopeNode.ARG0, TypeInteger.BOT, false, null, _lexer); - - ctrl(XCTRL); - _scope.mem(new MemMergeNode(false)); - - // Parse the sys import - _lexer = new Lexer(com.seaofnodes.simple.sys.SYS); - while( !_lexer.isEOF() ) { - parseStatement(); - _lexer.skipWhiteSpace(); - } - - // Reset lexer for program text - _lexer = new Lexer(_code._src); - _xScopes.push(_scope); - - // Parse whole program, as-if function header "{ int arg -> body }" - parseFunctionBody( _code._main,loc(),"arg"); - - // Kill an empty default main. Keep only if it was explicitly defined - // (programmer asked for a "main") or it has stuff (i.e. beginner - // default main). - FunNode main = _code.link(_code._main); - StopNode stop = _code._stop; - if( main.ret().expr()._type==Type.TOP && main.uctrl()==null ) { - // Kill an empty default main; so it does not attempt to put a - // "main" in any final ELF file - main.setDef(1,XCTRL); // Delete default start input - stop.delDef(stop._inputs.find(main.ret())); - } else { - // We have a non-empty default main. - // Check for an explicit main - for( Node n : stop._inputs ) - if( n instanceof FunNode fun && fun._name.equals("main") ) - // Found an explicit "main" AND we have a default "main" - throw error("Cannot define both an explicit main and a default main"); - main._name = "main"; - } - - if( !_lexer.isEOF() ) throw _errorSyntax("unexpected"); - - // Clean up and reset - _xScopes.pop(); - _scope.kill(); - for( StructNode init : INITS.values() ) - init.unkeep().kill(); - INITS.clear(); - stop.peephole(); - } - - /** - * Parses a function body, assuming the header is parsed. - */ - private ReturnNode parseFunctionBody( TypeFunPtr sig, Lexer loc, String... ids) { - // If this is a method, record & restore the existing struct fields, - // which are also the struct initializers. The method might update - // mutable fields, so when the function parse is done reset back to - // pre-function-parse values. - Node[] preParse = new Node[_scope.nIns()]; - for( int i=0; i<_scope.nIns(); i++ ) - preParse[i] = _scope.in(i)==null ? null : _scope.in(i).keep(); - - // Stack parser state on the local Java stack, and unstack it later - FunNode oldfun = _fun; - ScopeNode breakScope = _breakScope; _breakScope = null; - ScopeNode continueScope = _continueScope; _continueScope = null; - - FunNode fun = _fun = (FunNode)peep(new FunNode(loc(),sig,_nestedType,null,_code._start)); - // Once the function header is available, install in linker table - - // allowing recursive functions. Linker matches on declared args and - // exact fidx, and ignores the return (because the fidx will only match - // the exact single function). - _code.link(fun); - - Node rpc = new ParmNode("$rpc",0,TypeRPC.BOT,fun,con(TypeRPC.BOT)).peephole(); - - // Build a multi-exit return point for all function returns - RegionNode r = new RegionNode( null,null,null).init(); - assert r.inProgress(); - PhiNode rmem = new PhiNode(ScopeNode.MEM0,TypeMem.BOT,r,null).init(); - PhiNode rrez = new PhiNode(ScopeNode.ARG0,Type.BOTTOM,r,null).init(); - ReturnNode ret = new ReturnNode(r, rmem, rrez, rpc, fun).init(); - fun.setRet(ret); - assert ret.inProgress(); - _code._stop.addDef(ret); - - - // Pre-call the function from Start, with worse-case arguments. This - // represents all the future, yet-to-be-parsed functions calls and - // external calls. - _scope.push(new Kind.Func()); - ctrl(fun); // Scope control from function - // Private mem alias tracking per function - MemMergeNode mem = new MemMergeNode(true); - mem.addDef(null); // Alias#0 - mem.addDef(new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,con(TypeMem.BOT)).peephole()); // All aliases - _scope.mem(mem); - // All args, "as-if" called externally - for( int i=0; i<ids.length; i++ ) { - Type t = sig.arg(i); - _scope.define(ids[i], t, false, new ParmNode(ids[i],i+2,t,fun,con(t)).peephole(), loc); - } - - // Parse the body - Node last=ZERO; - while (!peek('}') && !_lexer.isEOF()) - last = parseStatement(); - - // Last expression is the return except for the top-level main - if( ctrl()._type==Type.CONTROL && fun.sig() != _code._main ) - fun.addReturn(ctrl(), _scope.mem().merge(), last); - - // Pop off the inProgress node on the multi-exit Region merge - assert r.inProgress(); - r ._inputs.pop(); - rmem._inputs.pop(); - rrez._inputs.pop(); - r._loc = loc(); // Final position - assert !r.inProgress(); - - // Force peeps, which have been avoided due to inProgress - ret.setDef(1,rmem.peephole()); - ret.setDef(2,rrez.peephole()); - ret.setDef(0,r.peephole()); - ret = (ReturnNode)ret.peephole(); - - // Function scope ends - _scope.pop(); - _fun = oldfun; - _breakScope = breakScope; - _continueScope = continueScope; - - // Reset all fields to pre-parse days - for( int i=0; i<preParse.length; i++ ) - _scope.setDef(i,preParse[i]==null ? null : preParse[i].unkeep()); - - return ret; - } - - /** - * Parses a block - * - * <pre> - * '{' statements '}' - * </pre> - * Does not parse the opening or closing '{}' - * @return a {@link Node} or {@code null} - */ - private Node parseBlock(Kind kind) { - // Enter a new scope - _scope.push(kind); - Node last = ZERO; - while (!peek('}') && !_lexer.isEOF()) - last = parseStatement(); - // Exit scope - last.keep(); - _scope.pop(); - return last.unkeep(); - } - - /** - * Parses a statement - * - * <pre> - * returnStatement | declStatement | blockStatement | ifStatement | expressionStatement - * </pre> - * @return a {@link Node} or {@code null} - */ - private Node parseStatement() { - if( matchx("return") ) return parseReturn(); - else if (matchx("if") ) return parseIf(); - else if (matchx("while") ) return parseWhile(); - else if (matchx("for") ) return parseFor(); - else if (matchx("break") ) return parseBreak(); - else if (matchx("continue")) return parseContinue(); - else if (matchx("struct") ) return parseStruct(); - else if (matchx("#showGraph")) return require(showGraph(),";"); - else if (matchx(";") ) return null; // Empty statement - // Break ambiguity around leading function types and starting a block - else if (peek('{') && !isTypeFun() ) { - match("{"); - return require(parseBlock(new Kind.Block()),"}"); - } - // Declaration or normal assignment/expression - else return parseDeclarationStatement(); - } - - /** - * Parses a while statement - * - * <pre> - * while ( expression ) statement - * </pre> - * @return a {@link Node}, never {@code null} - */ - private Node parseWhile() { - require("("); - return parseLooping(false); - } - - - /** - * Parses a for statement - * - * <pre> - * for( var x=init; test; incr ) body - * </pre> - * @return a {@link Node}, never {@code null} - */ - private Node parseFor() { - // { var x=init,y=init,...; - // while( test ) { - // body; - // next; - // } - // } - require("("); - _scope.push(new Kind.Block()); // Scope for the index variables - if( !match(";") ) // Can be empty init "for(;test;next) body" - parseDeclarationStatement(); // Non-empty init - Node rez = parseLooping(true); - _scope.pop(); // Exit index variable scope - return rez; - } - - // Shared by `for` and `while` - private Node parseLooping( boolean doFor ) { - - var savedContinueScope = _continueScope; - var savedBreakScope = _breakScope; - - // Loop region has two control inputs, the first is the entry - // point, and second is back edge that is set after loop is parsed - // (see end_loop() call below). Note that the absence of back edge is - // used as an indicator to switch off peepholes of the region and - // associated phis; see {@code inProgress()}. - - ctrl(new LoopNode(loc(),ctrl()).peephole()); // Note we set back edge to null here - - // At loop head, we clone the current Scope (this includes all - // names in every nesting level within the Scope). - // We create phis eagerly for all the names we find, see dup(). - - // Save the current scope as the loop head - ScopeNode head = _scope.keep(); - // Clone the head Scope to create a new Scope for the body. - // Create phis eagerly as part of cloning - _xScopes.push(_scope = _scope.dup(true)); // The true argument triggers creating phis - - // Parse predicate - var pred = peek(';') ? con(1) : parseAsgn(); - require( doFor ? ";" : ")" ); - - // IfNode takes current control and predicate - Node ifNode = new IfNode(ctrl(), pred.keep()).peephole(); - // Setup projection nodes - Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); - Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole(); - - // for( ;;next ) body - int nextPos = -1, nextEnd = -1; - if( doFor ) { - // Skip the next expression and parse it later - nextPos = pos(); - skipAsgn(); - nextEnd = pos(); - require(")"); - } - - // Clone the body Scope to create the break/exit Scope which accounts for any - // side effects in the predicate. The break/exit Scope will be the final - // scope after the loop, and its control input is the False branch of - // the loop predicate. Note that body Scope is still our current scope. - ctrl(ifF); - _xScopes.push(_breakScope = _scope.dup()); - _breakScope.addGuards(ifF,pred,true); // Up-cast predicate - - // No continues yet - _continueScope = null; - - // Parse the true side, which corresponds to loop body - // Our current scope is the body Scope - ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection - _scope.addGuards(ifT,pred.unkeep(),false); // Up-cast predicate - parseStatement().isKill(); // Parse loop body - _scope.removeGuards(ifT); - - // Merge the loop bottom into other continue statements - if (_continueScope != null) { - _continueScope = jumpTo(_continueScope); - _scope.kill(); - _scope = _continueScope; - } - - // Now append the next code onto the body code - if( doFor ) { - int old = pos(nextPos); - if( !peek(')') ) - parseAsgn(); - if( pos() != nextEnd ) - throw errorSyntax( "Unexpected code after expression" ); - pos(old); - } - - // The true branch loops back, so whatever is current _scope.ctrl gets - // added to head loop as input. endLoop() updates the head scope, and - // goes through all the phis that were created earlier. For each phi, - // it sets the second input to the corresponding input from the back - // edge. If the phi is redundant, it is replaced by its sole input. - var exit = _breakScope; - head.endLoop(_scope, exit); - head.unkeep().kill(); - - _xScopes.pop(); // Cleanup - _xScopes.pop(); // Cleanup - - _continueScope = savedContinueScope; - _breakScope = savedBreakScope; - - // At exit the false control is the current control, and - // the scope is the exit scope after the exit test. - // During sys parsing, there is no xscope here. - if( !_xScopes.isEmpty() ) { - _xScopes.pop(); - _xScopes.push( exit ); - } - _scope = exit; - return ZERO; - } - - private ScopeNode jumpTo(ScopeNode toScope) { - ScopeNode cur = _scope.dup(); - ctrl(XCTRL); // Kill current scope - // Prune nested lexical scopes that have depth > than the loop head - // We use _breakScope as a proxy for the loop head scope to obtain the depth - while( cur.depth() > _breakScope.depth() ) - cur.pop(); - // If this is a continue then first time the target is null - // So we just use the pruned current scope as the base for the - // "continue" - if( toScope == null ) - return cur; - // toScope is either the break scope, or a scope that was created here - assert toScope.depth() <= _breakScope.depth(); - toScope.ctrl(toScope.mergeScopes(cur,loc()).peephole()); - return toScope; - } - - private void checkLoopActive() { if (_breakScope == null) throw error("No active loop for a break or continue"); } - - private Node parseContinue() { checkLoopActive(); _continueScope = require(jumpTo( _continueScope ),";"); return ZERO; } - private Node parseBreak () { - checkLoopActive(); - // At the time of the break, and loop-exit conditions are only valid if - // they are ALSO valid at the break. It is the intersection of - // conditions here, not the union. - _breakScope.removeGuards(_breakScope.ctrl()); - _breakScope = require(jumpTo(_breakScope ),";"); - _breakScope.addGuards(_breakScope.ctrl(), null, false); - return ZERO; - } - - // Look for an unbalanced `)`, skipping balanced - private void skipAsgn() { - int paren=0; - while( true ) - // Next X char handles skipping complex comments - switch( _lexer.nextXChar() ) { - case Character.MAX_VALUE: - throw Utils.TODO(); - case ')': - if( --paren<0 ) { - posT( pos() - 1 ); - return; // Leave the `)` behind - } - break; - case '(': paren ++; break; - default: break; - } - } - - - /** - * Parses a statement - * - * <pre> - * if ( expression ) statement [else statement] - * </pre> - * @return a {@link Node}, never {@code null} - */ - private Node parseIf() { - // Parse predicate - require("("); - var pred = require(parseAsgn(), ")"); - return parseTrinary(pred,true,"else"); - } - - // Parse a conditional expression, merging results. - private Node parseTrinary( Node pred, boolean stmt, String fside ) { - pred.keep(); - - // IfNode takes current control and predicate - Node ifNode = new IfNode(ctrl(), pred).peephole(); - // Setup projection nodes - Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); - Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole().keep(); - // In if true branch, the ifT proj node becomes the ctrl - // But first clone the scope and set it as current - ScopeNode fScope = _scope.dup(); // Duplicate current scope - _xScopes.push(fScope); // For graph visualization we need all scopes - - // Parse the true side - ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection - _scope.addGuards(ifT,pred,false); // Up-cast predicate - Node lhs = (stmt ? parseStatement() : parseAsgn()).keep(); // Parse true-side - _scope.removeGuards(ifT); - - // See if a one-sided def was made: "if(pred) int x = 1;" and throw. - // See if any forward-refs were made, and copy them to the other side: - // "pred ? n*fact(n-1) : 1" - fScope.balanceIf(_scope); - - ScopeNode tScope = _scope; - - // Parse the false side - _scope = fScope; // Restore scope, then parse else block if any - ctrl(ifF.unkeep()); // Ctrl token is now set to ifFalse projection - // Up-cast predicate, even if not else clause, because predicate can - // remain true if the true clause exits: `if( !ptr ) return 0; return ptr.fld;` - _scope.addGuards(ifF,pred,true); - boolean doRHS = match(fside); - Node rhs = (doRHS - ? (stmt ? parseStatement() : parseAsgn()) - : con(lhs._type.makeZero())).keep(); - _scope.removeGuards(ifF); - if( doRHS ) - fScope = _scope; - pred.unkeep(); - - // Check the trinary widening int/flt - if( !stmt ) { - rhs = widenInt( rhs.unkeep(), lhs._type ).keep(); - lhs = widenInt( lhs.unkeep(), rhs._type ).keep(); - } - - _scope = tScope; - _xScopes.pop(); // Discard pushed from graph display - // See if a one-sided def was made: "if(pred) int x = 1;" and throw. - // See if any forward-refs were made, and copy them to the other side: - // "pred ? n*fact(n-1) : 1" - tScope.balanceIf(fScope); - - // Merge results - RegionNode r = ctrl(tScope.mergeScopes(fScope,loc())); - Node ret = peep(new PhiNode("",lhs._type.meet(rhs._type).glb(false),r,lhs.unkeep(),rhs.unkeep())); - // Immediately fail e.g. `arg ? 7 : ptr` - ParseException err; - if( !stmt && (err=ret.err()) !=null ) throw err; - r.peephole(); - return ret; - } - - /** - * Parses a return statement; "return" already parsed. - * The $ctrl edge is killed. - * - * <pre> - * 'return' expr ; - * </pre> - * @return an expression {@link Node}, never {@code null} - */ - private Node parseReturn() { - var expr = require(parseAsgn(), ";"); - // Need default memory, since it can be lazy, need to force - // a non-lazy Phi - _fun.addReturn(ctrl(), _scope.mem().merge(), expr); - ctrl(XCTRL); // Kill control - return expr; - } - - /** - * Dumps out the node graph - * @return {@code null} - */ - Node showGraph() { - System.out.println(new GraphVisualizer().generateDotOutput(_code._stop,_scope,_xScopes)); - return null; - } - - /** Parse: [name '='] expr - */ - private Node parseAsgn() { - int old = pos(); - String name = _lexer.matchId(); - int old_pre_equals = pos(); - // Just a plain expression, no assignment. - // Distinguish `var==expr` from `var=expr` - if( name==null || KEYWORDS.contains(name) || !matchOpx('=','=') ) - { pos(old); return parseExpression(); } - - // Find variable to update - Var def = _scope.lookup(name); - if( def==null ) - throw error("Undefined name '" + name + "'"); - - // If a method var, allow nested blocks and methods, but not fcns or other structs. - // Else safety check for accessing fields out of function scope. - if( instance(def) ) { - pos(old_pre_equals); // setup for `self.name = expr` - Node self = _scope.in(_scope.lookup("self")); // Insert self reference - return parsePostfixName(self,name); - } - - // TOP fields are for late-initialized fields; these have never - // been written to, and this must be the final write. Other writes - // outside the constructor need to check the final bit. - if( _scope.in(def._idx)._type!=Type.TOP && def._final && - // Inside an allocation, final assign is OK, outside nope. - // The alloc() call added the allocation scope - !(_scope.inAllocation() && def._idx >= _scope._kinds.last(-1)._lexSize) ) - throw error("Cannot reassign final '"+name+"'"); - - // Parse assignment expression - Node expr = parseAsgn(); - - // Lift expression, based on type - Node lift = liftExpr(expr.keep(), def.type(), def._final, true); - // Update - _scope.update(name,lift); - // Return un-lifted expr - return expr.unkeep(); - } - - // Make finals deep; widen ints to floats; narrow wide int types. - // Early error if types do not match variable. - private Node liftExpr( Node expr, Type t, boolean xfinal, boolean isLoad ) { - if( expr._type instanceof TypeMemPtr tmp && TYPES.get(tmp._obj._name).isFRef() ) - throw error("Must define forward ref "+tmp._obj._name); - // Final is deep on ptrs - if( xfinal && t instanceof TypeMemPtr tmp ) { - t = tmp.makeRO(); - expr = peep(new ReadOnlyNode(expr)); - } - // Auto-widen array to i64 (cast ptr to raw int bits) - if( t == TypeInteger.BOT && expr._type instanceof TypeMemPtr tmp && tmp._obj.isAry() ) - expr = peep(new AddNode(peep(new CastNode(t,ctrl(),expr)),off(tmp._obj,"[]"))); - // Auto-widen int to float - expr = widenInt( expr, t ); - // Auto-narrow wide ints to narrow ints. For loads, emit code to force - // the loaded value to match the declared sign/zero bits. For stores, - // just force the type, acting "as if" the store silently truncates. - Type et = expr._type; - if( isLoad ) { expr = zsMask(expr,t); et = expr._type; } - else if( et instanceof TypeInteger && t instanceof TypeInteger ) et=t; - - // Type is sane - if( et!=Type.BOTTOM && !et.shallowISA(t) ) - expr = peep(new CastNode(t,null,expr)); - return expr; - } - - private Node widenInt( Node expr, Type t ) { - return (expr._type instanceof TypeInteger || expr._type==Type.NIL) && t instanceof TypeFloat - ? peep(new ToFloatNode(expr)) : expr; - } - - /** - * Parse declaration or expression statement - * declStmt = type var['=' exprAsgn][, var['=' exprAsgn]]* ';' | exprAsgn ';' - * <p> - * exprAsgn = var '=' exprAsgn | expr - */ - private Node parseDeclarationStatement() { - int old = pos(); - Type t = type(); - if( peek('.') ) // Ambiguity static vars: "type.var", parse as expression - { pos(old); t=null; } - if( t == null ) - return require(parseAsgn(),";"); - - // now parse var['=' asgnexpr] in a loop - Node n = parseDeclaration(t); - while( match(",") ) - n = parseDeclaration(t); - return require(n,";"); - } - - /** Parse final: [!]var['=' asgn] - */ - private Node parseDeclaration(Type t) { - assert t!=null; - // Has var/val instead of a user-declared type - boolean inferType = t==Type.TOP || t==Type.BOTTOM; - boolean hasBang = match("!"); - Lexer loc = loc(); - String name = requireId(); - // Optional initializing expression follows - boolean xfinal = false; - boolean fld_final = false; // Field is final, but not deeply final - Node expr; - if( match("=") ) { - expr = isExternDecl() - ? externDecl(name,t) - : parseAsgn(); - // TOP means val and val is always final - xfinal = (t==Type.TOP) || - expr instanceof ExternNode || - // BOTTOM is var and var is always not-final - (t!=Type.BOTTOM && - // no Bang AND - !hasBang && - // Locals are not-final by default - !_scope.inFunction() && - // not-null (expecting null to be set to not-null) - expr._type != Type.NIL && - // Pointers are final by default; int/flt are not-final by default. - (t instanceof TypeNil)); - - // var/val, then type comes from expression - if( inferType ) { - if( expr._type==Type.NIL ) - throw error("a not-null/non-zero expression"); - t = expr._type; - if( !xfinal ) t = t.glb(false); // Widen if not final - } - // expr is a constant function - if( t instanceof TypeFunPtr && expr._type instanceof TypeFunPtr tfp && tfp.isConstant() ) { - if( expr instanceof ExternNode ) t = expr._type; // Upgrade declared type to exact function - else _code.link(tfp).setName(name); // Assign debug name to Simple function - } - - } else { - // Need an expression to infer the type. - // Also, if not-null then need an initializing expression. - // Allowed in a class def, because type/init will happen in the constructor. - if( (inferType || (t instanceof TypeNil tn && !tn.nullable() )) && !_scope.inConstructor() ) - throw errorSyntax("=expression"); - // Initial value for uninitialized struct fields. - expr = switch( t ) { - // Nullable pointers get a NIL; not-null get a TOP which - // signals that they *must* be initialized in the constructor. - case TypeNil tn -> tn.nullable() ? NIL : con(Type.TOP); - case TypeInteger ti -> ZERO; - case TypeFloat tf -> con(TypeFloat.FZERO); - // Bottom signals type inference: they must be initialized in - // the constructor and that's when we'll discover the type. - case Type tt -> { assert tt==Type.BOTTOM; yield con(tt); } - }; - // Nullable fields are set in the constructor, but remain shallow final. - // e.g. final pointer to a r/w array - if( t instanceof TypeNil tn && !tn.nullable() && !hasBang ) - fld_final = true; - } - - // Lift expression, based on type - Node lift = liftExpr(expr, t, xfinal, true); - // Rebuild the deep RO 't', not returned from liftExpr - if( xfinal && t instanceof TypeMemPtr tmp ) - t = tmp.makeRO(); - - // Lift type to the declaration. This will report as an error later if - // we cannot lift the type. - if( !lift._type.isa(t) ) - lift = peep(new CastNode(t,null,lift)); - // Define a new name - if( !_scope.define(name,t,xfinal || fld_final,lift, loc) ) - throw error("Redefining name '" + name + "'", loc); - return lift; - } - - - - /** - * Parse a struct declaration, and return the following statement. - * Structs cannot be redefined, but can be nested. - * - * @return zero - */ - private String _nestedType; - private Node parseStruct() { - // "struct" already parsed, so expect the struct name next. - String old = _nestedType; - String typeName = requireId(); - if( old!=null ) typeName = old+"."+typeName; - _nestedType = typeName; - // New declared or forward-ref type - TypeStruct ts = TYPES.get(typeName) instanceof TypeMemPtr tmp ? tmp._obj : TypeStruct.open(typeName); - TYPES.put(typeName,TypeMemPtr.make(ts)); - - // A Block scope parse, and inspect the scope afterward for fields. - _scope.push(new Kind.Define(TypeMemPtr.make(ts))); - require("{"); - int lexlen = _scope._kinds.last()._lexSize, nvar=lexlen; - while (!peek('}') && !_lexer.isEOF()) { - parseStatement().isKill(); - while( nvar < _scope._vars._len ) { - Var v = _scope.var(nvar++); - if( !v.isFRef() ) { - ts = ts.add( Field.make( v._name, v.type(), _code.getALIAS(), v._final, v._final && _scope.in( v._idx )._type != Type.TOP ) ); - TYPES.put( typeName, TypeMemPtr.make( ts ) ); - } - } - } - - // Count struct declarations - int varlen = _scope._vars._len; - ts = ts.close(); - TYPES.put(typeName,TypeMemPtr.make(ts)); - - // Generate an initializing struct - StructNode s = new StructNode(ts); - for( int i=lexlen; i<varlen; i++ ) - if( !_scope.var(i).isFRef() ) // Promote to outer scope, not defined here - s.addDef(_scope.in(i)); // Keep the program code to generate instance - - // Save instance default constructor - s.setType(s.compute()); // Do not call peephole, in case the entire struct is a big plain constant - INITS.put(typeName,s.keep()); - - // Having finalized the struct, upgrade all the 'self' pointers - for( Node n : s._inputs ) - if( n._type instanceof TypeFunPtr tfp && _code.link(tfp) instanceof FunNode fun ) - _code.addAll( fun._outputs ); - - - // Done with struct/block scope - _nestedType = old; - require("}"); - require(";"); - _scope.pop(); - return ZERO; - } - - - // Parse and return a type or null. Valid types always are followed by an - // 'id' which the caller must parse. This lets us distinguish forward ref - // types (which ARE valid here) from local vars in an (optional) forward - // ref type position. - - // t = int|i8|i16|i32|i64|u8|u16|u32|u64|byte|bool | flt|f32|f64 | val | var | struct[?] - private Type type() { - int old1 = pos(); - // Only type with a leading `{` is a function pointer... - if( peek('{') ) return typeFunPtr(); - - // Otherwise you get a type name - String tname = _lexer.matchId(); - if( tname==null ) return null; - - // Convert the type name to a type. - Type t0 = TYPES.get(tname); - // No new types as keywords - if( t0 == null && KEYWORDS.contains(tname) ) - return posT(old1); - if( t0 == Type.BOTTOM || t0 == Type.TOP ) return t0; // var/val type inference - - // Check for subtype. - while( true ) { - int old2 = pos(); - if( !match(".") ) break; - String sname = _lexer.matchId(); - if( sname==null ) { pos(old2); break; } - String tsname = tname+"."+sname; - t0 = TYPES.get(tsname); - if( t0==null ) { pos(old2); t0 = TYPES.get(tname); break; } - tname = tsname; - } - - // Still no type found? Assume forward reference - Type t1 = t0 == null ? TypeMemPtr.make(TypeStruct.open(tname)) :t0; // Null: assume a forward ref type - // Nest arrays and '?' as needed - Type t2 = t1; - while( true ) { - if( match("?") ) { - if( !(t2 instanceof TypeMemPtr tmp) ) - throw error("Type "+t0+" cannot be null"); - if( tmp.nullable() ) throw error("Type "+t2+" already allows null"); - t2 = tmp.makeNullable(); - } else if( match("[~]") ) { - t2 = typeAry(t2,true); - } else if( match("[]") ) { - t2 = typeAry(t2,false); - } else - break; - } - - // Check no forward ref - if( t0 != null ) return t2; - // Check valid forward ref, after parsing all the type extra bits. - // Cannot check earlier, because cannot find required 'id' until after "[]?" syntax - int old2 = pos(); - match("!"); - String id = _lexer.matchId(); - if( !(peek(',') || peek(';') || match("->")) || id==null ) - return posT(old1); // Reset lexer to reparse - pos(old2); // Reset lexer to reparse - // Yes a forward ref, so declare it - TYPES.put(tname,t1); - // Return the (array, final) type - return t2; - } - - // Make an array type of t - private TypeMemPtr typeAry( Type t, boolean efinal ) { - if( t instanceof TypeMemPtr tmp && tmp.notNull() ) - throw error("Arrays of reference types must always be nullable"); - String tname = "[]"+t.str(); - TypeMemPtr ta = (TypeMemPtr)TYPES.get(tname); - if( ta==null ) - // Remember final version - TYPES.put(tname, ta = TypeMemPtr.make( TypeStruct.makeAry(tname,TypeInteger.U32,_code.getALIAS(),t,_code.getALIAS(),true) )); - Field elem = ta._obj.field("[]"); - if( elem._final == efinal ) - return ta; - return TypeMemPtr.make( ta._obj.replace(elem.makeFrom(efinal))); - } - - // A function type is `{ type... -> type }` or `{ type }`. - private Type typeFunPtr() { - int old = pos(); // Record lexer position - match("{"); // Skip already-peeked '{' - Type t0 = type(); // Either return or first arg - if( t0==null ) return posT(old); // Not a function - if( match("}") ) // No-arg function { -> type } - return TypeFunPtr.make(match("?"),false,TypeFunPtr.TEMPTY,t0); - Ary<Type> ts = new Ary<>(Type.class); - ts.push(t0); // First argument - while( true ) { - if( match("->") ) { // End of arguments, parse return - Type ret = type(); - if( ret==null || !match("}") ) - return posT(old); // Not a function - return TypeFunPtr.make(match("?"),false,ts.asAry(),ret); - } - Type t1 = type(); - if( t1==null ) return posT(old); // Not a function - ts.push(t1); - } - } - - // True if a TypeFunPtr, without advancing parser - private boolean isTypeFun() { - int old = pos(); - if( typeFunPtr()==null ) return false; - pos(old); - return true; - } - - /** - * Parse an expression of the form: - * - * <pre> - * expr : bitwise [? expr [: expr]] - * </pre> - * @return an expression {@link Node}, never {@code null} - */ - private Node parseExpression() { - Node expr = parseBitwise(); - return match("?") ? parseTrinary(expr,false,":") : expr; - } - - /** - * Parse an bitwise expression - * - * <pre> - * bitwise : compareExpr (('&' | '|' | '^') compareExpr)* - * </pre> - * @return a bitwise expression {@link Node}, never {@code null} - */ - private Node parseBitwise() { - Node lhs = parseComparison(); - while( true ) { - if( false ) ; - else if( match("&") ) lhs = new AndNode(loc(),lhs,null); - else if( match("|") ) lhs = new OrNode(loc(),lhs,null); - else if( match("^") ) lhs = new XorNode(loc(),lhs,null); - else break; - lhs.setDef(2,parseComparison()); - lhs = peep(lhs); - } - return lhs; - } - - /** - * Parse an expression of the form: - * - * <pre> - * expr : additiveExpr op additiveExpr - * </pre> - * @return an comparator expression {@link Node}, never {@code null} - */ - private Node parseComparison() { - var lhs = parseShift(); - while( true ) { - int idx=0; boolean negate=false; - // Test for any local nodes made, and "keep" lhs during peepholes - if( false ) ; - else if( match("==") ) { idx=2; lhs = new BoolNode.EQ(lhs, null); } - else if( match("!=") ) { idx=2; lhs = new BoolNode.EQ(lhs, null); negate=true; } - else if( match("<=") ) { idx=2; lhs = new BoolNode.LE(lhs, null); } - else if( match("<" ) ) { idx=2; lhs = new BoolNode.LT(lhs, null); } - else if( match(">=") ) { idx=1; lhs = new BoolNode.LE(null, lhs); } - else if( match(">" ) ) { idx=1; lhs = new BoolNode.LT(null, lhs); } - else break; - // Peepholes can fire, but lhs is already "hooked", kept alive - lhs.setDef(idx,parseShift()); - lhs = peep(lhs.widen()); - if( negate ) // Extra negate for != - lhs = peep(new NotNode(lhs)); - } - return lhs; - } - - /** - * Parse an additive expression - * - * <pre> - * shiftExpr : additiveExpr (('<<' | '>>' | '>>>') additiveExpr)* - * </pre> - * @return a shift expression {@link Node}, never {@code null} - */ - private Node parseShift() { - Node lhs = parseAddition(); - while( true ) { - if( false ) ; - else if( match("<<") ) lhs = new ShlNode(loc(),lhs,null); - else if( match(">>>")) lhs = new ShrNode(loc(),lhs,null); - else if( match(">>") ) lhs = new SarNode(loc(),lhs,null); - else break; - lhs.setDef(2,parseAddition()); - ParseException err; - if( (err=lhs.err()) != null ) - throw err; - lhs = peep(lhs.widen()); - } - return lhs; - } - - /** - * Parse an additive expression - * - * <pre> - * additiveExpr : multiplicativeExpr (('+' | '-') multiplicativeExpr)* - * </pre> - * @return an add expression {@link Node}, never {@code null} - */ - private Node parseAddition() { - Node lhs = parseMultiplication(); - while( true ) { - if( false ) ; - else if( match("+") ) lhs = new AddNode(lhs,null); - else if( match("-") ) lhs = new SubNode(lhs,null); - else break; - lhs.setDef(2,parseMultiplication()); - lhs = peep(lhs.widen()); - } - return lhs; - } - - /** - * Parse an multiplicativeExpr expression - * - * <pre> - * multiplicativeExpr : unaryExpr (('*' | '/') unaryExpr)* - * </pre> - * @return a multiply expression {@link Node}, never {@code null} - */ - private Node parseMultiplication() { - var lhs = parseUnary(); - while( true ) { - if( false ) ; - else if( match("*") ) lhs = new MulNode(lhs,null); - else if( match("/") ) lhs = new DivNode(lhs,null); - else break; - lhs.setDef(2,parseUnary()); - lhs = peep(lhs.widen()); - } - return lhs; - } - - /** - * Parse a unary minus expression. - * - * <pre> - * unaryExpr : ('-') unaryExpr | '!') unaryExpr | postfixExpr | primaryExpr | '--' Id | '++' Id - * </pre> - * @return a unary expression {@link Node}, never {@code null} - */ - private Node parseUnary() { - // Pre-dec/pre-inc - int old = pos(); - if( match("--") || match("++") ) { - int delta = _lexer.peek(-1)=='+' ? 1 : -1; // Pre vs post - String name = _lexer.matchId(); - if( name!=null ) { - Var n = _scope.lookup(name); - if( n != null && !(n.type() instanceof TypeMemPtr) ) { - if( n._final ) - throw error("Cannot reassign final '"+n._name+"'"); - Node expr = n.type() instanceof TypeFloat - ? peep(new AddFNode(_scope.in(n),con(TypeFloat.constant(delta)))) - : zsMask(peep(new AddNode(_scope.in(n),con(delta))),n.type()); - _scope.update(n,expr); - return expr; - } - } - // Reset, try again - pos(old); - } - if (match("-")) return peep(new MinusNode(parseUnary()).widen()); - if (match("!")) return peep(new NotNode(parseUnary())); - // Else be a primary expression - return parsePrimary(); - } - - /** - * Parse a primary expression: - * - * <pre> - * primaryExpr : integerLiteral | "string" | 'char' | true | false | null | - * new Type | '(' expression ')' | Id['++','--'] | - * [static.]* Id - * </pre> - * @return a primary {@link Node}, never {@code null} - */ - private Node parsePrimary() { - if( _lexer.isNumber(_lexer.peek()) ) return parseLiteral(); - if( _lexer.peek('"') ) return newString(parseString()); - if( matchx("true" ) ) return con(1); - if( matchx("false") ) return ZERO; - if( matchx("null" ) ) return NIL; - if( match ("'" ) ) return parseChar(); - if( match ("(" ) ) return parsePostfix(require(parseAsgn(), ")")); - if( matchx("new" ) ) return parsePostfix(alloc()); - if( match ("{" ) ) return parsePostfix(require(func(),"}")); - - // Parse static variable lookup: `type.fld` in the type namespace - // CNC: not a full type parse, just a typename parse - int pos = pos(); - Type t = type(); - if( t!=null ) { - if( peek('.') ) - return parsePostfix(con(t)); - //pos(pos); // TODO: not a `type.fld` - //return null; - throw Utils.TODO(); - } - - // Expect an identifier now - - // - if null, make fref, normal var handling - // - if final constant, normal var handling - // - if method var, scan skipping blocks & methods, must hit correct struct no fcns - // - if normal var, scan skipping blocks, allowing last fcn - // - NO fcns nested in methods, NO methods nested in fcns - - String id = _lexer.matchId(); - if( id == null || KEYWORDS.contains(id) ) - throw errorSyntax("an identifier or expression"); - Var var = _scope.lookup(id); - - if( var==null ) { - // If missing, assume a forward reference - _scope.define(id, FRefNode.FREF_TYPE, true, XCTRL, loc()); - var = _scope.lookup(id); - } - - // If a method var, allow nested blocks and methods, but not fcns or other structs. - // Else safety check for accessing fields out of function scope. - if( instance(var) ) { - Node self = _scope.in(_scope.lookup("self")); // Insert self reference - return parsePostfixName(self,id); - } - - // Load local value - Node rvalue = _scope.in(var); - if( rvalue._type == Type.BOTTOM ) - if( rvalue instanceof FRefNode ) - return parsePostfix(rvalue); - else throw error("Cannot read uninitialized field '"+id+"'"); - - // Check for assign-update, x += e0; - char ch = _lexer.matchOperAssign(); - if( ch==0 ) // Normal primary, check for postfix updates - return parsePostfix(rvalue); - // Assign-update direct into Scope - Node op = opAssign(ch,rvalue, var.type() ); - _scope.update(var,op); - return postfix(ch) ? rvalue.unkeep() : op; - } - - // true if instance variable lookup (var is in any number of nested blocks, - // then an instance function then inside a struct declaration). - private boolean instance(Var var ) { - // If a method var, allow nested blocks and methods, but not fcns or other structs - Kind kind = _scope.kind(var); - if( kind instanceof Kind.Define ) { - for( int i = _scope.depth()-1; i>=0 && var._idx < _scope._kinds.at(i)._lexSize; i-- ) { - Kind xkind = _scope._kinds.at(i); - if( xkind instanceof Kind.Block ) continue; - if( xkind instanceof Kind.Func ) { - if( _scope._kinds.at(i-1)==kind ) - return true; // Instance variable: lookup in a method in a decl - } - throw Utils.TODO(); - } - } else { - // If a normal var, allow any nested blocks, and a last fcn - - // but not functions further out (which involves capture). - Node def = _scope.in(var._idx); - for( int i = _scope.depth()-1; i>=0 && var._idx < _scope._kinds.at(i)._lexSize; i-- ) - if( _scope._kinds.at(i) instanceof Kind.Func && - !(def instanceof FRefNode) && !(var._final && def._type.isConstant()) ) - throw error("Variable '"+var._name+"' is out of function scope and must be a final constant"); - } - return false; - } - - // Check for assign-update, "x += e0". - // Returns "x + e0"; the caller assigns value. - // if ch is postfix, then lhs is kept and caller will unkeep and return it. - private Node opAssign( char ch, Node lhs, Type t ) { - // RHS of the update. - lhs.keep(); // Alive across parseAsgn - Node rhs = - (byte)ch == 1 ? con( 1) : // var++ - (byte)ch == -1 ? con(-1) : // var-- - parseAsgn(); // var op= rhs - if( !postfix(ch) ) lhs.unkeep(); // Allow to die in next peep - // 4 cases: - // int + int ==>> narrow int - // int + flt ==>> error, caller must fail assigning flt into int - // flt + int ==>> use float op, wrap toFloat() - // flt + flt ==>> use float op - Node op = switch(ch) { - case 1, (char)-1, - '+' -> new AddNode(lhs,rhs); - case '-' -> new SubNode(lhs,rhs); - case '*' -> new MulNode(lhs,rhs); - case '/' -> new DivNode(lhs,rhs); - default -> throw Utils.TODO(); - }; - // Convert to float ops, or narrow int types; error if not declared type. - // Also, if postfix LHS is still keep() - return liftExpr(peep(op.widen()),t,false,true); - } - - - /** - Parse an allocation - */ - private Node alloc() { - Type t = type(); - if( t==null ) throw error("Expected a type"); - // Parse ary[ length_expr ] - if( match("[") ) { - if( !t.makeZero().isa(t) ) - throw error("Cannot allocate a non-nullable, since arrays always zero/null fill"); - Node len = parseAsgn(); - if( !(len._type instanceof TypeInteger) ) - throw error("Cannot allocate an array with length "+len._type); - require("]"); - TypeMemPtr tmp = typeAry(t,false); - return newArray(tmp._obj,len); - } - - if( !(t instanceof TypeMemPtr tmp) ) - throw error("Cannot allocate a "+t.str()); - - // Parse new struct { default_initialization } - StructNode s = INITS.get(tmp._obj._name); - if( s==null ) throw error("Unknown struct type '" + tmp._obj._name + "'"); - - Field[] fs = s._ts._fields; - // if the object is fully initialized, we can skip a block here. - // Check for constructor block: - boolean hasConstructor = match("{"); - Ary<Node> init=s._inputs; int idx=0; - if( hasConstructor ) { - idx = _scope.nIns(); - // Push a scope, and pre-assign all struct fields. - _scope.push(new Kind.Block()); - Lexer loc = loc(); - for( int i=0; i<fs.length; i++ ) - // An initial TOP means the field needs to be initialized. We - // store a BOT initially; any partial init will fall to BOT - // (merge BOT and the partial) and be obviously only a partial - // init. To be initialized the field needs a full clobber. - _scope.define(fs[i]._fname, fs[i]._t, fs[i]._final, s.in(i)._type==Type.TOP ? con(Type.BOTTOM) : s.in(i), loc); - // Parse the constructor body - require(parseBlock(new Kind.Alloc(tmp)),"}"); - init = _scope._inputs; - } - // Check that all fields are initialized - for( int i=idx; i<init.size(); i++ ) - if( init.at(i)._type == Type.TOP || init.at(i)._type == Type.BOTTOM ) - throw error("'"+tmp._obj._name+"' is not fully initialized, field '" + fs[i-idx]._fname + "' needs to be set in a constructor"); - Node ptr = newStruct(tmp._obj, off(tmp._obj, " len"), idx, init ); - if( hasConstructor ) - _scope.pop(); - return ptr; - } - - - /** - * Return a NewNode initialized memory. - * @param obj is the declared type, with GLB fields - * @param init is a collection of initialized fields - */ - private Node newStruct( TypeStruct obj, Node size, int idx, Ary<Node> init ) { - Field[] fs = obj._fields; - if( fs==null ) - throw error("Unknown struct type '" + obj._name + "'"); - int len = fs.length; - Node[] ns = new Node[2+len]; - ns[0] = ctrl(); // Control in slot 0 - // Total allocated length in bytes - ns[1] = size; - // Memory aliases for every field - for( int i = 0; i < len; i++ ) - if( !fs[i]._one ) - ns[2+i] = memAlias(fs[i]._alias); - Node nnn = new NewNode(TypeMemPtr.make(obj), ns).peephole().keep(); - for( int i = 0; i < len; i++ ) - if( !fs[i]._one ) - memAlias(fs[i]._alias, new ProjNode(nnn,i+2,memName(fs[i]._alias)).peephole()); - Node ptr = new ProjNode(nnn.unkeep(),1,obj._name).peephole().keep(); - - // Initial nonzero values for every field - for( int i = 0; i < len; i++ ) { - Node val = init.get( i + idx ); - if( !fs[i]._one && val._type != val._type.makeZero() ) { - Node mem = memAlias(fs[i]._alias); - Node st = new StoreNode(loc(),fs[i]._fname,fs[i]._alias,fs[i]._t,mem,ptr,off(obj,fs[i]._fname),val,true).peephole(); - memAlias(fs[i]._alias,st); - } - } - - return ptr.unkeep(); - } - - private static final Ary<Node> ALTMP = new Ary<>(Node.class); - private Node newArray(TypeStruct ary, Node len) { - ConFldOffNode base = off(ary, "[]"); - int scale= ary.aryScale(); - Node size = peep(new AddNode(base,peep(new ShlNode(null,len.keep(),con(scale))))); - ALTMP.clear(); ALTMP.add(len.unkeep()); ALTMP.add(con(ary._fields[1]._t.makeZero())); - return newStruct(ary,size,0,ALTMP); - } - private Node newString(String s) { - TypeMemPtr tmp = typeAry(TypeInteger.U8,true); - int lenAlias = tmp._obj.field("#" )._alias; - int elemAlias = tmp._obj.field("[]")._alias; - TypeConAryB con = TypeConAryB.make(s); - Type elem = con.elem(); - // Make a TMP, not-null (byte)2, singleton (true) (requires text - // strings are hash-interned), with a TypeStruct having a constant - // array body. - TypeMemPtr str = TypeMemPtr.make((byte)2,TypeStruct.makeAry("[]u8", TypeInteger.constant(s.length()), lenAlias, con, elemAlias, true),true); - assert str.isConstant(); - return con(str); - } - - // We set up memory aliases by inserting special vars in the scope these - // variables are prefixed by $ so they cannot be referenced in Simple code. - // Using vars has the benefit that all the existing machinery of scoping - // and phis work as expected - private Node memAlias(int alias ) { return _scope.mem(alias ); } - private void memAlias(int alias, Node st) { _scope.mem(alias, st); } - public static String memName(int alias) { return ("$"+alias).intern(); } - - /** - * Parse postfix expression; this can be a field expression, an array - * lookup or a postfix operator like '#' - * - * <pre> - * expr ('.' FIELD)* [ = expr ] // Field reference - * expr '#' // Postfix unary read operator - * expr ['++' | '--' ] // Postfix unary write operator - * expr ('[' expr ']')* = [ = expr ] // Array reference - * expr '(' [args,]* ')' // Function call - * </pre> - */ - private Node parsePostfix(Node expr) { - String name; - if( match(".") ) name = requireId(); - else if( match("#") ) name = "#"; - else if( match("[") ) name = "[]"; - else if( match("(") ) return parsePostfix(require(functionCall(expr,null),")")); - else return expr; // No postfix - - return parsePostfixName(expr,name); - } - - /** - * Parse postfix expression; this can be a field expression, an array - * lookup or a postfix operator like '#' - * - * <pre> - * expr ['++' | '--' ] // Postfix unary write operator - * expr [ [op]= expr ] // Field reference - * expr ('[' expr ']')* [ [op]= expr ] // Array reference - * </pre> - */ - private Node parsePostfixName(Node expr, String name) { - - if( expr._type==Type.NIL ) - throw error("Accessing unknown field '" + name + "' from 'null'"); - - // Sanity check expr for being a reference - if( !(expr._type instanceof TypeMemPtr ptr) ) - throw error( "Expected "+(name=="#" || name=="[]" ? "array" : "reference")+" but found " + expr._type.str() ); - - // Find the field from the Type. Lookup in the base object field names. - TypeMemPtr ptr2 = (TypeMemPtr)TYPES.get(ptr._obj._name); - ptr = (TypeMemPtr)ptr.join(ptr2);// Upgrade to latest TYPES - TypeStruct base = ptr._obj; - int fidx = base.find(name); - if( fidx == -1 ) - throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'"); - - // Get field type and layout offset from base type and field index fidx - Field f = base._fields[fidx]; // Field from field index - Type tf = f._t; - if( tf instanceof TypeMemPtr ftmp && ftmp.isFRef() ) - tf = ftmp.makeFrom(((TypeMemPtr)(TYPES.get(ftmp._obj._name)))._obj); - - // Field offset; fixed for structs, computed for arrays - Node off = (name.equals("[]") // If field is an array body - // Array index math - ? peep(new AddNode(off(base,"[]"),peep(new ShlNode(null,require(parseAsgn(),"]"),con(base.aryScale()))))) - // Struct field offsets are hardwired - : off(base,name)).keep(); - - // Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment - if( matchOpx('=','=') ) { - Node val = parseAsgn().keep(); - Node lift = liftExpr( val, tf, f._final, false ); - - Node st = new StoreNode(loc(), name, f._alias, tf, memAlias(f._alias), expr, off.unkeep(), lift, false); - // Arrays include control, as a proxy for a safety range check. - // Structs don't need this; they only need a NPE check which is - // done via the type system. - if( base.isAry() ) st.setDef(0,ctrl()); - memAlias(f._alias, st.peephole()); - return val.unkeep(); // "obj.a = expr" returns the expression while updating memory - } - - // Keep expr for possible store update - expr.keep(); - // Load field - Node load = f._one && INITS.containsKey( base._name ) - ? INITS.get(base._name).in(base.find(name)) - : new LoadNode(loc(),name, f._alias, tf, memAlias(f._alias), expr, off); - // Arrays include control, as a proxy for a safety range check - // Structs don't need this; they only need a NPE check which is - // done via the type system. - if( base.isAry() && !name.equals("#") ) load.setDef(0,ctrl()); - load = peep(load); - - // Check for assign-update, "ptr.fld += expr" or "ary[idx]++" - char ch = _lexer.matchOperAssign(); - if( ch!=0 ) { - Node op = opAssign(ch,load, tf ); - Node st = new StoreNode(loc(), name, f._alias, tf, memAlias(f._alias), expr.unkeep(), off.unkeep(), op, false); - // Arrays include control, as a proxy for a safety range check. - // Structs don't need this; they only need a NPE check which is - // done via the type system. - if( base.isAry() ) st.setDef(0,ctrl()); - memAlias(f._alias, peep(st)); - load = postfix(ch) ? load.unkeep() : op; - // And use the original loaded value as the result - return load; - //throw Utils.TODO(); // NO POSTFIX AFTER STORE, JUST RETURN - } else { - off.unkill(); - // Method call: Loaded a function from a named field, AND the base - // field type is final. - if( load._type instanceof TypeFunPtr && name!="[]" && - !(load instanceof ExternNode) && - ((TypeMemPtr)TYPES.get(base._name))._obj.field(name)._final && - // And calling a function - match("(") ) { - return parsePostfix(require(functionCall(load,expr),")")); - } - - // Drop expr, keeping load alive - load.keep(); - expr.unkill(); - load.unkeep(); - } - - return parsePostfix(load); - } - - - // zero/sign extend. "i" is limited to either classic unsigned (min==0) or - // classic signed (min=minus-power-of-2); max=power-of-2-minus-1. - private Node zsMask(Node val, Type t ) { - if( !(val._type instanceof TypeInteger tval && t instanceof TypeInteger t0 && !tval.isa(t0)) ) { - if( !(val._type instanceof TypeFloat tval && t instanceof TypeFloat t0 && !tval.isa(t0)) ) - return val; - // Float rounding - return peep(new RoundF32Node(val)); - } - if( t0._min==0 ) // Unsigned - return peep(new AndNode(null,val,con(t0._max))); - // Signed extension - int shift = Long.numberOfLeadingZeros(t0._max)-1; - Node shf = con(shift); - if( shf._type==TypeInteger.ZERO ) - return val; - return peep(new SarNode(null,peep(new ShlNode(null,val,shf.keep())),shf.unkeep())); - } - - /** - * Parse a function body; the caller will parse the surrounding "{}" - * - * <pre> - * { [type arg,]* -> expr } - * { expr } // The no-argument function - * </pre> - */ - private Node func() { - Ary<Type> ts = new Ary<>(Type.class); - Ary<String> ids = new Ary<>(String.class); - - // Defined in constructor? Add `self` argument. "static" calls still - // add a `self` they just ignore it. - if( _scope._kinds.last() instanceof Kind.Define define ) { - // Upgrade defined struct to latest fields - TypeMemPtr tmp = (TypeMemPtr)TYPES.get(define._tmp._obj._name); - ts .push(tmp); - ids.push("self"); - } - - // Parse other arguments - _lexer.skipWhiteSpace(); - Lexer loc = loc(); // First argument location - while( true ) { - Type t = type(); // Arg type - if( t==null ) break; - String id = requireId(); - ts .push(t ); // Push type/arg pairs - ids.push(id); - match(","); - } - require("->"); - // Make a concrete function type, with a fidx - TypeFunPtr tfp = _code.makeFun(TypeFunPtr.make(false,false,ts.asAry(),Type.BOTTOM)); - ReturnNode ret = parseFunctionBody(tfp,loc,ids.asAry()); - return con(ret._fun.sig()); - } - - /** - * Parse function call arguments; caller will parse the surrounding "()" - * <pre> - * ( arg* ) - * </pre> - */ - private Node functionCall(Node fcn, Node self) { - if( fcn._type == Type.NIL ) - throw error("Calling a null function pointer"); - if( !(fcn instanceof FRefNode) && !fcn._type.isa(TypeFunPtr.BOT) ) - throw error("Expected a function but got "+fcn._type.glb(false).str()); - fcn.keep(); // Keep while parsing args - - Ary<Node> args = new Ary<>( Node.class ); - args.push(null); // Space for ctrl,mem - args.push(null); - if( self != null ) // Method call has a self - args.push(self); - while( !peek(')') ) { - Node arg = parseAsgn(); - if( arg==null ) break; - args.push(arg.keep()); - if( !match(",") ) break; - } - // Control & memory after parsing args - args.set(0,ctrl().keep()); - args.set(1,_scope.mem().merge().keep()); - args.push(fcn); // Function pointer - // Unkeep them all - for( Node arg : args ) - arg.unkeep(); - // Dead into the call? Skip all the node gen - if( ctrl()._type == Type.XCONTROL ) { - for( Node arg : args ) - if( arg.isUnused() ) - arg.kill(); - return con(Type.TOP); - } - - // Into the call - CallNode call = (CallNode)new CallNode(loc(), args.asAry()).peephole(); - - // Post-call setup - CallEndNode cend = (CallEndNode)new CallEndNode(call).peephole(); - call.peephole(); // Rerun peeps after CallEnd, allows early inlining - // Control from CallEnd - ctrl(new CProjNode(cend,0,ScopeNode.CTRL).peephole()); - // Memory from CallEnd - MemMergeNode mem = new MemMergeNode(true); - mem.addDef(null); // Alias#0 - mem.addDef(new ProjNode(cend,1,ScopeNode.MEM0).peephole()); - _scope.mem(mem); - // Call result - return new ProjNode(cend,2,"#2").peephole(); - } - - // Just after parsing "type foo = " can parse `"C"` - private boolean isExternDecl( ) { - int old = pos(); - String s = parseString(); - if( "C".equals(s) ) return true; - pos(old); - return false; - } - // External linked constant - private Node externDecl( String ex, Type t ) { - if( t instanceof TypeFunPtr tfp ) // Generic TFP from type parse - t = _code.makeFun(tfp); // Get a FIDX, becomes a constant - return new ExternNode(t,ex).peephole(); - } - - /** - * Parse integer literal - * - * <pre> - * integerLiteral: [1-9][0-9]* | [0] - * floatLiteral: [digits].[digits]?[e [digits]]? - * </pre> - */ - private ConstantNode parseLiteral() { return con(_lexer.parseNumber()); } - public static Node con( long con ) { return con==0 ? ZERO : con(TypeInteger.constant(con)); } - public static ConstantNode con( Type t ) { return (ConstantNode)new ConstantNode(t).peephole(); } - public static ConFldOffNode off( TypeStruct base, String fname ) { return (ConFldOffNode)(new ConFldOffNode(base._name,fname).peephole()); } - public Node peep( Node n ) { - // Peephole, then improve with lexically scoped guards - return _scope.upcastGuard(n.peephole()); - } - - // Parse a string or null - private String parseString() { - if( !peek('"') ) return null; - _lexer.inc(); - int start = pos(); - while( !_lexer.isEOF() && _lexer.nextChar()!= '"' ) ; - if( _lexer.isEOF() ) - throw error("Unclosed string"); - return new String(_lexer._input,start,pos()-start-1); - } - - // Already parsed "'" - private Node parseChar() { - return require(con(TypeInteger.constant(_lexer.nextChar())),"'"); - } - - ////////////////////////////////// - // Utilities for lexical analysis - - // Return true and skip if "syntax" is next in the stream. - private boolean match (String syntax) { return _lexer.match (syntax); } - // Match must be "exact", not be followed by more id letters - private boolean matchx(String syntax) { return _lexer.matchx(syntax); } - private boolean matchOpx(char c0, char c1) { return _lexer.matchOpx(c0,c1); } - // Return true and do NOT skip if 'ch' is next - private boolean peek(char ch) { return _lexer.peek(ch); } - private boolean peekIsId() { return _lexer.peekIsId(); } - - // ch is +/- 1, means oper++ or oper-- means postfix - private static boolean postfix(char ch) { - return (byte)ch== 1 || (byte)ch== -1; - } - - public int pos() { return _lexer._position; } - private int pos(int pos) { - int old = _lexer._position; - _lexer._position = pos; - return old; - } - private Type posT(int pos) { _lexer._position = pos; return null; } - // Source code location - Lexer loc() { return new Lexer(_lexer); } - - - // Require and return an identifier - private String requireId() { - String id = _lexer.matchId(); - if (id != null && !KEYWORDS.contains(id) ) return id.intern(); - throw error("Expected an identifier, found '"+id+"'"); - } - - private String matchId() { - int old = pos(); - String id = _lexer.matchId(); - if( id==null ) return null; - if( !KEYWORDS.contains(id) ) return id; - pos(old); - return null; - } - - // Require an exact match - private Parser require(String syntax) { require(null, syntax); return this; } - private <N> N require(N n, String syntax) { - if (match(syntax)) return n; - throw errorSyntax(syntax); - } - - //////////////////////////////////// - // Lexer components - - // Lexer provides low level access to the raw file bytes, peeks and matches - // short strings, parses numbers, skips comments and whitespace, tracks - // line numbers, allows the parse position to be saved and restored, and - // serves as a location indicator for errors. - - public static class Lexer { - - // Input buffer; an array of text bytes read from a file or a string - private final byte[] _input; - // Tracks current position in input buffer - private int _position = 0; - - // - private int _line_number = 1; - // Start of current line - private int _line_start = 0; - - /** - * Record the source text for lexing - */ - public Lexer(String source) { - this(source.getBytes()); - } - - /** - * Direct from disk file source - */ - public Lexer(byte[] buf) { - _input = buf; - } - - /** - * Copy a lexer from a lexer - */ - private Lexer(Lexer l) { - _input = l._input; - _position = l._position; - _line_number = l._line_number; - _line_start = l._line_start; - } - - // Very handy in the debugger, shows the unparsed program - @Override - public String toString() { - return new String(_input, _position, _input.length - _position); - } - - // True if at EOF - private boolean isEOF() { - return _position >= _input.length; - } - - // Peek next character, or report EOF - private char peek() { - return isEOF() ? Character.MAX_VALUE // Special value that causes parsing to terminate - : (char) _input[_position]; - } - // Just crash if misused - public byte peek(int off) { return _input[_position+off]; } - - private void inc() { - if( _position++ < _input.length && _input[_position-1] == '\n' ) { - _line_number++; - _line_start = _position; - } - } - // Does not honor LF, so caller must roll back position on a LF - private char nextChar() { - char ch = peek(); - inc(); - return ch; - } - - // True if a white space - private boolean isWhiteSpace() { - return peek() <= ' '; // Includes all the use space, tab, newline, CR - } - - /** - * Return the next non-white-space character - */ - private void skipWhiteSpace() { - while( true ) { - if( isWhiteSpace() ) inc(); - // Skip // to end of line - else if( _position+2 < _input.length && - _input[_position ] == '/' && - _input[_position+1] == '/') { - inc(); inc(); - while( !isEOF() && _input[_position] != '\n' ) - inc(); - } else if( _position+2 < _input.length && - _input[_position ] == '/' && - _input[_position+1] == '*') { - // Skip /*comment*/ - while( !isEOF() && !(_input[_position-1] == '*' && _input[_position] == '/')) - inc(); - inc(); - } else break; - } - } - - // Next non-white-space character, or EOF - public char nextXChar() { skipWhiteSpace(); return nextChar(); } - - // Return true, if we find "syntax" after skipping white space; also - // then advance the cursor past syntax. - // Return false otherwise, and do not advance the cursor. - boolean match(String syntax) { - assert syntax.indexOf('\n')==-1; // No newlines in match - skipWhiteSpace(); - int len = syntax.length(); - if (_position + len > _input.length) return false; - for (int i = 0; i < len; i++) - if ((char) _input[_position + i] != syntax.charAt(i)) - return false; - _position += len; - return true; - } - - // Match must be exact and not followed by more ID characters. - // Prevents identifier "ifxy" from matching an "if" statement. - boolean matchx(String syntax) { - if( !match(syntax) ) return false; - if( !isIdLetter(peek()) ) return true; - _position -= syntax.length(); - return false; - } - // Match these two characters in a row - boolean matchOpx(char c0, char c1) { - skipWhiteSpace(); - if( _position+1 >= _input.length || _input[_position]!=c0 || _input[_position+1]==c1 ) - return false; - inc(); - return true; - } - - private boolean peek(char ch) { - skipWhiteSpace(); - return peek()==ch; - } - - boolean peekIsId() { - skipWhiteSpace(); - return isIdStart(peek()); - } - - // Return an identifier or null - String matchId() { - return peekIsId() ? parseId() : null; - } - - // Used for errors - String getAnyNextToken() { - if (isEOF()) return ""; - if (isIdStart(peek())) return parseId(); - if (isNumber(peek())) return parseNumberString(); - if (isPunctuation(peek())) return parsePunctuation(); - return String.valueOf(peek()); - } - - - boolean isNumber(char ch) {return Character.isDigit(ch);} - - // Return a constant Type, either TypeInteger or TypeFloat - private Type parseNumber() { - int old = _position; - int len = isLongOrDouble(); - if( len > 0 ) { - if( len > 1 && _input[old]=='0' ) - throw error("Syntax error: integer values cannot start with '0'",this); - long i = Long.parseLong(new String(_input,old,len)); - return TypeInteger.constant(i); - } - return TypeFloat.constant(Double.parseDouble(new String(_input,old,-len))); - } - private String parseNumberString() { - int old = _position; - int len = Math.abs(isLongOrDouble()); - return new String(_input,old,len); - } - - // Return +len that ends a long - // Return -len that ends a double - private int isLongOrDouble() { - int old = _position; - char c; - while( Character.isDigit(c=nextChar()) ) ; - if( !(c=='e' || c=='.') ) - return --_position - old; - if( peek() == '-' ) nextChar(); - while( Character.isDigit(c=nextChar()) || c=='e' || c=='.' ) ; - return -(--_position - old); - } - - // First letter of an identifier - private boolean isIdStart(char ch) { - return Character.isAlphabetic(ch) || ch == '_'; - } - - // All characters of an identifier, e.g. "_x123" - private boolean isIdLetter(char ch) { - return Character.isLetterOrDigit(ch) || ch == '_'; - } - - private String parseId() { - int start = _position; - while (isIdLetter(nextChar())) ; - return new String(_input, start, --_position - start); - } - - // - private boolean isPunctuation(char ch) { - return "=;[]<>()+-/*&|^".indexOf(ch) != -1; - } - - private String parsePunctuation() { - int start = _position; - return new String(_input, start, 1); - } - - // Next oper= character, or 0. - // As a convenience, mark "++" as a char 1 and "--" as char -1 (65535) - public char matchOperAssign() { - skipWhiteSpace(); - if( _position+2 >= _input.length ) return 0; - char ch0 = (char)_input[_position]; - if( "+-/*&|^".indexOf(ch0) == -1 ) return 0; - char ch1 = (char)_input[_position+1]; - if( ch1 == '=' ) { _position += 2; return ch0; } - if( isIdLetter((char)_input[_position+2]) ) return 0; - if( ch0 == '+' && ch1 == '+' ) { _position += 2; return (char) 1; } - if( ch0 == '-' && ch1 == '-' ) { _position += 2; return (char)-1; } - return 0; - } - } - - ParseException errorSyntax(String syntax) { return _errorSyntax("expected "+syntax); } - private ParseException _errorSyntax(String msg) { - return error("Syntax error, "+msg+": " + _lexer.getAnyNextToken()); - } - ParseException error(String msg) { return error(msg,_lexer); } - public static ParseException error(String msg, Lexer loc) { return new ParseException(msg,loc); } - - public static class ParseException extends RuntimeException { - public final Lexer _loc; - // file:line:charoff err - //String msg = "src:"+_line_number+":"+(_position-_line_start)+" "+errorMessage; - ParseException( String msg, Lexer loc ) { super(msg); _loc = loc; } - } - -} diff --git a/src/main/java/com/seaofnodes/simple/Var.java b/src/main/java/com/seaofnodes/simple/Var.java deleted file mode 100644 index cf549cd6..00000000 --- a/src/main/java/com/seaofnodes/simple/Var.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.type.*; - - -/** - * The tracked fields are now complex enough to deserve a array-of-structs layout - */ -public class Var { - - public final String _name; // Declared name - // These fields are not final for forward reference late updates or - // promotions. - public int _idx; // index in containing scope - private Type _type; // Declared type - public boolean _final; // Final field - public boolean _fref; // Forward ref - public Parser.Lexer _loc; // Source location - - public Var(int idx, String name, Type type, boolean xfinal, Parser.Lexer loc ) { - this(idx,name,type,xfinal,loc,false); - } - public Var(int idx, String name, Type type, boolean xfinal, Parser.Lexer loc, boolean fref) { - _idx = idx; - _name = name; - _type = type; - _final = xfinal; - _fref = fref; - _loc = loc; - } - public Type type() { - if( !_type.isFRef() ) return _type; - // Update self to no longer use the forward ref type - Type def = Parser.TYPES.get(((TypeMemPtr)_type)._obj._name); - return (_type=_type.meet(def)); - } - - // Forward reference variables (not types) must be BOTTOM and - // distinct from inferred variables - public boolean isFRef() { return _fref; } - - public void defFRef( Type type, boolean xfinal, Parser.Lexer loc ) { - assert isFRef() && xfinal; - _type = type; - _final = true; - _fref = false; - _loc = loc; - } - - @Override public String toString() { - return _type.toString()+(_final ? " ": " !")+_name; - } -} diff --git a/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java b/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java deleted file mode 100644 index e9b5eb2e..00000000 --- a/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeMem; - -abstract public class BuildLRG { - // Compute live ranges in a single forwards pass. Every def is a new live - // range except live ranges are joined at Phi. Also, gather the - // intersection of register masks in each live range. Example: "free - // always zero" register forces register zero, and calling conventions - // typically force registers. - - // Sets FAILED to the set of hard-conflicts (means no need for an IFG, - // since it will not color, just split the conflicted ranges now). Returns - // true if no hard-conflicts, although we still might not color. - public static boolean run(int round, RegAlloc alloc) { - for( Node bb : alloc._code._cfg ) - for( Node n : bb.outs() ) { - if( n instanceof PhiNode phi && !(phi._type instanceof TypeMem) ) { - // All Phi inputs end up with the same LRG. - // Pass 1: find any pre-existing LRG, to avoid make-then-Union a LRG - LRG lrg = alloc.lrg(phi); - if( lrg == null ) - for( int i=1; i<phi.nIns(); i++ ) - if( (lrg = alloc.lrg(phi.in(i))) != null ) - break; - // If none, make one. - if( lrg==null ) lrg = alloc.newLRG(n); - if( phi instanceof MachNode ) defLRG(alloc,n); - // Pass 2: everybody uses the same LRG - lrg=alloc.union(lrg,phi); - for( int i=phi instanceof ParmNode ? 2 : 1; i<n.nIns(); i++ ) - lrg=alloc.union(lrg,n.in(i)); - if( lrg._mask!=null && lrg._mask.isEmpty() ) - alloc.fail(lrg); - - } else if( n instanceof MachNode mach ) { - // Attempt to commute ops to keep live ranges compatible. - if( mach.commutes() && n.nOuts()==1 ) { - int uidx = n.out(0)._inputs.find(n); - RegMask mask1 = n.in (1) instanceof MachNode machx ? machx.outregmap() : alloc.lrg(n.in(1))._mask; - RegMask mask2 = n.in (2) instanceof MachNode machx ? machx.outregmap() : alloc.lrg(n.in(2))._mask; - RegMask masko = n.out(0) instanceof MachNode machx ? machx.regmap(uidx): alloc.lrg(n )._mask; - if( !mask1.overlap(masko) && mask2.overlap(masko) ) - n.swap12(); - } - - // Define live range - defLRG(alloc,n); - - // Now, look in the opposite direction. How are incoming - // LRGs affected by this node: For all uses, make live lrgs - for( int i=1; i<n.nIns(); i++ ) - if( n.in(i)!=null ) { - LRG lrg2 = alloc.lrg(n.in(i)); - if( lrg2 != null ) { // Anti-dep or other, no LRG - RegMask use_mask = mach.regmap(i); // use_mask is also null for anti-dep - if( use_mask!=null && !lrg2.machUse(mach,(short)i,use_mask.size1()).and(use_mask) ) - alloc.fail(lrg2); // Empty register mask, must split - } - } - - } - - // MultiNodes have projections which set registers - if( n instanceof MultiNode && !(n instanceof CFGNode) ) - 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/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java b/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java deleted file mode 100644 index 689ceceb..00000000 --- a/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.Utils; -import java.util.BitSet; -import java.util.IdentityHashMap; - -// Coalesce copies -abstract public class Coalesce { - - public static boolean coalesce( int round, RegAlloc alloc ) { - - // Walk all the splits, looking for coalesce chances - boolean progress = false; - for( CFGNode bb : alloc._code._cfg ) { - outer: - for( int j=0; j<bb.nOuts(); j++ ) { - Node n = bb.out(j); - if( !(n instanceof SplitNode) ) continue; - LRG v1 = alloc.lrg(n); - LRG v2 = alloc.lrg(n.in(1)); - if( v1 != v2 ) { - LRG ov1 = v1, ov2 = v2; - // Get the smaller neighbor count in v1 - if( v1.nadj() > v2.nadj() ) - { v1 = v2; v2 = alloc.lrg(n); } - int v2len = v2._adj==null ? 0 : v2._adj._len; - // See that they do not conflict (coalescing would make a self-conflict) - if( v1._adj!=null && v2._adj!=null && v1._mask.overlap(v2._mask) ) { - // Walk the smaller neighbors, and add to the larger. - // Check for direct conflicts along the way. - for( LRG v3 : v1._adj ) { - if( v3==v2 ) { - // Direct conflict, skip this LRG pair - v2._adj.setLen(v2len); - continue outer; - } - // Add missing LRGs from V1 to V2 - if( v2._adj.find(v3)== -1 ) - v2._adj.push(v3); - } - } - // Most constrained mask - RegMask mask = v1._mask==v2._mask ? v1._mask : v1._mask.copy().and(v2._mask); - // Check for capacity - if( v2.nadj() >= mask.size() ) { - // Fails capacity, will not be trivial colorable - if( v2._adj!=null ) v2._adj.setLen(v2len); - continue; - } - - // Union lrgs. - progress = true; - - // Since `n` is dying, remove from the LRGS BEFORE doing - // the union; this will let the union code keep the - // matching bits from the other side, preserving some - // info for biased coloring - if( ov1. _machDef==n ) ov1. _machDef = null; - if( ov1._splitDef==n ) ov1._splitDef = null; - if( ov2. _machUse==n ) ov2. _machUse = null; - if( ov2._splitUse==n ) ov2._splitUse = null; - // Keep larger adjacency list. - Ary<LRG> larger = v2._adj; - v2._adj = null; - - // Union lrgs. Swaps again, based on small lrg - LRG vWin = v1.union(v2); - vWin._adj = larger; - LRG vLose = v1.leader() ? v2 : v1; - - // LRGs below v2len were always in v2, but maybe not in v1. - // LRGs above v2len are in v1 but were not in v2. - - // Walk v2's neighbors, replacing vLose with vWin. - if( larger != null ) - for( LRG vn : larger ) { - int idx = vn._adj.find(vLose); - if( idx != -1 ) - vn._adj.del(idx); - if( vn._adj.find(vWin) == -1 ) - vn._adj.add(vWin); - } - } else { - - // Since `n` is dying, remove from the LRGS - if( v1. _machDef==n ) v1. _machDef=null; - if( v1. _machUse==n ) v1. _machUse=null; - if( v1._splitDef==n ) v1._splitDef=null; - if( v1._splitUse==n ) v1._splitUse=null; - } - - // Remove junk split - n.removeSplit(); - j--; - } - } - - if( progress ) - alloc.unify(); - - return true; - } -} diff --git a/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java deleted file mode 100644 index caa7ff44..00000000 --- a/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java +++ /dev/null @@ -1,521 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.print.*; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.SB; -import java.io.IOException; -import java.util.*; - -@SuppressWarnings("unchecked") -public class CodeGen { - public static final String PORTS = "com.seaofnodes.simple.node.cpus"; - // Last created CodeGen as a global; used all over to avoid passing about a - // "context". - public static CodeGen CODE; - - public enum Phase { - Parse, // Parse ASCII text into Sea-of-Nodes IR - Opto, // Run ideal optimizations - TypeCheck, // Last check for bad programs - LoopTree, // Build a loop tree; break infinite loops - Select, // Convert to target hardware nodes - Schedule, // Global schedule (code motion) nodes - LocalSched, // Local schedule - RegAlloc, // Register allocation - Encoding, // Encoding - Export, // Export - } - public Phase _phase; - - // --------------------------- - // Compilation source code - public final String _src; - // Compile-time known initial argument type - public final TypeInteger _arg; - - // --------------------------- - public CodeGen( String src ) { this(src, TypeInteger.BOT, 123L, true ); } - public CodeGen( String src, TypeInteger arg, long workListSeed, boolean reset ) { - CODE = this; - if( reset ) Type.reset(); - _main = makeFun(TypeFunPtr.MAIN); - _phase = null; - _callingConv = null; - _start = new StartNode(arg); - _stop = new StopNode(src); - _src = src; - _arg = arg; - _iter = new IterPeeps(workListSeed); - P = new Parser(this,arg); - } - - - // All passes up to Phase, except ELF - public CodeGen driver( Phase phase ) { return driver(phase,null,null); } - public CodeGen driver( Phase phase, String cpu, String callingConv ) { - if( _phase==null ) parse(); - int p1 = phase.ordinal(); - if( _phase.ordinal() < p1 && _phase.ordinal() < Phase.Opto .ordinal() ) opto(); - if( _phase.ordinal() < p1 && _phase.ordinal() < Phase.TypeCheck .ordinal() ) typeCheck(); - if( _phase.ordinal() < p1 && _phase.ordinal() < Phase.LoopTree .ordinal() ) loopTree(); - if( _phase.ordinal() < p1 && _phase.ordinal() < Phase.Select .ordinal() && cpu != null ) instSelect(cpu,callingConv); - if( _phase.ordinal() < p1 && _phase.ordinal() < Phase.Schedule .ordinal() ) GCM(); - if( _phase.ordinal() < p1 && _phase.ordinal() < Phase.LocalSched.ordinal() ) localSched(); - if( _phase.ordinal() < p1 && _phase.ordinal() < Phase.RegAlloc .ordinal() ) regAlloc(); - if( _phase.ordinal() < p1 && _phase.ordinal() < Phase.Encoding .ordinal() ) encode(); - return this; - } - - // Run all the phases through final ELF emission - public CodeGen driver( String cpu, String callingConv, String obj ) throws IOException { - return driver(Phase.Encoding,cpu,callingConv).exportELF(obj); - } - - - // --------------------------- - /** - * A counter, for unique node id generation. Starting with value 1, to - * avoid bugs confusing node ID 0 with uninitialized values. - */ - public int _uid = 1; - public int UID() { return _uid; } - public int getUID() { - return _uid++; - } - - // Next available memory alias number - private int _alias = 2; // 0 is for control, 1 for memory - public int getALIAS() { return _alias++; } - - - // idepths are cached and valid until *inserting* CFG edges (deleting is - // OK). This happens with inlining, which bumps the version to bulk - // invalidate the idepth caches. - private int _iDepthVersion = 0; - public void invalidateIDepthCaches() { _iDepthVersion++; } - public boolean validIDepth(int idepth) { - if( idepth==0 ) return false; - if( _iDepthVersion==0 ) return true; - return (idepth%100)==_iDepthVersion; - } - public int iDepthAt(int idepth) { - return 100*idepth+_iDepthVersion; - } - public int iDepthFrom(int idepth) { - assert idepth==0 || validIDepth(idepth); - return idepth+100; - } - - // Popular visit bitset, declared here, so it gets reused all over - public final BitSet _visit = new BitSet(); - public BitSet visit() { assert _visit.isEmpty(); return _visit; } - - // Start and stop; end points of the generated IR - public StartNode _start; - public StopNode _stop; - - // Global Value Numbering. Hash over opcode and inputs; hits in this table - // are structurally equal. - public final HashMap<Node,Node> _gvn = new HashMap<>(); - - // - private int _fidx =0; - public TypeFunPtr makeFun( TypeFunPtr fun ) { - int fidx = _fidx++; - assert fidx<64; // TODO: need a larger FIDX space - return fun.makeFrom(fidx); - } - // Signature for MAIN - public final TypeFunPtr _main; - // Reverse from a constant function pointer to the IR function being called - public FunNode link( TypeFunPtr tfp ) { - assert tfp.isConstant(); - return _linker.get(tfp.makeFrom(Type.BOTTOM)); - } - - // Insert linker mapping from constant function signature to the function - // being called. - public void link(FunNode fun) { - _linker.put(fun.sig().makeFrom(Type.BOTTOM),fun); - } - - // "Linker" mapping from constant TypeFunPtrs to heads of function. These - // TFPs all have exact single fidxs and their return is wiped to BOTTOM (so - // the return is not part of the match). - private final HashMap<TypeFunPtr,FunNode> _linker = new HashMap<>(); - - // Parser object - public final Parser P; - - // Parse ASCII text into Sea-of-Nodes IR - public int _tParse; - public CodeGen parse() { - assert _phase == null; - _phase = Phase.Parse; - long t0 = System.currentTimeMillis(); - - P.parse(); - _tParse = (int)(System.currentTimeMillis() - t0); - JSViewer.show(); - return this; - } - - - // --------------------------- - // Iterator peepholes. - public final IterPeeps _iter; - // Stop tracking deps while assertin - public boolean _midAssert; - // Statistics on peepholes - public int _iter_cnt, _iter_nop_cnt; - // Stat gathering - public void iterCnt() { if( !_midAssert ) _iter_cnt++; } - public void iterNop() { if( !_midAssert ) _iter_nop_cnt++; } - - // Run ideal optimizations - public int _tOpto; - public CodeGen opto() { - assert _phase == Phase.Parse; - _phase = Phase.Opto; - long t0 = System.currentTimeMillis(); - - // Pessimistic peephole optimization on a worklist - _iter.iterate(this); - - // OPTIMISTIC PASS GOES HERE - - // Not really a true optimistic pass, but look for unlinked functions. - // This can be removed, which may trigger another round of pessimistic. - // This is the point where we flip from a virtual Call Graph (any call - // can call any function) to having a correct (but conservative) CG. - int progress = 0; - while( progress != _start.nOuts() ) { - progress = _start.nOuts(); - FunNode main = link(_main); - for( int i=0; i<_start.nOuts(); i++ ) { - Node use = _start.out(i); - if( use instanceof FunNode fun && - fun.nIns()==2 && fun.in(1)==_start && fun != main && - (fun._name==null || fun._name.startsWith("sys.")) ) { - add(fun).setDef(1,Parser.XCTRL); - addAll(fun._outputs); - i--; - } - } - _iter.iterate(this); - } - - // Freeze field sizes; do struct layouts; convert field offsets into - // constants. - for( int i=0; i<_start.nOuts(); i++ ) { - Node use = _start.out(i); - if( use instanceof ConFldOffNode off ) { - TypeMemPtr tmp = (TypeMemPtr) Parser.TYPES.get(off._name); - off.subsume( off.asOffset(tmp._obj) ); - i--; - } - } - _iter.iterate(this); - - // To help with testing, sort StopNode returns by NID - Arrays.sort(_stop._inputs._es,0,_stop.nIns(),(x,y) -> x._nid - y._nid ); - - _tOpto = (int)(System.currentTimeMillis() - t0); - - // TODO: - // Optimistic - // TODO: - // loop unroll, peel, RCE, etc - return this; - } - public <N extends Node> N add( N n ) { return _iter.add(n); } - public void addAll( Ary<Node> ary ) { _iter.addAll(ary); } - - - // --------------------------- - // Last check for bad programs - public int _tTypeCheck; - public CodeGen typeCheck() { - // Demand phase Opto for cleaning up dead control flow at least, - // required for the following GCM. - assert _phase.ordinal() <= Phase.Opto.ordinal(); - _phase = Phase.TypeCheck; - long t0 = System.currentTimeMillis(); - - Parser.ParseException err = _stop.walk( Node::err ); - _tTypeCheck = (int)(System.currentTimeMillis() - t0); - if( err != null ) - throw err; - return this; - } - - // --------------------------- - // Build the loop tree; break never-exit loops - public int _tLoopTree; - public CodeGen loopTree() { - assert _phase.ordinal() <= Phase.TypeCheck.ordinal(); - _phase = Phase.LoopTree; - long t0 = System.currentTimeMillis(); - // Build the loop tree, fix never-exit loops - _start.buildLoopTree(_start,_stop); - _tLoopTree = (int)(System.currentTimeMillis() - t0); - return this; - } - - // --------------------------- - // Code generation CPU target - public Machine _mach; - // Chosen calling convention (usually either Win64 or SystemV) - public String _callingConv; - // Callee save registers - public RegMask _callerSave; - - // All returns have the following inputs: - // 0 - ctrl - // 1 - memory - // 2 - varies with returning GPR,FPR - // 3 - RPC - // 4+- Caller save registers - public RegMask[] _retMasks; - // Return Program Counter - public RegMask _rpcMask; - - // Convert to target hardware nodes - public int _tInsSel; - public CodeGen instSelect( String cpu, String callingConv ) { return instSelect(cpu,callingConv,PORTS); } - public CodeGen instSelect( String cpu, String callingConv, String base ) { - assert _phase.ordinal() == Phase.LoopTree.ordinal(); - _phase = Phase.Select; - - _callingConv = callingConv; - - // Not timing the class load... - // Look for CPU in fixed named place: - // com.seaofnodes.simple.node.cpus."cpu"."cpu.class" - String clzFile = base+"."+cpu+"."+cpu; - try { _mach = ((Class<Machine>) Class.forName( clzFile )).getDeclaredConstructor(new Class[]{CodeGen.class}).newInstance(this); } - catch( Exception e ) { throw new RuntimeException(e); } - - // Build global copies of common register masks. - long callerSave = _mach.callerSave(); - long neverSave = _mach. neverSave(); - int maxReg = Math.min(64,_mach.regs().length); - assert maxReg>=64 || (-1L << maxReg & callerSave)==0; // No stack slots in callerSave - _callerSave = new RegMask(callerSave); - - // Build a Return RegMask array. All returns have the following inputs: - // 0 - ctrl - // 1 - memory - // 2 - varies with returning GPR,FPR - // 3 - RPC - // 4+- Caller save registers - _retMasks = new RegMask[(maxReg-_callerSave.size()-Long.bitCount( neverSave ))+4]; - for( int reg=0, i=4; reg<maxReg; reg++ ) - if( !_callerSave.test(reg) && ((1L<<reg)&neverSave)==0 ) - _retMasks[i++] = new RegMask(reg); - _rpcMask = new RegMask(_mach.rpc()); - _retMasks[3] = _rpcMask; - - // Convert to machine ops - long t0 = System.currentTimeMillis(); - _uid = 1; // All new machine nodes reset numbering - var map = new IdentityHashMap<Node,Node>(); - _instSelect( _stop, map ); - _stop = ( StopNode)map.get(_stop ); - StartNode start = (StartNode)map.get(_start); - _start = start==null ? new StartNode(_start) : start; - _instOuts(_stop,visit()); - _visit.clear(); - _tInsSel = (int)(System.currentTimeMillis() - t0); - return this; - } - - // Walk all ideal nodes, recursively mapping ideal to machine nodes, then - // make a machine node for "this". - private Node _instSelect( Node n, IdentityHashMap<Node,Node> map ) { - if( n==null ) return null; - Node x = map.get(n); - if( x !=null ) return x; // Been there, done that - - // If n is a MachNode already, then its part of a multi-node expansion. - // It does not need instruction selection (already selected!) - // but it does need its inputs walked. - if( n instanceof MachNode ) { - for( int i=0; i < n.nIns(); i++ ) - n._inputs.set(i, _instSelect(n.in(i),map) ); - return n; - } - - // Produce a machine node from n; map it to flag as done so stops cycles. - map.put(n, x=_mach.instSelect(n) ); - // Walk machine op and replace inputs with mapped inputs - for( int i=0; i < x.nIns(); i++ ) - x._inputs.set(i, _instSelect(x.in(i),map) ); - if( x instanceof MachNode mach ) - mach.postSelect(this); // Post selection action - - return x; - } - - // Walk all machine Nodes, and set their output edges - private void _instOuts( Node n, BitSet visit ) { - if( visit.get(n._nid) ) return; - visit.set(n._nid); - for( Node in : n._inputs ) - if( in!=null ) { - in._outputs.push(n); - _instOuts(in,visit); - } - } - - - // --------------------------- - // Control Flow Graph in Reverse Post Order. - public int _tGCM; - public Ary<CFGNode> _cfg = new Ary<>(CFGNode.class); - - // Global schedule (code motion) nodes - public CodeGen GCM() { return GCM(false); } - public CodeGen GCM( boolean show) { - assert _phase.ordinal() <= Phase.Select.ordinal(); - _phase = Phase.Schedule; - long t0 = System.currentTimeMillis(); - - GlobalCodeMotion.buildCFG(this); - _tGCM = (int)(System.currentTimeMillis() - t0); - if( show ) - System.out.println(new GraphVisualizer().generateDotOutput(_stop,null,null)); - return this; - } - - // --------------------------- - // Local (basic block) scheduler phase, a classic list scheduler - public int _tLocal; - public CodeGen localSched() { - assert _phase == Phase.Schedule; - _phase = Phase.LocalSched; - long t0 = System.currentTimeMillis(); - ListScheduler.sched(this); - _tLocal = (int)(System.currentTimeMillis() - t0); - return this; - } - - - // --------------------------- - // Register Allocation - public int _tRegAlloc; - public RegAlloc _regAlloc; - public CodeGen regAlloc() { - assert _phase == Phase.LocalSched; - _phase = Phase.RegAlloc; - long t0 = System.currentTimeMillis(); - _regAlloc = new RegAlloc(this); - _regAlloc.regAlloc(); - _tRegAlloc = (int)(System.currentTimeMillis() - t0); - return this; - } - - // Human readable register name - public String reg(Node n) { return reg(n,null); } - public String reg(Node n, FunNode fun) { - if( _phase.ordinal() >= Phase.RegAlloc.ordinal() ) { - String s = _regAlloc.reg(n,fun); - if( s!=null ) return s; - } - return "N"+ n._nid; - } - - - // --------------------------- - // Encoding - public int _tEncode; - public Encoding _encoding; // Encoding object - public void preEncode() { } // overridden by alternative ports - public CodeGen encode() { - assert _phase == Phase.RegAlloc; - _phase = Phase.Encoding; - long t0 = System.currentTimeMillis(); - _encoding = new Encoding(this); - preEncode(); - _encoding.encode(); - _tEncode = (int)(System.currentTimeMillis() - t0); - return this; - } - - // --------------------------- - // Exporting to external formats - public CodeGen exportELF(String fname) throws IOException { - assert _phase == Phase.Encoding; - _phase = Phase.Export; - if( fname == null ) new LinkMem(this).link(); // In memory patching - else new ElfFile(this).export(fname); // External ELF file - return this; - } - - // --------------------------- - public boolean _asmLittle=true; - SB asm(SB sb) { return ASMPrinter.print(sb,this); } - public String asm() { return asm(new SB()).toString(); } - - - // Testing shortcuts - public Node ctrl() { return _stop.ret().ctrl(); } - public Node expr() { return _stop.ret().expr(); } - public String print() { return _stop.print(); } - - // Debugging helper - @Override public String toString() { - return _phase.ordinal() > Phase.Schedule.ordinal() - ? IRPrinter._prettyPrint( this ) - : _stop.p(9999); - } - - // Debugging helper - public Node f(int idx) { return _stop.find(idx); } - - - String printCFG() { - if( _cfg==null ) return "no CFG"; - SB sb = new SB(); - for( CFGNode cfg : _cfg ) { - sb.fix(8,""+cfg._idepth); - IRPrinter.printLine( cfg, sb ); - } - return sb.toString(); - } - - public static void print_as_hex(Encoding enc) { - for (byte b : enc._bits.toByteArray()) { - System.out.print(String.format("%02X", b)); - } - System.out.println(); - } - - public void print_as_hex() { - for (byte b : _encoding._bits.toByteArray()) { - System.out.print(String.format("%02X", b)); - } - System.out.println(); - } - - //// Debug purposes for now -// public CodeGen printENCODING() { -// ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); -// -// for(Node bb : CodeGen.CODE._cfg) { -// for(Node n: bb.outs()) { -// if(n instanceof MachNode) { -// ((MachNode) n).encoding(E); -// } -// } -// } -// -// print_as_hex(outputStream); -// // Get the raw bytes from the output stream -// return this; -// } -} diff --git a/src/main/java/com/seaofnodes/simple/codegen/ElfExpr.java b/src/main/java/com/seaofnodes/simple/codegen/ElfExpr.java deleted file mode 100644 index 9d9b8a1c..00000000 --- a/src/main/java/com/seaofnodes/simple/codegen/ElfExpr.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - -public class ElfExpr { - public static void main(String[] args) { - try (FileOutputStream fos = new FileOutputStream("hello.o")) { - ByteBuffer buffer = ByteBuffer.allocate(512); - buffer.order(ByteOrder.LITTLE_ENDIAN); - - // === ELF HEADER (64 bytes) === - buffer.put(new byte[]{ - 0x7F, 'E', 'L', 'F', // ELF Magic - 2, 1, 1, 0, // 64-bit, little-endian, version - 0, 0, 0, 0, 0, 0, 0, // ABI padding - 2, 0, // Type: Executable (ET_EXEC) - 0x3E, 0x00, // Machine: x86-64 - 1, 0, 0, 0, // ELF version - 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Entry point - 0, 0, 0, 0, 0, 0, 0, 0, // Program header offset - 64, 0, 0, 0, 0, 0, 0, 0, // Section header offset - 0, 0, 0, 0, // Flags - 64, 0, // ELF header size - 0, 0, // Program header entry size - 0, 0, // Number of program header entries - 40, 0, // Section header entry size - 2, 0, // Number of section headers - 1, 0 // Section header string table index - }); - - // === .TEXT SECTION (Executable Code) === - byte[] textSection = { - // sys_write (write(1, message, 13)) - (byte) 0x48, (byte) 0xC7, (byte) 0xC0, 0x01, 0x00, 0x00, 0x00, // mov rax, 1 - (byte) 0x48, (byte) 0xC7, (byte) 0xC7, 0x01, 0x00, 0x00, 0x00, // mov rdi, 1 - (byte) 0x48, (byte) 0xBE, (byte)0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rsi, message - (byte) 0x48, (byte) 0xC7, (byte) 0xC2, 0x0D, 0x00, 0x00, 0x00, // mov rdx, 13 - (byte) 0x0F, (byte) 0x05, // syscall - - // sys_exit (exit(0)) - (byte) 0x48, (byte) 0xC7, (byte) 0xC0, 0x3C, 0x00, 0x00, 0x00, // mov rax, 60 - (byte) 0x48, (byte) 0x31, (byte) 0xFF, // xor rdi, rdi - (byte) 0x0F, (byte) 0x05 // syscall - }; - - // Append section data - buffer.position(256); - buffer.put(textSection); - - // === STRING MESSAGE (.data Section) === - byte[] message = "Hello, World!\n".getBytes(); - buffer.position(400); - buffer.put(message); - - // === SECTION HEADERS === - buffer.position(64); - - // Null Section (required as first entry) - buffer.put(new byte[40]); - - // .text Section - buffer.putInt(1); // Offset in section string table (".text") - buffer.putInt(1); // Type: SHT_PROGBITS - buffer.putLong(6); // Flags: Executable | Allocatable - buffer.putLong(0); // Virtual address - buffer.putLong(256); // Offset to section data - buffer.putLong(textSection.length); // Section size - buffer.putInt(0); // Link - buffer.putInt(0); // Info - buffer.putLong(4); // Address alignment - buffer.putLong(0); // No extra entries - - // === SECTION HEADER STRING TABLE === - buffer.position(500); - buffer.put(".text".getBytes()); - buffer.put((byte) 0x00); // Null terminator - - fos.write(buffer.array()); - System.out.println("ELF object file 'hello.o' created."); - } catch (IOException e) { - e.printStackTrace(); - } - } -} diff --git a/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java b/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java deleted file mode 100644 index 8f9e2981..00000000 --- a/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java +++ /dev/null @@ -1,381 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.Encoding.BAOS; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Ary; -import java.io.*; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.HashMap; -import java.util.Map; - -public class ElfFile { - - final CodeGen _code; - Ary<Section> _sections = new Ary<>(Section.class); - - ElfFile( CodeGen code ) { _code = code; } - - public static int writeCString(DataSection strtab, String name) { - // place name in the string table - int pos = strtab._contents.size(); - byte[] name_bytes = name.getBytes(); - strtab._contents.write(name_bytes, 0, name_bytes.length); - // the strings are null terminated - strtab._contents.write(0); - return pos; - } - - public static void write4( ByteArrayOutputStream bits, int op ) { - bits.write(op ); - bits.write(op>> 8); - bits.write(op>>16); - bits.write(op>>24); - } - public static void write8( ByteArrayOutputStream bits, long i64 ) { - write4(bits, (int) i64 ); - write4(bits, (int)(i64>>32)); - } - - public static final int SYMBOL_SIZE = 4+1+1+2+8+8; - - public static final int SYM_BIND_LOCAL = 0; - public static final int SYM_BIND_GLOBAL = 1; - public static final int SYM_BIND_WEAK = 2; - - public static final int SYM_TYPE_NOTYPE = 0; - public static final int SYM_TYPE_OBJECT = 1; - public static final int SYM_TYPE_FUNC = 2; - public static final int SYM_TYPE_SECTION = 3; - - public static class Symbol { - String _name; - int _name_pos; - - // index in the symbol table - int _index; - - // which section is it in - int _parent; - - // depends on the symbol type - // TODO - int _value, _size; - - // top 4bits are the "bind", bottom 4 are "type" - int _info; - - // bind 1011 - // 00001111 - // 10110000 - - Symbol(String name, int parent, int bind, int type) { - _name = name; - _parent = parent; - _info = (bind << 4) | (type & 0xF); - } - - void writeHeader(ByteBuffer out) { - out.putInt(_name_pos); // name - out.put((byte) _info); // info - // default visibility - out.put((byte) 0); // other - out.putShort((short) _parent); // shndx - out.putLong(_value); // value - out.putLong(_size); // size - } - } - - public static final int SHF_WRITE = 1; - public static final int SHF_ALLOC = 2; - public static final int SHF_EXECINSTR = 4; - public static final int SHF_STRINGS = 32; - public static final int SHF_INFO_LINK = 64; - - public static final int SECTION_HDR_SIZE = 64; - abstract public static class Section { - String _name; - int _index; - - int _type, _flags; - int _link, _info; - - // where's the name in the string table - int _name_pos; - int _file_offset; - - Section(String name, int type) { - _name = name; - _type = type; - } - - void writeHeader(ByteBuffer out) { - out.putInt(_name_pos); // name - out.putInt(_type); // type - out.putLong(_flags); // flags - out.putLong(0); // addr - out.putLong(_file_offset); // offset - out.putLong(size()); // size - out.putInt(_link); // link - out.putInt(_info); // info - out.putLong(16); // addralign - if (_type == 2) { - out.putLong(SYMBOL_SIZE);// entsize - } else if (_type == 4) { - out.putLong(24); // entsize - } else { - out.putLong(0); // entsize - } - } - - abstract void write(ByteBuffer out); - abstract int size(); - } - - public static class SymbolSection extends Section { - Ary<Symbol> _symbols = new Ary<>(Symbol.class); - Ary<Symbol> _loc = new Ary<>(Symbol.class); - - SymbolSection(String name, int type) { - super(name, type); - } - - @Override - void writeHeader(ByteBuffer out) { - // points to string table section - _link = 1; - - // number of non-local symbols - _info = _loc.len() + 1; - - super.writeHeader(out); - } - - void push(Symbol s) { - if ((s._info >> 4) != SYM_BIND_LOCAL) { - _symbols.push(s); - } else { - _loc.push(s); - } - } - - @Override - void write(ByteBuffer out) { - // Index 0 both designates the first entry in the table and serves as the undefined symbol index - for( int i = 0; i < SYMBOL_SIZE/4; i++ ) { - out.putInt(0); - } - // index is already set - for( Symbol s : _loc ) { - s.writeHeader(out); - } - for( Symbol s : _symbols ) { - s.writeHeader(out); - } - } - - @Override - int size() { - return (1 + _symbols.len() + _loc.len()) * SYMBOL_SIZE; - } - } - - public static class DataSection extends Section { - ByteArrayOutputStream _contents; - - DataSection(String name, int type) { - super(name, type); - _contents = new ByteArrayOutputStream(); - } - - DataSection(String name, int type, ByteArrayOutputStream contents) { - super(name, type); - _contents = contents; - } - - @Override - void write(ByteBuffer out) { - out.put(_contents.toByteArray()); - } - - @Override - int size() { - return _contents.size(); - } - } - - private void pushSection(Section s) { - _sections.push(s); - // ELF is effectively "1-based", due to the NULL section - s._index = _sections._len; - } - - /* creates function and stores where it starts*/ - private final HashMap<TypeFunPtr,Symbol> _funcs = new HashMap<>(); - private void encodeFunctions(SymbolSection symbols, DataSection text) { - for( int i=0; i<_code._cfg._len; i++ ) { - if( !(_code._cfg.at(i) instanceof FunNode fun) ) continue; - // skip until the function ends - while( !(_code._cfg.at(i) instanceof ReturnNode ret) ) - i++; - int end = _code._encoding._opStart[ret._nid] + _code._encoding._opLen[ret._nid]; - - Symbol func = new Symbol(fun._name, text._index, SYM_BIND_GLOBAL, SYM_TYPE_FUNC); - func._value = _code._encoding._opStart[fun._nid]; - func._size = end - func._value; - symbols.push(func); - _funcs.put(fun.sig(), func); - } - } - - public void export(String fname) throws IOException { - DataSection strtab = new DataSection(".strtab", 3 /* SHT_SYMTAB */); - // first byte is reserved for an empty string - strtab._contents.write(0); - pushSection(strtab); - - // place all the symbols - SymbolSection symbols = new SymbolSection(".symtab", 2 /* SHT_STRTAB */); - pushSection(symbols); - - // zero flag by default - // we've already constructed this entire section in the encoding phase - DataSection text = new DataSection(".text", 1 /* SHT_PROGBITS */, _code._encoding._bits); - text._flags = SHF_ALLOC | SHF_WRITE | SHF_EXECINSTR; - pushSection(text); - - // Build and write constant pool - BAOS cpool = new BAOS(); - Encoding enc = _code._encoding; - enc.writeConstantPool(cpool,false); - DataSection rdata = new DataSection(".rodata", 1 /* SHT_PROGBITS */, cpool); - rdata._flags = SHF_ALLOC; - pushSection(rdata); - - // populate function symbols - encodeFunctions(symbols, text); - - int idx = 1; - for( Section s : _sections ) { - Symbol sym = new Symbol(s._name, idx++, SYM_BIND_LOCAL, SYM_TYPE_SECTION); - // we can reuse the same name pos from the actual section - sym._name_pos = s._name_pos; - sym._size = s.size(); - symbols.push(sym); - } - - // calculate local index - int num = 1; - for( Symbol s : symbols._loc ) - s._index = num++; - // extra space for .rela.text - int start_global = num+1; // Add one to skip the final .rela.text local symbol - for( Symbol a: symbols._symbols ) - a._index = start_global++; - - // create .text relocations - DataSection text_rela = new DataSection(".rela.text", 4 /* SHT_RELA */); - for( Node n : enc._externals.keySet()) { - int nid = n._nid; - String extern = enc._externals.get(n); - - Symbol sym = new Symbol(extern, 0, SYM_BIND_GLOBAL, SYM_TYPE_NOTYPE); - sym._index = start_global++; - symbols.push(sym); - - int offset = enc._opStart[nid] + enc._opLen[nid] - 4; - - // u64 offset - write8(text_rela._contents, offset); - // u64 info - write8(text_rela._contents, ((long)sym._index << 32L) | 4L /* PLT32 */); - // i64 addend - write8(text_rela._contents, -4); - } - - // Write relocations for the constant pool - for( Encoding.Relo relo : enc._bigCons.values() ) { - Symbol glob = new Symbol("CPOOL$"+start_global, rdata._index, SYM_BIND_GLOBAL, SYM_TYPE_FUNC); - glob._value = relo._target; - glob._size = relo._t.size(); - glob._index = start_global++; - symbols.push(glob); - write8(text_rela._contents, relo._opStart+relo._off); - write8(text_rela._contents, ((long)glob._index << 32L) | relo._elf ); - write8(text_rela._contents, -4); - } - - text_rela._flags = SHF_INFO_LINK; - text_rela._link = 2; - text_rela._info = text._index; - pushSection(text_rela); - - // Final .rela.text symbol - Symbol sym = new Symbol(text_rela._name, num++, SYM_BIND_LOCAL, SYM_TYPE_SECTION); - sym._name_pos = text_rela._name_pos; - sym._size = text_rela.size(); - symbols.push(sym); - - // populate string table - for( Section s : _sections ) { s._name_pos = writeCString(strtab, s._name); } - for( Symbol s : symbols._symbols ) { s._name_pos = writeCString(strtab, s._name); } - for( Symbol s : symbols._loc ) { s._name_pos = writeCString(strtab, s._name); } - - int size = 64; // ELF header - // size of all section data - for( Section s : _sections ) { - s._file_offset = size; - size += s.size(); - } - // headers go at the very end - int shdr_offset = size; - // there's a "null" section at the start of the section headers - size += SECTION_HDR_SIZE*(1 + _sections.len()); - - // System.out.printf("Hello %d, %d, %s\n", size, shdr_offset); - ByteBuffer out = ByteBuffer.allocate(size); - out.order(ByteOrder.LITTLE_ENDIAN); - - // ELF header - out.put(new byte[]{ 0x7F, 'E', 'L', 'F', 2, 1, 1, 0 }); - out.put(new byte[8]); - out.putShort((short)1 /* ET_REL */); // type - out.putShort((short)62 /* AMD64 */); // machine - out.putInt(1); // version - out.putLong(0); // entry - out.putLong(0); // phoff - out.putLong(shdr_offset); // shoff - out.putInt(0); // flags - out.putShort((short)64); // ehsize - out.putShort((short)0); // phentsize - out.putShort((short)0); // phnum - out.putShort((short)64); // shentsize - out.putShort((short) (1 + _sections.len())); // shnum - out.putShort((short)1); // shstrndx - - // raw section data - for( Section s : _sections ) { - s.write(out); - } - - // "null" section - for( int i = 0; i < 16; i++ ) { - out.putInt(0); - } - - for( Section s : _sections ) { - s.writeHeader(out); - } - assert out.position() == size; - - File file = new File(fname); - if( file.getParentFile()!=null ) - file.getParentFile().mkdirs(); - BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file)); - bos.write(out.array()); - bos.close(); - } - -} diff --git a/src/main/java/com/seaofnodes/simple/codegen/Encoding.java b/src/main/java/com/seaofnodes/simple/codegen/Encoding.java deleted file mode 100644 index 1065cd9f..00000000 --- a/src/main/java/com/seaofnodes/simple/codegen/Encoding.java +++ /dev/null @@ -1,531 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.print.IRPrinter; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import java.io.ByteArrayOutputStream; -import java.util.*; - - -/** - * Instruction encodings - * <p> - * This class holds the encoding bits, plus the relocation information needed - * to move the code to a non-zero offset, or write to an external file (ELF). - * <p> - * There are also a bunch of generic utilities for managing bits and bytes - * common in all encodings. - */ -public class Encoding { - - // Top-level program graph structure - public final CodeGen _code; - - // Instruction bytes. The registers are encoded already. Relocatable data - // is in a fixed format depending on the kind of relocation. - - // - RIP-relative offsets into the same chunk are just encoded with their - // check relative offsets; no relocation info is required. - - // - Fixed address targets in the same encoding will be correct for a zero - // base; moving the entire encoding requires adding the new base address. - - // - RIP-relative to external chunks have a zero offset; the matching - // relocation info will be used to patch the correct value. - - public static class BAOS extends ByteArrayOutputStream { - public byte[] buf() { return buf; } - void set( byte[] buf0, int count0 ) { buf=buf0; count=count0; } - }; - final public BAOS _bits = new BAOS(); - - public int [] _opStart; // Start of opcodes, by _nid - public byte[] _opLen; // Length of opcodes, by _nid - - // Function headers now padded when printing - public boolean _padFunHeads; - - // Big Constant relocation info. - public static class Relo { - public final Node _op; - public final Type _t; // Constant type - public final byte _off; // Offset from start of opcode - public final byte _elf; // ELF relocation type, e.g. 2/PC32 - public int _target; // Where constant is finally placed - public int _opStart; // Opcode start - Relo( Node op, Type t, byte off, byte elf ) { - _op=op; _t=t; _off=off; _elf=elf; - } - } - public final HashMap<Node,Relo> _bigCons = new HashMap<>(); - - Encoding( CodeGen code ) { _code = code; } - - // Shortcut to the defining register - public short reg(Node n) { - return _code._regAlloc.regnum(n); - } - - public int read1(int idx) { - byte[] buf = _bits.buf(); - return (buf[idx]&0xFF); - } - public int read2(int idx) { - byte[] buf = _bits.buf(); - return (buf[idx]&0xFF) | (buf[idx+1]&0xFF) <<8; - } - public int read4(int idx) { - byte[] buf = _bits.buf(); - return (buf[idx]&0xFF) | (buf[idx+1]&0xFF) <<8 | (buf[idx+2]&0xFF)<<16 | (buf[idx+3]&0xFF)<<24; - } - public long read8(int idx) { - return (read4(idx) & 0xFFFFFFFFL) | read4(idx+4); - } - - // This buffer is invalid/moving until after all encodings are written - public byte[] bits() { return _bits.buf(); } - - // 4 byte little-endian write - public void patch4( int idx, int val ) { - byte[] buf = _bits.buf(); - buf[idx ] = (byte)(val ); - buf[idx+1] = (byte)(val>> 8); - buf[idx+2] = (byte)(val>>16); - buf[idx+3] = (byte)(val>>24); - } - - static void padN(int n, BAOS bits) { - while( (bits.size()+n-1 & -n) > bits.size() ) - bits.write(0); - } - - // Convenience for writing log-N - static void addN( int log, Type t, BAOS bits ) { - long x = t instanceof TypeInteger ti - ? ti.value() - : log==3 - ? Double.doubleToRawLongBits( ((TypeFloat)t).value()) - : Float.floatToRawIntBits((float)((TypeFloat)t).value()); - addN(log,x,bits); - } - static void addN( int log, long x, BAOS bits ) { - for( int i=0; i < 1<<log; i++ ) { - bits.write((int)x); - x >>= 8; - } - } - - public Encoding add1( int op ) { addN(0,op,_bits); return this; } - public Encoding add2( int op ) { addN(1,op,_bits); return this; } - public Encoding add4( int op ) { addN(2,op,_bits); return this; } - public Encoding add8(long op ) { addN(3,op,_bits); return this; } - - - // Nodes need "relocation" patching; things done after code is placed. - // Record src and dst Nodes. - private final HashMap<Node,CFGNode> _internals = new HashMap<>(); - // Source is a Call, destination in the Fun. - public Encoding relo( CallNode call ) { - _internals.put(call,_code.link(call.tfp())); - return this; - } - public Encoding relo( ConstantNode con ) { - TypeFunPtr tfp = (TypeFunPtr)con._con; - _internals.put(con,_code.link(tfp)); - return this; - } - public void jump( CFGNode jmp, CFGNode dst ) { - _internals.put(jmp,dst.uctrlSkipEmpty()); - } - - - final HashMap<Node,String> _externals = new HashMap<>(); - public Encoding external( Node n, String extern ) { - _externals.put(n,extern); - return this; - } - - // Store t as a 32/64 bit constant in the code space; generate RIP-relative - // addressing to load it - public void largeConstant( Node relo, Type t, int off, int elf ) { - assert t.isConstant(); - assert (byte)off == off; - assert (byte)elf == elf; - _bigCons.put(relo,new Relo(relo,t,(byte)off,(byte)elf)); - } - - // -------------------------------------------------- - void encode() { - // Basic block layout: negate branches to keep blocks in-order; insert - // unconditional jumps. Attempt to keep backwards branches taken, - // forwards not-taken (this is the default prediction on most - // hardware). Layout is still Reverse Post Order but with more - // restrictions. - basicBlockLayout(); - - // Write encoding bits in order into a big byte array. - // Record opcode start and length. - writeEncodings(); - - // Short-form RIP-relative support: replace long encodings with short - // encodings and compact the code, changing all the offsets. - compactShortForm(); - - // Patch RIP-relative and local encodings now. - patchLocalRelocations(); - } - - // -------------------------------------------------- - // Basic block layout: negate branches to keep blocks in-order; insert - // unconditional jumps. Attempt to keep backwards branches taken, forwards - // not-taken (this is the default prediction on most hardware). Layout is - // still Reverse Post Order but with more restrictions. - private void basicBlockLayout() { - IdentityHashMap<LoopNode,Ary<CFGNode>> rpos = new IdentityHashMap<>(); - Ary<CFGNode> rpo = new Ary<>(CFGNode.class); - rpos.put(_code._start.loop(),rpo); - BitSet visit = _code.visit(); - rpo.add(_code._stop); - for( Node n : _code._start._outputs ) - if( n instanceof FunNode fun ) { - int x = rpo._len; - _rpo_cfg(fun, visit, rpos ); - assert rpo.at(x) instanceof ReturnNode; - } - rpo.add(_code._start); - - // Reverse in-place - for( int i=0; i< rpo.size()>>1; i++ ) - rpo.swap(i,rpo.size()-1-i); - visit.clear(); - _code._cfg = rpo; // Save the new ordering - } - - - private void _rpo_cfg(CFGNode bb, BitSet visit, IdentityHashMap<LoopNode,Ary<CFGNode>> rpos ) { - if( bb==null || visit.get(bb._nid) ) return; - visit.set(bb._nid); - CFGNode next = bb.uctrl(); - - // Loops run an inner "rpo_cfg", then append the entire loop body in - // place. This keeps loop bodies completely contained, although if - // Some Day Later we have real profile data we ought to arrange the - // branch orderings based on frequency. - if( bb instanceof LoopNode loop ) { - Ary<CFGNode> body = new Ary<>(CFGNode.class); // Private RPO for the loop - rpos.put(loop.loop(),body); // Find it via loop tree - _rpo_cfg(next,visit,rpos); // RPO the loop - body.add(loop); // Include loop last (first in RPO) - Ary<CFGNode> outer = rpos.get(loop.cfg(1).loop()); - outer.addAll(body); // Append to original CFG - return; - } - - // IfNodes visit 2 sides, and may choose to reorder them in the RPO - if( bb instanceof IfNode iff ) { - // Pick out the T/F projections - CProjNode t = iff.cproj(0); - CProjNode f = iff.cproj(1); - // Invert the branch or not - if( shouldInvert(t,f,iff.loopDepth()) ) { - iff.negate(); - t.invert(); - f.invert(); - CProjNode tmp=f; f=t; t=tmp; // Swap t/f - } - - // Whichever side is visited first becomes last in the RPO. With - // no loops, visit the False side last (so True side first) so that - // when the False RPO visit returns, the IF is immediately next. - // When the RPO is reversed, the fall-through path will always be - // following the IF. - _rpo_cfg(t,visit,rpos); // True side first - next = f; // False side last - } - - // If the *next* BB has already been visited, and we are not already a - // jump, we may need an unconditional forwards jump here - Ary<CFGNode> rpo = rpos.get(bb.loop()); - if( next!=null && visit.get(next._nid) && !(bb instanceof IfNode) ) { - boolean needJump = next instanceof LoopNode - // Empty blocks from an IF will invert the IF and backwards - // branch to the loop head. If not empty, or not an IF - // will need a jump. - ? (bb.nOuts()>1 || !(bb.cfg0() instanceof IfNode) ) - // Forwards jump. If all the blocks, in RPO order, to our - // target are empty, we will fall in and not need a jump. - : !backwardsEmptyScan(rpo,next); - if( needJump ) { - CFGNode jmp = _code._mach.jump(); - jmp._ltree = bb._ltree; - jmp.setDefX(0,bb); - next.setDef(next._inputs.find(bb),jmp); - rpo.add(jmp); - } - } - - _rpo_cfg(next,visit,rpos); - rpo.add(bb); - } - - // Should this test be inverted? - private static boolean shouldInvert(CFGNode t, CFGNode f, int bld) { - int tld = t.loopDepth(), fld = f.loopDepth(); - // These next two are symmetric and can happen in any order; if `tld < - // bld` is true, the `fld < bld` must be false, or else both directions - // exit the loop... and the IF test would not be in the loop. - - // true to exit a loop usually (and false falls into Yet Another Loop - // Block), invert if false is taking an empty backwards branch. - if( tld < bld ) return forwardsEmptyScan(f,bld); - // false to exit a loop, normally invert to true (except empty back) - if( fld < bld ) return !forwardsEmptyScan(t,bld); - - // Not exiting the loop; staying at this depth (or going deeper loop). - - // Jump/true to an empty backwards block - if( forwardsEmptyScan(t,bld) ) return false; - if( forwardsEmptyScan(f,bld) ) return true ; - - // Fall/false into a full block, Jump/true to an empty block. - if( f.nOuts()>1 && t.nOuts()==1 ) return false; - if( t.nOuts()>1 && f.nOuts()==1 ) return true ; - // Jump to a merge point, assuming other things are jumping there as well - if( f.out(0) instanceof RegionNode ) return true ; - if( t.out(0) instanceof RegionNode ) return false; - // Everything else equal, use pre-order - return t._pre > f._pre; - } - - // Do we continue forwards from 'c' to the Loop back edge, without doing - // anything else? Then this branch can directly jump to the loop head. - private static boolean forwardsEmptyScan( CFGNode c, int bld ) { - if( c.nOuts()!=1 || c.loopDepth()!=bld ) return false; - return c.uctrl() instanceof RegionNode cfg && - ((cfg instanceof LoopNode && cfg._ltree==c._ltree) || forwardsEmptyScan(cfg,bld)); - } - - // Is the CFG from "next" to the end empty? This means jumping to "next" - // will naturally fall into the end. - private static boolean backwardsEmptyScan(Ary<CFGNode> rpo, CFGNode next) { - for( int i=rpo._len-1; rpo.at(i)!=next; i-- ) - if( rpo.at(i).nOuts()!=1 ) - return false; - return true; - } - - // -------------------------------------------------- - // Write encoding bits in order into a big byte array. - // Record opcode start and length. - public FunNode _fun; // Currently encoding function - private void writeEncodings() { - _opStart= new int [_code.UID()]; - _opLen = new byte[_code.UID()]; - for( CFGNode bb : _code._cfg ) { - if( !(bb instanceof MachNode mach0) ) - _opStart[bb._nid] = _bits.size(); - else if( bb instanceof FunNode fun ) { - _fun = fun; // Currently encoding function - _opStart[bb._nid] = _bits.size(); - mach0.encoding( this ); - _opLen[bb._nid] = (byte) (_bits.size() - _opStart[bb._nid]); - } - for( Node n : bb._outputs ) { - if( n instanceof MachNode mach && !(n instanceof FunNode) ) { - _opStart[n._nid] = _bits.size(); - mach.encoding( this ); - _opLen[n._nid] = (byte) (_bits.size() - _opStart[n._nid]); - } - } - } - } - - // -------------------------------------------------- - // Short-form RIP-relative support: replace short encodings with long - // encodings and expand the code, changing all the offsets. - private void compactShortForm() { - int len = _code._cfg._len; - int[] oldStarts = new int[len]; - for( int i=0; i<len; i++ ) - oldStarts[i] = _opStart[_code._cfg.at(i)._nid]; - - // TODO: Rewrite this algo to use the small "_jmps" list of just the - // jumps instead of walking all blocks. - // TODO2: Allow short encodings on non-jumps. This algo doesn't - // really care jumps vs other rip-relative things. - - // Check the size of short jumps, adjust the block starts for a whole - // pass over all blocks. If some jumps go long, they might stretch the - // distance for other jumps, so another pass is needed. - int slide = -1; - while( slide != 0) { // While no fails - slide = 0; - for( int i=0; i<len; i++ ) { - CFGNode bb = _code._cfg.at(i); - _opStart[bb._nid] += slide; - // Slide down all other (non-CFG) ops in the block - for( Node n : bb._outputs ) - if( n instanceof MachNode && !(n instanceof CFGNode) ) - _opStart[n._nid] += slide; - if( bb instanceof RIPRelSize riprel ) { - CFGNode target = (bb instanceof IfNode iff ? iff.cproj(0) : (CFGNode)bb.out(0)).uctrlSkipEmpty(); - // Delta is from opStart to opStart. X86 at least counts - // the delta from the opEnd, but we don't have the end until - // we decide the size - so the encSize has to deal - int delta = _opStart[target._nid] - _opStart[bb._nid]; - byte opLen = riprel.encSize(delta); - // Recorded size is smaller than the current size? - if( _opLen[bb._nid] < opLen ) { - // Start sliding the code down; record slide amount and new size - slide += opLen - _opLen[bb._nid]; - _opLen[bb._nid] = opLen; - } - } - } - } - - - // Go again, inserting padding on function headers. Since no - // short-jumps span function headers, the padding will not make any - // short jumps fail. - for( int i=0; i<len; i++ ) { - CFGNode bb = _code._cfg.at(i); - // Functions pad to align 16 - if( bb instanceof FunNode ) { - int newStart = _opStart[bb._nid]+slide; - slide += (newStart+15 & -16)-newStart; - } - _opStart[bb._nid] += slide; - for( Node n : bb._outputs ) - if( n instanceof MachNode && !(n instanceof CFGNode) ) - _opStart[n._nid] += slide; - } - _padFunHeads = true; - - // Copy/slide the bits to make space for all the longer branches - int grow = _opStart[_code._cfg.at(len-1)._nid] - oldStarts[len-1]; - if( grow > 0 ) { // If no short-form ops, nothing to do here - int end = _bits.size(); - byte[] bits = new byte[end+grow]; - for( int i=len-1; i>=0; i-- ) { - int start = oldStarts[i]; - if( start==0 && i>1 ) continue; - int oldStart = oldStarts[i]; - int newStart = _opStart[_code._cfg.at(i)._nid]; - System.arraycopy(_bits.buf(),oldStart,bits,newStart,end-start); - end = start; - } - _bits.set(bits,bits.length); - } - - } - - - // -------------------------------------------------- - // Patch local encodings now - void patchLocalRelocations() { - // Walk the local code-address relocations - for( Node src : _internals.keySet() ) { - int start = _opStart[src._nid]; - Node dst = _internals.get(src); - int target = _opStart[dst._nid]; - ((RIPRelSize)src).patch(this, start, _opLen[src._nid], target - start); - } - } - - // -------------------------------------------------- - // Write the constant pool into the BAOS and optionally patch locally - void writeConstantPool( BAOS bits, boolean patch ) { - padN(16,bits); - - // radix sort the big constants by alignment - Ary<Relo>[] raligns = new Ary[5]; - for( Node op : _bigCons.keySet() ) { - Relo relo = _bigCons.get(op); - int align = relo._t.alignment(); - Ary<Relo> relos = raligns[align]==null ? (raligns[align]=new Ary<>(Relo.class)) : raligns[align]; - relos.add(relo); - } - - - // Types can be used more than once; collapse the dups - HashMap<Type,Integer> targets = new HashMap<>(); - - // By alignment - for( int align = 4; align >= 0; align-- ) { - Ary<Relo> relos = raligns[align]; - if( relos == null ) continue; - for( Relo relo : relos ) { - // Map from relo to constant start and patch - Integer target = targets.get(relo._t); - if( target==null ) { - targets.put(relo._t,target = bits.size()); - // Write constant into constant pool - switch( relo._t ) { - case TypeTuple tt -> cpool(align,bits,tt); - case TypeStruct ts -> cpool(bits,ts); - // Simple primitive (e.g. larger int, float) - default -> addN(align,relo._t,bits); - } - } - // Record target address and opcode start - relo._target = target; - relo._opStart= _opStart[relo._op._nid]; - // Go ahead and locally patch in-memory - if( patch ) - ((RIPRelSize)relo._op).patch(this, relo._opStart, _opLen[relo._op._nid], relo._target - relo._opStart); - } - } - } - - // Constant tuples put all entries at same alignment - private void cpool(int align, BAOS bits, TypeTuple tt) { - for( Type tx : tt._types ) - addN(align,tx,bits); - } - - // Structs use internal field layout - private void cpool( BAOS bits, TypeStruct ts ) { - // Field order by offset - int[] layout = ts.layout(); - int off=0; // offset in the struct - for( int fn=0; fn<ts._fields.length; fn++ ) { - Field f = ts._fields[layout[fn]]; - int foff = ts. offset(layout[fn]); - // Pad up to field - while( off < foff ) { bits.write(0); off++; }; - // Constant array fields are special - if( f._fname=="[]" ) { // Must be a constant array - ((TypeConAry)f._t).write(bits); - off += ((TypeConAry)f._t).len(); - } else { - int log = f._t.log_size(); - addN(log,f._t,bits); - off += 1<<log; - } - } - } - - - // A series of libc/external calls that Simple can link against in a JIT. - // Since no runtime in the JVM process, using magic numbers for the CPU - // emulators to pick up on. - public static int SENTINEL_CALLOC = -4; - public static int SENTINEL_WRITE = -8; - - void patchGlobalRelocations() { - for( Node src : _externals.keySet() ) { - int start = _opStart[src._nid]; - String dst = _externals.get(src); - int target = switch( dst ) { - case "calloc" -> SENTINEL_CALLOC; - case "write" -> SENTINEL_WRITE ; - default -> throw Utils.TODO(); - }; - ((RIPRelSize)src).patch(this, start, _opLen[src._nid], target - start); - } - } -} diff --git a/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java deleted file mode 100644 index 520a18a3..00000000 --- a/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java +++ /dev/null @@ -1,322 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.IterPeeps.WorkList; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.Utils; -import java.util.*; - -public abstract class GlobalCodeMotion { - - // Arrange that the existing isCFG() Nodes form a valid CFG. The - // Node.use(0) is always a block tail (either IfNode or head of the - // following block). There are no unreachable infinite loops. - public static void buildCFG( CodeGen code ) { - Ary<CFGNode> rpo = new Ary<>(CFGNode.class); - _rpo_cfg(null, code._start, code.visit(), rpo); - // Reverse in-place - for( int i=0; i< rpo.size()>>1; i++ ) - rpo.swap(i,rpo.size()-1-i); - // Set global CFG - code._cfg = rpo; - - schedEarly(code); - - // Break up shared global constants by functions - breakUpGlobalConstants(code._start); - - code._visit.clear(); - schedLate (code); - } - - // Post-Order of CFG - private static void _rpo_cfg(CFGNode def, Node use, BitSet visit, Ary<CFGNode> rpo) { - if( !(use instanceof CFGNode cfg) || visit.get(cfg._nid) ) - return; // Been there, done that - if( def instanceof ReturnNode && use instanceof CallEndNode ) - return; - assert !( def instanceof CallNode && use instanceof FunNode ); - visit.set(cfg._nid); - for( Node useuse : cfg._outputs ) - _rpo_cfg(cfg,useuse,visit,rpo); - rpo.add(cfg); - } - - // Break up shared global constants by functions - private static void breakUpGlobalConstants( Node start ) { - // For all global constants - for( int i=0; i< start.nOuts(); i++ ) { - Node con = start.out(i); - if( con instanceof MachNode mach && mach.isClone() ) { - breakUpGlobalConstants(con); - // While constant has users in different functions - while( true ) { - // Find a function user, and another function - FunNode fun = null; - boolean done=true; - for( Node use : con.outs() ) { - FunNode fun2 = use instanceof ReturnNode ret ? ret.fun() : use.cfg0().fun(); - if( fun==null || fun==fun2 ) fun=fun2; - else { done=false; break; } - } - // Single function user, so this constant is not shared - if( done ) { - if( con.in(0)==start ) i--; - con.setDef(0,fun); - break; - } - // Move function users to a private constant - Node con2 = mach.copy(); // Private constant clone - con2._inputs.set(0,null); // Preserve edge invariants from clone - con2.setDef(0,fun); - // Move function users to this private constant - for( int j=0; j<con._outputs._len; j++ ) { - Node use = con.out(j); - FunNode fun2 = use.cfg0().fun(); - if( fun2==fun ) { - use.setDef(use._inputs.find(con),con2); - j--; - } - } - } - } - } - } - - // ------------------------------------------------------------------------ - // Visit all nodes in CFG Reverse Post-Order, essentially defs before uses - // (except at loops). Since defs are visited first - and hoisted as early - // as possible, when we come to a use we place it just after its deepest - // input. - private static void schedEarly(CodeGen code) { - // Reverse Post-Order on CFG - for( CFGNode cfg : code._cfg ) { - cfg.loopDepth(); - for( Node n : cfg._inputs ) - _schedEarly(n,code ); - if( cfg instanceof RegionNode ) - for( Node phi : cfg._outputs ) - if( phi instanceof PhiNode ) - _schedEarly(phi,code ); - } - } - - private static void _schedEarly(Node n, CodeGen code) { - if( n==null || code._visit.get(n._nid) ) return; // Been there, done that - assert !(n instanceof CFGNode); - code._visit.set(n._nid); - // Schedule not-pinned not-CFG inputs before self. Since skipping - // Pinned, this never walks the backedge of Phis (and thus spins around - // a data-only loop), eventually attempting relying on some pre-visited- - // not-post-visited data op with no scheduled control. - for( Node def : n._inputs ) - if( def!=null && !(def instanceof PhiNode) ) - _schedEarly(def,code); - - // If not-pinned (e.g. constants, projections, phi) and not-CFG - if( !n.isPinned() ) { - // Schedule at deepest input - CFGNode early = code._start; // Maximally early, lowest idepth - if( n.in(0) instanceof CFGNode cfg ) early = cfg; - for( int i=1; i<n.nIns(); i++ ) - if( n.in(i)!=null && n.in(i).cfg0().idepth() > early.idepth() ) - early = n.in(i).cfg0(); // Latest/deepest input - n.setDef(0,early); - } - } - - // ------------------------------------------------------------------------ - private static void schedLate( CodeGen code ) { - CFGNode[] late = new CFGNode[code.UID()]; - Node[] ns = new Node[code.UID()]; - // Breadth-first scheduling - breadth(code._stop,ns,late); - - // Copy the best placement choice into the control slot - for( int i=0; i<late.length; i++ ) - if( ns[i] != null && !(ns[i] instanceof ProjNode) ) - ns[i].setDef(0,late[i]); - } - - private static void breadth(Node stop, Node[] ns, CFGNode[] late) { - // Things on the worklist have some (but perhaps not all) uses done. - WorkList<Node> work = new WorkList<>(); - work.push(stop); - Node n; - outer: - while( (n = work.pop()) != null ) { - assert late[n._nid]==null; // No double visit - // These I know the late schedule of, and need to set early for loops - if( n instanceof CFGNode cfg ) late[n._nid] = cfg.blockHead() ? cfg : cfg.cfg(0); - else if( n instanceof PhiNode phi ) late[n._nid] = phi.region(); - else if( n instanceof ProjNode && n.in(0) instanceof CFGNode cfg ) late[n._nid] = cfg; - else { - - // All uses done? - for( Node use : n._outputs ) - if( use!=null && late[use._nid]==null ) - continue outer; // Nope, await all uses done - - // Loads need their memory inputs' uses also done - if( n instanceof MemOpNode ld && ld._isLoad ) - for( Node memuse : ld.mem()._outputs ) - if( late[memuse._nid]==null && - // New makes new memory, never crushes load memory - !(memuse instanceof NewNode) && - // Load-use directly defines memory - (memuse._type instanceof TypeMem || - // Load-use directly defines memory - memuse instanceof CallNode || - // Load-use indirectly defines memory - (memuse._type instanceof TypeTuple tt && tt._types[ld._alias] instanceof TypeMem)) ) - continue outer; - - // All uses done, schedule - _doSchedLate(n,ns,late); - } - - // Walk all inputs and put on worklist, as their last-use might now be done - for( Node def : n._inputs ) { - if( def!=null && late[def._nid]==null ) - work.push(def); - // if the def has a load use, maybe the load can fire - if( def!=null ) - for( Node out : def._outputs ) - if( out instanceof MemOpNode ld && ld._isLoad && late[ld._nid]==null ) - work.push(ld); - } - - if( n instanceof LoopNode loop ) { - for( Node phi : loop._outputs ) { - if( phi instanceof PhiNode && late[phi._nid]==null ) - work.push(phi); - } - } - } - } - - private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late) { - // Walk uses, gathering the LCA (Least Common Ancestor) of uses - CFGNode early = n.in(0) instanceof CFGNode cfg ? cfg : n.in(0).cfg0(); - assert early != null; - CFGNode lca = null; - for( Node use : n._outputs ) - if( use != null ) - lca = use_block(n,use, late)._idom(lca,null); - - // Loads may need anti-dependencies, raising their LCA - if( n instanceof MemOpNode load && load._isLoad ) - lca = find_anti_dep(lca,load,early,late); - - - // Nodes setting a single register and getting killed will stay close - // to the uses, since they will be forced to spill anyway. The kill - // check is very weak, and some may be hoisted only to spill in the RA. - if( n instanceof MachNode mach ) { - RegMask out = mach.outregmap(); - if( out!=null && out.size1() ) { - int reg = mach.outregmap().firstReg(); - // Look for kills - outer: - for( CFGNode lca2=lca; lca2 != early; lca2 = lca2.idom() ) { - if( lca2 instanceof MachNode mach2 ) { - for( int i=1; i<lca2.nIns(); i++ ) { - RegMask mask = mach2.regmap(i); - if( mask!=null && mask.test(reg) ) { - early = lca2 instanceof IfNode ? lca2.idom() : lca2; - break outer; - } - } - RegMask kill = mach2.killmap(); - if( kill != null ) - throw Utils.TODO(); - } - } - } - } - - // Walk up from the LCA to the early, looking for best place. This is - // the lowest execution frequency, approximated by least loop depth and - // deepest control flow. - CFGNode best = lca; - lca = lca.idom(); // Already found best for starting LCA - for( ; lca != early.idom(); lca = lca.idom() ) - if( better(lca,best) ) - best = lca; - assert !(best instanceof IfNode); - ns [n._nid] = n; - late[n._nid] = best; - } - - // Block of use. Normally from late[] schedule, except for Phis, which go - // to the matching Region input. - private static CFGNode use_block(Node n, Node use, CFGNode[] late) { - if( !(use instanceof PhiNode phi) ) - return late[use._nid]; - CFGNode found=null; - for( int i=1; i<phi.nIns(); i++ ) - if( phi.in(i)==n ) - found = phi.region().cfg(i)._idom(found,null); - - assert found!=null; - return found; - } - - - // Least loop depth first, then largest idepth - private static boolean better( CFGNode lca, CFGNode best ) { - return lca.loopDepth() < best.loopDepth() || - lca instanceof NeverNode || - lca.idepth() > best.idepth() || - best instanceof IfNode; - } - - private static CFGNode find_anti_dep(CFGNode lca, MemOpNode load, CFGNode early, CFGNode[] late) { - // We could skip final-field loads here. - // Walk LCA->early, flagging Load's block location choices - for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() ) - cfg._anti = load._nid; - // Walk load->mem uses, looking for Stores causing an anti-dep - for( Node mem : load.mem()._outputs ) { - switch( mem ) { - case MemOpNode st: - if( !st._isLoad && load._alias == st._alias ) { - assert late[mem._nid] != null; - lca = anti_dep( load, late[mem._nid], mem.cfg0(), lca, st ); - } - break; // Loads do not cause anti-deps on other loads - case CallNode call: - assert late[call._nid]!=null; - lca = anti_dep(load,late[call._nid],call.cfg0(),lca,call); - break; - case PhiNode phi: - // Repeat anti-dep for matching Phi inputs. - // No anti-dep edges but may raise the LCA. - for( int i=1; i<phi.nIns(); i++ ) - if( phi.in(i)==load.mem() ) - lca = anti_dep(load,phi.region().cfg(i),load.mem().cfg0(),lca,null); - break; - case NewNode st: break; - case ReturnNode ret: break; // Load must already be ahead of Return - case MemMergeNode ret: break; // Mem uses now on ScopeMin - case NeverNode never: break; - default: throw Utils.TODO(); - } - } - return lca; - } - - // - private static CFGNode anti_dep( MemOpNode load, CFGNode stblk, CFGNode defblk, CFGNode lca, Node st ) { - // Store and Load overlap, need anti-dependence - if( stblk._anti==load._nid ) { - lca = stblk._idom(lca,null); // Raise Loads LCA - if( lca == stblk && st != null && st._inputs.find(load) == -1 ) // And if something moved, - st.addDef(load); // Add anti-dep as well - return lca; // Cap this stores' anti-dep to here - } - return lca; - } - -} diff --git a/src/main/java/com/seaofnodes/simple/codegen/IFG.java b/src/main/java/com/seaofnodes/simple/codegen/IFG.java deleted file mode 100644 index 6dd6fbf1..00000000 --- a/src/main/java/com/seaofnodes/simple/codegen/IFG.java +++ /dev/null @@ -1,549 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.Utils; -import java.util.BitSet; -import java.util.IdentityHashMap; - -// Interference Graph -abstract public class IFG { - // Interference Graph Support - - // Map from a Basic Block to Live-Out: {a map from a Live Range to a Def} - private static final IdentityHashMap<CFGNode,IdentityHashMap<LRG,Node>> BBOUTS = new IdentityHashMap<>(); - static void resetBBLiveOut() { - for( IdentityHashMap<LRG,Node> bbout : BBOUTS.values() ) - bbout.clear(); - } - static final IdentityHashMap<LRG,Node> TMP = new IdentityHashMap<>(); - - // Inteference Graph: Array of Bitsets - static final Ary<BitSet> IFG = new Ary<>(BitSet.class); - static void resetIFG() { - for( BitSet bs : IFG ) - if( bs!=null ) bs.clear(); - } - - // Set matching bit - private static void addIFG( LRG lrg0, LRG lrg1 ) { - short x0 = lrg0._lrg, x1 = lrg1._lrg; - // Triangulate - if( x0 < x1 ) _addIFG(x0,x1); - else _addIFG(x1,x0); - } - // Add lrg1 to lrg0's conflict set - private static void _addIFG( short x0, short x1 ) { - BitSet ifg = IFG.atX(x0); - if( ifg==null ) - IFG.setX(x0,ifg = new BitSet()); - ifg.set(x1); - } - - - private static final Ary<CFGNode> WORK = new Ary<>(CFGNode.class); - static void pushWork(CFGNode bb) { - if( WORK.find(bb) == -1 ) - WORK.push(bb); - } - - // ------------------------------------------------------------------------ - // Visit all blocks, using the live-out LRGs per-block and doing a backwards - // walk over each block. At the end of the walk, push the live-out sets to - // prior blocks. Due to loops, no single visitation order suffices. This - // problem can be fairly efficiently solved with a proper LIVE calculation - // and bitsets. In the interests of simplicity (and assumption of smaller - // programs) I am skipping LIVE and directly computing it during the IFG - // building. - - // Start by setting the live-out of the exit block (to empty), and putting - // it on a worklist. - - // - Pull from worklist a block who's live-out has changed - // - Walk backwards, adding interferences and computing live. - // - At block head, "push" the new live-out to prior blocks. - // - - If they pick up new live-outs, put them on the worklist. - - public static boolean build( int round, RegAlloc alloc ) { - // Reset all to empty - resetBBLiveOut(); - resetIFG(); - - // Last block has nothing live out - assert WORK.isEmpty(); - for( CFGNode bb : alloc._code._cfg ) - if( bb.blockHead()) - WORK.push( bb ); - - // Process blocks until no more changes - while( !WORK.isEmpty() ) - do_block(round,alloc,WORK.pop()); - - if( alloc.success() ) - convert2DAdjacency(alloc); - return alloc.success(); - } - - // Walk one block backwards, compute live-in from live-out, and build IFG - static void do_block( int round, RegAlloc alloc, CFGNode bb ) { - assert bb.blockHead(); - IdentityHashMap<LRG,Node> live_out = BBOUTS.get(bb); - TMP.clear(); - if( live_out != null ) - TMP.putAll(live_out); // Copy bits to temp - - // A backwards walk over instructions in the basic block - for( int inum = bb.nOuts()-1; inum >= 0; inum-- ) { - Node n = bb.out(inum); - if( n.in(0) != bb ) continue; - // In a backwards walk, proj users come before the node itself - if( n instanceof MultiNode ) - for( Node proj : n.outs() ) - if( proj instanceof ProjNode ) - do_node(alloc,proj); - if( n instanceof MachNode ) - do_node(alloc,n); - } - - // The bb head kills register, e.g. a CallEnd and caller-save registers - if( bb instanceof MachNode mach ) - kills(alloc,mach); - - // Push live-sets backwards to priors in CFG. - if( bb instanceof RegionNode ) - for( int i=1; i<bb.nIns(); i++ ) - mergeLiveOut(alloc,bb,i); - else - mergeLiveOut(alloc,bb,0); - } - - private static void do_node(RegAlloc alloc, Node n) { - // Defining means killing live LRG - LRG lrg = alloc.lrg(n); - if( lrg != null ) { - // Check for def-side self-conflict live ranges. These must split, - // and only happens during the first round a particular LRG splits. - selfConflict(alloc,n,lrg); - TMP.remove(lrg); // Kill def - } - - // Phis use and define the same live range, i.e. these LRGs already - // marked conflicted, no need to mark again - if( n instanceof PhiNode ) - return; - - // Kill any killed registers; milli-code routines like New can kill - // will not being a CFG. - if( n instanceof MachNode m ) - kills(alloc,m); - - // Interfere n with all live - if( lrg!=null ) { - // Interfere n with all live - for( LRG tlrg : TMP.keySet() ) { - assert tlrg.leader(); - // Skip self - if( lrg != tlrg && - // And register sets overlap - lrg._mask.overlap(tlrg._mask) ) - // Then tlrg and lrg interfere. If lrg needs the single - // last tlrg register at some point, either tlrg or lrg - // must fail. If *n* (a subset of lrg) needs the single - // last tlrg register then only tlrg must fail. - if( ((MachNode)n).outregmap().size1() ) { - if( !tlrg.clr(lrg._mask.firstReg()) ) // Clear bit, no interference - alloc.fail(tlrg); // Clearing drives mask to empty - } else addIFG(lrg,tlrg); // Add interference - } - } - - - // Check for self-conflict live ranges. These must split, and only - // happens during the first round when a particular LRG splits. - // Also record use-side spills for biased coloring. - // Then make all inputs live. - for( int i=1; i<n.nIns(); i++ ) { - Node def = n.in(i); - if( def==null ) continue; - LRG lrg1 = alloc.lrg(def); - if( lrg1==null ) continue; // Anti-dependence, etc, no register - - // Check use-side for self-conflict; if so we MUST split - selfConflict(alloc,def,lrg1); - - // Look for a must-use single register conflicting with some other must-def. - if( n instanceof MachNode m ) { - RegMask ni_mask = m.regmap(i); - if( ni_mask!=null && ni_mask.size1() ) { // Must-use single register - // Search all current live - for( LRG tlrg : TMP.keySet() ) { - assert tlrg.leader(); - Node live = TMP.get(tlrg); - if( live != def && live instanceof MachNode lmach && lmach.outregmap().overlap(ni_mask) ) { - // Deny the register, since it absolutely must be used here - if( !tlrg.clr(ni_mask.firstReg()) ) - // Then direct reg-reg conflict between use here (at n.in(i)) and def (of tlrg) there. - // Fail the older live range, it must move its register. - alloc.fail( tlrg ); - } - } - } - } - - // All inputs live - except in self conflicts, where we keep the prior def alive - // until it goes. - TMP.put(lrg1,def); - } - } - - // Single-register defines *must* have their register; other live ranges - // *must* avoid this register, so instead of interfering we remove the - // single register from the other rmask. - private static void kills( RegAlloc alloc, MachNode m ) { - RegMask killMask = m.killmap(); - if( killMask==null ) return; - // Kill registers with all live - for( LRG tlrg : TMP.keySet() ) { - assert tlrg.leader(); - // Always, tlrg cannot use kills - if( tlrg._mask.overlap(killMask) ) { - // Disallow clone-ables from killing registers. Just fail - // them and re-clone closer to target... so no kill. - // Special case for Intel XOR used to zero. - Node n = (Node)m; - CFGNode effUseBlk = n.out(0) instanceof PhiNode phi ? phi.region().cfg(phi._inputs.find(n)) : n.out(0).cfg0(); - if( m.isClone() && // Must be clonable - (n.nOuts()>1 || // Has many users OR - // Only 1 user but effective use is remote block - effUseBlk != n.cfg0() )) - // Then fail the clonable; it should split or move - alloc.fail(alloc.lrg((Node)m)); - // Else clonable cannot move - else if( !tlrg.sub(killMask) ) - alloc.fail(tlrg); - } - } - } - - // Check for self-conflict live ranges. These must split, and only happens - // during the first round a particular LRG splits. - private static void selfConflict(RegAlloc alloc, Node n, LRG lrg) { - selfConflict(alloc,n,lrg,TMP.get(lrg)); - } - private static void selfConflict(RegAlloc alloc, Node n, LRG lrg, Node prior) { - if( prior!=null && prior != n ) { - lrg.selfConflict(prior); - lrg.selfConflict(n); - alloc.fail(lrg); // 2 unrelated values live at once same live range; self-conflict - } - } - - // Merge TMP into bb's live-out set; if changes put bb on WORK - private static void mergeLiveOut( RegAlloc alloc, CFGNode priorbb, int i ) { - CFGNode bb = priorbb.cfg(i); - if( bb == null ) return; // Start has no prior - while( !bb.blockHead() ) bb = bb.cfg0(); - - // Lazy get live-out set for bb - IdentityHashMap<LRG, Node> lrgs = BBOUTS.computeIfAbsent( bb, k -> new IdentityHashMap<>() ); - - for( LRG lrg : TMP.keySet() ) { - Node def = TMP.get(lrg); - // Effective def comes from phi input from prior block - if( def instanceof PhiNode phi && phi.cfg0()==priorbb ) { - assert i!=0; - def = phi.in( i ); - } - Node def_bb = lrgs.get(lrg); - if( def_bb==null ) { - lrgs.put(lrg,def); - pushWork(bb); - } else { - // Alive twice with different definitions; self-conflict - selfConflict(alloc,def,lrg,def_bb); - } - } - } - - - static void convert2DAdjacency( RegAlloc alloc ) { - // Convert the 2-D array of bits (a 1-D array of BitSets) into an - // adjacency matrix. - int maxlrg = alloc._LRGS.length; - for( int i=1; i<maxlrg; i++ ) { - BitSet ifg = IFG.atX(i); - if( ifg != null ) { - LRG lrg0 = alloc._LRGS[i]; - for( int lrg = ifg.nextSetBit(0); lrg>=0; lrg=ifg.nextSetBit(lrg+1) ) { - LRG lrg1 = alloc._LRGS[lrg]; - lrg0.addNeighbor(lrg1); - lrg1.addNeighbor(lrg0); - } - } - } - } - - - // ------------------------------------------------------------------------ - // Color the inference graph. - - // Coloring works by removing "trivial" live ranges from the IFG - those - // live ranges with fewer neighbors than available colors (registers). - // These are trivial because even if every neighbor takes a unique color, - // there's at least one more available to color this live range. - - // If we hit a clique of non-trivial live ranges, we pick one to be "at - // risk" of not-coloring. Good choices include live ranges with a large - // area and few hot uses. - - // Then we reverse and put-back live ranges - picking a color as we go. - // If there's no spare color we'll have to spill this at-risk live range. - - public static boolean color(int round, RegAlloc alloc) { - int maxlrg = alloc._LRGS.length, nlrgs=0; - for( int i=1; i<maxlrg; i++ ) - if( alloc._LRGS[i] != null ) - nlrgs++; - - // Simplify - - // Walk all the LRGS looking for some trivial ones to start with. - // During this pass all LRGS are broken into 3 disjoint sets: - // - trivial, removed from IFG; color_stack[0 to sptr] - // - trivial, not (yet) removed from IFG; color_stack[sptr to swork] - // - unknown; color_stack[work to maxlrg] - - // Gather all not-unified (not-null); separate trivial and non-trivial set. - int sptr = 0, swork = 0; - LRG[] color_stack = new LRG[nlrgs]; - for( int i=0, j=0; i<maxlrg; i++ ) { - LRG lrg = alloc._LRGS[i]; - if( lrg==null ) continue; // Unified lrgs are null here - color_stack[j++] = lrg; - if( lrg.lowDegree() ) swap(color_stack, swork++, j-1); - } - - // Pull all lrgs from IFG, in trivial order if possible - while( sptr < color_stack.length ) { - // Swap best color up front - pickColor(color_stack,sptr,swork); - - // Pick a trivial lrg, and (temporarily) remove from the IFG. - LRG lrg = color_stack[sptr++]; - // If sptr was swork, then pulled an at-risk lrg - if( sptr > swork ) - swork = sptr; - - // Walk all neighbors and remove - if( lrg._adj != null ) { - for( LRG nlrg : lrg._adj ) { - // Remove and compress out neighbor - if( nlrg.removeCompress(lrg) ) { - // Neighbor is just exactly going trivial as 'lrg' is removed from IFG - // Find "j" position in the color_stack - int jx = swork; while( color_stack[jx] != nlrg ) jx++; - // Add trivial neighbor to trivial list. Pull lrg j out of - // unknown set, since its now in the trivial set - swap(color_stack, swork++, jx); - } - } - } - } - - // Reverse simplify (unstack the color stack), and set colors (registers) for live ranges - while( sptr > 0 ) { - LRG lrg = color_stack[--sptr]; - if( lrg==null ) continue; - RegMaskRW rmask = lrg._mask.copy(); - // Walk neighbors and remove adjacent colors - if( lrg._adj!=null ) { - for( LRG nlrg : lrg._adj ) { - nlrg.reinsert(lrg); - int reg = nlrg._reg; - if( reg!= -1 ) // Failed neighbors do not count - rmask.clr(reg); // Remove neighbor from my choices - } - } - // At-risk live-range did not color? - if( rmask.isEmpty() ) { - alloc.fail(lrg); - lrg._reg = -1; - } else { - // Pick first available register - short reg = rmask.firstReg(); - // Pick a "good" color from the choices. Typically, biased-coloring - // removes some over-spilling. - if( rmask.size() > 1 ) reg = biasColor(alloc,lrg,reg,rmask); - lrg._reg = reg; // Assign the register - } - } - - return alloc.success(); - } - - // Pick LRG from color stack - private static void pickColor(LRG[] color_stack, int sptr, int swork) { - // Out of trivial colorable, pick an at-risk to pull - if( sptr==swork ) - swap(color_stack,sptr,pickRisky(color_stack,sptr)); - // When coloring, we'd like to give more choices; so when coloring we'd - // like to see the single-def first (since no choices anyway), then - // non-split related (so more live ranges get colored), then - // split-related last, so they have more colors to bias towards. - - // Working in reverse, pick first split-related with many regs, then - // those with some regs, then single-def. - int bidx=sptr; - LRG best=color_stack[bidx]; - for( int idx = sptr+1; idx < swork; idx++ ) - if( betterLRG(best,color_stack[idx]) ) - best = color_stack[bidx=idx]; - if( bidx != sptr ) - swap(color_stack,sptr,bidx); // Pick best at sptr - } - - private static boolean betterLRG( LRG best, LRG lrg ) { - // If single-def varies, keep the not-single-def - if( best.size1() != lrg.size1() ) - return best.size1(); - // If hasSplit varies, keep the hasSplit - if( best.hasSplit() != lrg.hasSplit() ) - return lrg.hasSplit(); - // Keep large register count - return best.size() < lrg.size(); - } - - // Pick a live range that hasn't already spilled, or has a single-def- - // single-use that are not adjacent. - private static int pickRisky( LRG[] color_stack, int sptr ) { - int best=sptr; - int bestScore = pickRiskyScore(color_stack[best]); - for( int i=sptr+1; i<color_stack.length; i++ ) { - if( bestScore == 1000000 ) return best; // Already max score - int iScore = pickRiskyScore(color_stack[i]); - if( iScore > bestScore ) - { best = i; bestScore = iScore; } - } - return best; - } - - // Pick a live range to pull, that might not color. - // - // Picking a live range with a very large span, with defs and uses outside - // loops means spilling a relative cheap live range and getting that - // register over a large area. - // - // Picking a live range that is very close to coloring might allow it to - // color despite being risky. - private static int pickRiskyScore( LRG lrg ) { - // Pick single-def clonables that are not right next to their single-use. - // Failing to color these will clone them closer to their uses. - if( !lrg._multiDef && lrg._machDef.isClone() ) { - Node def = ((Node)lrg._machDef); - Node use = ((Node)lrg._machUse); - CFGNode cfg = def.cfg0(); - if( cfg != use.cfg0() || // Different blocks OR - // Same block, but not close - cfg._outputs.find(def) < cfg._outputs.find(use)+1 ) - return 1000000; - } - - // Always pick callee-save registers as being very large area recovered - // and very cheap to spill. - if( lrg._machDef instanceof CalleeSaveNode ) - return 1000000-2-lrg._mask.firstReg(); - if( lrg._splitDef != null && lrg._splitDef. in(1) instanceof CalleeSaveNode && - lrg._splitUse != null && lrg._splitUse.out(0) instanceof ReturnNode ) - return 1000000-1; - - // TODO: cost/benefit model. Perhaps counting loop-depth (freq) of def/use for cost - // and "area" for benefit - return 1000; - } - - private static short biasColor( RegAlloc alloc, LRG lrg, short reg, RegMask mask ) { - if( mask.size1() ) return reg; - // Check chain of splits up the def-chain. Take first allocated - // register, and if it's available in the mask, take it. - Node def = lrg._splitDef, use = lrg._splitUse; - int tidx=0, cnt=0; - - while( def != null || use != null ) { - if( cnt++ > 10 ) break; - - if( def != null ) { - short bias = biasColor( alloc, def, mask ); - if( bias >= 0 ) return bias; // Good bias - if( bias == -2 ) def = null; // Kill this side, no more searching - else if( (tidx=biasable(def)) == 0 ) def = null; - } - - if( use != null ) { - short bias = biasColor( alloc, use, mask ); - if( bias >= 0 ) return bias; // Good bias - if( bias == -2 ) use = null; // Kill this side, no more searching - else if( biasable(use)==0 ) use = null; - } - - if( def != null ) { - short bias = biasColorNeighbors( alloc, def, mask ); - if( bias >= 0 ) return bias; - // Advance def side - def = def.in(tidx); - if( alloc.lrg(def)==null ) def=null; - } - - if( use != null ) { - short bias = biasColorNeighbors( alloc, use, mask ); - if( bias >= 0 ) return bias; - use = use.out(0); - if( biasable(use)==0 ) use=null; - } - - } - return mask.firstReg(); - } - - private static int biasable(Node split) { - if( split instanceof SplitNode ) return 1; // Yes biasable, advance is slot 1 - if( split instanceof PhiNode phi ) return phi.region() instanceof LoopNode ? 2 : 1; // Yes biasable, advance is slot 1 - if( !(split instanceof MachNode mach) ) return 0; // Not biasable - return mach.twoAddress(); // Only biasable if 2-addr - } - - // 3-way return: - // - good bias reg, take it & exit - // - this path is cutoff; do not search here anymore - // - advance this side - private static short biasColor( RegAlloc alloc, Node split, RegMask mask ) { - short bias = alloc.lrg(split)._reg; - if( bias != -1 ) { - if( mask.test(bias) ) return bias; // Good bias - else return -2; // Kill this side - } else return -1; // Advance this side - } - - // Check if we can match "split" color, or else trim "mask" to colors - // "split" might get. - private static short biasColorNeighbors( RegAlloc alloc, Node split, RegMask mask ) { - LRG slrg = alloc.lrg(split); - if( slrg._adj == null ) return -1; // No trimming - - // Can I limit my own choices to valid neighbor choices? - for( LRG alrg : slrg._adj ) { - short reg = alrg._reg; - if( reg == -1 && alrg._mask.size1() ) - reg = alrg._mask.firstReg(); - if( reg != -1 ) { - mask.clr(alrg._reg); - if( mask.size1() ) - return mask.firstReg(); - } - } - return -1; // No obvious color choice, but mask got trimmed - } - - private static void swap( LRG[] ary, int x, int y ) { - LRG tmp = ary[x]; ary[x] = ary[y]; ary[y] = tmp; - } -} diff --git a/src/main/java/com/seaofnodes/simple/codegen/LRG.java b/src/main/java/com/seaofnodes/simple/codegen/LRG.java deleted file mode 100644 index 009ddcdb..00000000 --- a/src/main/java/com/seaofnodes/simple/codegen/LRG.java +++ /dev/null @@ -1,233 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import java.util.IdentityHashMap; - -/** A Live Range - * <p> - * A live range is a set of nodes and edges which must get the same register. - * Live ranges form an interconnected web with almost no limits on their - * shape. Live ranges also gather the set of register constraints from all - * their parts. - * <p> - * Most of the fields in this class are for spill heuristics, but there are a - * few key ones that define what a LRG is. LRGs have a unique dense integer - * `_lrg` number which names the LRG. New `_lrg` numbers come from the - * `RegAlloc._lrg_num` counter. LRGs can be unioned together - - * (<a href="https://en.wikipedia.org/wiki/Disjoint-set_data_structure">this is the Union-Find algorithm</a>) - * - and when this happens the lower numbered `_lrg` wins. Unioning only - * happens during `BuildLRG` and happens because either a `Phi` is forcing all - * its inputs and outputs into the same register, or because of a 2-address - * instruction. LRGs have matching `union` and `find` calls, and a set - * `_leader` field. - */ -public class LRG { - - // Dense live range numbers - final short _lrg; - - // U-F leader; null if leader - LRG _leader; - - // Choosen register - short _reg; - - // Count of single-register defs and uses - short _1regDefCnt, _1regUseCnt; - // Count of all defs, uses. Mostly interested 1 vs many - boolean _multiDef, _multiUse; - - // A sample MachNode def in the live range - public MachNode _machDef, _machUse; - short _uidx; // _machUse input - - // Some splits used in biased coloring - MachConcreteNode _splitDef, _splitUse; - - // All the self-conflicting defs for this live range - IdentityHashMap<Node,String> _selfConflicts; - - // AND of all masks involved; null if none have been applied yet - RegMask _mask; - - // Adjacent Live Range neighbors. Only valid during coloring - Ary<LRG> _adj; - - public int nadj() { return _adj==null ? 0 : _adj._len; } - - void addNeighbor(LRG lrg) { - if( _adj==null ) _adj = new Ary<>(LRG.class); - _adj.push(lrg); - } - - // Remove and compress neighbor list; store ex-neighbor past - // end for unwinding Simplify. True if exactly going low-degree - boolean removeCompress( LRG lrg ) { - _adj.swap(_adj.find(lrg),_adj._len-1); - return _adj._len-- == _mask.size(); - } - - void reinsert( LRG lrg ) { - assert _adj._es[_adj._len]==lrg; - _adj._len++; - } - - // More registers than neighbors - boolean lowDegree() { return nadj() < _mask.size(); } - - LRG( short lrg ) { _lrg = lrg; _reg = -1; } - - boolean leader() { return _leader == null; } - - // Fast-path Find from the Union-Find algorithm - LRG find() { - if( _leader==null ) return this; // I am the leader - if( _leader._leader==null ) // I point to the leader - return _leader; - return _rollup(); - } - // Slow-path rollup of U-F - private LRG _rollup() { - LRG ldr = _leader._leader; - // Roll-up - while( ldr._leader!=null ) ldr = ldr._leader; - LRG l2 = this; - while( l2 != ldr ) { - LRG l3 = l2._leader; - l2._leader = ldr; - l2 = l3; - } - return ldr; - } - - // Union `this` and `lrg`, keeping the lower numbered _lrg. - // Includes a number of fast-path cutouts. - LRG union( LRG lrg ) { - assert leader(); - if( lrg==null ) return this; - lrg = lrg.find(); - if( lrg==this ) return this; - return _lrg < lrg._lrg ? _union(lrg) : lrg._union(this); - } - // Union `this` and `lrg`, folding together all stats. - private LRG _union( LRG lrg ) { - // Set U-F leader - lrg._leader = this; - // Fold together stats - if( _machDef==null ) { - _machDef = lrg._machDef; - } else if( lrg._machDef!=null ) { - if( _machDef != lrg._machDef ) _multiDef=true; - if( _1regDefCnt==0 ) - _machDef = lrg._machDef; - else if( _machDef==lrg._machDef ) - _1regDefCnt--; - } - _1regDefCnt += lrg._1regDefCnt; - _multiDef |= lrg._multiDef; - - if( _machUse==null ) { - _machUse = lrg._machUse; - _uidx = lrg._uidx; - } else if( lrg._machUse!=null ) { - if( _machUse != lrg._machUse ) _multiUse=true; - if( _1regUseCnt==0 ) { - _machUse = lrg._machUse; - _uidx = lrg._uidx; - } - else if( _machUse==lrg._machUse ) - _1regUseCnt--; - } - _1regUseCnt += lrg._1regUseCnt; - _multiUse |= lrg._multiUse; - - // Fold deepest Split - _splitDef = deepSplit(_splitDef,lrg._splitDef); - _splitUse = deepSplit(_splitUse,lrg._splitUse); - - // Fold together masks - RegMask mask = _mask.and(lrg._mask); - if( mask==null ) - mask = _mask.copy().and(lrg._mask); - _mask = mask; - return this; - } - - private static MachConcreteNode deepSplit( MachConcreteNode s0, MachConcreteNode s1 ) { - return s0==null || (s1!=null && s0.cfg0().loopDepth() < s1.cfg0().loopDepth()) ? s1 : s0; - } - - // Record any Mach def for spilling heuristics - LRG machDef( MachNode def, boolean size1 ) { - if( _machDef!=null && _machDef!=def ) - _multiDef = true; - if( _machDef==null || size1 ) - _machDef = def; - if( size1 ) - _1regDefCnt++; - if( def instanceof SplitNode split ) - _splitDef = deepSplit(_splitDef,split); - return this; - } - - // Record any Mach use for spilling heuristics - LRG machUse( MachNode use, short uidx, boolean size1 ) { - if( _machUse!=null && _machUse!=use ) - _multiUse = true; - if( _machUse==null || size1 ) - { _machUse = use; _uidx = uidx; } - if( size1 ) - _1regUseCnt++; - if( use instanceof SplitNode split ) - _splitUse = deepSplit(_splitUse,split); - return this; - } - - boolean hasSplit() { return _splitDef != null || _splitUse != null; } - short size() { return _mask.size(); } - boolean size1() { return _mask.size1(); } - - - void selfConflict( Node def ) { - if( _selfConflicts == null ) _selfConflicts = new IdentityHashMap<>(); - _selfConflicts.put(def,""); - } - - - // Record intersection of all register masks. - // True if still has registers - boolean and( RegMask mask ) { - RegMask mask2 = mask.and(_mask); - if( mask2==null ) - mask2 = _mask.copy().and(mask); - _mask = mask2; - return !_mask.isEmpty(); - } - // Remove this singular register - // True if still has registers - boolean clr( int reg ) { - if( _mask.clr(reg) ) return true; - _mask = _mask.copy(); // Need a mutable copy - return _mask.clr(reg); - } - // Subtract mask (AND complement) - // True if still has registers - boolean sub( RegMask mask ) { - RegMask mask2 = _mask.sub(mask); - if( mask2==null ) - mask2 = _mask.copy().sub(mask); - _mask = mask2; - return !_mask.isEmpty(); - } - - @Override public String toString() { return toString(new SB()).toString(); } - - public SB toString( SB sb ) { - sb.p("V").p(_lrg); - if( _mask!=null ) _mask.toString(sb); - return sb; - } -} diff --git a/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java b/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java deleted file mode 100644 index 2415203d..00000000 --- a/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.util.Utils; - -// In-memory linking -public class LinkMem { - final CodeGen _code; - LinkMem( CodeGen code ) { _code = code; } - - public CodeGen link() { - Encoding enc = _code._encoding; - - // Patch external calls internally - enc.patchGlobalRelocations(); - - // Write any large constants into a constant pool; they - // are accessed by RIP-relative addressing. - enc.writeConstantPool(enc._bits,true); - - return _code; - } -} diff --git a/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java b/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java deleted file mode 100644 index ddf5a679..00000000 --- a/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java +++ /dev/null @@ -1,261 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.IterPeeps.WorkList; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.Utils; -import java.util.*; - -public abstract class ListScheduler { - - // eXtra per-node stuff for scheduling. - // Nodes are all block-local, and projections fold into their parent. - private static final IdentityHashMap<Node,XSched> XS = new IdentityHashMap<>(); - private static class XSched { - - Node _n; // Node this extra is for - int _bcnt; // Not-ready not-scheduled inputs - int _rcnt; // Ready not-scheduled inputs - boolean _ruse; // Node IS a "remote use", uses a other-block value - boolean _rdef; // Node IS a "remote def" of a value used in other block - boolean _single; // Defines a single register, likely to conflict - - static final Ary<XSched> FREE = new Ary<>(XSched.class); - static void alloc(CFGNode bb, Node n) { - XSched x = FREE.pop(); - XS.put(n, (x==null ? new XSched() : x).init(bb,n)); - } - void free() { _n = null; FREE.push(this); } - - static XSched get(Node n) { return n==null ? null : XS.get(n instanceof ProjNode && !(n.in(0) instanceof CallEndNode) ? n.in(0) : n); } - - private XSched init(CFGNode bb, Node n) { - _n = n; - _bcnt = 0; - _ruse = _rdef = _single = false; - - // Count block-locals and remote inputs - if( !(n instanceof PhiNode) ) - for( Node def : n._inputs ) - if( def != null && def != bb ) { - if( def.cfg0()==bb ) _bcnt++; // Raise block-local input count - else if( !def.isMem() ) _ruse = true; // Remote register user - } - // Count remote outputs and allowed registers - if( n instanceof MultiNode ) - for( Node use : n._outputs ) - computeSingleRDef(bb,use); - else - computeSingleRDef(bb,n); - - if( n instanceof NewNode || n instanceof CallNode ) - bb.fun()._hasCalls = true; - return this; - } - - private void computeSingleRDef(CFGNode bb, Node n) { - // See if this is 2-input, and that input is single-def - if( n instanceof MachNode mach && mach.twoAddress() != 0 ) { - XSched xs = XS.get(n.in(mach.twoAddress())); - if( xs != null ) - _single = xs._single; - } - // Visit all outputs - RegMaskRW rmask = !_single && n instanceof MachNode mach && mach.outregmap() != null ? mach.outregmap().copy() : null; - for( Node use : n._outputs ) { - // Remote use, so this is a remote def - if( use!=null && use.cfg0()!=bb ) _rdef = true; - // And mask between n's def mask and all n's users masks' - if( use instanceof MachNode mach && rmask != null ) - for( int i=1; i<use.nIns(); i++ ) - if( use.in(i)==n ) - rmask.and(mach.regmap(i)); - } - // Defines in a single register - _single |= rmask!=null && rmask.size1(); - } - - // If _bcnt==0, declare ready; move user bcnts into rcnts. - boolean isReady() { - if( _bcnt > 0 || _rcnt > 0 ) return false; - if( _n instanceof MultiNode ) - for( Node use : _n._outputs ) - if( use instanceof ProjNode ) readyUp(use); - // anti-dep from a multireg directly - else _readyUp(use); - else - readyUp(_n); - return true; - } - private static void readyUp(Node n) { - for( Node use : n._outputs ) - _readyUp(use); - } - private static void _readyUp(Node use) { - XSched xs = get(use); - if( xs!=null && !(use instanceof PhiNode) ) - { assert xs._bcnt>0; xs._bcnt--; xs._rcnt++; } - } - - boolean decIsReady() { - assert _rcnt > 0; - _rcnt--; - return isReady(); - } - } - - - // List schedule every block - public static void sched( CodeGen code ) { - XS.clear(); - for( CFGNode bb : code._cfg ) - if( bb.blockHead() ) - local(bb,false); - } - - // Classic list scheduler - private static void local( CFGNode bb, boolean trace ) { - assert XS.isEmpty(); - int len = bb.nOuts(); - - // Count block-locals, remote inputs and outputs - for( int i=0; i<len; i++ ) - XSched.alloc(bb,bb.out(i)); - - // Nodes are ready if they are not used by other nodes in this block. - // Move ready up front. - int ready = 0; - for( int i=0; i<len; i++ ) - if( XSched.get(bb.out(i)).isReady() ) // No block-local inputs? - bb._outputs.swap(ready++,i); // Move into ready set - assert len==0 || ready > 0; - - // Classic list scheduler. Behind sched is scheduled. Between sched - // and ready have zero counts and are ready to schedule. Ahead of - // ready is undefined. - // - // As nodes are scheduled, the nodes they use decrement their bcnts[]. Once - // that gets to 0, all the nodes using it are scheduled, so it's ready. - int sched = 0; - while( sched < len ) { - assert sched < ready; - int pick = best(bb._outputs,sched,ready,trace); // Pick best - Node best = bb._outputs.swap(pick,sched++); // Swap best to head of ready and move into the scheduled set - // Lower ready count of users, and since projections are ready as - // soon as their multi is ready, lower those now too. - for( Node use : best._outputs ) - if( use instanceof ProjNode ) - for( Node useuse : use._outputs ) - ready = ready(bb,useuse,ready); - else - ready = ready(bb,use,ready); - } - - // Reset for next block - for( XSched x : XS.values() ) - x.free(); - XS.clear(); - } - - private static int ready(CFGNode bb, Node use, int ready) { - if( use!=null && use.in(0)==bb && !(use instanceof PhiNode) && XSched.get(use).decIsReady() ) - bb._outputs.set(ready++, use); // Became ready, move into ready set - //bb._outputs.swap(ready++,use); // Became ready, move into ready set - return ready; - } - - - // Pick best between sched and ready. - private static int best( Ary<Node> blk, int sched, int ready, boolean trace ) { - int pick = sched; - Node p = blk.at(pick); - int score = score(p); - if( trace ) { System.out.printf("%4d N%d ",score,p._nid); System.out.println(p); } - for( int i=sched+1; i<ready; i++ ) { - Node n = blk.at(i); - int nscore = score(n); - if( trace ) { System.out.printf("%4d N%d ",nscore,n._nid); System.out.println(n); } - if( nscore > score ) - { score = nscore; pick = i; p = n; } - } - if( trace ) { System.out.printf("Pick: N%d %s\n\n",p._nid,p); } - return pick; - } - - // Highest score wins. Max at 1000, min at 10, except specials. - private static final int[] CNT = new int[3]; - static int score( Node n ) { - if( n instanceof ProjNode ) return 1001; // Pinned at block entry - if( n instanceof CProjNode ) return 1001; // Pinned at block entry - if( n instanceof PhiNode ) return 1000; - if( n instanceof CFGNode ) return 1; // Pinned at block exit - - int score = 500; - // Ideal nodes ignore register pressure scheduling - if( !(n instanceof MachNode) ) - return score; - - // Register pressure local scheduling: avoid overlapping specialized - // registers usages. - - // Subtract 10 (delay) if this op forces a live range to exist that - // cannot be resolved immediately; i.e., live count goes up. Scale to - // 20 if multi-def. - - // Subtract 100 if this op forces a single-def live range to exist - // which might conflict on the same register with other live ranges. - // Defines a single register based on def & uses, and the output is not - // ready. Scale to 200 for multi-def. - CNT[1]=CNT[2]=0; - XSched xn = XSched.get(n); - // If defining a remote value, just generically stall alot. Value is - // used in a later block, can we delay until the end of this block? - if( xn._rdef ) score = 200; // If defining a remote value, just generically stall alot - boolean flags = false; - if( n instanceof MultiNode ) { - for( Node use : n._outputs ) - flags |= singleUseNotReady( use, xn._single ); - } else - flags |= singleUseNotReady( n, xn._single ); - score += -10 * Math.min( CNT[1], 2 ); - score += -100 * Math.min( CNT[2], 2 ); - if( flags ) return 10; - - // Add 10 if this op ends a single-def live range, add 20 for 2 or more. - // Scale to 100,200 if the single-def is also single-register. - CNT[1]=CNT[2]=0; - for( int i=1; i<n.nIns(); i++ ) { - XSched xd = XSched.get( n.in(i) ); - if( xd != null && n.in(i).nOuts()==1 ) - CNT[xd._single || ((MachNode)n).regmap(i)!=null && ((MachNode)n).regmap(i).size1() ? 2 : 1]++; - } - score += 10 * Math.min( CNT[1], 2 ); - score += 100 * Math.min( CNT[2], 2 ); - - // Nothing special - assert 10 <= score && score <= 990; - return score; - } - - // Single use, and that use is not ready to fire once this one does. If - // true, stalling 'n' might reduce 'n's lifetime. Stall by 1 (2 if single) - // per not-ready. - - // If CFG user in this block, it must be last, so definitely stall - // single-def (which stalls flags on x86,arm until the jmp). - private static boolean singleUseNotReady( Node n, boolean single ) { - // If n can rematerialize, assume allocator will split into private uses - // as needed. No impact on local scheduling. - if( n.nOuts()>1 && n instanceof MachNode mach && mach.isClone() ) - return false; - for( Node use : n.outs() ) { - XSched xu = XSched.get(use); - if( xu != null && xu._n instanceof CFGNode ) return true; - if( xu != null && xu._bcnt > 0 ) - CNT[single ? 2 : 1]++; // Since bcnt>0 or CFG, stall until user is more ready - } - return false; - } - -} diff --git a/src/main/java/com/seaofnodes/simple/codegen/Machine.java b/src/main/java/com/seaofnodes/simple/codegen/Machine.java deleted file mode 100644 index 2766414f..00000000 --- a/src/main/java/com/seaofnodes/simple/codegen/Machine.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeFunPtr; - -abstract public class Machine { - // Human readable machine name. Something like "x86-64" or "arm" or "risc5" - public abstract String name(); - // Default opcode size for printing; 4 bytes for most 32-bit risc chips - public int defaultOpSize() { return 4; } - // Human-readable name for a register number, e.g. "RAX" or "R0" - public abstract String[] regs(); - // Create a split op; any register to any register, including stack slots - public abstract SplitNode split( String kind, byte round, LRG lrg); - // List of caller-save registers, as a 64bit mask - public abstract long callerSave(); - // List of never-save registers, e.g. RSP or a ZERO register if you have one - public abstract long neverSave(); - // Call Argument Mask. Passed in the function signature and argument - // number (2-based; 0 is for control and 1 for memory). Also passed in a 0 - // for the function itself, or for *outgoing* calls, the maximum stack slot - // given to the incoming function arguments (stack slots reserved for - // incoming arguments). - public abstract RegMask callArgMask(TypeFunPtr tfp, int arg, int maxArgSlot); - // Return register mask, based on signature (GPR vs FPR) - public abstract RegMask retMask(TypeFunPtr tfp); - // Return PC register - public abstract int rpc(); - // Return a MachNode unconditional branch - public abstract CFGNode jump(); - // Instruction select from ideal nodes - public abstract Node instSelect( Node n ); - - /** Stack Slot Numbering - -RA gives all registers a number, starting at 0. Stack slots continue this -numbering going up from the last register number. Common register numbers are -0-15 for GPRs, 16-31 for FPRs, 32 for flags, and stack slots starting at -register#33 going up. These numbers are machine-specific, YMMV, etc; e.g. RPC -is only in stack slot 0 on X86; other cpus start with the rpc in a register -which may spill into any generic spill slot. - -For ease of reading and printing, stack slots start again at slot#0 - but -during RA they are actually biased by CPU.MAX_REG. - -Stack Layout during Reg Alloc: - - Slot Value Description - ---- ------- ----------- -MAXREG+ R+1 argR maxArgSlot(calleR tfp), can be 0 - ... -MAXREG+ 1 arg0 - ---- -- Caller ------ -MAXREG+ 0 RPC -MAXREG+ R+E+N PAD optional alignment - -MAXREG+ + Spills Caller Frame Space; N spills -MAXREG+ R+E+1 Spills - -MAXREG+ R+E argE maxArgSlot(calleE tfp), can be 0 - ... -MAXREG+ R+2 arg0 - ---- -- Callee ------- - ... - -Slots defined by maxArgSlot do not have to contain the actual passed argument; -they can be "shadow slots". If they exist, they are property of the frame / -FunNode where the TFP comes from. - -For a given calleR, there are generally many calleE's and they all share the -low-range stack slots. - -Example:, Win64, main(int arg) { calloc(1,size); } - - Post-Warp: SP+56 shadow-main-arg -Pre-alloc Post-Alloc SP+48 RPC ---- ------- ---------- ------------- -40 spill#3 SP+40 SP+40 spill#3 -39 spill#2 SP+32 "" -38 spill#1 SP+24 "" -37 spill#0 SP+16 "" spill#0 -36 shadow-calloc#size SP+ 8 SP+ 8 shadow-calloc#size -35 shadow-calloc#1 SP+ 0 SP+ 0 shadow-calloc#1 ---- --- FRAME BASE ---------- ---- FRAME BASE ------- -34 shadow-main-arg SP+56 -33 RPC SP+48 - - - */ - - - // Maximum stack slot (or 0) for the args in this TFP. This will include - // shadow slots if defined in the ABI, even if all arguments are passed in - // registers. - public abstract short maxArgSlot(TypeFunPtr tfp); - -} diff --git a/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java b/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java deleted file mode 100644 index 777182dc..00000000 --- a/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.seaofnodes.simple.codegen; - -// This Node rip-relative encodings has sizes that vary by delta -public interface RIPRelSize { - // delta is the distance from the opcode *start* to the target start. Each - // hardware target adjusts as needed, X86 measures from the opcode *end* - // and small jumps are 2 bytes, so they'll need to subtract 2. - byte encSize(int delta); - - // Patch full encoding in. Used for both short and long forms. delta - // again is from the opcode *start*. - void patch( Encoding enc, int opStart, int opLen, int delta ); - -} diff --git a/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java b/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java deleted file mode 100644 index da521082..00000000 --- a/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java +++ /dev/null @@ -1,639 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.Utils; -import com.seaofnodes.simple.node.*; -import java.util.Arrays; -import java.util.BitSet; -import java.util.IdentityHashMap; - -/** - * "Briggs/Chaitin/Click". - * Graph coloring. - * <p> - * Every def and every use have a bit-set of allowed registers. - * Fully general to bizarre chips. - * Multiple outputs fully supported, e.g. add-with-carry or combined div/rem or "xor rax,rax" setting both rax and flags. - * <p> - * Intel 2-address accumulator-style ops fully supported. - * Op-to-stack-spill supported. - * All addressing modes supported. - * Register pairs could be supported with some grief. - * <p> - * Splitting instead of spilling (simpler implementation). - * Stack slots are "just another register" (tiny stack frames, simpler implementation). - * Stack/unstack during coloring with a marvelous trick (simpler implementation). - * <p> - * Both bitset and adjacency list formats for the interference graph; one of - * the few times it's faster to change data structures mid-flight rather than - * just wrap one of the two. - * <p> - * Liveness computation and interference graph built in the same one pass (one - * fewer passes per round of coloring). - * <p> - * Single-register def or use live ranges deny neighbors their required - * register and thus do not interfere, vs interfering and denying the color - * and coloring time. Basically all function calls do this, but many oddball - * registers also, e.g. older div/mod/mul ops. (5x smaller IFG, the only - * O(n^2) part of this operation). - */ - -public class RegAlloc { - // Main Coloring Algorithm: - // Repeat until colored: - // Build Live Ranges (LRGs) - // - Intersect allowed registers - // If hard conflicts (LRGs with no allowed registers) - // - Pre-split conflicted LRGs, and repeat - // Build Interference Graph (IFG) - // - Self conflicts split now - // Color (remove trivial first then any until empty; reverse assign colors - // - If color fails: - // - - Split uncolorable LRGs - - // Top-level program graph structure - final CodeGen _code; - - public int _spills, _spillScaled; - - // ----------------------- - // Live ranges with self-conflicts or no allowed registers - private final IdentityHashMap<LRG,String> _failed = new IdentityHashMap<>(); - void fail( LRG lrg ) { - assert lrg.leader(); - _failed.put(lrg,""); - } - boolean success() { return _failed.isEmpty(); } - - - // ----------------------- - // Map from Nodes to Live Ranges - private final IdentityHashMap<Node,LRG> _lrgs = new IdentityHashMap<>(); - short _lrg_num; - - // Define a new LRG, and assign n - LRG newLRG( Node n ) { - LRG lrg = lrg(n); - if( lrg!=null ) return lrg; - lrg = new LRG(_lrg_num++); - LRG old = _lrgs.put(n,lrg); assert old==null; - return lrg; - } - - // LRG for n - LRG lrg( Node n ) { - LRG lrg = _lrgs.get(n); - if( lrg==null ) return null; - LRG lrg2 = lrg.find(); - if( lrg != lrg2 ) - _lrgs.put(n,lrg2); - return lrg2; - } - - // Find LRG for n.in(idx), and also map n to it - LRG lrg2( Node n, int idx ) { - LRG lrg = lrg(n.in(idx)); - return union(lrg,n); - } - - // Union any lrg for n with lrg and map to the union - LRG union( LRG lrg, Node n ) { - LRG lrgn = _lrgs.get(n); - LRG lrg3 = lrg.union(lrgn); - _lrgs.put(n,lrg3); - return lrg3; - } - - // Force all unified to roll up; collect live ranges - LRG[] _LRGS; - void unify() { - Ary<LRG> lrgs = new Ary<>(LRG.class); - for( Node n : _lrgs.keySet() ) { - LRG lrg = lrg(n); - lrgs.setX(lrg._lrg,lrg); - } - _LRGS = lrgs.asAry(); - // Remove unified lrgs from failed set also - for( LRG lrg : _failed.keySet().toArray(new LRG[0]) ) { - if( !lrg.leader() ) { - LRG lrg2 = lrg.find(); - _failed.remove(lrg); - _failed.put(lrg2,""); - } - } - } - - - public short regnum( Node n ) { - LRG lrg = lrg(n); - return lrg==null ? -1 : lrg._reg; - } - - // Printable register number for node n - String reg( Node n ) { return reg(n,null); } - String reg( Node n, FunNode fun ) { - LRG lrg = lrg(n); - if( lrg==null ) return null; - // No register yet, use LRG - if( lrg._reg == -1 ) return "V"+lrg._lrg; - // Chosen machine register unless stack-slot and past RA - String[] regs = _code._mach.regs(); - if( lrg._reg < regs.length || _code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() || fun==null ) - return RegMask.reg(regs,lrg._reg); - // Stack-slot past RA uses the frame layout logic - return "[rsp+"+fun.computeStackOffset(_code,lrg._reg)+"]"; - } - - // ----------------------- - RegAlloc( CodeGen code ) { _code = code; } - - public void regAlloc() { - // Insert callee-save registers - String[] regs = _code._mach.regs(); - long neverSave= _code._mach.neverSave(); - for( CFGNode bb : _code._cfg ) - if( bb instanceof FunNode fun ) { - ReturnNode ret = fun.ret(); - int len = Math.min(regs.length,64); - for( int reg=0; reg<len; reg++ ) - if( !_code._callerSave.test(reg) && ((1L<<reg)&neverSave)==0 ) { - ret.addDef(new CalleeSaveNode(fun,reg,regs[reg])); - assert ret.regmap(ret.nIns()-1).firstReg()==reg; - } - } - // Cache reg masks for New and Call - for( CFGNode bb : _code._cfg ) { - if( bb instanceof CallEndMach cend ) cend.cacheRegs(_code); - for( Node n : bb._outputs ) - if( n instanceof NewNode nnn ) nnn.cacheRegs(_code); - } - - // Top driver: repeated rounds of coloring and splitting. - byte round=0; - while( !graphColor(round) ) { - split(round); - if( round >= 7 ) // Really expect to be done soon - throw Utils.TODO("Allocator taking too long"); - round++; - } - postColor(); // Remove no-op spills - } - - private boolean graphColor(byte round) { - _failed.clear(); - _lrgs.clear(); - _lrg_num = 1; - _LRGS=null; - - return - // Build Live Ranges - BuildLRG.run(round,this) && // if no hard register conflicts - // Build Interference Graph - IFG.build(round,this) && // If no self conflicts or uncolorable - // Conservative coalesce copies - Coalesce.coalesce(round,this) && - // Color attempt - IFG.color(round,this); // If colorable - } - - // ----------------------- - // Split conflicted live ranges. - void split(byte round) { - - // In C2, all splits are handling in one pass over the program. Here, - // in the name of clarity, we'll handle each failing live range - // independently... which generally requires a full pass over the - // program for each failing live range. i.e., might be a lot of - // passes. - - // Sort, to avoid non-deterministic HashMap ordering - LRG[] splits = _failed.keySet().toArray(new LRG[0]); - Arrays.sort(splits, (x,y) -> x._lrg - y._lrg ); - for( LRG lrg : splits ) - split(round,lrg); - } - - // Split this live range, top level heuristic - boolean split( byte round, LRG lrg ) { - assert lrg.leader(); // Already rolled up - - if( lrg._selfConflicts != null ) - return splitSelfConflict(round,lrg); - - // Register mask when empty; split around defs and uses with limited - // register masks. - if( lrg._mask.isEmpty() && (!lrg._multiDef || lrg._1regUseCnt==1) ) { - if( lrg._1regDefCnt <= 1 && - lrg._1regUseCnt <= 1 && - (lrg._1regDefCnt + lrg._1regUseCnt) > 0 ) - return splitEmptyMaskSimple(round,lrg); - // Repeated single-reg uses from a single def. Special for archs - // with more fixed regs. - if( !lrg._multiDef && lrg._1regDefCnt <= 1 && lrg._1regUseCnt > 2 ) - if( splitEmptyMaskByUse(round,lrg) ) - return true; - } - - // Generic split-by-loop depth. - return splitByLoop(round,lrg); - } - - // Split live range with an empty mask. Specifically forces splits at - // single-register defs or uses and not elsewhere. - boolean splitEmptyMaskSimple( byte round, LRG lrg ) { - // Live range has a single-def single-register, and/or a single-use - // single-register. Split after the def and before the use. Does not - // require a full pass. - - // Split just after def - if( lrg._1regDefCnt==1 && !lrg._machDef.isClone() ) - // Force must-split, even if a prior split same block because register - // conflicts. Example: - // alloc - // V1/rax - forced by alloc - // alloc - // V2/rax - kills prior RAX - // st4 [V1],len - No good, must split around - insertAfterAndReplace( makeSplit("def/empty1",round,lrg), (Node)lrg._machDef, false/*true*/); - // Split just before use - if( lrg._1regUseCnt==1 || (lrg._1regDefCnt==1 && ((Node)lrg._machDef).nOuts()==1) ) - insertBefore((Node)lrg._machUse,lrg._uidx,"use/empty1",round,lrg); - return true; - } - - // Single-def live range with an empty mask. There are many single-reg - // uses. Theory is there's many repeats of the same reg amongst the uses. - // In of splitting once per use, start by splitting into groups based on - // required input register. - boolean splitEmptyMaskByUse( byte round, LRG lrg ) { - Node def = (Node)lrg._machDef; - - // Look at each use, and break into non-overlapping register classes. - Ary<RegMask> rclass = new Ary<>(RegMask.class); - boolean done=false; - int ncalls=0; - while( !done ) { - done = true; - for( Node use : def._outputs ) - if( use instanceof MachNode mach ) { - if( mach instanceof CallNode ) ncalls++; - for( int i=1; i<use.nIns(); i++ ) - if( use.in(i)==def ) - done = putIntoRegClass( rclass, mach.regmap(i) ); - } - } - - // See how many register classes we split into. Generally not - // productive to split like this across calls, which are going to kill - // all registers anyways. - if( rclass._len <= 1 || ncalls > 1 ) return false; - - // Split by class - Ary<Node> ns = new Ary<>(Node.class); - for( RegMask rmask : rclass ) { - ns.addAll(def._outputs); - Node split = makeSplit(def,"popular",round,lrg,rmask); - split.insertAfter( def ); - if( split.nIns()>1 ) split.setDef(1,def); - // all uses by class to split - for( int j=0; j < def._outputs._len; j++ ) { - Node use = def._outputs.at(j); - if( use instanceof MachNode mach && use!=split ) { - // Check all use inputs for n, in case there's several - for( int i = 1; i < use.nIns(); i++ ) - // Find a def input, and check register class - if( use.in( i ) == def && mach.regmap( i ).overlap( rmask ) ) { - RegMask m = mach.regmap( i ); - if( m!=null && mach.regmap( i ).overlap( rmask ) ) - // Modify use to use the split version specialized to this rclass - { use.setDefOrdered( i, split ); j--; break; } - } - } - } - ns.clear(); - } - return true; - } - - - // Put use into a register class, perhaps adding a class or perhaps - // narrowing a class (and causing a repeat) - private static boolean putIntoRegClass( Ary<RegMask> rclass, RegMask rmask ) { - for( int i=0; i<rclass._len; i++ ) { - RegMask omask = rclass.at(i); - if( omask.and(rmask) == omask ) return true; // Within the same register class - if( omask.overlap(rmask) ) { - rclass.set(i,new RegMask(omask.copy().and(rmask))); - return false; // Need go again - } - } - // Add a new class, no need to go again - rclass.push(rmask); - return true; - } - - // Self conflicts require Phis (or two-address). - // Insert a split after every def. - boolean splitSelfConflict( byte round, LRG lrg ) { - // Sort conflict set, so we're deterministic - Node[] conflicts = lrg._selfConflicts.keySet().toArray(new Node[0]); - Arrays.sort(conflicts, (x,y) -> x._nid - y._nid ); - - // For all conflicts - for( Node def : conflicts ) { - assert lrg(def)==lrg; // Might be conflict use-side - // Split before each use that extends the live range; i.e. is a - // Phi or two-address - for( int i=0; i<def._outputs._len; i++ ) { - Node use = def.out(i); - if( (use instanceof PhiNode phi && - !(phi.region() instanceof LoopNode loop && phi.in(2)==def && def.cfg0().idepth() > loop.idepth() ) ) || - (use instanceof MachNode mach && mach.twoAddress()!=0 && use.in(mach.twoAddress())==def) ) - insertBefore( use, use._inputs.find(def), "use/self/use",round,lrg ); - } - // Split after the Phi which extends the LRG. Split also before - // Phi slot 1 (and not all inputs), because Phis extend the live range. - // TODO: split before all inputs (except the last; at least 1 split here must be extra) - if( def instanceof PhiNode phi && !(def instanceof ParmNode) ) { - SplitNode split = makeSplit("def/self",round,lrg); - insertAfterAndReplace(split,def,false); - if( split.nOuts()==0 ) - split.killOrdered(); - insertBefore(phi,1,"use/self/phi",round,lrg); - } - // Split before two-address ops which extend the live range - if( def instanceof MachNode mach && mach.twoAddress()!= 0 ) - insertBefore(def,mach.twoAddress(),"use/self/two",round,lrg); - } - return true; - } - - - // Generic: split around the outermost loop with non-split def/uses. This - // covers both self-conflicts (once we split deep enough) and register - // pressure issues. - boolean splitByLoop( byte round, LRG lrg ) { - findAllLRG(lrg); - - // Find min loop depth for all non-split defs and uses. - long ld = (-1L<<32) | 9999; - for( Node n : _ns ) { - if( lrg(n)==lrg ) // This is a LRG def - ld = ldepth(ld,n,n.cfg0()); - // PhiNodes check all CFG inputs - if( n instanceof PhiNode phi ) { - for( int i=1; i<n.nIns(); i++ ) - ld = ldepth(ld, phi.in(i), phi.region().cfg(i)); - } else { - // Others check uses - for( int i=1; i<n.nIns(); i++ ) - if( lrgSame(n.in(i),lrg) ) // This is a LRG use - ld = ldepth(ld,n,n.cfg0()); - } - } - int min = (int)ld; - int max = (int)(ld>>32); - - - // If the minLoopDepth is less than the maxLoopDepth: for-all defs and - // uses, if at minLoopDepth or lower, split after def and before use. - for( Node n : _ns ) { - if( n instanceof SplitNode && min!=max ) continue; // Ignoring splits; since spilling need to split in a deeper loop - if( n.isDead() ) continue; // Some Cloneable went dead by other spill changes - // If this is a 2-address commutable op (e.g. AddX86, MulX86) and the rhs has only a single user, - // commute the inputs... which chops the LHS live ranges' upper bound to just the RHS. - if( n instanceof MachNode mach && lrg(n)==lrg && mach.twoAddress()==1 && mach.commutes() && n.in(2).nOuts()==1 ) - n.swap12(); - - if( lrg(n)==lrg && // This is a LRG def - // At loop boundary, or splitting in inner loop - (min==max || n.cfg0().loopDepth() <= min) ) { - // Cloneable constants will be cloned at uses, not after def - if( !(n instanceof MachNode mach && mach.isClone()) && - // Single user is already a split adjacent - !(n.nOuts()==1 && n.out(0) instanceof SplitNode split && sameBlockNoClobber(split) ) ) - // Split after def in min loop nest - insertAfterAndReplace( makeSplit("def/loop",round,lrg), n,true); - } - - // PhiNodes check all CFG inputs - if( n instanceof PhiNode phi && !(n instanceof ParmNode)) { - for( int i=1; i<n.nIns(); i++ ) - // No split in front of a split - if( !(n.in(i) instanceof SplitNode) && - // splitting in inner loop or at loop border - (min==max || phi.region().cfg(i).loopDepth() <= min) && - // and not around the backedge of a loop (bad place to force a split, hard to remove) - !(phi.region() instanceof LoopNode && i==2 && (phi.in(i) instanceof PhiNode pp && pp.region()==phi.region())) ) - // Split before phi-use in prior block - insertBefore(phi,i, "use/loop/phi",round,lrg); - - } else { - // Others check uses - for( int i=1; i<n.nIns(); i++ ) { - // This is a LRG use - // splitting in inner loop or at loop border - if( lrgSame( n.in( i ), lrg ) && - (min == max || (n.in(i) instanceof MachNode mach && mach.isClone()) || n.cfg0().loopDepth() <= min) ) - // Split before in this block - insertBefore( n, i, "use/loop/use", round,lrg, false ); - } - } - } - return true; - } - - private boolean lrgSame(Node x, LRG lrg) { - LRG xlrg = lrg(x); - while( xlrg==null && x instanceof SplitNode ) - xlrg=lrg(x = x.in(1)); - return xlrg==lrg; - } - - private static long ldepth( long ld, Node n, CFGNode cfg ) { - // Do not count splits - if( n instanceof SplitNode ) return ld; - // Collect min/max loop depth - int min = (int)ld; - int max = (int)(ld>>32); - int d = cfg.loopDepth(); - // if n will lower the min loop and is in the tail end of the loop - // header, splitting "around" the loop will not help. Treat n as being - // in the loop. - if( d < min ) { - if( cfg.uctrl() instanceof LoopNode loop && loop.entry()==cfg ) { - for( int i=cfg.nOuts()-2; i>=0; i-- ) { - Node out = cfg.out(i); - if( n==out ) - { d = loop.loopDepth(); break; } // Treat n as being "in the loop" - if( !((out instanceof MachNode mach && mach.isClone()) || out instanceof SplitNode ) ) - break; // Treat b as "normal", out of loop - } - } - } - - // lower min, raise max, and re-fold - min = Math.min(min,d); - max = Math.max(max,d); - return ((long)max<<32) | min; - } - - // Find all members of a live range, both defs and uses - private final Ary<Node> _ns = new Ary<>(Node.class); - void findAllLRG( LRG lrg ) { - _ns.clear(); - int wd = 0; - _ns.push((Node)lrg._machDef); - _ns.push((Node)lrg._machUse); - while( wd < _ns._len ) { - Node n = _ns.at(wd++); - if( lrg(n)!=lrg ) continue; - for( Node def : n._inputs ) - if( lrg(def)==lrg && _ns.find(def)== -1 ) - _ns.push(def); - for( Node use : n._outputs ) - if( _ns.find(use)== -1 ) - _ns.push(use); - } - for( Node n : _ns ) assert !n.isDead(); - } - - void insertBefore(Node n, int i, String kind, byte round, LRG lrg, boolean skip) { - Node def = n.in(i); - // Effective block for use - CFGNode cfg = n instanceof PhiNode phi ? phi.region().cfg(i) : n.cfg0(); - // Use-side RegMask, if available - RegMask umask = n instanceof MachNode mach ? mach.regmap(i) : null; - // Def is a split ? - if( skip && def instanceof SplitNode ) { - boolean singleReg = umask!=null && umask.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,umask).insertBefore(n, i); - // Skip split-of-split same block - if( skip && def instanceof SplitNode && cfg==def.cfg0() ) - n.in(i).setDefOrdered(1,def.in(1)); - } - void insertBefore(Node n, int i, String kind, byte round, LRG lrg) { - insertBefore(n,i,kind,round,lrg,true); - } - - // Replace uses of `def` with `split`, and insert `split` immediately after - // `def` in the basic block. - public void insertAfterAndReplace( Node split, Node def, boolean must ) { - split.insertAfter(def); - if( split.nIns()>1 ) split.setDef(1,def); - for( int j=def.nOuts()-1; j>=0; j-- ) { - Node use = def.out(j); - if( use==split ) continue; // Skip self - // Can we avoid a split of a split? 'this' split is used by - // another split in the same block. - if( !must && use instanceof SplitNode split2 && sameBlockNoClobber(split2) ) - continue; - int idx = use._inputs.find(def); - use.setDefOrdered(idx,split); - if( j < def.nOuts() ) j++; - } - } - - private Node makeSplit( Node def, String kind, byte round, LRG lrg, RegMask umask ) { - // Clone simple constants if possible - Node split = def instanceof MachNode mach && mach.isClone() && (umask==null || mach.outregmap().overlap(umask)) - ? mach.copy() - : _code._mach.split(kind,round,lrg); - _lrgs.put(split,lrg); - return split; - } - private SplitNode makeSplit( String kind, byte round, LRG lrg ) { - SplitNode split = _code._mach.split(kind,round,lrg); - _lrgs.put(split,lrg); - return split; - } - - - // ----------------------- - // POST PASS: Remove empty spills that biased-coloring made - private void postColor() { - int maxReg = -1; - for( CFGNode bb : _code._cfg ) { // For all ops - if( bb instanceof FunNode ) - maxReg = -1; // Reset for new function - // Compute frame size, based on arguments and largest reg seen - if( bb instanceof ReturnNode ret ) - ret.fun().computeFrameAdjust(_code,maxReg); - // Raise frame size by max stack args passed, even if ignored - if( bb instanceof CallEndMach cend ) - maxReg = Math.max(maxReg,cend._xslot); - - for( int j=0; j<bb.nOuts(); j++ ) { - Node n = bb.out(j); - if( lrg(n)!=null ) - maxReg = Math.max(maxReg,lrg(n)._reg+1); - // Raise frame size by max stack args passed to New - if( n instanceof NewNode nnn ) - maxReg = Math.max(maxReg,nnn._xslot); - - if( !(n instanceof SplitNode ) ) continue; - int defreg = lrg(n )._reg; - int usereg = lrg(n.in(1))._reg; - // Attempt to bypass split - if( defreg != usereg && splitBypass(bb,j,n,defreg) ) - usereg = lrg(n.in(1))._reg; - - // Split has same reg? Useless! Can remove it! - if( defreg == usereg ) { - n.removeSplit(); - j--; - continue; - } - // Split is kept; count against split score - _spills++; - _spillScaled += (1<<bb.loopDepth()*3); - assert _spillScaled >= 0; - } - } - } - - private boolean splitBypass( CFGNode bb, int j, Node lo, int defreg ) { - // Attempt to bypass split - Node hi = lo.in(1); - while( true ) { - if( !(hi instanceof SplitNode && lo.cfg0() == hi.cfg0()) ) - return false; - if( lrg(hi.in(1))._reg==defreg ) - break; - hi = hi.in(1); - } - // Check no clobbers - for( int idx = j-1; bb.out(idx) != hi; idx++) { - Node n = bb.out(idx); - if( lrg(n)!=null && lrg(n)._reg == defreg ) - return false; // Clobbered - } - lo.setDefOrdered(1,hi.in(1)); - return true; - } - - - public boolean sameBlockNoClobber( SplitNode split ) { - Node def = split.in(1); - CFGNode cfg = def.cfg0(); - if( cfg != split.cfg0() ) return false; // Not same block - // Get multinode head - Node def0 = def instanceof ProjNode ? def.in(0) : def; - int defreg = lrg(def)._reg; - if( defreg == -1 ) defreg = lrg(def)._mask.firstReg(); - if( defreg == -1 ) return false; // no allowed registers -> clobbered - for( int idx = cfg._outputs.find(split) -1; idx >= 0; idx-- ) { - Node n = cfg.out(idx); - if( n==def0 ) return true; // No clobbers - if( lrg(n) == lrg(def) ) return false; // Self conflict - if( lrg(n)!=null && lrg(n)._reg == defreg ) - return false; // Clobbered - } - throw Utils.TODO(); - } -} diff --git a/src/main/java/com/seaofnodes/simple/codegen/RegMask.java b/src/main/java/com/seaofnodes/simple/codegen/RegMask.java deleted file mode 100644 index c7dda1da..00000000 --- a/src/main/java/com/seaofnodes/simple/codegen/RegMask.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; - -/** RegMask - * A "register mask" - 1 bit set for each allowed register. In addition, - * "stack slot" registers may be allowed, effectively making the set infinite. - * <p> - * For smaller and simpler machines it suffices to make such masks an i64 or - * i128 (64- or 128-bit integers), and this presentation is by far the better - * way to go... if all register allocations can fit in this bit limitation. - * The allocator will need bits for stack-based parameters and for splits - * which cannot get a register. For a 32-register machine like the X86, add 1 - * for flags - gives 33 registers. Using a Java `long` has 64 bits, leaving - * 31 for spills and stack passing. This is adequate for nearly all - * allocations; only the largest allocations will run this out. However, if - * we move to a chip with 64 registers we'll immediately run out, and need at - * least a 128 bit mask. Since you cannot *return* a 128 bit value directly - * in Java, Simple will pick up a `RegMask` class object. -*/ -public class RegMask { - - long _bits0, _bits1; - - private static final RegMask EMPTY = new RegMask(0L); - public static final RegMask FULL = new RegMask(-1L); - - public RegMask(int bit) { - if( bit < 64 ) _bits0 = 1L<<bit; - else _bits1 = 1L<<(bit-64); - } - public RegMask(long bits ) { _bits0 = bits; } - public RegMask(long bits0, long bits1 ) { _bits0 = bits0; _bits1 = bits1; } - public RegMask(RegMask r) { _bits0 = r._bits0; _bits1 = r._bits1; } - private RegMask() { _bits0 = _bits1 = 0; } - - // AND, with copy-on-write if changing - RegMask and( RegMask mask ) { - if( mask==null ) return this; - long bits0 = _bits0 & mask._bits0; - long bits1 = _bits1 & mask._bits1; - if( bits0==_bits0 && bits1==_bits1 ) return this; - if( bits0==mask._bits0 && bits1==mask._bits1 ) return mask; - if( bits0==0 && bits1==0 ) return EMPTY; - // Update-in-place a mutable mask, or make a defensive copy - return null; - } - RegMask sub( RegMask mask ) { - if( mask==null ) return this; - long bits0 = _bits0 & ~mask._bits0; - long bits1 = _bits1 & ~mask._bits1; - if( bits0==_bits0 && bits1==_bits1 ) return this; - if( bits0==mask._bits0 && bits1==mask._bits1 ) return mask; - if( bits0==0 && bits1==0 ) return EMPTY; - // Update-in-place a mutable mask, or make a defensive copy - return null; - } - - // Fails if bit is set, because this is immutable - public boolean clr( int reg ) { - if( reg < 64 ) return ((_bits0 >> reg)&1)==0; - if( reg < 128 ) return ((_bits1 >> reg)&1)==0; - return true; - } - - - public short firstReg() { - if( isEmpty() ) return -1; - return (short)(_bits0 != 0 - ? Long.numberOfTrailingZeros(_bits0) - : Long.numberOfTrailingZeros(_bits1)+64); - } - public short nextReg(short reg) { - if( reg<64 ) { - long bits0 = _bits0 >> (reg+1); - if( bits0!=0 ) - return (short)(reg+1+Long.numberOfTrailingZeros(bits0)); - reg=64; - } - long bits1 = _bits1 >> (reg-64+1); - if( bits1!=0 ) - return (short)(reg+1+Long.numberOfTrailingZeros(bits1)); - return -1; - } - - boolean isEmpty() { return _bits0==0 && _bits1==0; } - - public boolean test( int reg ) { - return ((reg<64 ? (_bits0 >> reg) : (_bits1 >> (reg-64))) & 1) != 0; - } - - // checks if the 2 masks have at least 1 bit in common - public boolean overlap( RegMask mask ) { return (_bits0 & mask._bits0)!=0 || (_bits1 & mask._bits1)!=0; } - - // Defensive writable copy - public RegMaskRW copy() { return new RegMaskRW( _bits0, _bits1 ); } - - // Has exactly 1 bit set - public boolean size1() { - return ((_bits0 & -_bits0)==_bits0 && _bits1==0) || - ((_bits1 & -_bits1)==_bits1 && _bits0==0); - } - - // Cardinality - public short size() { return (short)(Long.bitCount(_bits0)+Long.bitCount(_bits1)); } - - @Override public String toString() { return toString(new SB()).toString(); } - public SB toString(SB sb) { - Machine mach = CodeGen.CODE._mach; - if( _bits0==0 && _bits1==0 ) return sb.p("[]"); - sb.p("["); - String[] regs = mach.regs(); - for( int i=0; i<64; i++ ) - if( ((_bits0 >> i)&1) != 0 ) - sb.p(reg(regs,i)).p(","); - for( int i=0; i<64; i++ ) - if( ((_bits1 >> i)&1) != 0 ) - sb.p(reg(regs,i+64)).p(","); - return sb.unchar().p("]"); - } - - public static String reg(String[] regs, int i) { - return i<regs.length ? regs[i] : "[stk#"+i+"]"; - } -} diff --git a/src/main/java/com/seaofnodes/simple/codegen/RegMaskRW.java b/src/main/java/com/seaofnodes/simple/codegen/RegMaskRW.java deleted file mode 100644 index 1da9a3b1..00000000 --- a/src/main/java/com/seaofnodes/simple/codegen/RegMaskRW.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.seaofnodes.simple.codegen; - -// Mutable regmask(writable) -public class RegMaskRW extends RegMask { - public RegMaskRW(long x, long y) { super(x,y); } - // clears bit at position r. Returns true if the mask is still not empty. - public boolean clr(int r) { - if( r < 64 ) _bits0 &= ~(1L<<(r )); - else _bits1 &= ~(1L<<(r-64)); - return _bits0!=0 || _bits1!=0; - } - // sets bit at position r. - public void set(int r) { - if( r < 64 ) _bits0 |= (1L<<(r )); - else _bits1 |= (1L<<(r-64)); - } - @Override RegMaskRW and( RegMask mask ) { - if( mask==null ) return this; - _bits0 &= mask._bits0; - _bits1 &= mask._bits1; - return this; - } - @Override RegMaskRW sub( RegMask mask ) { - if( mask==null ) return this; - _bits0 &= ~mask._bits0; - _bits1 &= ~mask._bits1; - return this; - } - public RegMaskRW or( RegMask mask ) { - if( mask!=null ) { - _bits0 |= mask._bits0; - _bits1 |= mask._bits1; - } - return this; - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/AddFNode.java b/src/main/java/com/seaofnodes/simple/node/AddFNode.java deleted file mode 100644 index 792da62e..00000000 --- a/src/main/java/com/seaofnodes/simple/node/AddFNode.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; - -import java.util.BitSet; - -public class AddFNode extends Node { - public AddFNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override public String label() { return "AddF"; } - - @Override public String glabel() { return "+"; } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - in(1)._print0(sb.append("("), visited); - in(2)._print0(sb.append("+"), visited); - return sb.append(")"); - } - - @Override - public Type compute() { - if (in(1)._type instanceof TypeFloat i0 && - in(2)._type instanceof TypeFloat i1) { - if (i0.isConstant() && i1.isConstant()) - return TypeFloat.constant(i0.value()+i1.value()); - } - return in(1)._type.meet(in(2)._type); - } - - @Override - public Node idealize() { - Node lhs = in(1); - Type t2 = in(2)._type; - - // Add of 0. - if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==0 ) - return lhs; - - return null; - } - @Override Node copy(Node lhs, Node rhs) { return new AddFNode(lhs,rhs); } -} diff --git a/src/main/java/com/seaofnodes/simple/node/AddNode.java b/src/main/java/com/seaofnodes/simple/node/AddNode.java deleted file mode 100644 index f1615f70..00000000 --- a/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ /dev/null @@ -1,169 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.*; - -import static com.seaofnodes.simple.Parser.con; - -public class AddNode extends ArithNode { - public AddNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override public String label() { return "Add"; } - @Override public String op() { return "+"; } - - @Override long doOp( long x, long y ) { return x + y; } - @Override TypeInteger doOp(TypeInteger x, TypeInteger y) { - // Fold ranges like {0-1} + {2-3} into {2-4}. - if( !overflow(x._min,y._min) && - !overflow(x._max,y._max) ) - return TypeInteger.make(x._min+y._min,x._max+y._max); - return TypeInteger.BOT; - } - - @Override - public Node idealize () { - Node lhs = in(1); - Node rhs = in(2); - if( rhs instanceof AddNode add && add.err()!=null ) - return null; - Type t2 = rhs._type; - - // Add of 0. We do not check for (0+x) because this will already - // canonicalize to (x+0) - if( t2 == TypeInteger.ZERO ) - return lhs; - - // Add of same to a multiply by 2 - if( lhs==rhs ) - return new ShlNode(null,lhs,con(1)); - - // Goal: a left-spine set of adds, with constants on the rhs (which then fold). - - // Move non-adds to RHS - if( !(lhs instanceof AddNode) && rhs instanceof AddNode ) - return swap12(); - - // x+(-y) becomes x-y - if( rhs instanceof MinusNode minus ) - return new SubNode(lhs,minus.in(1)); - - // Now we might see (add add non) or (add non non) or (add add add) but never (add non add) - - // Do we have x + (y + z) ? - // Swap to (x + y) + z - // Rotate (add add add) to remove the add on RHS - if( rhs instanceof AddNode add ) - return new AddNode(new AddNode(lhs,add.in(1)).peephole(), add.in(2)); - - // Now we might see (add add non) or (add non non) but never (add non add) nor (add add add) - if( !(lhs instanceof AddNode) ) - // Rotate; look for (add (phi cons) con/(phi cons)) - return spine_cmp(lhs,rhs,this) ? swap12() : phiCon(this,true); - - // Now we only see (add add non) - - // Dead data cycle; comes about from dead infinite loops. Do nothing, - // the loop will peep as dead after a bit. - if( lhs.in(1) == lhs ) - return null; - - // Do we have (x + con1) + con2? - // Replace with (x + (con1+con2) which then fold the constants - // lhs.in(2) is con1 here - // If lhs.in(2) is not a constant, we add ourselves as a dependency - // because if it later became a constant then we could make this - // transformation. - if( addDep(lhs.in(2))._type.isConstant() && rhs._type.isConstant() ) - return new AddNode(lhs.in(1),new AddNode(lhs.in(2),rhs).peephole()); - - - // Do we have ((x + (phi cons)) + con) ? - // Do we have ((x + (phi cons)) + (phi cons)) ? - // Push constant up through the phi: x + (phi con0+con0 con1+con1...) - Node phicon = phiCon(this,true); - if( phicon!=null ) return phicon; - - // Now we sort along the spine via rotates, to gather similar things together. - - // Do we rotate (x + y) + z - // into (x + z) + y ? - if( spine_cmp(lhs.in(2),rhs,this) ) - return new AddNode(new AddNode(lhs.in(1),rhs).peephole(),lhs.in(2)); - - return super.idealize(); - } - - // Rotation is only valid for associative ops, e.g. Add, Mul, And, Or, Xor. - // Do we have ((phi cons)|(x + (phi cons)) + con|(phi cons)) ? - // Push constant up through the phi: x + (phi con0+con0 con1+con1...) - static Node phiCon(Node op, boolean rotate) { - Node lhs = op.in(1); - Node rhs = op.in(2); - if( rhs._type==TypeInteger.TOP ) return null; - // LHS is either a Phi of constants, or another op with Phi of constants - PhiNode lphi = pcon(lhs,op); - if( rotate && lphi==null && lhs.nIns() > 2 ) { - // Only valid to rotate constants if both are same associative ops - if( lhs.getClass() != op.getClass() ) return null; - lphi = pcon(lhs.in(2),op); // Will rotate with the Phi push - } - if( lphi==null ) return null; - if( lphi.region().nIns() <=2 ) return null; // Phi is collapsing - - // RHS is a constant or a Phi of constants - if( !(rhs instanceof ConstantNode) && pcon(rhs,op)==null ) - return null; - - // If both are Phis, must be same Region - if( rhs instanceof PhiNode && lphi.in(0) != rhs.in(0) ) - return null; - - // Note that this is the exact reverse of Phi pulling a common op down - // to reduce total op-count. We don't get in an endless push-up - // push-down peephole cycle because the constants all fold first. - Node[] ns = new Node[lphi.nIns()]; - ns[0] = lphi.in(0); - // Push constant up through the phi: x + (phi con0+con0 con1+con1...) - for( int i=1; i<ns.length; i++ ) - ns[i] = op.copy(lphi.in(i), rhs instanceof PhiNode ? rhs.in(i) : rhs).peephole(); - String label = lphi._label + (rhs instanceof PhiNode rphi ? rphi._label : ""); - Node phi = new PhiNode(label,lphi._minType,ns).peephole(); - // Rotate needs another op, otherwise just the phi - return lhs==lphi ? phi : op.copy(lhs.in(1),phi); - } - - /** - * Tests if the op is a phi and has all constant inputs. - * If not, returns null. - * If op is a phi, but its inputs are not all constants, then dep is added as - * a dependency to the phi's non-const input, because if later the input turn into a constant - * dep can make progress. - */ - static PhiNode pcon(Node op, Node dep) { - return op instanceof PhiNode phi && phi.allCons(dep) ? phi : null; - } - - // Compare two off-spine nodes and decide what order they should be in. - // Do we rotate ((x + hi) + lo) into ((x + lo) + hi) ? - // Generally constants always go right, then Phi-of-constants, then muls, then others. - // Ties with in a category sort by node ID. - // TRUE if swapping hi and lo. - static boolean spine_cmp( Node hi, Node lo, Node dep ) { - if( lo._type.isConstant() || lo instanceof ConFldOffNode ) return false; - if( hi._type.isConstant() || hi instanceof ConFldOffNode ) return true ; - - if( lo instanceof PhiNode lphi && lphi.region()._type==Type.XCONTROL ) return false; - if( hi instanceof PhiNode hphi && hphi.region()._type==Type.XCONTROL ) return false; - - if( lo instanceof PhiNode && lo.allCons(dep) ) return false; - if( hi instanceof PhiNode && hi.allCons(dep) ) return true ; - - if( lo instanceof PhiNode && !(hi instanceof PhiNode) ) return true; - if( hi instanceof PhiNode && !(lo instanceof PhiNode) ) return false; - - // Same category of "others" - return lo._nid > hi._nid; - } - - @Override Node copy(Node lhs, Node rhs) { return new AddNode(lhs,rhs); } - @Override Node copyF() { return new AddFNode(null,null); } -} diff --git a/src/main/java/com/seaofnodes/simple/node/AndNode.java b/src/main/java/com/seaofnodes/simple/node/AndNode.java deleted file mode 100644 index 70b57d6d..00000000 --- a/src/main/java/com/seaofnodes/simple/node/AndNode.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; - -public class AndNode extends ArithNode { - public AndNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); } - - @Override public String label() { return "And"; } - @Override public String op() { return "&"; } - @Override public String glabel() { return "&"; } - - @Override long doOp( long x, long y ) { return x & y; } - @Override TypeInteger doOp(TypeInteger x, TypeInteger y) { - // Sharpen allowed bits if either value is narrowed - long mask = x.mask() & y.mask(); - return mask < 0 ? TypeInteger.BOT : TypeInteger.make(0,mask); - } - - @Override - public Node idealize() { - Node lhs = in(1); - Node rhs = in(2); - Type t1 = lhs._type; - Type t2 = rhs._type; - if( !(t1 instanceof TypeInteger && t2 instanceof TypeInteger t2i) ) - return null; // Malformed, e.g. (17 & 3.14) - - // And of -1. We do not check for (-1&x) because this will already - // canonicalize to (x&-1). We do not check for zero, because - // the compute() call will return a zero already. - if( t2i.isConstant() && t2i.value()==-1 ) - return lhs; - - // Move constants to RHS: con*arg becomes arg*con - if( t1.isConstant() && !t2.isConstant() ) - return swap12(); - - // Do we have ((x & (phi cons)) & con) ? - // Do we have ((x & (phi cons)) & (phi cons)) ? - // Push constant up through the phi: x & (phi con0&con0 con1&con1...) - Node phicon = AddNode.phiCon(this,true); - if( phicon!=null ) - return phicon; - - return super.idealize(); - } - @Override Node copy(Node lhs, Node rhs) { return new AndNode(_loc,lhs,rhs); } -} diff --git a/src/main/java/com/seaofnodes/simple/node/ArithNode.java b/src/main/java/com/seaofnodes/simple/node/ArithNode.java deleted file mode 100644 index 96067c8e..00000000 --- a/src/main/java/com/seaofnodes/simple/node/ArithNode.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; - -import java.util.BitSet; - -public abstract class ArithNode extends Node { - // Source location for late reported errors - Parser.Lexer _loc; - - public ArithNode( Parser.Lexer loc, Node lhs, Node rhs) { super(null, lhs, rhs); _loc = loc; } - @Override public String glabel() { return op(); } - abstract String op(); - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - in(1)._print0(sb.append("("), visited); - in(2)._print0(sb.append(op()), visited); - return sb.append(")"); - } - - abstract long doOp(long x, long y); - abstract TypeInteger doOp(TypeInteger x, TypeInteger y); - - // Generic airthmetic op math: high returns high; low returns low; 2 - // constants fold; only 2 non-constants call specialized math. - @Override - public final TypeInteger compute() { - Type t1 = in(1)._type, t2 = in(2)._type; - if( t1.isHigh() || t2.isHigh() ) - return TypeInteger.TOP; - if( t1 instanceof TypeInteger x && - t2 instanceof TypeInteger y ) - return x.isConstant() && y.isConstant() - ? con(x,y) - : doOp(x,y); - return TypeInteger.BOT; - } - - private TypeInteger con( Type t1, Type t2 ) { - return TypeInteger.constant( doOp( ((TypeInteger)t1).value(), ((TypeInteger)t2).value()) ); - } - - // Check for "op(Phi(con,x),Phi(con,y))" and push-up through the Phi. - // Note that this is the exact reverse of Phi pulling a common op down - // to reduce total op-count. We don't get in an endless push-up - // push-down peephole cycle because the constants all fold first. - // Returns "Phi(op(con,con),op(x,y))". - - // This check is slightly different from the - - // Expected to be called after other ideal checks are done. - @Override public Node idealize() { - if( in(1) instanceof PhiNode lhs && - in(2) instanceof PhiNode rhs && - lhs.nIns() >= 2 && !lhs.inProgress() && - lhs.region()==rhs.region() && - lhs.nIns()>2 && // A 1-input Phi will collapse already - // Disallow with self-looping phi; these will collapse - (lhs.in(2)!=lhs && rhs.in(2)!=rhs) ) { - // Profit check: only 1 instance of `this` will remain, all the - // others will fold to constants. - int cnt=0; - for( int i=1; i<lhs.nIns(); i++ ) - if( isCon(lhs,rhs,i) ) - cnt++; - if( lhs.nIns()-1 - cnt <= 1 ) { - // Profit! Push up-phi and fold - Node[] ns = new Node[lhs.nIns()]; - ns[0] = lhs.in(0); - for( int i=1; i<lhs.nIns(); i++ ) { - ns[i] = isCon(lhs,rhs,i) - ? new ConstantNode(con(lhs.in(i)._type, rhs.in(i)._type)).peephole() - : copy(lhs.in(i), rhs.in(i)).peephole(); - } - String label = lhs._label==rhs._label ? lhs._label : lhs._label + rhs._label; - return new PhiNode(label,lhs._minType,ns).peephole(); - } - } - return null; - } - - static boolean isCon( PhiNode lhs, PhiNode rhs, int i ) { - return lhs.in(i)._type.isConstant() && rhs.in(i)._type.isConstant(); - } - - - static boolean overflow( long x, long y ) { - if( (x ^ y ) < 0 ) return false; // unequal signs, never overflow - return (x ^ (x + y)) < 0; // sum has unequal signs, so overflow - } - - @Override public Parser.ParseException err() { - if( in(1)._type.isHigh() || in(2)._type.isHigh() ) return null; - if( !(in(1)._type instanceof TypeInteger) ) return Parser.error("Cannot '"+op()+"' " + in(1)._type,_loc); - if( !(in(2)._type instanceof TypeInteger) ) return Parser.error("Cannot '"+op()+"' " + in(2)._type,_loc); - return null; - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/src/main/java/com/seaofnodes/simple/node/BoolNode.java deleted file mode 100644 index 2765c308..00000000 --- a/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ /dev/null @@ -1,164 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Utils; -import java.util.BitSet; -import static com.seaofnodes.simple.type.TypeInteger.*; - -abstract public class BoolNode extends Node { - - public BoolNode(Node lhs, Node rhs) { - super(null, lhs, rhs); - } - - abstract public String op(); // String opcode name - - @Override - public String label() { return getClass().getSimpleName(); } - - @Override - public String glabel() { return op(); } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - in(1)._print0(sb.append("("), visited); - in(2)._print0(sb.append(op()), visited); - return sb.append(")"); - } - - @Override - public TypeInteger compute() { - Type t1 = in(1)._type; - Type t2 = in(2)._type; - // Exactly equals? - if( t1.isHigh() || t2.isHigh() ) - return (TypeInteger)BOOL.dual(); - if( in(1)==in(2) ) - // LT fails, both EQ and LE succeed - return this instanceof LT ? FALSE : TRUE; - if( t1 instanceof TypeInteger i1 && - t2 instanceof TypeInteger i2 ) - return doOp(i1,i2); - if( t1 instanceof TypeFloat f1 && - t2 instanceof TypeFloat f2 && - f1.isConstant() && f2.isConstant() ) - return doOp(f1.value(), f2.value()) ? TRUE : FALSE; - return BOOL; - } - - TypeInteger doOp(TypeInteger t1, TypeInteger t2) { throw Utils.TODO(); } - boolean doOp(double lhs, double rhs) { throw Utils.TODO(); } - Node copyF(Node lhs, Node rhs) { return null; } - public boolean isFloat() { return false; } - - @Override - public Node idealize() { - // Compare of same - if( in(1)==in(2) ) - return this instanceof LT ? Parser.ZERO : new ConstantNode(TRUE); - - // Equals pushes constant to the right; 5==X becomes X==5. - if( this instanceof EQ ) { - if( !(in(2) instanceof ConstantNode) ) { - // con==noncon becomes noncon==con - if( in(1) instanceof ConstantNode || in(1)._nid > in(2)._nid ) - // Equals sorts by NID otherwise: non.high == non.low becomes non.low == non.high - return in(1)._type instanceof TypeFloat ? new EQF(in(2),in(1)) : new EQ(in(2),in(1)); - } - // Equals X==0 becomes a !X - if( (in(2)._type == ZERO || in(2)._type == Type.NIL) ) - return new NotNode(in(1)); - } - - // Do we have ((x * (phi cons)) * con) ? - // Do we have ((x * (phi cons)) * (phi cons)) ? - // Push constant up through the phi: x * (phi con0*con0 con1*con1...) - Node phicon = AddNode.phiCon(this,this instanceof EQ); - if( phicon!=null ) return phicon; - - return null; - } - - public static class EQ extends BoolNode { - public EQ(Node lhs, Node rhs) { super(lhs,rhs); } - public String op() { return "=="; } - TypeInteger doOp(TypeInteger i1, TypeInteger i2) { - if( i1==i2 && i1.isConstant() ) return TRUE; - if( i1._max < i2._min || i1._min > i2._max ) return FALSE; - return BOOL; - } - Node copy(Node lhs, Node rhs) { return new EQ(lhs,rhs); } - Node copyF() { return new EQF(null,null); } - } - - public static class NE extends BoolNode { - public NE(Node lhs, Node rhs) { super(lhs,rhs); } - public String op() { return "!="; } - TypeInteger doOp(TypeInteger i1, TypeInteger i2) { - if( i1==i2 && i1.isConstant() ) return TRUE; - if( i1._max < i2._min || i1._min > i2._max ) return FALSE; - return BOOL; - } - Node copy(Node lhs, Node rhs) { return new NE(lhs,rhs); } - Node copyF() { throw Utils.TODO(); } - } - - public static class LT extends BoolNode { - public LT(Node lhs, Node rhs) { super(lhs,rhs); } - public String op() { return "<" ; } - public String glabel() { return "<"; } - TypeInteger doOp(TypeInteger i1, TypeInteger i2) { - if( i1._max < i2._min ) return TRUE; - if( i1._min >= i2._max ) return FALSE; - return BOOL; - } - Node copy(Node lhs, Node rhs) { return new LT(lhs,rhs); } - Node copyF() { return new LTF(null,null); } - } - - public static class LE extends BoolNode { - public LE(Node lhs, Node rhs) { super(lhs,rhs); } - public String op() { return "<="; } - public String glabel() { return "<="; } - TypeInteger doOp(TypeInteger i1, TypeInteger i2) { - if( i1._max <= i2._min ) return TRUE; - if( i1._min > i2._max ) return FALSE; - return BOOL; - } - Node copy(Node lhs, Node rhs) { return new LE(lhs,rhs); } - Node copyF() { return new LEF(null,null); } - } - - public static class EQF extends EQ { - public EQF(Node lhs, Node rhs) { super(lhs,rhs); } - @Override boolean doOp(double lhs, double rhs) { return lhs == rhs; } - @Override public boolean isFloat() { return true; } - @Override Node copy(Node lhs, Node rhs) { return new EQF(lhs,rhs); } - } - public static class LTF extends LT { - public LTF(Node lhs, Node rhs) { super(lhs,rhs); } - @Override boolean doOp(double lhs, double rhs) { return lhs < rhs; } - @Override public boolean isFloat() { return true; } - @Override Node copy(Node lhs, Node rhs) { return new LTF(lhs,rhs); } - } - public static class LEF extends LE { - public LEF(Node lhs, Node rhs) { super(lhs,rhs); } - @Override boolean doOp(double lhs, double rhs) { return lhs <= rhs; } - @Override public boolean isFloat() { return true; } - @Override Node copy(Node lhs, Node rhs) { return new LEF(lhs,rhs); } - } - - // Unsigned less that, for range checks. Not directly user writable. - public static class ULT extends BoolNode { - public ULT(Node lhs, Node rhs) { super(lhs,rhs); } - public String op() { return "u<" ; } - public String glabel() { return "u<"; } - TypeInteger doOp(TypeInteger i1, TypeInteger i2) { - if( Long.compareUnsigned(i1._max,i2._min) < 0 ) return TRUE; - if( Long.compareUnsigned(i1._min,i2._max) >= 0 ) return FALSE; - return BOOL; - } - Node copy(Node lhs, Node rhs) { return new ULT(lhs,rhs); } - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/src/main/java/com/seaofnodes/simple/node/CFGNode.java deleted file mode 100644 index b9c70e5f..00000000 --- a/src/main/java/com/seaofnodes/simple/node/CFGNode.java +++ /dev/null @@ -1,208 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.Utils; -import java.util.BitSet; -import java.util.HashMap; -import java.util.HashSet; - -/** Control Flow Graph Nodes - * <p> - * CFG nodes have a immediate dominator depth (idepth) and a loop nesting - * depth(loop_depth). - * <p> - * idepth is computed lazily upon first request, and is valid even in the - * Parser, and is used by peepholes during parsing and afterward. - * <p> - * loop_depth is computed after optimization as part of scheduling. - * - * Start - Block head; has constants and trailing Funs - * Region - Block head; has Phis. Includes Fun and Parms. - * CallEnd - Block head; followed by CProj and Proj; also linked RPC Parms - * CProj - Block head - * - * If - Block tail; followed by CProj same block - * Call - Block tail; followed by CallEnd and linked Funs - * New/intrinsic - If followed by CProj: block tail else mid-block; followed by Proj - * - */ -public abstract class CFGNode extends Node { - - public CFGNode(Node... nodes) { super(nodes); } - public CFGNode(CFGNode cfg) { - super(cfg); - if( cfg != null ) { - _idepth = cfg._idepth; - _ltree = cfg._ltree; - _pre = cfg._pre; - } - } - - public CFGNode cfg(int idx) { return (CFGNode)in(idx); } - - // Block head is Start, Region, CProj, but not e.g. If, Return, Stop - public boolean blockHead() { return false; } - - // Get the one control following; error to call with more than one e.g. an - // IfNode or other multi-way branch. - public CFGNode uctrl() { - CFGNode c = null; - for( Node n : _outputs ) - if( n instanceof CFGNode cfg ) - { assert c==null; c = cfg; } - return c; - } - - // Used by the encoding / final BB layout - public CFGNode uctrlSkipEmpty() { - CFGNode x = this, y; - while( x.nOuts() == 1 && (y=x.uctrl())!=null && !(y instanceof CallNode) ) // Skip empty blocks - x = y; - return x; - } - - // ------------------------------------------------------------------------ - /** - * Immediate dominator tree depth, used to approximate a real IDOM during - * parsing where we do not have the whole program, and also peepholes - * change the CFG incrementally. - * <p> - * See {@link <a href="https://en.wikipedia.org/wiki/Dominator_(graph_theory)">...</a>} - */ - public int _idepth; - public int idepth() { - return CodeGen.CODE.validIDepth(_idepth) ? _idepth : (_idepth=CodeGen.CODE.iDepthFrom(idom().idepth())); - } - - // Return the immediate dominator of this Node and compute dom tree depth. - public CFGNode idom(Node dep) { return cfg(0); } - public final CFGNode idom() { return idom(null); } - - // Return the LCA of two idoms - public CFGNode _idom(CFGNode rhs, Node dep) { - if( rhs==null ) return this; - CFGNode lhs = this; - while( lhs != rhs ) { - var comp = lhs.idepth() - rhs.idepth(); - if( comp >= 0 ) lhs = (dep==null ? lhs : dep.addDep(lhs)).idom(); - if( comp <= 0 ) rhs = (dep==null ? rhs : dep.addDep(rhs)).idom(); - if( lhs==null || rhs==null ) return null; - } - return lhs; - } - - // Anti-dependence field support - public int _anti; // Per-CFG field to help find anti-deps - - // Find nearest enclosing FunNode - public FunNode fun() { - CFGNode cfg = this; - while( !(cfg instanceof FunNode fun) ) - cfg = cfg.idom(); - return fun; - } - - // ------------------------------------------------------------------------ - // Loop nesting - public LoopNode loop() { return _ltree._head; } - public int loopDepth() { return _ltree==null ? 0 : _ltree.depth(); } - - public LoopTree _ltree; - public int _pre; // Pre-order numbers for loop tree finding - static class LoopTree { - LoopTree _par; - final LoopNode _head; - int _depth; - LoopTree(LoopNode head) { _head = head; } - @Override public String toString() { return "LOOP"+_head._nid; } - int depth() { - return _depth==0 ? (_par==null ? 0 : (_depth = _par.depth()+1)) : _depth; - } - } - - // ------------------------------------------------------------------------ - // Tag all CFG Nodes with their containing LoopNode; LoopNodes themselves - // also refer to *their* containing LoopNode, as well as have their depth. - // Start is a LoopNode which contains all at depth 1. - public void buildLoopTree(StartNode start, StopNode stop) { - // Unlink all linked calls. This can remove RPC constants which - // shuffled the StartNode outputs so requires a while loop. - boolean done=false; - while(!done) { - done = true; - for( Node use : start._outputs ) - if( use instanceof FunNode fun ) - for( Node c : fun._inputs ) - if( c instanceof CallNode call ) - { call.unlink_all(); done=false; } - } - - _ltree = stop._ltree = Parser.XCTRL._ltree = new LoopTree((StartNode)this); - _bltWalk(2,null,stop, new BitSet()); - } - int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post ) { - // Pre-walked? - if( _pre!=0 ) return pre; - _pre = pre++; - // Pre-walk - for( Node use : _outputs ) - if( use instanceof CFGNode usecfg && !skip( usecfg ) ) - pre = usecfg._bltWalk( pre, use instanceof FunNode fuse ? fuse : fun, stop, post ); - - // Post-order work: find innermost loop - LoopTree inner = null, ltree; - for( Node use : _outputs ) { - if( !(use instanceof CFGNode usecfg) ) continue; - if( skip(usecfg) ) continue; - if( usecfg._type == Type.XCONTROL || // Do not walk dead control - usecfg._type == TypeTuple.IF_NEITHER ) // Nor dead IFs - continue; - // Child visited but not post-visited? - if( !post.get(usecfg._nid) ) { - // Must be a backedge to a LoopNode then - ltree = usecfg._ltree = new LoopTree((LoopNode)usecfg); - } else { - // Take child's loop choice, which must exist - ltree = usecfg._ltree; - // If falling into a loop, use the target loop's parent instead - if( ltree._head == usecfg ) { - if( ltree._par == null ) - // This loop never had an If test choose to take its - // exit, i.e. it is a no-exit infinite loop. - ltree._par = ltree._head.forceExit(fun,stop)._ltree; - ltree = ltree._par; - } - } - // Sort inner loops. The decision point is some branch far removed - // from either loop head OR either backedge so requires pre-order - // numbers to figure out innermost. - if( inner == null ) { inner = ltree; continue; } - if( inner == ltree ) continue; // No change - LoopTree outer = ltree._head._pre > inner._head._pre ? inner : ltree; - inner = ltree._head._pre > inner._head._pre ? ltree : inner; - inner._par = outer; - } - // Set selected loop - if( inner!=null ) - _ltree = inner; - // Tag as post-walked - post.set(_nid); - return pre; - } - - private boolean skip(CFGNode usecfg) { - // Only walk control users that are alive. - // Do not walk from a Call to linked Fun's. - return usecfg instanceof XCtrlNode || - (this instanceof CallNode && usecfg instanceof FunNode) || - (this instanceof ReturnNode && usecfg instanceof CallEndNode); - } - - - public String label( CFGNode target ) { - return (target instanceof LoopNode ? "LOOP" : "L")+target._nid; - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/CProjNode.java b/src/main/java/com/seaofnodes/simple/node/CProjNode.java deleted file mode 100644 index a6e21ea0..00000000 --- a/src/main/java/com/seaofnodes/simple/node/CProjNode.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMem; -import com.seaofnodes.simple.type.TypeTuple; -import com.seaofnodes.simple.util.Utils; -import java.util.BitSet; - -public class CProjNode extends CFGNode { - - // Which slice of the incoming multipart value - public int _idx; - - // Debugging label - public String _label; - - public CProjNode(Node ctrl, int idx, String label) { - super(ctrl); - _idx = idx; - _label = label; - } - public CProjNode(CProjNode c) { super(c); _idx = c._idx; _label = c._label; } - - @Override public String label() { return _label; } - - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(_label); } - - @Override public boolean blockHead() { return true; } - - public CFGNode ctrl() { return cfg(0); } - - @Override - public Type compute() { - Type t = ctrl()._type; - return t instanceof TypeTuple tt ? tt._types[_idx] : Type.BOTTOM; - } - - @Override - public Node idealize() { - if( ctrl()._type instanceof TypeTuple tt ) { - if( tt._types[_idx]==Type.XCONTROL ) - return Parser.XCTRL; // We are dead - if( ctrl() instanceof IfNode && tt._types[1-_idx]==Type.XCONTROL ) // Only true for IfNodes - return ctrl().in(0); // We become our input control - } - - // Flip a negating if-test, to remove the not - if( ctrl() instanceof IfNode iff && addDep(iff.pred()) instanceof NotNode not ) - return new CProjNode(new IfNode(iff.ctrl(),not.in(1)).peephole(),1-_idx,_idx==0 ? "False" : "True"); - - // Copy of some other input - return ((MultiNode)ctrl()).pcopy(_idx); - } - - // Only called during basic-block layout, inverts a T/F CProj - public void invert() { - _label = _idx == 0 ? "False" : "True"; - _idx = 1-_idx; - } - - @Override - public boolean eq( Node n ) { return _idx == ((CProjNode)n)._idx; } - - @Override - int hash() { return _idx; } - -} diff --git a/src/main/java/com/seaofnodes/simple/node/CallEndMach.java b/src/main/java/com/seaofnodes/simple/node/CallEndMach.java deleted file mode 100644 index d26ea33b..00000000 --- a/src/main/java/com/seaofnodes/simple/node/CallEndMach.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.util.SB; - -public class CallEndMach extends CallEndNode implements MachNode { - public CallEndMach( CallEndNode n ) { super(n); } - // MachNode specifics, shared across all CPUs - public int _xslot; - private RegMask _retMask; - private RegMask _kills; - public void cacheRegs(CodeGen code) { - // Return mask depends on TFP (either GPR or FPR) - _retMask = code._mach.retMask(call().tfp()); - // Kill mask is all caller-saves, and any mirror stack slots for args - // in registers. - RegMaskRW kills = code._callerSave.copy(); - // Start of stack slots - int maxReg = code._mach.regs().length; - // Incoming function arg slots, all low numbered in the RA - int fslot = fun()._maxArgSlot; - // Killed slots for this calls outgoing args - int xslot = code._mach.maxArgSlot(call().tfp()); - _xslot = (maxReg+fslot)+xslot; - for( int i=0; i<xslot; i++ ) - kills.set((maxReg+fslot)+i); - _kills = kills; - } - public String op() { return "cend"; } - public RegMask regmap(int i) { return null; } - public RegMask outregmap() { return null; } - public RegMask outregmap(int idx) { return idx==2 ? _retMask : null; } - public RegMask killmap() { return _kills; } - public void encoding( Encoding enc ) { } - public void asm(CodeGen code, SB sb) { } -} diff --git a/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/src/main/java/com/seaofnodes/simple/node/CallEndNode.java deleted file mode 100644 index b75852a4..00000000 --- a/src/main/java/com/seaofnodes/simple/node/CallEndNode.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.type.*; -import java.util.BitSet; - -/** - * CallEnd - */ -public class CallEndNode extends CFGNode implements MultiNode { - - // When set true, this Call/CallEnd/Fun/Return is being trivially inlined - private boolean _folding; - public final TypeRPC _rpc; - - public CallEndNode(CallNode call) { super(new Node[]{call}); _rpc = TypeRPC.constant(_nid); } - public CallEndNode(CallEndNode cend) { super(cend); _rpc = cend._rpc; } - - @Override public String label() { return "CallEnd"; } - @Override public boolean blockHead() { return true; } - - public CallNode call() { return (CallNode)in(0); } - - @Override public CFGNode idom(Node dep) { - // Folding the idom is the one inlining Return - return _folding ? cfg(1) : super.idom(dep); - } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - sb.append("cend( "); - sb.append( in(0) instanceof CallNode ? "Call, " : "----, "); - for( int i=1; i<nIns()-1; i++ ) - in(i)._print0(sb,visited).append(","); - sb.setLength(sb.length()-1); - return sb.append(")"); - } - - @Override - public Type compute() { - if( !(in(0) instanceof CallNode call) ) - return TypeTuple.RET.dual(); - Type ret = Type.BOTTOM; - if( addDep(call.fptr())._type instanceof TypeFunPtr tfp ) { - ret = tfp.ret(); - // Here, if I can figure out I've found *all* callers, then I can meet - // across the linked returns and join with the function return type. - if( tfp.isConstant() && nIns()>1 ) { - assert nIns()==2; // Linked exactly once for a constant - ret = ((TypeTuple)in(1)._type).ret(); // Return type - } - } - return TypeTuple.make(call._type,TypeMem.BOT,ret); - } - - @Override - public Node idealize() { - - // Trivial inlining: call site calls a single function; single function - // is only called by this call site. - if( !_folding && nIns()==2 && in(0) instanceof CallNode call ) { - Node fptr = call.fptr(); - if( fptr.nOuts() == 1 && // Only user is this call - fptr instanceof ConstantNode && // We have an immediate call - // Function is being called, and its not-null - fptr._type instanceof TypeFunPtr tfp && tfp.notNull() && - // Arguments are correct - call.err()==null ) { - ReturnNode ret = (ReturnNode)in(1); - FunNode fun = ret.fun(); - // Expecting Start, and the Call - if( fun.nIns()==3 ) { - assert fun.in(1) instanceof StartNode && fun.in(2)==call; - // Disallow self-recursive inlining (loop unrolling by another name) - CFGNode idom = call; - while( !(idom instanceof FunNode) && idom!=null ) - idom = idom.idom(); - // Inline? - if( idom != fun && idom != null ) { - // Trivial inline: rewrite - _folding = true; - // Rewrite Fun so the normal RegionNode ideal collapses - fun._folding = true; - fun.setDef(1,Parser.XCTRL); // No default/unknown StartNode caller - fun.setDef(2,call.ctrl()); // Bypass the Call; - fun.ret().setDef(3,null); // Return is folding also - CodeGen.CODE.addAll(fun._outputs); - // Inlining immediately blows all cache idepth fields past the inline point. - // Bump the global version number invalidating them en-masse. - CodeGen.CODE.invalidateIDepthCaches(); - return this; - } - } else { - addDep(fun); - } - } else { // Function ptr has multiple users (so maybe multiple call sites) - addDep(fptr); - } - } - - return null; - } - - @Override public Node pcopy(int idx) { - return _folding ? in(1).in(idx) : null; - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/CallNode.java b/src/main/java/com/seaofnodes/simple/node/CallNode.java deleted file mode 100644 index ba08c87a..00000000 --- a/src/main/java/com/seaofnodes/simple/node/CallNode.java +++ /dev/null @@ -1,176 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFunPtr; -import com.seaofnodes.simple.util.Utils; -import java.util.BitSet; - -/** - * Call - */ -public class CallNode extends CFGNode { - - // Source location for late reported errors - public final Parser.Lexer _loc; - - public CallNode(Parser.Lexer loc, Node... nodes) { super(nodes); _loc = loc; } - public CallNode(CallNode call) { super(call); _loc = call._loc; } - - @Override public String label() { return "Call"; } - - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { - String fname = name(); - if( fname == null ) fptr()._print0(sb,visited); - else sb.append(fname); - sb.append("( "); - for( int i=2; i<nIns()-1; i++ ) - in(i)._print0(sb,visited).append(","); - sb.setLength(sb.length()-1); - return sb.append(")"); - } - public String name() { - if( fptr()._type instanceof TypeFunPtr tfp && tfp.isConstant() ) { - FunNode fun = CodeGen.CODE.link(tfp); - if( fun !=null ) return fun._name; - if( fptr() instanceof ExternNode ex ) return ex._extern; - } - return null; - } - - - - Node ctrl() { return in(0); } - Node mem () { return in(1); } - // Args mapped 1-to-1 on inputs, so conceptually start at 2 - public Node arg(int idx) { return in(idx); } - // Same arg accounting as TFPs, although the numbering starts at 2 - public int nargs() { return nIns()-3; } // Minus control, memory, fptr - // args from input 2 to last; last is function input - public Node fptr() { return _inputs.last(); } - // Error if not a TFP - public TypeFunPtr tfp() { return (TypeFunPtr)fptr()._type; } - - // Call is to an externally supplied code - public boolean external() { return false; } - - - // Find the Call End from the Call - public CallEndNode cend() { - // Always in use slot 0 - if( nOuts()>0 && out(0) instanceof CallEndNode cend ) { - assert _cend()==cend; - return cend; - } else { - assert _cend()==null; - return null; - } - } - private CallEndNode _cend() { - CallEndNode cend=null; - for( Node n : _outputs ) - if( n instanceof CallEndNode cend0 ) - { assert cend == null; cend = cend0; } - return cend; - } - - // Get the one control following; error to call with more than one e.g. an - // IfNode or other multi-way branch. - @Override public CFGNode uctrl() { return cend(); } - - - @Override - public Type compute() { - return ctrl()._type; - } - - @Override - public Node idealize() { - CallEndNode cend = cend(); - if( cend==null ) return null; // Still building - - // Link: call calls target function. Linking makes the target FunNode - // point to this Call, and all his Parms point to the call arguments; - // also the CallEnd points to the Return. - Node progress = null; - if( fptr()._type instanceof TypeFunPtr tfp && tfp.nargs() == nargs() ) { - // If fidxs is negative, then infinite unknown functions - long fidxs = tfp.fidxs(); - if( fidxs > 0 ) { - // Wipe out the return which matching in the linker table - // Walk the (63 max) bits and link - for( ; fidxs!=0; fidxs = TypeFunPtr.nextFIDX(fidxs) ) { - int fidx = Long.numberOfTrailingZeros(fidxs); - TypeFunPtr tfp0 = tfp.makeFrom(fidx); - FunNode fun = CodeGen.CODE.link(tfp0); - if( fun!=null && !fun._folding && !linked(fun) ) - progress = link(fun); - } - } - } - - return progress; - } - - // True if Fun is linked to this Call - boolean linked( FunNode fun ) { - for( Node n : fun._inputs ) - if( n == this ) - return true; - return false; - } - - - // Link so this calls fun - private Node link( FunNode fun ) { - assert !linked(fun); - fun.addDef(this); - for( Node use : fun._outputs ) - if( use instanceof ParmNode parm ) - parm.addDef(parm._idx==0 ? new ConstantNode(cend()._rpc).peephole() : arg(parm._idx)); - // Call end points to function return - CodeGen.CODE.add(cend()).addDef(fun.ret()); - return this; - } - - // Unlink all linked functions - public void unlink_all() { - for( int i=0; i<_outputs._len; i++ ) - if( out(i) instanceof FunNode fun ) { - assert linked(fun); - int idx = fun._inputs.find(this); - for( Node use : fun._outputs ) - if( use instanceof ParmNode ) - use.delDef(idx); - fun.delDef(idx); - cend().delDef(cend()._inputs.find(fun.ret())); - assert !linked(fun); - i--; - } - } - - @Override - public Parser.ParseException err() { - if( !(fptr()._type instanceof TypeFunPtr tfp) ) - throw Utils.TODO(); - if( !tfp.notNull() ) - return Parser.error( "Might be null calling "+tfp, _loc); - if( nargs() != tfp.nargs() ) - return Parser.error( "Expecting "+tfp.nargs()+" arguments, but found "+nargs(), _loc); - - // Check for args - for( int i=0; i<tfp.nargs(); i++ ) - if( !arg(i+2)._type.isa(tfp.arg(i)) ) { - cend().addDep(arg(i+2)); // Upgrading arg type may allow inlining - return Parser.error( "Argument #" + i + " isa " + arg(i+2)._type + ", but must be a " + tfp.arg(i), _loc ); - } - - if( tfp.fidxs() < 0 ) - throw Utils.TODO(); // Infinite unknown TFPs? Should be fairly precise CG - - return null; - } - -} diff --git a/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java b/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java deleted file mode 100644 index 0afc5827..00000000 --- a/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.codegen.Encoding; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.type.Type; - -public class CalleeSaveNode extends ProjNode implements MachNode { - final RegMask _mask; - public CalleeSaveNode(FunNode fun, int reg, String label) { - super(fun,reg,label); - fun._outputs.pop(); - int i=0; - while( fun.out(i) instanceof PhiNode ) i++; - fun._outputs.insert(this,i); - _mask = new RegMask(reg); - } - @Override public RegMask regmap(int i) { return null; } - @Override public RegMask outregmap() { return _mask; } - @Override public void encoding( Encoding enc ) { } - - @Override public Type compute() { return Type.BOTTOM; } - @Override public Node idealize() { return null; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/CastMach.java b/src/main/java/com/seaofnodes/simple/node/CastMach.java deleted file mode 100644 index e45898f5..00000000 --- a/src/main/java/com/seaofnodes/simple/node/CastMach.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.Encoding; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.util.SB; - -/** - * Cast a pointer to read-only - */ -public class CastMach extends CastNode implements MachNode { - public CastMach( CastNode n ) { super(n); } - @Override public String op() { return label(); } - @Override public RegMask regmap(int i) { assert i==1; return RegMask.FULL; } - @Override public RegMask outregmap() { return RegMask.FULL; } - @Override public int twoAddress( ) { return 1; } - @Override public void encoding( Encoding enc ) { } - @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(in(1))); } -} diff --git a/src/main/java/com/seaofnodes/simple/node/CastNode.java b/src/main/java/com/seaofnodes/simple/node/CastNode.java deleted file mode 100644 index 6dc8e9fe..00000000 --- a/src/main/java/com/seaofnodes/simple/node/CastNode.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.type.TypeMemPtr; -import com.seaofnodes.simple.util.Utils; -import java.util.BitSet; - -// Upcast (join) the input to a t. Used after guard test to lift an input. -// Can also be used to make a type-assertion if ctrl is null. -public class CastNode extends Node { - public Type _t; - public CastNode(Type t, Node ctrl, Node in) { - super(ctrl, in); - _t = t; - setType(compute()); - } - - public CastNode(CastNode c) { - super(c.in(0), c.in(1)); // Call parent constructor - this._t = c._t; // Copy the Type field - setType(compute()); // Ensure type is recomputed - } - - @Override public String label() { return "("+_t.str()+")"; } - - @Override - public String uniqueName() { return "Cast_" + _nid; } - - @Override public boolean isPinned() { return true; } - @Override public boolean isConst() { return true; } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - return in(1)._print0(sb.append(label()), visited); - } - - @Override - public Type compute() { - // Cast array to int - Type t1 = in(1)._type; - if( _t == TypeInteger.BOT && t1 instanceof TypeMemPtr tmp && tmp._obj.isAry() ) - return _t; - // Freeze if the join is high but both inputs are low - Type tj = t1.join(_t); - if( tj.isHigh() && !t1.isHigh() ) - return _type==null ? _t : _type; - - return tj; - } - - @Override - public Node idealize() { - return in(1)._type.isa(_t) ? in(1) : null; - } - - @Override - public boolean eq(Node n) { - CastNode cast = (CastNode)n; // Contract - return _t==cast._t; - } - - @Override - int hash() { return _t.hashCode(); } - - @Override - public Parser.ParseException err() { - // Has a condition to test, so OK - if( in(0) != null ) return null; - // No condition to test, so this must optimize away - return Parser.error( "Type " + in(1)._type.str() + " is not of declared type " + _t.str(), null ); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java b/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java deleted file mode 100644 index 3e1d3c69..00000000 --- a/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.type.TypeStruct; -import com.seaofnodes.simple.util.SB; -import java.util.BitSet; - -/** - * A field offset. This becomes a constant *after* other optimizations which - * determine field sizes - some fields might be dead, or hold only zeros or - * small values. - */ - -public class ConFldOffNode extends ConstantNode { - public final String _name; // Struct name - public final String _fname; // Field name - public ConFldOffNode( String name, String fname ) { super(TypeInteger.BOT); _name = name; _fname = fname; } - - @Override public String label() { - return _fname == " len" ? "sizeof("+_name+")" : "#"+_fname; - } - @Override public String glabel() { return label(); } - @Override public String uniqueName() { return "Off_" + _nid; } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - return sb.append(label()); - } - - // Convert field offset to an integer - public Node asOffset(TypeStruct ts) { - return new ConstantNode(TypeInteger.constant(ts.offset(_fname==" len" ? ts._fields.length : ts.find(_fname)))).peephole(); - } - - - @Override public boolean eq(Node n) { - ConFldOffNode off = (ConFldOffNode)n; // Invariant - return _name==off._name && _fname==off._fname && super.eq(n); - } - @Override int hash() { return super.hash() * _name.hashCode() * _fname.hashCode(); } -} diff --git a/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/src/main/java/com/seaofnodes/simple/node/ConstantNode.java deleted file mode 100644 index 22bf03c4..00000000 --- a/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFunPtr; -import com.seaofnodes.simple.util.SB; -import java.util.BitSet; - -/** - * A Constant node represents a constant value. - * <p> - * Constants have no semantic inputs. However, we set Start as an input to - * Constants to enable a forward graph walk. This edge carries no semantic - * meaning, and it is present <em>solely</em> to allow visitation. - * <p> - * The Constant's value is the value stored in it. - */ - -public class ConstantNode extends Node { - public final Type _con; - public ConstantNode( Type type ) { - super(new Node[]{CodeGen.CODE._start}); - _con = _type = type; - } - public ConstantNode( ConstantNode con ) { super(con); _con = con._type; } - - public static Node make( Type type ) { - if( type==Type. CONTROL ) return new CtrlNode(); - if( type==Type.XCONTROL ) return new XCtrlNode(); - return new ConstantNode(type); - } - - @Override public String label() { return "#"+_con; } - @Override public String glabel() { return "#"+_con.gprint(); } - @Override public String uniqueName() { return "Con_" + _nid; } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - if( _con instanceof TypeFunPtr tfp && tfp.isConstant() ) { - FunNode fun = CodeGen.CODE.link(tfp); - if( fun!=null && fun._name != null ) - return sb.append("{ ").append(fun._name).append("}"); - } - return sb.append(_con==null ? "---" : _con.toString()); - } - - @Override public boolean isConst() { return true; } - @Override public Type compute() { return _con; } - @Override public Node idealize() { return null; } - @Override public boolean eq(Node n) { return _con==((ConstantNode)n)._con; } - @Override int hash() { return _con.hashCode(); } -} diff --git a/src/main/java/com/seaofnodes/simple/node/CtrlNode.java b/src/main/java/com/seaofnodes/simple/node/CtrlNode.java deleted file mode 100644 index eda7d684..00000000 --- a/src/main/java/com/seaofnodes/simple/node/CtrlNode.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; -import java.util.BitSet; - -public class CtrlNode extends CFGNode { - public CtrlNode() { super(CodeGen.CODE._start); } - @Override public String label() { return "Ctrl"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append("Cctrl"); } - @Override public boolean isConst() { return true; } - @Override public Type compute() { return Type.CONTROL; } - @Override public Node idealize() { return null; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/DivFNode.java b/src/main/java/com/seaofnodes/simple/node/DivFNode.java deleted file mode 100644 index 633f3224..00000000 --- a/src/main/java/com/seaofnodes/simple/node/DivFNode.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; - -import java.util.BitSet; - -public class DivFNode extends Node { - public DivFNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override public String label() { return "DivF"; } - - @Override public String glabel() { return "/"; } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - in(1)._print0(sb.append("("), visited); - in(2)._print0(sb.append("/"), visited); - return sb.append(")"); - } - - @Override - public Type compute() { - if (in(1)._type instanceof TypeFloat i0 && - in(2)._type instanceof TypeFloat i1) { - if (i0.isConstant() && i1.isConstant()) - return TypeFloat.constant(i0.value()/i1.value()); - } - return in(1)._type.meet(in(2)._type); - } - - @Override - public Node idealize() { - // Div of constant - if( in(2)._type instanceof TypeFloat f && f.isConstant() ) - return new MulFNode(in(1),new ConstantNode(TypeFloat.constant(1.0/f.value())).peephole()); - - return null; - } - @Override Node copy(Node lhs, Node rhs) { return new DivFNode(lhs,rhs); } -} diff --git a/src/main/java/com/seaofnodes/simple/node/DivNode.java b/src/main/java/com/seaofnodes/simple/node/DivNode.java deleted file mode 100644 index 0de1c946..00000000 --- a/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import java.util.BitSet; - -public class DivNode extends ArithNode { - public DivNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override public String label() { return "Div"; } - @Override public String op() { return "//"; } - - @Override long doOp( long x, long y ) { return y==0 ? 0 : x / y; } - @Override TypeInteger doOp(TypeInteger x, TypeInteger y) { - return TypeInteger.BOT; - } - - @Override - public Node idealize() { - // Div of 1. - if( in(2)._type == TypeInteger.TRUE ) - return in(1); - return super.idealize(); - } - - @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs); } - @Override Node copyF() { return new DivFNode(null,null); } -} diff --git a/src/main/java/com/seaofnodes/simple/node/ExternNode.java b/src/main/java/com/seaofnodes/simple/node/ExternNode.java deleted file mode 100644 index a10bb84a..00000000 --- a/src/main/java/com/seaofnodes/simple/node/ExternNode.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFunPtr; -import com.seaofnodes.simple.util.SB; -import java.util.BitSet; - -/** - A constant with external linkage. - */ - -public class ExternNode extends ConstantNode { - public final String _extern; - public ExternNode(Type t, String ex) { super(t); _extern = ex; } - - @Override public String label() { return "#"+_con+":"+_extern; } - @Override public String glabel() { return _con.print(new SB().p("#"), new BitSet(), true).p(":").p(_extern).toString(); } - @Override public String uniqueName() { return "Extern_" + _nid; } - - @Override public boolean eq(Node n) { return this==n; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/FRefNode.java b/src/main/java/com/seaofnodes/simple/node/FRefNode.java deleted file mode 100644 index 6093b8a6..00000000 --- a/src/main/java/com/seaofnodes/simple/node/FRefNode.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.Var; -import com.seaofnodes.simple.type.Type; -import java.util.BitSet; - -/** - * A Forward Reference. Its any final constant, including functions. When - * the Def finally appears its plugged into the forward reference, which then - * peepholes to the Def. - */ -public class FRefNode extends ConstantNode { - public static final Type FREF_TYPE = Type.BOTTOM; - public final Var _n; - public FRefNode( Var n ) { super(FREF_TYPE); _n = n; } - - @Override public String label() { return "FRef"+_n; } - - @Override public String uniqueName() { return "FRef_" + _nid; } - - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { - return sb.append("FRef_").append(_n); - } - - @Override public Node idealize() { - // When FRef finds its definition, idealize to it - return nIns()==1 ? null : in(1); - } - - public Parser.ParseException err() { return Parser.error("Undefined name '"+_n._name+"'",_n._loc); } - - @Override public boolean eq(Node n) { return this==n; } - @Override int hash() { return _n._idx; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/FunNode.java b/src/main/java/com/seaofnodes/simple/node/FunNode.java deleted file mode 100644 index 4bd0fd97..00000000 --- a/src/main/java/com/seaofnodes/simple/node/FunNode.java +++ /dev/null @@ -1,227 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.Encoding; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFunPtr; -import com.seaofnodes.simple.type.TypeTuple; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import java.util.BitSet; -import static com.seaofnodes.simple.codegen.CodeGen.CODE; - -public class FunNode extends RegionNode { - - // When set true, this Call/CallEnd/Fun/Return is being trivially inlined - boolean _folding; - - private TypeFunPtr _sig; // Initial signature - private ReturnNode _ret; // Return pointer - - public String _name; // Debug name - - public FunNode( Parser.Lexer loc, TypeFunPtr sig, String name, Node... nodes ) { super(loc,nodes); _name=name; _sig = sig; } - public FunNode( FunNode fun ) { - super( fun, fun==null ? null : fun._loc ); - if( fun!=null ) { - _sig = fun.sig(); - _name = fun._name; - } else { - _sig = TypeFunPtr.BOT; - _name = ""; - } - } - - @Override - public String label() { return _name == null ? "$fun"+_sig.fidx() : _name; } - - // Find the one CFG user from Fun. It's not always the Return, but always - // the Return *is* a CFG user of Fun. - @Override public CFGNode uctrl() { - for( Node n : _outputs ) - if( n instanceof CFGNode cfg && - (cfg instanceof RegionNode || cfg.cfg0()==this) ) - return cfg; - return null; - } - - public ParmNode rpc() { - ParmNode rpc = null; - for( Node n : _outputs ) - if( n instanceof ParmNode parm && parm._idx==0 ) - { assert rpc==null; rpc=parm; } - return rpc; - } - - // Cannot create the Return and Fun at the same time; one has to be first. - // So setting the return requires a second step. - public void setRet(ReturnNode ret) { _ret=ret; } - public ReturnNode ret() { assert _ret!=null; return _ret; } - - // Signature can improve over time - public TypeFunPtr sig() { return _sig; } - public void setSig( TypeFunPtr sig ) { - assert sig.isa(_sig); - if( _sig != sig ) { - CODE.add(this); - _sig = sig; - } - } - - public void setName( String name ) { - if( _name==null ) _name=name; - else _name += "."+name; - } - - @Override - public Type compute() { - // Only dead if no callers after SCCP - if( unknownCallers() ) - return Type.CONTROL; - return super.compute(); - } - - @Override - public Node idealize() { - - // Some linked path dies - Node progress = deadPath(); - if( progress!=null ) { - if( nIns()==3 && in(2) instanceof CallNode call ) - CODE.add(call.cend()); // If Start and one call, check for inline - return progress; - } - - // Upgrade inferred or user-written return type to actual - if( _ret!=null && _ret._type instanceof TypeTuple tt && tt.ret() != _sig.ret() ) - throw Utils.TODO(); - - // When can we assume no callers? Or no other callers (except main)? - // In a partial compilation, we assume Start gets access to any/all - // top-level public structures and recursively what they point to. - // This in turn is valid arguments to every callable function. - // - // In a total compilation, we can start from Start and keep things - // more contained. - - // If no default/unknown caller, use the normal RegionNode ideal rules - // to collapse - if( unknownCallers() ) return null; - - // If down to a single input, become that input - if( nIns()==2 && !hasPhi() ) { - CODE.add( CODE._stop ); // Stop will remove dead path - CODE.add( _ret ); // Return will compute to TOP control - return in(1); // Collapse if no Phis; 1-input Phis will collapse on their own - } - - return null; - } - - // Bypass Region idom, always assume depth == 1, one more than Start, - // unless folding then just a ID on input#1 - @Override public int idepth() { - return _folding ? super.idepth() : CodeGen.CODE.iDepthAt(1); - } - // Bypass Region idom, always assume idom is Start - @Override public CFGNode idom(Node dep) { return _folding && nIns()==3 ? cfg(2) : (nIns()>1 ? cfg(1) : null); } - - // Always in-progress until we run out of unknown callers - public boolean unknownCallers() { return nIns()>=2 && in(1) instanceof StartNode; } - - @Override public boolean inProgress() { return unknownCallers(); } - - // Add a new function exit point. - public void addReturn(Node ctrl, Node mem, Node rez) { _ret.addReturn(ctrl,mem,rez); } - - // Build the function body - public BitSet body() { - - // Reverse up (stop to start) CFG only, collect bitmap. - BitSet cfgs = new BitSet(); - cfgs.set(_nid); - walkUp(ret(),cfgs ); - - // Top down (start to stop) all flavors. CFG limit to bitmap. - // If data use bottoms out in wrong CFG, returns false - but tries all outputs. - // If any output hits an in-CFG use (e.g. phi), then keep node. - BitSet body = new BitSet(); - walkDown(this, cfgs, body, new BitSet()); - return body; - } - - private static void walkUp(CFGNode n, BitSet cfgs) { - if( cfgs.get(n._nid) ) return; - cfgs.set(n._nid); - if( n instanceof RegionNode r ) - for( int i=1; i<n.nIns(); i++ ) - walkUp(n.cfg(i),cfgs); - else walkUp(n.cfg0(),cfgs); - } - - private static boolean walkDown( Node n, BitSet cfgs, BitSet body, BitSet visit ) { - if( visit.get(n._nid) ) return body.get(n._nid); - visit.set(n._nid); - - if( n instanceof CFGNode && !cfgs.get(n._nid) ) - return false; - if( n.in(0)!=null && !cfgs.get(n.in(0)._nid) ) - return false; - boolean in = n.in(0)!=null || n instanceof CFGNode; - for( Node use : n._outputs ) - in |= walkDown(use,cfgs,body,visit); - if( in ) body.set(n._nid); - return in; - } - - - // ------------ - // MachNode specifics, shared across all CPUs - - // Function may call other functions? X86 requires 16b aligned SP on - // outbound calls, but not leaf. Set during an unrelated RA scan. - public boolean _hasCalls; - - // Maximum incoming argument slot, based on function type - public int _maxArgSlot; - - // Frame adjust, including padding, set during early Encoding - public int _frameAdjust; - - public String op() { return _frameAdjust==0 ? "entry" : "subi"; } - public void postSelect(CodeGen code) { - code.link(this); - // Max slot seen past space reserved for incoming arguments - _maxArgSlot = code._mach.maxArgSlot(sig()); - } - public RegMask regmap(int i) { return null; } - public RegMask outregmap() { return null; } - public void encoding( Encoding enc ) { throw Utils.TODO(); } - public void asm(CodeGen code, SB sb) { - if( _frameAdjust!=0 ) - sb.p("rsp -= #").p(_frameAdjust); - } - - public void computeFrameAdjust(CodeGen code, int maxReg) { - // Max slot seen, or 0. - int maxSlot = Math.max(maxReg - code._mach.regs().length,_maxArgSlot); - // Frame adjust before rounding, can be negative for no spills - int size = maxSlot - _maxArgSlot; - _frameAdjust = size*8; - } - - // Given a high register number and during encoding compute the stack - // pointer offset. Pre-RA we lack the fun._frameAdjust. We also need a - // FunNode. - public int computeStackOffset(CodeGen code, int reg) { - String[] regs = code._mach.regs(); - int stackSlot = reg - regs.length; - int slotRotate = stackSlot < _maxArgSlot - ? stackSlot + (_frameAdjust>>3) - : stackSlot - _maxArgSlot; - return slotRotate*8; - } - -} diff --git a/src/main/java/com/seaofnodes/simple/node/IfNode.java b/src/main/java/com/seaofnodes/simple/node/IfNode.java deleted file mode 100644 index c5d687e5..00000000 --- a/src/main/java/com/seaofnodes/simple/node/IfNode.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Utils; -import java.util.BitSet; - -public class IfNode extends CFGNode implements MultiNode { - - public IfNode(Node ctrl, Node pred) { - super(ctrl, pred); - CodeGen.CODE.add(this); // Because idoms are complex, just add it - } - public IfNode(IfNode iff) { super(iff); } - - @Override - public String label() { return "If"; } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - sb.append("if( "); - if( in(1)==null ) sb.append("never"); - else in(1)._print0(sb, visited); - return sb.append(" )"); - } - - public Node ctrl() { return in(0); } - public Node pred() { return in(1); } - - // No one unique control follows - @Override public CFGNode uctrl() { return null; } - - @Override - public Type compute() { - // If the If node is not reachable then neither is any following Proj - if (ctrl()._type != Type.CONTROL && ctrl()._type != Type.BOTTOM ) - return TypeTuple.IF_NEITHER; - Node pred = pred(); - Type t = pred._type; - // High types mean NEITHER side is reachable. - // Wait until the type falls to decide which way to go. - if( t.isHigh() ) - return TypeTuple.IF_NEITHER; - // If constant is 0 then false branch is reachable - // Else true branch is reachable - if( t.isConstant() ) - return (t==Type.NIL || t==TypeInteger.ZERO || (t instanceof TypeFunPtr tfp && tfp._fidxs==0) ) ? TypeTuple.IF_FALSE : TypeTuple.IF_TRUE; - // If adding a zero makes a difference, the predicate must not have a zero/null - if( !t.makeZero().isa(t) ) - return TypeTuple.IF_TRUE; - - return TypeTuple.IF_BOTH; - } - - @Override - public Node idealize() { - // Hunt up the immediate dominator tree. If we find an identical if - // test on either the true or false branch, that side wins. - if( !pred()._type.isHighOrConst() ) - for( CFGNode dom = idom(), prior=this; dom!=null; prior = dom, dom = dom.idom() ) - if( addDep(dom) instanceof IfNode iff && addDep(iff.pred())==pred() && prior instanceof CProjNode prj ) { - setDef(1,con( prj._idx==0 ? 1 : 0 )); - return this; - } - return null; - } - - // MachNode variants need to support this and negate the conditional test. - // The following CProjs will be inverted by the caller. - public void negate() { throw Utils.TODO(); } - - // Negate the sense of a test - public static String negate( String bop ) { - return switch( bop ) { - case "<" -> ">="; - case "<=" -> ">" ; - case "==" -> "!="; - case "!=" -> "=="; - case ">" -> "<="; - case ">=" -> "<" ; - default -> throw Utils.TODO(); - }; - } - - // Swap compare operands - public static String swap( String bop ) { - return switch( bop ) { - case "<" -> ">" ; - case "<=" -> ">="; - case "==" -> "=="; - case "!=" -> "!="; - case ">" -> "<" ; - case ">=" -> "<="; - default -> throw Utils.TODO(); - }; - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/src/main/java/com/seaofnodes/simple/node/LoadNode.java deleted file mode 100644 index b58ea498..00000000 --- a/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ /dev/null @@ -1,233 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Utils; -import java.util.BitSet; - -/** - * Load represents extracting a value from inside a memory object, - * in chapter 10 this means Struct fields. - */ -public class LoadNode extends MemOpNode { - - /** - * Load a value from a ptr.field. - * - * @param name The field we are loading - * @param mem The memory alias node - this is updated after a Store - * @param ptr The ptr to the struct base from where we load a field - * @param off The offset inside the struct base - */ - public LoadNode(Parser.Lexer loc, String name, int alias, Type glb, Node mem, Node ptr, Node off) { - super(loc, name, alias, true, glb, mem, ptr, off); - } - - // GraphVis DOT code (must be valid Java identifiers) and debugger labels - @Override public String label() { return "ld_"+mlabel(); } - // GraphVis node-internal labels - @Override public String glabel() { return "." +_name; } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); } - - @Override - public Type compute() { - if( !(mem()._type instanceof TypeMem mem) ) - return _declaredType; // No memory yet? Declared type - assert !_declaredType.isFRef(); - // No lifting if ptr might null-check - if( err()!=null || !(ptr()._type instanceof TypeMemPtr tmp) ) - return _declaredType; // No pointer yet? Declared type - Type t = tmp._obj.field(_name)._t; - if( t instanceof TypeConAry ary ) { - t = ary.elem(); // TODO: if offset is known, can peek the constant - } - // Lift from declared type and memory input - t = t.join(_declaredType).join(mem._t); - if( _declaredType.isFinal() ) - t = t.makeRO(); // Deep final applied - return t; - } - - @Override - public Node idealize() { - Node ptr = ptr(); - Node mem = mem(); - - if( mem instanceof CastNode cast ) { - setDef(1,cast.in(1)); - return this; - } - - // Simple Load-after-Store on same address. - if( mem instanceof StoreNode st && - ptr == st.ptr() && off() == st.off() ) { // Must check same object - assert _name.equals(st._name); // Equiv class aliasing is perfect - return extend(st.val()); - } - - // Simple load-after-MemMerge to a known alias can bypass. Happens when inlining. - if( mem instanceof MemMergeNode mem2 ) { - Node memA = mem2.alias(_alias); - for( Node ld : memA._outputs ) - if( ld instanceof LoadNode ) - CodeGen.CODE.add(ld); - setDef(1,memA); - return this; - } - - // Simple Load-after-New on same address. - if( mem instanceof ProjNode p && p.in(0) instanceof NewNode nnn && - ptr == nnn.proj(1) ) // Must check same object - return zero(nnn); // Load zero from new - - // Uplift control to a prior dominating load. - for( Node memuse : mem._outputs ) - // Find a prior load, has same mem,ptr,off but higher ctrl - if( memuse != this && memuse instanceof LoadNode ld && ptr==ld.ptr() && off()==ld.off() && - cfg0()!=null && cfg0()._idom(ld.cfg0(),this) == ld.cfg0() ) // Higher control means load is legal earlier - return ld; - - // Load-after-Store on same address, but bypassing provably unrelated - // stores. This is a more complex superset of the above two peeps. - // "Provably unrelated" is really weak. - if( ptr instanceof ReadOnlyNode ro ) - ptr = ro.in(1); - outer: - while( true ) { - switch( mem ) { - case StoreNode st: - if( ptr == addDep(st.ptr()) && off() == st.off() ) - return extend(castRO(st.val())); // Proved equal - // Can we prove unequal? Offsets do not overlap? - if( !off()._type.join(st.off()._type).isHigh() && // Offsets overlap - !neverAlias(ptr,st.ptr()) ) // And might alias - break outer; // Cannot tell, stop trying - // Pointers cannot overlap - mem = st.mem(); // Proved never equal - break; - case PhiNode phi: - // Assume related - addDep(phi); - break outer; - case ConstantNode top: break outer; // Assume shortly dead - case ProjNode mproj: // Memory projection - switch( mproj.in(0) ) { - case NewNode nnn1: - if( ptr instanceof ProjNode pproj && pproj.in(0) == mproj.in(0) ) - return zero(nnn1); - if( !(ptr instanceof ProjNode pproj && pproj.in(0) instanceof NewNode) ) - break outer; // Cannot tell, ptr not related to New - mem = nnn1.in(nnn1.findAlias(_alias));// Bypass unrelated New - break; - case StartNode start: break outer; - case CallEndNode cend: break outer; // TODO: Bypass no-alias call - default: throw Utils.TODO(); - } - break; - case CastNode cast: mem = cast.in(1); break; - case MemMergeNode merge: mem = merge.alias(_alias); break; - - default: - throw Utils.TODO(); - } - } - - // Push a Load up through a Phi, as long as it collapses on at least - // one arm. If at a Loop, the backedge MUST collapse - else we risk - // spinning the same transform around the loop indefinitely. - // BEFORE (2 Sts, 1 Ld): AFTER (1 St, 0 Ld): - // if( pred ) ptr.x = e0; val = pred ? e0 - // else ptr.x = e1; : e1; - // val = ptr.x; ptr.x = val; - if( mem() instanceof PhiNode memphi && memphi.region()._type == Type.CONTROL && memphi.nIns()== 3 && - // Offset can be hoisted - off() instanceof ConstantNode && - // Pointer can be hoisted - hoistPtr(ptr,memphi) ) { - - // Profit on RHS/Loop backedge - if( profit(memphi,2) || - // Else must not be a loop to count profit on LHS. - (!(memphi.region() instanceof LoopNode) && profit(memphi,1)) ) { - Node ld1 = ld(1); - Node ld2 = ld(2); - return new PhiNode(_name,_declaredType,memphi.region(),ld1,ld2); - } - } - - return null; - } - - // Load a flavored zero from a New - private Node zero(NewNode nnn) { - TypeStruct ts = nnn._ptr._obj; - Type zero = ts._fields[ts.findAlias(_alias)]._t.makeZero(); - return castRO(new ConstantNode(zero).peephole()); - } - - private Node ld( int idx ) { - Node mem = mem(), ptr = ptr(); - return new LoadNode(_loc,_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr, off()).peephole(); - } - - private static boolean neverAlias( Node ptr1, Node ptr2 ) { - return ptr1.in(0) != ptr2.in(0) && - // Unrelated allocations - ptr1 instanceof ProjNode && ptr1.in(0) instanceof NewNode && - ptr2 instanceof ProjNode && ptr2.in(0) instanceof NewNode; - } - - private static boolean hoistPtr(Node ptr, PhiNode memphi ) { - // Can I hoist ptr above the Region? - if( !(memphi.region() instanceof RegionNode r) ) - return false; // Dead or dying Region/Phi - // If ptr from same Region, then yes, just use hoisted split pointers - if( ptr instanceof PhiNode pphi && pphi.region() == r ) - return true; - - // No, so can we lift this ptr? - CFGNode cptr = ptr.cfg0(); - if( cptr != null ) - // Pointer is controlled high - // TODO: Really needs to be the LCA of all inputs is high - return cptr.idepth() <= r.idepth(); - - // Dunno without a longer walk - return false; - } - - // Profitable if we find a matching Store on this Phi arm. - private boolean profit(PhiNode phi, int idx) { - Node px = phi.in(idx); - if( px==null ) return false; - if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() ) return true; - if( px instanceof StoreNode st1 && ptr()==addDep(st1.ptr() )&& off()==st1.off() ) return true; - addDep(px); - return false; - } - - // Read-Only is a deep property, and cannot be cast-away - private Node castRO(Node rez) { - if( ptr()._type.isFinal() && !rez._type.isFinal() ) - return new ReadOnlyNode(rez).peephole(); - return rez; - } - - // When a load bypasses a store, the store might truncate bits - and the - // load will need to zero/sign-extend. - private Node extend(Node val) { - if( !(_declaredType instanceof TypeInteger ti) ) return val; - if( ti._min==0 ) // Unsigned - return new AndNode(null,val,con(ti._max)); - // Signed extension - int shift = Long.numberOfLeadingZeros(ti._max)-1; - Node shf = con(shift); - if( shf._type==TypeInteger.ZERO ) - return val; - Node shl = new ShlNode(null,val,shf.keep()).peephole(); - return new SarNode(null,shl,shf.unkeep()); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/src/main/java/com/seaofnodes/simple/node/LoopNode.java deleted file mode 100644 index fc50e17f..00000000 --- a/src/main/java/com/seaofnodes/simple/node/LoopNode.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.type.TypeMem; -import com.seaofnodes.simple.util.Utils; -import java.util.BitSet; -import java.util.HashSet; - -public class LoopNode extends RegionNode { - public LoopNode( Parser.Lexer loc, Node entry ) { super(loc,null,entry,null); } - public LoopNode( LoopNode loop ) { super(loop); } - - public CFGNode entry() { return cfg(1); } - public CFGNode back () { return cfg(2); } - - @Override - public String label() { return "Loop"; } - - @Override - public Type compute() { - if( inProgress() ) return Type.CONTROL; - return entry()._type; - } - - // Bypass Region idom, same as the default idom() using use in(1) instead of in(0) - @Override public int idepth() { - return CodeGen.CODE.validIDepth(_idepth) ? _idepth : (_idepth=CodeGen.CODE.iDepthFrom(idom().idepth())); - } - // Bypass Region idom, same as the default idom() using use in(1) instead of in(0) - @Override public CFGNode idom(Node dep) { return entry(); } - - // If this is an unreachable loop, it may not have an exit. If it does not - // (i.e., infinite loop), force an exit to make it reachable. - public StopNode forceExit( FunNode fun, StopNode stop ) { - // Walk the backedge, then immediate dominator tree util we hit this - // Loop again. If we ever hit a CProj from an If (as opposed to - // directly on the If) we found our exit. - CFGNode x = back(); - while( x != this ) { - if( x instanceof CProjNode exit && exit.in(0) instanceof IfNode iff ) { - CFGNode other = iff.cproj(1-exit._idx); - if( other!=null && other._ltree != _ltree && nested(_ltree,other._ltree) ) - return stop; // Found an exit, not an infinite loop - } - x = x.idom(); - } - // Found a no-exit loop. Insert an exit - NeverNode iff = (NeverNode)new NeverNode(back()).peephole(); // Ideal never-branch - CProjNode t = new CProjNode(iff,0,"True" ).init(); - CProjNode f = new CProjNode(iff,1,"False").init(); - setDef(2,t); // True continues loop, False (never) exits loop - ReturnNode ret = fun.ret(); - iff._ltree = t._ltree = _ltree; - ret._ltree = f._ltree = stop._ltree; - - // Now fold control into the exit. Might have 1 valid exit, or an - // XCtrl or a bunch of prior NeverNode exits. - Node top = new ConstantNode(Type.TOP).peephole(); - Node memout = new MemMergeNode(false); - memout.addDef(f); // placeholder for control - for( Node u : _outputs ) - if( u instanceof PhiNode phi && phi._type.isa(TypeMem.BOT) ) - memout.addDef(phi); - - Node ctrl = ret.ctrl(), mem = ret.mem(), expr = ret.expr(); - if( ctrl!=null && ctrl._type != Type.XCONTROL ) { - // Perfect aligned exit? - if( !(ctrl instanceof RegionNode r && - mem instanceof PhiNode pmem && pmem.region()==r && - expr instanceof PhiNode prez && prez.region()==r ) ) { - // Nope, insert an aligned exit layer - RegionNode r = new RegionNode(_loc,null,ctrl).init(); - ctrl = r; r._ltree = stop._ltree; - mem = new PhiNode(r,mem ).init(); - expr = new PhiNode(r,expr).init(); - } - // Append new Never exit - ctrl.keep().addDef(f ); - mem .keep().addDef(memout); - expr.keep().addDef(top); - } else { - ctrl = f .keep(); - mem = memout.keep(); - expr = top .keep(); - } - ret.setDef(0,ctrl.unkeep()); - ret.setDef(1,mem .unkeep()); - ret.setDef(2,expr.unkeep()); - ret._type = null; // This may LIFT the Return type, from [XCtrl,mem,expr] to [Ctrl,mem,expr] - ret.setType(ret.compute()); - - return stop; - } - - private static boolean nested(LoopTree inner, LoopTree outer) { - for( ; inner!=null; inner = inner._par ) - if( inner == outer ) - return true; - return false; - } - -} diff --git a/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java b/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java deleted file mode 100644 index 9440a7ea..00000000 --- a/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.util.Utils; -import java.io.ByteArrayOutputStream; -import java.util.BitSet; - -// Generic machine-specific class, has a few Node implementations that have to -// exist (abstract) but are not useful past the optimizer. -public abstract class MachConcreteNode extends Node implements MachNode { - - public MachConcreteNode(Node node) { super(node); } - public MachConcreteNode(Node[]nodes) { super(nodes); } - - @Override public String label() { return op(); } - @Override public Type compute () { throw Utils.TODO(); } - @Override public Node idealize() { throw Utils.TODO(); } - - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { - sb.append("(").append(op()).append(","); - for( int i=1; i<nIns(); i++ ) - (in(i)==null ? sb.append(" ---") : in(i)._print0(sb, visited)).append(","); - sb.setLength(sb.length()-1); - return sb.append(")"); - } - -} diff --git a/src/main/java/com/seaofnodes/simple/node/MachNode.java b/src/main/java/com/seaofnodes/simple/node/MachNode.java deleted file mode 100644 index c0b3af66..00000000 --- a/src/main/java/com/seaofnodes/simple/node/MachNode.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; - -public interface MachNode { - - // Easy access to the abstract Machine - default Machine machine() { return CodeGen.CODE._mach; } - - // Run a post-instruction-selection action - default void postSelect( CodeGen code ) { } - - // Register mask allowed on input i. 0 for no register. - RegMask regmap( int i ); - // Register mask allowed as a result. 0 for no register. - RegMask outregmap(); - // Multi-reg-defining machine op; idx comes from Proj. - // Sometimes these Projs have no uses, and just exist to kill a register. - default RegMask outregmap(int idx) { throw Utils.TODO(); } - - // Set of killed registers - default RegMask killmap() { return null; } - - // If this op updates the same register as input 'idx', e.g. many X86 "R1 += - // R2" style ops. This forces the output live range to equal the input live - // range, so they both get the same register. - - // This can hold true for projections, if the instruction updates many - // registers that it also takes in. Note that ops also use the same - // registers for both inputs and outputs but these are pinned and without any - // choices (e.g. always RCX for both an input and output) and are better - // served with just declaring as a fixed register. - - // Returns either 0 for not-two-adr or the updated input; for projections - // this is their Multi's updated input. - default int twoAddress( ) { return 0; } - // Ok to switch arguments - default boolean commutes() { return false; } - - // Instructions cheaper to recreate than to spill, such as loading small constants - default boolean isClone() { return false; } - - // Make a clone of a cheap instruction - default Node copy() { return null; } - - // Encoding is appended, relocations gathered. - void encoding( Encoding enc ); - - // Human-readable form appended to the SB. Things like the encoding, - // indentation, leading address or block labels not printed here. - // Just something like "R17=[R18+12] // Load array base". - // General form: "dst=src+src". - default void asm(CodeGen code, SB sb) { throw Utils.TODO(); } - - // Human readable opcode; e.g. "ld4" or "call". - default String op() { throw Utils.TODO(); } -} diff --git a/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java deleted file mode 100644 index 19acc5b9..00000000 --- a/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java +++ /dev/null @@ -1,180 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.type.*; -import java.util.*; - -/** - * Memory Merge - a merge of many aliases into a "fat memory". All aliases - * are here, but most will be lazy - take the default fat memory. - */ -public class MemMergeNode extends Node { - - /* - * In-Progress means this is being used by the Parser to track memory - * aliases. No optimizations are allowed. When no longer "in progress" - * normal peeps work. - */ - public final boolean _inProgress; - - public MemMergeNode( boolean inProgress) { _type = TypeMem.BOT; _inProgress = inProgress; } - public MemMergeNode(MemMergeNode mem) { super(mem); _inProgress = false; } - - - // If being used by a Scope, this is "in progress" from the Parser. - // Otherwise, it's a memory merge - boolean inProgress() { return _inProgress; } - - @Override public String label() { return "ALLMEM"; } - @Override public boolean isMem() { return true; } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - sb.append(_inProgress ? "Merge[" : "MEM[ "); - for( int j=2; j<nIns(); j++ ) { - sb.append(j); - sb.append(":"); - Node n = in(j); - while( n instanceof ScopeNode loop ) { - sb.append("Lazy_"); - n = loop.mem(j); - } - if( n==null ) sb.append("___ "); - else n._print0(sb, visited).append(" "); - } - sb.setLength(sb.length()-1); - return sb.append("]"); - } - - // Make a memory merge: no longer a Scope really, tracking memory state but - // not related to the parser in any way. - public Node merge() { - // Force default memory to not be lazy - MemMergeNode merge = new MemMergeNode(false); - for( Node n : _inputs ) - merge.addDef(n); - for( int i=1; i<nIns(); i++ ) - merge._mem(i,null); - return merge.peephole(); - } - - - @Override public Type compute() { return TypeMem.BOT; } - - @Override public Node idealize() { - if( inProgress() ) return null; - - // Fold defaults into the default - boolean progress=false, allDefault=true; - for( int i=2; i<nIns(); i++ ) { - if( in(i) instanceof CastNode cast ) - { setDef(i,cast.in(1)); progress=true; } - if( in(1) == in(i) ) { setDef(i,null); progress=true; } - else { allDefault=false; } - } - - // If not merging any memory (all memory is just the default) - if( allDefault ) - return in(1); // Become default memory - - // Collapse stacked all-mem - if( in(1) instanceof MemMergeNode mem ) { - // Goal is to swap my default mem with mem's default mem - for( int i=2; i<mem.nIns(); i++ ) { - if( mem.in(i) != null ) { - // deeper default mem has a non-default - if( i>=nIns() || in(i)==null ) - setDefX(i,mem.in(i)); - } - } - setDef(1,mem.in(1)); - return this; - } - - return progress ? this : null; - } - - - public Node in( Var v ) { return in(v._idx); } - - public Node alias( int alias ) { - return alias < nIns() && in(alias)!=null ? in(alias) : in(1); - } - - Node alias( int alias, Node st ) { - while( alias >= nIns() ) addDef(null); - return setDef(alias,st); - } - - // Read or update from memory. - // A shared implementation allows us to create lazy phis both during - // lookups and updates; the lazy phi creation is part of chapter 8. - Node _mem( int alias, Node st ) { - // Memory projections are made lazily; if one does not exist - // then it must be START.proj(1) - Node old = alias(alias); - if( old instanceof ScopeNode loop ) { - MemMergeNode loopmem = loop.mem(); - Node memdef = loopmem.alias(alias); - // Lazy phi! - old = memdef instanceof PhiNode phi && loop.ctrl()==phi.region() - // Loop already has a real Phi, use it - ? memdef - // Set real Phi in the loop head - // The phi takes its one input (no backedge yet) from a recursive - // lookup, which might have insert a Phi in every loop nest. - : loopmem.alias(alias, new PhiNode(Parser.memName(alias), TypeMem.make(alias,Type.BOTTOM),loop.ctrl(),loopmem._mem(alias,null),null).peephole() ); - alias(alias,old); - } - // Memory projections are made lazily; expand as needed - return st==null ? old : alias(alias,st); // Not lazy, so this is the answer - } - - - void _merge( MemMergeNode that, RegionNode r) { - int len = Math.max(nIns(),that.nIns()); - for( int i = 1; i < len; i++) - if( alias(i) != that.alias(i) ) { // No need for redundant Phis - // If we are in lazy phi mode we need to a lookup - // by alias as it will trigger a phi creation - Node lhs = this._mem(i,null); - Node rhs = that._mem(i,null); - alias(i, new PhiNode(Parser.memName(i), lhs._type.meet(rhs._type).glb(true), r, lhs, rhs).peephole()); - } - } - - // Fill in the backedge of any inserted Phis - void _endLoopMem( ScopeNode scope, MemMergeNode back, MemMergeNode exit ) { - Node exit_def = exit.alias(1); - for( int i=1; i<nIns(); i++ ) { - if( in(i) instanceof PhiNode phi && phi.region()==scope.ctrl() ) { - assert phi.in(2)==null; - phi.setDef(2,back.alias(i)==scope ? phi : back.alias(i)); // Fill backedge - } - if( exit_def == scope ) // Replace a lazy-phi on the exit path also - exit.alias(i,in(i)); - } - } - - // Now one-time do a useless-phi removal - void _useless( ) { - for( int i=2; i<nIns(); i++ ) { - if( in(i) instanceof PhiNode phi ) { - // Do an eager useless-phi removal - Node in = phi.peephole(); - CodeGen.CODE.addAll(phi._outputs); - phi.moveDepsToWorklist(); - if( in != phi ) { - if( !phi.iskeep() ) // Keeping phi around for parser elsewhere - phi.subsume(in); - setDef(i,in); // Set the update back into Scope - } - } - } - } - - @Override public boolean eq( Node n ) { - return this==n || !_inProgress; - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java deleted file mode 100644 index ead82322..00000000 --- a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ /dev/null @@ -1,109 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMemPtr; -import com.seaofnodes.simple.util.Utils; -import java.lang.StringBuilder; -import java.util.BitSet; - -/** - * Convenience common base for Load and Store. - * - * 0 - Control - * 1 - Memory - * 2 - Base of object, OOP - * 3 - Offset, integer types - * 4 - Value, for Stores only - */ -public abstract class MemOpNode extends Node { - - // The equivalence alias class - public final int _alias; - - // True if load-like, false if store-like. - // - // Stores produce memory (maybe as part of a tuple with other things), - // loads do not. - // - // Loads might pick up anti-dependences on prior Stores, and never cause an - // anti-dependence themselves. - // - // Stores must maximally sink to the least dominator use. Loads can be - // opportunistically hoisted. - public final boolean _isLoad; - - // Declared type; not final because it might be a forward-reference - // which will be lazily improved when the reference is declared. - public Type _declaredType; - - // A debug name, no semantic meaning - public final String _name; - // Source location for late reported errors - public final Parser.Lexer _loc; - - public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type glb, Node mem, Node ptr, Node off) { - super(null, mem, ptr, off); - _name = name; - _alias = alias; - _declaredType = glb; - _loc = loc; - _isLoad = isLoad; - } - public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type glb, Node mem, Node ptr, Node off, Node value) { - this(loc,name, alias, isLoad, glb, mem, ptr, off); - addDef(value); - } - public MemOpNode( Node mach, MemOpNode mop ) { - super(mach); - _name = mop==null ? null : mop._name; - _alias = mop==null ? 0 : mop._alias; - _loc = mop==null ? null : mop._loc; - _isLoad= mop==null ? true : mop._isLoad; - _declaredType = mop==null ? Type.BOTTOM : mop._declaredType; - if( mop==null ) - throw Utils.TODO("Load or not"); - } - - // Used by M2 when translating graph to Simple - public MemOpNode( boolean isLoad ) { - super((Node)null); - _name = null; - _alias = 0; - _loc = null; - _isLoad = isLoad; - _declaredType = Type.BOTTOM; - } - - static String mlabel(String name) { return "[]".equals(name) ? "ary" : ("#".equals(name) ? "len" : name); } - String mlabel() { return mlabel(_name); } - - public Node mem() { return in(1); } - public Node ptr() { return in(2); } - public Node off() { return in(3); } - - @Override public StringBuilder _print1( StringBuilder sb, BitSet visited ) { return _printMach(sb,visited); } - public StringBuilder _printMach( StringBuilder sb, BitSet visited ) { throw Utils.TODO(); } - public int log_size() { return _declaredType.log_size(); } - - @Override - public boolean eq(Node n) { - MemOpNode mem = (MemOpNode)n; // Invariant - return _alias==mem._alias; // When comparing types error to use "equals"; always use "==" - } - - @Override - int hash() { return _alias; } - - @Override - public Parser.ParseException err() { - Type ptr = ptr()._type; - // Already an error, but better error messages come from elsewhere - if( ptr == Type.BOTTOM ) return null; - if( ptr.isHigh() ) return null; // Assume it will fall to not-null - // Better be a not-nil TMP - if( ptr instanceof TypeMemPtr tmp && tmp.notNull() ) - return null; - return Parser.error( "Might be null accessing '" + _name + "'",_loc); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/MinusFNode.java b/src/main/java/com/seaofnodes/simple/node/MinusFNode.java deleted file mode 100644 index 99fc3144..00000000 --- a/src/main/java/com/seaofnodes/simple/node/MinusFNode.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; - -import java.util.BitSet; - -public class MinusFNode extends Node { - public MinusFNode(Node in) { super(null, in); } - - @Override public String label() { return "MinusF"; } - - @Override public String glabel() { return "-"; } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - in(1)._print0(sb.append("(-"), visited); - return sb.append(")"); - } - - @Override - public Type compute() { - if (in(1)._type instanceof TypeFloat i0) - return i0.isConstant() ? TypeFloat.constant(-i0.value()) : i0; - return TypeFloat.F64; - } - - @Override - public Node idealize() { - // -(-x) is x - if( in(1) instanceof MinusFNode minus ) - return minus.in(1); - - return null; - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/src/main/java/com/seaofnodes/simple/node/MinusNode.java deleted file mode 100644 index f26ce37e..00000000 --- a/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.util.Utils; -import java.util.BitSet; - -public class MinusNode extends Node { - public MinusNode(Node in) { super(null, in); } - - @Override public String label() { return "Minus"; } - - @Override public String glabel() { return "-"; } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - in(1)._print0(sb.append("(-"), visited); - return sb.append(")"); - } - - @Override - public Type compute() { - if( in(1)._type.isHigh() ) return TypeInteger.TOP; - if( in(1)._type instanceof TypeInteger i0 && - i0._min != Long.MIN_VALUE && i0._max != Long.MIN_VALUE ) - return TypeInteger.make(-i0._max,-i0._min); - return TypeInteger.BOT; - } - - @Override - public Node idealize() { - // -(-x) is x - if( in(1) instanceof MinusNode minus ) - return minus.in(1); - - return null; - } - @Override Node copyF() { return new MinusFNode(null); } -} diff --git a/src/main/java/com/seaofnodes/simple/node/MulFNode.java b/src/main/java/com/seaofnodes/simple/node/MulFNode.java deleted file mode 100644 index 6fbf4bbb..00000000 --- a/src/main/java/com/seaofnodes/simple/node/MulFNode.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; - -import java.util.BitSet; - -public class MulFNode extends Node { - public MulFNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override public String label() { return "MulF"; } - - @Override public String glabel() { return "*"; } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - in(1)._print0(sb.append("("), visited); - in(2)._print0(sb.append("*"), visited); - return sb.append(")"); - } - - @Override - public Type compute() { - if (in(1)._type instanceof TypeFloat i0 && - in(2)._type instanceof TypeFloat i1) { - if (i0.isConstant() && i1.isConstant()) - return TypeFloat.constant(i0.value()*i1.value()); - } - return in(1)._type.meet(in(2)._type); - } - - @Override - public Node idealize() { - Node lhs = in(1); - Node rhs = in(2); - Type t1 = lhs._type; - Type t2 = rhs._type; - - // Mul of 1. We do not check for (1*x) because this will already - // canonicalize to (x*1) - if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==1 ) - return lhs; - - // Move constants to RHS: con*arg becomes arg*con - if ( t1.isConstant() && !t2.isConstant() ) - return swap12(); - - return null; - } - @Override Node copy(Node lhs, Node rhs) { return new MulFNode(lhs,rhs); } -} diff --git a/src/main/java/com/seaofnodes/simple/node/MulNode.java b/src/main/java/com/seaofnodes/simple/node/MulNode.java deleted file mode 100644 index debc2c56..00000000 --- a/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import java.util.BitSet; - -public class MulNode extends ArithNode { - public MulNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override public String label() { return "Mul"; } - @Override public String op() { return "*"; } - - @Override long doOp( long x, long y ) { return x * y; } - @Override TypeInteger doOp(TypeInteger x, TypeInteger y) { - return TypeInteger.BOT; - } - - @Override - public Node idealize() { - Node lhs = in(1); - Node rhs = in(2); - Type t1 = lhs._type; - Type t2 = rhs._type; - - // Move constants to RHS: con*arg becomes arg*con - if ( t1.isConstant() && !t2.isConstant() ) - return swap12(); - - // Multiply by constant - if ( t2.isConstant() && t2 instanceof TypeInteger i ) { - // Mul of 1. We do not check for (1*x) because this will already - // canonicalize to (x*1) - long c = i.value(); - if( c==1 ) return lhs; - if( c==0 ) return Parser.ZERO; - // Mul by a power of 2, +/-1. Bit patterns more complex than this - // are unlikely to win on an X86 vs the normal "imul", and so - // become machine-specific. - if( (c & (c-1)) == 0 ) - return new ShlNode(null,lhs,con(Long.numberOfTrailingZeros(c))); - // 2**n + 1, e.g. 9 - long d = c-1; - if( (d & (d-1)) == 0 ) - return new AddNode(new ShlNode(null,lhs,con(Long.numberOfTrailingZeros(d))).peephole(),lhs); - // 2**n - 1, e.g. 31 - long e = c+1; - if( (e & (e-1)) == 0 ) - return new SubNode(new ShlNode(null,lhs,con(Long.numberOfTrailingZeros(e))).peephole(),lhs); - - } - - // Do we have ((x * (phi cons)) * con) ? - // Do we have ((x * (phi cons)) * (phi cons)) ? - // Push constant up through the phi: x * (phi con0*con0 con1*con1...) - Node phicon = AddNode.phiCon(this,true); - if( phicon!=null ) return phicon; - - return super.idealize(); - } - @Override Node copy(Node lhs, Node rhs) { return new MulNode(lhs,rhs); } - @Override Node copyF() { return new MulFNode(null,null); } -} diff --git a/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/src/main/java/com/seaofnodes/simple/node/MultiNode.java deleted file mode 100644 index b4d6d78e..00000000 --- a/src/main/java/com/seaofnodes/simple/node/MultiNode.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.util.Ary; - - -public interface MultiNode { - - abstract Ary<Node> outs(); - - // Find a projection by index - default ProjNode proj( int idx ) { - for( Node out : outs() ) - if( out instanceof ProjNode prj && prj._idx==idx ) - return prj; - return null; - } - - // Find a projection by index - default CProjNode cproj( int idx ) { - for( Node out : outs() ) - if( out instanceof CProjNode prj && prj._idx==idx ) - return prj; - return null; - } - - // Return not-null if this projection index is a ideal copy. - // Called by ProjNode ideal and used to collapse Multis. - default Node pcopy(int idx) { return null; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/NeverNode.java b/src/main/java/com/seaofnodes/simple/node/NeverNode.java deleted file mode 100644 index 516f218b..00000000 --- a/src/main/java/com/seaofnodes/simple/node/NeverNode.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeTuple; - -import java.util.BitSet; - -// "Never true" for infinite loop exits -public class NeverNode extends IfNode { - public NeverNode(NeverNode ctrl) { super(ctrl); } - public NeverNode(Node ctrl) { super(ctrl,null); } - - @Override public String label() { return "Never"; } - - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append("Never"); } - - @Override public Type compute() { return TypeTuple.IF_BOTH; } - - @Override public Node idealize() { return null; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/NewNode.java b/src/main/java/com/seaofnodes/simple/node/NewNode.java deleted file mode 100644 index 1785af4e..00000000 --- a/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ /dev/null @@ -1,131 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.SB; - -import java.util.BitSet; - -/** - * Allocation! Allocate a chunk of memory, and pre-zero it. - * The inputs include control and size, and ALL aliases being set. - * The output is large tuple, one for every alias plus the created pointer. - * New is expected to be followed by projections for every alias. - */ -public class NewNode extends Node implements MultiNode { - - public final TypeMemPtr _ptr; - public final int _len; - - public NewNode(TypeMemPtr ptr, Node... nodes) { - super(nodes); - assert !ptr.nullable(); - _ptr = ptr; - _len = ptr._obj._fields.length; - // Control in slot 0 - assert nodes[0]._type==Type.CONTROL || nodes[0]._type == Type.XCONTROL; - // Malloc-length in slot 1 - assert nodes[1]._type instanceof TypeInteger || nodes[1]._type==Type.NIL; - for( int i=0; i<_len; i++ ) - assert ptr._obj._fields[i]._one || nodes[2 + i]._type.isa( TypeMem.BOT ); - } - - public NewNode(NewNode nnn) { super(nnn); _ptr = nnn._ptr; _len = nnn._len; } - - public Node mem (int idx) { return in(idx+2); } - - @Override public String label() { return "new_"+glabel(); } - @Override public String glabel() { - return _ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._t.str() : _ptr._obj.str(); - } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - sb.append("new "); - return sb.append(_ptr._obj.str()); - } - - int findAlias(int alias) { - int idx = _ptr._obj.findAlias(alias); - assert idx!= -1; // Error, caller should be calling - return idx+2; // Skip ctrl, size - } - - // 0 - ctrl - // 1 - byte size - // 2-len+2 - aliases, one per field - // len+2 - 2*len+2 - initial values, one per field - public Node size() { return in(1); } - - @Override - public TypeTuple compute() { - Field[] fs = _ptr._obj._fields; - Type[] ts = new Type[fs.length+2]; - ts[0] = Type.CONTROL; - ts[1] = _ptr; - for( int i=0; i<fs.length; i++ ) { - if( _ptr._obj._fields[i]._one ) { - // Once-only fields use the declared type - ts[i+2] = _ptr._obj._fields[i]._t; - } else { - // Others take from the inputs - Type mt = in(i+2)._type; - TypeMem mem = mt==Type.TOP ? TypeMem.TOP : (TypeMem)mt; - Type tfld = mem._t.meet(mem._t.makeZero()); - Type tfld2 = tfld.join(fs[i]._t ); - ts[i+2] = TypeMem.make(fs[i]._alias,tfld2); - } - } - return TypeTuple.make(ts); - } - - @Override - public Node idealize() { - Node progress=null; - for( int i=1; i<nIns(); i++ ) { - if( in(i) instanceof CastNode cast ) - progress = setDef(i,cast.in(1)); - } - return progress == null ? null : this; - } - - @Override - public boolean eq(Node n) { return this == n; } - - @Override - int hash() { return _ptr.hashCode(); } - - // ------------ - // MachNode specifics, shared across all CPUs - public int _arg2Reg, _xslot; - private RegMask _arg3Mask; - private RegMask _retMask; - private RegMask _kills; - public void cacheRegs(CodeGen code) { - _arg2Reg = code._mach.callArgMask(TypeFunPtr.CALLOC,2,0).firstReg(); - _arg3Mask = code._mach.callArgMask(TypeFunPtr.CALLOC,3,0); - // Return mask depends on TFP (either GPR or FPR) - _retMask = code._mach.retMask(TypeFunPtr.CALLOC); - // Kill mask is all caller-saves, and any mirror stack slots for args - // in registers. - RegMaskRW kills = code._callerSave.copy(); - // Start of stack slots - int maxReg = code._mach.regs().length; - // Incoming function arg slots, all low numbered in the RA - int fslot = cfg0().fun()._maxArgSlot; - // Killed slots for this calls outgoing args - int xslot = code._mach.maxArgSlot(TypeFunPtr.CALLOC); - _xslot = (maxReg+fslot)+xslot; - for( int i=0; i<xslot; i++ ) - kills.set((maxReg+fslot)+i); - _kills = kills; - } - public String op() { return "alloc"; } - public RegMask regmap(int i) { return i==1 ? _arg3Mask : null; } - public RegMask outregmap() { return null; } - public RegMask outregmap(int idx) { return idx==1 ? _retMask : null; } - public RegMask killmap() { return _kills; } - public void asm(CodeGen code, SB sb) { - sb.p("#calloc, ").p(code.reg(size())); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/Node.java b/src/main/java/com/seaofnodes/simple/node/Node.java deleted file mode 100644 index 5e5be196..00000000 --- a/src/main/java/com/seaofnodes/simple/node/Node.java +++ /dev/null @@ -1,738 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.Utils; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.print.IRPrinter; -import com.seaofnodes.simple.print.JSViewer; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; -import com.seaofnodes.simple.type.TypeInteger; -import java.util.*; -import java.util.function.Function; -import static com.seaofnodes.simple.codegen.CodeGen.CODE; - -/** - * All Nodes in the Sea of Nodes IR inherit from the Node class. - * The Node class provides common functionality used by all subtypes. - * Subtypes of Node specialize by overriding methods. - */ -public abstract class Node implements Cloneable { - - /** - * Each node has a unique dense Node ID within a compilation context - * The ID is useful for debugging, for using as an offset in a bitvector, - * as well as for computing equality of nodes (to be implemented later). - */ - public int _nid; - - /** - * Inputs to the node. These are use-def references to Nodes. - * <p> - * Generally fixed length, ordered, nulls allowed, no unused trailing space. - * Ordering is required because e.g. "a/b" is different from "b/a". - * The first input (offset 0) is often a {@link CFGNode} node. - */ - public Ary<Node> _inputs; - - /** - * Outputs reference Nodes that are not null and have this Node as an - * input. These nodes are users of this node, thus these are def-use - * references to Nodes. - * <p> - * Outputs directly match inputs, making a directed graph that can be - * walked in either direction. These outputs are typically used for - * efficient optimizations but otherwise have no semantics meaning. - */ - public Ary<Node> _outputs; - - - /** - * Current computed type for this Node. This value changes as the graph - * changes and more knowledge is gained about the program. - */ - public Type _type; - - - Node(Node... inputs) { - _nid = CODE.getUID(); // allocate unique dense ID - _inputs = new Ary<>(Node.class); - Collections.addAll(_inputs,inputs); - _outputs = new Ary<>(Node.class); - for( Node n : _inputs ) - if( n != null ) - n.addUse( this ); - } - - // Make a Node using the existing arrays of nodes. - // Used by any pass rewriting all Node classes but not the edges. - Node( Node n ) { - assert CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Select.ordinal(); - _nid = CODE.getUID(); // allocate unique dense ID - _inputs = new Ary<>(n==null ? new Node[0] : n._inputs.asAry()); - _outputs = new Ary<>(Node.class); - _type = n==null ? Type.BOTTOM : n._type; - _deps = null; - _hash = 0; - } - - // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" - public abstract String label(); - - // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99" - public String uniqueName() { - // Get rid of $ as graphviz doesn't like it - String label = label().replaceAll("\\$", ""); - return label + _nid; - } - - // Graphical label, e.g. "+" or "Region" or "==" - public String glabel() { return label(); } - - // Extra fun stuff, for assembly printing. Jump labels, parser locations, - // variable types, etc. - public String comment() { return null; } - - // ------------------------------------------------------------------------ - - // Debugger Printing. - - // {@code toString} is what you get in the debugger. It has to print 1 - // line (because this is what a debugger typically displays by default) and - // has to be robust with broken graph/nodes. - @Override - public final String toString() { return print(); } - - // This is a *deep* print. We print with a mutually recursive tik-tok - // style; the common _print0 calls the per-Node _print1, which calls back - // to _print0; - public final String print() { - return _print0(new StringBuilder(), new BitSet()).toString(); - } - - // This is the common print: check for repeats, check for DEAD and print - // "DEAD" else call the per-Node print1. - public final StringBuilder _print0(StringBuilder sb, BitSet visited) { - if (visited.get(_nid) && !(this instanceof ConstantNode) ) - return sb.append(label()); - visited.set(_nid); - return isDead() - ? sb.append(uniqueName()).append(":DEAD") - : _print1(sb, visited); - } - // Every Node implements this; a partial-line recursive print - abstract public StringBuilder _print1(StringBuilder sb, BitSet visited); - - public String p(int depth) { return IRPrinter.prettyPrint(this,depth); } - - public boolean isConst() { return false; } - - // ------------------------------------------------------------------------ - // Graph Node & Edge manipulation - - /** - * Gets the ith input node - * @param i Offset of the input node - * @return Input node or null - */ - public Node in(int i) { return _inputs.get(i); } - public Node out(int i) { return _outputs.get(i); } - public final Ary<Node> outs() { return _outputs; } - - public int nIns() { return _inputs.size(); } - - public int nOuts() { return _outputs.size(); } - - public boolean isUnused() { return nOuts() == 0; } - - public CFGNode cfg0() { return (CFGNode)in(0); } - - /** - * Change a <em>def</em> into a Node. Keeps the edges correct, by removing - * the corresponding <em>use->def</em> edge. This may make the original - * <em>def</em> go dead. This function is co-recursive with {@link #kill}. - * <p> - - * This method is the normal path for altering a Node, because it does the - * proper default edge maintenance. It also <em>immediately</em> kills - * Nodes that lose their last use; at times care must be taken to avoid - * killing Nodes that are being used without having an output Node. This - * definitely happens in the middle of recursive {@link #peephole} calls. - * - * @param idx which def to set - * @param new_def the new definition - * @return new_def for flow coding - */ - public <N extends Node> N setDef(int idx, N new_def ) { - unlock(); - Node old_def = in(idx); - if( old_def == new_def ) return new_def; // No change - // If new def is not null, add the corresponding def->use edge - // This needs to happen before removing the old node's def->use edge as - // the new_def might get killed if the old node kills it recursively. - if( new_def != null ) - new_def.addUse(this); - // Set the new_def over the old (killed) edge - _inputs.set(idx,new_def); - if( old_def != null ) { // If the old def exists, remove a def->use edge - if( old_def.delUse(this) ) // If we removed the last use, the old def is now dead - old_def.kill(); // Kill old def - else CODE.add(old_def); // Else old lost a use, so onto worklist - } - moveDepsToWorklist(); - // Return new_def for easy flow-coding - return new_def; - } - public <N extends Node> N setDefX(int idx, N new_def ) { - while( nIns() <= idx ) addDef(null); - return setDef(idx,new_def); - } - - // Remove the numbered input, compressing the inputs in-place. This - // shuffles the order deterministically - which is suitable for Region and - // Phi, but not for every Node. If the def goes dead, it is recursively - // killed, which may include 'this' Node. - public Node delDef(int idx) { - unlock(); - Node old_def = in(idx); - _inputs.del(idx); - if( old_def.delUse(this) ) // If we removed the last use, the old def is now dead - old_def.kill(); // Kill old def - old_def.moveDepsToWorklist(); - return this; - } - - // Insert the numbered input, sliding other inputs to the right - Node insertDef(int idx, Node new_def) { - _inputs.add(idx,null); - return setDef(idx,new_def); - } - - - /** - * Add a new def to an existing Node. Keep the edges correct by - * adding the corresponding <em>def->use</em> edge. - * - * @param new_def the new definition, appended to the end of existing definitions - * @return new_def for flow coding - */ - public Node addDef(Node new_def) { - unlock(); - // Add use->def edge - _inputs.add(new_def); - // If new def is not null, add the corresponding def->use edge - if( new_def != null ) - new_def.addUse(this); - return new_def; - } - - // Breaks the edge invariants, used temporarily - @SuppressWarnings("unchecked") - protected <N extends Node> N addUse(Node n) { _outputs.add(n); return (N)this; } - - // Remove node 'use' from 'def's (i.e. our) output list, by compressing the list in-place. - // Return true if the output list is empty afterward. - // Error is 'use' does not exist; ok for 'use' to be null. - protected boolean delUse( Node use ) { - _outputs.del(_outputs.find(use)); - return _outputs.isEmpty(); - } - - // Shortcut for "popping" until n nodes. A "pop" is basically a - // setDef(last,null) followed by lowering the nIns() count. - public void popUntil(int n) { - unlock(); - while( nIns() > n ) { - Node old_def = _inputs.pop(); - if( old_def != null && // If it exists and - old_def.delUse(this) ) // If we removed the last use, the old def is now dead - old_def.kill(); // Kill old def - } - } - - /** - * Kill a Node with no <em>uses</em>, by setting all of its <em>defs</em> - * to null. This may recursively kill more Nodes, and is basically dead - * code elimination. - */ - public void kill( ) { - assert isUnused(); // Has no uses, so it is dead - unlock(); - moveDepsToWorklist(); - _type=null; // Flag as dead - while( nIns()>0 ) { // Set all inputs to null, recursively killing unused Nodes - Node old_def = _inputs.removeLast(); - // Revisit neighbor because removed use - if( old_def != null && CODE.add(old_def).delUse(this) ) - old_def.kill(); // If we removed the last use, the old def is now dead - } - assert isDead(); // Really dead now - } - - // Preserve CFG use-ordering when killing - public void killOrdered() { - CFGNode cfg = cfg0(); - cfg._outputs.remove(cfg._outputs.find(this)); - _inputs.set(0,null); - kill(); - } - - - // Mostly used for asserts and printing. - public boolean isDead() { return isUnused() && nIns()==0 && _type==null; } - - // Shortcuts to stop DCE mid-parse - // Add bogus null use to keep node alive - public <N extends Node> N keep() { return addUse(null); } - // Remove bogus null. - @SuppressWarnings("unchecked") - public <N extends Node> N unkeep() { - delUse(null); - return (N)this; - } - // Test "keep" status - public boolean iskeep() { return _outputs.find(null) != -1; } - public void unkill() { - if( unkeep().isUnused() ) - kill(); - } - public void isKill() { - if( isUnused() ) - kill(); - } - - - // Replace self with nnn in the graph, making 'this' go dead - public void subsume( Node nnn ) { - assert nnn!=this; - while( nOuts() > 0 ) { - Node n = _outputs.removeLast(); - n.unlock(); - int idx = n._inputs.find(this); - n._inputs.set(idx,nnn); - nnn.addUse(n); - CODE.add(n); - CODE.addAll(n._outputs); - } - kill(); - } - - // insert `this` immediately after `def` in the same basic block. - public void insertAfter( Node def ) { - CFGNode cfg = def.cfg0(); - int i = cfg._outputs.find(def)+1; - if( cfg instanceof CallEndNode ) { - cfg = cfg.uctrl(); i=0; - } else if( def.in(0) instanceof MultiNode ) { - assert i==0; - i = cfg._outputs.find(def.in(0))+1; - } - - while( cfg.out(i) instanceof PhiNode || cfg.out(i) instanceof CalleeSaveNode ) i++; - cfg._outputs.insert(this,i); - _inputs.set(0,cfg); - } - - // Insert this in front of use.in(uidx) with this, and insert this - // immediately before use in the basic block. - public void insertBefore( Node use, int uidx ) { - CFGNode cfg = use.cfg0(); - int i; - if( use instanceof PhiNode phi ) { - cfg = phi.region().cfg(uidx); - if( cfg instanceof CProjNode && cfg.in(0) instanceof NeverNode nvr ) - cfg = nvr.cfg0(); - i = cfg.nOuts()-1; - } else { - i = cfg._outputs.find(use); - } - cfg._outputs.insert(this,i); - _inputs.set(0,cfg); - if( _inputs._len > 1 && this instanceof SplitNode ) - setDefOrdered(1,use.in(uidx)); - use.setDefOrdered(uidx,this); - } - - public void setDefOrdered( int idx, Node def) { - // If old is dying, remove from CFG ordered - Node old = in(idx); - if( old!=null && old.nOuts()==1 ) { - CFGNode cfg = old.cfg0(); - if( cfg!=null ) { - cfg._outputs.remove(cfg._outputs.find(old)); - old._inputs.set(0,null); - } - } - setDef(idx,def); - } - - public void removeSplit() { - CFGNode cfg = cfg0(); - cfg._outputs.remove(cfg._outputs.find(this)); - _inputs.set(0,null); - if( _inputs._len > 1 ) subsume(in(1)); - } - - // ------------------------------------------------------------------------ - // Graph-based optimizations - - /** - * Try to peephole at this node and return a better replacement Node. - * Always returns some not-null Node (often this). - */ - public final Node peephole( ) { - if( _type==null ) // Brand-new node, never peeped before - JSViewer.show(); - Node n = peepholeOpt(); - if( n!=null ) // Made progress? - JSViewer.show(); // Show again - return n==null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n); // Cannot return null for no-progress - } - - /** - * Try to peephole at this node and return a better replacement Node if - * possible. We compute a {@link Type} and then check and replace: - * <ul> - * <li>if the Type {@link Type#isConstant}, we replace with a {@link ConstantNode}</li> - * <li>in a future chapter we will look for a - * <a href="https://en.wikipedia.org/wiki/Common_subexpression_elimination">Common Subexpression</a> - * to eliminate.</li> - * <li>we ask the Node for a better replacement. The "better replacement" - * is things like {@code (1+2)} becomes {@code 3} and {@code (1+(x+2))} becomes - * {@code (x+(1+2))}. By canonicalizing expressions we fold common addressing - * math constants, remove algebraic identities and generally simplify the - * code. </li> - * - * Unlike peephole above, this explicitly returns null for no-change, or not-null - * for a better replacement (which can be this). - * </ul> - */ - public final Node peepholeOpt( ) { - CODE.iterCnt(); - // Compute initial or improved Type - Type old = setType(compute()); - - // Replace constant computations from non-constants with a constant - // node. If peeps are disabled, still allow high Phis to collapse; - // they typically come from dead Regions, and we want the Region to - // collapse, which requires the Phis to die first. - if( !isConst() && _type.isHighOrConst() ) - return ConstantNode.make(_type).peephole(); - - // Global Value Numbering - if( _hash==0 ) { - Node n = CODE._gvn.get(this); // Will set _hash as a side effect - if( n==null ) - CODE._gvn.put(this,this); // Put in table now - else { - // Because of random worklist ordering, the two equal nodes - // might have different types. Because of monotonicity, both - // types are valid. To preserve monotonicity, the resulting - // shared Node has to have the best of both types. - n.setType(n._type.join(_type)); - _hash = 0; // Clear, since it never went in the table - return deadCodeElim(n);// Return previous; does Common Subexpression Elimination - } - } - - // Ask each node for a better replacement - Node n = idealize(); - if( n != null ) // Something changed - return n; // Report progress - - if( old!=_type ) return this; // Report progress; - CODE.iterNop(); - return null; // No progress - } - - // m is the new Node, self is the old. - // Return 'm', which may have zero uses but is alive nonetheless. - // If self has zero uses (and is not 'm'), {@link #kill} self. - private Node deadCodeElim(Node m) { - // If self is going dead and not being returned here (Nodes returned - // from peephole commonly have no uses (yet)), then kill self. - if( m != this && isUnused() && !isDead() ) { - // Killing self - and since self recursively kills self's inputs we - // might end up killing 'm', which we are returning as a live Node. - // So we add a bogus extra null output edge to stop kill(). - m.keep(); // Keep m alive - kill(); // Kill self because replacing with 'm' - m.unkeep(); // Okay to peephole m - } - return m; - } - - /** - * This function needs to be - * <a href="https://en.wikipedia.org/wiki/Monotonic_function">Monotonic</a> - * as it is part of a Monotone Analysis Framework. - * <a href="https://www.cse.psu.edu/~gxt29/teaching/cse597s21/slides/08monotoneFramework.pdf">See for example this set of slides</a>. - * <p> - * For Chapter 2, all our Types are really integer constants, and so all - * the needed properties are trivially true, and we can ignore the high - * theory. Much later on, this will become important and allow us to do - * many fancy complex optimizations trivially... because theory. - * <p> - * {@link #compute()} needs to be stand-alone, and cannot recursively call compute - * on its inputs, because programs are cyclic (have loops!) and this will just - * infinitely recurse until stack overflow. Instead, compute typically - * computes a new type from the {@link #_type} field of its inputs. - */ - public abstract Type compute(); - - // Set the type. Assert monotonic progress. - // If changing, add users to worklist. - public Type setType(Type type) { - Type old = _type; - assert old==null || type.isa(old); // Since _type not set, can just re-run this in assert in the debugger - if( old == type ) return old; - _type = type; // Set _type late for easier assert debugging - CODE.addAll(_outputs); - moveDepsToWorklist(); - return old; - } - - @SuppressWarnings("unchecked") - public <N extends Node> N init() { _type = compute(); return (N)this; } - - /** - * This function rewrites the current Node into a more "idealized" form. - * This is the bulk of our peephole rewrite rules, and we use this to - * e.g. turn arbitrary collections of adds and multiplies with mixed - * constants into a normal form that's easy for hardware to implement. - * Example: An array addressing expression: - * <pre> ary[idx+1]</pre> - * might turn into Sea-of-Nodes IR: - * <pre> (ary+12)+((idx+1) * 4)</pre> - * This expression can then be idealized into: - * <pre> ary + ((idx*4) + (12 + (1*4)))</pre> - * And more folding: - * <pre> ary + ((idx<<2) + 16)</pre> - * And during code-gen: - * <pre> MOV4 Rary,[Ridx<<2 +16] // or some such hardware-specific notation </pre> - * <p> - * {@link #idealize} has a very specific calling convention: - * <ul> - * <li>If NO change is made, return {@code null} - * <li>If ANY change is made, return not-null; this can be {@code this} - * <li>The returned Node does NOT call {@link #peephole} on itself; the {@link #peephole} call will recursively peephole it. - * <li>Any NEW nodes that are not directly returned DO call {@link #peephole}. - * </ul> - * <p> - * Examples: - * <table border="3"> - * <tr><th> before </th><th> after </th><th>return </th><th>comment </th></tr> - * <tr><td>{@code (x+5) }</td><td>{@code (x+5) }</td><td>{@code null }</td><td>No change</td></tr> - * <tr><td>{@code (5+x) }</td><td>{@code (x+5) }</td><td>{@code this }</td><td>Swapped arguments</td></tr> - * <tr><td>{@code ((x+1)+2)}</td><td>{@code (x+(1+2))}</td><td>{@code (x+_) }</td><td>Returns 2 new Nodes</td></tr> - * </table> - * - * The last entry deserves more discussion. The new Node {@code (1+2)} - * created in {@link #idealize} calls {@link #peephole} (which then folds - * into a constant). The other new Node {@code (x+3)} does not call - * peephole, because it is returned and peephole itself will recursively - * call peephole. - * <p> - * Since idealize calls peephole and peephole calls idealize, you must be - * careful that all idealizations are <em>monotonic</em>: all transforms - * remove some feature, so that the set of available transforms always - * shrinks. If you don't, you risk an infinite peephole loop! - * - * @return Either a new or changed node, or null for no changes. - */ - public abstract Node idealize(); - - - // Some of the peephole rules get complex, and search further afield than - // just the nearest neighbor. These peepholes can fail the pattern match - // on a node some distance away, and if that node ever changes we should - // retry the peephole. Track a set of Nodes dependent on `this`, and - // revisit them if `this` changes. - Ary<Node> _deps; - - /** - * Add a node to the list of dependencies. Only add it if its not an input - * or output of this node, that is, it is at least one step away. The node - * being added must benefit from this node being peepholed. - */ - <N extends Node> N addDep( N dep ) { - // Running peepholes during the big assert cannot have side effects - // like adding dependencies. - if( CODE._midAssert ) return dep; - if( dep._deps==null ) dep._deps = new Ary<>(Node.class); - if( dep._deps .find(this) != -1 ) return dep; // Already on list - if( dep._inputs .find(this) != -1 ) return dep; // No need for deps on immediate neighbors - if( dep._outputs.find(this) != -1 ) return dep; - dep._deps.add(this); - return dep; - } - - // Move the dependents onto a worklist, and clear for future dependents. - public void moveDepsToWorklist( ) { - if( _deps==null ) return; - CODE.addAll(_deps); - _deps.clear(); - } - - - // Two nodes are equal if they have the same inputs and the same "opcode" - // which means the same Java class, plus same internal parts. - @Override public final boolean equals( Object o ) { - if( o==this ) return true; - if( o.getClass() != getClass() ) return false; - Node n = (Node)o; - int len = _inputs.size(); - if( len != n._inputs.size() ) return false; - for( int i=0; i<len; i++ ) - if( in(i) != n.in(i) ) - return false; - return eq(n); - } - // Subclasses add extra checks (such as ConstantNodes have same constant), - // and can assume "this!=n" and has the same Java class. - public boolean eq( Node n ) { return true; } - - - // Cached hash. If zero, then not computed AND this Node is NOT in the GVN - // table - and can have its edges hacked (which will change his hash - // anyway). If Non-Zero then this Node is IN the GVN table, or is being - // probed to see if it can be inserted. His edges are "locked", because - // hacking his edges will change his hash. - int _hash; - - // If the _hash is set, then the Node is in the GVN table; remove it. - void unlock() { - if( _hash==0 ) return; - Node old = CODE._gvn.remove(this); // Pull from table - assert old==this; - _hash=0; // Out of table now - } - - - // Hash of opcode and inputs - @Override public final int hashCode() { - if( _hash != 0 ) return _hash; - int hash = hash(); - for( Node n : _inputs ) - if( n != null ) - hash = hash ^ (hash<<17) ^ (hash>>13) ^ n._nid; - if( hash==0 ) hash = 0xDEADBEEF; // Bad hash, so use some junky thing - return (_hash=hash); - } - // Subclasses add extra hash info (such as ConstantNodes constant) - int hash() { return 0; } - - // ------------------------------------------------------------------------ - // - - /** Is this Node Memory related */ - public boolean isMem() { return false; } - - /** Pinned in the schedule; these are data nodes whose input#0 is not allowed to change */ - public boolean isPinned() { return false; } - - // Semantic change to the graph (so NOT a peephole), used by the Parser. - // If any input is a float, flip to a float-flavored opcode and widen any - // non-float input. - public final Node widen() { - if( !hasFloatInput() ) return this; - Node flt = copyF(); - if( flt==null ) return this; - for( int i=1; i<nIns(); i++ ) - flt.setDef(i, in(i)._type instanceof TypeFloat ? in(i) : new ToFloatNode(in(i)).peephole()); - kill(); - return flt; - } - private boolean hasFloatInput() { - for( int i=1; i<nIns(); i++ ) - if( in(i)._type instanceof TypeFloat ) - return true; - return false; - } - Node copyF() { return null; } - - - // ------------------------------------------------------------------------ - // Peephole utilities - - // Swap inputs without letting either input go dead during the swap. - public Node swap12() { - unlock(); // Hash is order dependent - _inputs.swap(1,2); - return this; - } - - /** - * Does this node contain all constants? Ignores in(0), as is usually - * control. In an input is not a constant, we add dep as a dependency to - * it because dep can make progress if the input becomes a constant later. - * It is sufficient for one of the non-const inputs to have the dependency, - * so we don't bother checking the rest. - */ - boolean allCons(Node dep) { - for( int i=1; i<nIns(); i++ ) - if( !(in(i)._type.isConstant()) ) { - dep.addDep(in(i)); // If in(i) becomes a constant later, will trigger some peephole - return false; - } - return true; - } - - - // Make a shallow copy (same class) of this Node, with given inputs and - // empty outputs and a new Node ID. The original inputs are ignored. - // Does not need to be implemented in isCFG() nodes. - Node copy(Node lhs, Node rhs) { throw Utils.TODO("Binary ops need to implement copy"); } - - public Node copy() { - Node n; - try { n = (Node)clone(); } - catch( Exception e ) { throw new RuntimeException(e); } - n._nid = CODE.getUID(); // allocate unique dense ID - n._inputs = new Ary<>(Node.class); - n._outputs = new Ary<>(Node.class); - n._deps = null; - n._hash = 0; - return n; - } - - // Report any post-optimize errors - public Parser.ParseException err() { return null; } - - // Common integer constants - public static ConstantNode con(long x) { return (ConstantNode)(new ConstantNode(TypeInteger.constant(x)).peephole()); } - - // Utility to walk the entire graph applying a function; return the first - // not-null result. - final public <E> E walk( Function<Node,E> pred ) { - assert CODE._visit.isEmpty(); - E rez = _walk(pred); - CODE._visit.clear(); - return rez; - } - - private <E> E _walk( Function<Node,E> pred ) { - if( CODE._visit.get(_nid) ) return null; // Been there, done that - CODE._visit.set(_nid); - E x = pred.apply(this); - if( x != null ) return x; - for( Node def : _inputs ) if( def != null && (x = def._walk(pred)) != null ) return x; - for( Node use : _outputs ) if( use != null && (x = use._walk(pred)) != null ) return x; - return null; - } - - /** - * Debugging utility to find a Node by index - */ - public Node find(int nid) { return _find(nid, new BitSet()); } - private Node _find(int nid, BitSet bs) { - if( bs.get(_nid) ) return null; // Been there, done that - bs.set(_nid); - if( _nid==nid ) return this; - Node x; - for( Node def : _inputs ) if( def != null && (x = def._find(nid,bs)) != null ) return x; - for( Node use : _outputs ) if( use != null && (x = use._find(nid,bs)) != null ) return x; - return null; - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/NotNode.java b/src/main/java/com/seaofnodes/simple/node/NotNode.java deleted file mode 100644 index 4b80e61c..00000000 --- a/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Utils; -import java.util.BitSet; - -public class NotNode extends Node { - public NotNode(Node in) { super(null, in); } - - @Override public String label() { return "Not"; } - - @Override public String glabel() { return "!"; } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - in(1)._print0(sb.append("(!"), visited); - return sb.append(")"); - } - - @Override - public TypeInteger compute() { - Type t0 = in(1)._type; - if( t0.isHigh() ) return (TypeInteger)TypeInteger.BOOL.dual(); - if( t0 == Type.NIL || t0 == TypeInteger.ZERO ) return TypeInteger.TRUE; - if( t0 instanceof TypeNil tn && tn.notNull() ) return TypeInteger.FALSE; - if( t0 instanceof TypeInteger i && (i._min > 0 || i._max < 0) ) return TypeInteger.FALSE; - return TypeInteger.BOOL; - } - - @Override - public Node idealize() { return null; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/OrNode.java b/src/main/java/com/seaofnodes/simple/node/OrNode.java deleted file mode 100644 index ac0d8454..00000000 --- a/src/main/java/com/seaofnodes/simple/node/OrNode.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; - -public class OrNode extends ArithNode { - public OrNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); } - - @Override public String label() { return "Or"; } - @Override public String op() { return "|"; } - @Override public String glabel() { return "|"; } - - - @Override long doOp( long x, long y ) { return x | y; } - @Override TypeInteger doOp(TypeInteger x, TypeInteger y) { - return TypeInteger.BOT; - } - - @Override - public Node idealize() { - Node lhs = in(1); - Node rhs = in(2); - Type t1 = lhs._type; - Type t2 = rhs._type; - - // Or of 0. We do not check for (0|x) because this will already - // canonicalize to (x|0) - if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 ) - return lhs; - - // Move constants to RHS: con*arg becomes arg*con - if ( t1.isConstant() && !t2.isConstant() ) - return swap12(); - - // Do we have ((x | (phi cons)) | con) ? - // Do we have ((x | (phi cons)) | (phi cons)) ? - // Push constant up through the phi: x | (phi con0|con0 con1|con1...) - Node phicon = AddNode.phiCon(this,true); - if( phicon!=null ) return phicon; - - return super.idealize(); - } - @Override Node copy(Node lhs, Node rhs) { return new OrNode(_loc,lhs,rhs); } -} diff --git a/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/src/main/java/com/seaofnodes/simple/node/ParmNode.java deleted file mode 100644 index a14a4fb9..00000000 --- a/src/main/java/com/seaofnodes/simple/node/ParmNode.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.Type; -import java.util.BitSet; - -public class ParmNode extends PhiNode { - - // Argument indices are mapped one-to-one on CallNode inputs - public final int _idx; // Argument index - - public ParmNode(String label, int idx, Type declaredType, Node... inputs) { - super(label,declaredType,inputs); - _idx = idx; - } - public ParmNode(ParmNode parm) { super(parm, parm._label, parm._minType ); _idx = parm._idx; } - - @Override public String label() { return MemOpNode.mlabel(_label); } - - @Override public String glabel() { return _label; } - - public FunNode fun() { return (FunNode)in(0); } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - if( "main".equals(fun()._name) && _label.equals("arg") ) - return sb.append("arg"); - sb.append("Parm_").append(_label).append("("); - for( Node in : _inputs ) { - if (in == null) sb.append("____"); - else in._print0(sb, visited); - sb.append(","); - } - sb.setLength(sb.length()-1); - sb.append(")"); - return sb; - } - - - // Always in-progress until we run out of unknown callers - @Override public boolean inProgress() { return in(0) instanceof FunNode fun && fun.inProgress(); } - - @Override public boolean eq( Node n ) { - return ((ParmNode)n)._idx==_idx && super.eq(n); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/src/main/java/com/seaofnodes/simple/node/PhiNode.java deleted file mode 100644 index fcbacbc9..00000000 --- a/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ /dev/null @@ -1,271 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Utils; -import java.util.BitSet; - -public class PhiNode extends Node { - - public final String _label; - - // The Phi type we compute must stay within the domain of the Phi. Example - // Int stays Int, Ptr stays Ptr, Control stays Control, Mem stays Mem. - Type _minType; - - public PhiNode(String label, Type minType, Node... inputs) { - super(inputs); - _label = label; - assert minType!=null; - _minType = minType; - } - // Used by ParmNode - public PhiNode(PhiNode phi, String label, Type minType) { super(phi); _label = label; _type = _minType = minType; } - // Used by instruction Selection - public PhiNode(PhiNode phi) { this(phi,phi._label,phi._minType ); } - // Used by the infinite-loop exit breaker - public PhiNode(RegionNode r, Node sample) { - super(new Node[]{r}); - _label = ""; - _minType = sample._type; - while( nIns() < r.nIns() ) - addDef(sample); - } - - @Override public String label() { return "Phi_"+MemOpNode.mlabel(_label); } - - @Override public String glabel() { return "φ_"+_label; } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - if( !(region() instanceof RegionNode r) || r.inProgress() ) - sb.append("Z"); - sb.append("Phi("); - for( Node in : _inputs ) { - if (in == null) sb.append("____"); - else in._print0(sb, visited); - sb.append(","); - } - sb.setLength(sb.length()-1); - sb.append(")"); - return sb; - } - - public CFGNode region() { return (CFGNode)in(0); } - @Override public boolean isMem() { return _minType instanceof TypeMem; } - @Override public boolean isPinned() { return true; } - - @Override - public Type compute() { - if( !(region() instanceof RegionNode r) ) - return region()._type==Type.XCONTROL ? (_type instanceof TypeMem ? TypeMem.TOP : Type.TOP) : _type; - // During parsing Phis have to be computed type pessimistically. - if( r.inProgress() ) return _minType; - // Set type to local top of the starting type - //Type t = _minType.dual(); - Type t = Type.TOP; - for (int i = 1; i < nIns(); i++) - // If the region's control input is live, add this as a dependency - // to the control because we can be peeped should it become dead. - if( addDep(r.in(i))._type != Type.XCONTROL ) { - if( in(i)._type==Type.BOTTOM ) - return Type.BOTTOM; - t = t.meet(in(i)._type); - } - t = t.join( _minType ); - return t; - } - - @Override - public Node idealize() { - if( !(region() instanceof RegionNode r ) ) - return in(1); // Input has collapse to e.g. starting control. - // Can upgrade minType even while in-progress - if( _minType instanceof TypeMemPtr tmp && _minType.isFRef() ) { - TypeMemPtr tmp2 = (TypeMemPtr)CodeGen.CODE.P.TYPES.get(tmp._obj._name); - if( tmp2!=null && tmp2 != _minType ) { - _minType = tmp2; - return this; - } - } - if( r.inProgress() || r.nIns()<=1 ) - return null; // Input is in-progress - - // If we have only a single unique input, become it. - Node live = singleUniqueInput(); - if( live != null ) { - if( live._type.isa(_type) ) - return live; - // Keep the Phi upcast - return new CastNode(_type,null,live); - } - - // No bother if region is going to fold dead paths soon - for( int i=1; i<nIns(); i++ ) - if( r.in(i)._type == Type.XCONTROL ) - return null; - - // Simple Phi-after-MemMerge to a known alias can bypass. Happens when inlining. - if( _type instanceof TypeMem tmem && tmem._alias!=1 ) { - for( int i=1; i<nIns(); i++ ) - if( in(i) instanceof MemMergeNode mem ) { - setDef(i,mem.alias(tmem._alias)); - return this; - } - } - - // Generic "pull down op" - Node progress; - if( same_op() && (progress = drop_same_op()) != null ) - return progress; - - // If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove. - if( nIns()==3 ) { - if( in(1) instanceof CastNode cast && addDep(cast.in(1))==in(2) ) return in(2); - if( in(2) instanceof CastNode cast && addDep(cast.in(1))==in(1) ) return in(1); - } - // If merging a null-checked null and the checked value, just use the value. - // if( val ) ..; phi(Region,False=0/null,True=val); - // then replace with plain val. - if( nIns()==3 ) { - int nullx = -1; - if( in(1)._type == in(1)._type.makeZero() ) nullx = 1; - if( in(2)._type == in(2)._type.makeZero() ) nullx = 2; - if( nullx != -1 ) { - Node val = in(3-nullx); - if( val instanceof CastNode cast ) - val = cast.in(1); - if( addDep(r.idom(this)) instanceof IfNode iff && addDep(iff.pred())==val ) { - // Must walk the idom on the null side to make sure we hit False. - CFGNode idom = (CFGNode)r.in(nullx); - while( idom != null && idom.nIns() > 0 && idom.in(0) != iff ) idom = idom.idom(); - if( idom instanceof CProjNode proj && proj._idx==1 ) - return val; - } - } - } - - return null; - } - - // Same op on all Phi paths; all ops have only the Phi as a use. - // None have a control input. - private boolean same_op() { - for( int i=1; i<nIns(); i++ ) { - Node op = in(i); - if( in(1).getClass() != op.getClass() || op.in(0)!=null || in(1).nIns() != op.nIns() ) - return false; // Wrong class or CFG bound or mismatched inputs - if( in(1) instanceof MemOpNode mem && mem._alias != ((MemOpNode)op)._alias ) - return false; - if( op.nOuts() > 1 ) { // Too many users, but addDep in case lose users - for( Node out : op._outputs ) - if( out!=null && out!=this ) - addDep(out); - return false; - } - for( int j=1; j<in(1).nIns(); j++ ) - if( op.in(j) instanceof ScopeNode || (op.in(j)==null ^ in(1).in(j)==null) ) - return false; // Lazy Phi input - } - return true; - } - - private Node drop_same_op() { - assert !(in(1) instanceof CFGNode); - Node op = in(1); - Node cp = op.copy(); - cp._type = null; // Fresh type - cp.addDef(null); // No control - - for( int j=1; j<op.nIns(); j++ ) { - boolean needsPhi = false; - Node x = op.in(j); // Jth input from sample #1 - for( int i=2; i<nIns(); i++ ) - if( in(i).in(j) != x ) - { needsPhi=true; break; } - if( needsPhi ) { - x = new PhiNode(_label,op.in(j)._type.glb(false)); - x.addDef(region()); - for( int i=1; i<nIns(); i++ ) - x.addDef(in(i).in(j)); - x = x.peephole(); - } - cp.addDef(x); - } - // Test not running backwards, which can happen for e.g. And's - if( cp.compute().isa(compute()) ) - return cp; - cp.kill(); - return null; - } - - /** - * If only single unique input, return it - */ - private Node singleUniqueInput() { - if( region() instanceof LoopNode loop && loop.entry()._type == Type.XCONTROL ) - return null; // Dead entry loops just ignore and let the loop collapse - Node live = null; - for( int i=1; i<nIns(); i++ ) { - // If the region's control input is live, add this as a dependency - // to the control because we can be peeped should it become dead. - if( addDep(region().in(i))._type != Type.XCONTROL && in(i) != this ) - if( live == null || live == in(i) ) live = in(i); - else return null; - } - return live; - } - - @Override - boolean allCons(Node dep) { - if( !(region() instanceof RegionNode r) ) return false; - // When the region completes (is no longer in progress) the Phi can - // become a "all constants" Phi, and the "dep" might make progress. - dep.addDep(this); - if( r.inProgress() ) return false; - return super.allCons(dep); - } - - // True if last input is null - public boolean inProgress() { - return in(nIns()-1) == null; - } - - // Never equal if inProgress. - // Also, joins - @Override public boolean eq( Node n ) { - if( inProgress() ) return false; - Type min = ((PhiNode)n)._minType; - if( _minType==min ) return true; - Type mt = min.meet(_minType); - if( min!=mt && _minType!=mt ) return false; - //// Theory says these 2 Phis CAN be merged/GVNd, but I need to pick the - //// most general minType. - //_minType = ((PhiNode)n)._minType = mt; - //return true; - return false; - } - - @Override - public Parser.ParseException err() { - if( _type != Type.BOTTOM ) return null; - - // BOTTOM means we mixed e.g. int and ptr - for( int i=1; i<nIns(); i++ ) - // Already an error, but better error messages come from elsewhere - if( in(i)._type == Type.BOTTOM ) - return null; - - // Gather a minimal set of types that "cover" all the rest - boolean ti=false, tf=false, tp=false, tn=false; - for( int i=1; i<nIns(); i++ ) { - Type t = in(i)._type; - ti |= t instanceof TypeInteger x; - tf |= t instanceof TypeFloat x; - tp |= t instanceof TypeMemPtr x; - tn |= t==Type.NIL; - } - return ReturnNode.mixerr(ti,tf,tp,tn, ((RegionNode)region())._loc); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/src/main/java/com/seaofnodes/simple/node/ProjNode.java deleted file mode 100644 index a0394e5b..00000000 --- a/src/main/java/com/seaofnodes/simple/node/ProjNode.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMem; -import com.seaofnodes.simple.type.TypeTuple; - -import java.util.BitSet; - -public class ProjNode extends Node { - - // Which slice of the incoming multipart value - public final int _idx; - - // Debugging label - public final String _label; - - public ProjNode(Node ctrl, int idx, String label) { - super(new Node[]{ctrl}); - _idx = idx; - _label = label; - } - public ProjNode(ProjNode p) { super(p); _idx = p._idx; _label = p._label; } - - @Override public String label() { return _label; } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - if( _label != null ) return sb.append(_label); - if( in(0) instanceof CallEndNode cend && cend.call()!=null ) - return cend.call()._print0(sb,visited); - return sb.append("LONELY PROJ"); - } - - @Override public CFGNode cfg0() { - return in(0) instanceof CFGNode cfg ? cfg : in(0).cfg0(); - } - - @Override public boolean isMem() { return _type instanceof TypeMem; } - @Override public boolean isPinned() { return true; } - - @Override - public Type compute() { - Type t = in(0)._type; - return t instanceof TypeTuple tt ? tt._types[_idx] : Type.BOTTOM; - } - - @Override public Node idealize() { return ((MultiNode)in(0)).pcopy(_idx); } - - @Override - public boolean eq( Node n ) { return _idx == ((ProjNode)n)._idx; } - - @Override - int hash() { return _idx; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/ReadOnlyMach.java b/src/main/java/com/seaofnodes/simple/node/ReadOnlyMach.java deleted file mode 100644 index a35f5a1d..00000000 --- a/src/main/java/com/seaofnodes/simple/node/ReadOnlyMach.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.Encoding; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.util.SB; - -/** - * Cast a pointer to read-only - */ -public class ReadOnlyMach extends ReadOnlyNode implements MachNode { - public ReadOnlyMach( ReadOnlyNode n ) { super(n); } - @Override public String op() { return label(); } - @Override public RegMask regmap(int i) { assert i==1; return RegMask.FULL; } - @Override public RegMask outregmap() { return RegMask.FULL; } - @Override public int twoAddress( ) { return 1; } - @Override public void encoding( Encoding enc ) { } - @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(in(1))); } -} diff --git a/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java b/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java deleted file mode 100644 index d8fe6286..00000000 --- a/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMemPtr; -import java.util.BitSet; - -/** - * Cast a pointer to read-only - */ -public class ReadOnlyNode extends Node { - public ReadOnlyNode( Node n ) { super(null,n); } - public ReadOnlyNode( ReadOnlyNode n ) { super(n); } - @Override public String label() { return "ReadOnly"; } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - return in(1)._print0(sb.append("(const)"),visited); - } - @Override public Type compute() { - Type t = in(1)._type; - return t instanceof TypeMemPtr tmp ? tmp.makeRO() : t; - } - - @Override public Node idealize() { - if( in(1)._type instanceof TypeMemPtr tmp && tmp.isFinal() ) - return in(1); - return null; - } - -} diff --git a/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/src/main/java/com/seaofnodes/simple/node/RegionNode.java deleted file mode 100644 index 6f740a47..00000000 --- a/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ /dev/null @@ -1,168 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.type.Type; -import java.util.*; - -public class RegionNode extends CFGNode { - // Source location for late discovered errors - public Parser.Lexer _loc; - - public RegionNode(Parser.Lexer loc, Node... nodes) { super(nodes); _loc = loc; } - public RegionNode(RegionNode r, Parser.Lexer loc) { super(r); _loc = loc; } - public RegionNode(RegionNode r) { super(r); if( r!=null ) _loc = r._loc; } - - @Override - public String label() { return "Region"; } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - return sb.append(label()); - } - - @Override public boolean blockHead() { return true; } - - @Override - public Type compute() { - if( inProgress() ) return Type.CONTROL; - Type t = Type.XCONTROL; - for (int i = 1; i < nIns(); i++) - t = t.meet(in(i)._type); - return t; - } - - @Override - public Node idealize() { - if( inProgress() ) return null; - - Node progress = deadPath(); - if( progress!=null ) return progress; - - // If down to a single input, become that input - if( nIns()==2 && !hasPhi() ) - return in(1); // Collapse if no Phis; 1-input Phis will collapse on their own - - // If a CFG diamond with no merging, delete: "if( pred ) {} else {};" - if( !hasPhi() && nIns()>3 && // No Phi users, just a control user - in(1) instanceof CProjNode p1 && - in(2) instanceof CProjNode p2 && - addDep(p1.in(0))==addDep(p2.in(0)) && - p1.in(0) instanceof IfNode iff ) { - // Replace with the iff.ctrl directly - if( nIns()==3 ) return iff.ctrl(); - // Just delete the path for fat Regions - setDef(1,iff.ctrl()); - return delDef(2); - } - - // Flatten stack regions (no loops involved) - if( getClass() == RegionNode.class ) { - for( int i=1; i<nIns(); i++ ) - if( cfg(i) instanceof RegionNode region && region.getClass() == RegionNode.class && region.nIns()>2 && !hasMidUser(region) ) { - assert !region.inProgress(); - // Fold Phis - for( Node use : _outputs ) { - if( use instanceof PhiNode phi ) { - PhiNode phi2 = phi.in(i) instanceof PhiNode phi2x && phi2x.region()==region ? phi2x : null; - for( int j=1; j<region.nIns(); j++ ) - phi.addDef(phi2==null ? phi.in(i) : phi2.in(j)); - CodeGen.CODE.add(phi); - } - } - - // Fold Region - for( int j=1; j<region.nIns(); j++ ) - addDef(region.in(j)); - setDef(i,Parser.XCTRL); - return this; - } - } - - return null; - } - - Node deadPath() { - // Delete dead paths into a Region - int path = findDeadInput(); - if( path==0 ) return null; - // Do not delete the entry path of a loop (ok to remove the back edge - // and make the loop a single-entry Region which folds away the Loop). - // Folding the entry path confused the loop structure, moving the - // backedge to the entry point. - if( this instanceof LoopNode loop && loop.entry()==in(path) ) - return null; - // Cannot use the obvious output iterator here, because a Phi deleting - // an input might recursively delete *itself*. This shuffles the - // output array, and we might miss iterating an unrelated Phi. So on - // rare occasions we repeat the loop to get all the Phis. - int nouts = 0; - while( nouts != nOuts() ) { - nouts = nOuts(); - for( int i=0; i<nOuts(); i++ ) - if( out(i) instanceof PhiNode phi && phi.nIns()==nIns() ) - CodeGen.CODE.addAll(phi.delDef(path)._outputs); - } - return isDead() ? Parser.XCTRL : delDef(path); - } - - - private int findDeadInput() { - for( int i=1; i<nIns(); i++ ) - if( in(i)._type==Type.XCONTROL ) - return i; - return 0; // All inputs alive - } - - boolean hasPhi() { - for( Node phi : _outputs ) - if( phi instanceof PhiNode ) - return true; - return false; - } - - // Is there a value in-between 2 Regions? Difficult to correctly fuse - // without extensive testing, so just ignore. - boolean hasMidUser(RegionNode r) { - for( Node use : r._outputs ) { - if( use==this ) continue; - if( use instanceof PhiNode ) { - for( Node data : use._outputs ) { - if( !(data instanceof PhiNode phi2) || phi2.region()!=this ) - { addDep(use); addDep(data); return true; } - } - } else - { addDep(use); return true; } // Control user - } - return false; - } - - - // Immediate dominator of Region is a little more complicated. - @Override public int idepth() { - if( CodeGen.CODE.validIDepth(_idepth) ) - return _idepth; - int d=0; - for( Node n : _inputs ) - if( n!=null ) - d = Math.max(d,CodeGen.CODE.iDepthFrom(((CFGNode)n).idepth())); - return _idepth = d; - } - - @Override public CFGNode idom(Node dep) { - CFGNode lca = null; - // Walk the LHS & RHS idom trees in parallel until they match, or either fails. - // Because this does not cache, it can be linear in the size of the program. - for( int i=1; i<nIns(); i++ ) - lca = cfg(i)._idom(lca,dep); - return lca; - } - - // True if last input is null - public boolean inProgress() { return nIns()>1 && in(nIns()-1) == null; } - - // Never equal if inProgress - @Override public boolean eq( Node n ) { return !inProgress(); } - -} diff --git a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java deleted file mode 100644 index 7971733a..00000000 --- a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ /dev/null @@ -1,166 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.Encoding; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import java.util.BitSet; - -/** - * The Return node has two inputs. The first input is a control node and the - * second is the data node that supplies the return value. - * <p> - * In this presentation, Return functions as a Stop node, since multiple <code>return</code> statements are not possible. - * The Stop node will be introduced in Chapter 6 when we implement <code>if</code> statements. - * <p> - * The Return's output is the value from the data node. - */ -public class ReturnNode extends CFGNode { - - public FunNode _fun; - - public ReturnNode(Node ctrl, Node mem, Node data, Node rpc, FunNode fun ) { - super(ctrl, mem, data, rpc); - _fun = fun; - } - public ReturnNode( ReturnNode ret, FunNode fun ) { super(ret); _fun = fun; } - - public Node ctrl() { return in(0); } - public Node mem () { return in(1); } - public Node expr() { return in(2); } - public Node rpc () { return in(3); } - @Override public FunNode fun() { return _fun; } - - @Override - public String label() { return "Return"; } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - sb.append("return "); - if( expr()==null ) sb.append("----"); - else expr()._print0(sb, visited); - return sb.append(";"); - } - - // No one unique control follows; can be many call end sites - @Override public CFGNode uctrl() { return null; } - - @Override - public Type compute() { - if( inProgress () ) return TypeTuple.RET; // In progress - return TypeTuple.make(ctrl()._type,mem()._type,expr()._type); - } - - @Override public Node idealize() { - if( inProgress () ) return null; - if( _fun.isDead() ) return null; - - // Upgrade signature based on return type - Type ret = expr()._type; - TypeFunPtr fcn = _fun.sig(); - if( ret != fcn.ret() && ret.isa(fcn.ret()) ) - _fun.setSig(fcn.makeFrom(ret)); - - // If dead (cannot be reached; infinite loop), kill the exit values - if( ctrl()._type==Type.XCONTROL && - !(mem() instanceof ConstantNode && expr() instanceof ConstantNode) ) { - Node top = new ConstantNode(Type.TOP).peephole(); - setDef(1,top); - setDef(2,top); - return this; - } - - return null; - } - - public boolean inProgress() { - return ctrl().getClass() == RegionNode.class && ((RegionNode)ctrl()).inProgress(); - } - - // Gather parse-time return types for error reporting - private Type mt = Type.TOP; - private boolean ti=false, tf=false, tp=false, tn=false; - - // Add a return exit to the current parsing function - void addReturn( Node ctrl, Node rmem, Node expr ) { - assert inProgress(); - - // Gather parse-time return types for error reporting - Type t = expr._type; - mt = mt.meet(t); - ti |= t instanceof TypeInteger x; - tf |= t instanceof TypeFloat x; - tp |= t instanceof TypeMemPtr x; - tn |= t==Type.NIL; - - // Merge path into the One True Return - RegionNode r = (RegionNode)ctrl(); - // Assert that the Phis are in particular outputs; not reordered or shuffled - PhiNode mem = (PhiNode)r.out(0); assert mem._minType == TypeMem.BOT; - PhiNode rez = (PhiNode)r.out(1); assert rez._minType == Type.BOTTOM; - // Pop "inProgress" null off - r ._inputs.pop(); - mem._inputs.pop(); - rez._inputs.pop(); - // Add new return point - r .addDef(ctrl); - mem.addDef(rmem); - rez.addDef(expr); - // Back to being inProgress - r .addDef(null); - mem.addDef(null); - rez.addDef(null); - } - - @Override public Parser.ParseException err() { - return expr()._type/*mt*/==Type.BOTTOM ? mixerr(ti,tf,tp,tn,_fun._loc) : null; - } - - static Parser.ParseException mixerr( boolean ti, boolean tf, boolean tp, boolean tn, Parser.Lexer loc ) { - if( !ti && !tf && !tp && !tn ) - return Parser.error("No defined return type", loc); - SB sb = new SB().p("No common type amongst "); - if( ti ) sb.p("int and "); - if( tf ) sb.p("f64 and "); - if( tp || tn ) sb.p("reference and "); - return Parser.error(sb.unchar(5).toString(),loc); - } - - - // ------------ - // MachNode specifics, shared across all CPUs - public String op() { - return _fun._frameAdjust > 0 ? "addi" : "ret"; - } - // Correct Nodes outside the normal edges - public void postSelect(CodeGen code) { - FunNode fun = (FunNode)rpc().in(0); - _fun = fun; - fun.setRet(this); - } - public RegMask regmap(int i) { - return i==2 - ? CodeGen.CODE._mach.retMask(_fun.sig()) - : CodeGen.CODE._retMasks[i]; - } - public RegMask outregmap() { return null; } - public void encoding( Encoding enc ) { throw Utils.TODO(); } - public void asm(CodeGen code, SB sb) { - int frameAdjust = fun()._frameAdjust; - if( frameAdjust>0 ) - sb.p("rsp += #").p(frameAdjust).p("\nret"); - // Post code-gen, just print the "ret" - if( code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() ) - // Prints return reg (either RAX or XMM0), RPC and then the - // callee-save registers. - for( int i=2; i<nIns(); i++ ) - sb.p(code.reg(in(i),fun())).p(" "); - // If we did not get the expected rpc, print which one we got - else if( code._regAlloc.regnum(rpc()) != code._mach.rpc() ) - sb.p("[").p(code.reg(rpc())).p("]"); - } - -} diff --git a/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java b/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java deleted file mode 100644 index 7936086d..00000000 --- a/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; - -import java.util.BitSet; - -public class RoundF32Node extends Node { - public RoundF32Node(Node lhs) { super(null, lhs); } - - @Override public String label() { return "RoundF32"; } - - @Override public String glabel() { return "(f32)"; } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - return in(1)._print0(sb.append("((f32)"), visited).append(")"); - } - - @Override - public Type compute() { - if (in(1)._type instanceof TypeFloat i0 && i0.isConstant() ) - return TypeFloat.constant((float)i0.value()); - return in(1)._type; - } - - @Override - public Node idealize() { - Node lhs = in(1); - Type t1 = lhs._type; - - // RoundF32 of float - if( t1 instanceof TypeFloat tf && tf._sz==32 ) - return lhs; - - return null; - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/SarNode.java b/src/main/java/com/seaofnodes/simple/node/SarNode.java deleted file mode 100644 index ee15fa65..00000000 --- a/src/main/java/com/seaofnodes/simple/node/SarNode.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; - -public class SarNode extends ArithNode { - public SarNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); } - - @Override public String label() { return "Sar"; } - @Override public String op() { return ">>"; } - @Override public String glabel() { return ">>"; } - - @Override long doOp( long x, long y ) { return x >> y; } - @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) { - if( y.isConstant() ) { - int log = (int)y.value(); - return TypeInteger.make(-1L<<(63-log),(1L<<(63-log))-1); - } - return TypeInteger.BOT; - } - - @Override - public Node idealize() { - Node lhs = in(1); - Node rhs = in(2); - Type t2 = rhs._type; - - // Sar of 0. - if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 ) - return lhs; - - // TODO: x >> 3 >> (y ? 1 : 2) ==> x >> (y ? 4 : 5) - - return super.idealize(); - } - @Override Node copy(Node lhs, Node rhs) { return new SarNode(_loc,lhs,rhs); } -} diff --git a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java deleted file mode 100644 index 36746a81..00000000 --- a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ /dev/null @@ -1,468 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.Var; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.AryInt; -import com.seaofnodes.simple.util.Utils; - -import java.util.*; - -/** - * The Scope node is purely a parser helper - it tracks names to nodes with a - * stack of variables. - */ -public class ScopeNode extends MemMergeNode { - - /** - * The control is a name that binds to the currently active control - * node in the graph - */ - public static final String CTRL = "$ctrl"; - public static final String ARG0 = "arg"; - public static final String MEM0 = "$mem"; - - // All active/live variables in all nested scopes, all run together. - // Maps 1-to-1 to the _inputs array. - public final Ary<Var> _vars; - - // Lexical scope is typed: - public static class Kind { - // Basic block scoping - public static class Block extends Kind { } - // Function scope - public static class Func extends Kind { } - // Struct definition, mapping types to fields - public static class Define extends Kind { public Define(TypeMemPtr tmp) { _tmp=tmp; } public final TypeMemPtr _tmp; } - // Struct allocation, mapping values to fields - public static class Alloc extends Kind { public Alloc (TypeMemPtr tmp) { _tmp=tmp; } public final TypeMemPtr _tmp; } - - public int _lexSize; // Number of Vars in this scope - } - public final Ary<Kind> _kinds; - // Lexical scope nesting depth - public int depth() { return _kinds._len; } - - // Extra guards; tested predicates and casted results - private final Ary<Node> _guards; - - // A new ScopeNode - public ScopeNode() { - super(true); - _vars = new Ary<>(Var .class); - _kinds = new Ary<>(Kind.class); - _guards = new Ary<>(Node.class); - } - - @Override public String label() { return "Scope"; } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - sb.append("Scope[ "); - int j=1; - for( int i=0; i<nIns(); i++ ) { - if( j < depth() && i == _kinds.at(j)._lexSize ) { sb.append("| "); j++; } - Var v = _vars.at(i); - sb.append(v.type()); - sb.append(" "); - if( v._final ) sb.append("!"); - sb.append(v._name); - sb.append("="); - Node n = in(i); - while( n instanceof ScopeNode loop ) { - sb.append("Lazy_"); - n = loop.in(i); - } - if( n==null ) sb.append("___"); - else n._print0(sb, visited); - sb.append(", "); - } - sb.setLength(sb.length()-2); - return sb.append("]"); - } - - - public Node ctrl() { return in(0); } - public MemMergeNode mem() { return (MemMergeNode)in(1); } - public Var var(int i) { return _vars.at(i); } - - /** - * The ctrl of a ScopeNode is always bound to the currently active - * control node in the graph, via a special name '$ctrl' that is not - * a valid identifier in the language grammar and hence cannot be - * referenced in Simple code. - * - * @param n The node to be bound to '$ctrl' - * - * @return Node that was bound - */ - public <N extends Node> N ctrl(N n) { return setDef(0,n); } - public Node mem(Node n) { return setDef(1,n); } - - public void push( Kind kind ) { - kind._lexSize = _vars.size(); - _kinds.push(kind); - } - - // Pop a lexical scope - public void pop() { - promote(); // Promote forward references to the next outer scope - int n = _kinds.pop()._lexSize; - popUntil(n); // Pop off inputs going out of scope - _vars.setLen(n); // Pop off variables going out of scope - } - - - // Look for forward references in the last lexical scope and promote to the - // next outer lexical scope. At the last scope declare them an error. - public void promote() { - Kind kind = _kinds.last(); - int n = kind._lexSize; - for( int i=n; i<nIns(); i++ ) { - Var v = var(i); - if( !v.isFRef() ) continue; - if( depth()==1 ) - throw Parser.error("Undefined name '" + v._name + "'",v._loc); - _vars .swap(n,i); - _inputs.swap(n,i); - v._idx = n; - kind._lexSize = ++n; - } - } - - - public boolean inConstructor() { return _kinds.last() instanceof Kind.Define; } - public boolean inAllocation () { return _kinds.last() instanceof Kind.Alloc ; } - public boolean inFunction () { return _kinds.last() instanceof Kind.Func ; } - - public Kind kind( Var v ) { - for( int i=depth()-1; i>=0; i-- ) - if( v._idx >= _kinds.at(i)._lexSize ) - return _kinds.at(i); - throw Utils.TODO(); - } - - // Return the kind for the defining scope, or a function scope if found - // first. Block scopes means the variable can be r/w directly in the - // scope, Function scopes require final constants (no capture), and - // Constructor scopes mean an instance variable reference. - - // Walk up-lexical scope looking for defining lexical Kind. - // - Skip any amount of nested block scopes - // - Walking out of a function requires v be a final constant (for now) - - // and we can stop walking. - - // - Walking out of a method and (skipping Blocks) into the matching struct - // is OK, and this is a instance var load. - - // - Walking out of a method and into the wrong struct is an error; - // including nested structs containing (no nested classes yet) - - // Returns error, forward-ref, ok containing struct, ok containing function/block. - - public String outOfFunction( Var v ) { - //if( v==null ) return null; // Prolly forward reference - //int i; for( i=_lexSize._len-1; i>=0 && v._idx<_lexSize.at(i); i-- ) - // if( _kinds.at(i)=="{->}" ) return "{->}"; - //return _kinds.at(i); - throw Utils.TODO(); - } - - - // Find name in reverse, return an index into _vars or -1. Linear scan - // instead of hashtable, but probably doesn't matter until the scan - // typically hits many dozens of variables. - public int find( String name ) { - for( int i=_vars.size()-1; i>=0; i-- ) - if( var(i)._name.equals(name) ) - return i; - return -1; - } - - /** - * Create a new variable name in the current scope - */ - public boolean define( String name, Type declaredType, boolean xfinal, Node init, Parser.Lexer loc ) { - assert _kinds.isEmpty() || name.charAt(0)!='$' ; // Later scopes do not define memory - if( depth() > 0 ) - for( int i=_vars.size()-1; i>=_kinds.last()._lexSize; i-- ) { - Var n = var(i); - if( n._name.equals(name) ) { - if( !n.isFRef() ) return false; // Double define - FRefNode fref = (FRefNode)in(n._idx); // Get forward ref - if( !xfinal || !declaredType.isConstant() ) throw fref.err(); // Must be a final constant - n.defFRef(declaredType,xfinal,loc); // Declare full correct type, final, source location - setDef(n._idx,fref.addDef(init)); // Set FRef to defined; tell parser also - } - } - Var v = new Var(nIns(),name,declaredType,xfinal,loc,init==Parser.XCTRL); - _vars.add(v); - // Creating a forward reference - if( init==Parser.XCTRL ) - init = new FRefNode(v).init(); - addDef(init); - return true; - } - - // Read from memory - public Node mem( int alias ) { return mem()._mem(alias,null); } - // Write to memory - public void mem( int alias, Node st ) { mem()._mem(alias,st); } - - - /** - * Lookup a name in all scopes starting from most deeply nested. - * - * @param name Name to be looked up - * @return null if not found, or the implementing Node - */ - public Var lookup( String name ) { - int idx = find(name); - // -1 is missed in all scopes, not found - return idx == -1 ? null : update(var(idx),null); - } - - /** - * Redefine an existing name - * - * @param name Name being redefined - * @param n The node to bind to the name - */ - public void update( String name, Node n ) { - int idx = find(name); - assert idx>=0; - update(var(idx),n); - } - - public Var update( Var v, Node st ) { - Node old = in(v._idx); - if( old instanceof ScopeNode loop ) { - // Lazy Phi! - Node def = loop.in(v._idx); - old = def instanceof PhiNode phi && loop.ctrl()==phi.region() - // Loop already has a real Phi, use it - ? def - // Set real Phi in the loop head - // The phi takes its one input (no backedge yet) from a recursive - // lookup, which might have insert a Phi in every loop nest. - : loop.setDef(v._idx,new PhiNode(v._name, v.type(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).peephole()); - setDef(v._idx,old); - } - //assert !v._final || st==null; - if( st!=null ) setDef(v._idx,st); // Set new value - return v; - } - - /** - * Duplicate a ScopeNode; including all levels, up to Nodes. So this is - * neither shallow (would dup the Scope but not the internal HashMap - * tables), nor deep (would dup the Scope, the HashMap tables, but then - * also the program Nodes). - * <p> - * If the {@code loop} flag is set, the edges are filled in as the original - * Scope, as an indication of Lazy Phis at loop heads. The goal here is to - * not make Phis at loop heads for variables which are never touched in the - * loop body. - * <p> - * The new Scope is a full-fledged Node with proper use<->def edges. - */ - public ScopeNode dup() { return dup(false); } - public ScopeNode dup(boolean loop) { - ScopeNode dup = new ScopeNode(); - // Our goals are: - // 1) duplicate the name bindings of the ScopeNode across all stack levels - // 2) Make the new ScopeNode a user of all the nodes bound - // 3) Ensure that the order of defs is the same to allow easy merging - dup._vars .addAll(_vars ); - dup._kinds .addAll(_kinds ); - dup._guards .addAll(_guards ); - // The dup'd guards all need dup'd keepers, to keep proper accounting - // when later removing all guards - for( Node n : _guards ) - if( !(n instanceof CFGNode) ) - n.keep(); - dup.addDef(ctrl()); // Control input is just copied - - // Memory input is a shallow copy - MemMergeNode memdup = new MemMergeNode(true), mem = mem(); - memdup.addDef(null); - memdup.addDef(loop ? this : mem.in(1)); - for( int i=2; i<mem.nIns(); i++ ) - // For lazy phis on loops we use a sentinel - // that will trigger phi creation on update - memdup.addDef(loop ? this : mem.in(i)); - dup.addDef(memdup); - - // Copy of other inputs - for( int i=2; i<nIns(); i++ ) - // For lazy phis on loops we use a sentinel - // that will trigger phi creation on update - dup.addDef(loop && !var(i)._final ? this : in(i)); - return dup; - } - - /** - * Merges the names whose node bindings differ, by creating Phi node for such names - * The names could occur at all stack levels, but a given name can only differ in the - * innermost stack level where the name is bound. - * - * @param that The ScopeNode to be merged into this - * @return A new node representing the merge point - */ - public RegionNode mergeScopes(ScopeNode that, Parser.Lexer loc) { - RegionNode r = ctrl(new RegionNode(loc,null,ctrl(), that.ctrl()).keep()); - mem()._merge(that.mem(),r); - this ._merge(that ,r); - that.kill(); // Kill merged scope - CodeGen.CODE.add(r); - return r.unkeep(); - } - - private void _merge(ScopeNode that, RegionNode r) { - for( int i = 2; i < nIns(); i++) - if( in(i) != that.in(i) ) { // No need for redundant Phis - // If we are in lazy phi mode we need to a lookup - // by name as it will trigger a phi creation - Var v = var(i); - Node lhs = this.in(this.update(v,null)); - Node rhs = that.in(that.update(v,null)); - setDef(i, new PhiNode(v._name, v.type(), r, lhs, rhs).peephole()); - } - } - - // Balance arms of an IF. Extra lonely defs are thrown: "if(pred) int x;". - // Forward refs are copied to the other side, "as if" they were there all along. - public void balanceIf( ScopeNode scope ) { - for( int i = nIns(); i < scope.nIns(); i++ ) { - Var n = scope.var(i); - if( n.isFRef() ) { // RHS has forward refs - _vars.add(n); // Copy to LHS - addDef(scope.in(i)); - } else - throw Parser.error("Cannot define a '"+n._name+"' on one arm of an if",n._loc); - } - } - - - // peephole the backedge scope into this loop head scope - // We set the second input to the phi from the back edge (i.e. loop body) - public void endLoop(ScopeNode back, ScopeNode exit ) { - Node ctrl = ctrl(); - assert ctrl instanceof LoopNode loop && loop.inProgress(); - ctrl.setDef(2,back.ctrl()); - - mem()._endLoopMem( this, back.mem(), exit.mem() ); - this ._endLoop ( this, back , exit ); - back.kill(); // Loop backedge is dead - // Now one-time do a useless-phi removal - mem()._useless(); - this ._useless(); - - // The exit mem's lazy default value had been the loop top, - // now it goes back to predating the loop. - exit.mem().setDef(1,mem().in(1)); - } - - // Fill in the backedge of any inserted Phis - void _endLoop( ScopeNode scope, Node back, Node exit ) { - for( int i=2; i<nIns(); i++ ) { - if( var(i)._final ) continue; // Final vars did not get modified in the loop - if( var(i).type().isHighOrConst() ) continue; // Cannot lift higher than a constant, so no Phi - if( back.in(i) != scope ) { - PhiNode phi = (PhiNode)in(i); - assert phi.region()==scope.ctrl() && phi.in(2)==null; - phi.setDef(2,back.in(i)); // Fill backedge - } - if( exit.in(i) == scope ) // Replace a lazy-phi on the exit path also - exit.setDef(i,in(i)); - } - } - - - // Up-casting: using the results of an If to improve a value. - // E.g. "if( ptr ) ptr.field;" is legal because ptr is known not-null. - public void addGuards( Node ctrl, Node pred, boolean invert ) { - assert ctrl instanceof CFGNode; - _guards.add(ctrl); // Marker to distinguish 0,1,2 guards - // add pred & its cast to the normal input list, with special Vars - if( pred==null || pred.isDead() ) - return; // Dead, do not add any guards - // Invert the If conditional - if( invert ) - pred = pred instanceof NotNode not ? not.in(1) : CodeGen.CODE.add(new NotNode(pred).peephole()); - // This is a zero/null test. - // Compute the positive test type. - Type tnz = pred._type.nonZero(); - if( tnz!=null ) - _addGuard(tnz,ctrl,pred); - - // Compute the negative test type. - if( pred instanceof NotNode not ) { - Node npred = not.in(1); - Type tzero = npred._type.makeZero(); - _addGuard(tzero,ctrl,npred); - } - } - - private void _addGuard(Type guard, Node ctrl, Node pred) { - Type tcast = guard.join(pred._type); - if( tcast != pred._type && !tcast.isHigh() ) { - Node cast = new CastNode(tcast,ctrl,pred.keep()).peephole().keep(); - _guards.add(pred); - _guards.add(cast); - replace(pred,cast); - } - } - - - // Remove matching pred/cast pairs from this guarded region. - public ScopeNode removeGuards( Node ctrl ) { - assert ctrl instanceof CFGNode; - // 0,1 or 2 guards - while( true ) { - Node g = _guards.pop(); - if( g == ctrl ) break; - if( g instanceof CFGNode ) continue; - g .unkill(); // Pop/kill cast - _guards.pop().unkill(); // Pop/kill pred - } - return this; - } - - // If we find a guarded instance of pred, replace with the upcasted version - public Node upcastGuard( Node pred ) { - // If finding an instanceof pred, replace with cast. - // Otherwise, just pred itself. - int i = _guards._len; - while( i > 0 ) { - Node cast = _guards.at(--i); - if( cast instanceof CFGNode ) continue; // Marker between guard sets - Node xpred = _guards.at(--i); - if( xpred == pred ) - return cast; - } - return pred; - } - - // Kill guards also - @Override public void kill() { - for( Node n : _guards ) - if( !(n instanceof CFGNode) ) - n.unkill(); - _guards.clear(); - // Can have lazy uses remaining - if( isUnused() ) - super.kill(); - } - - - private void replace( Node old, Node cast ) { - assert old!=null && old!=cast; - for( int i=0; i<nIns(); i++ ) - if( in(i)==old ) - setDef(i,cast); - } - -} diff --git a/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/src/main/java/com/seaofnodes/simple/node/ShlNode.java deleted file mode 100644 index 2557bf6b..00000000 --- a/src/main/java/com/seaofnodes/simple/node/ShlNode.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; - -public class ShlNode extends ArithNode { - public ShlNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); } - - @Override public String label() { return "Shl"; } - @Override public String op() { return "<<"; } - @Override public String glabel() { return "<<"; } - - @Override long doOp( long x, long y ) { return x << y; } - @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) { - if( x == TypeInteger.ZERO ) - return TypeInteger.ZERO; - if( y.isConstant() ) { - int shf = (int)y.value(); - // If no overflow, shift endpoints - if( !(((x._min<<shf)>>shf) != x._min || - ((x._max<<shf)>>shf) != x._max ) ) - return TypeInteger.make(x._min<<shf,x._max<<shf); - } - return TypeInteger.BOT; - } - - @Override - public Node idealize() { - Node lhs = in(1); - Node rhs = in(2); - - if( rhs._type instanceof TypeInteger shl && shl.isConstant() ) { - // Shl of 0. - if( (shl.value()&63)==0 ) - return lhs; - // (x + c) << i => (x << i) + (c << i) - if( lhs instanceof AddNode add && addDep(add).in(2)._type instanceof TypeInteger c && c.isConstant() ) { - long sum = c.value() << shl.value(); - if( Integer.MIN_VALUE <= sum && sum <= Integer.MAX_VALUE ) - return new AddNode(new ShlNode(_loc,add.in(1),rhs).peephole(), Parser.con(sum) ); - } - } - - // TODO: x << 3 << (y ? 1 : 2) ==> x << (y ? 4 : 5) - - return super.idealize(); - } - @Override Node copy(Node lhs, Node rhs) { return new ShlNode(_loc,lhs,rhs); } -} diff --git a/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/src/main/java/com/seaofnodes/simple/node/ShrNode.java deleted file mode 100644 index 269f6b7f..00000000 --- a/src/main/java/com/seaofnodes/simple/node/ShrNode.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; - -public class ShrNode extends ArithNode { - public ShrNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); } - - @Override public String label() { return "Shr"; } - @Override public String op() { return ">>>"; } - @Override public String glabel() { return ">>>"; } - - @Override long doOp( long x, long y ) { return x >>> y; } - @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) { - return x == TypeInteger.ZERO ? x : TypeInteger.BOT; - } - - @Override - public Node idealize() { - Node lhs = in(1); - Node rhs = in(2); - Type t2 = rhs._type; - - // Shr of 0. - if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 ) - return lhs; - - // TODO: x >>> 3 >>> (y ? 1 : 2) ==> x >>> (y ? 4 : 5) - - return super.idealize(); - } - @Override Node copy(Node lhs, Node rhs) { return new ShrNode(_loc,lhs,rhs); } -} diff --git a/src/main/java/com/seaofnodes/simple/node/SplitNode.java b/src/main/java/com/seaofnodes/simple/node/SplitNode.java deleted file mode 100644 index d061cdbd..00000000 --- a/src/main/java/com/seaofnodes/simple/node/SplitNode.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.util.SB; -import java.util.BitSet; - -public abstract class SplitNode extends MachConcreteNode { - public final String _kind; // Kind of split - public final byte _round; - public SplitNode(String kind, byte round, Node[] nodes) { super(nodes); _kind = kind; _round = round; } - @Override public String op() { return "mov"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { - sb.append("mov("); - if( in(1) == null ) sb.append("---"); - else in(1)._print0(sb,visited); - return sb.append(")"); - } - @Override public Type compute() { return in(0)._type; } - @Override public Node idealize() { return null; } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))); - } - @Override public String comment() { return _kind + " #"+ _round; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/StartNode.java b/src/main/java/com/seaofnodes/simple/node/StartNode.java deleted file mode 100644 index e3806de7..00000000 --- a/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.*; -import java.util.Arrays; -import java.util.BitSet; -import java.util.HashSet; -import static com.seaofnodes.simple.util.Utils.TODO; - -/** - * The Start node represents the start of the function. - * <p> - * Start initially has 1 input (arg) from outside and the initial control. - * In ch10 we also add mem aliases as structs get defined; each field in struct - * adds a distinct alias to Start's tuple. - */ -public class StartNode extends LoopNode implements MultiNode { - - final Type _arg; - - public StartNode(Type arg) { super(null,null); _arg = arg; _type = compute(); } - public StartNode(StartNode start) { super(start); _arg = start==null ? null : start._arg; } - - @Override public String label() { return "Start"; } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - return sb.append(label()); - } - - @Override public CFGNode cfg0() { return null; } - - // Get the one control following; error to call with more than one e.g. an - // IfNode or other multi-way branch. For Start, its "main" - @Override public CFGNode uctrl() { - // Find "main", it's the start. - CFGNode C = null; - for( Node use : _outputs ) - if( use instanceof FunNode fun && fun.sig().isa(CodeGen.CODE._main) ) - { assert C==null; C = fun; } - return C; - } - - - @Override public TypeTuple compute() { - return TypeTuple.make(Type.CONTROL,TypeMem.TOP,_arg); - } - - @Override public Node idealize() { return null; } - - // No immediate dominator, and idepth==0 - @Override public int idepth() { return CodeGen.CODE.iDepthAt(0); } - @Override public CFGNode idom(Node dep) { return null; } - -} diff --git a/src/main/java/com/seaofnodes/simple/node/StopNode.java b/src/main/java/com/seaofnodes/simple/node/StopNode.java deleted file mode 100644 index 4b03bf4e..00000000 --- a/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.util.Utils; -import java.util.BitSet; - -public class StopNode extends CFGNode { - - public final String _src; - - public StopNode(String src) { - super(); - _src = src; - _type = compute(); - } - public StopNode(StopNode stop) { super(stop); _src = stop==null ? null : stop._src; } - - @Override - public String label() { - return "Stop"; - } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - // For the sake of many old tests, and single value prints as "return val" - ReturnNode ret1 = ret(); - if( ret1!=null ) return ret1._print0(sb,visited); - sb.append("Stop[ "); - for( Node ret : _inputs ) - if( ret!=null ) { - String name = ((ReturnNode)ret).fun()._name; - if( name== null || !name.startsWith("sys.") ) - ret._print0(sb, visited).append(" "); - } - return sb.append("]"); - } - - @Override public boolean blockHead() { return true; } - - - // If a single Return, return it. - // Otherwise, null because ambiguous. - public ReturnNode ret() { - Node ret1 = this; - for( Node ret : _inputs ) { - String name = ((ReturnNode)ret).fun()._name; - if( name==null || !name.startsWith("sys.") ) - ret1 = ret1==this ? ((ReturnNode)ret) : null; - } - return ret1==this ? null : (ReturnNode)ret1; - } - - @Override - public Type compute() { - return Type.BOTTOM; - } - - @Override - public Node idealize() { - int len = nIns(); - for( int i=0; i<nIns(); i++ ) - if( addDep(((ReturnNode)in(i)).fun()).isDead() ) - delDef(i--); - if( len != nIns() ) return this; - return null; - } - - @Override public int idepth() { - if( _idepth!=0 ) return _idepth; - int d=0; - for( Node ret : _inputs ) - d = Math.max(d,((ReturnNode)ret).idepth()+1); - return _idepth=d; - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/src/main/java/com/seaofnodes/simple/node/StoreNode.java deleted file mode 100644 index 5fb274db..00000000 --- a/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ /dev/null @@ -1,140 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.type.*; -import java.util.BitSet; - -/** - * Store represents setting a value to a memory based object, in chapter 10 - * this means a field inside a struct. - */ -public class StoreNode extends MemOpNode { - - private final boolean _init; // Initializing writes are allowed to write null - - /** - * @param name The struct field we are assigning to - * @param mem The memory alias node - this is updated after a Store - * @param ptr The ptr to the struct base where we will store a value - * @param off The offset inside the struct base - * @param value Value to be stored - */ - public StoreNode(Parser.Lexer loc, String name, int alias, Type glb, Node mem, Node ptr, Node off, Node value, boolean init) { - super(loc, name, alias, false, glb, mem, ptr, off, value); - _init = init; - } - - // GraphVis DOT code and debugger labels - @Override public String label() { return "st_"+mlabel(); } - // GraphVis node-internal labels - @Override public String glabel() { return "." +_name+"="; } - @Override public boolean isMem() { return true; } - - public Node val() { return in(4); } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - return sb.append(".").append(_name).append("=").append( val()).append(";"); - } - - @Override - public Type compute() { - Type val = val()._type; - Type mem0 = mem()._type; - if( mem0 == Type.TOP ) return TypeMem.TOP; - TypeMem mem = (TypeMem)mem0; // Invariant - if( mem == TypeMem.TOP ) return TypeMem.TOP; - Type t = Type.BOTTOM; // No idea on field contents - // Same alias, lift val to the declared type and then meet into other fields - if( mem._alias == _alias ) { - assert !_declaredType.isFRef(); - // Sharpen memory value; required for narrowing stores where the parser inserts - // zero/sign masking and somebody reads the TypeMem type. - val = val.join(_declaredType); - t = val.meet(mem._t); - } - return TypeMem.make(_alias,t); - } - - @Override - public Node idealize() { - - if( mem() instanceof CastNode cast ) { - setDef(1,cast.in(1)); - return this; - } - - // Simple store-after-store on same address. Should pick up the - // required init-store being stomped by a first user store. - if( mem() instanceof StoreNode st && - ptr()==st.ptr() && // Must check same object - off()==st.off() && // And same offset (could be "same alias" but this handles arrays to same index) - ptr()._type instanceof TypeMemPtr && // No bother if weird dead pointers - // Must have exactly one use of "this" or you get weird - // non-serializable memory effects in the worse case. - checkOnlyUse(st) ) { - assert _name.equals(st._name); // Equiv class aliasing is perfect - setDef(1,st.mem()); - return this; - } - - // Simple store-after-MemMerge to a known alias can bypass. Happens when inlining. - if( mem() instanceof MemMergeNode mem ) { - setDef(1,mem.alias(_alias)); - return this; - } - - // Value is automatically truncated by narrow store - if( val() instanceof AndNode and && and.in(2)._type.isConstant() ) { - int log = _declaredType.log_size(); - if( log<3 ) { // And-mask vs narrow store - long mask = ((TypeInteger)and.in(2)._type).value(); - long bits = (1L<<(8<<log))-1; - // Mask does not mask any of the stored bits - if( (bits&mask)==bits ) - // So and-mask is already covered by the store - { setDef(4,and.in(1)); return this; } - } - } - - // Store will chop high order bits off; math to change those bits can be dropped. - if( val() instanceof SarNode shr && - shr.in(1) instanceof ShlNode shl && - shr.in(2)._type.isConstant() && - shl.in(2)._type.isConstant() ) { - TypeInteger shrC = (TypeInteger) shr.in(2)._type; - - // size of the thing that sign-extends - int base_size = (1 << shr.in(1)._type.log_size()) << 3; - int not_affected_bits = base_size - (int) shrC.value(); - int store_size = (1 << log_size()) << 3; - // if the store is unrelated to the shift amount, then get rid of the shift - if( shl.in(2)._type == shr.in(2)._type && shrC.value() >= store_size && not_affected_bits >= store_size) { - setDef(4, shl.in(1)); - return this; - } - } - return null; - } - - // Check that "mem" has no uses except "this" - private boolean checkOnlyUse(Node mem) { - if( mem.nOuts()==1 ) return true; - // Add deps on the other uses (can be e.g. ScopeNode mid-parse) so that - // when the other uses go away we can retry. - for( Node use : mem._outputs ) - if( use != this ) - addDep(use); - return false; - } - - @Override - public Parser.ParseException err() { - Parser.ParseException err = super.err(); - if( err != null ) return err; - TypeMemPtr tmp = (TypeMemPtr)ptr()._type; - if( tmp._obj.field(_name)._final && !_init ) - return Parser.error("Cannot modify final field '"+_name+"'",_loc); - return null; - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/StructNode.java b/src/main/java/com/seaofnodes/simple/node/StructNode.java deleted file mode 100644 index b5b59b81..00000000 --- a/src/main/java/com/seaofnodes/simple/node/StructNode.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.SB; -import java.util.BitSet; - -/** - * Build a compound object - */ -public class StructNode extends Node { - - public final TypeStruct _ts; - public StructNode(TypeStruct ts) { _ts=ts; assert !ts._open; } - - @Override public String label() { return _ts==null ? "STRUCT?" : _ts.str(); } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - if( _ts==null ) return sb.append("STRUCT?"); - sb.append(_ts._name).append(" {"); - for( int i=0; i<nIns(); i++ ) { - sb.append(_ts._fields[i]._fname).append(":"); - sb.append(in(i)==null ? Type.BOTTOM : in(i)._type); - sb.append("; "); - } - sb.setLength(sb.length()-2); - return sb.append("}"); - } - - @Override - public TypeStruct compute() { - if( _ts==null ) return TypeStruct.BOT; - Field[] fs = new Field[_ts._fields.length]; - for( int i=0; i<fs.length; i++ ) - fs[i] = _ts._fields[i].makeFrom(in(i)==null ? Type.TOP : in(i)._type); - return TypeStruct.make(_ts._name,false,fs); - } - - @Override - public Node idealize() { return null; } - - @Override - public boolean eq(Node n) { return _ts == ((StructNode)n)._ts; } - - @Override - int hash() { return _ts==null ? 0 : _ts.hashCode(); } -} diff --git a/src/main/java/com/seaofnodes/simple/node/SubFNode.java b/src/main/java/com/seaofnodes/simple/node/SubFNode.java deleted file mode 100644 index 5c7e969f..00000000 --- a/src/main/java/com/seaofnodes/simple/node/SubFNode.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; - -import java.util.BitSet; - -public class SubFNode extends Node { - public SubFNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override public String label() { return "SubF"; } - - @Override public String glabel() { return "-"; } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - in(1)._print0(sb.append("("), visited); - in(2)._print0(sb.append("-"), visited); - return sb.append(")"); - } - - @Override - public Type compute() { - if (in(1)._type instanceof TypeFloat i0 && - in(2)._type instanceof TypeFloat i1) { - if (i0.isConstant() && i1.isConstant()) - return TypeFloat.constant(i0.value()-i1.value()); - } - return in(1)._type.meet(in(2)._type); - } - - @Override - public Node idealize() { - Node lhs = in(1); - Type t2 = in(2)._type; - - // Sub of 0. - if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==0 ) - return lhs; - - return null; - } - @Override Node copy(Node lhs, Node rhs) { return new SubFNode(lhs,rhs); } -} diff --git a/src/main/java/com/seaofnodes/simple/node/SubNode.java b/src/main/java/com/seaofnodes/simple/node/SubNode.java deleted file mode 100644 index 8e4f6f79..00000000 --- a/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import java.util.BitSet; - -public class SubNode extends ArithNode { - public SubNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override public String label() { return "Sub"; } - @Override public String op() { return "-"; } - - @Override long doOp( long x, long y ) { return x - y; } - @Override TypeInteger doOp(TypeInteger x, TypeInteger y) { - // Sub of same is 0 - if( in(1)==in(2) ) - return TypeInteger.ZERO; - // Fold ranges like {2-3} - {0-1} into {1-3}. - if( !AddNode.overflow(x._min,-y._max) && - !AddNode.overflow(x._max,-y._min) && - y._min != Long.MIN_VALUE ) - return TypeInteger.make(x._min-y._max,x._max-y._min); - - return TypeInteger.BOT; - } - - @Override - public Node idealize() { - // x - (-y) is x+y - if( in(2) instanceof MinusNode minus ) - return new AddNode(in(1),minus.in(1)); - - // (-x) - y is -(x+y) - if( in(1) instanceof MinusNode minus ) - return new MinusNode(new AddNode(minus.in(1),in(2)).peephole()); - - return super.idealize(); - } - - @Override Node copy(Node lhs, Node rhs) { return new SubNode(lhs,rhs); } - @Override Node copyF() { return new SubFNode(null,null); } -} diff --git a/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java b/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java deleted file mode 100644 index c098ad99..00000000 --- a/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; -import com.seaofnodes.simple.type.TypeInteger; - -import java.util.BitSet; - -public class ToFloatNode extends Node { - public ToFloatNode(Node lhs) { super(null, lhs); } - - @Override public String label() { return "ToFloat"; } - - @Override public String glabel() { return "(flt)"; } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - return in(1)._print0(sb.append("(flt)"), visited); - } - - @Override - public Type compute() { - if( in(1)._type == Type.NIL ) return TypeFloat.FZERO; - if (in(1)._type instanceof TypeInteger i0 ) { - if( i0.isHigh() ) return TypeFloat.F64.dual(); - if( i0.isConstant() ) - return TypeFloat.constant(i0.value()); - } - return TypeFloat.F64; - } - - @Override public Node idealize() { return null; } - @Override Node copy(Node lhs, Node rhs) { return new ToFloatNode(lhs); } -} diff --git a/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java b/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java deleted file mode 100644 index 8c620a04..00000000 --- a/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; -import java.util.BitSet; - -public class XCtrlNode extends CFGNode { - public XCtrlNode() { super(new Node[]{CodeGen.CODE._start}); } - @Override public String label() { return "Xctrl"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append("Xctrl"); } - @Override public boolean isConst() { return true; } - @Override public Type compute() { return Type.XCONTROL; } - @Override public Node idealize() { return null; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/XorNode.java b/src/main/java/com/seaofnodes/simple/node/XorNode.java deleted file mode 100644 index fdb4863e..00000000 --- a/src/main/java/com/seaofnodes/simple/node/XorNode.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; - -public class XorNode extends ArithNode { - public XorNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); } - - @Override public String label() { return "Xor"; } - @Override public String op() { return "^"; } - @Override public String glabel() { return "^"; } - - @Override long doOp( long x, long y ) { return x ^ y; } - @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) { - return TypeInteger.BOT; - } - - @Override - public Node idealize() { - Node lhs = in(1); - Node rhs = in(2); - Type t1 = lhs._type; - Type t2 = rhs._type; - - // Xor of 0. We do not check for (0^x) because this will already - // canonicalize to (x^0) - if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 ) - return lhs; - - // Move constants to RHS: con*arg becomes arg*con - if ( t1.isConstant() && !t2.isConstant() ) - return swap12(); - - // Do we have ((x ^ (phi cons)) ^ con) ? - // Do we have ((x ^ (phi cons)) ^ (phi cons)) ? - // Push constant up through the phi: x ^ (phi con0^con0 con1^con1...) - Node phicon = AddNode.phiCon(this,true); - if( phicon!=null ) return phicon; - - return super.idealize(); - } - @Override Node copy(Node lhs, Node rhs) { return new XorNode(_loc,lhs,rhs); } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java deleted file mode 100644 index 1c865254..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class AddARM extends MachConcreteNode implements MachNode { - AddARM( Node add) { super(add); } - @Override public String op() { return "add"; } - @Override public RegMask regmap(int i) { return arm.RMASK; } - @Override public RegMask outregmap() { return arm.WMASK; } - - // ADD (shifted register) - @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,arm.OP_ADD); } - // General form: "rd = rs1 + rs2" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2))); - } - -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java deleted file mode 100644 index eb75c1a0..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class AddFARM extends MachConcreteNode implements MachNode { - AddFARM(Node addf) { super(addf); } - @Override public String op() { return "addf"; } - @Override public RegMask regmap(int i) { return arm.DMASK; } - @Override public RegMask outregmap() { return arm.DMASK; } - - //FADD (scalar) - @Override public void encoding( Encoding enc ) { arm.f_scalar(enc,this,arm.OPF_OP_ADD);} - - // Default on double precision for now(64 bits) - // General form: "addf rd = src1 + src2 - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2))); - } - -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java deleted file mode 100644 index 93c9e8c3..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; - -public class AddIARM extends MachConcreteNode implements MachNode { - final int _imm; - AddIARM(Node add, int imm) { - super(add); - _inputs.pop(); - _imm = imm; - } - @Override public String op() { - return _imm == 1 ? "inc" : (_imm == -1 ? "dec" : "addi"); - } - @Override public RegMask regmap(int i) { return arm.RMASK; } - @Override public RegMask outregmap() { return arm.WMASK; } - //ADD (immediate) - @Override public void encoding( Encoding enc ) { - arm.imm_inst(enc,this, in(1), arm.OPI_ADD,_imm); - } - // General form: "addi rd = rs1 + imm" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + #").p(_imm); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java deleted file mode 100644 index 77115d54..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.MachConcreteNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.util.SB; - -public class AndARM extends MachConcreteNode implements MachNode { - AndARM(Node and) { super(and); } - @Override public String op() { return "and"; } - @Override public String glabel() { return "&"; } - @Override public RegMask regmap(int i) { return arm.RMASK; } - @Override public RegMask outregmap() { return arm.WMASK; } - // AND (shifted register) - @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,arm.OP_AND); } - // General form: #rd = rs1 & rs2 - @Override public void asm(CodeGen code, SB sb){ - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" & ").p(code.reg(in(2))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java deleted file mode 100644 index 5b739cd3..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class AndIARM extends MachConcreteNode implements MachNode { - final int _imm; - AndIARM(Node and, int imm) { - super(and); - _inputs.pop(); - _imm = imm; - } - @Override public String op() { return "andi"; } - @Override public RegMask regmap(int i) { return arm.RMASK; } - @Override public RegMask outregmap() { return arm.WMASK; } - @Override public void encoding( Encoding enc ) { - arm.imm_inst_n(enc,this, in(1), arm.OPI_AND,_imm); - } - // General form: "andi rd = rs1 & imm" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" & #").p(arm.decodeImm12(_imm)); - } -}
\ No newline at end of file diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java deleted file mode 100644 index b83fb3cb..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -// Arithmetic Shift Right (register) -public class AsrARM extends MachConcreteNode implements MachNode { - AsrARM(Node asr) {super(asr);} - @Override public String op() { return "sar"; } - @Override public RegMask regmap(int i) { return arm.RMASK; } - @Override public RegMask outregmap() { return arm.DMASK; } - @Override public void encoding( Encoding enc ) { arm.shift_reg(enc,this,arm.OP_ASR); } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> ").p(code.reg(in(2))); - } - -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java deleted file mode 100644 index 38373e67..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.Encoding; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.MachConcreteNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.util.SB; - -public class AsrIARM extends MachConcreteNode implements MachNode { - final int _imm; - AsrIARM(Node asr, int imm) { - super(asr); - _inputs.pop(); - _imm = imm; - } - @Override public String op() { return "asri"; } - @Override public RegMask regmap(int i) { return arm.RMASK; } - @Override public RegMask outregmap() { return arm.WMASK; } - @Override public void encoding( Encoding enc ) { - short rd = enc.reg(this); - short rn = enc.reg(in(1)); - assert _imm > 0; - enc.add4(arm.imm_shift(arm.OPI_ASR,_imm, 0b111111, rn, rd)); - } - // General form: "asri rd = rs1 >> imm" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> #").p(_imm); - } -}
\ No newline at end of file diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java deleted file mode 100644 index b9ede544..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; - -// Jump on flags, uses flags -public class BranchARM extends IfNode implements MachNode, RIPRelSize { - String _bop; - BranchARM(IfNode iff, String bop ) { - super(iff); - _bop = bop; - } - @Override public String op() { return "j"+_bop; } - @Override public String label() { return op(); } - - @Override public void postSelect(CodeGen code) { - Node set = in(1); - if( set==null ) return; // Never-node cutout - Node cmp = set.in(1); - // Bypass an expected Set and just reference the cmp directly - if( set instanceof SetARM) - _inputs.set(1,cmp); - else - throw Utils.TODO(); - } - - @Override public RegMask regmap(int i) { assert i==1; return arm.FLAGS_MASK; } - @Override public RegMask outregmap() { return null; } - @Override public void negate() { _bop = negate(_bop); } - - @Override public void encoding( Encoding enc ) { - // Assuming that condition flags are already set. These flags are set - // by comparison (or sub). No need for regs because it uses flags - if( in(1)!=null ) { - // B.cond - enc.add4( arm.b_cond(arm.OP_BRANCH, 0, arm.make_condition(_bop)) ); - } else { - if( _bop=="!=" ) return; // Inverted, no code - enc.add4(arm.b(arm.OP_UJMP, 0)); - } - enc.jump(this,cproj(0)); - } - - // Delta is from opcode start - @Override public byte encSize(int delta) { - if( in(1)==null && _bop=="!=" ) return 0; // Inverted never-node, no code - if( -(1<<19) <= delta && delta < (1<<19) ) return 4; - // 2 word encoding needs a tmp register, must teach RA - throw Utils.TODO(); - } - - // Delta is from opcode start - @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - assert !( in(1)==null && _bop=="!=" ); // Inverted never-node, no code no patch - if( opLen==4 ) { - enc.patch4(opStart,arm.b_cond(arm.OP_BRANCH, delta, arm.make_condition(_bop))); - } else { - throw Utils.TODO(); - } - } - - @Override public void asm(CodeGen code, SB sb) { - if( in(1)!=null ) { // Never-node - String src = code.reg(in(1)); - if( src!="flags" ) sb.p(src).p(" "); - } else if( _bop=="!=" ) { - sb.p("never"); - return; - } - CFGNode prj = cproj(0).uctrlSkipEmpty(); - if( !prj.blockHead() ) prj = prj.cfg0(); - sb.p(label(prj)); - } - @Override public String comment() { return "L"+cproj(1)._nid; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java deleted file mode 100644 index a2cb9bba..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeFunPtr; -import com.seaofnodes.simple.util.SB; - -public class CallARM extends CallNode implements MachNode, RIPRelSize { - final TypeFunPtr _tfp; - final String _name; - CallARM(CallNode call, TypeFunPtr tfp) { - super(call); - _inputs.pop(); // Pop constant target - assert tfp.isConstant(); - _tfp = tfp; - FunNode fun = CodeGen.CODE.link(tfp); - _name = fun==null ? ((ExternNode)call.fptr())._extern : fun._name; // Can be null for extern calls - } - - @Override public String op() { return "call"; } - @Override public String label() { return op(); } - @Override public String name() { return _name; } - @Override public TypeFunPtr tfp() { return _tfp; } - @Override public RegMask regmap(int i) { return arm.callInMask(_tfp,i,fun()._maxArgSlot); } - @Override public RegMask outregmap() { return null; } - @Override public int nargs() { return nIns()-2; } // Minus control, memory, fptr - - @Override public void encoding( Encoding enc ) { - FunNode fun = CodeGen.CODE.link(_tfp); - if( fun==null ) enc.external(this,_name); - else enc.relo(this); - // BL - enc.add4(arm.b(arm.OP_CALL,0)); // Target patched at link time - } - - // Delta is from opcode start, but X86 measures from the end of the 5-byte encoding - @Override public byte encSize(int delta) { return 4; } - - // Delta is from opcode start - @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - enc.patch4(opStart,arm.b(arm.OP_CALL,delta)); - } - - @Override public void asm(CodeGen code, SB sb) { - sb.p(_name); - for( int i=0; i<nargs(); i++ ) - sb.p(code.reg(arg(i+2))).p(" "); - sb.unchar(2); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java deleted file mode 100644 index a3e1cf19..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class CallRRARM extends CallNode implements MachNode { - CallRRARM(CallNode call) { super(call); } - @Override public String op() { return "callr"; } - @Override public String label() { return op(); } - @Override public RegMask regmap(int i) { - return i==_inputs._len - ? arm.RMASK // Function call target - : arm.callInMask(tfp(),i,fun()._maxArgSlot); // Normal argument - } - @Override public RegMask outregmap() { return null; } - - @Override public void encoding( Encoding enc ) { - // Needs a register, typically a jump-and-link-register opcode - // blr - short self = enc.reg(this); - enc.add4(arm.blr(arm.OP_CALLRARM, self)); - } - - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(fptr())).p(" "); - for( int i=0; i<nargs(); i++ ) - sb.p(code.reg(arg(i))).p(" "); - sb.unchar(2); - } - -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java deleted file mode 100644 index 794ff0c9..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class CmpARM extends MachConcreteNode implements MachNode { - CmpARM( Node cmp ) { super(cmp); } - @Override public String op() { return "cmp"; } - @Override public RegMask regmap(int i) { return arm.RMASK; } - @Override public RegMask outregmap() { return arm.FLAGS_MASK; } - - // SUBS (shifted register) - @Override public void encoding( Encoding enc ) { arm.r_reg_subs(enc,this,arm.OP_CMP); } - - // General form: "cmp rs1, rs2" - @Override public void asm(CodeGen code, SB sb) { - String dst = code.reg(this); - if( dst!="flags" ) sb.p(dst).p(" = "); - sb.p(code.reg(in(1))).p(", ").p(code.reg(in(2))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java deleted file mode 100644 index 67ffc27b..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -// compare instruction on float regs input(D-registers) -public class CmpFARM extends MachConcreteNode implements MachNode{ - CmpFARM(Node cmp) {super(cmp);} - @Override public String op() { return "cmpf"; } - @Override public RegMask regmap(int i) { return arm.DMASK; } - @Override public RegMask outregmap() { return arm.FLAGS_MASK; } - - // FCMP - @Override public void encoding( Encoding enc ) { arm.f_cmp(enc,this); } - // General form: "cmp d0, d1" - @Override public void asm(CodeGen code, SB sb) { - String dst = code.reg(this); - if( dst!="flags" ) sb.p(dst).p(" = "); - sb.p(code.reg(in(1))).p(", ").p(code.reg(in(2))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java deleted file mode 100644 index f84a1649..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -// Compare with immediate. -// CMP (immediate) - SUBS -public class CmpIARM extends MachConcreteNode implements MachNode { - final int _imm; - final String _bop; - CmpIARM(BoolNode bool, int imm) { - super(bool); - _inputs.pop(); - _bop = bool.op(); - _imm = imm; - } - CmpIARM( CmpIARM cmp ) { - super((Node)null); - addDef(null); - addDef(cmp.in(1)); - _bop = cmp._bop; - _imm = cmp._imm; - } - @Override public String op() { return _imm==0 ? "test" : "cmp"; } - @Override public RegMask regmap(int i) { return arm.RMASK; } - @Override public RegMask outregmap() { return arm.FLAGS_MASK; } - @Override public boolean isClone() { return true; } - @Override public Node copy() { return new CmpIARM(this); } - - @Override public void encoding( Encoding enc ) { arm.imm_inst_subs(enc,in(1),in(1), arm.OPI_CMP,_imm); } - - // General form: "cmp rs1, 1" - @Override public void asm(CodeGen code, SB sb) { - String dst = code.reg(this); - if( dst!="flags" ) sb.p(dst).p(" = "); - sb.p(code.reg(in(1))); - if( _imm != 0 ) sb.p(", #").p(_imm); - } - } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java deleted file mode 100644 index de254eeb..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class DivARM extends MachConcreteNode implements MachNode { - DivARM( Node div ) { super(div); } - @Override public String op() { return "div"; } - @Override public RegMask regmap(int i) { return arm.RMASK; } - @Override public RegMask outregmap() { return arm.WMASK; } - // SDIV - @Override public void encoding( Encoding enc ) { arm.madd(enc,this,arm.OP_DIV,3); } - // General form: "div dst /= src" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2))); - } - -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java deleted file mode 100644 index 813bebe4..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class DivFARM extends MachConcreteNode implements MachNode { - DivFARM( Node divf) { super(divf); } - @Override public String op() { return "divf"; } - @Override public RegMask regmap(int i) { return arm.DMASK; } - @Override public RegMask outregmap() { return arm.DMASK; } - - // FDIV (scalar) - @Override public void encoding( Encoding enc ) { arm.f_scalar(enc,this,arm.OPF_DIV); } - // General form: "VDIF = dst /= src" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java deleted file mode 100644 index b47b65ad..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.ConstantNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.type.TypeFloat; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; - -//FMOV (scalar, immediate) -//Floating-point move immediate. -public class FloatARM extends ConstantNode implements MachNode, RIPRelSize { - FloatARM(ConstantNode con) { super(con); } - @Override public String op() { return "ld8"; } - @Override public RegMask regmap(int i) { return null; } - @Override public RegMask outregmap() { return arm.DMASK; } - @Override public boolean isClone() { return true; } - @Override public FloatARM copy() { return new FloatARM(this); } - - @Override public void encoding( Encoding enc ) { - enc.largeConstant(this,_con,0,-1/*TODO: ARM-style ELF patching*/); - short dst = (short)(enc.reg(this) - arm.D_OFFSET); - enc.add4(arm.load_pc(arm.OPF_ARM, 0, dst)); - } - - // Delta is from opcode start. - @Override public byte encSize(int delta) { return 4; } - - // Delta is from opcode start - @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - short dst = (short)(enc.reg(this) - arm.D_OFFSET); - enc.patch4(opStart, arm.load_pc(arm.OPF_ARM, delta, dst)); - } - - // Human-readable form appended to the SB. Things like the encoding, - // indentation, leading address or block labels not printed here. - // Just something like "ld4\tR17=[R18+12] // Load array base". - // General form: "op\tdst=src+src" - @Override public void asm(CodeGen code, SB sb) { - _con.print(sb.p(code.reg(this)).p(" = #")); - } - -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java deleted file mode 100644 index 44a417f9..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.FunNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; - -public class FunARM extends FunNode implements MachNode { - FunARM(FunNode fun) { super(fun); } - @Override public void computeFrameAdjust(CodeGen code, int maxReg) { - super.computeFrameAdjust(code,maxReg); - if( _hasCalls ) // If non-leaf, pad to 16b - _frameAdjust = ((_frameAdjust+8) & -16); - } - @Override public void encoding( Encoding enc ) { - int sz = _frameAdjust; - if( sz == 0 ) return; // Skip if no frame adjust - if( sz >= 1L<<12 ) throw Utils.TODO(); - enc.add4(arm.imm_inst(arm.OPI_ADD, -sz&0xFFF, arm.RSP, arm.RSP)); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java deleted file mode 100644 index f1fa162f..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class I2F8ARM extends MachConcreteNode implements MachNode { - I2F8ARM(Node i2f8) { super(i2f8); } - @Override public String op() { return "cvt"; } - @Override public RegMask regmap(int i) { return arm.RMASK; } - @Override public RegMask outregmap() { return arm.DMASK; } - @Override public void encoding( Encoding enc ) { - // SCVTF - short self = (short)(enc.reg(this )-arm.D_OFFSET); - short reg1 = enc.reg(in(1)); - int body = arm.float_cast(arm.OP_FLOAT_C, 1, reg1, self); - enc.add4(body); - } - - // General form: "i2f8 (flt)int_value" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p("(flt)").p(code.reg(in(1))); - } - -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java deleted file mode 100644 index ee74b95e..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.ConstantNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.util.SB; - -// Integer constants -public class IntARM extends ConstantNode implements MachNode { - IntARM( ConstantNode con ) { super(con); } - - @Override public String op() { return "ldi"; } - @Override public RegMask regmap(int i) { return null; } - @Override public RegMask outregmap() { return arm.WMASK; } - - @Override public boolean isClone() { return true; } - @Override public IntARM copy() { return new IntARM(this); } - - @Override public void encoding( Encoding enc ) { - short self = enc.reg(this); - long x = _con==Type.NIL ? 0 : ((TypeInteger)_con).value(); - int nb0 = 0; - int nb1 = 0; - // Count number of 0000 and FFFF blocks - for (int i=0; i<64; i+=16) { - int block = (int)(x >> i) & 0xFFFF; - if (block == 0) nb0++; - if (block == 0xFFFF) nb1++; - } - int pattern; - int op; - if(nb0 >= nb1) { - // More 0 blocks then F blocks, use movz - pattern = 0; - op = arm.OP_MOVZ; - } else { - // More F blocks then 0 blocks, use movn - pattern = 0xFFFF; - op = arm.OP_MOVN; - } - int invert = pattern; - for (int i=0; i<4; i++) { - int block = (int)x & 0xFFFF; - x >>= 16; - if (block != pattern) { - enc.add4(arm.mov(op, i, block ^ invert, self)); - op = arm.OP_MOVK; - invert = 0; - } - } - if (op != arm.OP_MOVK) { - // All blocks are the same, special case - enc.add4(arm.mov(op, 0, 0, self)); - } - } - - // Human-readable form appended to the SB. Things like the encoding, - // indentation, leading address or block labels not printed here. - // Just something like "ld4\tR17=[R18+12] // Load array base". - // General form: "op\tdst=src+src" - @Override public void asm(CodeGen code, SB sb) { - String reg = code.reg(this); - _con.print(sb.p(reg).p(" = #")); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java deleted file mode 100644 index 1e5d2094..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.LoadNode; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.type.TypeFloat; -import com.seaofnodes.simple.util.SB; - -// Load memory addressing on ARM -// Support imm, reg(direct), or reg+off(indirect) addressing -// Base = base - base pointer, offset is added to base -// idx = null -// off = off - offset added to base - -public class LoadARM extends MemOpARM { - LoadARM(LoadNode ld, Node base, Node idx, int off) { - super(ld, base, idx, off, 0); - } - @Override public String op() { return "ld"+_sz; } - @Override public RegMask outregmap() { return arm.MEM_MASK; } - - private static final int[] OP_LOADS = new int[]{ arm.OP_LOAD_IMM_8, arm.OP_LOAD_IMM_16, arm.OP_LOAD_IMM_32, arm.OP_LOAD_IMM_64, }; - private int imm_op() { - return _declaredType == TypeFloat.F32 ? arm.OPF_LOAD_IMM_32 - : _declaredType == TypeFloat.F64 ? arm.OPF_LOAD_IMM_64 - : OP_LOADS[_declaredType.log_size()]; - } - - private static final int[] OP_LOAD_RS = new int[]{ arm.OP_LOAD_R_8 , arm.OP_LOAD_R_16 , arm.OP_LOAD_R_32 , arm.OP_LOAD_R_64, }; - - // ldr(immediate - unsigned offset) | ldr(register) - @Override public void encoding( Encoding enc ) { - ldst_encode(enc, imm_op(), OP_LOAD_RS[_declaredType.log_size()], this, size()); - } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(","); - asm_address(code,sb); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java deleted file mode 100644 index 80ce493f..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -// Logical Shift Left (register) -public class LslARM extends MachConcreteNode implements MachNode { - LslARM(Node asr) {super(asr);} - @Override public String op() { return "shr"; } - @Override public RegMask regmap(int i) { return arm.RMASK; } - @Override public RegMask outregmap() { return arm.WMASK; } - - @Override public void encoding( Encoding enc ) { arm.shift_reg(enc,this,arm.OP_LSL); } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" << ").p(code.reg(in(2))); - } - -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java deleted file mode 100644 index 7fe16cbe..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.Encoding; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.MachConcreteNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.util.SB; - -public class LslIARM extends MachConcreteNode implements MachNode { - final int _imm; - LslIARM(Node lsl, int imm) { - super(lsl); - _inputs.pop(); - _imm = imm; - } - @Override public String op() { return "lsli"; } - @Override public RegMask regmap(int i) { return arm.RMASK; } - @Override public RegMask outregmap() { return arm.WMASK; } - @Override public void encoding( Encoding enc ) { - short rd = enc.reg(this); - short rn = enc.reg(in(1)); - assert _imm > 0; - // UBFM <Xd>, <Xn>, #(-<shift> MOD 64), #(63-<shift>) - // immr must be (-<shift> MOD 64) = 64 - shift - enc.add4(arm.imm_shift(arm.OPI_LSL, 64 - _imm, (64 - _imm) - 1, rn, rd)); - } - // General form: "lsli rd = rs1 << imm" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" << #").p(_imm); - } -}
\ No newline at end of file diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java deleted file mode 100644 index fc005e04..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -// Logical Shift Right (register) -public class LsrARM extends MachConcreteNode implements MachNode { - LsrARM(Node asr) {super(asr);} - @Override public String op() { return "shr"; } - @Override public RegMask regmap(int i) { return arm.RMASK; } - @Override public RegMask outregmap() { return arm.WMASK; } - - @Override public void encoding( Encoding enc ) { arm.shift_reg(enc,this,arm.OP_LSR); } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >>> ").p(code.reg(in(2))); - } - -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java deleted file mode 100644 index 78844b0a..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -// logical right shift immediate -public class LsrIARM extends MachConcreteNode implements MachNode { - final int _imm; - LsrIARM(Node lsr, int imm) { - super(lsr); - _inputs.pop(); - _imm = imm; - } - @Override public String op() { return "lsri"; } - @Override public RegMask regmap(int i) { return arm.RMASK; } - @Override public RegMask outregmap() { return arm.WMASK; } - @Override public void encoding( Encoding enc ) { - short rd = enc.reg(this); - short rn = enc.reg(in(1)); - assert _imm > 0; - enc.add4(arm.imm_shift(arm.OPI_LSR,_imm, 0b111111, rn,rd)); - } - // General form: "lsri rd = rs1 >>> imm" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >>> #").p(_imm); - } -}
\ No newline at end of file diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java deleted file mode 100644 index e877931d..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import java.lang.StringBuilder; -import java.util.BitSet; - -public abstract class MemOpARM extends MemOpNode implements MachNode { - final int _off; // Limit 9 bits sized, or (13 bits<<logsize) unsigned - final int _imm; // Limit ? bits - final char _sz = (char)('0'+(1<<_declaredType.log_size())); - MemOpARM(MemOpNode mop, Node ptr, Node idx, int off, int imm) { - super(mop,mop); - assert ptr._type instanceof TypeMemPtr && !(ptr instanceof AddNode); - assert ptr() == ptr; - _inputs.setX(2, ptr); - _inputs.setX(3, idx); - _off = off; - _imm = imm; - } - - // Store-based flavors have a value edge - MemOpARM( MemOpNode mop, Node ptr, Node idx, int off, int imm, Node val ) { - this(mop,ptr,idx,off,imm); - _inputs.setX(4,val); - } - - @Override public String label() { return op();} - Node val() { return in(4); } // Only for stores - - @Override public StringBuilder _printMach(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); } - - @Override public Type compute() { throw Utils.TODO(); } - @Override public Node idealize() { throw Utils.TODO(); } - - int size() { return 1<<_declaredType.log_size(); } - - // Wider mask to store both GPRs and FPRs - @Override public RegMask regmap(int i) { - // 0 - ctrl - if( i==1 ) return null; // memory - if( i==2 ) return arm.RMASK; // ptr/base - if( i==3 ) return arm.RMASK; // off/index - if( i==4 ) return arm.RMASK; // value - return null; // Anti-dependence - } - - // Shared encoding for loads and stores(int and float) - public void ldst_encode( Encoding enc, int opcode_imm, int opcode_reg, Node xval, int size) { - short ptr = enc.reg(ptr()); - short off = enc.reg(off()); - short val = enc.reg(xval); - int body = off() == null - ? arm.load_str_imm(opcode_imm, _off, ptr, val, size) - : arm.indr_adr(opcode_reg, off, arm.STORE_LOAD_OPTION.SXTX, 0, ptr, val); - enc.add4(body); - } - - SB asm_address(CodeGen code, SB sb) { - sb.p("[").p(code.reg(ptr())).p("+"); - if( off() != null ) sb.p(code.reg(off())); - else sb.p(_off); - return sb.p("]"); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java deleted file mode 100644 index a23ec75b..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -// mulh signed multiply instruction(no-imm form) -public class MulARM extends MachConcreteNode implements MachNode { - MulARM(Node mul) {super(mul);} - @Override public String op() { return "mul"; } - @Override public RegMask regmap(int i) { return arm.RMASK; } - @Override public RegMask outregmap() { return arm.WMASK; } - - @Override public void encoding( Encoding enc ) { arm.madd(enc,this,arm.OP_MUL,31); } - // General form: "rd = rs1 * rs2" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java deleted file mode 100644 index 73ebbf00..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class MulFARM extends MachConcreteNode implements MachNode{ - MulFARM(Node mulf) {super(mulf);} - @Override public String op() { return "mulf"; } - @Override public RegMask regmap(int i) { return arm.DMASK; } - @Override public RegMask outregmap() { return arm.DMASK; } - @Override public void encoding( Encoding enc ) { arm.f_scalar(enc,this,arm.OPF_MUL); } - // Default on double precision for now(64 bits) - // General form: "VMUL.f32 rd = src1 * src2 - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java deleted file mode 100644 index 881b41f9..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.node.MachConcreteNode; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.util.SB; - -public class NegARM extends MachConcreteNode implements MachNode { - NegARM(Node sub) { - super(sub); - //_inputs.pop(); - } - @Override public String op() { return "neg"; } - @Override public RegMask regmap(int i) { return arm.RMASK; } - @Override public RegMask outregmap() { return arm.WMASK; } - @Override public void encoding( Encoding enc ) { - // reverse subtract with immediate 0 - arm.imm_inst(enc,this, in(1), 0b00100110, 0); - } - // General form: "subi rd = rs1 - imm" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = -").p(code.reg(in(1))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java deleted file mode 100644 index c1aad15f..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; - -public class NewARM extends NewNode implements MachNode, RIPRelSize { - // A pre-zeroed chunk of memory. - NewARM(NewNode nnn) { super(nnn); } - @Override public void encoding( Encoding enc ) { - // BL(branch with link) - enc.external(this,"calloc"). - add4(arm.mov(arm.OP_MOVZ, 0, 1, 0)). // movz x0,#1 - add4(arm.b(arm.OP_CALL, 0)); - } - - // Patch is for running "new" in a JIT. - // Delta is from opcode start - @Override public byte encSize(int delta) { return 4*2; } - - // Patch is for running "new" in a JIT. - // Delta is from opcode start - @Override public void patch(Encoding enc, int opStart, int opLen, int delta ) { - // Negative patches are JIT emulator targets. - if( opStart+delta < 0 ) { - enc.patch4(opStart+4, arm.b_calloc(arm.OP_CALL, delta-4)); - } else { - throw Utils.TODO(); - } - } - // General form: "alloc #bytes PC" - @Override public void asm(CodeGen code, SB sb) { - sb.p("ldi x0=#1\n"); - sb.p("call #calloc"); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java deleted file mode 100644 index 027a8128..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.node.MachConcreteNode; -import com.seaofnodes.simple.util.SB; - -public class NotARM extends MachConcreteNode implements MachNode{ - NotARM(NotNode not) {super(not);} - @Override public String op() { return "not"; } - @Override public RegMask regmap(int i) { return arm.RMASK; } - @Override public RegMask outregmap() { return arm.WMASK; } - @Override public RegMask killmap() { return arm.FLAGS_MASK; } - @Override public void encoding( Encoding enc ) { - // subs xzr, rs, #0 - // cset rd, eq // Set rd to 1 if rs == 0 (equal), else 0 - // subtracting zero from rs will just yield rs, it sets the zero flag and then it's used in cset - short self = enc.reg(this ); - short reg1 = enc.reg(in(1)); - int subs = arm.imm_inst(arm.OP_SUBS, 0, reg1, self); - enc.add4(subs); - int cset = arm.cond_set(arm.OP_CSET, 31, arm.COND.EQ, 63, reg1); - enc.add4(cset); - } - - @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java deleted file mode 100644 index e8245749..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -// Not using the ORRS variant. -public class OrARM extends MachConcreteNode implements MachNode { - OrARM(Node and) { super(and); } - @Override public String op() { return "or"; } - @Override public String glabel() { return "|"; } - @Override public RegMask regmap(int i) { return arm.RMASK; } - @Override public RegMask outregmap() { return arm.WMASK; } - @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,arm.OP_OR); } - // General form: #rd = rs1 & rs2 - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" | ").p(code.reg(in(2))); - } - -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java deleted file mode 100644 index a48caa51..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class OrIARM extends MachConcreteNode implements MachNode { - final int _imm; - OrIARM(Node or, int imm) { - super(or); - _inputs.pop(); - _imm = imm; - } - @Override public String op() { return "ori"; } - @Override public RegMask regmap(int i) { return arm.RMASK; } - @Override public RegMask outregmap() { return arm.WMASK; } - @Override public void encoding( Encoding enc ) { - arm.imm_inst_n(enc, this, in(1), arm.OPI_OR, _imm); - } - // General form: "ori rd = rs1 | imm" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" | #").p(_imm); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java deleted file mode 100644 index b419fe0a..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.Encoding; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.ParmNode; - -public class ParmARM extends ParmNode implements MachNode { - final RegMask _rmask; - ParmARM(ParmNode parm) { - super(parm); - _rmask = arm.callInMask(fun().sig(),_idx,0); - } - @Override public RegMask regmap(int i) { return null; } - @Override public RegMask outregmap() { return _rmask; } - @Override public void encoding( Encoding enc ) { } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java deleted file mode 100644 index 4b7d6f13..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.Encoding; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.ProjNode; - -public class ProjARM extends ProjNode implements MachNode { - ProjARM( ProjNode p ) { super(p); } - @Override public RegMask regmap(int i) { return null; } - @Override public RegMask outregmap() { - return ((MachNode)in(0)).outregmap(_idx); - } - @Override public void encoding( Encoding enc ) { } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java deleted file mode 100644 index 2136ebf5..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; - -public class RetARM extends ReturnNode implements MachNode { - RetARM(ReturnNode ret, FunNode fun) { super(ret, fun); fun.setRet(this); } - @Override public void encoding( Encoding enc ) { - int frameAdjust = ((FunARM)fun())._frameAdjust; - if( frameAdjust > 0 ) - enc.add4(arm.imm_inst(arm.OPI_ADD, (frameAdjust*-8)&0xFFF, arm.RSP, arm.RSP)); - enc.add4(arm.ret(arm.OP_RET)); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java deleted file mode 100644 index 01b46b8d..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -// conditional set? -// e.g CSET, stores flag info into GPRS after compare -public class SetARM extends MachConcreteNode implements MachNode { - final String _bop; // One of <,<=,== - SetARM(Node cmp, String bop) { - super(cmp); - _inputs.setLen(1); // Pop the cmp inputs - // Replace with the matched cmp - _inputs.push(cmp); - _bop = bop; - } - @Override public String op() { return "set"+_bop; } - @Override public RegMask regmap(int i) { assert i==1; return arm.FLAGS_MASK; } - @Override public RegMask outregmap() { return arm.WMASK; } - - @Override public void encoding( Encoding enc ) { - int body = arm.cset(arm.OP_CSET,0b11111, arm.make_condition(_bop), 0b011111, enc.reg(this)); - enc.add4(body); - } - - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)); - String src = code.reg(in(1)); - if( src!="flags" ) sb.p(" = ").p(src); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java deleted file mode 100644 index 5af45892..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.FunNode; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.node.SplitNode; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; - -public class SplitARM extends SplitNode { - SplitARM(String kind, byte round) { super(kind,round,new Node[2]);} - @Override public RegMask regmap(int i) { return arm.SPLIT_MASK; } - @Override public RegMask outregmap() { return arm.SPLIT_MASK; } - - @Override public void encoding( Encoding enc ) { - short dst = enc.reg(this ); - short src = enc.reg(in(1)); - - boolean dstX = dst >= arm.D_OFFSET; - boolean srcX = src >= arm.D_OFFSET; - - if (dst >= arm.MAX_REG) { - // store to sp - if(src >= arm.MAX_REG) { - throw Utils.TODO(); - } - int off = enc._fun.computeStackOffset(enc._code,dst); - if( srcX ) src -= arm.D_OFFSET; - enc.add4(arm.load_str_imm(arm.OP_STORE_IMM_64, off, arm.RSP, src, 8)); - return; - } - - if(src >= arm.MAX_REG) { - // Load from SP - int off = enc._fun.computeStackOffset(enc._code,src); - if( dstX ) dst -= arm.D_OFFSET; - enc.add4(arm.load_str_imm(arm.OP_LOAD_IMM_64, off, arm.RSP, dst, 8)); - return; - } - - // pick opcode based on regs - if(!dstX && !srcX) { - // GPR->GPR - enc.add4(arm.mov_reg(arm.OP_MOV, src, dst)); - } else if(dstX && srcX) { - // FPR->FPR - // fmov reg - enc.add4(arm.f_mov_reg(arm.OP_FMOV_REG, src - arm.D_OFFSET,dst - arm.D_OFFSET)); - } else if(!srcX && dstX) { - // GPR->FPR - // FMOV(general) 64 bits to DOUBLE-PRECISION - enc.add4(arm.f_mov_general(arm.OP_FMOV, 0b01, 0, 0b111, src, dst - arm.D_OFFSET)); - } else if( srcX && !dstX) { - //FPF->GPR - // FMOV(general) DOUBLE-PRECISION to 64 bits - enc.add4(arm.f_mov_general(arm.OP_FMOV, 0b01, 0, 0b110, src - arm.D_OFFSET, dst)); - } - } - - // General form: "mov dst = src" - @Override public void asm(CodeGen code, SB sb) { - FunNode fun = code._encoding==null ? null : code._encoding._fun; - sb.p(code.reg(this,fun)).p(" = ").p(code.reg(in(1),fun)); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java deleted file mode 100644 index 91385ac9..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.node.StoreNode; -import com.seaofnodes.simple.type.TypeFloat; -import com.seaofnodes.simple.util.SB; - -// Store memory addressing on ARM -// Support imm, reg(direct), or reg+off(indirect) addressing -// base - base pointer, offset is added to base -// null - index never allowed (no [reg+reg] mode) -// off - offset added to base -// imm - immediate value to store, only if val is null -// val - value to store or null - -//e.g s.cs[0] = 67; // C -// base = s.cs, off = 4, imm = 67, val = null -public class StoreARM extends MemOpARM { - StoreARM(StoreNode st, Node base, Node idx, int off, Node val) { - super(st, base, idx, off, 0, val); - } - @Override public String op() { return "st"+_sz; } - @Override public RegMask outregmap() { return null; } - - private static final int[] OP_STORES = new int[]{ arm.OP_STORE_IMM_8, arm.OP_STORE_IMM_16, arm.OP_STORE_IMM_32, arm.OP_STORE_IMM_64, }; - private int imm_op() { - return _declaredType == TypeFloat.F32 ? arm.OPF_STORE_IMM_32 - : _declaredType == TypeFloat.F64 ? arm.OPF_STORE_IMM_64 - : OP_STORES[_declaredType.log_size()]; - } - - private static final int[] OP_STORE_RS = new int[]{ arm.OP_STORE_R_8, arm.OP_STORE_R_16, arm.OP_STORE_R_32, arm.OP_STORE_R_64, }; - - @Override public void encoding( Encoding enc ) { - ldst_encode(enc, imm_op(), OP_STORE_RS[_declaredType.log_size()], val(), size()); - } - @Override public void asm(CodeGen code, SB sb) { - asm_address(code,sb).p(","); - if( val()==null ) sb.p("#").p(_imm); - else sb.p(code.reg(val())); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java deleted file mode 100644 index e3b95e50..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class SubARM extends MachConcreteNode implements MachNode { - SubARM(Node sub) { super(sub); } - @Override public String op() { return "sub"; } - @Override public RegMask regmap(int i) { return arm.RMASK; } - @Override public RegMask outregmap() { return arm.WMASK; } - - @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,arm.OP_SUB); } - - // General form: "sub # rd = rs1 - rs2" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - ").p(code.reg(in(2))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java deleted file mode 100644 index a252e542..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class SubFARM extends MachConcreteNode implements MachNode { - SubFARM(Node subf) {super(subf);} - @Override public String op() { return "subf"; } - @Override public RegMask regmap(int i) { return arm.DMASK; } - @Override public RegMask outregmap() { return arm.DMASK; } - @Override public void encoding( Encoding enc ) { arm.f_scalar(enc,this,arm.OPF_SUB); } - // Default on double precision for now(64 bits) - // General form: "vsub.f32 rd = src1 + src2 - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - ").p(code.reg(in(2))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java deleted file mode 100644 index b50107c9..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.node.MachConcreteNode; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.util.SB; - -public class SubIARM extends MachConcreteNode implements MachNode { - final int _imm; - SubIARM(Node sub, int imm) { - super(sub); - _inputs.pop(); - _imm = imm; - } - @Override public String op() { return _imm == -1 ? "dec" : "subi"; } - @Override public RegMask regmap(int i) { return arm.RMASK; } - @Override public RegMask outregmap() { return arm.WMASK; } - - @Override public void encoding( Encoding enc ) { - arm.imm_inst(enc,this, in(1), arm.OPI_SUB,_imm); - } - - // General form: "subi rd = rs1 - imm" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - #").p(_imm); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java deleted file mode 100644 index 29d490ef..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.ConstantNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.type.TypeFunPtr; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; - -public class TFPARM extends ConstantNode implements MachNode, RIPRelSize { - TFPARM( ConstantNode con ) { super(con); } - @Override public String op() { return "ldx"; } - @Override public RegMask regmap(int i) { return null; } - @Override public RegMask outregmap() { return arm.WMASK; } - @Override public boolean isClone() { return true; } - @Override public TFPARM copy() { return new TFPARM(this); } - @Override public void encoding( Encoding enc ) { - enc.relo(this); - short self = enc.reg(this); - // adrp x0, 0 - int adrp = arm.adrp(1,0, arm.OP_ADRP, 0,self); - // add x0, x0, 0 - enc.add4(adrp); - arm.imm_inst(enc,arm.OPI_ADD,0, 0); - } - - @Override public byte encSize(int delta) { - return 8; - } - - // Delta is from opcode start - @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - short rpc = enc.reg(this); - if(opLen == 8 ) { - // ARM encoding delta is from PC & 0xFFF - int target = opStart+delta; - int base = opStart & ~0xFFF; - delta = target-base; - int adrp_delta = delta >> 12; - // patch upper 20 bits via adrp - enc.patch4(opStart, arm.adrp(1, adrp_delta & 0b11, 0b10000, adrp_delta >> 2, rpc)); - // low 12 bits via add - enc.patch4(opStart+4, arm.imm_inst_l(arm.OPI_ADD, delta & 0xfff, rpc)); - } else { - // should not happen as one instruction is 4 byte, and TFP arm encodes 2. - throw Utils.TODO(); - } - } - - @Override public void asm(CodeGen code, SB sb) { - String reg = code.reg(this); - _con.print(sb.p(reg).p(" #")); - } - @Override public boolean eq(Node n) { return this==n; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java deleted file mode 100644 index cdcaa433..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.ConstantNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.type.TypeMemPtr; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; - -public class TMPARM extends ConstantNode implements MachNode, RIPRelSize { - TMPARM( ConstantNode con ) { super(con); } - @Override public String op() { return "ldp"; } - @Override public RegMask regmap(int i) { return null; } - @Override public RegMask outregmap() { return arm.WMASK; } - @Override public boolean isClone() { return true; } - @Override public TMPARM copy() { return new TMPARM(this); } - @Override public void encoding( Encoding enc ) { - enc.largeConstant(this,((TypeMemPtr)_con)._obj,0,-1); - short dst = enc.reg(this); - // adrp x0, 0 - enc.add4(arm.adrp(1,0, arm.OP_ADRP, 0,dst)); - // add x0, x0, 0 - arm.imm_inst(enc,arm.OPI_ADD,0, dst); - } - - @Override public byte encSize(int delta) { - return 8; - } - - // Delta is from opcode start - @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - short dst = enc.reg(this); - if(opLen == 8 ) { - // ARM encoding delta is from PC & 0xFFF - int target = opStart+delta; - int base = opStart & ~0xFFF; - delta = target-base; - int adrp_delta = delta >> 12; - // patch upper 20 bits via adrp - enc.patch4(opStart, arm.adrp(1, adrp_delta & 0b11, 0b10000, adrp_delta >> 2, dst)); - // low 12 bits via add - enc.patch4(opStart+4, arm.imm_inst_l(arm.OPI_ADD, delta & 0xfff, dst)); - } else { - throw Utils.TODO(); - } - } - - @Override public void asm(CodeGen code, SB sb) { - String reg = code.reg(this); - _con.print(sb.p(reg).p(" #")); - } - @Override public boolean eq(Node n) { return this==n; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java deleted file mode 100644 index 2dd5fd6b..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import java.util.BitSet; - -// unconditional jump -public class UJmpARM extends CFGNode implements MachNode, RIPRelSize { - UJmpARM() { } - @Override public String op() { return "jmp"; } - @Override public String label() { return op(); } - @Override public StringBuilder _print1( StringBuilder sb, BitSet visited ) { - return sb.append("jmp "); - } - @Override public RegMask regmap(int i) {return null; } - @Override public RegMask outregmap() { return null; } - @Override public Type compute() { throw Utils.TODO(); } - @Override public Node idealize() { throw Utils.TODO(); } - @Override public void encoding( Encoding enc ) { - enc.jump(this,uctrl()); - int body = arm.b(arm.OP_UJMP, 0); - enc.add4(body); - } - - // Delta is from opcode start - @Override public byte encSize(int delta) { - if( -(1<<26) <= delta && delta < (1<<26) ) return 4; - // 2 word encoding needs a tmp register, must teach RA - throw Utils.TODO(); - } - - // Delta is from opcode start - @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - if( opLen==4 ) { - enc.patch4(opStart,arm.b(arm.OP_UJMP, delta)); - } else { - throw Utils.TODO(); - } - } - - @Override public void asm(CodeGen code, SB sb) { - CFGNode target = uctrl(); - //assert target.nOuts() > 1; // Should optimize jmp to empty targets - sb.p(label(target)); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java deleted file mode 100644 index 52bda021..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class XorARM extends MachConcreteNode implements MachNode{ - XorARM(Node xor) {super(xor);} - @Override public String op() { return "xor"; } - @Override public String glabel() { return "^"; } - @Override public RegMask regmap(int i) { return arm.RMASK; } - @Override public RegMask outregmap() { return arm.WMASK; } - @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,arm.OP_XOR); } - // General form: "rd = x1 ^ x2" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ^ ").p(code.reg(in(2))); - } - -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java deleted file mode 100644 index 23b29644..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.node.MachConcreteNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.util.SB; - -public class XorIARM extends MachConcreteNode implements MachNode { - final int _imm; - XorIARM(Node xor, int imm) { - super(xor); - _inputs.pop(); - _imm = imm; - } - - @Override public String op() { return "xori"; } - @Override public RegMask regmap(int i) { return arm.RMASK; } - @Override public RegMask outregmap() { return arm.WMASK; } - - // General form: "xori rd = rs1 ^ imm" - @Override public void encoding( Encoding enc ) { arm.imm_inst_n(enc,this, in(1), arm.OPI_XOR,_imm); } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ^ #").p(_imm); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java deleted file mode 100644 index 9dbf724d..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java +++ /dev/null @@ -1,816 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Utils; - -public class arm extends Machine { - public arm( CodeGen code ) { - if( !"SystemV".equals(code._callingConv) ) - throw new IllegalArgumentException("Unknown calling convention "+code._callingConv); - } - - // ARM64 - @Override public String name() { return "arm"; } - - // GPR(S) - public static final int X0 = 0, X1 = 1, X2 = 2, X3 = 3, X4 = 4, X5 = 5, X6 = 6, X7 = 7; - public static final int X8 = 8, X9 = 9, X10 = 10, X11 = 11, X12 = 12, X13 = 13, X14 = 14, X15 = 15; - public static final int X16 = 16, X17 = 17, X18 = 18, X19 = 19, X20 = 20, X21 = 21, X22 = 22, X23 = 23; - public static final int X24 = 24, X25 = 25, X26 = 26, X27 = 27, X28 = 28, X29 = 29, X30 = 30, RSP = 31; - - // Floating point registers - public static final int D0 = 32, D1 = 33, D2 = 34, D3 = 35, D4 = 36, D5 = 37, D6 = 38, D7 = 39; - public static final int D8 = 40, D9 = 41, D10 = 42, D11 = 43, D12 = 44, D13 = 45, D14 = 46, D15 = 47; - public static final int D16 = 48, D17 = 49, D18 = 50, D19 = 51, D20 = 52, D21 = 53, D22 = 54, D23 = 55; - public static final int D24 = 56, D25 = 57, D26 = 58, D27 = 59, D28 = 60, D29 = 61, D30 = 62, D31 = 63; - - static final int FLAGS = 64; - static final int MAX_REG = 65; - public static final int D_OFFSET = 32; - - static final String[] REGS = new String[] { - "X0", "X1", "X2", "X3", "X4", "X5", "X6", "X7", - "X8", "X9", "X10", "X11", "X12", "X13", "X14", "X15", - "X16", "X17", "X18", "X19", "X20", "X21", "X22", "X23", - "X24", "X25", "X26", "X27", "X28", "X29", "RPC", "RSP", - "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", - "D8", "D9", "D10", "D11", "D12", "D13", "D14", "D15", - "D16", "D17", "D18", "D19", "D20", "D21", "D22", "D23", - "D24", "D25", "D26", "D27", "D28", "D29", "D30", "D31", - "flags" - }; - @Override public String[] regs() { return REGS; } - - // from (x0-x30) - // General purpose register mask: pointers and ints, not floats - static final long RD_BITS = 0xFFFFFFFFL; - static final RegMask RMASK = new RegMask(RD_BITS); - static final long WR_BITS = 0x7FFFFFFFL; // All the GPRs, not RSP - static final RegMask WMASK = new RegMask(WR_BITS); - - // Float mask from(d0–d31) - static final long FP_BITS = 0xFFFFFFFFL<<D0; - static final RegMask DMASK = new RegMask(FP_BITS); - - // Load/store mask; both GPR and FPR - static final RegMask MEM_MASK = new RegMask(WR_BITS | FP_BITS); - - static final RegMask SPLIT_MASK = new RegMask(WR_BITS | FP_BITS, -2L/*skip flags*/); - - static final RegMask FLAGS_MASK = new RegMask(FLAGS); - - // Arguments masks - static final RegMask X0_MASK = new RegMask(X0); - static final RegMask X1_MASK = new RegMask(X1); - static final RegMask X2_MASK = new RegMask(X2); - static final RegMask X3_MASK = new RegMask(X3); - static final RegMask X4_MASK = new RegMask(X4); - static final RegMask X5_MASK = new RegMask(X5); - static final RegMask X6_MASK = new RegMask(X6); - static final RegMask X7_MASK = new RegMask(X7); - - // Arguments(float) masks - static final RegMask D0_MASK = new RegMask(D0); - static final RegMask D1_MASK = new RegMask(D1); - static final RegMask D2_MASK = new RegMask(D2); - static final RegMask D3_MASK = new RegMask(D3); - static final RegMask D4_MASK = new RegMask(D4); - static final RegMask D5_MASK = new RegMask(D5); - static final RegMask D6_MASK = new RegMask(D6); - static final RegMask D7_MASK = new RegMask(D7); - - // major opcode: OP - public static int OP_ADD = 0b10_001_011; - public static int OPF_ADD = 0b00_011_110; - public static int OPF_OP_ADD = 0b00_101_0; - public static int OPI_ADD = 0b10_010_00100; - - public static int OPI_SUB = 0b11_010_00100; - public static int OP_UJMP = 0b000101; - - public static int OP_ADRP = 0b10000; - - public static int OP_ASR = 0b1010; - public static int OPI_ASR = 0b10_0100_1101; - - public static int OP_LSL = 0b1000; - public static int OPI_LSL =0b1101001101; - - public static int OP_LSR = 0b1001; - public static int OPI_LSR = 0b1101001101; - - public static int OP_BRANCH = 0b01_0101_00; - public static int OP_CALL = 0b10_010_1; - - public static int OP_CALLRARM = 0b1101011000111111000000; - - public static int OP_SUBS = 0b1111000100; - public static int OP_CSET = 0b10011010100; - public static int OP_CMP = 0b11101011; - public static int OPI_CMP = 0b1111000100; - - public static int OP_DIV = 0b10011010110; - public static int OPF_DIV = 0b000110; - - public static int OP_MUL = 0b10011011000; - public static int OPF_MUL = 0b10; - - public static int OPF_ARM = 0b01011100; - - public static int OP_FLOAT_C = 0b10011110; - - public static int OP_XOR = 0b11001010; - public static int OPI_XOR = 0b110100100; - - public static int OP_AND = 0b10_0010_10; - public static int OPI_AND = 0b10_0100_100; - - public static int OP_OR = 0b10101010; - public static int OPI_OR = 0b101100100; - - public static int OP_SUB = 0b11001011; - public static int OPF_SUB = 0b1110; - - public static int OP_MOVK = 0b111100101; - public static int OP_MOVN = 0b100100101; - public static int OP_MOVZ = 0b110100101; - - public static int OP_RET = 0b1101011001011111000000; - // Store/load opcodes - public static int OP_LOAD_R_64 = 0b11111000011; - public static int OP_LOAD_R_32 = 0b10111000011; - public static int OP_LOAD_R_16 = 0b01111000010; - public static int OP_LOAD_R_8 = 0b00111000011; - - public static int OP_LOAD_IMM_64 = 0b1111100101; - public static int OP_LOAD_IMM_32 = 0b1011100101; - public static int OP_LOAD_IMM_16 = 0b0111100101; - public static int OP_LOAD_IMM_8 = 0b0011100101; - - // load - public static int OPF_LOAD_R_64 = 0b11111100011; - public static int OPF_LOAD_R_32 = 0b10111100011; - - public static int OPF_LOAD_IMM_64 = 0b1111110101; - public static int OPF_LOAD_IMM_32 = 0b1011110101; - - public static int OP_STORE_R_64 = 0b11111000001; - public static int OP_STORE_R_32 = 0b10111000001; - public static int OP_STORE_R_16 = 0b01111000001; - public static int OP_STORE_R_8 = 0b00111000001; - - public static int OP_STORE_IMM_64 = 0b1111100100; - public static int OP_STORE_IMM_32 = 0b1011100100; - public static int OP_STORE_IMM_16 = 0b0111100100; - public static int OP_STORE_IMM_8 = 0b0011100100; - - public static int OPF_STORE_R_64 = 0b11111100001; - public static int OPF_STORE_R_32 = 0b10111100001; - - public static int OPF_STORE_IMM_32 = 0b1111110100; - public static int OPF_STORE_IMM_64 = 0b1111110100; - - - public static int OP_FMOV = 0b10011110; - public static int OP_FMOV_REG = 0b00011110; - // https://docsmirror.github.io/A64/2023-06/mov_orr_log_shift.html - public static int OP_MOV = 0b10101010000; - // Calling convention; returns a machine-specific register - // for incoming argument idx. - // index 0 for control, 1 for memory, real args start at index 2 - static final RegMask[] CALLINMASK = new RegMask[] { - X0_MASK, - X1_MASK, - X2_MASK, - X3_MASK, - X4_MASK, - X5_MASK, - X6_MASK, - X7_MASK, - }; - static final RegMask[] XMMS = new RegMask[] { - D0_MASK, - D1_MASK, - D2_MASK, - D3_MASK, - D4_MASK, - D5_MASK, - D6_MASK, - D7_MASK, - }; - - // ARM ENCODING - public enum OPTION { - UXTB, - UXTH, - UXTX, - SXTB, - SXTH, - SXTW, - SXTX, - } - - static public int cset(int opcode, int rm, COND cond, int rn, int rd) { - assert 0 <= rm && rm < 32; - assert 0 <= rn && rn < 32; - assert 0 <= rd && rd < 32; - return (opcode << 21) | (rm << 15) | (cond.ordinal() << 12) | (rn << 5) | rd; - } - - static public int cset(int opcode, COND cond, int rn, int rd) { - assert 0 <= rn && rn < 32; - assert 0 <= rd && rd < 32; - return (opcode << 16) | (cond.ordinal() << 12) | (rn << 5) | rd; - } - - public enum STORE_LOAD_OPTION { - UXTW, // base+ u32 index [<< logsize] - UXTX, // base+ u64 index [<< logsize] - SXTW, // base+ s32 index [<< logsize] - SXTX, // base+ s64 index [<< logsize] - } - - public enum COND { - EQ, - NE, - CS, - CC, - MI, - PL, - VS, - VC, - HI, - LS, - GE, - LT, - GT, - LE, - AL, - NV - } - - // True if signed 9-bit immediate - private static boolean imm9(TypeInteger ti) { - // 55 = 64-9 - return ti.isConstant() && ((ti.value()<<55)>>55) == ti.value(); - } - // True if signed 12-bit immediate - private static boolean imm12(TypeInteger ti) { - // 52 = 64-12 - return ti.isConstant() && ((ti.value()<<52)>>52) == ti.value(); - } - - - // Can we encode this in ARM's 12-bit LOGICAL immediate form? - // Some combination of shifted bit-masks. - private static int imm12Logical(TypeInteger ti) { - if( !ti.isConstant() ) return -1; - if( !ti.isConstant() ) return -1; - long val = ti.value(); - if (val == 0 || val == -1) return -1; // Special cases are not allowed - int immr = 0; - // Rotate until we have 0[...]1 - - // Rotate until: - // The number is negative (MSB is 1) - // Or the LSB is not 1 - while (val < 0 || (val & 1)==0) { - // circular rotation - val = (val >>> 63) | (val << 1); - immr++; - } - // Start by assuming that val might be made of two 32-bit chunks that repeat. - int size = 32; - long pattern = val; - // Is upper half of pattern the same as the lower? - while ((pattern & ((1L<<size)-1)) == (pattern >> size)) { - // Then only take one half - pattern >>= size; - size >>= 1; - } - size <<= 1; - int imms = Long.bitCount(pattern); - // Pattern should now be zeros followed by ones 0000011111 - if (pattern != (1L<<imms)-1) return -1; - imms--; - if (size == 64) return 0x1000 | immr << 6 | imms; - return (32-size)<<1 | immr << 6 | imms; - } - - // sh is encoded in opcode - public static int imm_inst(int opcode, int imm12, int rn, int rd) { - assert 0 <= rn && rn < 32; - assert 0 <= rd && rd < 32; - assert opcode >=0 && imm12 >= 0; // Caller zeros high order bits - return (opcode << 22) | (imm12 << 10) | (rn << 5) | rd; - } - - public static int imm_shift(int opcode, int imm, int imms, int rn, int rd) { - assert 0 <= rn && rn < 32; - assert 0 <= rd && rd < 32; - return (opcode << 22) | (1 << 22) | (imm << 16) | (imms << 10) | (rn << 5) | rd; - } - - public static void imm_inst(Encoding enc, Node n,Node n2, int opcode, int imm12) { - short self = enc.reg(n); - short reg1 = enc.reg(n2); - - int body = imm_inst(opcode, imm12&0xFFF, reg1, self); - enc.add4(body); - } - - public static void imm_inst_subs(Encoding enc, Node n,Node n2, int opcode, int imm12) { - short reg1 = enc.reg(n2); - // 31 = 11111 - int body = imm_inst(opcode, imm12&0xFFF, reg1, 31); - enc.add4(body); - } - - // for cases where rs1 and dst are the same, eg add x0, x0, 1 - public static void imm_inst(Encoding enc, int opcode, int imm12, int self) { - int body = imm_inst(opcode, imm12&0xFFF, self, self); - enc.add4(body); - } - - public static void imm_inst_n(Encoding enc, Node n, Node n2, int opcode, int imm13) { - short self = enc.reg(n); - short reg1 = enc.reg(n2); - int body = imm_inst_n(opcode, imm13, reg1, self); - enc.add4(body); - } - - // nth bit comes from immediate and not opcode - public static int imm_inst_n(int opcode, int imm13, int rn, int rd) { - assert 0 <= rn && rn < 32; - assert 0 <= rd && rd < 32; - assert 0 <= imm13 && imm13 <= 0x1FFF; - return (opcode << 23) | (imm13 << 10) | (rn << 5) | rd; - } - - public static int imm_inst_l(int opcode, int imm12, int self) { - int body = imm_inst(opcode, imm12&0xFFF, self, self); - return body; - } - - // for cases where rs1 and dst are the same, eg add x0, x0, 1 - public static int imm_inst_l(Encoding enc, Node n, int opcode, int imm12) { - short self = enc.reg(n); - short reg1 = enc.reg(n.in(1)); - int body = imm_inst(opcode, imm12&0xFFF, reg1, self); - return body; - } - - // for normal add, reg1, reg2 cases (reg-to-reg) - // using shifted-reg form - public static int r_reg(int opcode, int shift, int rm, int imm6, int rn, int rd) { - assert 0 <= rm && rm < 32; - assert 0 <= rn && rn < 32; - assert 0 <= rd && rd < 32; - return (opcode << 24) | (shift << 21) | (rm << 16) | (imm6 << 10) | (rn << 5) | rd; - } - public static void r_reg(Encoding enc, Node n, int opcode) { - short self = enc.reg(n); - short reg1 = enc.reg(n.in(1)); - short reg2 = enc.reg(n.in(2)); - int body = r_reg(opcode, 0, reg2, 0, reg1, self >= 32 ? reg1: self); - enc.add4(body); - } - - public static void r_reg_subs(Encoding enc, Node n, int opcode) { - short reg1 = enc.reg(n.in(1)); - short reg2 = enc.reg(n.in(2)); - // 31 = 11111 - int body = r_reg(opcode, 0, reg2, 0, reg1, 31); - enc.add4(body); - } - - public static int shift_reg(int opcode, int rm, int op2, int rn, int rd) { - assert 0 <= rn && rn < 32; - assert 0 <= rm && rm < 32; - assert 0 <= rd && rd < 32; - return (opcode << 21) | (rm << 16) | (op2 << 10) | (rn << 5) | rd; - } - public static void shift_reg(Encoding enc, Node n, int op2) { - short self = enc.reg(n); - short reg1 = enc.reg(n.in(1)); - short reg2 = enc.reg(n.in(2)); - int body = shift_reg(0b10011010110, reg2, op2, reg1, self); - enc.add4(body); - } - - // MUL can be considered an alias for MADD with the third operand Ra being set to 0 - public static int madd(int opcode, int rm, int ra, int rn, int rd) { - assert 0 <= rn && rn < 32; - assert 0 <= rd && rd < 32; - assert 0 <= rm && rm < 32; - return (opcode << 21) | (rm << 16) | (ra << 10) | (rn << 5) | rd; - } - public static void madd(Encoding enc, Node n, int opcode, int ra) { - short self = enc.reg(n); - short reg1 = enc.reg(n.in(1)); - short reg2 = enc.reg(n.in(2)); - int body = madd(opcode, reg2, ra, reg1, self); - enc.add4(body); - } - - // encodes movk, movn, and movz - public static int mov(int opcode, int shift, int imm16, int rd) { - assert 0 <= rd && rd < 32; - return (opcode << 23) | (shift << 21) | (imm16 << 5) | rd; - } - - public static int mov_reg(int opcode, int src, int rd) { - assert 0 <= rd && rd < 32; - return (opcode << 21) | (src << 16) | 0b11111 << 5 | rd; - } - - public static int ret(int opcode) { - return (opcode << 10); - } - - // FMOV (scalar, immediate) - public static int f_scalar(int opcode, int ftype, int rm, int op, int rn, int rd) { - assert 0 <= rn && rn < 32; - assert 0 <= rd && rd < 32; - assert 0 <= rm && rm < 32; - return (opcode << 24) | (ftype << 22) | (1 << 21) | (rm << 16) | (op << 10) | (rn << 5) | rd; - } - - public static int f_mov_reg(int opcode, int rn, int rd) { - assert 0 <= rn && rn < 32; - assert 0 <= rd && rd < 32; - return (opcode << 24) | (0b01100000010000 << 10) | (rn << 5) | rd; - } - public static int f_mov_general(int opcode, int ftype, int rmode, int opcode1, int rn, int rd) { - assert 0 <= rn && rn < 32; - assert 0 <= rd && rd < 32; - return (opcode << 24) |(ftype << 22) | (1 << 21) | (rmode << 19) | (opcode1 << 16) | (rn << 5) | rd; - } - public static void f_scalar(Encoding enc, Node n, int op ) { - short self = (short)(enc.reg(n) -D_OFFSET); - short reg1 = (short)(enc.reg(n.in(1))-D_OFFSET); - short reg2 = (short)(enc.reg(n.in(2))-D_OFFSET); - int body = f_scalar(0b00011110, 1, reg2, op, reg1, self); - enc.add4(body); - } - - // share same encoding with LDR (literal, SIMD&FP) flavour - public static int load_pc(int opcode, int offset, int rt) { - offset>>=2; - return (opcode << 24) | (offset << 5) | rt; - } - // int l - public static int adrp(int op, int imlo,int opcode, int imhi, int rd) { - assert 0 <= rd && rd < 32; - return (op << 31) | (imlo << 29) |(opcode << 24) | (imhi << 5) | rd; - } - - // [Rptr+Roff] - public static int indr_adr(int opcode, int off, STORE_LOAD_OPTION option, int s, int ptr, int rt) { - assert 0 <= ptr && ptr < 32; - assert 0 <= rt && rt < 32; - return (opcode << 21) | (off << 16) | (option.ordinal() << 13) | (s << 12) | (2 << 10) | (ptr << 5) | rt; - } - // [Rptr+imm9] - public static int load_str_imm(int opcode, int imm12, int ptr, int rt, int size) { - assert 0 <= ptr && ptr < 32; - assert 0 <= rt && rt < 32; - - if(size == 8) imm12 = imm12 >> 3; - if(size == 4) imm12 = imm12 >> 2; - if(size == 2) imm12 = imm12 >> 1; - // size == 1 imm12 = imm12 - return (opcode << 22) | ((imm12) << 10) | (ptr << 5) | rt; - } - - // encoding for vcvt, size is encoded in operand - // <Qd>, <Qm> - // F32.S32 - //encoded as op = 0b00, size = 0b10. - // VCVT<c>.<Td>.<Tm> <Dd>, <Dm> - // opcode is broken down into 4 pieces - public static int f_convert(int opcode_1, int opcode_2, int opcode_3, int opcode_4, int vd, int vm) { - return (opcode_1 << 28) | (opcode_2 << 24) | (opcode_3 << 20) | (opcode_4 << 16) | - (vd << 12) | (0x01100010 << 4) | vm; - } - public static int float_cast(int opcode, int ftype, int rn, int rd) { - assert 0 <= rd && rd < 32; - assert 0 <= rn && rn < 32; - return (opcode << 24) | (ftype << 22) | (2176 << 10) | (rn << 5) | rd; - } - - - // ftype = 3 - public static int f_cmp(int opcode, int ftype, int rm, int rn) { - assert 0 <= rn && rn < 32; - assert 0 <= rm && rm < 32; - // Todo: |8 is not needed - return (opcode << 24) | (ftype << 21) | (rm << 16) | (8 << 10) | (rn << 5); - } - public static void f_cmp(Encoding enc, Node n) { - short reg1 = (short)(enc.reg(n.in(1))-D_OFFSET); - short reg2 = (short)(enc.reg(n.in(2))-D_OFFSET); - int body = f_cmp(0b00011110, 3, reg1, reg2); - enc.add4(body); - } - - public static COND make_condition(String bop) { - return switch (bop) { - case "==" -> COND.EQ; - case "!=" -> COND.NE; - case "<" -> COND.LT; - case "<=" -> COND.LE; - case ">=" -> COND.GE; - case ">" -> COND.GT; - default -> throw Utils.TODO(); - }; - } - - public static int b_cond(int opcode, int delta, COND cond) { - // 24-5 == 19bits offset range - assert -(1<<19) <= delta && delta < (1<<19); - assert (delta&3)==0; - delta>>=2; - delta &= (1L<<19)-1; // Zero extend - return (opcode << 24) | ((delta)<< 5) | cond.ordinal(); - } - - public static int cond_set(int opcode, int rm, COND cond, int rn, int rd) { - assert 0 <= rd && rd < 32; - assert 0 <= rn && rn < 32; - return (opcode << 21) | (rm << 16) | (cond.ordinal() << 12) | (rn << 5) | rd; - } - - // Branch with Link to Register calls a subroutine at an address in a register, setting register X30 to PC+4. - public static int blr(int opcode, int rd) { - assert 0 <= rd && rd < 32; - return opcode << 10 | rd << 5; - } - public static int b(int opcode, int delta) { - assert -(1<<26) <= delta && delta < (1<<26); - assert (delta&3)==0; - delta>>=2; - delta &= (1L<<26)-1; // Zero extend - return (opcode << 26) | delta; - } - // no aligned assert(assert (delta&3)==0;) - public static int b_calloc(int opcode, int delta) { - assert -(1<<26) <= delta && delta < (1<<26); - delta>>=2; - delta &= (1L<<26)-1; // Zero extend - return (opcode << 26) | delta; - } - - @Override public RegMask callArgMask(TypeFunPtr tfp, int idx, int maxArgSlot ) { return callInMask(tfp,idx,maxArgSlot); } - static RegMask callInMask(TypeFunPtr tfp, int idx, int maxArgSlot ) { - if( idx==0 ) return CodeGen.CODE._rpcMask; - if( idx==1 ) return null; - // Count floats in signature up to index - if( idx-2 >= tfp.nargs() ) return null; // Anti-dependence - // Count floats in signature up to index - int fcnt=0; - for( int i=2; i<idx; i++ ) - if( tfp.arg(i-2) instanceof TypeFloat) - fcnt++; - // Floats up to XMMS in XMM registers - if( tfp.arg(idx-2) instanceof TypeFloat ) { - if( fcnt < XMMS.length ) - return XMMS[fcnt]; - } else { - RegMask[] cargs = CALLINMASK; - if( idx-2-fcnt < cargs.length ) - return cargs[idx-2-fcnt]; - } - throw Utils.TODO(); // Pass on stack slot - } - - public static long decodeImm12(int imm12) { - int immr = (imm12 >> 6) & 0x3F; - int imms = imm12 & 0x3F; - int size; - if ((imm12 & 0x1000) != 0) { - size = 64; - } else { - size = 31-(imms >> 1); - size |= size >> 1; - size |= size >> 2; - size |= size >> 4; - size++; - imms &= ~((32-size) << 1); - } - long val = (2L << imms)-1; - while (size < 64) { - val |= val << size; - size <<= 1; - } - val = (val >>> immr) | val << (64-immr); - return val; - } - - // Return the max stack slot used by this signature, or 0 - @Override public short maxArgSlot( TypeFunPtr tfp ) { - int icnt=0, fcnt=0; // Count of ints, floats - for( int i=0; i<tfp.nargs(); i++ ) { - if( tfp.arg(i) instanceof TypeFloat ) fcnt++; - else icnt++; - } - int nstk = Math.max(icnt-8,0)+Math.max(fcnt-8,0); - return (short)nstk; - } - - private static final long CALLEE_SAVE = - 1L<<X19 | - 1L<<X20 | 1L<<X21 | 1L<<X22 | 1L<<X23 | - 1L<<X24 | 1L<<X25 | 1L<<X26 | 1L<<X27 | - 1L<<X28 | - 1L<<D9 | 1L<<D10 | 1L<<D11 | - 1L<<D12 | 1L<<D13 | 1L<<D14 | 1L<<D15; - static final long CALLER_SAVE = ~CALLEE_SAVE & ~(1L<<RSP); - @Override public long callerSave() { return CALLER_SAVE; } - @Override public long neverSave() { return 1L<<RSP; } - @Override public RegMask retMask( TypeFunPtr tfp ) { - return tfp.ret() instanceof TypeFloat ? D0_MASK : X0_MASK; - } - @Override public int rpc() { return X30; } - - // Create a split op; any register to any register, including stack slots - @Override public SplitNode split(String kind, byte round, LRG lrg) { return new SplitARM(kind,round); } - - // Return a MachNode unconditional branch - @Override public CFGNode jump() { - return new UJmpARM(); - } - - // Instruction selection - @Override public Node instSelect(Node n ) { - return switch( n ) { - case AddFNode addf -> new AddFARM(addf); - case AddNode add -> add(add); - case AndNode and -> and(and); - case BoolNode bool -> cmp(bool); - case CallNode call -> call(call); - case CastNode cast -> new CastMach(cast); - case CallEndNode cend-> new CallEndMach(cend); - case CProjNode c -> new CProjNode(c); - case ConstantNode con-> con(con); - case DivFNode divf -> new DivFARM(divf); - case DivNode div -> new DivARM(div); - case FunNode fun -> new FunARM(fun); - case IfNode iff -> jmp(iff); - case LoadNode ld -> ld(ld); - case MemMergeNode mem-> new MemMergeNode(mem); - case MinusNode neg -> new NegARM(neg); - case MulFNode mulf -> new MulFARM(mulf); - case MulNode mul -> new MulARM(mul); - case NewNode nnn -> new NewARM(nnn); - case NotNode not -> new NotARM(not); - case OrNode or -> or(or); - case ParmNode parm -> new ParmARM(parm); - case PhiNode phi -> new PhiNode(phi); - case ProjNode prj -> new ProjARM(prj); - case ReadOnlyNode read -> new ReadOnlyMach(read); - case ReturnNode ret -> new RetARM(ret,ret.fun()); - case SarNode sar -> asr(sar); - case ShlNode shl -> lsl(shl); - case ShrNode shr -> lsr(shr); - case StartNode start -> new StartNode(start); - case StopNode stop -> new StopNode(stop); - case StoreNode st -> st(st); - case SubFNode subf -> new SubFARM(subf); - case SubNode sub -> sub(sub); - case ToFloatNode tfn -> new I2F8ARM(tfn); - case XorNode xor -> xor(xor); - - case LoopNode loop -> new LoopNode(loop); - case RegionNode region-> new RegionNode(region); - default -> throw Utils.TODO(); - }; - } - - private Node cmp(BoolNode bool){ - Node cmp = _cmp(bool); - return new SetARM(cmp, IfNode.negate(bool.op())); - } - private Node _cmp(BoolNode bool) { - if( bool.isFloat() ) - return new CmpFARM(bool); - return bool.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti - ? new CmpIARM(bool, (int)ti.value()) - : new CmpARM(bool); - } - - private Node ld(LoadNode ld) { - return new LoadARM(address(ld), ld.ptr(), idx, off); - } - - private Node jmp(IfNode iff) { - // If/Bool combos will match to a Cmp/Set which sets flags. - // Most general arith ops will also set flags, which the Jmp needs directly. - // Loads do not set the flags, and will need an explicit TEST - String op = "!="; - if( iff.in(1) instanceof BoolNode bool ) op = bool.op(); - else if( iff.in(1)==null ) op = "=="; // Never-node cutout - else iff.setDef(1, new BoolNode.NE(iff.in(1), new ConstantNode(TypeInteger.ZERO))); - return new BranchARM(iff, op); - } - - private Node add(AddNode add) { - return add.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && imm12(ti) - ? new AddIARM(add, (int)ti.value()) - : new AddARM(add); - } - - private Node sub(SubNode sub) { - return sub.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) - ? new SubIARM(sub, (int)(ti.value())) - : new SubARM(sub); - } - - private Node con( ConstantNode con ) { - if( !con._con.isConstant() ) return new ConstantNode( con ); // Default unknown caller inputs - return switch( con._con ) { - case TypeInteger ti -> new IntARM(con); - case TypeFloat tf -> new FloatARM(con); - case TypeFunPtr tfp -> new TFPARM(con); - case TypeMemPtr tmp -> new TMPARM(con); - case TypeNil tn -> throw Utils.TODO(); - // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code. - case Type t -> t==Type.NIL ? new IntARM(con) : new ConstantNode(con); - }; - } - - private Node call(CallNode call){ - return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp - ? new CallARM(call, tfp) - : new CallRRARM(call); - } - - private Node or(OrNode or) { - int imm12; - return or.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && (imm12 = imm12Logical(ti)) != -1 - ? new OrIARM(or, imm12) - : new OrARM(or); - } - - private Node xor(XorNode xor) { - int imm12; - return xor.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && (imm12 = imm12Logical(ti)) != -1 - ? new XorIARM(xor, imm12) - : new XorARM(xor); - } - - private Node and(AndNode and) { - int imm12; - return and.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && (imm12 = imm12Logical(ti)) != -1 - ? new AndIARM(and, imm12) - : new AndARM(and); - } - - private Node asr(SarNode asr) { - return asr.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && ti.value() >= 0 && ti.value() < 63 - ? new AsrIARM(asr, (int)ti.value()) - : new AsrARM(asr); - } - - private Node lsl(ShlNode lsl) { - return lsl.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && ti.value() >= 0 && ti.value() < 63 - ? new LslIARM(lsl, (int)ti.value()) - : new LslARM(lsl); - } - - private Node lsr(ShrNode lsr) { - return lsr.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && ti.value() >= 0 && ti.value() < 63 - ? new LsrIARM(lsr, (int)ti.value()) - : new LsrARM(lsr); - } - - - private static int off; - private static Node idx; - private Node st(StoreNode st) { - return new StoreARM(address(st),st.ptr(),idx,off,st.val()); - } - - // Gather addressing mode bits prior to constructing. This is a builder - // pattern, but saving the bits in a *local* *global* here to keep mess - // contained. - private <N extends MemOpNode> N address(N mop ) { - off = 0; // Reset - idx = null; - Node base = mop.ptr(); - // Skip/throw-away a ReadOnly, only used to typecheck - if( base instanceof ReadOnlyNode read ) base = read.in(1); - assert !(base instanceof AddNode) && base._type instanceof TypeMemPtr; // Base ptr always, not some derived - if( mop.off() instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm9(ti) ) { - off = (int)ti.value(); - assert off == ti.value(); // In 32-bit range - } else { - idx = mop.off(); - } - return mop; - } - - -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java deleted file mode 100644 index 2cd00845..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.ConstantNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; - -// Add upper 20bits to PC. Immediate comes from the relocation info. -public class AUIPC extends ConstantNode implements MachNode, RIPRelSize { - AUIPC( Type tfp ) { super(tfp); } - @Override public RegMask regmap(int i) { return null; } - @Override public RegMask outregmap() { return riscv.WMASK; } - @Override public boolean isClone() { return true; } - @Override public AUIPC copy() { return new AUIPC(_con); } - @Override public String op() { return "auipc"; } - @Override public void encoding( Encoding enc ) { - enc.relo(this); - short dst = enc.reg(this); - enc.add4(riscv.u_type(riscv.OP_AUIPC, dst, 0)); - } - - // Delta is from opcode start, but X86 measures from the end of the 5-byte encoding - @Override public byte encSize(int delta) { return 4; } - - // Delta is from opcode start - @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - //short rpc = enc.reg(this); - //// High half is where the TFP constant used to be, the last input - //short auipc = enc.reg(in(_inputs._len-1)); - //enc.patch4(opStart,riscv.i_type(0x67, rpc, 0, auipc, delta)); - throw Utils.TODO(); - } - - @Override public void asm(CodeGen code, SB sb) { - String reg = code.reg(this); - sb.p(reg).p(" = PC+#"); - if( _con == null ) sb.p("---"); - else _con.print(sb); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java deleted file mode 100644 index 8636df9f..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -// fadd.d -public class AddFRISC extends MachConcreteNode implements MachNode { - AddFRISC(Node addf) {super(addf);} - @Override public String op() { return "addf"; } - @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; } - @Override public RegMask outregmap() { return riscv.FMASK; } - @Override public void encoding( Encoding enc ) { riscv.rf_type(enc,this,riscv.RM.RNE,1); } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java deleted file mode 100644 index 511b0583..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.util.Utils; - -public class AddIRISC extends ImmRISC { - // Used to inst-selection as a direct match against an ideal Add/Sub - public AddIRISC( Node add, int imm12, boolean pop ) { super(add,imm12,pop); } - @Override public String op() { return "addi"; } - @Override public String glabel() { return "+"; } - @Override int opcode() { return riscv.OP_IMM; } - @Override int func3() {return 0;} - @Override public AddIRISC copy() { - // Clone the AddI, using the same inputs-only code used during inst select. - // Output edges are missing. - AddIRISC add = new AddIRISC(in(1),_imm12,false); - // Copys happen when output edges should be valid, so correct missing output edges. - in(1)._outputs.add(add); - return add; - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java deleted file mode 100644 index 7010916f..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class AddRISC extends MachConcreteNode implements MachNode { - public AddRISC( Node add ) { super(add); } - AddRISC( Node base, Node off ) { - super(new Node[3]); - _inputs.set(1,base); - _inputs.set(2,off ); - } - @Override public String op() { return "add"; } - @Override public RegMask regmap(int i) { return riscv.RMASK; } - @Override public RegMask outregmap() { return riscv.WMASK; } - @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,0,0); } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java deleted file mode 100644 index 842c8bdc..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.util.Utils; - -public class AndIRISC extends ImmRISC { - public AndIRISC( Node and, int imm) { super(and,imm); } - @Override public String op() { return "andi"; } - @Override public String glabel() { return "&"; } - @Override int opcode() { return riscv.OP_IMM; } - @Override int func3() {return 7;} -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java deleted file mode 100644 index 1845985e..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class AndRISC extends MachConcreteNode implements MachNode { - public AndRISC(Node and) { super(and); } - @Override public String op() { return "and"; } - @Override public RegMask regmap(int i) { return riscv.RMASK; } - @Override public RegMask outregmap() { return riscv.WMASK; } - @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,0b111,0); } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" & ").p(code.reg(in(2))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java deleted file mode 100644 index b7284265..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import java.util.BitSet; - -// Conditional branch such as: BEQ -public class BranchRISC extends IfNode implements MachNode, RIPRelSize { - String _bop; - // label is obtained implicitly - public BranchRISC( IfNode iff, String bop, Node n1, Node n2 ) { - super(iff); - _bop = bop; - _inputs.setX(1,n1); - _inputs.setX(2,n2); - } - - @Override public String op() { return "b"+_bop; } - @Override public String label() { return op(); } - @Override public String comment() { return "L"+cproj(1)._nid; } - @Override public RegMask regmap(int i) { return riscv.RMASK; } - @Override public RegMask outregmap() { return null; } - @Override public void negate() { - _bop = negate(_bop); - // Cannot encode ">" or "<=", so flip these - if( _bop.equals(">") || _bop.equals("<=") ) { - swap12(); // Cannot negate the test, so swap the operands - _bop = swap(_bop); // Swap test to match - } - } - - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { - sb.append("if( "); - if( in(1)==null ) sb.append("0"); - else in(1)._print0(sb,visited); - sb.append(_bop); - if( in(2)==null ) sb.append("0"); - else in(2)._print0(sb,visited); - return sb.append(" )"); - } - - @Override public void encoding( Encoding enc ) { - if( in(1)==null && _bop=="!=" ) return; // Inverted never-node, no code - enc.jump(this,cproj(0)); - if( in(1)==null ) // Never node - enc.add4(riscv.j_type(riscv.OP_JAL, 0, 0)); - else { - // Todo: relocs (for offset - immf) - short src1 = enc.reg(in(1)); - short src2 = in(2)==null ? (short)riscv.ZERO : enc.reg(in(2)); - enc.add4(riscv.b_type(riscv.OP_BRANCH, riscv.jumpop(_bop), src1, src2, 0)); - } - } - - // Delta is from opcode start - @Override public byte encSize(int delta) { - if( in(1)==null && _bop=="!=" ) return 0; // Inverted never-node, no code - if( -4*1024 <= delta && delta < 4*1024 ) return 4; - // 2 word encoding needs a tmp register, must teach RA - throw Utils.TODO(); - } - - // Delta is from opcode start - @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - assert !( in(1)==null && _bop=="!=" ); // Inverted never-node, no code no patch - if( in(1)==null ) { // Never node - enc.patch4(opStart,riscv.j_type(riscv.OP_JAL, 0, delta)); - } else { - short src1 = enc.reg(in(1)); - short src2 = in(2)==null ? (short)riscv.ZERO : enc.reg(in(2)); - if( opLen==4 ) { - enc.patch4(opStart,riscv.b_type(riscv.OP_BRANCH, riscv.jumpop(_bop), src1, src2, delta)); - } else { - throw Utils.TODO(); - } - } - } - - @Override public void asm(CodeGen code, SB sb) { - if( in(1)==null && _bop=="!=" ) { - sb.p("never"); - return; - } - String src1 = in(1)==null ? "#0" : code.reg(in(1)); - String src2 = in(2)==null ? "#0" : code.reg(in(2)); - sb.p(src1).p(" ").p(_bop).p(" ").p(src2).p(" "); - CFGNode prj = cproj(0).uctrlSkipEmpty(); - if( !prj.blockHead() ) prj = prj.cfg0(); - sb.p(label(prj)); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java deleted file mode 100644 index a676ba60..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeFunPtr; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; - -public class CallRISC extends CallNode implements MachNode, RIPRelSize { - final TypeFunPtr _tfp; - final String _name; - CallRISC( CallNode call, TypeFunPtr tfp ) { - super(call); - assert tfp.isConstant(); - _inputs.pop(); // Pop constant target - _tfp = tfp; - FunNode fun = CodeGen.CODE.link(tfp); - _name = fun==null ? ((ExternNode)call.fptr())._extern : fun._name; // Can be null for extern calls - } - - @Override public String op() { return "call"; } - @Override public String label() { return op(); } - @Override public String name() { return _name; } - @Override public TypeFunPtr tfp() { return _tfp; } - @Override public RegMask regmap(int i) { return riscv.callInMask(_tfp,i,fun()._maxArgSlot); } - @Override public RegMask outregmap() { return riscv.RPC_MASK; } - @Override public int nargs() { return nIns()-2; } // Minus control, memory, fptr - - @Override public void encoding( Encoding enc ) { - // Short form +/-4K: beq r0,r0,imm12 - // Long form: auipc rX,imm20/32; jal r0,[rX+imm12/32] - FunNode fun = CodeGen.CODE.link(_tfp); - if( fun==null ) enc.external(this,_name); - else enc.relo(this); - short rpc = enc.reg(this); - enc.add4(riscv.j_type(riscv.OP_JAL, rpc, 0)); - } - - // Delta is from opcode start - @Override public byte encSize(int delta) { - if( -(1L<<20) <= delta && delta < (1L<<20) ) return 4; - // 2 word encoding needs a tmp register, must teach RA - throw Utils.TODO(); - } - - // Delta is from opcode start - @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - short rpc = enc.reg(this); - if( opLen==4 ) { - enc.patch4(opStart,riscv.j_type(riscv.OP_JAL, rpc, delta)); - } else { - throw Utils.TODO(); - } - } - - @Override public void asm(CodeGen code, SB sb) { - sb.p(_name).p(" "); - for( int i=0; i<nargs(); i++ ) - sb.p(code.reg(arg(i+2))).p(", "); - sb.unchar(2).p(" ").p(code.reg(fptr())); - - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java deleted file mode 100644 index ac36ed12..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class CallRRISC extends CallNode implements MachNode { - CallRRISC( CallNode call ) { super(call); } - @Override public String op() { return "callr"; } - @Override public String label() { return op(); } - @Override public RegMask regmap(int i) { - return i==_inputs._len-1 - ? riscv.RMASK // Function call target - : riscv.callInMask(tfp(),i,fun()._maxArgSlot); // Normal argument - } - @Override public RegMask outregmap() { return null; } - - @Override public void encoding( Encoding enc ) { - short rpc = enc.reg(this); - short src = enc.reg(in(_inputs._len-1)); - int body = riscv.i_type(riscv.OP_JALR, rpc, 0, src, 0); - enc.add4(body); - } - - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(fptr())).p(" "); - for( int i=0; i<nargs(); i++ ) - sb.p(code.reg(arg(i))).p(" "); - sb.unchar(2); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java deleted file mode 100644 index 68ed6f6c..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -// fdiv.d -public class DivFRISC extends MachConcreteNode implements MachNode{ - DivFRISC(Node divf) {super(divf);} - @Override public String op() { return "divf"; } - @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; } - @Override public RegMask outregmap() { return riscv.FMASK; } - @Override public void encoding( Encoding enc ) { riscv.rf_type(enc,this,riscv.RM.RUP,13); } - // Default on double precision for now(64 bits) - // General form: "fdiv.d rd = src1 / src2 - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java deleted file mode 100644 index 515ffa47..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; - -public class DivRISC extends MachConcreteNode implements MachNode{ - public DivRISC(Node div) {super(div);} - @Override public String op() { return "div"; } - @Override public RegMask regmap(int i) { return riscv.RMASK; } - @Override public RegMask outregmap() { return riscv.WMASK; } - @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,4,1); } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2))); - } - -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java deleted file mode 100644 index 81726200..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.ConstantNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.type.TypeFloat; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; - -public class FltRISC extends ConstantNode implements MachNode, RIPRelSize { - FltRISC(ConstantNode con) { super(con); } - @Override public String op() { return "ld8"; } - @Override public RegMask regmap(int i) { return null; } - @Override public RegMask outregmap() { return riscv.FMASK; } - @Override public boolean isClone() { return true; } - @Override public FltRISC copy() { return new FltRISC(this); } - - @Override public void encoding( Encoding enc ) { - enc.largeConstant(this,_con, 0, -1); - short dst = (short)(enc.reg(this) - riscv.F_OFFSET); - short tmp = (short)riscv.T6; - // AUIPC dst,#hi20_constant_pool - enc.add4(riscv.u_type(riscv.OP_AUIPC, tmp, 0)); - // Load dst,[dst+#low12_constant_pool] - enc.add4(riscv.i_type(riscv.OP_LOADFP, dst, 0b11, tmp, 0)); - } - @Override public RegMask killmap() { return new RegMask(riscv.T6); } - - // Delta is from opcode start. - @Override public byte encSize(int delta) { return 8; } - - // Delta is from opcode start - @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - short dst = (short)(enc.reg(this) - riscv.F_OFFSET); - short tmp = (short)riscv.T6; - // AUIPC dst,#hi20_constant_pool - enc.patch4(opStart , riscv.u_type(riscv.OP_AUIPC, tmp, delta>>12)); - // Load dst,[dst+#low12_constant_pool] - enc.patch4(opStart+4, riscv.i_type(riscv.OP_LOADFP, dst, 0b11, tmp, delta & 0xFFF)); - } - - @Override public void asm(CodeGen code, SB sb) { - _con.print(sb.p(code.reg(this)).p(" = #")); - } - -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java deleted file mode 100644 index 6fce6244..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.FunNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; - -public class FunRISC extends FunNode implements MachNode { - FunRISC(FunNode fun) {super(fun);} - @Override public void computeFrameAdjust(CodeGen code, int maxReg) { - super.computeFrameAdjust(code,maxReg); - if( _hasCalls ) // If non-leaf, pad to 16b - _frameAdjust = ((_frameAdjust+8) & -16); - } - @Override public void encoding( Encoding enc ) { - int sz = _frameAdjust; - if( sz == 0 ) return; // Skip if no frame adjust - if( sz >= 1L<<12 ) throw Utils.TODO(); - enc.add4(riscv.i_type(riscv.OP_IMM, riscv.RSP, 0, riscv.RSP, -sz & 0xFFF)); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java deleted file mode 100644 index db0547fe..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -// fcvt.d.w -// Converts a 32-bit signed integer, in integer register rs1 into a double-precision floating-point number in floating-point register rd. -public class I2F8RISC extends MachConcreteNode implements MachNode { - I2F8RISC(Node i2f8) {super(i2f8);} - @Override public String op() { return "cvtf"; } - @Override public RegMask regmap(int i) { assert i==1; return riscv.RMASK; } - @Override public RegMask outregmap() { return riscv.FMASK; } - @Override public void encoding( Encoding enc ) { - short dst = (short)(enc.reg(this )-riscv.F_OFFSET); - short src1 = enc.reg(in(1)); - int body = riscv.r_type(riscv.OP_FP,dst,riscv.RM.RNE.ordinal(),src1,0,0x69); - enc.add4(body); - } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p("(flt)").p(code.reg(in(1))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java deleted file mode 100644 index f2beb25c..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.util.SB; -import java.util.BitSet; - -abstract public class ImmRISC extends MachConcreteNode implements MachNode { - public final int _imm12; - ImmRISC( Node add, int imm12, boolean pop ) { - super(add); - _imm12 = imm12; - // If pop, we are moving from an ideal Node to MachNode; the ideal Node - // inputs got copied here, but we are absorbing the immediate into the - // MachNode and so do not want the constant input. - if( pop ) _inputs.pop(); - // if NOT pop, we are the 2nd-half of a 2-part expansion, reset the - // input edges to no-control - else { _inputs.set(0,null); _inputs.setX(1,add); } - } - ImmRISC( Node add, int imm12 ) { this(add,imm12,true); } - @Override public RegMask regmap(int i) { return riscv.RMASK; } - @Override public RegMask outregmap() { return riscv.WMASK; } - @Override public boolean isClone() { - // Allow cloning of the 2nd half of a 2-part constant; this might - // trigger cloning the first part also. - return in(1) instanceof LUI || in(1) instanceof AUIPC; - } - - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { - int imm12 = (_imm12<<20)>>20; // Sign extend 12 bits - in(1)._print0(sb.append("( "), visited); - return sb.append(String.format(" %s #%d )",glabel(),imm12)); - } - - abstract int opcode(); - abstract int func3(); - - @Override public void encoding( Encoding enc ) { - short dst = enc.reg(this ); - short src = enc.reg(in(1)); - enc.add4(riscv.i_type(opcode(), dst, func3(), src, _imm12 & 0xFFF)); - } - - // General form: "addi rd = rs1 + imm" - @Override public void asm(CodeGen code, SB sb) { - int imm12 = (_imm12<<20)>>20; // Sign extend 12 bits - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ").p(glabel()).p(" #").p(imm12); - if( in(1) instanceof LUI lui ) - sb.p(" // #").hex4((int)(((TypeInteger)lui._con).value()) + imm12); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java deleted file mode 100644 index edd1850f..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.ConstantNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.util.SB; - -// Special instruction for loading 8 byte constants from the constant pool. - -public class Int8RISC extends ConstantNode implements MachNode, RIPRelSize { - public Int8RISC(ConstantNode con) { super(con); } - - @Override public String op() { return "ld8"; } - @Override public RegMask regmap(int i) { return null; } - @Override public RegMask outregmap() { return riscv.WMASK; } - @Override public boolean isClone() { return true; } - @Override public Int8RISC copy() { return new Int8RISC(this); } - - @Override public byte encSize(int delta) { return 8; } - - @Override public void encoding( Encoding enc ) { - short dst = enc.reg(this); - short tmp = (short)riscv.T6; - enc.largeConstant(this,_con, 0, -1); - // AUIPC dst,#hi20_constant_pool - enc.add4(riscv.u_type(riscv.OP_AUIPC, tmp, 0)); - // Load dst,[dst+#low12_constant_pool] - enc.add4(riscv.i_type(riscv.OP_LOAD, dst, 0b11, tmp, 0)); - } - @Override public RegMask killmap() { return new RegMask(riscv.T6); } - - // Delta is from opcode start - @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - short dst = enc.reg(this); - short tmp = (short)riscv.T6; - // AUIPC dst,#hi20_constant_pool - enc.patch4(opStart , riscv.u_type(riscv.OP_AUIPC, tmp, delta>>12)); - // Load dst,[dst+#low12_constant_pool] - enc.patch4(opStart+4, riscv.i_type(riscv.OP_LOAD, dst, 0b11, tmp, delta & 0xFFF)); - } - - @Override public void asm(CodeGen code, SB sb) { - _con.print(sb.p(code.reg(this)).p(" = #")); - } - -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java deleted file mode 100644 index f8af913a..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.ConstantNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.util.SB; - -// 12-bit integer constant. Larger constants are made up in the instruction -// selection by adding with a LUI. -public class IntRISC extends ConstantNode implements MachNode { - public IntRISC(ConstantNode con) { super(con); } - @Override public String op() { return "ldi"; } - @Override public RegMask regmap(int i) { return null; } - @Override public RegMask outregmap() { return riscv.WMASK; } - @Override public boolean isClone() { return true; } - @Override public IntRISC copy() { return new IntRISC(this); } - @Override public void encoding( Encoding enc ) { - short dst = enc.reg(this); - int val = _con==Type.NIL ? 0 : (int)(((TypeInteger)_con).value() & 0xFFF); - // Explicit truncation of larger immediates; this will sign-extend on - // load and this is handled during instruction selection. - enc.add4(riscv.i_type(riscv.OP_IMM, dst, 0, riscv.ZERO, val)); - } - @Override public void asm(CodeGen code, SB sb) { - String reg = code.reg(this); - _con.print(sb.p(reg).p(" = #")); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java deleted file mode 100644 index 7333f2b7..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.ConstantNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.util.SB; - -// Load upper 20bits. -public class LUI extends ConstantNode implements MachNode { - public LUI( int imm20 ) { - super(TypeInteger.constant(imm20)); - assert (imm20 & 0xFFF)==0; - } - @Override public RegMask regmap(int i) { return null; } - @Override public RegMask outregmap() { return riscv.WMASK; } - @Override public boolean isClone() { return true; } - @Override public LUI copy() { return new LUI((int)((TypeInteger)_con).value()); } - @Override public String op() { return "lui"; } - @Override public void encoding( Encoding enc ) { - long x = ((TypeInteger)_con).value(); - int imm20 = (int)(x>>12) & 0xFFFFF; - short dst = enc.reg(this); - int lui = riscv.u_type(riscv.OP_LUI, dst, imm20); - enc.add4(lui); - } - - @Override public void asm(CodeGen code, SB sb) { - String reg = code.reg(this); - sb.p(reg).p(" = #").hex4((int)(((TypeInteger)_con).value())); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java deleted file mode 100644 index 7e08a01d..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -// Load memory addressing on RISC -// Support imm, reg(direct), or reg+off(indirect) addressing -// Base = base - base pointer, offset is added to base -// idx = null -// off = off - imm12 added to base -public class LoadRISC extends MemOpRISC { - public LoadRISC(LoadNode ld, Node base, int off) { super(ld, base, off, null); } - @Override public String op() { return "ld" +_sz; } - @Override public RegMask regmap(int i) { return riscv.RMASK; } - @Override public RegMask outregmap() { return riscv.MEM_MASK; } - @Override public void encoding( Encoding enc ) { - short dst = enc.reg(this ); - short ptr = enc.reg(ptr()); - int op = dst >= riscv.F_OFFSET ? riscv.OP_LOADFP : riscv.OP_LOAD; - if( dst >= riscv.F_OFFSET ) dst -= riscv.F_OFFSET; - enc.add4(riscv.i_type(op, dst, func3(), ptr, _off)); - } - - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(","); - asm_address(code,sb); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java deleted file mode 100644 index 93c90052..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; -import java.util.BitSet; - -public abstract class MemOpRISC extends MemOpNode implements MachNode { - final int _off; // Limit 12 bits - final char _sz; - MemOpRISC(MemOpNode mop, Node base, int off, Node val) { - super(mop,mop); - assert base._type instanceof TypeMemPtr; - _inputs.setX(2, base ); // Base can be an Add, is no longer raw object base - _inputs.setX(3, null); // Never an index - _inputs.setX(4, val ); - _off = off; - _sz = (char)('0'+(1<<_declaredType.log_size())); - } - - @Override public String label() { return op(); } - Node val() { return in(4); } // Only for stores - - @Override public StringBuilder _printMach(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); } - - @Override public Type compute() { throw Utils.TODO(); } - @Override public Node idealize() { throw Utils.TODO(); } - - // func3 is based on load/store size and extend - int func3() { - int func3 = -1; - // no unsigned flavour for store, so both signed and unsigned trigger the same - if(this instanceof StoreRISC) { - if( _declaredType == TypeInteger. I8 || _declaredType == TypeInteger.U8 || _declaredType == TypeInteger.BOOL) func3=0; // SB - if( _declaredType == TypeInteger.I16 || _declaredType == TypeInteger.U16 ) func3=1; // SH - if( _declaredType == TypeInteger.I32 || _declaredType == TypeInteger.U32 ) func3=2; // SW - if( _declaredType instanceof TypeMemPtr) func3=3; // SD - if( _declaredType == TypeInteger.BOT ) func3=3; // SD - if( func3 == -1 ) throw Utils.TODO(); - return func3; - } - if( _declaredType == TypeInteger. I8 ) func3=0; // LB - if( _declaredType == TypeInteger.I16 ) func3=1; // LH - if( _declaredType == TypeInteger.I32 ) func3=2; // LW - if( _declaredType == TypeInteger.BOT ) func3=3; // LD - if( _declaredType == TypeInteger. U8 ) func3=4; // LBU - if( _declaredType == TypeInteger.BOOL) func3=4; // LBU - if( _declaredType == TypeInteger.U16 ) func3=5; // LHU - if( _declaredType == TypeInteger.U32 ) func3=6; // LWU - - // float - if( _declaredType == TypeFloat.F32) func3 = 2; // fLW fSW - if( _declaredType == TypeFloat.F64) func3 = 3; // fLD fSD - - if( _declaredType instanceof TypeMemPtr) func3 = 3; // 8 byte pointers (pick ld) - if( func3 == -1 ) throw Utils.TODO(); - return func3; - } - - // Register mask allowed on input i. - @Override public RegMask regmap(int i) { - // 0 - ctrl - // 1 - memory - if( i==2 ) return riscv.RMASK; // base - // 2 - index - if( i==4 ) return riscv.MEM_MASK; // value - throw Utils.TODO(); - } - - SB asm_address(CodeGen code, SB sb) { - sb.p("[").p(code.reg(ptr())).p("+"); - return sb.p(_off).p("]"); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java deleted file mode 100644 index 5cc40079..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -// fmul.d -public class MulFRISC extends MachConcreteNode implements MachNode { - MulFRISC(Node mulf) {super(mulf);} - @Override public String op() { return "mulf"; } - @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; } - @Override public RegMask outregmap() { return riscv.FMASK; } - @Override public void encoding( Encoding enc ) { riscv.rf_type(enc,this,riscv.RM.RNE,9); } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java deleted file mode 100644 index 9cb9ace7..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -// mulh signed multiply instruction(no-imm form) -public class MulRISC extends MachConcreteNode implements MachNode{ - public MulRISC(Node mul) {super(mul);} - @Override public String op() { return "mul"; } - @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.RMASK; } - @Override public RegMask outregmap() { return riscv.WMASK; } - @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,0,1); } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java deleted file mode 100644 index d8183d09..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class NegRISC extends MachConcreteNode implements MachNode { - public NegRISC( Node neg ) { super(neg); } - @Override public String op() { return "neg"; } - @Override public RegMask regmap(int i) { assert i==1; return riscv.RMASK; } - @Override public RegMask outregmap() { return riscv.WMASK; } - @Override public void encoding( Encoding enc ) { - short dst = enc.reg(this ); - short src = enc.reg(in(1)); - riscv.r_type(riscv.OP,dst,0,0,src,0x20); - } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = -").p(code.reg(in(1))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java deleted file mode 100644 index 6c03bda6..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.print.ASMPrinter; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; - -public class NewRISC extends NewNode implements MachNode, RIPRelSize { - // A pre-zeroed chunk of memory. - NewRISC( NewNode nnn ) { super(nnn); } - @Override public void encoding( Encoding enc ) { - // Generic external encoding; 2 ops. - enc.external(this,"calloc"); - // A1 is a caller-save, allowed to crush building external address - // auipc - enc.add4(riscv.i_type(riscv.OP_IMM , _arg2Reg , 0, riscv.ZERO, 1)); - enc.add4(riscv.u_type(riscv.OP_AUIPC, riscv.A2 , 0)); - enc.add4(riscv.i_type(riscv.OP_JALR , riscv.RPC, 0, riscv.A2, 0)); - } - - // Patch is for running "new" in a JIT. - // Delta is from opcode start - @Override public byte encSize(int delta) { return 4*3; } - - // Patch is for running "new" in a JIT. - // Delta is from opcode start - @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - // Negative patches are JIT emulator targets. - if( opStart+delta < 0 ) { - delta -= 4; // Offset from the AUIPC start - int imm20 = delta>>12; - if( ((delta>>11)&1)==1 ) imm20++; // Correct accidental sign extension - int imm12 = delta&0xFFF; - enc.patch4(opStart+4,riscv.u_type(riscv.OP_AUIPC, riscv.A2 , imm20)); - enc.patch4(opStart+8,riscv.i_type(riscv.OP_JALR , riscv.RPC, 0, riscv.A2, imm12)); - } else - throw Utils.TODO(); - } - - // General form: "alloc #bytes PC" - @Override public void asm(CodeGen code, SB sb) { - sb.p("ldi a1=#1\n"); - sb.p("auipc a2=#calloc\n"); - sb.p("call a2+#calloc, a0"); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java deleted file mode 100644 index 1369ee76..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.MachConcreteNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.NotNode; -import com.seaofnodes.simple.util.SB; - -public class NotRISC extends MachConcreteNode implements MachNode { - public NotRISC(NotNode not) { super(not); } - @Override public String op() { return "not"; } - @Override public RegMask regmap(int i) { return riscv.RMASK; } - @Override public RegMask outregmap() { return riscv.WMASK; } - @Override public void encoding( Encoding enc ) { - short dst = enc.reg(this ); - short src = enc.reg(in(1)); - enc.add4(riscv.i_type(riscv.OP_IMM, dst, 2, src, 1)); - } - @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java deleted file mode 100644 index 963b9d50..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.util.Utils; - -public class OrIRISC extends ImmRISC { - public OrIRISC( Node and, int imm) { super(and,imm); } - @Override int opcode() { return riscv.OP_IMM; } - @Override int func3() { return 6; } - @Override public String glabel() { return "|"; } - @Override public String op() { return "ori"; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java deleted file mode 100644 index a310a1f4..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class OrRISC extends MachConcreteNode implements MachNode { - public OrRISC(Node or) { super(or); } - @Override public String op() { return "or"; } - @Override public RegMask regmap(int i) { return riscv.RMASK; } - @Override public RegMask outregmap() { return riscv.WMASK; } - @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,6,0); } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" | ").p(code.reg(in(2))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java deleted file mode 100644 index 0f8db4e5..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.Encoding; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.ParmNode; - -public class ParmRISC extends ParmNode implements MachNode { - final RegMask _rmask; - ParmRISC(ParmNode parm) { - super(parm); - _rmask = riscv.callInMask(fun().sig(),_idx,0); - } - @Override public RegMask regmap(int i) { return null; } - @Override public RegMask outregmap() { return _rmask; } - @Override public void encoding( Encoding enc ) { } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java deleted file mode 100644 index 2f9ac469..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.Encoding; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.ProjNode; - -public class ProjRISC extends ProjNode implements MachNode { - ProjRISC(ProjNode p) { super(p); } - @Override public RegMask regmap(int i) { return null; } - @Override public RegMask outregmap() { - return ((MachNode)in(0)).outregmap(_idx); - } - @Override public void encoding( Encoding enc ) { } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java deleted file mode 100644 index 7e4631d3..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.Encoding; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.FunNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.ReturnNode; -import com.seaofnodes.simple.util.Utils; - -public class RetRISC extends ReturnNode implements MachNode { - public RetRISC(ReturnNode ret, FunNode fun) { super(ret, fun); fun.setRet(this); } - @Override public void encoding( Encoding enc ) { - int sz = fun()._frameAdjust; - if( sz >= 1L<<12 ) throw Utils.TODO(); - if( sz != 0 ) - enc.add4(riscv.i_type(riscv.OP_IMM, riscv.RSP, 0, riscv.RSP, sz & 0xFFF)); - short rpc = enc.reg(rpc()); - enc.add4(riscv.i_type(riscv.OP_JALR, riscv.ZERO, 0, rpc, 0)); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java deleted file mode 100644 index 3424cd01..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.BoolNode; -import com.seaofnodes.simple.node.MachConcreteNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.util.SB; - -// corresponds to feq.d = equal -// fle.d = less than or equal -// flt.d = less than -public class SetFRISC extends MachConcreteNode implements MachNode { - final String _bop; // One of <,<=,== - SetFRISC( BoolNode bool ) { - super(bool); - assert bool.isFloat(); - _bop = bool.op(); - } - @Override public String op() { return "set"+_bop; } - @Override public RegMask regmap(int i) { return riscv.FMASK; } - @Override public RegMask outregmap() { return riscv.WMASK; } - @Override public void encoding( Encoding enc ) { - short dst = enc.reg(this ); - short src1 = (short)(enc.reg(in(1))-riscv.F_OFFSET); - short src2 = (short)(enc.reg(in(2))-riscv.F_OFFSET); - int body = riscv.r_type(riscv.OP_FP,dst,riscv.fsetop(_bop),src1,src2,0b1010001/*FCMP*/); - enc.add4(body); - } - - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ").p(_bop).p(" ").p(code.reg(in(2))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java deleted file mode 100644 index 690e2102..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.util.Utils; -import com.seaofnodes.simple.node.Node; - -// corresponds to slti,sltiu -public class SetIRISC extends ImmRISC { - final boolean _unsigned; // slti vs sltiu - public SetIRISC( Node src, int imm12, boolean unsigned ) { - super(src,imm12); - _unsigned = unsigned; - } - @Override public String op() { return "slt" + (_unsigned ? "u":"") + "i"; } - @Override public String glabel() { return "<"+ (_unsigned ? "u":""); } - @Override int opcode() { return 0b0010011; } - @Override int func3() { return _unsigned ? 0b011 : 0b010; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java deleted file mode 100644 index 848cc59f..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -// corresponds to slt sltu -public class SetRISC extends MachConcreteNode implements MachNode { - final boolean _unsigned; - public SetRISC( Node cmp, boolean unsigned ) { super(cmp); _unsigned=unsigned; } - @Override public String op() { return "slt" + (_unsigned ? "u":""); } - @Override public RegMask regmap(int i) { return riscv.RMASK; } - @Override public RegMask outregmap() { return riscv.WMASK; } - @Override public void encoding( Encoding enc ) { - riscv.r_type(enc,this,_unsigned ? 0b011 : 0b010,0); - } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" < ").p(code.reg(in(2))); - } -}
\ No newline at end of file diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java deleted file mode 100644 index 12c0ddec..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.util.Utils; - -public class SllIRISC extends ImmRISC { - public SllIRISC( Node and, int imm) { super(and,imm); } - @Override int opcode() { return riscv.OP_IMM; } - @Override int func3() { return 1; } - @Override public String glabel() { return "<<"; } - @Override public String op() { return "slli"; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java deleted file mode 100644 index 5a4d4cc6..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -// Shift left logical -public class SllRISC extends MachConcreteNode implements MachNode { - public SllRISC(Node sll) { super(sll); } - @Override public String op() { return "shl"; } - @Override public RegMask regmap(int i) { return riscv.RMASK; } - @Override public RegMask outregmap() { return riscv.WMASK; } - @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,1,0); } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" << ").p(code.reg(in(2))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java deleted file mode 100644 index 6082cbbf..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.FunNode; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.node.SplitNode; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; - -public class SplitRISC extends SplitNode { - SplitRISC( String kind, byte round ) { super(kind,round, new Node[2]); } - @Override public RegMask regmap(int i) { return riscv.SPLIT_MASK; } - @Override public RegMask outregmap() { return riscv.SPLIT_MASK; } - @Override public void encoding( Encoding enc ) { - short dst = enc.reg(this ); - short src = enc.reg(in(1)); - - // Flag for being either XMM or stack - boolean dstX = dst >= riscv.F_OFFSET; - boolean srcX = src >= riscv.F_OFFSET; - - if( dst >= riscv.MAX_REG ) { - // Store to SP - if( src >= riscv.MAX_REG ) { - throw Utils.TODO(); // Very rare stack-stack move - } - int off = enc._fun.computeStackOffset(enc._code,dst); - int op = srcX ? riscv.OP_STOREFP : riscv.OP_STORE; - if( srcX ) src -= riscv.F_OFFSET; - enc.add4(riscv.s_type(op, 0b011, riscv.RSP, src, off)); - return; - } - if( src >= riscv.MAX_REG ) { - // Load from SP - int off = enc._fun.computeStackOffset(enc._code,src); - int op = dstX ? riscv.OP_LOADFP : riscv.OP_LOAD; - if( dstX ) dst -= riscv.F_OFFSET; - enc.add4(riscv.i_type(op, dst, 0b011, riscv.RSP, off)); - return; - } - // pick opcode based on regs - if( !dstX && !srcX ) { - // GPR->GPR - enc.add4(riscv.r_type(riscv.OP,dst,0,src,riscv.ZERO,0)); - } else if( dstX && srcX ) { - // FPR->FPR - src -= riscv.F_OFFSET; - dst -= riscv.F_OFFSET; - enc.add4(riscv.r_type(riscv.OP_FP, dst, 0b000, src, src, 0b0010101)); // dst = FMIN(src,src) - } else if(!srcX && dstX) { - //GPR->FPR - // fmv.d.x - enc.add4(riscv.r_type(riscv.OP_FP, dst - riscv.F_OFFSET, 0, src, 0, 0b1111001)); - } else if(srcX && !dstX) { - //FPR->GPR - //fmv.x.d - enc.add4(riscv.r_type(riscv.OP_FP, dst, 0, src - riscv.F_OFFSET, 0, 0b1110001)); - } - } - - // General form: "mov dst = src" - @Override public void asm(CodeGen code, SB sb) { - FunNode fun = code._encoding==null ? null : code._encoding._fun; - sb.p(code.reg(this,fun)).p(" = ").p(code.reg(in(1),fun)); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java deleted file mode 100644 index fcfa0774..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.util.Utils; - -public class SraIRISC extends ImmRISC { - public SraIRISC( Node and, int imm) { - super(and,imm | (0x20 << 5)); - } - @Override int opcode() { return riscv.OP_IMM; } - @Override int func3() { return 5;} - @Override public String glabel() { return ">>"; } - @Override public String op() { return "srai"; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java deleted file mode 100644 index e2e34415..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -// Right Shift Arithmetic -public class SraRISC extends MachConcreteNode implements MachNode { - public SraRISC(Node sra) { super(sra); } - @Override public String op() { return "sar"; } - @Override public RegMask regmap(int i) { return riscv.RMASK; } - @Override public RegMask outregmap() { return riscv.WMASK; } - @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,0b101,0b00100000); } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> ").p(code.reg(in(2))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java deleted file mode 100644 index 2f73e139..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.util.Utils; - -public class SrlIRISC extends ImmRISC { - public SrlIRISC( Node and, int imm, boolean pop) { super(and,imm,pop); } - @Override int opcode() { return riscv.OP_IMM; } - @Override int func3() { return 5; } - @Override public String glabel() { return ">>>"; } - @Override public String op() { return "srli"; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java deleted file mode 100644 index 1eccc1c8..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -// Right Shift Logical -public class SrlRISC extends MachConcreteNode implements MachNode { - public SrlRISC(Node srli) { super(srli); } - @Override public String op() { return "shr"; } - @Override public RegMask regmap(int i) { return riscv.RMASK; } - @Override public RegMask outregmap() { return riscv.WMASK; } - @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,7,0); } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >>> ").p(code.reg(in(2))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java deleted file mode 100644 index d06c0c35..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; - -// Store memory addressing on ARM -// Support imm, reg(direct), or reg+off(indirect) addressing -// Base = base - base pointer, offset is added to base -// idx = null -// off = off - offset added to base) -// imm = imm - immediate value to store -// val = Node of immediate value to store(null if its a constant immediate) - -//e.g s.cs[0] = 67; // C -// base = s.cs, off = 4, imm = 67, val = null - -// sw rs2,offset(rs1) -public class StoreRISC extends MemOpRISC { - StoreRISC( StoreNode st, Node base, int off, Node val ) { super(st, base, off, val); } - @Override public String op() { return "st"+_sz; } - @Override public RegMask regmap(int i) { - // 0 - ctrl - if( i==1 ) return null; // mem - if( i==2 ) return riscv.RMASK; // ptr - // 2 - index - if( i==4 ) return riscv.MEM_MASK; // Wide mask to store GPR and FPR - return null; // Anti-dependence - } - @Override public RegMask outregmap() { return null; } - - @Override public void encoding( Encoding enc ) { - short val = enc.reg(val()); - short ptr = enc.reg(ptr()); - int op = val >= riscv.F_OFFSET ? riscv.OP_STOREFP : riscv.OP_STORE; - if( val >= riscv.F_OFFSET ) val -= riscv.F_OFFSET; - enc.add4(riscv.s_type(op, func3()&7, ptr, val == -1 ? riscv.ZERO : val, _off)); - } - - @Override public void asm(CodeGen code, SB sb) { - asm_address(code,sb).p(","); - if( val()==null ) sb.p("#").p("0"); - else sb.p(code.reg(val())); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java deleted file mode 100644 index da3e92f4..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -// fsub.d -public class SubFRISC extends MachConcreteNode implements MachNode { - SubFRISC(Node subf) {super(subf);} - @Override public String op() { return "subf"; } - @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; } - @Override public RegMask outregmap() { return riscv.FMASK; } - @Override public void encoding( Encoding enc ) { riscv.rf_type(enc,this,riscv.RM.RNE,5); } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java deleted file mode 100644 index 2adfce23..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class SubRISC extends MachConcreteNode implements MachNode { - public SubRISC( Node sub ) { super(sub); } - @Override public String op() { return "sub"; } - @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.RMASK; } - @Override public RegMask outregmap() { return riscv.WMASK; } - @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,0,0x20); } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - ").p(code.reg(in(2))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java deleted file mode 100644 index 7e057507..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.ConstantNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.type.TypeFunPtr; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; - -public class TFPRISC extends ConstantNode implements MachNode, RIPRelSize { - TFPRISC(ConstantNode con) { super(con); } - @Override public String op() { return "ldx"; } - @Override public RegMask regmap(int i) { return null; } - @Override public RegMask outregmap() { return riscv.WMASK; } - @Override public boolean isClone() { return true; } - @Override public TFPRISC copy() { return new TFPRISC(this); } - @Override public void encoding( Encoding enc ) { - enc.relo(this); - // TODO: 1 op encoding, plus a TODO if it does not fit - short dst = enc.reg(this); - TypeFunPtr tfp = (TypeFunPtr)_con; - // auipc t0,0 - enc.add4(riscv.u_type(riscv.OP_AUIPC, dst, 0)); - // addi t1,t0 + #0 - enc.add4(riscv.i_type(riscv.OP_IMM, dst, 0, dst, 0)); - } - - @Override public byte encSize(int delta) { - if( -(1L<<11) <= delta && delta < (1L<<11) ) return 4; - throw Utils.TODO(); - } - - // Delta is from opcode start - @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - short rpc = enc.reg(this); - if( opLen==8 ) { - // AUIPC (upper 20 bits) - // opstart of add - int next = opStart + opLen; - enc.patch4(opStart,riscv.u_type(riscv.OP_AUIPC, rpc, delta>>12)); - // addi(low 12 bits) - enc.patch4(next,riscv.i_type(riscv.OP_IMM, rpc, 0, rpc, delta & 0xFFF)); - // addi - } else { - // should not happen as one instruction is 4 byte, and TFP arm encodes 2. - throw Utils.TODO(); - } - } - - @Override public void asm(CodeGen code, SB sb) { - _con.print(sb.p(code.reg(this)).p(" #")); - } - @Override public boolean eq(Node n) { return this==n; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java deleted file mode 100644 index 44e73d1d..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.ConstantNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.type.TypeMemPtr; -import com.seaofnodes.simple.util.SB; - -public class TMPRISC extends ConstantNode implements MachNode, RIPRelSize { - TMPRISC(ConstantNode con) { super(con); } - @Override public String op() { return "ldp"; } - @Override public RegMask regmap(int i) { return null; } - @Override public RegMask outregmap() { return riscv.WMASK; } - @Override public boolean isClone() { return true; } - @Override public TMPRISC copy() { return new TMPRISC(this); } - @Override public void encoding( Encoding enc ) { - enc.largeConstant(this,((TypeMemPtr)_con)._obj,0,-1); - short dst = enc.reg(this); - // AUIPC dst,#hi20_constant_pool - enc.add4(riscv.u_type(riscv.OP_AUIPC, dst, 0)); - // addi dst,[dst+#low12_constant_pool] - enc.add4(riscv.i_type(riscv.OP_IMM, dst, 0, dst, 0)); - } - - @Override public byte encSize(int delta) { return 8; } - - // Delta is from opcode start - @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - short dst = enc.reg(this); - // AUIPC dst,#hi20_constant_pool - enc.patch4(opStart , riscv.u_type(riscv.OP_AUIPC, dst, delta>>12)); - // Load dst,[dst+#low12_constant_pool] - enc.patch4(opStart+4, riscv.i_type(riscv.OP_IMM, dst, 0, dst, delta & 0xFFF)); - } - - @Override public void asm(CodeGen code, SB sb) { - _con.print(sb.p(code.reg(this)).p(" #")); - } - @Override public boolean eq(Node n) { return this==n; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java deleted file mode 100644 index c4d5d8af..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import java.util.BitSet; - -// unconditional jump -public class UJmpRISC extends CFGNode implements MachNode, RIPRelSize { - @Override public String op() { return "jmp"; } - @Override public String label() { return op(); } - @Override public StringBuilder _print1( StringBuilder sb, BitSet visited ) { return sb.append("jmp "); } - @Override public RegMask regmap(int i) {return null; } - @Override public RegMask outregmap() { return null; } - @Override public Type compute() { throw Utils.TODO(); } - @Override public Node idealize() { throw Utils.TODO(); } - @Override public void encoding( Encoding enc ) { - // Short form +/-4K: beq r0,r0,imm12 - // Long form: auipc rX,imm20/32; jal r0,[rX+imm12/32] - enc.jump(this,uctrl()); - enc.add4(riscv.j_type(riscv.OP_JAL, 0, 0)); - } - - // Delta is from opcode start - @Override public byte encSize(int delta) { - if( -(1L<<20) <= delta && delta < (1L<<20) ) return 4; - // 2 word encoding needs a tmp register, must teach RA - throw Utils.TODO(); - } - - // Delta is from opcode start - @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - if( opLen==4 ) { - enc.patch4(opStart,riscv.j_type(riscv.OP_JAL, 0, delta)); - } else { - throw Utils.TODO(); - } - } - - @Override public void asm(CodeGen code, SB sb) { - CFGNode target = uctrl(); - //assert target.nOuts() > 1; // Should optimize jmp to empty targets - sb.p(label(target)); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java deleted file mode 100644 index 19ea6bc7..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.util.Utils; - -public class XorIRISC extends ImmRISC { - public XorIRISC( Node and, int imm) { super(and,imm); } - public XorIRISC( Node and, int imm, boolean pop) { super(and,imm,pop); } - @Override int opcode() { return riscv.OP_IMM; } - @Override int func3() { return 4; } - @Override public String glabel() { return "^"; } - @Override public String op() { return "xori"; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java deleted file mode 100644 index 413a7c3b..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class XorRISC extends MachConcreteNode implements MachNode { - public XorRISC(Node or) { super(or); } - @Override public String op() { return "xor"; } - @Override public RegMask regmap(int i) { return riscv.RMASK; } - @Override public RegMask outregmap() { return riscv.WMASK; } - @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,4,0); } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ^ ").p(code.reg(in(2))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java deleted file mode 100644 index 7a6361bc..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java +++ /dev/null @@ -1,543 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Utils; - -public class riscv extends Machine { - - public riscv(CodeGen code) { - if( !"SystemV".equals(code._callingConv) ) - throw new IllegalArgumentException("Unknown calling convention "+code._callingConv); - } - - @Override public String name() {return "riscv";} - //@Override public int maxReg() { return MAX_REG; } - - // Using ABI names instead of register names - public static int ZERO = 0, RPC= 1, RSP= 2, S12= 3, S13= 4, T0 = 5, T1 = 6, T2 = 7; - public static int S0 = 8, S1 = 9, A0 = 10, A1 = 11, A2 = 12, A3 = 13, A4 = 14, A5 = 15; - public static int A6 = 16, A7 = 17, S2 = 18, S3 = 19, S4 = 20, S5 = 21, S6 = 22, S7 = 23; - public static int S8 = 24, S9 = 25, S10= 26, S11= 27, T3 = 28, T4 = 29, T5 = 30, T6 = 31; - - // FP registers - public static int F0 = 32, F1 = 33, F2 = 34, F3 = 35, F4 = 36, F5 = 37, F6 = 38, F7 = 39; - public static int FS0 = 40, FS1 = 41, FA0 = 42, FA1 = 43, FA2 = 44, FA3 = 45, FA4 = 46, FA5 = 47; - public static int FA6 = 48, FA7 = 49, FS2 = 50, FS3 = 51, FS4 = 52, FS5 = 53, FS6 = 54, FS7 = 55; - public static int FS8 = 56, FS9 = 57, FS10 = 58, FS11 = 59, FT8 = 60, FT9 = 61, FT10 = 62, FT11 = 63; - - static final int MAX_REG = 64; - - public static final int F_OFFSET = 32; - - static final String[] REGS = new String[] { - "zero","rpc" , "rsp" , "s12" , "s13" , "t0" , "t1" , "t2" , - "s0" , "s1" , "a0" , "a1" , "a2" , "a3" , "a4" , "a5" , - "a6" , "a7" , "s2" , "s3" , "s4" , "s5" , "s6" , "s7" , - "s8" , "s9" , "s10" , "s11" , "t3" , "t4" , "t5" , "t6" , - "f0" , "f1" , "f2" , "f3" , "f4" , "f5" , "f6" , "f7" , - "fs0" , "fs1" , "fa0" , "fa1" , "fa2" , "fa3" , "fa4" , "fa5" , - "fa6" , "fa7" , "fs2" , "fs3" , "fs4" , "fs5" , "fs6" , "fs7" , - "fs8" , "fs9" , "fs10", "fs11", "ft8" , "ft9" , "ft10", "ft11" - // Register-based naming hella easier to debug than "official" register - // names when single-stepping in debugger. - //"zero", "r1" , "rsp" , "r3" , "r4" , "r5" , "r6" , "r7" , - //"r8" , "r9" , "r10" , "r11", "r12", "r13", "r14", "r15", - //"r16" , "r17" , "r18" , "r19", "r20", "r21", "r22", "r23", - //"r24" , "r25" , "r26" , "r27", "r28", "r29", "r30", "r31", - //"f0" , "f1" , "f2" , "f3" , "f4" , "f5" , "f6" , "f7" , - //"fs0" , "fs1" , "fa0" , "fa1" , "fa2" , "fa3" , "fa4" , "fa5" , - //"fa6" , "fa7" , "fs2" , "fs3" , "fs4" , "fs5" , "fs6" , "fs7" , - //"fs8" , "fs9" , "fs10", "fs11", "ft8" , "ft9" , "ft10", "ft11" - }; - @Override public String[] regs() { return REGS; } - - // General purpose register mask: pointers and ints, not floats - static final long RD_BITS = 0b11111111111111111111111111111111L; // All the GPRs - static final RegMask RMASK = new RegMask(RD_BITS); - - static final long WR_BITS = 0b11111111111111111111111111111010L; // All the GPRs, minus ZERO and RSP - static final RegMask WMASK = new RegMask(WR_BITS); - // Float mask from(f0-ft10). TODO: ft10,ft11 needs a larger RegMask - static final long FP_BITS = 0b11111111111111111111111111111111L<<F0; - static final RegMask FMASK = new RegMask(FP_BITS); - - // Load/store mask; both GPR and FPR - static final RegMask MEM_MASK = new RegMask(WR_BITS | FP_BITS); - - static final RegMask SPLIT_MASK = new RegMask( WR_BITS | FP_BITS, -1L ); - - - // Arguments masks - public static RegMask A0_MASK = new RegMask(A0); - public static RegMask A1_MASK = new RegMask(A1); - public static RegMask A2_MASK = new RegMask(A2); - public static RegMask A3_MASK = new RegMask(A3); - public static RegMask A4_MASK = new RegMask(A4); - public static RegMask A5_MASK = new RegMask(A5); - public static RegMask A6_MASK = new RegMask(A6); - public static RegMask A7_MASK = new RegMask(A7); - - public static final RegMask RPC_MASK = new RegMask(RPC); - - // Float arguments masks - public static RegMask FA0_MASK = new RegMask(FA0); - public static RegMask FA1_MASK = new RegMask(FA1); - public static RegMask FA2_MASK = new RegMask(FA2); - public static RegMask FA3_MASK = new RegMask(FA3); - public static RegMask FA4_MASK = new RegMask(FA4); - public static RegMask FA5_MASK = new RegMask(FA5); - public static RegMask FA6_MASK = new RegMask(FA6); - public static RegMask FA7_MASK = new RegMask(FA7); - - // Int arguments calling conv - static RegMask[] CALLINMASK = new RegMask[] { - A0_MASK, - A1_MASK, - A2_MASK, - A3_MASK, - A4_MASK, - A5_MASK, - A6_MASK, - A7_MASK - }; - - static RegMask[] XMMS = new RegMask[] { - FA0_MASK, - FA1_MASK, - FA2_MASK, - FA3_MASK, - FA4_MASK, - FA5_MASK, - FA6_MASK, - FA7_MASK - }; - - // major opcode: OP - public static int OP_LOAD = 0b00_000_11; - public static int OP_LOADFP = 0b00_001_11; - public static int OP_CUSTOM0 = 0b00_010_11; - public static int OP_IMM = 0b00_100_11; - public static int OP_AUIPC = 0b00_101_11; - - public static int OP_STORE = 0b01_000_11; - public static int OP_STOREFP = 0b01_001_11; - public static int OP_CUSTOM1 = 0b01_010_11; - public static int OP = 0b01_100_11; - public static int OP_LUI = 0b01_101_11; - - public static int OP_CUSTOM2 = 0b10_010_11; - public static int OP_FP = 0b10_100_11; - - public static int OP_BRANCH = 0b11_000_11; - public static int OP_JALR = 0b11_001_11; - public static int OP_RESERVED= 0b11_010_11; - public static int OP_JAL = 0b11_011_11; - - - // Since riscv instructions are fixed we can just or them together - public static int r_type(int opcode, int rd, int func3, int rs1, int rs2, int func7) { - assert 0 <= rs1 && rs1 < 32; - assert 0 <= rs2 && rs2 < 32; - assert 0 <= rd && rd < 32; - return (func7 << 25) | (rs2 << 20) | (rs1 << 15) | (func3 << 12) | (rd << 7) | opcode; - } - public static void r_type(Encoding enc, Node n, int func3, int func7) { - short dst = enc.reg(n); - short src1 = enc.reg(n.in(1)); - short src2 = enc.reg(n.in(2)); - int body = r_type(OP,dst,func3,src1,src2,func7); - enc.add4(body); - } - - public static void rf_type(Encoding enc, Node n, RM func3, int func7) { - short dst = (short)(enc.reg(n )-F_OFFSET); - short src1 = (short)(enc.reg(n.in(1))-F_OFFSET); - short src2 = (short)(enc.reg(n.in(2))-F_OFFSET); - int body = r_type(OP_FP,dst,func3.ordinal(),src1,src2,func7); - enc.add4(body); - } - - - public static int u_type(int opcode, int rd, int imm20) { - assert 0 <= rd && rd < 32; - return (imm20 << 12) | (rd << 7) | opcode; - } - - public static int j_type(int opcode, int rd, int delta) { - assert -(1L<<20) <= delta && delta < (1L<<20); - assert 0 <= rd && rd < 32; - // Messy branch offset encoding - // 31 30-21 20 19-12 11-7 6-0 - // 20 10- 1 11 19-12 rpc JAL - assert (delta&1)==0; // Low bit is always zero, not encoded - int imm10_01 = (delta>> 1) & 0x3FF; - int imm11 = (delta>>11) & 1; - int imm12_19 = (delta>>12) & 0xFF; - int imm20 = (delta>>19) & 1; - int bits = imm20<<19 | imm10_01 << 9 | imm11 << 8 | imm12_19; - return bits << 12 | rd << 7 | opcode; - } - - - public static int i_type(int opcode, int rd, int func3, int rs1, int imm12) { - assert 0 <= rd && rd < 32; - assert 0 <= rs1 && rs1 < 32; - assert opcode >= 0 && func3 >=0 && imm12 >= 0; // Zero-extend by caller - return (imm12 << 20) | (rs1 << 15) | (func3 << 12) | (rd << 7) | opcode; - } - - // S-type instructions(store) - public static int s_type(int opcode, int func3, int rs1, int rs2, int imm12) { - assert 0 <= rs1 && rs1 < 32; - assert 0 <= rs2 && rs2 < 32; - assert 0 <= func3; - - assert imm12 >= 0; // Masked to high zero bits by caller - int imm_lo = imm12 & 0x1F; - int imm_hi = imm12 >> 5; - return (imm_hi << 25) | (rs2 << 20) | (rs1 << 15) | (func3 << 12) | (imm_lo << 7) | opcode; - } - - // BRANCH - public static int b_type(int opcode, int func3, short rs1, short rs2, int delta) { - assert 0 <= rs1 && rs1 < 32; - assert 0 <= rs2 && rs2 < 32; - assert -4*1024 <= delta && delta < 4*1024; - assert (delta&1)==0; // Low bit is always zero, not encoded - // Messy branch offset encoding - // 31 30 29 28 27 26 25 24-20 19-15 14-12 11 10 9 8 7 6-0 - // 12 10 9 8 7 6 5 SRC2 SRC1 FUNC3 4 3 2 1 11 OP - int imm4_1 = (delta>> 1) & 0xF; - int imm10_5= (delta>> 5) &0x3F; - int imm11 = (delta>>11) & 1; - int imm12 = (delta>>12) & 1; - int imm5 = imm4_1<<1 | imm11; - int imm7 = imm12<<6 | imm10_5; - return (imm7 << 25 ) | (rs2 << 20) | (rs1 << 15) | (func3 << 12) | (imm5 << 7) | opcode; - } - - public enum RM { - RNE, // Round to Nearest, ties to Even - RTZ, // Round towards Zero - RDN, // Round Down - RUP, // Round Up - DIRECT, // Round to Nearest, ties to Max Magnitude - RESERVED1, // Reserved for futue use - RESERVED2, // Reserved for future use - DYN, // In instruction’s rm field, selects dynamic rounding mode; In Rounding Mode register, reserved - } - - // Since opcode is the same just return back func3 - static public int jumpop(String op) { - return switch(op) { - case "==" -> 0x0; - case "!=" -> 0x1; - case "<" -> 0x4; - case ">=" -> 0x5; - case "u<" -> 0x6; - case "u<=" -> 0x7; - default -> throw Utils.TODO(); - }; - } - - // Since opcode is the same just return back func3 - static public int fsetop(String op) { - return switch(op) { - case "<" -> 1; - case "<=" -> 0; - case "==" -> 2; - default -> throw Utils.TODO(); - }; - } - - @Override public RegMask callArgMask( TypeFunPtr tfp, int idx, int maxArgSlot ) { return callInMask(tfp,idx,maxArgSlot); } - static RegMask callInMask( TypeFunPtr tfp, int idx, int maxArgSlot ) { - if( idx==0 ) return RPC_MASK; - if( idx==1 ) return null; - if( idx-2 >= tfp.nargs() ) return null; // Anti-dependence - // Count floats in signature up to index - int fcnt=0; - for( int i=2; i<idx; i++ ) - if( tfp.arg(i-2) instanceof TypeFloat ) - fcnt++; - // Floats up to XMMS in XMM registers - if( tfp.arg(idx-2) instanceof TypeFloat ) { - if( fcnt < XMMS.length ) - return XMMS[fcnt]; - } else { - RegMask[] cargs = CALLINMASK; - if( idx-2-fcnt < cargs.length ) - return cargs[idx-2-fcnt]; - } - // Pass on stack slot(8 and higher) - if( maxArgSlot>0 ) throw Utils.TODO(); - return new RegMask(MAX_REG + 1 + (idx - 2)); - } - - @Override public short maxArgSlot( TypeFunPtr tfp ) { - int icnt=0, fcnt=0; // Count of ints, floats - for( int i=0; i<tfp.nargs(); i++ ) { - if( tfp.arg(i) instanceof TypeFloat ) fcnt++; - else icnt++; - } - int nstk = Math.max(icnt-8,0)+Math.max(fcnt-8,0); - return (short)nstk; - } - - static final long CALLER_SAVE = - (1L<<RPC) | - (1L<<A0 ) | (1L<<A1 ) | (1L<<A2 ) | (1L<<A3 ) | (1L<<A4 ) | (1L<<A5 ) | (1L<<A6 ) | (1L<<A7 ) | - (1L<<T0 ) | (1L<<T1 ) | (1L<<T2 ) | (1L<<T3 ) | (1L<<T4 ) | (1L<<T5 ) | (1L<<T6 ) | - (1L<<FA0) | (1L<<FA1) | (1L<<FA2) | (1L<<FA3) | (1L<<FA4) | (1L<<FA5) | (1L<<FA6) | (1L<<FA7) | - (1L<<F0 ) | (1L<<F1 ) | (1L<<F2 ) | (1L<<F3 ) | (1L<<F4 ) | (1L<<F5 ) | (1L<<F6 ) | (1L<<F7 ) | - (1L<<FT8) | (1L<<FT9) | (1L<<FT10)| (1L<<FT11); - - @Override public long callerSave() { return CALLER_SAVE; } - @Override public long neverSave() { return (1L<<RSP) | (1L<<ZERO); } - @Override public RegMask retMask( TypeFunPtr tfp ) { - return tfp.ret() instanceof TypeFloat ? FA0_MASK : A0_MASK; - } - @Override public int rpc() { return RPC; } - - - // Return a MachNode unconditional branch - @Override public CFGNode jump() { return new UJmpRISC(); } - - // Create a split op; any register to any register, including stack slots - @Override public SplitNode split(String kind, byte round, LRG lrg) { return new SplitRISC(kind,round); } - - // True if signed 12-bit immediate - public static boolean imm12(TypeInteger ti) { - // 52 = 64-12 - return ti.isConstant() && ((ti.value()<<52)>>52) == ti.value(); - } - // True if HIGH 20-bit signed immediate, with all zeros low. - public static boolean imm20Exact(TypeInteger ti) { - // shift left 32 to clear out the upper 32 bits. - // shift right SIGNED to sign-extend upper 32 bits; then shift 12 more to clear out lower 12 bits. - // shift left 12 to re-center the bits. - return ti.isConstant() && (((ti.value()<<32)>>44)<<12) == ti.value(); - } - - @Override public Node instSelect( Node n ) { - return switch (n) { - case AddFNode addf -> addf(addf); - case AddNode add -> add(add); - case AndNode and -> and(and); - case BoolNode bool -> cmp(bool); - case CallNode call -> call(call); - case CastNode cast -> new CastMach(cast); - case CallEndNode cend -> new CallEndMach(cend); - case CProjNode c -> new CProjNode(c); - case ConstantNode con -> con(con); - case DivFNode divf -> new DivFRISC(divf); - case DivNode div -> new DivRISC(div); - case FunNode fun -> new FunRISC(fun); - case IfNode iff -> jmp(iff); - case LoadNode ld -> ld(ld); - case MemMergeNode mem -> new MemMergeNode(mem); - case MinusNode neg -> new NegRISC(neg); - case MulFNode mulf -> new MulFRISC(mulf); - case MulNode mul -> new MulRISC(mul); - case NewNode nnn -> nnn(nnn); - case NotNode not -> new NotRISC(not); - case OrNode or -> or(or); - case ParmNode parm -> new ParmRISC(parm); - case PhiNode phi -> new PhiNode(phi); - case ProjNode prj -> prj(prj); - case ReadOnlyNode read-> new ReadOnlyMach(read); - case ReturnNode ret -> new RetRISC(ret, ret.fun()); - case SarNode sar -> sra(sar); - case ShlNode shl -> sll(shl); - case ShrNode shr -> srl(shr); - case StartNode start -> new StartNode(start); - case StopNode stop -> new StopNode(stop); - case StoreNode st -> st(st); - case SubFNode subf -> new SubFRISC(subf); - case SubNode sub -> sub(sub); - case ToFloatNode tfn -> i2f8(tfn); - case XorNode xor -> xor(xor); - - case LoopNode loop -> new LoopNode(loop); - case RegionNode region -> new RegionNode(region); - default -> throw Utils.TODO(); - }; - } - - private Node addf(AddFNode addf) { - return new AddFRISC(addf); - } - - private Node add(AddNode add) { - if( add.in(2) instanceof ConstantNode off2 && off2._con instanceof TypeInteger ti && imm12(ti) ) - return new AddIRISC(add, (int)ti.value(),true); - return new AddRISC(add); - } - - private Node and(AndNode and) { - if( and.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti ) { - if( imm12(ti) ) - return new AndIRISC(and, (int)ti.value()); - // Could be any size low bit mask - if( ti.value() == 0xFFFFFFFFL ) - return new SrlIRISC(new SllIRISC(and,32),32,false); - } - return new AndRISC(and); - } - - private Node call(CallNode call) { - return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp - ? new CallRISC(call, tfp) - : new CallRRISC(call); - } - private Node nnn(NewNode nnn) { - // TODO: pass in the TFP for alloc - return new NewRISC(nnn); - } - - private Node cmp(BoolNode bool) { - // Float variant directly implemented in hardware - if( bool.isFloat() ) - return new SetFRISC(bool); - - // Only < and <u are implemented in hardware. - // x < y - as-is - // x <= y - flip and negate; !(y < x); `slt tmp=y,x; xori dst=tmp,#1` - // x == y - sub and vs0 == `sub tmp=x-y; sltu dst=tmp,#1` - - // x > y - swap; y < x - // x >= y - swap and negate; !(x < y); `slt tmp=y,x;` then NOT. - // x != y - sub and vs0 == `sub tmp=x-y; sltu dst=tmp,#1` then NOT. - - // The ">", ">=" and "!=" in Simple include a NotNode, which can be - // implemented with a XOR. If one of the above is followed by a NOT - // we can remove the double XOR in the encodings. - - return switch( bool.op() ) { - case "<" -> bool.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) - ? new SetIRISC(bool, (int)ti.value(),false) - : new SetRISC(bool, false); - // x <= y - flip and negate; !(y < x); `slt tmp=y,x; xori dst=tmp,#1` - case "<=" -> new XorIRISC(new SetRISC(bool.swap12(), false),1,false); - // x == y - sub and vs0 == `sub tmp=x-y; sltu dst=tmp,#1` - case "==" -> new SetIRISC(new SubRISC(bool),1,true); - default -> throw Utils.TODO(); - }; - } - - private Node con( ConstantNode con ) { - if( !con._con.isConstant() ) return new ConstantNode( con ); // Default unknown caller inputs - return switch( con._con ) { - case TypeInteger ti -> { - if( imm12(ti) ) yield new IntRISC(con); - long x = ti.value(); - if( imm20Exact(ti) ) yield new LUI((int)x); - if( (x<<32)>>32 == x ) { // Signed lower 32-bit immediate - // Here, the low 12 bits get sign-extended, which means if - // bit11 is set, the value is negative and lowers the LUI - // value. Add a bit 12 to compensate - if( ((x>>11)&1)==1 ) x += 0x1000; - yield new AddIRISC(new LUI((int)(x & ~0xFFF)), (int)(x & 0xFFF),false); - } - // Need more complex sequence for larger constants... or a load - // from a constant pool, which does not need an extra register - yield new Int8RISC(con); - } - // Load from constant pool - case TypeFloat tf -> new FltRISC(con); - case TypeFunPtr tfp -> new TFPRISC(con); - case TypeMemPtr tmp -> new TMPRISC(con); - case TypeNil tn -> throw Utils.TODO(); - // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code. - case Type t -> t==Type.NIL ? new IntRISC(con) : new ConstantNode(con); - }; - } - - private Node jmp( IfNode iff ) { - if( iff.in(1) instanceof BoolNode bool && !bool.isFloat() ) { - // if less than or equal switch inputs - String bop = bool.op(); - if( bop.equals("<=") || bop.equals(">") ) - return new BranchRISC(iff, IfNode.swap(bop), bool.in(2), bool.in(1)); - return new BranchRISC(iff, bop, bool.in(1), bool.in(2)); - } - // Vs zero - return new BranchRISC(iff, iff.in(1)==null ? "==" : "!=", iff.in(1), null); - } - - private Node or(OrNode or) { - if( or.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti)) - return new OrIRISC(or, (int)ti.value()); - return new OrRISC(or); - } - - private Node xor(XorNode xor) { - if( xor.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti)) - return new XorIRISC(xor, (int)ti.value()); - return new XorRISC(xor); - } - - private Node sra(SarNode sar) { - if( sar.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti)) - return new SraIRISC(sar, (int)ti.value()); - return new SraRISC(sar); - } - - private Node srl(ShrNode shr) { - if( shr.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti)) - return new SrlIRISC(shr, (int)ti.value(),true); - return new SrlRISC(shr); - } - - private Node sll(ShlNode sll) { - if( sll.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti)) - return new SllIRISC(sll, (int)ti.value()); - return new SllRISC(sll); - } - - private Node sub(SubNode sub) { - return sub.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) - ? new AddIRISC(sub, (int)(-ti.value()),true) - : new SubRISC(sub); - } - - private Node i2f8(ToFloatNode tfn) { - assert tfn.in(1)._type instanceof TypeInteger ti; - return new I2F8RISC(tfn); - } - - private Node prj(ProjNode prj) { - return new ProjRISC(prj); - } - - private Node ld(LoadNode ld) { - return new LoadRISC(ld,address(ld),off); - } - - private Node st(StoreNode st) { - Node xval = st.val() instanceof ConstantNode con && con._con == TypeInteger.ZERO ? null : st.val(); - return new StoreRISC(st,address(st), off, xval); - } - - // Gather addressing mode bits prior to constructing. This is a builder - // pattern, but saving the bits in a *local* *global* here to keep mess - // contained. - private static int off; - private Node address( MemOpNode mop ) { - off = 0; // Reset - Node base = mop.ptr(); - // Skip/throw-away a ReadOnly, only used to typecheck - if( base instanceof ReadOnlyNode read ) base = read.in(1); - assert !(base instanceof AddNode) && base._type instanceof TypeMemPtr; // Base ptr always, not some derived - if( mop.off() instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) ) { - off = (int)ti.value(); - } else { - base = new AddRISC(base,mop.off()); - base._type = mop.ptr()._type; - } - return base; - } - -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java deleted file mode 100644 index 2513de37..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; - -public class AddFMemX86 extends MemOpX86 { - AddFMemX86( AddFNode add, LoadNode ld , Node base, Node idx, int off, int scale, Node val ) { - super(add,ld, base, idx, off, scale, 0, val ); - } - @Override public String op() { return "addf"+_sz; } - @Override public RegMask regmap(int i) { - if( i==1 ) return null; // Memory - if( i==2 ) return x86_64_v2.RMASK; // base - if( i==3 ) return x86_64_v2.RMASK; // index - if( i==4 ) return x86_64_v2.XMASK; // value - throw Utils.TODO(); - } - @Override public RegMask outregmap() { return x86_64_v2.XMASK; } - @Override public int twoAddress() { return 4; } - @Override public void encoding( Encoding enc ) { - // addsd xmm0, DWORD PTR [rdi+0xc] - short dst = enc.reg(this ); - short ptr = enc.reg(ptr()); - short idx = enc.reg(idx()); - // F opcode - enc.add1(0xF2); - // rex prefix must come next (REX.W is not set) - x86_64_v2.rexF(dst, ptr, idx, false, enc); - - // FP ADD - enc.add1(0x0F).add1(0x58); - - x86_64_v2.indirectAdr(_scale, idx, ptr, _off, dst, enc); - } - - // General form: "add dst = src + [base + idx<<2 + 12]" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = "); - sb.p(val()==null ? "#"+_imm : code.reg(val())).p(" + "); - asm_address(code,sb); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java deleted file mode 100644 index 02e0c7ea..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class AddFX86 extends MachConcreteNode implements MachNode { - AddFX86( Node addf ) { super(addf); } - @Override public String op() { return "addf"; } - @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; } - @Override public RegMask outregmap() { return x86_64_v2.XMASK; } - @Override public int twoAddress() { return 1; } - @Override public boolean commutes() { return true; } - - @Override public void encoding( Encoding enc ) { - // F2 0F 58 /r ADDSD xmm1, xmm2/m64 - short dst = (short)(enc.reg(this ) - x86_64_v2.XMM_OFFSET); - short src = (short)(enc.reg(in(2)) - x86_64_v2.XMM_OFFSET); - // Fopcode - enc.add1(0xF2); - // rex prefix must come next (REX.W is not set) - x86_64_v2.rexF(dst, src, 0, false, enc); - - enc.add1(0x0F).add1(0x58); - - enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src)); - } - - // General form: "addf dst += src" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" += ").p(code.reg(in(2))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java deleted file mode 100644 index 0ba390c6..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.Encoding; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; - -public class AddIX86 extends ImmX86 { - AddIX86( Node add, int imm ) { super(add,imm); } - @Override public String op() { - return _imm == 1 ? "inc" : (_imm == -1 ? "dec" : "addi"); - } - @Override public String glabel() { return "+"; } - @Override int opcode() { return 0x81; } - @Override int mod() { return 0; } - @Override public final void encoding( Encoding enc ) { - if( _imm== 1 || _imm== -1 ) { - short dst = enc.reg(this); // Also src1 - enc.add1( x86_64_v2.rex(0,dst,0)); - enc.add1(0xFF); - enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, _imm==1 ? 0 : 1, dst) ); - } else super.encoding(enc); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java deleted file mode 100644 index 3ee721e3..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class AddMemX86 extends MemOpX86 { - AddMemX86( AddNode add, LoadNode ld , Node base, Node idx, int off, int scale, int imm, Node val ) { - super(add,ld, base, idx, off, scale, imm, val ); - } - @Override public String op() { return "add"+_sz; } - @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - @Override public int twoAddress() { return 4; } - @Override public void encoding( Encoding enc ) { - // add something to register from memory - // add eax,DWORD PTR [rdi+0xc] - // REX.W + 03 /r ADD r64, r/m64 - short dst = enc.reg(this ); - short ptr = enc.reg(ptr()); - short idx = enc.reg(idx()); - - enc.add1(x86_64_v2.rex(dst, ptr, idx)); - // opcode - enc.add1(0x03); - - x86_64_v2.indirectAdr(_scale, idx, ptr, _off, dst, enc); - } - - // General form: "add dst += [base + idx<<2 + 12]" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" += "); - asm_address(code,sb); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java deleted file mode 100644 index 84543e8f..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.node.Node; - -public class AddX86 extends RegX86 { - AddX86( Node add ) { super(add); } - @Override public String op() { return "add"; } - @Override public String glabel() { return "+"; } - @Override int opcode() { return 0x03; } - @Override public boolean commutes() { return true; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java deleted file mode 100644 index 3c1b567c..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.node.Node; - -public class AndIX86 extends ImmX86 { - AndIX86( Node add, int imm ) { super(add,imm); } - @Override public String op() { return "andi"; } - @Override public String glabel() { return "&"; } - @Override int opcode() { return 0x81; } - @Override int mod() { return 4; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java deleted file mode 100644 index dedbce45..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.node.Node; - -public class AndX86 extends RegX86 { - AndX86( Node add ) { super(add); } - @Override public String op() { return "and"; } - @Override public String glabel() { return "&"; } - @Override int opcode() { return 0x23; } - @Override public boolean commutes() { return true; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java deleted file mode 100644 index 460f568b..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.CallNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.util.SB; - -public class CallRX86 extends CallNode implements MachNode { - CallRX86( CallNode call ) { super(call); } - @Override public String op() { return "callr"; } - @Override public String label() { return op(); } - @Override public RegMask regmap(int i) { - return i==_inputs._len - ? x86_64_v2.WMASK // Function call target - : x86_64_v2.callInMask(tfp(),i,fun()._maxArgSlot); // Normal argument - } - @Override public RegMask outregmap() { return null; } - @Override public void encoding( Encoding enc ) { - // FF /2 CALL r/m64 - // calls the function in the register - short src = enc.reg(fptr()); - enc.add1(0xFF); - enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.INDIRECT,2,src)); - } - - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(fptr())).p(" "); - for( int i=0; i<nargs(); i++ ) - sb.p(code.reg(arg(i))).p(" "); - sb.unchar(2); - } - -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java deleted file mode 100644 index ffdce7df..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeFunPtr; -import com.seaofnodes.simple.util.SB; - -public class CallX86 extends CallNode implements MachNode, RIPRelSize { - final TypeFunPtr _tfp; - final String _name; - CallX86( CallNode call, TypeFunPtr tfp ) { - super(call); - _inputs.pop(); // Pop constant target - assert tfp.isConstant(); - _tfp = tfp; - FunNode fun = CodeGen.CODE.link(tfp); - _name = fun==null ? ((ExternNode)call.fptr())._extern : fun._name; // Can be null for extern calls - } - @Override public String op() { return "call"; } - @Override public String label() { return op(); } - @Override public String name() { return _name; } - @Override public TypeFunPtr tfp() { return _tfp; } - @Override public RegMask regmap(int i) { return x86_64_v2.callInMask(_tfp,i,fun()._maxArgSlot); } - @Override public RegMask outregmap() { return null; } - @Override public int nargs() { return nIns()-2; } // Minus control, memory, fptr - - @Override public void encoding( Encoding enc ) { - FunNode fun = CodeGen.CODE.link(_tfp); - if( fun==null ) enc.external(this,_name); - else enc.relo(this); - enc.add1(0xe8).add4(0); - } - - // Delta is from opcode start, but X86 measures from the end of the 5-byte encoding - @Override public byte encSize(int delta) { return 5; } - - // Delta is from opcode start - @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - enc.patch4(opStart+1, delta-5); - } - - @Override public void asm(CodeGen code, SB sb) { - sb.p(_name).p(" "); - for( int i=0; i<nargs(); i++ ) - sb.p(code.reg(in(i+2))).p(" "); - sb.unchar(2); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java deleted file mode 100644 index 5b97ef63..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class CmpFX86 extends MachConcreteNode implements MachNode { - CmpFX86( Node cmp ) { super(cmp); } - @Override public String op() { return "cmpf"; } - @Override public RegMask regmap(int i) { return x86_64_v2.XMASK; } - @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; } - - @Override public void encoding( Encoding enc ) { - // UCOMISD - short src1 = (short)(enc.reg(in(1)) - x86_64_v2.XMM_OFFSET); - short src2 = (short)(enc.reg(in(2)) - x86_64_v2.XMM_OFFSET); - // float opcode - enc.add1(0x66); - // rex prefix must come next (REX.W is not set) - x86_64_v2.rexF(src1, src2, 0, false, enc); - - enc.add1(0x0F).add1(0x2E); - enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, src1, src2)); - } - - // General form: "cmp src1,src2" - @Override public void asm(CodeGen code, SB sb) { - String dst = code.reg(this); - if( dst!="flags" ) sb.p(dst).p(" = "); - sb.p(code.reg(in(1))).p(", ").p(code.reg(in(2))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java deleted file mode 100644 index 8b8318eb..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -// Compare immediate. Sets flags. -public class CmpIX86 extends MachConcreteNode implements MachNode { - final int _imm; - CmpIX86( Node cmp, int imm, boolean swap ) { - super(cmp); - _inputs.del(swap ? 1 : 2); - _imm = imm; - } - CmpIX86( CmpIX86 cmp ) { - super((Node)null); - addDef(null); - addDef(cmp.in(1)); - _imm = cmp._imm; - } - - @Override public String op() { return _imm==0 ? "test" : "cmp"; } - @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } - @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; } - @Override public boolean isClone() { return true; } - @Override public Node copy() { return new CmpIX86(this); } - - @Override public final void encoding( Encoding enc ) { - short dst = enc.reg(in(1)); // Also src1 - enc.add1(x86_64_v2.rex(0, dst, 0)); - // opcode; 0x81 or 0x83; 0x69 or 0x6B - enc.add1( 0x81 + (x86_64_v2.imm8(_imm) ? 2 : 0) ); - enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 7, dst) ); - // immediate(4 bytes) 32 bits or (1 byte)8 bits - if( x86_64_v2.imm8(_imm) ) enc.add1(_imm); - else enc.add4(_imm); - } - @Override public void asm(CodeGen code, SB sb) { - String dst = code.reg(this); - if( dst!="flags" ) sb.p(dst).p(" = "); - sb.p(code.reg(in(1))); - if( _imm != 0 ) sb.p(", #").p(_imm); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java deleted file mode 100644 index 11f900ed..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; -import com.seaofnodes.simple.util.SB; - -public class CmpMemX86 extends MemOpX86 { - final boolean _swap; // Op switched LHS, RHS - CmpMemX86( BoolNode bool, LoadNode ld, Node base, Node idx, int off, int scale, int imm, Node val, boolean swap ) { - super(bool,ld, base, idx, off, scale, imm, val ); - _swap = swap; - assert !_swap || val() != null || bool.op()=="==" || bool.op()=="!="; // Cannot swap with immediate - } - @Override public String op() { return ((val()==null && _imm==0) ? "test" : "cmp") + _sz; } - @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; } - @Override public void encoding( Encoding enc ) { - // REX.W + 81 /7 id CMP r/m64, imm32 | REX.W + 39 /r CMP r/m64,r64 - // CMP [mem], imm32 - - short ptr = enc.reg(ptr()); - short idx = enc.reg(idx()); - short src = enc.reg(val()); - - if(src == -1) { - assert !_swap; - int log = _declaredType.log_size(); - x86_64_v2.rexF(-1, ptr, idx, log==3, enc); - switch(log) { - case 0: enc.add1(0x80); break; - case 1: enc.add1(0x66).add1(0x81); break; - case 2: enc.add1(0x81); break; - case 3: enc.add1(0x81); break; - } - x86_64_v2.indirectAdr(_scale, idx, ptr, _off, 7, enc); - switch(log) { - case 0: enc.add1(_imm); break; - case 1: enc.add2(_imm); break; - case 2: enc.add4(_imm); break; - case 3: enc.add8(_imm); break; - } - } else { - encVal(enc, _declaredType, ptr, idx, src, _off, _scale, _swap); - } - } - - static void encVal(Encoding enc, Type decl, short ptr, short idx, short src, int off, int scale, boolean _swap) { - if(decl instanceof TypeFloat) { - src -= (short)x86_64_v2.XMM_OFFSET; - enc.add1(decl==TypeFloat.F32 ? 0xF3 : 0xF2).add1(0x0F).add1(0xC2); - } else { - int log = decl.log_size(); - x86_64_v2.rexF(src, ptr, idx, log == 3, enc); - int swap = _swap ? 2 : 0; - switch(log) { - case 0: enc.add1(0x38+swap); break; - case 1: enc.add1(0x66).add1(0x39+swap); break; - case 2: enc.add1(0x39+swap); break; - case 3: enc.add1(0x39+swap); break; - } - } - x86_64_v2.indirectAdr(scale, idx, ptr, off, src, enc); - } - - // General form: "cmp dst = src, [base + idx<<2 + 12]" - @Override public void asm(CodeGen code, SB sb) { - String dst = code.reg(this); - if( dst!="flags" ) sb.p(dst).p(" = "); - if( _swap ) asm_address(code,sb).p(","); - if( val()==null ) { - if( _imm!=0 ) sb.p("#").p(_imm); - } else { - sb.p(code.reg(val())); - } - if( !_swap ) asm_address(code,sb.p(", ")); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java deleted file mode 100644 index e7305cdf..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class CmpX86 extends MachConcreteNode implements MachNode { - CmpX86( Node add ) { super(add); } - @Override public String op() { return "cmp"; } - @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } - @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; } - // This one is marginal: cloning a Cmp[reg,reg] means stretching the - // lifetimes of 2 normal registers in exchange for shortening a flags - // register lifetime. Spilling flags is (probably) relatively expensive - // compared to some normal registers - and those normal registers might not - // be spilling! - @Override public boolean isClone() { return true; } - - @Override public void encoding( Encoding enc ) { - short dst = enc.reg(in(1)); - short src = enc.reg(in(2)); - - enc.add1(x86_64_v2.rex(dst, src, 0)); - enc.add1(0x3B); - enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src)); - } - @Override public void asm(CodeGen code, SB sb) { - String dst = code.reg(this); - if( dst!="flags" ) sb.p(dst).p(" = "); - sb.p(code.reg(in(1))).p(", ").p(code.reg(in(2))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java deleted file mode 100644 index 3427aea5..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class DivFX86 extends MachConcreteNode implements MachNode { - DivFX86( Node divf) { super(divf); } - @Override public String op() { return "divf"; } - @Override public RegMask regmap(int i) { return x86_64_v2.XMASK; } - @Override public RegMask outregmap() { return x86_64_v2.XMASK; } - @Override public int twoAddress() { return 1; } - - @Override public void encoding( Encoding enc ) { - // F2 0F 5E /r DIVSD xmm1, xmm2/m64 - short dst = (short)(enc.reg(this ) - x86_64_v2.XMM_OFFSET); - short src = (short)(enc.reg(in(2)) - x86_64_v2.XMM_OFFSET); - - // Fopcode - enc.add1(0xF2); - // rex prefix must come next (REX.W is not set) - x86_64_v2.rexF(dst, src, 0, false, enc); - - enc.add1(0x0F).add1(0x5E); - - enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src)); - } - - - // General form: "divf dst /= src" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" /= ").p(code.reg(in(2))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java deleted file mode 100644 index b5904a7f..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class DivX86 extends MachConcreteNode implements MachNode { - DivX86( Node div ) { super(div); } - @Override public String op() { return "div"; } - @Override public RegMask regmap(int i) { - return (i==1) ? x86_64_v2.RAX_MASK : x86_64_v2.DIV_MASK; - } - @Override public RegMask outregmap() { return x86_64_v2.RAX_MASK; } - @Override public RegMask killmap() { return x86_64_v2.RDX_MASK; } - - @Override public void encoding( Encoding enc ) { - // REX.W + F7 /7 IDIV r/m64 - short src = enc.reg(in(2)); - // sign extend rax before 128/64 bits division - // sign extend rax to rdx:rax - enc.add1(x86_64_v2.REX_W); - enc.add1(0x99); - enc.add1(x86_64_v2.rex(0, src, 0)); - // divide - enc.add1(0xF7); // opcode - enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 0x07, src)); - } - // General form: "div dst /= src" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2))); - } - @Override public String comment() { return "kill rdx"; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java deleted file mode 100644 index 9bcbac8f..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class FltX86 extends ConstantNode implements MachNode, RIPRelSize{ - private byte _encSize; - FltX86(ConstantNode con ) { super(con); } - @Override public String op() { return "ld8"; } - @Override public RegMask regmap(int i) { return null; } - @Override public RegMask outregmap() { return x86_64_v2.XMASK; } - @Override public boolean isClone() { return true; } - @Override public Node copy() { return new FltX86(this); } - - @Override public void encoding( Encoding enc ) { - // Load the constant into a FPR - // movsd xmm, [rip + 0] - // F2 0F 10 /r MOVSD xmm1, m64 - _encSize = 1/*Fop*/+0/*REX*/+2/*op*/+1/*MODRM*/+4/*offset*/; - short dst = (short)(enc.reg(this ) - x86_64_v2.XMM_OFFSET); - // Fopcode - enc.add1(0xF2); - // rex prefix must come next (REX.W is not set) - _encSize += x86_64_v2.rexF(dst, 0, 0, false, enc); - enc.add1(0x0F).add1(0x10); - // hard-code rip here - enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.INDIRECT, dst, 0x05)); - enc.add4(0); - enc.largeConstant(this,_con,_encSize-4,2/*ELF encoding PC32*/); - } - - // Delta is from opcode start - @Override public byte encSize(int delta) { - // TODO: 1-byte RIP offset? - return (byte)_encSize; - } - - // Delta is from opcode start - @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - enc.patch4(opStart+opLen-4,delta-opLen); - } - - @Override public void asm(CodeGen code, SB sb) { - _con.print(sb.p(code.reg(this)).p(" = #")); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java deleted file mode 100644 index cbd5fdbf..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.Encoding; -import com.seaofnodes.simple.node.FunNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.util.SB; - -public class FunX86 extends FunNode implements MachNode { - FunX86( FunNode fun ) { super(fun); } - @Override public void postSelect(CodeGen code) { - super.postSelect(code); - // One more for RPC pre-pushed; X86 special - _maxArgSlot++; - } - - @Override public void computeFrameAdjust(CodeGen code, int maxReg) { - super.computeFrameAdjust(code,maxReg); - // Alignment, but off by RPC - if( _hasCalls ) // If non-leaf, pad to 16b - _frameAdjust = ((_frameAdjust+8+8) & -16)-8; - } - - @Override public void encoding( Encoding enc ) { - int sz = _frameAdjust; - if( sz == 0 ) return; // Skip if no frame adjust - // opcode: 0x83, addi rsp with immediate 8 - enc.add1( x86_64_v2.REX_W ).add1( x86_64_v2.imm8(sz) ? 0x83 : 0x81 ); - enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 0b101, x86_64_v2.RSP) ); - if( x86_64_v2.imm8(sz) ) enc.add1(sz); - else enc.add4(sz); - } - -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java deleted file mode 100644 index 9f3d7905..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class I2f8X86 extends MachConcreteNode implements MachNode { - I2f8X86(Node i2f8 ) { super(i2f8); } - @Override public String op() { return "cvtf"; } - @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.WMASK; } - @Override public RegMask outregmap() { return x86_64_v2.XMASK; } - - @Override public void encoding( Encoding enc ) { - // F2 0F 2A /r CVTSI2SD xmm1, r32/m32 - short dst = (short)(enc.reg(this ) - x86_64_v2.XMM_OFFSET); - short src = enc.reg(in(1)); - - // Fopcode - enc.add1(0xF2); - // rex prefix must come next (REX.W is not set) - x86_64_v2.rexF(dst, src, 0, true, enc); - - enc.add1(0x0F).add1(0x2A); - - enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src)); - } - - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p("(flt)").p(code.reg(in(1))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java deleted file mode 100644 index 5e406911..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.util.SB; - -public abstract class ImmX86 extends MachConcreteNode implements MachNode { - final int _imm; - ImmX86( Node add, int imm ) { - super(add); - _inputs.pop(); // Pop constant input off - _imm = imm; - } - @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } - @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - @Override public int twoAddress() { return 1; } - @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; } - - abstract int opcode(); - abstract int mod(); - - @Override public void encoding( Encoding enc ) { - short dst = enc.reg(this); // Also src1 - enc.add1(x86_64_v2.rex(0, dst, 0)); - // opcode; 0x81 or 0x83; 0x69 or 0x6B - enc.add1( opcode() + (x86_64_v2.imm8(_imm) ? 2 : 0) ); - enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, mod(), dst) ); - // immediate(4 bytes) 32 bits or (1 byte)8 bits - if( x86_64_v2.imm8(_imm) ) enc.add1(_imm); - else enc.add4(_imm); - } - - // General form: "addi dst += #imm" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java deleted file mode 100644 index 3f2f696c..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.util.SB; - -// Integer constants -public class IntX86 extends ConstantNode implements MachNode { - IntX86( ConstantNode con ) { super(con); } - @Override public String op() { - return _con == Type.NIL || _con == TypeInteger.ZERO ? "xor" : "ldi"; - } - @Override public boolean isClone() { return true; } - @Override public Node copy() { return new IntX86(this); } - @Override public RegMask regmap(int i) { return null; } - @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - // Zero-set uses XOR kills flags - @Override public RegMask killmap() { - return _con == Type.NIL || _con == TypeInteger.ZERO ? x86_64_v2.FLAGS_MASK : null; - } - - @Override public void encoding( Encoding enc ) { - short dst = enc.reg(this); - // Short form for zero - if( _con==Type.NIL || _con==TypeInteger.ZERO ) { - // XOR dst,dst. Can skip REX is dst is low 8, makes this a 32b - // xor, which will also zero the high bits. - if( dst >= 8 ) enc.add1(x86_64_v2.rex(dst, dst, 0)); - enc.add1(0x33); // opcode - enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, dst)); - return; - } - - // Simply move the constant into a GPR - // Conditional encoding based on 64 or 32 bits - //REX.W + C7 /0 id MOV r/m64, imm32 - - long imm = ((TypeInteger)_con).value(); - if (Integer.MIN_VALUE <= imm && imm < 0) { - // We need sign extension, so use imm32 into 64 bit register - // REX.W + C7 /0 id MOV r/m64, imm32 - enc.add1(x86_64_v2.rex(0, dst, 0)); - enc.add1(0xC7); // 32 bits encoding - enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 0x00, dst)); - enc.add4((int)imm); - } else if (0 <= imm && imm <= 0xFFFFFFFFL) { - // We want zero extension into 64 bit register - // so move 32 bit into 32 bit register which zeros - // the upper bits in the 64 bit register. - // B8+ rd id MOV r32, imm32 - if (dst >= 8) enc.add1(0x41); - enc.add1(0xB8 + (dst & 0x07)); - enc.add4((int)imm); - } else { - // Just write the full 64 bit constant - // REX.W + B8+ rd io MOV r64, imm64 - enc.add1(x86_64_v2.rex(0, dst, 0)); - enc.add1(0xB8 + (dst & 0x07)); - enc.add8(imm); // 64 bits encoding - } - } - - // Human-readable form appended to the SB. Things like the encoding, - // indentation, leading address or block labels not printed here. - // Just something like "ld4\tR17=[R18+12] // Load array base". - // General form: "op\tdst=src+src" - @Override public void asm(CodeGen code, SB sb) { - String reg = code.reg(this); - if( _con == Type.NIL || _con == TypeInteger.ZERO ) - sb.p(reg).p(",").p(reg); - else - _con.print(sb.p(reg).p(" = #")); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java deleted file mode 100644 index de27aa5c..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; - -// Jump on flags, uses flags -public class JmpX86 extends IfNode implements MachNode, RIPRelSize { - String _bop; - JmpX86( IfNode iff, String bop ) { - super(iff); - _bop = bop; - } - @Override public String op() { return "j"+_bop; } - @Override public String label() { return op(); } - @Override public void postSelect(CodeGen code) { - Node set = in(1); - if( set==null ) return; // Never-node cutout - Node cmp = set.in(1); - // Bypass an expected Set and just reference the cmp directly - if( set instanceof SetX86 setx && (cmp instanceof CmpX86 || cmp instanceof CmpIX86 || cmp instanceof CmpMemX86 || cmp instanceof CmpFX86) ) { - _inputs.set( 1, cmp ); - _bop = setx._bop; - } else - throw Utils.TODO(); - } - @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.FLAGS_MASK; } - @Override public RegMask outregmap() { return null; } - @Override public void negate() { _bop = negate(_bop); } - - @Override public void encoding( Encoding enc ) { - if( in(1)!=null ) { - int op = x86_64_v2.jumpop(_bop); - enc.add1(op-16).add1(0); // Short form jump - } else { - if( _bop=="!=" ) return; // Inverted, no code - enc.add1(0xEB).add1(0); // Never-node - } - enc.jump(this,cproj(0)); - } - - // Delta is from opcode start, but X86 measures from the end of the 2-byte encoding - @Override public byte encSize(int delta) { - if( in(1)==null && _bop=="!=" ) return 0; // Inverted never-node, no code - return (byte)(x86_64_v2.imm8(delta-2) ? 2 : 6); - } - - // Delta is from opcode start - @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - assert !( in(1)==null && _bop=="!=" ); // Inverted never-node, no code no patch - byte[] bits = enc.bits(); - if( opLen==2 ) { - assert in(1)==null || bits[opStart] == x86_64_v2.jumpop(_bop)-16; - delta -= 2; // Offset from opcode END - assert (byte)delta==delta; - bits[opStart+1] = (byte)delta; - } else { - assert in(1)==null || bits[opStart] == x86_64_v2.jumpop(_bop)-16; - delta -= 6; // Offset from opcode END - bits[opStart] = 0x0F; - bits[opStart+1] = (byte)(in(1)==null ? 0xE9 : x86_64_v2.jumpop(_bop)); - enc.patch4(opStart+2,delta); - } - } - - @Override public void asm(CodeGen code, SB sb) { - if( in(1)!=null ) { // Never-node - String src = code.reg(in(1)); - if( src!="flags" ) sb.p(src).p(" "); - } else if( _bop=="!=" ) { - sb.p("never"); - return; - } - CFGNode prj = cproj(0).uctrlSkipEmpty(); // 0 is True is jump target - if( !prj.blockHead() ) prj = prj.cfg0(); - sb.p(label(prj)); - } - - @Override public String comment() { return "L"+cproj(1)._nid; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java deleted file mode 100644 index 2cd6018f..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class LeaX86 extends MachConcreteNode implements MachNode { - final int _scale; - final int _offset; - LeaX86( Node add, Node base, Node idx, int scale, int offset ) { - super(add); - assert scale==0 || scale==1 || scale==2 || scale==3; - _inputs.pop(); - _inputs.pop(); - _inputs.push(base); - _inputs.push(idx); - _scale = scale; - _offset = offset; - } - - @Override public String op() { return "lea"; } - @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.RMASK; } - @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - - @Override public void encoding( Encoding enc ) { - // REX.W + 8D /r LEA r64,m - short dst = enc.reg(this); - short ptr = enc.reg(in(1)); - short idx = enc.reg(in(2)); - // ptr is null - // just do: [(index * s) + disp32] - if( ptr == -1 ) { - enc.add1(x86_64_v2.rex(dst, 0, idx)); - enc.add1(0x8D); // opcode - enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.INDIRECT, dst, 0x04)); - enc.add1(x86_64_v2.sib(_scale, idx, x86_64_v2.RBP)); - enc.add4(_offset); - return; - } - - enc.add1(x86_64_v2.rex(dst, ptr, idx)); - enc.add1(0x8D); // opcode - // rsp is hard-coded here(0x04) - x86_64_v2.indirectAdr(_scale, idx, ptr, _offset, dst, enc); - } - - // General form: "lea dst = base + 4*idx + 12" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = "); - if( in(1) != null ) - sb.p(code.reg(in(1))).p(" + "); - sb.p(code.reg(in(2))).p("<<").p(_scale); - if( _offset!=0 ) sb.p(" + #").p(_offset); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java deleted file mode 100644 index 441cf693..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.LoadNode; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; - -public class LoadX86 extends MemOpX86 { - LoadX86( LoadNode ld, Node base, Node idx, int off, int scale ) { - super(ld,ld, base, idx, off, scale, 0); - } - @Override public String op() { return "ld"+_sz; } - @Override public RegMask outregmap() { return x86_64_v2.MEM_MASK; } - @Override public void encoding( Encoding enc ) { - // REX.W + 8B /r MOV r64, r/m64 - // Zero extension for u8, u16 and u32 but sign extension i8, i16, i32 - // Use movsx and movzx - short dst = enc.reg(this ); - short ptr = enc.reg(ptr()); - short idx = enc.reg(idx()); - enc(enc, _declaredType, dst, ptr, idx, _off, _scale); - } - - static void enc( Encoding enc, Type decl, short dst, short ptr, short idx, int off, int scale ) { - if( decl == TypeFloat.F32) enc.add1(0xF3); - if( decl == TypeFloat.F64) enc.add1(0xF2); - - if( decl.isa(TypeFloat.F64) ) - dst -= (short)x86_64_v2.XMM_OFFSET; - - x86_64_v2.rexF(dst, ptr, idx, decl != TypeInteger.U32 && decl != TypeFloat.F32 && decl != TypeFloat.F64, enc); - - if( false ) ; - else if( decl == TypeFloat.F32 ) enc.add1(0x0F).add1(0x10); // F3 0F 10 /r MOVSS xmm1, m32 - else if( decl == TypeFloat.F64 ) enc.add1(0x0F).add1(0x10); // F2 0F 10 /r MOVSD xmm1, m64 - else if( decl.isa(TypeInteger.I8)) enc.add1(0x0F).add1(0xBE); // sign extend: REX.W + 0F BE /r MOVSX r64, r/m8 - else if( decl == TypeInteger.I16 ) enc.add1(0x0F).add1(0xBF); // sign extend: REX.W + 0F BF /r MOVSX r64, r/m16 - else if( decl == TypeInteger.I32 ) enc.add1(0x63); // sign extend: REX.W + 63 /r MOVSXD r64, r/m32 - else if( decl == TypeInteger.U8 ) enc.add1(0x0F).add1(0xB6); // zero extend: REX.W + 0F B6 /r MOVZX r64, r/m8 - else if( decl == TypeInteger.U16 ) enc.add1(0x0F).add1(0xB7); // zero extend: REX.W + 0F B7 /r MOVZX r64, r/m16 - // Covers U32, I64/BOT, TMP - else if( decl.log_size()>=2 ) enc.add1(0x8B); // zero extend: 8B /r MOV r32, r/m32 - else throw Utils.TODO(); - - // includes modrm internally - x86_64_v2.indirectAdr(scale, idx, ptr, off, dst, enc); - } - - - // General form: "ldN dst,[base + idx<<2 + 12]" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(","); - asm_address(code,sb); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java deleted file mode 100644 index bd56d022..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class MemAddX86 extends MemOpX86 { - MemAddX86( StoreNode st, Node base, Node idx, int off, int scale, int imm, Node val ) { - super(st, st, base, idx, off, scale, imm, val ); - } - @Override public String op() { - return (_imm == 1 ? "inc" : (_imm == -1 ? "dec" : "add")) + _sz; - } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return null; } - @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; } - @Override public void encoding( Encoding enc ) { - // add something to memory - // REX.W + 01 /r | REX.W + 81 /0 id - // ADD [mem], imm32/reg - short ptr = enc.reg(ptr()); - short idx = enc.reg(idx()); - short src = enc.reg(val()); - - enc.add1(x86_64_v2.rex(src, ptr, idx)); - // opcode - enc.add1( src == -1 ? (_imm==1 ? 0xFF : 0x81): 0x01); - - // includes modrm - x86_64_v2.indirectAdr(_scale, idx, ptr, _off, src == -1 ? 0 : src, enc); - if( src == -1 && _imm!=1 ) enc.add4(_imm); - } - // General form: "add [base + idx<<2 + 12] += src" - @Override public void asm(CodeGen code, SB sb) { - asm_address(code,sb); - if( val()==null ) { - if( _imm != 1 && _imm != -1 ) sb.p(" += #").p(_imm); - } else sb.p(" += ").p(code.reg(val())); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java deleted file mode 100644 index 5f090717..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import java.lang.StringBuilder; -import java.util.BitSet; - -// Generic X86 memory operand base. -// inputs: -// ctrl - will appear for possibly RCE (i.e. array ops) -// mem - memory dependence edge -// base - Basic object pointer -// idx/null - Scaled index offset, or null if none -// val/null - Value to store, as part of an op-to-mem or op-from-mem. Null for loads, or if an immediate is being used -// Constants: -// offset - offset added to base. Can be zero. -// scale - scale on index; only 0,1,2,4,8 allowed, 0 is only when index is null -// imm - immediate value to store or op-to-mem, and only when val is null -public abstract class MemOpX86 extends MemOpNode implements MachNode { - final int _off; // Limit 32 bits - final int _scale; // Limit 0,1,2,3 - final int _imm; // Limit 32 bits - final char _sz; // Handy print name - MemOpX86( Node op, MemOpNode mop, Node base, Node idx, int off, int scale, int imm ) { - super(op,mop); - assert base._type instanceof TypeMemPtr && !(base instanceof AddNode); - assert (idx==null && scale==0) || (idx!=null && 0<= scale && scale<=3); - - // Copy memory parts from e.g. the LoadNode over the opcode, e.g. an Add - if( op != mop ) { - _inputs.set(0,mop.in(0)); // Control from mem op - _inputs.set(1,mop.in(1)); // Memory from mem op - _inputs.set(2,base); // Base handed in - } - - assert ptr() == base; - _inputs.setX(3,idx); - _off = off; - _scale = scale; - _imm = imm; - _sz = (char)('0'+(1<<_declaredType.log_size())); - } - - // Store-based flavors have a value edge - MemOpX86( Node op, MemOpNode mop, Node base, Node idx, int off, int scale, int imm, Node val ) { - this(op,mop,base,idx,off,scale,imm); - _inputs.setX(4,val); - } - - Node idx() { return in(3); } - Node val() { return in(4); } // Only for stores, including op-to-memory - - @Override public StringBuilder _printMach(StringBuilder sb, BitSet visited) { - return sb.append(".").append(_name); - } - - @Override public String label() { return op(); } - @Override public Type compute() { throw Utils.TODO(); } - @Override public Node idealize() { throw Utils.TODO(); } - - // Register mask allowed on input i. - @Override public RegMask regmap(int i) { - if( i==1 ) return null; // Memory - if( i==2 ) return x86_64_v2.RMASK; // base in GPR - if( i==3 ) return x86_64_v2.RMASK; // index in GPR - if( i==4 ) return _sz >= 2 - ? x86_64_v2.MEM_MASK // value in GPR or XMM - : x86_64_v2.RMASK; // Bytes and shorts in GPR only - return null; // Anti-dependence - } - - // "[base + idx<<2 + 12]" - SB asm_address(CodeGen code, SB sb) { - sb.p("[").p(code.reg(ptr())); - if( idx() != null ) { - sb.p("+").p(code.reg(idx())); - if( _scale != 0 ) - sb.p("*").p((1<<_scale)); - } - if( _off != 0 ) - sb.p("+").p(_off); - return sb.p("]"); - } - -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java deleted file mode 100644 index 7932c893..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class MulFX86 extends MachConcreteNode implements MachNode { - MulFX86( Node mulf) { super(mulf); } - @Override public String op() { return "mulf"; } - @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; } - @Override public RegMask outregmap() { return x86_64_v2.XMASK; } - @Override public int twoAddress() { return 1; } - @Override public boolean commutes() { return true; } - - @Override public void encoding( Encoding enc ) { - // F2 0F 59 /r MULSD xmm1,xmm2/m64 - short dst = (short)(enc.reg(this ) - x86_64_v2.XMM_OFFSET); - short src = (short)(enc.reg(in(2)) - x86_64_v2.XMM_OFFSET); - - // Fopcode - enc.add1(0xF2); - // rex prefix must come next (REX.W is not set) - x86_64_v2.rexF(dst, src, 0, false, enc); - - enc.add1(0x0F).add1(0x59).add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src)); - } - - // General form: "mulf dst *= src" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" *= ").p(code.reg(in(2))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java deleted file mode 100644 index 280b8185..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class MulIX86 extends MachConcreteNode implements MachNode { - final int _imm; - MulIX86( Node add, int imm ) { - super(add); - _inputs.pop(); // Pop the constant input - _imm = imm; // Record the constant - } - @Override public String op() { return "muli"; } - @Override public String glabel() { return "*"; } - @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } - @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - // Rare 3-address form - @Override public final void encoding( Encoding enc ) { - short dst = enc.reg(this ); // Also src1 - short src = enc.reg(in(1)); - enc.add1(x86_64_v2.rex(src, dst, 0)); - // opcode; 0x69 or 0x6B - enc.add1( 0x69 + (x86_64_v2.imm8(_imm) ? 2 : 0) ); - enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src) ); - // immediate(4 bytes) 32 bits or (1 byte)8 bits - if( x86_64_v2.imm8(_imm) ) enc.add1(_imm); - else enc.add4(_imm); - } - // General form: "addi dst += #imm" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * #").p(_imm); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java deleted file mode 100644 index 4d2a11ed..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class MulX86 extends MachConcreteNode implements MachNode { - MulX86( Node mul ) { super(mul); } - @Override public String op() { return "mul"; } - @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.RMASK; } - @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - @Override public int twoAddress() { return 1; } - @Override public boolean commutes() { return true; } - - @Override public void encoding( Encoding enc ) { - // REX.W + 0F AF /r IMUL r64, r/m64 - short dst = enc.reg(this ); // src1 - short src = enc.reg(in(2)); // src2 - enc.add1(x86_64_v2.rex(dst, src, 0)); - enc.add1(0x0F); // opcode - enc.add1(0xAF); // opcode - enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src)); - } - - // General form: "mul dst *= src" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java deleted file mode 100644 index 0c2baced..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class NegX86 extends MachConcreteNode implements MachNode { - NegX86(MinusNode not) { super(not); } - @Override public String op() { return "neg"; } - @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } - @Override public RegMask outregmap() { return x86_64_v2.RMASK; } - @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; } - @Override public int twoAddress() { return 1; } - - @Override public void encoding( Encoding enc ) { - short dst = enc.reg(this ); - enc.add1(x86_64_v2.rex(0, dst, 0)); - enc.add1(0xF7); // opcode - enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 3, dst)); - } - @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java deleted file mode 100644 index fc896dce..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeFunPtr; -import com.seaofnodes.simple.util.SB; - -public class NewX86 extends NewNode implements MachNode { - // A pre-zeroed chunk of memory. - NewX86( NewNode nnn ) { super(nnn); } - @Override public void encoding( Encoding enc ) { - enc.external(this,"calloc"); - // ldi rcx,#1 // number of elements to calloc - enc.add1(0xB8 + _arg2Reg).add4(1); - // E8 cd CALL rel32; - enc.add1(0xE8); - enc.add4(0); // offset - } - // General form: "alloc #bytes PC" - @Override public void asm(CodeGen code, SB sb) { - sb.p("ldi rcx = #1\n"); - sb.p("call #calloc"); - } - -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java deleted file mode 100644 index d9056be9..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class NotX86 extends MachConcreteNode implements MachNode { - NotX86(NotNode not) { super(not); } - @Override public String op() { return "not"; } - @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } - @Override public RegMask outregmap() { return x86_64_v2.RMASK; } - @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; } - - @Override public void encoding( Encoding enc ) { - assert !(in(1) instanceof NotNode); // Cleared out by peeps - assert !(in(1) instanceof BoolNode); // Cleared out by peeps - short dst = enc.reg(this ); - short src = enc.reg(in(1)); - - // Pre-zero using XOR dst,dst; since zero'd will not have a - // byte-dependency from the setz. Can skip REX is dst is low 8, makes - // this a 32b xor, which will also zero the high bits. - if( dst >= 8 ) enc.add1(x86_64_v2.rex(dst, dst, 0)); - enc.add1(0x33); // opcode - enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, dst)); - - // test rdi,rdi - enc.add1(x86_64_v2.rex(src, src, 0)); - enc.add1(0x85); - enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, src, src)); - - // setz (sete dil) - enc.add1(x86_64_v2.rex(dst, 0, 0)); - enc.add1(0x0F); - enc.add1(0x94); - enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, 0)); - } - @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java deleted file mode 100644 index 15f4824d..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.node.Node; - -public class OrIX86 extends ImmX86 { - OrIX86( Node add, int imm ) { super(add,imm); } - @Override public String op() { return "ori"; } - @Override public String glabel() { return "|"; } - @Override int opcode() { return 0x81; } - @Override int mod() { return 1; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java deleted file mode 100644 index bc8f0ea8..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.node.Node; - -public class OrX86 extends RegX86 { - OrX86( Node add ) { super(add); } - @Override public String op() { return "or"; } - @Override public String glabel() { return "|"; } - @Override int opcode() { return 0x0B; } - @Override public boolean commutes() { return true; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java deleted file mode 100644 index 5cfdd804..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.Encoding; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.ParmNode; - -public class ParmX86 extends ParmNode implements MachNode { - final RegMask _rmask; - ParmX86( ParmNode parm ) { - super(parm); - _rmask = x86_64_v2.callInMask(fun().sig(),_idx,1/*RPC*/); - } - @Override public RegMask regmap(int i) { return null; } - @Override public RegMask outregmap() { return _rmask; } - @Override public void encoding( Encoding enc ) { } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java deleted file mode 100644 index 046c7b4c..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.Encoding; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.ProjNode; - -public class ProjX86 extends ProjNode implements MachNode { - ProjX86( ProjNode p ) { super(p); } - @Override public RegMask regmap(int i) { return null; } - @Override public RegMask outregmap() { - return ((MachNode)in(0)).outregmap(_idx); - } - @Override public void encoding( Encoding enc ) {} -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java deleted file mode 100644 index 5a475efe..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public abstract class RegX86 extends MachConcreteNode { - RegX86( Node add ) { super(add); } - @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } - @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; } - @Override public int twoAddress() { return 1; } - abstract int opcode(); - @Override public void encoding( Encoding enc ) { - // REX.W + 01 /r - short dst = enc.reg(in(1)); // src1 - short src = enc.reg(in(2)); // src2 - - enc.add1(x86_64_v2.rex(dst, src, 0)); - enc.add1(opcode()); // opcode - enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src)); - } - // General form: "add dst += src" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" ").p(glabel()).p("= ").p(code.reg(in(2))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java deleted file mode 100644 index a35d43a2..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; - -public class RetX86 extends ReturnNode implements MachNode { - RetX86( ReturnNode ret, FunNode fun ) { super(ret, fun); fun.setRet(this); } - @Override public void encoding( Encoding enc ) { - int sz = fun()._frameAdjust; - if( sz != 0 ) { - enc.add1( x86_64_v2.REX_W ).add1( x86_64_v2.imm8(sz) ? 0x83 : 0x81 ); - enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 0b000, x86_64_v2.RSP) ); - if( x86_64_v2.imm8(sz) ) enc.add1(sz); - else enc.add4(sz); - } - enc.add1(0xC3); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java deleted file mode 100644 index 95e0c336..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.Encoding; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.util.SB; - -// Arithmetic Right Shift -public class SarIX86 extends MachConcreteNode implements MachNode { - final int _imm; - SarIX86( Node sari, int imm ) { super(sari); assert x86_64_v2.imm8(imm); _inputs.pop(); _imm = imm; } - @Override public String op() { return "sari"; } - @Override public String glabel() { return ">>"; } - int opcode() { return 0xC1; } - int mod() { return 7; } - - @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } - @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - @Override public int twoAddress() { return 1; } - - @Override public void encoding(Encoding enc) { - short dst = enc.reg(this); // Also src1 - enc.add1(x86_64_v2.rex(0, dst, 0)); - enc.add1( opcode()); - - enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, mod(), dst) ); - - // immediate(4 bytes) 32 bits or (1 byte)8 bits - if( x86_64_v2.imm8(_imm) ) enc.add1(_imm); - else enc.add4(_imm); - } - - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java deleted file mode 100644 index 4cc8b558..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.util.Utils; - -public class SarX86 extends RegX86 { - SarX86( Node add ) { super(add); } - @Override public String op() { return "sar"; } - @Override public String glabel() { return ">>"; } - @Override public RegMask regmap(int i) { - if (i == 1) return x86_64_v2.WMASK; - if (i == 2) return x86_64_v2.RCX_MASK; - throw Utils.TODO(); - } - @Override int opcode() { return 0xD3; } - @Override public void encoding( Encoding enc ) { - short dst = enc.reg(this ); // src1 - short src = enc.reg(in(2)); // src2 - enc.add1(x86_64_v2.rex(0, dst, 0)); - enc.add1(opcode()); // opcode - enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 7, dst)); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java deleted file mode 100644 index 0eff2afe..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; - -// Corresponds to the x86 instruction "sete && setne". -// Use result of comparison without jump. -public class SetX86 extends MachConcreteNode implements MachNode { - final String _bop; // One of <,<=,== - final boolean _unsigned; - // Constructor expects input is an X86 and not an Ideal node. - SetX86( Node cmp, String bop, boolean unsigned ) { - super(cmp); - _inputs.setLen(1); // Pop the cmp inputs - // Replace with the matched cmp - _inputs.push(cmp); - _bop = bop; - _unsigned = unsigned; - } - @Override public String op() { return "set"+_bop; } - @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.FLAGS_MASK; } - @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - - @Override public void encoding( Encoding enc ) { - // REX + 0F 94 - short dst = enc.reg(this ); - - // Optional rex, for dst - if( dst >= 4 ) enc.add1(x86_64_v2.rex(0, dst, 0, false)); - enc.add1(0x0F); // opcode - enc.add1(switch (_bop) { - case "==" -> 0x94; // SETE - case "<" -> _unsigned ? 0x92 : 0x9C; // SETB /SETL - case "<=" -> _unsigned ? 0x96 : 0x9E; // SETBE/SETLE - case ">=" -> _unsigned ? 0x93 : 0x9D; // SETAE/SETGE - default -> throw Utils.TODO(); - }); - enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 0, dst)); - - // low 8 bites are set, now zero extend for next instruction - if( dst >= 4 ) enc.add1(x86_64_v2.rex(dst, dst, 0, false)); - enc.add1(0x0F); // opcode - enc.add1(0xB6); // opcode - enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, dst)); - } - - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)); - String src = code.reg(in(1)); - if( src!="flags" ) sb.p(" = ").p(src); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java deleted file mode 100644 index f7b2f28a..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.util.SB; - -public class ShlIX86 extends MachConcreteNode implements MachNode { - final int _imm; - ShlIX86( Node shli, int imm ) { - super(shli); - assert x86_64_v2.imm8(imm); - _inputs.pop(); // Pop constant input off - _imm = imm; - } - public int opcode() { return 0xC1; } - public int mod() { return 4; } - - @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } - @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - @Override public int twoAddress() { return 1; } - - @Override public String op() { return "shli"; } - @Override public String glabel() { return "<<"; } - @Override public void encoding(Encoding enc) { - short dst = enc.reg(this); // Also src1 - enc.add1(x86_64_v2.rex(0, dst, 0)); - enc.add1( opcode()); - - enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, mod(), dst) ); - - // immediate(4 bytes) 32 bits or (1 byte)8 bits - if( x86_64_v2.imm8(_imm) ) enc.add1(_imm); - else enc.add4(_imm); - } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java deleted file mode 100644 index 5fee72d9..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.util.Utils; - -public class ShlX86 extends RegX86 { - ShlX86( Node add ) { super(add); } - @Override public String op() { return "shl"; } - @Override public String glabel() { return "<<"; } - @Override public RegMask regmap(int i) { - if (i == 1) return x86_64_v2.WMASK; - if (i == 2) return x86_64_v2.RCX_MASK; - throw Utils.TODO(); - } - @Override int opcode() { return 0xD3; } - @Override public final void encoding( Encoding enc ) { - short dst = enc.reg(this ); // src1 - short src = enc.reg(in(2)); // src2 - enc.add1(x86_64_v2.rex(0, dst, 0)); - enc.add1(opcode()); // opcode - enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 4, dst)); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java deleted file mode 100644 index 7e0378b1..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.util.SB; - -public class ShrIX86 extends MachConcreteNode implements MachNode { - final int _imm; - ShrIX86( Node shri, int imm ) { super(shri); assert x86_64_v2.imm8(imm); _inputs.pop(); _imm = imm; } - @Override public String op() { return "shri"; } - @Override public String glabel() { return ">>>"; } - int opcode() { return 0xC1; } - int mod() { return 5; } - - @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } - @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - @Override public int twoAddress() { return 1; } - - @Override public void encoding(Encoding enc) { - short dst = enc.reg(this); // Also src1 - enc.add1(x86_64_v2.rex(0, dst, 0)); - enc.add1( opcode()); - - enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, mod(), dst) ); - - // immediate(4 bytes) 32 bits or (1 byte)8 bits - if( x86_64_v2.imm8(_imm) ) enc.add1(_imm); - else enc.add4(_imm); - } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java deleted file mode 100644 index 67dcf055..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.util.Utils; - -public class ShrX86 extends RegX86 { - ShrX86( Node add ) { super(add); } - @Override public String op() { return "shr"; } - @Override public String glabel() { return ">>>"; } - @Override public RegMask regmap(int i) { - if (i == 1) return x86_64_v2.WMASK; - if (i == 2) return x86_64_v2.RCX_MASK; - throw Utils.TODO(); - } - @Override int opcode() { return 0xD3; } - @Override public final void encoding( Encoding enc ) { - short dst = enc.reg(this ); // src1 - short src = enc.reg(in(2)); // src2 - enc.add1(x86_64_v2.rex(0, dst, 0)); - enc.add1(opcode()); // opcode - enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 5, dst)); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java deleted file mode 100644 index 4a14240a..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.FunNode; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.node.SplitNode; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; - -public class SplitX86 extends SplitNode { - SplitX86( String kind, byte round ) { super(kind,round, new Node[2]); } - @Override public String op() { return "mov"; } - @Override public RegMask regmap(int i) { return x86_64_v2.SPLIT_MASK; } - @Override public RegMask outregmap() { return x86_64_v2.SPLIT_MASK; } - - // Need to handle 8 cases: . reg->reg, reg->xmm, reg->flags, xmm->reg, xmm->xmm, xmm->flags, flags->reg, flags->xmm, - // flags->flags. - @Override public void encoding( Encoding enc ) { - // REX.W + 8B /r MOV r64, r/m64 - short dst = enc.reg(this ); - short src = enc.reg(in(1)); - if( dst == x86_64_v2.FLAGS ) { - // mov reg, flags - // push rcx - // popf (Pop the top of the stack into the FLAGS register) - // 50+rd PUSH r64 - x86_64_v2.rexF(0,src,0,false,enc); - enc.add1(0x50 + src); - // popf - enc.add1(0x9D); - return; - } - if( src == x86_64_v2.FLAGS ) { - // mov flags, reg - // pushf; pop reg - enc.add1(0x9C); - // 58+ rd POP r64 - x86_64_v2.rexF(0,dst,0,false, enc); - enc.add1(0x58 + dst); - return; - } - - // Flag for being either XMM or stack - boolean dstX = dst >= x86_64_v2.XMM_OFFSET; - boolean srcX = src >= x86_64_v2.XMM_OFFSET; - - // Stack spills - if( dst >= x86_64_v2.MAX_REG ) { - int off = enc._fun.computeStackOffset(enc._code,dst); - if( src >= x86_64_v2.MAX_REG ) { - // Rare stack-stack move. push [RSP+soff]; pop [RSP+doff] - int soff = enc._fun.computeStackOffset(enc._code,src); - enc.add1(0xFF); - x86_64_v2.indirectAdr(0, (short)-1/*index*/, (short)x86_64_v2.RSP, soff, 6, enc); - enc.add1(0x8F); - x86_64_v2.indirectAdr(0, (short)-1/*index*/, (short)x86_64_v2.RSP, off, 0, enc); - return; - } - StoreX86.encVal(enc, srcX ? TypeFloat.F64 : TypeInteger.BOT, (short)x86_64_v2.RSP, (short)-1/*index*/, src, off, 0); - return; - } - if( src >= x86_64_v2.MAX_REG ) { - int off = enc._fun.computeStackOffset(enc._code,src); - LoadX86.enc(enc, dstX ? TypeFloat.F64 : TypeInteger.BOT, dst, (short)x86_64_v2.RSP, (short)-1, off, 0); - return; - } - - // reg-reg move. Adjust numbering for GPR vs FPR reg set - if( dstX ) dst -= (short) x86_64_v2.XMM_OFFSET; - if( srcX ) src -= (short) x86_64_v2.XMM_OFFSET; - - // 0x66 if moving between register classes - if( dstX ^ srcX ) enc.add1(0x66); - if( !dstX && srcX ) - { short tmp=src; src=dst; dst=tmp; } - enc.add1(x86_64_v2.rex(dst, src, 0)); - - // pick opcode based on regs - if( !dstX && !srcX ) { - // reg->reg (MOV r64, r/m64) - enc.add1(0x8B); - } else if( dstX && srcX ) { - // xmm->xmm (NP 0F 28 /r MOVAPS xmm1, xmm2/m128) - enc.add1(0x0F); - enc.add1(0x28); - } else if( dstX && !srcX ) { - // reg->xmm (66 REX.W 0F 6E /r MOVQ xmm, r/m64) - enc.add1(0x0F); - enc.add1(0x6E); - } else if( !dstX && srcX ) { - // xmm->reg(66 0F 7E /r MOVQ r/m64, xmm) - enc.add1(0x0F); - enc.add1(0x7E); - } - - enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src)); - } - - // General form: "mov dst = src" - @Override public void asm(CodeGen code, SB sb) { - FunNode fun = code._encoding==null ? null : code._encoding._fun; - sb.p(code.reg(this,fun)).p(" = ").p(code.reg(in(1),fun)); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java deleted file mode 100644 index 4403b02b..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.ConstantNode; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.node.StoreNode; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; -import com.seaofnodes.simple.util.SB; -import java.util.BitSet; - -public class StoreX86 extends MemOpX86 { - StoreX86( StoreNode st, Node base, Node idx, int off, int scale, int imm, Node val ) { - super(st,st, base, idx, off, scale, imm, val); - } - @Override public String op() { return "st"+_sz; } - @Override public StringBuilder _printMach(StringBuilder sb, BitSet visited) { - Node val = val(); - sb.append(".").append(_name).append("="); - if( val==null ) sb.append(_imm); - else val._print0(sb,visited); - return sb.append(";"); - } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return null; } - @Override public void encoding( Encoding enc ) { - // REX.W + C7 /0 id MOV r/m64, imm32 | - // REX.W + 89 /r MOV r/m64, r64 - short ptr = enc.reg(ptr()); - short idx = enc.reg(idx()); - short src = enc.reg(val()); - - if( src == -1 ) { - int log = _declaredType.log_size(); - x86_64_v2.rexF(-1, ptr, idx, log==3, enc); - switch( log ) { - case 0: enc .add1(0xC6); break; - case 1: enc.add1(0x66).add1(0xC7); break; - case 2: enc .add1(0xC7); break; - case 3: enc .add1(0xC7); break; - } - x86_64_v2.indirectAdr(_scale, idx, ptr, _off, src, enc); - switch( log ) { - case 0: enc.add1(_imm); break; - case 1: enc.add2(_imm); break; - case 2: enc.add4(_imm); break; - case 3: enc.add4(_imm); break; // Limit of a 4 byte immediate - } - } else { - encVal(enc,_declaredType,ptr,idx,src,_off,_scale); - } - } - - // Non-immediate encoding - static void encVal( Encoding enc, Type decl, short ptr, short idx, short src, int off, int scale ) { - int log = decl.log_size(); - // Float reg being stored - if( src >= x86_64_v2.XMM_OFFSET ) { - src -= (short)x86_64_v2.XMM_OFFSET; - assert log == 2 || log == 3; - enc.add1( log==2 ? 0xF3 : 0xF2 ); - x86_64_v2.rexF(src,ptr,idx,false,enc); - enc.add1(0x0F).add1(0x11); - } else { - // byte stores from sil, dil, bpl, spl need a rex - if( log == 0 && src >= x86_64_v2.RSP ) enc.add1(x86_64_v2.rex(src,ptr,idx,false)); - else x86_64_v2.rexF(src,ptr,idx,log==3,enc); - switch( log ) { - case 0: enc .add1(0x88); break; - case 1: enc.add1(0x66).add1(0x89); break; - case 2: enc .add1(0x89); break; - case 3: enc .add1(0x89); break; - } - } - x86_64_v2.indirectAdr(scale, idx, ptr, off, src, enc); - } - - - // General form: "stN [base + idx<<2 + 12],val" - @Override public void asm(CodeGen code, SB sb) { - asm_address(code,sb).p(","); - if( val()==null ) sb.p("#").p(_imm); - else sb.p(code.reg(val())); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java deleted file mode 100644 index b6530a87..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class SubFX86 extends MachConcreteNode implements MachNode { - SubFX86( Node subf) { super(subf); } - @Override public String op() { return "subf"; } - @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; } - @Override public RegMask outregmap() { return x86_64_v2.XMASK; } - @Override public int twoAddress() { return 1; } - - @Override public void encoding( Encoding enc ) { - // F2 0F 5C /r SUBSD xmm1, xmm2/m64 - short dst = (short)(enc.reg(this ) - x86_64_v2.XMM_OFFSET); - short src = (short)(enc.reg(in(2)) - x86_64_v2.XMM_OFFSET); - // Fopcode - enc.add1(0xF2); - // rex prefix must come next (REX.W is not set) - x86_64_v2.rexF(dst, src, 0, false, enc); - - enc.add1(0x0F).add1(0x5C); - enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src)); - } - - // General form: "subf dst -= src" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" -= ").p(code.reg(in(2))); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java deleted file mode 100644 index 8da34bf5..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.node.Node; - -public class SubX86 extends RegX86 { - SubX86( Node add ) { super(add); } - @Override public String op() { return "sub"; } - @Override public String glabel() { return "-"; } - @Override int opcode() { return 0x2B; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java deleted file mode 100644 index 359b6d83..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.node.cpus.riscv.riscv; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFunPtr; -import com.seaofnodes.simple.util.SB; - -// Function constants -public class TFPX86 extends ConstantNode implements MachNode, RIPRelSize { - TFPX86( ConstantNode con ) { super(con); } - @Override public String op() { - return _con == Type.NIL ? "xor" : "ldx"; - } - @Override public boolean isClone() { return true; } - @Override public Node copy() { return new TFPX86(this); } - @Override public RegMask regmap(int i) { return null; } - @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - // Zero-set uses XOR kills flags - @Override public RegMask killmap() { - return _con == Type.NIL ? x86_64_v2.FLAGS_MASK : null; - } - - @Override public void encoding( Encoding enc ) { - short dst = enc.reg(this); - // Short form for zero - if( _con==Type.NIL ) { - // XOR dst,dst. Can skip REX is dst is low 8, makes this a 32b - // xor, which will also zero the high bits. - if( dst >= 8 ) enc.add1(x86_64_v2.rex(dst, dst, 0)); - enc.add1(0x33); // opcode - enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, dst)); - return; - } - - // lea to load function pointer address - // lea rax, [rip+disp32] - enc.relo(this); - enc.add1(x86_64_v2.rex(dst, 0, 0)); - enc.add1(0x8D); // opcode - enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.INDIRECT, dst, 0b101)); - enc.add4(0); - } - - @Override public byte encSize(int delta) { - return 7; - } - - // Delta is from opcode start - @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - enc.patch4(opStart + 3, delta - 7); - } - - // Human-readable form appended to the SB. Things like the encoding, - // indentation, leading address or block labels not printed here. - // Just something like "ld4\tR17=[R18+12] // Load array base". - // General form: "op\tdst=src+src" - @Override public void asm(CodeGen code, SB sb) { - String reg = code.reg(this); - if( _con == Type.NIL ) - sb.p(reg).p(",").p(reg); - else - _con.print(sb.p(reg).p(" = #")); - } - @Override public boolean eq(Node n) { return this==n; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java deleted file mode 100644 index c23f6c5f..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeMemPtr; -import com.seaofnodes.simple.util.SB; - -public class TMPX86 extends ConstantNode implements MachNode, RIPRelSize{ - TMPX86(ConstantNode con ) { super(con); } - @Override public String op() { return "ldp"; } - @Override public RegMask regmap(int i) { return null; } - @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - @Override public boolean isClone() { return true; } - @Override public Node copy() { return new TMPX86(this); } - - @Override public void encoding( Encoding enc ) { - short dst = enc.reg(this); - x86_64_v2.rexF(dst, 0, 0, true, enc); - enc.add1(0x8D).add1(x86_64_v2.modrm(x86_64_v2.MOD.INDIRECT, dst, 0b101)).add4(0); - int opLen = 1/*rexF*/+1/*opcode 0x8D*/+1/*modrm*/+4/*pc rel offset*/; - // Base of constant array - TypeMemPtr tmp = (TypeMemPtr)_con; - int baseOff = tmp._obj.aryBase(); - enc.largeConstant(this,tmp._obj,opLen-baseOff,2/*ELF encoding PC32*/); - } - - // Delta is from opcode start - @Override public byte encSize(int delta) { return (byte)7; } - - // Delta is from opcode start - @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - enc.patch4(opStart+opLen-4,delta-opLen); - } - - @Override public void asm(CodeGen code, SB sb) { - _con.print(sb.p(code.reg(this)).p(" = #")); - } - @Override public boolean eq(Node n) { return this==n; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java deleted file mode 100644 index 92d2a87d..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import java.util.BitSet; - -// unconditional jump -public class UJmpX86 extends CFGNode implements MachNode, RIPRelSize { - UJmpX86() { } - @Override public String op() { return "jmp"; } - @Override public String label() { return op(); } - @Override public StringBuilder _print1( StringBuilder sb, BitSet visited ) { - return sb.append("jmp "); - } - @Override public RegMask regmap(int i) {return null; } - @Override public RegMask outregmap() { return null; } - @Override public Type compute() { throw Utils.TODO(); } - @Override public Node idealize() { throw Utils.TODO(); } - @Override public void encoding( Encoding enc ) { - enc.jump(this,uctrl()); - // Short-form jump - enc.add1(0xEB).add1(0); - //// E9 cd JMP rel32 - //enc.add1(0xE9).add4(0); - } - - // Delta is from opcode start, but X86 measures from the end of the 2-byte encoding - @Override public byte encSize(int delta) { return (byte)(x86_64_v2.imm8(delta-2) ? 2 : 5); } - - // Delta is from opcode start - @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - byte[] bits = enc.bits(); - if( opLen==2 ) { - assert bits[opStart] == (byte)0xEB; - delta -= 2; // Offset from opcode END - assert (byte)delta==delta; - bits[opStart+1] = (byte)delta; - } else { - assert bits[opStart] == (byte)0xEB; - bits[opStart] = (byte)0xE9; // Long form - delta -= 5; // Offset from opcode END - enc.patch4(opStart+1,delta); - } - } - - @Override public void asm(CodeGen code, SB sb) { - CFGNode target = uctrl(); - //assert target.nOuts() > 1; // Should optimize jmp to empty targets - sb.p(label(target)); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java deleted file mode 100644 index 2d989a50..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.node.Node; - -public class XorIX86 extends ImmX86 { - XorIX86( Node add, int imm ) { super(add,imm); } - @Override public String op() { return "xori"; } - @Override public String glabel() { return "^"; } - @Override int opcode() { return 0x81; } - @Override int mod() { return 6; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java deleted file mode 100644 index 86703903..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.node.Node; - -public class XorX86 extends RegX86 { - XorX86( Node add ) { super(add); } - @Override public String op() { return "xor"; } - @Override public String glabel() { return "^"; } - @Override int opcode() { return 0x33; } - @Override public boolean commutes() { return true; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java deleted file mode 100644 index 026e12aa..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ /dev/null @@ -1,649 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Utils; - -public class x86_64_v2 extends Machine { - public x86_64_v2( CodeGen code ) {} - // X86-64 V2. Includes e.g. SSE4.2 and POPCNT. - @Override public String name() { return "x86_64_v2"; } - @Override public int defaultOpSize() { return 5; } - - public static final int RAX = 0, RCX = 1, RDX = 2, RBX = 3, RSP = 4, RBP = 5, RSI = 6, RDI = 7; - public static final int R08 = 8, R09 = 9, R10 = 10, R11 = 11, R12 = 12, R13 = 13, R14 = 14, R15 = 15; - - public static final int XMM0 = 16, XMM1 = 17, XMM2 = 18, XMM3 = 19, XMM4 = 20, XMM5 = 21, XMM6 = 22, XMM7 = 23; - public static final int XMM8 = 24, XMM9 = 25, XMM10 = 26, XMM11 = 27, XMM12 = 28, XMM13 = 29, XMM14 = 30, XMM15 = 31; - public static final int FLAGS = 32; - public static final int MAX_REG = 33; - public static final int RPC = 33; - - // Human-readable name for a register number, e.g. "RAX". - public static final String[] REGS = new String[]{ - "rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi", - "r8" , "r9" , "r10", "r11", "r12", "r13", "r14", "r15", - "xmm0", "xmm1", "xmm2" , "xmm3" , "xmm4" , "xmm5" , "xmm6" , "xmm7" , - "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15", - "flags", - }; - // Hard crash for bad register number, fix yer bugs! - @Override public String[] regs() { return REGS; } - - public static int XMM_OFFSET = 16; - // General purpose register mask: pointers and ints, not floats - static final long RD_BITS = 0b1111111111111111L; // All the GPRs - static RegMask RMASK = new RegMask(RD_BITS); - // No RSP in the *write* general set. - static final long WR_BITS = 0b1111111111101111L; // All the GPRs minus RSP - static RegMask WMASK = new RegMask(WR_BITS); - // Xmm register mask - static final long FP_BITS = 0b1111111111111111L << XMM0; // All the XMMs - static RegMask XMASK = new RegMask(FP_BITS); - static RegMask FLAGS_MASK = new RegMask(FLAGS); - static RegMask RPC_MASK = new RegMask(RPC); - // Divide mask: exclude RDX:RAX - static RegMask DIV_MASK = new RegMask(RD_BITS & ~(1L<<RAX) & ~(1L<<RDX)); - - static final long SPILLS = -(1L << MAX_REG); - static final RegMask SPLIT_MASK = new RegMask(WR_BITS | FP_BITS /* | (1L<<FLAGS)*/ | SPILLS, -1L ); - - // Load/store mask; both GPR and FPR - static RegMask MEM_MASK = new RegMask(WR_BITS | FP_BITS); - - public static RegMask RAX_MASK = new RegMask(RAX); - public static RegMask RCX_MASK = new RegMask(RCX); - public static RegMask RDX_MASK = new RegMask(RDX); - public static RegMask RDI_MASK = new RegMask(RDI); - public static RegMask R08_MASK = new RegMask(R08); - public static RegMask R09_MASK = new RegMask(R09); - public static RegMask RSI_MASK = new RegMask(RSI); - - public static RegMask XMM0_MASK = new RegMask(XMM0); - - // Encoding - public static int REX = 0x40; - public static int REX_W = 0x48; - public static int REX_WR = 0x4C; - public static int REX_WRB= 0x4D; - public static int REX_WB = 0x49; - - public enum MOD { - INDIRECT, // [mem] - INDIRECT_disp8, // [mem + 0x12] - INDIRECT_disp32, // [mem + 0x12345678] - DIRECT, // mem - }; - - // opcode included here - // 0F 84 cd JE rel32 - // 0F 85 cd JNE rel32 - // 0F 8F cd JG rel32 - // 0F 8C cd JL rel32 - // 0F 8E cd JLE rel32 - // 0F 8D cd JGE rel32 - static public int jumpop(String op) { - return switch (op) { - case "==" -> 0x84; - case "!=" -> 0x85; - case ">" -> 0x8F; - case "<" -> 0x8C; - case "<=" -> 0x8E; - case ">=" -> 0x8D; - default -> throw new IllegalArgumentException("Too many arguments"); - }; - } - - public static int modrm(MOD mod, int reg, int m_r) { - if( reg == -1 ) reg=0; // Missing reg in this flavor - // combine all the bits - assert 0 <= reg && reg < 16; - assert 0 <= m_r && m_r < 16; - return (mod.ordinal() << 6) | ((reg & 0x07) << 3) | m_r & 0x07; - } - - // 00 000 000 - // same bit-layout as modrm - public static int sib(int scale, int index, int base) { - assert 0 <= base && base < 16; - assert 0 <= index && index < 16; - return (scale << 6) | ((index & 0x07) << 3) | base & 0x07; - } - - // reg1 is reg(R) - // reg2 is r/mem(B) - // reg3 is X(index) - // reg4 is X(base) - - // 0 denotes no direct register - public static int rex(int reg, int ptr, int idx, boolean wide) { - // assuming 64 bit by default so: 0100 1000 - assert -1 <= reg && reg < 16; - assert -1 <= ptr && ptr < 16; - assert -1 <= idx && idx < 16; - - int rex = wide ? REX_W : REX; - if( 8 <= reg ) rex |= 0b00000100; // REX.R - if( 8 <= ptr ) rex |= 0b00000001; // REX.B - if( 8 <= idx ) rex |= 0b00000010; // REX.X - return rex; - } - - public static int rex(int reg, int ptr, int idx) { - return rex(reg, ptr, idx, true); - } - - // rex for floats. Return size (0 or 1) - // don't need to use REX if 0x40. - public static byte rexF(int reg, int ptr, int idx, boolean wide, Encoding enc) { - int rex = rex(reg, ptr, idx, wide); - if (rex == REX) return 0; - enc.add1(rex); - return 1; - } - // Function used for encoding indirect memory addresses - // Does not always generate SIB byte e.g index == -1. - // -1 denotes empty value, not set - note 0 is different from -1 as it can represent rax. - // Looks for best mod locally - public static void indirectAdr( int scale, short index, short base, int offset, int reg, Encoding enc ) { - // Assume indirect - assert 0 <= base && base < 16; - assert -1 <= index && index < 16 && index != RSP; - - MOD mod = MOD.INDIRECT; - // is 1 byte enough or need more? - if( offset != 0 ) - mod = imm8(offset) - ? MOD.INDIRECT_disp8 - : MOD.INDIRECT_disp32; - - if( mod == MOD.INDIRECT && (base == RBP || base == R13) ) { - mod = MOD.INDIRECT_disp8; - } else if( index == -1 && (base == RSP || base == R12) ) { - index = RSP; - } - - if( index == -1 ) { - enc.add1(modrm(mod, reg, base)); - } else { - enc.add1(modrm(mod, reg, x86_64_v2.RSP)); - enc.add1(sib(scale, index, base)); - } - - if( mod == MOD.INDIRECT_disp8 ) { - enc.add1(offset); - } else if( mod == MOD.INDIRECT_disp32 ) { - enc.add4(offset); - } - } - - - // Limit of float args passed in registers - static RegMask[] XMMS8 = new RegMask[]{ - new RegMask(XMM0), new RegMask(XMM1), new RegMask(XMM2), new RegMask(XMM3), - new RegMask(XMM4), new RegMask(XMM5), new RegMask(XMM6), new RegMask(XMM7), - }; - - // Map from function signature and argument index to register. - // Used to set input registers to CallNodes, and ParmNode outputs. - @Override public RegMask callArgMask( TypeFunPtr tfp, int idx, int maxArgSlot ) { return callInMask(tfp,idx,maxArgSlot); } - static RegMask callInMask( TypeFunPtr tfp, int idx, int maxArgSlot ) { - if( idx==0 ) return RPC_MASK; - if( idx==1 ) return null; - if( idx-2 >= tfp.nargs() ) return null; // Anti-dependence - return switch( CodeGen.CODE._callingConv ) { - case "SystemV" -> callSys5 (tfp,idx,maxArgSlot); - case "Win64" -> callWin64(tfp,idx,maxArgSlot); - default -> throw Utils.TODO(); - }; - } - - // Maximum stack args used by this signature - @Override public short maxArgSlot( TypeFunPtr tfp ) { - return switch( CodeGen.CODE._callingConv ) { - case "SystemV" -> maxArgSlotSys5 (tfp); - case "Win64" -> maxArgSlotWin64(tfp); - default -> throw Utils.TODO(); - }; - } - - // Win64 - max *4* args in registers of all kinds; after that on stack. - // Stack args land in increasing memory order with empty/mirror slots for - // every register. - // - // foo( int i0, flt f1, int i2, flt f3, int i4, flt f5, int i6, ... ) - // - // -- Prior Frame -- - // i6 - // f5 - // i4 - // empty mirror, XMM3= f3 - // empty mirror, R08 = i2 - // empty mirror, XMM1= f1 - // empty mirror, RCX = i0 - // -- Caller Frame; 16b align -- - // RPC - // PAD/ALIGN - // -- Callee Frame; 16b align -- - static RegMask[] WIN64_CALL = new RegMask[] { - RCX_MASK, - RDX_MASK, - R08_MASK, - R09_MASK, - }; - - static RegMask callWin64(TypeFunPtr tfp, int idx, int maxArgSlot ) { - // idx 2,3,4,5 passed in registers, with stack slot mirrors. - // idx >= 6 passed on stack, starting at slot#1 (#0 reserved for RPC). - if( idx >= 6 ) - return new RegMask(MAX_REG+maxArgSlot+(idx-2)); - return tfp.arg(idx-2) instanceof TypeFloat - ? XMMS8 [idx-2] - : WIN64_CALL[idx-2]; - } - static short maxArgSlotWin64(TypeFunPtr tfp) { - return (short)tfp.nargs(); - } - - // Sys5: max 6 GPRs and 8 FPRS filled first. Extra args land in increasing - // memory order as needed - no mirror space. - // - // foo( int i0, flt f1, int i2, flt f3, int i4, flt f5, int i6, ... ) - // RDI =i0, RSI =i2, RDX =i4, RCX =i6, R08 =i8, R09 =i10 - // XMM0=f1, XMM1=f3, XMM2=f5, XMM3=f7, XMM4=f9, XMM5=f11, XMM6=f13, XMM7=f15 - // - // -- Prior Frame -- - // f18 - // i18 - // f17 - // i16 - // i14 - // i12 - // -- Caller Frame; 16b align -- - // RPC - // PAD/ALIGN - // -- Callee Frame; 16b align -- - // SystemV(param passing) - static RegMask[] SYS5_CALL = new RegMask[] { - RDI_MASK, - RSI_MASK, - RDX_MASK, - RCX_MASK, - R08_MASK, - R09_MASK, - }; - - static RegMask callSys5(TypeFunPtr tfp, int idx, int maxArgSlot ) { - // First 6 integers passed in registers: rdi,rsi,rdx,rcx,r08,r09 - // First 8 floats passed in registers: xmm0-xmm7 - int icnt=0, fcnt=0; // Count of ints, floats - for( int i=2; i<idx; i++ ) { - if( tfp.arg(i-2) instanceof TypeFloat ) fcnt++; - else icnt++; - } - int nstk = Math.max(icnt-6,0)+Math.max(fcnt-8,0); - return tfp.arg(idx-2) instanceof TypeFloat - ? fcnt<8 ? XMMS8 [fcnt] : new RegMask(MAX_REG+maxArgSlot+nstk) - : icnt<6 ? SYS5_CALL[icnt] : new RegMask(MAX_REG+maxArgSlot+nstk); - } - static short maxArgSlotSys5(TypeFunPtr tfp) { - int icnt=0, fcnt=0; // Count of ints, floats - for( int i=0; i<tfp.nargs(); i++ ) { - if( tfp.arg(i) instanceof TypeFloat ) fcnt++; - else icnt++; - } - int nstk = Math.max(icnt-6,0)+Math.max(fcnt-8,0); - return (short)nstk; - } - - static final long SYSTEM5_CALLER_SAVE = - (1L << RAX) | (1L << RCX) | (1L << RDX) | - (1L << RDI) | (1L << RSI) | - (1L << R08) | (1L << R09) | (1L << R10) | (1L << R11) | - (1L << FLAGS) | // Flags are killed - // All FP regs are killed - FP_BITS; - - static final long WIN64_CALLER_SAVE = - (1L<< RAX) | (1L<< RCX) | (1L<< RDX) | - (1L<< R08) | (1L<< R09) | (1L<< R10) | (1L<< R11) | - (1L<<FLAGS)| // Flags are killed - // Only XMM0-XMM5 are killed; XMM6-XMM15 are preserved - (1L<<XMM0) | (1L<<XMM1) | (1L<<XMM2) | (1L<<XMM3) | - (1L<<XMM4) | (1L<<XMM5) ; - - @Override public long callerSave() { - return switch (CodeGen.CODE._callingConv) { - case "SystemV" -> SYSTEM5_CALLER_SAVE; - case "Win64" -> WIN64_CALLER_SAVE; - default -> throw new IllegalArgumentException("Unknown calling convention: " + CodeGen.CODE._callingConv); - }; - } - @Override public long neverSave() { return 1L<<RSP; } - @Override public RegMask retMask( TypeFunPtr tfp ) { - return tfp.ret() instanceof TypeFloat ? XMM0_MASK : RAX_MASK; - } - @Override public int rpc() { return RPC; } - - - // Create a split op; any register to any register, including stack slots - @Override public SplitNode split(String kind, byte round, LRG lrg) { - return new SplitX86(kind, round); - } - - // Return a MachNode unconditional branch - @Override public CFGNode jump() { - return new UJmpX86(); - } - - // Instruction selection - @Override - public Node instSelect(Node n) { - return switch (n) { - case AddFNode addf -> addf(addf); - case AddNode add -> add(add); - case AndNode and -> and(and); - case BoolNode bool -> cmp(bool); - case CProjNode c -> new CProjNode(c); - case CallEndNode cend -> new CallEndMach(cend); - case CallNode call -> call(call); - case CastNode cast -> new CastMach(cast); - case ConstantNode con -> con(con); - case DivFNode divf -> new DivFX86(divf); - case DivNode div -> new DivX86(div); - case FunNode fun -> new FunX86(fun); - case IfNode iff -> jmp(iff); - case LoadNode ld -> ld(ld); - case MemMergeNode mem -> new MemMergeNode(mem); - case MinusNode neg -> new NegX86(neg); - case MulFNode mulf -> new MulFX86(mulf); - case MulNode mul -> mul(mul); - case NewNode nnn -> new NewX86(nnn); - case NotNode not -> new NotX86(not); - case OrNode or -> or(or); - case ParmNode parm -> new ParmX86(parm); - case PhiNode phi -> new PhiNode(phi); - case ProjNode prj -> prj(prj); - case ReadOnlyNode read-> new ReadOnlyMach(read); - case ReturnNode ret -> new RetX86(ret, ret.fun()); - case SarNode sar -> sar(sar); - case ShlNode shl -> shl(shl); - case ShrNode shr -> shr(shr); - case StartNode start -> new StartNode(start); - case StopNode stop -> new StopNode(stop); - case StoreNode st -> st(st); - case SubFNode subf -> new SubFX86(subf); - case SubNode sub -> sub(sub); - case ToFloatNode tfn -> i2f8(tfn); - case XCtrlNode x -> new ConstantNode(Type.XCONTROL); - case XorNode xor -> xor(xor); - - case LoopNode loop -> new LoopNode(loop); - case RegionNode region -> new RegionNode(region); - default -> throw Utils.TODO(); - }; - } - - public static boolean imm8( long imm ) { return -128 <= imm && imm <= 127; } - - public static boolean imm32( long imm ) { return (int)imm==imm; } - - // Attempt a full LEA-style break down. - private Node add(AddNode add) { - Node lhs = add.in(1); - Node rhs = add.in(2); - if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && ld._declaredType.log_size() >= 3) - return new AddMemX86(add, address(ld), ld.ptr(), idx, off, scale, 0, rhs); - -// if(rhs instanceof LoadNode ld && ld.nOuts() == 1 && ld._declaredType.log_size() >= 3) { -// throw Utils.TODO(); // Swap load sides -// } - - // Attempt a full LEA-style break down. - // Returns one of AddX86, AddIX86, LeaX86, or LHS - if( rhs instanceof ConstantNode off2 && off2._con instanceof TypeInteger toff ) { - long imm = toff.value(); - assert imm!=0; // Folded in peeps - if( (int)imm != imm ) // Full 64bit immediate - return new AddX86(add); - // Now imm <= 32bits - if( lhs instanceof AddNode ladd ) - // ((base + (idx << scale)) + off) - return _lea(add, ladd.in(1), ladd.in(2), (int)imm); - if( lhs instanceof ShlNode shift ) - // (idx << scale) + off; no base - return _lea(add, null, shift, (int)imm); - - // lhs + rhs1 - return new AddIX86(add, (int)imm); - } - return _lea(add, lhs, rhs, 0); - } - - - private Node addf(AddFNode addf) { - if(addf.in(1) instanceof LoadNode ld && ld.nOuts() == 1) - return new AddFMemX86(addf, address(ld), ld.ptr(), idx, off, scale, addf.in(2)); - -// if(addf.in(2) instanceof LoadNode ld && ld.nOuts() == 1) -// throw Utils.TODO(); // Swap load sides - - return new AddFX86(addf); - } - - - private Node _lea(Node add, Node base, Node idx, int off) { - int scale = 0; - if( base instanceof ShlNode && !(idx instanceof ShlNode) ) - throw Utils.TODO(); // Bug in canonicalization, should on RHS - if( idx instanceof ShlNode shift && shift.in(2) instanceof ConstantNode shfcon && - shfcon._con instanceof TypeInteger tscale && 0 <= tscale.value() && tscale.value() <= 3 ) { - idx = shift.in(1); - scale = ((int) tscale.value()); - } - // (base + idx) + off - return off == 0 && scale == 0 - ? new AddX86(add) - : new LeaX86(add, base, idx, scale, off); - } - - - private Node and(AndNode and) { - if( and.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) ) - return new AndIX86(and, (int)ti.value()); - return new AndX86(and); - } - - private Node call(CallNode call) { - return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp - ? new CallX86(call, tfp) - : new CallRX86(call); - } - - // Because X86 flags, a normal ideal Bool is 2 X86 ops: a "cmp" and at "setz". - // Ideal If reading from a setz will skip it and use the "cmp" instead. - private static boolean swap, unsigned; - private Node cmp( BoolNode bool ) { - swap = unsigned = false; - Node cmp = _cmp(bool); - return new SetX86(cmp, swap ? IfNode.swap(bool.op()) : bool.op(), unsigned); - } - - private Node _cmp(BoolNode bool) { - // Float variant - if( bool.isFloat() ) { - unsigned = true; - return new CmpFX86(bool); - } - Node lhs = bool.in(1); - Node rhs = bool.in(2); - - // Since cmp does not record a BOP, SetX/Jmp need to know if the CMP is swapped. - - // Vs memory - if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && rhs._type.isa(ld._declaredType) ) - return new CmpMemX86(bool, address(ld), ld.ptr(), idx, off, scale, imm(rhs), val, false); - - // Operands swap in the encoding directly, no need for Set/Jmp to swap `bop` - if( rhs instanceof LoadNode ld && ld.nOuts() == 1 && lhs._type.isa(ld._declaredType) && - (val!=null || bool.op()=="==" || bool.op()=="!=") ) - return new CmpMemX86(bool, address(ld), ld.ptr(), idx, off, scale, imm(lhs), val, true ); - - // Vs immediate - if( rhs instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) ) - return new CmpIX86(bool, (int)ti.value(), false); - - // Operand swap compare; Set and Jmp need to swap `bop` - if( lhs instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) ) - return new CmpIX86(bool, (int)ti.value(), swap=true); - - // x vs y - return new CmpX86(bool); - } - - private Node con( ConstantNode con ) { - if( !con._con.isConstant() ) - return new ConstantNode(con); // Default unknown caller inputs - return switch (con._con) { - case TypeInteger ti -> new IntX86(con); - case TypeFloat tf -> new FltX86(con); - case TypeFunPtr tfp -> new TFPX86(con); - case TypeMemPtr tmp -> new TMPX86(con); - case TypeNil tn -> throw Utils.TODO(); - // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code. - case Type t -> t == Type.NIL ? new IntX86(con) : new ConstantNode(con); - }; - } - - private Node i2f8(ToFloatNode tfn) { - assert tfn.in(1)._type instanceof TypeInteger ti; - return new I2f8X86(tfn); - } - - private Node jmp(IfNode iff) { - // If/Bool combos will match to a Cmp/Set which sets flags. - // Most general arith ops will also set flags, which the Jmp needs directly. - // Loads do not set the flags, and will need an explicit TEST - String op = "!="; - if( iff.in(1) instanceof BoolNode bool ) op = swap ? IfNode.swap(bool.op()) : bool.op(); - else if( iff.in(1)==null ) op = "=="; // Never-node cutout - else iff.setDef(1, new BoolNode.NE(iff.in(1), new ConstantNode(TypeInteger.ZERO))); - return new JmpX86(iff, op); - } - - private Node ld(LoadNode ld) { - return new LoadX86(address(ld), ld.ptr(), idx, off, scale); - } - - private Node mul(MulNode mul) { - if( mul.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) ) - return new MulIX86(mul, (int)ti.value()); - return new MulX86(mul); - } - - private Node or(OrNode or) { - if( or.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) ) - return new OrIX86(or, (int)ti.value()); - return new OrX86(or); - } - - private Node prj( ProjNode prj ) { - return new ProjX86(prj); - } - - private Node sar(SarNode sar) { - if( sar.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm8(ti.value())) - return new SarIX86(sar, (int)(ti.value() & 0xff) ); - return new SarX86(sar); - } - - private Node shl(ShlNode shl) { - if( shl.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm8(ti.value())) - return new ShlIX86(shl, (int)(ti.value() & 0xff) ); - return new ShlX86(shl); - } - - private Node shr( ShrNode shr ) { - if( shr.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm8(ti.value()) ) - return new ShrIX86(shr, (int)(ti.value() & 0xff) ); - return new ShrX86(shr); - } - - private Node st( StoreNode st ) { - // Look for "*ptr op= val" - Node op = st.val(); - if( op instanceof AddNode ) { - if( op.in(1) instanceof LoadNode ld && stld_match(st,ld) ) - return new MemAddX86(address(st), st.ptr(), idx, off, scale, imm(op.in(2)), val); - if( op.in(2) instanceof LoadNode ld && stld_match(st,ld) ) - return new MemAddX86(address(st), st.ptr(), idx, off, scale, imm(op.in(1)), val); - } - return new StoreX86(address(st), st.ptr(), idx, off, scale, imm(st.val()), val); - } - private static boolean stld_match(StoreNode st, LoadNode ld ) { - return - ld.in(0) == st.in(0) && - ld.mem() == st.mem() && - ld.ptr() == st.ptr() && - ld.off() == st.off(); - } - - - private Node sub (SubNode sub ){ - return sub.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti - ? new AddIX86(sub, (int)-ti.value()) - : new SubX86(sub); - } - - private Node xor (XorNode xor){ - if( xor.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) ) - return new XorIX86(xor, (int)ti.value()); - return new XorX86(xor); - } - - // Gather X86 addressing mode bits prior to constructing. This is a - // builder pattern, but saving the bits in a *local* *global* here to keep - // mess contained. - private static int off, scale, imm; - private static Node idx, val; - private <N extends MemOpNode > N address(N mop) { - off = scale = imm = 0; // Reset - idx = val = null; - Node base = mop.ptr(); - // Skip/throw-away a ReadOnly, only used to typecheck - if(base instanceof ReadOnlyNode read) base = read.in(1); - assert !(base instanceof AddNode) && base._type instanceof TypeMemPtr; // Base ptr always, not some derived - if(mop.off() instanceof AddNode add && add.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) { - off = (int) ti.value(); - assert off == ti.value(); // In 32-bit range - idx = add.in(1); - if(idx instanceof ShlNode shift && shift.in(2) instanceof ConstantNode shfcon && - shfcon._con instanceof TypeInteger tscale && 0 <= tscale.value() && tscale.value() <= 3) { - idx = shift.in(1); - scale = (int) tscale.value(); - } - } else { - if(mop.off() instanceof ConstantNode con && con._con instanceof TypeInteger ti) { - off = (int) ti.value(); - assert off == ti.value(); // In 32-bit range - } else { - idx = mop.off(); - } - } - return mop; - } - - private int imm( Node xval ) { - assert val == null && imm == 0; - if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti) { - val = null; - imm = (int) ti.value(); - assert imm == ti.value(); // In 32-bit range - } else { - val = xval; - } - return imm; - } - -} diff --git a/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java b/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java deleted file mode 100644 index a9f86ce8..00000000 --- a/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java +++ /dev/null @@ -1,306 +0,0 @@ -package com.seaofnodes.simple.print; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.Encoding.Relo; -import com.seaofnodes.simple.codegen.Encoding; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import java.util.HashSet; - -public abstract class ASMPrinter { - - public static SB print(SB sb, CodeGen code) { - if( code._cfg==null ) - return sb.p("Need _cfg set, run after GCM"); - - // instruction address - int iadr = 0; - for( int i=0; i<code._cfg._len; i++ ) - if( code._cfg.at(i) instanceof FunNode fun ) - iadr = print(iadr,sb,code,fun,i); - - // constant pool - iadr = (iadr+15)&-16; // pad to 16 - Encoding enc = code._encoding; - if( enc!=null && !enc._bigCons.isEmpty() && iadr < enc._bits.size() ) { - // radix sort the big constants by alignment - Ary<Relo>[] raligns = new Ary[5]; - for( Node op : enc._bigCons.keySet() ) { - Relo relo = enc._bigCons.get(op); - int align = relo._t.alignment(); - Ary<Relo> relos = raligns[align]==null ? (raligns[align]=new Ary<>( Relo.class)) : raligns[align]; - relos.add(relo); - } - - HashSet<Type> targets = new HashSet<>(); - sb.p("--- Constant Pool ------").nl(); - - // By alignment - for( int align = 4; align >= 0; align-- ) { - Ary<Relo> relos = raligns[align]; - if( relos == null ) continue; - for( Relo relo : relos ) { - if( targets.contains(relo._t) ) continue; - targets.add(relo._t); - sb.hex2(iadr).p(" "); - switch( relo._t ) { - case TypeTuple tt -> { - for( Type tx : tt._types ) { - pN(enc,sb,iadr,align).p(" "); - iadr += (1<<align); - } - } - - case TypeStruct ts -> { - int sz = ts.size(); - int log = 1<<align; - sz = (sz + (log -1)) & -log; // Round up final padding - for( int i=0; i<sz; i++ ) - sb.hex1(enc.read1(iadr++)); - } - - // Simple primitive (e.g. larger int, float) - default -> { - pN(enc,sb,iadr,align).fix(9-(1<<align),""); - iadr += (1<<align); - } - } - relo._t.print(sb).nl(); - } - } - } - - return sb; - } - - private static SB pN( Encoding enc, SB sb, int iadr, int log ) { - switch( log ) { - case 0: sb.hex1(enc.read1(iadr)); break; - case 1: sb.hex2(enc.read2(iadr)); break; - case 2: sb.hex4(enc.read4(iadr)); break; - case 3: sb.hex8(enc.read8(iadr)); break; - } - return sb; - } - - - private static int print(int iadr, SB sb, CodeGen code, FunNode fun, int cfgidx) { - FunNode old=null; - if( code._encoding!=null ) { - old = code._encoding._fun; - code._encoding._fun = fun; // Useful printing after RA - } - // Function header - sb.nl().p("---"); - if( fun._name != null ) sb.p(fun._name).p(" "); - fun.sig().print(sb); - sb.p("---------------------------").nl(); - if( code._encoding!=null && code._encoding._padFunHeads ) - iadr = (iadr + 15)&-16; // All function entries padded to 16 align - - if( fun._frameAdjust != 0 ) - iadr = doInst(iadr,sb,code,fun,cfgidx,fun,true,true); - while( !(code._cfg.at(cfgidx) instanceof ReturnNode) ) - iadr = doBlock(iadr,sb,code,fun,cfgidx++); - - // Function separator - sb.p("---"); - fun.sig().print(sb); - sb.p("---------------------------").nl(); - if( code._encoding != null ) - code._encoding._fun = old; - return iadr; - } - - static private final int opWidth = 5; - static private final int argWidth = 30; - static int doBlock(int iadr, SB sb, CodeGen code, FunNode fun, int cfgidx) { - final int encWidth = code._mach==null ? 2 : code._mach.defaultOpSize()*2; - CFGNode bb = code._cfg.at(cfgidx); - if( bb != fun && !(bb instanceof IfNode) && !(bb instanceof CallEndNode) && !(bb instanceof CallNode) && !(bb instanceof CProjNode && bb.in(0) instanceof CallEndNode )) - sb.p(label(bb)).p(":").nl(); - if( bb instanceof CallNode ) return iadr; - final boolean postAlloc = code._phase.ordinal() > CodeGen.Phase.RegAlloc.ordinal(); - final boolean postEncode= code._phase.ordinal() >=CodeGen.Phase.Encoding.ordinal(); - - boolean once=false; - for( Node n : bb.outs() ) { - if( !(n instanceof PhiNode phi) ) continue; - if( phi._type instanceof TypeMem || phi._type instanceof TypeRPC ) continue; // Nothing for the hidden ones - // Post-RegAlloc phi prints all on one line - if( postAlloc ) { - if( !once ) { once=true; sb.fix(4," ").p(" ").fix(encWidth,"").p(" "); } - sb.p(phi._label).p(':').p(code.reg(phi,fun)).p(','); - } else { - // Pre-RegAlloc phi prints one line per - sb.fix(4," ").p(" ").fix(encWidth,"").p(" ").fix(opWidth,phi._label).p(" ").p(code.reg(phi,fun)); - if( phi.getClass() == PhiNode.class ) { - sb.p(" = phi( "); - for( int i=1; i<phi.nIns(); i++ ) - sb.p("N").p(phi.in(i)._nid).p(","); - sb.unchar().p(" )"); - } - sb.nl(); - } - } - if( once ) sb.unchar().nl(); - - // All the non-phis - for( int i=0; i<bb.nOuts(); i++ ) - if( !(bb.out(i) instanceof PhiNode) ) - iadr = doInst(iadr, sb,code, fun, cfgidx, bb.out(i),postAlloc, postEncode ); - - return iadr; - } - - static int doInst( int iadr, SB sb, CodeGen code, FunNode fun, int cfgidx, Node n, boolean postAlloc, boolean postEncode ) { - if( n==null || n instanceof CProjNode ) return iadr; - if( postAlloc && n instanceof CalleeSaveNode ) return iadr; - if( postEncode && n instanceof ProjNode ) return iadr; - if( n instanceof MemMergeNode ) return iadr; - if( n.getClass() == ConstantNode.class ) return iadr; // Default placeholders - final int dopz = code._mach==null ? 2 : code._mach.defaultOpSize(); - final int encWidth = dopz*2; - - // All blocks ending in a Region will need to either fall into or jump - // to this block. Until the post-reg-alloc block layout cleanup, we - // need to assume a jump. There's no real hardware op here, yet. - if( n instanceof RegionNode cfg && !(n instanceof FunNode) ) { - if( postEncode ) return iadr; // All jumps inserted already - while( cfgidx < code._cfg._len-1 ) { - CFGNode next = code._cfg.at(++cfgidx); - if( next == n ) return iadr; // Fall-through, no branch - if( next.nOuts()>1 ) - break; // Has code in the block, need to jump around - // No code in the block, can fall through it - } - sb.hex2(iadr++).p(" ").fix(encWidth,"??").p(" ").fix(opWidth,"JMP").p(" ").fix(argWidth,label(cfg)).nl(); - return iadr; - } - - // ProjNodes following a multi (e.g. Call or New results), - // get indent slightly and just print their index & node# - if( n instanceof ProjNode proj ) { - if( proj._type instanceof TypeMem ) return iadr; // Nothing for the hidden ones - sb.fix(4," ").p(" ").fix(encWidth,"").p(" ").fix(opWidth,proj._label==null ? "---" : proj._label).p(" ").p(code.reg(n,fun)).nl(); - return iadr; - } - - // ADDR ENCODING Op--- dst = src op src // Comment - // 1234 abcdefgh ld4 RAX = [Rbase + off] // Comment - sb.hex2(iadr).p(" "); - - // Encoding - int fatEncoding = 0; - if( code._encoding != null && code._encoding._opLen!=null ) { - int size = code._encoding._opLen[n._nid]; - if( code._asmLittle ) - for( int i=0; i<Math.min(size,dopz); i++ ) - sb.hex1(code._encoding._bits.buf()[iadr++]); - else { - iadr += Math.min(size,dopz); - for( int i=0; i<Math.min(size,dopz); i++ ) - sb.hex1(code._encoding._bits.buf()[iadr-i-1]); - } - for( int i=size*2; i<encWidth; i++ ) - sb.p(" "); - fatEncoding = size - (encWidth>>1); // Not-printed parts of encoding - } else - sb.fix(encWidth,""); - sb.p(" "); - - // Op; generally "ld4" or "call" - sb.fix(opWidth, n instanceof MachNode mach ? mach.op() : n.label()).p(" "); - - // General asm args - String isMultiOp = null; - if( n instanceof MachNode mach ) { - int old = sb.len(); - mach.asm(code,sb); - int len = sb.len(); - isMultiOp = isMultiOp(sb,old,len); - sb.fix(argWidth-(len-old),""); // Pad out - - } else if( !(n._type instanceof TypeMem) ) { - // Room for some inputs - sb.fix(5, n._nid+":" ); - int off = 0; - for( int i=0; i<n.nIns(); i++ ) { - sb.fix(5, n.in(i) == null ? "___" : String.valueOf(n.in(i)._nid)); - off += 5; - if( off >= argWidth ) break; - } - if( off < argWidth ) sb.fix(argWidth-off,""); - } - sb.p(" "); - - // Comment - String comment = n.comment(); - if( comment!=null ) sb.p("// ").p(comment); - - sb.nl(); - - // Printing more op bits than fit - if( isMultiOp != null && code._encoding != null && code._encoding._opLen!=null ) { - // Multiple ops, template style, no RA, no scheduling. Print out - // one-line-per-newline, with encoding bits up front. - int size = code._encoding._opLen[n._nid]; - int off = Math.min(size,dopz); - while( isMultiOp!=null ) { - sb.hex2(iadr).p(" "); - int len = Math.min(size-off,dopz); - for( int i=0; i<len; i++ ) - sb.hex1(code._encoding._bits.buf()[iadr++]); - off += len; - for( int i=len; i<dopz; i++ ) sb.p(" "); - sb.p(" "); - int x = isMultiOp.indexOf('\n'); - if( x== -1 ) { // Last line - sb.p(isMultiOp).nl(); - isMultiOp = null; - } else { - sb.p(isMultiOp.substring(0,x+1)); // Includes the newline - isMultiOp = isMultiOp.substring(x+1); - } - - } - - } else if( fatEncoding > 0 ) { - // Extra bytes past the default encoding width, all put on a line by - // themselves. X86 special for super long encodings - sb.hex2(iadr).p(" "); - for( int i=0; i<fatEncoding; i++ ) - sb.hex1(code._encoding._bits.buf()[iadr++]); - sb.nl(); - } - - - // MultiNodes are immediately followed by projection(s) - if( !(n instanceof CFGNode) && n instanceof MultiNode ) { - for( Node proj : n._outputs ) { - if( proj instanceof ProjNode ) // it could also be an ante-dep - doInst(iadr,sb,code,fun, cfgidx, proj,postAlloc,postEncode); - } - } - - return iadr; - } - - private static String label( CFGNode bb ) { - return (bb instanceof LoopNode ? "LOOP" : "L")+bb._nid; - } - - private static String isMultiOp(SB sb, int old, int len) { - for( int i=old; i<len; i++ ) - if( sb.at(i)=='\n' ) { - String s = sb.subString(i+1,len); - sb.setLen(i); - return s; - } - return null; - } - -} diff --git a/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java b/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java deleted file mode 100644 index 20c8439a..00000000 --- a/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java +++ /dev/null @@ -1,287 +0,0 @@ -package com.seaofnodes.simple.print; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.node.*; -import java.util.*; -import static com.seaofnodes.simple.util.Utils.TODO; - -/** - * Simple visualizer that outputs GraphViz dot format. - * The dot output must be saved to a file and run manually via dot to generate the SVG output. - * Currently, this is done manually. - */ -public class GraphVisualizer { - - /** - * If set to true we put the control nodes in a separate cluster from - * data nodes. - */ - boolean _separateControlCluster = false; - - public GraphVisualizer(boolean separateControlCluster) { this._separateControlCluster = separateControlCluster; } - public GraphVisualizer() { this(false); } - - public String generateDotOutput(Parser parse) { return generateDotOutput(parse._code._stop,parse._scope,parse._xScopes); } - public String generateDotOutput(StopNode stop, Node scope, Stack<ScopeNode> xScopes) { - - // Since the graph has cycles, we need to create a flat list of all the - // nodes in the graph. - Collection<Node> all = findAll(xScopes, stop, scope); - StringBuilder sb = new StringBuilder(); - sb.append("digraph chapter18 {\n"); - sb.append("/*\n"); - sb.append(stop._src); - sb.append("\n*/\n"); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes - - // CNC - turned off Apr/8/2024, gives more flex in the layout and - // removes some of the more ludicrous layout choices. - // Preserve node input order - //sb.append("\tordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.append("\tconcentrate=\"true\";\n"); - - // Force nested scopes to order - sb.append("\tcompound=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Now the scopes, in a cluster no edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scope( sb, xscope ); - - // Walk the Node edges - nodeEdges(sb, all); - - // Walk the active Scope edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scopeEdges( sb, xscope ); - - sb.append("}\n"); - return sb.toString(); - } - - private void nodesByCluster(StringBuilder sb, boolean doCtrl, Collection<Node> all) { - if (!_separateControlCluster && doCtrl) // all nodes in 1 cluster - return; - // Just the Nodes first, in a cluster no edges - sb.append(doCtrl ? "\tsubgraph cluster_Controls {\n" : "\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name - for (Node n : all) { - if (n instanceof ProjNode || n instanceof CProjNode || n instanceof MemMergeNode || n==Parser.XCTRL) - continue; // Do not emit, rolled into MultiNode or Scope cluster already - if( _separateControlCluster && doCtrl && !(n instanceof CFGNode) ) continue; - if( _separateControlCluster && !doCtrl && (n instanceof CFGNode) ) continue; - sb.append("\t\t").append(n.uniqueName()).append(" [ "); - String lab = n.glabel(); - if (n instanceof MultiNode) { - // Make a box with the MultiNode on top, and all the projections on the bottom - sb.append("shape=plaintext label=<\n"); - sb.append("\t\t\t<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\" CELLPADDING=\"4\">\n"); - sb.append("\t\t\t<TR><TD BGCOLOR=\"yellow\">").append(lab).append("</TD></TR>\n"); - sb.append("\t\t\t<TR>"); - boolean doProjTable = false; - n._outputs.sort((x,y) -> x instanceof ProjNode xp && y instanceof ProjNode yp ? (xp._idx - yp._idx) : (x._nid - y._nid)); - for (Node use : n._outputs) { - if (use instanceof ProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append("<TD>").append("\n"); - sb.append("\t\t\t\t<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">").append("\n"); - sb.append("\t\t\t\t<TR>"); - } - sb.append("<TD PORT=\"p").append(proj._idx).append("\">").append(proj.glabel()).append("</TD>"); - } - if (use instanceof CProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append("<TD>").append("\n"); - sb.append("\t\t\t\t<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">").append("\n"); - sb.append("\t\t\t\t<TR>"); - } - sb.append("<TD PORT=\"p").append(proj._idx).append("\" BGCOLOR=\"yellow\">").append(proj.glabel()).append("</TD>"); - } - } - if (doProjTable) { - sb.append("</TR>").append("\n"); - sb.append("\t\t\t\t</TABLE>").append("\n"); - sb.append("\t\t\t</TD>"); - } - sb.append("</TR>\n"); - sb.append("\t\t\t</TABLE>>\n\t\t"); - - } else { - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - if( n instanceof CFGNode ) sb.append("shape=box style=filled fillcolor=yellow "); - else if (n instanceof PhiNode) sb.append("style=filled fillcolor=lightyellow "); - sb.append("label=\"").append(lab).append("\" "); - } - sb.append("];\n"); - } - if (!_separateControlCluster) { - // Force Region & Phis to line up - for (Node n : all) { - if (n instanceof RegionNode region) { - sb.append("\t\t{ rank=same; "); - sb.append(region.uniqueName()).append(";"); - for (Node phi : region._outputs) - if (phi instanceof PhiNode) sb.append(phi.uniqueName()).append(";"); - sb.append("}\n"); - } - } - } - sb.append("\t}\n"); // End Node cluster - } - - private void nodes(StringBuilder sb, Collection<Node> all) { - nodesByCluster(sb, true, all); - nodesByCluster(sb, false, all); - } - - // Build a nested scope display, walking the _prev edge - private void scope( StringBuilder sb, ScopeNode scope ) { - sb.append("\tnode [shape=plaintext];\n"); - int last = scope.nIns(); - int max = scope.depth(); - for( int i = 0; i < max; i++ ) { - int level = max-i-1; - String scopeName = makeScopeName(scope, level); - sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name - // Special for memory ScopeMinNode - if( level==0 ) { - MemMergeNode n = scope.mem(); - sb.append("\t\t").append(n.uniqueName()).append(" [label=\"").append(n.glabel()).append("\"];\n"); - } - sb.append("\t\t").append(scopeName).append(" [label=<\n"); - sb.append("\t\t\t<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n"); - // Add the scope level - sb.append("\t\t\t<TR><TD BGCOLOR=\"cyan\">").append(level).append("</TD>"); - int lexStart = scope._kinds.at(level)._lexSize; - for( int j=lexStart; j<last; j++ ) { - var v = scope._vars.at(j); - sb.append("<TD PORT=\"").append(makePortName(scopeName, v._name)).append("\">").append(v._name).append("</TD>"); - } - last = lexStart; - sb.append("</TR>\n"); - sb.append("\t\t\t</TABLE>>];\n"); - } - // Scope clusters nest, so the graphics shows the nested scopes, so - // they are not closed as they are printed; so they just keep nesting. - // We close them all at once here. - sb.append( "\t}\n".repeat( max ) ); // End all Scope clusters - } - - private String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } - private String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - - // Walk the node edges - private void nodeEdges(StringBuilder sb, Collection<Node> all) { - // All them edge labels - sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // Do not display the Constant->Start edge; - // ProjNodes handled by Multi; - // ScopeNodes are done separately - if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeNode || n==Parser.XCTRL ) - continue; - for( int i=0; i<n.nIns(); i++ ) { - Node def = n.in(i); - if( n instanceof PhiNode && def instanceof RegionNode ) { - // Draw a dotted use->def edge from Phi to Region. - sb.append('\t').append(n.uniqueName()); - sb.append(" -> "); - sb.append(def.uniqueName()); - sb.append(" [style=dotted taillabel=").append(i).append("];\n"); - } else if( def != null ) { - // Most edges land here use->def - sb.append('\t').append(n.uniqueName()).append(" -> "); - if( def instanceof CProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else if( def instanceof ProjNode proj ) { - String mname = proj.in(0).uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - // Number edges, so we can see how they track - sb.append("[taillabel=").append(i); - // The edge from New to ctrl is just for anchoring the New - if ( n instanceof NewNode ) - sb.append(" color=green"); - // control edges are colored red - else if( def instanceof CFGNode ) - sb.append(" color=red"); - else if( def.isMem() ) - sb.append(" color=blue"); - // Backedges do not add a ranking constraint - if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) - sb.append(" constraint=false"); - sb.append("];\n"); - } - } - } - } - - // Walk the scope edges - private void scopeEdges( StringBuilder sb, ScopeNode scope ) { - sb.append("\tedge [style=dashed color=cornflowerblue];\n"); - int level=0; - for( var v : scope._vars ) { - Node def = scope.in(v._idx); - while( def instanceof ScopeNode lazy ) - def = lazy.in(v._idx); - if( def==null ) continue; - while( level < scope.depth() && v._idx >= scope._kinds.at(level)._lexSize ) - level++; - String scopeName = makeScopeName(scope, level-1); - sb.append("\t") - .append(scopeName).append(":") - .append('"').append(makePortName(scopeName, v._name)).append('"') // wrap port name with quotes because $ctrl is not valid unquoted - .append(" -> "); - if( def instanceof CProjNode proj ) { - sb.append(def.in(0).uniqueName()).append(":p").append(proj._idx); - } else if( def instanceof ProjNode proj ) { - sb.append(def.in(0).uniqueName()).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - sb.append(";\n"); - } - } - - /** - * Finds all nodes in the graph. - */ - public static Collection<Node> findAll(Stack<ScopeNode> ss, Node... ns) { - final HashMap<Integer, Node> all = new HashMap<>(); - for( Node n : ns ) - walk(all, n); - if( ss != null ) - for( Node n : ss ) - walk(all, n); - return all.values(); - } - - /** - * Walk a subgraph and populate distinct nodes in the all list. - */ - private static void walk(HashMap<Integer,Node> all, Node n) { - if(n == null ) return; - if (all.get(n._nid) != null) return; // Been there, done that - all.put(n._nid, n); - for (Node c : n._inputs) - walk(all, c); - for( Node c : n._outputs ) - walk(all, c); - } -} diff --git a/src/main/java/com/seaofnodes/simple/print/IRPrinter.java b/src/main/java/com/seaofnodes/simple/print/IRPrinter.java deleted file mode 100644 index d1265bef..00000000 --- a/src/main/java/com/seaofnodes/simple/print/IRPrinter.java +++ /dev/null @@ -1,331 +0,0 @@ -package com.seaofnodes.simple.print; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeFunPtr; -import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import java.util.ArrayList; -import java.util.BitSet; -import java.util.HashMap; - -public abstract class IRPrinter { - - // Print a node on 1 line, columnar aligned, as: - // NNID NNAME DDEF DDEF [[ UUSE UUSE ]] TYPE - // 1234 sssss 1234 1234 1234 1234 1234 1234 tttttt - public static SB printLine( Node n, SB sb ) { - if( n==null ) return sb; - sb.p("%4d %-7.7s ".formatted(n._nid,n.label())); - if( n._inputs==null ) - return sb.p("DEAD\n"); - for( Node def : n._inputs ) - sb.p(def==null ? "____" : "%4d".formatted(def._nid)) - // Lazy Phi indicator - .p(n instanceof MemMergeNode && def instanceof MemMergeNode ? "^" : " "); - for( int i = n._inputs.size(); i<4; i++ ) - sb.p(" "); - sb.p(" [[ "); - for( Node use : n._outputs ) - sb.p(use==null ? "____ " : "%4d ".formatted(use._nid)); - int lim = 6 - Math.max(n._inputs.size(),4); - for( int i = n._outputs.size(); i<lim; i++ ) - sb.p(" "); - sb.p(" ]] "); - if( n._type!= null ) sb.p(n._type.str()); - return sb.p("\n"); - } - - - public static String prettyPrint(Node node, int depth) { - return CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Schedule.ordinal() - ? _prettyPrintScheduled( node, depth ) - : _prettyPrint( node, depth ); - } - - // Another bulk pretty-printer. Makes more effort at basic-block grouping. - private static String _prettyPrint( Node node, int depth ) { - // First, a Breadth First Search at a fixed depth. - BFS bfs = new BFS(node,depth); - // Convert just that set to a post-order - ArrayList<Node> rpos = new ArrayList<>(); - BitSet visit = new BitSet(); - for( int i=bfs._lim; i< bfs._bfs.size(); i++ ) - postOrd( bfs._bfs.get(i), null, rpos, visit, bfs._bs); - // Reverse the post-order walk - SB sb = new SB(); - boolean gap=false; - for( int i=rpos.size()-1; i>=0; i-- ) { - Node n = rpos.get(i); - if( n instanceof CFGNode || n instanceof MultiNode ) { - if( !gap ) sb.p("\n"); // Blank before multihead - if( n instanceof FunNode fun ) - fun.sig().print(sb.p("--- ").p(fun._name==null ? "" : fun._name).p(" ")).p("----------------------\n"); - printLine( n, sb ); // Print head - while( --i >= 0 ) { - Node t = rpos.get(i); - if( !(t.in(0) instanceof MultiNode) ) { i++; break; } - printLine( t, sb ); - } - if( n instanceof ReturnNode ret ) { - FunNode fun = ret.fun(); - sb.p("--- "); - if( fun != null ) - fun.sig().print(sb.p(fun._name==null ? "" : fun._name).p(" ")); - sb.p("----------------------\n"); - } - if( !(n instanceof CallNode) ) { - sb.p("\n"); // Blank after multitail - gap = true; - } - } else { - printLine( n, sb ); - gap = false; - } - } - return sb.toString(); - } - - private static void postOrd(Node n, Node prior, ArrayList<Node> rpos, BitSet visit, BitSet bfs) { - if( !bfs.get(n._nid) ) - return; // Not in the BFS visit - if( n instanceof FunNode && !(prior instanceof StartNode) ) - return; // Only visit Fun from Start - if( visit.get(n._nid) ) return; // Already post-order walked - visit.set(n._nid); - // First walk the CFG, then everything - if( n instanceof CFGNode ) { - for( Node use : n._outputs ) - // Follow CFG, not across call/function borders, and not around backedges - if( use instanceof CFGNode && !(n instanceof CallNode && use instanceof FunNode) && - use.nOuts() >= 1 && !(use._outputs.get(0) instanceof LoopNode) ) - postOrd(use, n, rpos,visit,bfs); - for( Node use : n._outputs ) - // Follow CFG, not across call/function borders - if( use instanceof CFGNode && !(n instanceof CallNode && use instanceof FunNode) ) - postOrd(use,n,rpos,visit,bfs); - } - // Follow all outputs - for( Node use : n._outputs ) - if( use != null && - !(n instanceof CallNode && use instanceof FunNode) && - (n instanceof FunNode || !(use instanceof ParmNode)) ) - postOrd(use, n, rpos,visit,bfs); - // Post-order - rpos.add(n); - } - - // Breadth-first search, broken out in a class to keep in more independent. - // Maintains a root-set of Nodes at the limit (or past by 1 if MultiHead). - public static class BFS { - // A breadth first search, plus MultiHeads for any MultiTails - public final ArrayList<Node> _bfs; - public final BitSet _bs; // Visited members by node id - public final int _depth; // Depth limit - public final int _lim; // From here to _bfs._len can be roots for a reverse search - public BFS( Node base, int d ) { - _depth = d; - _bfs = new ArrayList<>(); - _bs = new BitSet(); - - add(base); // Prime the pump - int idx=0, lim=1; // Limit is where depth counter changes - while( idx < _bfs.size() ) { // Ran out of nodes below depth - Node n = _bfs.get(idx++); - for( Node def : n._inputs ) - if( def!=null && !_bs.get(def._nid) ) - add(def); - if( idx==lim ) { // Depth counter changes at limit - if( --d < 0 ) - break; // Ran out of depth - lim = _bfs.size(); // New depth limit - } - } - // Toss things past the limit except multi-heads - while( idx < _bfs.size() ) { - Node n = _bfs.get(idx); - if( n instanceof MultiNode ) idx++; - else del(idx); - } - // Root set is any node with no inputs in the visited set - lim = _bfs.size(); - for( int i=_bfs.size()-1; i>=0; i-- ) - if( !any_visited(_bfs.get(i)) ) - swap( i,--lim); - _lim = lim; - } - void swap( int x, int y ) { - if( x==y ) return; - Node tx = _bfs.get(x); - Node ty = _bfs.get(y); - _bfs.set(x,ty); - _bfs.set(y,tx); - } - void add(Node n) { - _bfs.add(n); - _bs.set(n._nid); - } - void del(int idx) { - _bs.clear(_bfs.get(idx)._nid); - Utils.del(_bfs, idx); - } - boolean any_visited( Node n ) { - for( Node def : n._inputs ) - if( def!=null && _bs.get(def._nid) ) - return true; - return false; - } - } - - public static String _prettyPrint( CodeGen code ) { - SB sb = new SB(); - // Print the Start "block" - printLine(code._start,sb); - for( Node n : code._start._outputs ) - printLine(n,sb); - sb.nl(); - - // Skip start, stop - for( int i=1; i<code._cfg._len-1; i++ ) { - CFGNode blk = code._cfg.at(i); - if( blk.blockHead() && (!(blk instanceof CProjNode) || (blk.cfg0() instanceof IfNode )) ) { - if( blk instanceof FunNode fun ) - sb.p("--- ").p(fun.label()).p(" ---------------------------").nl(); - // Print block header - sb.p("%-13.13s".formatted(label(blk)+":")); - sb.p( " ".repeat(4) ).p(" [[ "); - if( blk instanceof RegionNode ) - for( int j=1; j<blk.nIns(); j++ ) - label(sb,blk.cfg(j)); - else - label(sb,blk.cfg(0)); - sb.p(" ]] \n"); - } - printLine(blk,sb); - if( blk instanceof ReturnNode ret ) - sb.p("--- ").p(ret.fun().label()).p(" ---------------------------").nl(); - - // Block contents - for( Node n : blk._outputs ) { - if( n instanceof CFGNode cfg ) continue; - printLine(n,sb); - if( !(n instanceof CFGNode) && n instanceof MultiNode ) - for( Node use : n._outputs ) - if( use instanceof ProjNode ) - printLine(use,sb); - } - } - - printLine(code._stop,sb); - return sb.toString(); - } - - // Bulk pretty printer, knowing scheduling information is available - private static String _prettyPrintScheduled( Node node, int depth ) { - // Backwards DFS walk to depth. - HashMap<Integer,Integer> ds = new HashMap<>(); - Ary<Node> ns = new Ary<>(Node.class); - _walk(ds,ns,node,depth); - // Remove data projections, these are force-printed behind their multinode head - for( int i=0; i<ns.size(); i++ ) { - if( ns.get(i) instanceof ProjNode proj && !(proj.in(0) instanceof CFGNode) ) { - ns.del(i--); - ds.remove(proj._nid); - } - } - // Print by block with least idepth - SB sb = new SB(); - Ary<Node> bns = new Ary<>(Node.class); - while( !ds.isEmpty() ) { - CFGNode blk = null; - for( Node n : ns ) { - CFGNode cfg = n instanceof CFGNode cfg0 && cfg0.blockHead() ? cfg0 : n.cfg0(); - if( blk==null || cfg.idepth() < blk.idepth() ) - blk = cfg; - } - Integer d = ds.remove(blk._nid); - ns.del(ns.find(blk)); - - // Print block header - sb.p("%-13.13s".formatted(label(blk)+":")); - sb.p( " ".repeat(4) ).p(" [[ "); - if( blk instanceof StartNode ) ; - else if( blk instanceof RegionNode || blk instanceof StopNode ) - for( int i=(blk instanceof StopNode ? 3 : 1); i<blk.nIns(); i++ ) - label(sb,blk.cfg(i)); - else - label(sb,blk.cfg(0)); - sb.p(" ]] \n"); - printLine(blk,sb); - - // Collect block contents that are in the depth limit - bns.clear(); - int xd = Integer.MAX_VALUE; - for( Node use : blk._outputs ) { - Integer i = ds.get(use._nid); - if( i!=null && !(use instanceof CFGNode cfg && cfg.blockHead()) ) { - if( bns.find(use)==-1 ) - bns.add(use); - xd = Math.min(xd,i); - } - } - // Print Phis up front, if any - for( int i=0; i<bns.size(); i++ ) - if( bns.get(i) instanceof PhiNode phi ) - printLine( phi, sb,bns,i--,ds,ns); - - // Print block contents in depth order, bumping depth until whole block printed - for( ; !bns.isEmpty(); xd++ ) - for( int i=0; i<bns.size(); i++ ) { - Node n = bns.get(i); - if( ds.get(n._nid)==xd ) { - printLine( n, sb, bns, i--, ds,ns ); - if( n instanceof MultiNode && !(n instanceof CFGNode) ) { - for( Node use : n._outputs ) { - if( use instanceof ProjNode ) - printLine(use,sb,bns,bns.indexOf(use),ds,ns); - } - } - } - } - sb.p("\n"); - } - return sb.toString(); - } - - private static void _walk( HashMap<Integer,Integer> ds, Ary<Node> ns, Node node, int d ) { - Integer nd = ds.get(node._nid); - if( nd!=null && d <= nd ) return; // Been there, done that - Integer old = ds.put(node._nid,d) ; - if( old == null ) - ns.add(node); - if( d == 0 ) return; // Depth cutoff - for( Node def : node._inputs ) - if( def != null && - !(node instanceof LoopNode loop && loop.back()==def) && - // Don't walk into or out of functions - !(node instanceof CallEndNode && def instanceof ReturnNode) && - !(node instanceof FunNode && def instanceof CallNode) && - !(node instanceof ParmNode && !(def instanceof FunNode)) - ) - _walk(ds,ns,def,d-1); - } - - static String label( CFGNode blk ) { - if( blk instanceof StartNode ) return "START"; - return (blk instanceof LoopNode ? "LOOP" : "L")+blk._nid; - } - static void label( SB sb, CFGNode blk ) { - if( !blk.blockHead() ) blk = blk.cfg(0); - sb.p( "%-9.9s ".formatted( label( blk ) ) ); - } - static void printLine( Node n, SB sb, Ary<Node> bns, int i, HashMap<Integer,Integer> ds, Ary<Node> ns ) { - printLine( n, sb ); - if( i != -1 ) bns.del(i); - ds.remove(n._nid); - int idx = ns.find(n); - if( idx!=-1 ) ns.del(idx); - } - -} diff --git a/src/main/java/com/seaofnodes/simple/print/JSViewer.java b/src/main/java/com/seaofnodes/simple/print/JSViewer.java deleted file mode 100644 index 62ff0001..00000000 --- a/src/main/java/com/seaofnodes/simple/print/JSViewer.java +++ /dev/null @@ -1,405 +0,0 @@ -package com.seaofnodes.simple.print; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import java.io.IOException; -import java.nio.file.Paths; -import java.util.Collection; -import java.util.Stack; -import static com.seaofnodes.simple.codegen.CodeGen.CODE; - -public class JSViewer implements AutoCloseable { - // Display programs in an endless loop - public static void main( String[] args ) throws Exception { - try(var js = new JSViewer()) { - js.run(); - } - } - - static boolean SHOW; - - // WebSocket to the browser for display - static SimpleWebSocket SERVER; - - static int N; // Dot frames - - JSViewer() throws Exception { - // Launch server; handshake - SERVER = new SimpleWebSocket(Paths.get("docs/index.html").toUri(),12345) ; - } - - void run( ) throws Exception { - SERVER.put("!"); - while( true ) { - String src = SERVER.get(); - switch( src ) { - case null: return; - case "null": return; - case "+": break; // Client requests more frames, but we send them all anyway - default: - System.out.println(src); - try { - N=0; - // Use parser scope, xscope when building views - CodeGen code = new CodeGen(src); - SHOW = true; - show(); - // Parse program, generating views at every parse point - code.parse(); - // No longer user parse internal state when building views - code.opto(); - - // Catch and ignore Parser errors - } catch(RuntimeException re) { - System.err.println(re); - } finally { - SHOW = false; - SERVER.put("#"); // Final frame - } - break; - } - } - } - - @Override public void close() throws IOException { - SERVER.close(); - SERVER=null; - } - - public static void show() { if( SERVER!=null && SHOW ) _show(); } - private static void _show() { - // Skip util we at least get the Parse object made - if( CODE._phase==null || CODE._phase.ordinal() < CodeGen.Phase.Parse.ordinal() ) - return; - boolean midParse = CODE._phase == CodeGen.Phase.Parse; - Stack<ScopeNode> xScopes = midParse ? CODE.P._xScopes : null; - - Collection<Node> all = GraphVisualizer.findAll(xScopes, CODE._stop, midParse ? CODE.P._scope: null); - SB sb = new SB(); - sb.p("digraph view_").p(N++).p(" {\n").ii(); - if( midParse ) - sb.i().p("// POS: ").p(CODE.P.pos()).nl(); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.i().p("rankdir=BT;\n"); // Force Nodes before Scopes - - // CNC - turned off Apr/8/2024, gives more flex in the layout and - // removes some of the more ludicrous layout choices. - // Preserve node input order - //sb.append("ordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.i().p("concentrate=\"true\";\n"); - - // Force nested scopes to order - sb.i().p("compound=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Now the scopes, in a cluster no edges - if( midParse ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scope( sb, xscope ); - - // Walk the Node edges - nodeEdges(sb, midParse && !xScopes.empty() ? xScopes.peek() : null, all); - - // Walk the active Scope edges - if( midParse ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scopeEdges( sb, xscope ); - - - sb.p("}\n").di(); - // Tell client another DOT frame - String dot = sb.toString(); - System.out.println(dot); - try { - SERVER.put(dot); - } catch( IOException ioe ) { - try { SERVER.close();} catch( IOException ignored ){} - } - } - - - private static void nodes(SB sb, Collection<Node> all) { - // Just the Nodes first, in a cluster no edges - sb.i().p("subgraph cluster_Nodes {\n").ii(); // Magic "cluster_" in the subgraph name - for( Node n : all ) { - if( n instanceof ProjNode || n instanceof CProjNode || n instanceof MemMergeNode ) - continue; // Do not emit, rolled into MultiNode or Scope cluster already - sb.i().p(n.uniqueName()).p(" [ "); - if( n instanceof MultiNode && !(n instanceof StartNode) ) { - // Make a box with the MultiNode on top, and all the projections on the bottom - sb. p("shape=plaintext label=<\n").ii(); - sb.i().p("<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\" CELLPADDING=\"4\">\n"); - // Row over single cell for node label - cell(sb.i().p("<TR>"), n.glabel(), n, null).p("</TR>\n"); - // Row over single cell, for nested table - sb.i().p("<TR><TD>\n").ii(); - sb.i().p("<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">").p("\n"); - sb.i().p("<TR>"); - n._outputs.sort((x,y) -> x instanceof ProjNode xp && y instanceof ProjNode yp ? (xp._idx - yp._idx) : ((x==null ? 99999 : x._nid) - (y==null ? 99999 : y._nid))); - boolean empty_row=true; - for( Node use : n._outputs ) { - int idx = idx(n); - if( idx != -1 ) { - cell(sb,use.glabel(),use,"p"+idx); - empty_row=false; - } - } - // At least one cell on row - if( empty_row ) sb.p("<TD></TD>"); - sb. p("</TR>").p("\n"); - sb.i().p("</TABLE>").p("\n").di(); - sb.i().p("</TD></TR>\n"); - sb.i().p("</TABLE>>\n").di(); - sb.i().p("];\n"); - - } else { - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - //else if (n instanceof PhiNode) sb.p("style=filled fillcolor=lightyellow "); - node(sb,n.glabel(),n); - sb.p("];\n"); - } - } - // Force Region & Phis to line up - for (Node n : all) { - if (n instanceof RegionNode region) { - sb.i().p("{ rank=same; "); - sb.p(region.uniqueName()).p(";"); - for (Node phi : region._outputs) - if (phi instanceof PhiNode) sb.p(phi.uniqueName()).p(";"); - sb.p("}\n"); - } - } - sb.di().i().p("}").nl(); // End Node cluster - } - - // Build a nested scope display, walking the _prev edge - private static void scope( SB sb, ScopeNode scope ) { - sb.i().p("node [shape=plaintext];\n"); - int last = scope.nIns(); - int max = scope.depth()+1; // One more than lexsize - for( int i = 0; i < max; i++ ) { - int level = max-i-1; - String scopeName = makeScopeName(scope, level); - sb.i().p("subgraph cluster_").p(scopeName).p(" {\n").ii(); // Magic "cluster_" in the subgraph name - sb.i().p(scopeName).p(" [label=<\n").ii(); - sb.i().p("<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n"); - int lexStart = level==0 ? 0 : scope._kinds.at(level-1)._lexSize; - - // Special for memory ScopeMinNode - MemMergeNode n = scope.nIns()>1 ? scope.mem() : null; - if( level==0 && n!=null && n.nIns()>2 ) { - sb.i().p("<TR>"); - for( int m=2; m<n.nIns(); m++ ) - cell(sb,"#"+m,n.alias(m),"m"+m); - sb.p("</TR>\n"); // End scope level - } - - // Scope variables, empty if none - if( lexStart<last ) { - sb.i().p("<TR>\n"); - for( int j=lexStart; j<last; j++ ) { - if( scope.in(j)==null ) continue; - var v = scope._vars.at(j); - cell(sb.i(),v._name,scope.in(j),makePortName(scopeName, v._name)).nl(); - } - last = lexStart; - sb.i().p("</TR>\n"); - } - sb.i().p("</TABLE>>];\n").di(); - // Scope label - sb.i().p(scopeName).p("A [label=\"").p(level).p("\" shape=doublecircle style=filled fillcolor=aqua fontsize=10 margin=0 width=0.2 ];\n"); - } - // Scope clusters nest, so the graphics shows the nested scopes, so - // they are not closed as they are printed; so they just keep nesting. - // We close them all at once here. - for( int i=0; i<max; i++ ) - sb.di().i().p("}\n"); - } - - // Append a cell, with color - private static SB cell(SB sb, String text, Node n, String port) { - sb.p("<TD"); - String color = color(n); - sb.p(" BGCOLOR=\"").p(color).p("\""); - if( port!=null ) sb.p(" PORT=\"").p(port).p("\""); - return colorcell(sb.p(">"),text,n,dark(color)).p("</TD>"); - } - private static SB node(SB sb, String text, Node n) { - String color = color(n); - sb.p("style=filled fillcolor=").p(color).p(" "); - String shape = switch( n ) { - case FunNode fun -> "shape=box peripheries=2"; - case CFGNode cfg -> "shape=box"; - default -> ""; - }; - sb.p(shape).p(" "); - return colorcell(sb.p("label=<"),text,n,dark(color)).p(">"); - } - - private static String color(Node n) { - return switch( n ) { - case CFGNode cfg -> "yellow"; - case NewNode nnn -> "lightgreen"; - case StructNode s -> "lightgreen"; - default -> typeColor(n._type); - }; - } - private static String typeColor(Type t) { - return t==null ? "white" : switch( t ) { - case TypeMem mem -> "blue"; - case TypeMemPtr tmp -> "green"; - case TypeFunPtr tfp -> "orange"; - case TypeRPC rpc -> "antiquewhite"; - case TypeInteger ti -> "lightblue"; - case TypeFloat tf -> "aqua"; - case Type tn -> tn==Type.NIL ? "lightgray" : "red"; - }; - } - private static final String[] DARKS = new String[]{"blue","green","black"}; - private static boolean dark(String color) { return Utils.find(DARKS,color) != -1; } - - - - // Called with an open div - private static SB colorcell(SB sb, String text, Node n, boolean dark) { - if( dark ) sb.p("<font color=\"white\">"); - sb.p(text); - if( n._type!=null ) { - sb.p("<br /><font point-size=\"10\">"); - n._type.gprint(sb); - sb.p("</font>"); - } - if( dark ) sb.p("</font>"); - return sb; - } - - - private static String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } - private static String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - - // Walk the node edges - private static void nodeEdges(SB sb, ScopeNode scope, Collection<Node> all) { - // All them edge labels - String scopeName = scope!=null ? makeScopeName(scope, 0) : null; - sb.i().p("edge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // Do not display the Constant->Start edge; - if( n instanceof ConstantNode || - n instanceof XCtrlNode || - // ProjNodes handled by Multi; - n instanceof ProjNode || - n instanceof CProjNode || - // ScopeNodes are done separately - n instanceof ScopeNode || - n instanceof MemMergeNode - ) - continue; - if( n.isDead() ) - throw Utils.TODO(); - // Normal edges - for( int i=0; i<n.nIns(); i++ ) { - Node def = n.in(i); - if( def==null ) continue; - sb.i().p(n.uniqueName()).p(" -> "); - if( n instanceof PhiNode && def instanceof RegionNode ) { - // Draw a dotted use->def edge from Phi to Region. - sb.p(def.uniqueName()); - sb.p(" [style=dotted taillabel=").p(i).p("];\n"); - } else { - // Most edges land here use->def - if( def instanceof CProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.p(mname).p(":p").p(proj._idx); - } else if( def instanceof ProjNode proj ) { - String mname = proj.in(0).uniqueName(); - sb.p(mname).p(":p").p(proj._idx); - } else sb.p(def.uniqueName()); - // Number edges, so we can see how they track - sb.p("[taillabel=").p(i); - // Color the edge - nodeEdgeColor(sb,def._type); - // Backedges do not add a ranking constraint - if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) - sb.p(" constraint=false"); - sb.p("];\n"); - } - } - - // Bonus edge if hooked by parser - if( (n.iskeep() || n.isUnused()) && scopeName != null ) { - sb.i().p(scopeName).p(" -> ").p(n.uniqueName()).p(" [ style=dashed color=grey];\n"); - } - // Force functions above return - if( n instanceof ReturnNode ret && ret.inProgress() && !ret.fun().isDead() ) - sb.i().p(ret.ctrl().uniqueName()).p(" -> ").p(ret.fun().uniqueName()).p("[style=invis]\n"); - } - } - - private static void nodeEdgeColor(SB sb, Type t) { - sb.p(" color=").p(typeColor(t)); - } - - // Walk the scope edges - private static void scopeEdges( SB sb, ScopeNode scope ) { - sb.i().p("edge [style=dashed color=cornflowerblue];\n"); - int level=0; - for( var v : scope._vars ) { - if( v._idx==1 ) continue; // Memory done separately - Node def = scope.in(v._idx); - while( def instanceof ScopeNode lazy ) - def = lazy.in(v._idx); - if( def==null ) continue; - while( level < scope.depth() && v._idx >= scope._kinds.at(level)._lexSize ) - level++; - String scopeName = makeScopeName(scope, level); - sb.i() - .p(scopeName).p(":") - .p('"').p(makePortName(scopeName, v._name)).p('"') // wrap port name with quotes because $ctrl is not valid unquoted - .p(" -> "); - defPort(sb,def).p(";\n"); - } - - // Memory - if( scope.nIns()>1 ) { - MemMergeNode n = scope.mem(); - for( int i=2; i<n.nIns(); i++ ) { - Node def = n.in(i); - while( def instanceof ScopeNode lazy ) - def = lazy.in(i); - if( def==null ) continue; - String scopeName = makeScopeName(scope, 0); - sb.i().p(scopeName).p(":m").p(i).p(" -> "); - defPort(sb,def).p(";\n"); - } - } - - } - - // (C)Projection index or -1 - private static int idx(Node n) { - return n instanceof ProjNode p ? p._idx : (n instanceof CProjNode cp ? cp._idx : -1); - } - - private static SB defPort(SB sb, Node def) { - int idx = idx(def); - return idx != -1 - ? sb.p(def.in(0).uniqueName()).p(":p").p(idx) - : sb.p(def.uniqueName()); - } -} diff --git a/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java b/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java deleted file mode 100644 index 4077533d..00000000 --- a/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java +++ /dev/null @@ -1,149 +0,0 @@ -package com.seaofnodes.simple.print; - -import com.seaofnodes.simple.util.Utils; -import java.io.*; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Base64; - -// Why-Oh-Why is this needed? - -// Pass in a URI and a port. -// Opens the client; client has to open a websocket at the given port. -// Connects, handshakes. -// Thereafter you can call get/put to swap frames with client. - -class SimpleWebSocket extends ServerSocket { - final InputStream _in; - final OutputStream _out; - - SimpleWebSocket(URI uri, int port) throws IOException, NoSuchAlgorithmException { - super(port); - // Launch client - java.awt.Desktop.getDesktop().browse(uri); - // Look for client; get in/out streams - Socket sock = accept(); - _in = sock. getInputStream(); - _out = sock.getOutputStream(); - - - // Establish web socket with handshake; easier with a buffered reader - BufferedReader br = new BufferedReader(new InputStreamReader(_in)); - String key = null; - while( br.ready() ) { - String line = br.readLine(); - if( line.startsWith("Sec-WebSocket-Key: ") ) - key = line.substring(19); - } - assert key!=null; - // Magic handshake - byte[] response = ("HTTP/1.1 101 Switching Protocols\r\n" - + "Connection: Upgrade\r\n" - + "Upgrade: websocket\r\n" - + "Sec-WebSocket-Accept: " - + Base64.getEncoder().encodeToString(MessageDigest.getInstance("SHA-1").digest((key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes( StandardCharsets.UTF_8 ))) - + "\r\n\r\n").getBytes( StandardCharsets.UTF_8 ); - _out.write(response,0,response.length); - } - - final byte[] _ins = new byte[1024]; - final byte[] _str = new byte[1024]; - final byte[] _key = new byte[4]; - int _x, _len; - private int rawget() { assert _x<_len; return _ins[_x++]&0xFF; } - - // TODO: Flip to Direct Byte Buffer, this is just a poor-mans version - private void flipRead() throws IOException { - // Move _x to _len to offset 0 - System.arraycopy(_ins,_x,_ins,0,(_len-_x)); - _len = _len-_x; - _x=0; - // Read more - int len = _in.read(_ins,_len,_ins.length-_len); - if( len == -1 ) - throw Utils.TODO(); // EOF - _len += len; - } - // Block until you read at least len - private void atLeast(int len) throws IOException { - if( _x+len <= _len ) return; - flipRead(); - } - - - // Classic read from browser client. Encoded, so decoded here - public String get() throws IOException { - atLeast(1); // Need 1 byte for opcode - int op = rawget(); - if( (op&0x80)!=0x80 ) // FIN bit not set? - throw Utils.TODO( "Multi-part from client: " + op ); // Multipart message - op &= 0x7F; // Strip FIN bit - switch( op&15) { - case 1: { - atLeast(5); // Min message length - // Message length - int len = rawget()-128; - if( len > 125 ) { - if( len > 126 ) - throw Utils.TODO(); // Multi-byte length - len = (rawget()<<8) | rawget(); - } - atLeast(len+4); // Rest of message - // Encoding key - _key[0] = (byte)rawget(); - _key[1] = (byte)rawget(); - _key[2] = (byte)rawget(); - _key[3] = (byte)rawget(); - - // Decode bytes - for( int i=0; i<len; i++ ) - _str[i] = (byte)(rawget() ^ _key[i & 3]); - - return new String(_str,0,len); - } - // Client closes - case 8: return null; - // Ping - case 9: - // Send a PONG back. TODO: echo PING data in the PONG - _out.write(0x8A); // FIN + PONG - _out.write(0x00); // LENGTH==0 - return get(); - // Not (yet) implemented: continuation frames, ping/pong - default: - throw Utils.TODO("Unexpected op from client: "+op); // Multi-part message - } - } - - // Classic put to browser client. No encoding. - public void put(String msg) throws IOException { - int len = msg.length(); - if( len <= 125 ) { - _out.write(129); // FIN, opcode 1 - whole text message - _out.write(len); - for( int i=0; i<msg.length(); i++ ) - _out.write(msg.charAt(i)); - return; - } - if( len > 65535 ) - throw Utils.TODO("MOAR LENGTH"); - _out.write(129); // FIN, opcode 1 - whole text message - _out.write(126); // Length is 16b - _out.write(len>>>8); - _out.write(len&255); - for( int i=0; i<msg.length(); i++ ) - _out.write(msg.charAt(i)); - } - - @Override public void close() throws IOException { - if( _out != null ) { - _out.write(0b10001000); - _out.flush(); - } - super.close(); - } -} diff --git a/src/main/java/com/seaofnodes/simple/sys.java b/src/main/java/com/seaofnodes/simple/sys.java deleted file mode 100644 index 45106770..00000000 --- a/src/main/java/com/seaofnodes/simple/sys.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.seaofnodes.simple; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - - -/** Default included namespaces for Simple. - - You can think of this as {@code #include <stdio.h>} for C or {@code import java.lang.*} for Java. - */ -public abstract class sys { - - public static final String SYS; - - static { - try { - SYS = Files.readString(Path.of("src/main/smp/sys.smp")); - } - catch( Exception e ) { - throw new RuntimeException(e); - } - } - -} diff --git a/src/main/java/com/seaofnodes/simple/type/Field.java b/src/main/java/com/seaofnodes/simple/type/Field.java deleted file mode 100644 index b2b2deca..00000000 --- a/src/main/java/com/seaofnodes/simple/type/Field.java +++ /dev/null @@ -1,136 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.SB; -import java.util.*; - -/** - * Represents a field in a struct. This is not a Type in the type system. - */ -public class Field extends Type { - - // The pair {fieldName,type} uniquely identifies a field. - - // Field name - public String _fname; - // Type of the field - public Type _t; - // Unique memory alias, not sensibly part of a "type" but very convenient here. - public int _alias; - // Field must be written to exactly once, no more, no less - public boolean _final; - // Field is final in the declaration; value is the same for all instances - // and will be moved to the class object - public boolean _one; - - private static final Ary<Field> FREE = new Ary<>(Field.class); - private Field(String fname, Type type, int alias, boolean xfinal, boolean one ) { super(TFLD); init(fname,type,alias,xfinal,one); } - private Field init(String fname, Type type, int alias, boolean xfinal, boolean one ) { - _fname = fname; - _t = type; - _alias = alias; - _final = xfinal; - _one = one; - return this; - } - - // Return a filled-in Field; either from free list or alloc new. - private static Field malloc(String fname, Type type, int alias, boolean xfinal, boolean one ) { - return FREE.isEmpty() ? new Field(fname,type,alias,xfinal,one) : FREE.pop().init(fname,type,alias,xfinal,one); - } - // Malloc-from - public Field malloc( ) { return malloc(_fname,null,_alias,_final,_one); } - @Override Field free(Type t) { - Field f = (Field)t; - assert !f.isFree() && !f._terned; - f._t = null; - f._dual = null; - f._hash = 0; - FREE.push(f); - return this; - } - @Override boolean isFree() { return _t ==null; } - - // Make and intern with listed fields - public static Field make( String fname, Type type, int alias, boolean xfinal, boolean one ) { - Field f = malloc(fname,type,alias,xfinal,one); - Field f2 = f.intern(); - if( f2==f ) return f; - return VISIT.isEmpty() ? f2.free(f) : f2.delayFree(f); - } - public Field makeFrom( Type type ) { - return type == _t ? this : make(_fname,type,_alias,_final,_one); - } - public Field makeFrom( boolean xfinal ) { - return xfinal == _final ? this : make(_fname,_t,_alias,xfinal,_one); - } - - // Cycle-making-breaking - void setType(Type t) { assert _t ==null; _t = t; } - - public static final Field BOT = make(" ",Type.BOTTOM,-999,true,false); - public static final Field TEST = make("test",Type.NIL,-2,false,false); - public static final Field TEST2 = make("test",Type.NIL,-2,true, false); - public static final Field FLT = make("flt ",TypeFloat.F32,-2,false,false); - public static void gather(ArrayList<Type> ts) { ts.add(TEST); ts.add(TEST2); ts.add(FLT); } - - @Override Field xmeet( Type that ) { - Field fld = (Field)that; // Invariant - if( this==BOT || fld==BOT ) return BOT; - if( this==BOT.dual() ) return fld ; - if( fld ==BOT.dual() ) return this; - if( _fname!=fld._fname ) { assert !_fname.equals(fld._fname); return BOT; } - assert _alias==fld._alias; - assert _one ==fld._one ; - return make(_fname, _t.meet(fld._t ),_alias,_final | fld._final, _one); - } - - @Override - Field xdual() { return malloc(_fname, _t.dual(),_alias,!_final,_one); } - - @Override Field rdual() { - if( _dual!=null ) return dual(); - assert !_terned; - Field d = malloc(_fname,null,_alias,!_final,_one); - (_dual = d)._dual = this; // Cross link duals - d._t = _t._terned ? _t.dual() : _t.rdual(); - return d; - } - - @Override boolean _isConstant() { return _t._isConstant(); } - @Override boolean _isFinal() { return _final && _t._isFinal(); } - @Override Field _makeRO() { return _final ? this : make(_fname, _t._makeRO(),_alias,true,_one); } - boolean isGLB2() { return _final && _t._isGLB(true); } - Field glb2() { - Type glb = _t._glb(true); - return (glb== _t && _final ) ? this : make(_fname,glb,_alias,true,_one); - } - @Override Field _close() { return makeFrom( _t._close()); } - - // Override in subclasses - int hash() { return _fname.hashCode() ^ _t.hashCode() ^ _alias ^ (_final ? 1024 : 0) ^ (_one ? 2048 : 0); } - - private boolean static_eq( Field f ) { - return _fname.equals(f._fname) && _alias==f._alias && _final==f._final && _one==f._one && _t !=null && f._t !=null; - } - @Override boolean eq( Type t ) { - Field f = (Field)t; - return static_eq(f) && _t ==f._t; - } - @Override boolean cycle_eq(Type t) { - Field f = (Field)t; - return static_eq(f) && _t.cycle_eq(f._t ); - } - - @Override int nkids() { return 1; } - @Override Type at( int idx ) { return _t; } - @Override void set( int idx, Type t ) { _t = t; } - - @Override - public SB _print( SB sb, BitSet visit, boolean html ) { - sb.p(_final?"":"!").p(_one?"$":"").p(_fname).p(":").p(_alias).p(" : "); - return _t ==null ? sb.p("---") : _t.print(sb,visit,html); - } - - @Override public String str() { return (_final?"":"!")+(_one?"$":"")+_fname; } -} diff --git a/src/main/java/com/seaofnodes/simple/type/Type.java b/src/main/java/com/seaofnodes/simple/type/Type.java deleted file mode 100644 index b7a8408e..00000000 --- a/src/main/java/com/seaofnodes/simple/type/Type.java +++ /dev/null @@ -1,514 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.util.*; -import java.util.*; - -/** - * These types are part of a Monotone Analysis Framework, - * @see <a href="https://www.cse.psu.edu/~gxt29/teaching/cse597s21/slides/08monotoneFramework.pdf">see for example this set of slides</a>. - * <p> - * The types form a lattice; @see <a href="https://en.wikipedia.org/wiki/Lattice_(order)">a symmetric complete bounded (ranked) lattice.</a> - * <p> - * This wild lattice theory will be needed later to allow us to easily beef up - * the analysis and optimization of the Simple compiler... but we don't need it - * now, just know that it is coming along in a later Chapter. - * <p>g - * One of the fun things here is that while the theory is deep and subtle, the - * actual implementation is darn near trivial and is generally really obvious - * what we're doing with it. Right now, it's just simple integer math to do - * simple constant folding e.g. 1+2 == 3 stuff. - */ - -public class Type /*implements Cloneable*/ { - // Interning tables - - // Main intern table; a collection of all unique types. - static final HashMap<Type,Type> INTERN = new HashMap<>(); - // Starting intern table; used to rapidly reset the main table between tests - private static final HashMap<Type,Type> INTERN0= new HashMap<>(); - // Lifetime management during cyclic installs is complex; this is used to - // track created-but-already-interned objects - private static final Ary<Type> FREES = new Ary<>(Type.class); - // "visit" bit management, based solely on _uid - static final BitSet BITS = new BitSet(); - - // "visit" bit management. Key varies by kind of visit: - - // - "close" an open cyclic type. Used by the Parser, it will hit on - // same-named structs, even if the structs have different states of - // completion/fields. - // - "uid/name" - Either the UID or name works (unlike "close" which must - // use the name). Used to make cyclic copies with changes, like make - // read-only where there should be only 1 copy of a name in a cycle. - // - "pair of uids" - For recursive MEET, where we need to key off two types - static final HashMap<Object,Type> VISIT = new HashMap<>(); - - // Every Type has a UID to allow visit bits on cyclic visits - private static int UID=1; - public char _uid; // Unique ID for every type - - // ---------------------------------------------------------- - // Simple types are implemented fully here. "Simple" means: the code and - // type hierarchy are simple, not that the Type is conceptually simple. - static final byte TBOT = 0; // Bottom (ALL) - static final byte TTOP = 1; // Top (ANY) - static final byte TCTRL = 2; // Ctrl flow bottom - static final byte TXCTRL = 3; // Ctrl flow top (mini-lattice: any-xctrl-ctrl-all) - static final byte TNIL = 4; // low null of all flavors - static final byte TXNIL = 5; // high or choice null - static final byte TSIMPLE = 6; // End of the Simple Types - - static final byte TPTR = 7; // All nil-able scalar values - static final byte TINT = 8; // All Integers; see TypeInteger - static final byte TFLT = 9; // All Floats ; see TypeFloat - static final byte TCONARY =10; // Constant array - static final byte TRPC =11; // Return Program Control (Return PC or RPC) - static final byte TTUPLE =12; // Tuples; finite collections of unrelated Types, kept in parallel - - static final byte TCYCLIC =13; // Has internal pointers, needs recursive treatment - static final byte TMEMPTR =13; // Memory pointer to a struct type - static final byte TFUNPTR =14; // Function pointer; unique signature and code address (just a bit) - static final byte TMEM =15; // All memory (alias 0) or A slice of memory - with specific alias - static final byte TSTRUCT =16; // Structs; tuples with named fields - static final byte TFLD =17; // Named fields in structs - - // Basic RTTI, useful for a lot of fast tests. - public final byte _type; - public boolean _terned; - - public boolean is_simple() { return _type < TSIMPLE; } - private static final String[] STRS = new String[]{"Bot","Top","Ctrl","~Ctrl","null","~nil"}; - static final int[] CNTS = new int[TFLD+1]; - protected Type(byte type) { - _type = type; // RTTI - _uid = (char)UID++; // A unique ID for every type - assert _uid!=0; // Overflow - CNTS[type]++; - } - - public static final Type BOTTOM = new Type( TBOT ).intern(); // ALL - public static final Type TOP = BOTTOM.dual(); - public static final Type CONTROL = new Type( TCTRL ).intern(); // Ctrl - public static final Type XCONTROL = CONTROL.dual(); - public static final Type NIL = new Type( TNIL ).intern(); // low null of all flavors - public static final Type XNIL = NIL.dual(); - public static Type[] gather() { - ArrayList<Type> ts = new ArrayList<>(); - ts.add(BOTTOM); - ts.add(CONTROL); - ts.add(NIL); - TypeNil.gather(ts); - TypePtr.gather(ts); - TypeInteger.gather(ts); - TypeFloat.gather(ts); - TypeMemPtr.gather(ts); - TypeFunPtr.gather(ts); - TypeMem.gather(ts); - Field.gather(ts); - TypeStruct.gather(ts); - TypeTuple.gather(ts); - TypeRPC.gather(ts); - TypeConAry.gather(ts); - - int sz = ts.size(); - for( int i = 0; i < sz; i++ ) - ts.add(ts.get(i).dual()); - return ts.toArray(new Type[ts.size()]); - } - - // Is high or on the lattice centerline. - public boolean isHigh () { return _type==TTOP || _type==TXCTRL || _type==TXNIL; } - public boolean isHighOrConst() { return isHigh() || isConstant(); } - - // ---------------------------------------------------------- - - // Notes on Type interning: At the moment it is not easy to reset the - // interned types because we hold static references to several types and - // these are scattered around. This means the INTERN cache will retain all - // types from every run of the Parser. For this to work correctly types - // must be rigorous about defining when they are the same. Also types need - // to be immutable once defined. The rationale for interning is - // *correctness* with cyclic type definitions. Simple structural recursive - // checks go exponential with merely sharing, but with cycles they will - // stack overflow and crash. Interning means we do not need to have sharing - // checks with every type compare, only during interning. - - // Factory method which interns "this" - @SuppressWarnings("unchecked") - <T extends Type> T intern() { - //assert check(); - assert !_terned; // Do not ask for already-interned - T t2 = (T)INTERN.get(this); - if( t2!=null ) { - assert t2._dual != null; // Prior is complete with dual - assert this != t2; // Do not hashcons twice, should not get self back - return t2; // Return prior - } - // Mid recursive type construction - if( !VISIT.isEmpty() && _type >= TCYCLIC ) { - // No intern attempt at all - return (T)this; - } - // Not in type table - _dual = null; // No dual yet - INTERN.put(this,this); // Put in table without dual - _terned = true; - //Util.hash_quality_check_per(INTERN,"INTERN"); - T d = xdual(); // Compute dual without requiring table lookup, and not setting name - _dual = d; - if( this==d ) return d; // Self-symmetric? Dual is self - assert !equals(d); // Self-symmetric is handled by caller - assert d._dual==null; // Else dual-dual not computed yet - assert INTERN.get(d)==null; - d._dual = (T)this; - INTERN.put(d,d); // Install dual also - d._terned = true; - //assert check(); - return (T)this; - } - - // Recursive visit elsewhere, interns with dual already - <T extends Type> T _intern() { - //assert check(); - assert !_terned; - Type t = INTERN.get(this); - if( t!=null ) return (T)t.delayFree(this).delayFree(_dual); - assert !INTERN.containsKey( _dual ); - INTERN.put(this,this); - INTERN.put(_dual,_dual); - _terned = _dual._terned = true; - //assert check(); - return (T)this; - } - - private static boolean check() { - for( Type t : INTERN.keySet() ) { - Type t2 = INTERN.get(t); - assert t==t2; - } - return true; - } - - public static Type find(int uid) { - for( Type t : INTERN.keySet() ) - if( t._uid==uid ) return t; - return null; - } - - - int _hash; // Hash cache; not-zero when set. - @Override - public final int hashCode() { - if( _hash!=0 ) return _hash; - _hash = hash(); - if( _hash==0 ) _hash = 0xDEADBEEF; // Bad hash from subclass; use some junk thing - return _hash; - } - // Override in subclasses - int hash() { return _type; } - - @Override - public final boolean equals( Object o ) { - if( o==this ) return true; - if( !(o instanceof Type t)) return false; - if( _type != t._type ) return false; - return eq(t); - } - // Overridden in subclasses; subclass can assume "this!=t" and java classes are same - boolean eq(Type t) { return this==t; } - boolean cycle_eq(Type t) { assert _type < TCYCLIC; return eq(t); } - // A pair of uids - int pid( Type that ) { - return _uid < that._uid ? (_uid<<16 | that._uid) : (that._uid<<16 | _uid); - } - - // At/Set child at 'idx' to t - Type at ( int idx ) { throw Utils.TODO(); } - void set( int idx, Type t ) { throw Utils.TODO(); } - int nkids() { assert _type < TTUPLE; return 0; } // Number of kids - - - // Clear and re-insert the basic Type INTERN table - public static void reset() { - VISIT.clear(); - FREES.clear(); - if( INTERN0.isEmpty() ) { - // First time run all <clinit> and collect basic types - Type t = TypeRPC .BOT; // Force class load, intern - Type u = TypeTuple.BOT; // Force class load, intern - Type w = TypePtr .PTR; // Force class load, intern - INTERN0.putAll(INTERN); - } else { - // Later times, reset back to first time - VISIT.put(0,CONTROL); // Defeat assert - for( Iterator<Type> it = INTERN.keySet().iterator(); it.hasNext(); ) { - Type t = it.next(); - if( !INTERN0.containsKey(t) ) - { t._terned=false; t.free(t); } - it.remove(); - } - VISIT.clear(); - INTERN.putAll(INTERN0); - } - } - - // ---------------------------------------------------------- - public final Type meet(Type t) { - // Shortcut for the self case - if( t == this ) return this; - // Same-type is always safe in the subclasses - if( _type==t._type ) return xmeet(t); - // TypeNil vs TypeNil meet - if( this instanceof TypeNil ptr0 && t instanceof TypeNil ptr1 ) - return ptr0.nmeet(ptr1); - // Reverse; xmeet 2nd arg is never "is_simple" and never equal to "this". - if( is_simple() ) return this.xmeet(t ); - if( t.is_simple() ) return t .xmeet(this); - return Type.BOTTOM; // Mixing 2 unrelated types - } - - // Compute meet right now. Overridden in subclasses. - // Handle cases where 'this.is_simple()' and unequal to 't'. - // Subclassed xmeet calls can assert that '!t.is_simple()'. - Type xmeet(Type t) { - assert is_simple(); // Should be overridden in subclass - // ANY meet anything is thing; thing meet ALL is ALL - if( _type==TBOT || t._type==TTOP ) return this; - if( _type==TTOP || t._type==TBOT ) return t; - - // RHS TypeNil vs NIL/XNIL - if( _type== TNIL ) return t instanceof TypeNil ptr ? ptr.meet0() : (t._type==TXNIL ? TypePtr.PTR : BOTTOM); - if( _type== TXNIL ) return t instanceof TypeNil ptr ? ptr.meetX() : (t._type== TNIL ? TypePtr.PTR : BOTTOM); - - // 'this' is only {TCTRL,TXCTRL} - // Other non-simple RHS things bottom out - if( !t.is_simple() ) return BOTTOM; - // If RHS is NIL/XNIL - if( t._type==TNIL || t._type==TXNIL ) return BOTTOM; - // Both are {TCTRL,TXCTRL} and unequal to each other - return _type==TCTRL || t._type==TCTRL ? CONTROL : XCONTROL; - } - - - // The dual is pre-computed on creation and available "for free" thereafter - Type _dual; - public final <T extends Type> T dual() { return (T)_dual; } - - <T extends Type> T xdual() { - return (T)new Type(switch( _type ) { - case TBOT -> TTOP; - case TTOP -> TBOT; - case TCTRL -> TXCTRL; - case TXCTRL-> TCTRL; - case TNIL -> TXNIL; - case TXNIL -> TNIL; - default -> throw Utils.TODO(); // Should not reach here - }); - } - - - // ---------------------------------------------------------- - // Our lattice is defined with a MEET and a DUAL. - // JOIN is dual of meet of both duals. - public final Type join(Type t) { - if( this==t ) return this; - return dual().meet(t.dual()).dual(); - } - - // True if this "isa" t; e.g. 17 isa TypeInteger.BOT - public boolean isa( Type t ) { return meet(t)==t; } - - // True if this "isa" t up to named structures - public boolean shallowISA( Type t ) { return isa(t); } - - public Type nonZero() { return TypePtr.NPTR; } - - // Make a zero version of this type, 0 for integers and null for pointers. - public Type makeZero() { return Type.NIL; } - - // Is forward-reference - public boolean isFRef() { return false; } - - // ---------------------------------------------------------- - - // Cyclic types! Flag the start of a cyclic type by putting a sentinel in - // VISIT (and eventually clearing VISIT when done). Then do a normal - // recursive descent visit of all types; this will accumilate types without - // interning them. When done we have to visit the possibly cyclic type and - // intern the whole cycle, possibly hitting the entire cycle on a prior - // interned cycle. Any sub-part might also be interned, including whole - // disjoint cycles. - - // E.g. a new type: "A<->B -> C1<->D1" gets created; the "C1<->D1" cycle - // already exists as "C0<->D0", but the "A<->B" cycle is new. The "A<->B" - // cycle will get interned - except the pointer to C1 needs to get replaced - // with C0. The "C1<->D1" cycle gets freed and "B" will point to the - // "C0<->D0" cycle. - - Type recurOpen() { assert VISIT.isEmpty(); VISIT.put(0L,BOTTOM); return this; } - Type recurClose() { - VISIT.remove(0L); // Just ignore the sentinel - Ary<Type> ts = new Ary<>(Type.class); - ts.addAll(VISIT.values()); - if( ts.find(this)== -1 ) ts.add(this); - - // Pass #1: replace already interned fields - assert BITS.isEmpty(); - for( int i=0; i<ts._len; i++ ) - if( ts.at(i).tern()._terned ) - ts.del(i--); - BITS.clear(); - - // Pass#2: compute recursive duals - for( Type t : ts ) - t.rdual(); - - // Pass#3: Recursive install - for( Type t : ts ) - if( !t._terned && INTERN.get(t)==null ) - t.install(); - // Upgrade the result - Type rez = INTERN.get(this); - assert rez!=null; - - // Pass#4?: Free up any created-but-already interned - for( Type t : FREES ) - t.rfree(); - FREES.clear(); - VISIT.clear(); - return rez; - } - - // Read-only recursive visits do not *make* cyclic types and so the cleanup - // is much easier. - static boolean recurClose(boolean rez) { VISIT.clear(); return rez; } - - final Type tern() { - if( _terned ) return this; - if( !BITS.get(_uid) ) { - BITS.set(_uid); - int nkids = nkids(); - for( int i=0; i<nkids; i++ ) - set(i,at(i).tern()); - } - Type told = INTERN.get(this); - return told==null ? this : told.delayFree(this); - } - - // Recursive dual. Visit all types recursively. Each visited type is - // *new*, and hits in the intern table, or not. If not, we recursively - // call rdual to get its dual, and cross-link the duals. If hitting, we - // use the intern type and mark the prior type for freeing. - Type rdual() { assert !_terned; return xdual(); } - - // Recursively free type and children - final void rfree() { - if( isFree() ) return; - int nkids = nkids(); - for( int i=0; i<nkids; i++ ) - if( !at(i)._terned ) - at(i).rfree(); - free(this); - } - - Type free(Type free) { return this; } - boolean isFree() { return false; } - - <T extends Type> T delayFree(Type free) { - assert !free._terned; - FREES.push(free); - return (T)this; - } - - final Type install() { - if( this instanceof TypeStruct ) { - Type x = _intern(); // Stop the recursion - assert x==this; - } - int nkids = nkids(); - for( int i=0; i<nkids; i++ ) { - Type kid = at(i); - if( !kid._terned ) { - Type ikid = kid.install(); - if( ikid != kid ) { - set(i,ikid); - _dual.set(i,ikid.dual()); - } - } - } - return this instanceof TypeStruct ? this : _intern(); - } - - // Strict constant values, things on the lattice centerline. - // Excludes both high and low values - public final boolean isConstant() { return recurClose(recurOpen()._isConstant()); } - boolean _isConstant() { return _type==TNIL; } - - // Are all reachable struct Fields are final? - public final boolean isFinal() { return recurClose(recurOpen()._isFinal()); } - boolean _isFinal() { assert _type < TCYCLIC; return true; } - - public final Type makeRO() { - if( isFinal() ) return this; - return recurOpen()._makeRO().recurClose(); - } - Type _makeRO() { return this; } - - // Compute greatest lower bound in the lattice. If values are in memory, - // ints and floats cannot widen. - public final boolean isGLB(boolean mem) { return recurClose(recurOpen()._isGLB(mem)); }; - boolean _isGLB(boolean mem) { - return switch(_type) { - case TBOT -> false; - case TNIL -> false; - case TCTRL -> true; - case TXCTRL -> false; - case TXNIL -> false; - case TTOP -> false; - default -> throw Utils.TODO(); - }; - } - public final Type glb(boolean mem) { - if( isGLB(mem) ) return this; - Type glb = recurOpen()._glb(mem).recurClose(); - assert this.isa(glb); - return glb; - } - Type _glb(boolean mem) { assert is_simple(); return Type.BOTTOM; } - - Type _close() { return this; } - - // ---------------------------------------------------------- - - // Size in bits to hold an instance of this type. - // Sizes are expected to be between 1 and 64 bits. - // Size 0 means this either takes no space (such as a known-zero field) - // or isn't a scalar to be stored in memory. - public int log_size () { return 3; } // log-size of a type; log_size for a struct is usually undefined - public int size() { return 1<<log_size(); } - public int alignment() { return log_size(); } // alignment; for structs, max align of Fields - - // ---------------------------------------------------------- - // Useful in the debugger, which calls toString everywhere. - // This is a more verbose dev-friendly print. - @Override - public final String toString() { - return print(new SB(), new BitSet(), false).toString(); - } - public final String gprint() { - return print(new SB(), new BitSet(), true).toString(); - } - public final SB print(SB sb) { return print(sb, new BitSet(), false); } - public final SB gprint(SB sb) { return print(sb, new BitSet(), true ); } - - // Used during recursive printing, or combined with SB Node printing - public final SB print(SB sb, BitSet visit, boolean html ) { - if( visit.get(_uid) ) return sb.p(str()); - visit.set(_uid); - return _print(sb,visit,html); - } - SB _print(SB sb, BitSet visit, boolean html ) { return sb.p(str()); } - - // This is used by error messages, and is a shorted print. - public String str() { return STRS[_type]; } -} diff --git a/src/main/java/com/seaofnodes/simple/type/TypeConAry.java b/src/main/java/com/seaofnodes/simple/type/TypeConAry.java deleted file mode 100644 index d4003f87..00000000 --- a/src/main/java/com/seaofnodes/simple/type/TypeConAry.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import java.io.ByteArrayOutputStream; -import java.util.ArrayList; - -/** - * Represents a constant array of primitives - */ -public class TypeConAry<A> extends Type { - boolean _any; - // One of byte,short,int,long,float,double array - public final A _ary; - - TypeConAry( boolean any, byte type, A ary ) { super(type); _any = any; _ary = ary; } - public static final TypeConAry BOT = new TypeConAry(false, TINT, null).intern(); - public static void gather(ArrayList<Type> ts) { - ts.add(BOT); - TypeConAryB.gather(ts); - TypeConAryI.gather(ts); - } - - @Override public String str() { return (_any?"~":"") + "[]"; } - @Override TypeConAry xdual() { - if( _ary==null ) - return new TypeConAry(!_any,_type,null); - return this; - } - - @Override Type xmeet(Type t) { - if( t instanceof TypeInteger ti ) return imeet(ti); - TypeConAry ary = (TypeConAry)t; // Invariant - if( this==BOT ) return BOT; - if( ary ==BOT ) return BOT; - if( this==BOT.dual() ) return ary ; - if( ary ==BOT.dual() ) return this; - assert _ary!=ary._ary; // Already interned and this!=t - return elem().meet(ary.elem()); - } - Type imeet( TypeInteger ti ) { - if( this==BOT.dual() ) return ti; - if( this==BOT ) return BOT; - Type elem = elem(); - if( !(elem instanceof TypeInteger) ) return BOTTOM; - if( ti.isHigh() && elem.isa(ti.dual()) ) - return this; - return elem.meet(ti); - } - - - @Override public boolean isHigh() { return this==TOP; } - @Override boolean _isConstant() { return true; } - - // Meet-over-elements type - public Type elem() { - if( _ary==null ) - return _any ? Type.TOP : BOTTOM; - int len = len(); - long min = Long.MAX_VALUE; - long max = Long.MIN_VALUE; - for( int i=0; i<len; i++ ) { - min = Math.min(min,at8(i)); - max = Math.max(max,at8(i)); - } - return TypeInteger.make(min,max); - } - public long at8(int idx) { throw Utils.TODO(); } - public int len() { throw Utils.TODO(); } - @Override public int log_size() { throw Utils.TODO(); } - public void write( ByteArrayOutputStream baos ) { throw Utils.TODO(); } - - @Override boolean eq(Type t) { - return t instanceof TypeConAry ary && _any==ary._any && ary._ary==null; - } - - @Override int hash() { - assert _ary==null; - return _any ? 1024 : 0; - } - -} diff --git a/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java b/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java deleted file mode 100644 index fd2b2635..00000000 --- a/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.seaofnodes.simple.type; - -import java.util.ArrayList; -import java.util.Arrays; -import java.io.ByteArrayOutputStream; - -public class TypeConAryB extends TypeConAry<byte[]> { - private TypeConAryB(byte[] bs) { super(false,TINT,bs); } - public static TypeConAryB make( byte[] bs ) { return new TypeConAryB(bs).intern(); } - public static TypeConAryB make( String s ) { return make(s.getBytes()); } - static final TypeConAryB ABC = make("abc"); - static final TypeConAryB ABCD = make("abcd"); - public static void gather(ArrayList<Type> ts) { ts.add(ABC); ts.add(ABCD); } - @Override public long at8(int idx) { return _ary[idx]; } - @Override public int len() { return _ary.length; } - @Override public int log_size() { return 0; } - @Override public String str() { return "[\""+new String(_ary)+"\"]"; } - @Override boolean eq(Type t) { return Arrays.equals(_ary,((TypeConAryB)t)._ary); } - @Override int hash() { return Arrays.hashCode(_ary); } - @Override public void write( ByteArrayOutputStream baos ) { baos.write(_ary,0,_ary.length); } -} diff --git a/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java b/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java deleted file mode 100644 index 924e3d73..00000000 --- a/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.seaofnodes.simple.type; - -import java.util.ArrayList; -import java.util.Arrays; - -public class TypeConAryI extends TypeConAry<int[]> { - private TypeConAryI(int[] bs) { super(false,TINT,bs); } - public static TypeConAryI make( int[] bs ) { return new TypeConAryI(bs).intern(); } - private static final TypeConAryI I123 = make(new int[]{1,2,3}); - public static void gather(ArrayList<Type> ts) { ts.add(I123); } - @Override public long at8(int idx) { return _ary[idx]; } - @Override public int len() { return _ary.length; } - @Override public int log_size() { return 2; } - @Override public String str() { return "[i32]"; } - @Override boolean eq(Type t) { return Arrays.equals(_ary,((TypeConAryI)t)._ary); } - @Override int hash() { return Arrays.hashCode(_ary); } -} diff --git a/src/main/java/com/seaofnodes/simple/type/TypeFloat.java b/src/main/java/com/seaofnodes/simple/type/TypeFloat.java deleted file mode 100644 index f3de581c..00000000 --- a/src/main/java/com/seaofnodes/simple/type/TypeFloat.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import com.seaofnodes.simple.util.Ary; -import java.util.ArrayList; - -/** - * Float Type - */ -public class TypeFloat extends Type { - - // - high -64, high -32, con 0, low +32, low +64 - public byte _sz; - - /** - * The constant value or 0 - */ - public double _con; - - private static final Ary<TypeFloat> FREE = new Ary<>(TypeFloat.class); - private TypeFloat(byte sz, double con) { super(TFLT); init(sz,con); } - private TypeFloat init( byte sz, double con ) { _sz = sz; _con = con; return this; } - public static TypeFloat malloc(byte sz, double con) { return FREE.isEmpty() ? new TypeFloat(sz,con) : FREE.pop().init(sz,con); } - private static TypeFloat make(int sz, double con) { - TypeFloat f = malloc((byte)sz,con); - TypeFloat t2 = f.intern(); - return t2==f ? f : t2.free(f); - } - @Override TypeFloat free(Type t) { - TypeFloat f = (TypeFloat)t; - f._hash = 0; - f._dual=null; - FREE.push(f); - return this; - } - - public static TypeFloat constant(double con) { return make(0, con); } - - public final static TypeFloat ONE = constant(1.0); - public final static TypeFloat FZERO = constant(0.0); - public final static TypeFloat F32 = make(32, 0); - public final static TypeFloat F64 = make(64, 0); - - public static void gather(ArrayList<Type> ts) { ts.add(F64); ts.add(F32); ts.add(constant(3.141592653589793)); } - - @Override public String str() { - return switch( _sz ) { - case -64 -> "~flt"; - case -32 -> "~f32"; - case 0 -> ""+_con+((float)_con==_con ? "f" : ""); - case 32 -> "f32"; - case 64 -> "flt"; - default -> throw Utils.TODO(); - }; - } - private boolean isF32() { return ((float)_con)==_con; } - - @Override public boolean isHigh () { return _sz< 0; } - @Override public int log_size() { return _sz==32 || _sz==-32 ? 2 : 3; } - - public double value() { return _con; } - - @Override - public TypeFloat xmeet(Type other) { - TypeFloat f = (TypeFloat)other; - // Invariant from caller: 'this' != 'other' and same class (TypeFloat). - TypeFloat i = (TypeFloat)other; // Contract - // Larger size in i1, smaller in i0 - TypeFloat i0 = _sz < i._sz ? this : i; - TypeFloat i1 = _sz < i._sz ? i : this; - - if( i1._sz== 64 ) return F64; - if( i0._sz==-64 ) return i1; - if( i1._sz== 32 ) - return i0._sz==0 && !i0.isF32() ? F64 : F32; - if( i1._sz!= 0 ) return i1; - // i1 is a constant - if( i0._sz==-32 ) - return i1.isF32() ? i1 : F64; - // Since both are constants, and are never equals (contract) unequals - // constants fall to bottom - return i0.isF32() && i1.isF32() ? F32 : F64; - } - - @Override - TypeFloat xdual() { - return _isConstant() ? this : new TypeFloat((byte)-_sz,0); // Constants are a self-dual - } - - @Override boolean _isGLB(boolean mem) { return _glb(mem)==this; } - @Override Type _glb(boolean mem) { - if( !mem ) return F64; - if( _isConstant() ) return isF32() ? F32 : F64; - return isHigh() ? dual() : this; - } - - @Override boolean _isConstant() { return _sz==0; } - - @Override public Type makeZero() { return FZERO; } - @Override - int hash() { return (int)(Double.hashCode(_con) ^ _sz ^ (1<<17)); } - @Override - public boolean eq( Type t ) { - TypeFloat i = (TypeFloat)t; // Contract - // Allow NaN to check for equality - return _sz==i._sz && Double.doubleToLongBits(_con)==Double.doubleToLongBits(i._con); - } - -} diff --git a/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java b/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java deleted file mode 100644 index b21dd451..00000000 --- a/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java +++ /dev/null @@ -1,241 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import com.seaofnodes.simple.util.Ary; -import java.util.*; - -/** - * Represents a Pointer to a function. - * <p></p> - * Functions have argument types and a return type, making a function - * signature. Within a signature there can be many instances of functions, and - * each function is labeled with a small integer constant. A TFP can represent - * a single signature and a set of functions; the set might contain only 1 - * function or the zeroth function (the null ptr). - * <p></p> - * Functions with different signatures cannot be mixed, and will result in a - * bottom function type, which can only be null-checked. - */ -public class TypeFunPtr extends TypeNil { - // A TypeFunPtr is Signature and a set of functions. - public Type[] _sig; - public Type _ret; - // TODO: - boolean _open; // Extra args are true:BOTTOM, false:TOP - // Cheesy easy implementation for a small set; 1 bit per unique function - // within the same Type[]. Can be upgraded to a BitSet for larger classes - // of functions. Negative means "these 63 concrete bits plus infinite - // unknown more" - public long _fidxs; // 63 unique functions per signature - - private static final Ary<TypeFunPtr> FREE = new Ary<>(TypeFunPtr.class); - private TypeFunPtr(byte nil, boolean open, Type[] sig, Type ret, long fidxs) { super(TFUNPTR,nil); init(nil,open,sig,ret,fidxs); } - private TypeFunPtr init(byte nil, boolean open, Type[] sig, Type ret, long fidxs ) { - assert sig != null; - _nil = nil; - _open = open; - _sig = sig; - _ret = ret; - _fidxs= fidxs; - return this; - } - static TypeFunPtr malloc( byte nil, boolean open, Type[] sig, Type ret, long fidxs ) { - return FREE.isEmpty() ? new TypeFunPtr(nil,open,sig,ret,fidxs) : FREE.pop().init(nil,open,sig,ret,fidxs); - } - @Override TypeFunPtr free(Type t) { - TypeFunPtr fun = (TypeFunPtr)t; - assert !fun.isFree() && !fun._terned; - fun._sig = null; - fun._ret = null; - fun._dual = null; - fun._hash = 0; - FREE.push(fun); - return this; - } - @Override boolean isFree() { return _sig==null; } - - // All fields directly listed - public static TypeFunPtr make( byte nil, boolean open, Type[] sig, Type ret, long fidxs ) { - TypeFunPtr fun = malloc(nil,open,sig,ret,fidxs); - TypeFunPtr f2 = fun.intern(); - if( f2==fun ) return fun; - return VISIT.isEmpty() ? f2.free(fun) : f2.delayFree(fun); - } - public static TypeFunPtr make( boolean nil, boolean open, Type[] sig, Type ret ) { return make((byte)(nil ? 3 : 2),open,sig,ret,-1); } - - - @Override TypeFunPtr makeFrom( byte nil ) { return nil ==_nil ? this : make( nil,_open,_sig,_ret, _fidxs); } - public TypeFunPtr makeFrom( Type ret ) { return ret ==_ret ? this : make( _nil,_open,_sig, ret, _fidxs); } - public TypeFunPtr makeFrom( int fidx ) { return make((byte)2, _open, _sig,_ret,1L<<fidx ); } - - - public static final Type[] TEMPTY = new Type[0]; - static final Type[] TINT = new Type[]{TypeInteger.BOT}; - static final Type[] TINTMEM = new Type[]{TypeInteger.BOT,TypeFloat.F32}; - static final Type[] TINTINT = new Type[]{TypeInteger.BOT,TypeInteger.BOT}; - public static TypeFunPtr BOT = make((byte)3,true ,TEMPTY,Type.BOTTOM,-1); - public static TypeFunPtr TEST = make((byte)2,false,TINTMEM,TypeInteger.BOT, 1); - public static TypeFunPtr TEST0 = make((byte)3,false,TINTMEM,TypeInteger.BOT, 3); - public static TypeFunPtr MAIN = make((byte)3,false,TINT ,Type.BOTTOM,-1); // Main can return anything - public static TypeFunPtr CALLOC= make((byte)3,false,TINTINT,TypeMemPtr .BOT,-1); - public static void gather(ArrayList<Type> ts) { ts.add(TEST); ts.add(TEST0); ts.add(BOT); ts.add(MAIN);ts.add(CALLOC); } - - @Override - Type xmeet(Type t) { - TypeFunPtr that = (TypeFunPtr) t; - // if equal, no matters. - // if short is BOT, chop ; recurPre on short. - // if short is TOP, copy long; recurPre on long. - TypeFunPtr min = _sig.length < that._sig.length ? this : that; - if( _sig.length != that._sig.length && (min._open ^ min==this) ) - return that.xmeet(this); - // Recurse all common fields - Type[] args = new Type[_sig.length]; - for( int i=0; i<min._sig.length; i++ ) - args[i] = _sig[i].meet(that._sig[i]); - // Handle extra args - for( int i=min._sig.length; i<_sig.length; i++ ) - args[i] = _sig[i]; - return make(xmeet0(that),_open | that._open, args, _ret.meet(that._ret), _fidxs | that._fidxs); - } - static Type[] xmeet( Type[] ts0, Type[] ts1) { - if( ts0==ts1 ) return ts0; - assert ts0.length==ts1.length; - Type[] ts = new Type[ts0.length]; - for( int i=0; i<ts0.length; i++ ) - ts[i] = ts0[i].meet(ts1[i]); - return ts; - } - - @Override TypeFunPtr xdual() { return malloc(dual0(), !_open, xdual(_sig), _ret.dual(), ~_fidxs); } - static Type[] xdual(Type[] ts) { - Type[] dual = new Type[ts.length]; - for( int i=0; i<ts.length; i++ ) - dual[i] = ts[i].dual(); - return dual; - } - - @Override TypeFunPtr rdual() { - if( _dual!=null ) return dual(); - assert !_terned; - Type[] sigs = new Type[_sig.length]; - TypeFunPtr d = malloc(dual0(),!_open,sigs,null,~_fidxs); - (_dual = d)._dual = this; // Cross link duals - // Return rdual - d._ret = _ret._terned ? _ret.dual() : _ret.rdual(); - // Signature rdual - for( int i=0; i<_sig.length; i++ ) - sigs[i] = _sig[i]._terned ? _sig[i].dual() : _sig[i].rdual(); - return d; - } - - // RHS is NIL; do not deep-dual when crossing the centerline - @Override public Type meet0() { return _nil==3 ? this : make((byte)3,_open,_sig,_ret,_fidxs); } - - @Override public boolean isHigh() { return _nil <= 1 || (_nil==2 && _fidxs==0); } - - @Override boolean _isConstant() { return (_nil==2 && Long.bitCount(_fidxs)==1) || (_nil==3 && _fidxs==0); } - - @Override boolean _isFinal() { return true; } - @Override boolean _isGLB(boolean mem) { return true; } - @Override TypeFunPtr _glb(boolean mem) { return this; } - @Override TypeFunPtr _close() { - Type[] sig = new Type[_sig.length]; - TypeFunPtr fun = malloc(_nil,false,sig,null,_fidxs); - // Now start the recursion - fun._ret = _ret._close(); - for( int i=0; i<sig.length; i++ ) - sig[i] = _sig[i]._close(); - - return fun; - } - - @Override public int log_size() { return 2; } // (1<<2)==4-byte pointers - - public Type arg(int i) { return _sig[i]; } - public long fidxs() { return _fidxs; } - public Type ret() { return _ret; } - public int nargs() { return _sig.length; } - public int fidx() { assert Long.bitCount(_fidxs)==1; return Long.numberOfTrailingZeros(_fidxs); } - - - @Override int nkids() { return _sig.length+1; } - @Override Type at( int idx ) { - return idx == _sig.length ? _ret : _sig[idx]; - } - @Override void set( int idx, Type t ) { - if( idx == _sig.length ) _ret = t; - else _sig[idx] = t; - } - - @Override - int hash() { - long hash = _ret.hashCode() ^ _fidxs ^ super.hash(); - for( Type arg : _sig ) - hash = hash*19 ^ arg.hashCode(); - return Utils.fold(hash); - } - - @Override - boolean eq(Type t) { - // Recursive; so use cyclic equals - if( !VISIT.isEmpty() ) { - assert TypeStruct.CEQUALS.isEmpty(); - boolean rez = cycle_eq(t); - TypeStruct.CEQUALS.clear(); - return rez; - } - TypeFunPtr fun = (TypeFunPtr)t; // Invariant - if( !super.eq(fun) || _open != fun._open || _fidxs != fun._fidxs || _sig.length != fun._sig.length ) return false; - if( _sig == fun._sig && _ret == fun._ret ) return true; - - if( !_ret.eq(fun._ret) ) return false; - for( int i = 0; i < _sig.length; i++ ) - if( !_sig[i].eq(fun._sig[i]) ) - return false; - return true; - } - @Override boolean cycle_eq(Type t) { - if( t._type != TFUNPTR ) return false; - TypeFunPtr fun = (TypeFunPtr)t; // Invariant - if( !super.eq(fun) || _open != fun._open || _fidxs != fun._fidxs || _sig.length != fun._sig.length ) return false; - if( _sig == fun._sig && _ret == fun._ret ) return true; - - // Check to see if we've ever compared this pair of types before; - // if so, then assume the cycle is equal here. - int pid = pid(fun); - if( TypeStruct.CEQUALS.find(pid)!= -1 ) return true; - TypeStruct.CEQUALS.push(pid); - if( !_ret.cycle_eq(fun._ret) ) return false; - for( int i = 0; i < _sig.length; i++ ) - if( !_sig[i].cycle_eq(fun._sig[i]) ) - return false; - return true; - } - - @Override public String str() { return "{"+printFIDX()+"}"; } - - SB _print(SB sb, BitSet visit, boolean html ) { - sb.p(x()).p("{ "); - if( _sig!=null ) - for( Type t : _sig ) - sb.p(t.str()).p(" "); // Short form in signature - _ret.print(sb.p(html ? "→ " : "-> "),visit,html).p(" #"); - // Print fidxs - return sb.p(printFIDX()).p("}").p(q()); - } - String printFIDX() { - String tilde = isHigh() ? "~" : ""; - long fidxs = isHigh() ? ~_fidxs : _fidxs; - String fidx = fidxs==0 ? "" - : Long.bitCount(fidxs) == 1 ? ""+Long.numberOfTrailingZeros(fidxs) - : fidxs == -1 ? "ALL" - : "b"+Long.toBinaryString(fidxs); // Just some function bits - return tilde+fidx; - } - - // Usage: for( long fidxs=fidxs(); fidxs!=0; fidxs=nextFIDX(fidxs) { int fidxs = Long.numberOfTrailingZeros(fidxs); ... } - public static long nextFIDX(long fidxs) { return fidxs & (fidxs-1); } - -} diff --git a/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/src/main/java/com/seaofnodes/simple/type/TypeInteger.java deleted file mode 100644 index 99f237a9..00000000 --- a/src/main/java/com/seaofnodes/simple/type/TypeInteger.java +++ /dev/null @@ -1,153 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import java.util.ArrayList; - -/** - * Integer Type - */ -public class TypeInteger extends Type { - /** - * Describes an integer *range* - everything from min to max; both min and - * max are inclusive. If min==max, this is a constant. - * <br> - * If min <= max, this is a below center (towards bottom). - * If min > max, this is an above center (towards top). - */ - public long _min, _max; - - private static final Ary<TypeInteger> FREE = new Ary<>(TypeInteger.class); - private TypeInteger(long min, long max) { super(TINT); init(min,max); } - private TypeInteger init(long min, long max) { _min = min; _max = max; return this; } - public static TypeInteger malloc(long lo, long hi) { return FREE.isEmpty() ? new TypeInteger(lo,hi) : FREE.pop().init(lo,hi); } - public static TypeInteger make(long lo, long hi) { - TypeInteger i = malloc(lo,hi); - TypeInteger t2 = i.intern(); - return t2==i ? i : t2.free(i); - } - @Override TypeInteger free(Type t) { - TypeInteger i = (TypeInteger)t; - i._min = i._max = 0; - i._hash = 0; - i._dual = null; - FREE.push(i); - return this; - } - - public static TypeInteger constant(long con) { return make(con, con); } - - public final static TypeInteger ZERO= make(0,0); - public final static TypeInteger FALSE=ZERO; - public final static TypeInteger TRUE= make(1,1); - public final static TypeInteger TWO = make(2,2); // Shows up in some Simple tests as the starting argument - - public final static TypeInteger I1 = make(-1,0); - public final static TypeInteger I8 = make(-128,127); - public final static TypeInteger I16 = make(-32768,32767); - public final static TypeInteger I32 = make(-1L<<31,(1L<<31)-1); - public final static TypeInteger BOT = make(Long.MIN_VALUE,Long.MAX_VALUE); - public final static TypeInteger TOP = (TypeInteger)BOT.dual(); - - public final static TypeInteger U1 = make(0,1); - public final static TypeInteger BOOL= U1; - public final static TypeInteger U8 = make(0,255); - public final static TypeInteger U16 = make(0,65535); - public final static TypeInteger U32 = make(0,(1L<<32)-1); - - public static void gather(ArrayList<Type> ts) { ts.add(I32); ts.add(BOT); ts.add(U1); ts.add(I1); ts.add(U8); } - - @Override public String str() { - if( _isConstant() ) return ""+_min; - long lo = _min, hi = _max; - String x = ""; - if( hi < lo ) { - lo = _max; hi = _min; - x = "~"; - } - return x+_str(lo,hi); - } - private static String _str(long lo, long hi) { - if( lo==Long.MIN_VALUE && hi==Long.MAX_VALUE ) return "i64"; - if( lo== 0 && hi== 1 ) return "bool"; - if( lo== -1 && hi== 0 ) return "i1"; - if( lo== -128 && hi== 127 ) return "i8"; - if( lo== -32768 && hi== 32767 ) return "i16"; - if( lo== -1L<<31 && hi==(1L<<31)-1 ) return "i32"; - if( lo== 0 && hi== 255 ) return "u8"; - if( lo== 0 && hi== 65535 ) return "u16"; - if( lo== 0 && hi==(1L<<32)-1 ) return "u32"; - return "["+lo+"-"+hi+"]"; - } - - @Override public boolean isHigh () { return _min > _max; } - - @Override public int log_size() { - if( isHigh() ) return 0; // High types are dead, and should never hit code emission. - if( this==I8 || this==U8 || this==BOOL ) return 0; // 1<<0 == 1 bytes - if( this==I16 || this==U16 ) return 1; // 1<<1 == 2 bytes - if( this==I32 || this==U32 ) return 2; // 1<<2 == 4 bytes - if( this==BOT ) return 3; // 1<<3 == 8 bytes - if( isConstant() ) { // just const here - if (-0xFF <= _min && _min <= 0xFF) return 0; - else if (-0xFFFF <= _min && _min <= 0xFFFF) return 1; - else if (-0xFFFFFFFFL <= _min && _min <= 0xFFFFFFFFL) return 2; - else return 3; - } - throw Utils.TODO(); - } - - public long value() { assert isConstant(); return _min; } - - // AND-mask of forced zeros. e.g. unsigned types will return their mask; - // u8 will return 0xFF. But also a range of 16-18 (0x10-0x12) will return - // 0x13 - no value in the range {16,17,18} will allow bit 0x04 to be set. - public long mask() { - if( isHigh() ) return 0; - if( isConstant() ) return _min; - // Those bit positions which differ min to max - long x = _min ^ _max; - // Highest '1' bit in the differ set. Since the range is from min to - // max, all values below here are possible. - long ff1 = Long.highestOneBit(x); - // Make a all-1's mask from ff1, and set over the same bits (either min - // or max is ok). - long mask = _min | (ff1-1) | ff1; - return mask; - } - - @Override - public Type xmeet(Type other) { - if( other instanceof TypeConAry ary ) return ary.imeet(this); - // Invariant from caller: 'this' != 'other' and same class (TypeInteger) - TypeInteger i = (TypeInteger)other; // Contract - return make(Math.min(_min,i._min), Math.max(_max,i._max)); - } - - @Override TypeInteger xdual() { - return _min==_max ? this : malloc(_max,_min); - } - - @Override public TypeInteger nonZero() { - if( isHigh() ) return this; - if( this==ZERO ) return null; // No sane answer - if( _min==0 ) return make(1,Math.max(_max,1)); // specifically good on BOOL - if( _max==0 ) return make(_min,-1); - return this; - } - @Override public Type makeZero() { return ZERO; } - @Override boolean _isConstant() { return _min == _max; } - @Override boolean _isGLB(boolean mem) { return _glb(mem)==this; } - @Override Type _glb(boolean mem) { - if( !mem ) return BOT; - // GLB is not well-defined in memory; depends on the size of the memory field - if( _isConstant() ) return BOT; - return isHigh() ? dual() : this; - } - - @Override int hash() { return Utils.fold(_min) * Utils.fold(_max); } - @Override public boolean eq( Type t ) { - return t instanceof TypeInteger i && _min==i._min && _max==i._max; - } -} diff --git a/src/main/java/com/seaofnodes/simple/type/TypeMem.java b/src/main/java/com/seaofnodes/simple/type/TypeMem.java deleted file mode 100644 index 99b19c75..00000000 --- a/src/main/java/com/seaofnodes/simple/type/TypeMem.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import java.util.ArrayList; -import java.util.BitSet; - - -/** - * Represents a slice of memory corresponding to a set of aliases - */ -public class TypeMem extends Type { - - // Which slice of memory? - // 1 and TOP means no slice. - // 1 and BOT means all memory. - // N means alias slice#N. - public int _alias; - public Type _t; // Memory contents, some scalar type - - private static final Ary<TypeMem> FREE = new Ary<>(TypeMem.class); - private TypeMem(int alias, Type t) { super(TMEM); init(alias,t); } - private TypeMem init(int alias, Type t) { - assert alias>1 || (t==Type.TOP || t==Type.BOTTOM); - _alias = alias; - _t = t; - return this; - } - @Override TypeMem free(Type t) { - TypeMem mem = (TypeMem)t; - mem._alias= -99; - mem._dual = null; - mem._hash = 0; - FREE.push(mem); - return this; - } - @Override boolean isFree() { return _alias == -99; } - - public static TypeMem malloc(int alias, Type t) { - return FREE.isEmpty() ? new TypeMem(alias,t) : FREE.pop().init(alias,t); - } - public static TypeMem make(int alias, Type t) { - TypeMem f = malloc(alias,t); - TypeMem f2 = f.intern(); - if( f2==f ) return f; - return VISIT.isEmpty() ? f2.free(f) : f2.delayFree(f); - } - - public static final TypeMem TOP = make(1, Type.TOP ); - public static final TypeMem BOT = make(1, Type.BOTTOM); - - public static void gather(ArrayList<Type> ts) { ts.add(make(2,Type.NIL)); ts.add(make(2,TypeInteger.ZERO)); ts.add(BOT); } - - @Override - TypeMem xmeet(Type t) { - TypeMem that = (TypeMem) t; // Invariant: TypeMem and unequal - if( this==TOP ) return that; - if( that==TOP ) return this; - if( this==BOT ) return BOT; - if( that==BOT ) return BOT; - int alias = _alias==that._alias ? _alias : 1; - Type mt = _t.meet(that._t); - return make(alias,mt); - } - - @Override - Type xdual() { return _t._dual==_t ? this : malloc(_alias,_t.dual()); } - - @Override TypeMem rdual() { - if( _dual!=null ) return dual(); - assert !_terned; - TypeMem d = malloc(_alias,null); - (_dual = d)._dual = this; // Cross link duals - d._t = _t._terned ? _t.dual() : _t.rdual(); - return d; - } - - @Override public boolean isHigh() { return _t.isHigh(); } - @Override public int log_size() { throw Utils.TODO(); } - @Override boolean _isFinal() { return _t._isFinal(); } - @Override boolean _isGLB(boolean mem) { return _t._isGLB(true); } - @Override public Type _glb(boolean mem) { return make(_alias,_t._glb(true)); } - - @Override int hash() { return 9876543 + _alias + _t.hashCode(); } - - @Override boolean eq(Type t) { - TypeMem that = (TypeMem) t; // Invariant - return _alias == that._alias && _t == that._t; - } - - @Override boolean cycle_eq(Type t) { - TypeMem that = (TypeMem) t; // Invariant - return _alias == that._alias && _t.cycle_eq(that._t); - } - - @Override int nkids() { return 1; } - @Override Type at( int idx ) { return _t; } - @Override void set( int idx, Type t ) { _t = t; } - - @Override public SB _print(SB sb, BitSet visit, boolean html) { - sb.p("#"); - if( _alias==0 ) return sb.p(_t._type==TTOP ? "TOP" : "BOT"); - return _t.print(sb.p(_alias).p(":"),visit,html); - } - - @Override public String str() { return toString(); } -} diff --git a/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java deleted file mode 100644 index 6041155d..00000000 --- a/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java +++ /dev/null @@ -1,147 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import java.util.*; - -/** - * Represents a Pointer to memory. - * - * Null is generic pointer to non-existent memory. - * *void is a non-Null pointer to all possible refs, both structs and arrays. - * Pointers can be to specific struct and array types, or a union with Null. - * The distinguished *$BOT ptr represents union of *void and Null. - * The distinguished *$TOP ptr represents the dual of *$BOT. - */ -public class TypeMemPtr extends TypeNil { - // A TypeMemPtr is pair (obj,nil) - // where obj is a TypeStruct, possibly TypeStruct.BOT/TOP - // where nil is an explicit null is allowed or not - - // Examples: - // (Person,false) - a not-nil Person - // (Person,true ) - a Person or a nil - // (BOT ,false) - a not-nil void* (unspecified struct) - // (TOP ,true ) - a nil - - public TypeStruct _obj; - public boolean _one; // Singleton instance - - private static final Ary<TypeMemPtr> FREE = new Ary<>(TypeMemPtr.class); - private TypeMemPtr(byte nil, TypeStruct obj, boolean one) { super(TMEMPTR,nil); init(nil,obj,one); } - private TypeMemPtr init(byte nil, TypeStruct obj, boolean one) { _nil=nil; _obj=obj; _one=one; return this; } - // Return a filled-in TypeMemPtr; either from free list or alloc new. - private static TypeMemPtr malloc(byte nil, TypeStruct obj, boolean one) { - return FREE.isEmpty() ? new TypeMemPtr(nil,obj,one) : FREE.pop().init(nil,obj,one); - } - - // All fields directly listed - public static TypeMemPtr make(byte nil, TypeStruct obj, boolean one) { - TypeMemPtr tmp = malloc(nil, obj, one); - TypeMemPtr t2 = tmp.intern(); - if( t2==tmp ) return tmp; - return VISIT.isEmpty() ? t2.free(tmp) : t2.delayFree(tmp); - } - @Override TypeMemPtr free(Type t) { - TypeMemPtr tmp = (TypeMemPtr)t; - tmp._nil = -99; - tmp._obj = null; - tmp._dual = null; - tmp._hash = 0; - FREE.push(tmp); - return this; - } - @Override boolean isFree() { return _obj==null; } - - static TypeMemPtr make(byte nil, TypeStruct obj) { return make(nil, obj, false); } - public static TypeMemPtr make (TypeStruct obj) { return make((byte)2, obj); } - public static TypeMemPtr makeNullable(TypeStruct obj) { return make((byte)3, obj); } - - public TypeMemPtr makeFrom(TypeStruct obj) { return obj==_obj ? this : make(_nil, obj, _one); } - public TypeMemPtr makeNullable() { return makeFrom((byte)3); } - @Override TypeMemPtr makeFrom(byte nil) { return nil==_nil ? this : make(nil, _obj, _one); } - - // An abstract pointer, pointing to either a Struct or an Array. - // Can also be null or not, so 4 choices {TOP,BOT} x {nil,not} - public static final TypeMemPtr BOT = make((byte)3, TypeStruct.BOT); - public static final TypeMemPtr TOP = BOT.dual(); - public static final TypeMemPtr NOTBOT = make((byte)2,TypeStruct.BOT); - - public static final TypeMemPtr TEST= make((byte)2, TypeStruct.TEST); - public static void gather(ArrayList<Type> ts) { ts.add(NOTBOT); ts.add(BOT); ts.add(TEST); } - - @Override - public TypeNil xmeet(Type t) { - TypeMemPtr that = (TypeMemPtr) t; - assert !isFree() && !that.isFree(); - return make(xmeet0(that), (TypeStruct)_obj.meet(that._obj), _one & that._one); - } - - @Override - TypeMemPtr xdual() { return malloc( dual0(), _obj.dual(), _one); } - - @Override TypeMemPtr rdual() { - if( _dual!=null ) return dual(); - assert !_terned; - TypeMemPtr d = malloc(dual0(), null, _one); - (_dual = d)._dual = this; // Cross link duals - d._obj = _obj._terned ? _obj.dual() : _obj.rdual(); - return d; - } - - // RHS is NIL; do not deep-dual when crossing the center line - @Override Type meet0() { return _nil==3 ? this : make((byte)3,_obj); } - - @Override boolean _isConstant() { return _one && _obj._isConstant(); } - @Override boolean _isFinal() { return _obj._isFinal(); } - @Override TypeMemPtr _makeRO() { return makeFrom(_obj._makeRO()); } - @Override boolean _isGLB(boolean mem) { return _obj.isGLB2(); } - @Override TypeMemPtr _glb(boolean mem) { return make((byte)3,_obj.glb2()); } - @Override TypeMemPtr _close() { return makeFrom(_obj._close()); } - - - // True if this "isa" t up to named structures - @Override public boolean shallowISA( Type t ) { - if( !(t instanceof TypeMemPtr that) ) return false; - if( this==that ) return true; - if( xmeet0(that)!=that._nil ) return false; - if( _obj==that._obj ) return true; - if( _obj._name.equals(that._obj._name) ) - return true; // Shallow, do not follow matching names, just assume ok - throw Utils.TODO(); // return _obj.shallowISA(that._obj); - } - - // Is forward-reference - @Override public boolean isFRef() { return _obj.isFRef(); } - - @Override public int log_size() { return 3; } // (1<<3)==8-byte pointers - - @Override int hash() { return _obj.hashCode() ^ super.hash() ^ (_one ? 2048 : 0); } - - @Override boolean eq(Type t) { - TypeMemPtr ptr = (TypeMemPtr)t; // Invariant - return super.eq(ptr) && _one == ptr._one && _obj == ptr._obj; - } - @Override boolean cycle_eq(Type t) { - if( t._type != TMEMPTR ) return false; - TypeMemPtr ptr = (TypeMemPtr)t; // Invariant - return super.eq(ptr) && _one == ptr._one && _obj.cycle_eq(ptr._obj); - } - - @Override int nkids() { return 1; } - @Override Type at( int idx ) { return _obj; } - @Override void set( int idx, Type t ) { _obj = (TypeStruct)t; } - - @Override public String str() { - if( this== NOTBOT) return "*void"; - if( this== BOT) return "*void?"; - return x()+"*"+_obj.str()+q(); - } - - @Override SB _print(SB sb, BitSet visit, boolean html ) { - if( this== NOTBOT) return sb.p("*void"); - if( this== BOT) return sb.p("*void?"); - return _obj.print(sb.p(x()).p("*"),visit,html).p(q()); - } -} diff --git a/src/main/java/com/seaofnodes/simple/type/TypeNil.java b/src/main/java/com/seaofnodes/simple/type/TypeNil.java deleted file mode 100644 index 56d73b00..00000000 --- a/src/main/java/com/seaofnodes/simple/type/TypeNil.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import java.util.ArrayList; - -/** - * Nil-able Scalar types - */ -public abstract class TypeNil extends Type { - // 0 = high-subclass choice nil - // 1 = high-subclass no nil - // 2 = low -subclass no nil - // 3 = low -subclass also nil - byte _nil; - - TypeNil(byte t, byte nil ) { super(t); _nil = nil; } - - public static void gather(ArrayList<Type> ts) { } - - abstract TypeNil makeFrom(byte nil); - - byte xmeet0(TypeNil that) { return (byte)Math.max(_nil,that._nil); } - - byte dual0() { return (byte)(3-_nil); } - - // RHS is NIL - abstract Type meet0(); - - // RHS is XNIL - Type meetX() { - return _nil==0 ? XNIL : (_nil<=2 ? TypePtr.NPTR : TypePtr.PTR); - } - - Type nmeet(TypeNil tn) { - // Invariants: both are TypeNil subclasses and unequal classes. - // If this is TypePtr, we went to TypePtr.nmeet and not here. - // If that is TypePtr, this is not (invariant); reverse and go again. - if( tn instanceof TypePtr ts ) return ts.nmeet(this); - - // Two mismatched TypeNil, no Scalar. - if( _nil==0 && tn._nil==0 ) return XNIL; - if( _nil<=2 && tn._nil<=2 ) return TypePtr.NPTR; - return TypePtr.PTR; - } - - @Override public boolean isHigh () { return _nil <= 1; } - @Override public boolean isHighOrConst() { return isHigh() || isConstant(); } - - public boolean notNull() { return _nil==1 || _nil==2; } - public boolean nullable() { return _nil==3; } - - @Override boolean _isConstant() { return false; } - @Override Type _glb(boolean mem) { return Type.NIL; } - - final String q() { return _nil == 1 || _nil == 2 ? "" : "?"; } - final String x() { return isHigh() ? "~" : ""; } - - int hash() { return _nil<<17; } - - @Override boolean eq(Type ptr) { return ptr instanceof TypeNil nil && _nil ==nil._nil; } -} diff --git a/src/main/java/com/seaofnodes/simple/type/TypePtr.java b/src/main/java/com/seaofnodes/simple/type/TypePtr.java deleted file mode 100644 index 75478119..00000000 --- a/src/main/java/com/seaofnodes/simple/type/TypePtr.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import java.util.ArrayList; -import java.util.BitSet; - -/** - * Represents a Scalar; a single register-sized value. - */ -public class TypePtr extends TypeNil { - private static final String[] STRS = new String[]{"~ptr","~nptr","nptr","ptr"}; - - private TypePtr(byte nil) { super(TPTR,nil); } - - // An abstract pointer, pointing to either a Struct or an Array. - // Can also be null or not, so 4 choices {TOP,BOT} x {nil,not} - public static TypePtr XPTR = new TypePtr((byte)0).intern(); - public static TypePtr XNPTR= new TypePtr((byte)1).intern(); - public static TypePtr NPTR = (TypePtr)XNPTR.dual(); - public static TypePtr PTR = (TypePtr)XPTR.dual(); - private static final TypePtr[] PTRS = new TypePtr[]{XPTR,XNPTR,NPTR,PTR}; - - public static void gather(ArrayList<Type> ts) { ts.add(PTR); ts.add(NPTR); } - - TypeNil makeFrom(byte nil) { throw Utils.TODO(); } - - @Override public TypeNil xmeet(Type t) { - TypePtr that = (TypePtr) t; - return PTRS[xmeet0(that)]; - } - - @Override TypePtr xdual() { return new TypePtr((byte)(3-_nil)); } - - // High scalar loses, low scalar wins - @Override TypeNil nmeet(TypeNil tn) { - if( _nil==0 ) return tn; // High scalar loses - if( _nil==1 ) return tn.makeFrom(xmeet0(tn)); // High scalar loses - if( _nil==2 ) return tn._nil==3 ? PTR : NPTR; // Low scalar wins - return PTR; // this - } - - - // RHS is NIL - @Override Type meet0() { return isHigh() ? NIL : PTR; } - // RHS is XNIL - // 0->xscalar, 1->nscalar, 2->nscalar, 3->scalar - @Override Type meetX() { return _nil==0 ? XNIL : (_nil==3 ? PTR : NPTR); } - - boolean _isGLB(boolean mem) { return this==PTR; } - @Override TypePtr _glb(boolean mem) { return PTR; } - - @Override public String str() { return STRS[_nil]; } - @Override public SB _print(SB sb, BitSet visit, boolean html) { return sb.p(str()); } -} diff --git a/src/main/java/com/seaofnodes/simple/type/TypeRPC.java b/src/main/java/com/seaofnodes/simple/type/TypeRPC.java deleted file mode 100644 index bd5e2923..00000000 --- a/src/main/java/com/seaofnodes/simple/type/TypeRPC.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import java.util.ArrayList; -import java.util.BitSet; -import java.util.HashSet; - -/** - * Return Program Control or Return PC or RPC - */ -public class TypeRPC extends Type { - - // A set of CallEndNode IDs (or StopNode); commonly just one. - // Basically a sparse bit set - final HashSet<Integer> _rpcs; - - // If true, invert the meaning of the bits - final boolean _any; - - private TypeRPC(boolean any, HashSet<Integer> rpcs) { - super(TRPC); - _any = any; - _rpcs = rpcs; - } - private static TypeRPC make(boolean any, HashSet<Integer> rpcs) { - return new TypeRPC(any,rpcs).intern(); - } - - public static TypeRPC constant(int cend) { - HashSet<Integer> rpcs = new HashSet<>(); - rpcs.add(cend); - return make(false,rpcs); - } - - public static final TypeRPC BOT = make(true,new HashSet<>()); - private static final TypeRPC TEST2 = constant(2); - private static final TypeRPC TEST3 = constant(2); - - public static void gather(ArrayList<Type> ts) { ts.add(BOT); ts.add(TEST2); ts.add(TEST3); } - - @Override public String str() { - if( _rpcs.isEmpty() ) - return _any ? "$[ALL]" : "$[]"; - if( _rpcs.size()==1 ) - for( Integer rpc : _rpcs ) - return _any ? "$[-"+rpc+"]" : "$["+rpc+"]"; - return "$["+(_any ? "-" : "")+_rpcs+"]"; - } - - @Override - public TypeRPC xmeet(Type other) { - TypeRPC rpc = (TypeRPC)other; - // If the two sets are equal, the _any must be unequal (invariant), - // so they cancel and all bits are set. - if( _rpcs.equals(rpc._rpcs) ) - return BOT; - // Classic union of bit sets (which might be infinite). - HashSet<Integer> lhs = _rpcs, rhs = rpc._rpcs; - // Smaller on left - if( lhs.size() > rhs.size() ) { lhs = rpc._rpcs; rhs = _rpcs; } - - HashSet<Integer> rpcs = new HashSet<>(); - boolean any = true; - // If both sets are infinite, intersect. - if( _any && rpc._any ) { - for( Integer i : lhs ) if( rhs.contains(i) ) rpcs.add(i); - - } else if( !_any && !rpc._any ) { - // if neither set is infinite, union. - rpcs.addAll( lhs ); - rpcs.addAll( rhs ); - any = false; - } else { - // If one is infinite, subtract the other from it. - HashSet<Integer> sub = _any ? rpc._rpcs : _rpcs; - HashSet<Integer> inf = _any ? _rpcs : rpc._rpcs; - for( Integer i : inf ) - if( inf.contains(i) && !sub.contains(i) ) - rpcs.add(i); - } - return make(any,rpcs); - } - - @Override - Type xdual() { return new TypeRPC(!_any,_rpcs); } - - @Override boolean _isConstant() { return !_any && _rpcs.size()==1; } - @Override boolean _isGLB(boolean mem) { return true; } - - @Override - int hash() { return _rpcs.hashCode() ^ (_any ? -1 : 0) ; } - @Override - public boolean eq( Type t ) { - TypeRPC rpc = (TypeRPC)t; // Contract - return _any==rpc._any && _rpcs.equals(rpc._rpcs); - } - -} diff --git a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/src/main/java/com/seaofnodes/simple/type/TypeStruct.java deleted file mode 100644 index 73fecdab..00000000 --- a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java +++ /dev/null @@ -1,480 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.util.*; -import java.util.*; - -/** - * Represents a struct type. - */ -public class TypeStruct extends Type { - - // A Struct has a name and a set of fields; the fields themselves have - // names, types and aliases. The name has no semantic meaning, but is - // useful for debugging. - - // During parsing a mid-declaration struct is flagged as a "open". It is - // treated as having infinite fields with correct name and type BOTTOM. - - public String _name; // Struct name - public boolean _open; // infinite fields are all true:BOTTOM, false:TOP - public Field[] _fields; - - private TypeStruct(String name, boolean open, Field[] fields) { - super(TSTRUCT); - _name = name; - _open = open; - _fields = fields; - } - - private static final Ary<TypeStruct> FREE = new Ary<>(TypeStruct.class); - // Return a filled-in TypeStruct; either from free list or alloc new. - private static TypeStruct malloc(String name, boolean open, Field[] fields) { - if( FREE.isEmpty() ) return new TypeStruct(name,open,fields); - TypeStruct ts = FREE.pop(); - assert ts.isFree(); - ts._name = name; - ts._open = open; - ts._fields = fields; - return ts; - } - // Free ts; return this. - @Override TypeStruct free(Type t) { - TypeStruct ts = (TypeStruct)t; - assert !ts.isFree() && !ts._terned; - ts._fields = null; - ts._offs = null; - ts._dual = null; - ts._hash = 0; - FREE.push(ts); - return this; - } - @Override boolean isFree() { return _fields==null; } - - - // All fields directly listed - public static TypeStruct make(String name, boolean open, Field... fields) { - TypeStruct ts = malloc(name, open, fields); - TypeStruct t2 = ts.intern(); - if( t2==ts ) return ts; - return VISIT.isEmpty() ? t2.free(ts) : ts.delayFree(ts); - } - // New open struct with no fields - public static TypeStruct open( String name ) { return make(name,true); } - - // Array - public static TypeStruct makeAry(String name, TypeInteger len, int lenAlias, Type body, int bodyAlias, boolean efinal) { - return make(name,false, - Field.make("#" ,len , lenAlias,true ,false), - Field.make("[]",body,bodyAlias,efinal,false)); - } - - public TypeStruct add( Field f ) { - assert _open && find(f._fname)==-1; // No double field names - Field[] flds = Arrays.copyOf(_fields,_fields.length+1); - flds[_fields.length] = f; - return make(_name,true,flds); - } - public TypeStruct replace( Field f ) { - assert !_open; - Field[] flds = Arrays.copyOf(_fields,_fields.length); - flds[find(f._fname)] = f; - return make(_name,false,flds); - } - - - public final TypeStruct close() { - return (TypeStruct)recurOpen()._close().recurClose(); - } - @Override TypeStruct _close() { - TypeStruct ts = (TypeStruct)VISIT.get(_name); - if( ts!=null ) return ts; - ts = recurPre(_name,false); - Field[] flds = ts._fields; - - // Now start the recursion - for( int i=0; i<flds.length; i++ ) - flds[i].setType(_fields[i]._t._close()); - - return ts; - } - - static final AryInt CEQUALS = new AryInt(); - - public static final TypeStruct BOT = open("$STRUCT"); - public static final TypeStruct TOP = BOT.dual(); - public static final TypeStruct TEST= make("test",false,Field.TEST); - private static final TypeStruct ARY = makeAry("[]i64",TypeInteger.U32,-1,TypeInteger.BOT,-2,false); - private static final TypeStruct STR = makeAry("[]u8" ,TypeInteger.U32,-1,TypeInteger.U8 ,-4,false); - private static final TypeStruct ABC = makeAry("[]u8",TypeInteger.constant(3),-1,TypeConAryB.ABC,-4,true); - - // A pair of self-cyclic types - private static final TypeStruct SINT0 = open("%SINT"); - private static final TypeStruct SFLT0 = open("%SFLT"); - private static final TypeStruct SINT1 = SINT0.add(Field.make("a", TypeInteger.U32, -1, false, false)).add(Field.make("s2",TypeMemPtr.make((byte)2,SFLT0),-2, false, false)); - private static final TypeStruct SFLT1 = SFLT0.add(Field.make("b", TypeFloat .F32, -3, false, false)).add(Field.make("s1",TypeMemPtr.make((byte)2,SINT1),-4, false, false)); - public static final TypeStruct SFLT2 = SFLT1.close(); - - - public static void gather(ArrayList<Type> ts) { - ts.add(BOT); - ts.add(TEST); - ts.add(ARY); - ts.add(STR); - ts.add(ABC); - ts.add(SINT0); - ts.add(SFLT0); - ts.add(SINT1); - ts.add(SFLT1); - ts.add(SFLT2); - ts.add(((TypeMemPtr)(SFLT2.field("s1")._t))._obj); - // Break cyclic init: built a struct - Field fcalloc = Field.make("calloc",TypeFunPtr.CALLOC,-2,true,true); - TypeStruct scalloc = make("calloc",false,fcalloc); - ts.add(scalloc); - - } - - // Find field index by name - public int find(String fname) { - for( int i=0; i<_fields.length; i++ ) - if( _fields[i]._fname.equals(fname) ) - return i; - return -1; - } - public Field field(String fname) { - int idx = find(fname); - return idx== -1 ? null : _fields[idx]; - } - - // Find field index by alias - public int findAlias( int alias ) { - for( int i=0; i<_fields.length; i++ ) - if( _fields[i]._alias==alias ) - return i; - return -1; - } - - @Override - TypeStruct xmeet(Type t) { - TypeStruct that = (TypeStruct) t; - assert !isFree() && !that.isFree(); - if( this==TOP ) return that; - if( that==TOP ) return this; - if( this==BOT ) return BOT; - if( that==BOT ) return BOT; - - // Within the same compilation unit, struct names are unique. If the - // names differ, its different structs. Across many compilation units, - // structs with the same name but different field layouts can be - // interned... which begs the question: - // "What is the meet of structs from two different compilation units?" - // And the answer is: "don't ask". - if( !_name.equals(that._name) ) - return BOT; // It's a struct; that's about all we know - - // if equal, no matters. - // if short is BOT, chop ; recurPre on short. - // if short is TOP, copy long; recurPre on long. - TypeStruct min = _fields.length < that._fields.length ? this : that; - if( _fields.length != that._fields.length && (min._open ^ min==this) ) - return that.xmeet(this); - - // Check all other fields are sanely similar; same struct type but - // different field contents (e.g. field "age" is either 'int' or '18'). - int len = Math.min(_fields.length,that._fields.length); - for( int i=0; i<len; i++ ) { - Field f0 = _fields[i], f1 = that._fields[i]; - if( !f0._fname.equals(f1._fname) || f0._alias != f1._alias || f0._one != f1._one ) - return BOT; - } - - // See if we get a mid-recursion hit; breaks cycles - Integer pid = pid(that); - TypeStruct ts = (TypeStruct)VISIT.get(pid); - if( ts!=null ) return ts; - // If not mid-recursion, we are now; flag recursion. - boolean notRecur = VISIT.isEmpty(); - if( notRecur ) recurOpen(); - - // Setup and install the type, prior to recursing, so we can find our - // recursive self again. - ts = recurPre(pid,_open | that._open); - - // Recurse all common fields - Field[] flds = ts._fields; - for( int i=0; i<len; i++ ) { - Field f0 = _fields[i], f1 = that._fields[i]; - flds[i].setType(f0._t.meet(f1._t )); - flds[i]._final = f0._final | f1._final; - } - - // Handle extra fields via `_open` - for( int i=len; i<_fields.length; i++ ) - flds[i].setType(_fields[i]._t ); - - // Cleanup post-recursion; intern/install at cycle end - if( notRecur ) ts = (TypeStruct)ts.recurClose(); - return ts; - } - - @Override - TypeStruct xdual() { - if( _name=="$STRUCT" ) - return malloc("$STRUCT",false,new Field[0]); - Field[] flds = new Field[_fields.length]; - for( int i=0; i<_fields.length; i++ ) - flds[i] = _fields[i].dual(); - return malloc(_name,!_open,flds); - } - - // Recursive dual - @Override TypeStruct rdual() { - if( _dual!=null ) return dual(); - assert !_terned; - Field[] flds = new Field[_fields.length]; - TypeStruct d = malloc(_name,!_open,flds); - (_dual = d)._dual = this; // Cross link duals - for( int i=0; i<_fields.length; i++ ) - flds[i] = _fields[i]._terned ? _fields[i].dual() : _fields[i].rdual(); - return d; - } - - // Is forward-reference - @Override public boolean isFRef() { return _open; } - - @Override boolean _isConstant() { - if( VISIT.containsKey(_uid) ) return true; // Cycles assume constant - VISIT.put(_uid,this); - // Check all fields for being constant - for( Field field : _fields ) - if( !field._isConstant() ) - return false; - return true; - } - - // All fields are final - @Override boolean _isFinal() { - if( _open ) return false; // May have more more non-final fields - if( VISIT.containsKey(_uid) ) // Test: been here before? - return true; // Cycles assume final - VISIT.put(_uid,this); // Set: dont do this again - for( Field fld : _fields ) - if( !fld._isFinal() ) - return false; - return true; - } - - private TypeStruct recurPre(Object key, boolean open) { - // Make a clone of original; suitable for hashing so can build - // e.g. TMPs to clone as part of cycles - but will fail the 'eq' check - // until the entire cycle is built. - Field[] flds = new Field[_fields.length]; - for( int i=0; i<flds.length; i++ ) - flds[i] = _fields[i].malloc(); // Blank copy, but can be hashed - TypeStruct ts = malloc(_name, open, flds ); - VISIT.put(key,ts); - return ts; - } - - // Make a read-only version - @Override TypeStruct _makeRO() { - // Check for already visited - TypeStruct ts = (TypeStruct)VISIT.get(_name); - if( ts!=null ) return ts; // Already visited - ts = recurPre(_name,_open); // Make a new type with blank fields - Field[] flds = ts._fields; - for( Field fld : flds ) fld._final = true; - - // Now start the recursion - for( int i=0; i<flds.length; i++ ) - flds[i].setType(_fields[i]._t._makeRO()); - - return ts; - } - - - // All fields are at GLB already - boolean isGLB2() { - if( VISIT.containsKey(_uid) ) return true; // Cycles assume GLB - VISIT.put(_uid,this); - for( Field fld : _fields ) - if( !fld.isGLB2() ) - return false; - return true; - } - - // Keeps the same struct, but lower-bounds all fields. - public TypeStruct glb2() { - TypeStruct ts = (TypeStruct)VISIT.get(_name); - if( ts!=null ) return ts; - ts = recurPre(_name,_open); - Field[] flds = ts._fields; - for( int i=0; i<flds.length; i++ ) - flds[i]._final = true ; - - // Now start the recursion - for( int i=0; i<flds.length; i++ ) - flds[i].setType(_fields[i]._t._glb(true)); - - return ts; - } - - // log_size for a struct is not defined, unless its exactly some power of - // 2. *Total size* is well-defined, and is available in the offsets. - @Override public int log_size() { throw Utils.TODO(); } - @Override public int size() { return offset(_fields.length); } - @Override public int alignment() { - int align = 0; - for( Field f : _fields ) - align = Math.max(align, f._t.alignment()); - return align; - } - - // If false, always false. - // If true , maybe true, need to check recursive fields. - private boolean static_eq( TypeStruct ts ) { - return _name.equals(ts._name) && _open==ts._open && _fields.length==ts._fields.length; - } - - @Override boolean eq(Type t) { - // Recursive; so use cyclic equals - if( !VISIT.isEmpty() ) { - assert CEQUALS.isEmpty(); - boolean rez = cycle_eq(t); - CEQUALS.clear(); - return rez; - } - // Normal equals - TypeStruct ts = (TypeStruct)t; // Invariant - if( !static_eq(ts) ) return false; - if( _fields==ts._fields ) return true; - for( int i = 0; i < _fields.length; i++ ) - if( !_fields[i].eq(ts._fields[i]) ) - return false; - return true; - } - - @Override boolean cycle_eq(Type t) { - if( this==t ) return true; - TypeStruct ts = (TypeStruct)t; // Invariant - if( !static_eq(ts) ) return false; - if( _fields==ts._fields ) return true; - // Check to see if we've ever compared this pair of types before; - // if so, then assume the cycle is equal here. - int pid = pid(ts); - if( CEQUALS.find(pid)!= -1 ) return true; - CEQUALS.push(pid); - // Recursively check fields - for( int i = 0; i < _fields.length; i++ ) - if( !_fields[i].cycle_eq(ts._fields[i]) ) - return false; - return true; - } - - @Override - int hash() { - long hash = _name.hashCode() ^ (_open ? 4 : 0); - for( Field f : _fields ) - hash = Utils.rot(hash,13) ^ ((long)f._fname.hashCode() * f._alias); - return Utils.fold(hash); - } - - @Override int nkids() { return _fields.length; } - @Override Type at( int idx ) { return _fields[idx]; } - @Override void set( int idx, Type t ) { _fields[idx] = (Field)t; } - - @Override - SB _print(SB sb, BitSet visit, boolean html ) { - if( isFree() ) return sb.p("FREE:").p(_name); - if( isAry() && field("[]")._t instanceof TypeConAry con ) - return sb.p(con.str()); - sb.p(_name); - if( html || isAry() ) - return sb; - sb.p(" {"); - for( Field f : _fields ) - (f._t ==null ? sb.p("---") : f._t.print(sb,visit,html)).p(f._final ? " " : " !").p(f._fname).p("; "); - if( _open ) sb.p("... "); - return sb.p("}"); - } - - @Override public String str() { return (isFree() ? "FREE:":"")+_name; } - - - public boolean isAry() { return _fields.length>=2 && _fields[_fields.length-1]._fname=="[]"; } - - public int aryBase() { - assert isAry(); - if( _offs==null ) _offs = offsets(); - return _offs[1]; - } - public int aryScale() { - assert isAry(); - return _fields[1]._t.log_size(); - } - - - // Field offsets as packed byte offsets - private int[] _offs; // Lazily computed and not part of the type semantics - public int offset(int idx) { - if( _offs==null ) _offs = offsets(); - return _offs[idx]; - } - private int[] offsets() { // Field byte offsets - assert CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal(); - // Compute a layout for a collection of fields - assert _fields != null; // No forward refs - - // Compute a layout - _offs = new int[_fields.length+1]; - if( isAry() ) { // Arrays used fixed u32 len, padding to elems, var length - assert _fields[1]._fname=="[]"; - // Pad out to the element alignment - int align = _fields[1]._t.alignment(); - _offs[1] = (4 + ((1<<align)-1)) & -(1<<align); - if( _fields[1]._t instanceof TypeConAry ary ) // Array length is well-defined for constants, not defined for runtime arrays - _offs[2] = _offs[1]+ary.len(); - - } else { - int[] cnts = new int[5]; // Count of fields at log field size - int flen = _fields.length; - for( int i=0; i<flen; i++ ) - if( !_fields[i]._one ) - cnts[_fields[i]._t.log_size()]++; // Log size is 0(byte), 1(i16/u16), 2(i32/f32), 3(i64/dbl) - int off = 0, idx = 0; // Base common struct fields go here, e.g. Mark/Klass - // Compute offsets to the start of each power-of-2 aligned fields. - int[] offs = new int[4]; - for( int i=3; i>=0; i-- ) { - offs[i] = off; - off += cnts[i]<<i; - } - // Assign offsets to all fields. - // Really a hidden radix sort. - for( int i=0; i<flen; i++ ) { - if( _fields[i]._one ) continue; - int log = _fields[i]._t.log_size(); - _offs[idx++] = offs[log]; // Field offset - offs[log] += 1<<log; // Next field offset at same alignment - cnts[log]--; // Count down, should be all zero at end - } - _offs[flen] = off; // Max struct size, no trailing padding - } - return _offs; - } - - // Field order with increasing offset - public int[] layout() { - offset(0); - int len = _offs.length; - int[] is = new int[len]; - for( int i=0; i<len; i++ ) is[i] = i; - // Stoopid hand rolled bubble sort - for( int i=0; i<len; i++ ) - for( int j=i+1; j<len; j++ ) - if( _offs[is[j]] < _offs[is[i]] ) - { int tmp = is[i]; is[i] = is[j]; is[j] = tmp; } - return is; - } - -} diff --git a/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/src/main/java/com/seaofnodes/simple/type/TypeTuple.java deleted file mode 100644 index df9aba31..00000000 --- a/src/main/java/com/seaofnodes/simple/type/TypeTuple.java +++ /dev/null @@ -1,119 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import java.util.*; - -public class TypeTuple extends Type { - - public Type[] _types; - - private static final Ary<TypeTuple> FREE = new Ary<>(TypeTuple.class); - private TypeTuple(Type[] types) { super(TTUPLE); _types = types; } - private TypeTuple init( Type[] types ) { _types = types; return this; } - private static TypeTuple malloc(Type[] types) { - return FREE.isEmpty() ? new TypeTuple(types) : FREE.pop().init(types); - } - // All fields directly listed - public static TypeTuple make(Type... types) { - TypeTuple tt = malloc(types); - TypeTuple t2 = tt.intern(); - return t2==tt ? tt : t2.free(tt); - } - @Override TypeTuple free(Type t) { - TypeTuple tt = (TypeTuple)t; - tt._types = null; - tt._dual = null; - tt._hash = 0; - FREE.push(tt); - return this; - } - @Override boolean isFree() { return _types==null; } - - - public static final TypeTuple BOT = malloc(new Type[0]).intern(); - public static final TypeTuple TOP = BOT.dual(); - - public static final TypeTuple TEST = make(TypeInteger.BOT,TypeMemPtr.TEST); - public static final TypeTuple START= make(Type.CONTROL,TypeMem.TOP,TypeInteger.BOT); - public static final TypeTuple RET = make(Type.CONTROL,TypeMem.BOT,Type.BOTTOM); - - public static final TypeTuple IF_BOTH = make(Type. CONTROL,Type. CONTROL); - public static final TypeTuple IF_NEITHER = make(Type.XCONTROL,Type.XCONTROL); - public static final TypeTuple IF_TRUE = make(Type. CONTROL,Type.XCONTROL); - public static final TypeTuple IF_FALSE = make(Type.XCONTROL,Type. CONTROL); - - public static void gather(ArrayList<Type> ts) { ts.add(BOT); ts.add(TEST); ts.add(START); ts.add(IF_TRUE); } - - @Override - Type xmeet(Type other) { - TypeTuple that = (TypeTuple)other; // contract from xmeet - if( this==TOP ) return that; - if( that==TOP ) return this; - if( _types.length != that._types.length ) - return BOT; - return make(TypeFunPtr.xmeet(_types,that._types)); - } - - @Override TypeTuple xdual() { - if( _types.length==0 ) return malloc(null); - return malloc(TypeFunPtr.xdual(_types)); - } - - @Override boolean _isConstant() { - for( Type t : _types ) - if( !t._isConstant() ) - return false; - return true; - } - @Override boolean _isFinal() { throw Utils.TODO(); } - - @Override TypeMemPtr _makeRO() { throw Utils.TODO(); } - - @Override public int log_size() { throw Utils.TODO(); } - @Override public int alignment() { - assert isConstant(); - int align = 0; - for( Type t : _types ) - align = Math.max(align,t.alignment()); - return align; - } - - public Type ret() { assert _types.length==3; return _types[2]; } - - @Override SB _print(SB sb, BitSet visit, boolean html ) { - if( this==TOP ) return sb.p("[TOP]"); - if( this==BOT ) return sb.p("[BOT]"); - sb.p("[ "); - for( Type t : _types ) - t.print(sb,visit,html).p(", "); - return sb.unchar(2).p("]"); - } - - @Override public String str() { return print(new SB(),new BitSet(),false).toString(); } - - @Override - int hash() { - int sum = 0; - if( _types!=null ) for( Type type : _types ) sum ^= type.hashCode(); - return sum; - } - - @Override - boolean eq( Type t ) { - TypeTuple that = (TypeTuple)t; // Contract - if( _types==null && that._types==null ) return true; - if( _types==null || that._types==null ) return false; - if( _types.length != that._types.length ) return false; - for( int i=0; i<_types.length; i++ ) - if( _types[i]!=that._types[i] ) - return false; - return true; - } - - @Override int nkids() { return _types.length; } - @Override Type at( int idx ) { return _types[idx]; } - @Override void set( int idx, Type t ) { _types[idx] = t; } - -} diff --git a/src/main/java/com/seaofnodes/simple/util/Ary.java b/src/main/java/com/seaofnodes/simple/util/Ary.java deleted file mode 100644 index d67801c2..00000000 --- a/src/main/java/com/seaofnodes/simple/util/Ary.java +++ /dev/null @@ -1,229 +0,0 @@ -package com.seaofnodes.simple.util; - -import java.lang.StringBuilder; -import java.lang.reflect.Array; -import java.util.*; - -// ArrayList with saner syntax -public class Ary<E> extends AbstractList<E> implements List<E> { - public E[] _es; - public int _len; - - public Ary(E[] es) { this(es,es.length); } - public Ary(E[] es, int len) { if( es.length==0 ) es=Arrays.copyOf(es,1); _es=es; _len=len; } - @SuppressWarnings("unchecked") - public Ary(Class<E> clazz) { this((E[]) Array.newInstance(clazz, 1),0); } - - /** @return list is empty */ - public boolean isEmpty() { return _len==0; } - /** @return active list length */ - public int len() { return _len; } - public int size() { return len(); } - - /** @param i element index - * @return element being returned; throws if OOB - * @exception AIOOBE if !(0 <= i < _len) - */ - public E get( int i ) { return at(i); } - public E at( int i ) { - range_check(i); - return _es[i]; - } - public E atX( int i ) { - if( i >= _len ) return null; - return _es[i]; - } - - /** @return last element */ - public E last( ) { return at(_len-1); } - public E last(int x ) { return at(_len-1+x); } - - /** Add element in amortized constant time - * @param e Element to add at end of list - **/ - public E push( E e ) { - if( _len >= _es.length ) _es = Arrays.copyOf(_es,Math.max(1,_es.length<<1)); - return (_es[_len++] = e); - } - /** @return remove and return last element */ - public E pop( ) { - return _len==0 ? null : _es[--_len]; - } - @Override public E removeLast() { return pop(); } - - - /** Add element in amortized constant time - * @param e Element to add at end of list - * @return true */ - public boolean add( E e ) { - if( _len >= _es.length ) _es = Arrays.copyOf(_es,Math.max(1,_es.length<<1)); - _es[_len++] = e; - return true; - } - - /** Fast, constant-time, element removal. Does not preserve order - * @param i element to be removed - * @return element removed */ - public E del( int i ) { - range_check(i); - E tmp = _es[i]; - _es[i]=_es[--_len]; - return tmp; - } - - /** Slow, linear-time, element removal. Preserves order. - * @param i element to be removed - * @return element removed */ - @Override public E remove( int i ) { - range_check(i); - E e = _es[i]; - System.arraycopy(_es,i+1,_es,i,(--_len)-i); - return e; - } - - /** Slow, linear-time, element insertion. Preserves order. - * @param i where to insert - * @return element inserted */ - public E insert( E e, int i ) { - setLen(_len+1); - System.arraycopy(_es,i,_es,i+1,_len-i); - _es[i] = e; - return e; - } - - - /** Remove all elements */ - public void clear( ) { _len=0; } - - /** Set existing element - * @param i element to set - * @param e value to set - * @return old value - * @exception AIOOBE if !(0 <= i < _len) - */ - public E set( int i, E e ) { - range_check(i); - E old = _es[i]; - _es[i] = e; - return old; - } - - /** Set existing element - * @param i element to set - * @param e value to set - * @return old value - * @exception AIOOBE if !(0 <= i < _len) - */ - public E setX( int i, E e ) { - if( i >= _len ) { - while( i >= _es.length ) _es = Arrays.copyOf( _es, _es.length << 1 ); - _len = i+1; - } - E old = _es[i]; - _es[i] = e; - return old; - } - - public Ary<E> setLen( int len ) { - if( len > _len ) - while( len>= _es.length ) _es = Arrays.copyOf(_es,_es.length<<1); - _len = len; - while( _es.length > (len<<1) ) // Shrink if hugely too large - _es = Arrays.copyOf(_es,_es.length>>1); - Arrays.fill(_es,len,_es.length,null); - return this; - } - - public E swap( int i, int j ) { - range_check(i); - range_check(j); - E tmp = _es[i]; - if( i==j ) return tmp; - _es[i] = _es[j]; - _es[j] = tmp; - return tmp; - } - - - /** @param c Collection to be added */ - public boolean addAll( Collection<? extends E> c ) { - if( c==null ) return false; - for( E e : c ) add(e); - return true; - } - public Ary<E> addAll( Iterable<? extends E> it ) { for( E e : it ) add(e); return this; } - - /** @param es Array to be added */ - public <F extends E> Ary<E> addAll( F[] es ) { - if( es==null || es.length==0 ) return this; - while( _len+es.length > _es.length ) _es = Arrays.copyOf(_es,_es.length<<1); - System.arraycopy(es,0,_es,_len,es.length); - _len += es.length; - return this; - } - - /** @param c Collection to be added */ - public Ary<E> addAll( Ary<? extends E> c ) { - if( c==null || c._len==0 ) return this; - while( _len+c._len > _es.length ) _es = Arrays.copyOf(_es,_es.length<<1); - System.arraycopy(c._es,0,_es,_len,c._len); - _len += c._len; - return this; - } - - /** @return compact array version */ - public E[] asAry() { return Arrays.copyOf(_es,_len); } - - /** Find the first matching element using ==, or -1 if none. Note that - * most del calls shuffle the list, so the first element might be random. - * @param e Element to find - * @return index of first matching element, or -1 if none */ - public int find( E e ) { - for( int i=0; i<_len; i++ ) if( _es[i]==e ) return i; - return -1; - } - - - /** @return an iterator */ - @Override public Iterator<E> iterator() { return new Iter(); } - private class Iter implements Iterator<E> { - int _i=0; - @Override public boolean hasNext() { return _i<_len; } - @Override public E next() { return _es[_i++]; } - } - - @Override public String toString() { - StringBuilder sb = new StringBuilder().append('{'); - for( int i=0; i<_len; i++ ) { - if( i>0 ) sb.append(','); - if( _es[i] != null ) sb.append(_es[i].toString()); - } - return sb.append('}').toString(); - } - - private void range_check( int i ) { - if( i < 0 || i>=_len ) - throw new ArrayIndexOutOfBoundsException(""+i+" >= "+_len); - } - - // Note that the hashCode() and equals() are not invariant to changes in the - // underlying array. If the hashCode() is used (e.g., inserting into a - // HashMap) and the then the array changes, the hashCode() will change also. - @Override public boolean equals( Object o ) { - if( this==o ) return true; - if( !(o instanceof Ary ary) ) return false; - if( _len != ary._len ) return false; - if( _es == ary._es ) return true; - for( int i=0; i<_len; i++ ) - if( !(_es[i]==null ? (ary._es[i] == null) : _es[i].equals(ary._es[i])) ) - return false; - return true; - } - @Override public int hashCode( ) { - int sum=_len; - for( int i=0; i<_len; i++ ) - sum += _es[i]==null ? 0 : _es[i].hashCode(); - return sum; - } - -} diff --git a/src/main/java/com/seaofnodes/simple/util/AryInt.java b/src/main/java/com/seaofnodes/simple/util/AryInt.java deleted file mode 100644 index b498fd78..00000000 --- a/src/main/java/com/seaofnodes/simple/util/AryInt.java +++ /dev/null @@ -1,192 +0,0 @@ -package com.seaofnodes.simple.util; - -import java.util.Arrays; -import java.util.Collection; -import java.util.function.IntUnaryOperator; - -// ArrayList with saner syntax -public class AryInt { - public int[] _es; - public int _len; - public AryInt(int[] es) { this(es,es.length); } - public AryInt(int[] es, int len) { _es=es; _len=len; } - public AryInt() { this(new int[1],0); } - - /** @return list is empty */ - public boolean isEmpty() { return _len==0; } - /** @return active list length */ - public int len() { return _len; } - /** @param i element index - * @return element being returned; throws if OOB */ - public int at( int i ) { - range_check(i); - return _es[i]; - } - /** @param i element index - * @return element being returned, or 0 if OOB */ - public int atX( int i ) { - return i < _len ? _es[i] : 0; - } - /** @return last element */ - public int last( ) { return at(_len-1); } - public int last(int x ) { return at(_len-1+x); } - - /** @return remove and return last element */ - public int pop( ) { - range_check(0); - return _es[--_len]; - } - - /** Add element in amortized constant time - * @param e element to add at end of list - * @return 'this' for flow-coding */ - public AryInt push( int e ) { - if( _len >= _es.length ) _es = Arrays.copyOf(_es,Math.max(1,_es.length<<1)); - _es[_len++] = e; - return this; - } - - /** Slow, linear-time, element insert. Preserves order. - * @param i index to insert at, between 0 and _len inclusive. - * @param e intlement to insert - */ - public void insert( int i, int e ) { - if( i < 0 || i>_len ) - throw new ArrayIndexOutOfBoundsException(""+i+" >= "+_len); - if( _len >= _es.length ) _es = Arrays.copyOf(_es,Math.max(1,_es.length<<1)); - System.arraycopy(_es,i,_es,i+1,(_len++)-i); - _es[i] = e; - } - - /** Fast, constant-time, element removal. Does not preserve order - * @param i element to be removed - * @return element removed */ - public int del( int i ) { - range_check(i); - int tmp = _es[i]; - _es[i]=_es[--_len]; - return tmp; - } - - /** Slow, linear-time, element removal. Preserves order. - * @param i element to be removed - * @return element removed */ - public int remove( int i ) { - range_check(i); - int e = _es[i]; - System.arraycopy(_es,i+1,_es,i,(--_len)-i); - return e; - } - - /** Remove all elements */ - public void clear( ) { Arrays.fill(_es,0,_len,0); _len=0; } - - // Extend and set - public int setX( int i, int e ) { - while( i>= _es.length ) _es = Arrays.copyOf(_es,_es.length<<1); - if( i >= _len ) _len = i+1; - return (_es[i] = e); - } - - public int set( int i, int e ) { - range_check(i); - return (_es[i] = e); - } - - public AryInt set_as( int e ) { _es[0] = e; _len=1; return this; } - public AryInt set_len( int len ) { - if( len > _len ) - while( len>= _es.length ) _es = Arrays.copyOf(_es,_es.length<<1); - _len = len; - while( _es.length > (len<<1) ) // Shrink if hugely too large - _es = Arrays.copyOf(_es,_es.length>>1); - return this; - } - - /** @param c Collection to be added */ - public AryInt addAll( Collection<? extends Integer> c ) { for( int e : c ) push(e); return this; } - - /** @param es Array to be added */ - public AryInt addAll( int[] es ) { - if( es.length==0 ) return this; - while( _len+es.length > _es.length ) _es = Arrays.copyOf(_es,_es.length<<1); - System.arraycopy(es,0,_es,_len,es.length); - _len += es.length; - return this; - } - - /** @param es Array to be added */ - public AryInt addAll( AryInt ary ) { - if( ary._len==0 ) return this; - while( _len+ary._len > _es.length ) _es = Arrays.copyOf(_es,_es.length<<1); - System.arraycopy(ary._es,0,_es,_len,ary._len); - _len += ary._len; - return this; - } - - public AryInt map_update( IntUnaryOperator f ) { for( int i = 0; i<_len; i++ ) _es[i] = f.applyAsInt(_es[i]); return this; } - - /** @return compact array version, using the internal base array where possible. */ - public int[] asAry() { return _len==_es.length ? _es : Arrays.copyOf(_es,_len); } - - /** Sorts in-place */ - public void sort_update() { Arrays.sort(_es, 0, _len); } - /** Find the first matching element using ==, or -1 if none. Note that - * most del calls shuffle the list, so the first element might be random. - * @param e intlement to find - * @return index of first matching element, or -1 if none */ - public int find( int e ) { - for( int i=0; i<_len; i++ ) if( _es[i]==e ) return i; - return -1; - } - - @Override public String toString() { - SB sb = new SB().p('['); - for( int i=0; i<_len; i++ ) - sb.p(_es[i]).p(','); - return sb.unchar().p(']').toString(); - } - - private void range_check( int i ) { - if( i < 0 || i>=_len ) - throw new ArrayIndexOutOfBoundsException(""+i+" >= "+_len); - } - - // Binary search sorted _es. Returns insertion point. - // Undefined results if _es is not sorted. - public int binary_search( int e ) { - int lo=0, hi=_len-1; - while( lo <= hi ) { - int mid = (hi + lo) >>> 1; // midpoint, rounded down - int mval = _es[mid]; - if( e==mval ) { - // If dups, get to the first. - while( mid>0 && e==_es[mid-1] ) mid--; - return mid; - } - if( e >mval ) lo = mid+1; - else hi = mid-1; - } - return lo; - } - - // Note that the hashCode() and equals() are not invariant to changes in the - // underlying array. If the hashCode() is used (e.g., inserting into a - // HashMap) and the then the array changes, the hashCode() will change also. - @Override public boolean equals( Object o ) { - if( this==o ) return true; - if( !(o instanceof AryInt ary) ) return false; - if( _len != ary._len ) return false; - if( _es == ary._es ) return true; - for( int i=0; i<_len; i++ ) - if( _es[i] != ary._es[i] ) - return false; - return true; - } - @Override public int hashCode( ) { - int sum=_len; - for( int i=0; i<_len; i++ ) - sum += _es[i]; - return sum; - } -} diff --git a/src/main/java/com/seaofnodes/simple/util/SB.java b/src/main/java/com/seaofnodes/simple/util/SB.java deleted file mode 100644 index d7afd6eb..00000000 --- a/src/main/java/com/seaofnodes/simple/util/SB.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.seaofnodes.simple.util; - -/** Tight/tiny StringBuilder wrapper. - * Short short names on purpose; so they don't obscure the printing. - * Can't believe this wasn't done long, long ago. */ -public final class SB { - private final StringBuilder _sb; - private int _indent = 0; - public SB( ) { _sb = new StringBuilder( ); } - public SB(String s) { _sb = new StringBuilder(s); } - public SB p( String s ) { _sb.append(s); return this; } - public SB p( float s ) { - if( Float.isNaN(s) ) - _sb.append( "Float.NaN"); - else if( Float.isInfinite(s) ) { - _sb.append(s > 0 ? "Float.POSITIVE_INFINITY" : "Float.NEGATIVE_INFINITY"); - } else _sb.append(s); - return this; - } - public SB p( double s ) { - if( Double.isNaN(s) ) - _sb.append("Double.NaN"); - else if( Double.isInfinite(s) ) { - _sb.append(s > 0 ? "Double.POSITIVE_INFINITY" : "Double.NEGATIVE_INFINITY"); - } else _sb.append(s); - return this; - } - public SB p( char s ) { _sb.append(s); return this; } - public SB p( int s ) { _sb.append(s); return this; } - public SB p( long s ) { _sb.append(s); return this; } - public SB p( boolean s) { _sb.append(s); return this; } - // 1 byte, 2 hex digits, 8 bits - public SB hex1(int s) { - int digit = (s>>4) & 0xf; - _sb.append((char)((digit <= 9 ? '0' : ('A'-10))+digit)); - digit = s & 0xf; - _sb.append((char)((digit <= 9 ? '0' : ('A'-10))+digit)); - return this; - } - // 2 bytes, 4 hex digits, 16 bits, Big Endian - public SB hex2(int s) { return hex1(s>> 8).hex1(s); } - // 4 bytes, 8 hex digits, 32 bits, Big Endian - public SB hex4(int s) { return hex2(s>>16).hex2(s); } - // 8 bytes, 16 hex digits, 64 bits, Big Endian - public SB hex8(long s) { return hex4((int)(s>>32)).hex4((int)s); } - - // Fixed width field - public SB fix( int sz, String s ) { - for( int i=0; i<sz; i++ ) - _sb.append( i < s.length() ? s.charAt(i) : ' '); - return this; - } - public char at(int idx ) { return _sb.charAt(idx); } - - // Not spelled "p" on purpose: too easy to accidentally say "p(1.0)" and - // suddenly call the autoboxed version. - public SB pobj( Object s ) { _sb.append(s.toString()); return this; } - public SB i( int d ) { for( int i=0; i<d+_indent; i++ ) p(" "); return this; } - public SB i( ) { return i(0); } - public SB ip(String s) { return i().p(s); } - public SB s() { _sb.append(' '); return this; } - - // Increase indentation - public SB ii( int i) { _indent += i; return this; } - public SB ii() { return ii(1); } - // Decrease indentation - public SB di( int i) { _indent -= i; return this; } - public SB di() { return di(1); } - - public SB nl( ) { return p(System.lineSeparator()); } - // Last printed a nl - public boolean was_nl() { - int len = _sb.length(); - String nl = System.lineSeparator(); - int nlen = nl.length(); - if( len < nlen ) return false; - for( int i=0; i<nlen; i++ ) - if( _sb.charAt(len-nlen+i)!=nl.charAt(i) ) - return false; - return true; - } - - // Delete last char. Useful when doing string-joins and JSON printing and an - // extra separater char needs to be removed: - // - // sb.p('['); - // for( Foo foo : foos ) - // sb.p(foo).p(','); - // sb.unchar().p(']'); // remove extra trailing comma - // - public SB unchar() { return unchar(1); } - public SB unchar(int x) { _sb.setLength(_sb.length()-x); return this; } - public SB setLen(int len) { _sb.setLength(len); return this; } - public String subString(int start, int end ) { return _sb.substring(start,end); } - - public SB clear() { _sb.setLength(0); return this; } - public int len() { return _sb.length(); } - @Override public String toString() { return _sb.toString(); } -} diff --git a/src/main/java/com/seaofnodes/simple/util/Utils.java b/src/main/java/com/seaofnodes/simple/util/Utils.java deleted file mode 100644 index 577a60af..00000000 --- a/src/main/java/com/seaofnodes/simple/util/Utils.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.seaofnodes.simple.util; - -import java.util.ArrayList; - -public class Utils { - public static RuntimeException TODO() { return TODO("Not yet implemented"); } - public static RuntimeException TODO(String msg) { return new RuntimeException(msg); } - - /** - * Fast, constant-time, element removal. Does not preserve order - * - * @param array ArrayList to modify - * @param i element to be removed - * @return element removed - */ - public static <E> void del(ArrayList<E> array, int i) { - E last = array.removeLast(); - if (i < array.size()) array.set(i, last); - } - - /** - * Search a list for an element by reference - * - * @param ary List to search in - * @param x Object to be searched - * @return >= 0 on success, -1 on failure - */ - public static <E> int find( ArrayList<E> ary, E x ) { - for( int i=0; i<ary.size(); i++ ) - if( ary.get(i)==x ) - return i; - return -1; - } - /** - * Search a list for an element by reference - * - * @param ary List to search in - * @param x Object to be searched - * @return >= 0 on success, -1 on failure - */ - public static <E> int find( E[] ary, E x ) { - for( int i=0; i<ary.length; i++ ) - if( ary[i]==x ) - return i; - return -1; - } - - // Rotate a long, nice for hashes - public static long rot( long x, int n ) { return (x<<n) | (x>>>n); } - - // Fold a 64bit hash into 32 bits - public static int fold( long x ) { return (int)((x>>32) ^ x); } -} |
