diff options
| author | Cliff Click <cliffc@acm.org> | 2026-10-24 15:25:58 -0700 |
|---|---|---|
| committer | Cliff Click <cliffc@acm.org> | 2026-10-24 15:25:58 -0700 |
| commit | 4ae6a06d94b228a5eb67c8473131a791e310c233 (patch) | |
| tree | 839dfa84f1bfb982122ce62183b6ae4145a4c615 /chapter-modules/src/main | |
| parent | c4364363dbd969c9aa27aa7c43fbe6d40544501a (diff) | |
| download | Simple-4ae6a06d94b228a5eb67c8473131a791e310c233.tar.gz Simple-4ae6a06d94b228a5eb67c8473131a791e310c233.zip | |
Rename modules chapter to 28a
Diffstat (limited to 'chapter-modules/src/main')
298 files changed, 0 insertions, 28157 deletions
diff --git a/chapter-modules/src/main/c/simple_crt.c b/chapter-modules/src/main/c/simple_crt.c deleted file mode 100644 index 8656a3cc..00000000 --- a/chapter-modules/src/main/c/simple_crt.c +++ /dev/null @@ -1,61 +0,0 @@ -#include <stdint.h> -#include <stdlib.h> -#include <string.h> -#include <errno.h> - -// errno may be thread-local or a macro calling a platform accessor. -int32_t simple_errno(void) { return errno; } - -// Native process arguments converted to ordinary Simple array layouts. -// The outer array is mutable; every contained byte array is published as -// immutable and non-null to Simple code. -typedef struct SimpleBytes { - uint32_t len; - unsigned char bytes[]; -} SimpleBytes; - -typedef struct SimpleArgs { - uint32_t len; - uint32_t pad; - SimpleBytes *args[]; -} SimpleArgs; - -__attribute__((CALL_CONV)) -extern int64_t simple_main(SimpleArgs *args); - -static SimpleArgs *simple_args(int argc, char **argv) { - if (argc < 0 || (uint64_t)argc > UINT32_MAX) - return NULL; - - SimpleArgs *args = calloc(1, sizeof(*args) + (size_t)argc * sizeof(args->args[0])); - if (args == NULL) - return NULL; - args->len = (uint32_t)argc; - - for (int i = 0; i < argc; i++) { - size_t len = strlen(argv[i]); - if (len > UINT32_MAX) { - free(args); - return NULL; - } - SimpleBytes *str = malloc(sizeof(*str) + len); - if (str == NULL) { - for (int j = 0; j < i; j++) free(args->args[j]); - free(args); - return NULL; - } - str->len = (uint32_t)len; - memcpy(str->bytes, argv[i], len); - args->args[i] = str; - } - return args; -} - -int main(int argc, char **argv) { - SimpleArgs *args = simple_args(argc, argv); - if (args == NULL) - return 1; - // The language-level hidden receiver is not part of the emitted ABI for - // free functions; source argument zero occupies native argument zero. - return (int)simple_main(args); -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter-modules/src/main/java/com/seaofnodes/simple/IterPeeps.java deleted file mode 100644 index e65ef6d9..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ /dev/null @@ -1,349 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.Opto; -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.IdentityHashMap; -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. It's 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; - final WorkList<CallEndNode> _workInline; - private int _cnt; - - 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 and inlining to a fixed point - */ - public void iterate( CodeGen code ) { - boolean didInline = false; - Ary<Node> defer = new Ary<>(Node.class); - while( true ) { - // Clean up everything that does not grow the code - iteratePeeps(code); - - // Pick an inline candidate, no real heuristic, first come, first served - boolean inlined = false; - CallEndNode cend; - while( (cend=_workInline.pop()) != null) { - if( cend.isDead() ) continue; - byte inline = cend.maybeInline(); - if( inline == -1 ) defer.add(cend); - if( inline > 0 ) { - inlined = true; - break; - } - } - // Inlined, run peeps until clean again - if( inlined ) { didInline = true; continue; } - // No more candidates, check the defer list - if( !didInline ) break; // No more progress, so all the "maybe inline after cleanup" do not progress - // Some inlining happened, retry all the "try again after cleanup" calls - didInline = false; - code.addAll(defer); - defer.clear(); - } - } - - // Run all the code-reduction and type-lifting peeps as possible - private void iteratePeeps( CodeGen code ) { - assert !CodeGen.expensiveAssert(0) || (progressOnList(code, _work) && schedulableUses(code)); - - Node n; - while( (n=_work.pop()) != null ) { - if( n.isDead() ) continue; - _cnt++; // Useful for debugging, searching which peephole broke things - var obs = code._obs; // Collect peephole neighbors for graph display - if( obs != null ) obs.before(n); - Node x = n.peepholeOpt(); - if( n instanceof CallEndNode cend ) - _workInline.push(cend); - if( x != null ) { - assert !x.isDead(); // Peepholes return alive answers - // 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); - // A self-returning peephole can have rewritten its input - // edges. The new defs gained a user and may have - // backwards, user-sensitive peepholes of their own. - for( Node z : x._inputs ) _work.push(z); - // 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(); - // Very expensive assert. - assert !CodeGen.expensiveAssert(_cnt) || (progressOnList(code, _work) && schedulableUses(code)); - } - boolean unused = n.isUnused(); - if( unused ) { - assert !(n instanceof StopNode); // StopNodes can die if all code in the compunit dies - n.kill(); // Just plain dead - } - if( obs != null ) obs.after(n, x!=null ? x : unused ? n : null, true); - } - assert !CodeGen.expensiveAssert(0) || schedulableUses(code); - - } - - // 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 ) { - code._midAssert = true; - Node changed = code._stop.walk( n -> { - // Ignore most in-progress things - if( n.iskeep() ) return null; - // Types must be forwards, even if on the worklist. - Type nval = n.compute(); - assert nval.isa(n._type) : "Non-monotonic peep: "+n+"#"+n._nid+" old="+n._type+" new="+nval+" inputs="+inputTypes(n)+" peep="+n; - if( list.on(n) ) - return null; // On worklist is ok! - if( n instanceof CallEndNode cend ) { - if( code._iter._workInline.on(cend) ) - return null; // On inline worklist is ok! - assert cend.maybeInline() <= 0 : "Inline fired and not on worklist, CallEndNode#"+cend._nid; - } - // Off list: must be live, in GVN table, and no peeps. - assert n.nOuts() > 0 : "Unused live node: "+n; - assert code._gvn.get(n)==n : "Not in GVN table: "+n; - Node m = n.peepholeOpt(); - assert m==null : "Peep fired and not on worklist, "+n.getClass().getSimpleName()+"#"+n._nid+" -> "+m; - return m; - }); - code._midAssert = false; - return changed==null; - } - - private static String inputTypes(Node n) { - StringBuilder sb = new StringBuilder("["); - for( Node in : n._inputs ) - sb.append(in==null ? "null" : in.getClass().getSimpleName()+"#"+in._nid+"="+in+":"+in._type+":keep="+in.iskeep()).append(','); - return sb.append(']').toString(); - } - - // GCM assumes every movable node can be placed no earlier than its inputs - // and no later than the LCA of its uses. Catch graphs where a use escapes - // above an input-defined branch before the later scheduling pass walks off - // the top of the idom tree. - private static final IdentityHashMap<Node,CFGNode> earlyCache = new IdentityHashMap<>(); - private static final BitSet VISIT = new BitSet(); - public static boolean schedulableUses(CodeGen code) { - earlyCache.clear(); - Node bad = code._stop.walk( n -> { - if( n.iskeep() || n.isDead() || n.isConst() || - n instanceof CFGNode || n instanceof PhiNode || n instanceof ProjNode ) - return null; - assert VISIT.isEmpty(); - CFGNode early = CFGNode.earlyCFG(n,earlyCache,VISIT); - VISIT.clear(); - if( early == null ) return null; - CFGNode lca = null; - for( Node use : n._outputs ) { - CFGNode ublk = useBlock(n,use); - if( ublk != null ) - lca = ublk.domLCA(lca,null); - } - if( lca == null ) return null; - if( !early.sameFun(lca) ) return null; - assert early.dominates(lca,null) : badSchedule(n,early,lca); - return null; - }); - return bad == null; - } - - private static CFGNode useBlock(Node n, Node use) { - if( use == null ) - return null; - if( use instanceof PhiNode phi ) { - CFGNode found = null; - for( int i=1; i<phi.nIns(); i++ ) - if( phi.in(i)==n ) { - if( i >= phi.region().nIns() ) - return null; - found = phi.region().cfg(i).domLCA(found,null); - } - return found; - } - return CFGNode.safeCFG(use); - } - - private static String badSchedule(Node n, CFGNode early, CFGNode lca) { - StringBuilder sb = new StringBuilder("Unschedulable data node ") - .append(n.getClass().getSimpleName()).append('#').append(n._nid) - .append(" early=").append(cfg(early)) - .append(" use-lca=").append(cfg(lca)) - .append(" node=").append(n).append("\ninputs:"); - for( int i=0; i<n.nIns(); i++ ) { - Node in = n.in(i); - sb.append("\n in").append(i).append(": ").append(node(in)) - .append(" cfg=").append(cfg(CFGNode.safeCFG(in))); - } - sb.append("\noutputs:"); - for( Node use : n._outputs ) - if( use != null ) - sb.append("\n use ").append(node(use)) - .append(" block=").append(cfg(useBlock(n,use))); - return sb.toString(); - } - - private static String node(Node n) { - return n==null ? "null" : n.getClass().getSimpleName()+"#"+n._nid+" "+n; - } - - private static String cfg(CFGNode cfg) { - return cfg==null ? "null" : cfg.getClass().getSimpleName()+"#"+cfg._nid+" "+cfg; - } - - /** - * 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); - } - public void addAll( E[] es ) { - for( E n : es ) - push(n); - } - - - /** - * True if Node is on the WorkList - */ - public boolean on( E x ) { return _on.get(x._nid); } - - /** - * 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 boolean isEmpty() { return _len==0; } - //Node[] asAry() { return Arrays.copyOf(_es,_len); } - //public void clear() { - // _len = 0; - // _on.clear(); - // _R.setSeed(_seed); - // _totalWork = 0; - //} - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/Parser.java b/chapter-modules/src/main/java/com/seaofnodes/simple/Parser.java deleted file mode 100644 index 031d36c6..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/Parser.java +++ /dev/null @@ -1,3157 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.node.ScopeNode.Kind; - -import static com.seaofnodes.simple.util.Utils.TODO; - -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 { - - // Compile driver - public final CodeGen _code; - - // The Lexer. Thin wrapper over a byte[] buffer with a cursor. - private Lexer _lexer; - - // Class prefix string; TODO: use something short: "%" - public static final String CLZ = "class:"; - private static final String clzPrefix = CLZ; - public static String addClzPrefix( String x ) { - assert !x.startsWith(clzPrefix); - return (clzPrefix+x).intern(); - } - public static boolean startsClzPrefix( String s ) { - return s.startsWith(clzPrefix); - } - public static boolean isPublicClassName( String name ) { - if( startsClzPrefix(name) ) name = name.substring(clzPrefix.length()); - return !name.isEmpty() && name.charAt(0)!='_' && name.indexOf("._")==-1; - } - - // Source file for compilation - public CompUnit _ref; - - // Current classname being parsed - private String _nestedType; - /** - * 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 _breakScope; // Merge all the while-breaks here - ScopeNode _continueScope; // Merge all the while-continues here - ScopeNode _returnScope; // Merge all the function exits here - Ary<FRefNode> _frefs; // Forward refs created during this parse - TypeStruct _ctorOpenStruct; // Open struct being initialized by an allocation constructor block. - // Constructor exit types survive scope teardown until all fields are known. - // A null entry denotes a constructor with no returning path. - private ArrayList<IdentityHashMap<Var,Type>> _ctorExits; - - // Mapping from a type name to a Type. The string name matches - // `type.str()` call. - public static final HashMap<String, Type> TYPES = new HashMap<>(); - public static final HashMap<String, Type> INIT_TYPES = - 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 - }}; - - - public Parser(CodeGen code ) { - _code = code; - } - - @Override - public String toString() { return _lexer==null ? "" : _lexer.toString(); } - - // Debugging utility to find a Node by index - public Node f(int nid) { return _code.f(nid); } - - // Read and set CTRL - private Node ctrl() { return _scope.ctrl(); } - private <N extends Node> N ctrl(N n) { return _scope.ctrl(n); } - // Read and set Memory - private Node mem() { return _scope.mem(1); } - private void mem(Node mem) { _scope.mem(mem); } - // Structural label for precise memory Phis. The '$' prefix cannot be - // referenced in Simple source. - public static String memName(int alias) { return ("$"+alias).intern(); } - - // Ordinary parsing carries one whole-memory value. Alias partitioning is - // graph optimization, not parser state. - private void storeMem( Node st, int alias, Node prior ) { - mem(alias==1 ? st : mergeAlias(prior,alias,st)); - } - - // A semantic graph partition representing the complete memory after a - // precise alias update. ScopeNode treats this as one opaque value. - private Node mergeAlias(Node bulk, int alias, Node precise) { - assert bulk != null : "Missing bulk memory for alias #"+alias; - MemMergeNode merge = new MemMergeNode(null,bulk); - merge.alias(alias,precise); - return peep(merge); - } - - - public Ary<FRefNode> parse( CompUnit ref ) { - assert _scope == null && _breakScope == null && _continueScope == null && _returnScope == null; - assert _frefs == null; - _lexer = new Lexer(ref); - _frefs = new Ary<>(FRefNode.class); - // Starting Scope has control, memory, initial arguments - _scope = new ScopeNode(); - _scope.define(ScopeNode.CTRL, Type.CONTROL , false, null, _lexer); - _scope.define(ScopeNode.MEM0, TypeMem.BOT , false, null, _lexer); - // Track active scopes for Graph display - _xScopes.push(_scope); - ctrl(_code.XCTRL); - mem(con(TypeMem.BOT)); - - // File-level struct declaration - _ref = ref; - _nestedType = ref._cname; - String typeName = addClzPrefix(ref._cname); - - ReturnNode clzret = parseStruct( true, typeName ); - - if( !_lexer.isEOF() ) throw _errorSyntax("unexpected"); - - // All still-unresolved forward refs - // should immediately resolve to a file-based name which is a Simple - // class type. - Ary<FRefNode> frefs = new Ary<>(FRefNode.class); - for( FRefNode fref : _frefs ) { - if( fref.nIns() != 1 && fref._con == FRefNode.FREF_TYPE ) continue; - CompUnit cu = ParseAll.findCompUnitOrThrow(_code,_ref,fref._name); - fref._con = TypeMemPtr.make((byte)2,TypeStruct.make(addClzPrefix(cu._cname),true),true); - frefs.push(fref); - } - - // The class being published - ref._clz = (TypeStruct)TYPES.get(typeName); - - // Clean up and reset - _xScopes.pop(); - _scope.kill(); - _scope = null; - _frefs = null; - - return frefs; - } - - - /** - * 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) { - // Record & restore the existing scope vars. The function parse might - // update fields, so when the parse is done reset back to the pre-parse - // state. - Node[] preParse = _scope.save(); - - // Parse function body normally - ReturnNode ret = _parseFunctionBody(null, sig,loc,finals,ids); - - // Reset all fields to pre-parse days - _scope.restore(preParse); - return ret; - } - - - // Parse a function body - - // Stack the {break,continue,return} scopes and a new lexical scope. - // Build a FunNode and parameters, extending scope by parm names. - // Parse the function body - // - while( not end-of-function ) - // - - parseStatement - // - - If isInit, also incrementally extend the self-type. - // If isInit - // - upgrade and close the self-type - // - Gather fields and store values - // - <init> returns self, not last - // Unstack the {break,continue,return} scopes. - - private ReturnNode _parseFunctionBody( String funName, TypeFunPtr sig, Lexer loc, String[] ids) { - return _parseFunctionBody(funName, sig, loc, null, ids); - } - - private ReturnNode _parseFunctionBody( String funName, TypeFunPtr sig, Lexer loc, java.util.BitSet finals, String[] ids) { - Constructor outerCtor=_pendingCtor; - _pendingCtor=null; - boolean isInit = FunNode.isInit(funName); - boolean isCtor = isInit || isExplicitConstructorName(funName); - String typeName = isCtor ? ((TypeMemPtr)sig._sig[0])._obj._name : null; - - // Stack parser state on the local Java stack, and unstack it later - _scope.push(new Kind.Func(funName,isCtor && !funName.endsWith(".<clinit>"))); - ScopeNode breakScope = _breakScope; _breakScope = null; - ScopeNode continueScope = _continueScope; _continueScope = null; - ScopeNode returnScope = _returnScope; _returnScope = null; - - int oldUID = _code.UID(); // Used to approximate function size - FunNode fun = (FunNode)peep(new FunNode(loc(),sig, funName, _ref, null, _ref._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,ConstantNode.seed(TypeRPC.BOT).peephole()).peephole(); - - // 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.ctrl(fun); // Scope control from function - // Private mem alias tracking per function - Node defaultMem = new ProjNode(_ref._start,1,ScopeNode.MEM0).peephole(); - Node privMem = new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,defaultMem).peephole(); - mem(privMem); - // All args, "as-if" called externally - for( int i=0; i<ids.length; i++ ) { - Type t = sig.arg(i); - // Args take a default input if called from Start/external-world - _scope.define(ids[i], t, (i==0 && ids[0]=="self") || (finals!=null && finals.get(i)), new ParmNode(ids[i],i+2,t,fun,ConstantNode.seed(t).peephole()).peephole(), loc); - } - - if( isCtor && !isInit ) { - int lex=_scope.klast()._lexSize; - TypeStruct shape=(TypeStruct)TYPES.get(typeName); - for( Field fld : shape._fields ) { - int idx=fieldVarIdxBefore(lex,fld._fname); - if( idx<0 ) continue; - Var v=_scope.var(idx); - if( v._fref || v._extern!=0 || v._uninit || _scope.in(v)._type.isConstant() ) continue; - Node value=convertExpr( - new LoadNode(loc,fld._fname,fld._alias,ctrl(),fun.parm(3),fun.parm(2),off(shape,fld._fname)).peephole(),v.type()); - _scope.update(v,value); - } - } - - // The initializer owns its run-once flag store. Parent initialization - // is a structural requirement, executed before this body by CLInit lowering. - if( fun.isClz() ) { - Node self = fun.parm(2); - Field flag = ((TypeMemPtr)self._type)._obj.field("$init"); - Node prior = mem().keep(); - Node st = peep(new StoreNode(loc,"$init",flag._alias,flag._t,ctrl(),prior,self, - fldoff(self,"$init"),con(1),false)); - storeMem(st,flag._alias,prior); - prior.unkeep(); - } - - // Parse the function body. - Node last=_code.ZERO; // Last statement as the default result - - int nvar = 0; - while (!peek('}') && !_lexer.isEOF()) { - // Parse a statement; record last statement as default return - last = _code.add(parseStatement()); - // For <init> and <clinit>, local vars are really struct fields and - // get exposed as discovered. This allows late local functions to - // use early declared fields. - if( isInit ) - nvar = updateSelfAsFieldsDiscovered( typeName, nvar ); - } - - if( isInit ) finishConstructor(); - _pendingCtor=outerCtor; - - ParmNode self = fun.parm(2); - // Last expression is the return - if( ctrl()._type==Type.CONTROL ) { - // Constructors get special return types - if( fun.isClz() ) { - assert last!=null; - } else if( isCtor ) { - last = fun.parm(3); - } - addReturn(last); - // exit path is now dead (nothing live after a "return") - } - if( isCtor ) - upgradeSelfTypeAndStoreFields( typeName, fun.isClz(), self, fun.parm(3), isInit ); - - // Build a return from the _returnScope. - // Can be no returns for never-exit functions - Node rctl = _returnScope==null ? _code.XCTRL : _returnScope.ctrl().peephole(); - Node rmem = rctl==_code.XCTRL ? con(TypeMem.TOP) : _returnScope.mem(); - Node expr = rctl==_code.XCTRL ? con(Type.TOP) : _returnScope._inputs.last().peephole(); - ReturnNode ret = (ReturnNode)peep(new ReturnNode(rctl, rmem, expr, rpc, fun)); - fun.setRet(ret); - // Approximate function size, for inlining heuristics - fun._approxUIDs = _code.UID() - oldUID; - for( Node cend : ret.outs() ) - if( cend instanceof CallEndNode ) - throw TODO(); //_code.add(cend); // have call ends ALREADY? just barely made the Return - - // Export (and keep-alive) functions until prove dead - _ref.addFun(_code,fun); - - // Unstack parser state - if( _returnScope != null ) _returnScope.kill(); - _returnScope = returnScope; - _continueScope = continueScope; - _breakScope = breakScope; - _scope.pop(); - - return ret; - } - - private int updateSelfAsFieldsDiscovered( String typeName, int nvar ) { - int lex = _scope.klast()._lexSize + 2/*Skip self and selfMem*/; - // Load self type in and out of the TYPES hashtable. - TypeStruct tself = (TypeStruct)TYPES.get(typeName); - // Start looking for newly declared vars at the lexical scope start, - // skipping two ("self" and "selfmem") and fields seen already. - while( lex + nvar < _scope.nIns() ) { - Var v = _scope.var(lex + nvar++); - // Field with sharper type - Field old = tself.field(v._name); - Field fld = v._extern!=0 - ? Field.make(v._name,v.type(),v._extern,false,true) - : old == null - ? Field.make(v._name,v.type(),_code.alias(_ref._cname),v._final) - : old.makeFrom(v.type()); - tself = tself.addOrUpdate(fld); - } - TYPES.put(typeName,tself); - return nvar; - } - - - void upgradeSelfTypeAndStoreFields( String typeName, boolean isClz, ParmNode self, ParmNode smem, boolean upgrade ) { - int lex = _scope.klast()._lexSize; - assert _scope.var(lex)._name=="self"; - TypeStruct tself = (TypeStruct)TYPES.get(typeName); - int base = lex+2; // Skip ctrl, mem - if( upgrade ) { - // Default <init>/<clinit> defines the struct and can sharpen fields. - // A named constructor only initializes the already-closed struct. - for( int i=0; i<tself.nkids(); i++ ) { - Field tfld = tself._fields[i]; - int vidx = fieldVarIdx(base,tfld._fname); - if( vidx == -1 ) - continue; - Var v = _scope.var(vidx); - // Forward-ref fields where placed in tself but will get promoted - // to an outer scope - and should not be in tself. - if( v._fref ) - tself = tself.remove(i--); - else if( v.type() != tfld._t ) { - assert v.type().isa(tfld._t); - tself = tself.replace( tfld.makeFrom( v.type() ) ); - } - } - - // Close Struct type after parsing struct body. - TYPES.put( tself._name, tself = tself.close() ); - // Improve self, selfMem types in scope. - // Class self-type is a singleton pointer. Instance <init> receives the - // broad allocation pointer, while private memory tracks initialized fields. - self._declaredType = self._type = isClz ? TypeMemPtr.make((byte)2,tself,true) : TypeMemPtr.make(constructorRecv(tself)); - FunNode fun = self.fun(); - fun.setSig(fun.sig().makeFrom(self._type,0)); - if( !isClz ) // <init> returns upgraded private memory - smem._declaredType = smem._type = TypeMem.makePrivate(tself); - } - - if( !upgrade ) { - IdentityHashMap<Var,Type> exit = null; - if( _returnScope!=null && !_returnScope.ctrl()._type.isHigh() ) { - exit = new IdentityHashMap<>(); - int first = _scope._kinds.at(_scope.depth()-2)._lexSize+2; - for( int i=first; i<lex; i++ ) - exit.put(_scope.var(i),_returnScope.in(i)._type); - } - _ctorExits.add(exit); - } else if( !isClz ) { - checkInitFields(tself,base); - } - - // When can _returnScope be null here? A never-exit constructor will - // not have any returns, and thus no need to gather values and store - // them into the (never) constructed object - if( _returnScope==null ) - return; - - // A MemMerge to gather field updates as stores. Classes update the - // normal public memory. <init> updates the private memory that got - // passed in - is treated like a normal argument and not like memory. - Node mmm = isClz ? _returnScope.mem(1) : smem; - - - // Store constructor results into fields - for( int i=0; i<tself.nkids(); i++ ) { - Field fld = tself._fields[i]; - int vidx = upgrade - ? fieldVarIdx(base,fld._fname) - : fieldVarIdxBefore(lex,fld._fname); - if( vidx == -1 ) - continue; - Var v = _scope.var(vidx); - if( v._fref || v._extern!=0 ) continue; // C storage has no Simple initializer - // NewNode's TOP field is the zero-code poison for a required - // field. Leave it alone unless the declaration body supplied a - // value; otherwise a user constructor must initialize it. - Node val = _returnScope.in(vidx); - if( upgrade && v._uninit && - !initialized(val._type) ) continue; - // A numeric merge can still be untyped while parsing. Retain its - // declared conversion at the helper boundary, as assignments do. - if( fld._t instanceof TypeInteger || fld._t instanceof TypeFloat ) - val=convertExpr(val,fld._t); - // Store value into extended struct - Node prior = mmm.keep(); - Node st = peep(new StoreNode(null, fld._fname, fld._alias, fld._t, null, prior, self, off(tself,fld._fname), val, fld._final)); - mmm = mergeAlias(prior,fld._alias,st); - prior.unkeep(); - } - mmm = mmm.peephole(); - - if( !isClz ) - // Stuff private mem into normal expression return value. - _returnScope.setDef(_returnScope.nIns()-1,mmm); - else - // Parser memory is one opaque whole-memory edge. Publish the - // completed class-field aggregate as the <clinit> return memory. - _returnScope.mem(mmm); - //_code.add(mmm); - } - - // Validate each user constructor, or an implicit empty one, before the - // declaration scope goes away. Later fields use their declaration defaults. - private static boolean initialized(Type t) { - return t!=Type.BOTTOM && !t.isHigh() && (!(t instanceof TypeNil tn) || tn.notNull()); - } - - private void checkInitFields(TypeStruct ts, int base) { - if( _returnScope==null || _returnScope.ctrl()._type.isHigh() ) return; - boolean implicit = _ctorExits.isEmpty(); - if( implicit ) _ctorExits.add(new IdentityHashMap<>()); - for( IdentityHashMap<Var,Type> exit : _ctorExits ) { - if( exit==null ) continue; - for( Field fld : ts._fields ) { - int idx = fieldVarIdx(base,fld._fname); - if( idx == -1 ) continue; - Var v = _scope.var(idx); - if( !v._uninit || v._fref ) continue; - Type t = exit.getOrDefault(v,_returnScope.in(idx)._type); - if( !initialized(t) ) - throw error("'"+ts._name+"' is not fully initialized, field '"+fld._fname+ - (implicit ? "' needs to be set in a constructor" : - "' is only partially set in the constructor"),v._loc); - } - } - } - - private int fieldVarIdx( int base, String name ) { - for( int i=base; i<_scope.nIns(); i++ ) - if( _scope.var(i)._name == name ) - return i; - return -1; - } - - // Named constructors update field Vars from the enclosing initializer - // scope. Constructor arguments and locals begin at limit and must not - // shadow the field state gathered into private memory. - private int fieldVarIdxBefore( int limit, String name ) { - for( int i=limit-1; i>=0; i-- ) - if( _scope.var(i)._name == name ) - return i; - return -1; - } - - private boolean isConstructorDecl(String name) { - TypeStruct self = _scope.constructorSelf(); - return self != null && name == initFieldName(self._name); - } - - private boolean inExplicitConstructor() { - Kind.Func func = (Kind.Func)_scope._kinds.at(_scope.enclosingFuncOrDecl()); - return isExplicitConstructorName(func._name); - } - - private boolean isExplicitConstructorName(String funName) { - TypeStruct self = _scope.constructorSelf(); - return self != null && funName == (self._name+"."+initFieldName(self._name)).intern(); - } - - private static TypeStruct constructorRecv(TypeStruct self) { - Field[] fs = new Field[self._fields.length]; - for( int i=0; i<fs.length; i++ ) { - Field fld = self._fields[i]; - fs[i] = fld.makeFrom(fld._t.makeStorage()); - } - return TypeStruct.make(self._name,self._open,fs); - } - - - /** - * 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 = _code.ZERO; - while (!peek('}') && !_lexer.isEOF()) { - _code.add(last); - 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 (inStructDeclaration() && matchx("new")) return parseConstructorDeclaration(); - else if (matchx(";") ) return _code.ZERO; // 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(); - } - - private boolean inStructDeclaration() { - return _scope.klast() instanceof Kind.Func fun && FunNode.isInstance(fun._name); - } - - // Constructor declaration: new TypeName = { args -> body }; - private record Constructor(String name, Lexer body) {} - private Constructor _pendingCtor; - - private Node parseConstructorDeclaration() { - TypeStruct self = _scope.constructorSelf(); - assert self != null; - String name = requireId(); - String expect = initFieldName(self._name); - if( name != expect ) - throw error("Constructor name '" + name + "' must match type name '" + expect + "'"); - require("="); - if( _pendingCtor!=null ) throw error("Redefining constructor '"+name+"'"); - _pendingCtor=new Constructor(name,loc()); - // Parse the body after all field declarations are known, so self.x - // and x share a local slot even when x is declared below this body. - require("{"); - int depth=1; - while( depth>0 ) { - _lexer.skipWhiteSpace(); - if( _lexer.isEOF() ) throw error("Unclosed constructor body"); - char ch=_lexer.nextChar(); - if( ch=='{' ) depth++; - else if( ch=='}' ) depth--; - else if( ch=='"' || ch=='\'' ) { - char quote=ch; - while( !_lexer.isEOF() ) { - ch=_lexer.nextChar(); - if( ch=='\\' ) _lexer.nextChar(); - else if( ch==quote ) break; - } - } - } - require(";"); - return _code.ZERO; - } - - private void finishConstructor() { - if( _pendingCtor==null ) return; - Constructor ctor=_pendingCtor; - _pendingCtor=null; - Lexer after=_lexer; - _lexer=new Lexer(ctor.body()); - defineConstructor(ctor.name()); - _lexer=after; - } - - private void defineConstructor(String name) { - TypeStruct self=_scope.constructorSelf(); - Node ctor = constructorFunc(name); - TypeFunPtr sig = (TypeFunPtr)ctor._type; - String clzName = addClzPrefix(self._name); - TypeStruct clz = (TypeStruct)TYPES.get(clzName); - Field old = clz==null ? null : clz.field("<ctor>"); - if( old != null ) - throw error("Redefining constructor '" + name + "'"); - Field fld = Field.make("<ctor>",sig,_code.alias(_ref._cname),true); - TYPES.put(clzName,clz==null ? TypeStruct.make(clzName,true,fld) : clz.add(fld)); - require(ctor,";"); - } - - /** - * 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(),null,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 - CFGNode ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); - CFGNode ifF = (CFGNode)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.balanceLoopFRefs(_scope); - exit.balanceLoopFRefs(_scope); - 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 _code.ZERO; - } - - private ScopeNode jumpTo(ScopeNode toScope) { - ScopeNode cur = _scope.dup(); - ctrl(_code.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 _code.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((CFGNode)_breakScope.ctrl(), null, false); - return _code.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 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 - CFGNode ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); - CFGNode 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 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 "&&" -> con(lhs._type.makeZero()); // pred && expr_is_ignored - case "||" -> parseLogical(); // pred || expr - default -> throw TODO(); - }; - rhs.keep(); - _scope.removeGuards(ifF); - // The RHS of `||` is parsed on the false side, so retain its updated - // control, memory and locals just as an explicit else arm does. - 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("",r,lhs.unkeep(),rhs.unkeep())); - // Immediately fail e.g. `arg ? 7 : ptr` - ParseException err; - if( !fside.equals("else") && (err=ret.err()) !=null ) throw err; - ret.keep(); - r.peephole(); - return ret.unkeep(); - } - - /** - * 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(), ";"); - return addReturn(expr); - } - private Node addReturn(Node expr) { - expr.keep(); - // Need default memory, since it can be lazy, need to force - // a non-lazy Phi - mem(); // Force a pending lazy bulk-memory Phi - // For <init> and <clinit> - all fields in scope will be live-on-exit - // and stored into `self` and need to have lazy-phi's inserted. - int lexN = _scope.enclosingFuncOrDecl(); - Kind.Func k = (Kind.Func)_scope._kinds.at(lexN); - if( FunNode.isInit(k._name) ) { - int nestedLexSize = lexN+1 < _scope.depth() ? _scope._kinds.at(lexN+1)._lexSize : _scope.nIns(); - for( int i=k._lexSize; i<nestedLexSize; i++ ) - _scope.update(_scope.var(i),null); - if( _returnScope != null ) { - Var vexpr = _returnScope._vars.pop(); - Node oldX = _returnScope.removeLast(); - int rlen = _returnScope.nIns(); - while( rlen < nestedLexSize ) { - _returnScope._vars.add(_scope.var(rlen)); - _returnScope.addDef(con(_scope.var(rlen++).type().makeZero())); - } - _returnScope._vars.add(vexpr); - _returnScope.addDef(oldX); - } - } else if( isExplicitConstructorName(k._name) ) { - // Field bindings live in the enclosing declaration, not among the - // constructor's arguments/locals. Force lazy Phis on every exit. - int first = _scope._kinds.at(lexN-1)._lexSize+2; - for( int i=first; i<k._lexSize; i++ ) - _scope.update(_scope.var(i),null); - } - - // No prior merge point? Just clone and hang on to it - if( _returnScope == null ) { - _returnScope = _scope.dup(); - while( lexN+1 < _returnScope.depth() ) - _returnScope._pop(); // Pop a nested block scope until we hit the function scope - _returnScope.define("$expr", inferredReturnType(expr._type), true, expr, null); - - } else { - // For <init> and <clinit> - ALL FIELDS IN LAST SCOPE are live - // and need to have lazy-phi's inserted. - - // And fields might not match.... will need matching Var/define with default values - int rlen = _returnScope.nIns()-1; - RegionNode r = ctrl(new RegionNode(null, null,_returnScope.ctrl(), _scope.ctrl()).init().keep()); - _returnScope._merge(_scope, r, rlen); - Node oldExpr = _returnScope.in(rlen); - _returnScope.setDef(rlen,new PhiNode("$expr", r, oldExpr, expr).peephole()); - _code.add(r); - _returnScope.ctrl(r.unkeep()); - } - ctrl(_code.XCTRL); // Kill control - return expr.unkeep(); - } - - /** Parse: [name '='] expr - */ - private Node parseAsgn() { - int old = pos(); - String name = _lexer.matchId(); - // Just a plain expression, no assignment. - // Distinguish `var==expr` from `var=expr` - if( name==null || KEYWORDS.contains(name) || !matchOpx('=','=') ) - { pos(old); return parseExpression(); } - - // Find variable to update - Var def = _scope.lookup(name); - if( def==null ) - def = defineOpenConstructorField(name); - if( def==null ) - throw error("Undefined name '" + name + "'"); - - if( def._extern!=0 ) // Parse the C binding as a memory lvalue - { pos(old); return parseExpression(); } - - // 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+"'"); - checkSharedFunction(def); - - // Parse assignment expression - Node expr = parseAsgn(); - - // Lift expression, based on type - Type decl = def.type(); // Declared type - Node lift = convertExpr(expr.keep(), decl).keep(); - // Update - _scope.update(name,lift); - - // Check for storing into an instance field instead of a local var - int kx = _scope.kindx(def); // Declaration scope - int fx = _scope.enclosingFunction(); // Enclosing function scope - Kind kk = _scope._kinds.at(kx); - Kind fk = _scope._kinds.at(fx); - // Access instance field from 'self' - if( kx+1 == fx && kk instanceof Kind.Func inst && FunNode.isInstance(inst._name) && - fk instanceof Kind.Func method && !inExplicitConstructor() ) { - // Named field offset on an unknown struct type - TypeMemPtr self = (TypeMemPtr)_scope.var(fk._lexSize).type(); - TypeStruct sobj = (TypeStruct)TYPES.get(self._obj._name); - Field fld = sobj.field(name); - int alias = fld._alias; - Node ptr = _scope.in(method._lexSize); - Node off = fldoff(ptr,name); - Node prior = mem().keep(); - Node st = new StoreNode(loc(), name, alias, decl, ctrl(), prior, ptr, off, lift, inExplicitConstructor()).peephole().keep(); - storeMem(st,alias,prior); - st.unkeep(); - prior.unkeep(); - } - lift.unkill(); - - - // Return un-lifted expr - return expr.unkeep(); - } - - // Widen ints to floats; narrow wide int types. - // Early error if types do not match variable. - private Node convertExpr( Node expr, Type t ) { - if( !expr._type.accessISA(t) ) - throw error("Type "+expr._type.str()+" is not of declared type "+t.str()); - if( t instanceof TypeMemPtr tmp && tmp.isFinal() ) - expr = peep(new ReadOnlyNode(expr)); - // A FunPtr carries the semantic edge to its function's Return. Do not - // hide that edge behind a Convert which can fold to a plain constant. - if( expr instanceof FunPtrNode && t instanceof TypeFunPtr ) - return expr; - // Auto-widen array to i64 (cast ptr to raw int bits) - if( t == TypeInteger.BOT && expr._type instanceof TypeMemPtr tmp && tmp._obj.isAry() ) - return peep(new AddNode(peep(new PtrToIntNode(expr)),off(tmp._obj,"[]"))); - // Widen ints to floats, narrow wide types - return peep(new ConvertNode(t,expr)); - } - - // Inferred `var` declarations describe writable slots, not the exact - // value used to initialize them. Explicitly typed locals retain the - // user's nullability and numeric-width guarantees; `val` retains the - // initializer's precise type. - private static Type mutableInferredType(Type t) { - return switch( t ) { - case TypeInteger ti -> TypeInteger.BOT; - case TypeFloat tf -> TypeFloat.F64; - case TypeMemPtr tmp -> tmp.makeVar(); - case TypeFunPtr tfp -> tfp; - case TypeConAry<?> a -> a; - case TypePtr ptr -> TypePtr.PTR; - case TypeScalar ts -> TypeScalar.BOT; - default -> t; - }; - } - - private Type constructorFieldType(Type t) { - if( t instanceof TypeMemPtr ptr && ptr._obj.isConAry() ) { - TypeStruct declared=(TypeStruct)TYPES.get(ptr._obj._name); - TypeStruct shape=typeAry(declared._fields[1]._t,ptr._obj._fields[1]._final); - return TypeMemPtr.make((byte)2,shape,false).withAccess(ptr._ro); - } - return mutableInferredType(t); - } - - // Seed an inferred return merge with the declared numeric family instead - // of a transient constant/range. Pointer nullability remains part of the - // inferred function result and is combined by the later Phi. - private static Type inferredReturnType(Type t) { - return switch( t ) { - case TypeInteger ti -> TypeInteger.BOT; - case TypeFloat tf -> TypeFloat.F64; - default -> t; - }; - } - - 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,";"); - } - - // 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; // Field is final, but not deeply final - boolean uninit = false; // Required non-null constructor field - Node expr; - if( match("=") ) { - if( isExternDecl() ) { - if( !(t instanceof TypeFunPtr) ) { - Node ptr = externData(name,t); - // The lexical binding is a fixed address; its contents are mutable. - if( !_scope.define(name,t,true,ptr,loc) ) - throw error("Redefining name '"+name+"'",loc); - _scope.lookup(name)._extern = _code.externAlias(name); - return _code.ZERO; - } - expr = externDecl(name,t); - t = expr._type; // Upgrade declared type to the exact extern decl type - } else if( isConstructorDecl(name) ) { - expr = constructorFunc(name); - } else { - expr = 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 = mutableInferredType(t); - // Instance fields may change until the constructor finishes. - // Keep the declared family/access, not the default's constant - // value or string contents. Class constants remain static. - else if( _scope.klast() instanceof Kind.Func fun && FunNode.isInstance(fun._name) && !(t instanceof TypeFunPtr) ) - t = constructorFieldType(t); - } - - // expr is a constant function - if( t instanceof TypeFunPtr decl && expr._type instanceof TypeFunPtr tfp && tfp.isConstant() ) { - t = decl.makeFrom(tfp.fidx()); - FunNode fun = _code.link(tfp); - if( fun != null ) - fun.setName(name); // Assign debug name to Simple function - } - - } else { - // Since no initializer, not-final but might need an initializer - - // 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 ) { - // Required pointers retain their declared shape, but include - // null until every constructor path assigns them. - case TypeNil tn -> tn.nullable() ? con(Type.NIL) : con(t.meet(Type.NIL)); - case TypeInteger ti -> xfinal ? con(Type.BOTTOM) : _code.ZERO; - case TypeFloat tf -> con(xfinal ? Type.BOTTOM : 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); } - }; - // Every non-null pointer without a default is constructor-required. - // Without `!` it is also shallow-final; mutability and definite - // initialization are independent properties. - if( (xfinal && !(t instanceof TypeNil)) || (t instanceof TypeNil tn && !tn.nullable()) ) { - uninit = true; - if( xfinal ) fld_final = true; - } - } - - // Lift expression, based on type - // Do not let an authoritative Convert hide the nullable - // not-yet-initialized state from constructor-local type flow. - Node lift = uninit ? expr : convertExpr(expr, t); - - // Define a new name - if( !_scope.define(name,t,xfinal || fld_final,lift, loc) ) - throw error("Redefining name '" + name + "'", loc); - _scope.lookup(name)._uninit = uninit; - return lift; - } - - - /** - * Parse a struct declaration, and return the following statement. - * Structs cannot be redefined, but can be nested. - * - * @return zero - */ - private Node parseStruct( ) { - // keyword "struct" already parsed, so expect the struct name next. - String typeName = requireId(); - require("{"); - - // create type class:NESTED.typeName; no fields. - - String old = _nestedType; - String fullName = _scope.depth()==1 && _nestedType.endsWith(typeName) ? old : (old+"."+typeName).intern(); - _nestedType = fullName; // Recursive structs start with this as their basename - - ReturnNode ret = parseStruct( false, fullName ); - - _nestedType = old; - - // Insert a field into class:NESTED.typeName of "val typeName = fcn-ptr-to-init" - // Function does malloc internally. - // Future self: means subtyping has to deal with "who does the malloc" - String clzName = addClzPrefix(fullName); - TypeFunPtr sig = ret.fun().sig(); - TypeStruct ts = (TypeStruct)TYPES.get(clzName); - String initName = initFieldName(fullName); - Field oldFld = ts==null ? null : ts.field(initName); - Field fld = oldFld==null - ? Field.make(initName,sig,_code.alias(_ref._cname),true) - : oldFld.makeFrom(sig); - Field ctorFld = ts==null ? null : ts.field("<ctor>"); - // A same-named struct shares its file's class object and flag. - Field flag = ts==null ? null : ts.field("$init"); - ts = ctorFld==null - ? TypeStruct.make(clzName,false,fld) - : TypeStruct.make(clzName,false,fld,ctorFld); - if( flag!=null ) ts=ts.add(flag); - TypeFunPtr alloc = makeAllocator((TypeStruct)TYPES.get(fullName),ret,ctorFld); - ts = ts.add(Field.make("<new>",alloc,_code.alias(_ref._cname),true)); - TYPES.put(clzName,ts); - - // Insert a field in the containing class with the nested class type. - // This is basically sugar for "val CLZNAME = <init>" - // and is a nicer version of updateSelfAsFieldsDiscovered. - _scope.define(typeName, sig, true, new FunPtrNode(sig,_code._start,ret).peephole(), loc()); - - require("}"); - return require(_code.ZERO,";"); - } - - // Build the declaration-side allocation helper. Allocation sites only - // call this function; all layout, initialization and publication choices - // are made here, where the complete struct and constructor are known. - private TypeFunPtr makeAllocator(TypeStruct ts, ReturnNode initRet, Field ctorFld) { - TypeFunPtr ctor = ctorFld==null ? null : (TypeFunPtr)ctorFld._t; - int nargs = ctor==null ? 0 : ctor.nargs()-2; // Hide self and selfMem - Type[] atypes = new Type[nargs]; - for( int i=0; i<nargs; i++ ) atypes[i] = ctor.arg(i+2); - TypeMemPtr ptr = TypeMemPtr.make(ts); - TypeFunPtr sig = TypeFunPtr.make1((byte)2,true,atypes,ptr,_code.fidx(_ref._cname)); - String name = (ts._name+".<new>").intern(); - - int oldUID = _code.UID(); - FunNode fun = (FunNode)new FunNode(loc(),sig,name,_ref,null,_ref._start).peephole(); - _code.link(fun); - Node rpc = new ParmNode("$rpc",0,TypeRPC.BOT,fun,ConstantNode.seed(TypeRPC.BOT).peephole()).peephole(); - Node pub = new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun, - new ProjNode(_ref._start,1,ScopeNode.MEM0).peephole()).peephole(); - Node[] parms = new Node[nargs]; - for( int i=0; i<nargs; i++ ) - parms[i] = new ParmNode("arg"+i,i+2,atypes[i],fun,ConstantNode.seed(atypes[i]).peephole()).peephole(); - - NewNode nnn = new NewNode(ts,fun,off(ts," len")).init(); - Node self = new ProjNode(nnn,0,ts.str()).init(); - Node priv = new ProjNode(nnn,1,"#selfMem").init(); - Node[] initArgs = new Node[]{fun,pub,self,priv, - new FunPtrNode(initRet.fun().sig(),_code._start,initRet).peephole()}; - CallEndNode cend = rawCall(initArgs,initRet.fun()); - Node ctl = new CProjNode(cend,0,ScopeNode.CTRL).peephole(); - pub = new ProjNode(cend,1,ScopeNode.MEM0).peephole(); - priv = new ProjNode(cend,2,"#selfMem").peephole(); - - if( ctor != null ) { - Node[] cargs = new Node[nargs+5]; - cargs[0]=ctl; cargs[1]=pub; cargs[2]=self; cargs[3]=priv; - for( int i=0; i<nargs; i++ ) cargs[i+4]=parms[i]; - FunNode cfun = _code.link(ctor); - cargs[cargs.length-1] = new FunPtrNode(ctor,_code._start,cfun.ret()).peephole(); - cend = rawCall(cargs,cfun); - ctl = new CProjNode(cend,0,ScopeNode.CTRL).peephole(); - pub = new ProjNode(cend,1,ScopeNode.MEM0).peephole(); - priv = new ProjNode(cend,2,"#selfMem").peephole(); - } - - priv.keep(); - for( Field field : ts._fields ) { - Type storage = field._t.makeStorage(); - Field escaped = field._final ? field : field.makeFrom(storage).makeFrom(true); - pub.keep(); - Node esc = new EscapeNode(escaped,self,priv,pub).peephole(); - MemMergeNode merge = new MemMergeNode(null,pub); - merge.alias(field._alias,esc); - Node next = merge.peephole(); - pub.unkeep(); - pub = next; - } - priv.unkill(); - ReturnNode ret = (ReturnNode)new ReturnNode(ctl,pub,self,rpc,fun).peephole(); - fun.setRet(ret); - fun._approxUIDs = _code.UID() - oldUID; - _ref.addFun(_code,fun); - return sig; - } - - private CallEndNode rawCall(Node[] args, FunNode target) { - // Establish this known declaration-side edge immediately, but do not - // peephole or inline it while the surrounding factory is being built. - // IterPeeps therefore starts with the complete initializer caller set. - CallNode call = new CallNode(loc(),args).init(); - CallEndNode cend = new CallEndNode(call,TypeRPC.constant(_code.rpc(_ref._cname))).init(); - call.link(target); - return cend; - } - - // Parse a struct declaration (not an allocation); file-level is a class-init, and scope structs are normal - private ReturnNode parseStruct( boolean isClz, String typeName ) { - var oldExits = _ctorExits; - _ctorExits = new ArrayList<>(); - // Record & restore global state set during parsing the <init> code - Node oldCtrl= _scope.ctrl().keep(); - Node oldMem = _scope.mem ().keep(); - - // Make the future clazz/instance struct. - TypeStruct tself = (TypeStruct)TYPES.get(typeName); - if( tself==null ) - TYPES.put(typeName, tself = TypeStruct.make(typeName,true)); - else { - assert tself._open; - TYPES.put(typeName,tself = tself.defined()); - } - - // Class object keeps a runtime flag to signal the <clinit> has run. - if( isClz ) - TYPES.put(typeName,tself=tself.add(Field.make("$init",TypeInteger.BOOL,_code.alias(_ref._cname),false))); - - // Signature of the default constructor - TypeFunPtr sig = structInitSig(isClz,tself); - // Function and argument names of default constructor - String fname = (typeName + (isClz ? ".<clinit>" : ".<init>")).intern(); - String[] ids = new String[]{"self", isClz ? "arg" : "#selfMem"}; - - // Struct decls look like function bodies. Parse function body normally. - ReturnNode ret = _parseFunctionBody(fname,sig,loc(),ids); - _ctorExits = oldExits; - // Unwind global state. - ctrl(oldCtrl.unkeep()); - mem (oldMem .unkeep()); - return ret; - } - - // <cl/init> signature: { self arg/selfMem -> BOT/selfMem } - // Self-memory is the rare *private* (never aliased) memory for the new object. - private TypeFunPtr structInitSig( boolean isClz, TypeStruct tself ) { - // Private uninitialized memory from new object, no escaped values. - TypeMem privMem = isClz ? null : TypeMem.make(1,tself,true,isClz,false,null,null); - Type targ = isClz ? TypeInteger.BOT : privMem; - // Return for private memory has to name all escaped fields, which are not known yet. - // Escape ALL fields, and sharpen later. - Type tret = isClz ? Type.BOTTOM : TypeMem.makePrivate(tself); - Type[] targs = new Type[]{ TypeMemPtr.make((byte)2,tself,isClz), targ }; - // FIDX for FREFs is in the global namespace, needs the correct clz name. - String clzBare = tself._name; - int order = 3; // 3 is always the <init> order, 2nd in the file just behind the <clinit> - if( isClz ) { - assert startsClzPrefix( clzBare ); - clzBare = clzBare.substring(clzPrefix.length()).intern(); - order = 2; // 2 is always the <clinit> order, first in file and skipping reserved #1 - } - return TypeFunPtr.make1((byte)2, true, targs, tret, _code.fidx(clzBare,order)); - } - - // A class type carries the constructor function as a required final field. - private TypeStruct classStruct( String typeName, TypeFunPtr init, boolean open ) { - return TypeStruct.make(addClzPrefix(typeName),open,Field.make(initFieldName(typeName),init,_code.alias(_ref._cname),true)); - } - - private static String initFieldName( String typeName ) { - if( startsClzPrefix(typeName) ) - typeName = typeName.substring(clzPrefix.length()); - int idx = typeName.lastIndexOf('.'); - return (idx == -1 ? typeName : typeName.substring(idx+1)).intern(); - } - - private TypeStruct latestStruct( TypeStruct ts ) { - Type t = TYPES.get(ts._name); - return t instanceof TypeStruct ts2 ? ts2 : ts; - } - - private Field fieldOrOpen( TypeStruct ts, String name, boolean xfinal ) { - ts = latestStruct(ts); - Field fld = ts.field(name); - if( fld != null ) return fld; - if( !ts._open || !ts._fref ) return null; - fld = Field.make(name,Type.BOTTOM,_code.alias(_ref._cname),xfinal); - TYPES.put(ts._name,ts = ts.add(fld)); - if( _ctorOpenStruct != null && _ctorOpenStruct._name == ts._name ) - _ctorOpenStruct = ts; - return fld; - } - - private Var defineOpenConstructorField( String name ) { - if( _ctorOpenStruct==null ) return null; - Field fld = fieldOrOpen(_ctorOpenStruct,name,true); - if( fld == null ) return null; - _scope.define(name,fld._t,fld._final,con(Type.TOP),loc()); - return _scope.lookup(name); - } - - - // 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[?] - - // Structs have nested names, and can be discovered with a partial name - // match. Example, searching for a type name "C.D" in the namespace "A.B". - // This will match "A.B.C.D", then "A.C.D", then "C.D". - - // Will also match external names like "sys.io" or "externModule.subtype.subtype" - - private Type type() { - // Only type with a leading `{` is a function pointer... - if( peek('{') ) return typeFunPtr(); - - // Otherwise you get a type name - int old1 = pos(); - boolean writable = match("!"); - boolean readonly = !writable && match("~"); - String tname = _lexer.matchId(); - if( tname==null ) return posT(old1); - - // Convert the type name to a type. - Type t0 = TYPES.get(tname), t1 = t0; - // No new types as keywords - if( KEYWORDS.contains(tname) ) { - if( t0 == null ) return posT(old1); - // Something like "int" or "f64" or "var". - // Can be "int[]" so still need to check array-ness - } else { - if( t0 == Type.BOTTOM || t0 == Type.TOP ) return t0; // var/val type inference - - if( t0 != null ) { - while( true ) { - int old2 = pos(); - if( !match(".") ) break; - String sname = matchId(); - if( sname==null ) { pos(old2); break; } - sname = tname+"."+sname; - Type t = TYPES.get(sname); - if( t==null ) { pos(old2); break; } - tname = sname; - t0 = t; - } - } else { - // Type A.B.C starts with just A. Search our nested type path, - // e.g. path "X.Y.Z", in order: - - // If A==X its now a local type. - // If another dot, repeat for B==Y then C==Z - // Else if AX.B exists, done - // Else AX.B is a local forward ref. - // - // Else its a short cut form, one of: - // X.Y.Z.A - // X.Y.A - // X.A - // Else this all failed, assume a forward ref with A as an external symbol: - // A - - String root = _nestedType; - int ridx = root.indexOf('.'); - if( ridx != -1 ) root = root.substring(0,ridx); - if( tname.equals(root) ) { - boolean localForward = false; - while( true ) { - int old2 = pos(); - if( !match(".") ) break; - String sname = matchId(); - if( sname==null ) { pos(old2); break; } - sname = tname+"."+sname; - Type t = TYPES.get(sname); - if( t==null && !(_nestedType.equals(tname) || _nestedType.startsWith(tname+".")) && !localForward ) { - pos(old2); - break; - } - tname = sname; - t0 = t; - localForward |= t==null; - } - } else { - String nest = _nestedType, fullq = tname; - while( true ) { - fullq = nest+"."+tname; // Full qualified name - t0 = TYPES.get(fullq); - if( t0 != null ) - { tname = fullq; break; } // Search succeeded - // Subtract a layer from nested typenames - int idx = nest.lastIndexOf('.'); - if( idx== -1 ) break; // Search failed; leave as an external symbol - nest = nest.substring(0,idx); - } - if( t0==null ) { - CompUnit cu = ParseAll.findCompUnit(_code,_ref,tname); - if( cu != null ) { - tname = cu._cname; - t0 = TYPES.get(tname); - } - while( true ) { - int old2 = pos(); - if( !match(".") ) break; - String sname = matchId(); - if( sname==null ) { pos(old2); break; } - tname = tname+"."+sname; - t0 = TYPES.get(tname); - } - } else { - while( true ) { - int old2 = pos(); - if( !match(".") ) break; - String sname = matchId(); - if( sname==null ) { pos(old2); break; } - sname = tname+"."+sname; - Type t = TYPES.get(sname); - if( t==null ) { pos(old2); break; } - tname = sname; - t0 = t; - } - } - } - } - - tname = tname.intern(); - // Still no type found? Assume forward reference - t1 = t0 == null - ? TypeStruct.forward(tname) // Null: assume a forward ref type - : t0; - } - - // Structs are always actually references - if( t1 instanceof TypeStruct ts1 ) - t1 = TypeMemPtr.make(ts1); - - // 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 ) { - if( tmp.nullable() ) throw error("Type "+t2+" already allows null"); - t2 = tmp.makeNullable(); - } else - throw error("Type "+t0+" cannot be null"); - } else if( match("[~]") ) { - t2 = TypeMemPtr.make(typeAry(t2,true)).makeRO(); - } else if( match("[]") ) { - t2 = TypeMemPtr.make(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(';') || peek('=') || match("->")) || id==null ) - return posT(old1); // Reset lexer to reparse - pos(old2); // Reset lexer to reparse - // Yes a forward ref, so declare it - forwardRefType(tname,(TypeStruct)((TypeMemPtr)t1)._obj); - // Return the (array, final) type - return t2; - } - - private TypeMemPtr forwardRefType( String tname, TypeStruct tself ) { - TYPES.put(tname,tself); - String clzName = addClzPrefix( tname ); - TYPES.put(clzName, classStruct(tname,structInitSig(false,tself),true)); - return TypeMemPtr.make(tself); - } - - private TypeMemPtr forwardRefType( String tname ) { - tname = fullTypeName(tname); - return forwardRefType(tname,TypeStruct.forward(tname)); - } - - private String fullTypeName( String tname ) { - String nest = _nestedType, fullq; - while( true ) { - fullq = nest+"."+tname; - if( TYPES.get(fullq) != null ) - return fullq.intern(); - int idx = nest.lastIndexOf('.'); - if( idx== -1 ) return fullq.intern(); - nest = nest.substring(0,idx); - } - } - - // Make an array type of t. Always record a mutable version, - // but return the requested version. - private TypeStruct typeAry( Type t, boolean efinal ) { - // Non-null reference arrays are valid types, even though an ordinary - // zero-filled `new T[len]` cannot construct one. Allocation retains - // that check in alloc(); trusted runtimes (notably argv construction) - // may fill the private array completely before publishing it. - String tname = ("[]"+t.str()).intern(); - TypeStruct ta = (TypeStruct)TYPES.get(tname); - if( ta==null ) { - // Primitive arrays use the null compunit - String rcname = TYPES.get(t) instanceof TypeStruct ? _ref._cname : ("_."+t).intern(); - ta = TypeStruct.makeAry(tname,TypeInteger.U32,_code.alias(rcname),t,_code.alias(rcname), false ); - TYPES.put(tname,ta); - } - // Already have the aliases, just efinal is wrong - return TypeStruct.makeAry(tname,TypeInteger.U32,ta._fields[0]._alias,t,ta._fields[1]._alias,efinal ); - } - - private Type implicitSelfType() { - TypeStruct self = _scope.constructorSelf(); - // Ordinary instance methods accept the declared object shape. The - // constructor-only receiver starts at the low/default field values, - // and is built explicitly by constructorFunc(). - return self == null ? TypePtr.PTR : TypeMemPtr.make(self); - } - - // 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 - Ary<Type> ts = new Ary<>(Type.class); - // Always reserve a receiver slot. Outside constructor scopes it is - // broad and unused, so call sites can uniformly pass their self. - ts.push(implicitSelfType()); - if( match("}") ) // No-arg function { -> type } - return TypeFunPtr.make(match("?"),ts.asAry(),t0); - 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("?"),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)); - 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 = parseCompDir(); - // No compare - if( dir==0 ) return lhs; - lhs.keep(); - - // Compare - Node rhs = parseShift().keep(); - Node cmp = makeCompBool(dir,lhs.unkeep(),rhs).keep(); // Convert to a bool - - // Stacked compares? - int dir0 = parseCompDir(); - if( dir0 == 0 ) { - rhs.unkill(); - return cmp.unkeep(); - } - - // rhs is keeped() and becomes lhs - // cmp is NOT keeped() and is the last test - Node ifNode = new IfNode(ctrl(), cmp.unkeep()).peephole(); - Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); - Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole(); - // False side does nothing but capture memory & side-effects - ScopeNode fail = _scope.dup(); - fail.ctrl(ifF); - - // Loop over stacked compares. Each loop moves the old RHS to become - // the new LHS, so e.g. (a < b < c < d) compares (LHS:a < RHS:b) then - // compares (LHS:b < RHS:c) then (LHS:c < RHS:d), etc. - while( dir0 != 0 ) { - if( Math.abs(dir) != Math.abs(dir0) ) - throw error("Mixing relational directions in a chained relational test"); - // True side parses next arm of test - ctrl(ifT.unkeep()); - lhs = rhs; // lhs is keeped() and is old RHS - rhs = parseShift().keep(); - cmp = makeCompBool(dir,lhs.unkeep(),rhs); // Convert to a bool - ifNode = new IfNode(ctrl(), cmp).peephole(); - ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); - ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole(); - // Merge result into the fail case - ctrl(ifF); - fail.mergeScopes(_scope.dup(), loc()).peephole(); - // Check next stacked compare - dir0 = parseCompDir(); - } - rhs.unkill(); - ctrl(ifT.unkeep()); - RegionNode r = fail.mergeScopes(_scope, loc()).init(); - _scope = fail; - return new PhiNode("", r,_code.ZERO,con(1)).peephole(); - } - - private int parseCompDir() { - if( match("<=") ) return -1; // Negative for "equals-or-less" - if( match(">=") ) return -2; // 1 for <, 2 for > - if( match("<" ) ) return 1; // Positive for just "less" - if( match(">" ) ) return 2; - return 0; - } - - 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); - } - - /** - * 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); - } - 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); - } - 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); - } - 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; // Inc vs Dec - String name = _lexer.matchId(); - if( name!=null ) { - Var n = _scope.lookup(name); - if( n != null && !(n.type() instanceof TypeMemPtr) ) { - if( n._extern!=0 ) - return parsePostfixName(_scope.in(n),name,delta); - if( n._final && !_scope.canInit(n) ) - throw error("Cannot reassign final '"+n._name+"'"); - checkSharedFunction(n); - Node expr = convertExpr(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())); - 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['++','--'] | - * type['.' Id]* - * </pre> - * @return a primary {@link Node}, never {@code null} - */ - private Node parsePrimary() { - if( _lexer.isNumber(_lexer.peek()) ) return parseLiteral(); - if( _lexer.peek('"') ) return parsePostfix(newString(parseString())); - if( matchx("true" ) ) return con(1); - if( matchx("false") ) return _code.ZERO; - if( matchx("null" ) ) return con(Type.NIL); - if( match ("'" ) ) return parseChar(); - if( match ("(" ) ) return parsePostfix(require(parseAsgn(), ")")); - if( matchx("new" ) ) return parsePostfix(alloc()); - if( match ("{" ) ) return parsePostfix(require(func(),"}")); - - int pos = pos(); - // Expect an identifier now - String id = _lexer.matchId(); - if( id == null || KEYWORDS.contains(id) ) - throw errorSyntax("an identifier or expression"); - int pos2 = pos(); - // Attempt a local var lookup first - Var var = _scope.lookup(id); - if( var==null ) { - // Not a local var, try again as a type name - pos(pos); - Type t = type(); - if( t!=null ) { - // Static <clinit> typename lookup - if( peek('.') ) // type.fld, which is really a field lookup in the class object - return parsePostfix(con(t)); - // Direct reference to a <clinit> type. Convert the instance - // type to a class type name - if( !(t instanceof TypeMemPtr tmp) ) { - //return new FRefNode(addClzPrefix(tmp._obj._name), loc()).peephole(); - // Not a local var, but yes a bare type... disallow a local - // FREF and return null, not-a-primary parse. - pos(pos); - return null; - } - } - - // Insert FREF outside enclosing Kind.Func scope. - pos(pos2); - t = FRefNode.FREF_TYPE; - // Check if this is a class. - CompUnit cu = ParseAll.findCompUnit(_code,_ref,id); - if( cu!=null ) { - // Must be a final class-type - String clzName = addClzPrefix(cu._cname); - TypeStruct clz = (TypeStruct)TYPES.get(clzName); - if( clz == null ) - TYPES.put(clzName,clz = TypeStruct.make(clzName,true)); - t = TypeMemPtr.make((byte)2,clz,true); - } - // Define the FRef - var = _scope.defineFRef(id,t,true,loc()); - _frefs.push((FRefNode)_scope.in(var)); - } - - // Load local value - Node rvalue = _scope.in(var); - if( var._extern!=0 ) - return parsePostfixName(rvalue,id,0); - if( var._fref ) - return parsePostfix(rvalue); - // A constructor may read the unfinished object, but each field it - // reads must already be initialized on every path. Read the current - // local value; other fields may still be waiting for their first write. - if( inExplicitConstructor() && var._uninit && missingInit(rvalue,new BitSet()) ) - throw error("Field '"+id+"' might not be initialized",var._loc); - - // Check for an instance field load - int kx = _scope.kindx(var); // Declaration scope - int fx = _scope.enclosingFunction(); // Enclosing function scope - Kind kk = _scope._kinds.at(kx); - Kind fk = _scope._kinds.at(fx); - // Access instance field from 'self' - if( kx+1 == fx && kk instanceof Kind.Func inst && FunNode.isInstance(inst._name) && fk instanceof Kind.Func method && !inExplicitConstructor() ) - return parsePostfixName(_scope.in(method._lexSize),id,0); - - // Check for a function-escaping variable; these require true - // closures. Final constants are OK; final vars require a hidden var - // argument; not-final fields can just use an explicit struct arg. - if( kx < fx && !inExplicitConstructor() && // Out of scope - !(var._final && rvalue._type.isConstant()) ) { // Not a final constant - if( kx > 0 ) // Global scope is OK, only ever one of these (not one per function invoke) - throw error("Variable '"+var._name+"' is out of function scope and must be a final constant"); - - // var.idx==2 is self argument, 3 is args, 4 or more are now static globals - if( var._idx >= 4 ) { - // Read from global scope. - // Surely a nicer way to get the top-level scope compile name? - Var clzptr = _scope.var( 2 ); - // Get the most recent sharpen global - TypeStruct clz = (TypeStruct) TYPES.get( ((TypeMemPtr) clzptr.type())._obj._name ); - TypeMemPtr clztmp = TypeMemPtr.make( (byte) 2, clz, true ); - return parsePostfixName( con( clztmp ), id,0 ); - //return parsePostfixName(_scope.in(2),id); - } - } - - // Check for assign-update, x += e0; - char ch = _lexer.matchOperAssign(); - if( ch==0 ) { // Normal primary, check for postfix updates - int selfx = _scope.kindFcn(var)._lexSize; - // Might be a method call from inside a method, so no explicit "self". - // Pass the in-scope "self" - return parsePostfixMethod(rvalue,_scope.in(_scope.update(_scope.var(selfx),null))); - } - // Assign-update direct into Scope - if( var._final && !_scope.canInit(var) ) - throw error("Cannot reassign final '"+var._name+"'"); - checkSharedFunction(var); - Node op = opAssign(ch,rvalue, var.type() ); - _scope.update(var,op); - return postfix(ch) ? rvalue.unkeep() : op; - } - - // Check for assign-update, "x += e0". - // Returns "x + e0"; the caller assigns value. - // if ch is postfix, then lhs is kept and caller will unkeep and return it. - private Node opAssign( char ch, Node lhs, 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 OrNode(null,lhs,rhs); - case '&' -> new AndNode(null,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 TODO(); - }; - // Convert to float ops, or narrow int types; error if not declared type. - // Also, if postfix LHS is still keep() - return convertExpr(peep(op),t); - } - - - /** - Parse an allocation - */ - private Node alloc() { - Type t = type(); - if( t==null ) { - String tname = _lexer.matchId(); - if( tname==null || KEYWORDS.contains(tname) ) - throw error("Expected a type"); - t = forwardRefType(tname); - } - // Parse ary[ length_expr ] - if( match("[") ) { - if( !t.makeZero().isa(t) ) - throw error("Cannot allocate a non-nullable, since arrays are always zero/null filled"); - Node len = parseAsgn().keep(); - require("]"); - return allocArray(typeAry(t,false),len); - } else { - if( !(t instanceof TypeMemPtr tmp) ) - throw error("Cannot allocate a "+t.str()); - return allocStruct(tmp._obj); - } - } - - private Node allocStruct(TypeStruct ts) { - Ary<Node> ctorArgs = constructorArgs(); - // A field declaration can leave a short forward type name in TYPES. - // Allocation happens in lexical context, so resolve that short name - // to the completed nested type before selecting its class helper. - String typeName = ts._name.indexOf('.') == -1 ? fullTypeName(ts._name) : ts._name; - Type resolved = TYPES.get(typeName); - if( resolved instanceof TypeStruct rts ) ts = rts; - TypeStruct clz = (TypeStruct)TYPES.get(addClzPrefix(typeName)); - if( clz==null ) - throw error("Unknown struct type '" + ts._name + "'"); - Field afld = clz.field("<new>"); - Node alloc; - if( afld != null ) { - TypeFunPtr tfp = (TypeFunPtr)afld._t; - FunNode fun = _code.link(tfp); - alloc = fun==null - ? new FRefNode((addClzPrefix(typeName)+".<new>").intern(),loc()) - : new FunPtrNode(tfp,_code._start,fun.ret()).peephole(); - } else { - if( !ts._fref ) - throw error("Constructor arguments for '" + ts._name + "' but no constructor is defined"); - Type[] sig = new Type[ctorArgs._len]; - for( int i=0; i<ctorArgs._len; i++ ) - sig[i] = ctorArgs.at(i)._type; - alloc = new FRefNode((addClzPrefix(typeName)+".<new>").intern(),loc(), - TypeFunPtr.make(false,sig,TypeMemPtr.make(ts))); - } - Ary<Node> args = new Ary<>(Node.class); - args.add(ctrl()); args.add(mem()); - for( Node arg : ctorArgs ) args.add(arg.unkeep()); - args.add(alloc); - return functionCall(args); - } - - private Ary<Node> constructorArgs() { - Ary<Node> args = new Ary<>(Node.class); - if( !match("(") ) - return args; - while( !peek(')') ) { - Node arg = parseAsgn(); - if( arg==null ) break; - args.push(arg.keep()); - if( !match(",") ) break; - } - require(")"); - return args; - } - - private Node allocArray(TypeStruct ts, Node len) { - Field lenFld = ts.field("#" ); - Field bodyFld = ts.field("[]"); - - ConFldOffNode base = off(ts, "[]"); - int scale = ts.aryScale(); - Node size = peep(new AddNode(base,peep(new ShlNode(null,len,con(scale))))); - - // Build a NewNode; takes in and puts out all aliases. - NewNode nnn = new NewNode(ts, ctrl(), size ).init(); - ProjNode self = new ProjNode(nnn,0,ts.str()).init().keep(); - ProjNode smem = new ProjNode(nnn,1,"#selfMem").init().keep(); - - // Store length. Rest of array is zero'd via CALLOC during CodeGen. - // Length is casted to sanity. - // TODO: Needs runtime check. - Node st = peep(new StoreNode(null, "#", 1, TypeInteger.U32, null, smem, self, off(ts,"#"), len.unkeep(), true)); - - // TODO: Allow add-on "constructor" to init the array elements - - Node prior = mem(); - Node esc = peep(new EscapeNode(lenFld,self,st,prior)); - mem(mergeAlias(prior,lenFld._alias,esc)); - prior = mem(); - esc = peep(new EscapeNode(bodyFld,self,smem,prior)); - mem(mergeAlias(prior,bodyFld._alias,esc)); - smem.unkeep(); - return self.unkeep(); - } - - private Node newString(String s) { - TypeStruct ts = typeAry(TypeInteger.U8,true); - int lenAlias = ts.field("#" )._alias; - int elemAlias = ts.field("[]")._alias; - TypeConAryB body = TypeConAryB.make(s); - TypeInteger slen = TypeInteger.constant(s.length()); - // 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", slen, lenAlias, body, elemAlias, true),true); - assert str.isConstant(); - return con(str.makeRO()); - } - - /** - * 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 parsePostfixMethod(Node expr, Node self) { - // TODO: This is too broad because the parser lacks strong types here. - // Some normal functions can end up with a "self" argument. - - // Self method call? Self is a TMP-not-CLZ, then assume and pass self. - if( match("(") ) { - expr = parsePostfix(require(functionCall(expr,self),")")); - } else if( expr != self ) - self.isKill(); // Not a method call, no need for self - return parsePostfix(expr); - } - private Node parsePostfix(Node expr) { - _lexer.skipWhiteSpace(); - Lexer site = loc(); - String name; - if( match(".") ) name = requireId(); - else if( match("#") ) name = "#"; - else if( match("[") ) name = "[]"; - // Can get here without an explicit receiver: "f()" or - // "self.method()(next)(next)(next)". Pass the current function self. - else if( match("(") ) return parsePostfix(require(functionCall(expr,defaultSelf()),")")); - else return expr; // No postfix - - return parsePostfixName(expr,name,0,site); - } - - private boolean privateSelf(Node ptr) { - while( ptr instanceof ConvertNode || ptr instanceof ReadOnlyNode || ptr instanceof GuardNode ) ptr=ptr.in(1); - Node self=defaultSelf(); - while( self instanceof ConvertNode || self instanceof ReadOnlyNode || self instanceof GuardNode ) self=self.in(1); - return ptr==self; - } - - // An explicit self.x read sees the same current value as bare x. Loading - // the allocation here would miss updates still held in local SSA values. - private Node privateField(String name, int pre) { - int idx=fieldVarIdxBefore(_scope._kinds.at(_scope.enclosingFuncOrDecl())._lexSize,name); - if( idx<0 ) throw error("Cannot resolve field '"+name+"'"); - Var v=_scope.var(idx); - if( pre==0 && matchOpx('=','=') ) { - checkSharedFunction(v); - Node value=parseAsgn().keep(); - _scope.update(v,convertExpr(value,v.type())); - return value.unkeep(); - } - Node value=_scope.in(_scope.update(v,null)); - if( v._uninit && missingInit(value,new BitSet()) ) - throw error("Field '"+name+"' might not be initialized",v._loc); - char ch=pre==0 ? _lexer.matchOperAssign() : (char)pre; - if( ch!=0 ) { - checkSharedFunction(v); - Node op=opAssign(ch,value,v.type()); - _scope.update(v,op); - if( pre!=0 && postfix(ch) ) value.unkeep(); - return pre==0 && postfix(ch) ? value.unkeep() : op; - } - return parsePostfixMethod(value,defaultSelf()); - } - - private void checkSharedFunction(Var v) { - if( _scope.canInit(v) && v._final && v.type() instanceof TypeFunPtr t && t.isConstant() ) - throw error("Cannot reassign shared function '"+v._name+"'"); - } - - // A loop Phi is temporarily bottom before its backedge is parsed. That is - // not an unwritten field: follow its live inputs to the actual defaults. - private boolean missingInit(Node value, BitSet seen) { - if( value==null || seen.get(value._nid) ) return false; - seen.set(value._nid); - if( value instanceof PhiNode phi && !(value instanceof ParmNode) ) { - for( int i=1; i<phi.nIns(); i++ ) - if( phi.region().in(i)!=null && phi.region().in(i)._type!=Type.XCONTROL && missingInit(phi.in(i),seen) ) - return true; - return false; - } - return !initialized(value._type); - } - - private Node defaultSelf() { - int selfx = _scope._kinds.at(_scope.enclosingFuncOrDecl())._lexSize; - return _scope.in(_scope.update(_scope.var(selfx),null)); - } - - /** - * 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> - */ - - // pre is +/-1 for a prefix update, otherwise parse a postfix operation. - private Node parsePostfixName(Node expr, String name, int pre) { - return parsePostfixName(expr,name,pre,loc()); - } - - private Node parsePostfixName(Node expr, String name, int pre, Lexer site) { - if( inExplicitConstructor() && privateSelf(expr) ) return privateField(name,pre); - // Keep expr across possible updates - expr.keep(); - - // Get the best known TypeStruct from a pointer type. - TypeStruct ts = null, ts2; - if( expr._type instanceof TypeMemPtr tmp && (ts2=((TypeStruct)TYPES.get(tmp._obj._name))) != null ) - ts = (TypeStruct)tmp._obj.join(ts2); - // A Load's flow type is intentionally not promised during parsing, - // but its declaring field type is still known. Recover that static - // type for chained accesses such as `_grow(1).buf[len++]`; otherwise - // the array Store is parsed as alias #1 and can hide the independent - // side effect of the postfix `len++`. - if( ts==null && expr instanceof LoadNode load ) { - TypeStruct ots = load.off() instanceof ConFldOffNode coff ? coff._ts : null; - if( ots==null && load.ptr()._type instanceof TypeMemPtr owner ) { - Type owner0 = TYPES.get(owner._obj._name); - ots = owner0 instanceof TypeStruct obj ? obj : owner._obj; - } - if( ots != null ) { - Field loaded = ots.field(load._name); - if( loaded != null && loaded._t instanceof TypeMemPtr ltmp ) { - Type loaded0 = TYPES.get(ltmp._obj._name); - ts = loaded0 instanceof TypeStruct obj ? obj : ltmp._obj; - } - } - } - - Field fld = ts==null ? null : ts.field(name); - if( fld==null && ts!=null && !startsClzPrefix(ts._name) ) { - Type clz0 = TYPES.get(addClzPrefix(ts._name)); - if( clz0 instanceof TypeStruct clz && (fld = latestStruct(clz).field(name)) != null ) { - expr.unkeep(); - ts = latestStruct(clz); - expr = con(TypeMemPtr.make((byte)2,ts,true)).keep(); - } - } - if( fld==null && ts!=null ) - fld = fieldOrOpen(ts,name,false); - if( fld==null && ts!=null && !latestStruct(ts)._open ) - throw error("Accessing unknown field '"+name+"' from '*"+ts._name+"'"); - - // We have to record "active touches" of a new class, to track when we - // need to force class initialization. Basically, we insert an CLInit, - // and if it does not fold away it'll turn into a runtime check. - Kind.Func func = (Kind.Func)_scope._kinds.at(_scope.enclosingFuncOrDecl()); - if( (ts==null || ts==TypeStruct.BOT || ts.isHigh() || startsClzPrefix(ts._name)) && - !(ts!=null && (ts._name+".<clinit>").equals(func._name)) ) - classInit(site,expr); - - // With no field, we will update the bulk memory - int alias = fld==null ? 1 : fld._alias; - Type decl = fld==null ? Type.BOTTOM : fld._t; - - // Field offset; fixed for structs, computed for arrays - Node off = (name=="[]" - // Array element math, with unknown base offset and shift amount. - ? peep(new AddNode(fldoff(expr,"[]"),peep(new ShlNode(null,require(parseAsgn(),"]"),fldoff(expr,"<<"))))) - // Named field offset on an unknown struct type - : fldoff(expr,name)).keep(); - - // Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment - if( pre==0 && matchOpx('=','=') ) { - // Field assignment - Node val = parseAsgn().keep(); - // Lift value for store - Node lift = fld==null ? val : new ConvertNode(fld._t,val).peephole(); - // Memory for store, post assignment expression - Node mem = mem(); - // Store to field - mem.keep(); - if( expr._type instanceof TypeMemPtr view && view._ro && ctrl()._type!=Type.XCONTROL ) - throw error("Cannot modify final field '"+name+"'"); - Node st = new StoreNode(loc(), name, alias, decl, ctrl(), mem, expr.unkeep(), off.unkeep(), lift, false).peephole().keep(); - storeMem(st,alias,mem); - st.unkeep(); - mem.unkeep(); - - return val.unkeep(); // "obj.a = expr" returns the expression while updating memory - } - - // Memory for load - Node mem = mem(); - // Loading from a constant array, the declared type is the - // meet-over-elements and not the array itself. - if( name=="[]" && decl instanceof TypeConAry conary ) - decl = conary.elem(); - - // Load field - Node load = peep(new LoadNode(loc(),name, alias, ctrl(), mem, expr, off)); - - // Check for assign-update, "ptr.fld += expr" or "ary[idx]++" - char ch = pre==0 ? _lexer.matchOperAssign() : (char)pre; - if( ch!=0 ) { - if( fld!=null && decl==Type.BOTTOM ) - throw error( "'" + ts._name + "' is not fully initialized, field '" + fld._fname + "' needs to be set in a constructor" ); - Node op = opAssign(ch,load, decl ).keep(); - mem.keep(); - if( expr._type instanceof TypeMemPtr view && view._ro && ctrl()._type!=Type.XCONTROL ) - throw error("Cannot modify final field '"+name+"'"); - Node st = new StoreNode(loc(), name, alias, decl, ctrl(), mem, expr.unkeep(), off.unkeep(), op, false).peephole().keep(); - storeMem(st,alias,mem); - st.unkeep(); - mem.unkeep(); - - op.unkeep(); // A narrow Store may have folded away the expression's conversion. - if( postfix(ch) ) load.unkeep(); - return pre==0 && postfix(ch) ? load : op; - } - off.unkill(); - // Might be a method call, so pass expr as 'self' - return parsePostfixMethod(load,expr.unkeep()); - } - - - // 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(); - Type self = implicitSelfType(); - ts.push(self); - ids.push(self == TypePtr.PTR ? "#self" : "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 = TypeFunPtr.make1((byte)2,true,ts.asAry(),Type.BOTTOM,_code.fidx(_ref._cname)); - ReturnNode ret = parseFunctionBody(tfp,loc,finals,ids.asAry()); - return new FunPtrNode(tfp,_code._start,ret).peephole(); - } - - private Node constructorFunc(String name) { - require("{"); - Ary<Type> ts = new Ary<>(Type.class); - Ary<String> ids = new Ary<>(String.class); - java.util.BitSet finals = new java.util.BitSet(); - TypeStruct self = _scope.constructorSelf(); - assert self != null; - ts.push(TypeMemPtr.make(constructorRecv(self))); - ids.push("self"); - // Explicit constructors use the same hidden allocation arguments as - // <init>: public memory is the call memory edge (index 1), followed by - // self (index 2) and the private, unescaped object memory (index 3). - ts.push(TypeMem.makePrivate(self)); - ids.push("#selfMem"); - - _lexer.skipWhiteSpace(); - Lexer loc = loc(); - while( true ) { - Type t = type(); - if( t==null ) break; - finals.set(ids.size(),bindingFinal(t)); - String id = requireId(); - ts .push(t ); - ids.push(id); - match(","); - } - require("->"); - TypeFunPtr tfp = TypeFunPtr.make1((byte)2,true,ts.asAry(),TypeMem.makePrivate(self),_code.fidx(_ref._cname)); - Node[] preParse = _scope.save(); - ReturnNode ret = _parseFunctionBody((self._name+"."+name).intern(),tfp,loc,finals,ids.asAry()); - _scope.restore(preParse); - require("}"); - return new FunPtrNode(tfp,_code._start,ret).peephole(); - } - - /** - * Parse function call arguments; caller will parse the surrounding "()" - * <pre> - * ( arg* ) - * </pre> - */ - private Node functionCall(Node fcn, Node self) { - Lexer site = loc(); - site._position--; // Opening '('; arguments can contain nested calls. - if( fcn._type == Type.NIL ) - throw error("Calling a null function pointer"); - 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.keep()); - 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,mem().keep()); - args.push(fcn); // Function pointer - // Unkeep them all - for( Node arg : args ) - arg.unkeep(); - return functionCall(args,site); - } - - private Node functionCall( Ary<Node> args ) { - return functionCall(args,loc()); - } - - private Node functionCall( Ary<Node> args, Lexer site ) { - // 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(site, args.asAry()).peephole(); - // Post-call setup - CallEndNode cend = (CallEndNode)new CallEndNode(call,TypeRPC.constant(_code.rpc(_ref._cname))).peephole(); - call.peephole(); // Rerun peeps after CallEnd, allows early inlining - cend = (CallEndNode)cend.keep().peephole(); // TODO: Might inline - // Control from CallEnd - ctrl(new CProjNode(cend,0,ScopeNode.CTRL).peephole()); - // Memory from CallEnd - Node mem = new ProjNode(cend,1,ScopeNode.MEM0).peephole(); - mem(mem); - // Call result - return new ProjNode(cend.unkeep(),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 ConstantNode externDecl( String ex, Type t ) { - if( t instanceof TypeFunPtr tfp ) { // Generic TFP from type parse - int fidx = _code.fidx(_ref._cname); // Get a FIDX - tfp = tfp.makeFrom(fidx); // Become a constant - _code.externFunc(fidx,ex); // Map fidx to extern name - t = tfp; - } - return (ExternNode)(new ExternNode(t,ex).peephole()); - } - - // A lexical C binding has a singleton namespace with one external field. - // The namespace occupies no storage; its field offset supplies the address. - private Node externData(String name, Type t) { - if( !(t instanceof TypeInteger) && !(t instanceof TypeFloat) ) - throw error("C data binding requires an integer or float type"); - String cname = ("C:"+name).intern(); - TypeStruct ts = TypeStruct.make(cname,false,Field.make(name,t,_code.externAlias(name),false,true)); - Type old = TYPES.putIfAbsent(cname,ts); - if( old!=null && old!=ts ) - throw error("Conflicting types for C data '"+name+"'"); - return con(TypeMemPtr.make((byte)2,ts,true)); - } - - /** - * Parse integer literal - * - * <pre> - * integerLiteral: [1-9][0-9]* | [0] - * floatLiteral: [digits].[digits]?[e [digits]]?[f]? - * </pre> - */ - private ConstantNode parseLiteral() { return con(_lexer.parseNumber()); } - ConstantNode con( long con ) { return _code.con(con); } - ConstantNode con( Type t ) { return _code.con(t ); } - private void classInit(Lexer site, Node ptr) { - // Ordinary load machinery resolves the flag alias and offset when - // the receiver is still a forward reference. - Field flag = ptr._type instanceof TypeMemPtr tmp ? tmp._obj.field("$init") : null; - int alias = flag==null ? 1 : flag._alias; - Node ready = new LoadNode(site,"$init",alias,null,mem(),ptr,fldoff(ptr,"$init")).peephole(); - CLInitNode init = new CLInitNode(site,ctrl(),mem(),ptr,ready).peephole().keep(); - ctrl(new CProjNode(init,0,ScopeNode.CTRL).peephole()); - Node bulk = new ProjNode(init,1,ScopeNode.MEM0).peephole().keep(); - flag = init.flag(); - mem(flag==null ? bulk : mergeAlias(bulk,flag._alias, - new ProjNode(init,2,memName(flag._alias)).peephole())); - bulk.unkeep(); - init.unkeep(); - } - - // Field offset for a known type (but unknown offset) - public static ConFldOffNode off( TypeStruct ts, String fname ) { - return (ConFldOffNode)(new ConFldOffNode(ts,fname).peephole()); - } - // Field offset for an unknown type - public Node fldoff( Node n, String name ) { - return new FldOffNode(n,name,loc()).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)),"'"); - } - - private boolean noLowerCase(String s) { - for( int i=0; i<s.length(); i++ ) - if( Character.isLowerCase(s.charAt(i)) ) - return false; - return true; - } - - - ////////////////////////////////// - // 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; - public final CompUnit _cu; // Original source, retained across inlining. - // Tracks current position in input buffer - private int _position = 0; - - // - private int _line_number = 1; - // Start of current line - private int _line_start = 0; - - /** True when this location precedes {@code that} in the same source. */ - public boolean before(Lexer that) { - return that != null && (_cu!=null ? _cu==that._cu : _input==that._input) && _position < that._position; - } - - public String position() { return _line_number+":"+(_position-_line_start+1); } - public int pos() { return _position; } - - /** - * Record the source text for lexing - */ - public Lexer(String source) { - this(source.getBytes()); - } - - /** - * Direct from disk file source - */ - public Lexer(byte[] buf) { - _input = buf; - _cu = null; - } - - private Lexer(CompUnit cu) { - _input = cu._src.getBytes(); - _cu = cu; - } - - // Imported location: source text is only needed when reporting an error. - private Lexer(CompUnit cu, int pos) { - _input = null; - _cu = cu; - _position = pos; - } - - - /** - * Copy a lexer from a lexer - */ - private Lexer(Lexer l) { - _input = l._input; - _cu = l._cu; - _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 _input==null ? _cu._cname+"@"+_position : 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 "EOF"; - 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(); - boolean f32 = peek()=='f'; - if( f32 ) nextChar(); - int n = Math.abs(len); - if( f32 ) - return TypeFloat.constant(Float.parseFloat(new String(_input,old,n))); - 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,n))); - } - private String parseNumberString() { - int old = _position; - int len = Math.abs(isLongOrDouble()); - if( peek()=='f' ) { - nextChar(); - len++; - } - 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).intern(); - } - - // - 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+" but found `" + _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-modules/src/main/java/com/seaofnodes/simple/Var.java b/chapter-modules/src/main/java/com/seaofnodes/simple/Var.java deleted file mode 100644 index af371d21..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/Var.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Utils; - - -/** - * The tracked fields are now complex enough to deserve a array-of-structs layout - */ -public class Var { - - public final String _name; // Declared name - // These fields are not final for forward reference late updates or - // promotions. - public int _idx; // index in containing scope - private Type _type; // Declared type - public boolean _final; // Final field - public boolean _fref; // Forward ref - // Parser-only definite-initialization marker. The current binding's - // nullable type carries the path-sensitive state through SSA merges. - public boolean _uninit; - public int _extern; // C storage alias; zero for ordinary variables - public Parser.Lexer _loc; // Source location - - public Var(int idx, String name, Type type, boolean xfinal, Parser.Lexer loc ) { - this(idx,name,type,xfinal,loc,false); - } - public Var(int idx, String name, Type type, boolean xfinal, Parser.Lexer loc, boolean fref) { - _idx = idx; - _name = name; - _type = type; - _final = xfinal; - _fref = fref; - _loc = loc; - } - public Type type() { return _type; } - public Type _type() { return _type; } // ScopeNode printer needs to have no side effects - - // 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 (_fref ? "FREF " : "")+(_uninit ? "UNINIT " : "")+_type.toString()+(_final ? " ": " !")+_name; - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java deleted file mode 100644 index 0b314978..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java +++ /dev/null @@ -1,101 +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) { - FunNode fun = null; - for( Node bb : alloc._code._cfg ) { - if( bb instanceof FunNode fun2 ) - fun = fun2; - 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) ) { - if( phi instanceof MachNode mach && mach.outregmap()==null ) - continue; - // 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,fun); - if( phi instanceof MachNode ) defLRG(alloc,n,fun); - // 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,fun); - - // 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,fun); - } - } - - // Collect live ranges - alloc.unify(); - - return alloc.success(); - } - - private static void defLRG( RegAlloc alloc, Node n, FunNode fun ) { - MachNode mach = (MachNode)n; - RegMask def_mask = mach.outregmap(); - if( def_mask == null ) return; - LRG lrg = mach.twoAddress() == 0 - ? alloc.newLRG(n,fun) // 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-modules/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java deleted file mode 100644 index 6c70b71d..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java +++ /dev/null @@ -1,214 +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.*; -import java.util.*; - -// Search for Class Initialization Cycles. - -// Done after Opto, so dead code does not make a cycle. - -// Build the inits-graph, a call-graph plus CLInits plus class parents. - -// (1) Scan all functions f, building _edges[f][]: the set of <cinit> fcns from -// CompUnits from CLInitNodes, and the set of fcn call targets in f and the -// class parent. These are all reported as fidxs (ints). -// -// (2) Walk the _edges graph using Tarjans SCC. Reject cycles containing more -// than one <clinit>. -// -// (3) Report on <clinit> cycles -// -public class ClassInitDependencies { - private final CodeGen _code; - // Function FIDX -> callees, touched initializers, and parent initializer. - public final int[][] _edgess; - private final int[] _pres; - private final int[] _lows; - private final int[] _stk ; - private int _sp; - - - // Build once; tests can inspect the graph independently of cycle checking. - public ClassInitDependencies( CodeGen code ) { - _code = code; - assert code._phase.ordinal() >= CodeGen.Phase.IterPostOpto.ordinal(); - _pres = new int[code._linker._len]; - _lows = new int[code._linker._len]; - _stk = new int[code._linker._len]; - - // - BitSet visit = code.visit(); // Visit set per-fcn - Ary<CFGNode> work = new Ary<>(CFGNode.class); // Worklist per-fcn - IntHashMap iset = new IntHashMap(0); // dedup hash set per-fcn - _edgess = new int[code._linker._len][]; - - // For all functions, build the _edges array - for( FunNode fun : code._linker ) - if( fun!=null && !fun.isDead() ) - // Visit function and collect direct class touches and callees - _edgess[fun.sig().fidx()] = walk(code,fun,work,iset,visit,null); - visit.clear(); - } - - // Return the outgoing init edge set for this function - private static int[] walk( CodeGen code, FunNode fun, Ary<CFGNode> work, IntHashMap iset, BitSet visit, - Ary<Parser.Lexer> sites) { - // Walk function body, looking for outgoing init edges - assert iset .isEmpty(); // int hash set for de-dup - assert visit.isEmpty(); // needs a visit set, because loops - assert work .isEmpty(); // needs a worklist, because DAG, parallel explore paths - - // Children classes always dependent on parents - if( fun.isClz() && fun._compunit._par != null ) - iset.put(code.link(fun._compunit._par).sig().fidx(),1); - - work.push(fun); - while( !work.isEmpty() ) { - CFGNode cfg = work.pop(); - if( visit.get(cfg._nid) ) continue; // Been there, done that - visit.set(cfg._nid); - assert !cfg._type.isHigh(); - // Do not walk off end of function - if( cfg instanceof ReturnNode ) continue; - - // CLInit adds a class init requirement to this function - // (plus all parent classes) - if( cfg instanceof CLInitNode init ) { - FunNode clz = code.link(init.cu()); // <clinit> referred to - iset.put(clz.sig().fidx(),1); // Add to edge set - if( sites!=null && sites.atX(clz.sig().fidx())==null ) sites.setX(clz.sig().fidx(),init._loc); - } - - // Calls union all their fptr fidxs into the callee set - if( cfg instanceof CallNode call && call.fptr()._type instanceof TypeFunPtr fp ) { - assert !XInt.isHigh(fp.fidxs()) && fp.nfcns()>0; // Call is alive with valid targets - // Union call targets into the callee set - for( int dstx = XInt.next(fp.fidxs(),0); dstx>=0; dstx=XInt.next(fp.fidxs(),dstx) ) { - iset.put(dstx,1); - if( sites!=null && sites.atX(dstx)==null ) sites.setX(dstx,call._loc); - } - } - - // Forward recursion to walk the whole function - for( Node use : cfg._outputs ) { - // Do not walk into another function - if( !(use instanceof CFGNode cfg1) || use instanceof FunNode || use instanceof StopNode ) continue; - // Expect control flow edge is sensible - assert (use instanceof RegionNode r && r._inputs.find(cfg)>0) || use.in(0)==cfg; - if( !visit.get(cfg1._nid) ) // Small speed opt - work.add(cfg1); - } - } - - visit.clear(); - int[] edges = iset.isEmpty() ? null : iset.keySet(); - iset.clear(); - return edges; - } - - // Return an error string, or null if no cycle contains multiple clinits. - public String check() { - Arrays.fill(_pres,0); - _sp = 0; - _pres[0] = 1; // Preorders stored in pres[0] and start at 1 - - int idx = -1; - for( int fidx = 1; fidx < _edgess.length; fidx++ ) - if( _pres[fidx] == 0 && // Already visited - (idx = scc(fidx)) != -1 ) // And found a cycle - return report(idx); // Report cycle - return null; - } - - // Every internal edge of an SCC belongs to a cycle. Print all of them: - // stack order is not a path, and enumerating all cycle paths can explode. - // This bookkeeping and formatting are only needed on an error. - private String report(int idx) { - BitSet members = new BitSet(); - for( int i=idx; i<_sp; i++ ) members.set(_stk[i]); - SB sb = new SB().p("Cyclic class initialization:"); - // Recover one surviving source site per edge only after finding an - // error. A representative touch is enough; runtime first-touch order - // need not be statically known. Lexer snapshots retain the source CU - // even when the node has been inlined into a different function. - Ary<CFGNode> work = new Ary<>(CFGNode.class); - IntHashMap iset = new IntHashMap(0); - BitSet visit = new BitSet(); - Ary<Parser.Lexer> sites = new Ary<>(Parser.Lexer.class); - for( int i=idx; i<_sp; i++ ) { - int src = _stk[i]; - FunNode fun = _code.link(src); - Arrays.fill(sites._es,null); - sites.clear(); - walk(_code,fun,work,iset,visit,sites); - if( _edgess[src]!=null ) - for( int dst : _edgess[src] ) - if( members.get(dst) ) { - sb.p("\n ").p(name(src)).p(" -> ").p(name(dst)); - Parser.Lexer loc = sites.atX(dst); - if( loc!=null && loc._cu!=null ) - sb.p('\n').p(SourceError.format(loc._cu,loc.pos(),"cyclic class initialization")); - else if( fun.isClz() && fun._compunit._par==_code.link(dst)._compunit ) - sb.p("\n parent initialization requirement"); - } - } - return sb.toString(); - } - - private String name(int fidx) { - FunNode fun = _code.link(fidx); - String owner=fun._compunit._cname; - if( fun.isClz() ) return owner; - String label=fun.label(); - return (label.startsWith(owner+".") ? label : owner+"."+label)+" [fidx "+fidx+"]"; - } - - // Tarjan SCC - int scc( int fidx ) { - // Assign pre-order number - _lows[fidx] = _pres[fidx] = _pres[0]++; - _stk[_sp++] = fidx; - // walk outbound edges - if( _edgess[fidx]!=null ) { - for( int edge : _edgess[fidx] ) { - if( edge >= _pres.length ) continue; // Out of bounds happens when calling C fcns - if( _pres[edge] == 0 ) { // Not visited - int idx = scc(edge); - if( idx != -1 ) return idx; // Found a cycle - _lows[fidx] = Math.min(_lows[fidx],_lows[edge]); - } else if( _edgess[edge]!=null && onStk(edge) ) { - _lows[fidx] = Math.min(_lows[fidx],_pres[edge]); - } - } - } - // Found a cycle - if( _lows[fidx] == _pres[fidx] ) { - int idx = find(fidx); - if( hasClzCycle(idx) ) - return idx; // Found a class cycle - // Pop stack, including fidx - _sp = idx; - } - return -1; // No cycle - } - - private int find(int e) { - for( int i=0; i<_sp; i++ ) if( _stk[i] == e ) return i; - return -1; - } - private boolean onStk(int e) { return find(e) != -1; } - - private boolean hasClzCycle(int idx) { - int clinits = 0; - for( int i=idx; i<_sp; i++ ) { - FunNode fun = _code.link(_stk[i]); - if( fun!=null && fun.isClz() && ++clinits > 1 ) - return true; - } - return false; - } - -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java deleted file mode 100644 index 689ceceb..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java deleted file mode 100644 index e6bace8c..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java +++ /dev/null @@ -1,625 +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.*; -import java.io.File; -import java.util.*; - - -// Compile Driver for a Compilation Unit -@SuppressWarnings("unchecked") -public class CodeGen { - - // Last created CodeGen as a global; used all over to avoid passing about a "context". - public static CodeGen CODE; - - // Location of built-in CPU ports - public static final String PORTS = "com.seaofnodes.simple.node.cpus"; - - // Compile phases - public enum Phase { - Parse, // Parse ASCII text into Sea-of-Nodes IR - Iter, // Pessimistic peepholes after parse - Opto, // SCCP, numeric modes, seed the peephole worklist - IterPostOpto, // Peepholes and inlining using SCCP results - FinalizeIR, // Field layout, offset cleanup, function/CU roots - TypeCheck, // Last check for bad programs - Lower, // Expand checked class initialization to ordinary IR - LoopTree, // Build a loop tree; break infinite loops - Select, // Convert to target hardware nodes - Schedule, // Global schedule (code motion) nodes - LocalSched, // Local schedule - RegAlloc, // Register allocation - Encoding, // Encoding - Export, // Export - LastPhase // After last phase - } - public Phase _phase; - // Null for normal compilation; owned by this compilation, not a global viewer. - public GraphObserver<Node> _obs; - - /** True when tests deliberately randomize Iter worklist order. */ - public static boolean iterSeedOverridden() { return System.getProperty("simple.iter.seed") != null; } - - // Expensive assert sampling: - // -Dsimple.assert.expensive=0 disables these checks - // -Dsimple.assert.expensive=1 checks every step - // -Dsimple.assert.expensive=N checks once every 1<<N steps - private static final int EXPENSIVE_ASSERT_LOG = Integer.getInteger("simple.assert.expensive",8); - public static boolean expensiveAssert() { return EXPENSIVE_ASSERT_LOG > 0; } - public static boolean expensiveAssert( int trip ) { - if( EXPENSIVE_ASSERT_LOG <= 0 ) return false; - if( EXPENSIVE_ASSERT_LOG == 1 ) return true; - int shift = Math.min(EXPENSIVE_ASSERT_LOG,30); - return (trip & ((1<<shift)-1)) == 0; - } - - // --------------------------- - // Module Source Root - public final String _modDir; - // Module Build Root - public final String _buildDir; - // Slashed path from source root to the entry file, without ".smp". - public final String _srcName; - - // Only available for tests, otherwise source comes from the CompUnit - private final String _src; - - // Current Working Directory; default module base - private final String _cwd; - - // Source files discovered in this invocation. All live initializers - // participate in cycle checking, including fully inlined bodies. - public HashMap<String,CompUnit> _compunits; - - // Test shortcut for only one compilation unit - public CompUnit compunit() { - assert _compunits.size()==1; - for( CompUnit cu : _compunits.values() ) - return cu; - assert false : "no compilation unit"; - return null; - } - - // True when fidx is published through a public class or instance field. - public boolean publicFIDX(int fidx) { - if( fidx < LocalIds.RESERVED ) return false; - // A public class initializer is itself an external root; unlike - // constructors and methods, it is not reached through a class field. - FunNode fun = _linker.atX(fidx); - if( fun != null && fun.isClz() && fun.isPublic() ) - return true; - // Include instance methods even while their constant function fields - // are stored in each instance rather than on the class object. - for( Type t : Parser.TYPES.values() ) - if( t instanceof TypeStruct clz && - Parser.startsClzPrefix(clz._name) && - Parser.isPublicClassName(clz._name) && - resolvedClass(clz) ) { - if( publicFieldFIDX(clz,fidx) ) return true; - Type self = Parser.TYPES.get(clz._name.substring(Parser.CLZ.length())); - if( self instanceof TypeStruct ts && publicFieldFIDX(ts,fidx) ) return true; - } - return false; - } - - private static boolean publicFieldFIDX(TypeStruct clz, int fidx) { - boolean hasCtor = clz.field("<ctor>") != null; - for( Field fld : clz._fields ) - if( fld._fname.charAt(0)!='_' && fld._t instanceof TypeFunPtr tfp && - // With a user constructor, class:N.N is the private <init>. - !(hasCtor && clz.hiddenInit(fld)) && - XInt.bit(tfp._fidxs,fidx) ) - return true; - return false; - } - - public static boolean hasUserConstructor(TypeStruct self) { - Type t = Parser.TYPES.get(Parser.addClzPrefix(self._name)); - return t instanceof TypeStruct clz && clz.field("<ctor>") != null; - } - - // A forward reference gets a class object and constructor signature so - // parsing can proceed, but it has no callable definition yet. - private static boolean resolvedClass(TypeStruct clz) { - Type t = Parser.TYPES.get(clz._name.substring(Parser.CLZ.length())); - return !(t instanceof TypeStruct ts) || !ts._fref; - } - - private int[] _publicFIDXs, _publicAliases; - - /** - * Freeze the externally visible portion of the program after ParseAll has - * loaded every compilation unit and closed/upgraded all types. Later - * optimization can remove linker entries and create inline-only fidxs; - * neither operation changes this interface. - */ - void freezePublicInterface() { - assert _publicFIDXs==null && _publicAliases==null; - int[] fidxs = XInt.EMPTY; - for( int fidx=LocalIds.RESERVED; fidx<_linker._len; fidx++ ) - if( publicFIDX(fidx) ) - fidxs = XInt.make(fidxs,fidx); - int[] aliases = XInt.EMPTY; - for( Type t : Parser.TYPES.values() ) - if( t instanceof TypeStruct clz && - Parser.startsClzPrefix(clz._name) && - Parser.isPublicClassName(clz._name) && - resolvedClass(clz) ) { - boolean hasCtor = clz.field("<ctor>") != null; - for( Field fld : clz._fields ) - if( fld._fname.charAt(0)!='_' && fld._t!=Type.TOP && - !(hasCtor && clz.hiddenInit(fld)) ) - aliases = XInt.make(aliases,fld._alias); - } - _publicFIDXs = fidxs; - _publicAliases = aliases; - } - - public int[] publicFIDXs() { assert _publicFIDXs !=null; return _publicFIDXs; } - public int[] publicAliases(){ assert _publicAliases!=null; return _publicAliases; } - public boolean publicInterfaceFrozen() { return _publicFIDXs != null; } - - // --------------------------- - - // Very common nodes, cached here - public final ConstantNode ZERO; - public final XCtrlNode XCTRL; - - public Node constant( Type t ) { - assert _phase==null || _phase.ordinal() <= Phase.Lower.ordinal(); - return ConstantNode.make(t).peephole(); - } - public ConstantNode con( Type t ) { return (ConstantNode)constant(t); } - public ConstantNode con( long con ) { return con==0 ? ZERO : con(TypeInteger.constant(con)); } - - - // --------------------------- - // Test setup; no module nor file with specific argument - public CodeGen( String src ) { this(src, 126L, true ); } - - // Test setup; no module nor file; can alter seed & argument; can re-run same CodeGen - public CodeGen( String src, Type arg ) { - this(null,null,null, src, 126L, true, arg); - } - // Test setup; no module nor file; can alter seed & argument; can re-run same CodeGen - public CodeGen( String src, long workListSeed, boolean resetTypes ) { - this(null,null,null, src, workListSeed, resetTypes, TypeInteger.BOT); - } - - // Generic CodeGen, including full module setup - public CodeGen( String modDir, String buildDir, - String srcName, String src, long workListSeed, boolean resetTypes, Type arg ) { - // Public singleton to avoid passing about this state to a huge count - // of places. Probably becomes a TLS at some point. - CODE = this; - if( resetTypes ) Type.reset(); // Reset to recover from failed test - - _cwd = System.getProperty("user.dir")+"/"; - _modDir = modDir == null ? _cwd : modDir; - _buildDir = buildDir == null ? _cwd : buildDir; - _srcName = srcName; - _callingConv = null; // Calling convention - // Source code from test strings, not files - _src = src; - // All the compilation units - _compunits = new HashMap<>(); - _phase = null; - // Start GVN table - _gvn = new HashMap<>(); - // Allow whole-suite worklist-order sweeps without rewriting the many - // explicit seeds used by tests and command-line compilation. - String iterSeed = System.getProperty("simple.iter.seed"); - _iter = new IterPeeps(iterSeed == null ? workListSeed : Long.parseLong(iterSeed)); - // End points of graph - _stop = new StopNode().init(); - _start = new StartNode(null,_stop,arg).init(); - ZERO = con(TypeInteger.ZERO).keep(); - XCTRL = new XCtrlNode().peephole().keep(); - P = new Parser(this); - } - - - // Run the requested phases. ELF output contains machine code and data. - - // No code emission, just IR generation - public CodeGen driver( Phase phase ) { return driver(phase,null,null,false,false,0); } - // No object file writing, but code generation for a specific cpu/os pair (allows emulation) - public CodeGen driver( Phase phase, String cpu, String callingConv ) { - if( phase.ordinal() < Phase.Export.ordinal() ) - // No code outputted - return driver(phase,cpu,callingConv,false,false,0); - if( _srcName == null ) - throw new RuntimeException("No source filename provided, so do not know how to name the obj file"); - boolean emitEntrySymbol = !_srcName.contains("."); - return driver( phase, cpu, callingConv, false, emitEntrySymbol, 0 ); - } - // Write an object file for a specific cpu/os pair - - public CodeGen driver( String cpu, String callingConv, boolean inMemory, boolean emitEntrySymbol ) { return driver(Phase.Export,cpu,callingConv,inMemory,emitEntrySymbol,0); } - - // The Generic driver - public CodeGen driver( Phase target, String cpu, String callingConv, boolean inMemory, boolean emitEntrySymbol, int dump ) { - // Parse the entry source and discover all referenced source files. - step(target, Phase.Parse, this::parse, dump); - // Run peepholes to a fixed point - step(target, Phase.Iter, this::iter, dump); - // Run SCCP (and resolve int/flt expressions) - step(target, Phase.Opto, this::opto, dump); - // Run peepholes to a fixed point again, on the better types. - step(target, Phase.IterPostOpto, this::iterPostOpto, dump); - // Field layouts; field symbols become constant offsets - step(target, Phase.FinalizeIR, this::finalizeIR, dump); - // Final error check - bad programs fail here - step(target, Phase.TypeCheck, this::typeCheck, dump); - // Replace "fat" nodes with their exploded explicit IR; this can - // introduce CFG diamonds - step(target, Phase.Lower, this::lower, dump); - // Compute the loop-tree, used for approximating exec frequencies for - // scheduling and register allocation - step(target, Phase.LoopTree, this::loopTree, dump); - // Instruction selection - step(target, Phase.Select, () -> instSelect(cpu,callingConv), dump); - // Global Code Motion - step(target, Phase.Schedule, this::GCM, dump); - // Local (basic block only) scheduling - step(target, Phase.LocalSched, this::localSched, dump); - // Register allocation - step(target, Phase.RegAlloc, this::regAlloc, dump); - // Encode machine instructions into bits - step(target, Phase.Encoding, this::encode, dump); - // Export machine code and data into one ELF file - step(target, Phase.Export, () -> exportELF(inMemory,emitEntrySymbol), dump); - return this; - } - - // The driver names the order; optional phases decide locally whether to run. - 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(dump); - } - - // All phase entry points share ordering checks, timing, and viewer notification. - // Record elapsed time even on failure, but report completion only on success. - CodeGen phase(Phase next, boolean ordered, Runnable work) { - assert ordered : "Cannot run "+next+" after "+_phase; - _phase = next; - long t0 = System.currentTimeMillis(); - work.run(); // Do The Phase - _times[next.ordinal()] = System.currentTimeMillis() - t0; - if( _obs!=null ) _obs.phase(next.name()); // Record for viewer - return this; - } - - public String entryClinitName() { - return Parser.addClzPrefix(_srcName==null ? "Test" : _srcName.replace('/','.'))+".<clinit>"; - } - - // Verbose printing during compilation - private void dump(int dump) { - int p2 = _phase.ordinal(); - if( (dump & (1<<p2)) == 0 ) return; - if( (dump & (1<<30)) != 0 ) - System.err.println("After "+_phase+":"); - System.err.println(_phase.ordinal() >= Phase.LocalSched.ordinal() - ? asm(new SB()).toString() - : IRPrinter.prettyPrint(this)); - } - - - // Record times by phase - public final long[] _times = new long[Phase.LastPhase.ordinal()]; - - // --------------------------- - /** - * 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++; - } - - - // Allocate dense alias/function/call IDs within this invocation. - // Class slots let a forward reference reserve an initializer's identity. - public final LocalIds _aliases = new LocalIds(); - public int alias(String clz) { return _aliases.next(clz); } - - // C storage has one global alias per symbol, including across object files. - public int externAlias(String name) { return _aliases.next("C:"+name,LocalIds.RESERVED); } - - // External data addresses supplied by an in-memory linker (image offsets). - public final HashMap<String,Integer> _externDataAddresses = new HashMap<>(); - - // Compute local function index (FIDX) from global function info. This is - // called *in order* during parsing, and that order is part of the global - // unique mapping - public final LocalIds _fidxs = new LocalIds(); - // Return local index for specific global file & order. Used to find fidxs for e.g. FREF class <init> fcns - public int fidx( String clz, int order ) { return _fidxs.next(clz, order); } - // New fidx in the current file - public int fidx( String clz ) { return fidx(clz, -1); } - - // Compute local RPC index from global RPC info, one per call - public final LocalIds _rpcs = new LocalIds(); - public int rpc( String clz ) { return _rpcs.next(clz); } - - - // 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; - - // 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 linker lookup, suitable for printers and debugger display. - // In particular, do not lazily remove a partially dead function. - public FunNode _link(int fidx) { return _linker.atX(fidx); } - public FunNode _link(TypeFunPtr tfp) { return _link(tfp.fidx()); } - // Return the FunNode from a fidx - public FunNode link( int fidx ) { - FunNode fun =_linker.atX(fidx); - if( fun!=null && fun.isDead() ) { - _linker.setX(fidx,null); fun = null; } - return fun; - } - // Return the <clinit> for a CompUnit - public FunNode link( CompUnit cu ) { - for( FunNode fun : _linker ) - if( fun!=null && fun._compunit == cu && fun.isClz() ) - return fun; - return null; - } - - // 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). - public final Ary<FunNode> _linker = new Ary<>(FunNode.class); - public final Ary<CallNode> _calls = new Ary<>(CallNode.class); - - // Extern function declarations; input is the FIDX assigned to the name. - final HashMap<Integer,String> _externFunc = new HashMap<>(); - public void externFunc(int fidx, String ex) { - assert !_externFunc.containsKey(fidx); - _externFunc.put(fidx,ex); - } - public String externFunc(int fidx) { return _externFunc.get(fidx); } - - public String funcName(int fidx ) { - FunNode fun = _link(fidx); - if( fun!=null ) return fun._name; - return externFunc(fidx); - } - - public boolean owns(FunNode fun) { - return fun._compunit != null && fun._compunit._src != null; - } - - - // --------------------------- - // Parser object - public final Parser P; - - // Parse ASCII text into Sea-of-Nodes IR - public CodeGen parse() { return phase(Phase.Parse, _phase==null, () -> ParseAll.parse(this,_src)); } - - - // --------------------------- - // 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 after parsing. This lifts loaded and parsed - // types before the optimistic Opto pass resets types and lets them fall. - public CodeGen iter() { return phase(Phase.Iter, _phase==Phase.Parse, () -> Iter.run(this)); } - - // SCCP and numeric-mode resolution; leave cleanup work queued for Iter. - 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)); } - - // Root a surviving function without claiming that it has unknown callers. - void keepInCompUnit(FunNode fun) { - StopCUNode stop = fun._compunit._stop; - if( stop._inputs.find(fun.ret()) == -1 ) - add(stop).addDef(fun.ret()); - } - public <N extends Node> N add( N n ) { return _iter.add(n); } - public void addAll( Ary<Node> ary ) { _iter.addAll(ary); } - public void addAll( Node n ) { _iter.add(n); _iter.addAll(n._inputs); } - - // --------------------------- - // Last check for bad programs - public CodeGen typeCheck() { return phase(Phase.TypeCheck, _phase==Phase.FinalizeIR, () -> TypeCheck.run(this)); } - - public static TypeStruct requiredStruct(Node n) { - // CNC: remove? cleanup? - if( n instanceof NewNode alloc ) return alloc._ts; - if( n instanceof ConFldOffNode off ) return off._ts; - if( n instanceof FldOffNode off && off.in(0)._type instanceof TypeMemPtr ptr ) return ptr._obj; - return null; - } - - // --------------------------- - // TypeCheck queued surviving CLInits during its existing error walk. - public CodeGen lower() { - return phase(Phase.Lower, _phase==Phase.TypeCheck, () -> _iter.iterate(this)); - } - - // --------------------------- - // Build the loop tree; break never-exit loops - public CodeGen loopTree() { - return phase(Phase.LoopTree, _phase==Phase.Lower, () -> { _start.buildLoopTree(_linker,_stop); FinishCalls.run(this); }); - } - - // --------------------------- - - - // --------------------------- - // Code generation CPU target - public Machine _mach; - // Chosen calling convention (usually either Win64 or SystemV) - public String _callingConv; - public String _cCallingConv; - // Callee save registers - public RegMask _callerSave; - public RegMask _cCallerSave; - - // 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 ) { - return InstSelect.select(this,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) { - if( _phase.ordinal() >= Phase.RegAlloc.ordinal() ) { - String s = _regAlloc.reg(n); - if( s!=null ) return s; - } - return "N"+ n._nid; - } - - - // --------------------------- - // Encoding - public Encoding _encoding; - public CodeGen encode() { return phase(Phase.Encoding, _phase==Phase.RegAlloc, () -> _encoding = new Encoding(this).encode()); } - - // --------------------------- - // Exporting to external formats - ElfWriter _elf; - public CodeGen exportELF( boolean inMemory, boolean emitEntrySymbol ) { - return phase(Phase.Export, _phase==Phase.Encoding, () -> { - if( inMemory ) new LinkMem(this).link(_encoding); - else _elf = new ElfWriter(this).export(emitEntrySymbol); - }); - } - - - // --------------------------- - - public boolean _asmLittle=true; - public String asm() { return asm(new SB()).toString(); } - SB asm(SB sb) { - ASMPrinter.print(sb,this); - return sb; - } - - - // Testing shortcuts - public Node ctrl() { return compunit()._stop.ret().ctrl(); } - public Node expr() { return compunit()._stop.ret().expr(); } - public String print() { return compunit()._stop.print(); } - - // Debugging helper - @Override public String toString() { - if( _stop == null ) - return "No StopNode"; - return IRPrinter.prettyPrint(this); - } - - // Debugging helper - public Node f(int idx) { - for( CompUnit cu : _compunits.values() ) { - Node n = (cu._stop == null ? _stop : cu._stop).find(idx); - if( n != null ) return n; - } - return null; - } - - - 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(); - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java deleted file mode 100644 index c8b0a82c..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeStruct; -import com.seaofnodes.simple.util.*; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.*; -import static com.seaofnodes.simple.codegen.CodeGen.CODE; - -// A source file and its class. Parent links follow the source directory tree. -public class CompUnit { - public final CompUnit _par; // Parent, e.g. A/B - final String _fname; // Full slashed file name starting from module root, e.g. A/B/C - final public String _cname; // Full dotted class name starting from module root, e.g. A.B.C - final String _name; // Base name, e.g. C - final File _smp; // Simple source file, e.g. module_root/A/B/C.smp - final public String _src; // Source code, from file or test case - - public String sourceFile() { - return _smp!=null ? _smp.getAbsolutePath() : null; - } - - public TypeStruct _clz; // The one TypeStruct being published - - // _deps records units required for types, code, or class references, - // without requiring their initializers to run. These source - // dependencies may form a cycle. - Ary<CompUnit> _deps; - - // Per-Compilation-Unit Start/StopNodes, keeping alive all exported Nodes. - // Null means no code loaded (yet). - public StartCUNode _start; - public StopCUNode _stop ; - - - // Discovered source file. Source is loaded if required to parse, and null - // otherwise. - CompUnit( CompUnit par, String fname, String cname, String name ) throws IOException { - _par = par; - _fname= fname; - _cname= cname; - _name = name; - _smp = source(fname); - - _src = _smp.exists() ? Files.readString(_smp.toPath()) : null; - } - - // The bundled library is ordinary source. A local sys tree can replace it; - // this chapter never loads Simple code from an object file. - static File source(String fname) { - File file=new File(CODE._modDir,fname+".smp"); - if( file.exists() || !(fname.equals("sys") || fname.startsWith("sys/")) ) return file; - return new File("src/main/smp",fname+".smp"); - } - - // Test case. No source file. Source code passed in as a String. Fake - // class name. - CompUnit( String fname, String src ) { - _par = null; - _name = fname; - _fname= fname; - _cname= fname; - _smp = null; - _src = src; - } - - // Record source discovery without adding an initialization edge. - void addDep(CompUnit dep) { - if( _deps == null ) _deps = new Ary<>(CompUnit.class); - _deps.add(dep); - } - - public void addFun(CodeGen code, FunNode fun) { - assert fun.in(1)==_start; - _stop.addDef(fun.ret()); - code.add(_stop); - _stop.addDep(fun); - } - - void setStart(CodeGen code) { - _stop = new StopCUNode().init(); - _start = new StartCUNode(code._start,_stop, Type.BOTTOM, _fname).init(); - code._stop.addDef(_stop); - } - - @Override public String toString() { - String x = "{"+_cname; - if( _smp!=null && _smp.exists()) x += ",hasSMP"; - if( _src!=null ) x += ",hasSrc"; - x += "}"; - return x; - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java deleted file mode 100644 index c52209dd..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java +++ /dev/null @@ -1,460 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeFunPtr; -import com.seaofnodes.simple.type.TypeStruct; -import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.BAOS; -import java.io.*; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.HashMap; - -public class ElfWriter { - - final CodeGen _code; - Ary<Section> _sections; - StringSection _strtab; - - ElfWriter( CodeGen code ) { - _code = code; - } - - // ------------------------------------------------------------------------ - abstract public class Section { - static final int SHT_NULL = 0; - static final int SHT_PROGBITS= 1; - static final int SHT_SYMTAB = 2; - static final int SHT_STRTAB = 3; - static final int SHT_RELA = 4; - - final String _name; - final int _type, _entsize, _index, _name_pos, _flags; - int _link, _info; - int _file_offset; - - Section(String name, int type, int entsize, int flags) { - _name = name; - _type = type; - _entsize = entsize; - _flags = flags; - _index = _sections._len; - _sections.push(this); - _name_pos = name.isEmpty() - ? 0 // Null section - : _strtab == null - ? 1 // StringSection - // All the other sections - : _strtab.writeCString(name); - } - - 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 - out.putLong(_entsize); // Fixed-size element size - } - - abstract void write(ByteBuffer out); - abstract int size(); - } - - // ------------------------------------------------------------------------ - // Generic data sections; carries about a BAOS for bits - public class DataSection extends Section { - static final int SHF_WRITE = 1; - static final int SHF_ALLOC = 2; - static final int SHF_EXECINSTR = 4; - static final int SHF_INFO_LINK = 64; - - final BAOS _contents; - - DataSection( String name, int type, int flags, int entsize ) { this(name, type, flags, entsize, new BAOS() ); } - DataSection( String name, int type, BAOS contents, int flags ) { this(name, type, flags, 0, contents ); } - private DataSection( String name, int type, int flags, int entsize, BAOS contents ) { - super(name, type, entsize, flags); - _contents = contents; - } - - @Override void write(ByteBuffer out) { out.put(_contents.toByteArray()); } - @Override int size() { return _contents.size(); } - public void write2( int op ) { - _contents.write(op ); - _contents.write(op>> 8); - } - public void write4( int op ) { - _contents.write(op ); - _contents.write(op>> 8); - _contents.write(op>>16); - _contents.write(op>>24); - } - public void write8( long i64 ) { - write4( (int) i64 ); - write4( (int)(i64>>32)); - } - - } - - // ------------------------------------------------------------------------ - // Section to collect and de-dup strings - public class StringSection extends DataSection { - final HashMap<String,Integer> _dedup = new HashMap<>(); - - StringSection() { - super(".strtab",SHT_STRTAB, 0, 0 ); - int emptyoff = writeCString(""); // Always empty string at offset 0 - assert emptyoff == 0; - int namepos = writeCString(".strtab"); - assert namepos == _name_pos; - } - - public int writeCString(String name) { - if( name==null ) return 0; // Anonymous functions have no name - Integer ii = _dedup.get(name); - if( ii != null ) return ii; - // place name in the string table - int pos = _contents.size(); - _dedup.put(name,pos); - byte[] name_bytes = name.getBytes(); - _contents.write(name_bytes); - // the strings are null terminated - _contents.write(0); - return pos; - } - } - - // ------------------------------------------------------------------------ - // The relocations; 3 words per relocation - public class ReloSection extends DataSection { - ReloSection( int info ) { - this(".rela.text",info); - } - ReloSection(String name,int info) { - super(name, SHT_RELA, SHF_INFO_LINK, 24/*entsize*/); - _link = 2; - _info = info; - } - - void writeReloOffPlus(int offset, int symidx, byte relo) { - writeRelo(offset,symidx,relo,-4); - } - void writeRelo(int offset,int symidx,byte relo,long addend) { - assert symidx > 0; - write8(offset); - write8(((long)symidx << 32L) | relo); - write8(addend); - } - } - - - // ------------------------------------------------------------------------ - // Symbols section; emit the symbols into the data section directly - public class SymbolSection extends DataSection { - public static final int SYMBOL_SIZE = 4+1+1+2+8+8; - final BAOS _globals; - final int _nlocals; - - SymbolSection(int nlocals) { - super(".symtab", SHT_SYMTAB, 0/*flags*/, SYMBOL_SIZE); - _globals = new BAOS(); - _nlocals = nlocals; - } - - // Global symbol index - int gidx() { return _nlocals+(_globals.size()/SYMBOL_SIZE); } - - public int symbol( String name, int shndx, int bind, int type, long value, long size ) { - int gidx = gidx(); // Global index before I write to _globals - BAOS baos = bind==SYM_BIND_LOCAL ? _contents : _globals; - write4(baos,_strtab.writeCString(name)); - int info = (bind << 4) | (type & 0xF); - baos.write(info); - baos.write(0); // other/visibility - write2(baos,shndx); // section index - write8(baos,value); - write8(baos,size); - return bind==SYM_BIND_LOCAL ? -99 : gidx; - } - - public int symbol( String name, int shndx, int bind, int type ) { - return symbol(name,shndx,bind,type,0,0); - } - - // creates function and stores where it starts. - // Filters to just this class.obj file. - void encodeFunctions(StopNode stop, Encoding enc, int text_idx, boolean emitEntrySymbol) { - for( Node stopcu : stop._inputs ) { - for( Node ret : stopcu._inputs ) { - FunNode fun = ((ReturnNode)ret).fun(); - if( !_code.owns(fun) ) - continue; - int end = enc.opStart(fun.ret()) + enc.opLen(fun.ret()); - long value = enc.opStart(fun); - long size = end - value; - if( fun._name != null ) { // Anonymous functions have no name - // The C runtime owns `main` and adapts argc/argv into - // a normal Simple array. Keep the Simple function on - // its native ABI under a private entry symbol. - String name = emitEntrySymbol && "main".equals(fun._name) - ? "simple_main" - : fun._name; - symbol(name, text_idx, SYM_BIND_GLOBAL, SYM_TYPE_FUNC, value, size); - } - } - } - } - - // Insert a default-main for top-level compilations, if the top-level - // does not declare a main. - void symbolEntryClinit(int text_idx, Encoding enc) { - FunNode clinit = null; - String entryName = _code.entryClinitName(); - for( FunNode fun : _code._linker ) - if( fun != null && _code.owns(fun) ) { - if( "main".equals(fun._name) ) - return; // encodeFunctions exported this as simple_main - if( entryName.equals(fun._name) ) - clinit = fun; // Keep top-level entry, if we don't find an explicit main - } - // No top-level main, so the top-level clinit becomes a default main - if( clinit != null ) { - assert enc.opStart(clinit) == 0; - symbol("simple_main",text_idx, SYM_BIND_GLOBAL, SYM_TYPE_FUNC, enc.opStart(clinit), 0); - return; - } - throw new IllegalStateException("Missing entry <clinit> "+entryName); - } - - static void write2( BAOS bits, int op ) { - bits.write(op ); - bits.write(op>> 8); - } - static void write4( BAOS bits, int op ) { - bits.write(op ); - bits.write(op>> 8); - bits.write(op>>16); - bits.write(op>>24); - } - static void write8( BAOS bits, long i64 ) { - write4(bits, (int) i64 ); - write4(bits, (int)(i64>>32)); - } - - @Override - void writeHeader(ByteBuffer out) { - // points to string table section - _link = _strtab._index; - _info = _nlocals; - super.writeHeader(out); - } - - @Override void write(ByteBuffer out) { - out.put(_contents.toByteArray()); - out.put(_globals .toByteArray()); - } - @Override int size() { - return _contents.size() + _globals.size(); - } - } - - 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 final int SECTION_HDR_SIZE = 64; - - // ------------------------------------------------------------------------ - - - // ------------------------------------------------------------------------ - - // Native code can live above 4G, outside our four-byte function pointers. - // Give address-taken natives a low entry which tail-jumps through the PLT. - // A weak, stable name makes references from separate objects agree on one - // entry. Do not patch references straight to the local copy: that would - // bypass the linker's choice and break pointer equality across objects. - private int nativeEntry(String name, HashMap<String,Integer> entries, - DataSection text, SymbolSection symbols, ReloSection relos) { - Integer old = entries.get(name); - if( old!=null ) return old; - int offset = text.size(); - text._contents.write(0xE9); // jmp rel32; preserve arguments and return address - text.write4(0); - int target = symbols.symbol(name,0,SYM_BIND_GLOBAL,SYM_TYPE_FUNC); - relos.writeReloOffPlus(offset+1,target,(byte)4/*PLT32*/); - int entry = symbols.symbol("__simple_native$"+name,text._index, - SYM_BIND_WEAK,SYM_TYPE_FUNC,offset,5); - entries.put(name,entry); - return entry; - } - - public ElfWriter export(boolean emitEntrySymbol) { - // Sections are created in the order they are emitted. - _sections = new Ary<>(Section.class); - - // Null section - Section nullSection = new DataSection("",Section.SHT_NULL,0,0); - - // String section next; written out early and available to record/dedup - // strings for all remaining sections - _strtab = new StringSection(); - - // place all the symbols - int nlocals = 1/*null*/+9/*number of sections*/; - SymbolSection symbols = new SymbolSection(nlocals); - - // we've already constructed these entire sections in the encoding phase - Encoding enc = _code._encoding; - // Append ELF-only thunks to a copy. Export must not change instruction - // offsets or the image used by the in-memory linker and emulators. - BAOS textBits = new BAOS(enc._bits.toByteArray(),enc._bits.size()); - DataSection text = new DataSection(".text" , Section.SHT_PROGBITS, textBits, DataSection.SHF_ALLOC | DataSection.SHF_WRITE | DataSection.SHF_EXECINSTR ); - // Constant pool - DataSection rodata = new DataSection(".rodata", Section.SHT_PROGBITS, enc._cpool, DataSection.SHF_ALLOC ); - // Static/global pool - DataSection rwdata = new DataSection(".data" , Section.SHT_PROGBITS, enc._sdata, DataSection.SHF_ALLOC | DataSection.SHF_WRITE ); - - - // populate function symbols - symbols.encodeFunctions(_code._stop, _code._encoding, text._index, emitEntrySymbol); - // Export the source compilation unit <clinit> as the C runtime "main". - // Encoding keeps this function at text offset zero. - if( emitEntrySymbol ) - symbols.symbolEntryClinit(text._index,enc); - - // create external .text relocations - ReloSection relocations = new ReloSection(text._index); - HashMap<String,Integer> nativeEntries = new HashMap<>(); - - for( Node n : enc._externals.keySet()) { - String extern = enc._externals.get(n); - boolean nativeAddress = n instanceof FunPtrNode && - _code.externFunc(((TypeFunPtr)n._type).fidx())!=null; - int symidx = nativeAddress - ? nativeEntry(extern,nativeEntries,text,symbols,relocations) - : symbols.symbol(extern,0,SYM_BIND_GLOBAL,SYM_TYPE_FUNC); - int offset = enc.opStart(n) + enc.opLen(n) - 4; - // Address materialization and static fields name the same thunk; - // direct calls still go straight to the native symbol's PLT entry. - relocations.writeReloOffPlus(offset,symidx,(byte)(nativeAddress ? 2/*PC32*/ : 4/*PLT32*/)); - } - - // Native ELF output is AMD64. Data addresses use PC32, not PLT calls. - for( Node n : enc._externData.keySet() ) { - String extern = enc._externData.get(n); - int symidx = symbols.symbol(extern, 0, SYM_BIND_GLOBAL, SYM_TYPE_OBJECT); - int offset = enc.opStart(n) + enc.opLen(n) - 4; - relocations.writeReloOffPlus(offset, symidx, (byte)2/*PC32*/); - } - - // Stable function identities are also addressable from data, including - // anonymous functions which have no source-level exported name. - HashMap<String,Integer> dataSymbols=new HashMap<>(); - for( FunNode fun : _code._linker ) - if( fun!=null && !fun.isDead() && _code.owns(fun) && enc.opStart(fun)>=0 ) { - String name=_code._fidxs.symbol(fun.sig().fidx()); - dataSymbols.put(name,symbols.symbol(name,text._index,SYM_BIND_GLOBAL,SYM_TYPE_FUNC,enc.opStart(fun),0)); - } - for( StaticData.ObjectData obj : enc._data.objects.values() ) { - int section=obj.external ? 0 : obj.readOnly ? rodata._index : rwdata._index; - dataSymbols.put(obj.symbol,symbols.symbol(obj.symbol,section,SYM_BIND_GLOBAL,SYM_TYPE_OBJECT, - obj.external ? 0 : obj.offset,obj.external ? 0 : obj.size)); - } - for( Encoding.Relo relo : enc._bigCons.values() ) { - StaticData.ObjectData obj=enc._data.object(relo._t); - relocations.writeReloOffPlus(relo._opStart+relo._off,dataSymbols.get(obj.symbol),relo._elf); - } - ReloSection roRelos=new ReloSection(".rela.rodata",rodata._index); - ReloSection rwRelos=new ReloSection(".rela.data",rwdata._index); - for( StaticData.Relocation relo : enc._data.relocations ) { - String name=enc._data.targetSymbol(relo.value()); - String nativeName=relo.value() instanceof TypeFunPtr fp ? _code.externFunc(fp.fidx()) : null; - Integer sym=nativeName==null ? dataSymbols.get(name) - : nativeEntry(nativeName,nativeEntries,text,symbols,relocations); - if( sym==null ) { - sym=symbols.symbol(name,0,SYM_BIND_GLOBAL,SYM_TYPE_NOTYPE); - dataSymbols.put(name,sym); - } - // AMD64 absolute 32/64-bit addresses; linker rejects overflow. - byte kind=(byte)(relo.logSize()==2 ? 10 : 1); - (relo.object().readOnly ? roRelos : rwRelos).writeRelo(relo.object().offset+relo.offset(),sym,kind,0); - } - - // Create a "Simple" section for Simple types and ir - - // Write the local section header symbols - assert nlocals == 1+_sections._len; - symbols.symbol("",0,0,0); // Null symbol at index 0 - for( Section s : _sections ) - symbols.symbol(s._name, s._index, SYM_BIND_LOCAL, SYM_TYPE_SECTION, 0, s.size()); - - int size = 64; // ELF header size - // 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; - size += SECTION_HDR_SIZE*_sections.len(); - - 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) _sections.len()); // shnum - out.putShort((short) _strtab._index); // shstrndx - - // raw section data - for( Section s : _sections ) - s.write(out); - - // Section headers - for( Section s : _sections ) - s.writeHeader(out); - - assert out.position() == size; - - String objName = _code._buildDir + "/" + _code._srcName + ".o"; - File file = new File(objName); - if( file.getParentFile()!=null ) - file.getParentFile().mkdirs(); - try { - BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file)); - bos.write(out.array()); - bos.close(); - } catch( IOException ioe ) { - // Caller does not want to deal, but it is a failed compilation in any case - throw new RuntimeException(ioe); - } - // Reset for next ELF - _sections = null; - _strtab = null; - return this; - } - -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Encoding.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Encoding.java deleted file mode 100644 index 06bf14f5..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Encoding.java +++ /dev/null @@ -1,570 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.print.IRPrinter; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.*; -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; - - // CFG Nodes in RPO order, per-encoding. - public Ary<CFGNode> _cfg; - - // 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 - // chunk 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 final BAOS _bits = new BAOS(); // Instructions / encodings - public final BAOS _cpool = new BAOS(); // Constant r/o pool - public final BAOS _sdata = new BAOS(); // Static r/w pool - public final HashMap<Node,Relo> _bigCons = new HashMap<>(); - public final StaticData _data; - public final HashMap<String,Integer> _externalAddresses = new HashMap<>(); - - // 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; - _readOnly = readOnly(t); - } - public boolean readOnly() { return _readOnly; } - private final boolean _readOnly; - private static boolean readOnly(Type t) { - // Class initializers still write their fields, even when every - // final value is constant. Their storage must remain writable. - return !(t instanceof TypeStruct ts) || - (!Parser.startsClzPrefix(ts._name) && ts.isConstant()); - } - } - - Encoding( CodeGen code ) { - _code = code; - _data = new StaticData(this); - } - - // 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, 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; } - - private final static int SENTINEL = -1; - private final IntHashMap _opStart = new IntHashMap(SENTINEL); - private final IntHashMap _opLen = new IntHashMap(SENTINEL); - - - // Read op start for n - public int opStart(Node n) { return _opStart.get(n._nid); } - // Read op length for n - public byte opLen (Node n) { return (byte)_opLen.get(n._nid); } - - // Set op start for n - public void opStart(Node n, int off) { - assert off != SENTINEL; - _opStart.put(n._nid,off); - } - - // Set op start for n - public void opStartAdd(Node n, int off) { - if( off==0 ) return; - int old = _opStart.get(n._nid); - assert old != SENTINEL && old+off != SENTINEL; - _opStart.put(n._nid,old+off); - } - - // Set op length for n - public void opLen(Node n, byte len) { - assert len != SENTINEL; - _opLen.put(n._nid,len); - } - - // 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 ) { - FunNode fun = _code.link(call.tfp()); - if( fun != null && !_code.owns(fun) ) - _externals.put(call,fun._name); - else - _internals.put(call,fun); - return this; - } - // Code address as a constant - public Encoding relo( FunPtrNode fptr ) { - TypeFunPtr tfp = (TypeFunPtr)fptr._type; - // Native functions have no local FunNode, but are still callable. - // Do not treat their addresses as identities of deleted functions. - String nativeName = _code.externFunc(tfp.fidx()); - if( nativeName!=null ) return external(fptr,nativeName); - FunNode fun = _code.link(tfp); - if( fun != null && !_code.owns(fun) ) - _externals.put(fptr,fun._name); - else - _internals.put(fptr,fun); - return this; - } - // Local jump - public void jump( CFGNode jmp, CFGNode dst ) { - _internals.put(jmp,dst.uctrlSkipEmpty()); - } - - // External references only located by Strings a link-time - public final HashMap<Node,String> _externals = new HashMap<>(); - public Encoding external( Node n, String extern ) { - _externals.put(n,extern); - return this; - } - - public final HashMap<Node,String> _externData = new HashMap<>(); - public void externalData(Node n, String name) { _externData.put(n,name); } - - // Store t as a 32/64 bit constant in the code space; generate RIP-relative - // addressing to load it. Type is stored in either the .rodata or .data. - public void largeConstant( Node relo, Type t, int off, int elf ) { - // TODO: Any-old struct in the cpool - assert t.isConstant() || (t instanceof TypeStruct ts && Parser.startsClzPrefix(ts._name)); - assert (byte)off == off; - assert (byte)elf == elf; - _bigCons.put(relo,new Relo(relo,t,(byte)off,(byte)elf)); - } - - // -------------------------------------------------- - Encoding 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(); - - // Place pool objects and record relocations within their contents. - _data.write(); - - // Patch RIP-relative and local encodings now. - patchLocalRelocations(); - - return this; - } - - // -------------------------------------------------- - // 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<>(); - _cfg = new Ary<>(CFGNode.class); - rpos.put(_code._start.loop(),_cfg); - BitSet visit = _code.visit(); - - // Do them all except the <clinit>s - Ary<FunNode> clinits = new Ary<>(FunNode.class); - FunNode entry = null; - String entryName = _code.entryClinitName(); - for( FunNode fun : _code._linker ) { - if( fun != null && !fun.isDead() && _code.owns(fun) ) { - if( fun.isClz() ) { - if( fun._name.equals(entryName) ) - entry = fun; - else - clinits.add(fun); - } else { - int x = _cfg._len; - _rpo_cfg(fun,visit,rpos); - assert _cfg.at(x) instanceof ReturnNode; - } - } - } - // Now the <clinit>s last, so when reversed they become first. The - // program entry <clinit> is last of all, so it lands at offset zero. - for( FunNode clinit : clinits ) { - int x = _cfg._len; - _rpo_cfg(clinit,visit,rpos); - assert _cfg.at(x) instanceof ReturnNode; - } - if( entry != null ) { - int x = _cfg._len; - _rpo_cfg(entry,visit,rpos); - assert _cfg.at(x) instanceof ReturnNode; - } - - - // Reverse in-place - for( int i=0; i< _cfg.size()>>1; i++ ) - _cfg.swap(i,_cfg.size()-1-i); - visit.clear(); - } - - - 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, then `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. - - // Current function is used by the spill-op encodings to query the stack - // frame layout and get the spill offsets. - private void writeEncodings() { - for( CFGNode bb : _cfg ) { - if( !(bb instanceof MachNode mach0) ) - opStart(bb, _bits.size()); - else if( bb instanceof FunNode ) { - opStart(bb, _bits.size()); - mach0.encoding( this ); - opLen(bb, (byte) (_bits.size() - opStart(bb))); - } - for( Node n : bb._outputs ) { - if( n instanceof MachNode mach && !(n instanceof FunNode) && - // FunPtrs are encoding in the block of their control, not - // the block of the ReturnNode input - which is also a CFG. - !(n instanceof FunPtrNode && bb instanceof ReturnNode) ) { - opStart(n, _bits.size()); - mach.encoding( this ); - opLen(n, (byte) (_bits.size() - opStart(n))); - } - } - } - } - - // -------------------------------------------------- - // Short-form RIP-relative support: replace short encodings with long - // encodings and expand the code, changing all the offsets. - private void compactShortForm() { - int len = _cfg._len; - int[] oldStarts = new int[len]; - for( int i=0; i<len; i++ ) - oldStarts[i] = opStart(_cfg.at(i)); - - // 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 = _cfg.at(i); - opStartAdd(bb, slide); - // Slide down all other (non-CFG) ops in the block - for( Node n : bb._outputs ) - if( n instanceof MachNode && !(n instanceof CFGNode) && - // Match writeEncodings(): FunPtrs are encoded in the - // block of their control, not again at the Return. - !(n instanceof FunPtrNode && bb instanceof ReturnNode) ) - opStartAdd(n, 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) - opStart(bb); - byte opLen = riprel.encSize(delta); - // Recorded size is smaller than the current size? - if( opLen(bb) < opLen ) { - // Start sliding the code down; record slide amount and new size - slide += opLen - opLen(bb); - opLen(bb, 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 = _cfg.at(i); - // Functions pad to align 16 - if( bb instanceof FunNode ) { - int newStart = opStart(bb)+slide; - slide += (newStart+15 & -16)-newStart; - } - opStartAdd(bb, slide); - for( Node n : bb._outputs ) - if( n instanceof MachNode && !(n instanceof CFGNode) && - !(n instanceof FunPtrNode && bb instanceof ReturnNode) ) - opStartAdd(n, slide); - } - - // Copy/slide the bits to make space for all the longer branches - int grow = opStart(_cfg.at(len-1)) - 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(_cfg.at(i)); - 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); - Node dst = _internals.get(src); - // If function is entirely dead, only the function pointer remains - // and, it can only be used to test against zero or equals to - // another function pointer... i.e., there Is No Code Here. - int target = dst == null ? start : opStart(dst); - ((RIPRelSize)src).patch(this, start, opLen(src), target - start); - } - } - - // Emit a single scalar as bits - static void addN( int log, Type t, BAOS bits ) { - long x = switch( t ) { - case TypeInteger ti -> ti.value(); - case TypeFloat tf -> log==3 - ? Double.doubleToRawLongBits( tf.value()) - : Float.floatToRawIntBits((float)tf.value()); - case TypeFunPtr tfp -> 0; // These need to be relocated - case TypeMemPtr tmp -> 0; // These need to be relocated - default -> { if( t==Type.NIL ) yield 0; else throw Utils.TODO(); } - }; - addN(log,x,bits); - } - - // 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; - - int externalAddress(String name) { - Integer address=_externalAddresses.get(name); - if( address!=null ) return address; - return switch(name) { - case "calloc" -> SENTINEL_CALLOC; - case "write" -> SENTINEL_WRITE; - default -> throw new IllegalArgumentException("Unresolved external function '"+name+"'"); - }; - } - - void patchGlobalRelocations() { - for( Node src : _externData.keySet() ) { - String name = _externData.get(src); - Integer target = _code._externDataAddresses.get(name); - if( target==null ) throw new IllegalArgumentException("Unresolved C data symbol '"+name+"'"); - int start = opStart(src); - ((RIPRelSize)src).patch(this,start,opLen(src),target-start); - } - for( Node src : _externals.keySet() ) { - int start = opStart(src); - String dst = _externals.get(src); - int target = externalAddress(dst); - ((RIPRelSize)src).patch(this, start, opLen(src), target - start); - } - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/FinalizeIR.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/FinalizeIR.java deleted file mode 100644 index 923486c6..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/FinalizeIR.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.node.*; -import java.util.Arrays; - -// Comnpute field layouts only after SCCP and its peepholes converge. -// Keep surviving functions rooted without introducing unknown callers. -abstract class FinalizeIR { - static void run(CodeGen code) { - // Compute field layouts and replace symbolic field offsets by constants. - for( int i=0; i<code._start.nOuts(); i++ ) { - Node use = code._start.out(i); - if( use instanceof ConFldOffNode off ) { - Node con = off.asOffset(); - assert con != null : off._ts._name+"."+off._fname; - off.subsume(con); - i--; - } - } - code._iter.iterate(code); // Propagate field offsets and clean up. - - // Retain surviving functions for emission without adding unknown callers. - for( FunNode fun : code._linker ) - if( fun != null ) { - // A folded static function field can leave a detached body - // with internal cycles. It has no callers; later phases must - // not revive it merely because its nodes are still allocated. - if( fun.isDead() || fun._type.isHigh() ) { - code._linker.set(fun.sig().fidx(),null); - continue; - } - code.keepInCompUnit(fun); - } - for( CompUnit cu : code._compunits.values() ) { - ProjNode mem = cu._start.proj(1); - if( mem != null ) code.add(mem.unkeep()); - } - // Stable print order: initializers first, then other Returns by NID. - for( Node stop : code._stop._inputs ) - Arrays.sort(stop._inputs._es,0,stop.nIns(), - (x,y) -> ((((ReturnNode)x).fun().isClz() ? -1 : x._nid) - - (((ReturnNode)y).fun().isClz() ? -1 : y._nid) )); - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/FinishCalls.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/FinishCalls.java deleted file mode 100644 index 7ba696d3..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/FinishCalls.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.node.*; - -// Detach analysis call edges after building the loop tree. Every live body -// belongs to this invocation; preserve it for scheduling and emission. -abstract class FinishCalls { - - - static void run(CodeGen code) { - - // 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. - for( FunNode fun : code._linker ) { - // Already linked to start, not going dead - if( fun==null || fun.isDead() ) - continue; - - // Insert the post-Opto code-generation hook. Unlike an Opto-time - // Start input, this no longer carries unknown-caller semantics. - ReturnNode ret = fun.ret(); - StartNode start = fun._compunit._start; - assert start!=null; - if( fun.nIns() <= 1 || fun.in(1) != start ) { - fun.insertDef(1,start); - for( Node use : fun._outputs ) - if( use instanceof ParmNode parm ) - parm.insertDef(1,ConstantNode.raw(parm._type)); - if( fun._compunit._stop._inputs.find(ret) == -1 ) - fun._compunit._stop.addDef(ret); - } - // Unlink from Call - for( int i=2; i<fun.nIns(); i++ ) - ((CallNode)fun.in(i)).unlink(fun,i--); - if( fun.rpc()==null ) { // Ensure valid 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. - ParmNode rpc = new ParmNode( "$rpc", 0, ret.rpc()._type, fun ); - rpc.addDef(ret.rpc()); - ret.setDef( 3, rpc.init() ); - } - } - - code._stop.walk(n -> { - if( n instanceof CallEndNode cend ) - for( int i=1; i<cend.nIns(); i++ ) - if( cend.in(i) instanceof ReturnNode ) - cend.delDef(i--); - if( n instanceof CallNode call ) - call.unlink_all(); - return null; - }); - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java deleted file mode 100644 index 4477025b..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java +++ /dev/null @@ -1,304 +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 - assert !( def instanceof ReturnNode && use instanceof CallEndNode ); // All calls unwired now - 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. - assert CodeGen.CODE._mach==null || CodeGen.CODE.owns(fun); - 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()]; - Ary<Node> antideps = new Ary<>(Node.class); - // Breadth-first scheduling - breadth(code._stop,ns,late,anti, antideps); - - // 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, Ary<Node> antideps) { - // 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(antideps) ) - if( late[memuse._nid]==null ) - continue outer; - - // All uses done, schedule - _doSchedLate(n,ns,late,anti,antideps); - } - - // 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, Ary<Node> antideps) { - // 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, antideps); - - // 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 parm ) - 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; - } - - // A writer may consume a load's memory through an 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, Ary<Node> antideps) { - // We could skip final-field loads here. - // Walk LCA->early, flagging Load's block location choices - for( CFGNode cfg=lca; 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(antideps) ) { - if( mem instanceof PhiNode phi ) { - // Inputs carrying different memory might overwrite this alias. - // Raise the LCA for those paths; the unchanged-memory paths - // do 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); - } else { - // Hunt for clobbering stores - assert mem instanceof MemOpNode || mem instanceof EscapeNode || - mem instanceof CallNode || mem instanceof CLInitNode; - assert late[mem._nid]!=null; - lca = anti_dep(load,late[mem._nid],mem.cfg0(),lca,mem,anti); - } - } - 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. - // A different Phi input need not be dominated by this memory's - // definition. Stop at its depth rather than looking for that ancestor. - // Retain the sentinel too: a function entry has a 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-modules/src/main/java/com/seaofnodes/simple/codegen/IFG.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/IFG.java deleted file mode 100644 index bcdb14bf..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/IFG.java +++ /dev/null @@ -1,580 +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(); - WORK.clear(); - - // Last block has nothing live out - 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) ) { - Node live = TMP.get(tlrg); - // Disallow clone-ables from living across killed registers. - // Fail them and let splitting re-clone closer to the target use. - if( live instanceof MachNode mach && mach.isClone() ) - alloc.fail(tlrg); - else if( !tlrg.sub(killMask) ) - alloc.fail(tlrg); - } - } - } - - // Check for self-conflict live ranges. These must split, and only happens - // during the first round a particular LRG splits. - private static void selfConflict(RegAlloc alloc, Node n, LRG lrg) { - selfConflict(alloc,n,lrg,TMP.get(lrg)); - } - private static void selfConflict(RegAlloc alloc, Node n, LRG lrg, Node prior) { - if( prior!=null && prior != n ) { - lrg.selfConflict(prior); - lrg.selfConflict(n); - alloc.fail(lrg); // 2 unrelated values live at once same live range; self-conflict - } - } - - // Merge TMP into bb's live-out set; if changes put bb on WORK - private static void mergeLiveOut( RegAlloc alloc, CFGNode priorbb, int i ) { - CFGNode bb = priorbb.cfg(i); - if( bb == null ) return; // Start has no prior - while( !bb.blockHead() ) bb = bb.cfg0(); - - // Lazy get live-out set for bb - IdentityHashMap<LRG, Node> lrgs = BBOUTS.computeIfAbsent( bb, k -> new IdentityHashMap<>() ); - - for( LRG lrg : TMP.keySet() ) { - Node def = TMP.get(lrg); - // Effective def comes from phi input from prior block - if( def instanceof PhiNode phi && phi.cfg0()==priorbb ) { - assert i!=0; - def = phi.in( i ); - } - // 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,alloc); - - // 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, RegAlloc alloc) { - // Out of trivial colorable, pick an at-risk to pull - if( sptr==swork ) - swap(color_stack,sptr,pickRisky(color_stack,sptr,alloc)); - // 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 - } - - // Returns true if 'lrg' is better than 'best' - 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(); - // If both are single register (so both sizes are 1), - // return a multi-use over a multi-def - if( best.size1() ) - return !best._multiUse && lrg._multiUse; - // 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, RegAlloc alloc ) { - int best=sptr; - int bestScore = pickRiskyScore(color_stack[best],alloc); - for( int i=sptr+1; i<color_stack.length; i++ ) { - if( bestScore == 1000000 ) return best; // Already max score - int iScore = pickRiskyScore(color_stack[i],alloc); - 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, RegAlloc alloc ) { - // Callee-save live ranges cover the whole function and are cheap to - // split. Rank scheduled area recovered against the loop-scaled cost - // of the def- and use-side moves. - if( lrg._machDef instanceof CalleeSaveNode ) - return areaScore(functionArea(lrg._fun,alloc), - splitCost((Node)lrg._machDef,(Node)lrg._machUse)); - if( lrg._splitDef != null && lrg._splitDef. in(1) instanceof CalleeSaveNode && - lrg._splitUse != null && lrg._splitUse.out(0) instanceof ReturnNode ) - return areaScore(functionArea(lrg._fun,alloc), - splitCost(lrg._splitDef,lrg._splitUse)); - - // Pick single-def clonables that are not right next to their single-use. - // Rank by the area recovered by cloning closer to the use. - 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(); - int area = cfg != use.cfg0() - ? functionArea(lrg._fun,alloc) - : cfg._outputs.find(use)-cfg._outputs.find(def)-1; - // A single-use function address can move next to its use without - // adding an instruction. Prefer that to saving a callee-save - // register just to hold the address. Stay in the same block and - // require a gap so the clone makes progress. - if( def instanceof FunPtrNode && !lrg._multiUse && - cfg == use.cfg0() && area > 0 ) - return 1000000; - if( area > 0 ) - return areaScore(area,loopCost(use)); - } - - // Default for ranges without a specific area/cost estimate. - return 1000 + (lrg._multiUse ? -100 : 0); - } - - private static int functionArea(FunNode fun, RegAlloc alloc) { - int area=0; - for( CFGNode bb : alloc._code._cfg ) - if( bb.fun()==fun ) - area += bb.nOuts(); - return area; - } - - private static int areaScore(int area, int cost) { - return 1000 + (area<<10)/cost; - } - - private static int splitCost(Node def, Node use) { - return loopCost(def) + (use==null ? 0 : loopCost(use)); - } - - private static int loopCost(Node n) { - return 1 << (n.cfg0().loopDepth()*3); - } - - 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-modules/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java deleted file mode 100644 index db35d524..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; -import java.util.BitSet; -import java.util.IdentityHashMap; - -// Replace ideal nodes with machine nodes while preserving CFG/loop metadata. -// Selection also owns target ABI masks and rebuilding the copied graph's uses. -class InstSelect { - private final CodeGen _code; - private InstSelect(CodeGen code) { _code=code; } - - static CodeGen select(CodeGen code, String cpu, String callingConv, String base) { - // Ideal evaluation schedules the ideal graph without a CPU port. - if( cpu==null ) return code; - return code.phase(CodeGen.Phase.Select, code._phase==CodeGen.Phase.LoopTree, - () -> new InstSelect(code).run(cpu,callingConv,base)); - } - - private 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.forName(clzFile).asSubclass(Machine.class).getDeclaredConstructor(CodeGen.class).newInstance(_code); } - catch( Exception e ) { throw new RuntimeException(e); } - - _code._cCallingConv = _code._mach.cCallingConv(callingConv); - - // Build global copies of common register masks. - long callerSave = _code._mach.callerSave(); - long cCallerSave= _code._mach.callerSave(_code._cCallingConv); - 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 - assert maxReg>=64 || (-1L << maxReg & cCallerSave)==0; // No stack slots in cCallerSave - _code._callerSave = new RegMask(callerSave); - _code._cCallerSave= new RegMask(cCallerSave); - - // 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(); - - // Replace the CompUnit stop (and list of functions) - // with hardware-specific ones - for( CompUnit cu : _code._compunits.values() ) { - cu._start= (StartCUNode)map.get(cu._start); - cu._stop = (StopCUNode )map.get(cu._stop ); - } - } - - 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. - if( n instanceof MemOpNode mem && mem.off() instanceof ExternOffsetNode off ) { - // External fields replace base+offset with symbol+0. Keep the ideal - // declaring type on the address for machine load/store width selection. - TypeMemPtr ptr = (TypeMemPtr)mem.ptr()._type; - n._inputs.set(2,new ExternNode(TypeMemPtr.make((byte)2,ptr._obj,true),off._extern)); - n._inputs.set(3,ConstantNode.raw(TypeInteger.ZERO)); - } - map.put(n, x=_code._mach.instSelect(n) ); - // Carry loop-tree and pre-order info across the ideal->mach transition - if( n instanceof CFGNode ncfg && x instanceof CFGNode xcfg ) { - xcfg._ltree = ncfg._ltree; - xcfg._pre = ncfg._pre; - } - // 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) ); - // Post selection action - if( x instanceof MachNode mach ) { - if( n instanceof ReturnNode ret ) - ((ReturnNode)mach)._fun = (FunNode)map.get(ret._fun); - 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); - // CallNode special: outputs are partially ordered; CallEnd in slot 0 - if( in instanceof CallNode call && n instanceof CallEndNode cend && call.out(0) != cend ) - call._outputs.swap(0,call.nOuts()-1); - _instOuts(in,visit); - } - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Iter.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Iter.java deleted file mode 100644 index 1ffd9737..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Iter.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; - -// Establish a pessimistic fixed point after parsing. Later Iter runs -// reuse the worklist and types produced by the preceding phase. -abstract class Iter { - static void run(CodeGen code) { - // Close parser-in-progress states. Seed a fresh pessimistic solve; - // exposure sets start at FULL and can only shrink during Iter. - code._start.walk(n -> { - if( !(n instanceof ConstantNode) ) - n._type = iterEscapeReset(n._type); - code.add(n); - return null; - }); - code._iter.iterate(code); - // Expensive assert. - assert !CodeGen.expensiveAssert() || Opto.fixedPointCheck(code); - } - - private static Type iterEscapeReset(Type t) { - if( t instanceof TypeMem mem ) - return mem._one ? mem : mem.makeFrom(XInt.FULL,XInt.FULL); - if( t instanceof TypeTuple tt ) { - TypeTuple rez = tt; - for( int i=0; i<tt._types.length; i++ ) { - Type tx = iterEscapeReset(tt._types[i]); - if( tx != tt._types[i] ) rez = rez.makeFrom(i,tx); - } - return rez; - } - return t; - } - - -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/LRG.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/LRG.java deleted file mode 100644 index fdde8838..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/LRG.java +++ /dev/null @@ -1,240 +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; - - // Function fully containing the live range; used to find the stack frame - // layout, for spill op offsets. - final public FunNode _fun; - - 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, FunNode fun ) { _lrg = lrg; _reg = -1; _fun = fun; } - - 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 ) { - assert _fun == lrg._fun; - // 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-modules/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java deleted file mode 100644 index 252b5bf0..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.type.TypeStruct; -import com.seaofnodes.simple.util.SB; -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) { - // Patch external calls internally - enc.patchGlobalRelocations(); - - // Place constant pool after code, static data after constants - int code = 0; // Code start - int cpool = (enc._bits .size() + code + 15) & -16; // pad to 16 - int sdata = (enc._cpool.size() + cpool + 15) & -16; - - - // Patch local ops, e.g. loading float constants from the constant pool - for( Node op : enc._bigCons.keySet() ) { - Encoding.Relo relo = enc._bigCons.get(op); - int target = enc._data.address(enc._data.object(relo._t),cpool,sdata); - ((RIPRelSize)relo._op).patch(enc, relo._opStart, enc.opLen(relo._op), target - relo._opStart); - } - - enc._data.link(cpool,sdata); - return _code; - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java deleted file mode 100644 index b6b3a22c..00000000 --- a/chapter-modules/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 CFGNode) ? 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-modules/src/main/java/com/seaofnodes/simple/codegen/LocalIds.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/LocalIds.java deleted file mode 100644 index 462248dd..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/LocalIds.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import java.util.HashMap; -import java.util.Arrays; - -// Dense IDs for this invocation. A class slot can be reserved before its file -// is parsed; every later reference to that slot gets the same ID. -public class LocalIds { - public static final int RESERVED=2; - private int _next=RESERVED; - private final com.seaofnodes.simple.util.Ary<String> _owners=new com.seaofnodes.simple.util.Ary<>(String.class); - private final HashMap<String,int[]> _slots=new HashMap<>(); - - public int next(String owner) { return next(owner,-1); } - public int next(String owner, int slot) { - int[] ids=_slots.get(owner); - if( ids==null ) ids=new int[]{RESERVED,1}; - if( slot==-1 ) slot=ids[0]++; - else if( slot>=ids[0] ) ids[0]=slot+1; - if( slot>=ids.length ) ids=Arrays.copyOf(ids,Math.max(slot+1,ids.length*2)); - if( ids[slot]==0 ) { ids[slot]=_next++; _owners.setX(ids[slot],owner); } - _slots.put(owner,ids); - return ids[slot]; - } - public String owner(int id) { return _owners.atX(id); } - public String symbol(int id) { return owner(id)+".$fun"+id; } - public int nextInline() { return _next++; } - public int next(int old) { - int id=_next++; - _owners.setX(id,owner(old)); - return id; - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Machine.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Machine.java deleted file mode 100644 index 3eee8fc3..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Machine.java +++ /dev/null @@ -1,127 +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. - // The FunNode holds the stack layout after RegAlloc, which in turn - // dictates the stack slot (the frame offset) for stack splits. - public abstract SplitNode split( LRG lrg, String kind, byte round ); - - // List of caller-save registers, as a 64bit mask - public abstract long callerSave(); - public long callerSave(String conv) { return 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); - public RegMask callArgMask(TypeFunPtr tfp, int arg, int maxArgSlot, String conv) { return callArgMask(tfp,arg,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); - public short maxArgSlot(TypeFunPtr tfp, String conv) { return maxArgSlot(tfp); } - - // Calling convention used for external C symbols. - public String cCallingConv(String callingConv) { return callingConv; } - - // 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()._type instanceof TypeFunPtr tfp && tfp.isConstant() && - CodeGen.CODE.externFunc(tfp.fidx())!=null && - tfp.ret() instanceof TypeInteger ret && ret.log_size()<3 ) - return ret; - return null; - } - -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Opto.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Opto.java deleted file mode 100644 index 4db277df..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Opto.java +++ /dev/null @@ -1,317 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Ary; - -import java.util.BitSet; - -// 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); - unlinkStart(code); - - // OPTIMISTIC INTERPROCEDURAL SCCP - sccp(code, oldTypes); - - // SCCP can leave a recursive numeric component at BOTTOM with no - // arithmetic mode selected. Resolve the whole component from all - // available int/float evidence before IterPeeps consumes the types. - resolveNumericModes(code); - - if( !code._iter._work.isEmpty() ) - 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 - 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(CodeGen code) { - // Keep the Start & Start.ProjMem alive even as we remove all uses - for( CompUnit cu : code._compunits.values() ) { - ProjNode mem = cu._start.proj(1); - if( mem!=null ) mem.keep(); - } - - // Iterate the Stop-of-Stops - for( Node stop : code._stop._inputs ) { - for( int i=0; i<stop.nIns(); i++ ) { - ReturnNode ret = (ReturnNode)stop.in(i); - FunNode fun = ret.fun(); - // Start is a semantic unknown-caller edge. Public functions - // keep it; closed-world functions must be reached by a - // concrete Call edge discovered during SCCP. Initializers, - // including private ones, are called by opaque CLInit - // obligations whose Call edges are introduced only in Lower. - if( !fun.isClz() && - !code.publicFIDX(fun.sig().fidx()) && - fun.in(1) instanceof StartNode ) { - fun.removeDeadPath(1); - // Unhook from Stop without treating the ReturnNode as - // DEAD. It stays in limbo until Opto links when a caller - // is discovered (or not, and the function is really dead). - stop._inputs.del(i--); - ret.delUse(stop); - } - // Unlink the existing conservative call linkages. - for( int j=1; j<fun.nIns(); j++ ) - if( fun.in(j) instanceof CallNode call ) - call.unlink(fun,j--); - } - } - } - - // 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 - int[] fidxs = tfp.fidxs(); - // An unresolved forward call can still carry the infinite, inferred - // function set into Opto. There is no finite call graph to link yet; - // type checking will report the unresolved reference afterwards. This - // can yet be valid if the call is eventually dead. - // `if( false ) frefCall(args);` - if( XInt.isHigh(fidxs) ) return; - for( int fidx = XInt.next(fidxs,0); fidx >=0; fidx = XInt.next(fidxs,fidx) ) { - // unlinkStart deliberately leaves uncalled functions in a - // temporarily dead limbo. A newly discovered call revives them; - // do not use the cleanup lookup which removes dead linker entries. - 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); - } - } - } - - // Start added some FIDXs, the same functions now can be reached from start - // and called by anybody. Add the CG edges. - private static void linkStart( CodeGen code, TypeTuple tt ) { - TypeMem tmem = (TypeMem)tt._types[1]; - int[] fidxs = tmem._escFs; - if( XInt.isHigh(fidxs) ) { - // A scalar BOTTOM can flow into memory while compiling an error - // program. It exposes all public functions, not private fields. - for( FunNode fun : code._linker ) - if( fun != null && code.publicFIDX(fun.sig().fidx()) ) - linkStart(code,fun); - return; - } - for( int fidx = XInt.next(fidxs,0); fidx >=0; fidx = XInt.next(fidxs,fidx) ) { - FunNode fun = code._linker.atX(fidx); - if( fun==null ) assert code._externFunc.containsKey(fidx); - else linkStart(code,fun); - } - } - - // Function is now reachable by unknown callers through Start. The - // default inputs must therefore use conservative declared types. - private static void linkStart( CodeGen code, FunNode fun ) { - assert !fun.isDead(); - StartCUNode start = fun._compunit._start; - if( !fun.unknownCallers() ) { - assert !start.isDead(); - code.keepInCompUnit(fun); - // Function is reachable by any *remote* caller who gets the pointer! - fun.insertDef(1,start); - code._iter.add(fun); - for( Node p : fun._outputs ) - if( p instanceof ParmNode parm ) { - Node defalt = parm._idx==1 ? start.proj(1) : ConstantNode.seed(parm._declaredType).peephole(); - parm.insertDef(1,defalt); - code.add(parm); - } - } - } - - - // 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) : "Non-monotonic SCCP: "+n.getClass().getSimpleName()+"#"+n._nid+" "+n+" old="+oval+" new="+nval; // Types start high and always fall - Type pesiVal = oldTypes.atX(n._nid); - assert pesiVal==null || nval.isa(pesiVal) : "SCCP below pessimistic type: "+n.getClass().getSimpleName()+"#"+n._nid+" "+n+" pessimistic="+pesiVal+" new="+nval; // Never fall worse than the pessimistic pass - n._type = nval; - - // Now we have a series of stanzas where we lazily create the Call - // Graph - adding graph edges between every call site and called - // function, including external (unknown) callers. These edges are - // not in from the start because they are *too many*; we would get - // O(n^2) edges. So all along we have been treating them as - // "virtual" CFG edges - a FunNode taking a Start input is treated - // as if every call on the planet might call it. Now we make these - // CG edges *concrete*, adding them back as we discover that a call - // calls a particular function. - - // If a TFP adds a new function input to a call, link to the new - // Fun. This adds new edges to the graph allowing argument values - // to flow from calls into functions. The added edges are - // effectively a refined Call Graph. - 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. Like above, adds new Call - // Graph edges to the graph. - if( n instanceof CallNode call && nval == Type.CONTROL && call.fptr()._type instanceof TypeFunPtr ) - linkCG(code,call.tfp(),call); - - // If an otherwise-dead function pointer escapes, any future linked - // caller might find and call it. Force the function to be alive - // and called by Start. - if( n instanceof StartNode ) - linkStart(code,(TypeTuple)n._type); - - // Since n._type changed, visit all output neighbors - code._iter.addAll(n._outputs); - n.moveDepsToWorklist(code._iter); - // Quadratic (expensive) small-step assert. - assert !CodeGen.expensiveAssert(count) || worklistCheck(code); - } - } - - // Quadratic (expensive) fixed-point assert - public static boolean fixedPointCheck(CodeGen code) { - return fixedPointCheck(code,code._stop); - } - public static boolean fixedPointCheck(CodeGen code, Node root) { - root.walk( x -> { - Type xval = x.compute(); - assert xval == x._type; - return null; - }); - return true; - } - - // Quadratic (expensive) small-step assert for SCCP, where changed nodes - // may already be on the worklist. - private static boolean worklistCheck(CodeGen code) { - code._midAssert = true; - Node root = code._start; - root.walk( x -> { - Type xval = x.compute(); - assert xval == x._type || (x._type.isa( xval ) && code._iter._work.on( x )) - : "Non-monotonic or missed SCCP work: "+x.getClass().getSimpleName()+"#"+x._nid+" "+x+" old="+x._type+" new="+xval; - return null; - }); - code._midAssert = false; - 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.add(x); - return null; - }); - } - - // Resolve arithmetic modes which are ambiguous only because they belong to - // a recursive component. Float wins because integer operands can be - // converted to float. - private static void resolveNumericModes(CodeGen code) { - final BitSet visited = new BitSet(); - code._stop.walk(n -> { - if( n instanceof ModeNode modeNode && modeNode.mode()==0 ) { - visited.clear(); - visited.set(n._nid); // Inspect inputs, not Bool's integer result - int mode = 0; - for( int i=1; i<n.nIns(); i++ ) - mode |= numericEvidence(n.in(i),visited); - // A closed recursive numeric component can have no remaining - // concrete evidence after SCCP, e.g. dead all-integer loops. - // With no float evidence, settle to integer. - if( mode == 0 ) - mode = 1; - if( mode != 0 ) { - // If both evidence, FP wins - modeNode.setMode((byte)((mode&2)==2 ? 2 : 1)); - code.add(n); - code.addAll(n.outs()); - } - } - return null; - }); - } - - // Gather numeric evidence through Phi and interprocedural return cycles. - // The visited set makes self- and mutually-recursive calls finite. - private static int numericEvidence(Node n, BitSet visited) { - if( n == null ) return 0; - if( visited.get(n._nid) ) return 0; - visited.set(n._nid); - - Type t = n._type; - if( t instanceof TypeFloat ) return 2; - if( t instanceof TypeInteger ) return 1; - if( t!=Type.BOTTOM && !(t instanceof TypeTuple) ) - return 0; // Something unrelated, like a ptr - - int evidence = 0; - assert !(n instanceof ConvertNode); // No test case - switch( n ) { - case ModeNode modeNode when modeNode.mode() != 0 -> { return modeNode.mode(); } - case ModeNode modeNode -> {} - case PhiNode phi -> {} - case ReturnNode ret -> {} - case ProjNode proj when proj.in(0) instanceof CallEndNode cend -> { - for( int i=1; i<cend.nIns(); i++ ) - evidence |= numericEvidence(cend.in(i),visited); - } - default -> { return 0; } - } - for( int i=1; i<n.nIns(); i++ ) - evidence |= numericEvidence(n.in(i),visited); - - return evidence; - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java deleted file mode 100644 index 171d9923..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java +++ /dev/null @@ -1,285 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.print.*; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.*; -import java.io.File; -import java.io.IOException; -import java.util.*; - -// Parse a given file, and all its dependencies -public abstract class ParseAll { - - static void parse(CodeGen code, String src) { - Parser.TYPES.clear(); - Parser.TYPES.putAll(Parser.INIT_TYPES); - if( src != null ) { - // No source file, just the source itself. - parseSource(code,code._srcName==null ? "Test" : code._srcName,src); - } else { - // Path from module root to source file - parsePath(code,code._srcName); - } - } - - // Worklist of symbols to search and files to parse - private static final Ary<CompUnit> WORK = new Ary<>(CompUnit.class); - - - // Return a compilation unit (cached or new). If new, it does not necessarily - // need to be compiled. - public static CompUnit makeCUnit( CodeGen code, CompUnit par, String name ) throws IOException { - int idx=name.indexOf('/'); - if( idx>=0 ) return makeCUnit(code,makeCUnit(code,par,name.substring(0,idx)),name.substring(idx+1)); - String fname=par==null ? name : par._fname+"/"+name; - CompUnit cu=code._compunits.get(fname); - if( cu==null ) { - String cname=par==null ? name : par._cname+"."+name; - code._compunits.put(fname,cu=new CompUnit(par,fname,cname,name)); - } - return cu; - } - - - // Parse a top-level string, used for e.g. testing - static void parseSource( CodeGen code, String src ) { - parseSource(code,"Test",src); - } - - // Parse a top-level string with an explicit compilation-unit name. - static void parseSource( CodeGen code, String fname, String src ) { - CompUnit cunit = new CompUnit(fname,src); - code._compunits.put(cunit._fname,cunit); - parseAll(code,cunit); - } - - // module_root/.../src.smp is source file name. - static void parsePath(CodeGen code, String fname) { - try { - // Split and build the parent-path down to the source file. - CompUnit cu = makeCUnit( code, null, fname ); - // Compile this CompUnit - parseAll(code,cu); - } catch( IOException ioe ) { - throw new RuntimeException(ioe); - } - } - - private static void enqueue(CompUnit cu) { - if( cu._par!=null ) enqueue(cu._par); - if( cu._start==null && WORK.find(cu)==-1 ) WORK.add(cu); - } - - private static void parseAll(CodeGen code, CompUnit cunit) { - WORK.clear(); // Cleanup from prior tests - // Prime the pump with the first CompUnit - enqueue(cunit); - // Work through all unparsed compilation units and parse - while( !WORK.isEmpty() ) { - CompUnit cu = WORK.remove(0); - if( cu._src==null ) - throw new RuntimeException("Missing source file '"+cu._fname+".smp'"); - parseOne(code,cu); - } - - // Many types will have been declared cyclically either internally or - // across multiple compilation units. - - // Close over all recursive types, and upgrade TYPES - Ary<TypeStruct> ary = new Ary<>(TypeStruct.class); - for( Type t : Parser.TYPES.values() ) - if( t instanceof TypeStruct ts ) - ary.add(ts); - TypeStruct[] tss = Type.closeOver(ary.asAry(),Parser.TYPES); - for( TypeStruct ts : tss ) - Parser.TYPES.put(ts._name,ts); - for( CompUnit cu : code._compunits.values() ) - cu._clz = (TypeStruct)Parser.TYPES.get(Parser.addClzPrefix(cu._cname)); - - // Walk over all Nodes, and upgrade the internal constants to the - // closed-over types. - code._stop.walk( (Node n) -> { - n.upgradeType(Parser.TYPES); - if( n instanceof FRefNode fref ) { - boolean alloc = fref._name.endsWith(".<new>"); - String clzName = alloc - ? fref._name.substring(0,fref._name.length()-".<new>".length()).intern() - : fref._name; - TypeStruct clz = (TypeStruct)Parser.TYPES.get(clzName); - if( clz == null ) - return null; - Node con; - if( alloc ) { - Field ctor = clz.field("<new>"); - if( ctor == null || !(ctor._t instanceof TypeFunPtr tfp) ) { - String name = clzName.startsWith(Parser.CLZ) - ? clzName.substring(Parser.CLZ.length()) : clzName; - int dot = name.lastIndexOf('.'); - throw Parser.error("Unknown struct type '"+(dot==-1 ? name : name.substring(dot+1))+"'",fref._loc); - } - FunNode fun = code.link(tfp); - if( fun == null ) return null; - con = new FunPtrNode(tfp,code._start,fun.ret()).peephole(); - } else { - // Class pointer constant: - TypeMemPtr clzptr = TypeMemPtr.make((byte)2,clz,true); - con = code.con(clzptr); - } - // Replace and optimize - fref.addDef(con); - code.add(fref); - } - // Adds sort by NIDs, which just got shuffled - if( n instanceof AddNode add ) - code.add(add); - return null; - } ); - - // The complete public interface is now known. Keep this independent - // of later graph/linker liveness so SCCP computations remain local and - // monotonic. - code.freezePublicInterface(); - } - - // Parse one Simple source code file. Add all the FRefs produced to the worklist. - private static void parseOne( CodeGen code, CompUnit cunit ) { - cunit.setStart(code); - // Parse a source file, return missing external references - Ary<FRefNode> frefs = code.P.parse(cunit); - - // Find all missing external references, or complain. This can trigger more parses. - while( !frefs.isEmpty() ) { - FRefNode fref = frefs.remove(0); - // Symbol not found in source code (or we would not be here). - // Search the module for the fref - try { - CompUnit xcunit = findCUnitModule(code,cunit,fref._name); - if( xcunit != null ) { - // Replace the FRef with the discovered class name. - fref._name = Parser.addClzPrefix(xcunit._cname); - cunit.addDep(xcunit); - } else { - throw new RuntimeException("Undefined name '" + fref._name +"'"); - } - } catch( IOException ioe ) { - throw new RuntimeException(ioe); - } - } - addRequiredTypes(code,cunit); - } - - private static void addRequiredTypes(CodeGen code, CompUnit cunit) { - LinkedHashSet<String> required = new LinkedHashSet<>(); - code._stop.walk(n -> { - TypeStruct ts = CodeGen.requiredStruct(n); - if( ts!=null && ts._fref ) required.add(ts._name); - return null; - }); - for( String typeName : required ) { - try { - CompUnit xcunit = findRequiredType(code,cunit,typeName); - if( xcunit != null ) - cunit.addDep(xcunit); - else - assert false : "expected to find required type"; - } catch( IOException ioe ) { - throw new RuntimeException(ioe); - } - } - } - - private static CompUnit findRequiredType(CodeGen code, CompUnit cunit, String typeName) throws IOException { - for( CompUnit cu = cunit; cu != null; cu = cu._par ) { - String symbol = typeName.startsWith(cu._cname+".") - ? typeName.substring(cu._cname.length()+1) - : typeName; - CompUnit xcunit = findCUnitModule(code,cu,symbol); - if( xcunit != null ) - return xcunit; - } - return null; - } - - - // Symbol not found in source code (or we would not be here). - // Search the module for the fref - public static CompUnit findCompUnitOrThrow(CodeGen code, CompUnit cu, String symbol) { - CompUnit cu2 = findCompUnit(code,cu,symbol); - if( cu2 != null ) return cu2; - throw new RuntimeException("Undefined name '" + symbol +"'"); - } - - public static CompUnit findCompUnit(CodeGen code, CompUnit cu, String symbol) { - // Symbol not found in source code (or we would not be here). - // Search the module for the fref - try { - CompUnit local = findCUnitModule(code,cu,symbol); - if( local != null ) - return local; - return null; - } catch( IOException ioe ) { - throw new RuntimeException(ioe); - } - } - - // Find the symbol in module, starting from cunit. This searches up the nested - // class chain and sideways 1 step, so that any prefix of the class chain can - // be skipped as a shortcut. Symbol can have dotted names, which then go - // down into classes. e.g. - // A/X/Y exists. - // A/B contains X.Y, which is a shortcut for A.X.Y - - // However if A/B contains "Y", the search would be for A.B.Y then A.Y both of - // which do not exist. - private static CompUnit findCUnitModule( CodeGen code, CompUnit cunit, String symbol ) throws IOException { - if( cunit == null ) { - if( !symbol.equals("sys") || !CompUnit.source("sys").exists() ) return null; - CompUnit sys=makeCUnit(code,null,"sys"); - enqueue(sys); - return sys; - } - - // Check self - if( cunit._name.equals(symbol) ) - return cunit; - - // If symbol is itself nested (has embedded dots), move the leading part - // over to the CompUnit path. So e.g.: - // CompUnit{A.B}, Symbol="X.Y" - // becomes - // CompUnit{A.B.X}, Symbol="Y" - int dotIdx = symbol.indexOf("."); - if( dotIdx != -1 ) { - String pre = symbol.substring(0,dotIdx); - String rest = symbol.substring(dotIdx+1 ); - String fname = cunit._fname + "/" + pre; - File f = CompUnit.source(fname); - if( f.exists() ) { - CompUnit sub = makeCUnit(code,cunit,pre); - // enqueue also parses parents: their initialization must precede a child. - CompUnit dcunit = findCUnitModule(code,sub,rest); - if( dcunit != null ) - return dcunit; // Nested dot search found cunit - } - } - - // Look in mod / fname / name / symbol.smp - String fname = cunit._fname + "/" + symbol; - File f = CompUnit.source(fname); - if( f.exists() ) { - CompUnit sub = makeCUnit(code,cunit,symbol); - // If not seen before or out of date, parse the new nested cunit - if( sub._clz == null && WORK.find(sub) == -1 ) - enqueue(sub); - return sub; - } - - // Look up the scope - return findCUnitModule( code, cunit._par, symbol ); - } - - -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java deleted file mode 100644 index 48319006..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java deleted file mode 100644 index ac792988..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java +++ /dev/null @@ -1,780 +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; - - private boolean _done; - - // ----------------------- - // 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(); } - public boolean done() { return _done; } - - - // ----------------------- - // 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, FunNode fun ) { - LRG lrg = lrg(n); - if( lrg!=null ) return lrg; - lrg = new LRG(_lrg_num++, fun); - LRG old = _lrgs.put(n,lrg); assert old==null; - return lrg; - } - - // LRG for n - public 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 ) { - LRG lrg = _lrg(n); - return lrg==null ? null : reg(lrg); - } - String reg( LRG lrg ) { - // 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() || lrg._fun==null ) - return RegMask.reg(regs,lrg._reg); - // Stack-slot past RA uses the frame layout logic - return "[rsp+"+lrg._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); - } - // Expensive assert: useful when chasing cross-function allocator edges. - //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 - _done = true; - } - - boolean verifyFunctionLocalEdges() { - for( CFGNode bb : _code._cfg ) - for( Node use : bb._outputs ) { - FunNode useFun = funOf(use); - for( int i=1; i<use.nIns(); i++ ) { - Node def = use.in(i); - if( def==null || def.isConst() || allowedCrossFunctionEdge(use,i,def) ) - continue; - FunNode defFun = funOf(def); - if( defFun != null && useFun != null && defFun != useFun ) - throw new AssertionError(crossFunctionMessage(def,defFun,use,useFun,i)); - } - } - return true; - } - - private static String crossFunctionMessage(Node def, FunNode defFun, Node use, FunNode useFun, int idx) { - StringBuilder sb = new StringBuilder("Cross-function value edge: "); - sb.append(def.uniqueName()).append("(").append(defFun.label()).append(") -> ") - .append(use.uniqueName()).append("(").append(useFun.label()).append(") input ").append(idx) - .append(" def=").append(def).append(" use=").append(use).append("\n all uses:"); - for( Node out : def.outs() ) { - FunNode outFun = funOf(out); - sb.append("\n ").append(out.uniqueName()).append("(") - .append(outFun==null ? "?" : outFun.label()).append(")"); - } - return sb.toString(); - } - - private static boolean allowedCrossFunctionEdge(Node use, int idx, Node def) { - // Function metadata/control edges are not value live ranges. - if( use instanceof FunNode || use instanceof StopNode || use instanceof StopCUNode ) - return true; - // Return-linked function pointers carry function identity/lifetime, not - // an allocator value flowing from the callee body into this function. - if( use instanceof FunPtrNode && def instanceof ReturnNode ) - return true; - // Parm defaults and call-linked argument edges are the entry protocol; - // BuildLRG also skips Parm input 1. - if( use instanceof ParmNode ) - return true; - // Linked CallEnd -> Return edges should be gone before RegAlloc, but - // diagnose allocator value edges rather than call graph metadata here. - if( use instanceof CallEndNode && def instanceof ReturnNode ) - return true; - return false; - } - - private static FunNode funOf(Node n) { - if( n instanceof ParmNode parm ) return parm.fun(); - if( n instanceof ReturnNode ret ) return ret.fun(); - if( n instanceof FunNode fun ) return fun; - if( n instanceof CFGNode cfg ) return cfg.fun(); - CFGNode cfg = n.cfg0(); - return cfg==null ? null : cfg.fun(); - } - - 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() ) { - 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; - } - if( lrg._mask.isEmpty() && lrg._multiDef && lrg._1regUseCnt > 1 && lrg._1regDefCnt <= 1 ) { - if( splitEmptyMaskSingleUses(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. - - // Compute def nOuts *before* replacing those uses with a split - int defNouts = ((Node)lrg._machDef).nOuts(); - // Split just after def - if( lrg._1regDefCnt==1 && !lrg._machDef.isClone() ) - // Force must-split, even if a prior split same block because register - // conflicts. Example: - // alloc - // V1/rax - forced by alloc - // alloc - // V2/rax - kills prior RAX - // st4 [V1],len - No good, must split around - insertAfterAndReplace( makeSplit("def/empty1",round,lrg), (Node)lrg._machDef, false/*true*/); - // Split just before use - if( lrg._machUse!=null && (lrg._1regUseCnt==1 || (lrg._1regDefCnt==1 && defNouts==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; - } - - // Empty-mask live range with several direct single-register uses, often - // through a Phi with multiple defs. Split those uses directly instead of - // doing a loop-depth split, which might keep isolating the wrong edge. - boolean splitEmptyMaskSingleUses( byte round, LRG lrg ) { - findAllLRG(lrg); - if( _ns._len <= 5 ) - return false; - boolean progress = false; - for( Node n : _ns ) { - if( !(n instanceof MachNode mach) ) continue; - for( int i=1; i<n.nIns(); i++ ) { - RegMask use = mach.regmap(i); - if( use != null && use.size1() && lrg(n.in(i))==lrg ) { - insertBefore(n,i,"use/emptyN",round,lrg); - progress = true; - } - } - } - return progress; - } - - // 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); - - boolean emptyMulti = lrg._mask.isEmpty() && lrg._multiDef && lrg._1regDefCnt <= 1 && - (lrg._1regUseCnt > 1 || _ns._len > 5); - // Multi-def empty-mask conflicts need the hot/deep side isolated from - // fixed register uses. Ordinary pressure splits keep the old shallow - // boundary behavior. - 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 || (emptyMulti ? n.cfg0().loopDepth() >= 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 || (emptyMulti ? phi.region().cfg(i).loopDepth() >= 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()) || - (emptyMulti ? n.cfg0().loopDepth() >= max : 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(lrg,kind,round); - _lrgs.put(split,lrg); - return split; - } - private SplitNode makeSplit( String kind, byte round, LRG lrg ) { - SplitNode split = _code._mach.split(lrg,kind,round); - _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("Should not reach here"); - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/RegMask.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/RegMask.java deleted file mode 100644 index 9cb9ec16..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/RegMask.java +++ /dev/null @@ -1,121 +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; } - - // 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-modules/src/main/java/com/seaofnodes/simple/codegen/RegMaskRW.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/RegMaskRW.java deleted file mode 100644 index 1da9a3b1..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/codegen/SourceError.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/SourceError.java deleted file mode 100644 index f2f46146..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/SourceError.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.util.SB; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; - -/** Format one parser byte offset. Source is read and text built only on error. */ -public abstract class SourceError { - public static String format(CompUnit cu, int pos, String message) { - String file=cu.sourceFile(); - String name=file==null ? cu._fname+".smp" : file; - byte[] source; - try { - source=cu._src!=null ? cu._src.getBytes(StandardCharsets.UTF_8) - : file==null ? null : Files.readAllBytes(Path.of(file)); - } catch( IOException ex ) { source=null; } - if( source==null || pos<0 || pos>source.length ) - return name+": error: "+message+" (source unavailable at byte offset "+pos+")"; - - int line=1, start=0; - for( int i=0; i<pos; i++ ) - if( source[i]=='\n' ) { line++; start=i+1; } - int end=pos; - while( end<source.length && source[end]!='\n' ) end++; - if( end>start && source[end-1]=='\r' ) end--; - int point=Math.min(pos,end); - String before=new String(source,start,point-start,StandardCharsets.UTF_8); - String text=new String(source,start,end-start,StandardCharsets.UTF_8); - int column=before.codePointCount(0,before.length())+1; - SB sb=new SB().p(name).p(':').p(line).p(':').p(column).p(": error: ").p(message).p("\n "); - int width=0, caret=0; - for( int i=0; i<text.length(); ) { - if( i==before.length() ) caret=width; - int ch=text.codePointAt(i); - if( ch=='\t' ) { - int spaces=8-(width%8); - for( int j=0; j<spaces; j++ ) sb.p(' '); - width+=spaces; - } else { sb.p(new String(Character.toChars(ch))); width++; } - i+=Character.charCount(ch); - } - if( before.length()==text.length() ) caret=width; - sb.p("\n "); - for( int i=0; i<caret; i++ ) sb.p('-'); - return sb.p('^').toString(); - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/StaticData.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/StaticData.java deleted file mode 100644 index 616753d4..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/StaticData.java +++ /dev/null @@ -1,177 +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.BAOS; -import java.util.*; - -// Pool objects have identity independently of their initial field values. -// References in their contents use the same symbols in ELF and emulator images. -public final class StaticData { - public static final class ObjectData { - public final Type type; - public final String symbol; - public final boolean readOnly, external; - public final int alignment, size; - public int offset; - ObjectData(Type t, String name, boolean ro, boolean ext) { - type=t; symbol=name; readOnly=ro; external=ext; - alignment=t.alignment(); - int align=1<<alignment; - size=t instanceof TypeStruct ts ? (Math.max(1,ts.size())+align-1)&-align : align; - } - } - public record Relocation(ObjectData object, int offset, int logSize, Type value) {} - - final Encoding enc; - final LinkedHashMap<Object,ObjectData> objects = new LinkedHashMap<>(); - final ArrayList<Relocation> relocations = new ArrayList<>(); - final String prefix; - - public ArrayList<ObjectData> entries() { - ArrayList<ObjectData> entries=new ArrayList<>(objects.values()); - entries.sort(Comparator.comparingInt((ObjectData obj) -> obj.alignment).reversed()); - return entries; - } - - StaticData(Encoding enc) { - this.enc=enc; - prefix=enc._code.entryClinitName()+"$DATA$"; - } - - static boolean isClass(Type t) { - return t instanceof TypeStruct ts && Parser.startsClzPrefix(ts._name); - } - - static CompUnit owner(CodeGen code,TypeStruct ts) { - return owner(code,ts._name.substring(Parser.CLZ.length())); - } - static CompUnit owner(CodeGen code,String name) { - if(name==null) return null; - CompUnit owner=null; - for( CompUnit cu : code._compunits.values() ) - if( cu._cname!=null && (name.equals(cu._cname) || name.startsWith(cu._cname+".")) && - (owner==null || cu._cname.length()>owner._cname.length()) ) owner=cu; - return owner; - } - - ObjectData object(Type t) { - Object key=isClass(t) ? ((TypeStruct)t)._name - : t instanceof TypeFunPtr fp ? functionSymbol(fp) : t; - ObjectData old=objects.get(key); - if( old!=null ) return old; - boolean clz=isClass(t); - if( clz && Parser.TYPES.get(((TypeStruct)t)._name) instanceof TypeStruct canonical ) t=canonical; - CompUnit owner=clz ? owner(enc._code,(TypeStruct)t) - : t instanceof TypeFunPtr fp ? owner(enc._code,enc._code._fidxs.owner(fp.fidx())) : null; - String symbol=clz ? ((TypeStruct)t)._name.substring(Parser.CLZ.length())+".$class" - : t instanceof TypeFunPtr fp ? functionSymbol(fp) : prefix+objects.size(); - ObjectData obj=new ObjectData(t,symbol,!clz && (!(t instanceof TypeStruct ts) || ts.isConstant()), - owner!=null && owner._src==null); - objects.put(key,obj); - // Register before following fields: class objects may refer to each other. - if( !obj.external && t instanceof TypeStruct ts ) - for( Field f : ts._fields ) - if( !f._extern && f._t.isConstant() ) target(f._t); - return obj; - } - - private static boolean pointerConstant(TypeMemPtr ptr) { - return ptr.notNull() && (isClass(ptr._obj) || (ptr._obj.isConstant() && (!ptr._obj.isAry() || ptr._obj.isConAry()))); - } - - private void target(Type t) { - if( t instanceof TypeMemPtr ptr && pointerConstant(ptr) ) object(ptr._obj); - if( t instanceof TypeFunPtr fp && fp.notNull() && - enc._code.link(fp)==null && !enc._code._externFunc.containsKey(fp.fidx()) ) - // A dead function still needs a unique non-null identity for comparisons. - object(fp); - } - - void write() { - ArrayList<Encoding.Relo> refs=new ArrayList<>(enc._bigCons.values()); - refs.sort(Comparator.comparingInt(r -> r._op._nid)); - for( Encoding.Relo ref : refs ) object(ref._t); - // Definitions belong to their compilation unit, even if every local use folded. - ArrayList<CompUnit> units=new ArrayList<>(enc._code._compunits.values()); - units.sort(Comparator.comparing(cu -> cu._cname==null ? "" : cu._cname)); - for( CompUnit cu : units ) if( cu._src!=null && cu._clz!=null ) object(cu._clz); - for( ObjectData obj : entries() ) { - if( obj.external ) continue; - BAOS bits=obj.readOnly ? enc._cpool : enc._sdata; - Encoding.padN(1<<obj.alignment,bits); - obj.offset=bits.size(); - if( obj.type instanceof TypeStruct ts ) writeStruct(obj,ts,bits); - else Encoding.addN(obj.alignment,obj.type,bits); - while( bits.size()<obj.offset+obj.size ) bits.write(0); - } - for( Encoding.Relo ref : refs ) { - ObjectData obj=object(ref._t); - ref._target=obj.offset; - ref._opStart=enc.opStart(ref._op); - } - } - - private void writeStruct(ObjectData obj, TypeStruct ts, BAOS bits) { - int[] layout=ts.layout(); - for( int idx : layout ) { - if( idx==ts._fields.length ) continue; - Field f=ts._fields[idx]; - if( f._extern ) continue; - int off=ts.offset(idx); - while( bits.size()<obj.offset+off ) bits.write(0); - if( f._fname.equals("[]") ) ((TypeConAry)f._t).write(bits); - else { - Type value=f._t.isConstant() ? f._t : f._t.makeZero(); - int log=f._t.log_size(); - Encoding.addN(log,value,bits); - if( (value instanceof TypeFunPtr fp && fp.notNull()) || - (value instanceof TypeMemPtr ptr && pointerConstant(ptr)) ) - relocations.add(new Relocation(obj,off,log,value)); - } - } - while( bits.size()<obj.offset+ts.size() ) bits.write(0); - } - - String functionSymbol(TypeFunPtr fp) { - String ext=enc._code._externFunc.get(fp.fidx()); - return ext!=null ? ext : enc._code._fidxs.symbol(fp.fidx()); - } - - String targetSymbol(Type value) { - if( value instanceof TypeMemPtr ptr ) return object(ptr._obj).symbol; - TypeFunPtr fp=(TypeFunPtr)value; - return enc._code.link(fp)==null && !enc._code._externFunc.containsKey(fp.fidx()) - ? object(fp).symbol : functionSymbol(fp); - } - - void link(int cpool, int sdata) { - for( Relocation rel : relocations ) { - long address; - if( rel.value instanceof TypeMemPtr ptr ) address=address(object(ptr._obj),cpool,sdata); - else { - TypeFunPtr fp=(TypeFunPtr)rel.value; - String ext=enc._code._externFunc.get(fp.fidx()); - FunNode fun=enc._code.link(fp); - if( ext!=null ) address=enc.externalAddress(ext); - else if( fun==null ) address=address(object(fp),cpool,sdata); - else { - if( !enc._code.owns(fun) ) throw new IllegalArgumentException("Unlinked function "+functionSymbol(fp)); - address=enc.opStart(fun); - } - } - // Emulator native-call sentinels are signed 32-bit addresses. - if( rel.logSize==2 && (address<Integer.MIN_VALUE || address>0xffff_ffffL) ) - throw new IllegalArgumentException("Function address outside 32 bits: "+address); - BAOS bits=rel.object.readOnly ? enc._cpool : enc._sdata; - int off=rel.object.offset+rel.offset; - for( int i=0; i<(1<<rel.logSize); i++ ) bits.buf()[off+i]=(byte)(address>>>(8*i)); - } - } - - int address(ObjectData obj,int cpool,int sdata) { - if( obj.external ) throw new IllegalArgumentException("Unlinked class object "+obj.symbol); - return obj.offset+(obj.readOnly ? cpool : sdata); - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java deleted file mode 100644 index 9b4b0995..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.util.Ary; - -// Reject invalid optimized programs before lowering erases their source-level -// obligations. Queue surviving CLInits during the same walk for Lower's Iter. -abstract class TypeCheck { - static void run(CodeGen code) { - // Check for class-init cycles first; such a cycle can prevent other - // things from resolving types. The dependency graph is temporary. - String clzInitCycle = new ClassInitDependencies(code).check(); - if( clzInitCycle != null ) - throw Parser.error(clzInitCycle,null); - - Ary<Node> errs = new Ary<>(Node.class); - code._stop.walk( n -> { - if( n.err() != null ) - errs.add(n); - if( n instanceof CLInitNode ) code.add(n); - return null; - }); - - if( !errs.isEmpty() ) { - // Prefer an originating error over a secondary error caused by a - // BOTTOM input. If all errors are downstream-poisoned, retain - // the old first-error behavior. - Node min = null; - for( Node n : errs ) - if( noBottomInputs(n) ) - { min = n; break; } - if( min==null ) - min = errs.at(0); - for( Node n : errs ) - if( n!=min && - noBottomInputs(n) && - ((n instanceof MemOpNode nmop && - min instanceof MemOpNode mmop && - nmop._loc.before(mmop._loc)) || - (n instanceof CFGNode ncfg && - min instanceof CFGNode mincfg && - ncfg.idepth() < mincfg.idepth())) ) - min = n; - if( code._obs != null ) code._obs.error(min, min.err().getMessage()); - throw min.err(); - } - } - - private static boolean noBottomInputs(Node n) { - for( Node def : n._inputs ) - if( def!=null && !(def instanceof FldOffNode) && def._type==Type.BOTTOM ) - return false; - return true; - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/AddNode.java deleted file mode 100644 index 8835208b..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ /dev/null @@ -1,179 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.type.*; - -public class AddNode extends ArithNode { - @Override boolean allowFloat() { return true; } - public AddNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - public AddNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs, mode); } - - - @Override public String label() { return "Add"; } - @Override public String op() { return "+"; } - - @Override long doOp( long x, long y ) { return x + y; } - @Override double doOp(double x, double 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 () { - // Settle the one-shot arithmetic mode before applying family-specific - // algebraic identities. - if( _mode==0 ) - return super.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.isa(compute()) ) - return lhs; - - // Add of same to a multiply by 2 - if( lhs==rhs && _mode==1 ) - return new ShlNode(null,lhs,CodeGen.CODE.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),_mode); - - // 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 copy(copy(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)) - if( spine_cmp(lhs,rhs,this) ) return swap12(); - Node n = phiCon(this,true); - return n == null ? super.idealize() : n; - } - - // 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.nIns()>=2 && 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 copy(lhs.in(1),copy(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 copy(copy(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)|(op y con)) ? - // 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,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,_mode); } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/AndNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/AndNode.java deleted file mode 100644 index a737b703..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/AndNode.java +++ /dev/null @@ -1,49 +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, (byte)1); } - - - @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 && forceInt(lhs) ) - 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-modules/src/main/java/com/seaofnodes/simple/node/ArithNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ArithNode.java deleted file mode 100644 index 094b927f..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ArithNode.java +++ /dev/null @@ -1,208 +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.type.*; -import com.seaofnodes.simple.util.Utils; - -import java.util.HashMap; -import java.util.IdentityHashMap; - -public abstract class ArithNode extends Node implements ModeNode { - // Source location for late reported errors - Parser.Lexer _loc; - // Mode is 0 for unknown, 1 for int, 2 for flt - byte _mode; - - public ArithNode( Parser.Lexer loc, Node lhs, Node rhs) { super(null, lhs, rhs); _loc = loc; } - public ArithNode( Parser.Lexer loc, Node lhs, Node rhs, byte mode) { this(loc,lhs,rhs); _mode=mode; } - - 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); - double doOp(double x, double y) { throw Utils.TODO(); } - - @Override public byte mode() { return _mode; } - @Override public Node setMode(byte mode) { - assert _mode==0 && (mode==1 || mode==2); - unlock(); // _mode participates in GVN hash and equality - _mode = mode; - return this; - } - static byte mode( Type t1, Type t2 ) { - if( t1 instanceof TypeFloat || t2 instanceof TypeFloat ) return 2; - if( t1 instanceof TypeInteger && t2 instanceof TypeInteger ) return 1; - if( t1 instanceof TypeMemPtr && t2 instanceof TypeMemPtr ) return 1; - if( t1 instanceof TypeFunPtr && t2 instanceof TypeFunPtr ) return 1; - return 0; - } - - // Int-only ops, such as &, | and shifts, can collapse to one input. If - // that input, or the live arms of a Phi input, are still mode-polymorphic - // ops, settle them before returning the value so the replacement remains - // at least as narrow as the int-only op. - static boolean forceInt(Node n) { - if( n instanceof ModeNode mode && mode.mode()==0 ) { - mode.setMode((byte)1); - n.setType(n.compute()); - // Mode changes remove n from GVN even if the caller's remaining - // conditions prevent replacing the int-only operation by n. - CodeGen.CODE.add(n); - } - return true; - } - - // Check that a settled mode agrees with all data inputs. Takes the Node - // instead of a varargs list both to avoid allocation and to keep this ready - // to become shared mode-aware Node behavior later. - static Parser.ParseException nodeErr(Node n, Parser.Lexer loc, String op, byte mode, boolean allowFloat) { - Type bad = null; - for( int i=1; i<n.nIns(); i++ ) { - Type t = n.in(i)._type; - if( t==null ) - return null; - if( t.isHigh() ) - return null; - // A weak input is not evidence of a bad mode while the graph is - // still being built. Loop Phis commonly await their backedge. - if( CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal() && t == Type.BOTTOM ) - return null; - bad = switch( t ) { - case TypeInteger ti -> mode==1 ? null : t; - case TypeMemPtr ti -> mode==1 ? null : t; - case TypeFunPtr ti -> mode==1 ? null : t; - case TypeFloat tf -> mode==2 ? null : t; - default -> t; - }; - if( bad != null ) break; - } - boolean unsupportedFloat = false; - if( !allowFloat ) - for( int i=1; i<n.nIns(); i++ ) - if( n.in(i)._type instanceof TypeFloat ) { - bad = n.in(i)._type; - unsupportedFloat = true; - break; - } - if( bad == null ) - return null; - if( unsupportedFloat || n instanceof BoolNode ) - return Parser.error("Cannot '"+op+"' "+bad,loc); - if( n.nIns()==3 ) - return Parser.error("Cannot "+n.in(1)._type+" "+op+" "+n.in(2)._type,loc); - return Parser.error("Cannot '"+op+"' "+bad,loc); - } - - // Arithmetic subclasses which have a floating-point interpretation override. - boolean allowFloat() { return false; } - - // Generic arithmetic 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==null || t2==null ) return Type.TOP; - if( t1.isHigh() || t2.isHigh() ) return Type.TOP; - if( t1==Type.BOTTOM || t2==Type.BOTTOM ) return Type.BOTTOM; - byte mode = _mode==0 ? mode(t1,t2) : _mode; - if( mode==0 ) - return CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal() ? Type.BOTTOM : Type.TOP; - if( mode==1 ) { - 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; - } - // FP handling - 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); - } - return TypeFloat.F64; - } - - 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))". - - // Expected to be called after other ideal checks are done. - @Override public Node idealize() { - Type t1 = in(1)._type, t2 = in(2)._type; - - // Can we decide int vs flt? - if( _mode==0 ) { - byte mode = mode(t1,t2); - if( mode!=0 ) return setMode(mode).init(); - } - if( _mode==2 ) { - if( t1 instanceof TypeInteger ) return copy(new ToFloatNode(in(1)).peephole(),in(2)); - if( t2 instanceof TypeInteger ) return copy(in(1),new ToFloatNode(in(2)).peephole()); - } - - if( in(1) instanceof PhiNode lhs && - in(2) instanceof PhiNode rhs && - lhs.nIns() >= 2 && !lhs.inProgress() && - // Disallow with self-looping phi; these will collapse - (lhs.nIns() > 2 && lhs.in(2)!=lhs && rhs.nIns() > 2 && rhs.in(2)!=rhs) ) { - Node r = lhs.region(); - if( r==rhs.region() && !(r instanceof FunNode) && - // For loops, the backedge *must* fold away, lest we unroll forever - (!(r instanceof LoopNode) || isCon(lhs,rhs,2)) ) { - // Profit check: only 1 instance of `this` will remain, all the - // others will fold to constants. For loops, *all* must fold away - 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) - ? ConstantNode.make(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, 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() { - return nodeErr(this,_loc,op(),_mode,allowFloat()); - } - - @Override public boolean eq( Node n ) { return _mode==((ArithNode)n)._mode; } - @Override public int hash() { return _mode; } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/BoolNode.java deleted file mode 100644 index dd0bc996..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ /dev/null @@ -1,188 +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.type.*; -import com.seaofnodes.simple.util.Utils; -import java.util.HashMap; -import java.util.IdentityHashMap; -import static com.seaofnodes.simple.type.TypeInteger.*; - -abstract public class BoolNode extends Node implements ModeNode { - - // Mode is 0 for unknown, 1 for int, 2 for flt - byte _mode; - - public BoolNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - public BoolNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs); _mode = mode; } - - - abstract public String op(); // String opcode name - - @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; - if( t1==null || t2==null ) - return BOOL; - // 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; - byte mode = _mode==0 ? ArithNode.mode(t1,t2) : _mode; - if( mode==0 ) - return BOOL; - // Handle integer compares - if( mode==1 ) { - if( t1 instanceof TypeInteger i1 && - t2 instanceof TypeInteger i2 ) - return doOp(i1,i2); - return BOOL; - } - // Floating compare. Mixed inputs are converted in idealize(). - 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(); } - public boolean isFloat() { assert _mode!=0; return _mode==2; } - @Override public byte mode() { return _mode; } - @Override public Node setMode(byte mode) { - assert _mode==0 && (mode==1 || mode==2); - unlock(); // _mode participates in GVN hash and equality - _mode = mode; - return this; - } - - @Override - public Node idealize() { - // Compare of same - if( in(1)==in(2) ) - return this instanceof LT ? CodeGen.CODE.ZERO : ConstantNode.make(TRUE); - // Can we decide int vs flt? - Type t1 = in(1)._type, t2 = in(2)._type; - if( t1==null || t2==null ) - return null; - if( _mode==0 ) { - byte mode = ArithNode.mode(t1,t2); - if( mode!=0 ) return setMode(mode).init(); - } - if( _mode==2 ) { - if( t1 instanceof TypeInteger ) return copy(new ToFloatNode(in(1)).peephole(),in(2)); - if( t2 instanceof TypeInteger ) return copy(in(1),new ToFloatNode(in(2)).peephole()); - } - - - // 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 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; - } - - @Override public boolean eq( Node n ) { return _mode==((BoolNode)n)._mode; } - @Override public int hash() { return _mode; } - @Override public Parser.ParseException err() { - return ArithNode.nodeErr(this,null,op(),_mode,!(this instanceof ULT)); - } - - public static class EQ extends BoolNode { - public EQ(Node lhs, Node rhs) { super(lhs,rhs); } - public EQ(Node lhs, Node rhs, byte mode) { super(lhs,rhs, mode); } - - 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; - } - @Override boolean doOp(double lhs, double rhs) { return lhs==rhs; } - Node copy(Node lhs, Node rhs) { return new EQ(lhs,rhs,_mode); } - } - - public static class NE extends BoolNode { - public NE(Node lhs, Node rhs) { super(lhs,rhs); } - public NE(Node lhs, Node rhs, byte mode) { super(lhs,rhs,mode); } - - 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; - } - @Override boolean doOp(double lhs, double rhs) { return lhs!=rhs; } - Node copy(Node lhs, Node rhs) { return new NE(lhs,rhs,_mode); } - } - - public static class LT extends BoolNode { - public LT(Node lhs, Node rhs) { super(lhs,rhs); } - public LT(Node lhs, Node rhs, byte mode) { super(lhs,rhs, mode); } - - 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; - } - @Override boolean doOp(double lhs, double rhs) { return lhs<rhs; } - Node copy(Node lhs, Node rhs) { return new LT(lhs,rhs,_mode); } - } - - public static class LE extends BoolNode { - public LE(Node lhs, Node rhs) { super(lhs,rhs); } - public LE(Node lhs, Node rhs, byte mode) { super(lhs,rhs,mode); } - - 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; - } - @Override boolean doOp(double lhs, double rhs) { return lhs<=rhs; } - Node copy(Node lhs, Node rhs) { return new LE(lhs,rhs,_mode); } - } - - // 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,(byte)1); } - public ULT(Node lhs, Node rhs, byte mode) { - super(lhs,rhs,(byte)1); - assert mode==1; - } - - 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-modules/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java deleted file mode 100644 index df9e4fff..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java +++ /dev/null @@ -1,265 +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 com.seaofnodes.simple.util.Utils; - -import java.util.BitSet; -import java.util.HashMap; -import java.util.IdentityHashMap; - -/** - * 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. - // Directly visible by design. Empty sets share EMPTY; splitAlias performs - // copy-on-first-write and removes the node from GVN before mutation. - public BitSet _aliases; - - public BulkMemPhiNode(String label, Node... inputs) { - this(label,EMPTY,inputs); - } - - private BulkMemPhiNode(String label, BitSet aliases, Node... inputs) { - super(label, inputs); - _aliases = aliases; - } - - public BulkMemPhiNode(BulkMemPhiNode phi) { - super(phi,phi._label); - _aliases = phi._aliases.isEmpty() ? EMPTY : (BitSet)phi._aliases.clone(); - } - - public BulkMemPhiNode(RegionNode r, Node sample) { - super(r,sample); - _aliases = EMPTY; - } - - public boolean isSplit(int alias) { return _aliases.get(alias); } - - - @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,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<nOuts(); i++ ) { - int alias = outputAlias(out(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 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 ParmNode p -> { assert p._idx==1; yield 0; } - case ProjNode proj -> 0; - case ConstantNode con -> { - assert !(con._con instanceof TypeMem tmem && tmem._alias != 1); - yield 0; - } - case MemMergeNode mmm -> missingAlias(mmm); - case MemOpNode mem -> unsplit(mem._alias); - case BulkMemPhiNode bulk -> missingAlias(bulk); - default -> throw Utils.TODO("Should not reach here"); - }; - } - - // 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. - use = addDepForwards(use); - return switch(use) { - case ScopeNode scope -> 0; - case ParmNode parm -> 0; - case MemMergeNode mmm -> missingAlias(mmm); - case BulkMemPhiNode bulk -> missingAlias(bulk); - case MemOpNode mem -> unsplit(mem._alias); - case MemPhiNode phi -> unsplit(phi._alias); - case EscapeNode esc -> esc.pub()==this ? unsplit(esc.fld()._alias) : 0; - default -> { - assert use instanceof ReturnNode || use instanceof CallNode || use instanceof CLInitNode - : "Unexpected bulk-memory user "+use.getClass().getSimpleName()+"#"+use._nid+": "+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; - } - - // First explicit MemMerge slice still covered by this Phi. A user's slice - // may point to this Phi after a precise Phi folds; splitting is still valid. - 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); - assert ((TypeMem)mem._type)._alias==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 - Node bulk = bphi.init(); - 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(null,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); - mmm.init(); - // Slicing changes representation, not the set of values in memory. - // Newly exposed precise inputs can carry conservative bulk escape - // summaries; do not let those invent escapes absent from this Phi. - TypeMem old = (TypeMem)_type; - TypeMem mem = (TypeMem) mmm._type; - mmm._type = TypeMem.make(mem._alias,mem._t,mem._one,mem._clz, - mem._final,old._escFs,old._escAs); - 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.BOT.makeFrom(alias)); - 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().outs() ) - 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-modules/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/CFGNode.java deleted file mode 100644 index 4f225fbe..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/CFGNode.java +++ /dev/null @@ -1,303 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Ary; - -import java.util.BitSet; -import java.util.IdentityHashMap; - -/** 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; - } - - // Safe read of the control input of a data node. - public static CFGNode safeCFG(Node n) { - if( n==null ) return null; - if( !(n.in(0) instanceof CFGNode cfg) ) return null; - return cfg._safeCFG(); - } - private CFGNode _safeCFG() { - return switch(this) { - case CLInitNode init when init.bypass() -> init.cfg0()._safeCFG(); - case CallNode call when call.cend()._folding -> call.cfg0()._safeCFG(); - case CallEndNode cend when cend._folding -> cend.cfg(1)._safeCFG(); - case FunNode fun when fun._folding -> fun.cfg(1)._safeCFG(); - case ReturnNode ret when ret.fun()._folding -> ret.cfg0()._safeCFG(); - default -> this; - }; - } - - // True when this node dominates `sub` in the current idom tree. - public boolean dominates(CFGNode sub, Node dep) { - for( CFGNode cfg=sub; cfg!=null; cfg=cfg.idom() ) - if( cfg == this ) - return true; - else if( dep != null ) dep.addDep(cfg); - return false; - } - - public static CFGNode deepest(CFGNode a, CFGNode b) { - if( a == null ) return b; - if( b == null ) return a; - return b.idepth() > a.idepth() ? b : a; - } - - public static CFGNode earlyCFG(Node n) { - return earlyCFG(n,new IdentityHashMap<>(),new BitSet()); - } - - public static CFGNode earlyCFG(Node n, IdentityHashMap<Node,CFGNode> cache, BitSet active) { - if( n == null ) return null; - CFGNode cached = cache.get(n); - if( cached != null ) return cached; - if( active.get(n._nid) ) - return safeCFG(n); - active.set(n._nid); - CFGNode early = null; - for( Node x : n._inputs ) - early = deepest(early,defCFG(x,cache,active)); - active.clear(n._nid); - cache.put(n,early); - return early; - } - - private static CFGNode defCFG(Node n, IdentityHashMap<Node,CFGNode> cache, BitSet active) { - if( n == null || n.isConst() ) return null; // no impact on early cfg - return switch( n ) { - case PhiNode phi -> phi.region(); - case CFGNode cfg -> cfg; - case ProjNode projNode -> safeCFG( n ); - default -> earlyCFG( n, cache, active ); - }; - } - - public boolean sameFun(CFGNode that) { - FunNode fun = fun(); - return fun != null && fun == that.fun(); - } - - // Find nearest enclosing FunNode - public FunNode fun() { - CFGNode cfg = this; - while( !(cfg instanceof FunNode fun) ) { - if( cfg==null ) return null; - 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 - public static class LoopTree { - public LoopTree _par; - public final LoopNode _head; - public int _depth; - LoopTree(LoopNode head) { _head = head; } - @Override public String toString() { return label(_head); } - public 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( Ary<FunNode> funs, StopNode stop) { - _ltree = stop._ltree = CodeGen.CODE.XCTRL._ltree = new LoopTree((StartNode)this); - Ary<FunNode> work = new Ary<>(FunNode.class); - - BitSet post = new BitSet(); - int pre = 1; - _pre = pre++; - for( FunNode fun : funs ) { - if( fun!=null && !fun.isDead() && !fun._type.isHigh() ) { - work.push(fun); - if(fun.in(1) instanceof StartCUNode start && start._pre==0 ) { - start._pre = pre++; - start._ltree = start.cfg(1)._ltree = _ltree; - post.set(start._nid); - } - }; - } - - for( int i=0; i<work._len; i++ ) - pre = work.at(i)._bltWalk(pre,work.at(i),stop, post, work); - } - - int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post, Ary<FunNode> work ) { - // 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 ) { - if( skip( usecfg ) ) { - if( usecfg._pre==0 && usecfg instanceof FunNode fun2 ) work.add( fun2 ); - } else - pre = usecfg._bltWalk( pre, use instanceof FunNode fuse ? fuse : fun, stop, post, work ); - } - - // 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; - } - - public 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 static String label( CFGNode target ) { - return switch(target) { - case StartNode start -> start.label(); - case FunNode fun -> fun.label(); - case LoopNode loop -> "LOOP"+target._nid; - default -> "L"+target._nid; - }; - } - -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/CLInitNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/CLInitNode.java deleted file mode 100644 index 0a0de3cf..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/CLInitNode.java +++ /dev/null @@ -1,165 +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.CompUnit; -import com.seaofnodes.simple.type.*; -import java.util.*; - -// Track class initializers - the code just after this point expects the named -// class to be initialized. If this code path dies, then the dependency dies -// also - dead code does not force class init. -// -public class CLInitNode extends CFGNode implements MultiNode { - public Parser.Lexer _loc; // For error reporting - - // Inputs: control, whole memory, class pointer, loaded $init flag - // Results: control, whole memory, known-true flag alias memory. - // For field references, the ptr might be the dst clz ptr or not. - public CLInitNode(Parser.Lexer loc, Node ctrl, Node mem, Node ptr, Node ready) { - super(ctrl,mem,ptr,ready); - _loc = loc; - } - - - public CLInitNode(CLInitNode init) { - super(init); - _loc = init._loc; - } - - public Node mem() { return in(1); } - public Node ptr() { return in(2); } - public Node ready() { return in(3); } - - // The field/alias can be unresolved until forward references are loaded. - public Field flag() { - return ptr()._type instanceof TypeMemPtr tmp && Parser.startsClzPrefix(tmp._obj._name) - ? tmp._obj.field("$init") : null; - } - @Override public String label() { return "CLInit"; } - - // Best effort dst compunit, based on ptr - or null - public CompUnit cu() { - if( !(ptr()._type instanceof TypeMemPtr ptr && Parser.startsClzPrefix(ptr._obj._name)) ) - return null; - // Convert "class:sys.ary" into "sys/ary" and lookup CompUnit - String fname = ptr._obj._name.substring(6).replace(".","/"); - return CodeGen.CODE._compunits.get(fname); - } - - @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - FunNode fun = fun(); - CompUnit cu = cu(); - return p.p("CLInit ").p(fun._name).p(" -> ").p(cu==null ? "---" : cu._cname); - } - - // Block head is Start, Region, CProj, but not e.g. If, Return, Stop - public boolean blockHead() { return true; } - - private boolean instance() { - return ptr()._type==Type.NIL || - (ptr()._type instanceof TypeMemPtr tmp && tmp._obj!=TypeStruct.BOT && - !tmp._obj.isHigh() && !tmp._obj._open && !tmp._obj._fref && - !Parser.startsClzPrefix(tmp._obj._name)); - } - - public boolean bypass() { return instance() || ready()._type==TypeInteger.TRUE; } - - @Override public Type compute() { - if( in(0)._type.isHigh() || ptr()._type.isHigh() ) - return TypeTuple.make(Type.XCONTROL,TypeMem.TOP,TypeMem.TOP); - if( instance() ) - return TypeTuple.make(in(0)._type,mem()._type,mem()._type); - Field flag = flag(); - Type slice = flag==null ? TypeMem.BOT : TypeMem.make(flag._alias,TypeInteger.TRUE); - // The initializer may write arbitrary public memory. Only its flag - // has a guaranteed value on the normal continuation. - Type bulk = ready()._type.isHigh() ? TypeMem.TOP : - ready()._type==TypeInteger.TRUE ? mem()._type : TypeMem.BOT; - return TypeTuple.make(in(0)._type,bulk,slice); - } - - @Override public Node pcopy(int idx, Node dep) { - dep.addDep(ptr()); - dep.addDep(ready()); - if( instance() ) return idx==0 ? in(0) : mem(); - if( ready()._type!=TypeInteger.TRUE ) return null; - if( idx==0 ) return in(0); - if( idx==1 ) return mem(); - // Already established on the incoming path; this is a memory fact, - // not a new store to the class object. - return CodeGen.CODE.con(((TypeTuple)_type)._types[2]); - } - - @Override public Node idealize() { - if( CodeGen.CODE._phase!=CodeGen.Phase.Lower || bypass() || in(0)._type.isHigh() ) return null; - if( CodeGen.CODE._midAssert ) return this; - // Until entry arguments become arrays, ordinary initializers receive zero. - return expand(CodeGen.CODE.con(TypeInteger.ZERO)); - } - - // Called only after cycle/type checking. The initialized arm skips the - // call; both arms rejoin with whole memory and a known-true flag slice. - private Node expand(Node arg) { - FunNode target = CodeGen.CODE.link(cu()); - assert target!=null && !target.isDead(); - keep(); - Node ctrl = in(0); - Node memory = mem(); - - // A child body may assume its parent has finished. Establish that - // requirement first, then test the child's flag in the resulting memory. - CompUnit parent = cu()._par; - if( parent!=null ) { - Node ptr = CodeGen.CODE.con(TypeMemPtr.make((byte)2,parent._clz,true)); - Field flag = parent._clz.field("$init"); - Node off = CodeGen.CODE.con(parent._clz.offset(parent._clz.find("$init"))); - Node parentReady = CodeGen.CODE.add(new LoadNode(_loc,"$init",flag._alias,ctrl,memory,ptr,off).init()); - CLInitNode init = CodeGen.CODE.add(new CLInitNode(_loc,ctrl,memory,ptr,parentReady).init()); - ctrl = CodeGen.CODE.add(new CProjNode(init,0,ScopeNode.CTRL).init()); - memory = CodeGen.CODE.add(new ProjNode(init,1,ScopeNode.MEM0).init()); - } - - Node childOff = CodeGen.CODE.con(cu()._clz.offset(cu()._clz.find("$init"))); - // Read the child's flag after establishing its parent's memory effects. - Node ready = CodeGen.CODE.add(new LoadNode(_loc,"$init",flag()._alias,ctrl,memory,ptr(),childOff).init()); - - IfNode iff = CodeGen.CODE.add(new IfNode(ctrl,ready).init()); - Node done = CodeGen.CODE.add(new CProjNode(iff,0,"True").init()); - Node cold = CodeGen.CODE.add(new CProjNode(iff,1,"False").init()); - - Node fptr = new FunPtrNode(target.sig(),CodeGen.CODE._start,target.ret()).peephole(); - CallNode call = CodeGen.CODE.add(new CallNode(_loc,cold,memory,ptr(),arg,fptr).init()); - CallEndNode end = new CallEndNode(call,TypeRPC.constant(CodeGen.CODE.rpc(_loc._cu._cname))); - // This is a lowered call, after interprocedural analysis. Do not add - // new parameter flows to a body already optimized by SCCP. - CodeGen.CODE.add(end.init()); - Node called = CodeGen.CODE.add(new CProjNode(end,0,ScopeNode.CTRL).init()); - Node after = CodeGen.CODE.add(new ProjNode(end,1,ScopeNode.MEM0).init()); - RegionNode region = CodeGen.CODE.add(new RegionNode(_loc,null,done,called).init()); - Node merged = CodeGen.CODE.add(new BulkMemPhiNode(ScopeNode.MEM0,region,memory,after).init()); - - // Replace the individual outputs; no tuple-pack node or second - // representation of CLInit is needed while the projections collapse. - for( Node use : _outputs.asAry() ) { - if( use==null ) continue; // Our temporary keep. - assert use instanceof Proj; - int idx = ((Proj)use).idx(); - use.subsume(idx==0 ? region : idx==1 ? merged : CodeGen.CODE.con(((TypeTuple)_type)._types[2])); - } - CodeGen.CODE.invalidateIDepthCaches(); - return unkeep(); // Iter removes this now-unused node. - } - - // Resolve a precise flag slice even when the parser did not yet know the - // class/alias and could only install the whole-memory projection. - static Node slice(Node mem, int alias, Node dep) { - if( !(mem instanceof ProjNode proj && proj._idx==1 && proj.in(0) instanceof CLInitNode init) ) - return mem; - dep.addDep(init.ptr()); - Field flag = init.flag(); - if( flag==null || flag._alias!=alias ) return mem; - return new ProjNode(init,2,Parser.memName(alias)).peephole(); - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/CProjNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/CProjNode.java deleted file mode 100644 index 2bd48d92..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/CProjNode.java +++ /dev/null @@ -1,73 +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.TypeTuple; - -import java.util.HashMap; -import java.util.IdentityHashMap; - -public class CProjNode extends CFGNode implements Proj { - - // 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 CodeGen.CODE.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 && iff.pred() != null && 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,this); - } - - // 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; } - - @Override public int idx() { return _idx; } - -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/CallEndMach.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/CallEndMach.java deleted file mode 100644 index 05707132..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/CallEndMach.java +++ /dev/null @@ -1,38 +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 = (call().external() ? code._cCallerSave : 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 = call().external() - ? code._mach.maxArgSlot(call().tfp(),code._cCallingConv) - : 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-modules/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/CallEndNode.java deleted file mode 100644 index 4422265f..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/CallEndNode.java +++ /dev/null @@ -1,321 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.type.*; - -import java.util.HashMap; -import java.util.IdentityHashMap; - -/** - * CallEnd - */ -public class CallEndNode extends CFGNode implements MultiNode { - - // When set true, this Call/CallEnd/Fun/Return is being trivially inlined - boolean _folding; - // +2: must clone, so await all cleanup but OK - // +1: trivial now, so can inline without cloning and without waiting for cleanup - // 0: take a look, - // -1: somethings wrong, but might improve - // -2: never and stop asking, - private byte _inline; - public final TypeRPC _rpc; - - public CallEndNode(CallNode call, TypeRPC rpc) { - super(new Node[]{call}); - _rpc = rpc; - } - public CallEndNode(CallEndNode cend) { super(cend); _rpc = cend._rpc; } - public CallEndNode(TypeRPC rpc) { - super(new Node[0]); - _rpc = TypeRPC.constant(CodeGen.CODE._rpcs.next(rpc.rpc())); - } - private CallEndNode(int nIns, TypeRPC rpc) { - super(new Node[nIns]); - _rpc = rpc; - } - - - @Override public boolean blockHead() { return true; } - - public CallNode call() { return (CallNode)in(0); } - public boolean folding() { return _folding; } - - @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.STATE.dual(); - // Grab the TFP and use the functions declared return type. - // If the call.fptr() is a FRef, return will be BOTTOM. - Type ftype = addDep(call.fptr())._type; - if( !(ftype instanceof TypeFunPtr tfp) ) - return ftype.isHigh() ? TypeTuple.STATE.dual() : TypeTuple.STATE; - // Mid-fold, just take the one single callers' return type - if( _folding ) { - TypeTuple tt = (TypeTuple)in(1)._type; - return tt.makeFrom(2,tt.ret()); - } - - // Here, if I can figure out I've linked *all* callers, then I can meet - // across the linked returns and join with the function return type. - - // External functions have an exact FIDX but deliberately no local - // FunNode/Return edge. They conservatively crush public memory and - // produce their declared return type. A missing non-external target - // is still an unresolved/error call. - int externs = 0; - // The signature bounds widening (e.g. an i32 result stays i32). - // Use the callee signature, not FunPtrNode's live recursive range. - Type bound = tfp.ret(); - if( !XInt.isHigh(tfp.fidxs()) ) { - bound = Type.TOP; - for( int fidx = XInt.next(tfp.fidxs(),0); fidx >= 0; fidx = XInt.next(tfp.fidxs(),fidx) ) { - if( CodeGen.CODE.externFunc(fidx)!=null ) - externs++; - FunNode fun = CodeGen.CODE._link(fidx); - bound = bound.meet(fun==null ? tfp.ret() : addDep(fun).sig().ret()); - } - } - boolean hasExtern = externs != 0; - - // If we have an error-call, e.g. wrong args, then we never link. - // Pre-Opto this looks like a missing target fcn, and we assume it will - // appear later - meanwhile, we use a conservative approx of memory - // effects. - if( (nIns()-1)+externs < tfp.nfcns() ) { - // If during Opto, assume call won't be called and thus won't return anything. - // Lowered calls deliberately have no links into optimized bodies. - boolean opto = CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal() && - CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Lower.ordinal(); - Type tmem = opto ? TypeMem.TOP : TypeMem.BOT; - Type ret = opto ? Type.TOP : tfp.ret(); - // Use the same widening before and after linking a callee. - return TypeTuple.make(Type.CONTROL,tmem,widenResult(ret,bound)); - } - - // A linked function for every concrete function - TypeTuple state = hasExtern - ? TypeTuple.make(Type.CONTROL,call.exposed(this),tfp.ret()) - : TypeTuple.STATE.dual(); - for( int i=1; i<nIns(); i++ ) - state = (TypeTuple)state.meet(in(i)._type); - // A reachable call whose presently linked exits are all unreachable - // keeps a conservative continuation. In particular, do not let a - // transient no-return result tear down the caller before/during SCCP. - // If the call remains out-of-line, its continuation can conservatively - // contribute class-init dependencies even though it never executes. - if( state.ctl()==Type.XCONTROL ) - return TypeTuple.make(Type.CONTROL,TypeMem.TOP,Type.TOP); - // Join before widening: FunPtrNode follows the live Return type, so - // joining it afterward would undo the widening. - return state.makeFrom(2,widenResult(state.ret().join(tfp.ret()),bound)); - } - - private static Type widenResult(Type ret, Type bound) { - // 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. - if( ret instanceof TypeInteger ti && !ti.isHigh() && !ti.isConstant() ) - ret = ti.same_but_slightly_wider_than().join(bound); - return ret; - } - - @Override - public Node idealize() { - return null; - } - - public byte maybeInline() { - byte inline = inlineCandidate(); - if( CodeGen.CODE._midAssert ) - return inline; - _inline = inline; - if( _inline > 0 ) - doInline(); - - return _inline; - } - - // Check if candidate right now, sets no flags - private byte inlineCandidate() { - if( _inline < -2 ) return -2; // Never - if( _folding ) return -2; // No, and will collapse anyways - if( !(in(0) instanceof CallNode call) ) return -2; // malformed because mid-death - - if( nIns()!=2 ) return -1; // Not exactly 1 link, but can change - - // Have exactly 1 linked function - ReturnNode ret = (ReturnNode)in(1); - FunNode fun = ret.fun(); - - // Preserve initializer boundaries until post-Opto cycle validation. - if( fun.isClz() ) return -2; - - // Heuristic forced inlining turned off via name - if( fun._name != null && - fun._name.endsWith("_noInline") ) - return -2; // Never, stop asking - - // Disallow self-recursive inlining (loop unrolling by another name). - for( int i=1; i < fun.nIns(); i++ ) - if( fun.cfg(i).fun()==fun ) // Check for linked call input inside "fun" - { addDep(fun); return -1; } // No, but can lose the recursive edge - - // Things that might improve, but not now a candidate - Node fptr = call.fptr(); - // Constant Function is being called, and its not-null - if( !(fptr._type instanceof TypeFunPtr tfp && tfp.notNull() && tfp.isConstant()) ) - { addDep(fptr); return -1; } - - // Arguments are correct - if( call.nargs()!=tfp.nargs() || !goodArgs(call,tfp) ) - { addDep(fptr); return -1; } - - // Encouraged inlining because small size and constructor. - int maxSize = fun.inlineLimit(); - // A large parse can leave only a tiny initializer. Recognize that - // and await cleanup to shrink the body. - if( fun._approxUIDs >= maxSize ) { - if( !smallBody(fun) ) return -1; - } else if( fun.body().cardinality() >= maxSize ) // Expensive size check - return -1; // Try later (if cleanup makes the real body small enough) - - // Can be cloned but not trivial - if( fun.nIns() > 2 ) { addDep(fun); return 2; } - - // A trivial candidate right now - return 1; - } - - // Bounded, conservative recognition of straight-line 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. - // Watch only this small prefix, including an unsupported node that may fold. - private static final Node[] TINYBODY = new Node[15]; // Tiny worklist - private boolean smallBody(FunNode fun) { - ReturnNode ret = fun.ret(); - for( Node in : ret._inputs ) - if( in!=null ) addDep(in); - if( ret.ctrl()!=fun ) return false; - 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 false; - if( n!=fun && !(n instanceof ParmNode) && - !(n instanceof StoreNode) && !(n instanceof MemMergeNode) ) - return false; // Something unexpected - if( n.nOuts()>TINYBODY.length ) return false; // Too big - for( Node use : n._outputs ) { - if( use==null ) continue; - int j=0; - while( j<len && TINYBODY[j]!=use ) j++; - if( j<len ) continue; // Already in TINYBODY - if( len==TINYBODY.length ) { addDep(use); return false; } // Too big - TINYBODY[len++] = use; // Add to worklist - } - } - return true; - } - - public void doInline() { - assert _inline > 0; // Only called when possible to inline - - ReturnNode ret = (ReturnNode)in(1); - FunNode fun = ret.fun(); - CallNode call = call(); - assert fun.sig().isa(call.fptr()._type); - - // Clone first - if( _inline==2 ) { - assert !CodeGen.CODE._midAssert; // Triggered inlining - // Remove the existing function linkage - call.unlink_all(); - // Clone the function body - FunNode fun2 = fun.copyBody(); - // Call uses the unique new function - Node fptr2 = new FunPtrNode(fun2.sig(),CodeGen.CODE._start,fun2.ret()).peephole(); - call.setDef(call.nIns()-1,fptr2); - // Link to the new function - call.link(fun2); - fun = fun2; - } - - // Trivial inlining: call site calls a single function; single function - // is only called by this call site. - doTrivialInlining(fun); - } - - boolean goodArgs( CallNode call, TypeFunPtr tfp ) { - for( int i=0; i<tfp.nargs(); i++ ) { - Node arg = call.arg(i+2); - // The graph's function-pointer type can still contain open forward - // references here. Compare against its recursively upgraded form, - // so deep mutability requirements participate before inlining. - Type formal = tfp.arg(i).upgradeType(Parser.TYPES); - if( !arg._type.isa(formal) || !arg._type.accessISA(formal) ) - { addDep(arg); return false; } - } - return true; - } - - - // Do trivial inlining. Inlining does not need to clone code, merely - // triggers folding the Call/Fun and Return/CallEnd away. - private boolean doTrivialInlining( FunNode fun ) { - assert !CodeGen.CODE._midAssert; // Triggered inlining - // Trivial inline: rewrite - _folding = true; - // Rewrite Fun so the normal RegionNode ideal collapses - fun._folding = true; - fun.setDef(1,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(); - // More peeps - CodeGen.CODE.add(fun); - CodeGen.CODE.add(this); - CodeGen.CODE.addAll(_outputs); - CodeGen.CODE.addAll(_inputs); - return true; // Always return true - } - - - @Override public Node pcopy(int idx, Node dep) { - return _folding ? in(1).in(idx) : null; - } - - @Override public Node copy() { - return _folding ? super.copy() : new CallEndNode(_rpc); - } - -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/CallNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/CallNode.java deleted file mode 100644 index f791225a..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/CallNode.java +++ /dev/null @@ -1,220 +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.type.Type; -import com.seaofnodes.simple.type.TypeFunPtr; -import com.seaofnodes.simple.type.TypeMem; -import com.seaofnodes.simple.type.TypeRPC; -import com.seaofnodes.simple.type.XInt; -import com.seaofnodes.simple.util.Utils; -import java.util.HashMap; -import java.util.IdentityHashMap; - -/** - * 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; CodeGen.CODE._calls.add(this); } - public CallNode(CallNode call) { super(call); _loc = call._loc; } - - - @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() { - if( !(fptr()._type instanceof TypeFunPtr tfp) ) return false; - if( XInt.isHigh(tfp.fidxs()) ) return true; - for( int fidx=XInt.next(tfp.fidxs(),-1); fidx>=0; fidx=XInt.next(tfp.fidxs(),fidx) ) - if( CodeGen.CODE.externFunc(fidx)!=null ) return true; - return false; - } - - // As in the escape chapter, native code may retain an argument even if - // this path never returns. StopCU reads this independently of CallEnd. - TypeMem exposed(Node dep) { - dep.addDepForwards(this); - dep.addDepForwards(fptr()); - if( _type!=Type.CONTROL || !external() ) return TypeMem.START; - Type input=dep.addDepForwards(mem())._type; - TypeMem escaped=input instanceof TypeMem m ? m : input.isHigh() ? TypeMem.TOP : TypeMem.BOT; - for( int i=2; i<nIns()-1; i++ ) escaped=escaped.escapes(dep.addDepForwards(arg(i))._type); - return TypeMem.make(1,Type.BOTTOM,false,false,false,escaped._escFs,escaped._escAs); - } - - - // 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() { - // Lowering may introduce calls after the call graph has been analyzed. - // These use the callee interface without reopening its parameter flow. - if( CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Lower.ordinal() ) return null; - 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 - int[] fidxs = tfp.fidxs(); - if( !XInt.isHigh(fidxs) ) { - // Walk the bits and link - for( int fidx = XInt.next(fidxs,0); fidx > 0; fidx = XInt.next(fidxs,fidx) ) { - FunNode fun = CodeGen.CODE.link(fidx); - 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); - CodeGen.CODE.add(fun).addDef(this); - for( Node use : fun._outputs ) - if( use instanceof ParmNode parm ) { - Node def = parm._idx==0 ? ConstantNode.seed(cend()._rpc).peephole() : arg(parm._idx); - CodeGen.CODE.add(parm).addDef(def); - // Linking can happen during post-SCCP inlining. Keep an RPC - // Parm's cached finite set synchronized with its newly added - // input instead of waiting for the next worklist visit. - if( parm._idx==0 && parm._type instanceof TypeRPC ) - parm._type = parm._type.meet(def._type); - } - // Call end points to function return - CodeGen.CODE.add(cend()).addDef(fun.ret()); - return this; - } - - // Unlink a single function - public void unlink( FunNode fun, int path ) { - assert linked(fun); - for( Node use : fun._outputs ) - if( use instanceof ParmNode ) - use.delDef(path); - fun.delDef(path); - CodeGen.CODE.add(fun); - CallEndNode cend = cend(); - ReturnNode ret = fun.ret(); - if( cend != null ) { - ret.keep(); - cend.delDef(cend._inputs.find(ret)); - CodeGen.CODE.add(cend); - ret.unkeep(); - } - assert !linked(fun); - } - - - // Unlink all linked functions - public void unlink_all() { - for( int i=0; i<_outputs._len; i++ ) - if( out(i) instanceof FunNode fun ) { - int path = fun._inputs.find(this); - unlink(fun,path); - i--; - } - } - - @Override - public Parser.ParseException err() { - if( fptr()._type == Type.BOTTOM ) - return null; // Wrong, but failing earlier - if( !(fptr()._type instanceof TypeFunPtr tfp) ) - return Parser.error("Expected a function, but found " + fptr()._type, _loc); - 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)) ) { - // Constructors return private memory and carry hidden self and - // selfMem arguments ahead of the source-level arguments. - int argno = i - (tfp.ret() instanceof TypeMem ? 2 : 0); - return Parser.error( "Argument #" + argno + " isa " + arg(i+2)._type + ", but must be a " + tfp.arg(i), _loc ); - } - - if( tfp.isHigh() ) - throw Utils.TODO(); // Infinite unknown TFPs? Should be fairly precise CG - - return null; - } - -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java deleted file mode 100644 index 0afc5827..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java deleted file mode 100644 index 0b8053e2..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java +++ /dev/null @@ -1,78 +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.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.type.TypeStruct; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import java.util.HashMap; - -/** - * 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 TypeStruct _ts; // Struct holding field - public final String _fname; // Field name - public ConFldOffNode( TypeStruct ts, String fname ) { super(TypeInteger.BOT); _ts = ts; _fname = fname; } - - - @Override public String label() { - return _fname == " len" ? "sizeof("+_ts._name+")" : "#"+_fname; - } - - @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - return p.p(label()); - } - - @Override public Node idealize() { - // Some array offsets are known early - if( _ts.isAry() ) { - // Length always at offset zero - if( _fname==" len" ) - return CodeGen.CODE.ZERO; - // The shift depends on the element - if( _fname=="<<" ) - return Node.con(_ts.aryScale()); - // The base depends on the layout (alignment for large elements may - // involve padding) and MAY depend on the length if I go for - // smaller array lengths. - } - return null; - } - - // Convert field offset to an integer - public Node asOffset() { - int fldx = _fname==" len" ? _ts._fields.length : _ts.find(_fname); - if( fldx == -1 ) return null; - // " len" selects the terminal size slot, not a Field. - if( fldx < _ts._fields.length && _ts._fields[fldx]._extern ) - return new ExternOffsetNode(_fname).peephole(); - return ConstantNode.make(TypeInteger.constant(_ts.offset(fldx))).peephole(); - } - - // Upgrade the internal type - @Override boolean _upgradeType( HashMap<String,Type> TYPES) { - boolean progress = super._upgradeType(TYPES); - TypeStruct ts = (TypeStruct)_ts.upgradeType(TYPES); - if( ts == _ts ) return progress; - _ts = ts; - return true; - } - - @Override public boolean eq(Node n) { - ConFldOffNode off = (ConFldOffNode)n; // Invariant - return _ts._name==off._ts._name && _fname.equals(off._fname) && super.eq(n); - } - @Override int hash() { return super.hash() * _ts._name.hashCode() * _fname.hashCode(); } - - @Override public Parser.ParseException err() { - return _ts._fref ? Parser.error("Unknown struct type '"+_ts._name+"'",null) : null; - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ConstantNode.java deleted file mode 100644 index eabf2639..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ /dev/null @@ -1,86 +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 java.util.HashMap; -import java.util.IdentityHashMap; - -/** - * 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 TypeNode { - public ConstantNode( Type type ) { - this(type,false); - } - protected ConstantNode( Type type, boolean raw ) { - super(type,new Node[]{CodeGen.CODE._start}); - assert raw || !(type instanceof TypeFunPtr tfp && tfp.isConstant()) : - "Unique function identities require a Return-linked FunPtrNode"; - _type = type; - } - public ConstantNode( ConstantNode con ) { super(con); } - - - // Semantic constant construction through Lower. A unique internal - // function identity carries a lifetime edge to the function's Return. - public static Node make( Type type ) { - if( type==Type. CONTROL ) return new CtrlNode(); - if( type==Type.XCONTROL ) return new XCtrlNode(); - if( CodeGen.CODE._phase != null && - CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Lower.ordinal() ) - return raw(type); - if( type instanceof TypeFunPtr tfp && tfp.isConstant() ) { - FunNode fun = CodeGen.CODE._link(tfp); - if( fun != null ) - return new FunPtrNode(tfp,CodeGen.CODE._start,fun.ret()).init(); - // TODO: A singleton FIDX can also emerge after merging and - // guarding several function pointers. Preserve that identity - // even if its internal Return is no longer available. - return raw(type); - } - return new ConstantNode(type); - } - - // Exact construction for deserialization and graph-incomplete machine - // selection. No semantic classification, compute, idealize, or GVN. - public static ConstantNode raw( Type type ) { return new ConstantNode(type,true); } - public static ConstantNode raw( ConstantNode con ) { return new ConstantNode(con); } - - // Analysis input created from whole cloth, rather than a first-class - // runtime value. In particular, a constructor call descriptor does not - // own the constructor function; the class FunPtr does. - public static ConstantNode seed( Type type ) { return new ConstantNode(type,true); } - - @Override protected String repeatName() { - return _con==null || _con.toString().length()<=32 ? null : uniqueName(); - } - - @Override public String label() { return "Con"; } - @Override public Node copy() { return raw(this); } - - - @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; } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ConvertNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ConvertNode.java deleted file mode 100644 index cdc890a7..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ConvertNode.java +++ /dev/null @@ -1,101 +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.type.*; - -import java.util.HashMap; - -// Convert a value to an authoritative destination type. The destination is -// known structurally from a declaration; the source family might sharpen later. -public class ConvertNode extends Node { - private Type _dst; - - public ConvertNode(Type dst, Node val) { - super(null,val); - // Folding this conversion would replace the Return-linked FunPtrNode - // with an unhooked ConstantNode. - assert !(dst instanceof TypeFunPtr && val instanceof FunPtrNode); - _dst = dst; - } - - public Type dst() { return _dst; } - public Node val() { return in(1); } - - @Override public String label() { return "Convert_"+_dst.str(); } - @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - return p.p(label()).open().n(val()).close(); - } - - // The declaration fixes the result family during pessimistic parsing. - // From SCCP onward, preserve dead inputs and stronger facts in that family. - @Override public Type compute() { - Type src = val()._type; - if( CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal() ) { - if( src.isHigh() ) return Type.TOP; - if( (src.isa(_dst) && src.accessISA(_dst)) ) return src; - } - return _dst; - } - - @Override public Node idealize() { - Type src = val()._type; - if( src==Type.BOTTOM || src==Type.TOP ) return null; - if( (src.isa(_dst) && src.accessISA(_dst)) ) return val(); - - // Managed arrays are represented by an object pointer, while C wants - // the address of the first element. The representation cast itself is - // zero-code; the Add skips the array header. - if( _dst==TypeInteger.BOT && src instanceof TypeMemPtr tmp && tmp._obj.isAry() ) - return new AddNode(new PtrToIntNode(val()).peephole(),Parser.off(tmp._obj,"[]")); - - // Integer (and nil-as-zero) to floating point. - if( (src instanceof TypeInteger || src==Type.NIL) && _dst instanceof TypeFloat ) - return new ToFloatNode(val()); - - // Convert to i64 first, then apply the destination's integer width. - if( src instanceof TypeFloat && _dst instanceof TypeInteger ) - return new ConvertNode(_dst,new ToIntegerNode(val()).peephole()); - - // Narrow integers produce the declared sign/zero extension. - if( src instanceof TypeInteger && _dst instanceof TypeInteger dst ) { - if( dst._min==0 ) - return new AndNode(null,val(),con(dst._max)); - int shift = Long.numberOfLeadingZeros(dst._max)-1; - Node shf = con(shift); - if( shf._type==TypeInteger.ZERO ) return val(); - return new SarNode(null,new ShlNode(null,val(),shf.keep()).peephole(),shf.unkeep()); - } - - // Narrow f64 to f32. - if( src instanceof TypeFloat && _dst instanceof TypeFloat ) - return new RoundF32Node(val()); - - // Wait for more source information, or report an error after Opto. - return null; - } - - @Override boolean _upgradeType(HashMap<String,Type> TYPES) { - Type dst = _dst.upgradeType(TYPES); - if( dst==_dst ) return false; - unlock(); - _dst = dst; - return true; - } - - @Override public Parser.ParseException err() { - if( val()._type==Type.BOTTOM || - val()._type instanceof TypeNil src && src.nullable() && _dst instanceof TypeNil dst && dst.notNull() ) { - Node out = out(0); - if( out instanceof PhiNode phi ) out = phi.out(0); - if( out instanceof StoreNode st ) - return Parser.error( "'" + ((TypeMemPtr)st.ptr()._type)._obj._name + "' is not fully initialized, field '" + st._name + "' is only partially set in the constructor", null ); - } - return Parser.error("Type "+val()._type.str()+" is not of declared type "+_dst.str(),null); - } - - @Override public boolean eq(Node n) { return _dst==((ConvertNode)n)._dst; } - @Override int hash() { return _dst.hashCode(); } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/CtrlNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/CtrlNode.java deleted file mode 100644 index 813e6fcb..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/CtrlNode.java +++ /dev/null @@ -1,17 +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-modules/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/DivNode.java deleted file mode 100644 index 0db2b7a2..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.type.TypeFloat; -import java.util.BitSet; - -public class DivNode extends ArithNode { - @Override boolean allowFloat() { return true; } - public DivNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - public DivNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs, mode); } - - - @Override public String op() { return _mode==1 ? "//" : "/"; } - - @Override long doOp( long x, long y ) { return y==0 ? 0 : x / y; } - @Override double doOp(double x, double y ) { return 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.isa(compute()) ) - return in(1); - - // Div of constant - if( _mode==2 ) { - if( in(2)._type == TypeFloat.constant(1.) && in(1)._type.isa(compute()) ) - return in(1); - if( in(2)._type instanceof TypeFloat f && f.isConstant() ) - return new MulNode(in(1),ConstantNode.make(TypeFloat.constant(1.0/f.value())).peephole(),_mode); - } - - return super.idealize(); - } - - @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs,_mode); } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/EscapeNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/EscapeNode.java deleted file mode 100644 index f7518184..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/EscapeNode.java +++ /dev/null @@ -1,109 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.*; -import java.util.HashMap; -import java.util.IdentityHashMap; - -/** - * Merge public and private aliases. - * 0 - control - * 1 - private pointer - * 2 - private alias - * 3 - public alias - */ -public class EscapeNode extends TypeNode { - - public EscapeNode(Field fld, Node self, Node priv, Node pub ) { - super(fld,null,self,priv,pub); - } - public EscapeNode(EscapeNode esc ) { - super(esc); // Copy constructor - // Except we do not want/need self ptr past Opto. - // *NOTE* the weird direct stomp; this is correct because this - // constructor does NOT set the backedges, because it is used to - // copy-construct an entire graph and ALL edges will get rewritten. - _inputs.set(1,null); - _con = esc._con; - } - - // Pointer to some private (unescaped) memory from a NewNode - public Node self() { return in(1); } - public Node priv() { return in(2); } - public Node pub () { return in(3); } - public Field fld() { return (Field)_con; } - - - @Override - protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - p.p("Esc#").p(fld()._alias).p(" {"); - if( self()==null ) p.p("---"); - else p.n(self()).p(","); - p.n(priv()).p("}, "); - p.n(pub ()); - return p; - } - - @Override - public Type compute() { - if( priv()._type.isHigh() ) return TypeMem.TOP; - if( pub ()._type.isHigh() ) return TypeMem.TOP; - TypeMem mpriv = (TypeMem)priv()._type; - TypeMem mpub = (TypeMem)pub ()._type; - // Private memory comes from an allocation or other non-aliased source. - // Public memory comes from anywhere, might be bulk memory with a very weak mem type. - Field fld = fld(); - assert mpriv._alias==1 || mpriv._alias==fld._alias; - assert mpub ._alias==1 || mpub ._alias==fld._alias; - Type tpriv = mpriv._t instanceof TypeStruct ts && ts.findAlias(fld._alias)!=-1 - ? ts.at(ts.findAlias(fld._alias))._t - : mpriv._t; - assert !(mpub._t instanceof TypeStruct); // Never expect this, but if it starts to happen, need to optimize - Type tpub = mpub._t; - - // t is meet of pub & priv. Pub memory can be very weak (e.g. alias 1) - // and we're no worse than the declared field type - Type t = tpriv.meet(tpub).join(fld._t); - int[] fidxes = XInt.meet(mpriv._escFs, mpub._escFs); - int[] aliases = XInt.meet(mpriv._escAs, mpub._escAs); - return TypeMem.make(fld._alias, t, false, false, fld._final, fidxes, aliases).escapesFrom(t); - } - - @Override public Node idealize() { - // Sharpen private memory input - if( priv() instanceof MemMergeNode mmm ) { - setDef(2,mmm.alias(fld()._alias)); - return this; - } - // Sharpen public memory input - if( pub() instanceof MemMergeNode mmm ) { - setDef(3,mmm.alias(fld()._alias)); - return this; - } - // New allocation is dead - if( self()._type.isHigh() ) - if( pub()._type.isa(_type) ) return pub(); - else addDep(pub()); - return null; - } - -// Upgrade the internal type -@Override boolean _upgradeType( HashMap<String,Type> TYPES) { - Type t = _con.upgradeType(TYPES); - if( t == _con ) return false; - unlock(); // Unlock before changing _con - _con = t; - return true; -} - - @Override public boolean eq(Node n) { - if( fld()._alias!=((EscapeNode)n).fld()._alias ) - return false; - assert _con == ((EscapeNode)n)._con; - return true; - } - - @Override int hash() { return _con.hashCode(); } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ExternNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ExternNode.java deleted file mode 100644 index 2d1dc29f..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ExternNode.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.util.SB; - -import java.util.BitSet; -import java.util.HashMap; -import java.util.IdentityHashMap; - -/** - A constant with external linkage. - */ - -public class ExternNode extends ConstantNode { - public final String _extern; - // An exact external function identity has no local FunNode/Return to own. - // Its lifetime and eventual relocation are carried by the extern symbol - // and CodeGen's FIDX-to-extern mapping instead. - public ExternNode(Type t, String ex) { super(t,true); _extern = ex; } - - - @Override public String label() { return "#"+_con+":"+_extern; } - - @Override public boolean eq(Node n) { return this==n; } - - -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ExternOffsetNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ExternOffsetNode.java deleted file mode 100644 index 23ed5468..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ExternOffsetNode.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import java.util.HashMap; -import java.util.IdentityHashMap; - -// A field address supplied by the C linker, not a displacement from the base. -// The class base remains available for ideal type/alias analysis. Instruction -// selection replaces it with this symbol's address and a zero displacement. -public class ExternOffsetNode extends ExternNode { - public ExternOffsetNode(String name) { super(TypeInteger.BOT,name); } - - @Override public Node copy() { return new ExternOffsetNode(_extern); } - -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/FRefNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/FRefNode.java deleted file mode 100644 index 644f1434..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/FRefNode.java +++ /dev/null @@ -1,50 +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. - - * FRefs can be external references, to other Simple defined values in ELF - * files, or external FFI/C libs. If external, a file system lookup needs to - * find it in another file, and the name (one time) change to match the found - * location. - * - * Example, a ambiguous name "foo" is looked up externally; it might resolve - * to "A.B.foo" or "libc.foo" or the like; the name is changed to match. - */ -public class FRefNode extends ConstantNode { - public static final Type FREF_TYPE = Type.BOTTOM; - public String _name; - public final Parser.Lexer _loc; // Source location - - public FRefNode( String name, Parser.Lexer loc ) { this(name,loc,FREF_TYPE); } - public FRefNode( String name, Parser.Lexer loc, Type type ) { - super(type); - _name = name; - _loc = loc; - } - - @Override public String label() { return "FRef"+_name; } - - - @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - return p.p("FRef_").p(_name); - } - - @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 '"+_name+"'",_loc); } - - @Override public boolean eq(Node n) { return this==n; } - @Override int hash() { return _name.hashCode(); } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/FldOffNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/FldOffNode.java deleted file mode 100644 index 535241a6..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/FldOffNode.java +++ /dev/null @@ -1,54 +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.type.*; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import java.util.HashMap; - -/** - * 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 FldOffNode extends TypeNode { - public final String _fname; // Field name - public final Parser.Lexer _loc; // - public FldOffNode( Node base, String fname, Parser.Lexer loc ) { super(TypeInteger.BOT,base); _fname = fname; _loc=loc; } - - - @Override public String label() { - return _fname == " len" ? "sizeof()" : "#"+_fname; - } - - @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - return p.p(label()); - } - - @Override public Type compute() { - return Type.BOTTOM; - } - - @Override public Node idealize() { - // When base() gets typed, this xforms into a ConFldOffNode - if( in(0)._type instanceof TypeMemPtr tmp ) - return new ConFldOffNode(tmp._obj,_fname); - return null; - } - - @Override public boolean eq(Node n) { - FldOffNode off = (FldOffNode)n; // Invariant - return _fname.equals(off._fname) && super.eq(n); - } - @Override int hash() { return super.hash() * _fname.hashCode(); } - - @Override public Parser.ParseException err() { - if( in(0)._type instanceof TypeMemPtr tmp && tmp._obj._fref ) - return Parser.error("Unknown struct type '"+tmp._obj._name+"'",null); - return null; - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/FunNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/FunNode.java deleted file mode 100644 index ad79130e..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/FunNode.java +++ /dev/null @@ -1,463 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import java.util.Arrays; -import java.util.BitSet; -import java.util.HashMap; -import java.util.IdentityHashMap; -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 - public boolean _folding; - public boolean folding() { return _folding; } - - private TypeFunPtr _sig; // Initial signature - private ReturnNode _ret; // Return pointer - - public String _name; // Debug name - - public int _approxUIDs; // Approximate function size, used as a inlining heuristic - public CompUnit _compunit; // Defined in source file - public final boolean _extern; // External function; no code, name only - - private FunNode( Parser.Lexer loc, Node[] nodes, TypeFunPtr sig, String name, CompUnit compunit, boolean ext ) { - super(loc,nodes); - _name = name; - _sig = sig; - _extern = ext; - _compunit = compunit; - } - public FunNode( Parser.Lexer loc, TypeFunPtr sig, String name, CompUnit compunit, Node... nodes ) { this(loc,nodes,sig,name,compunit,false); } - public FunNode( FunNode fun ) { - super( fun, fun._loc ); - _sig = fun.sig(); - _name = fun._name; - _compunit = fun._compunit; - _extern = fun._extern; - } - - - @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 parm(int idx) { - ParmNode pidx = null; - for( Node n : _outputs ) - if( n instanceof ParmNode parm && parm._idx==idx ) - { assert pidx==null; pidx=parm; } - return pidx; - } - public ParmNode rpc() { return parm(0); } - - // Cannot create the Return and Fun at the same time; one has to be first. - // So setting the return requires a second step. - public void setRet(ReturnNode ret) { _ret=ret; } - public ReturnNode ret() { assert _ret!=null; return _ret; } - - // Signature can improve over time - public TypeFunPtr sig() { return _sig; } - public void setSig( TypeFunPtr sig ) { - assert sig.isa(_sig) || resolvesForwardSelf(sig,_sig); - if( _sig != sig ) { - CODE.add(this); - // Changing the signature can allow more inlining - if( _ret != null ) - for( Node use : ret().outs() ) - if( use instanceof CallEndNode || use instanceof FunPtrNode ) - CODE.add(use); - unlock(); - _sig = sig; - } - } - - // A class initializer starts parsing with an open singleton receiver and - // closes that same receiver after discovering its fields. Function - // arguments are contravariant, so ordinary isa does not describe this - // parser-time forward-reference resolution even though every other part - // of the signature sharpens normally. - private static boolean resolvesForwardSelf(TypeFunPtr sig, TypeFunPtr old) { - if( !(sig.arg(0) instanceof TypeMemPtr now) || - !(old.arg(0) instanceof TypeMemPtr prior) || - now._obj._name != prior._obj._name || - !prior._obj._open || now._obj._open ) - return false; - return sig.makeFrom(old.arg(0),0).isa(old); - } - - @Override boolean _upgradeType( HashMap<String,Type> TYPES) { - TypeFunPtr sig = (TypeFunPtr)_sig.upgradeType(TYPES); - if( sig == _sig ) return false; - unlock(); - _sig = sig; - return true; - } - - public void setName( String name ) { - if( _name==null ) - _name = _compunit==null || _compunit._cname==null || _compunit._cname.indexOf('.')==-1 - ? name - : (_compunit._cname+"."+name).intern(); - } - - @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 there are no unknown callers, the Start input is only a - // fake hook and 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 unknown caller hooks never kill Start. - Node progress = deadPath(unknownCallers()); - if( progress!=null ) { - if( nIns()==2 && in(1) instanceof CallNode call ) - CODE.add(call.cend()); // If no 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() && tt.ret().isa(_sig.ret()) ) { - setSig(_sig.makeFrom(tt.ret())); - return this; - } - - // Attempt to get rid of the unknown caller. - // - Must be past pessimistic Iter, which discovers and links calls - // through class-field loads and other initially imprecise values. - // - FIDX is not in the escape set - // - Not called anywhere else; post Opto called-not-escaped functions need to still be hooked to start - // Opaque CLInit obligations need their bodies even without Call edges. - if( !isClz() && in(1) instanceof StartCUNode start ) { - boolean preOpto = CodeGen.CODE._phase == null || - CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal(); - if( !preOpto && !start.escapedFIDX(_sig.fidx()) && nIns()<=2 && _ret!=null ) { - _compunit._stop.delDef(_compunit._stop._inputs.find(ret())); - return removeDeadPath(1); - } else { - addDep(start); - } - } - - - // 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 - @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 ? cfg(1) : null; - } - - // Always in-progress until we run out of unknown callers - public boolean unknownCallers() { return nIns()>=2 && in(1) instanceof StartNode; } - - public boolean isModInit( ) { - // The one top-level <clinit> with no internal dots: - // "sys.<clinit>" is a module, but "sys.io.<clinit>" is not. - return isClz() && _name.indexOf('.')==_name.lastIndexOf('.'); - } - public boolean isClz ( ) { return isClz (_name); } - public boolean isInit( ) { return isInit(_name); } - public boolean isInstance() { return isInstance(_name); } - public static boolean isClz (String name ) { return name!=null && name.endsWith(".<clinit>"); } - public static boolean isInit(String name ) { return name!=null && name.endsWith("init>"); } - public static boolean isInstance(String name ) { return name!=null && name.endsWith(".<init>"); } - - // Allocation factories and instance initializers carry extra memory boilerplate. - public int inlineLimit() { - return isInstance() || (_name!=null && _name.endsWith(".<new>")) ? 200 : 100; - } - - // Function is public (callable from Start directly). - public boolean isPublic( ) { - // Never true for anonymous functions - if( _name == null ) return false; - // Only public for <clinit> and <init>, all others - // are found via field loads. The <init> Start hook also supplies its - // conservative pre-Opto construction inputs; StopCU separately keeps - // its private-memory return from escaping as a public result. - if( !isInit() ) return false; - // Private class, has to be found via program text, not linker - if( _name.charAt(0)=='_' ) return false; - - // No instances of "...clz1._clz2..." where the class name path - // includes a private class. - if( _name.indexOf("._") != -1 ) return false; - - // Public class <cl/init> - return true; - } - - @Override public boolean inProgress() { - return unknownCallers() && CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal(); - } - - // Build the function body set - public BitSet body() { - // Iter drains ordinary peeps between inlines; body discovery precedes - // this inline's folding, so function boundaries are stable. - assert !_folding; - // Forward control includes paths that never reach Return. - BitSet cfgs = new BitSet(); - walkCFG(this,cfgs); - cfgs.set(ret()._nid); - - // 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(); - // The function owns its Return even when it has no executable exit. - body.set(ret()._nid); - BitSet visit = new BitSet(); - int old; - do { - old = body.cardinality(); - visit.clear(); - walkDown(this, cfgs, body, visit); - } while( old != body.cardinality() ); - return body; - } - - private static void walkCFG(CFGNode n, BitSet cfgs) { - if( cfgs.get(n._nid) ) return; - cfgs.set(n._nid); - if( n instanceof ReturnNode ) return; - for( Node use : n._outputs ) { - if( !(use instanceof CFGNode cfg) || cfg instanceof FunNode ) continue; - if( cfg instanceof RegionNode ? cfg._inputs.find(n)>0 : cfg.in(0)==n ) - walkCFG(cfg,cfgs); - } - } - - private static boolean walkDown( Node n, BitSet cfgs, BitSet body, BitSet visit ) { - if( n==null ) return false; - if( visit.get(n._nid) ) return body.get(n._nid); - visit.set(n._nid); - // Visit self as CFG outside the function - if( n instanceof CFGNode && !cfgs.get(n._nid) ) - return false; - // Pretend the NewNode is a CFG, so its projections stay with it in loops - if( n instanceof NewNode nnn ) cfgs.set(n._nid); - // Visit n.cfg() outside of function - if( n.in(0)!=null && !cfgs.get(n.in(0)._nid) ) - return false; - // Phis inside the function must have their body flag set BEFORE - // recursion walks around a loop and finds them again. - if( n instanceof PhiNode phi && cfgs.get(phi.in(0)._nid) ) - body.set(phi._nid); // Find data cycles - boolean in = n.in(0)!=null || n instanceof CFGNode; - for( Node use : n._outputs ) { - // Will hit a backedge, so 'n' MUST be in the function, and must be - // set before the cyclic walk finds 'n' again. - if( use instanceof PhiNode puse && puse.in(0) instanceof LoopNode loop && puse.in(2)==n ) - body.set(n._nid); // Set before walk - in |= walkDown(use,cfgs,body,visit); - } - if( in ) body.set(n._nid); - return in; - } - - // Clone function body. Give function a new FIDX. - FunNode copyBody() { - // Build the function body BitSet - BitSet body = body(); - assert body.cardinality() < inlineLimit(); - // Walk the body, cloning - IdentityHashMap<Node,Node> map = new IdentityHashMap<>(); - BitSet visit = CodeGen.CODE.visit(); - bodyCopy( visit, body, map, this ); - bodyCopy( visit, body, map, _ret ); // May be disconnected from executable control. - visit.clear(); - bodyEdge( visit, body, map, this ); - bodyEdge( visit, body, map, _ret ); - visit.clear(); - assert map.size()==body.cardinality(); - - // New function/return cross-link each other - FunNode fun2 = (FunNode )map.get(this); - ReturnNode ret2 = (ReturnNode)map.get(_ret); - fun2._ret = ret2; - ret2._fun = fun2; - BitSet body2 = new BitSet(); - for( Node n : map.values() ) - body2.set(n._nid); - - // Remove non-body callers. Body-local call edges were cloned along - // with their CallEnds and must stay linked so their cloned types remain - // monotonic during the post-Opto Iter. - for( int i=1; i<fun2.nIns(); i++ ) - if( !(fun2.in(i) instanceof CallNode call) || !body2.get(call._nid) ) - fun2.removeDeadPath(i--); - - // Linking adds graph edges and worklist entries; identity-map order - // would make inlining depend on the host JVM's object hashes. - Node[] olds = map.keySet().toArray(new Node[0]); - Arrays.sort(olds, (x,y) -> Integer.compare(x._nid,y._nid)); - for( Node old : olds ) - if( old instanceof CallNode oldCall ) - relinkClonedCall(oldCall,(CallNode)map.get(oldCall),map); - - // Flip to a new FIDX to avoid confusion with the old one. - // This is a non-monotonic (sideways) type move, only applicable - // because fun2 is new. The fidx is also a purely local fiction, - // since this body will inline and the fidx goes dead. - fun2._sig = _sig.makeFrom(CodeGen.CODE._fidxs.nextInline()); - return fun2; - } - - private static void relinkClonedCall( CallNode oldCall, CallNode call, IdentityHashMap<Node,Node> map ) { - CallEndNode oldCend = oldCall.cend(); - if( oldCend==null || oldCend._folding ) - return; - for( int i=1; i<oldCend.nIns(); i++ ) { - ReturnNode oldRet = (ReturnNode)oldCend.in(i); - FunNode oldFun = oldRet.fun(); - FunNode fun = (FunNode)map.get(oldFun); - if( fun==null ) fun = oldFun; - if( !call.linked(fun) ) - call.link(fun); - } - CallEndNode cend = call.cend(); - if( cend.nIns() > 1 ) - cend._type = cend.compute(); - } - - // Clone small function - private void bodyCopy( BitSet visit, BitSet body, IdentityHashMap<Node,Node> map, Node n ) { - if( n==null ) return; - if( !body.get(n._nid) ) return; // Not part of the body - if( visit.get(n._nid) ) return; // Been there, done that - visit.set(n._nid); - Node m = n.copy(); - CodeGen.CODE.add(m); - map.put(n,m); - m._type = n._type; - for( Node x : n._outputs ) - bodyCopy(visit,body,map,x); - } - private void bodyEdge( BitSet visit, BitSet body, IdentityHashMap<Node,Node> map, Node n ) { - if( n==null ) return; - if( !body.get(n._nid) ) return; // Not part of the body - if( visit.get(n._nid) ) return; // Been there, done that - visit.set(n._nid); - Node m = map.get(n); - for( Node e : n._inputs ) { - Node x = e; - if( e != null ) { - if( n instanceof CallEndNode cend && e != cend.call() && - (!(e instanceof ReturnNode ret) || map.get(ret.fun()) == null) ) - continue; - x = map.get(e); - if( x == null ) { - if( n instanceof CallEndNode cend && e != cend.call() ) - continue; - x = e; - } - } - m.addDef(x); - } - for( Node x : n._outputs ) - bodyEdge(visit,body,map,x); - } - - - // no 2 FunNodes are ever GVN'able - @Override public boolean eq( Node n ) { - return this==n; - } - - @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-modules/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java deleted file mode 100644 index 3d6031b8..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.type.*; - -import java.util.HashMap; -import java.util.IdentityHashMap; - -// Constant function pointer whose type follows the callee signature. -public class FunPtrNode extends TypeNode { - public FunPtrNode(TypeFunPtr tfp, StartNode start, ReturnNode ret) { - super(tfp,start,ret); - } - public FunPtrNode(FunPtrNode fptr) { super(fptr); } - - - public ReturnNode ret() { return (ReturnNode)in(1); } - public FunNode fun() { return ret().fun(); } - - @Override public String label() { return "#"+_con; } - @Override public Node copy() { - FunPtrNode fptr = new FunPtrNode((TypeFunPtr)_con,null,null); - fptr._type = _type; - return fptr; - } - - @Override - protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - FunNode fun = CodeGen.CODE._link(((TypeFunPtr)_con).fidx()); - return fun!=null && fun._name!=null - ? p.p("{ ").p(fun._name).p("}") - : p.p(_con); - } - - @Override public boolean isConst() { return true; } - @Override public Type compute() { - ReturnNode ret = ret(); - if( ret == null ) return _con; - if( !(ret._type instanceof TypeTuple tt) ) return ret._type.oob(); - TypeFunPtr tfp = ret.fun().sig(); - // Both the sig and the return value must be true, so JOIN both - Type tret = tfp._ret.join(tt.ret()); - return tfp.makeFrom(tret); - } - @Override public Node idealize() { - ReturnNode ret = ret(); - if( ret==null ) return null; - // Function died (never executed), but fcn ptr is alive. - // Can be checked for null, or for unequals another FunPtr. - FunNode fun = addDep(ret.fun()); - TypeFunPtr sig = fun.sig(); - if( sig != _con ) - { liftType(sig); return this; } - // TODO: If the function body dies but this value remains live only as - // a null/equality sentinel, preserve the identity without the Return. - if( fun.isDead() ) - { setDef(1,null); liftType(sig.makeFrom(Type.TOP)); return this; } - return null; - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/GuardMach.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/GuardMach.java deleted file mode 100644 index 202b1ab8..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/GuardMach.java +++ /dev/null @@ -1,17 +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.type.Type; -import com.seaofnodes.simple.util.SB; - -public class GuardMach extends GuardNode implements MachNode { - public GuardMach(GuardNode guard) { super(guard); } - @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 in(1)._type==Type.BOTTOM ? 0 : 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-modules/src/main/java/com/seaofnodes/simple/node/GuardNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/GuardNode.java deleted file mode 100644 index e985da72..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/GuardNode.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeScalar; -import com.seaofnodes.simple.util.Utils; - -import java.util.HashMap; -import java.util.IdentityHashMap; - -// A branch-proven refinement to zero/null or non-zero/non-null. The value -// family is deliberately taken from the input when it becomes known. -public class GuardNode extends Node { - public final boolean _nonZero; - - public GuardNode(boolean nonZero, Node ctrl, Node val) { - super(ctrl,val); - _nonZero = nonZero; - } - - public GuardNode(GuardNode guard) { - super(guard); - _nonZero = guard._nonZero; - } - - - @Override public String label() { return _nonZero ? "(!=0)" : "(==0)"; } - - @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - return p.p(label()).n(in(1)); - } - - @Override public Type compute() { - // Guard is a pinned, path-local fact. If its control path is not - // executable, the fact is not executable either, regardless of how the - // value input sharpens later. - Type c = in(0)._type; - if( c != Type.CONTROL && c != Type.BOTTOM ) - return Type.TOP; - Type t = in(1)._type; - if( t==Type.BOTTOM || t==Type.TOP ) return t; - // The scalar envelope does not identify a value family. Preserve it - // until SCCP learns whether this is an int, float, pointer, or fptr; - // choosing generic ptr here can be weaker than a later concrete type. - if( t==TypeScalar.BOT || t==TypeScalar.TOP ) return t; - Type t2 = _nonZero ? t.nonZero() : t.makeZero(); - return t2==null ? (_type==null ? Type.TOP : _type) : t2; // No sane answer for e.g. not-zero of a zero - } - - @Override public Node idealize() { - if( in(0)._type != Type.CONTROL && in(0)._type != Type.BOTTOM ) - return null; - if( in(1)._type==Type.BOTTOM || in(1)._type==Type.TOP ) return null; - if( _type.isHighOrConst() && in(1)._type.isa(_type) ) - return in(1); - return null; - } - - @Override public boolean eq(Node n) { return _nonZero==((GuardNode)n)._nonZero; } - @Override int hash() { return _nonZero ? 1 : 0; } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/IfNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/IfNode.java deleted file mode 100644 index 471a28fe..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/IfNode.java +++ /dev/null @@ -1,100 +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 - 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== XInt.EMPTY) ) ? 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 && - prior instanceof CProjNode prj && - (addDep(iff.pred())==pred() || - pred() instanceof GuardNode cast && cast._nonZero && iff.pred()==cast.in(1) ) ) { - setDef(1,con( prj._idx==0 ? 1 : 0 )); - return this; - } - return null; - } - - // MachNode variants need to support this and negate the conditional test. - // The following CProjs will be inverted by the caller. - public void negate() { throw Utils.TODO(); } - - // Negate the sense of a test - public static String negate( String bop ) { - return switch( bop ) { - case "<" -> ">="; - case "<=" -> ">" ; - case "==" -> "!="; - case "!=" -> "=="; - case ">" -> "<="; - case ">=" -> "<" ; - default -> throw Utils.TODO(); - }; - } - - // Swap compare operands - public static String swap( String bop ) { - return switch( bop ) { - case "<" -> ">" ; - case "<=" -> ">="; - case "==" -> "=="; - case "!=" -> "!="; - case ">" -> "<" ; - case ">=" -> "<="; - default -> throw Utils.TODO(); - }; - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/LoadNode.java deleted file mode 100644 index e1a495ad..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ /dev/null @@ -1,331 +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.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, Node ctrl, Node mem, Node ptr, Node off) { - super(loc, name, alias, true, Type.BOTTOM, ctrl, 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 EscapeNode || use instanceof CallNode || - use instanceof CLInitNode ) 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 mem0 = mem()._type; - Type ptr0 = ptr()._type; - // Validate argument types - if( ptr0.isHigh() ) - return TypeScalar.TOP; - if( !(mem0 instanceof TypeMem mem) ) - return mem0.isHigh() ? TypeScalar.TOP : TypeScalar.BOT; - if( mem._t == Type.TOP ) - return TypeScalar.TOP; - if( ptr0 == Type.NIL ) - return Type.BOTTOM; // Load from nil is error - if( ptr0 == Type.BOTTOM ) - return Type.BOTTOM; // Error in is error out - if( !(ptr0 instanceof TypeMemPtr tmp) ) - return scalar(mem._t); - if( tmp.nullable() ) - return Type.BOTTOM; // Load from nil-able is error - - // Load field from object - Field pfld = tmp._obj.field(_name); - // No field? Open objects might yet get the field when falling; - // closed objects with missing field are an error. - if( pfld == null ) - return tmp.isHigh() || tmp._obj.isHigh() ? TypeScalar.TOP : TypeScalar.BOT; - - Type t = pfld._t; - boolean readonly = t instanceof TypeMemPtr view && view._ro; - // Load member of constant array - if( t instanceof TypeConAry ary ) - t = ary.elem(); // TODO: if offset is known, can peek the constant - if( mem._alias==_alias ) - t = t.join(mem._t); - - // A deeply read-only base produces a deeply read-only value. The - // generic declared field type must not cast this information away. - if( ptr0.isFinal() || readonly ) - t = t.makeRO(); - return scalar(t); - } - - // Loads produce ordinary scalar values. Field/memory joins can still - // expose the enclosing global lattice bounds through an uninitialized - // Type.TOP/BOTTOM field; keep those results inside the scalar envelope. - private static Type scalar(Type t) { - return t==Type.TOP ? TypeScalar.TOP : t==Type.BOTTOM ? TypeScalar.BOT : t; - } - - @Override - public Node idealize() { - Node mem = mem(); - Node ptr = ptr(); - - // Null checks are handled by types. Array elements retain control for - // range checks, but reading an array's length has no index to check. - if( in(0)!=null && ptr._type instanceof TypeMemPtr tmp && - (!tmp._obj.isAry() || _name.equals("#")) ) { - setDef(0,null); - return this; - } - - // Forward-ref loads eventually sharpen to a declared type - Field fld; - if( ptr._type instanceof TypeMemPtr tmp && - (fld=tmp._obj.field(_name)) != null && - _alias != fld._alias) { - assert _alias==1 || _alias == fld._alias; - assert !tmp._obj._open && !tmp._obj._fref; - unlock(); // Alias participates in GVN semantics - _alias = fld._alias; - return this; - } - // Must sharpen alias first - if( _alias == 1 ) - return null; - - Node slice = CLInitNode.slice(mem,_alias,this); - if( slice!=mem ) { setDef(1,slice); return this; } - - if( mem instanceof MemMergeNode merge ) { - setDef(1,merge.alias(_alias)); - // In support of uplifting control to a prior dominating load, if - // we move our memory, recheck other loads to see if they can hoist. - for( Node use : in(1).outs() ) - if( use instanceof LoadNode ld && use != this && ptr == ld.ptr() ) - CodeGen.CODE.add(ld); - - return this; - } - - // 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 - addDep(ld)._type.isa(_type) ) // and not rolling backwards - return ld; - - Node win = find(mem,ptr,null,null); - if( win!=null ) return folded(win); - 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) ) { - // Integer loop Phis widen to i64. Keep the load's declared - // width at the result, as for ordinary typed loop variables. - if( _type instanceof TypeInteger && !declaredType().isa(_type) ) return null; - PhiNode phi = new PhiNode(_name,memphi.region()).keep(); - phi.setType(_type instanceof TypeInteger ? TypeInteger.BOT : _type); - 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)); - } - CodeGen.CODE.add(phi.unkeep()); - return _type instanceof TypeInteger ? new ConvertNode(declaredType(),phi) : phi; - } - } - - return null; - } - - // Load a flavored zero from a New - private Node zero(NewNode nnn) { - Type zero = declaredType().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); - Field fld; - boolean external = ptr._type instanceof TypeMemPtr tmp && - (fld=tmp._obj.field(_name))!=null && fld._extern; - while( mem!=null ) { - addDep(mem); - if( mem==stop ) return mem; - switch( mem ) { - case MemMergeNode merge: mem=merge.alias(_alias); break; - case StoreNode st: - // Alias 1 is still unresolved, not a proven different field. - if( st._alias==1 ) return null; - addDep(st.ptr()); - if( _alias==st._alias && (ptr==st.ptr() || ptr==st.nnptr()) && off()==st.off() ) { - // A full-width load cannot narrow the forwarded value. - if( declaredType() instanceof TypeInteger ti && ti.log_size()==3 && - !addDep(st.val())._type.isa(_type) ) return null; - return st; - } - if( (external || _alias==st._alias) && (external || !neverAlias(ptr,st.ptr())) && - !addDep(off())._type.join(addDep(st.off())._type).isHigh() ) return null; - mem=st.mem(); - break; - case ProjNode proj: - // New's memory is private; public memory is joined at Escape. - if( external || !(proj.in(0) instanceof NewNode nnn) ) return null; - if( declaredType()==Type.BOTTOM ) return null; - return nnn; - case EscapeNode esc: - if( external ) return null; - addDep(esc.self()); - if( esc.self()==ptr ) { mem=esc.priv(); break; } - if( neverAlias(ptr,esc.self()) ) { mem=esc.pub(); break; } - return null; - 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,mp.region()).keep(); - for( int i=1; i<mp.nIns(); i++ ) phi.addDef(load(mp.in(i),ptr,stop,value)); - return CodeGen.CODE.add(phi.unkeep().peephole()); - } - Node fold=folded(win); - if( fold!=null ) return fold.peephole(); - } - LoadNode ld=new LoadNode(_loc,_name,_alias,in(0),mem,ptr,off()); - 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( shift==0 ) { - if( !val._type.isa(_type) ) - { addDep(val); return null; } - return val; - } - Node shl = new ShlNode(null,val,shf.keep()).peephole(); - return new SarNode(null,shl,shf.unkeep()); - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/LoopNode.java deleted file mode 100644 index 005a6624..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/LoopNode.java +++ /dev/null @@ -1,106 +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.TypeMem; - -import java.util.HashMap; -import java.util.IdentityHashMap; - -public class LoopNode extends RegionNode { - public LoopNode( Parser.Lexer loc, Node xctrl, Node entry ) { super(loc,xctrl,entry,null); } - public LoopNode( LoopNode loop ) { super(loop); } - - - public CFGNode entry() { return cfg(1); } - public CFGNode back () { return cfg(2); } - - @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(1.2f,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 = ConstantNode.make(Type.TOP).peephole(); - Node memout = new MemMergeNode(); - memout.addDef(f); // placeholder for control - memout.addDef(top); - for( Node u : _outputs ) - if( u instanceof PhiNode phi && phi._type.isa(TypeMem.BOT) ) - memout.addDef(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(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()); - // Force sane types to exit; might locally break monotonicity - ret.init(); - ret.out(0).init(); - stop.init(); - CodeGen.CODE.add(CodeGen.CODE._start); - CodeGen.CODE.add(mem); - - 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-modules/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java deleted file mode 100644 index b25fcaaf..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.type.Type; -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 new AssertionError("Do not call compute on "+getClass().getSimpleName()); } - @Override public Node idealize() { throw new AssertionError("Do not call idealize on "+getClass().getSimpleName()); } - - @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-modules/src/main/java/com/seaofnodes/simple/node/MachNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/MachNode.java deleted file mode 100644 index 025b5dc4..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java deleted file mode 100644 index cbf19c55..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java +++ /dev/null @@ -1,154 +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; - -import java.util.*; - -/** - * Memory Merge - a merge of many aliases into a "fat memory". All aliases - * are here, but most will be lazy - take the default fat memory. - */ -public class MemMergeNode extends Node { - - public MemMergeNode( Node ...nodes) { super(nodes); _type = TypeMem.BOT; } - public MemMergeNode(MemMergeNode mem) { super(mem); } - - - @Override public String label() { return "ALLMEM"; } - @Override public boolean isMem() { return true; } - - @Override - protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - p.p("MEM[ "); - for( int j=2; j<nIns(); j++ ) { - p.p(j); - p.p(":"); - Node n = in(j); - while( n instanceof ScopeNode loop ) { - p.p("Lazy_"); - n = loop.mem(); - } - if( n==null ) p.p("___ "); - else p.n(n).p(" "); - } - return p.unchar(' ').p("]"); - } - - - @Override public Type compute() { - Type tmem = in(1)._type; - if( !(tmem instanceof TypeMem defmem) ) - // A MemMerge is structurally memory even while its default input - // is a weak, not-yet-specialized Phi. - return tmem.isHigh() ? TypeMem.TOP : TypeMem.BOT; - // Is this a single private instance memory? - if( defmem._one ) { - if( !(defmem._t instanceof TypeStruct ts) ) - return defmem; - // Perfect singleton memory, so all updates are parallel and - // independent and stack. - for( int i=2; i<nIns(); i++ ) { - Node in = in(i); - if( in !=null && in._type.isHigh() ) - return TypeMem.TOP; - if( in instanceof StoreNode st ) { - Type val = ((TypeMem)st._type)._t; - Field old = ts.field(st._name); - // Lazy add an expected field for open structs. - if( old==null ) { assert ts._open; - old = Field.make(st._name,Type.BOTTOM,st._alias,st._init); - ts = ts.add(old); - } - Field fld = old.makeFrom(val); - // TODO: val leaks into perfect singleton memory, along with its fidxs and aliases - ts = ts.replace(fld); - } - } - return TypeMem.makePrivate(ts); - - } - - // Normal public memory; meet across escaped inputs - TypeMem mem = defmem; - for( int i=2; i<nIns(); i++ ) { - // Has this alias escaped? - if( XInt.bit(mem._escAs,i) && - in(i) != null && !in(i)._type.isHigh() ) { - mem = (TypeMem)mem.meet(in(i)._type); - } - } - return mem; - } - - @Override public Node idealize() { - assert nIns() != 0 && in(1)!=null; // Always have a default. if( nIns()==0 ) return null; - - // Fold defaults into the default - boolean progress=false, allDefault=true; - for( int i=2; i<nIns(); i++ ) { - if( in(1) == in(i) ) { setDef(i,null); progress=true; } - else { allDefault=false; } - } - - // If not merging any memory (all memory is just the default) - if( allDefault ) - return in(1); // Become default memory - - // Collapse stacked merged-mem - if( in(1) instanceof MemMergeNode mem ) { - // A dead memory cycle can lose its last use when we bypass mem. - // Keep the peephole's result alive until the worklist sees it. - keep(); - // Goal is to swap my default mem with mem's default mem - for( int i=2; i<mem.nIns(); i++ ) { - if( mem.in(i) != null ) { - // deeper default mem has a non-default - if( i>=nIns() || in(i)==null ) - setDefX(i,mem.in(i)); - } - } - CodeGen.CODE.add(mem.in(1)); - setDef(1,mem.in(1)); - return unkeep(); - } - - return progress ? this : null; - } - - - public Node in( Var v ) { return in(v._idx); } - - public Node alias( int alias ) { - assert !(in(1) instanceof BulkMemPhiNode bulk && bulk.isSplit(alias) && - (alias >= nIns() || in(alias)==null)); - return alias < nIns() && in(alias)!=null ? in(alias) : in(1); - } - - public Node alias( int alias, Node st ) { return setDefX(alias,st); } - - // 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 - } - } - } - } - - @Override public boolean eq( Node n ) { - return this==n; - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/MemOpNode.java deleted file mode 100644 index fa399e1d..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ /dev/null @@ -1,140 +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.type.*; -import com.seaofnodes.simple.util.*; -import java.lang.StringBuilder; -import java.util.*; - -/** - * 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 TypeNode { - - // The equivalence alias class - public int _alias; - - // True if load-like, false if store-like. - // Used on CPU-specific combined memory+arithmetic ops. - // - // Stores produce memory (maybe as part of a tuple with other things), - // loads do not. - // - // Loads might pick up anti-dependencies 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; - - // Field name. - 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 decl, Node ctrl, Node mem, Node ptr, Node off) { - super(decl, ctrl, mem, ptr, off); - _name = name; - _alias = alias; - _loc = loc; - _isLoad = isLoad; - } - public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type decl, Node ctrl, Node mem, Node ptr, Node off, Node value) { - this(loc, name, alias, isLoad, decl, ctrl, mem, ptr, off); - addDef(value); - } - public MemOpNode( Node ideal, MemOpNode mop ) { - super(ideal,mop instanceof LoadNode ld ? ld.declaredType() : mop._con); - _name = mop._name; - _alias = mop._alias; - _loc = mop._loc; - _isLoad= mop._isLoad; - } - - - static String mlabel(String name) { return "[]"==name ? "ary" : ("#"==name ? "len" : name); } - String mlabel() { return mlabel(_name); } - - public Node mem() { return in(1); } - public Node ptr() { return in(2); } - public Node off() { return in(3); } - - // Current declared field type. This is deliberately a live lookup: - // cyclic/forward types can sharpen by replacing the pointer's struct. - public Type declaredType() { - if( !(ptr()._type instanceof TypeMemPtr tmp) ) - return Type.BOTTOM; - Field fld = tmp._obj.field(_name); - return fld == null || fld._t == null ? Type.BOTTOM : fld._t; - } - - @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return _printMach(p); } - protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) { throw Utils.TODO(); } - - - // 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>1 && _alias==other._alias && declaredType()==other.declaredType() && err()==null; - } - - @Override - public boolean eq(Node n) { - MemOpNode mem = (MemOpNode)n; // Invariant - return _alias==mem._alias && super.eq(mem); - } - - @Override - int hash() { return _alias ^ super.hash(); } - - @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 Parser.error( "Might be null accessing '" + _name + "'",_loc); - if( tmp._obj.field(_name)==null && - CodeGen.CODE._phase.ordinal() > CodeGen.Phase.FinalizeIR.ordinal() ) - return Parser.error("Accessing unknown field '"+_name+"' from '*"+tmp._obj._name+"'",_loc); - // Sane field - if( off() instanceof ConFldOffNode && CodeGen.CODE._phase.ordinal() > CodeGen.Phase.FinalizeIR.ordinal() ) - return Parser.error("Accessing unknown field '"+_name+"' from '*"+tmp._obj._name+"'",_loc); - return null; - } - - // Memory writers which must follow this read. A packaging node is not a - // write; follow this alias through aggregates to its writers. - public Ary<Node> antiDeps( Ary<Node> antideps ) { - antideps.clear(); - return antiDeps(mem(),antideps); - } - - private Ary<Node> antiDeps( Node mem, Ary<Node> antideps ) { - for( Node use : mem._outputs ) { - if( use instanceof MemMergeNode merge ) { - if( merge.alias(_alias)==mem ) - antiDeps(merge,antideps); - } else if( use instanceof CallNode || use instanceof CLInitNode || - (use instanceof MemOpNode st && !st._isLoad && st._alias==_alias) || - (use instanceof EscapeNode esc && esc.fld()._alias==_alias) || - (use instanceof MemPhiNode phi && phi._alias==_alias) || - (use instanceof BulkMemPhiNode phi && !phi.isSplit(_alias)) ) { - if( !antideps.contains(use) ) antideps.add(use); - } - } - return antideps; - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java deleted file mode 100644 index 5ae716a7..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMem; - -import java.util.HashMap; -import java.util.IdentityHashMap; - -/** A Phi for one precise memory alias. */ -public class MemPhiNode extends PhiNode { - - public final int _alias; - - public MemPhiNode(String label, int alias, Node... inputs) { - super(label, inputs); - assert alias > 1; - _alias = alias; - } - - public MemPhiNode(MemPhiNode phi) { - super(phi,phi._label); - _alias = phi._alias; - } - - - @Override - public Type compute() { - Type t = super.compute(); - assert BulkMemPhiNode.checkMem(this,_alias); - if( !(t instanceof TypeMem tmem) ) return t; - assert tmem._alias==1 || tmem._alias==_alias; - // This Phi structurally knows its alias, but not whether the field is - // final. Do not infer slice finality from evolving input types. - return TypeMem.make(_alias,tmem._t,tmem._one,tmem._clz,false,tmem._escFs,tmem._escAs); - } - - @Override - public Node idealize() { - // A precise memory Phi can always bypass a MemMerge and select its - // matching slice. - for( int i=1; i<nIns(); i++ ) { - if( in(i) instanceof MemMergeNode mem ) { - setDef(i,mem.alias(_alias)); - return this; - } - Node slice = CLInitNode.slice(in(i),_alias,this); - if( slice!=in(i) ) { setDef(i,slice); 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-modules/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/MinusNode.java deleted file mode 100644 index 4d7e3ddb..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Utils; -import java.util.HashMap; -import java.util.IdentityHashMap; - -public class MinusNode extends Node implements ModeNode { - // Mode is 0 for unknown, 1 for int, 2 for flt - byte _mode; - - public MinusNode(Node in) { super(null, in); } - public MinusNode(Node in, byte mode) { super(null, in); _mode = mode; } - - - @Override public byte mode() { return _mode; } - @Override public Node setMode(byte mode) { - assert _mode==0 && (mode==1 || mode==2); - unlock(); // _mode participates in GVN hash and equality - _mode = mode; - return this; - } - - @Override protected String format() { return "(-%1)"; } - - @Override - public Type compute() { - Type t = in(1)._type; - if( t==null ) - return Type.TOP; - if( t.isHigh() ) - return _mode==0 ? Type.TOP : - _mode==1 ? TypeInteger.TOP : - t instanceof TypeFloat ? t : TypeFloat.F64.dual(); - byte mode = _mode; - if( mode==0 ) { - if( t instanceof TypeFloat ) mode=2; - if( t instanceof TypeInteger ) mode=1; - } - if( mode==0 ) - return CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal() ? Type.BOTTOM : Type.TOP; - if( mode==1 ) { - if( t 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; - } - if( t instanceof TypeFloat f ) - return f.isConstant() ? TypeFloat.constant(-f.value()) : f; - return TypeFloat.F64; - } - - @Override - public Node idealize() { - // -(-x) is x - if( in(1) instanceof MinusNode minus && addDep(minus.in(1))._type.isa(compute()) ) - return minus.in(1); - - // Can we decide int vs flt? - if( _mode==0 ) { - byte mode = (byte)( - in(1)._type instanceof TypeInteger ? 1 : - in(1)._type instanceof TypeFloat ? 2 : 0); - if( mode!=0 ) return setMode(mode).init(); - } - - return null; - } - @Override public Parser.ParseException err() { - return ArithNode.nodeErr(this,null,"-",_mode,true); - } - @Override public boolean eq( Node n ) { return _mode==((MinusNode)n)._mode; } - @Override public int hash() { return _mode; } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ModeNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ModeNode.java deleted file mode 100644 index dc635447..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ModeNode.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.seaofnodes.simple.node; - -/** - * A node whose numeric input family is selected once: unknown, integer, or - * floating point. Implementors remain ordinary Nodes; this interface only - * centralizes the structural mode contract shared by arithmetic, comparisons, - * and unary minus. - */ -public interface ModeNode { - byte mode(); - Node setMode(byte mode); -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/MulNode.java deleted file mode 100644 index 49c6432a..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import java.util.BitSet; - -public class MulNode extends ArithNode { - @Override boolean allowFloat() { return true; } - public MulNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - public MulNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs, mode); } - - - @Override public String op() { return "*"; } - - @Override long doOp( long x, long y ) { return x * y; } - @Override double doOp(double x, double 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.isa(compute()) ? lhs : super.idealize(); - if( c==0 ) return CodeGen.CODE.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,_mode); } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/MultiNode.java deleted file mode 100644 index 152d525a..00000000 --- a/chapter-modules/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, Node dep) { return null; } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/NeverNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/NeverNode.java deleted file mode 100644 index a7b4cf65..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/NeverNode.java +++ /dev/null @@ -1,24 +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(float ignore, 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-modules/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/NewNode.java deleted file mode 100644 index 5cbe1b61..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/NewNode.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.codegen.*; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.SB; -import java.util.HashMap; -import java.util.IdentityHashMap; - -/** - * Allocation! Allocate a chunk of memory, no init. - * Takes in ctrl and size, and TypeStruct. - * Produces a ptr and private memory. -*/ -public class NewNode extends Node implements MultiNode { - public TypeStruct _ts; - public NewNode( TypeStruct ts, Node ctrl, Node size ) { super(ctrl,size); _ts = ts; } - - public Node ctrl() { return in(0); } - public Node size() { return in(1); } - - // --- Serialization - public NewNode(NewNode nnn) { super(nnn); _ts = nnn._ts; } - - - @Override public String label() { return "new_"+(_ts.isAry() ? "ary_"+_ts._fields[1]._t.str() : _ts.str()); } - - @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - return p.p("new ").p(_ts.str()); - } - - @Override public TypeTuple compute() { - if( ctrl()._type.isHigh() ) - return TypeTuple.DEAD_NEW; - TypeStruct ts = _ts.isAry() ? _ts : _ts.makeInit(); - return TypeTuple.make(TypeMemPtr.make(_ts), - // Arrays get pre-zeroed by constructor. - // Objects start with default-zero memory for - // fields that can be zero-initialized. This is - // still private memory, so no aliases escape yet. - TypeMem.make(1,ts,true,false,false,null,null)); - } - - @Override public Node idealize() { return null; } - - @Override boolean _upgradeType( HashMap<String,Type> TYPES) { - Type old = _ts; _ts = (TypeStruct)_ts.upgradeType(TYPES); return old != _ts; - } - - // Two NewNodes are never equal; NewNodes always make independent pointers - // so they are not functional - they internally bump-ptr allocate always a - // new pointer. - @Override public boolean eq(Node n) { return this==n; } - - @Override int hash() { return _ts.hashCode(); } - - - @Override public Parser.ParseException err() { - return _ts._fref ? Parser.error("Unknown struct type '"+_ts._name+"'",null) : null; - } - - - // ------------ - // 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,code._cCallingConv).firstReg(); - _arg3Mask = code._mach.callArgMask(TypeFunPtr.CALLOC,3,0,code._cCallingConv); - // 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._cCallerSave.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,code._cCallingConv); - _xslot = (maxReg+fslot)+xslot; - for( int i=0; i<xslot; i++ ) - kills.set((maxReg+fslot)+i); - _kills = kills; - } - public String op() { return "alloc"; } - public RegMask regmap(int i) { return i==1 ? _arg3Mask : null; } - public RegMask outregmap() { return null; } - public RegMask outregmap(int idx) { return idx==0 ? _retMask : null; } - public RegMask killmap() { return _kills; } - public void asm(CodeGen code, SB sb) { - sb.p("#malloc, ").p(code.reg(size())); - } - -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/Node.java deleted file mode 100644 index de39ca80..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/Node.java +++ /dev/null @@ -1,696 +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.codegen.CodeGen; -import com.seaofnodes.simple.print.IRPrinter; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.*; -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 String label() { return getClass().getSimpleName().replace("Node",""); } - - 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 - @SuppressWarnings("unchecked") - protected <N extends Node> N addUse(Node n) { _outputs.add(n); return (N)this; } - - // Remove node 'use' from 'def's (i.e. our) output list, by compressing the list in-place. - // Return true if the output list is empty afterward. - // Error is 'use' does not exist; ok for 'use' to be null. - 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 - } - } - public Node removeLast() { - Node old_def = _inputs.pop(); - if( old_def != null ) - old_def.delUse(this); - return 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 - public <N extends Node> N keep() { return addUse(null); } - // Remove bogus null. - @SuppressWarnings("unchecked") - public <N extends Node> N unkeep() { - delUse(null); - return (N)this; - } - // Test "keep" status - public boolean iskeep() { return _outputs.find(null) != -1; } - public void unkill() { - if( unkeep().isUnused() ) - kill(); - } - public void isKill() { - if( isUnused() ) - kill(); - } - - // Replace self with nnn in the graph, making 'this' go dead - public void subsume( Node nnn ) { - assert nnn!=this; - while( nOuts() > 0 ) { - Node n = _outputs.removeLast(); - n.unlock(); - int idx = n._inputs.find(this); - n._inputs.set(idx,nnn); - nnn.addUse(n); - CODE.add(n); - CODE.addAll(n._outputs); - } - kill(); - } - - // insert `this` immediately after `def` in the same basic block. - public void insertAfter( Node def ) { - CFGNode cfg = def.cfg0(); - int i = cfg._outputs.find(def)+1; - if( cfg instanceof CallEndNode ) { - cfg = cfg.uctrl(); i=0; - } else if( def.in(0) instanceof MultiNode ) { - assert i==0; - i = cfg._outputs.find(def.in(0))+1; - } - - while( cfg.out(i) instanceof PhiNode || cfg.out(i) instanceof CalleeSaveNode ) i++; - cfg._outputs.insert(this,i); - _inputs.set(0,cfg); - } - - // Insert this in front of use.in(uidx) with this, and insert this - // immediately before use in the basic block. - public void insertBefore( Node use, int uidx ) { - CFGNode cfg = use.cfg0(); - int i; - if( use instanceof PhiNode phi ) { - cfg = phi.region().cfg(uidx); - if( cfg instanceof CProjNode && cfg.in(0) instanceof NeverNode nvr ) - cfg = nvr.cfg0(); - i = cfg.nOuts()-1; - } else { - i = cfg._outputs.find(use); - } - cfg._outputs.insert(this,i); - _inputs.set(0,cfg); - if( _inputs._len > 1 && this instanceof SplitNode ) - setDefOrdered(1,use.in(uidx)); - use.setDefOrdered(uidx,this); - } - - public void setDefOrdered( int idx, Node def) { - // If old is dying, remove from CFG ordered - Node old = in(idx); - if( old!=null && old.nOuts()==1 ) { - CFGNode cfg = old.cfg0(); - if( cfg!=null ) { - cfg._outputs.remove(cfg._outputs.find(old)); - old._inputs.set(0,null); - } - } - setDef(idx,def); - } - - public void removeSplit() { - CFGNode cfg = cfg0(); - cfg._outputs.remove(cfg._outputs.find(this)); - _inputs.set(0,null); - if( _inputs._len > 1 ) subsume(in(1)); - } - - // ------------------------------------------------------------------------ - // Graph-based optimizations - - /** - * Try to peephole at this node and return a better replacement Node. - * Always returns some not-null Node (often this). - */ - public final Node peephole( ) { - var obs = CODE._midAssert ? null : 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; // Cannot return null for no-progress - } - - /** - * Try to peephole at this node and return a better replacement Node if - * possible. We compute a {@link Type} and then check and replace: - * <ul> - * <li>if the Type {@link Type#isConstant}, we replace with a {@link ConstantNode}</li> - * <li>in a future chapter we will look for a - * <a href="https://en.wikipedia.org/wiki/Common_subexpression_elimination">Common Subexpression</a> - * to eliminate.</li> - * <li>we ask the Node for a better replacement. The "better replacement" - * is things like {@code (1+2)} becomes {@code 3} and {@code (1+(x+2))} becomes - * {@code (x+(1+2))}. By canonicalizing expressions we fold common addressing - * math constants, remove algebraic identities and generally simplify the - * code. </li> - * - * Unlike peephole above, this explicitly returns null for no-change, or not-null - * for a better replacement (which can be this). - * </ul> - */ - public final Node peepholeOpt( ) { - CODE.iterCnt(); - // Compute initial or improved Type - Type old = setType(compute()); - - // Replace constant computations from non-constants with a constant node. - if( !isConst() && !(this instanceof ParmNode parm && parm.inProgress()) && - _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: "+this+"#"+_nid+" old="+old+" new="+type; - //assert !type.hasClosedBuilder0(); // New types should not have closed builders - 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.at(idx); } - - private boolean addDepImpl( Node dep ) { - // Running peepholes during the big assert cannot have side effects - // like adding dependencies. - if( CODE._midAssert ) return false; - if( CODE._obs != null ) CODE._obs.dep(this, dep); - if( dep._deps==null ) dep._deps = new Ary<>(Node.class); - if( dep._deps .find(this) != -1 ) return false; // Already on list - if( dep._outputs.find(this) != -1 ) return false; - return true; - } - - /** - * 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. - */ - public <N extends Node> N addDep( N dep ) { - if( !addDepImpl(dep) ) return dep; - if( dep._inputs .find(this) != -1 ) return dep; // No need for deps on immediate neighbors - dep._deps.add(this); - return dep; - } - - public <N extends Node> N addDepForwards( N dep ) { - if( !addDepImpl(dep) ) 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; } - - // ------------------------------------------------------------------------ - // 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; - if( n instanceof CallNode call ) CODE._calls.add(call); - return n; - } - - // Report any post-optimize errors - public Parser.ParseException err() { return null; } - - // Common integer constants - public static ConstantNode con(long x) { return (ConstantNode)ConstantNode.make(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; - } - - - // If a Type upgrade happens, put on worklist - boolean _upgradeType( HashMap<String,Type> TYPES ) { return false; } - public final void upgradeType( HashMap<String,Type> TYPES ) { - Type old = _type; _type = _type.upgradeType(TYPES); - boolean progress = _upgradeType(TYPES); - if( progress || old != _type ) { - CODE.add(this); - CODE.addAll(_outputs); - if( _deps!=null ) CODE.addAll(_deps); - } - } - - /** - * 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-modules/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/NotNode.java deleted file mode 100644 index e22d16b6..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ /dev/null @@ -1,25 +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 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-modules/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/OrNode.java deleted file mode 100644 index 263c3e87..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/OrNode.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; - -public class OrNode extends ArithNode { - public OrNode( Parser.Lexer loc, Node lhs, Node rhs) { super(loc,lhs, rhs, (byte)1); } - - - @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 && t1.isa(TypeInteger.BOT) && forceInt(lhs) ) - 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-modules/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ParmNode.java deleted file mode 100644 index 316efb93..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ParmNode.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.type.*; - -import java.util.HashMap; -import java.util.IdentityHashMap; - -public class ParmNode extends PhiNode { - - // Argument indices are mapped one-to-one on CallNode inputs - public final int _idx; // Argument index - public Type _declaredType; - - public ParmNode(String label, int idx, Type declaredType, Node... inputs) { - super(label, inputs); - _idx = idx; - _declaredType = declaredType; - _type = declaredType; - } - public ParmNode(ParmNode parm) { super(parm, parm._label ); _idx = parm._idx; _declaredType = parm._declaredType; } - - - @Override public String label() { return MemOpNode.mlabel(_label); } - - @Override boolean _upgradeType( HashMap<String,Type> TYPES) { - Type t = _declaredType.upgradeType(TYPES); - if( t == _declaredType ) return false; - unlock(); - _declaredType = t; - return true; - } - - public FunNode fun() { return (FunNode)in(0); } - - @Override - protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - if( fun()._name!=null && fun()._name.endsWith("<clinit>") && _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() { - 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 ) - return _declaredType; - // 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); - } - } - // 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 && !ti.isHigh() && !ti.isConstant() ) - t = ti.same_but_slightly_wider_than(); - return t.join(_declaredType); - } - - @Override - public Node idealize() { - if( !(region() instanceof FunNode) ) - // A dead function can lose every caller before the Parm itself is - // removed; there is no replacement input in that transient form. - return nIns()==1 ? null : in(1); // Input has collapse to e.g. starting control. - // If function is folding, do all possible peeps - if( fun()._folding ) return super.idealize(); - - // 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-modules/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/PhiNode.java deleted file mode 100644 index 61e4b468..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ /dev/null @@ -1,275 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.*; -import java.util.HashMap; -import java.util.IdentityHashMap; - -public class PhiNode extends Node { - - public final String _label; - - public PhiNode( String label, Node... inputs) { - super(inputs); - _label = label; - } - public static PhiNode make(String label, Type minType, Node... inputs) { - return minType instanceof TypeMem mem - ? mem._alias==1 - ? new BulkMemPhiNode(label,inputs) - : new MemPhiNode(label,mem._alias,inputs) - : new PhiNode(label, inputs); - } - // Used by ParmNode - public PhiNode(PhiNode phi, String label) { super(phi); _label = label; } - // Used by instruction Selection - public PhiNode(PhiNode phi) { this(phi,phi._label); } - // Used by the infinite-loop exit breaker - public PhiNode(RegionNode r, Node sample) { - super(new Node[]{r}); - _label = ""; - _type = sample._type; - while( nIns() < r.nIns() ) - addDef(sample); - } - - - @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 _type instanceof TypeMem; } - - @Override - public Type compute() { - if( !(region() instanceof RegionNode r) ) - return Type.TOP; - // During parsing Phis have to be computed type pessimistically. - if( r.inProgress() || in(nIns()-1)==null ) - return 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); - } - } - - // 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( r instanceof LoopNode && // Only around loops - t instanceof TypeInteger ti && // Only widen integers - !ti.isHigh() && !ti.isConstant() ) { // Only widen live ranges - // Widen, to prevent infinite falling of TypeIntegers - return ti.same_but_slightly_wider_than(); - } - - return t; - } - - @Override - public Node idealize() { - RegionNode r = (RegionNode)region(); - if( r.inProgress() || nOuts()==0 ) - return null; // Input is in-progress - - // If we have only a single unique input, become it. - Node live = singleUniqueInput(); - if( live != null ) - return live; - - // No bother if region is going to fold dead paths soon - for( int i=1; i<nIns(); i++ ) - if( r.in(i)._type == Type.XCONTROL ) - return null; - - // Generic "pull down op" - Node progress; - if( same_op() && (progress = drop_same_op()) != null ) - return progress; - - // If merging Phi(ZERO, guardNZ(N)) at the matching `if(N)` join, the - // Phi is exactly N. The guard arm proves N is non-zero, and the other - // arm contributes N's zero value. This is only legal if N is already - // available at the Phi region; otherwise a return-scope/live-on-exit - // Phi can lose the zero arm and export a branch-local value upward. - Node unguard; - if( (unguard=matchingGuardMerge(r,1)) != null ) return unguard; - if( (unguard=matchingGuardMerge(r,2)) != null ) return unguard; - - return null; - } - - private Node matchingGuardMerge(RegionNode r, int nzIdx) { - Node zero = in(3-nzIdx); - if( !(in(nzIdx) instanceof GuardNode cast && cast._nonZero) || - zero._type.makeZero()!=zero._type || - cast.in(1)==this ) - return null; - if( !(r.in(nzIdx) instanceof CProjNode nz && nz._idx==0 && nz.ctrl() instanceof IfNode iff) ) - return null; - if( !(r.in(3-nzIdx) instanceof CProjNode z && z._idx==1 && z.ctrl()==iff) ) - return null; - // Predicate was guarded? - Node n = cast.in(1), tested = null; - while( true ) { - if( iff.pred()==n ) tested = n; - if( !(n instanceof GuardNode guard && guard._nonZero) ) break; - n = guard.in(1); - } - CFGNode early = CFGNode.earlyCFG(n); - return tested!=null && (early==null || early.dominates(r,this)) ? n : 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 EscapeNode || 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); - } - // Do not introduce an operand Phi mixing incompatible type families. - 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,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. - @Override public boolean eq( Node n ) { - return !inProgress() && super.eq(n); - } - - @Override - public Parser.ParseException err() { - if( _type != Type.BOTTOM ) return null; - - // Global BOTTOM retains the same role for non-scalar families. - 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; - - SB sb = new SB().p("No common type amongst "); - for( int i=1; i<nIns(); i++ ) - sb.p(in(i)._type.toString()).p(" and "); - return Parser.error(sb.unchar(5).toString(),null); - } - -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/Proj.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/Proj.java deleted file mode 100644 index c8b9378f..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/Proj.java +++ /dev/null @@ -1,2 +0,0 @@ -package com.seaofnodes.simple.node; -public interface Proj { int idx(); } diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ProjNode.java deleted file mode 100644 index a0142379..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ProjNode.java +++ /dev/null @@ -1,59 +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; -import java.util.HashMap; -import java.util.IdentityHashMap; - -public class ProjNode extends Node implements Proj { - - // 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,this); } - - @Override - public boolean eq( Node n ) { return _idx == ((ProjNode)n)._idx; } - - @Override - int hash() { return _idx; } - - @Override public int idx() { return _idx; } - -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/PtrToIntMach.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/PtrToIntMach.java deleted file mode 100644 index 9a9b94fd..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/PtrToIntMach.java +++ /dev/null @@ -1,17 +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; - -/** Zero-code machine form of {@link PtrToIntNode}. */ -public class PtrToIntMach extends PtrToIntNode implements MachNode { - public PtrToIntMach(PtrToIntNode ptr) { super(ptr); } - @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-modules/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java deleted file mode 100644 index 094734fa..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.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; -import com.seaofnodes.simple.type.TypeMemPtr; - - -/** - * Reinterpret a non-null managed pointer as its raw integer address. - * - * This node changes representation families but emits no code. No - * control-dependent proof can turn a pointer into an integer; array-to-C-pointer - * conversion adds the array body offset separately. - */ -public class PtrToIntNode extends Node { - public PtrToIntNode(Node ptr) { super(null,ptr); } - public PtrToIntNode(PtrToIntNode ptr) { super(ptr); } - - - @Override protected String format() { return "(i64*)%1"; } - - @Override public Type compute() { - return in(1)._type.isHigh() ? TypeInteger.TOP : TypeInteger.BOT; - } - - @Override public Node idealize() { return null; } - - @Override public Parser.ParseException err() { - Type t = in(1)._type; - if( t instanceof TypeMemPtr ptr ) - return ptr.notNull() ? null : Parser.error("Cannot convert a nullable pointer to an integer",null); - return Parser.error("Cannot convert "+t.str()+" to an integer pointer",null); - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ReadOnlyMach.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ReadOnlyMach.java deleted file mode 100644 index a35f5a1d..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java deleted file mode 100644 index ab7e0ae0..00000000 --- a/chapter-modules/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 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-modules/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/RegionNode.java deleted file mode 100644 index 923773c4..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ /dev/null @@ -1,172 +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.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 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,CodeGen.CODE.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); - } - } - if( !isDead() ) delDef(path); - return isDead() ? CodeGen.CODE.XCTRL : this; - } - - 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-modules/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ReturnNode.java deleted file mode 100644 index 0bb090ee..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ /dev/null @@ -1,129 +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; - -/** - * The Return node has two inputs. The first input is a control node and the - * second is the data node that supplies the return value. - * <p> - * In this presentation, Return functions as a Stop node, since multiple <code>return</code> statements are not possible. - * The Stop node will be introduced in Chapter 6 when we implement <code>if</code> statements. - * <p> - * The Return's output is the value from the data node. - */ -public class ReturnNode extends CFGNode { - - public FunNode _fun; - - public ReturnNode(Node ctrl, Node mem, Node data, Node rpc, FunNode fun ) { - super(ctrl, mem, data, rpc); - _fun = fun; - } - public ReturnNode( ReturnNode ret, FunNode fun ) { super(ret); _fun = fun; } - - - public Node ctrl() { return in(0); } - public Node mem () { return in(1); } - public Node expr() { return in(2); } - public Node rpc () { return in(3); } - @Override public FunNode fun() { return _fun; } - - @Override - 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() { - return TypeTuple.make(ctrl()._type,mem()._type,expr()._type); - } - - @Override public Node idealize() { - 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)); - - return null; - } - - @Override public Parser.ParseException err() { - if( ctrl()._type == Type.CONTROL && - expr()._type == Type.TOP ) - return Parser.error("No defined return type",null); - if( ctrl()._type == Type.CONTROL && - expr()._type == TypeScalar.BOT ) - return Parser.error("Cannot return generic scalar",null); - // With no user constructor, <init> doubles as the public no-arg - // constructor. Required fields remain TOP in its private memory: a - // typed parser poison which deliberately emitted no Store or code. - if( ctrl()._type == Type.CONTROL && _fun.isInstance() ) { - TypeStruct self = ((TypeMemPtr)_fun.sig().arg(0))._obj; - if( !CodeGen.hasUserConstructor(self) && expr()._type instanceof TypeMem mem ) - for( Field fld : self._fields ) { - if( !(fld._t instanceof TypeNil tn && tn.notNull()) ) continue; - Type actual = mem._alias==1 && mem._t instanceof TypeStruct ts - ? ts.field(fld._fname)._t - : mem._alias==fld._alias ? mem._t : Type.TOP; - if( actual==Type.TOP || actual instanceof TypeNil atn && atn.nullable() ) - return Parser.error("'"+self._name+"' is not fully initialized, field '"+ - fld._fname+"' is only partially set in the constructor",null); - } - } - return null; - } - - @Override public boolean eq( Node n ) { - return !_fun.isDead() && !((ReturnNode)n)._fun.isDead(); - } - - // ------------ - // MachNode specifics, shared across all CPUs - public String op() { - return _fun._frameAdjust > 0 ? "epilog" : "ret "; - } - // Correct Nodes outside the normal edges - public void postSelect(CodeGen code) { - _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("\n").p("rsp += #").p(frameAdjust).p("\nret "); - // Post code-gen, just print the "ret" - if( code._phase.ordinal() < CodeGen.Phase.RegAlloc.ordinal() || - (code._phase.ordinal() == CodeGen.Phase.RegAlloc.ordinal() && !code._regAlloc.done()) ) - // 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))).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-modules/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java deleted file mode 100644 index 02d3bbaa..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java +++ /dev/null @@ -1,33 +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 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-modules/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/SarNode.java deleted file mode 100644 index fa63334b..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/SarNode.java +++ /dev/null @@ -1,37 +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, (byte)1); } - - - @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 && forceInt(lhs) && 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-modules/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ScopeNode.java deleted file mode 100644 index 694d5dcb..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ /dev/null @@ -1,719 +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.codegen.CompUnit; -import com.seaofnodes.simple.codegen.ParseAll; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.Utils; -import java.util.*; - -/** - * The Scope node is purely a parser helper - it tracks names to nodes with a - * stack of variables. - */ -public class ScopeNode extends MemMergeNode { - - /** - * The control is a name that binds to the currently active control - * node in the graph - */ - public static final String CTRL = "$ctrl"; - public static final String ARG0 = "arg"; - public static final String MEM0 = "$mem"; - - // All active/live variables in all nested scopes, all run together. - // Maps 1-to-1 to the _inputs array. - public final Ary<Var> _vars; - - // Lexical scope is typed: - public static class Kind { - // Basic block scoping - public static class Block extends Kind { } - // Constructor/initializer block for an allocated struct. - public static class Constructor extends Block { - public Constructor(TypeStruct self) { _self = self; } - public final TypeStruct _self; - } - // Function scope - public static class Func extends Kind { - public final String _name; - public final boolean _constructor; - public Func(String name) { this(name,false); } - public Func(String name, boolean constructor) { _name=name; _constructor=constructor; } - } - - public int _lexSize; // Number of Vars in this scope - } - public final Ary<Kind> _kinds; - // Lexical scope nesting depth - public int depth() { return _kinds._len; } - public Kind klast() { return _kinds.last(); } - - // Extra guards; tested predicates and cast results - private final Ary<Node> _guards; - - // A new ScopeNode - public ScopeNode() { - super(); - _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 = var(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(1); } - public Var var(int i) { return _vars.at(i); } - - /** - * The ctrl of a ScopeNode is always bound to the currently active - * control node in the graph, via a special name '$ctrl' that is not - * a valid identifier in the language grammar and hence cannot be - * referenced in Simple code. - * - * @param n The node to be bound to '$ctrl' - * - * @return Node that was bound - */ - public <N extends Node> N ctrl(N n) { return setDef(0,n); } - public Node mem(Node n) { return setDef(1,n); } - - public void push( Kind kind ) { - kind._lexSize = _vars.size(); - _kinds.push(kind); - } - - // Pop a lexical scope - public void pop() { - promote(null); // Promote forward references to the next outer scope - _pop(); - } - // Pop a scope, no promote FRefs - public void _pop() { - 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(CodeGen code) { - Kind kind = klast(); - int n = kind._lexSize; - for( int i=n; i<nIns(); i++ ) { - Var v = var(i); - Node vn = in(i); - if( !v.isFRef() ) continue; - // Shuffle vars and inputs to move the forward reference to - // *before* the start of the using scope ("as if" it was not - // actually a forward reference). - for( int j=n; j<=i; j++ ) { - Var vtmp = var(j); - Node ntmp = in (j); - v._idx = j; - _vars .set(j,v ); - _inputs.set(j,vn); - v = vtmp; - vn = ntmp; - } - kind._lexSize = ++n; - } - } - - - public boolean inFunction () { return klast() instanceof Kind.Func; } - // Is any enclosing scope a constructor? - public boolean inConstructor() { return constructorSelf() != null; } - // Nearest constructor self type, if any. - public TypeStruct constructorSelf() { - for( int i=depth()-1; i>=0; i-- ) - if( _kinds.at(i) instanceof Kind.Constructor ctor ) - return ctor._self; - else if( _kinds.at(i) instanceof Kind.Func func && FunNode.isInstance(func._name) ) - return ((TypeMemPtr)var(_kinds.at(i)._lexSize).type())._obj; - return null; - } - - // Find nearest enclosing function scope - public int enclosingFunction() { - for( int i=depth()-1; i>=0; i-- ) - if( _kinds.at(i) instanceof Kind.Func func && !FunNode.isInstance(func._name) ) - return i; - throw Utils.TODO("Should not reach here"); - } - - // Find nearest enclosing function or declaration scope - public int enclosingFuncOrDecl() { - for( int i=depth()-1; i>=0; i-- ) - if( _kinds.at(i) instanceof Kind.Func func ) - return i; - throw Utils.TODO("Should not reach here"); - } - - // Which lexical scope contains this Var? - public int kindx( Var v ) { - for( int i=depth()-1; i>=0; i-- ) - if( v._idx >= _kinds.at(i)._lexSize ) - return i; - return -1; - } - - // Which lexical scope contains this Var? - - - public int kindFcnx( Var v ) { - for( int i=depth()-1; i>=0; i-- ) - if( v._idx >= _kinds.at(i)._lexSize && _kinds.at(i) instanceof Kind.Func ) - return i; - throw Utils.TODO("Should not reach here"); - } - - // Which function scope contains this Var? - public Kind kindFcn( Var v ) { - int kx = kindFcnx(v); - return kx == -1 ? null : _kinds.at(kx); - } - - // Final fields are writable local values inside their own initializer or - // constructor. Final parameters, local variables, and captured bindings - // remain final. Use the same rule when deciding which loop Phis to build. - public boolean canInit(Var v) { - int fx=enclosingFuncOrDecl(); - Kind.Func fun=(Kind.Func)_kinds.at(fx); - if( !fun._constructor || v._extern!=0 ) return false; - int dx=FunNode.isInstance(fun._name) ? fx : fx-1; - return dx>=0 && kindx(v)==dx && v._idx>=_kinds.at(dx)._lexSize+2; - } - - private boolean loopVar(Var v) { - return !v._final || (canInit(v) && !v.type().isHighOrConst()); - } - - // 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!=MEM0 ; // Later scopes do not define memory - if( depth() > 0 ) - for( int i=_vars.size()-1; i>=klast()._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 - return true; - } - } - Var v = new Var(nIns(),name,declaredType,xfinal,loc,init==CodeGen.CODE.XCTRL); - _vars.add(v); - // Creating a forward reference - if( init==CodeGen.CODE.XCTRL ) - //init = new FRefNode(v).init(); - throw Utils.TODO(); - addDef(init); - return true; - } - - // Normal lookup was tried and failed. Append a forward ref in the current - // lexical scope. Scope pops promote it outwards "as if" the final constant - // had been declared before the using scope. - - public Var defineFRef( String id, Type t, boolean xfinal, Parser.Lexer loc ) { - Var var = new Var(nIns(),id,t,xfinal,loc,true); - FRefNode fref = new FRefNode(id,loc).init(); - fref._type = fref._con = t; - _vars.add(var); - addDef(fref); - return var; - } - - - // Parser state carries one complete, conservative memory value. Precise - // alias partitions belong to the graph and are introduced by peepholes. - public Node mem( int alias ) { - assert alias == 1; - return in(update(var(1),null)); - } - public void mem( int alias, Node st ) { - assert alias == 1; - update(var(1),st); - } - - - /** - * Lookup a name in all scopes starting from most deeply nested. - * - * @param name Name to be looked up - * @return null if not found, or the implementing Node - */ - public Var lookup( String name ) { - int idx = find(name); - // -1 is missed in all scopes, not found - return idx == -1 ? null : update(var(idx),null); - } - - /** - * Redefine an existing name - * - * @param name Name being redefined - * @param n The node to bind to the name - */ - public void update( String name, Node n ) { - int idx = find(name); - assert idx>=0; - update(var(idx),n); - } - - public Var update( Var v, Node st ) { - Node old = in(v._idx); - if( old instanceof ScopeNode loop ) { - // Lazy Phi! - Node def = loop.in(v._idx); - old = def instanceof PhiNode phi && loop.ctrl()==phi.region() - // Loop already has a real Phi, use it - ? def - // Set real Phi in the loop head - // The phi takes its one input (no backedge yet) from a recursive - // lookup, which might have insert a Phi in every loop nest. - : loop.setDef(v._idx,PhiNode.make(v._name, v.type(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).init()); - 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 ordinary parser-state value. At loop heads the - // Scope sentinel triggers the same lazy-Phi machinery as variables. - dup.addDef(loop ? this : mem()); - - // Copy of other inputs - for( int i=2; i<nIns(); i++ ) - // For lazy phis on loops we use a sentinel - // that will trigger phi creation on update - dup.addDef(loop && loopVar(var(i)) ? 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()); - this._merge(that,r); - that.kill(); // Kill merged scope - CodeGen.CODE.add(r); - return r.unkeep(); - } - - public void _merge(ScopeNode that, RegionNode r) { - _merge(that,r,nIns()); - } - public void _merge(ScopeNode that, RegionNode r, int max ) { - for( int i = 1; i < max; 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. - // 'this' scope is the default, 'scope' might have frefs. - public void balanceIf( ScopeNode scope ) { - if( nIns()==scope.nIns() ) - return; // Fast-path cutout - int kidx = enclosingFunction(); - assert _kinds.at(kidx) == scope._kinds.at(kidx); - if( kidx==0 ) kidx = 1; - for( int i = _kinds.at(kidx-1)._lexSize; i < scope.nIns(); i++ ) { - Var n = scope.var(i); - if( i<nIns() && var(i)==n ) continue; - if( n.isFRef() ) { // RHS has forward refs - _vars.insert(n,i); - insertDef(i,scope.in(i)); - } else - throw Parser.error("Cannot define a '"+n._name+"' on one arm of an if",n._loc); - } - } - - public void balanceLoopFRefs( ScopeNode scope ) { - for( int i = nIns(); i < scope.nIns(); i++ ) { - Var n = scope.var(i); - if( n.isFRef() ) { // Loop body has forward refs - Var v = new Var(nIns(),n._name,n.type(),n._final,n._loc,true); - v._uninit = n._uninit; - _vars.add(v); - addDef(scope.in(i)); - } - } - } - - - // ------------------------------------------------ - // 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()); - - this._endLoop( this, back, exit); - back.kill(); // Loop backedge is dead - // Now one-time do a useless-phi removal - this._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( !loopVar(var(i)) ) continue; - Node n = in(i); - if( var(i).type().isHighOrConst() && !(n instanceof PhiNode phi && phi.inProgress()) ) { // Cannot lift higher than a constant, so no Phi - Node con = ConstantNode.make(var(i).type()).init(); - n.subsume(con); - n = con; - } else if( back.in(i) != scope ) { - PhiNode phi = (PhiNode)n; - 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,n); - } - } - - // ------------------------------------------------ - // 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. - // - // This operation is lexical, not a full semantic theorem prover, and thus - // its behavior changes the language spec. Optimizations that hide the - // lexical structure (e.g. constant folding control flow) can hide what is - // guarded or how the guard is applied, making it somewhat fragile. Several - // good use cases are covered here, but not everything you might like. - // - // Simple lexical facts work well: - // - // ``` - // if( ptr ) - // return ptr.field; // `ptr` is guarded non-null here. - // - // if( a && a.ptr ) - // return a.ptr.x; // `a` is guarded while parsing the RHS load, - // // and remains guarded on the true arm. - // ``` - // - // Short-circuit expressions are lowered as nested Phis. We recursively - // decompose the recognized Phi shape, so chains such as: - // - // ``` - // if( x && y && z ) - // use(x,y,z); - // ``` - // - // discover the nested `x && y` and then `x`, `y`, and `z`; this is not - // merely a one-level lookup. Likewise, nested tests carry lexical guards - // inwards: - // - // ``` - // if( x && y ) - // if( z && a ) - // if( x.ptr && z.ptr ) - // ... - // ``` - // - // Here `x` is already guarded while parsing the outer true arm, `z` while - // parsing the inner true arm, and both are available before the matching - // loads. The later post-test decomposition is just a convenience for - // values still schedulable at the post-test control. - // - // The hard limit is that a semantic implication is not always a - // schedulable Node. For `(x && y)`, the true side implies that `y` was - // true, but `y` might be a value computed only on the RHS control arm: - // - // ``` - // val ptr = ...; - // if( ptr && (ptr=fcn() ? 0 : arg ) {} - // ``` - // - // A Guard pinned after the Phi merge and using the branch-local `ptr` - // value would give that value a use above its defining arm. GCM then has - // no legal block between the value's early control and the LCA of its - // uses. Thus `_addGuards` first checks that the predicate's effective - // early CFG dominates the control where the Guard would be pinned. If it - // does not, we skip that guard even if the fact is semantically true. - // - // A more complete answer would need a richer logical/proof node, or would - // need to rematerialize predicates on the guarded path instead of using - // branch-local nodes directly. Plain GuardNodes are intentionally kept to - // facts represented by values already available at the guard control. - // - // TODO: Make this more robust for shapes like: - // ``` - // if( !ary || !ary# ) exit(); // facts about ary are lost, despite the early exit - // for( ...; ary# ) ...; - // ``` - // Here the guard ends up guarding the whole expression (both ary and ary# - // are not-null), but loses that ary is not-null. A better answer might be - // a dedicated logical expression node, or some sort of gather-of-checked - // values to Guard post-test. - - public void addGuards( CFGNode ctrl, Node pred, boolean invert ) { - _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( CFGNode ctrl, Node pred, boolean invert ) { - if( pred==null || pred.isDead() ) - return; - CFGNode early = CFGNode.earlyCFG(pred); - if( early != null && !early.dominates(ctrl,null) ) - 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(); - } - // Short-circuit logic is represented by a Phi. For `a || b` being - // false, or `a && b` being true, both individual operands have the - // same proven value as the whole expression. The "skipped RHS" path - // is the Region input whose Phi value is the controlling If predicate. - 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()); - Node zeroPred = pred instanceof NotNode not ? not.in(1) : null; - // This is a zero/null test. - // Compute the positive test type. - _addGuard(true,ctrl,pred); - - // Compute the negative test type. - if( zeroPred != null ) - _addGuard(false,ctrl,zeroPred); - } - - 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(boolean nonZero, Node ctrl, Node pred) { - Node guard = new GuardNode(nonZero,ctrl,pred).peephole(); - if( guard != pred ) { - pred.keep(); - guard.keep(); - _guards.add(pred); - _guards.add(guard); - replace(pred,guard,nonZero); - } - } - - - // Remove matching pred/cast pairs from this guarded region. - public void 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 - } - } - - // 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, boolean nonZero ) { - assert old!=null && old!=cast; - for( int i=0; i<nIns(); i++ ) - if( in(i)==old || nonZero && nullCheckedPhi(in(i),old) ) - setDef(i,cast); - } - - private static boolean nullCheckedPhi( Node n, Node old ) { - if( !(n instanceof PhiNode phi) || phi.nIns()!=3 ) - return false; - if( phi.in(1)==null || phi.in(2)==null ) - return false; - int zero = phi.in(1)._type == phi.in(1)._type.makeZero() ? 1 : - phi.in(2)._type == phi.in(2)._type.makeZero() ? 2 : -1; - if( zero == -1 ) - return false; - Node val = phi.in(3-zero); - return val==old || - val instanceof GuardNode guard && guard._nonZero && guard.in(1)==old; - } - - // ------------------------------------------------------------------------ - // Insert a top-level public external symbol, "as-if" some #include/import - // was executed at the top of tile. - public Var insertExtern( ExternNode ext ) { - // Size of top-level scope - int lexSize = _kinds.at(0)._lexSize; - Var var = new Var(lexSize,ext._extern,ext._con,true,null); - insert(var,ext,0); - return var; - } - - // ------------------------------------------------------------------------ - // Insert a Var with Node n in the lex scope just prior to kidx - private void insert( Var var, Node n, int kidx ) { - // Insert just prior to scope kidx - int nidx = _kinds.at(kidx)._lexSize; - _vars.insert(var,nidx); - insertDef(nidx,n); - // All outer Kinds lexical offsets bump by one - for( int i = kidx; i < depth(); i++ ) - _kinds.at(i)._lexSize++; - // All Vars outside the innermost lexical scope bump out one - for( int i=nidx+1; i<_vars._len; i++ ) - var(i)._idx++; - } - - // Save the existing Node state to an array - public Node[] save() { - Node[] olds = new Node[nIns()]; - for( int i=0; i<nIns(); i++ ) - olds[i] = in(i)==null ? null : in(i).keep(); - return olds; - } - - // Restore saved Node state back into scope - public void restore( Node[] olds ) { - assert olds.length<=nIns(); - for( int i=0; i<olds.length; i++ ) - setDef(i,olds[i]==null ? null : olds[i].unkeep()); - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ShlNode.java deleted file mode 100644 index 380fc246..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ShlNode.java +++ /dev/null @@ -1,49 +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; - -public class ShlNode extends ArithNode { - public ShlNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs, (byte)1); } - - - @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 && forceInt(lhs) && 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(), CodeGen.CODE.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-modules/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ShrNode.java deleted file mode 100644 index 17170a1f..00000000 --- a/chapter-modules/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, (byte)1); } - - - @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 && forceInt(lhs) && 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-modules/src/main/java/com/seaofnodes/simple/node/SplitNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/SplitNode.java deleted file mode 100644 index 3042f857..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/SplitNode.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.Encoding; -import com.seaofnodes.simple.codegen.LRG; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.util.SB; - -public abstract class SplitNode extends MachConcreteNode { - public final LRG _lrg; // Live range for split - public final String _kind; // Kind of split - public final byte _round; // Debugging info for which RegAlloc round - public SplitNode(LRG lrg,String kind, byte round, Node[] nodes) { super(nodes); _lrg=lrg; _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(); - } - public FunNode fun(Encoding enc) { return enc._code._regAlloc.lrg(this)._fun; } - @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-modules/src/main/java/com/seaofnodes/simple/node/StartCUNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/StartCUNode.java deleted file mode 100644 index 0f7a322a..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/StartCUNode.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.type.Type; - -import java.util.HashMap; -import java.util.IdentityHashMap; - -// Start of a single compilation unit, distinct from the outer whole-program Start. -public class StartCUNode extends StartNode { - public final String _fname; - public StartCUNode(StartNode start, StopCUNode stop, Type arg, String fname) { super(start,stop,arg); _fname = fname; } - public StartCUNode(StartCUNode start) { super(start); _fname = start._fname; } - - - @Override public CFGNode cfg0() { return (CFGNode)in(0); } - - // IDom is cfg0() and depth is always Start+1 which is 0+1 or 1 - @Override public int idepth() { return 1; } - @Override public CFGNode idom(Node dep) { return cfg0(); } - -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/StartNode.java deleted file mode 100644 index 3c605e30..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ /dev/null @@ -1,87 +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.Arrays; -import java.util.HashSet; - -/** - * The Start node represents the external world calling, be it calling the - * entry <clinit> or just some linked caller calling. - * <p> - * Start initially has 1 input (arg) from outside and the initial control. - * In ch10 we also add mem aliases as structs get defined; each field in struct - * adds a distinct alias to Start's tuple. - * <p> - * By ch25, Start is the external world (or linker) calling with everything - * that has escaped. Start is NOT the entry <clinit> itself, which can assume - * no memory nor fidxs exist nor escaped. Start gets escaped things from Stop; - * together these close the Grand Cycle between the code we know and the code - * we don't. - */ -public class StartNode extends LoopNode implements MultiNode { - - final Type _arg; - - public StartNode(StartNode start, StopNode stop, Type arg) { super(null,start,stop); _arg = arg; } - public StartNode(StartNode start) { super(start); _arg = start==null ? null : start._arg; } - - public Type arg() { return _arg; } - - @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, it's the class init for - // this compilation unit. - @Override public CFGNode uctrl() { - // Find module.<clinit>, it's the start. - CFGNode C = null; - for( Node use : _outputs ) - if( use instanceof FunNode fun && CodeGen.CODE.entryClinitName().equals(fun._name) ) - { assert C==null; C = fun; } - if( C==null ) - for( FunNode fun : CodeGen.CODE._linker ) - if( fun!=null && !fun.isDead() && CodeGen.CODE.entryClinitName().equals(fun._name) ) - { assert C==null; C = fun; } - return C; - } - - boolean escapedFIDX(int fidx) { - // Public class fields are callable by the unknown external world - // regardless of the current SCCP approximation of Start memory. - if( CodeGen.CODE.publicFIDX(fidx) ) return true; - if( !(_type instanceof TypeTuple tt) ) - return !_type.isHigh(); // TOP never escapes, BOTTOM always escapes - TypeMem tmem = (TypeMem)tt._types[1]; - if( tmem == TypeMem.BOT ) return false; - if( tmem == TypeMem.TOP ) return false; - if( XInt.isHigh(tmem._escFs) ) - return false; - return XInt.bit(tmem._escFs,fidx); - } - - @Override public TypeTuple compute() { - StopNode stop = (StopNode)in(1); - TypeMem tmem = stop._type instanceof TypeTuple tt - ? tt.mem() - : stop._type.isHigh() ? TypeMem.START : TypeMem.BOT; - assert tmem._alias==1 && !tmem._one; - return TypeTuple.make(Type.CONTROL,tmem,_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; } - - @Override public boolean eq( Node n ) { return true; } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/StopCUNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/StopCUNode.java deleted file mode 100644 index 53c2325c..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/StopCUNode.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.type.*; - -import java.util.HashMap; -import java.util.IdentityHashMap; - -// Stop of a single compilation unit, distinct from the outer whole-program Stop. -public class StopCUNode extends StopNode { - public StopCUNode() { super(); } - public StopCUNode(StopNode stop) { super(stop); } - - - public StartCUNode start() { - for( Node use : _outputs ) - if( use instanceof StartCUNode start ) - return start; - return null; - } - - @Override - public TypeTuple compute() { - // During Parsing, new Stops and Returns are being added. This Stop - // cannot know if more are coming, so must assume the worst. - if( CodeGen.CODE._phase ==null || CodeGen.CODE._phase == CodeGen.Phase.Parse ) - return TypeTuple.STATE; - - StartCUNode start = start(); - // Memory start from all returns - TypeMem xmem = TypeMem.START; - // Escape summaries are monotone discovery facts. In Opto, a Return - // can widen from a precise set to FULL; the public-only normalization - // below must not forget private bits already discovered precisely from - // that Return on an earlier SCCP step. - if( CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal() && - CodeGen.CODE._phase.ordinal() <= CodeGen.Phase.Lower.ordinal() && - _type instanceof TypeTuple old && old.mem() instanceof TypeMem omem ) - xmem = xmem.makeFrom(XInt.meet(xmem._escFs,omem._escFs), - XInt.meet(xmem._escAs,omem._escAs)); - - for( Node def : _inputs ) { - ReturnNode ret = (ReturnNode)def; - if( ret._type == Type.TOP ) continue; - if( !(ret._type instanceof TypeTuple tret) ) - return TypeTuple.STATE; // Must be Type.BOTTOM - - FunNode fun = ret.fun(); - boolean noUserCtor = fun.isInstance() && - !CodeGen.hasUserConstructor(((TypeMemPtr)fun.sig().arg(0))._obj); - // Instance <init> is an allocation helper, never an externally - // callable constructor when a user constructor exists. Otherwise - // the same no-arg <init> is the public constructor. - boolean pub = fun.isPublic() && (fun.isClz() || noUserCtor); - TypeFunPtr sig = fun.sig(); - // If public init, the function escapes and the related class escapes - if( pub ) { - xmem = xmem.escapes(sig); - // Classes also escape their public clazz pointer, which then - // escapes all their public fields. Private fields do not - // escape, and are only available from the local class code. - if( fun.isClz() ) - xmem = xmem.escapesAliases(sig._sig[0]); - } - - // The instance <init> Start edge supplies conservative pre-Opto - // inputs, but its private-memory result is consumed only by the - // allocation sequence and is never an externally visible return. - if( fun.isInstance() && !noUserCtor ) continue; - - // Does this function escape? If not, then no need to MEET - // here as its effects are not visible to the outside world. - addDepForwards(start); // Check again if function escapes - if( pub || start.escapedFIDX(sig.fidx()) ) { - // External callers already hold their object arguments, so - // writes through those arguments expose addresses too. - for( Node use : fun._outputs ) - if( use instanceof ParmNode p && p._idx>=2 ) - xmem=publicBottom(xmem.escapes(addDepForwards(p)._type)); - if( tret.ctl().isHigh() ) continue; // Return not reachable - // Capture (precisely?) all escaping pointer aliases and fidxs. - // Escaped fidxs means the linked world can call that function; - // escaped aliases means the linked world can R/W those aliases. - xmem = xmem.escapes(tret.ret()); - xmem = publicBottom(xmem); - xmem = (TypeMem)xmem.meet(publicBottom(tret.mem(),xmem)); - } - } - // Native retention is shared by the whole program, including calls - // on paths with no Return. A class boundary must not hide it. - for( CallNode call : CodeGen.CODE._calls ) - if( !call.isDead() ) xmem=publicBottom(xmem.escapes(call.exposed(this))); - assert xmem._alias==1 && !xmem._one; - return TypeTuple.make(Type.CONTROL,xmem,Type.BOTTOM); - } - - // FULL escape sets arise from global BOTTOM. Across a compilation-unit - // boundary global BOTTOM exposes every public field, while retaining any - // private escapes already discovered precisely. - private TypeMem publicBottom(TypeMem mem) { return publicBottom(mem,mem); } - private TypeMem publicBottom(TypeMem mem, TypeMem prior) { - // The pessimistic Iter pass naturally lifts transient FULL inputs to - // precise finite sets. Replacing FULL early there would let a later - // input refinement add bits, reversing Iter's monotonic direction. - // This normalization is needed only for Opto's optimistic grand cycle. - if( CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal() || - CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Lower.ordinal() ) return mem; - int[] escFs = XInt.isHigh(mem._escFs) - ? XInt.meet(prior._escFs,CodeGen.CODE.publicFIDXs()) - : mem._escFs; - int[] escAs = XInt.isHigh(mem._escAs) - ? XInt.meet(prior._escAs,CodeGen.CODE.publicAliases()) - : mem._escAs; - return escFs==mem._escFs && escAs==mem._escAs ? mem : mem.makeFrom(escFs,escAs); - } - - @Override - public Node idealize() { - int len = nIns(); - for( int i=0; i<nIns(); i++ ) - if( in(i) instanceof ReturnNode ret && addDep(ret.fun()).isDead() ) - delDef(i--); - if( len != nIns() ) return this; - return null; - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/StopNode.java deleted file mode 100644 index 7238f1f9..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.type.*; - -import java.util.HashMap; -import java.util.IdentityHashMap; - -// A collection of function ReturnNodes for a compilation unit. -public class StopNode extends CFGNode { - - public StopNode() { - super(); - _type = compute(); - } - public StopNode(StopNode stop) { super(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 = null; - for( Node n : _inputs ) { - if( n instanceof ReturnNode ret ) { - if( ret1 != null ) { ret1=null; break; } - ret1 = ret; - } - } - if( ret1!=null ) return p.n(ret1); - - p.p("Stop[ "); - for( Node n : _inputs ) - if( n instanceof ReturnNode ret ) { - String name = 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() { - ReturnNode ret1 = null; - for( Node n : _inputs ) { - // TODO: remove isPublic - if( n instanceof ReturnNode ret && ret.fun().isPublic() ) { - if( ret1 != null ) return null; // Ambiguous - ret1 = ret; - } - } - return ret1; - } - - @Override - public TypeTuple compute() { - // During Parsing, new Stops and Returns are being added. This Stop - // cannot know if more are coming, so must assume the worst. - if( CodeGen.CODE._phase ==null || CodeGen.CODE._phase == CodeGen.Phase.Parse ) - return TypeTuple.STATE; - - // Just meet-over-inputs. - TypeTuple tt = TypeTuple.STOP_HIGH; - for( Node def : _inputs ) - tt = (TypeTuple)tt.meet(def._type); - assert tt.mem()._alias==1 && !tt.mem()._one; - return tt; - } - - @Override public Node idealize() { - int len = nIns(); - for( int i=0; i<nIns(); i++ ) - if( in(i).nIns()==0 ) - delDef(i--); - if( len != nIns() ) return this; - return null; - } - - @Override public int idepth() { - if( validIDepth() ) return _idepth; - int d=0; - for( Node ret : _inputs ) - // All public results are hooked on the Stop, including data - // values, so must check CFG - if( ret instanceof CFGNode cfg ) - d = Math.max(d,cfg.idepth()+1); - return cacheIDepth(d); - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/StoreNode.java deleted file mode 100644 index bdd07c8d..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ /dev/null @@ -1,326 +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; -import java.util.HashMap; -import java.util.IdentityHashMap; - -/** - * 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 { - - final boolean _init; // Initializing writes are allowed to write null - // Semantic store width in bytes. Zero is the sole undecided state; - // otherwise hardware and ideal Stores are the 1/2/4/8-byte variants. - private byte _size; - - /** - * @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 decl, Node ctrl, Node mem, Node ptr, Node off, Node value, boolean init) { - this(loc,name,alias,decl,ctrl,mem,ptr,off,value,init,(byte)0); - } - private StoreNode(Parser.Lexer loc, String name, int alias, Type decl, Node ctrl, Node mem, Node ptr, Node off, Node value, boolean init, byte size) { - super(loc, name, alias, false, decl, ctrl, mem, ptr, off, value); - _init = init; - _size = size; - } - - - @Override boolean canDrop(MemOpNode other, Node dep) { - return super.canDrop(other,dep) && _init==((StoreNode)other)._init; - } - - // Debugger label - @Override public String label() { return "st_"+mlabel(); } - @Override public boolean isMem() { return true; } - - public Node nnptr() { - if( !(ptr() instanceof GuardNode cast) ) return ptr(); - assert cast._nonZero; - return cast.in(1); - } - public Node val() { return in(4); } - public int storeSize() { assert _size==1 || _size==2 || _size==4 || _size==8; return _size; } - public int log_size() { return Integer.numberOfTrailingZeros(storeSize()); }; - - @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; - Type mem0 = mem()._type; - Type ptr0 = ptr()._type; - // Validate argument types - if( ptr0.isHigh() ) - return TypeMem.TOP; - if( !(mem0 instanceof TypeMem mem) || - !(ptr0 instanceof TypeMemPtr ptr) ) - return mem0.isHigh() || ptr0==Type.NIL ? TypeMem.TOP : TypeMem.BOT; - - // Sharpen memory value; required for narrowing stores where the parser - // inserts zero/sign masking and somebody reads the TypeMem type. - Type decl = declaredType(); - if( decl != Type.BOTTOM ) - { assert decl != Type.TOP; val = val.join(decl); } - // A singleton class's final constant field lives in the static image. - // Its precise memory slice is a constant too; ordinary peepholes can - // replace this Store before scheduling or instruction selection. - // A constant runtime value alone is not enough: mutable fields (notably - // $init) still need their writes, and instance fields need allocation. - Field fld = ptr._obj.field(_name); - if( _alias!=1 && ptr._one && Parser.startsClzPrefix(ptr._obj._name) && - fld!=null && fld._final && decl.isConstant() ) - return TypeMem.make(_alias,val,true,true,true,mem._escFs,mem._escAs).escapesFrom(val); - - // Allocation uses a private TypeMem and nothing else does. This - // memory is truly private; a temporary singleton until it escapes - - // which is never does in a constructor. - if( !_name.equals("[]") && mem._one ) - // Just track the stored value - return TypeMem.make(_alias,val,true,false,_init,null,null).escapesFrom(val); - - // Normal aliasing Store. - assert mem._alias==1 || mem._alias==_alias; // Perfect aliasing - - // Single alias store. If memory is fat, attempt to get a narrow slice. - Type tfld = mem._alias==1 && mem._t instanceof TypeStruct ts - ? ts._fields[ts.findAlias(_alias)]._t - : mem._t; - - // Same alias, meet into other fields. - Type t = val.meet(tfld); - // Bulk memory's final bit describes its conservative remainder, not - // this newly precise slice. Slice finality comes from the field. - boolean xfinal = fld != null && fld._final; - return TypeMem.make(_alias,t,mem._one,mem._clz,xfinal,mem._escFs,mem._escAs).escapesFrom(val); - } - - @Override - public Node idealize() { - // Freeze the semantic Store variant as soon as the pointer exposes a - // canonical field declaration. Waiting until after structural memory - // peeps can lose the declaration to a flow-sensitive constant field - // state. - if( _size==0 && (_size= storageType()) != 0 ) { - unlock(); // Width participates in the cached hash. - return init(); - } - - assert !(mem()._type instanceof TypeMem mem) || _alias == 1 || mem._alias == 1 || _alias == mem._alias; - - // Simple store-after-MemMerge to a known alias can bypass. Happens when inlining. - Node mem = mem(); - if( _alias != 1 && mem instanceof MemMergeNode merge ) { - setDef(1,merge.alias(_alias)); - return this; - } - - Field fld; - if( _alias==1 && ptr()._type instanceof TypeMemPtr tmp && (fld=tmp._obj.field(_name)) != null && _size != 0 ) { - // Expand Store(bulkMem,alias#1) into MemMerge(bulkMem,#N:Store(bulkMem,alias#N)). - - // Normally I'd like to just drop in this xform and move on, but - // users of the bulk Store memory might nontheless depend on the - // sharp memory type (e.g. I64) from the store's memory. Using the - // MemMerge means using the bulk memory which e.g. might be BOT. - // Check for store users with the same alias, and use the new store - // directly, preserving the sharper graph. - Node st = new StoreNode(_loc,_name,fld._alias,fld._t,in(0),mem,ptr(),off(),val(),_init,_size).peephole(); - for( int i=0; i<nOuts(); i++ ) { - Node use = out(i); - if( useAlias(use)==fld._alias ) { - int idx = use._inputs.find(this); - assert idx != -1; - CodeGen.CODE.add(use).setDef(idx,st); - i--; // setDef removed use from this Store's outputs - } - } - MemMergeNode mmm = mem instanceof BulkMemPhiNode bulk - ? bulk.aggregate(mem,fld._alias,st) - : new MemMergeNode(null,mem); - if( !(mem instanceof BulkMemPhiNode) ) - mmm.alias(fld._alias,st); - return mmm.init(); - } - - // Simple store-after-store on same address. Should pick up the - // required init-store being stomped by a first user store. - if( mem instanceof StoreNode st && - ptr()==st.ptr() && // Must check same object - off()==st.off() && // And same offset (could be "same alias" but this handles arrays to same index) - ptr()._type instanceof TypeMemPtr && // No bother if weird dead pointers - // Must have exactly one use of "this" or you get weird - // non-serializable memory effects in the worse case. - checkOnlyUse(st) ) { - assert _name==st._name; // Equiv class aliasing is perfect - setDef(1,st.mem()); - return this; - } - - // Private constructor memory describes the value that the next helper - // reads. Keep its conversion, even if a machine store could truncate it. - // This is the same rule as in the construction and escape chapters. - boolean priv = mem()._type instanceof TypeMem tm && tm._one; - // Value is automatically truncated by narrow store - if( !priv && _size!=0 && val() instanceof AndNode and && and.in(2)._type.isConstant() ) { - int log = 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( !priv && _size!=0 && 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; - } - - // Flow-sensitive pointer types can carry a current field value (e.g. the - // constant zero in a u32 field). Storage width comes from the canonical - // struct declaration. If the pointer/definition is not available yet, - // leave the Store undecided instead of consulting the value or `_con`. - private byte storageType() { - if( !(ptr()._type instanceof TypeMemPtr tmp) ) - return 0; - TypeStruct obj = (TypeStruct)Parser.TYPES.get(tmp._obj._name); - Field fld = obj.field(_name); - if( fld == null || fld._t == null ) - return 0; - if( fld._t == Type.BOTTOM ) - return 0; // Store into a final field - assert fld._t != Type.TOP; - // Freshly parsed field stores retain the declaring structure on their - // symbolic offset even after the pointer's flow type sharpens. - if( fld._t.isConstant() ) { - if( !(off() instanceof ConFldOffNode coff) ) - return 0; - fld = coff._ts.field(_name); - if( fld == null || fld._t == null ) - return 0; - } - // Compute storage size from field type - byte size = (byte)(1 << fld._t.log_size()); - assert size==1 || size==2 || size==4 || size==8; - return size; - } - - // Alias required by a direct consumer of this Store's memory result. - // Keep in sync with BulkMemPhiNode.outputAlias. - private int useAlias(Node use) { - return switch( use ) { - case MemOpNode mem -> mem._alias; - case MemPhiNode phi -> phi._alias; - case EscapeNode esc -> esc.pub()==this ? esc.fld()._alias : 0; - default -> 0; - }; - } - - // 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 || _alias==1 || _size==0 || _init ) 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,null,mem,ptr(),off()).peephole(); - Node value = CodeGen.CODE.add(new PhiNode(_name,loop,init,val()).peephole()); - Node sink = new StoreNode(_loc,_name,_alias,_con,null,mem,ptr(),off(),value,_init,_size); - // 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; - assert _size>0 || CodeGen.CODE._phase.ordinal() <= CodeGen.Phase.FinalizeIR.ordinal(); - if( ptr()._type == Type.TOP ) // Dead store? - return null; - TypeMemPtr tmp = (TypeMemPtr)ptr()._type; - Field f = tmp._obj.field(_name); - if( f!=null && (tmp._ro || f._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; } - @Override int hash() { return super.hash() ^ _size; } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/SubNode.java deleted file mode 100644 index adad2ed5..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ /dev/null @@ -1,56 +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 { - @Override boolean allowFloat() { return true; } - public SubNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - public SubNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs,mode); } - - - @Override public String op() { return "-"; } - - @Override long doOp( long x, long y ) { return x - y; } - @Override double doOp(double x, double 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 and unresolved numeric modes for type checking. - if( _mode==1 && 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,_mode); } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java deleted file mode 100644 index 1acf55b6..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ToFloatNode.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 com.seaofnodes.simple.type.TypeInteger; - - -public class ToFloatNode extends Node { - public ToFloatNode(Node lhs) { super(null, lhs); } - - - @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-modules/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java deleted file mode 100644 index 9136aacc..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java +++ /dev/null @@ -1,24 +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-modules/src/main/java/com/seaofnodes/simple/node/TypeNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/TypeNode.java deleted file mode 100644 index 970472da..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/TypeNode.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Utils; -import java.util.BitSet; -import java.util.HashMap; - -// Node carries an extra internal type -public abstract class TypeNode extends Node { - public Type _con; - public TypeNode(Type t, Node... nodes) { super(nodes); assert t!=null; _con = t; } - public TypeNode(TypeNode tn) { super(tn); _con = tn._con; } - // Copy edges from ideal, but 'this' is a Type-Mach-Node (e.g. MemOpX86) and needs a _con - public TypeNode(Node ideal, Type con) { super(ideal); _con = con; } - - // Upgrade the internal type - @Override boolean _upgradeType( HashMap<String,Type> TYPES) { - return liftType(_con.upgradeType(TYPES)); - } - - // Lift type - public boolean liftType( Type t ) { - if( t == _con ) return false; - unlock(); // Unlock before changing _con - _con = t; - return true; - } - - @Override public boolean eq(Node n) { return _con==((TypeNode)n)._con; } - @Override int hash() { return _con.hashCode(); } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java deleted file mode 100644 index 3792e224..00000000 --- a/chapter-modules/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 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-modules/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/XorNode.java deleted file mode 100644 index e9cd13a5..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/XorNode.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; - -public class XorNode extends ArithNode { - public XorNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs, (byte)1); } - - - @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 && forceInt(lhs) && 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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java deleted file mode 100644 index 76e47ca2..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java deleted file mode 100644 index 67a10b48..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java deleted file mode 100644 index 4c635d07..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java deleted file mode 100644 index 1a0365ad..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java deleted file mode 100644 index 6292e98e..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java deleted file mode 100644 index 77f14d4c..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java deleted file mode 100644 index 7cb42eec..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java deleted file mode 100644 index 31bbde86..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java +++ /dev/null @@ -1,80 +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; - final boolean _fp; - BranchARM(IfNode iff, String bop, boolean fp ) { - super(iff); - _bop = bop; - _fp = fp; - } - @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, _fp)) ); - } 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, _fp))); - } 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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java deleted file mode 100644 index 4de28f3a..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.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; - _name = CodeGen.CODE.funcName(tfp.fidx()); - } - - @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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java deleted file mode 100644 index 1949ae46..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java deleted file mode 100644 index 1bd9c372..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java deleted file mode 100644 index 67ffc27b..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java deleted file mode 100644 index cd4dd9cf..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java deleted file mode 100644 index e94b6491..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java deleted file mode 100644 index 68ad6047..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java deleted file mode 100644 index 814a82c2..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java deleted file mode 100644 index 31dcfc36..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/FltARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/FltARM.java deleted file mode 100644 index db2540f7..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/FltARM.java +++ /dev/null @@ -1,45 +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.util.SB; -import com.seaofnodes.simple.util.Utils; - -//FMOV (scalar, immediate) -//Floating-point move immediate. -public class FltARM extends ConstantNode implements MachNode, RIPRelSize { - final String _ext; - FltARM( ConstantNode con, String ext ) { super(con); _ext = ext; } - @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 FltARM copy() { return new FltARM(this,_ext); } - - @Override public void encoding( Encoding enc ) { - if( _ext!=null ) throw Utils.TODO(); - 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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java deleted file mode 100644 index 17cafcde..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java deleted file mode 100644 index b1c30afc..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java deleted file mode 100644 index 72b76edd..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java +++ /dev/null @@ -1,39 +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; -import com.seaofnodes.simple.util.Utils; - -// Integer constants -public class IntARM extends ConstantNode implements MachNode { - final String _ext; - IntARM( ConstantNode con, String ext ) { super(con); _ext = ext; } - - @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,_ext); } - - @Override public void encoding( Encoding enc ) { - if( _ext!=null ) throw Utils.TODO(); - 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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java deleted file mode 100644 index 53494092..00000000 --- a/chapter-modules/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[_con.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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java deleted file mode 100644 index 954cfac2..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java deleted file mode 100644 index b32153ab..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java deleted file mode 100644 index 91be34e3..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java deleted file mode 100644 index 243ce968..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java deleted file mode 100644 index 14eaf510..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java +++ /dev/null @@ -1,81 +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 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 int _bytes; - final char _sz; - 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; - _bytes = mop instanceof StoreNode st ? st.storeSize() : 1<<_con.log_size(); - _sz = (char)('0'+_bytes); - } - - // 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 new AssertionError("Do not call compute on "+getClass().getSimpleName()); } - @Override public Node idealize() { throw new AssertionError("Do not call idealize on "+getClass().getSimpleName()); } - - int size() { return _bytes; } - - // 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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java deleted file mode 100644 index f598cefb..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java deleted file mode 100644 index a4f81fd4..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java deleted file mode 100644 index 881b41f9..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java deleted file mode 100644 index 8bb8983e..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java +++ /dev/null @@ -1,39 +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("\n"); // Multi-op trigger - sb.p("ldi ").p(code._mach.regs()[_arg2Reg]).p(" = #1\n"); - sb.p("call #calloc"); - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java deleted file mode 100644 index f8e450f6..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java deleted file mode 100644 index 5b7264ec..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java deleted file mode 100644 index 86d07bee..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java deleted file mode 100644 index b419fe0a..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java deleted file mode 100644 index 4b7d6f13..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java deleted file mode 100644 index a7ed5073..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java deleted file mode 100644 index e5b06c39..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.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.*; -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 <,<=,== - final boolean _fp; // FP compare (unsigned encoding) - SetARM(Node cmp, String bop, boolean fp) { - super(cmp); - _inputs.setLen(1); // Pop the cmp inputs - // Replace with the matched cmp - _inputs.push(cmp); - _bop = bop; - _fp = fp; - } - @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, _fp), 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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java deleted file mode 100644 index dcfeb5d8..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java +++ /dev/null @@ -1,60 +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(LRG lrg, String kind, byte round) { super(lrg,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 = fun(enc).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 = fun(enc).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)); - } - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java deleted file mode 100644 index 25e5d501..00000000 --- a/chapter-modules/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(LRG lrg,String kind,byte round) { super(lrg,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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java deleted file mode 100644 index c9b2127a..00000000 --- a/chapter-modules/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(LRG lrg,String kind,byte round) { super(lrg,kind,round); } - @Override public RegMask regmap(int i) { return MASK; } - @Override public RegMask outregmap() { return MASK; } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java deleted file mode 100644 index 0b90ac8b..00000000 --- a/chapter-modules/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[Integer.numberOfTrailingZeros(_bytes)], 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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java deleted file mode 100644 index fd5cd4de..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java deleted file mode 100644 index 1a751434..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java deleted file mode 100644 index b2c0b2ed..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java deleted file mode 100644 index a053ba46..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.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; -import com.seaofnodes.simple.util.Utils; - -public class TFPARM extends FunPtrNode implements MachNode, RIPRelSize { - final String _ext; - // Pointer to a Simple function - TFPARM( FunPtrNode fptr ) { super(fptr); _ext = null; } - // Pointer to an Extern "C" function - TFPARM( TypeFunPtr fptr, String ext ) { super(fptr,CodeGen.CODE._start,null); _type = fptr; _ext = ext; } - @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((TypeFunPtr)_con,_ext); } - @Override public void encoding( Encoding enc ) { - if( _ext==null ) enc.relo(this); // Internal patch - else enc.external(this,_ext); // ELF-file external patch - 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 rpc = enc.reg(this); - if(opLen == 8 ) { - arm.patch_adrp_add(enc, opStart, delta, 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); - _type.print(sb.p(reg).p(" #")); - } - @Override public boolean eq(Node n) { return this==n; } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java deleted file mode 100644 index b2531f97..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java +++ /dev/null @@ -1,49 +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 { - final String _ext; - TMPARM( ConstantNode con, String ext ) { super(con); _ext = ext; } - @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,_ext); } - @Override public void encoding( Encoding enc ) { - if( _ext!=null ) enc.externalData(this,_ext); - else 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.patch_adrp_add(enc, opStart, delta, 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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java deleted file mode 100644 index aecd02bb..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java deleted file mode 100644 index d56397a3..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java deleted file mode 100644 index 12143ccd..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java deleted file mode 100644 index 3c965da5..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java +++ /dev/null @@ -1,503 +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 patch_adrp_add(Encoding enc, int opStart, int delta, int rd) { - int target = opStart + delta; - int pageDelta = ((target & ~0xFFF) - (opStart & ~0xFFF)) >> 12; - int imm21 = pageDelta & 0x1FFFFF; - enc.patch4(opStart , Arm64.adrp(1, imm21 & 3, OP_ADRP, imm21 >>> 2, rd)); - enc.patch4(opStart+4, Arm64.imm_inst_l(OPI_ADD, target & 0xFFF, rd)); - } - - 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 - boolean hidden = hiddenSelf(tfp); - int sigidx = idx-2; - if( hidden ) { - if( sigidx==0 ) return null; - sigidx--; - } - // Count floats in signature up to index - int fcnt=0; - for( int i=hidden ? 1 : 0; i<idx-2; i++ ) - if( tfp.arg(i) 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( sigidx-fcnt < cargs.length ) - return cargs[sigidx-fcnt]; - } - // Pass on stack slot (8 and higher) - if( maxArgSlot>0 ) throw Utils.TODO(); - return new RegMask(MAX_REG + 1 + sigidx); - } - - // 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=hiddenSelf(tfp) ? 1 : 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 boolean hiddenSelf(TypeFunPtr tfp) { - return tfp.nargs() > 0 && tfp.arg(0) == TypePtr.PTR; - } - - 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(LRG lrg, String kind, byte round) { - 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(lrg,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(lrg,kind,round); - return new SplitARM(lrg,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 AddNode add -> add(add); - case AndNode and -> and(and); - case BoolNode bool -> cmp(bool); - case CallNode call -> call(call); - case PtrToIntNode ptr -> new PtrToIntMach(ptr); - case GuardNode guard -> new GuardMach(guard); - case CallEndNode cend-> new CallEndMach(cend); - case CLInitNode init -> new CLInitNode(init); - case CProjNode c -> new CProjNode(c); - case ConstantNode con-> con(con); - case DivNode div -> div.mode()==2 ? new DivFARM(div) : new DivARM(div); - case EscapeNode esc -> new EscapeNode(esc); - case FunNode fun -> new FunARM(fun); - case FunPtrNode fptr -> fptr(fptr); - case IfNode iff -> jmp(iff); - case LoadNode ld -> ld(ld); - case MemMergeNode mem-> new MemMergeNode(mem); - case MinusNode neg -> { - if( neg.mode()==2 ) throw Utils.TODO(); - yield new NegARM(neg); - } - case MulNode mul -> mul.mode()==2 ? new MulFARM(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 StartCUNode start -> new StartCUNode(start); - case StopCUNode stop -> new StopCUNode(stop); - case StartNode start -> new StartNode(start); - case StopNode stop -> new StopNode(stop); - case StoreNode st -> st(st); - case SubNode sub -> sub.mode()==2 ? new SubFARM(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(), cmp instanceof CmpFARM); - } - 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 = "!="; - boolean fp = false; - if( iff.in(1) instanceof BoolNode bool ) { op = bool.op(); fp = bool.isFloat(); } - else if( iff.in(1)==null ) op = "=="; // Never-node cutout - else iff.setDef(1, new BoolNode.NE(iff.in(1), - ConstantNode.raw(TypeInteger.ZERO), - (byte)(iff.in(1)._type instanceof TypeFloat ? 2 : 1))); - return new BranchARM(iff, op, fp); - } - - private Node add(AddNode add) { - if( add.mode()==2 ) - return new AddFARM(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 ConstantNode.raw(con); // Default unknown caller inputs - String ext = con instanceof ExternNode ext0 ? ext0._extern : null; - return switch( con._con ) { - case TypeInteger ti -> new IntARM(con,ext); - case TypeFloat tf -> new FltARM(con,ext); - case TypeMemPtr tmp -> new TMPARM(con,ext); - case TypeFunPtr tfp -> new TFPARM(tfp,ext); - case TypeNil tn -> throw Utils.TODO(); - // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code. - case Type t -> t==Type.NIL ? new IntARM(con,ext) : ConstantNode.raw(con); - }; - } - - private Node fptr( FunPtrNode con ) { - return new TFPARM(con); - } - - private Node call(CallNode call){ - return call.fptr() instanceof FunPtrNode fptr - ? new CallARM(call, (TypeFunPtr)fptr._type) - : call.fptr() instanceof ConstantNode con - ? new CallARM(call, (TypeFunPtr)con._type) - : 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 Node prj(ProjNode prj) { - if( prj.in(0) instanceof CLInitNode ) return new ProjNode(prj); - TypeInteger ret = cReturn(prj); - if( ret!=null ) return new ExtendARM(prj,ret); - return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjARM(prj); - } - - private static int off; - private static Node idx; - private Node st(StoreNode st) { - return new StoreARM(address(st),st.ptr(),idx,off,st.val()); - } - - // Gather addressing mode bits prior to constructing. This is a builder - // pattern, but saving the bits in a *local* *global* here to keep mess - // contained. - private <N extends MemOpNode> N address(N mop ) { - off = 0; // Reset - idx = null; - Node base = mop.ptr(); - // Skip/throw-away a ReadOnly, only used to typecheck - if( base instanceof ReadOnlyNode read ) base = read.in(1); - assert !(base instanceof AddNode) && base._type instanceof TypeMemPtr; // Base ptr always, not some derived - if( mop.off() instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm9(ti) ) { - off = (int)ti.value(); - assert off == ti.value(); // In 32-bit range - } else { - idx = mop.off(); - } - return mop; - } - -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java deleted file mode 100644 index 59bad64e..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.isa.RiscV; -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.FunPtrNode; -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 FunPtrNode implements MachNode, RIPRelSize { - AUIPC( FunPtrNode fptr ) { super(fptr); } - @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(this); } - @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+#"); - _type.print(sb); - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java deleted file mode 100644 index d87e49b9..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java deleted file mode 100644 index 8012b61e..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java deleted file mode 100644 index 7010916f..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java deleted file mode 100644 index 9fd65e7d..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java deleted file mode 100644 index 1845985e..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java deleted file mode 100644 index a9d03da5..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java deleted file mode 100644 index b1426a3f..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java +++ /dev/null @@ -1,62 +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; - _name = CodeGen.CODE.funcName(tfp.fidx()); - } - - @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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java deleted file mode 100644 index cda4bc79..00000000 --- a/chapter-modules/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 riscv.RPC_MASK; } - - @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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java deleted file mode 100644 index 8eb4fb3b..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java deleted file mode 100644 index 515ffa47..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java deleted file mode 100644 index 5bd3ec1c..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java deleted file mode 100644 index a56ad216..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java deleted file mode 100644 index a110d156..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.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.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 { - final String _ext; - FltRISC( ConstantNode con, String ext ) { super(con); _ext = ext; } - @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,_ext); } - - @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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java deleted file mode 100644 index d3d64b06..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java deleted file mode 100644 index 1b53ab0f..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java deleted file mode 100644 index ee247c3e..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java deleted file mode 100644 index dd4a02a3..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.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.util.SB; -import com.seaofnodes.simple.util.Utils; - -// Special instruction for loading 8 byte constants from the constant pool. - -public class Int8RISC extends ConstantNode implements MachNode, RIPRelSize { - final String _ext; - Int8RISC( ConstantNode con, String ext ) { super(con); _ext = ext; } - @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,_ext); } - - @Override public byte encSize(int delta) { return 8; } - - @Override public void encoding( Encoding enc ) { - if( _ext!=null ) throw Utils.TODO(); - 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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java deleted file mode 100644 index 35d2bc65..00000000 --- a/chapter-modules/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 { - 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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java deleted file mode 100644 index aedd8b47..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java deleted file mode 100644 index e9f3ec02..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java +++ /dev/null @@ -1,65 +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.*; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; - -// 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 int func3() { - int func3 = -1; - // no unsigned flavour for store, so both signed and unsigned trigger the same - Type declType = _con; - if( declType == TypeInteger. I8 ) func3=0; // LB - if( declType == TypeInteger.I16 ) func3=1; // LH - if( declType == TypeInteger.I32 ) func3=2; // LW - if( declType == TypeInteger.BOT ) func3=3; // LD - if( declType == TypeInteger. U8 ) func3=4; // LBU - if( declType == TypeInteger.BOOL) func3=4; // LBU - if( declType == TypeInteger.U16 ) func3=5; // LHU - if( declType == TypeInteger.U32 ) func3=6; // LWU - if( declType instanceof TypeInteger ti ) { - if( -128 <= ti._min && ti._max < 128 ) func3 = 0; // LB - } - - // float - if( declType == TypeFloat.F32) func3 = 2; // fLW fSW - if( declType == TypeFloat.F64) func3 = 3; // fLD fSD - - if( declType instanceof TypeMemPtr) func3 = 3; // 8 byte pointers (pick ld) - if( declType instanceof TypeFunPtr) func3 = 3; // 8 byte function pointers - if( declType instanceof TypeConAry ary ) - func3 = switch( ary.log_size() ) { - case 0 -> 4; // LBU - case 1 -> 5; // LHU - case 2 -> 2; // LW - case 3 -> 3; // LD - default -> throw Utils.TODO("RISC-V constant-array load for "+declType); - }; - if( func3 == -1 ) throw Utils.TODO("RISC-V load for "+declType); - return func3; - } - - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(","); - asm_address(code,sb); - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java deleted file mode 100644 index e0bbc219..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java +++ /dev/null @@ -1,51 +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; - final int _bytes; - 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; - _bytes = mop instanceof StoreNode st ? st.storeSize() : 1<<_con.log_size(); - _sz = (char)('0'+_bytes); - } - - @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 new AssertionError("Do not call compute on "+getClass().getSimpleName()); } - @Override public Node idealize() { throw new AssertionError("Do not call idealize on "+getClass().getSimpleName()); } - - // func3 is based on load/store size and extend - abstract int 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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java deleted file mode 100644 index dacec2b5..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java deleted file mode 100644 index 9cb9ace7..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java deleted file mode 100644 index 3070c56c..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java deleted file mode 100644 index 40d0359c..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.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.*; -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("\n"); // Multi-op trigger - sb.p("ldi ").p(code._mach.regs()[_arg2Reg]).p(" = #1\n"); - sb.p("auipc a2=#calloc\n"); - sb.p("call a2+#calloc"); - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java deleted file mode 100644 index 6f499db1..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java deleted file mode 100644 index 937595a3..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java deleted file mode 100644 index a310a1f4..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java deleted file mode 100644 index 0f8db4e5..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java deleted file mode 100644 index 2f9ac469..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java deleted file mode 100644 index 33143c6b..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java deleted file mode 100644 index d037dd5c..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java deleted file mode 100644 index 9bc86565..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java deleted file mode 100644 index 848cc59f..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java deleted file mode 100644 index 1425caa2..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java deleted file mode 100644 index 5a4d4cc6..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java deleted file mode 100644 index d455337b..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java +++ /dev/null @@ -1,61 +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( LRG lrg, String kind, byte round ) { super(lrg, 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 = fun(enc).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 = fun(enc).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)); - } - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java deleted file mode 100644 index b316b303..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java deleted file mode 100644 index e2e34415..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java deleted file mode 100644 index d0ea6b18..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java deleted file mode 100644 index 1eccc1c8..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java deleted file mode 100644 index c3fcf4d3..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java +++ /dev/null @@ -1,56 +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.type.*; -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 _bytes >= 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)); - } - - // func3 is based on load/store size and extend - @Override int func3() { - return Integer.numberOfTrailingZeros(_bytes); - } - - @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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java deleted file mode 100644 index 044820cf..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java deleted file mode 100644 index 2adfce23..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java deleted file mode 100644 index 44ef1a95..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java +++ /dev/null @@ -1,56 +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 TFPRISC extends FunPtrNode implements MachNode, RIPRelSize { - final String _ext; - // Pointer to a Simple function - TFPRISC( FunPtrNode fptr ) { super(fptr); _ext = null; } - // Pointer to an Extern "C" function - TFPRISC( TypeFunPtr fptr, String ext ) { super(fptr,CodeGen.CODE._start,null); _type = fptr; _ext = ext; } - @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((TypeFunPtr)_con,_ext); } - @Override public void encoding( Encoding enc ) { - if( _ext==null ) enc.relo(this); // Internal patch - else enc.external(this,_ext); // ELF-file external patch - // TODO: 1 op encoding, plus a TODO if it does not fit - short dst = enc.reg(this); - // 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 ) { - int hi20 = (delta + 0x800) >> 12; - int lo12 = delta - (hi20 << 12); - // AUIPC (upper 20 bits) - // opstart of add - enc.patch4(opStart ,RiscV.u_type(RiscV.OP_AUIPC, rpc, hi20)); - // addi(low 12 bits) - enc.patch4(opStart+4,RiscV.i_type(RiscV.OP_IMM, rpc, 0, rpc, lo12 & 0xFFF)); - // addi - } else { - // should not happen as one instruction is 4 byte, and TFP arm encodes 2. - throw Utils.TODO(); - } - } - - @Override public void asm(CodeGen code, SB sb) { - _type.print(sb.p(code.reg(this)).p(" #")); - } - @Override public boolean eq(Node n) { return this==n; } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java deleted file mode 100644 index a5ff3753..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java +++ /dev/null @@ -1,44 +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 { - final String _ext; - TMPRISC( ConstantNode con, String ext ) { super(con); _ext = ext; } - @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,_ext); } - @Override public void encoding( Encoding enc ) { - if( _ext!=null ) enc.externalData(this,_ext); - else enc.largeConstant(this,((TypeMemPtr)_con)._obj,0,-1); - short dst = enc.reg(this); - // AUIPC dst,#hi20_constant_pool - enc.add4(RiscV.u_type(RiscV.OP_AUIPC, dst, 0)); - // addi dst,[dst+#low12_constant_pool] - enc.add4(RiscV.i_type(RiscV.OP_IMM, dst, 0, dst, 0)); - } - - @Override public byte encSize(int delta) { return 8; } - - // Delta is from opcode start - @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - short dst = enc.reg(this); - // AUIPC dst,#hi20_constant_pool - enc.patch4(opStart , RiscV.u_type(RiscV.OP_AUIPC, dst, (delta+0x800)>>12)); - // ADDI's low 12 bits are signed; round the AUIPC page accordingly. - 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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java deleted file mode 100644 index 448d9ec3..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java deleted file mode 100644 index f4323cae..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java deleted file mode 100644 index 413a7c3b..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java deleted file mode 100644 index 0195542c..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java +++ /dev/null @@ -1,450 +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";} - - // 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/ptr arguments calling convention - 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 - boolean hidden = hiddenSelf(tfp); - int sigidx = idx-2; - if( hidden ) { - if( sigidx==0 ) return null; - sigidx--; - } - // Count floats in signature up to index - int fcnt=0; - for( int i=hidden ? 1 : 0; i<idx-2; i++ ) - if( tfp.arg(i) 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( sigidx-fcnt < cargs.length ) - return cargs[sigidx-fcnt]; - } - // Pass on stack slot(8 and higher) - if( maxArgSlot>0 ) throw Utils.TODO(); - return new RegMask(MAX_REG + 1 + sigidx); - } - - @Override public short maxArgSlot( TypeFunPtr tfp ) { - int icnt=0, fcnt=0; // Count of ints, floats - for( int i=hiddenSelf(tfp) ? 1 : 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 boolean hiddenSelf(TypeFunPtr tfp) { - return tfp.nargs() > 0 && tfp.arg(0) == TypePtr.PTR; - } - - 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(LRG lrg, String kind, byte round ) { return new SplitRISC(lrg, 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 AddNode add -> add(add); - case AndNode and -> and(and); - case BoolNode bool -> cmp(bool); - case CallNode call -> call(call); - case PtrToIntNode ptr -> new PtrToIntMach(ptr); - case GuardNode guard -> new GuardMach(guard); - case CallEndNode cend -> new CallEndMach(cend); - case CLInitNode init -> new CLInitNode(init); - case CProjNode c -> new CProjNode(c); - case ConstantNode con -> con(con); - case DivNode div -> div.mode()==2 ? new DivFRISC(div) : new DivRISC(div); - case EscapeNode esc -> new EscapeNode(esc); - case FunNode fun -> new FunRISC(fun); - case FunPtrNode fptr -> fptr(fptr); - case IfNode iff -> jmp(iff); - case LoadNode ld -> ld(ld); - case MemMergeNode mem -> new MemMergeNode(mem); - case MinusNode neg -> { - if( neg.mode()==2 ) throw Utils.TODO(); - yield new NegRISC(neg); - } - case MulNode mul -> mul.mode()==2 ? new MulFRISC(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 StartCUNode start -> new StartCUNode(start); - case StopCUNode stop -> new StopCUNode(stop); - case StartNode start -> new StartNode(start); - case StopNode stop -> new StopNode(stop); - case StoreNode st -> st(st); - case SubNode sub -> sub.mode()==2 ? new SubFRISC(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 add(AddNode add) { - if( add.mode()==2 ) - return new AddFRISC(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 FunPtrNode fptr - ? new CallRISC(call, (TypeFunPtr)fptr._type) - : call.fptr() instanceof ConstantNode con - ? new CallRISC(call, (TypeFunPtr)con._type) - : 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 ConstantNode.raw(con); // Default unknown caller inputs - String ext = con instanceof ExternNode ext0 ? ext0._extern : null; - return switch( con._con ) { - case TypeInteger ti -> { - // External; without knowing the size assume we have to load something - if( ext!=null ) yield new Int8RISC(con,ext); - 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,null); - } - // Load from constant pool - case TypeFloat tf -> new FltRISC(con,ext); - case TypeMemPtr tmp -> new TMPRISC(con,ext); - case TypeFunPtr tfp -> new TFPRISC(tfp,ext); - case TypeNil tn -> throw Utils.TODO(); - // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code. - case Type t -> t==Type.NIL ? new IntRISC(con) : ConstantNode.raw(con); - }; - } - - private Node fptr( FunPtrNode con ) { - return new TFPRISC(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) { - if( prj.in(0) instanceof CLInitNode ) return new ProjNode(prj); - TypeInteger ret = cReturn(prj); - if( ret!=null ) return new ExtendRISC(prj,ret); - return prj.in(0) instanceof StartNode ? new ProjNode(prj) : 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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java deleted file mode 100644 index 74af9266..00000000 --- a/chapter-modules/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( AddNode 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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java deleted file mode 100644 index c7d0cf9c..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java deleted file mode 100644 index 0e652a93..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java deleted file mode 100644 index ed65b0a8..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java deleted file mode 100644 index 84543e8f..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java deleted file mode 100644 index 3c1b567c..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java deleted file mode 100644 index dedbce45..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java deleted file mode 100644 index f97163eb..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java deleted file mode 100644 index 6bafbf37..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java +++ /dev/null @@ -1,55 +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; - final boolean _extern; - CallX86( CallNode call, TypeFunPtr tfp ) { - super(call); - _inputs.pop(); // Pop constant target - assert tfp.isConstant(); - _tfp = tfp; - _name = CodeGen.CODE.funcName(tfp.fidx()); - _extern = CodeGen.CODE.externFunc(tfp.fidx()) != null; - } - @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 _extern - ? x86_64_v2.callInMask(_tfp,i,fun()._maxArgSlot,CodeGen.CODE._cCallingConv) - : 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 boolean external() { return _extern; } - - @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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java deleted file mode 100644 index 5ef7a476..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java deleted file mode 100644 index 151f8ca8..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java deleted file mode 100644 index 4014045b..00000000 --- a/chapter-modules/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,_con.log_size(),_imm,ptr,idx,_off,_scale); - } else encVal(enc,_con,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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java deleted file mode 100644 index 7faea17b..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java deleted file mode 100644 index 7bbb5102..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java deleted file mode 100644 index 9f375a80..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java deleted file mode 100644 index 1a6ae123..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java deleted file mode 100644 index a79a1017..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java deleted file mode 100644 index 59f06341..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java +++ /dev/null @@ -1,39 +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 FltX86 extends ConstantNode implements MachNode, RIPRelSize{ - private byte _encSize; - final String _ext; - FltX86( ConstantNode con, String ext ) { super(con); _ext = ext; } - @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,_ext); } - - @Override public void encoding( Encoding enc ) { - if( _ext!=null ) throw Utils.TODO(); - _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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java deleted file mode 100644 index 0e44aee7..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.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.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); - _maxArgSlot = code._mach.maxArgSlot(sig(),code._callingConv); - // 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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java deleted file mode 100644 index ee357901..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java deleted file mode 100644 index 5a4ff4b2..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java deleted file mode 100644 index 2ffaa3d1..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java +++ /dev/null @@ -1,43 +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; -import com.seaofnodes.simple.util.Utils; - -// Integer constants -public class IntX86 extends ConstantNode implements MachNode { - final String _ext; - IntX86( ConstantNode con, String ext ) { super(con); _ext = ext; } - @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,_ext); } - @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 ) { - if( _ext!=null ) throw Utils.TODO(); - 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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java deleted file mode 100644 index 10cee059..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java deleted file mode 100644 index 4b00e4dd..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java deleted file mode 100644 index 5c472dbe..00000000 --- a/chapter-modules/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 _con instanceof TypeFloat ? x86_64_v2.XMASK : x86_64_v2.WMASK; } - @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, _con, 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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java deleted file mode 100644 index 2e18a058..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java deleted file mode 100644 index fcca7f8f..00000000 --- a/chapter-modules/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 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; - int size = mop instanceof StoreNode st ? st.storeSize() : 1<<_con.log_size(); - _sz = (char)('0'+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 new AssertionError("Do not call compute on "+getClass().getSimpleName()); } - @Override public Node idealize() { throw new AssertionError("Do not call idealize on "+getClass().getSimpleName()); } - - // 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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java deleted file mode 100644 index 74616db7..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java deleted file mode 100644 index 5dcb253d..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java deleted file mode 100644 index 6f3fcdeb..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java deleted file mode 100644 index 86610f6d..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java deleted file mode 100644 index d6a06c2f..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.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.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("\n"); // Multi-op trigger - sb.p("ldi ").p(code._mach.regs()[_arg2Reg]).p(" = #1\n"); - sb.p("call #calloc"); - } - -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java deleted file mode 100644 index 1e4e7c26..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java deleted file mode 100644 index 15f4824d..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java deleted file mode 100644 index bc8f0ea8..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java deleted file mode 100644 index 5cfdd804..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java deleted file mode 100644 index 046c7b4c..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java deleted file mode 100644 index 06755143..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java deleted file mode 100644 index e4467bb4..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java deleted file mode 100644 index cf6275dc..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java deleted file mode 100644 index 92493712..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java deleted file mode 100644 index 864c1b55..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java deleted file mode 100644 index 1e62243e..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java deleted file mode 100644 index 768d97e7..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java deleted file mode 100644 index 8a0d1b58..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java deleted file mode 100644 index 1ba20fee..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java deleted file mode 100644 index cb7694f9..00000000 --- a/chapter-modules/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(LRG lrg,String kind,byte round) { super(lrg,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,fun(enc).computeStackOffset(enc._code,reg),toFlags); - else - X86.flags(enc,reg,toFlags); - } else super.encoding(enc); - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java deleted file mode 100644 index 46f2cb16..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java +++ /dev/null @@ -1,45 +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( LRG lrg, String kind, byte round ) { super(lrg,kind,round, new Node[2]); } - @Override public RegMask regmap(int i) { return x86_64_v2.SPLIT_MASK; } - @Override public RegMask outregmap() { return x86_64_v2.SPLIT_MASK; } - - // Moves among GPRs, XMM registers, and stack slots. - @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 = fun(enc).computeStackOffset(enc._code,dst); - if( src >= x86_64_v2.MAX_REG ) { - // Rare stack-stack move. push [RSP+soff]; pop [RSP+doff] - int soff = fun(enc).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 = fun(enc).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); - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java deleted file mode 100644 index 9ffa25fc..00000000 --- a/chapter-modules/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,Integer.numberOfTrailingZeros(_sz-'0'),_imm,ptr,idx,_off,_scale); - else encVal(enc,_con,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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java deleted file mode 100644 index 29f0033a..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java deleted file mode 100644 index 8da34bf5..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java deleted file mode 100644 index e3563713..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java +++ /dev/null @@ -1,51 +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.TypeFunPtr; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; - -// Function constants -public class TFPX86 extends FunPtrNode implements MachNode, RIPRelSize { - private byte _opLen; - final String _ext; // External name - // Pointer to a Simple function - TFPX86( FunPtrNode fptr ) { this((TypeFunPtr)fptr._type,null); } - // Spill clone: copy inputs without registering their reverse edges; - // insertBefore will place and register the clone in its use block. - private TFPX86( TFPX86 fptr ) { super(fptr); _ext = fptr._ext; } - // Pointer to an Extern "C" function - TFPX86( TypeFunPtr fptr, String ext ) { super(fptr,CodeGen.CODE._start,null); _type = fptr; _ext = ext; } - @Override public String op() { return "ldx"; } - @Override public String label() { return op(); } - @Override public boolean isClone() { return true; } - @Override public TFPX86 copy() { return new TFPX86(this); } - @Override public RegMask regmap(int i) { return null; } - @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - @Override public void encoding( Encoding enc ) { - if( _ext==null ) enc.relo(this); - else enc.external(this,_ext); - _opLen=X86.rip(enc,0,0x8D,true,enc.reg(this)); - } - - @Override public byte encSize(int delta) { return _opLen; } - - // Delta is from opcode start, X86 expects it from opcode end - @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - enc.patch4(opStart + opLen - 4, delta - opLen); - } - - // Human-readable form appended to the SB. Things like the encoding, - // indentation, leading address or block labels not printed here. - // Just something like "ld4\tR17=[R18+12] // Load array base". - // General form: "op\tdst=src+src" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)); - if( _ext == null ) _type.print(sb.p(" #")); - else sb.p(_ext); - } - @Override public boolean eq(Node n) { return this==n; } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java deleted file mode 100644 index 682597f4..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.seaofnodes.simple.node.cpus.x86_64_v2; - -import com.seaofnodes.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{ - private byte _opLen; - final String _ext; - TMPX86( ConstantNode con, String ext ) { super(con); _ext = ext; } - @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,_ext); } - - @Override public void encoding( Encoding enc ) { - short dst = enc.reg(this); - // 0 or 1 for REX depending on the dst. - _opLen = X86.rip(enc,0,0x8D,true,dst); - if( _ext!=null ) enc.externalData(this,_ext); - else enc.largeConstant(this,((TypeMemPtr)_con)._obj, _opLen-4, 2/*ELF encoding PC32*/); - } - - // Delta is from opcode start - @Override public byte encSize(int delta) { return _opLen; } - - // 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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java deleted file mode 100644 index 591a51c3..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java deleted file mode 100644 index 2d989a50..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java deleted file mode 100644 index 86703903..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java deleted file mode 100644 index 3dc93bf6..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ /dev/null @@ -1,596 +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); } - @Override public RegMask callArgMask( TypeFunPtr tfp, int idx, int maxArgSlot, String conv ) { return callInMask(tfp,idx,maxArgSlot,conv); } - static RegMask callInMask( TypeFunPtr tfp, int idx, int maxArgSlot ) { - return callInMask(tfp,idx,maxArgSlot,CodeGen.CODE._callingConv); - } - static RegMask callInMask( TypeFunPtr tfp, int idx, int maxArgSlot, String conv ) { - if( idx==0 ) return RPC_MASK; - if( idx==1 ) return null; - if( idx-2 >= tfp.nargs() ) return null; // Anti-dependence - return switch( conv ) { - 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 maxArgSlot(tfp,CodeGen.CODE._callingConv); - } - @Override public short maxArgSlot( TypeFunPtr tfp, String conv ) { - return switch( conv ) { - 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). - int sigidx = idx-2; - if( hiddenSelf(tfp) ) { - if( sigidx==0 ) return null; - sigidx--; - } - if( sigidx >= 4 ) - return new RegMask(MAX_REG+maxArgSlot+sigidx); - return tfp.arg(idx-2) instanceof TypeFloat - ? XMMS8 [sigidx] - : WIN64_CALL[sigidx]; - } - static short maxArgSlotWin64(TypeFunPtr tfp) { - return (short)Math.max(4,tfp.nargs() - (hiddenSelf(tfp) ? 1 : 0)); - } - static short maxIncomingArgSlotWin64(TypeFunPtr tfp) { - return (short)Math.max(0,tfp.nargs() - (hiddenSelf(tfp) ? 1 : 0) - 4); - } - - // 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 sigidx = idx-2; - if( hiddenSelf(tfp) ) { - if( sigidx==0 ) return null; - sigidx--; - } - int icnt=0, fcnt=0; // Count of ints, floats - for( int i=hiddenSelf(tfp) ? 1 : 0; i<idx-2; i++ ) { - if( tfp.arg(i) 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=hiddenSelf(tfp) ? 1 : 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; - } - private static boolean hiddenSelf(TypeFunPtr tfp) { - return tfp.nargs() > 0 && tfp.arg(0) == TypePtr.PTR; - } - - 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 callerSave(CodeGen.CODE._callingConv); - } - @Override public long callerSave(String conv) { - return switch (conv) { - case "SystemV" -> SYSTEM5_CALLER_SAVE; - case "win64" -> WIN64_CALLER_SAVE; - default -> throw new IllegalArgumentException("Unknown calling convention: " + conv); - }; - } - @Override public String cCallingConv(String callingConv) { - return 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(LRG lrg, String kind, byte round ) { - return lrg._machDef!=null && lrg._machDef.outregmap().test(FLAGS) - ? new SplitFlagsX86(lrg,kind,round) - : new SplitX86(lrg,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 AddNode add -> add(add); - case AndNode and -> and(and); - case BoolNode bool -> cmp(bool); - case CLInitNode init -> new CLInitNode(init); - case CProjNode c -> new CProjNode(c); - case CallEndNode cend -> new CallEndMach(cend); - case CallNode call -> call(call); - case PtrToIntNode ptr -> new PtrToIntMach(ptr); - case GuardNode guard -> new GuardMach(guard); - case ConstantNode con -> con(con); - case DivNode div -> div.mode()==2 ? new DivFX86(div) : new DivX86(div); - case EscapeNode esc -> new EscapeNode(esc); - case FunNode fun -> new FunX86(fun); - case FunPtrNode fptr -> fptr(fptr); - case IfNode iff -> jmp(iff); - case LoadNode ld -> ld(ld); - case MemMergeNode mem -> new MemMergeNode(mem); - case MinusNode neg -> { - if( neg.mode()==2 ) throw Utils.TODO(); - yield new NegX86(neg); - } - case MulNode mul -> mul.mode()==2 ? new MulFX86(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 StartCUNode start -> new StartCUNode(start); - case StopCUNode stop -> new StopCUNode(stop); - case StartNode start -> new StartNode(start); - case StopNode stop -> new StopNode(stop); - case StoreNode st -> st(st); - case SubNode sub -> sub.mode()==2 ? new SubFX86(sub) : sub(sub); - case ToFloatNode tfn -> i2f8(tfn); - case ToIntegerNode cvt -> new F8ToIX86(cvt); - case XCtrlNode x -> ConstantNode.raw(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) { - if( add.mode()==2 ) - return new AddFX86(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); - // 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 _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 FunPtrNode con - ? new CallX86(call, (TypeFunPtr)con._type) - : call.fptr() instanceof ConstantNode con && con._type instanceof TypeFunPtr - ? new CallX86(call, (TypeFunPtr)con._type) - : 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()) && - (!(lhs instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value())) || - 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 ConstantNode.raw(con); // Default unknown caller inputs - String ext = con instanceof ExternNode ext0 ? ext0._extern : null; - return switch (con._con) { - case TypeInteger ti -> new IntX86(con,ext); - case TypeFloat tf -> new FltX86(con,ext); - case TypeMemPtr tmp -> new TMPX86(con,ext); - case TypeFunPtr tfp -> new TFPX86(tfp,ext); - case TypeNil tn -> throw Utils.TODO(); - // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code. - case Type t -> t == Type.NIL ? new IntX86(con,null) : ConstantNode.raw(con); - }; - } - - private Node fptr( FunPtrNode con ) { - return new TFPX86(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), - ConstantNode.raw(TypeInteger.ZERO), - (byte)(iff.in(1)._type instanceof TypeFloat ? 2 : 1))); - 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 ) { - if( prj.in(0) instanceof CLInitNode ) return new ProjNode(prj); - TypeInteger ret = cReturn(prj); - if( ret!=null ) return new ExtendX86(prj,ret); - return prj.in(0) instanceof StartNode ? new ProjNode(prj) : 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 && ti.isConstant() && imm32(ti.value()) ) { - val = null; - imm = (int) ti.value(); - assert imm == ti.value(); // In 32-bit range - } else { - val = xval; - } - return imm; - } - -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java b/chapter-modules/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java deleted file mode 100644 index 58506e9c..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java +++ /dev/null @@ -1,101 +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 com.seaofnodes.simple.util.BAOS; -import java.util.ArrayList; -import com.seaofnodes.simple.codegen.Encoding; -import java.util.HashMap; - -/** 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=_enc==null ? _code._cfg : _enc._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; - if( n instanceof EscapeNode ) return true; - return n.getClass()==ConstantNode.class; - } - @Override public boolean postAlloc() { return _code._phase.ordinal()>CodeGen.Phase.RegAlloc.ordinal() || - (_code._phase==CodeGen.Phase.RegAlloc && _code._regAlloc!=null && _code._regAlloc.done()); } - @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(n) : -1; } - @Override public int size(Node n) { return encoded() ? _enc.opLen(n)&255 : 0; } - private static ArrayList<Data> entries(Iterable<Encoding.Relo> relos, boolean ro) { - var data=new ArrayList<Data>(); - for( Encoding.Relo r : relos ) { - if( r.readOnly()!=ro ) continue; - int align=1<<r._align; - int size=r._t instanceof TypeStruct ? (r._structSize+align-1)&-align : align; - 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._cpool.size()>0 ) - pools.add(new Pool("Constant Pool",_enc._cpool.buf(),0,base,objects(true))); - base=(base+_enc._cpool.size()+15)&-16; - if( _enc._sdata.size()>0 ) - pools.add(new Pool("Static Data",_enc._sdata.buf(),0,base,objects(false))); - return pools; - } - private ArrayList<Data> objects(boolean ro) { - var data=new ArrayList<Data>(); - for( var obj : _enc._data.entries() ) - if( !obj.external && obj.readOnly==ro ) - data.add(new Data(obj,obj.symbol+": "+obj.type.str(),obj.alignment,obj.size)); - return data; - } - // Retain this diagnostic entry point for checking that pool printing never lays out types. - static int printConstantPool(int address, SB sb, BAOS bits, HashMap<Node,Encoding.Relo> relos, - boolean ro, String name) { - sb.p(com.seaofnodes.print.ASMPrinter.pool(new Pool(name,bits.buf(),0,address,entries(relos.values(),ro)))); - return address+bits.size(); - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/print/IRPrinter.java b/chapter-modules/src/main/java/com/seaofnodes/simple/print/IRPrinter.java deleted file mode 100644 index 531a549a..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/print/IRPrinter.java +++ /dev/null @@ -1,60 +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 StartCUNode ) return Kind.UNIT; - if( n instanceof StopCUNode ) return Kind.UNIT; - 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>>(); - for( var cu : code._compunits.values() ) { - var funs=new ArrayList<Node>(); - for( FunNode fun : code._linker ) - if( fun!=null && fun._compunit==cu ) funs.add(fun); - units.add(new com.seaofnodes.print.IRPrinter.Unit<>(cu._cname,cu._start,cu._stop,funs)); - } - return PRINT.program(code._start,code._stop,units); - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter-modules/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java deleted file mode 100644 index 9ca0ec51..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.seaofnodes.simple.print; - -import com.seaofnodes.graph.GraphAdapter; -import com.seaofnodes.graph.GraphSnapshot.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.type.TypeMem; - -/** Chapter 25's view of the IR, including partially constructed parse-time graphs. */ -public class SimpleGraphAdapter extends GraphAdapter<Node> { - - @Override protected boolean show(Node n) { - if( n != CodeGen.CODE.ZERO && n != CodeGen.CODE.XCTRL ) return true; - for( int i=0; i<n.nOuts(); i++ ) - if( n.out(i) != null ) return true; - return false; // The parser's keep is not a graph use. - } - - @Override protected String edgeName(Node n, int i) { - return n instanceof ScopeNode scope && i < scope._vars.size() - ? scope.var(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 Proj 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 StartCUNode ) return Kind.UNIT; - if( n instanceof FunNode ) return Kind.FUN; - // Start inherits Loop for whole-program analysis, but is not a source loop. - if( n instanceof StartNode ) return Kind.START; - if( n instanceof LoopNode ) return Kind.LOOP; - if( n instanceof RegionNode ) return Kind.REGION; - if( n instanceof PhiNode ) return Kind.PHI; - if( n instanceof CFGNode ) return Kind.CTRL; - return isMem(n) ? Kind.MEM : Kind.DATA; - } - - @Override protected Role role(Node n, int i) { - if( n instanceof ScopeNode || n instanceof ConstantNode || n instanceof FunPtrNode || - 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 Proj ) - return n instanceof CFGNode ? Role.CTRL : isMem(n) ? Role.MEM : Role.DATA; - // StartCU inherits Region, but slot 0 really connects it to Start. - if( n instanceof StartCUNode && i == 0 ) return Role.CTRL; - if( n instanceof RegionNode && i == 0 ) return Role.ASSOC; - // Preserve known slot roles even when an input is not attached/typed yet. - 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 EscapeNode && i >= 2) ) return Role.MEM; - Node def = n.in(i); - if( def instanceof CFGNode ) return Role.CTRL; - if( def != null && isMem(def) ) return Role.MEM; - return Role.DATA; - } - - private boolean isMem(Node n) { - // These Phi subclasses are memory even before their first type computation. - return n instanceof MemPhiNode || n instanceof BulkMemPhiNode || n.isMem() || n._type instanceof TypeMem; - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter-modules/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java deleted file mode 100644 index f2ed2f72..00000000 --- a/chapter-modules/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 25'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-modules/src/main/java/com/seaofnodes/simple/sys.java b/chapter-modules/src/main/java/com/seaofnodes/simple/sys.java deleted file mode 100644 index 45106770..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/type/Field.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/Field.java deleted file mode 100644 index ec4a588d..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/Field.java +++ /dev/null @@ -1,142 +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 { - static final Field[] EMPTY = new Field[0]; - - // 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 is only written in the constructor - public boolean _final; - // Native C storage, outside the containing object's layout. - public boolean _extern; - - private static final Ary<Field> FREE = new Ary<>(Field.class); - private Field(String fname, Type type, int alias, boolean xfinal, boolean external ) { super(TFLD); init(fname,type,alias,xfinal,external); } - private Field init(String fname, Type type, int alias, boolean xfinal, boolean external ) { - _fname = fname; - _t = type; - _alias = alias; - _final = xfinal; - _extern = external; - 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 external ) { - return FREE.isEmpty() ? new Field(fname,type,alias,xfinal,external) : FREE.pop().init(fname,type,alias,xfinal,external); - } - // Malloc-from - public Field malloc( ) { return malloc(_fname,null,_alias,_final,_extern); } - @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 ) { - return make(fname,type,alias,xfinal,false); - } - public static Field make( String fname, Type type, int alias, boolean xfinal, boolean external ) { - Field f = malloc(fname,type,alias,xfinal,external); - 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,_extern); - } - public Field makeFrom( boolean xfinal ) { - return xfinal == _final ? this : make(_fname,_t,_alias,xfinal,_extern); - } - - // Cycle-making-breaking - void setType(Type t) { assert _t ==null; _t = t; } - - public static final Field BOT = make(" ",Type.BOTTOM,-999,false); - public static final Field TEST = make("test",Type.NIL,-2,false); - public static final Field TEST2 = make("test",Type.NIL,-2,true); - public static final Field FLT = make("flt ",TypeFloat.F32,-2,false); - public static final Field EXT = make("test",TypeInteger.I32,-2,false,true); - public static void gather(ArrayList<Type> ts) { ts.add(TEST); ts.add(TEST2); ts.add(FLT); ts.add(EXT); } - - @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; - if( _extern!=fld._extern ) return BOT; - return make(_fname, _t.meet(fld._t ), _alias, _final | fld._final,_extern); - } - - @Override - Field xdual() { return malloc(_fname, _t.dual(),_alias,!_final,_extern); } - - @Override Field rdual() { - if( _dual!=null ) return dual(); - assert !_terned; - Field d = malloc(_fname,null,_alias,!_final,_extern); - (_dual = d)._dual = this; // Cross link duals - d._t = _t._terned ? _t.dual() : _t.rdual(); - return d; - } - - @Override boolean _isConstant() { return _t._isConstant(); } - @Override boolean _isFinal() { return _final && _t._isFinal(); } - @Override Field _makeRO() { return _final ? this : make(_fname, _t._makeRO(),_alias,true,_extern); } - @Override Field _close( String name, HashMap<String, Type> TYPES ) { return makeFrom( _t._close(name, TYPES )); } - - @Override Type _upgradeType(HashMap<String,Type> TYPES) { - return makeFrom(_t._upgradeType(TYPES)); - } - - // Override in subclasses - int hash() { return _fname.hashCode() ^ _t.hashCode() ^ _alias ^ (_final ? 1024 : 0) ^ (_extern ? 2048 : 0); } - - private boolean static_eq( Field f ) { - return _fname.equals(f._fname) && _alias==f._alias && _final==f._final && _extern==f._extern && _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) { - if( this==t ) return true; - Field f = (Field)t; - return static_eq(f) && _t.cycle_eq(f._t ); - } - - @Override public int nkids() { return 1; } - @Override public Type at( int idx ) { return _t; } - @Override public void set( int idx, Type t ) { _t = t; } - // Tags: final/!final; +alias+name+external-storage flag - - - @Override - public SB _print( SB sb, BitSet visit, boolean html ) { - sb.p(_extern ? "extern C " : "").p(_final?"":"!").p(_fname).p(":").p(_alias).p(" : "); - return _t ==null ? sb.p("---") : _t.print(sb,visit,html); - } - - @Override public String str() { return (_final?"":"!")+_fname; } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/Type.java deleted file mode 100644 index 1979368e..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/Type.java +++ /dev/null @@ -1,548 +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 = 5; // End of the Simple Types - - static final byte TSCALAR = 6; // Integer, float, memory pointer or function pointer - static final byte TPTR = 7; // All nil-able pointer 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 TCYCLIC =12; // Has internal pointers, needs recursive treatment - static final byte TMEMPTR =12; // Memory pointer to a struct type - static final byte TFUNPTR =13; // Function pointer; unique signature and code address (just a bit) - static final byte TMEM =14; // All memory (alias 0) or A slice of memory - with specific alias - static final byte TFLD =15; // Named fields in structs - static final byte TSTRUCT =16; // Structs; tuples with named fields - static final byte TTUPLE =17; // Tuples; finite collections of unrelated Types, kept in parallel - - static final byte TMAX =18; - - // Basic RTTI, useful for a lot of fast tests. - public final byte _type; - public boolean _terned; - - public boolean is_simple() { return _type <= TSIMPLE; } - public boolean is_nokids() { return _type < TCYCLIC; } - private static final String[] STRS = new String[]{"Bot","Top","Ctrl","~Ctrl","null","~nil"}; - protected Type(byte type) { - _type = type; // RTTI - _uid = (char)UID++; // A unique ID for every type - if( UID > 32000 ) - type = type; - assert _uid!=0; // Overflow - } - - 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); - TypeScalar.gather(ts); - 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") - public <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 is_nokids(); 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 - public Type at( int idx ) { throw Utils.TODO("Should not reach here: type has no children"); } - public void set( int idx, Type t ) { throw Utils.TODO("Should not reach here: type has no children"); } - public int nkids() { 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) { - assert _terned && t._terned; - // 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); - // TypeConAry meet its element type is OK - 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); - // Two distinct scalar families meet at the scalar envelope. - if( this instanceof TypeScalar scalar0 && t instanceof TypeScalar scalar1 ) - return scalar0.smeet(scalar1); - // Mixing 2 unrelated types - return Type.BOTTOM; - } - - // 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 || _type==TXNIL ) { - if( t instanceof TypeNil ptr ) return _type==TNIL ? ptr.meet0() : ptr.meetX(); - if( t instanceof TypeScalar scalar ) { - if( t._type==TSCALAR ) return t.isHigh() ? this : t; - return TypeScalar.BOT; - } - return t._type==(_type==TNIL ? TXNIL : 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: unknown simple type"); - }); - } - - - // ---------------------------------------------------------- - // Our lattice is defined with a MEET and a DUAL. - // JOIN is dual of meet of both duals. - public final Type join(Type t) { - if( this==t ) return this; - return dual().meet(t.dual()).dual(); - } - - // True if this "isa" t; e.g. 17 isa TypeInteger.BOT - public boolean isa( Type t ) { return meet(t)==t; } - - public Type nonZero() { return this==NIL ? TypePtr.XNPTR : TypePtr.NPTR; } - - // Make a zero version of this type, 0 for integers and null for pointers. - public Type makeZero() { return Type.NIL; } - - // 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 accumulate 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() { return recurClose(null); } - Type recurClose(Type[] types) { - 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; - if( types!=null ) - for( int i=0; i<types.length; i++ ) - types[i] = INTERN.get(types[i]); - - // 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; } - - final <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 - if( x!=this ) return x.delayFree(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 || _terned ? 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? - // 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; } - - public final boolean isFinal() { return recurClose(recurOpen()._isFinal()); } - boolean _isFinal() { assert is_nokids(); return true; } - - public final Type makeRO() { - if( isFinal() ) return this; - return recurOpen()._makeRO().recurClose(); - } - Type _makeRO() { return this; } - - /** Declared shape used when a value is published into mutable memory. */ - public final Type makeStorage() { - return recurOpen()._makeStorage().recurClose(); - } - Type _makeStorage() { assert is_simple(); return BOTTOM; } - - // Bulk close-over all recursive types - public static TypeStruct[] closeOver(TypeStruct[] ts, HashMap<String,Type> TYPES) { - BOTTOM.recurOpen(); - for( TypeStruct t : ts ) - t._close(null, TYPES); - for( int i=0; i<ts.length; i++ ) { - // Parser only tracks the mutable name of arrays, so the closeOver - // returns that one. However, internal types might have immutable - // versions. - String name = ts[i].isAry() ? (ts[i]._name+"!").intern() : ts[i]._name; - ts[i] = (TypeStruct)VISIT.get(name); - } - BOTTOM.recurClose(ts); - return ts; - } - - Type _close( String name, HashMap<String, Type> TYPES ) { return this; } - public Type widen() { return this; } - - // Replace recursively all TypeBuilders with cyclic TypeStructs - public Type upgradeType(HashMap<String,Type> TYPES) { - return recurOpen()._upgradeType(TYPES).recurClose(); - } - Type _upgradeType(HashMap<String,Type> TYPES) { assert is_nokids(); 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-modules/src/main/java/com/seaofnodes/simple/type/TypeConAry.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeConAry.java deleted file mode 100644 index 860cefc5..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeConAry.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.util.BAOS; -import com.seaofnodes.simple.util.Utils; -import java.util.ArrayList; -import java.util.HashMap; - -/** - * Represents a constant array of primitives - */ -public abstract class TypeConAry<A> extends TypeScalar { - 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 _makeStorage() { return this; } - - // 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( BAOS baos ) { throw Utils.TODO("Should not reach here: abstract constant array cannot be written"); } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java deleted file mode 100644 index bddcb1fb..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.util.BAOS; -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( BAOS baos ) { baos.write(_ary); } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java deleted file mode 100644 index b1b061de..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/type/TypeFloat.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeFloat.java deleted file mode 100644 index 8638b8c9..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeFloat.java +++ /dev/null @@ -1,111 +0,0 @@ -package 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.ArrayList; -import java.util.HashMap; - -/** - * Float Type - */ -public class TypeFloat extends TypeScalar { - - // - 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("Should not reach here: unknown float size"); - }; - } - 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 _isConstant() { return _sz==0; } - - @Override Type _makeStorage() { - if( _isConstant() ) return isF32() ? F32 : F64; - return isHigh() ? dual() : this; - } - - @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-modules/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java deleted file mode 100644 index 301fcc8b..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java +++ /dev/null @@ -1,313 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.util.*; -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; - boolean _open; // Extra args are true:scalar BOT, false:scalar TOP - // Set of functions. Bit zero is used for mutable/immutable safety. Sign - // bit repeats infinitely. - public int[] _fidxs; - - private static final Ary<TypeFunPtr> FREE = new Ary<>(TypeFunPtr.class); - private TypeFunPtr(byte nil) { super(TFUNPTR,nil); } - private TypeFunPtr init(byte nil, boolean open, Type[] sig, Type ret, int[] fidxs ) { - assert sig != null; - _nil = nil; - _open = open; - _sig = sig; - _ret = ret; - assert _fidxs == null; - _fidxs= XInt.intern(fidxs); - return this; - } - static TypeFunPtr malloc( byte nil, boolean open, Type[] sig, Type ret, int[] fidxs ) { - return (FREE.isEmpty() ? new TypeFunPtr(nil) : 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; - fun._fidxs = null; - 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, int[] fidxs ) { - TypeFunPtr fun = malloc(nil,open,sig,ret,fidxs); - TypeFunPtr f2 = fun.intern(); - if( f2==fun ) return fun; - return VISIT.isEmpty() ? f2.free(fun) : f2.delayFree(fun); - } - // Trim trailing args which match the default. - private static TypeFunPtr makeAdjusted( byte nil, boolean open, Type[] sig, Type ret, int[] fidxs ) { - Type def = open ? TypeScalar.BOT : TypeScalar.TOP; - int nargs = sig.length; - while( nargs > 0 && sig[nargs-1] == def ) - nargs--; - if( nargs != sig.length ) - sig = nargs == 0 ? ARG_EMPTY : Arrays.copyOf(sig,nargs); - return make(nil,open,sig,ret,fidxs); - } - public static TypeFunPtr make( boolean nil, Type[] sig, Type ret ) { return makeAdjusted((byte)(nil ? 3 : 2),true,sig,ret, XInt.FULL); } - public static TypeFunPtr make1( byte nil, boolean open, Type[] sig, Type ret, int fidx ) { - return makeAdjusted(nil,open,sig,ret,XInt.make(fidx)); - } - - - @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 make1(_nil,_open,_sig,_ret,fidx); } - public TypeFunPtr makeFrom( Type arg, int idx ) { - // Alter named arg - Type[] sig = _sig.clone(); - sig[idx] = arg; - return make(_nil,_open,sig,_ret,_fidxs); - } - @Override public Type nonZero() { return makeFrom((byte)(_nil <= 1 ? 1 : 2)); } - - 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, XInt.FULL); - public static TypeFunPtr TEST = make((byte)2,true,TINTMEM,TypeInteger.BOT, XInt.SET1); - public static TypeFunPtr TEST0 = make((byte)3,true,TINTMEM,TypeInteger.BOT, XInt.SET3); - public static TypeFunPtr MAIN = make((byte)3,true,TINT ,Type.BOTTOM, XInt.FULL); // Main can return anything - public static TypeFunPtr CALLOC= make((byte)3,true,TINTINT,TypeMemPtr .BOT, XInt.FULL); - public static void gather(ArrayList<Type> ts) { - ts.add(TEST); - ts.add(TEST0); - ts.add(BOT); - ts.add(MAIN); - ts.add(CALLOC); - ts.add(make((byte)2,false,TINT,TypeInteger.I32, XInt.SET1)); - ts.add(make((byte)2,true ,TINT,TypeInteger.BOT, XInt.SET3)); - ts.add(make1((byte)2,true, - new Type[]{TypeInteger.BOT}, - TypeInteger.BOT, 65)); - ts.add(make1((byte)2,true, - new Type[]{TypeInteger.BOT, TypeScalar.BOT}, - TypeInteger.BOT, 65)); - // Explicit trailing defaults must agree with the implicit argument tail. - for( boolean open : new boolean[]{false,true} ) { - Type def = open ? TypeScalar.BOT : TypeScalar.TOP; - ts.add(make1((byte)2,open,TINT,TypeInteger.I32,1)); - ts.add(make1((byte)2,open,new Type[]{TypeInteger.BOT,def,def},TypeInteger.I32,1)); - ts.add(make1((byte)2,open,new Type[]{def,def},TypeInteger.I32,1)); - ts.add(make1((byte)2,open,new Type[]{def,TypeInteger.BOT},TypeInteger.I32,1)); - } - } - - private static final Type[] ARG_EMPTY = new Type[0]; - @Override - Type xmeet(Type t) { - TypeFunPtr that = (TypeFunPtr) t; - boolean open = _open | that._open; - Type def0 = this._open ? TypeScalar.BOT : TypeScalar.TOP; - Type def1 = that._open ? TypeScalar.BOT : TypeScalar.TOP; - Type def = open ? TypeScalar.BOT : TypeScalar.TOP; - - int nargs = Math.max(_sig.length,that._sig.length); - Type[] args = new Type[nargs]; - - for( int i=0; i<nargs; i++ ) { - Type t0 = i < this._sig.length ? this._sig[i] : def0; - Type t1 = i < that._sig.length ? that._sig[i] : def1; - args[i] = t0.meet(t1); - } - - while( nargs>0 && args[nargs-1] == def ) - nargs--; - - if( nargs != args.length ) - args = nargs == 0 ? ARG_EMPTY : Arrays.copyOf(args,nargs); - - return make(xmeet0(that), open, args, _ret.meet(that._ret), XInt.meet(_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(), XInt.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, XInt.dual(_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; deep-dual when crossing the centerline - @Override public Type meet0() { - if( _nil==3 ) return this; - if( _nil==2 ) return make((byte)3,_open,_sig,_ret,_fidxs); - - return make((byte)3,_open,_sig,_ret,_fidxs); - } - - @Override public boolean isHigh() { return _nil <= 1 || (_nil==2 && _fidxs== XInt.EMPTY); } - - @Override public boolean _isConstant() { return (_nil==2 && XInt.isConstant(_fidxs)) || (_nil==3 && _fidxs== XInt.EMPTY); } - - @Override boolean _isFinal() { return true; } - @Override TypeFunPtr _makeStorage() { return this; } - - @Override TypeFunPtr _close( String name, HashMap<String, Type> TYPES ) { - Type[] sig = new Type[_sig.length]; - TypeFunPtr fun = malloc(_nil,true,sig,null,_fidxs); - // Now start the recursion - fun._ret = _ret._close(name, TYPES ); - for( int i=0; i<sig.length; i++ ) - sig[i] = _sig[i]._close(name, TYPES ); - return fun; - } - - // Replace recursively all TypeBuilders with cyclic TypeStructs - @Override Type _upgradeType(HashMap<String,Type> TYPES) { - Type[] sig = new Type[_sig.length]; - TypeFunPtr fun = malloc(_nil,_open,sig,null,_fidxs); - // Now start the recursion - fun._ret = _ret._upgradeType(TYPES); - for( int i=0; i<sig.length; i++ ) - sig[i] = _sig[i]._upgradeType(TYPES); - return fun; - } - - @Override public int log_size() { return 2; } // (1<<2)==4-byte pointers - - public Type arg(int i) { return _sig[i]; } - public int[] fidxs() { return _fidxs; } - public Type ret() { return _ret; } - public int nargs() { return _sig.length; } - public int fidx() { return XInt.onlyBit(_fidxs); } - public int nfcns() { - if( XInt.isHigh(_fidxs) ) return Integer.MAX_VALUE; // Infinite choices - return XInt.bitCount(_fidxs); - } - - - // Used to walk over type structures - @Override public int nkids() { return _sig.length+1; } - @Override public Type at( int idx ) { - return idx == _sig.length ? _ret : _sig[idx]; - } - @Override public void set( int idx, Type t ) { - if( idx == _sig.length ) _ret = t; - else _sig[idx] = t; - } - - - @Override - int hash() { - long hash = _ret.hashCode() ^ XInt.hash(_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()+"}"; - } - - @Override - 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==null ? "---" : t.str()).p(" "); // Short form in signature - if( !_open ) sb.p("^ "); - _ret.print(sb.p(html ? "→ " : "-> "),visit,html).p(" #"); - // Print fidxs - return sb.p(printFIDX()).p("}").p(q()); - } - String printFIDX() { - String tilde = isHigh() ? "~" : ""; - String fidx = XInt.str(_fidxs); - 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-modules/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeInteger.java deleted file mode 100644 index a2cf08b0..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeInteger.java +++ /dev/null @@ -1,185 +0,0 @@ -package 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.ArrayList; -import java.util.HashMap; - -/** - * Integer Type - */ -public class TypeInteger extends TypeScalar { - /** - * 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( ) { - return _widen < 3 ? make(_min,_max, (byte)(_widen+1)) : BOT; - } - // 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() ) return log(_min); // just const here - int lo = log(_min); - int hi = log(_max); - return Math.max(lo,hi); - } - - private static int log(long x) { - if( -0xFFL <= x && x <= 0xFFL) return 0; - if( -0xFFFFL <= x && x <= 0xFFFFL) return 1; - if(-0xFFFFFFFFL <= x && x <= 0xFFFFFFFFL) return 2; - return 3; - } - - 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 Type _makeStorage() { - 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-modules/src/main/java/com/seaofnodes/simple/type/TypeMem.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeMem.java deleted file mode 100644 index 4234a5e5..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeMem.java +++ /dev/null @@ -1,229 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.util.*; -import java.util.ArrayList; -import java.util.BitSet; -import java.util.HashMap; - - -/** - * 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 - public boolean _one; // Singleton memory, corresponding to TypeMemPtr._one. - public boolean _clz; // Class-object singleton memory; true singletons stay constant after construction. - public boolean _final;// Memory is only set in the constructor - - // Escaped aliases and functions - escaped into this memory. These are - // reachable from some chain of indirections from `_t`. Bit zero is - // used for mutable/immutable safety. Sign bit repeats infinitely. - public int[] _escFs; - public int[] _escAs; - - static public int CNT; - - private static final Ary<TypeMem> FREE = new Ary<>(TypeMem.class); - - private TypeMem() { super(TMEM); CNT++; } - private TypeMem init(int alias, Type t, boolean one, boolean clz, boolean xfinal, int[] fidxs, int[] aliases) { - _alias = alias; - _t = t; - _one = one; - _clz = clz; - // Public bulk memory is an aggregate of otherwise-unsplit aliases; - // a single final bit cannot describe it. Keep the lattice-dual high - // form distinct, and retain finality for private singleton memory and - // precise alias slices. - _final = t!=null && alias==1 && !one && !t.isHigh() ? false : xfinal; - - assert _escFs == null && _escAs == null; - _escFs = XInt.intern(fidxs); - _escAs = XInt.intern(aliases); - return this; - } - @Override TypeMem free(Type t) { - TypeMem mem = (TypeMem)t; - mem._alias= -99; - mem._dual = null; - mem._hash = 0; - mem._t = null; - mem._one = false; - mem._clz = false; - mem._final = false; - mem._escFs = null; - mem._escAs = null; - FREE.push(mem); - return this; - } - @Override boolean isFree() { return _alias == -99; } - - public static TypeMem malloc(int alias, Type t, boolean one, boolean clz, boolean xfinal, int[] fidxs, int[] aliases) { - return (FREE.isEmpty() ? new TypeMem() : FREE.pop()).init(alias,t,one,clz,xfinal,fidxs,aliases); - } - public static TypeMem make(int alias, Type t, boolean one, boolean clz, boolean xfinal, int[] fidxs, int[] aliases) { - if( fidxs ==null ) fidxs = XInt.EMPTY; - if( aliases==null ) aliases= XInt.EMPTY; - TypeMem f = malloc(alias,t,one,clz,xfinal, fidxs, aliases); - TypeMem f2 = f.intern(); - if( f2==f ) return f; - return VISIT.isEmpty() ? f2.free(f) : f2.delayFree(f); - } - public static TypeMem make(int alias, Type t) { return make(alias,t,false,false,false,null,null); } - // Make a private memory - public static TypeMem makePrivate(Type t) { return make(1,t,true,false,false,escapeFIDX(XInt.EMPTY,t), escapeAlias(XInt.EMPTY,t)); } - - public TypeMem makeFrom(int alias) { return make(alias,_t,_one,_clz,_final,_escFs,_escAs); } - public TypeMem makeFrom(int[] fidxes, int[] aliases) { - return make(_alias,_t,_one,_clz,_final,fidxes,aliases); - } - // Existing escapes plus `t` escapes - public TypeMem escapes(Type t) { - return make(_alias,_t,_one,_clz,_final, escapeFIDX(_escFs,t), escapeAlias(_escAs,t)); - } - // Existing escapes plus `t` escapes - public TypeMem escapesFrom(Type t) { - return make(_alias,_t,_one,_clz,_final, escapeFIDX(_escFs,t), escapeAlias(_escAs,t)); - } - public TypeMem escapesAliases(Type t) { - return make(_alias,_t,_one,_clz,_final, _escFs, escapeAlias(_escAs,t)); - } - - public static final TypeMem TOP = make(1, Type.TOP ,true ,true, true , XInt.EMPTY, XInt.EMPTY); - public static final TypeMem BOT = make(1, Type.BOTTOM,false,false,true , XInt.FULL , XInt.FULL ); - public static final TypeMem START= make(1,Type.BOTTOM,false,false,false, XInt.EMPTY, XInt.EMPTY); - public static final TypeMem SELF_MEM = make(1, TypeStruct.BOT); - - public static void gather(ArrayList<Type> ts) { - ts.add(make(2,Type.NIL)); - ts.add(make(2,TypeInteger.ZERO)); - ts.add(BOT); - ts.add(SELF_MEM); - ts.add(START); - ts.add(make(2,TypeInteger.ZERO,true,false,true,XInt.EMPTY,XInt.make(2))); - ts.add(make(2,TypeInteger.BOT,false,false,false,XInt.EMPTY,XInt.FULL)); - } - - @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; - // Treat a alias-1 has either very-high or very-low, according to "_t" - int alias = _alias==that._alias ? _alias // Same-same - : this._alias==1 && this._t.isHigh() ? that._alias // this is high-1 alias, so take that - : that._alias==1 && that._t.isHigh() ? this._alias // that is high-1 alias, so take this - : 1; - Type mt = _t.meet(that._t); - int[] fidxs = XInt.meet( _escFs, that._escFs ); - int[] aliases= XInt.meet( _escAs, that._escAs ); - // Singleton and class facts are kept only when both sides agree. - // Finality is a low-side fact, and flips on the dual side. - boolean highAlias1 = (this._alias==1 && this._t.isHigh()) || (that._alias==1 && that._t.isHigh()); - boolean xfinal = highAlias1 ? (_final & that._final) : (_final | that._final); - return make(alias, mt, _one & that._one, _clz & that._clz, xfinal, fidxs, aliases); - } - - @Override - Type xdual() { return malloc(_alias,_t.dual(),!_one,!_clz,!_final, XInt.dual(_escFs), XInt.dual(_escAs)); } - - @Override TypeMem rdual() { - if( _dual!=null ) return dual(); - assert !_terned; - TypeMem d = malloc(_alias,null,!_one,!_clz,!_final, XInt.dual(_escFs), XInt.dual(_escAs)); - (_dual = d)._dual = this; // Cross link duals - d._t = _t._terned ? _t.dual() : _t.rdual(); - if( d._alias==1 && !d._one ) { assert !d._t.isHigh(); d._final = false; } - return d; - } - - @Override public TypeMem makeZero() { return TOP; } - @Override public boolean isHigh() { return _t.isHigh(); } - @Override boolean _isConstant() { return _one && _clz && _alias!= 1 && _t._isConstant(); } - @Override public int log_size() { throw Utils.TODO("Should not reach here: memory has no scalar storage size"); } - @Override boolean _isFinal() { throw Utils.TODO("Should not reach here"); } - @Override TypeMem _makeStorage() { throw Utils.TODO("Should not reach here"); /*return make(_alias,_t._makeStorage(),_one,_clz,_final,_escFs,_escAs);*/ } - - @Override TypeMem _close( String name, HashMap<String, Type> TYPES ) { return malloc(_alias,_t._close(name, TYPES ),_one,_clz,_final,_escFs,_escAs); } - - @Override Type _upgradeType(HashMap<String,Type> TYPES) { - return make(_alias,_t._upgradeType(TYPES),_one,_clz,_final,_escFs,_escAs); - } - - @Override int hash() { return 9876543 + _alias + _t.hashCode() + (_one ? 8196 : 0) + (_clz ? 4098 : 0) + (_final ? 16392 : 0) + XInt.hash(_escFs) + XInt.hash(_escAs); } - - @Override boolean eq(Type t) { - TypeMem that = (TypeMem) t; // Invariant - return _alias == that._alias && _t == that._t && _one == that._one && _clz == that._clz && _final == that._final && _escFs == that._escFs && _escAs == that._escAs; - } - - @Override boolean cycle_eq(Type t) { - TypeMem that = (TypeMem) t; // Invariant - return _alias == that._alias && _one == that._one && _clz == that._clz && _final == that._final && _escFs == that._escFs && _escAs == that._escAs && _t.cycle_eq(that._t); - } - - @Override public int nkids() { return 1; } - @Override public Type at( int idx ) { return _t; } - @Override public 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( !_final ) sb.p('!'); // Mutable memory - if( _one ) sb.p(_clz ? 'z' : '-'); - if( _alias==0 ) return sb.p(_t._type==TTOP ? "TOP" : "BOT"); - sb.p(_alias).p(":["); - _t.print(sb,visit,html).p(","); - XInt.str(sb,_escFs).p(","); - return XInt.str(sb,_escAs).p("]"); - } - - @Override public String str() { - if( this==BOT ) return "MemBot"; - if( this==TOP ) return "MemTop"; - SB sb = new SB().p('#'); - if( !_final ) sb.p('!'); // Mutable memory - if( _one ) sb.p(_clz ? 'z' : '-'); - if( _alias==0 ) return sb.p(_t._type==TTOP ? "TOP" : "BOT").toString(); - sb.p(_alias).p(":[").p(_t.str()).p(","); - XInt.str(sb,_escFs).p(","); - return XInt.str(sb,_escAs).p("]").toString(); - } - - - // Tracking bulk escaped aliases and fidxs - - // Escape all FIDXs from t - private static int[] escapeFIDX( int[] xs, Type t ) { - return switch(t) { - case TypeMemPtr tmp -> escapeFIDX(xs,tmp._obj); - case TypeStruct ts -> ts._open && !ts.isAry() - ? XInt.FULL // Open structs can have any fields - : XInt.meet(xs,ts.fidxs()); - case TypeFunPtr tfp -> XInt.meet(xs,tfp.fidxs()); - case TypeMem mem -> XInt.meet(xs,mem._escFs); - case Type tt -> tt == BOTTOM ? XInt.FULL : xs; - }; - } - - private static int[] escapeAlias( int[] xs, Type t ) { - return switch(t) { - case TypeMemPtr tmp -> escapeAlias(xs,tmp._obj); - case TypeStruct ts -> ts._open && !ts.isAry() - ? XInt.FULL // Open structs can have any fields - : XInt.meet(xs,ts.aliases()); - case TypeMem mem -> XInt.meet(xs,mem._escAs); - case Type tt -> tt == BOTTOM ? XInt.FULL : xs; - }; - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java deleted file mode 100644 index 73ca6190..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java +++ /dev/null @@ -1,179 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.AryInt; -import com.seaofnodes.simple.util.SB; -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 pointer value; the pointer itself is constant. - public boolean _pub; // Escaped/public pointer. False means constructor-private. - - private static final Ary<TypeMemPtr> FREE = new Ary<>(TypeMemPtr.class); - private TypeMemPtr(byte nil, TypeStruct obj, boolean one, boolean pub) { super(TMEMPTR,nil); init(nil,obj,one,pub); } - private TypeMemPtr init(byte nil, TypeStruct obj, boolean one, boolean pub) { _nil=nil; _obj=obj; _ro=obj==TypeStruct.BOT; _one=one; _pub=pub; return this; } - // Return a filled-in TypeMemPtr; either from free list or alloc new. - static TypeMemPtr malloc(byte nil, TypeStruct obj, boolean one, boolean pub) { - return FREE.isEmpty() ? new TypeMemPtr(nil,obj,one,pub) : FREE.pop().init(nil,obj,one,pub); - } - - // All fields directly listed - public static TypeMemPtr make(byte nil, TypeStruct obj, boolean one) { - return make(nil,obj,one,true); - } - public static TypeMemPtr make(byte nil, TypeStruct obj, boolean one, boolean pub) { - return make(nil,obj,one, pub,obj==TypeStruct.BOT); - } - private static TypeMemPtr make(byte nil, TypeStruct obj, boolean one, boolean pub, boolean ro) { - TypeMemPtr tmp = malloc(nil, obj, one, pub).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._one = false; - tmp._pub = true; - 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 static TypeMemPtr makePrivate (TypeStruct obj) { return make((byte)2, obj, false, false); } - - public TypeMemPtr makeFrom(TypeStruct obj) { return obj==_obj ? this : make(_nil, obj, _one, _pub,_ro); } - public TypeMemPtr makeNullable() { return makeFrom((byte)3); } - /** Inferred mutable local: nullable, public and non-singleton. */ - public TypeMemPtr makeVar() { return (TypeMemPtr)makeStorage(); } - /** Public-memory shape: nullable and non-singleton, preserving the referent. */ - @Override TypeMemPtr _makeStorage() { return make(_nil,_obj._makeStorage(),false,true,_ro); } - @Override TypeMemPtr makeFrom(byte nil) { return nil==_nil ? this : make(nil, _obj, _one, _pub,_ro); } - @Override public Type nonZero() { return makeFrom((byte)(_nil <= 1 ? 1 : 2)); } - - 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, _pub); - 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(); - // Can't keep _one if mixing two unequal singletons; the result is not - // either singleton. Once either side is public, the meet is public. - boolean one = _one && that._one && (_obj==that._obj || !(_obj.isConAry() || that._obj.isConAry())); - return make(xmeet0(that), (TypeStruct)_obj.meet(that._obj), one, _pub | that._pub,_ro | that._ro); - } - - @Override - TypeMemPtr xdual() { return malloc( dual0(), _obj.dual(), !_one, !_pub).access(!_ro); } - - @Override TypeMemPtr rdual() { - if( _dual!=null ) return dual(); - assert !_terned; - TypeMemPtr d = malloc(dual0(), null, !_one, !_pub).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,true,_ro); } - - @Override boolean _isConstant() { return _one; } - @Override boolean _isFinal() { return _ro; } - @Override TypeMemPtr _makeRO() { return withAccess(true); } - @Override TypeMemPtr _close( String name, HashMap<String, Type> TYPES ) { return malloc(_nil,_obj._close(name, TYPES ),_one,_pub).access(_ro); } - - @Override Type _upgradeType(HashMap<String,Type> TYPES) { - return makeFrom((TypeStruct)_obj._upgradeType(TYPES)); - } - - @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) ^ (_pub ? 4096 : 0); } - - @Override boolean eq(Type t) { - TypeMemPtr ptr = (TypeMemPtr)t; // Invariant - return _ro==ptr._ro && super.eq(ptr) && _one == ptr._one && _pub == ptr._pub && _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 && _pub == ptr._pub && _obj.cycle_eq(ptr._obj); - } - - @Override public int nkids() { return 1; } - @Override public Type at( int idx ) { return _obj; } - @Override public 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==null?"---":_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-modules/src/main/java/com/seaofnodes/simple/type/TypeNil.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeNil.java deleted file mode 100644 index 2c1c1d76..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeNil.java +++ /dev/null @@ -1,61 +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 TypeScalar { - // 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; } - - 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-modules/src/main/java/com/seaofnodes/simple/type/TypePtr.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypePtr.java deleted file mode 100644 index 7e9e70e4..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypePtr.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.util.AryInt; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import java.util.ArrayList; -import java.util.BitSet; -import java.util.HashMap; - -/** - * 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("Should not reach here: plain pointer nil state is canonical"); } - - @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); } - - @Override TypePtr _makeStorage() { 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-modules/src/main/java/com/seaofnodes/simple/type/TypeRPC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeRPC.java deleted file mode 100644 index 7acc99de..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeRPC.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.util.Ary; -import java.util.*; - -/** - * Return Program Control or Return PC or RPC - */ -public class TypeRPC extends Type { - - // A set of CallEndNode IDs (or StopNode); commonly just one. High-bit - // XInt sets represent complements/infinite sets. - public int[] _rpcs; - - private static final Ary<TypeRPC> FREE = new Ary<>(TypeRPC.class); - private TypeRPC init(int[] rpcs) { _rpcs = XInt.intern(rpcs); return this; } - private TypeRPC() { super(TRPC); } - private static TypeRPC malloc(int[] rpcs) { - return (FREE.isEmpty() ? new TypeRPC() : FREE.pop()).init(rpcs); - } - private static TypeRPC make(int[] rpcs) { - TypeRPC rpc = malloc(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) { - return make(XInt.make(cend)); - } - - public int rpc() { - return XInt.onlyBit(_rpcs); - } - - - public static final TypeRPC BOT = make(XInt.FULL); - private static final TypeRPC TEST2 = constant(2); - private static final TypeRPC TEST3 = constant(2); - public static final TypeRPC EMPTY = make(XInt.EMPTY); - - public static void gather(ArrayList<Type> ts) { ts.add(BOT); ts.add(TEST2); ts.add(TEST3); } - - @Override public TypeRPC makeZero() { return EMPTY; } - - - @Override public String str() { - if( _rpcs == XInt.FULL ) return "$[ALL]"; - if( _rpcs == XInt.EMPTY ) return "$[]"; - return "$"+XInt.str(_rpcs); - } - - @Override - public TypeRPC xmeet(Type other) { - TypeRPC rpc = (TypeRPC)other; - return make(XInt.meet(_rpcs,rpc._rpcs)); - } - - @Override TypeRPC xdual() { return malloc(XInt.dual(_rpcs)); } - @Override Type rdual() { - TypeRPC d = xdual(); - (_dual = d)._dual = this; - return d; - } - - @Override boolean _isConstant() { return XInt.isConstant(_rpcs); } - @Override TypeRPC _makeStorage() { return this; } - - - @Override - int hash() { return XInt.hash(_rpcs); } - @Override - public boolean eq( Type t ) { - TypeRPC rpc = (TypeRPC)t; // Contract - return _rpcs==rpc._rpcs; - } - -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeScalar.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeScalar.java deleted file mode 100644 index e369e6a5..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeScalar.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.util.SB; - -import java.util.ArrayList; -import java.util.BitSet; -import java.util.HashMap; - -/** - * The envelope for values which can live in an ordinary register or memory - * field: integers, floats, memory pointers, and function pointers. - * - * `BOT` means any scalar value and `TOP` means no scalar value. They sit - * strictly inside the global {@link Type#BOTTOM}/{@link Type#TOP} pair, which - * also includes non-values such as control and memory. - */ -public class TypeScalar extends Type { - protected TypeScalar(byte type) { super(type); } - private TypeScalar() { this(TSCALAR); } - - public static final TypeScalar BOT = new TypeScalar().intern(); - public static final TypeScalar TOP = BOT.dual(); - - public static void gather(ArrayList<Type> ts) { ts.add(BOT); } - - /** Meet two different scalar families. */ - final Type smeet(TypeScalar scalar) { - if( _type == TSCALAR ) return isHigh() ? scalar : this; - if( scalar._type == TSCALAR ) return scalar.isHigh() ? this : scalar; - return BOT; - } - - @Override Type xmeet(Type t) { - TypeScalar scalar = (TypeScalar)t; - return isHigh() ? scalar : this; - } - - @Override TypeScalar xdual() { return new TypeScalar(); } - @Override public boolean isHigh() { return this == TOP; } - @Override Type _makeStorage() { return BOT; } - - - @Override public String str() { return isHigh() ? "~scalar" : "scalar"; } - @Override SB _print(SB sb, BitSet visit, boolean html) { return sb.p(str()); } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeStruct.java deleted file mode 100644 index 67169009..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeStruct.java +++ /dev/null @@ -1,640 +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 is essentially another field, - // immutable and type ignored (BOT). - - /** - * `_open` and `_fref` are independent lattice coordinates. - * - * `_open` says the structural tail is not known yet. On the low side an - * open struct has infinitely many implicit BOT fields; its dual has an - * implicit TOP tail. A real declaration is therefore open while its body - * is being parsed: - * - * ```text - * struct S { int x; /* more declared fields may follow *\/ } - * ``` - * - * `_fref` says no actual declaration has contributed to the type. The - * parser may still invent fields demanded by uses, leaving a useful open - * structural constraint: - * - * ```text - * S? p; p.x; // fref S { BOT x; ... } - * ``` - * - * Meeting that constraint with a real definition ANDs `_fref` off while - * retaining and checking the invented field. Closing recursive types must - * not by itself clear `_fref`; closure discovers shape, not a declaration. - */ - - public String _name; // Struct name - public boolean _open; - public boolean _fref; - public Field[] _fields; - - private TypeStruct() { super(TSTRUCT); } - private static final Ary<TypeStruct> FREE = new Ary<>(TypeStruct.class); - private TypeStruct init(String name, boolean open, boolean fref, Field[] fields) { - // Check for const array element name with mutable field - //assert !name.startsWith("[~]") || fields.length!=2 || fields[1]._t==null || (fields[1]._final != fields[1]._t.isHigh()); - _name = name; - _open = open; - _fref = fref; - _fields = fields; - return this; - } - // Return a filled-in TypeStruct; either from free list or alloc new. - private static TypeStruct malloc(String name, boolean open, boolean fref, Field[] fields) { - return (FREE.isEmpty() ? new TypeStruct() : FREE.pop()).init(name,open,fref,fields); - } - // Free ts; return this. - @Override TypeStruct free(Type t) { - TypeStruct ts = (TypeStruct)t; - assert !ts.isFree() && !ts._terned; - ts._name = null; - ts._fields = null; - ts._offs = null; - ts._dual = null; - ts._hash = 0; - ts._aliases = null; - ts._fidxs = null; - 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) { - return make(name,open,false,fields); - } - public static TypeStruct make(String name, boolean open, boolean fref, Field... fields) { - TypeStruct ts = malloc(name, open, fref, fields); - TypeStruct t2 = ts.intern(); - if( t2==ts ) return ts; - return VISIT.isEmpty() ? t2.free(ts) : ts.delayFree(ts); - } - - // Summary aliases, after interning. Skips private - // fields which need to be explicitly loaded. - private int[] _aliases; - public int[] aliases() { - if( _aliases != null ) return _aliases; - if( _fields.length==0 ) return (_aliases=XInt.EMPTY); - assert _terned; - int maxAlias = 0; - for( Field f : _fields ) - maxAlias = Math.max(maxAlias,f._alias); - int len = XInt.idx(maxAlias)+1; - if( (maxAlias&31)==31 ) len++; - int[] xs = XInt.free(len); - for( Field f : _fields ) - if( f._fname.charAt(0) != '_' && // Skip private fields. - !hiddenInit(f) && // Skip private allocation helper. - f._t != Type.TOP ) // Skip uninit fields - xs[XInt.idx(f._alias)] |= XInt.mask(f._alias); - return (_aliases=XInt.intern(xs)); - } - - // Summary fidxs, after interning. Skips private - // fields which need to be explicitly loaded. - private int[] _fidxs; - public int[] fidxs() { - if( _fidxs != null ) return _fidxs; - assert _terned; - int[] xs = XInt.EMPTY; - for( Field f : _fields ) - if( f._fname.charAt(0) != '_' && // Skip private fields. - !hiddenInit(f) && // Skip private allocation helper. - f._t instanceof TypeFunPtr tfp ) // Looking for TFP - xs = XInt.meet(xs,tfp._fidxs); - return (_fidxs = xs); - } - - // With a user constructor, the same-named function field is the hidden - // default <init> allocation helper, not part of the public class surface. - public boolean hiddenInit(Field fld) { - if( field("<ctor>") == null || !_name.startsWith("class:") ) return false; - int idx = _name.lastIndexOf('.'); - String name = _name.substring(idx<0 ? "class:".length() : idx+1); - return fld._fname.equals(name); - } - - // New open struct with no fields - public static TypeStruct open( String name ) { return make(name,true); } - public static TypeStruct forward( String name ) { return make(name,true,true); } - public TypeStruct defined() { return _fref ? make(_name,_open,false,_fields) : this; } - - // Array, variant body - 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 ), - Field.make("[]",body,bodyAlias,efinal)); - } - // Array, known constant body - public static TypeStruct makeAry(String name, int lenAlias, int bodyAlias, TypeConAry con) { - Type len = TypeInteger.constant(con.len()); - return make(name,false, - Field.make("#" ,len , lenAlias,true), - Field.make("[]" ,con.elem(),bodyAlias,true), - Field.make("[con]",con ,bodyAlias,true)); - } - - public TypeStruct makeInit() { - Field[] fs = new Field[_fields.length]; - for( int i=0; i<_fields.length; i++ ) { - if( _fields[i]._extern ) { fs[i] = _fields[i]; continue; } - Type zero = _fields[i]._t.makeZero(); - fs[i] = _fields[i].makeFrom(_fields[i]._t != Type.BOTTOM && zero.isa(_fields[i]._t) ? zero : Type.TOP); - } - return make(_name,_open,_fref,fs); - } - - public TypeStruct add( Field f ) { - assert find(f._fname)==-1; // No double field names - Field[] flds = Arrays.copyOf(_fields,_fields.length+1); - flds[_fields.length] = f; - return make(_name,_open,_fref,flds); - } - public TypeStruct replace( Field f ) { - Field[] flds = Arrays.copyOf(_fields,_fields.length); - flds[find(f._fname)] = f; - return make(_name,_open,_fref,flds); - } - public TypeStruct remove( int i ) { - Field[] flds = new Field[_fields.length-1]; - System.arraycopy(_fields, 0,flds,0,i); - System.arraycopy(_fields,i+1,flds,i,flds.length-i); - return make(_name,_open,_fref,flds); - } - - public TypeStruct addOrUpdate(Field fld) { - int idx = find(fld._fname); - int len = idx == -1 ? (idx=_fields.length)+1 : _fields.length; - Field[] flds = Arrays.copyOf(_fields,len); - flds[idx] = fld; - return make(_name,_open,_fref,flds); - } - - public final TypeStruct close() { - return (TypeStruct)recurOpen()._close(_name, null).recurClose(); - } - @Override TypeStruct _close( String name, HashMap<String, Type> TYPES ) { - assert (name==null) != (TYPES==null); // One or the other - String namePlusFinal = isAry() ? (_name+(_fields[1]._final ? "~" : "!")).intern() : _name; - TypeStruct ts = (TypeStruct)VISIT.get(namePlusFinal); - if( ts!=null ) return ts; - TypeStruct base = name==null ? (TypeStruct)TYPES.get(_name) : this; - assert base != null; - boolean open = base._fref ? base._open : name != _name && name != null && _open; - ts = base.recurPre(namePlusFinal, _name, open, base._fref ); - Field[] flds = ts._fields; - // Now start the recursion - for( int i=0; i<flds.length; i++ ) - flds[i].setType(base._fields[i]._t._close(name, TYPES)); - - // Note: this whole complex shenanigans here is because there are - // actually two flavors of any array (non-/final contents) but I - // allow the parser to represent both with the same name string. - if( isAry() && _fields[1]._final && !flds[1]._final ) // This is a final array variant - flds[1] = flds[1].makeFrom(true); // Add final to the base TYPES non-final array - - return ts; - } - - // Needs full recursive treatment; e.g. - // struct LL { LL? next; int !q; } // Q is mutable field. - // Stored into a not-mutable, so need that version also: - // struct LL { LL? next; int q; } // Q is NOT mutable field. - // Now upgrade: - // it is not the default version from TYPES. - // it is fully cyclic - // - // Evil question: has no *open* internal fields, so can I assume it is fully correct and never needs upgrade? - // - // upgrades are for well-formed types with internal ptrs to early open - // versions of other types, as happens in all the CoRecur tests. - // During the co-recur tests, can I make non-default cycles with - // weaker versions of other early types? - // Seems plausible... - @Override Type _upgradeType(HashMap<String,Type> TYPES) { - TypeStruct base = (TypeStruct)TYPES.get(_name); - if( this == base ) return this; // Already the good form - TypeStruct ts = (TypeStruct)VISIT.get(_uid); - if( ts!=null ) return ts; - // Main work: replace open with closed - if( _open ) - return base; - // Build recursive version - ts = recurPre(_uid,_name,false,_fref); - Field[] flds = ts._fields; - // Now start the recursion - for( int i=0; i<flds.length; i++ ) - flds[i].setType(_fields[i]._t._upgradeType(TYPES)); - return ts; - } - - static final AryInt CEQUALS = new AryInt(); - - public static final String TOPNAME = "$TOP", BOTNAME = "$BOT"; - public static final TypeStruct TOP = make("$TOP",false); - public static final TypeStruct BOT = TOP.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",-1,-4,TypeConAryB.ABC); - // Representative unresolved forms for the exhaustive lattice tests: - // bare forward ref, forward ref constrained by a field use, and definition. - private static final TypeStruct FREF0 = forward("%FREF"); - private static final TypeStruct FREF1 = FREF0.add(Field.make("x",Type.BOTTOM,-20,false)); - private static final TypeStruct FDEF1 = make("%FREF",false,Field.make("x",TypeInteger.BOT,-20,false)); - - // A pair of self-cyclic types - private static final TypeStruct SINT0 = make("%SINT",false); - private static final TypeStruct SFLT0 = make("%SFLT",false); - public static final TypeStruct SINT1 = SINT0.add(Field.make("a", TypeInteger.U32, -1, false)).add(Field.make("s2",TypeMemPtr.make((byte)2,SFLT0),-2, false)); - public static final TypeStruct SFLT1 = SFLT0.add(Field.make("b", TypeFloat .F32, -3, false)).add(Field.make("s1",TypeMemPtr.make((byte)2,SINT1),-4, false)); - - @Override public boolean isHigh() { return _name==TOPNAME; } - - public static void gather(ArrayList<Type> ts) { - ts.add(BOT); - ts.add(TEST); - ts.add(ARY); - ts.add(STR); - ts.add(ABC); - ts.add(FREF0); - ts.add(FREF1); - ts.add(FDEF1); - ts.add(SINT0); - ts.add(SFLT0); - ts.add(SINT1); - ts.add(SFLT1); - // Break cyclic init: built a struct - Field fcalloc = Field.make("calloc",TypeFunPtr.CALLOC,-2,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==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; - - // 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); - - assert (_name==that._name) == (_name.equals(that._name)); // Strings are interned - String name = - that._name==TOPNAME ? _name : - _name==TOPNAME ? that._name : - that._name==_name ? _name : - BOTNAME; - if( name==BOTNAME ) return BOT; - - // 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._extern != f1._extern ) - 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,name,_open | that._open,_fref & that._fref); - - // 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() { - Field[] flds = new Field[_fields.length]; - for( int i=0; i<_fields.length; i++ ) - flds[i] = _fields[i].dual(); - return malloc(sdual(),!_open,!_fref,flds); - } - - // Recursive dual - @Override TypeStruct rdual() { - if( _dual!=null ) return dual(); - assert !_terned; - Field[] flds = new Field[_fields.length]; - TypeStruct d = malloc(sdual(),!_open,!_fref,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; - } - - private String sdual() { - return - _name==TOPNAME ? BOTNAME : - _name==BOTNAME ? TOPNAME : - _name; - } - - @Override boolean _isConstant() { - assert !_open; // who is asking about constant open structs? if( _open ) return false; // Infinite BOT fields - assert !VISIT.containsKey(_uid); //expect constants to not include self-cycles. 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 non-final fields - if( VISIT.containsKey(_uid) ) // Test: been here before? - return true; // Cycles assume final - VISIT.put(_uid,this); // Set: don't do this again - for( Field fld : _fields ) - if( !fld._isFinal() ) - return false; - return true; - } - - private TypeStruct recurPre(Object key, String name, boolean open, boolean fref) { - // 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, fref, flds ); - ts._aliases = aliases(); - VISIT.put(key,ts); - return ts; - } - - private interface FieldTransform { Type apply(Type t); } - - private TypeStruct recurTransform(Object key, String name, boolean open, boolean fref, boolean xfinal, FieldTransform transform) { - TypeStruct ts = (TypeStruct)VISIT.get(key); - if( ts!=null ) return ts; // Already visited - ts = recurPre(key,name,open,fref); - Field[] flds = ts._fields; - for( Field fld : flds ) - fld._final |= xfinal; - - // Now start the recursion - for( int i=0; i<flds.length; i++ ) - flds[i].setType(transform.apply(_fields[i]._t)); - - return ts; - } - - // Make a read-only version - @Override TypeStruct _makeRO() { - return recurTransform(_name,_name,_open,_fref,true,Type::_makeRO); - } - - @Override TypeStruct _makeStorage() { - return recurTransform(_uid,_name,_open,false,false,Type::_makeStorage); - } - - // 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("Should not reach here: structs have no scalar log size"); } - @Override public int size() { return offset(_fields.length); } - @Override public int alignment() { - int align = 0; - for( Field f : _fields ) - if( !f._extern ) 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 ) { - assert (_name==ts._name) == (_name.equals(ts._name)); // Strings are interned - return _name==ts._name && _open==ts._open && _fref==ts._fref && _fields.length==ts._fields.length; - } - - @Override boolean eq(Type t) { - TypeStruct ts = (TypeStruct)t; // Invariant - - // Recursive; so use cyclic equals - if( !VISIT.isEmpty() ) { - assert CEQUALS.isEmpty(); - boolean rez = cycle_eq(t); - CEQUALS.clear(); - return rez; - } - // Normal equals - 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) ^ (_fref ? 8 : 0); - for( Field f : _fields ) - hash = Utils.rot(hash,13) ^ ((long)f._fname.hashCode() * f._alias); - return Utils.fold(hash); - } - - @Override public int nkids() { return _fields.length; } - @Override public Field at( int idx ) { return _fields[idx]; } - @Override public void set( int idx, Type t ) { _fields[idx] = (Field)t; } - // Tags 0-4 - closed +#nflds (+name) - // Tags 5 - closed(+ nflds +name) - // Tags 6-10- open +#nflds (+name) - // Tags 11 - open (+ nflds +name) - - - @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( isAry() ) - // Array names deliberately canonicalize mutable and immutable - // element variants. The verbose debugger print must retain the - // otherwise invisible lattice coordinate. - return sb.p(_fields[1]._final ? "[final]" : "[mutable]"); - if( html ) - 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 && !_fref ) 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 find("[]")!=-1; } - public boolean isConAry() { - return isAry() && _fields[1]._t instanceof TypeConAry; - } - - 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]._extern ) - 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]._extern ) { _offs[idx++] = -1; 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-modules/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeTuple.java deleted file mode 100644 index 0d70bdf2..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeTuple.java +++ /dev/null @@ -1,158 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.util.*; -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 TypeTuple makeFrom(int idx, Type tx ) { - if( _types[idx]==tx ) return this; - Type[] types = _types.clone(); - types[idx] = tx; - return make(types); - } - public TypeTuple meetFrom(int idx, Type t ) { return makeFrom(idx,_types[idx].meet(t)); } - - public static final TypeTuple BOT = malloc(new Type[0]).intern(); - public static final TypeTuple TOP = BOT.dual(); - // The default state, a triple of CONTROL, Memory, and Value, with bottoms - // for all. This is the default StopNode type, when new functions are - // being created and we cannot tell what the future holds. This is the - // default ReturnNode type, returning what the worst a function can do. - public static final TypeTuple STATE = make(Type.CONTROL,TypeMem.BOT,Type.BOTTOM); - public static final TypeTuple STOP_HIGH = make(Type.CONTROL,TypeMem.START,Type.TOP); - - public static final TypeTuple TEST = make(TypeInteger.BOT,TypeMemPtr.TEST); - public static final TypeTuple DEAD_NEW = make(TypeMemPtr.TOP,TypeMem.TOP); - - 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(STATE); 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); - boolean isCon=true; - for( Type t : _types ) - if( t._dual!=t ) - // Not a constant - return malloc(TypeFunPtr.xdual(_types)); - return this; - } - @Override TypeTuple rdual() { - if( _dual!=null ) return dual(); - assert !_terned; - assert _types.length>0; - Type[] ts = new Type[_types.length]; - TypeTuple d = malloc(ts); - (_dual = d)._dual = this; // Cross link duals - // Collection rdual - for( int i=0; i<_types.length; i++ ) - ts[i] = _types[i]._terned ? _types[i].dual() : _types[i].rdual(); - return d; - } - - @Override boolean _isConstant() { - for( Type t : _types ) - if( !t._isConstant() ) - return false; - return true; - } - @Override boolean _isFinal() { throw Utils.TODO("Should not reach here: tuples are not stored object values"); } - - @Override TypeMemPtr _makeRO() { throw Utils.TODO("Should not reach here: tuples cannot be made read-only"); } - - @Override Type _upgradeType(HashMap<String,Type> TYPES) { - Type[] ts = new Type[_types.length]; - for( int i=0; i<ts.length; i++ ) - ts[i] = _types[i]._upgradeType(TYPES); - return make(ts); - } - - @Override public int log_size() { throw Utils.TODO("Should not reach here: tuples have no scalar storage size"); } - @Override public int alignment() { - assert isConstant(); - int align = 0; - for( Type t : _types ) - align = Math.max(align,t.alignment()); - return align; - } - - public Type ctl() { assert _types.length>=3; return _types[0]; } - public Type ret() { assert _types.length>=3; return _types[2]; } - public TypeMem mem() { assert _types.length>=3; - return _types[1] instanceof TypeMem tmem ? tmem : (_types[1].isHigh() ? TypeMem.START : TypeMem.BOT); - } - - @Override public int nkids() { return _types.length; } - @Override public Type at( int idx ) { return _types[idx]; } - @Override public void set( int idx, Type t ) { _types[idx] = t; } - - - @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; - } - -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/type/XInt.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/XInt.java deleted file mode 100644 index 126a4e64..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/XInt.java +++ /dev/null @@ -1,244 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.util.*; -import java.lang.Integer; -import java.util.Arrays; - -// Stupidly overkill interned immutable int[]'s -public abstract class XInt { - private static final Ary<Ary<int[]>> FREES = new Ary(Ary.class); - private static final Ary<Ary<int[]>> INTERNS = new Ary(Ary.class); - private static int PROBES, HITS, NEWS; - - public static final int[] EMPTY = intern(new int[]{0, 0}); - public static final int[] FULL = intern(new int[]{0,-1}); - public static final int[] SET1 = make(1); - public static final int[] SET3 = make(SET1, 2); - - static int idx(int x) { return (x>>5)+1; } - static int mask(int x) { return 1<<(x&31); } - - // No hash set, still mutable - public static boolean isMutable(int[] xs) { return xs[0]==0; } - // Return hash code; must be set hence frozen - public static int hash(int[] xs) { return xs[0]; } - - // High bit set (infinite 1's extension) - public static boolean isHigh(int[] xs) { return xs[xs.length-1] < 0; } - - // Check for a bit - public static boolean bit(int[] xs, int x) { - int idx = idx(x); - if( idx >= xs.length ) - return isHigh(xs); - return (xs[idx] & mask(x))!=0; - } - - // Number of bits set - public static int bitCount(int[] xs) { - assert !isHigh(xs); // Dont ask if infinite - int cnt=0; - for( int i=1; i<xs.length; i++ ) - cnt += Integer.bitCount(xs[i]); - return cnt; - } - - // Largest bit - public static int max(int[] xs) { - assert !isHigh(xs); - return Integer.numberOfTrailingZeros(xs[xs.length-1])+((xs.length-1-1)<<5); - } - - // Has 1 bit set - public static boolean isConstant(int[] xs) { return !isHigh(xs) && bitCount(xs)==1; } - // Return the 1 set bit or assert - public static int onlyBit(int[] xs) { - assert isConstant(xs); - for( int i=1; i<xs.length; i++ ) - if( xs[i] != 0 ) - return Integer.numberOfTrailingZeros(xs[i]) + ((i-1)<<5); - throw Utils.TODO("Should not reach here"); - } - - // Subset check: ys is a subset of xs - public static boolean subset(int[] xs, int[] ys) { - assert !isMutable(ys); - if( xs==ys ) return true; - if( xs==FULL ) return true; - if( ys==EMPTY ) return true; - int min = Math.min(xs.length,ys.length); - for( int i=1; i<min; i++ ) - if( (xs[i] | ys[i])!=xs[i] ) - return false; - if( xs.length == ys.length ) - return true; - if( ys.length < xs.length ) - return !isHigh(ys); // If ys is infinite, it'll hit one of the xs extra zeros - return isHigh(xs); - } - - - // Construct a set with 1 bit set - public static int[] make(int x ) { return make(EMPTY,x); } - - // Add a bit - public static int[] make(int[] xs, int x ) { - assert !isMutable(xs); - int idx = idx(x); - if( idx >= xs.length && isHigh(xs) ) - return xs; // Set in the default infinite - if( idx < xs.length && (mask(x) & xs[idx])!=0 ) - return xs; // Already set - int len = Math.max(xs.length,idx+1); - if( (x&31)==31 && idx+1==len ) - len++; - int[] ys = free(len); - System.arraycopy(xs,1,ys,1,xs.length-1); - ys[idx] |= mask(x); - return intern(ys); - } - - // Interning, and life-cycle management - // Intern the array - public static int[] intern(int[] xs) { - if( !isMutable(xs) ) return xs; // Already interned - PROBES++; - - // Trim trailing 0s or -1s, use the infinite extend bit - int len = xs.length; - while( len > 2 && - ((xs[len-1]== 0 && xs[len-2]>=0) || - (xs[len-1]== -1 && xs[len-2]< 0) ) ) - len--; - // Replace with trimmed - if( len < xs.length ) { - var ys = free(len); - System.arraycopy(xs,1,ys,1,len-1); - xs = _free(xs,ys); - } - - // Compute lame hash code - int hash = 0; - for( int i=1; i<len; i++ ) - hash += xs[i]; - if( hash == 0 ) hash = 0xCAFEBABE; - xs[0] = hash; // Set lame hashcode, and mark immutable - - // Get length-specific INTERN table - var xss = INTERNS.atX(len); - if( xss==null ) - INTERNS.setX( xs.length, xss = new Ary<>(int[].class)); - - // Probe for a hit - for( int[] ys : xss ) - if( Arrays.equals(xs,ys) ) // Hit! - // Free new array and return old array - { HITS++; return _free(xs,ys); } - - // Intern new array and return - return xss.push(xs); - } - - // Return Ary of free int[]s of fixed size - private static Ary<int[]> _xss(int len) { - Ary<int[]> xss = FREES.atX(len); - if( xss==null ) - FREES.setX(len, xss = new Ary<>(int[].class)); - return xss; - } - // Pull free array from free list - static int[] free(int len) { - int[] xs = _xss(len).pop(); - if( xs==null ) - { NEWS++; xs = new int[len]; } - assert isMutable(xs); - return xs; - } - // Return ys. Put xs on free list - static int[] _free(int[] xs, int[] ys) { - Arrays.fill(xs,0); // Mutable again - _xss(xs.length).push(xs); - return ys; - } - - // Meet: Set union - public static int[] meet(int[] xs, int[] ys) { - if( xs==ys ) return xs; - boolean isX=true, isY=true; - int min = Math.min(xs.length,ys.length); - int max = Math.max(xs.length,ys.length); - for( int i=1; i<max; i++ ) { - int x = i < xs.length ? xs[i] : isHigh(xs) ? -1 : 0; - int y = i < ys.length ? ys[i] : isHigh(ys) ? -1 : 0; - if( (x | y)!=x ) isX=false; - if( (x | y)!=y ) isY=false; - } - if( isX ) return xs; - if( isY ) return ys; - // Get a free one - int[] ms = free(max); - for( int i=1; i<min; i++ ) - ms[i] = xs[i] | ys[i]; - for( int i=min; i<ms.length; i++ ) { - int x = i < xs.length ? xs[i] : isHigh(xs) ? -1 : 0; - int y = i < ys.length ? ys[i] : isHigh(ys) ? -1 : 0; - ms[i] = x | y; - } - return intern(ms); - } - - // Dual: flip all the bits - public static int[] dual(int[] xs) { - int[] ds = free(xs.length); - for( int i=1; i<xs.length; i++ ) - ds[i] = ~xs[i]; - return intern(ds); - } - - - // Next set bit, or -1 - // for( int bit = XInt.next(bits,0); bit >=0; bit = XInt.next(bits,bit) ) {...bit...} - public static int next(int[] xs, int x) { - assert !isHigh(xs); // No iterating infinite bitsets - x++; // Skip current - for( ; !bit(xs,x); x++ ) - if( idx(x)>=xs.length ) - return -1; - return x; - } - - // Remap all bits - - - public static String str(int[] xs) { return str(new SB(),xs).toString(); } - public static SB str(SB sb, int[] xs) { - sb.p("[ "); - // Printing ranges; - // 1,2,3 prints 1-3 - // 1,2,4 prints 1,2,4 - // - int max = (xs.length-1)<<5; - int low = 0; // No low range - for( int i=1; i<max; i++ ) { - if( bit(xs,i) ) { // Set bit, start or extend range - if( low==0 ) low=i; // No range, start one - } else { // Break in sequence, ending a range - range(sb,low,i-1); // Print prior range, and start new one - low=0; // Reset range - } - } - // Print any partial range - range(sb,low,max-1).unchar(); - if( isHigh(xs) ) - sb.p(",..."); - return sb.p("]"); - } - - private static SB range( SB sb, int low, int mid ) { - if( low==0 ) ; - else if( low==mid ) sb.p(low).p(','); - else if( low==mid-1 ) sb.p(low).p(',').p(mid).p(','); - else sb.p(low).p('-').p(mid).p(','); - return sb; - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/util/Ary.java b/chapter-modules/src/main/java/com/seaofnodes/simple/util/Ary.java deleted file mode 100644 index d67801c2..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/util/AryInt.java b/chapter-modules/src/main/java/com/seaofnodes/simple/util/AryInt.java deleted file mode 100644 index b498fd78..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/util/BAOS.java b/chapter-modules/src/main/java/com/seaofnodes/simple/util/BAOS.java deleted file mode 100644 index 61ce7387..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/util/BAOS.java +++ /dev/null @@ -1,162 +0,0 @@ -package com.seaofnodes.simple.util; - -import java.util.HashMap; -import java.util.Arrays; - -// "ByteArrayOutputStream", and Input Stream and not synchronized and has -// matching pack/unpack strategies. -public class BAOS { - - private byte[] _buf; - private int _len; - - public BAOS() { _buf = new byte[1]; } - public BAOS(byte[] buf) { _buf = buf; } - public BAOS(byte[] buf, int x) { _buf = buf; _len = x; } - - public byte[] toByteArray() { - return Arrays.copyOf(_buf,_len); - } - - public byte[] buf() { return _buf; } - public int size() { return _len; } - public void set( byte[] buf, int len ) { _buf=buf; _len=len; } - public void clear() { _len=0; } - - // ----------------------------------------------------------------------- - // Basic read, write (expand) - - private void grow(int len) { - while( _len+len >= _buf.length ) - _buf = Arrays.copyOf(_buf,_buf.length*2); - } - - // Write a byte, grow as needed; silent chop - public BAOS write(int b) { - grow(1); - _buf[_len++] = (byte)b; // Silent chop - return this; - } - - // Read an UNSIGNED bytes at INDEX, AIOOBE if off end - public int read1(int idx) { return _buf[idx] & 0xFF; } - - // Read an UNSIGNED byte, AIOOBE if off end - public int read() { return read1(_len++); } - - // write 2 bytes - public BAOS write2( int x ) { - grow(2); - _buf[_len++] = (byte)(x>> 0); - _buf[_len++] = (byte)(x>> 8); - return this; - } - - // Read a u16 in 2 bytes - public int read2(int idx) { return read1(idx) | (read1(idx+1)<<8); } - public int read2() { int rez = read2(_len); _len+=2; return rez; } - - // write 4 bytes - public BAOS write4( int x ) { - grow(4); - _buf[_len++] = (byte)(x>> 0); - _buf[_len++] = (byte)(x>> 8); - _buf[_len++] = (byte)(x>>16); - _buf[_len++] = (byte)(x>>24); - return this; - } - - // Read a i32 in 4 bytes - public int read4(int idx) { return read2(idx) | (read2(idx+2)<<16); } - public int read4() { int rez = read4(_len); _len+=4; return rez; } - - // write 8 bytes - public BAOS write8( long x ) { - write4((int) x ); - write4((int)(x>>32)); - return this; - } - - // Read a i64 in 8 bytes - public long read8(int idx) { return ((long)read4(idx) & 0xFFFFFFFFL) | ((long)read4(idx+4)<<32); } - public long read8() { long rez = read8(_len); _len+=8; return rez; } - - // Write byte array - public void write( byte[] bs ) { - grow(bs.length); - System.arraycopy(bs,0,_buf,_len,bs.length); - _len += bs.length; - } - - // Read/fill byte array - public byte[] read( byte[] bs ) { - System.arraycopy(_buf,_len,bs,0,bs.length); - _len += bs.length; - return bs; - } - - // ----------------------------------------------------------------------- - // Write a packed integer to the BAOS. Track a histogram to make more - // intelligent choices in the future. - private static final HashMap<Long,Integer> PACKED_HISTOGRAM = new HashMap<>(); - private BAOS histo(long x) { - Integer cnt = PACKED_HISTOGRAM.get(x); - PACKED_HISTOGRAM.put(x,cnt==null ? 1 : cnt+1); - return this; - } - - // Write a u8 (checked) - public void packed1(int x) { - assert 0 <= x && x <= 255; - histo(x).write(x); - } - - public int packed1() { return read(); } - - // Write a u16 (checked) - public void packed2(int x) { - assert 0 <= x && x <= 65535; - (( x <= 254 ) ? write(x) : write(0xFF).write2(x)).histo(x); - } - - // Read a packed u16 - public int packed2() { - int x = read(); - return x==0xFF ? read2() : x; - } - - // Write a packed i32 - public void packed4(int x) { - ((-127 <= x && x <= 127 ) ? write(x) : write(-128).write4(x)).histo(x); - } - - // Read a packed i32 - public int packed4() { - int x = _buf[_len++]; // Signed byte read - return x== -128 ? read4() : x; - } - - // Write a packed i64 - public void packed8(long x) { - ((-127 <= x && x <= 127 ) ? write((int)x) : write(-128).write8(x)).histo(x); - } - - // Read a packed i64 - public long packed8() { - int x = _buf[_len++]; // Signed byte read - return x== -128 ? read8() : x; - } - - public void packedS(String s) { - packed4(s.length()); - for( int i=0; i<s.length(); i++ ) - packed4(s.charAt(i)); - } - - public String packedS() { - int len = packed4(); - String s = new String(_buf,_len,len).intern(); - _len += len; - return s; - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/util/IntHashMap.java b/chapter-modules/src/main/java/com/seaofnodes/simple/util/IntHashMap.java deleted file mode 100644 index b356ec31..00000000 --- a/chapter-modules/src/main/java/com/seaofnodes/simple/util/IntHashMap.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.seaofnodes.simple.util; - -import java.util.Arrays; - -// Int-Int Hash Map -public class IntHashMap { - private int[] _es; - private int _size; - private final int _sentinel; - - // Pick a sentinel value; not allowed for keys or values. Sentinel is - // returned on a miss. Writing sentinel counts as a remove. - public IntHashMap(int sentinel) { - _sentinel = sentinel; - _es = new int[4]; - Arrays.fill(_es,sentinel); - } - - public int size() { return _size; } - public boolean isEmpty() { return _size==0; } - public void clear() { - _size = 0; - Arrays.fill(_es,_sentinel); - } - - private int cap() { return _es.length>>1; } - - public int put( int key, int val ) { - int slot = probe(key); - int old = _es[(slot<<1)+1]; - if( old == _sentinel && val != _sentinel ) _size++; - if( old != _sentinel && val == _sentinel ) _size--; - // Check cap, resize at 3/4 full - if( _size > ((cap()>>1) + (cap()>>2)) ) { - grow(); - slot = probe(key); - } - - _es[ slot<<1 ] = key; - _es[(slot<<1)+1] = val; - return old; - } - - public int get( int key ) { - int slot = probe(key); - return _es[(slot<<1)+1]; - } - - private int probe( int key ) { - int mask = cap()-1; - int slot = key & mask; - while( _es[slot<<1] != _sentinel && _es[slot<<1] != key ) - slot = (slot + (key|1)) & mask; - return slot; - } - - // Double underlying array size - private void grow() { - int[] old = _es; - _es = new int[_es.length<<1]; - Arrays.fill(_es,_sentinel); - for( int i=0; i<old.length; i+=2 ) - if( old[i] != _sentinel ) { - int slot = probe(old[i]); - _es[ slot<<1 ] = old[i ]; - _es[(slot<<1)+1] = old[i+1]; - } - } - - // Just all the keys - public int[] keySet() { - int[] keys = new int[_size]; - int j=0; - for( int i=0; i<_es.length; i+=2 ) - if( _es[i] != _sentinel ) - keys[j++] = _es[i]; - return keys; - } - - @Override public String toString() { - SB sb = new SB().p("[ "); - for( int i=0; i<_es.length; i+=2 ) - if( _es[i] != _sentinel ) - sb.p('(').p(_es[i]).p(',').p(_es[i+1]).p(')').p(','); - return sb.unchar().p(']').toString(); - } -} diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/util/SB.java b/chapter-modules/src/main/java/com/seaofnodes/simple/util/SB.java deleted file mode 100644 index cc59f2a6..00000000 --- a/chapter-modules/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-modules/src/main/java/com/seaofnodes/simple/util/Utils.java b/chapter-modules/src/main/java/com/seaofnodes/simple/util/Utils.java deleted file mode 100644 index 577a60af..00000000 --- a/chapter-modules/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-modules/src/main/smp/sys.smp b/chapter-modules/src/main/smp/sys.smp deleted file mode 100644 index 7705a187..00000000 --- a/chapter-modules/src/main/smp/sys.smp +++ /dev/null @@ -1,11 +0,0 @@ - -// top-level default standard library import - -val adt = adt; // Abstract Data Types -val ary = ary; // Array utilities -val aryi64 = aryi64; // Variable sized array of i64 -val aryu8 = aryu8; // Variable sized array of u8 -val char = char; // Character utilities -val io = io; // IO library -val libc = libc; // libc FFI bindings -val scan = scan; // A simple string parser diff --git a/chapter-modules/src/main/smp/sys/adt.smp b/chapter-modules/src/main/smp/sys/adt.smp deleted file mode 100644 index a4fe40bf..00000000 --- a/chapter-modules/src/main/smp/sys/adt.smp +++ /dev/null @@ -1,3 +0,0 @@ - -// Abstract data types. -val bitset = bitset; diff --git a/chapter-modules/src/main/smp/sys/adt/bitset.smp b/chapter-modules/src/main/smp/sys/adt/bitset.smp deleted file mode 100644 index d02cd2fe..00000000 --- a/chapter-modules/src/main/smp/sys/adt/bitset.smp +++ /dev/null @@ -1,57 +0,0 @@ - -// A set of non-negative integers, represented one bit per value. -struct bitset { - i64[] !_bits; - - new bitset = { -> - _bits = new i64[1]; - }; - - // Return 0 for missing, non-zero for true - val get = { int bit -> - if( (bit >> 6) >= _bits# ) return false; - i64 mask = 1 << (bit & 63); - return (_bits[bit >> 6] & mask); - }; - - val _grow = { int bit -> - int x = bit >> 6; - int len = _bits#; - if( x < len ) return self; - while( x >= len ) len *= 2; - var bits = new i64[len]; - for( int i=0; i<_bits#; i++ ) - bits[i] = _bits[i]; - _bits = bits; - return self; - }; - - // Set - val set = { int bit -> - i64 mask = 1 << (bit & 63); - _grow(bit)._bits[bit >> 6] |= mask; - return self; - }; - - // Test and set - val tset = { int bit -> - int idx = bit >> 6; - i64 mask = 1 << (bit & 63); - if( _grow(bit)._bits[idx] & mask ) - return true; // Already set - _bits[idx] |= mask; - return false; // Was false (and now set) - }; - - val clr = { int bit -> - if( (bit >> 6) >= _bits# ) return self; - i64 mask = 1 << (bit & 63); - _bits[bit >> 6] &= (mask ^ -1); - return self; - }; - - val clear = { -> - for( int i=0; i<_bits#; i++ ) _bits[i] = 0; - return self; - }; -}; diff --git a/chapter-modules/src/main/smp/sys/ary.smp b/chapter-modules/src/main/smp/sys/ary.smp deleted file mode 100644 index 8312e94c..00000000 --- a/chapter-modules/src/main/smp/sys/ary.smp +++ /dev/null @@ -1,24 +0,0 @@ - -// Array utilities - -// Copy byte array contents. Copies the shorter of the buffers and len -val copy = { u8[] dst, int doff, u8[~] src, int soff, int len -> - if( len > dst# - doff ) len = dst# - doff; - if( len > src# - soff ) len = src# - soff; - for( int i=0; i<len; i++ ) - dst[i+doff] = src[i+soff]; - return dst; -}; - -// Compare lexigraphically array contents. Compares up to shorter array or -// len. Returns <0 or >0 for first non-matching byte. Returns <0 or >0 for -// the larger array after that, or 0 if both arrays are fully equal. -val cmp = { u8[~] src1, u32 off1, u8[~] src2, u32 off2, u32 len -> - u32 len1 = len > src1# - off1 ? src1# - off1 : len; - u32 len2 = len > src2# - off2 ? src2# - off2 : len; - u32 max = len1 > len2 ? len2 : len1; - for( int i=0; i<max; i++ ) - if( src1[off1+i] != src2[off2+i] ) - return src1[off1+i] - src2[off2+i]; // Bytes determine compare - return len1 - len2; // Length determines compare -}; diff --git a/chapter-modules/src/main/smp/sys/aryi64.smp b/chapter-modules/src/main/smp/sys/aryi64.smp deleted file mode 100644 index 98b72605..00000000 --- a/chapter-modules/src/main/smp/sys/aryi64.smp +++ /dev/null @@ -1,82 +0,0 @@ - -// Ary/Vector of i64s -struct aryi64 { - u32 !len = 0; - i64[] !buf; - - new aryi64 = { i64[] b -> - buf = b; - }; - - // 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; - }; - - val get = { int index -> return buf[index]; }; - - // Write the bracketed array-list form to aryu8. - val str = { -> - // TODO: clunky default constructor syntax - var str = new aryu8().add('['); - for( int i=0; i<len; i++ ) - str.num(buf[i]).add(',').add(' '); - if( len ) str.undo(2); - return str.add(']'); - }; - - val swap = { int i, int j -> - i64 tmp = buf[i]; - buf[i] = buf[j]; - buf[j] = tmp; - self; - }; -}; - - - -// 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, without giving a clue as to why. -val parseAryI64 = { u8[~] str -> - !scan !s = new scan(str); - if( !s.match('[') ) return null; - // Array of parsed results - var nums = new aryi64(new i64[1]); - - // Parse a list of numbers - if( s.match(']') ) // Handle the empty list - return nums; - - // Parse number commas "123, " until something else - while( s.x < s.buf# ) { - nums.add(s.number()); - if( s.bad ) return null; - if( !s.match(',') ) - break; - }; - // Close the list - if( !s.match(']') ) return null; - s.skip(); - return s.x == s.buf# ? nums : null; -}; - -val copy = { i64[] dst, int doff, i64[~] src, int soff, int len -> - for( int i=0; i<len; i++ ) - dst[i+doff] = src[i+soff]; - return dst; -}; diff --git a/chapter-modules/src/main/smp/sys/aryu8.smp b/chapter-modules/src/main/smp/sys/aryu8.smp deleted file mode 100644 index 21cf920b..00000000 --- a/chapter-modules/src/main/smp/sys/aryu8.smp +++ /dev/null @@ -1,87 +0,0 @@ - -// Ary/Vector of u8s - a resizable array of bytes. -struct aryu8 { - u32 !len = 0; - u8[] !buf; - - new aryu8 = { -> buf = new u8[1]; }; - - // 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]; - ary.copy(buf2,0,buf,0,buf#); - buf = buf2; - self; - }; - - val clear = { -> len = 0; self; }; - - // Add a character to aryu8 - val add = { u8 c -> - _grow(1).buf[len++] = c; - self; - }; - - // Add all of a plain array to the aryu8 - val addAry = { u8[~] xs -> - _grow(xs#); - ary.copy(buf,len,xs,0,xs#); - len += xs#; - self; - }; - - // Remove x bytes 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; // Cast buffer pointer to i64 for native C call - libc.write(fd,ptr,len); - }; -}; - - -// Parse one integer, ignore trailing data; returns 0 with an empty string. -// If you want a more robust parsing, use the `scan` class -val atoi = { u8[~] str -> - int x = 0; - while( x < str# && str[x] <= ' ' ) x++; - if( x == str# ) return 0; - bool neg = false; - if( str[x]=='-' ) { neg=true; x++; } - i64 n = 0; - while( x < str# && '0' <= str[x] <= '9' ) - n = n*10 + str[x++]-'0'; - return neg ? -n : n; -}; - -// Zero-terminate string for libc strings. No error checking, just copies and -// appends a zero. -{ u8[~] -> u8[] } str0 = { u8[~] str -> - u8[] !strz = new u8[str#+1]; - ary.copy(strz,0,str,0,str#); - strz[str#] = '\0'; - strz; -}; diff --git a/chapter-modules/src/main/smp/sys/char.smp b/chapter-modules/src/main/smp/sys/char.smp deleted file mode 100644 index 22ca4213..00000000 --- a/chapter-modules/src/main/smp/sys/char.smp +++ /dev/null @@ -1,8 +0,0 @@ - -val toupper = { u8 c -> - return c >= 'a' && c <= 'z' ? c - ('a' - 'A') : c; -}; - -val tolower = { u8 c -> - return c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c; -}; diff --git a/chapter-modules/src/main/smp/sys/io.smp b/chapter-modules/src/main/smp/sys/io.smp deleted file mode 100644 index d6263ffe..00000000 --- a/chapter-modules/src/main/smp/sys/io.smp +++ /dev/null @@ -1,64 +0,0 @@ - -// Simple I/O Library - -// Write byte array to stdout -val p = { u8[~] str -> write(1,str); }; -val p_noInline = { u8[~] str -> write(1,str); }; - -// Return fd contents as a u8[]. -val read = { i32 fd -> - var buf = new u8[8*1024]; - i64 ptr = buf; - int len = 0; - while( true ) { - i32 rez = libc.read(fd,ptr,buf#); - if( rez > buf# ) libc._exit(libc.errno()); - len += rez; - if( rez < buf# ) - break; - libc._exit(-2); // Need to implement for more than 8K input - } - var out = new u8[len]; - ary.copy(out,0,buf,0,len); -}; - -// Write all of str to fd. -val write = { i32 fd, u8[~] str -> - i64 ptr = str; // Convert array pointer to a i64 for the native call - libc.write(fd,ptr,str#); -}; - -// Write all of str to fd. -val writeOff = { i32 fd, u8[~] str, u32 off, u32 len -> - i64 ptr = str; // Convert array pointer to a i64 for the native call - ptr += off; // Skip offset - // Cap at available length - if( len > str# - off ) len = str# - off; - // Do the write - libc.write(fd,ptr,len); -}; - -// Return stdin as a u8[]. -val stdin = { -> read(0); }; - -// Create file for writing. Return fd, or -1 and errno has the error code -val create = { u8[~] str -> - i64 ptr = sys.aryu8.str0(str); - i32 perms = 6*64 + 6*8 + 6; // 0666 - return libc.creat(ptr, perms); -}; - -// Open file for reading. Return fd, or -1 and errno has the error code -val open = { u8[~] str -> - i64 ptr = sys.aryu8.str0(str); - return libc.open(ptr, sys.libc.O_RDONLY, 0); -}; - -// Close an open fd. Return 0, or -1 and errno has the error code -val close = { i32 fd -> return libc.close(fd); }; - -// Remove a file. Return 0, or -1 and errno has the error code -val remove = { u8[~] str -> - i64 ptr = sys.aryu8.str0(str); - return libc.unlink(ptr); -}; diff --git a/chapter-modules/src/main/smp/sys/libc.smp b/chapter-modules/src/main/smp/sys/libc.smp deleted file mode 100644 index 17ee65cb..00000000 --- a/chapter-modules/src/main/smp/sys/libc.smp +++ /dev/null @@ -1,44 +0,0 @@ - -// libc bindings: -// https://www.man7.org/linux/man-pages/man2/<libc call>.2.html - -// The buffers are int-versions of zero-terminated strings. -// int buf = aryu8.str0("some_file"); -// buf mode perm-> fd -{ i64 i32 i32 -> i32 } open = "C"; -// buf perm -> fd -{ i64 i32 -> i32 } creat = "C"; - -// fd buf len -> len -{ i32 i64 u32 -> i32 } write = "C"; -// fd buf len -> len -{ i32 i64 u32 -> i32 } read = "C"; -// fd -> err -{ i32 -> i32 } close = "C"; -// path -> err -{ i64 -> i32 } unlink = "C"; - -i32 O_RDONLY = 0; -i32 O_WRONLY = 1; -i32 O_RDWR = 2; -i32 O_CREAT = 512; // 02000 on x86_64-pc-cygwin -i32 O_TRUNC = 1024; // 04000 on x86_64-pc-cygwin - -i32 STDIN = 0; -i32 STDOUT = 1; -i32 STDERR = 2; - -// status -{ i32 -> i32 } _exit = "C"; -// errno is not an ordinary C data symbol; read it through the native runtime. -{ i32 } simple_errno = "C"; -val errno = { -> simple_errno(); }; -// 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; diff --git a/chapter-modules/src/main/smp/sys/scan.smp b/chapter-modules/src/main/smp/sys/scan.smp deleted file mode 100644 index 44bedaaa..00000000 --- a/chapter-modules/src/main/smp/sys/scan.smp +++ /dev/null @@ -1,39 +0,0 @@ - -// Scanner; a simple string parser. -struct scan { - int !x; // Cursor - bool !bad; // True after a malformed token - u8[~] buf; // Buffer to parse - - new scan = { u8[~] b -> buf = b; }; - - // Skip whitespace - val skip = { -> - while( x < buf# && buf[x] <= ' ' ) - x++; - return self; - }; - - // True if find required character, and skip it; else False. - val match = { u8 c -> - skip(); - if( x >= buf# ) return false; - if( buf[x] != c ) return false; - x++; - return true; - }; - - // Parse a simple number - val number = { -> - skip(); - bool neg = match('-'); - if( x >= buf# || buf[x] < '0' || '9' < buf[x] ) { - bad = true; - return 0; - } - i64 n = 0; - while( x < buf# && '0' <= buf[x] & buf[x] <= '9' ) - n = n*10 + (buf[x++] - '0'); - return neg ? -n : n; - }; -}; |
