diff options
Diffstat (limited to 'chapter-interprocedural-sccp/src/main')
272 files changed, 0 insertions, 22472 deletions
diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/IterPeeps.java deleted file mode 100644 index a3b66e09..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ /dev/null @@ -1,230 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.node.*; -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 { - - public final WorkList<Node> _work; - - private final WorkList<CallEndNode> _workInline; - - public IterPeeps( long seed ) { - _work = new WorkList<>(seed); - _workInline = 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 ) { - while( true ) { - iteratePeeps(code); - CallEndNode cend; - while( (cend=_workInline.pop())!=null ) - if( cend.inlineSmall() ) break; - if( cend==null ) return; - } - } - - // Only try growing inlines once the shrinking peepholes reach a fixed point. - private void iteratePeeps( CodeGen code ) { - assert progressOnList(code, _work, true); - int cnt=0; - - Node n; - while( (n=_work.pop()) != null ) { - if( n.isDead() ) continue; - if( n instanceof CallEndNode cend ) _workInline.push(cend); - cnt++; // Useful for debugging, searching which peephole broke things - var obs = CodeGen.CODE._obs; - if( obs != null ) obs.before(n); - Node x = n.peepholeOpt(); - if( x != null ) { - if( x.isDead() ) { - if( obs != null ) obs.after(n, x, true); - 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(); - assert progressOnList(code, _work, true); // Very expensive assert - } - if( n.isUnused() && !(n instanceof StopNode) ) - n.kill(); // Just plain dead - if( obs != null ) obs.after(n, x, true); - } - - } - - // 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. - // Pessimistic solver assert - public static boolean progressOnList(CodeGen code, WorkList<Node> list, boolean dir ) { - code._midAssert = true; - Node changed = code._stop.walk( n -> { - Node m = n; - Type nval = n.compute(); - if( (!n.iskeep() || n._nid<=8) && // Types must be forwards, even if on worklist - ( dir - ? nval.isa(n._type) // Pesi: new value lifts over old - : n._type.isa(nval) // Opto: new value falls over old - ) ) { - if( list.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 - */ - public 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/Parser.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/Parser.java deleted file mode 100644 index 9cf565ac..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/Parser.java +++ /dev/null @@ -1,2156 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.node.*; -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() { - // Graph callbacks during scope setup need a source position. - _lexer = new Lexer(sys.SYS); - - _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(con(TypeMem.BOT)); - - // Parse the sys import - 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; - // Gather an explicit main - FunNode xmain = null; - for( Node n : stop._inputs ) - if( n instanceof ReturnNode ret && "main".equals(ret.fun()._name) ) - if( xmain != null ) throw Utils.TODO(); - else xmain = ret.fun(); - - 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())); - if( xmain != null ) - _code.setMain(xmain); - - } else { - // We have a non-empty default main. - if( xmain != null ) - // 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) { - return parseFunctionBody(sig, loc, null, ids); - } - - private ReturnNode parseFunctionBody( TypeFunPtr sig, Lexer loc, java.util.BitSet finals, 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 BulkMemPhiNode(ScopeNode.MEM0,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 - // Unknown callers supply whole memory; field aliases are discovered lazily. - _scope.mem(new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,con(TypeMem.BOT)).peephole()); - // All args, "as-if" called externally - for( int i=0; i<ids.length; i++ ) { - Type t = sig.arg(i); - _scope.define(ids[i], t, finals==null ? t instanceof TypeNil : finals.get(i), 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 ) - if( fun.sig() != _code._main ) - fun.addReturn(ctrl(), _scope.mem(), last); - else { - fun.setDef(1,XCTRL); // Kill default main - _code.addAll(fun._outputs); - } - - // 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(";") ) 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,"else"); - } - - // Parse a conditional expression, merging results. - private Node parseTrinary( Node pred, String fside ) { - // IfNode takes current control and predicate - pred.keep(); - 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 - fScope.ctrl(ifF); // The pending arm already starts at False - _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 - // Parse true-side flavor - Node lhs = switch( fside ) { - case "else" -> parseStatement(); // if( pred ) stmts; - case ":" -> parseAsgn(); // pred ? asgn; - case "&&" -> parseLogical(); // pred && expr - case "||" -> pred; // pred || expr_is_ignored - default -> throw Utils.TODO(); - }; - lhs.keep(); - _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 - ifF.unkeep(); // fScope already owns the false control - // 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); - // Parse false-side flavor - boolean doRHS = false; // RHS is optional for if/else and trinary - Node rhs = switch( fside ) { - case "else" -> (doRHS=match(fside)) ? parseStatement() : con(lhs._type.makeZero()); - case ":" -> (doRHS=match(fside)) ? parseAsgn() : con(lhs._type.makeZero()); - case "&&" -> rhs = pred; - case "||" -> rhs = parseLogical(); - default -> throw Utils.TODO(); - }; - rhs.keep(); - _scope.removeGuards(ifF); - if( doRHS || fside.equals("||") ) - fScope = _scope; - pred.unkeep(); - - // Check the trinary widening int/flt - if( !fside.equals("else") ) { - 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( !fside.equals("else") && (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(), ";"); - // Force the lazy whole-memory Phi before closing this exit. - _fun.addReturn(ctrl(), _scope.mem(), expr); - ctrl(XCTRL); // Kill control - return expr; - } - - - - /** 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.canInit(def) ) - throw error("Cannot reassign final '"+name+"'"); - - // Parse assignment expression - Node expr = parseAsgn(); - - // Lift expression, based on type - Node lift = liftExpr(expr.keep(), def.type(), true); - // Update - _scope.update(name,lift); - // Return un-lifted expr - return expr.unkeep(); - } - - // Apply declared access permissions; widen floats and narrow integers. - // Early error if types do not match variable. - private Node liftExpr( Node expr, Type t, boolean isLoad ) { - if( expr._type instanceof TypeMemPtr tmp && TYPES.get(tmp._obj._name).isFRef() ) - throw error("Must define forward ref "+tmp._obj._name); - // Read-only access is deep; a fixed binding alone does not remove access. - if( t instanceof TypeMemPtr tmp && tmp.isFinal() ) { - expr = peep(new ReadOnlyNode(expr)); - } - if( !expr._type.accessISA(t) ) - throw error("Type " + expr._type.str() + " is not of declared type " + t.str()); - // 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. - if( expr._type instanceof TypeFloat && t instanceof TypeInteger ) - expr = peep(new ToIntegerNode(expr)); - 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) || !et.accessISA(t)) ) - expr = peep(new CastNode(t.isConstant() ? t : t.widen(),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,";"); - } - - // Widen a reassignable binding without discarding the initializer's - // permissions. Lattice glb includes readonly memory and is too broad here. - private Type inferredType(Type t) { - if( t instanceof TypeMemPtr tmp ) { - if( tmp._obj.isAry() ) { - Type elem = tmp._obj._fields[1]._t; - if( elem instanceof TypeConAry ) - elem = ((TypeMemPtr)TYPES.get(tmp._obj._name))._obj._fields[1]._t; - return typeAry(elem,tmp._obj._fields[1]._final).withAccess(tmp._ro).makeNullable(); - } - Type base = TYPES.get(tmp._obj._name); - TypeMemPtr ptr = (TypeMemPtr)base; - return ptr.withAccess(tmp._ro).makeNullable(); - } - return t.glb(false); - } - - // The binding modifier sits immediately before the name. It never - // changes permissions on the referenced object. - private boolean bindingFinal(Type t) { - if( match("!") ) return false; - if( match("~") ) return true; - return t==Type.TOP || t instanceof TypeNil; - } - - /** 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 xfinal = bindingFinal(t); - Lexer loc = loc(); - String name = requireId(); - // Optional initializing expression follows - boolean fld_final = false; // Fixed field independent of pointee access - boolean initialized = match("="); - Node expr; - if( initialized ) { - expr = isExternDecl() - ? externDecl(name,t) - : parseAsgn(); - // 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 = inferredType(t); // 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 -> xfinal ? con(Type.TOP) : ZERO; - case TypeFloat tf -> con(xfinal ? Type.TOP : 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() && xfinal ) - fld_final = true; - } - - // Lift expression, based on type - Node lift = liftExpr(expr, t, true); - - // 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.widen(),null,lift)); - // Define a new name - if( !_scope.define(name,t,xfinal || fld_final,lift, loc) ) - throw error("Redefining name '" + name + "'", loc); - _scope.lookup(name)._init = initialized; - 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 && v._init && _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(); - boolean writable = match("!"); - boolean readonly = !writable && match("~"); - // 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 posT(old1); - - // 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 - // A qualifier before the name applies to the struct reference, not - // to an enclosing array. Arrays have their own [] / [~] qualifier. - if( t1 instanceof TypeMemPtr tmp ) { - if( readonly || !writable ) t1 = tmp.makeRO(); - } else if( writable || readonly ) - return posT(old1); - - // 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).makeRO(); - } 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(); - if( !match("!") ) 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,((TypeMemPtr)t1).withAccess(false)); - // 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) )); - // Reuse aliases, but preserve this occurrence's element permissions. - TypeStruct ary = ta._obj; - return TypeMemPtr.make(TypeStruct.makeAry(tname,TypeInteger.U32,ary._fields[0]._alias,t,ary._fields[1]._alias,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 = parseLogical(); - return match("?") ? parseTrinary(expr,":") : expr; - } - - /** - * Parse an bitwise expression - * - * <pre> - * bitwise : compareExpr (('&' | '|' | '^') compareExpr)* - * </pre> - * @return a bitwise expression {@link Node}, never {@code null} - */ - - private Node parseLogical() { - Node lhs = parseBitwise(); - while (true) { - if( false ) ; - else if( match("&&") ) lhs = parseTrinary(lhs, "&&"); - else if( match("||") ) lhs = parseTrinary(lhs, "||"); - else break; - } - return lhs; - } - - private Node parseBitwise() { - Node lhs = parseEquality(); - while( true ) { - if( false ) ; - else if( matchOp('&') ) lhs = new AndNode(loc(),lhs,null); - else if( matchOp('|') ) lhs = new OrNode(loc(),lhs,null); - else if( match ("^") ) lhs = new XorNode(loc(),lhs,null); - else break; - lhs.setDef(2,parseEquality()); - lhs = peep(lhs); - } - return lhs; - } - - - - /** - * Parse an eq/ne expression - */ - private Node parseEquality() { - Node lhs = parseComparison(); - while( true ) { - boolean eq = false; - if( match("==") ) eq = true; - else if( !match("!=") ) break; - lhs.keep(); - Node rhs = parseComparison(); - lhs = peep(new BoolNode.EQ(lhs.unkeep(),rhs).widen()); - if( !eq ) lhs = peep(new NotNode(lhs)); - } - return lhs; - } - - /** - * Parse an expression of the form: - * - * <pre> - * expr : shiftExpr < shiftExpr <= shiftExpr... - * expr : shiftExpr > shiftExpr >= shiftExpr... - * </pre> - * @return an comparator expression {@link Node}, never {@code null} - */ - private Node parseComparison() { - Node lhs = parseShift(); - int dir = 0; - - while( true ) { - int dir0; - if( false ) ; - else if( match("<=" ) ) dir0 = -1; - else if( match(">=" ) ) dir0 = -2; - else if( match("<" ) ) dir0 = 1; - else if( match(">" ) ) dir0 = 2; - else break; - lhs.keep(); - // Check and record direction - if( dir==0 ) { - dir = Math.abs(dir0); - Node rhs = parseShift(); - lhs = makeCompBool(dir0,lhs.unkeep(),rhs); // Convert to a bool - } else if( dir == Math.abs(dir0) ) { - // e0 < lhs < ??? - Node ifNode = new IfNode(ctrl(), lhs).peephole(); - Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole(); - Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole(); - // False side does nothing - ScopeNode fScope = _scope.dup(); - fScope.ctrl(ifF); - // True side executes next part of range - ctrl(ifT); - _scope.addGuards(ifT,lhs,false); - Node rhs = parseShift(); - _scope.removeGuards(ifT); // TODO: I think should remain true as long as possible - Node next = makeCompBool(dir0, lhs, rhs).keep(); - // Merge result - RegionNode r = ctrl(_scope.mergeScopes(fScope, loc())); - assert next._type.meet(lhs._type).isa( TypeInteger.BOOL ); - lhs = peep(new PhiNode("",TypeInteger.BOOL,r,next.unkeep(),lhs.unkeep())); - r.peephole(); - } else { - throw error("Mixing relational directions in a chained relational test"); - } - } - return lhs; - } - - private Node makeCompBool( int dir0, Node lhs, Node rhs ) { - // Convert to a bool - if( Math.abs(dir0)==2 ) // Swap direction - { Node tmp = lhs; lhs = rhs; rhs = tmp; } - lhs = peep(dir0 < 0 ? new BoolNode.LE(lhs,rhs) : new BoolNode.LT(lhs,rhs)); - return peep(lhs.widen()); - } - - - /** - * 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('.') ) - // A namespace lookup is not a reference declaration. - return parsePostfix(con(TYPES.get(((TypeMemPtr)t)._obj._name))); - //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 - if( var._final ) - throw error("Cannot reassign final '"+var._name+"'"); - 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); - case '&' -> new AndNode(loc(),lhs,rhs); - case '|' -> new OrNode(loc(),lhs,rhs); - case '^' -> new XorNode(loc(),lhs,rhs); - case Lexer.SHL -> new ShlNode(loc(),lhs,rhs); - case Lexer.SAR -> new SarNode(loc(),lhs,rhs); - case Lexer.SHR -> new ShrNode(loc(),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,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 + "'"); - Node prior = mem().keep(); - MemMergeNode input = new MemMergeNode((Node)null); - for( Field f : fs ) if( !f._one ) input.alias(f._alias,prior); - // Allocation zeroes its fields; nonzero initializers become explicit Stores. - Node[] ns = new Node[3]; - ns[0] = ctrl(); - ns[1] = input.peephole(); - ns[2] = size; - Node nnn = new NewNode(TypeMemPtr.make(obj),ns).peephole().keep(); - Node ptr = new ProjNode(nnn,0,obj._name).peephole().keep(); - Node out = new ProjNode(nnn,1,"$mem").peephole().keep(); - MemMergeNode after = new MemMergeNode(prior); - for( Field f : fs ) if( !f._one ) after.alias(f._alias,out); - mem(after.peephole()); - out.unkeep(); - nnn.unkeep(); - prior.unkill(); - for( int i=0; i<fs.length; i++ ) { - Node val = init.get(idx+i); - if( !fs[i]._one && val._type != val._type.makeZero() ) { - Field f = fs[i]; - Node st = new StoreNode(loc(),f._fname,f._alias,f._t,mem(),ptr,off(obj,f._fname),val,true).peephole(); - mem(new MemMergeNode(mem(),f._alias,st).peephole()); - } - } - 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.makeRO()); - } - - // Memory is one hidden SSA variable, including across branches and loops. - private Node mem() { return _scope.mem(); } - private void mem(Node n) { _scope.mem(n); } - - private void store(String name, int alias, Type glb, Node ptr, Node off, Node val, boolean init, Node ctrl) { - if( !init && ptr._type instanceof TypeMemPtr view && view._ro && ctrl()._type!=Type.XCONTROL ) - throw error("Cannot modify final field '"+name+"'"); - Node prior = mem().keep(); - Node st = new StoreNode(loc(),name,alias,glb,prior,ptr,off,val,init); - st.setDef(0,ctrl); - st = st.peephole(); - mem(new MemMergeNode(prior,alias,st).peephole()); - prior.unkill(); - } - - /** - * 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); - if( !ptr._obj.isAry() ) - ptr = ptr.makeFrom((TypeStruct)ptr._obj.join(ptr2._obj)); // Resolve the shape, retaining access. - 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); - if( base.isAry() && tf instanceof TypeConAry con ) - tf = con.elem(); - - expr.keep(); - // 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, false ); - - store(name,f._alias,tf,expr.unkeep(),off.unkeep(),lift,false,base.isAry() ? ctrl() : null); - return val.unkeep(); // "obj.a = expr" returns the expression while updating memory - } - - // 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, mem(), 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); - store(name,f._alias,tf,expr.unkeep(),off.unkeep(),op,false,base.isAry() ? ctrl() : null); - 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 TypeFloat && t instanceof TypeInteger ) - val = new ToIntegerNode(val).peephole(); - 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); - java.util.BitSet finals = new java.util.BitSet(); - - // 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; - finals.set(ids.size(),bindingFinal(t)); - 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,finals,ids.asAry()); - return new FunPtrNode(ret._fun.sig(),ret).peephole(); - } - - /** - * 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().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()); - // A call may update any alias, so its memory result is the new whole state. - _scope.mem(new ProjNode(cend,1,ScopeNode.MEM0).peephole()); - // 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(); - StringBuilder sb = new StringBuilder(); - while( !_lexer.isEOF() ) { - char c = _lexer.nextChar(); - if( c=='"' ) return sb.toString(); - if( c!='\\' ) { - sb.append(c); - continue; - } - if( _lexer.isEOF() ) - throw error("Unclosed string"); - sb.append(escapedChar("string")); - } - throw error("Unclosed string"); - } - - private char escapedChar(String kind) { - char esc = _lexer.nextChar(); - return switch( esc ) { - case '0' -> '\u0000'; - case 'n' -> '\n'; - case 't' -> '\t'; - case 'r' -> '\r'; - case '\\' -> '\\'; - case '"' -> '"'; - case '\'' -> '\''; - default -> throw error("Unknown "+kind+" escape \\\\"+esc); - }; - } - - // Already parsed "'" - private Node parseChar() { - if( _lexer.isEOF() ) throw error("Unclosed character"); - char c = _lexer.nextChar(); - if( c=='\\' ) { - if( _lexer.isEOF() ) throw error("Unclosed character"); - c = escapedChar("character"); - } - return require(con(TypeInteger.constant(c)),"'"); - } - - ////////////////////////////////// - // 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 matchOp(char c0 ) { return _lexer.matchOp(c0); } - 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 this char, and the next char must be different. - // Handles '&&' vs '&' - boolean matchOp( char c0 ) { - skipWhiteSpace(); - if( _position+1 >= _input.length || _input[_position]!=c0 || _input[_position+1]==c0 ) - return false; - inc(); - return true; - } - // 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) - // Distinct tags for the multi-character compound operators. - static final char SHL = 2, SAR = 3, SHR = 4; - - public char matchOperAssign() { - skipWhiteSpace(); - if( match("<<=" ) ) return SHL; - if( match(">>>=") ) return SHR; - if( match(">>=" ) ) return SAR; - 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/Var.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/Var.java deleted file mode 100644 index e13b85c5..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/Var.java +++ /dev/null @@ -1,56 +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 _init; // Explicit declaration initializer - 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; - } - // Raw declared type, without resolving a forward reference while printing. - public Type _type() { return _type; } - 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java deleted file mode 100644 index 2319714b..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -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() ) { - // A synthetic never-exit value has no runtime definition. - if( n._type==Type.TOP ) continue; - if( n instanceof PhiNode phi && !(phi._type instanceof TypeMem) ) { - // All real 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++ ) - // TOP keeps the type merge neutral, but needs no register. - if( n.in(i)._type!=Type.TOP ) - 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): mach.outregmap(); - if( masko!=null && !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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java deleted file mode 100644 index 689ceceb..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java deleted file mode 100644 index af670bda..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java +++ /dev/null @@ -1,399 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.graph.GraphObserver; - -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 com.seaofnodes.simple.util.Utils; -import java.io.IOException; -import java.util.*; - -@SuppressWarnings("unchecked") -public class CodeGen { - public GraphObserver<Node> _obs; - 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, // Build ideal IR from source - Iter, // Pessimistic peepholes - Opto, // Optimistic interprocedural SCCP - IterPostOpto, // Peepholes and inlining using SCCP results - FinalizeIR, // Finish function cleanup and layouts - TypeCheck, // Check the surviving ideal graph - LoopTree, // Discover loops and connect nonreturning paths - Select, // Select machine instructions - Schedule, // Global code motion - LocalSched, // Order instructions within blocks - RegAlloc, // Assign registers and stack slots - Encoding, // Encode instructions - Export; // Link in memory or write ELF - - } - public Phase _phase; - - // Run through target, skipping phases already completed. - public CodeGen driver(Phase target) { return driver(target,null,null); } - public CodeGen driver(Phase target, String cpu, String callingConv) { return driver(target,cpu,callingConv,0); } - public CodeGen driver(Phase target, String cpu, String callingConv, int dump) { - if( target.ordinal()>=Phase.RegAlloc.ordinal() && cpu==null && _mach==null ) - throw new IllegalArgumentException("Machine code requires a CPU target"); - if( target==Phase.Export ) throw new IllegalArgumentException("Use exportELF with an output file or null for memory linking"); - step(target, Phase.Parse, this::parse, dump); - step(target, Phase.Iter, this::iter, dump); - step(target, Phase.Opto, this::opto, dump); - step(target, Phase.IterPostOpto, this::iterPostOpto, dump); - step(target, Phase.FinalizeIR, this::finalizeIR, dump); - step(target, Phase.TypeCheck, this::typeCheck, dump); - step(target, Phase.LoopTree, this::loopTree, dump); - step(target, Phase.Select, () -> instSelect(cpu,callingConv), dump); - step(target, Phase.Schedule, this::GCM, dump); - step(target, Phase.LocalSched, this::localSched, dump); - step(target, Phase.RegAlloc, this::regAlloc, dump); - step(target, Phase.Encoding, this::encode, dump); - return this; - } - - private void step(Phase target, Phase next, Runnable work, int dump) { - if( next.ordinal()>target.ordinal() || (_phase!=null && _phase.ordinal()>=next.ordinal()) ) return; - Phase prior = _phase; - work.run(); - if( _phase!=prior && (dump & (1<<_phase.ordinal()))!=0 ) { - if( (dump & (1<<30))!=0 ) System.err.println("After "+_phase+":"); - System.err.println(this); - } - } - - public final long[] _times = new long[Phase.values().length]; - private CodeGen phase(Phase next, boolean ordered, Runnable work) { - assert ordered : "Cannot run "+next+" after "+_phase; - _phase = next; - long t0 = System.currentTimeMillis(); - try { work.run(); } - finally { _times[next.ordinal()] = System.currentTimeMillis()-t0; } - if( _obs!=null ) _obs.phase(next.name()); - return this; - } - - - // --------------------------- - // 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) { this(src, arg==null ? TypeInteger.BOT : arg, 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); - } - - - // 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++; } - - - // Inlining can insert CFG edges and invalidate cached depth ordering. - private char _iDepthVersion; - public char iDepthVersion() { return _iDepthVersion; } - public void invalidateIDepthCaches() { - assert _iDepthVersion < Character.MAX_VALUE : "Dominator cache version exceeds 65535"; - ++_iDepthVersion; - } - - // Popular visit bitset, declared here, so it gets reused all over - public final BitSet _visit = new BitSet(); - public BitSet visit() { assert _visit.isEmpty(); return _visit; } - - // 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<>(); - - // Source of unique function indices - 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 TypeFunPtr _main; - public void setMain(FunNode main) { - _main = main.sig(); - link(main); - } - // Reverse from a constant function pointer to the IR function being called. - // Error to call with a non-constant TFP - public FunNode link( TypeFunPtr tfp ) { return link(tfp.fidx()); } - // Read-only diagnostic lookup; preserve stale entries. - public FunNode _link(TypeFunPtr tfp) { return _link(tfp.fidx()); } - public FunNode _link(int fidx) { return _linker.atX(fidx); } - // Return the FunNode from a fidx, cleaning up dead entries. - public FunNode link( int fidx ) { - FunNode fun =_linker.atX(fidx); - if( fun!=null && fun.isDead() ) { - _linker.setX(fidx,null); fun = null; } - return fun; - } - - // Insert linker mapping from constant function signature to the function - // being called. - public void link(FunNode fun) { - int fidx = fun.sig().fidx(); - _linker.setX(fidx,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). - final Ary<FunNode> _linker = new Ary<>(FunNode.class); - - // Native symbols belong to function identities, not to one ExternNode. - // Selection and spill copies can turn that node into an ordinary constant. - private final HashMap<TypeFunPtr,String> _externs = new HashMap<>(); - public void externFunc(TypeFunPtr tfp, String name) { _externs.put(tfp.makeFrom(Type.BOTTOM),name); } - public String externFunc(TypeFunPtr tfp) { return _externs.get(tfp.makeFrom(Type.BOTTOM)); } - - // Parser object - public final Parser P; - - // Parse ASCII text into Sea-of-Nodes IR - public CodeGen parse() { - return phase(Phase.Parse, _phase==null, () -> { - P.parse(); - }); - } - - - // --------------------------- - // 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++; } - - // Pessimistic peepholes reach a fixed point after parsing. - public CodeGen iter() { return phase(Phase.Iter, _phase==Phase.Parse, () -> _iter.iterate(this)); } - - // SCCP computes types and queues changed nodes; Iter applies their rewrites. - public CodeGen opto() { return phase(Phase.Opto, _phase==Phase.Iter, () -> Opto.opto(this)); } - public CodeGen iterPostOpto() { return phase(Phase.IterPostOpto, _phase==Phase.Opto, () -> _iter.iterate(this)); } - - public CodeGen finalizeIR() { return phase(Phase.FinalizeIR, _phase==Phase.IterPostOpto, () -> FinalizeIR.run(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 CodeGen typeCheck() { return phase(Phase.TypeCheck, _phase==Phase.FinalizeIR, () -> TypeCheck.run(this)); } - - // --------------------------- - // Build the loop tree; break never-exit loops - public CodeGen loopTree() { - return phase(Phase.LoopTree, _phase==Phase.TypeCheck, () -> { - // Build the loop tree, fix never-exit loops - _start.buildLoopTree(_start,_stop); - }); - } - - // --------------------------- - // 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 CodeGen instSelect( String cpu, String callingConv ) { return instSelect(cpu,callingConv,PORTS); } - public CodeGen instSelect( String cpu, String callingConv, String base ) { - if( cpu==null ) return this; - return phase(Phase.Select, _phase==Phase.LoopTree, - () -> new InstSelect(this).run(cpu,callingConv,base)); - } - - - // --------------------------- - // Control Flow Graph in Reverse Post Order. - public Ary<CFGNode> _cfg = new Ary<>(CFGNode.class); - - // Global schedule (code motion) nodes - public CodeGen GCM() { - return phase(Phase.Schedule, (_phase==Phase.LoopTree || _phase==Phase.Select), () -> { - GlobalCodeMotion.buildCFG(this); - - }); - } - - // --------------------------- - // Local (basic block) scheduler phase, a classic list scheduler - public CodeGen localSched() { - return phase(Phase.LocalSched, _phase==Phase.Schedule, () -> ListScheduler.sched(this)); - } - - - // --------------------------- - // Register Allocation - public RegAlloc _regAlloc; - public CodeGen regAlloc() { - return phase(Phase.RegAlloc, _phase==Phase.LocalSched, () -> { - _regAlloc = new RegAlloc(this); - _regAlloc.regAlloc(); - }); - } - - // 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 Encoding _encoding; // Encoding object - public void preEncode() { } // overridden by alternative ports - public CodeGen encode() { - return phase(Phase.Encoding, _phase==Phase.RegAlloc, () -> { - _encoding = new Encoding(this); - preEncode(); - _encoding.encode(); - }); - } - - // --------------------------- - // Exporting to external formats - public CodeGen exportELF(String fname) throws IOException { - assert _phase==Phase.Encoding; - _phase=Phase.Export; - long t0=System.currentTimeMillis(); - try { - if( fname == null ) new LinkMem(this).link(); // In memory patching - else new ElfFile(this).export(fname); // External ELF file - } finally { _times[Phase.Export.ordinal()]=System.currentTimeMillis()-t0; } - if( _obs!=null ) _obs.phase(Phase.Export.name()); - 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 ) - : IRPrinter.prettyPrint(this); - } - - // 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,""+(int)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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/ElfExpr.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/ElfExpr.java deleted file mode 100644 index 9d9b8a1c..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java deleted file mode 100644 index b6267600..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java +++ /dev/null @@ -1,382 +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]; - - if( fun._name==null ) continue; // Anonymous addresses are relocated within .text. - Symbol func = new Symbol(fun._name, text._index, SYM_BIND_GLOBAL, SYM_TYPE_FUNC); - func._value = _code._encoding._opStart[fun._nid]; - func._size = end - func._value; - symbols.push(func); - _funcs.put(fun.sig(), func); - } - } - - public void export(String fname) throws IOException { - DataSection strtab = new DataSection(".strtab", 3 /* SHT_SYMTAB */); - // first byte is reserved for an empty string - strtab._contents.write(0); - pushSection(strtab); - - // place all the symbols - SymbolSection symbols = new SymbolSection(".symtab", 2 /* SHT_STRTAB */); - pushSection(symbols); - - // zero flag by default - // we've already constructed this entire section in the encoding phase - DataSection text = new DataSection(".text", 1 /* SHT_PROGBITS */, _code._encoding._bits); - text._flags = SHF_ALLOC | SHF_WRITE | SHF_EXECINSTR; - pushSection(text); - - // Build and write constant pool - BAOS cpool = new BAOS(); - Encoding enc = _code._encoding; - enc.writeConstantPool(cpool,false); - DataSection rdata = new DataSection(".rodata", 1 /* SHT_PROGBITS */, cpool); - rdata._flags = SHF_ALLOC; - pushSection(rdata); - - // populate function symbols - encodeFunctions(symbols, text); - - int idx = 1; - for( Section s : _sections ) { - Symbol sym = new Symbol(s._name, idx++, SYM_BIND_LOCAL, SYM_TYPE_SECTION); - // we can reuse the same name pos from the actual section - sym._name_pos = s._name_pos; - sym._size = s.size(); - symbols.push(sym); - } - - // calculate local index - int num = 1; - for( Symbol s : symbols._loc ) - s._index = num++; - // extra space for .rela.text - int start_global = num+1; // Add one to skip the final .rela.text local symbol - for( Symbol a: symbols._symbols ) - a._index = start_global++; - - // 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/Encoding.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/Encoding.java deleted file mode 100644 index 019e3892..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/Encoding.java +++ /dev/null @@ -1,543 +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 implements com.seaofnodes.isa.CodeSink { - - // Top-level program graph structure - public final CodeGen _code; - - // Instruction bytes. The registers are encoded already. Relocatable data - // is in a fixed format depending on the kind of relocation. - - // - RIP-relative offsets into the same chunk are just encoded with their - // check relative offsets; no relocation info is required. - - // - Fixed address targets in the same encoding will be correct for a zero - // base; moving the entire encoding requires adding the new base address. - - // - RIP-relative to external chunks have a zero offset; the matching - // relocation info will be used to patch the correct value. - - public static class BAOS extends ByteArrayOutputStream { - public byte[] buf() { return buf; } - void set( byte[] buf0, int count0 ) { buf=buf0; count=count0; } - }; - final public BAOS _bits = new BAOS(); - - public int [] _opStart; // Start of opcodes, by _nid - public byte[] _opLen; // Length of opcodes, by _nid - - // 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 int _align; // Log alignment, captured for diagnostic display - public final int _structSize; // Capture layout during encoding, not printing - 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; - _align = t.alignment(); - _structSize = t instanceof TypeStruct ts ? ts.size() : 0; - } - } - 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; - // A native address has no local FunNode. Keep its symbol even after - // selection or a spill copy has replaced the original ExternNode. - String name = _code.externFunc(tfp); - if( name!=null ) return external(con,name); - _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); - FunNode main = _code.link(_code._main); - for( Node n : _code._start._outputs ) - if( n instanceof FunNode fun && fun != main ) { - int x = rpo._len; - _rpo_cfg(fun, visit, rpos ); - assert rpo.at(x) instanceof ReturnNode; - } - // Do any main function last... so it lands at offset 0 in the RPO - if( main!=null ) - _rpo_cfg(main, visit, rpos ); - 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/FinalizeIR.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/FinalizeIR.java deleted file mode 100644 index 9e3b3e32..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/FinalizeIR.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; -import java.util.Arrays; - -// Settle layouts and detach the analyzed call graph before the backend. -abstract class FinalizeIR { - static void run(CodeGen code) { - fieldSizes(code); - code._iter.iterate(code); - removeCallGraph(code); - Arrays.sort(code._stop._inputs._es,0,code._stop.nIns(),(x,y) -> x._nid - y._nid); - } - - // Freeze field sizes; do struct layouts; convert field offsets into - // constants. - private static void fieldSizes( CodeGen code ) { - for( int i=0; i<code._start.nOuts(); i++ ) { - Node use = code._start.out(i); - if( use instanceof ConFldOffNode off ) { - TypeMemPtr tmp = (TypeMemPtr) Parser.TYPES.get(off._name); - off.subsume( off.asOffset(tmp._obj) ); - i--; - } - } - } - - // The remaining passes assume all calls are unlinked; i.e. we are throwing - // away the Call Graph here. Functions only reachable from internal calls - // need to be re-hooked to stop/start less they go dead. - private static void removeCallGraph(CodeGen code) { - code._start.walk( x -> { - // Unlink all existing (conservative) linkages. - if( x instanceof CallNode call ) { - for( Node y : call._outputs ) { - // Keep function alive and hooked to Start so it gets - // codegen'd. Function is callable by this call via - // TFP, although perhaps not by any other means. - if( y instanceof FunNode fun && !(fun.in(1) instanceof StartNode) ) { - ParmNode rpc = fun.rpc(); - if( rpc==null ) // Ensure valid RPC - rpc = makeRPC(fun); - // Insert a hook to Start and Stop - fun.insertDef(1,code._start); - for( Node use : fun._outputs ) - if( use instanceof ParmNode parm ) - parm.insertDef(1,ConstantNode.make(parm._type).peephole()); - code._stop.addDef(fun.ret()); - } - } - call.unlink_all(); - } - return null; - } ); - - } - - // Make a valid Parm RPC. - // First make sure a valid RPC; single-call into multi-fun will have each - // function having a single call site, so the RPC becomes a constant and - // folds - but since multiple targets, the Call never inlines. Recreate a - // valid RPC so codegen (and Eval2) understands the calling convention. - private static ParmNode makeRPC( FunNode fun ) { - ParmNode rpc = new ParmNode("$rpc",0,fun.ret().rpc()._type,fun); - for( int i=1; i<fun.nIns(); i++ ) { - CallEndNode cend = ((CallNode)fun.in(i)).cend(); - rpc.addDef(ConstantNode.make(cend._rpc).peephole()); - } - fun.ret().setDef(3,rpc.init()); - return rpc; - } - - -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java deleted file mode 100644 index da45db4f..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java +++ /dev/null @@ -1,311 +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 ); // All calls unwired now - visit.set(cfg._nid); - for( Node useuse : cfg._outputs ) - _rpo_cfg(cfg,useuse,visit,rpo); - rpo.add(cfg); - } - - // After early scheduling, every global chain member has Start in slot 0. - // Give each function one private copy of the entire dependency graph. - private static void breakUpGlobalConstants( StartNode start ) { - var globals = new IdentityHashMap<Node,Boolean>(); - var cons = new ArrayList<Node>(); - for( Node con : start._outputs ) - if( con!=null && !(con instanceof CFGNode) && - (con.isConst() || (con instanceof MachNode mach && mach.outregmap()!=null)) ) { - globals.put(con,true); - cons.add(con.keep()); // Preserve original inputs while rewiring users. - } - - var copies = new IdentityHashMap<FunNode,IdentityHashMap<Node,Node>>(); - for( Node con : cons ) - for( Node use : con._outputs.asAry() ) { - if( use==null || globals.containsKey(use) ) continue; - FunNode fun = useFun(use); - if( fun==null ) continue; // Global metadata has no function owner. - var local = copies.computeIfAbsent(fun,f -> new IdentityHashMap<>()); - Node copy = cloneGlobal(con,fun,globals,local); - for( int i=0; i<use.nIns(); i++ ) - if( use.in(i)==con ) - use.setDef(i,copy); - } - for( Node con : cons ) con.unkill(); - } - - private static Node cloneGlobal(Node con, FunNode fun, IdentityHashMap<Node,Boolean> globals, - IdentityHashMap<Node,Node> copies) { - Node copy = copies.get(con); - if( copy!=null ) return copy; - copies.put(con,copy=con.copyEmpty()); - copy.addDef(fun); - for( int i=1; i<con.nIns(); i++ ) { - Node def = con.in(i); - copy.addDef(globals.containsKey(def) ? cloneGlobal(def,fun,globals,copies) : def); - } - return copy; - } - - private static FunNode useFun(Node use) { - if( use instanceof ReturnNode ret ) return ret.fun(); - if( use instanceof ParmNode parm ) return parm.fun(); - CFGNode cfg = use.cfg0(); - while( cfg!=null && !(cfg instanceof FunNode) ) - cfg = cfg.idom(); - return (FunNode)cfg; - } - - - // ------------------------------------------------------------------------ - // 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 ); - // In dead infinite loops, entire code blocks may be unreachable - // from below. Reach down from the CFG to their Phis so their - // inputs are scheduled too. - 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 inputs first, except Phis: following their backedges would - // enter a data cycle before its control has been scheduled. - for( Node def : n._inputs ) - if( def!=null && !(def instanceof PhiNode) ) - _schedEarly(def,code); - - // An existing edge 0 already supplies control (or a Phi/Proj binding). - if( n.in(0)==null ) { - // Schedule at deepest input - CFGNode early = code._start; // Maximally early, lowest idepth - 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()]; - // Record Load NIDs at all their CFG block choices, then check against - // Store block choices to force a Load above an anti-dependent Store. - int[] anti = new int[code.UID()]; - // Breadth-first scheduling - breadth(code._stop,ns,late,anti); - - // 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, int[] anti) { - // 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.antiDeps() ) - if( late[memuse._nid]==null ) - continue outer; - - // All uses done, schedule - _doSchedLate(n,ns,late,anti); - } - - // A use just finished; reconsider its inputs and waiting loads, - // even when the shared memory input was already scheduled. - for( Node def : n._inputs ) { - if( def==null ) continue; - if( late[def._nid]==null ) work.push(def); - wakeLoads(def,late,work,new BitSet()); - } - 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, int[] anti) { - // 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).domLCA(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,anti); - - - // Walk up from the LCA to the early, looking for best place. This is - // the lowest execution frequency, approximated by least loop depth and - // deepest control flow. - 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 ParmNode ) return late[use._nid]; - 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).domLCA(found,null); // Can be more than one matching input. - - 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; - } - - // An allocation may consume a load's memory through a partial aggregate. - private static void wakeLoads(Node def, CFGNode[] late, WorkList<Node> work, BitSet visit) { - if( visit.get(def._nid) ) return; - visit.set(def._nid); - for( Node out : def._outputs ) - if( out instanceof MemOpNode ld && ld._isLoad && late[ld._nid]==null ) work.push(ld); - if( def instanceof MemMergeNode ) - for( int i=1; i<def.nIns(); i++ ) - if( def.in(i)!=null ) wakeLoads(def.in(i),late,work,visit); - } - - private static CFGNode find_anti_dep(CFGNode lca, MemOpNode load, CFGNode early, CFGNode[] late, int[] anti) { - // 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() ) - anti[cfg._nid] = load._nid; - // Walk load->mem uses, looking for Stores causing an anti-dep - for( Node mem : load.antiDeps() ) { - switch( mem ) { - case MemOpNode st: - assert late[st._nid]!=null; - lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st,anti); - break; - case CallNode st: - assert late[st._nid]!=null; - lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st,anti); - break; - case PhiNode phi: - // Inputs carrying different memory might overwrite this alias. - // Raise the LCA for those paths; unchanged memory does not - // clobber the load. No scheduling edge to the Phi. - 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,anti); - break; - case NewNode st: - assert late[st._nid]!=null; - lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st,anti); - break; - default: throw Utils.TODO(); - } - } - return lca; - } - - // - private static CFGNode anti_dep( MemOpNode load, CFGNode stblk, CFGNode defblk, CFGNode lca, Node st, int[] anti ) { - // A conditional writer can precede the load without dominating its late block. - // Walk back to the first overlap with the load's placement range. - // Another Phi input need not be dominated by this memory definition. - // Include its depth, and retain the sentinel for an entry's null idom. - for( ; stblk != defblk.idom() && stblk.idepth() >= defblk.idepth(); stblk = stblk.idom() ) { - // Store and Load overlap, need anti-dependence - if( anti[stblk._nid]==load._nid ) { - lca = stblk.domLCA(lca,null); // Raise Loads LCA - if( lca == stblk && st != null && !(st instanceof CallNode) && st._inputs.find(load) == -1 ) // And if something moved, - st.addDef(load); // Add anti-dep as well; Calls already end their block. - return lca; // Cap this stores' anti-dep to here - } - } - return lca; - } - -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/IFG.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/IFG.java deleted file mode 100644 index bcd1957d..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/IFG.java +++ /dev/null @@ -1,539 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -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) ) { - 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 ); - } - // The synthetic exit carries no value on this Phi arm. - if( def._type==Type.TOP ) continue; - 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!=null && lrg._machUse!=null && lrg._machDef.isClone() ) { - Node def = (Node)lrg._machDef; - Node use = (Node)lrg._machUse; - CFGNode cfg = def.cfg0(); - if( lrg._multiUse || cfg != use.cfg0() || // Many uses or different blocks OR - // Same block, but not close - cfg._outputs.find(def)+1 < cfg._outputs.find(use) ) - 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; // Prefer the backedge in loops - 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(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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java deleted file mode 100644 index e8e87785..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.node.*; -import java.util.BitSet; -import java.util.IdentityHashMap; - -// Replace ideal nodes with machine nodes, then rebuild the copied graph's uses. -class InstSelect { - private final CodeGen _code; - InstSelect(CodeGen code) { _code=code; } - - @SuppressWarnings("unchecked") - void run(String cpu, String callingConv, String base) { - _code._callingConv = callingConv; - - // Look for CPU in fixed named place: - // com.seaofnodes.simple.node.cpus."cpu"."cpu.class" - String clzFile = base+"."+cpu+"."+cpu; - try { _code._mach = ((Class<Machine>) Class.forName( clzFile )).getDeclaredConstructor(new Class[]{CodeGen.class}).newInstance(_code); } - catch( Exception e ) { throw new RuntimeException(e); } - - // Build global copies of common register masks. - long callerSave = _code._mach.callerSave(); - long neverSave = _code._mach. neverSave(); - int maxReg = Math.min(64,_code._mach.regs().length); - assert maxReg>=64 || (-1L << maxReg & callerSave)==0; // No stack slots in callerSave - _code._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 - _code._retMasks = new RegMask[(maxReg-_code._callerSave.size()-Long.bitCount( neverSave ))+4]; - for( int reg=0, i=4; reg<maxReg; reg++ ) - if( !_code._callerSave.test(reg) && ((1L<<reg)&neverSave)==0 ) - _code._retMasks[i++] = new RegMask(reg); - _code._rpcMask = new RegMask(_code._mach.rpc()); - _code._retMasks[3] = _code._rpcMask; - - // Convert to machine ops - _code._uid = 1; // All new machine nodes reset numbering - var map = new IdentityHashMap<Node,Node>(); - _instSelect( _code._stop, map ); - _code._stop = ( StopNode)map.get(_code._stop ); - StartNode start = (StartNode)map.get(_code._start); - _code._start = start==null ? new StartNode(_code._start) : start; - _instOuts(_code._stop,_code.visit()); - _code._visit.clear(); - } - - 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=_code._mach.instSelect(n) ); - // Stop retains the callable body. Its lifetime edge is not a machine operand. - if( n instanceof FunPtrNode ) x._inputs.pop(); - // 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(_code); // Post selection action - - return x; - } - - 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); - } - } - -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/LRG.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/LRG.java deleted file mode 100644 index 0c36ae2b..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/LRG.java +++ /dev/null @@ -1,235 +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; } - - public RegMask useMask() { return _machUse==null ? null : _machUse.regmap(_uidx); } - - 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java deleted file mode 100644 index 2415203d..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java deleted file mode 100644 index ddf5a679..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/Machine.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/Machine.java deleted file mode 100644 index a95faefc..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/Machine.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeFunPtr; -import com.seaofnodes.simple.type.TypeInteger; - -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); - - // Only the declared C result width describes the physical return register. - // Simple functions already return canonical full-width integer values. - protected static TypeInteger cReturn(ProjNode prj) { - if( prj._idx==2 && prj.in(0) instanceof CallEndNode cend && - cend.call().fptr() instanceof ExternNode ext && - ((TypeFunPtr)ext._con).ret() instanceof TypeInteger ret && ret.log_size()<3 ) - return ret; - return null; - } - -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/Opto.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/Opto.java deleted file mode 100644 index 9f07c1d1..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/Opto.java +++ /dev/null @@ -1,182 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Ary; - -// Optimistic Algorithm -abstract public class Opto { - - // Optimistic analysis - extended SCCP - - // Start all Types at TOP, and propagate changes until stable. Unlike a - // normal Sparse Conditional Constant Propagation, this version is - // interprocedural, computing effectively CFA0 in linear time using the - // normal SCCP algorithm. - - // This algo also builds a Call Graph; as Calls become reachable (CONTROL - // not TOP nor XCONTROL), they "link" against the reaching function - // pointers - adding edges from the Call to the Fun, and from the Return to - // the CallEnd. This allows the normal algorithm to use the normal edge - // propagation rules as function argument types stabilize. In the end, we - // get a fairly precise set of Call Graph edges between CallNodes and - // FunNodes. - public static void opto(CodeGen code) { - - // Reset Types before running the worklist algorithm - Ary<Type> oldTypes = new Ary<>(Type.class); // Used for asserts only - resetToTop(code,oldTypes); - - // The whole-world assumption: we use it if compiling `main`, and not otherwise. - // The assumption is no unknown callers into functions: - // - during or past an Opto phase (but not before, since parse through - // pessimistic must assume somebody calls an unknown function which - // calls everybody). - // - Have a `main` to start tracing the Call Graph from - // - - True for most test cases - // - - False for lacking a main; we assume the linker will call all public functions - - // True if we can make the "whole world" assumption, which means we are - // compiling the program entry: `main`. - boolean wholeWorld = code.link(code._main) != null; - - - // If wholeWorld, remove the Start inputs to functions - they are not - // called unless they get linked (hence go dead and can be removed). - // If not wholeWorld, keep only public methods (and not the std lib). - unlinkStart(wholeWorld,code); - - - // OPTIMISTIC INTERPROCEDURAL SCCP - sccp(code, oldTypes); - - // After SCCP add everything changed to the worklist and see if - // peepholes can make more progress. - moveChangesToWorklist(code, oldTypes); - - - } - - // Reset Types before running the worklist algorithm - private static void resetToTop(CodeGen code, Ary<Type> oldTypes ) { - code._start.walk( x -> { - oldTypes.setX(x._nid,x._type); // Record original types for asserts - x._type = Type.TOP; // Reset all Nodes to TOP - code._iter.add(x); // Visit everybody at least once; most Nodes produce better than TOP - // Unlink all existing (conservative) linkages. - if( x instanceof CallNode call ) - call.unlink_all(); - return null; - } ); - } - - - // If wholeWorld, remove the Start inputs to functions - they are not - // called unless they get linked (hence go dead and can be removed). If - // not wholeWorld, keep only public methods (and not the std lib). - private static void unlinkStart(boolean wholeWorld, CodeGen code) { - for( FunNode fun : code._linker ) { - if( fun != null && !fun.isDead() && !funIsPublic(code,wholeWorld,fun) ) { - assert fun.in(1)==code._start; // Start always in slot 1 - fun.removeDeadPath(1); - // By same logic, remove stop->return linkage, without - // remove the (now dead) ReturnNode; it awaits in limbo - // for the opto phase to link to some valid call - code._stop._inputs.del(code._stop._inputs.find(fun.ret())); - fun.ret().delUse(code._stop); - } - } - } - - // Function is public (callable from Start directly). - // - Always true for main - // - True for function addresses used as values - // - Otherwise false for stdlib or anonymous functions - // - Named functions use `!wholeWorld` - private static boolean funIsPublic( CodeGen code, boolean wholeWorld, FunNode fun ) { - // Always true for main - if( fun.sig().fidx() == code._main.fidx() ) return true; - // No escape analysis yet: a surviving address may be called from outside. - if( fun.hasPtr() ) return true; - // Never true for stdlib or anonymous functions - if( fun._name == null || fun._name.startsWith("sys.") ) - return false; - // Named functions (and not stdlib) are callable if not whole-world. - return !wholeWorld; - } - - // As part of building a CallGraph, when opto finds a function ptr flowing - // into a Call, link the Call and Fun. - private static void linkCG(CodeGen code, TypeFunPtr tfp, CallNode call) { - if( tfp.nargs() != call.nargs() ) return; // Error calls hit this - for( long fidxs=tfp.fidxs(); fidxs!=0; fidxs=TypeFunPtr.nextFIDX(fidxs) ) { - int fidx = Long.numberOfTrailingZeros(fidxs); - FunNode fun = code.link(fidx); - // null here means an external function; i.e. this Call - // calls to an outside library and all its arguments escape. - if( fun != null && !call.linked(fun) ) { - call.link( fun ); - code._iter.add(fun); - code._iter.addAll(fun._outputs); - } - } - } - - // The core SCCP algorithm - private static void sccp( CodeGen code, Ary<Type> oldTypes ) { - // Iter workList is empty at this point - // OPTIMISTIC PASS - Node n; - int count = 0; // Debug counter - while( (n = code._iter._work.pop()) != null ) { - if( n.isDead() ) continue; - count++; - Type oval = n._type, nval = n.compute(); - if( oval == nval ) continue; - assert oval.isa(nval); // Types start high and always fall - Type pesiVal = oldTypes.at(n._nid); - assert nval.isa(pesiVal); // Never fall worse than the pessimistic pass - n._type = nval; - - // If a TFP adds a new function input to a call, link to the new Fun - if( nval instanceof TypeFunPtr tfp ) { - for( Node use : n._outputs ) - if( !use._type.isHigh() && use instanceof CallNode call && call.fptr() == n ) - linkCG(code,tfp,call); - } - - // Link a Call which becomes alive - if( n instanceof CallNode call && oval.isHigh() && !nval.isHigh() && call.fptr()._type instanceof TypeFunPtr ) - linkCG(code,call.tfp(),call); - - // Since n._type changed, visit all output neighbors - code._iter.addAll(n._outputs); - n.moveDepsToWorklist(code._iter); - // Quadratic (expensive) small-step assert - assert check(code); - } - } - - // Quadratic (expensive) small-step assert - private static boolean check(CodeGen code) { - code._start.walk( x -> { - Type xval = x.compute(); - assert xval == x._type || (x._type.isa( xval ) && code._iter._work.on( x )); - return null; - }); - return true; - } - - // After SCCP add everything changed to the worklist and see if peepholes - // can make more progress. - private static void moveChangesToWorklist(CodeGen code, Ary<Type> oldTypes) { - code._start.walk( x -> { - assert x.compute() == x._type; // Hit the fixed point - assert x._nid >= oldTypes._len || x._type.isa(oldTypes.at(x._nid)); // Hit at least the bottom-up type - code._iter.add(x); - return null; - }); - } - -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java deleted file mode 100644 index 777182dc..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java deleted file mode 100644 index 565fa6ba..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java +++ /dev/null @@ -1,704 +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 com.seaofnodes.simple.type.Type; -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<>(); - // Loop Phis get one chance to color after only cold self-conflict splits. - // If the same Phi self-conflicts again, split its hot def/backedge also. - private final IdentityHashMap<PhiNode,String> _deferredLoopSelf = 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,""); - } - } - } - - - // Diagnostic lookup: do not compress leaders or rewrite the node mapping. - private LRG _lrg(Node n) { - LRG lrg = _lrgs.get(n); - if( lrg != null ) - while( lrg._leader != null ) lrg = lrg._leader; - return lrg; - } - - 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); - } - - // Optional diagnostic for values accidentally shared across functions. - //assert verifyFunctionLocalEdges(); - - // 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 new IllegalStateException("Register allocation made no progress after eight rounds"); - round++; - } - postColor(); // Remove no-op spills - } - - boolean verifyFunctionLocalEdges() { - for( CFGNode bb : _code._cfg ) { - if( bb instanceof StartNode ) continue; - FunNode fun = bb.fun(); - for( Node use : bb._outputs ) - if( use instanceof MachNode mach && !(use instanceof ParmNode) ) - for( int i=1; i<use.nIns(); i++ ) { - Node def = use.in(i); - if( def==null || mach.regmap(i)==null || def.cfg0()==null || def.cfg0() instanceof StartNode ) continue; - assert def.cfg0().fun()==fun : "Cross-function register edge: "+def.uniqueName()+" -> "+use.uniqueName()+" input "+i; - } - } - return true; - } - - 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 && - // A fixed-register clone with flexible uses must split at the uses. - !(lrg._1regDefCnt==1 && lrg._machDef.isClone() && 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() ) - // An earlier copy is reusable only if no intervening clobber. - // Being in the same block alone is insufficient. 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); - // Split just before use - if( lrg._1regUseCnt==1 ) - insertBefore((Node)lrg._machUse,lrg._uidx,"use/empty1",round,lrg); - return true; - } - - // Popular single-def value: share a split among uses with compatible masks. - boolean splitEmptyMaskByUse( byte round, LRG lrg ) { - Node def = (Node)lrg._machDef; - // Snapshot distinct users: rewriting an input changes def's output list, - // and a call can use the same value in more than one argument. - Ary<Node> uses = new Ary<>(Node.class); - for( Node use : def._outputs ) - if( uses.find(use)==-1 ) uses.push(use); - - Ary<RegMask> rclass = new Ary<>(RegMask.class); - int ncalls=0; - for( Node use : uses ) - if( use instanceof MachNode mach ) { - // Sharing copies across several calls usually loses to their kills. - if( use instanceof CallNode && ++ncalls > 1 ) return false; - for( int i=1; i<use.nIns(); i++ ) - if( use.in(i)==def && mach.regmap(i)!=null ) - putIntoRegClass(rclass,mach.regmap(i)); - } - if( rclass._len <= 1 ) return false; - - for( RegMask rmask : rclass ) { - Node split = makeSplit(def,"popular",round,lrg,rmask); - split.insertAfter(def); - if( split.nIns()>1 ) split.setDef(1,def); - for( Node use : uses ) - if( use instanceof MachNode mach ) - for( int i=1; i<use.nIns(); i++ ) { - RegMask mask = mach.regmap(i); - if( use.in(i)==def && mask!=null && mask.overlap(rmask) ) { - assert rmask.and(mask)==rmask; - use.setDefOrdered(i,split); - } - } - } - return true; - } - - // Narrowing preserves all earlier users of a class; new classes are disjoint. - // Each use contains its first overlapping class, so one pass suffices. - private static void putIntoRegClass( Ary<RegMask> rclass, RegMask rmask ) { - for( int i=0; i<rclass._len; i++ ) { - RegMask omask = rclass.at(i); - if( !omask.overlap(rmask) ) continue; - if( omask.and(rmask)!=omask ) - rclass.set(i,new RegMask(omask.copy().and(rmask))); - return; - } - rclass.push(rmask); - } - - - // 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 ); - - // First encounter with a loop Phi: try only cold splits this round. - // Remember the Phi across rounds so a persistent self-conflict forces - // the hot def/backedge splits on the next encounter. - IdentityHashMap<PhiNode,String> deferred = new IdentityHashMap<>(); - for( Node def : conflicts ) { - if( def instanceof PhiNode phi && phi.region() instanceof LoopNode ) - deferLoopSelf(phi,deferred); - for( Node use : def._outputs ) - if( use instanceof PhiNode phi && phi.region() instanceof LoopNode && - phi._inputs.find(def)==2 ) - deferLoopSelf(phi,deferred); - } - - // 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 && - !(deferred.containsKey(phi) && phi.region() instanceof LoopNode && phi._inputs.find(def)==2) && - !(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 and before its entry. Also isolate inputs - // shared with another Phi: otherwise the shared input keeps the - // conflicting live ranges joined, even after splitting the entry. - if( def instanceof PhiNode phi && !(def instanceof ParmNode) ) { - if( !deferred.containsKey(phi) ) { - SplitNode split = makeSplit("def/self",round,lrg); - insertAfterAndReplace(split,def,false); - if( split.nOuts()==0 ) - split.killOrdered(); - } - for( int i=1; i<(deferred.containsKey(phi) ? 2 : phi.nIns()); i++ ) - if( i==1 || sharedPhiInput(phi,i) ) - insertBefore(phi,i,"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; - } - - private void deferLoopSelf(PhiNode phi, IdentityHashMap<PhiNode,String> deferred) { - if( !_deferredLoopSelf.containsKey(phi) ) { - _deferredLoopSelf.put(phi,""); - deferred.put(phi,""); - } - } - - - private static boolean sharedPhiInput(PhiNode phi, int i) { - for( Node use : phi.in(i)._outputs ) - if( use instanceof PhiNode && use!=phi ) return true; - return false; - } - - // 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++ ) - if( phi.in(i)._type!=Type.TOP ) - 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 split && !split.isNonStackSplit() && min!=max ) continue; // Ordinary splits already reach stack storage. - 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 ) { - // Ordinary splits already reach stack storage; register-only transfers do not. - if( n instanceof SplitNode split && !split.isNonStackSplit() ) 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; - if( lrg._machDef!=null ) _ns.push((Node)lrg._machDef); - if( lrg._machUse!=null ) _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); - // Phi splitting may visit the synthetic never-exit arm. - if( def._type==Type.TOP ) return; - // 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; - - // Same register, or the next instruction can use the source directly. - if( defreg == usereg || splitForward(bb,j,n,usereg) ) { - n.removeSplit(); - j--; - continue; - } - // Split is kept; count against split score - _spills++; - _spillScaled += (1<<bb.loopDepth()*3); - assert _spillScaled >= 0; - } - } - } - - // A sole, adjacent use can read the source register directly. Nothing can - // clobber it in between; only the operand mask and two-address tie remain. - private boolean splitForward( CFGNode bb, int j, Node split, int reg ) { - if( split.nOuts()!=1 || j+1==bb.nOuts() ) return false; - Node use = split.out(0); - if( use!=bb.out(j+1) || use instanceof PhiNode || use instanceof CFGNode || - !(use instanceof MachNode mach) ) return false; - int i = use._inputs.find(split); - RegMask mask = mach.regmap(i); - return mask!=null && mask.test(reg) && mach.twoAddress()!=i; - } - - 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); - } - // Walk backward from the later copy, including instructions without results. - for( int idx = j-1; bb.out(idx) != hi; idx--) { - Node n = bb.out(idx); - if( clobbers(n,defreg) ) - return false; // Clobbered - } - lo.setDefOrdered(1,hi.in(1)); - return true; - } - - - private boolean clobbers(Node n, int reg) { - LRG lrg = lrg(n); - return lrg!=null && (lrg._reg==reg || lrg._reg==-1 && lrg._mask.size1() && lrg._mask.firstReg()==reg) || - n instanceof MachNode mach && mach.killmap()!=null && mach.killmap().test(reg); - } - - 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( clobbers(n,defreg) ) - return false; // Clobbered - } - throw Utils.TODO(); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/RegMask.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/RegMask.java deleted file mode 100644 index 303bba9e..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/RegMask.java +++ /dev/null @@ -1,122 +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 < 63 ) { - long bits0 = _bits0 & (-1L << (reg+1)); - if( bits0!=0 ) return (short)Long.numberOfTrailingZeros(bits0); - } - long bits1 = reg < 64 ? _bits1 : reg < 127 ? _bits1 & (-1L << (reg-63)) : 0; - return bits1==0 ? -1 : (short)(64+Long.numberOfTrailingZeros(bits1)); - } - - 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 !isEmpty() && (((_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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/RegMaskRW.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/RegMaskRW.java deleted file mode 100644 index 1da9a3b1..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java deleted file mode 100644 index 0997fd2f..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.Parser; - -abstract class TypeCheck { - static void run(CodeGen code) { - Parser.ParseException err = code._stop.walk(n -> { - Parser.ParseException msg = n.err(); - if( msg != null && code._obs != null ) code._obs.error(n, msg.getMessage()); - return msg; - }); - if( err != null ) - throw err; - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/AddFNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/AddFNode.java deleted file mode 100644 index 4ad02dbb..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/AddFNode.java +++ /dev/null @@ -1,31 +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 ArithFNode { - public AddFNode(Node lhs, Node rhs) { super(lhs, rhs); } - - @Override public String label() { return "AddF"; } - - @Override public String glabel() { return "+"; } - - @Override double doOp( double x, double y ) { return x+y; } - - @Override - public Node idealize() { - // Add of 0. - Type t2 = in(2)._type; - if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==0 ) - return in(1); - - // Move constants to RHS: con+arg becomes arg+con - if ( in(1)._type.isConstant() && !t2.isConstant() ) - return swap12(); - - return null; - } - @Override Node copy(Node lhs, Node rhs) { return new AddFNode(lhs,rhs); } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/AddNode.java deleted file mode 100644 index 2bbe1053..00000000 --- a/chapter-interprocedural-sccp/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 && lhs._type instanceof TypeInteger ) - 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.meet(op._type),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 instanceof ParmNode) && 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/AndNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/AndNode.java deleted file mode 100644 index 9957472b..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/AndNode.java +++ /dev/null @@ -1,48 +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 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ArithFNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ArithFNode.java deleted file mode 100644 index dafba626..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ArithFNode.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; - -public abstract class ArithFNode extends Node { - public ArithFNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - return p.open().n(in(1)).p(glabel()).n(in(2)).close(); - } - - abstract double doOp(double x, double y); - - @Override - public Type compute() { - Type t1 = in(1)._type, t2 = in(2)._type; - if( t1.isHigh() || t2.isHigh() ) - return Type.TOP; - if( t1 instanceof TypeFloat f1 && - t2 instanceof TypeFloat f2 ) { - if( f1.isConstant() && f2.isConstant() ) - return TypeFloat.constant(doOp(f1.value(),f2.value())); - } - return t1.meet(t2); - } - -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ArithNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ArithNode.java deleted file mode 100644 index 54290330..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ArithNode.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; - - -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; } - abstract String op(); - - @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - return p.open().n(in(1)).p(op()).n(in(2)).close(); - } - - 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 Type compute() { - Type t1 = in(1)._type, t2 = in(2)._type; - if( t1.isHigh() || t2.isHigh() ) - return Type.TOP; - if( t1 instanceof TypeInteger x && - t2 instanceof TypeInteger y ) - return x.isConstant() && y.isConstant() - ? con(x,y) - : doOp(x,y).makeWide( (byte)Math.max(x._widen,y._widen) ); - 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.region() instanceof FunNode) && - 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/BoolNode.java deleted file mode 100644 index 0d82c5b9..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ /dev/null @@ -1,156 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Utils; -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 protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - return p.open().n(in(1)).p(op()).n(in(2)).close(); - } - - @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 ((phi cons) cmp con) ? - // Do we have ((phi cons) cmp (phi cons)) ? - // Push the compare up through the phi: (phi con0 cmp con, con1 cmp con...) - 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 "<" ; } - 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 "<="; } - 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<" ; } - 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java deleted file mode 100644 index 7d4f1fb8..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java +++ /dev/null @@ -1,240 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMem; -import java.util.BitSet; - -/** - * A Phi for bulk memory. It covers all aliases except {@link #_aliases}; - * those aliases must be represented by parallel precise MemPhis at the same - * program slice. - */ -public class BulkMemPhiNode extends PhiNode { - - private static final BitSet EMPTY = new BitSet(); - - // Excluded Aliases; empty means NO exclusions. - // Each split clones the exclusions; published sets are never mutated. - public final BitSet _aliases; - - public BulkMemPhiNode(String label, Node... inputs) { - this(label,EMPTY,inputs); - } - - private BulkMemPhiNode(String label, BitSet aliases, Node... inputs) { - super(label,TypeMem.BOT,inputs); - _aliases = aliases; - } - - public boolean isSplit(int alias) { return _aliases.get(alias); } - - public BulkMemPhiNode(BulkMemPhiNode phi) { super(phi); _aliases = phi._aliases; } - - @Override public String label() { return "BulkPhi"+_aliases; } - - @Override - public Type compute() { - Type t = super.compute(); - return t instanceof TypeMem mem ? mem : - t==Type.BOTTOM ? TypeMem.BOT : - TypeMem.TOP; - } - - @Override - public Node idealize() { - Node progress = super.idealize(); - if( progress != null ) return progress; - if( ((RegionNode)region()).inProgress() || region().nIns()<=1 ) - return null; // Input is in-progress - - // "Peek through" a MemMerge that covers this alias set on its default - for( int i=1; i<nIns(); i++ ) - if( in(i) instanceof MemMergeNode mmm && canPeek(mmm) ) { - setDef(i,CodeGen.CODE.add(mmm.in(1))); - return this; - } - - // If any input or output uses or defines a specific alias we cover, - // make a private MemPhi for it, and add it to the excluded list. - for( int i=1; i<nIns(); i++ ) { - int alias = inputAlias(in(i)); - if( alias!=0 ) - return slice(alias); - } - - for( int i=0; i<_outputs.size(); i++ ) { - int alias = outputAlias(_outputs.get(i)); - if( alias!=0 ) - return slice(alias); - } - - return null; - } - - static public boolean checkMem(Node n, int alias) { - for( Node m : n._inputs ) - // Bulk memory supplies this alias - if( m instanceof BulkMemPhiNode bulk ) { - if( bulk._aliases.get(alias) ) - return false; - } else if( m instanceof MemMergeNode mmm && - mmm.in(1) instanceof BulkMemPhiNode bulk && - bulk._aliases.get(alias) && - (alias >= mmm.nIns() || mmm.in(alias)==null) ) - return false; - return true; - } - - - // True if the MemMerge has no precise slice still covered by this Phi. - private boolean canPeek(MemMergeNode mmm) { - return mmm.in(1)!=null && missingAlias(mmm)==0; - } - - // Return an alias required by an input but still covered by this Phi, or 0. - private int inputAlias(Node n) { - return switch(n) { - case ProjNode proj -> 0; - case ParmNode parm -> 0; - case ConstantNode con -> 0; - case MemMergeNode mmm -> missingAlias(mmm); - case MemOpNode mem -> unsplit(mem._alias); - case BulkMemPhiNode bulk -> missingAlias(bulk); - default -> throw new AssertionError("Unexpected memory input "+n); - }; - } - - // Return an alias required by a user but still covered by this Phi, or 0. - private int outputAlias(Node use) { - // This decision depends backwards on an immediate user's alias/set. - // Normal graph-neighbor enqueueing flows from defs to uses, so retain - // an explicit forward dependency to revisit this Phi when the user - // sharpens. - addDepForwards(use); - return switch(use) { - case ScopeNode scope -> 0; - case MemMergeNode mmm -> missingAlias(mmm); - case BulkMemPhiNode bulk -> missingAlias(bulk); - case MemOpNode mem -> unsplit(mem._alias); - case MemPhiNode phi -> unsplit(phi._alias); - default -> { - assert use instanceof ReturnNode || use instanceof CallNode || use instanceof ParmNode - : "Unexpected bulk-memory user "+use; - yield 0; - } - }; - } - - // Alias zero/one is bulk, and an already excluded precise alias cannot - // trigger another split of this Phi. - private int unsplit(int alias) { - if( alias <= 1 ) return 0; - assert !_aliases.get(alias); - return alias; - } - - // First alias excluded by the neighboring bulk Phi but not by this one. - private int missingAlias(BulkMemPhiNode bulk) { - for( int alias = bulk._aliases.nextSetBit(0); alias >= 0; alias = bulk._aliases.nextSetBit(alias+1) ) - if( !_aliases.get(alias) ) - return alias; - return 0; - } - - // Every explicit slot requests that alias, including a partial allocation - // input whose slot still points to this bulk Phi. - private int missingAlias(MemMergeNode mmm) { - for( int alias=2; alias<mmm.nIns(); alias++ ) - if( mmm.in(alias)!=null && - mmm.alias(alias)!=mmm.in(1) && - !_aliases.get(alias) ) - return alias; - return 0; - } - - // Slice out given alias - private MemMergeNode slice( int alias ) { - assert !_aliases.get(alias); - // Inputs are the same; MemPhi will sharpen his own inputs. If a - // parallel precise Phi already exists, reuse it instead of building a - // duplicate for the same Region/alias pair. - Node mem = precisePhi(alias); - - BitSet aliases = ((BitSet)_aliases.clone()); - aliases.set(alias); - BulkMemPhiNode bphi = new BulkMemPhiNode(_label,aliases); - for( int i=0; i<nIns(); i++ ) - bphi.addDef(in(i)); - // Installing bphi creates a new bulk user with a different exclusion - // set. Queue the original memory inputs before bphi itself can peep - // away and hide those neighbor relationships. - for( int i=1; i<bphi.nIns(); i++ ) - CodeGen.CODE.add(bphi.in(i)); - // Do not peephole bphi here, as BulkMemPhi can recursively start a - // second bulk rewrite before this one has finished. Let the worklist - // discover any further splits - bphi.setType(TypeMem.BOT); - Node bulk = bphi; - CodeGen.CODE.add(bulk); - CodeGen.CODE.add(mem); - return aggregate(bulk,alias,mem); - } - - // Build aggregate memory over a bulk Phi, preserving every precise slice - // which the bulk has already excluded. The named alias is newly supplied - // (or replaces its old slice). - MemMergeNode aggregate(Node bulk, int alias, Node precise) { - MemMergeNode mmm = new MemMergeNode(bulk); - for( int old = _aliases.nextSetBit(0); old >= 0; old = _aliases.nextSetBit(old+1) ) - { assert old != alias; mmm.alias(old,precisePhi(old));} - mmm.alias(alias,precise); - assert checkMerge(mmm); - return mmm; - } - - // Find or make the precise Phi parallel to this bulk Phi at the same merge. - private Node precisePhi(int alias) { - MemPhiNode mphi = _findPhi(alias); - if( mphi!=null ) return mphi; - - mphi = new MemPhiNode("$"+alias,alias); - mphi.addDef(region()); - // Due to cycles, must set before calling peephole - mphi.setType(TypeMem.make(alias,Type.BOTTOM)); - for( int i=1; i<nIns(); i++ ) - mphi.addDef(CodeGen.CODE.add(preciseInput(in(i),alias))); - return CodeGen.CODE.add(mphi); - } - - MemPhiNode _findPhi(int alias) { - for( Node use : region()._outputs ) - if( use instanceof MemPhiNode mphi && mphi._alias==alias ) - return mphi; - return null; - } - - // Select an alias already split out of a predecessor's bulk memory. - private Node preciseInput(Node mem, int alias) { - if( mem instanceof MemMergeNode mmm ) - return mmm.alias(alias); - if( mem instanceof BulkMemPhiNode bulk && bulk.isSplit(alias) ) - return bulk.precisePhi(alias); - return mem; - } - - // Every alias excluded by a bulk default must be supplied explicitly. - static boolean checkMerge(MemMergeNode mmm) { - if( !(mmm.in(1) instanceof BulkMemPhiNode bulk) ) return true; - for( int alias = bulk._aliases.nextSetBit(0); alias >= 0; alias = bulk._aliases.nextSetBit(alias+1) ) - if( alias >= mmm.nIns() || mmm.in(alias)==null ) - return false; - return true; - } - - @Override public boolean eq(Node n) { - return _aliases.equals(((BulkMemPhiNode)n)._aliases) && super.eq(n); - } - - @Override int hash() { return _aliases.hashCode(); } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/CFGNode.java deleted file mode 100644 index 6805c1ef..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/CFGNode.java +++ /dev/null @@ -1,222 +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; - _idepthVersion = cfg._idepthVersion; - _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 char _idepth; - public char _idepthVersion; - public int idepth() { return validIDepth() ? _idepth : cacheIDepth(idom().idepth()+1); } - // Zero depth means uncached. Check before narrowing so overflow cannot wrap. - final int cacheIDepth(int depth) { - assert 0 <= depth && depth <= Character.MAX_VALUE : "Dominator depth exceeds 65535"; - _idepthVersion = CodeGen.CODE.iDepthVersion(); - return _idepth = (char)depth; - } - - final boolean validIDepth() { return _idepth!=0 && _idepthVersion==CodeGen.CODE.iDepthVersion(); } - - - // 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 domLCA(CFGNode rhs, Node dep) { - if( rhs==null ) return this; - CFGNode lhs = this; - while( lhs != rhs ) { - if( lhs==null || rhs==null ) return null; - int comp = lhs.idepth() - rhs.idepth(); - if( comp >= 0 ) lhs = (dep==null ? lhs : dep.addDep(lhs)).idom(); - if( comp <= 0 ) rhs = (dep==null ? rhs : dep.addDep(rhs)).idom(); - } - return lhs; - } - - // 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 contains the function bodies at depth 0; real loops add depth. - 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++; - // Seed the function's outer tree before walking its body. Synthetic - // exits can be added before the post-order walk reaches the entry. - if( this instanceof FunNode ) _ltree = stop._ltree; - if( this instanceof ReturnNode ) { - _ltree = fun._ltree; - post.set(_nid); - return pre; // Stop/Start links describe the outside world, not a loop. - } - // 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/CProjNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/CProjNode.java deleted file mode 100644 index c62de840..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/CProjNode.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -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; - -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 protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p(_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] : (t.isHigh() ? Type.TOP : 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/CallEndMach.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/CallEndMach.java deleted file mode 100644 index d26ea33b..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/CallEndNode.java deleted file mode 100644 index aa586b17..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/CallEndNode.java +++ /dev/null @@ -1,232 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.type.*; - -/** - * CallEnd - */ -public class CallEndNode extends CFGNode implements MultiNode { - - // When set true, this Call/CallEnd/Fun/Return is being trivially inlined - private boolean _folding; - public boolean folding() { return _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 - protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - p.p("cend( "); - p.p( in(0) instanceof CallNode ? "Call, " : "----, "); - for( int i=1; i<nIns()-1; i++ ) - p.n(in(i)).p(","); - return p.unchar(',').unchar(' ').close(); - } - - @Override - public Type compute() { - if( !(in(0) instanceof CallNode call) || call._type != Type.CONTROL ) - return TypeTuple.RET.dual(); - // Mid-fold, just take the one single callers' return type - if( _folding ) return in(1)._type; - // Grab the TFP and use the functions declared return type. - Type ftype = addDep(call.fptr())._type; - Type ret = ftype.isHigh() ? Type.TOP : Type.BOTTOM; - if( ftype 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( 1+tfp.nfcns() == nIns() ) { // A linked function for every concrete function - ret = Type.TOP; - for( int i=1; i<nIns(); i++ ) { - Type tret = in(i)._type instanceof TypeTuple rtt ? rtt.ret() : in(i)._type; - ret = ret.meet(tret); - } - // Recursion is a loop in disguise. For f(x-1)+1, the result - // flows Return -> CallEnd -> Add -> Phi -> Return, without - // crossing ParmNode.compute's widening point. Widen here too, - // using the loop rule in PhiNode.compute. Keep constants and - // above-center ranges narrow, and stay within the return type. - Type oldret = _type instanceof TypeTuple tt ? tt.ret() : Type.TOP; - if( ret instanceof TypeInteger ti && ret != oldret && - !ti.isHigh() && !ti.isConstant() && - (!(oldret instanceof TypeInteger oldi) || ti._widen <= oldi._widen) ) - ret = ti.same_but_slightly_wider_than(TypeInteger.BOT).join(tfp.ret()); - } - } - 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). - // Disallow if still folding other things, as it makes other - // dependency checks carry long chains of half-folded calls. - // Dead control may end the walk before an enclosing function. - CFGNode idom = call; - while( idom!=null ) { - idom = idom.idom(); - if( idom instanceof FunNode ) break; - if( idom instanceof CallEndNode cend && cend._folding ) break; - } - // Inline? - if( idom instanceof FunNode fun2 && fun2 != fun && !fun2._folding ) { - // 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); - // Repeat defs 1 layer down, for users of Parm (Phis) - for( Node parm : fun._outputs ) - if( parm instanceof ParmNode ) - CodeGen.CODE.addAll(parm._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 if( idom!=null ) { - addDep(idom); - } - } else { - addDep(fun); - } - } else { // Function ptr has multiple users (so maybe multiple call sites) - addDep(fptr); - } - } - - return null; - } - - // Clone only tiny, straight-line boilerplate, after ordinary peeps settle. - // Larger bodies still use the existing single-caller, non-cloning inline. - public boolean inlineSmall() { - if( isDead() || _folding || nIns()!=2 || !(in(0) instanceof CallNode call) ) return false; - Node fptr = call.fptr(); - if( !(fptr instanceof ConstantNode) || - !(fptr._type instanceof TypeFunPtr tfp && tfp.notNull() && tfp.isConstant()) ) - { addDep(fptr); return false; } - if( call.err()!=null ) return false; - ReturnNode ret = (ReturnNode)in(1); - FunNode fun = ret.fun(); - if( fun._folding ) return false; - int len = smallBody(fun); - if( len==0 ) return false; - // All callers consume the three normal return projections. - for( Node use : _outputs ) - if( !((use instanceof ProjNode p && p._idx>=0 && p._idx<3) || - (use instanceof CProjNode cp && cp._idx==0)) ) return false; - - // Substitute control and arguments directly; no new function identity, - // parameter Phis, or return address is needed for this straight line. - Node[] copy = new Node[len]; - for( int i=0; i<len; i++ ) { - Node n = TINYBODY[i]; - copy[i] = n==fun ? call.ctrl() : - n instanceof ParmNode parm ? (parm._idx==0 ? null : call.arg(parm._idx)) : - n==ret ? null : n.copyEmpty(); - } - for( int i=0; i<len; i++ ) { - Node n = TINYBODY[i]; - if( n instanceof StoreNode || n instanceof MemMergeNode ) { - for( Node in : n._inputs ) copy[i].addDef(mapped(in,copy)); - CodeGen.CODE.add(copy[i]); - } - } - Node[] result = new Node[3]; - for( int i=0; i<3; i++ ) result[i] = mapped(ret.in(i),copy).keep(); - keep(); - call.unlink_all(); - CodeGen.CODE.add(fun); - CodeGen.CODE.addAll(fun._outputs); - // subsume kills each projection; retain CallEnd until all are replaced. - while( nOuts()>1 ) { - Node use = out(0)==null ? out(1) : out(0); - int idx = use instanceof ProjNode p ? p._idx : ((CProjNode)use)._idx; - CodeGen.CODE.addAll(use._outputs); - use.subsume(result[idx]); - } - unkill(); - for( Node n : result ) n.unkill(); - CodeGen.CODE.invalidateIDepthCaches(); - return true; - } - - private static Node mapped(Node n, Node[] copy) { - for( int i=0; i<copy.length; i++ ) - if( TINYBODY[i]==n ) return copy[i]; - return n; // Constants outside the body are shared. - } - - // Bounded recognition of initializer boilerplate. Walk uses from the entry - // and stop at Return, never following callers or constant inputs. This also - // avoids scanning sparse MemMerge input arrays while a large body shrinks. - // Watch this small prefix, including an unsupported node that may fold. - private static final Node[] TINYBODY = new Node[15]; // Tiny worklist - private int smallBody(FunNode fun) { - ReturnNode ret = fun.ret(); - for( Node in : ret._inputs ) - if( in!=null ) addDep(in); - if( ret.ctrl()!=fun ) return 0; - TINYBODY[0] = fun; - int len=1; - for( int i=0; i<len; i++ ) { - Node n = TINYBODY[i]; - addDep(n); - if( n==ret ) continue; - if( n instanceof ParmNode parm && parm.region()!=fun ) return 0; - if( n!=fun && !(n instanceof ParmNode) && - !(n instanceof StoreNode) && !(n instanceof MemMergeNode) ) - return 0; - if( n.nOuts()>TINYBODY.length ) return 0; - for( Node use : n._outputs ) { - if( use==null ) continue; - int j=0; - while( j<len && TINYBODY[j]!=use ) j++; - if( j<len ) continue; - if( len==TINYBODY.length ) { addDep(use); return 0; } - TINYBODY[len++] = use; - } - } - return len; - } - - @Override public Node pcopy(int idx) { - return _folding ? in(1).in(idx) : null; - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/CallNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/CallNode.java deleted file mode 100644 index e63b3f62..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/CallNode.java +++ /dev/null @@ -1,175 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -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; - -/** - * 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 protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - String fname = name(); - if( fname == null ) p.n(fptr()); - else p.p(fname); - p.p("( "); - for( int i=2; i<nIns()-1; i++ ) - p.n(in(i)).p(","); - return p.unchar(',').unchar(' ').close(); - } - public String name() { - if( fptr()._type instanceof TypeFunPtr tfp && tfp._isConstant() && tfp.notNull() ) { - 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 - public boolean linked( FunNode fun ) { - for( Node n : fun._inputs ) - if( n == this ) - return true; - return false; - } - - // Link so this calls fun - public 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)) || !arg(i+2)._type.accessISA(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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java deleted file mode 100644 index 0afc5827..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/CastMach.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/CastMach.java deleted file mode 100644 index e45898f5..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/CastNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/CastNode.java deleted file mode 100644 index 4e93f3e6..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/CastNode.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -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; - -// 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 boolean isConst() { return true; } - - @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - return p.p(label()).n(in(1)); - } - - @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)._type.accessISA(_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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java deleted file mode 100644 index afdd29be..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -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; - -/** - * 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 protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - return p.p(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.equals(off._name) && _fname.equals(off._fname) && super.eq(n); - } - @Override int hash() { return super.hash() * _name.hashCode() * _fname.hashCode(); } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ConstantNode.java deleted file mode 100644 index 7c330175..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFunPtr; -import com.seaofnodes.simple.util.SB; - -/** - * 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(); - if( type instanceof TypeFunPtr tfp && tfp.isConstant() && tfp.notNull() ) { - FunNode fun = CodeGen.CODE.link(tfp); - if( fun!=null && !fun.isDead() ) return new FunPtrNode(tfp,fun.ret()); - } - return new ConstantNode(type); - } - - @Override protected String repeatName() { - return _con==null || _con.toString().length()<=32 ? null : uniqueName(); - } - - @Override public String label() { return "Con"; } - - @Override - protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - if( _con instanceof TypeFunPtr tfp && tfp._isConstant() && tfp.notNull() ) { - FunNode fun = CodeGen.CODE._link(tfp); - if( fun!=null && fun._name != null ) - return p.p("{ ").p(fun._name).p("}"); - } - return p.p(_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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/CtrlNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/CtrlNode.java deleted file mode 100644 index cf630ad4..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/CtrlNode.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; - -public class CtrlNode extends CFGNode { - public CtrlNode() { super(CodeGen.CODE._start); } - @Override public String label() { return "Ctrl"; } - @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("Cctrl"); } - @Override public boolean isConst() { return true; } - @Override public Type compute() { return Type.CONTROL; } - @Override public Node idealize() { return null; } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/DivFNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/DivFNode.java deleted file mode 100644 index 0d037a30..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/DivFNode.java +++ /dev/null @@ -1,26 +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 ArithFNode { - public DivFNode(Node lhs, Node rhs) { super(lhs, rhs); } - - @Override public String label() { return "DivF"; } - - @Override public String glabel() { return "/"; } - - @Override double doOp( double x, double y ) { return x/y; } - - @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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/DivNode.java deleted file mode 100644 index 7b5ae16c..00000000 --- a/chapter-interprocedural-sccp/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 && in(1)._type instanceof TypeInteger ) - 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ExternNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ExternNode.java deleted file mode 100644 index 7a26e2e9..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ExternNode.java +++ /dev/null @@ -1,25 +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; - if( t instanceof TypeFunPtr tfp ) CodeGen.CODE.externFunc(tfp,ex); - } - - @Override public String label() { return "#"+_con+":"+_extern; } - - @Override public boolean eq(Node n) { return this==n; } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/FRefNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/FRefNode.java deleted file mode 100644 index e7333c23..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/FRefNode.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.Var; -import com.seaofnodes.simple.type.Type; - -/** - * 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 protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - return p.p("FRef_").p(_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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/FunNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/FunNode.java deleted file mode 100644 index 70e1c200..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/FunNode.java +++ /dev/null @@ -1,257 +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; - public boolean folding() { return _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; } - - // Direct calls are known; any other use of an address may have unknown callers. - public boolean hasPtr() { - for( Node use : ret()._outputs ) - if( use instanceof FunPtrNode ) - for( Node user : use._outputs ) - if( !(user instanceof CallNode call) || call.fptr()!=use || - call._inputs.find(use)!=call.nIns()-1 ) return true; - return false; - } - - // 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; - Type t = Type.XCONTROL; - for (int i = 1; i < nIns(); i++) { - // Since no unknown callers, we are not main and the Start input - // will be a Tuple with XControl, so ignore it. Need to be called - // from someplace other than Start - if( !(in(i) instanceof StartNode) ) - t = t.meet(in(i)._type); - } - return t; - } - - @Override - public Node idealize() { - - // Some linked path dies, except main never kills Start - Node progress = deadPath(unknownCallers()); - 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() ) { - setSig(_sig.makeFrom(tt.ret())); - return this; - } - - - // 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 folding (already inlined) down to a single input, become that input. - // Cannot collapse if not-inlining, and might not inline if call site - // calls multiple targets. - if( _folding && 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() : 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; - } - - // FunNodes must match signature (equivalent: no 2 FunNodes are ever GVN'able) - @Override public boolean eq( Node n ) { - return _sig == ((FunNode)n)._sig; - } - - @Override public int hash() { - return _sig.hashCode(); - } - - // ------------ - // 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java deleted file mode 100644 index 43242c32..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.TypeFunPtr; - -// A function address keeps its body alive, even without a linked Call. -public class FunPtrNode extends ConstantNode { - public FunPtrNode(TypeFunPtr tfp, ReturnNode ret) { - super(tfp); - addDef(ret); - } - public ReturnNode ret() { return (ReturnNode)in(1); } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/IfNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/IfNode.java deleted file mode 100644 index c96993e5..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/IfNode.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Utils; - -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 - protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - p.p("if( "); - if( in(1)==null ) p.p("never"); - else p.n(in(1)); - return p.p(" )"); - } - - public Node ctrl() { return in(0); } - public Node pred() { return in(1); } - - // 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 - assert !_inputs.isEmpty(); - 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; - } - if( dom instanceof RegionNode r ) - for( Node c : r._inputs ) - if( c!= null ) addDep(c); // If region loses input, idom changes - } - 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/LoadNode.java deleted file mode 100644 index e057ef1c..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ /dev/null @@ -1,263 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.Parser; -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); - } - - @Override boolean canDrop(MemOpNode other, Node dep) { - return super.canDrop(other,dep) && !clobbered(dep); - } - - // Check only immediate memory users. Writers clobber memory; Phis and - // aggregates might lead to a clobber, so stop rather than search further. - private boolean clobbered(Node dep) { - Node mem = mem(); - dep.addDepForwards(mem); - for( Node use : mem._outputs ) { - if( use==null ) continue; - dep.addDepForwards(use); - if( use instanceof StoreNode || use instanceof PhiNode || - use instanceof MemMergeNode || - use instanceof NewNode || use instanceof CallNode ) return true; - } - return false; - } - - // Debugger label - @Override public String label() { return "ld_"+mlabel(); } - @Override - protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p(".").p(_name); } - - @Override - public Type compute() { - Type tmem = mem()._type; - if( !(tmem instanceof TypeMem mem) ) - return tmem; // No memory yet? Assume TOP/BOT - assert !_declaredType.isFRef(); - // No lifting if ptr might null-check - Type tptr = ptr()._type; - if( err() != null ) - return _declaredType; - if( !(tptr instanceof TypeMemPtr tmp) ) - return tptr; // No pointer yet? Assume TOP/BOT - // Load field from object - Field f = tmp._obj.field(_name); - // No field? Open objects might yet get the field when falling; - // closed objects with missing field are an error. - if( f == null ) - return tmp.isHigh() ? Type.TOP : _declaredType; - Type t = f._t; - // Load member of constant array - 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(MemMergeNode.contents(mem(),_alias,this)); - if( ptr()._type.isFinal() || _declaredType.isFinal() ) - t = t.makeRO(); // Deep final applied - // Pinch between declared type - t = t.join(_declaredType); - return t; - } - - @Override - public Node idealize() { - if( mem() instanceof CastNode cast ) { - setDef(1,cast.in(1)); - return this; - } - if( mem() instanceof MemMergeNode merge ) { - setDef(1,CodeGen.CODE.add(merge.alias(_alias))); - return this; - } - Node ptr = ptr(); - Node mem = mem(); - - Node win = find(mem,ptr,null,null); - if( win!=null ) return folded(win); - - // Uplift control to a prior dominating load. - for( Node memuse : mem._outputs ) - // 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().domLCA(ld.cfg0(),this) == ld.cfg0() ) // Higher control means load is legal earlier - return ld; - - if( ptr instanceof ReadOnlyNode ro ) ptr=ro.in(1); - - // Push a Load up through a Phi, as long as it collapses on at least - // one arm. If at a Loop, the backedge MUST collapse - else we risk - // 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 MemPhiNode memphi && memphi.region()._type == Type.CONTROL && memphi.nIns()== 3 && - // Retain control on both copies only if it dominates both paths. - // The Region itself, or a later range check, cannot move to its inputs. - (in(0)==null || cfg0().domLCA(memphi.region().idom(this),this)==cfg0()) && - // Offset can be hoisted - off() instanceof ConstantNode && - // Pointer can be hoisted - hoistPtr(ptr,memphi) ) { - - // Returning to this memory means no change only if the pointer - // is also loop invariant. A pointer Phi may select another object. - Node stop = memphi.region() instanceof LoopNode && - !(ptr instanceof PhiNode p && p.region()==memphi.region()) ? memphi : null; - if( find(memphi.in(2),ptr,stop,new BitSet())!=null || - (!(memphi.region() instanceof LoopNode) && find(memphi.in(1),ptr,null,new BitSet())!=null) ) { - PhiNode phi = new PhiNode(_name,_declaredType,memphi.region()).keep(); - phi.setType(_type); // Backedge searches may return this value Phi. - for( int i=1; i<memphi.nIns(); i++ ) { - Node p = ptr instanceof PhiNode pp && pp.region()==memphi.region() ? pp.in(i) : ptr; - phi.addDef(load(memphi.in(i),p,stop,phi)); - } - return CodeGen.CODE.add(phi.unkeep()); - } - } - - 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(); - assert zero.isa(_type); // Catch an uninitialized non-null field - return castRO(new ConstantNode(zero).peephole()); - } - - // Search only; no new nodes or rewiring. Return a Store/New that folds, - // the unchanged loop memory, or a merge whose every arm folds. Null fails. - // Register dependencies so a later pointer/offset rewrite retries the query. - private Node find(Node mem, Node ptr, Node stop, BitSet visit) { - if( ptr instanceof ReadOnlyNode ro ) ptr=ro.in(1); - while( mem!=null ) { - addDep(mem); - if( mem==stop ) return mem; - switch( mem ) { - case MemMergeNode merge: mem=merge.alias(_alias); break; - case CastNode cast: mem=cast.in(1); break; - case StoreNode st: - addDep(st.ptr()); - if( _alias==st._alias && ptr==st.ptr() && off()==st.off() ) return st; - if( _alias==st._alias && !neverAlias(ptr,st.ptr()) && - !addDep(off())._type.join(addDep(st.off())._type).isHigh() ) return null; - mem=st.mem(); - break; - case ProjNode proj: - if( !(proj.in(0) instanceof NewNode nnn) ) return null; - if( ptr instanceof ProjNode p && p.in(0)==nnn ) return nnn; - if( !(ptr instanceof ProjNode p && p.in(0) instanceof NewNode) ) return null; - mem=nnn.mem(); // Distinct allocations cannot overlap. - break; - case MemPhiNode phi: - addDep(phi.region()); - // Only the original loop closes a successful cycle. Another - // loop or an unfinished merge needs a separate proof. - if( visit==null || !(phi.region() instanceof RegionNode r) || r instanceof LoopNode || - r.inProgress() || phi.inProgress() || r._type!=Type.CONTROL ) return null; - if( visit.get(phi._nid) ) return phi; // Already proved every arm. - for( int i=1; i<phi.nIns(); i++ ) - if( find(phi.in(i),ptr,stop,visit)==null ) return null; - visit.set(phi._nid); - return phi; - default: return null; - } - } - return null; - } - - private Node folded(Node win) { - return win instanceof StoreNode st ? extend(castRO(st.val())) : zero((NewNode)win); - } - - // Materialize only after the profitability search succeeds. The value Phi - // stands for a load which comes back around the loop with unchanged memory. - private Node load(Node mem, Node ptr, Node stop, PhiNode value) { - Node win=find(mem,ptr,stop,new BitSet()); - if( win!=null ) { - if( win==stop ) return value; - if( win instanceof MemPhiNode mp ) { - PhiNode phi=new PhiNode(_name,_declaredType,mp.region()).keep(); - for( int i=1; i<mp.nIns(); i++ ) phi.addDef(load(mp.in(i),ptr,stop,value)); - return phi.unkeep().peephole(); - } - return folded(win).peephole(); - } - LoadNode ld=new LoadNode(_loc,_name,_alias,_declaredType,mem,ptr,off()); - ld.setDef(0,in(0)); - return ld.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; - } - - // Read-Only is a deep property, and cannot be cast-away - private Node castRO(Node rez) { - if( (ptr()._type.isFinal() || _declaredType.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/LoopNode.java deleted file mode 100644 index 1d493db7..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/LoopNode.java +++ /dev/null @@ -1,104 +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 validIDepth() ? _idepth : cacheIDepth(entry().idepth()+1); } - // 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 FunNode 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 fun; // 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 = fun._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(); - MemMergeNode memout = new MemMergeNode(new ConstantNode(TypeMem.BOT).peephole()); - memout.setDef(0,f); - for( Node u : _outputs ) - if( u instanceof BulkMemPhiNode ) memout.setDef(1,u); - else if( u instanceof MemPhiNode phi ) memout.alias(phi._alias,phi); - memout.init(); - - 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 = fun._ltree; - mem = new BulkMemPhiNode(ScopeNode.MEM0,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 fun; - } - - private static boolean nested(LoopTree inner, LoopTree outer) { - for( ; inner!=null; inner = inner._par ) - if( inner == outer ) - return true; - return false; - } - -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java deleted file mode 100644 index 9c0755b9..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.util.Utils; -import java.io.ByteArrayOutputStream; - -// 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 protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - p.open().p(op()).p(","); - for( int i=1; i<nIns(); i++ ) - (in(i)==null ? p.p(" ---") : p.n(in(i))).p(","); - return p.unchar(',').close(); - } - -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/MachNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/MachNode.java deleted file mode 100644 index c0b3af66..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java deleted file mode 100644 index 7a329117..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.type.*; -import java.util.*; - -/** A default slice and explicit alias overrides. A null default means partial - * memory: absent aliases are not covered, rather than having unknown contents. - */ -public class MemMergeNode extends Node { - public MemMergeNode(MemMergeNode mem) { super(mem); } - - public MemMergeNode(Node bulk) { super(null,bulk); _type = TypeMem.BOT; } - public MemMergeNode(Node bulk, int alias, Node precise) { - this(bulk); - alias(alias,precise); - } - public Node alias(int alias) { - Node n = alias < nIns() ? in(alias) : null; - assert n!=null || !(in(1) instanceof BulkMemPhiNode bulk) || !bulk.isSplit(alias); - n = n==null ? in(1) : n; - assert n!=null : "Alias not covered: "+alias; - return n; - } - - public void alias(int alias, Node mem) { - assert alias > 1; - while( nIns()<=alias ) addDef(null); - setDef(alias,mem); - } - - // Scalar contents of one alias. New zeroes the allocated fields; - // join it with the incoming contents for all previously allocated objects. - // Phis use their cached types, so this query does not recurse around loops. - static Type contents(Node mem, int alias, Node dep) { - // A loop memory Phi can also be a user of the querying Store. - // Its type changing still needs to revisit this query. - dep.addDepForwards(mem); - if( mem instanceof CastNode cast ) return contents(cast.in(1),alias,dep); - if( mem instanceof MemMergeNode merge ) - return contents(merge.alias(alias),alias,dep); - if( mem instanceof ProjNode proj && proj.in(0) instanceof NewNode nnn ) { - assert proj._idx==1 && nnn.field(alias)!=null; - dep.addDep(nnn); - if( dep.addDep(nnn.in(0))._type.isHigh() ) return Type.TOP; - return contents(nnn.mem(),alias,dep).meet(nnn.field(alias)._t.makeZero()); - } - // Function parameters and call results may contain arbitrary heap values. - // Their bulk memory types do not imply empty or zero-filled storage. - if( mem._type==Type.TOP || mem._type==TypeMem.TOP ) return Type.TOP; - if( mem._type instanceof TypeMem mt && mt._alias==alias ) return mt._t; - return Type.BOTTOM; - } - - @Override public String label() { return "MemMerge"; } - @Override public boolean isMem() { return true; } - @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - p.p("MEM["); - for( int i=1; i<nIns(); i++ ) - if( in(i)!=null ) { - p.p(i==1 ? "default:" : i+":"); - p.n(in(i)).p(" "); - } - return p.p("]"); - } - - - @Override public Type compute() { - for( Node n : _inputs ) - if( n != null && !n._type.isHigh() ) - return TypeMem.BOT; - return TypeMem.TOP; - } - - @Override public Node idealize() { - 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(i)!=null && in(i)==in(1) ) { setDef(i,null); progress=true; } - if( in(i) instanceof MemMergeNode mem ) { - setDef(i,CodeGen.CODE.add(mem.alias(i))); - progress=true; - } - if( in(i)!=null ) allDefault=false; - } - if( allDefault && in(1)!=null ) return in(1); - - if( in(1) instanceof MemMergeNode mem ) { - for( int i=2; i<mem.nIns(); i++ ) - if( mem.in(i)!=null && (i>=nIns() || in(i)==null) ) - alias(i,mem.in(i)); - CodeGen.CODE.add(mem.in(1)); - setDef(1,mem.in(1)); - return this; - } - assert BulkMemPhiNode.checkMerge(this); - return progress ? this : null; - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/MemOpNode.java deleted file mode 100644 index e7b2c792..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ /dev/null @@ -1,146 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -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; -import java.util.ArrayList; - -/** - * 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); } - - @Override public boolean isMem() { return !_isLoad; } - - public Node mem() { return in(1); } - public Node ptr() { return in(2); } - public Node off() { return in(3); } - - @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return _printMach(p); } - protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) { throw Utils.TODO(); } - public int log_size() { return _declaredType.log_size(); } - - // Extra conditions for factoring matching memory operations through a Phi. - // The caller has already checked the opcode, input shape and control. - boolean canDrop(MemOpNode other, Node dep) { - dep.addDep(ptr()); - return _alias==other._alias && _declaredType==other._declaredType && err()==null; - } - - @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); - } - // Memory writers which must follow this read. A packaging node is not a - // write, but can hide New's input, so follow this alias through aggregates. - public ArrayList<Node> antiDeps() { - ArrayList<Node> deps = new ArrayList<>(); - antiDeps(mem(),deps,new BitSet()); - return deps; - } - - private void antiDeps(Node mem, ArrayList<Node> deps, BitSet visit) { - if( visit.get(mem._nid) ) return; - visit.set(mem._nid); - for( Node use : mem._outputs ) { - if( use instanceof MemMergeNode merge ) { - Node slice = _alias<merge.nIns() ? merge.in(_alias) : null; - if( (slice==mem) || (slice==null && merge.in(1)==mem) ) - antiDeps(merge,deps,visit); - } else if( use instanceof CallNode || - (use instanceof MemOpNode st && !st._isLoad && st._alias==_alias) || - (use instanceof NewNode nnn && nnn.field(_alias)!=null) || - (use instanceof MemPhiNode phi && phi._alias==_alias) || - (use instanceof BulkMemPhiNode phi && !phi.isSplit(_alias)) ) { - if( !deps.contains(use) ) deps.add(use); - } - } - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java deleted file mode 100644 index 787333d9..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMem; - -/** A Phi for one field alias, parallel to a bulk Phi at the same Region. */ -public class MemPhiNode extends PhiNode { - public final int _alias; - - public MemPhiNode(String label, int alias, Node... inputs) { - super(label,TypeMem.make(alias,Type.BOTTOM),inputs); - assert alias > 1; - _alias = alias; - } - - public MemPhiNode(MemPhiNode phi) { super(phi); _alias = phi._alias; } - - @Override public String label() { return "MemPhi_"+_alias; } - - @Override public Type compute() { - assert BulkMemPhiNode.checkMem(this,_alias); - if( !(region() instanceof RegionNode r) ) - return (region()._type==Type.XCONTROL || region()._type==Type.TOP) ? TypeMem.TOP : _type; - if( r.inProgress() ) return r instanceof LoopNode ? _minType : TypeMem.BOT; - Type t = Type.TOP; - for( int i=1; i<nIns(); i++ ) - if( addDep(r.in(i))._type!=Type.XCONTROL && r.in(i)._type!=Type.TOP ) - t = t.meet(MemMergeNode.contents(in(i),_alias,this)); - return TypeMem.make(_alias,t); - } - - @Override public Node idealize() { - for( int i=1; i<nIns(); i++ ) - if( in(i) instanceof MemMergeNode mem ) { - setDef(i,CodeGen.CODE.add(mem.alias(_alias))); - return this; - } - if( nIns()==3 && region() instanceof LoopNode loop && !loop.inProgress() && - in(2) instanceof StoreNode st && st.mem()==this ) { - Node sink = st.sink(this); - if( sink!=null ) return sink; - } - return super.idealize(); - } - - @Override public boolean eq(Node n) { return _alias==((MemPhiNode)n)._alias && super.eq(n); } - @Override int hash() { return _alias; } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/MinusFNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/MinusFNode.java deleted file mode 100644 index 74439b8c..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/MinusFNode.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; - - -public class MinusFNode extends Node { - public MinusFNode(Node in) { super(null, in); } - - @Override public String label() { return "MinusF"; } - - @Override protected String format() { return "(-%1)"; } - - @Override - public Type compute() { - if( in(1)._type.isHigh() ) return TypeFloat.TOP; - 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/MinusNode.java deleted file mode 100644 index 94c69785..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ /dev/null @@ -1,33 +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; - -public class MinusNode extends Node { - public MinusNode(Node in) { super(null, in); } - - @Override public String label() { return "Minus"; } - - @Override protected String format() { return "(-%1)"; } - - @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,i0._widen); - return TypeInteger.BOT; - } - - @Override - public Node idealize() { - // -(-x) is x - if( in(1) instanceof MinusNode minus && addDep(minus.in(1))._type instanceof TypeInteger ) - return minus.in(1); - - return null; - } - @Override Node copyF() { return new MinusFNode(null); } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/MulFNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/MulFNode.java deleted file mode 100644 index 5c04af43..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/MulFNode.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 MulFNode extends ArithFNode { - public MulFNode(Node lhs, Node rhs) { super(lhs, rhs); } - - @Override public String label() { return "MulF"; } - - @Override public String glabel() { return "*"; } - - @Override double doOp( double x, double y ) { return x*y; } - - @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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/MulNode.java deleted file mode 100644 index 6e1dc053..00000000 --- a/chapter-interprocedural-sccp/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._type instanceof TypeInteger ? lhs : null; - 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/MultiNode.java deleted file mode 100644 index b4d6d78e..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/NeverNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/NeverNode.java deleted file mode 100644 index 66879b66..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/NeverNode.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeTuple; - - -// "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 protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("Never"); } - - @Override public Type compute() { return TypeTuple.IF_BOTH; } - - @Override public Node idealize() { return null; } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/NewNode.java deleted file mode 100644 index 840d2759..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; - - -/** Allocate a zeroed object. Inputs {ctrl, $mem, size}; - * results {ptr, $mem}. - * The memory input and result cover only the aliases in the allocated struct. - */ -public class NewNode extends Node implements MultiNode { - - public final TypeMemPtr _ptr; - - public NewNode(TypeMemPtr ptr, Node... nodes) { - super(nodes); - assert !ptr.nullable(); - _ptr = ptr; - assert nodes.length==3; - assert nodes[0]._type==Type.CONTROL || nodes[0]._type==Type.XCONTROL; - assert nodes[1]._type instanceof TypeMem; - assert nodes[2]._type instanceof TypeInteger || nodes[2]._type==Type.NIL; - } - - public Node mem() { return in(1); } - public Node size() { return in(2); } - - public Field field(int alias) { - for( Field f : _ptr._obj._fields ) - if( !f._one && f._alias==alias ) return f; - return null; - } - - public NewNode(NewNode nnn) { super(nnn); _ptr = nnn._ptr; } - - - @Override public String label() { return "new_"+(_ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._t.str() : _ptr._obj.str()); } - @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - return p.p("new ").p(_ptr._obj.str()); - } - - - @Override - public TypeTuple compute() { - return in(0)._type.isHigh() - ? TypeTuple.make(_ptr.makeHigh(),TypeMem.TOP) - : TypeTuple.make(_ptr,TypeMem.BOT); - } - - @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==2 ? _arg3Mask : null; } - public RegMask outregmap() { return null; } - public RegMask outregmap(int idx) { return idx==0 ? _retMask : null; } - public RegMask killmap() { return _kills; } - public void asm(CodeGen code, SB sb) { - sb.p("#calloc, ").p(code.reg(size())); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/Node.java deleted file mode 100644 index 414530b4..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/Node.java +++ /dev/null @@ -1,693 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.BaseNode; - -import com.seaofnodes.simple.IterPeeps; -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.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 extends BaseNode<Node> implements Cloneable { - - /** - * 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) { - super(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 ) { - super(CODE.getUID()); // allocate unique dense ID - assert CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Select.ordinal(); - _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(); - - public String p(int depth) { return IRPrinter.prettyPrint(this,depth); } - - public boolean isConst() { return false; } - - // ------------------------------------------------------------------------ - // Graph Node & Edge manipulation - - @Override public String typeName() { return _type==null ? null : _type.toString(); } - - /** - * 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 - public void insertDef(int idx, Node new_def) { - _inputs.insert(null,idx); - 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 - protected <N extends Node> void addUse(N n) { _outputs.add(n); } - - // 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. - public boolean delUse( Node use ) { - _outputs.del(_outputs.find(use)); - moveDepsToWorklist(); // User-count and anti-dependence queries can now change. - 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 ) { - if( CODE.add(old_def).delUse(this) ) - old_def.kill(); // If we removed the last use, the old def is now dead - old_def.moveDepsToWorklist(); // Use-count changes can enable distant rewrites. - } - } - assert isDead(); // Really dead now - } - - // Preserve CFG use-ordering when killing - public void killOrdered() { - CFGNode cfg = cfg0(); - cfg._outputs.remove(cfg._outputs.find(this)); - _inputs.set(0,null); - kill(); - } - - // Mostly used for asserts and printing. - public boolean isDead() { return isUnused() && nIns()==0 && _type==null; } - - // Shortcuts to stop DCE mid-parse - // Add bogus null use to keep node alive - @SuppressWarnings("unchecked") - public <N extends Node> N keep() { addUse(null); return (N)this; } - // 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); - n.moveDepsToWorklist(); // Rewiring can change a dependent query without changing type. - 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() { - var obs = CodeGen.CODE._midAssert ? null : CodeGen.CODE._obs; - if( obs != null ) obs.before(this); - Node n = peepholeOpt(); - Node rez = n == null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n); - if( obs != null ) obs.after(this, n == null ? null : rez, false); - return rez; - } - - /** - * 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) : "Monotonicity test failed"; - 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; - public int nDeps() { return _deps == null ? 0 : _deps.size(); } - public Node dep(int idx) { return _deps.get(idx); } - - /** - * Add a node to the list of dependencies. Only add it if it's 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 ) { return addDep(dep,false); } - - // Keep an explicit dependency when inspecting an immediate user's shape. - <N extends Node> N addDepForwards(N dep) { return addDep(dep,true); } - - private <N extends Node> N addDep(N dep, boolean forwards) { - // Running peepholes during the big assert cannot have side effects - // like adding dependencies. - if( CODE._midAssert ) return dep; - var obs = CodeGen.CODE._midAssert ? null : CodeGen.CODE._obs; - if( obs != null ) obs.dep(this, dep); - if( dep._deps==null ) dep._deps = new Ary<>(Node.class); - if( dep._deps .find(this) != -1 ) return dep; // Already on list - if( !forwards && dep._inputs .find(this) != -1 ) return dep; // No need for deps on immediate neighbors - if( !forwards && dep._outputs.find(this) != -1 ) return dep; - dep._deps.add(this); - return dep; - } - - // Move the dependents onto a worklist, and clear for future dependents. - public void moveDepsToWorklist( ) { moveDepsToWorklist(CODE._iter); } - public void moveDepsToWorklist( IterPeeps iter ) { - if( _deps == null ) return; - iter.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; } - - // 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() { return copyEmpty(); } - - // Exact-class copy with fresh identity and no edges. Unlike machine - // rematerialization copies, this never registers or copies input edges. - public final Node copyEmpty() { - 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/NotNode.java deleted file mode 100644 index 775b069b..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Utils; - -public class NotNode extends Node { - public NotNode(Node in) { super(null, in); } - - @Override public String label() { return "Not"; } - - @Override protected String format() { return "(!%1)"; } - - @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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/OrNode.java deleted file mode 100644 index d16dd194..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/OrNode.java +++ /dev/null @@ -1,42 +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 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 && lhs._type instanceof TypeInteger ) - 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ParmNode.java deleted file mode 100644 index cc654abb..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ParmNode.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.type.TypeMemPtr; - -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); } - - public FunNode fun() { return (FunNode)in(0); } - - @Override - protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - if( "main".equals(fun()._name) && _label.equals("arg") ) - return p.p("arg"); - p.p("Parm_").p(_label).open(); - for( Node in : _inputs ) p.n(in).p(","); - return p.unchar(',').close(); - } - - - // Always in-progress until we run out of unknown callers - @Override public boolean inProgress() { return in(0) instanceof FunNode fun && fun.inProgress(); } - - @Override - public Type compute() { - Type t = super.compute(); - // Recursion is a loop in disguise. A Parm merges the first argument - // with those from recursive calls, just as a loop Phi merges its entry - // and backedge. Use the widening rule in PhiNode.compute here too. - // CallEndNode.compute handles the separate cycle through return values; - // widening this Parm alone cannot stop f(x-1)+1 from growing its result. - if( t instanceof TypeInteger ti && t != _type && - !ti.isHigh() && !ti.isConstant() && - (!(_type instanceof TypeInteger oldi) || ti._widen <= oldi._widen) ) - return ti.same_but_slightly_wider_than(_minType); - return t; - } - - @Override - public Node idealize() { - if( !(region() instanceof FunNode) ) - return in(1); // Input has collapse to e.g. starting control. - // If function is folding, do all possible peeps - if( fun()._folding ) return super.idealize(); - - // Can upgrade minType even while in-progress - if( _minType instanceof TypeMemPtr tmp && _minType.isFRef() ) { - TypeMemPtr tmp2 = (TypeMemPtr) Parser.TYPES.get(tmp._obj._name); - if( tmp2!=null && tmp2 != _minType ) { - _minType = tmp2; - return this; - } - } - // Skip most phi optimizations on parms - return null; - } - - @Override public boolean eq( Node n ) { - return ((ParmNode)n)._idx==_idx && super.eq(n); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/PhiNode.java deleted file mode 100644 index 7f2bb1f4..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ /dev/null @@ -1,311 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Utils; - -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; - - int lattice_drop; - - 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); - } - - public static PhiNode make(String label, Type type, Node... inputs) { - return type instanceof TypeMem ? new BulkMemPhiNode(label,inputs) : new PhiNode(label,type,inputs); - } - - @Override public String label() { return "Phi_"+MemOpNode.mlabel(_label); } - - @Override - protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - if( !(region() instanceof RegionNode r) || r.inProgress() ) - p.p("Z"); - p.p("Phi("); - for( Node in : _inputs ) p.n(in).p(","); - return p.unchar(',').close(); - } - - public CFGNode region() { return (CFGNode)in(0); } - @Override public boolean isMem() { return _minType instanceof TypeMem; } - boolean isRPC() { return false; } - - @Override - public Type compute() { - if( !(region() instanceof RegionNode r) ) - return region()._type==Type.XCONTROL || region()._type==Type.TOP ? (_type instanceof TypeMem ? TypeMem.TOP : Type.TOP) : _type; - // During parsing Phis have to be computed type pessimistically. - if( r.inProgress() || in(nIns()-1)==null ) - // Loop-Phis must lift to the declared type, because that is how - // the Parser keeps precise types until the loop finishes parsing. - // Similar, ParmNodes use precise minType until all calls are - // linked (post opto). - return r instanceof LoopNode || (this instanceof ParmNode) ? _minType : Type.BOTTOM; - // Set type to local top of the starting type - 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. - Type ctrl = addDep(r.in(i))._type; - if( ctrl != Type.XCONTROL && ctrl != Type.TOP ) { - if( in(i)._type==Type.BOTTOM ) - return Type.BOTTOM; - t = t.meet(in(i)._type); - } - } - Type newt = t.join( _minType ); - - // Widen loop Phis so integer ranges cannot grow one step per trip forever. - // Recursion is a loop in disguise: ParmNode.compute widens arguments, - // and CallEndNode.compute widens results. Both are needed because the - // recursive result can feed itself without passing through a Parm. - // Above-center ranges may still fall to unwidened constants. - // Widen only below-center ranges to preserve monotonicity. - if( region() instanceof LoopNode && // Only around loops - newt instanceof TypeInteger newi && - // Types changed and are falling (the optimistic case, expected to fall forever) - newi != _type ) { - if( !newi.isHigh() && !newi.isConstant() && (!(_type instanceof TypeInteger oldi) || newi._widen <= oldi._widen) ) - return newi.same_but_slightly_wider_than(_minType); - } - - return newt; - } - - @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) Parser.TYPES.get(tmp._obj._name); - if( tmp2!=null && tmp2._obj != tmp._obj ) { - _minType = tmp.makeFrom(tmp2._obj); - 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; - // Let the bulk input split this alias before collapsing a precise Phi. - if( live instanceof BulkMemPhiNode ) return null; - // 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; - - // Phi(op(a,b,...),op(c,d,...)) -> op(Phi(a,c),Phi(b,d),...). - Node down; - if( same_op() && (down=drop_same_op())!=null ) return down; - - // 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); - Node ridom = r.idom(this); - if( ridom 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; - } else if( ridom != null ) addDep(ridom); - } - } - - return null; - } - - // Same op on all Phi paths; all ops have only the Phi as a use. - // None have a control input. - private boolean same_op() { - Node op = in(1); - if( op instanceof CFGNode || op instanceof ConstantNode || op instanceof PhiNode || - op instanceof ProjNode || op instanceof NewNode || op instanceof ScopeNode || op instanceof MemMergeNode ) return false; - // A bulk Phi must split its aliases before factoring precise Stores. - if( this instanceof BulkMemPhiNode ) return false; - // Bulk splitting currently identifies parallel slices by Region/alias. - // Factoring can introduce another slice at a different memory point; - // wait until that Region's bulk partitioning has finished. - if( op instanceof MemOpNode ) - for( Node use : region()._outputs ) - if( use instanceof BulkMemPhiNode ) { - addDepForwards(use); - return false; - } - Node busy = null; - for( int i=1; i<nIns(); i++ ) { - Node n = in(i); - if( addDep(region().in(i))._type==Type.XCONTROL || - op.getClass()!=n.getClass() || n.nIns()!=op.nIns() || n.in(0)!=null || !op.eq(n) ) return false; - if( n instanceof MemOpNode mem && !mem.canDrop((MemOpNode)op,this) ) return false; - // Moving a Store must remove the old effect, not duplicate it. - // Allow one shared arm: the other arms disappear into the single - // factored operation, usually reducing the total operation count. - addDepForwards(n); - if( n.nOuts()>1 ) { - for( Node use : n._outputs ) - if( use!=null && use!=this ) addDepForwards(use); - if( n instanceof StoreNode || busy!=null ) return false; - busy=n; - } - for( int j=1; j<n.nIns(); j++ ) - if( n.in(j) instanceof ScopeNode || ((n.in(j)==null) != (op.in(j)==null)) ) return false; - } - return true; - } - - private Node drop_same_op() { - Node op = in(1), cp = op.copyEmpty(); - cp._type = null; - cp.addDef(null); - for( int j=1; j<op.nIns(); j++ ) { - Node x = op.in(j); - boolean different = false; - for( int i=2; i<nIns(); i++ ) - if( in(i).in(j)!=x ) different=true; - if( different ) { - Node[] ins = new Node[nIns()]; - ins[0] = region(); - Type t = Type.TOP; - for( int i=1; i<nIns(); i++ ) { - ins[i] = in(i).in(j); - t = t.meet(ins[i]._type); - } - // Not accepts both pointers and integers, but a Phi cannot mix them. - if( t==Type.BOTTOM ) { - for( int i=1; i<ins.length; i++ ) addDep(ins[i]); - cp.kill(); - return null; - } - PhiNode phi = j==1 && op instanceof MemOpNode mem - ? new MemPhiNode(_label,mem._alias,ins) - : PhiNode.make(_label,t.glb(false),ins); - x = phi.peephole(); - } - cp.addDef(x); - } - // Factoring must not widen the result (e.g. correlated And operands). - if( cp.compute().isa(compute()) ) return cp; - for( int i=1; i<nIns(); i++ ) - for( int j=1; j<in(i).nIns(); j++ ) - if( in(i).in(j)!=null ) addDep(in(i).in(j)); - 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ProjNode.java deleted file mode 100644 index 92c6c2be..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ProjNode.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMem; -import com.seaofnodes.simple.type.TypeTuple; - - -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 - protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - if( _label != null ) return p.p(_label); - if( in(0) instanceof CallEndNode cend && cend.call()!=null ) - return p.n(cend.call()); - return p.p("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 Type compute() { - Type t = in(0)._type; - return t instanceof TypeTuple tt ? tt._types[_idx] : t.oob(); - } - - @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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ReadOnlyMach.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ReadOnlyMach.java deleted file mode 100644 index a35f5a1d..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java deleted file mode 100644 index 94c1b6f6..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMemPtr; - -/** - * 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 protected String format() { return "(const)%1"; } - @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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/RegionNode.java deleted file mode 100644 index 59030dee..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ /dev/null @@ -1,174 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -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 protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - return p.p(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(false); - 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 && - addDepForwards(p1).in(0)==addDepForwards(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 stacked regions (no loops involved) - if( getClass() == RegionNode.class && nIns()>2 ) { - 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(boolean skipStart) { - // Delete dead paths into a Region - int path = findDeadInput(skipStart ? 2 :1); - 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; - return removeDeadPath(path); - } - - public Node removeDeadPath(int path) { - // 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.in(path)._outputs); - CodeGen.CODE.addAll(phi.delDef(path)._outputs); - } - } - return isDead() ? Parser.XCTRL : delDef(path); - } - - private int findDeadInput(int startIdx) { - for( int i=startIdx; 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); if( data!=null ) 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( validIDepth() ) return _idepth; - int depth=0; - for( Node n : _inputs ) - if( n!=null ) - depth = Math.max(depth,((CFGNode)n).idepth()+1); - return cacheIDepth(depth); - } - - @Override public CFGNode idom(Node dep) { - CFGNode lca = null; - // Recompute from predecessors: CFG edits can change the dominator. - for( int i=1; i<nIns(); i++ ) - if( !cfg(i)._type.isHigh() ) - lca = cfg(i).domLCA(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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ReturnNode.java deleted file mode 100644 index 266657be..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ /dev/null @@ -1,163 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -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; - -/** Keeps all preceding memory effects and the return value alive: {ctrl, $mem, value, rpc}. */ -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 - protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - p.p("return "); - if( expr()==null ) p.p("----"); - else p.n(expr()); - return p.p(";"); - } - - // 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 - // Inlining can delete the entry while callers still use this return. - return TypeTuple.make(ctrl()._type,TypeMem.BOT,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(); - } - - // Remember parsed type kinds for diagnostics; the optimized expression decides validity. - 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; - 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() { - if( ctrl()._type != Type.CONTROL ) return null; // Exit path is dead - return expr()._type==Type.BOTTOM || expr()._type==Type.TOP ? 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 ) - // Hit when the function never returns. e.g. `f = { -> return f() };` - return Parser.error("No defined return type", loc); - // Rather ugly way to print conflicting return types - int cnt = (ti?1:0) + (tf?1:0) + (tp||tn?1:0); - if( cnt==1 ) return null; // Uniform return type, something else errored and will report - 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java deleted file mode 100644 index 10a0e4aa..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; - - -public class RoundF32Node extends Node { - public RoundF32Node(Node lhs) { super(null, lhs); } - - @Override public String label() { return "RoundF32"; } - - @Override protected String format() { return "((f32)%1)"; } - - @Override - public Type compute() { - if (in(1)._type instanceof TypeFloat i0 && i0.isConstant() ) - return TypeFloat.constant((float)i0.value()); - if( in(1)._type==TypeFloat.F64 ) return TypeFloat.F32; - 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/SarNode.java deleted file mode 100644 index 429be229..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/SarNode.java +++ /dev/null @@ -1,36 +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 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 && lhs._type instanceof TypeInteger ) - 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ScopeNode.java deleted file mode 100644 index 307fc0f9..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ /dev/null @@ -1,530 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -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 variable records. - */ -public class ScopeNode extends Node { - - /** - * 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"; - - public Node in(Var v) { return in(v._idx); } - - @Override public Type compute() { return Type.BOTTOM; } - @Override public Node idealize() { return null; } - - // All active/live variables in all nested scopes, all run together - 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() { - _type = Type.BOTTOM; - _vars = new Ary<>(Var .class); - _kinds = new Ary<>(Kind .class); - _guards = new Ary<>(Node .class); - } - - @Override public String label() { return "Scope"; } - - @Override - protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - p.p("Scope[ "); - int j=1; - for( int i=0; i<nIns(); i++ ) { - if( j < depth() && i == _kinds.at(j)._lexSize ) { p.p("| "); j++; } - Var v = _vars.at(i); - p.p(v._type()); - p.p(" "); - if( v._final ) p.p("!"); - p.p(v._name); - p.p("="); - Node n = in(i); - while( n instanceof ScopeNode loop ) { - p.p("Lazy_"); - n = loop.in(i); - } - p.n(n); - p.p(", "); - } - return p.unchar(", ").p("]"); - } - - - public Node ctrl() { return in(0); } - public Node mem() { return in(lookup(MEM0)); } - 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) { update(MEM0,n); return 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; - } - } - - - // Allocation fields live in the scope immediately outside the constructor - // block. Nested blocks retain that permission; surrounding bindings do not. - public boolean canInit(Var v) { - for( int i=_kinds._len-1; i>0; i-- ) - if( _kinds.at(i) instanceof Kind.Alloc ) - return v._idx >= _kinds.at(i-1)._lexSize && v._idx < _kinds.at(i)._lexSize; - return false; - } - - 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; - } - - /** - * 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,PhiNode.make(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 is one binding, with the same lazy loop Phi as scalar names. - for( int i=1; i<nIns(); i++ ) - dup.addDef(loop && !_vars.at(i)._final ? this : in(i)); - return dup; - } - - /** - * Merges the names whose node bindings differ, by creating Phi node for such names - * The names could occur at all stack levels, but a given name can only differ in the - * innermost stack level where the name is bound. - * - * @param that The ScopeNode to be merged into this - * @return A new node representing the merge point - */ - public RegionNode mergeScopes(ScopeNode that, Parser.Lexer loc) { - RegionNode r = ctrl(new RegionNode(loc,null,ctrl(), that.ctrl()).keep()); - _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 = 1; 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, PhiNode.make(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()); - - _endLoop(this,back,exit); - back.kill(); // Loop backedge is dead - // Now one-time do a useless-phi removal - _useless(); - } - - // Fill in the backedge of any inserted Phis - void _endLoop( ScopeNode scope, Node back, Node exit ) { - for( int i=1; i<nIns(); i++ ) { - if( _vars.at(i)._final ) continue; // Final vars did not get modified in the loop - if( _vars.at(i).type().isHighOrConst() ) continue; // Cannot lift higher than a constant, so no Phi - if( back.in(i) != scope ) { - PhiNode phi = (PhiNode)in(i); - assert phi.region()==scope.ctrl() && phi.in(2)==null; - phi.setDef(2,back.in(i)); // Fill backedge - } - if( exit.in(i) == scope ) // Replace a lazy-phi on the exit path also - exit.setDef(i,in(i)); - } - } - - - // Now one-time do a useless-phi removal - void _useless( ) { - for( int i=1; 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 - } - } - } - } - - // 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 between guard sets - // add pred & its cast to the normal input list, with special Vars - if( pred==null || pred.isDead() ) - return; // Dead, do not add any guards - _addGuards(ctrl,pred,invert); - } - - private void _addGuards( Node ctrl, Node pred, boolean invert ) { - if( pred==null || pred.isDead() ) - return; - if( !availableAt(pred,(CFGNode)ctrl,new BitSet()) ) - return; - // A single negation is handled below. For !!p (including p != null) - // or a negated short-circuit Phi, also discover the underlying facts. - if( pred instanceof NotNode not && - (not.in(1) instanceof NotNode || not.in(1) instanceof PhiNode) ) { - pred.keep(); // Recursive peepholes may rediscover this Not. - _addGuards(ctrl,not.in(1),!invert); - pred.unkeep(); - } - // A false || or true && proves both operands. Only guard values - // available at this control, not ones confined to the RHS branch. - if( pred instanceof PhiNode phi && phi.nIns()==3 && phi.region() instanceof RegionNode r && r.nIns()==3 ) - for( int i=1; i<3; i++ ) - if( r.in(i) instanceof CProjNode prj && prj.ctrl() instanceof IfNode iff ) { - Node skip = shortCircuitSkippedPred(phi.in(i),iff.pred(),prj,invert); - if( skip != null ) { - _addGuards(ctrl,skip,invert); - _addGuards(ctrl,phi.in(3-i),invert); - break; - } - } - // 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); - } - } - - // A Phi's value is available at its Region. Other data nodes require all - // their inputs, including pinned control, to dominate the proposed guard. - private static boolean availableAt(Node n, CFGNode ctrl, BitSet visit) { - if( n==null || n instanceof ConstantNode || visit.get(n._nid) ) return true; - visit.set(n._nid); - if( n instanceof PhiNode phi ) n = phi.region(); - if( n instanceof CFGNode cfg ) { - for( CFGNode dom=ctrl; dom!=null; dom=dom.idom() ) - if( dom==cfg ) return true; - return false; - } - for( Node def : n._inputs ) - if( !availableAt(def,ctrl,visit) ) return false; - return true; - } - - private static Node shortCircuitSkippedPred( Node val, Node pred, CProjNode prj, boolean invert ) { - if( val == null ) return null; - if( val == pred ) - return (prj._idx==0)==invert ? val : null; - if( val instanceof NotNode not && not.in(1)==pred ) - return (prj._idx==1)==invert ? val : null; - if( val._type == TypeInteger.TRUE && invert ) - return prj._idx==0 ? pred : CodeGen.CODE.add(new NotNode(pred).peephole()); - if( val._type == TypeInteger.FALSE && !invert ) - return prj._idx==1 ? pred : CodeGen.CODE.add(new NotNode(pred).peephole()); - return null; - } - - 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; - // Pop the guards up to this region's marker. - 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ShlNode.java deleted file mode 100644 index e2951784..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ShlNode.java +++ /dev/null @@ -1,48 +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 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 && lhs._type instanceof TypeInteger ) - 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ShrNode.java deleted file mode 100644 index 0023431f..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ShrNode.java +++ /dev/null @@ -1,32 +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 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 && lhs._type instanceof TypeInteger ) - 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/SplitNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/SplitNode.java deleted file mode 100644 index a7fe6c5f..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/SplitNode.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.util.SB; - -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; } - // A register-only transfer may need another split to reach stack storage. - public boolean isNonStackSplit() { return false; } - @Override public String op() { return "mov"; } - @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - p.p("mov("); - if( in(1) == null ) p.p("---"); - else p.n(in(1)); - return p.close(); - } - @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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/StartNode.java deleted file mode 100644 index e13d35c0..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.*; -import java.util.HashSet; -import static com.seaofnodes.simple.util.Utils.TODO; - -/** Start supplies control, whole memory, and the argument, in that order. */ -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 protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - return p.p(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.BOT,_arg); - } - - @Override public Node idealize() { return null; } - - // No immediate dominator, and idepth==0 - @Override public int idepth() { return 0; } - @Override public CFGNode idom(Node dep) { return null; } - -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/StopNode.java deleted file mode 100644 index 9f7a925b..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.util.Utils; - -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 - protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - // For the sake of many old tests, and single value prints as "return val" - ReturnNode ret1 = ret(); - if( ret1!=null ) return p.n(ret1); - p.p("Stop[ "); - for( Node ret : _inputs ) - if( ret!=null ) { - String name = ((ReturnNode)ret).fun()._name; - if( name== null || !name.startsWith("sys.") ) - p.n(ret).p(" "); - } - return p.p("]"); - } - - @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( validIDepth() ) return _idepth; - int d=0; - for( Node ret : _inputs ) - d = Math.max(d,((ReturnNode)ret).idepth()+1); - return cacheIDepth(d); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/StoreNode.java deleted file mode 100644 index 47f12d82..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ /dev/null @@ -1,183 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.type.*; - -/** - * 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; - } - - // Debugger label - @Override boolean canDrop(MemOpNode other, Node dep) { - return super.canDrop(other,dep) && _init==((StoreNode)other)._init; - } - - @Override public String label() { return "st_"+mlabel(); } - @Override public boolean isMem() { return true; } - - public Node val() { return in(4); } - - @Override - protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - return p.p(".").p(_name).p("=").p( val()).p(";"); - } - - @Override - public Type compute() { - Type val = val()._type; - assert !_declaredType.isFRef(); - Type t = val.join(_declaredType).meet(MemMergeNode.contents(mem(),_alias,this)); - return TypeMem.make(_alias,t); - } - - @Override - public Node idealize() { - if( mem() instanceof MemMergeNode merge ) { - setDef(1,CodeGen.CODE.add(merge.alias(_alias))); - return this; - } - - - 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() && _alias==st._alias && // Must check same object - off()==st.off() && // And same offset - ptr()._type instanceof TypeMemPtr && // No bother if weird dead pointers - // Must have exactly one use of "this" or you get weird - // non-serializable memory effects in the worse case. - checkOnlyUse(st) ) { - assert _name.equals(st._name); // Equiv class aliasing is perfect - setDef(1,st.mem()); - return this; - } - - // Value is automatically truncated by narrow store - if( val() instanceof AndNode and && and.in(2)._type.isConstant() ) { - int log = _declaredType.log_size(); - if( log<3 ) { // And-mask vs narrow store - long mask = ((TypeInteger)and.in(2)._type).value(); - long bits = (1L<<(8<<log))-1; - // Mask does not mask any of the stored bits - if( (bits&mask)==bits ) - // So and-mask is already covered by the store - { setDef(4,and.in(1)); return this; } - } - } - - // 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; - } - - // Phi(mem, Store(phi,ptr,off,val)) -> Store(mem,ptr,off,Phi(load(mem),val)). - // With no observers of the backedge store, only the last value matters. - // The entry load preserves the field even when the loop takes zero trips. - Node sink(MemPhiNode phi) { - phi.addDepForwards(this); - if( nOuts()!=1 ) return null; - phi.addDep(ptr()); - // Start with a fixed field of an allocation dominating the loop. - // In particular, do not sink stores through a loop-varying address. - if( !(ptr() instanceof ProjNode p) || !(p.in(0) instanceof NewNode obj) || - !(ptr()._type instanceof TypeMemPtr tmp) || tmp._obj.isAry() || - !(off() instanceof ConstantNode) || err()!=null ) return null; - LoopNode loop = (LoopNode)phi.region(); - // Bulk splitting identifies precise slices by Region/alias. Finish it - // before replacing this memory point with a Store. - for( Node use : loop._outputs ) - if( use instanceof BulkMemPhiNode ) { - phi.addDepForwards(use); - return null; - } - phi.addDep(obj); - CFGNode ctrl = loop.entry(); - while( ctrl!=null && ctrl!=obj.cfg0() ) { - phi.addDep(ctrl); - ctrl=ctrl.idom(phi); - } - if( ctrl==null ) return null; - - Node mem = phi.in(1); - Node init = new LoadNode(_loc,_name,_alias,_declaredType,mem,ptr(),off()).peephole(); - Node value = CodeGen.CODE.add(new PhiNode(_name,_declaredType,loop,init,val()).peephole()); - Node sink = new StoreNode(_loc,_name,_alias,_declaredType,mem,ptr(),off(),value,_init); - // Break the memory cycle, killing the old store. Keep the Phi alive - // even if that store was its last use, until our caller replaces it. - phi.keep(); - phi.setDef(2,mem); - phi.unkeep(); - return sink; - } - - // 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; - if( ptr()._type == Type.TOP ) - return null; // This means we have an error input, report elsewhere - TypeMemPtr tmp = (TypeMemPtr)ptr()._type; - if( (tmp._ro || tmp._obj.field(_name)._final) && !_init ) - return Parser.error("Cannot modify final field '"+_name+"'",_loc); - return null; - } - - // Do not GVN Stores. Two equal Stores on two of three paths are the easy - // failure: sharing them makes GCM place the Store on all three paths. - // Sharing is safe only if every exit path goes through an original Store, - // or GCM splits the shared Store to keep it on those paths. Stores must - // sink, not run on extra paths. GVN does not check this; GCM does not split. - // Identity also disables generic Phi factoring of Stores. - // The closed-loop Store sink is a separate peep and still applies. - @Override public boolean eq(Node n) { return this == n; } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/StructNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/StructNode.java deleted file mode 100644 index 78b20e9f..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/StructNode.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.SB; - -/** - * 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 - protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - if( _ts==null ) return p.p("STRUCT?"); - p.p(_ts._name).p(" {"); - for( int i=0; i<nIns(); i++ ) { - p.p(_ts._fields[i]._fname).p(":"); - p.p(in(i)==null ? Type.BOTTOM : in(i)._type); - p.p("; "); - } - return p.unchar("; ").p("}"); - } - - @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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/SubFNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/SubFNode.java deleted file mode 100644 index 3fc4f7ca..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/SubFNode.java +++ /dev/null @@ -1,27 +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 ArithFNode { - public SubFNode(Node lhs, Node rhs) { super(lhs, rhs); } - - @Override public String label() { return "SubF"; } - - @Override public String glabel() { return "-"; } - - @Override double doOp( double x, double y ) { return x-y; } - - @Override - public Node idealize() { - // Sub of 0. - Type t2 = in(2)._type; - if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==0 ) - return in(1); - - return null; - } - @Override Node copy(Node lhs, Node rhs) { return new SubFNode(lhs,rhs); } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/SubNode.java deleted file mode 100644 index 2c4fed39..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ /dev/null @@ -1,54 +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() { - Node lhs = in(1); - Node rhs = in(2); - Type t1 = lhs._type; - Type t2 = rhs._type; - - // Keep invalid operands for type checking. - if( t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) { - if( y.isConstant() && y.value()==0 ) return lhs; - if( x.isConstant() && x.value()==0 ) return new MinusNode(rhs); - } - - // x - (-y) is x+y - if( rhs instanceof MinusNode minus ) - return new AddNode(lhs,minus.in(1)); - - // (-x) - y is -(x+y) - if( lhs instanceof MinusNode minus ) - return new MinusNode(new AddNode(minus.in(1),rhs).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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java deleted file mode 100644 index 840a95ec..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java +++ /dev/null @@ -1,27 +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; - - -public class ToFloatNode extends Node { - public ToFloatNode(Node lhs) { super(null, lhs); } - - @Override public String label() { return "ToFloat"; } - - @Override protected String format() { return "(flt)%1"; } - - @Override - public Type compute() { - if( in(1)._type.isHigh() ) return TypeFloat.F64.dual(); - if( in(1)._type == Type.NIL ) return TypeFloat.FZERO; - if( in(1)._type instanceof TypeInteger i0 && 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java deleted file mode 100644 index df740202..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java +++ /dev/null @@ -1,23 +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; - -// Truncate toward zero, saturating overflow; NaN converts to zero. -public class ToIntegerNode extends Node { - public ToIntegerNode(Node lhs) { super(null,lhs); } - @Override public String label() { return "ToInteger"; } - @Override protected String format() { return "(int)%1"; } - - @Override public Type compute() { - Type t = in(1)._type; - if( t.isHigh() ) return TypeInteger.TOP; - if( t instanceof TypeFloat tf && tf.isConstant() ) - return TypeInteger.constant((long)tf.value()); - return TypeInteger.BOT; - } - - @Override public Node idealize() { return null; } - @Override Node copy(Node lhs, Node rhs) { return new ToIntegerNode(lhs); } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java deleted file mode 100644 index 477b9af8..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; - -public class XCtrlNode extends CFGNode { - public XCtrlNode() { super(new Node[]{CodeGen.CODE._start}); } - @Override public String label() { return "Xctrl"; } - @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("Xctrl"); } - @Override public boolean isConst() { return true; } - @Override public Type compute() { return Type.XCONTROL; } - @Override public Node idealize() { return null; } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/XorNode.java deleted file mode 100644 index 75cea98e..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/XorNode.java +++ /dev/null @@ -1,42 +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 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 && lhs._type instanceof TypeInteger ) - 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java deleted file mode 100644 index 76e47ca2..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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,Arm64.OP_ADD); } - // General form: "rd = rs1 + rs2" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2))); - } - -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java deleted file mode 100644 index 67a10b48..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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,Arm64.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java deleted file mode 100644 index 4c635d07..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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), Arm64.OPI_ADD,_imm); - } - // General form: "addi rd = rs1 + imm" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + #").p(_imm); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java deleted file mode 100644 index 1a0365ad..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.MachConcreteNode; -import com.seaofnodes.simple.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,Arm64.OP_AND); } - // General form: #rd = rs1 & rs2 - @Override public void asm(CodeGen code, SB sb){ - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" & ").p(code.reg(in(2))); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java deleted file mode 100644 index 6292e98e..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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), Arm64.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(Arm64.decodeImm12(_imm)); - } -}
\ No newline at end of file diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java deleted file mode 100644 index 77f14d4c..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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,Arm64.OP_ASR); } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> ").p(code.reg(in(2))); - } - -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java deleted file mode 100644 index 7cb42eec..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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(Arm64.imm_shift(Arm64.OPI_ASR,_imm, 0b111111, rn, rd)); - } - // General form: "asri rd = rs1 >> imm" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> #").p(_imm); - } -}
\ No newline at end of file diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java deleted file mode 100644 index 127f17ff..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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( Arm64.b_cond(Arm64.OP_BRANCH, 0, Arm64.make_condition(_bop)) ); - } else { - if( _bop=="!=" ) return; // Inverted, no code - enc.add4(Arm64.b(Arm64.OP_UJMP, 0)); - } - enc.jump(this,cproj(0)); - } - - // 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,Arm64.b_cond(Arm64.OP_BRANCH, delta, Arm64.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java deleted file mode 100644 index e1717cb3..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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(Arm64.b(Arm64.OP_CALL,0)); // Target patched at link time - } - - // Delta is from opcode start, but X86 measures from the end of the 5-byte encoding - @Override public byte encSize(int delta) { return 4; } - - // Delta is from opcode start - @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - enc.patch4(opStart,Arm64.b(Arm64.OP_CALL,delta)); - } - - @Override public void asm(CodeGen code, SB sb) { - sb.p(_name).p(" "); - for( int i=0; i<nargs(); i++ ) - sb.p(code.reg(arg(i+2))).p(", "); - sb.unchar(2); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java deleted file mode 100644 index 1949ae46..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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(Arm64.blr(Arm64.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java deleted file mode 100644 index 1bd9c372..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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,Arm64.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java deleted file mode 100644 index 67ffc27b..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java deleted file mode 100644 index cd4dd9cf..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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), Arm64.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java deleted file mode 100644 index e94b6491..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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,Arm64.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java deleted file mode 100644 index 68ad6047..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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,Arm64.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java deleted file mode 100644 index 814a82c2..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.util.SB; - -// Convert a narrow C return register to Simple's full-width integer value. -public class ExtendARM extends MachConcreteNode { - final int _bits; - final boolean _signed; - ExtendARM(ProjNode prj, TypeInteger ret) { - super(prj); - _inputs.set(0,null); - _inputs.add(new ProjARM(prj)); - _bits = 8 << ret.log_size(); - _signed = ret._min < 0; - } - @Override public String op() { return (_signed ? "sext" : "zext")+_bits; } - @Override public RegMask regmap(int i) { return arm.RMASK; } - @Override public RegMask outregmap() { return arm.WMASK; } - @Override public void encoding(Encoding enc) { - short dst = enc.reg(this), src = enc.reg(in(1)); - // SBFM/UBFM aliases: sign/zero extend the low byte, short or int. - enc.add4(Arm64.imm_shift(_signed ? Arm64.OPI_ASR : Arm64.OPI_LSR,0,_bits-1,src,dst)); - } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java deleted file mode 100644 index 31dcfc36..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class F8ToIARM extends MachConcreteNode implements MachNode { - F8ToIARM(Node f8toi) { super(f8toi); } - @Override public String op() { return "cvti"; } - @Override public RegMask regmap(int i) { return arm.DMASK; } - @Override public RegMask outregmap() { return arm.WMASK; } - @Override public void encoding( Encoding enc ) { - enc.add4(Arm64.floatToInteger(enc.reg(in(1))-arm.D_OFFSET,enc.reg(this))); - } - - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1))); - } - -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java deleted file mode 100644 index dc21e075..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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(Arm64.load_pc(Arm64.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, Arm64.load_pc(Arm64.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java deleted file mode 100644 index 17cafcde..00000000 --- a/chapter-interprocedural-sccp/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.isa.Arm64; -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); - _frameAdjust = (_frameAdjust+15) & -16; // Keep SP aligned, including leaf spills. - } - @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(Arm64.imm_inst(Arm64.OPI_SUB, sz, arm.RSP, arm.RSP)); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java deleted file mode 100644 index b1c30afc..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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 = Arm64.float_cast(Arm64.OP_FLOAT_C, 1, reg1, self); - enc.add4(body); - } - - // General form: "i2f8 (flt)int_value" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p("(flt)").p(code.reg(in(1))); - } - -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java deleted file mode 100644 index 7a9e74c6..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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(); - Arm64.constant(enc,self,x); - } - - // 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java deleted file mode 100644 index 8fc22a67..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.LoadNode; -import com.seaofnodes.simple.node.Node; -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 size() >= 4 ? arm.MEM_MASK : arm.WMASK; } - - private static final int[] OP_LOADS = new int[]{ Arm64.OP_LOAD_IMM_8, Arm64.OP_LOAD_IMM_16, Arm64.OP_LOAD_IMM_32, Arm64.OP_LOAD_IMM_64, }; - - private static final int[] OP_LOAD_RS = new int[]{ Arm64.OP_LOAD_R_8 , Arm64.OP_LOAD_R_16 , Arm64.OP_LOAD_R_32 , Arm64.OP_LOAD_R_64, }; - - // ldr(immediate - unsigned offset) | ldr(register) - @Override public void encoding( Encoding enc ) { - ldst_encode(enc, OP_LOADS[Integer.numberOfTrailingZeros(size())], OP_LOAD_RS[_declaredType.log_size()], this, size()); - } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(","); - asm_address(code,sb); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java deleted file mode 100644 index 954cfac2..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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,Arm64.OP_LSL); } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" << ").p(code.reg(in(2))); - } - -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java deleted file mode 100644 index b32153ab..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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(Arm64.imm_shift(Arm64.OPI_LSL, 64 - _imm, (64 - _imm) - 1, rn, rd)); - } - // General form: "lsli rd = rs1 << imm" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" << #").p(_imm); - } -}
\ No newline at end of file diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java deleted file mode 100644 index 91be34e3..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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,Arm64.OP_LSR); } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >>> ").p(code.reg(in(2))); - } - -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java deleted file mode 100644 index 243ce968..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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(Arm64.imm_shift(Arm64.OPI_LSR,_imm, 0b111111, rn,rd)); - } - // General form: "lsri rd = rs1 >>> imm" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >>> #").p(_imm); - } -}
\ No newline at end of file diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java deleted file mode 100644 index dfbe5e06..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -import com.seaofnodes.print.ExprPrinter; - -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; - -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 protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) { return p.p(".").p(_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 size() >= 4 ? arm.MEM_MASK : 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); - // The allocated register selects integer vs SIMD/FP encoding, even for - // bitwise copies. ARM numbers each register bank from zero in the opcode. - boolean fp = val >= arm.D0; - assert !fp || size >= 4; - if( fp ) { - opcode_imm |= 1<<4; - opcode_reg |= 1<<5; - val -= arm.D0; - } - int body = off() == null - ? Arm64.load_str_imm(opcode_imm, _off, ptr, val, size) - : Arm64.indr_adr(opcode_reg, off, Arm64.STORE_LOAD_OPTION.SXTX, 0, ptr, val); - enc.add4(body); - } - - SB asm_address(CodeGen code, SB sb) { - sb.p("[").p(code.reg(ptr())).p("+"); - if( off() != null ) sb.p(code.reg(off())); - else sb.p(_off); - return sb.p("]"); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java deleted file mode 100644 index f598cefb..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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,Arm64.OP_MUL,31); } - // General form: "rd = rs1 * rs2" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2))); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java deleted file mode 100644 index a4f81fd4..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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,Arm64.OPF_MUL); } - // Default on double precision for now(64 bits) - // General form: "VMUL.f32 rd = src1 * src2 - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2))); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java deleted file mode 100644 index 881b41f9..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java deleted file mode 100644 index 7110df62..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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(Arm64.mov(Arm64.OP_MOVZ, 0, 1, 0)). // movz x0,#1 - add4(Arm64.b(Arm64.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, Arm64.b_calloc(Arm64.OP_CALL, delta-4)); - } else { - throw Utils.TODO(); - } - } - // General form: "alloc #bytes PC" - @Override public void asm(CodeGen code, SB sb) { - sb.p("ldi x0=#1\n"); - sb.p("call #calloc"); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java deleted file mode 100644 index f8e450f6..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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 = Arm64.imm_inst(Arm64.OP_SUBS, 0, reg1, 31); - enc.add4(subs); - int cset = Arm64.cset(Arm64.OP_CSET, 31, Arm64.COND.EQ, 31, self); - enc.add4(cset); - } - - @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java deleted file mode 100644 index 5b7264ec..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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,Arm64.OP_OR); } - // General form: #rd = rs1 & rs2 - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" | ").p(code.reg(in(2))); - } - -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java deleted file mode 100644 index 86d07bee..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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), Arm64.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java deleted file mode 100644 index b419fe0a..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java deleted file mode 100644 index 4b7d6f13..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java deleted file mode 100644 index a7ed5073..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; - -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(Arm64.imm_inst(Arm64.OPI_ADD, frameAdjust, arm.RSP, arm.RSP)); - enc.add4(Arm64.ret(Arm64.OP_RET)); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java deleted file mode 100644 index afbbd3bf..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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 ) { - // Using RM==RN==31 implies a *zero* value, not R31. - int body = Arm64.cset(Arm64.OP_CSET,0b11111, Arm64.make_condition(_bop), 0b011111, enc.reg(this)); - enc.add4(body); - } - - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)); - String src = code.reg(in(1)); - if( src!="flags" ) sb.p(" = ").p(src); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java deleted file mode 100644 index f93f0f24..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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(Arm64.load_str_imm(Arm64.OP_STORE_IMM_64, off, arm.RSP, src, 8)); - return; - } - - if(src >= arm.MAX_REG) { - // Load from SP - int off = enc._fun.computeStackOffset(enc._code,src); - if( dstX ) dst -= arm.D_OFFSET; - enc.add4(Arm64.load_str_imm(Arm64.OP_LOAD_IMM_64, off, arm.RSP, dst, 8)); - return; - } - - // pick opcode based on regs - if(!dstX && !srcX) { - // GPR->GPR - enc.add4(Arm64.mov_reg(Arm64.OP_MOV, src, dst)); - } else if(dstX && srcX) { - // FPR->FPR - // fmov reg - enc.add4(Arm64.f_mov_reg(Arm64.OP_FMOV_REG, src - arm.D_OFFSET,dst - arm.D_OFFSET)); - } else if(!srcX && dstX) { - // GPR->FPR - // FMOV(general) 64 bits to DOUBLE-PRECISION - enc.add4(Arm64.f_mov_general(Arm64.OP_FMOV, 0b01, 0, 0b111, src, dst - arm.D_OFFSET)); - } else if( srcX && !dstX) { - //FPF->GPR - // FMOV(general) DOUBLE-PRECISION to 64 bits - enc.add4(Arm64.f_mov_general(Arm64.OP_FMOV, 0b01, 0, 0b110, src - arm.D_OFFSET, dst)); - } - } - - // General form: "mov dst = src" - @Override public void asm(CodeGen code, SB sb) { - FunNode fun = code._encoding==null ? null : code._encoding._fun; - sb.p(code.reg(this,fun)).p(" = ").p(code.reg(in(1),fun)); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java deleted file mode 100644 index 3e045a6f..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -import com.seaofnodes.simple.codegen.*; - -// NZCV transfers require a GPR. Further splits can spill that GPR to the stack. -public class SplitFlagsARM extends SplitARM { - private static final RegMask MASK = new RegMask(arm.WR_BITS,1L); - SplitFlagsARM(String kind,byte round) { super(kind,round); } - @Override public boolean isNonStackSplit() { return true; } - @Override public RegMask regmap(int i) { return MASK; } - @Override public RegMask outregmap() { return MASK; } - - @Override public void encoding(Encoding enc) { - short dst=enc.reg(this), src=enc.reg(in(1)); - if( dst==src ) return; - if( src==arm.FLAGS ) enc.add4(Arm64.flags(dst,false)); - else if( dst==arm.FLAGS ) enc.add4(Arm64.flags(src,true)); - else super.encoding(enc); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java deleted file mode 100644 index 46eff4ca..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.codegen.*; - -// Keep a saved NZCV value in GPRs and stack slots through subsequent spills. -public class SplitGPRARM extends SplitARM { - private static final RegMask MASK = new RegMask(arm.WR_BITS,-2L); - SplitGPRARM(String kind,byte round) { super(kind,round); } - @Override public RegMask regmap(int i) { return MASK; } - @Override public RegMask outregmap() { return MASK; } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java deleted file mode 100644 index 2cc54f76..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.node.StoreNode; -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[]{ Arm64.OP_STORE_IMM_8, Arm64.OP_STORE_IMM_16, Arm64.OP_STORE_IMM_32, Arm64.OP_STORE_IMM_64, }; - - private static final int[] OP_STORE_RS = new int[]{ Arm64.OP_STORE_R_8, Arm64.OP_STORE_R_16, Arm64.OP_STORE_R_32, Arm64.OP_STORE_R_64, }; - - @Override public void encoding( Encoding enc ) { - ldst_encode(enc, OP_STORES[Integer.numberOfTrailingZeros(size())], OP_STORE_RS[_declaredType.log_size()], val(), size()); - } - @Override public void asm(CodeGen code, SB sb) { - asm_address(code,sb).p(","); - if( val()==null ) sb.p("#").p(_imm); - else sb.p(code.reg(val())); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java deleted file mode 100644 index fd5cd4de..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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,Arm64.OP_SUB); } - - // General form: "sub # rd = rs1 - rs2" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - ").p(code.reg(in(2))); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java deleted file mode 100644 index 1a751434..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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,Arm64.OPF_SUB); } - // Default on double precision for now(64 bits) - // General form: "vsub.f32 rd = src1 + src2 - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - ").p(code.reg(in(2))); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java deleted file mode 100644 index b2c0b2ed..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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), Arm64.OPI_SUB,_imm); - } - - // General form: "subi rd = rs1 - imm" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - #").p(_imm); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java deleted file mode 100644 index 2c41dc57..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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 = Arm64.adrp(1,0, Arm64.OP_ADRP, 0,self); - // add x0, x0, 0 - enc.add4(adrp); - arm.imm_inst(enc,Arm64.OPI_ADD,0, 0); - } - - @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; - // ADRP splits a signed page offset into two unsigned bit fields. - // Mask the high field too, including for an address below this PC. - enc.patch4(opStart, Arm64.adrp(1, adrp_delta & 0b11, 0b10000, (adrp_delta >> 2) & 0x7FFFF, rpc)); - // low 12 bits via add - enc.patch4(opStart+4, Arm64.imm_inst_l(Arm64.OPI_ADD, delta & 0xfff, rpc)); - } else { - // should not happen as one instruction is 4 byte, and TFP arm encodes 2. - throw Utils.TODO(); - } - } - - @Override public void asm(CodeGen code, SB sb) { - String reg = code.reg(this); - _con.print(sb.p(reg).p(" #")); - } - @Override public boolean eq(Node n) { return this==n; } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java deleted file mode 100644 index d1cafa96..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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(Arm64.adrp(1,0, Arm64.OP_ADRP, 0,dst)); - // add x0, x0, 0 - arm.imm_inst(enc,Arm64.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, Arm64.adrp(1, adrp_delta & 0b11, 0b10000, adrp_delta >> 2, dst)); - // low 12 bits via add - enc.patch4(opStart+4, Arm64.imm_inst_l(Arm64.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java deleted file mode 100644 index aecd02bb..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; - -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; - -// unconditional jump -public class UJmpARM extends CFGNode implements MachNode, RIPRelSize { - UJmpARM() { } - @Override public String op() { return "jmp"; } - @Override public String label() { return op(); } - @Override protected String format() { return "jmp "; } - @Override public RegMask regmap(int i) {return null; } - @Override public RegMask outregmap() { return null; } - @Override public Type compute() { throw Utils.TODO(); } - @Override public Node idealize() { throw Utils.TODO(); } - @Override public void encoding( Encoding enc ) { - enc.jump(this,uctrl()); - int body = Arm64.b(Arm64.OP_UJMP, 0); - enc.add4(body); - } - - // Delta is from opcode start - @Override public byte encSize(int delta) { - if( -(1<<26) <= delta && delta < (1<<26) ) return 4; - // 2 word encoding needs a tmp register, must teach RA - throw Utils.TODO(); - } - - // Delta is from opcode start - @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - if( opLen==4 ) { - enc.patch4(opStart,Arm64.b(Arm64.OP_UJMP, delta)); - } else { - throw Utils.TODO(); - } - } - - @Override public void asm(CodeGen code, SB sb) { - CFGNode target = uctrl(); - //assert target.nOuts() > 1; // Should optimize jmp to empty targets - sb.p(label(target)); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java deleted file mode 100644 index d56397a3..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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,Arm64.OP_XOR); } - // General form: "rd = x1 ^ x2" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ^ ").p(code.reg(in(2))); - } - -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java deleted file mode 100644 index 12143ccd..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -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), Arm64.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java deleted file mode 100644 index fae98a99..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java +++ /dev/null @@ -1,466 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.isa.Arm64; -import static com.seaofnodes.isa.Arm64.*; -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); - - // 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 - - // True if signed 9-bit immediate - private static boolean imm9(TypeInteger ti) { - // 55 = 64-9 - return ti.isConstant() && ((ti.value()<<55)>>55) == ti.value(); - } - // True if signed 12-bit immediate - private static boolean imm12(TypeInteger ti) { - // 52 = 64-12 - return ti.isConstant() && ((ti.value()<<52)>>52) == ti.value(); - } - - // Can we encode this in ARM's 12-bit LOGICAL immediate form? - // Some combination of shifted bit-masks. - private static int imm12Logical(TypeInteger ti) { - return ti.isConstant() ? Arm64.logicalImmediate(ti.value()) : -1; - } - - public static void imm_inst(Encoding enc, Node n,Node n2, int opcode, int imm12) { - short self = enc.reg(n); - short reg1 = enc.reg(n2); - - // ADD/SUB encode an unsigned magnitude; switch operation for a negative addend. - if( imm12<0 && (opcode==OPI_ADD || opcode==OPI_SUB) ) { - opcode ^= OPI_ADD ^ OPI_SUB; - imm12 = -imm12; - } - int body = Arm64.imm_inst(opcode, imm12&0xFFF, reg1, self); - enc.add4(body); - } - - public static void imm_inst_subs(Encoding enc, Node n,Node n2, int opcode, int imm12) { - short reg1 = enc.reg(n2); - // 31 = 11111 - int body = Arm64.imm_inst(opcode, imm12&0xFFF, reg1, 31); - enc.add4(body); - } - - // for cases where rs1 and dst are the same, eg add x0, x0, 1 - public static void imm_inst(Encoding enc, int opcode, int imm12, int self) { - int body = Arm64.imm_inst(opcode, imm12&0xFFF, self, self); - enc.add4(body); - } - - public static void imm_inst_n(Encoding enc, Node n, Node n2, int opcode, int imm13) { - short self = enc.reg(n); - short reg1 = enc.reg(n2); - int body = Arm64.imm_inst_n(opcode, imm13, reg1, self); - enc.add4(body); - } - - // for cases where rs1 and dst are the same, eg add x0, x0, 1 - public static int imm_inst_l(Encoding enc, Node n, int opcode, int imm12) { - short self = enc.reg(n); - short reg1 = enc.reg(n.in(1)); - int body = Arm64.imm_inst(opcode, imm12&0xFFF, reg1, self); - return body; - } - - // for normal add, reg1, reg2 cases (reg-to-reg) - // using shifted-reg form - - public static void r_reg(Encoding enc, Node n, int opcode) { - short self = enc.reg(n); - short reg1 = enc.reg(n.in(1)); - short reg2 = enc.reg(n.in(2)); - int body = Arm64.r_reg(opcode, 0, reg2, 0, reg1, self >= 32 ? reg1: self); - enc.add4(body); - } - - public static void r_reg_subs(Encoding enc, Node n, int opcode) { - short reg1 = enc.reg(n.in(1)); - short reg2 = enc.reg(n.in(2)); - // 31 = 11111 - int body = Arm64.r_reg(opcode, 0, reg2, 0, reg1, 31); - enc.add4(body); - } - - public static void shift_reg(Encoding enc, Node n, int op2) { - short self = enc.reg(n); - short reg1 = enc.reg(n.in(1)); - short reg2 = enc.reg(n.in(2)); - int body = Arm64.shift_reg(0b10011010110, reg2, op2, reg1, self); - enc.add4(body); - } - - // MUL can be considered an alias for MADD with the third operand Ra being set to 0 - - public static void madd(Encoding enc, Node n, int opcode, int ra) { - short self = enc.reg(n); - short reg1 = enc.reg(n.in(1)); - short reg2 = enc.reg(n.in(2)); - int body = Arm64.madd(opcode, reg2, ra, reg1, self); - enc.add4(body); - } - - public static void f_scalar(Encoding enc, Node n, int op ) { - short self = (short)(enc.reg(n) -D_OFFSET); - short reg1 = (short)(enc.reg(n.in(1))-D_OFFSET); - short reg2 = (short)(enc.reg(n.in(2))-D_OFFSET); - int body = Arm64.f_scalar(0b00011110, 1, reg2, op, reg1, self); - enc.add4(body); - } - - public static void f_cmp(Encoding enc, Node n) { - short reg1 = (short)(enc.reg(n.in(1))-D_OFFSET); - short reg2 = (short)(enc.reg(n.in(2))-D_OFFSET); - int body = Arm64.f_cmp(0b00011110, 3, reg2, reg1); - enc.add4(body); - } - - @Override public RegMask callArgMask(TypeFunPtr tfp, int idx, int maxArgSlot ) { return callInMask(tfp,idx,maxArgSlot); } - static RegMask callInMask(TypeFunPtr tfp, int idx, int maxArgSlot ) { - if( idx==0 ) return CodeGen.CODE._rpcMask; - if( idx==1 ) return null; - // Count floats in signature up to index - 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)); - } - - // 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) { - RegMask def = lrg._machDef==null ? null : lrg._machDef.outregmap(), use = lrg.useMask(); - if( (def!=null && def.size1() && def.test(FLAGS)) || - (use!=null && use.size1() && use.test(FLAGS)) ) - return new SplitFlagsARM(kind,round); - // Spilling the saved flags requires a GPR between NZCV and the stack. - if( lrg._machDef instanceof SplitFlagsARM || lrg._machDef instanceof SplitGPRARM ) - return new SplitGPRARM(kind,round); - 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 BulkMemPhiNode phi -> new BulkMemPhiNode(phi); - case MemPhiNode phi -> new MemPhiNode(phi); - case PhiNode phi -> new PhiNode(phi); - case ProjNode prj -> prj(prj); - case ReadOnlyNode read -> new 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 ToIntegerNode cvt -> new F8ToIARM(cvt); - 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, 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; - } - - private Node prj(ProjNode prj) { - TypeInteger ret = cReturn(prj); - return ret==null ? new ProjARM(prj) : new ExtendARM(prj,ret); - } - -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java deleted file mode 100644 index 14581111..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java deleted file mode 100644 index d87e49b9..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java deleted file mode 100644 index 8012b61e..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java deleted file mode 100644 index 7010916f..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java deleted file mode 100644 index 9fd65e7d..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java deleted file mode 100644 index 1845985e..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java deleted file mode 100644 index a9d03da5..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.RiscV; -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; - -// 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 protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - p.p("if( "); - if( in(1)==null ) p.p("0"); - else p.n(in(1)); - p.p(_bop); - if( in(2)==null ) p.p("0"); - else p.n(in(2)); - return p.p(" )"); - } - - @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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java deleted file mode 100644 index f0ad8e88..00000000 --- a/chapter-interprocedural-sccp/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.isa.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); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java deleted file mode 100644 index 5eee596d..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java deleted file mode 100644 index 8eb4fb3b..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java deleted file mode 100644 index 515ffa47..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java deleted file mode 100644 index 5bd3ec1c..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.RiscV; -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.util.SB; - -// Convert a narrow C return register to Simple's full-width integer value. -public class ExtendRISC extends MachConcreteNode { - final int _bits; - final boolean _signed; - ExtendRISC(ProjNode prj, TypeInteger ret) { - super(prj); - _inputs.set(0,null); - _inputs.add(new ProjRISC(prj)); - _bits = 8 << ret.log_size(); - _signed = ret._min < 0; - } - @Override public String op() { return (_signed ? "sext" : "zext")+_bits; } - @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), src = enc.reg(in(1)); - int shift = 64-_bits; - enc.add4(RiscV.i_type(RiscV.OP_IMM,dst,1,src,shift)); - enc.add4(RiscV.i_type(RiscV.OP_IMM,dst,5,dst,shift | (_signed ? 0x400 : 0))); - } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java deleted file mode 100644 index a56ad216..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.RiscV; -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class F8ToIRISC extends MachConcreteNode implements MachNode { - F8ToIRISC(Node f8toi) {super(f8toi);} - @Override public String op() { return "cvti"; } - @Override public RegMask regmap(int i) { assert i==1; return riscv.FMASK; } - @Override public RegMask outregmap() { return riscv.WMASK; } - @Override public void encoding( Encoding enc ) { - RiscV.floatToInteger(enc,enc.reg(this),enc.reg(in(1))-riscv.F_OFFSET); - } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1))); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java deleted file mode 100644 index 51ddc9db..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java deleted file mode 100644 index d3d64b06..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java deleted file mode 100644 index 1b53ab0f..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java deleted file mode 100644 index ee247c3e..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.RiscV; -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.util.SB; - -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 protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - int imm12 = (_imm12<<20)>>20; // Sign extend 12 bits - p.p("( ").n(in(1)); - return p.p(String.format(" %s #%d )",glabel(),imm12)); - } - - abstract int opcode(); - abstract int func3(); - - @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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java deleted file mode 100644 index 23ac8481..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java deleted file mode 100644 index 9088abbd..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java deleted file mode 100644 index aedd8b47..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java deleted file mode 100644 index 2e3dfd8d..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java deleted file mode 100644 index adf21848..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.print.ExprPrinter; - -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.*; - -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 protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) { return p.p(".").p(_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 - if( _declaredType instanceof TypeInteger ti ) { - if( -128 <= ti._min && ti._max < 128 ) func3 = 0; // LB - } - - // 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java deleted file mode 100644 index dacec2b5..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java deleted file mode 100644 index 9cb9ace7..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java deleted file mode 100644 index 3070c56c..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java deleted file mode 100644 index 965a3c7c..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java deleted file mode 100644 index 6f499db1..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.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)); - // SEQZ is unsigned: negative nonzero inputs must also produce false. - enc.add4(RiscV.i_type(RiscV.OP_IMM, dst, 3, src, 1)); - } - @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java deleted file mode 100644 index 937595a3..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java deleted file mode 100644 index a310a1f4..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java deleted file mode 100644 index 0f8db4e5..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java deleted file mode 100644 index 2f9ac469..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java deleted file mode 100644 index 33143c6b..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java deleted file mode 100644 index d037dd5c..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java deleted file mode 100644 index 9bc86565..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java +++ /dev/null @@ -1,21 +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 ) { - this(src,imm12,unsigned,true); - } - public SetIRISC( Node src, int imm12, boolean unsigned, boolean pop ) { - super(src,imm12,pop); - if( !pop ) _inputs.setLen(2); - _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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java deleted file mode 100644 index 848cc59f..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java deleted file mode 100644 index 1425caa2..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java deleted file mode 100644 index 5a4d4cc6..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java deleted file mode 100644 index 8a16f6b7..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java deleted file mode 100644 index b316b303..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java deleted file mode 100644 index e2e34415..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java deleted file mode 100644 index d0ea6b18..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java deleted file mode 100644 index 1eccc1c8..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java deleted file mode 100644 index 48cb7c78..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.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 _sz >= '4' - ? riscv.MEM_MASK // Word/dword value in GPR or FPR - : riscv.RMASK; // Byte/short stores require a GPR - 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java deleted file mode 100644 index 044820cf..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java deleted file mode 100644 index 2adfce23..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java deleted file mode 100644 index e57f6e09..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.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) { return 8; } - - // Delta is from opcode start - @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - short rpc = enc.reg(this); - if( opLen==8 ) { - // ADDI sign-extends its low 12 bits, so round the AUIPC high part. - enc.patch4(opStart ,RiscV.u_type(RiscV.OP_AUIPC, rpc, (delta+0x800)>>12)); - enc.patch4(opStart+4,RiscV.i_type(RiscV.OP_IMM, rpc, 0, rpc, delta & 0xFFF)); - } else { - // should not happen as one instruction is 4 byte, and TFP arm encodes 2. - throw Utils.TODO(); - } - } - - @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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java deleted file mode 100644 index 7bc2db7e..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.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); - // Round the high part because ADDI's low 12 bits are signed. - enc.patch4(opStart , RiscV.u_type(RiscV.OP_AUIPC, dst, (delta+0x800)>>12)); - // ADDI 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java deleted file mode 100644 index 448d9ec3..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.RiscV; -import com.seaofnodes.print.ExprPrinter; - -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; - -// unconditional jump -public class UJmpRISC extends CFGNode implements MachNode, RIPRelSize { - @Override public String op() { return "jmp"; } - @Override public String label() { return op(); } - @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("jmp "); } - @Override public RegMask regmap(int i) {return null; } - @Override public RegMask outregmap() { return null; } - @Override public Type compute() { throw Utils.TODO(); } - @Override public Node idealize() { throw Utils.TODO(); } - @Override public void encoding( Encoding enc ) { - // Short form +/-4K: beq r0,r0,imm12 - // Long form: auipc rX,imm20/32; jal r0,[rX+imm12/32] - enc.jump(this,uctrl()); - enc.add4(RiscV.j_type(RiscV.OP_JAL, 0, 0)); - } - - // Delta is from opcode start - @Override public byte encSize(int delta) { - if( -(1L<<20) <= delta && delta < (1L<<20) ) return 4; - // 2 word encoding needs a tmp register, must teach RA - throw Utils.TODO(); - } - - // Delta is from opcode start - @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - if( opLen==4 ) { - enc.patch4(opStart,RiscV.j_type(RiscV.OP_JAL, 0, delta)); - } else { - throw Utils.TODO(); - } - } - - @Override public void asm(CodeGen code, SB sb) { - CFGNode target = uctrl(); - //assert target.nOuts() > 1; // Should optimize jmp to empty targets - sb.p(label(target)); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java deleted file mode 100644 index f4323cae..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java deleted file mode 100644 index 413a7c3b..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java deleted file mode 100644 index 7c994624..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java +++ /dev/null @@ -1,428 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.RiscV; -import static com.seaofnodes.isa.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 - }; - - // Since riscv instructions are fixed we can just or them together - - 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 = RiscV.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 = RiscV.r_type(OP_FP,dst,func3.ordinal(),src1,src2,func7); - enc.add4(body); - } - - @Override public RegMask callArgMask( TypeFunPtr tfp, int idx, int maxArgSlot ) { return callInMask(tfp,idx,maxArgSlot); } - static RegMask callInMask( TypeFunPtr tfp, int idx, int maxArgSlot ) { - if( idx==0 ) return 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 BulkMemPhiNode phi -> new BulkMemPhiNode(phi); - case MemPhiNode phi -> new MemPhiNode(phi); - case PhiNode phi -> new PhiNode(phi); - case ProjNode prj -> prj(prj); - case ReadOnlyNode read-> new 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 ToIntegerNode cvt -> new F8ToIRISC(cvt); - 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,false); - default -> throw Utils.TODO(); - }; - } - - private Node con( ConstantNode con ) { - if( !con._con.isConstant() ) return new ConstantNode( con ); // Default unknown caller inputs - return switch( con._con ) { - case TypeInteger ti -> { - 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) { - TypeInteger ret = cReturn(prj); - if( ret!=null ) return new ExtendRISC(prj,ret); - 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java deleted file mode 100644 index 974f690f..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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 ) { - X86.memory(enc,0xF2,0x0F58,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(ptr()),enc.reg(idx()),_off,_scale); - } - - // General form: "add dst = src + [base + idx<<2 + 12]" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = "); - sb.p(val()==null ? "#"+_imm : code.reg(val())).p(" + "); - asm_address(code,sb); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java deleted file mode 100644 index c7d0cf9c..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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 ) { - X86.sse(enc,0xF2,0x0F58,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET); - } - - // General form: "addf dst += src" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" += ").p(code.reg(in(2))); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java deleted file mode 100644 index 0e652a93..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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 - X86.unary(enc,0xFF,_imm==1 ? 0 : 1,dst); - } else super.encoding(enc); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java deleted file mode 100644 index ed65b0a8..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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 ) { - X86.memory(enc,0,0x03,true,enc.reg(this),enc.reg(ptr()),enc.reg(idx()),_off,_scale); - } - - // 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java deleted file mode 100644 index 84543e8f..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java deleted file mode 100644 index 3c1b567c..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java deleted file mode 100644 index dedbce45..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java deleted file mode 100644 index f97163eb..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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 ) { - X86.callRegister(enc,enc.reg(fptr())); - } - - @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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java deleted file mode 100644 index d1a43b83..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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); - X86.call(enc); - } - - // 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java deleted file mode 100644 index 5ef7a476..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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 ) { - X86.sse(enc,0x66,0x0F2E,false,enc.reg(in(1))-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET); - } - - // General form: "cmp src1,src2" - @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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java deleted file mode 100644 index 151f8ca8..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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 ) { - X86.imm(enc,0x81,7,enc.reg(in(1)),_imm); - } - @Override public void asm(CodeGen code, SB sb) { - String dst = code.reg(this); - if( dst!="flags" ) sb.p(dst).p(" = "); - sb.p(code.reg(in(1))); - if( _imm != 0 ) sb.p(", #").p(_imm); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java deleted file mode 100644 index 7b7d98d5..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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 ) { - short ptr=enc.reg(ptr()), idx=enc.reg(idx()), src=enc.reg(val()); - if( src==-1 ) { - assert !_swap; - X86.cmpImm(enc,_declaredType.log_size(),_imm,ptr,idx,_off,_scale); - } else encVal(enc,_declaredType,ptr,idx,src,_off,_scale,_swap); - } - - static void encVal(Encoding enc, Type decl, short ptr, short idx, short src, int off, int scale, boolean _swap) { - assert !(decl instanceof TypeFloat); // Selected separately as CmpFX86. - X86.cmpMem(enc,decl.log_size(),src,ptr,idx,off,scale,_swap); - } - - // General form: "cmp dst = src, [base + idx<<2 + 12]" - @Override public void asm(CodeGen code, SB sb) { - String dst = code.reg(this); - if( dst!="flags" ) sb.p(dst).p(" = "); - if( _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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java deleted file mode 100644 index 7faea17b..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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 ) { - X86.reg(enc,0x3B,enc.reg(in(1)),enc.reg(in(2))); - } - @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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java deleted file mode 100644 index 7bbb5102..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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 ) { - X86.sse(enc,0xF2,0x0F5E,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET); - } - - - // General form: "divf dst /= src" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" /= ").p(code.reg(in(2))); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java deleted file mode 100644 index 9f375a80..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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 ) { - X86.divide(enc,enc.reg(in(2))); - } - // General form: "div dst /= src" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2))); - } - @Override public String comment() { return "kill rdx"; } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java deleted file mode 100644 index 1a6ae123..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.util.SB; - -// Convert a narrow C return register to Simple's full-width integer value. -public class ExtendX86 extends MachConcreteNode { - final int _bits; - final boolean _signed; - ExtendX86(ProjNode prj, TypeInteger ret) { - super(prj); - _inputs.set(0,null); - _inputs.add(new ProjX86(prj)); - _bits = 8 << ret.log_size(); - _signed = ret._min < 0; - } - @Override public String op() { return (_signed ? "sext" : "zext")+_bits; } - @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } - @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - @Override public void encoding(Encoding enc) { - X86.extend(enc,_bits,_signed,enc.reg(this),enc.reg(in(1))); - } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java deleted file mode 100644 index a79a1017..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.util.SB; - -public class F8ToIX86 extends MachConcreteNode implements MachNode { - F8ToIX86(Node f8toi ) { super(f8toi); } - @Override public String op() { return "cvti"; } - @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.XMASK; } - @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; } - @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - - @Override public void encoding( Encoding enc ) { - X86.floatToInteger(enc,enc.reg(this),enc.reg(in(1))-x86_64_v2.XMM_OFFSET); - } - - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1))); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java deleted file mode 100644 index da9bdce7..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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 ) { - _encSize=X86.rip(enc,0xF2,0x0F10,false,enc.reg(this)-x86_64_v2.XMM_OFFSET); - enc.largeConstant(this,_con,_encSize-4,2/*ELF 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java deleted file mode 100644 index c7a20e4d..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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 - X86.imm(enc,0x81,5,x86_64_v2.RSP,sz); - } - -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java deleted file mode 100644 index ee357901..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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 ) { - X86.sse(enc,0xF2,0x0F2A,true,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(1))); - } - - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p("(flt)").p(code.reg(in(1))); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java deleted file mode 100644 index 5a4ff4b2..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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 ) { - X86.imm(enc,opcode(),mod(),enc.reg(this),_imm); - } - - // General form: "addi dst += #imm" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java deleted file mode 100644 index 656b1543..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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 ) { - X86.constant(enc,enc.reg(this),_con==Type.NIL ? 0 : ((TypeInteger)_con).value()); - } - - // 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java deleted file mode 100644 index 10cee059..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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 && _bop=="!=" ) return; - X86.branch(enc,in(1)==null ? -1 : x86_64_v2.jumpop(_bop)&15); - 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; - return X86.branchSize(delta,in(1)!=null); - } - - // Delta is from opcode start - @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - assert !(in(1)==null && _bop=="!="); - X86.patchBranch(enc.bits(),opStart,opLen,delta,in(1)==null ? -1 : x86_64_v2.jumpop(_bop)&15); - } - - @Override public 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java deleted file mode 100644 index 4b00e4dd..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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 ) { - X86.lea(enc,enc.reg(this),enc.reg(in(1)),enc.reg(in(2)),_offset,_scale); - } - - // 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java deleted file mode 100644 index d4f1413e..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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 ) { - // The chapter chooses the register bank and extension from its own type facts. - boolean xmm=dst>=x86_64_v2.XMM_OFFSET; - if( xmm && decl.log_size()==3 ) decl=TypeFloat.F64; - else if( !xmm && decl instanceof TypeFloat ) - decl=decl==TypeFloat.F32 ? TypeInteger.U32 : TypeInteger.BOT; - boolean signed=decl.isa(TypeInteger.I8) || decl==TypeInteger.I16 || decl==TypeInteger.I32; - X86.load(enc,decl.log_size(),signed,xmm, - xmm ? dst-x86_64_v2.XMM_OFFSET : dst,ptr,idx,off,scale); - } - - - // General form: "ldN dst,[base + idx<<2 + 12]" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(","); - asm_address(code,sb); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java deleted file mode 100644 index 2e18a058..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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 ) { - X86.addMem(enc,enc.reg(val()),_imm,enc.reg(ptr()),enc.reg(idx()),_off,_scale); - } - // General form: "add [base + idx<<2 + 12] += src" - @Override public void asm(CodeGen code, SB sb) { - asm_address(code,sb); - if( val()==null ) { - if( _imm != 1 && _imm != -1 ) sb.p(" += #").p(_imm); - } else sb.p(" += ").p(code.reg(val())); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java deleted file mode 100644 index 309e62d9..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.print.ExprPrinter; - -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; - -// 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 protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) { - return p.p(".").p(_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 >= '4' - ? x86_64_v2.MEM_MASK // value in GPR or XMM - : x86_64_v2.RMASK; // Bytes and shorts in GPR only - return null; // Anti-dependence - } - - // "[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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java deleted file mode 100644 index 74616db7..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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 ) { - X86.sse(enc,0xF2,0x0F59,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET); - } - - // General form: "mulf dst *= src" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" *= ").p(code.reg(in(2))); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java deleted file mode 100644 index 5dcb253d..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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 ) { - X86.imul(enc,enc.reg(this),enc.reg(in(1)),_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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java deleted file mode 100644 index 6f3fcdeb..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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 ) { - X86.reg(enc,0x0FAF,enc.reg(this),enc.reg(in(2))); - } - - // General form: "mul dst *= src" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2))); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java deleted file mode 100644 index 86610f6d..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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 ) { - X86.unary(enc,0xF7,3,enc.reg(this)); - } - @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java deleted file mode 100644 index 1cfc1c42..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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"); - X86.constant(enc,_arg2Reg,1); - X86.call(enc); - } - // 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java deleted file mode 100644 index 1e4e7c26..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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); - X86.not(enc,enc.reg(this),enc.reg(in(1))); - } - @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java deleted file mode 100644 index 15f4824d..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java deleted file mode 100644 index bc8f0ea8..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java deleted file mode 100644 index 5cfdd804..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java deleted file mode 100644 index 046c7b4c..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java deleted file mode 100644 index 06755143..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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 ) { - X86.reg(enc,opcode(),enc.reg(in(1)),enc.reg(in(2))); - } - // General form: "add dst += src" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" ").p(glabel()).p("= ").p(code.reg(in(2))); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java deleted file mode 100644 index e4467bb4..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; - -public 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 ) X86.imm(enc,0x81,0,x86_64_v2.RSP,sz); - X86.ret(enc); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java deleted file mode 100644 index cf6275dc..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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) { - X86.shift(enc,mod(),enc.reg(this),_imm); - } - - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java deleted file mode 100644 index 92493712..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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 ) { - X86.unary(enc,opcode(),7,enc.reg(this)); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java deleted file mode 100644 index 864c1b55..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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 ) { - int op=switch (_bop) { - case "==" -> 0x94; // SETE - case "<" -> _unsigned ? 0x92 : 0x9C; // SETB /SETL - case "<=" -> _unsigned ? 0x96 : 0x9E; // SETBE/SETLE - case ">=" -> _unsigned ? 0x93 : 0x9D; // SETAE/SETGE - case ">" -> _unsigned ? 0x97 : 0x9F; // SETA /SETG - default -> throw Utils.TODO(); - }; - X86.set(enc,op&15,enc.reg(this)); - } - - @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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java deleted file mode 100644 index 1e62243e..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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) { - X86.shift(enc,mod(),enc.reg(this),_imm); - } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java deleted file mode 100644 index 768d97e7..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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 ) { - X86.unary(enc,opcode(),4,enc.reg(this)); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java deleted file mode 100644 index 8a0d1b58..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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) { - X86.shift(enc,mod(),enc.reg(this),_imm); - } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java deleted file mode 100644 index 1ba20fee..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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 ) { - X86.unary(enc,opcode(),5,enc.reg(this)); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java deleted file mode 100644 index 4650b535..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -import com.seaofnodes.simple.codegen.*; - -// Flag snapshots can use GPRs or stack slots, but never XMM registers. -public class SplitFlagsX86 extends SplitX86 { - private static final RegMask MASK = new RegMask(x86_64_v2.WR_BITS | (1L<<x86_64_v2.FLAGS) | x86_64_v2.SPILLS,-1L); - SplitFlagsX86(String kind,byte round) { super(kind,round); } - @Override public RegMask regmap(int i) { return MASK; } - @Override public RegMask outregmap() { return MASK; } - - @Override public void encoding(Encoding enc) { - short dst=enc.reg(this), src=enc.reg(in(1)); - if( dst==src ) return; - if( dst==x86_64_v2.FLAGS || src==x86_64_v2.FLAGS ) { - boolean toFlags = dst==x86_64_v2.FLAGS; - int reg = toFlags ? src : dst; - if( reg>=x86_64_v2.MAX_REG ) - X86.flagsStack(enc,enc._fun.computeStackOffset(enc._code,reg),toFlags); - else - X86.flags(enc,reg,toFlags); - } else super.encoding(enc); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java deleted file mode 100644 index 4e9eda7d..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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; } - - // Ordinary GPR, XMM, and stack moves. Flags use SplitFlagsX86. - @Override public void encoding( Encoding enc ) { - short dst=enc.reg(this), src=enc.reg(in(1)); - assert dst!=x86_64_v2.FLAGS && src!=x86_64_v2.FLAGS; - boolean dstX=dst>=x86_64_v2.XMM_OFFSET, srcX=src>=x86_64_v2.XMM_OFFSET; - // Stack spills - if( dst >= x86_64_v2.MAX_REG ) { - int off = enc._fun.computeStackOffset(enc._code,dst); - if( src >= x86_64_v2.MAX_REG ) { - // Rare stack-stack move. push [RSP+soff]; pop [RSP+doff] - int soff = enc._fun.computeStackOffset(enc._code,src); - X86.stackCopy(enc,soff,off); - return; - } - StoreX86.encVal(enc, srcX ? TypeFloat.F64 : TypeInteger.BOT, (short)x86_64_v2.RSP, (short)-1/*index*/, src, off, 0); - return; - } - if( src >= x86_64_v2.MAX_REG ) { - int off = enc._fun.computeStackOffset(enc._code,src); - LoadX86.enc(enc, dstX ? TypeFloat.F64 : TypeInteger.BOT, dst, (short)x86_64_v2.RSP, (short)-1, off, 0); - return; - } - - X86.move(enc,dstX,srcX,dstX ? dst-x86_64_v2.XMM_OFFSET : dst, - srcX ? src-x86_64_v2.XMM_OFFSET : src); - } - - // General form: "mov dst = src" - @Override public void asm(CodeGen code, SB sb) { - FunNode fun = code._encoding==null ? null : code._encoding._fun; - sb.p(code.reg(this,fun)).p(" = ").p(code.reg(in(1),fun)); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java deleted file mode 100644 index c83dfd44..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -import com.seaofnodes.print.ExprPrinter; - -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; - -public class StoreX86 extends MemOpX86 { - StoreX86( StoreNode st, Node base, Node idx, int off, int scale, int imm, Node val ) { - super(st,st, base, idx, off, scale, imm, val); - } - @Override public String op() { return "st"+_sz; } - @Override protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) { - Node val = val(); - p.p(".").p(_name).p("="); - if( val==null ) p.p(_imm); - else p.n(val); - return p.p(";"); - } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return null; } - @Override public void encoding( Encoding enc ) { - short ptr=enc.reg(ptr()), idx=enc.reg(idx()), src=enc.reg(val()); - if( src==-1 ) X86.storeImm(enc,_declaredType.log_size(),_imm,ptr,idx,_off,_scale); - else encVal(enc,_declaredType,ptr,idx,src,_off,_scale); - } - - // Non-immediate encoding - static void encVal( Encoding enc, Type decl, short ptr, short idx, short src, int off, int scale ) { - boolean xmm=src>=x86_64_v2.XMM_OFFSET; - X86.store(enc,decl.log_size(),xmm, - xmm ? src-x86_64_v2.XMM_OFFSET : src,ptr,idx,off,scale); - } - - - // General form: "stN [base + idx<<2 + 12],val" - @Override public void asm(CodeGen code, SB sb) { - asm_address(code,sb).p(","); - if( val()==null ) sb.p("#").p(_imm); - else sb.p(code.reg(val())); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java deleted file mode 100644 index 29f0033a..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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 ) { - X86.sse(enc,0xF2,0x0F5C,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET); - } - - // General form: "subf dst -= src" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" -= ").p(code.reg(in(2))); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java deleted file mode 100644 index 8da34bf5..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java deleted file mode 100644 index fff2fb04..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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 ) { - if( _con==Type.NIL ) { X86.constant(enc,enc.reg(this),0); return; } - enc.relo(this); - X86.rip(enc,0,0x8D,true,enc.reg(this)); - } - - @Override public byte encSize(int delta) { - return 7; - } - - // 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java deleted file mode 100644 index c78ac6de..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; -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.rexF(dst, 0, 0, true, enc); - enc.add1(0x8D).add1(X86.modrm(X86.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java deleted file mode 100644 index 591a51c3..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.isa.X86; - -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; - -// unconditional jump -public class UJmpX86 extends CFGNode implements MachNode, RIPRelSize { - UJmpX86() { } - @Override public String op() { return "jmp"; } - @Override public String label() { return op(); } - @Override protected String format() { return "jmp "; } - @Override public RegMask regmap(int i) {return null; } - @Override public RegMask outregmap() { return null; } - @Override public Type compute() { throw Utils.TODO(); } - @Override public Node idealize() { throw Utils.TODO(); } - @Override public void encoding( Encoding enc ) { - enc.jump(this,uctrl()); - X86.branch(enc,-1); - } - - // Delta is from opcode start, but X86 measures from the end of the 2-byte encoding - @Override public byte encSize(int delta) { - return X86.branchSize(delta,false); - } - - // Delta is from opcode start - @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - X86.patchBranch(enc.bits(),opStart,opLen,delta,-1); - } - - @Override public void asm(CodeGen code, SB sb) { - CFGNode target = uctrl(); - //assert target.nOuts() > 1; // Should optimize jmp to empty targets - sb.p(label(target)); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java deleted file mode 100644 index 2d989a50..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java deleted file mode 100644 index 86703903..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java deleted file mode 100644 index fcae17f8..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ /dev/null @@ -1,564 +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 | 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; - - // 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"); - }; - } - - // 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 lrg._machDef!=null && lrg._machDef.outregmap().test(FLAGS) - ? new SplitFlagsX86(kind,round) - : 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 BulkMemPhiNode phi -> new BulkMemPhiNode(phi); - case MemPhiNode phi -> new MemPhiNode(phi); - case PhiNode phi -> new PhiNode(phi); - case ProjNode prj -> prj(prj); - case ReadOnlyNode read-> new 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 ToIntegerNode cvt -> new F8ToIX86(cvt); - 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 ) - return new AddMemX86(add,address(ld),ld.ptr(),idx,off,scale, 0,lhs); - - // Attempt a full LEA-style break down. - // Returns one of AddX86, AddIX86, LeaX86, or LHS - if( rhs instanceof ConstantNode 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 ) { - TypeInteger ret = cReturn(prj); - if( ret!=null ) return new ExtendX86(prj,ret); - 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java deleted file mode 100644 index 48296ce7..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.seaofnodes.simple.print; - -import com.seaofnodes.print.AssemblyAdapter; -import com.seaofnodes.print.ASMPrinter.Data; -import com.seaofnodes.print.ASMPrinter.Pool; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; -import java.util.ArrayList; -import com.seaofnodes.simple.codegen.Encoding; - -/** Machine-specific facts; traversal, columns and byte display live in print/. */ -public final class ASMPrinter extends AssemblyAdapter<Node> { - private final CodeGen _code; - private final Encoding _enc; - private ASMPrinter(CodeGen code) { - super(new IRPrinter()); - _code=code; - _enc=code._encoding; - } - public static SB print(SB sb, CodeGen code) { - return sb.p(new com.seaofnodes.print.ASMPrinter<>(new ASMPrinter(code)).print()); - } - @Override public ArrayList<Node> blocks() { - var blocks=new ArrayList<Node>(); - var cfg=_code._cfg; - if( cfg!=null ) for( Node n : cfg ) blocks.add(n); - return blocks; - } - @Override public String op(Node n) { return n instanceof MachNode m ? m.op() : n.label(); } - @Override public String register(Node n) { return _code.reg(n); } - @Override public String comment(Node n) { return n.comment(); } - @Override public String phiName(Node n) { return ((PhiNode)n)._label; } - @Override public String operands(Node n) { - SB sb=new SB(); - if( n instanceof MachNode m ) m.asm(_code,sb); - else if( !(n._type instanceof TypeMem) ) { - sb.p(n._nid).p(": "); - for( Node def : n._inputs ) sb.p(def==null ? "___" : Integer.toString(def._nid)).p(' '); - } - return sb.toString(); - } - @Override public boolean labelBlock(Node n) { - return !(n instanceof FunNode) && !(n instanceof IfNode) && !(n instanceof CallNode) && - !(n instanceof CallEndNode) && !(n instanceof CProjNode && ir.input0(n) instanceof CallEndNode); - } - @Override public boolean hidden(Node n) { - if( n instanceof PhiNode || n instanceof ProjNode ) - if( n._type instanceof TypeMem || n._type instanceof TypeRPC ) return true; - if( postAlloc() && n instanceof CalleeSaveNode ) return true; - if( n instanceof MemMergeNode ) return true; - return n.getClass()==ConstantNode.class; - } - @Override public boolean postAlloc() { return _code._phase.ordinal()>CodeGen.Phase.RegAlloc.ordinal(); } - @Override public boolean encoded() { return _enc!=null && _code._phase.ordinal()>=CodeGen.Phase.Encoding.ordinal(); } - @Override public int defaultSize() { return _code._mach==null ? 2 : _code._mach.defaultOpSize(); } - @Override public boolean littleEndian() { return _code._asmLittle; } - @Override public boolean prologue(Node n) { return ((FunNode)n)._frameAdjust!=0; } - @Override public byte[] bytes() { return encoded() ? _enc._bits.buf() : null; } - @Override public int offset(Node n) { - return encoded() && _enc._opStart!=null && n._nid<_enc._opStart.length ? _enc._opStart[n._nid] : -1; - } - @Override public int size(Node n) { - return encoded() && _enc._opLen!=null && n._nid<_enc._opLen.length ? _enc._opLen[n._nid]&255 : 0; - } - private static ArrayList<Data> entries(Iterable<Encoding.Relo> relos) { - var data=new ArrayList<Data>(); - for( Encoding.Relo r : relos ) { - int align=1<<r._align; - int size=r._t instanceof TypeStruct ? (r._structSize+align-1)&-align : align; - if( r._t instanceof TypeTuple t ) size=align*t._types.length; - data.add(new Data(r._t,r._t.str(),r._align,size)); - } - return data; - } - @Override public ArrayList<Pool> pools(int codeEnd) { - var pools=new ArrayList<Pool>(); - if( !encoded() ) return pools; - int base=(codeEnd+15)&-16; - if( !_enc._bigCons.isEmpty() && base<_enc._bits.size() ) - pools.add(new Pool("Constant Pool",_enc._bits.buf(),base,base,entries(_enc._bigCons.values()))); - return pools; - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/print/IRPrinter.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/print/IRPrinter.java deleted file mode 100644 index c912ab77..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/print/IRPrinter.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.seaofnodes.simple.print; - -import com.seaofnodes.print.IRAdapter; -import com.seaofnodes.simple.node.*; -import java.util.ArrayList; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.codegen.CodeGen; - -/** This chapter's read-only IR facts and the familiar debugger entry points. */ -public final class IRPrinter extends IRAdapter<Node> { - private static final com.seaofnodes.print.IRPrinter<Node> PRINT = - new com.seaofnodes.print.IRPrinter<>(new IRPrinter()); - @Override public boolean global(Node n) { return n.isConst(); } - @Override public String type(Node n) { return n._type==null ? "" : n._type.str(); } - @Override public String inputMark(Node n, Node def) { return n instanceof MemMergeNode && def instanceof MemMergeNode ? "^" : " "; } - @Override public Kind kind(Node n) { - if( n instanceof StartNode ) return Kind.START; - if( n instanceof StopNode ) return Kind.STOP; - if( n instanceof FunNode ) return Kind.FUN; - if( n instanceof LoopNode ) return Kind.LOOP; - if( n instanceof RegionNode ) return Kind.REGION; - if( n instanceof ParmNode ) return Kind.PARM; - if( n instanceof PhiNode ) return Kind.PHI; - if( n instanceof CallEndNode ) return Kind.CALL_END; - if( n instanceof CallNode ) return Kind.CALL; - if( n instanceof ReturnNode ) return Kind.RETURN; - if( n instanceof ConstantNode ) return Kind.CONSTANT; - if( n instanceof CProjNode ) return Kind.CPROJ; - if( n instanceof ProjNode ) return n instanceof CFGNode ? Kind.CPROJ : Kind.PROJ; - if( n instanceof CFGNode ) return Kind.CTRL; - if( n instanceof MultiNode ) return Kind.MULTI; - return Kind.DATA; - } - @Override public int index(Node n) { - if( n instanceof ProjNode p ) return p._idx; - if( n instanceof CProjNode p ) return p._idx; - return n._nid; - } - @Override public String functionName(Node n) { return ((FunNode)n)._name==null ? "" : ((FunNode)n)._name; } - @Override public String signature(Node n) { - FunNode fun=(FunNode)n; - return (fun._name==null ? "" : fun._name)+" "+fun.sig().str(); - } - - public static String prettyPrint(Node n, int depth) { return PRINT.prettyPrint(n,depth); } - public static SB printLine(Node n, SB sb) { return sb.p(PRINT.line(n)); } - - public static String prettyPrint(CodeGen code) { - var units=new ArrayList<com.seaofnodes.print.IRPrinter.Unit<Node>>(); - var funs=new ArrayList<Node>(); - for( Node n : code._start._outputs ) if( n instanceof FunNode ) funs.add(n); - units.add(new com.seaofnodes.print.IRPrinter.Unit<>(null,null,null,funs)); - return PRINT.program(code._start,code._stop,units); - } - public static String _prettyPrint(CodeGen code) { - if( code._cfg==null ) return prettyPrint(code); - var blocks=new ArrayList<Node>(); - for( Node n : code._cfg ) blocks.add(n); - return PRINT.scheduled(blocks); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java deleted file mode 100644 index 8dc6ea47..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.seaofnodes.simple.print; - -import com.seaofnodes.graph.GraphAdapter; -import com.seaofnodes.graph.GraphSnapshot.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.type.TypeMem; - -/** Chapter 24's view of the IR; browser, transport and layout are shared. */ -public class SimpleGraphAdapter extends GraphAdapter<Node> { - - @Override protected String edgeName(Node n, int i) { - return n instanceof ScopeNode scope && i < scope._vars.size() - ? scope._vars.get(i)._name : null; - } - @Override protected int edgeJump(Node n, int i) { - return n instanceof CallEndNode && i>0 && n.in(i) instanceof ReturnNode ret - ? ref(ret.fun()) : 0; - } - @Override protected int projectionIndex(Node n) { return n instanceof ProjNode p ? p._idx : n instanceof CProjNode p ? p._idx : -1; } - - @Override protected boolean folding(Node n) { - if( n instanceof FunNode fun ) return fun.folding(); - if( n instanceof ReturnNode ret ) return ret.fun().folding(); - if( n instanceof CallEndNode cend ) return cend.folding(); - if( n instanceof CallNode ) - for( int i=0; i<n.nOuts(); i++ ) - if( n.out(i) instanceof CallEndNode cend && cend.folding() ) return true; - return false; - } - - @Override protected Kind kind(Node n) { - if( n instanceof StopNode ) return Kind.STOP; - if( n instanceof ScopeNode ) return Kind.SCOPE; - if( n instanceof FunNode ) return Kind.FUN; - if( n instanceof StartNode ) return Kind.CTRL; - if( n instanceof LoopNode ) return Kind.LOOP; - if( n instanceof RegionNode ) return Kind.REGION; - if( n instanceof PhiNode ) return Kind.PHI; - return n instanceof CFGNode ? Kind.CTRL : isMem(n) ? Kind.MEM : Kind.DATA; - } - - @Override protected Role role(Node n, int i) { - if( n instanceof ScopeNode || n instanceof ConstantNode || n instanceof CtrlNode || n instanceof XCtrlNode ) return Role.ASSOC; - if( n instanceof PhiNode ) return i == 0 ? Role.ASSOC : isMem(n) ? Role.MEM : Role.DATA; - if( n instanceof ProjNode || n instanceof CProjNode ) - return n instanceof CFGNode ? Role.CTRL : isMem(n) ? Role.MEM : Role.DATA; - if( n instanceof RegionNode && i == 0 ) return Role.ASSOC; - if( i == 0 || n instanceof RegionNode || n instanceof StopNode ) return Role.CTRL; - if( n instanceof MemMergeNode || (i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode || n instanceof CallNode || n instanceof NewNode)) ) return Role.MEM; - Node def = n.in(i); - if( def != null && isMem(def) ) return Role.MEM; - return Role.DATA; - } - - private boolean isMem(Node n) { return n.isMem() || n._type instanceof TypeMem; } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java deleted file mode 100644 index b2c5492e..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.seaofnodes.simple.print; - -import com.seaofnodes.graph.GraphCapture; -import com.seaofnodes.graph.GraphViewer; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.node.Node; -import java.util.ArrayList; -import java.io.IOException; - -/** Chapter 24's compilation context; capture and event bookkeeping are shared. */ -public class SimpleGraphObserver extends GraphCapture<Node> { - private CodeGen _code; - public SimpleGraphObserver() { super(new SimpleGraphAdapter()); } - public static void main(String[] args) throws IOException { - GraphViewer.run(new SimpleGraphObserver()); - } - @Override protected void compile(String src) { - _code = new CodeGen(src); - _code._obs = this; - _code.driver(CodeGen.Phase.TypeCheck); - } - @Override protected String phase() { return _code._phase.name(); } - @Override protected int pos() { return _code._phase == CodeGen.Phase.Parse ? _code.P.pos() : -1; } - @Override protected void detach() { - if( _code != null ) _code._obs = null; - _code = null; - } - @Override protected Node scope() { return _code.P._scope; } - @Override protected void roots(ArrayList<Node> roots) { - roots.add(_code._stop); - if( _code._phase == CodeGen.Phase.Parse ) { - roots.addAll(_code.P._xScopes); - } - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/sys.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/sys.java deleted file mode 100644 index 45106770..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/Field.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/Field.java deleted file mode 100644 index 1f22cb05..00000000 --- a/chapter-interprocedural-sccp/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 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/Type.java deleted file mode 100644 index b9a6a1f3..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/Type.java +++ /dev/null @@ -1,526 +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 _type==t._type && 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); - // Constant arrays meet their integer element types. - if( this instanceof TypeConAry tcon && t instanceof TypeInteger ti ) return tcon.ymeet(ti); - if( t instanceof TypeConAry tcon && this instanceof TypeInteger ti ) return tcon.ymeet(ti); - 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 - // Applies for pessimistic case - 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; } - - // Writable array slots are invariant in their element access permissions. - // Otherwise an alias could insert a readonly pointer into a writable slot. - public boolean accessISA(Type dst) { return true; } - - // Is forward-reference - public boolean isFRef() { return false; } - - // Cap at limits - public Type oob() { return isHigh() ? TOP : BOTTOM; } - - // ---------------------------------------------------------- - - // 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; } - public Type widen() { 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeConAry.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeConAry.java deleted file mode 100644 index 0bc264fc..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeConAry.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.seaofnodes.simple.type; - -import java.io.ByteArrayOutputStream; -import com.seaofnodes.simple.util.Utils; -import java.util.ArrayList; - -/** - * Represents a constant array of primitives - */ -public abstract class TypeConAry<A> extends Type { - boolean _any; - byte _widen; - // One of byte or int array - public final A _ary; - - TypeConAry( boolean any, byte widen, A ary ) { super(TCONARY); _any = any; _widen = widen; _ary = ary; } - public static void gather(ArrayList<Type> ts) { - ts.add(TypeConAryB.ABC); - ts.add(TypeConAryB.ABCD); - ts.add(TypeConAryI.I123); - } - // Fresh, uninterned type sharing this array; xdual must not intern. - abstract TypeConAry<A> _make(boolean any, byte widen); - - @Override TypeConAry<A> xdual() { return _make( !_any, (byte)(3-_widen) ); } - - @Override Type xmeet(Type t) { - TypeConAry ary = (TypeConAry)t; // Invariant - // Same base array but different? - if( _equals(ary) ) - // Return array with larger widen - return _widen >= ary._widen ? this : ary; - // Unrelated constant arrays, falls to some int range - return elem().meet(ary.elem()); - } - - Type ymeet( TypeInteger ti ) { - // if i can isa *each* element, then maybe can keep. - // no good to i.isa(elem()) because fails the dual - for( int j=0; j<len(); j++ ) - if( !ti.isa(TypeInteger.make(at8(j),at8(j),ti._widen)) ) - return ti.meet(elem()); - byte widen = (byte)Math.max(_widen,ti._widen); - return widen==_widen ? this : _make(_any,widen).intern(); - } - - private boolean _equals(TypeConAry ary) { - int len = len(); - if( len != ary.len() ) return false; - for( int i=0; i<len; i++ ) - if( at8(i) != ary.at8(i) ) - return false; - return true; - } - - @Override public boolean isHigh() { return this==TOP; } - @Override boolean _isConstant() { return true; } - @Override Type _glb(boolean mem) { return this; } - @Override boolean _isGLB(boolean mem) { 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,_widen); - } - public abstract long at8(int idx); - public abstract int len(); - @Override public abstract int log_size(); - public void write( ByteArrayOutputStream baos ) { throw Utils.TODO("Should not reach here: abstract constant array cannot be written"); } - -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java deleted file mode 100644 index 095cdeca..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.seaofnodes.simple.type; - -import java.io.ByteArrayOutputStream; -import java.util.Arrays; - -public class TypeConAryB extends TypeConAry<byte[]> { - private TypeConAryB(boolean any, byte widen, byte[] bs) { super(any,widen,bs); } - @Override TypeConAryB _make( boolean any, byte widen ) { return new TypeConAryB(any,widen,_ary); } - public static TypeConAryB make( byte[] bs ) { return new TypeConAryB(false,(byte)0,bs).intern(); } - public static TypeConAryB make( String s ) { return make(s.getBytes()); } - static final TypeConAryB ABC = make("abc"); - static final TypeConAryB ABCD = make("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 t instanceof TypeConAryB ary && _widen==ary._widen && Arrays.equals(_ary,ary._ary); } - @Override int hash() { return Arrays.hashCode(_ary); } - @Override public void write( ByteArrayOutputStream baos ) { baos.write(_ary,0,_ary.length); } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java deleted file mode 100644 index b1b061de..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.seaofnodes.simple.type; - -import java.util.Arrays; - -public class TypeConAryI extends TypeConAry<int[]> { - private TypeConAryI(boolean any, byte widen, int[] bs) { super(any,widen,bs); } - public static TypeConAryI make( int[] bs ) { return new TypeConAryI(false,(byte)0,bs).intern(); } - @Override TypeConAryI _make( boolean any, byte widen ) { return new TypeConAryI(any,widen,_ary); } - static final TypeConAryI I123 = make(new int[]{1,2,3}); - @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 t instanceof TypeConAryI ary && _widen==ary._widen && Arrays.equals(_ary,ary._ary); } - @Override int hash() { return Arrays.hashCode(_ary); } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeFloat.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeFloat.java deleted file mode 100644 index f3de581c..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java deleted file mode 100644 index 50743f69..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java +++ /dev/null @@ -1,269 +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 ) { - // Trim trailing args which match the implicit default. - Type def = open ? Type.BOTTOM : Type.TOP; - int nargs = sig.length; - while( nargs > 0 && sig[nargs-1] == def ) - nargs--; - if( nargs != sig.length ) - sig = nargs == 0 ? TEMPTY : Arrays.copyOf(sig,nargs); - 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); - // Explicit trailing defaults must agree with the implicit argument tail. - for( boolean open : new boolean[]{false,true} ) { - Type def = open ? Type.BOTTOM : Type.TOP; - ts.add(make((byte)2,open,TINT,TypeInteger.I32,1)); - ts.add(make((byte)2,open,new Type[]{TypeInteger.BOT,def,def},TypeInteger.I32,1)); - ts.add(make((byte)2,open,new Type[]{def,def},TypeInteger.I32,1)); - ts.add(make((byte)2,open,new Type[]{def,TypeInteger.BOT},TypeInteger.I32,1)); - } - } - - @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 public 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); } - public int nfcns() { - if( _fidxs < 0 ) return Integer.MAX_VALUE; // Infinite choices - return Long.bitCount(_fidxs); - } - - - // Used to walk over type structures - @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() { - if( this==BOT ) return "FunBot"; - if( this==BOT.dual() ) return "FunTop"; - return "{"+printFIDX()+"}"; - } - - SB _print(SB sb, BitSet visit, boolean html ) { - if( this==BOT ) return sb.p("FunBot"); - if( this==BOT.dual() ) return sb.p("FunTop"); - 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 fidx = Long.numberOfTrailingZeros(fidxs); ... } - public static long nextFIDX(long fidxs) { return fidxs & (fidxs-1); } - -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeInteger.java deleted file mode 100644 index 74fbfb9f..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeInteger.java +++ /dev/null @@ -1,183 +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; - - public byte _widen; - - private static final Ary<TypeInteger> FREE = new Ary<>(TypeInteger.class); - private TypeInteger(long min, long max, byte widen) { super(TINT); init(min,max,widen); } - private TypeInteger init(long min, long max, byte widen) { - _min = min; _max = max; _widen=widen; - return this; - } - public static TypeInteger malloc(long lo, long hi, byte widen) { return FREE.isEmpty() ? new TypeInteger(lo,hi,widen) : FREE.pop().init(lo,hi,widen); } - public static TypeInteger make(long lo, long hi) { return make(lo,hi,(byte)0); } - public static TypeInteger make(long lo, long hi, byte widen) { - TypeInteger i = malloc(lo,hi,widen); - TypeInteger t2 = i.intern(); - return t2==i ? i : t2.free(i); - } - public TypeInteger makeWide(byte widen) { - if( widen == _widen ) return this; - if( _min == Long.MIN_VALUE && _max == Long.MAX_VALUE ) - return TypeInteger.BOT; - if( _min==_max ) return this; // Leave constants always narrow - return make(_min,_max,widen); - } - @Override TypeInteger free(Type t) { - TypeInteger i = (TypeInteger)t; - i._min = i._max = 0; - i._widen = 0; - i._hash = 0; - i._dual = null; - FREE.push(i); - return this; - } - - public Type same_but_slightly_wider_than( Type minType ) { - return _widen < 3 ? make(_min,_max, (byte)(_widen+1)) : minType; - } - // Force max widen - @Override public Type widen() { - return _widen==3 ? this : make(_min,_max,(byte)3); - } - 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,(byte)3); - public final static TypeInteger I8 = make(-128,127,(byte)3); - public final static TypeInteger I16 = make(-32768,32767,(byte)3); - public final static TypeInteger I32 = make(-1L<<31,(1L<<31)-1,(byte)3); - public final static TypeInteger BOT = make(Long.MIN_VALUE,Long.MAX_VALUE,(byte)3); - public final static TypeInteger TOP = (TypeInteger)BOT.dual(); - - public final static TypeInteger U1 = make(0,1,(byte)3); - public final static TypeInteger BOOL= U1; - public final static TypeInteger U8 = make(0,255,(byte)3); - public final static TypeInteger U16 = make(0,65535,(byte)3); - public final static TypeInteger U32 = make(0,(1L<<32)-1,(byte)3); - - public final static TypeInteger FATBOOL = make(0,1,(byte)2); - - public static void gather(ArrayList<Type> ts) { - ts.add(I32); ts.add(BOT); ts.add(U1); ts.add(I1); ts.add(U8); ts.add(ZERO); ts.add(FATBOOL); - // Ranges before and after excluding zero, at each widening level. - for( byte widen=0; widen<=3; widen++ ) { - ts.add(make(0,255,widen)); - ts.add(make(1,255,widen)); - ts.add(make(-255,0,widen)); - ts.add(make(-255,-1,widen)); - } - } - - @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; - } - if( -128 <= _min && _max < 128 ) return 0; // 1 byte - throw Utils.TODO(); - } - - public long value() { assert _isConstant(); return _min; } - - // AND-mask of forced zeros. e.g. unsigned types will return their mask; - // u8 will return 0xFF. But also a range of 16-18 (0x10-0x12) will return - // 0x13 - no value in the range {16,17,18} will allow bit 0x04 to be set. - public long mask() { - if( isHigh() ) return 0; - if( isConstant() ) return _min; - // Those bit positions which differ min to max - long x = _min ^ _max; - // Highest '1' bit in the differ set. Since the range is from min to - // max, all values below here are possible. - long ff1 = Long.highestOneBit(x); - // Make a all-1's mask from ff1, and set over the same bits (either min - // or max is ok). - long mask = _min | (ff1-1) | ff1; - return mask; - } - - @Override - public Type xmeet(Type other) { - // Invariant from caller: 'this' != 'other' and same class (TypeInteger) - TypeInteger i = (TypeInteger)other; // Contract - return make(Math.min(_min,i._min), Math.max(_max,i._max), (byte)Math.max(_widen,i._widen)); - } - - @Override TypeInteger xdual() { return malloc(_max,_min,(byte)(3-_widen)); } - - @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),_widen); // specifically good on BOOL - if( _max==0 ) return make(_min,-1,_widen); - 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) + _widen; } - @Override public boolean eq( Type t ) { - return t instanceof TypeInteger i && _min==i._min && _max==i._max && _widen==i._widen; - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeMem.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeMem.java deleted file mode 100644 index d5ee8bd8..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeMem.java +++ /dev/null @@ -1,110 +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) { - if( this==BOT ) return sb.p("MemBot"); - if( this==TOP ) return sb.p("MemTop"); - 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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java deleted file mode 100644 index 9fc90544..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java +++ /dev/null @@ -1,177 +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; - // Deep read-only access through this pointer; independent of slot finality. - public boolean _ro; - - 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; _ro=obj==TypeStruct.BOT; _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) { - return make(nil,obj,one,obj==TypeStruct.BOT); - } - private static TypeMemPtr make(byte nil, TypeStruct obj, boolean one, boolean ro) { - TypeMemPtr tmp = malloc(nil, obj, one).access(ro); - 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,_ro); } - public TypeMemPtr makeNullable() { return makeFrom((byte)3); } - @Override TypeMemPtr makeFrom(byte nil) { return nil==_nil ? this : make(nil, _obj, _one,_ro); } - public TypeMemPtr makeHigh() { return make((byte)0,_obj.makeHigh(),false); } - - private TypeMemPtr access(boolean ro) { _ro=ro; return this; } - - public TypeMemPtr withAccess(boolean ro) { - if( _ro==ro ) return this; - TypeMemPtr ptr = malloc(_nil, _obj, _one); - ptr._ro = ro; - TypeMemPtr res = ptr.intern(); - if( res==ptr ) return ptr; - return VISIT.isEmpty() ? res.free(ptr) : res.delayFree(ptr); - } - - // Writable slots can load and store references, so their element access - // must agree in both directions. A read-only outer view can weaken access. - @Override public boolean accessISA(Type dst) { - if( !(dst instanceof TypeMemPtr ptr) ) return true; - if( _ro && !ptr._ro ) return false; - if( ptr._ro || _obj._fields==null || ptr._obj._fields==null || !_obj.isAry() || !ptr._obj.isAry() ) return true; - Type a = _obj._fields[1]._t, b = ptr._obj._fields[1]._t; - return a.accessISA(b) && b.accessISA(a); - } - - // 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); ts.add(TEST.makeRO()); } - - @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,_ro | that._ro); - } - - @Override - TypeMemPtr xdual() { return malloc( dual0(), _obj.dual(), _one).access(!_ro); } - - @Override TypeMemPtr rdual() { - if( _dual!=null ) return dual(); - assert !_terned; - TypeMemPtr d = malloc(dual0(), null, _one).access(!_ro); - (_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,false,_ro); } - - @Override boolean _isConstant() { return _one && _obj._isConstant(); } - @Override boolean _isFinal() { return _ro; } - @Override TypeMemPtr _makeRO() { return withAccess(true); } - @Override boolean _isGLB(boolean mem) { return _ro && _nil==3 && !_one && _obj.isGLB2(); } - @Override TypeMemPtr _glb(boolean mem) { return make((byte)3,_obj.glb2(),false,true); } - @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 || (_ro && !that._ro) ) 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 (_ro ? 8192 : 0) ^ _obj.hashCode() ^ super.hash() ^ (_one ? 2048 : 0); } - - @Override boolean eq(Type t) { - TypeMemPtr ptr = (TypeMemPtr)t; // Invariant - return _ro==ptr._ro && 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 _ro==ptr._ro && 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==BOT ) return "PtrBot"; - if( this==TOP ) return "PtrTop"; - if( this== NOTBOT) return "*void"; - return x()+"*"+_obj.str()+q(); - } - - @Override SB _print(SB sb, BitSet visit, boolean html ) { - if( this==BOT ) return sb.p("PtrBot"); - if( this==TOP ) return sb.p("PtrTop"); - if( this== NOTBOT) return sb.p("*void"); - return _obj.print(sb.p(x()).p("*"),visit,html).p(q()); - } -} diff --git a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeNil.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeNil.java deleted file mode 100644 index 56d73b00..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypePtr.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypePtr.java deleted file mode 100644 index 75478119..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeRPC.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeRPC.java deleted file mode 100644 index a1389540..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeRPC.java +++ /dev/null @@ -1,111 +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; -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 - HashSet<Integer> _rpcs; - - // If true, invert the meaning of the bits - boolean _any; - - private static final Ary<TypeRPC> FREE = new Ary<>(TypeRPC.class); - private TypeRPC init(boolean any, HashSet<Integer> rpcs) { _any=any; _rpcs=rpcs; return this; } - private TypeRPC() { super(TRPC); } - private static TypeRPC malloc(boolean any, HashSet<Integer> rpcs) { - return (FREE.isEmpty() ? new TypeRPC() : FREE.pop()).init(any,rpcs); - } - private static TypeRPC make(boolean any, HashSet<Integer> rpcs) { - TypeRPC rpc = (FREE.isEmpty() ? new TypeRPC() : FREE.pop()).init(any,rpcs); - TypeRPC t2 = rpc.intern(); - return t2==rpc ? rpc : t2.free(rpc); - } - @Override TypeRPC free(Type t) { - TypeRPC rpc = (TypeRPC)t; - rpc._rpcs = null; - rpc._dual = null; - rpc._hash = 0; - FREE.push(rpc); - return this; - } - @Override boolean isFree() { return _rpcs==null; } - - 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 malloc(!_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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeStruct.java deleted file mode 100644 index 55942b0e..00000000 --- a/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeStruct.java +++ /dev/null @@ -1,493 +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 makeHigh() { - Field[] fs = new Field[_fields.length]; - for( int i=0; i<_fields.length; i++ ) - fs[i] = _fields[i].makeFrom(Type.TOP); - return make(_name,false,fs); - } - - 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() { - Object key = isAry() ? _uid : _name; - TypeStruct ts = (TypeStruct)VISIT.get(key); - if( ts!=null ) return ts; - ts = recurPre(key,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( this==BOT ) return sb.p("StructBot"); - if( this==TOP ) return sb.p("StructTop"); - 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() { - if( this==BOT ) return "StructBot"; - if( this==TOP ) return "StructTop"; - 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.FinalizeIR.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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/type/TypeTuple.java deleted file mode 100644 index df9aba31..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/util/Ary.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/util/Ary.java deleted file mode 100644 index d67801c2..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/util/AryInt.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/util/AryInt.java deleted file mode 100644 index b498fd78..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/util/SB.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/util/SB.java deleted file mode 100644 index d7afd6eb..00000000 --- a/chapter-interprocedural-sccp/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/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/util/Utils.java b/chapter-interprocedural-sccp/src/main/java/com/seaofnodes/simple/util/Utils.java deleted file mode 100644 index 577a60af..00000000 --- a/chapter-interprocedural-sccp/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); } -} diff --git a/chapter-interprocedural-sccp/src/main/smp/sys.smp b/chapter-interprocedural-sccp/src/main/smp/sys.smp deleted file mode 100644 index 6a6d3ca3..00000000 --- a/chapter-interprocedural-sccp/src/main/smp/sys.smp +++ /dev/null @@ -1,199 +0,0 @@ -// -*- mode: java; -*- - -// top-level default import -struct sys { - // libc bindings - /** https://www.man7.org/linux/man-pages/man2/<libc call>.2.html */ - struct libc { - i32 errno = "C"; - // fd buf len -> len - { i32 i64 u32 -> u32 } write = "C"; - // fd buf len -> len - { i32 i64 u32 -> u32 } read = "C"; - // status - { i32 -> i32 } _exit = "C"; - // addr len prot flags fd off -> void* - { i64 i64 i32 i32 i32 i32 -> i64 } mmap = "C"; - // mmap flags - i32 PROT_EXEC = 4; - i32 PROT_READ = 1; - i32 PROT_WRITE= 2; - i32 PROT_NONE = 0; - i32 MAP_PRIVATE = 2; - i32 MAP_ANON = 32; - }; - struct ary { - val copy = { u8[] dst, int doff, u8[~] src, int soff, int len -> - for( int i=0; i<len; i++ ) - dst[i+doff] = src[i+soff]; - return dst; - }; - }; - - // Ary/Vector of u8s - a resizable array of bytes. - struct aryu8 { - u32 !len = 0; - u8[] !buf; - // Grow as needed - val _grow = { int len2 -> - if( len+len2 < buf# ) return self; - int newlen = buf# * 2; - while( len+len2 >= newlen ) newlen *= 2; - u8[] buf2 = new u8[newlen]; - sys.ary.copy(buf2,0,buf,0,buf#); - buf = buf2; - self; - }; - - // Add a character to aryu8 - val add = { u8 c -> - _grow(1).buf[len++] = c; - self; - }; - - // Add a plain array to the aryu8 - val addAry = { u8[~] ary -> - _grow(ary#); - sys.ary.copy(buf,len,ary,0,ary#); - len += ary#; - self; - }; - - // Remove x chars from the tail. - val undo = { int x -> - len = len >= x ? len-x : 0; - self; - }; - - // Print ASCII number into aryu8 - val num = { i64 x -> - if( !x ) { add('0'); return self; } - if( x < 0 ) { add('-'); x = -x; } - i64 ten = 1, log = 0; - while( ten <= x ) { ten *= 10; log++; } - for( int i=0; i<log; i++ ) { - ten /= 10; - i64 q = x/ten; - u8 digit = q+'0'; - add(digit); - x -= q*ten; - } - self; - }; - - // Write to fd, returns the number of bytes written (len). - val write = { i32 fd -> i64 ptr = buf; sys.libc.write(fd,ptr,len); }; - }; - - - // Scanner; a simple string parser. - struct Scan { - int !x; - u8[~] buf; - - // Skip whitespace - val skip = { -> - while( buf[x] <= ' ' ) - x++; - return self; - }; - - // True if find required character, and skip it; else False. - val match = { u8 c -> - skip(); - if( buf[x] != c ) return false; - x++; - return true; - }; - - // Parse a simple number - val number = { -> - skip(); - bool neg = match('-'); - i64 n = 0; - while( '0' <= buf[x] & buf[x] <= '9' ) - n = n*10 + (buf[x++] - '0'); - return neg ? -n : n; - }; - }; - - // Ary/Vector of i64s - struct aryi64 { - u32 !len = 0; - i64[] !buf; - // Grow as needed - val _grow = { int len2 -> - if( len+len2 < buf# ) return self; - int newlen = buf# * 2; - while( len+len2 >= newlen ) newlen *= 2; - i64[] buf2 = new i64[newlen]; - for( int i=0; i<buf#; i++ ) - buf2[i] = buf[i]; - buf = buf2; - self; - }; - - // Add a i64 to aryu8 - val add = { i64 c -> - _grow(1).buf[len++] = c; - self; - }; - - // Write to aryu8 - val str = { -> - // TODO: clunky default constructor syntax - var str = new sys.aryu8 { buf = new u8[1]; }.add('['); - for( int i=0; i<len; i++ ) - str.num(buf[i]).add(',').add(' '); - str.undo(2).add(']'); - }; - }; - - // Parse a comma-separated list of numbers, wrapped with square brackets, - // with optional whitespace e.g. "[ -17, 0,99,12456789 ] ". - // parseAryI64 & aryi64.str are bijections modulo whitespace. - // Returns null if the parse failed. - val parseAryI64 = { u8[~] str -> - !sys.Scan !s = new sys.Scan{ buf = str; }; - if( !s.match('[') ) return null; - // Array of parsed results - var nums = new sys.aryi64{ buf=new i64[1]; }; - // Parse a list of numbers - if( !s.match(']') ) { // Handle the empty list - // Parse "123, " until a something else - while( s.x < s.buf# ) { - nums.add(s.number()); - if( !s.match(',') ) - break; - }; - if( !s.match(']') ) return null; // Close the list - } - return nums; - }; - - struct io { - // Print string to stdout - val p = { u8[~] str -> - i64 ptr = str; // cast array base to i64 - return sys.libc.write(1,ptr,str#); - }; - - // Return stdin as a u8[] - val stdin = { -> - var buf = new u8[8*1024]; - i64 ptr = buf; - int len = 0; - while( true ) { - int rez = sys.libc.read(0,ptr,buf#); - if( rez < 0 ) sys.libc._exit(sys.libc.errno); - len += rez; - if( rez < buf# ) - break; - sys.libc._exit(-2); // Need to implement - } - // Compress - var dst = new u8[len]; - return sys.ary.copy(dst,0,buf,0,len); - }; - }; -}; |
