{
+ 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/Compiler.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/Compiler.java
new file mode 100644
index 0000000..4798830
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/Compiler.java
@@ -0,0 +1,983 @@
+package com.compilerprogramming.ezlang.compiler;
+
+import com.compilerprogramming.ezlang.compiler.codegen.CodeGen;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+import com.compilerprogramming.ezlang.compiler.print.GraphVisualizer;
+import com.compilerprogramming.ezlang.compiler.sontypes.*;
+import com.compilerprogramming.ezlang.exceptions.CompilerException;
+import com.compilerprogramming.ezlang.lexer.Lexer;
+import com.compilerprogramming.ezlang.parser.AST;
+import com.compilerprogramming.ezlang.parser.Parser;
+import com.compilerprogramming.ezlang.semantic.SemaAssignTypes;
+import com.compilerprogramming.ezlang.semantic.SemaDefineTypes;
+import com.compilerprogramming.ezlang.types.Scope;
+import com.compilerprogramming.ezlang.types.Symbol;
+import com.compilerprogramming.ezlang.types.Type;
+import com.compilerprogramming.ezlang.types.TypeDictionary;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Stack;
+
+public class Compiler {
+
+ public static ConstantNode ZERO; // Very common node, cached here
+ public static ConstantNode NIL; // Very common node, cached here
+ public static XCtrlNode XCTRL; // Very common node, cached here
+
+ TypeDictionary typeDictionary;
+
+ // Compile driver
+ public CodeGen _code;
+
+ /**
+ * 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.
+ *
+ * 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.
+ *
+ * We keep a list of all ScopeNodes so that we can show them in graphs
+ */
+ public ScopeNode _scope;
+
+ /**
+ * 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 _xScopes = new Stack<>();
+
+ ScopeNode _continueScope;
+ ScopeNode _breakScope; // Merge all the while-breaks here
+ FunNode _fun; // Current function being parsed
+
+
+ // Mapping from a type name to a Type. The string name matches
+ // `type.str()` call. No TypeMemPtrs are in here, because Simple does not
+ // have C-style '*ptr' references.
+ public static HashMap TYPES = new HashMap<>();
+
+ private ArrayList ctorStack = new ArrayList<>();
+
+ public Compiler(CodeGen codeGen, SONTypeInteger arg) {
+ this._code = codeGen;
+ }
+
+ public void parse() {
+ this.typeDictionary = createAST(_code._src);
+ Map types = new HashMap<>();
+ populateDefaultTypes(types);
+ populateTypes(types);
+ TYPES.putAll(types);
+ ZERO = con(SONTypeInteger.ZERO).keep();
+ NIL = con(SONType.NIL).keep();
+ XCTRL= new XCtrlNode().peephole().keep();
+ processFunctions();
+ }
+
+ public TypeDictionary createAST(String src) {
+ Parser parser = new Parser();
+ var program = parser.parse(new Lexer(src));
+ var typeDict = new TypeDictionary();
+ var sema = new SemaDefineTypes(typeDict);
+ sema.analyze(program);
+ var sema2 = new SemaAssignTypes(typeDict);
+ sema2.analyze(program);
+ return typeDict;
+ }
+
+
+ private void populateDefaultTypes(Map types) {
+ types.put(typeDictionary.INT.name(), SONTypeInteger.BOT);
+ for (SONType t: SONType.gather()) {
+ types.put(t.str(), t);
+ }
+ }
+
+ private void populateTypes(Map structTypes) {
+ // First process struct types
+ for (var symbol: typeDictionary.getLocalSymbols()) {
+ if (symbol instanceof Symbol.TypeSymbol typeSymbol) {
+ if (typeSymbol.type instanceof Type.TypeStruct typeStruct) {
+ createSONStructType(structTypes,typeSymbol.name, typeStruct);
+ }
+ else if (typeSymbol.type instanceof Type.TypeArray typeArray) {
+ getSONType(structTypes, typeArray);
+ }
+ }
+ }
+ // Next process function types
+ for (var symbol: typeDictionary.getLocalSymbols()) {
+ if (symbol instanceof Symbol.FunctionTypeSymbol functionTypeSymbol) {
+ createFunctionType(structTypes, functionTypeSymbol);
+ }
+ }
+ }
+
+ private void processFunctions() {
+ for (var symbol: typeDictionary.getLocalSymbols()) {
+ if (symbol instanceof Symbol.FunctionTypeSymbol functionTypeSymbol) {
+ generateFunction(functionTypeSymbol);
+ }
+ }
+ }
+
+ private void createFunctionType(Map structTypes, Symbol.FunctionTypeSymbol functionSymbol) {
+ Type.TypeFunction functionType = (Type.TypeFunction) functionSymbol.type;
+ Ary params = new Ary<>(SONType.class);
+ for (var symbol: functionType.args) {
+ if (symbol instanceof Symbol.ParameterSymbol parameterSymbol) {
+ SONType paramType = getSONType(structTypes, parameterSymbol.type);
+ params.push(paramType);
+ }
+ }
+ var retType = getSONType(structTypes, functionType.returnType);
+ SONTypeFunPtr tfp = _code.makeFun2(new SONTypeTuple(params.asAry()),retType);
+ structTypes.put(functionSymbol.name, tfp);
+ }
+
+ private void createSONStructType(Map structTypes, String typeName, Type.TypeStruct typeStruct) {
+ Ary fs = new Ary<>(Field.class);
+ for (int i = 0; i < typeStruct.numFields(); i++) {
+ String name = typeStruct.getFieldName(i);
+ Type type = typeStruct.getField(name); // FIXME
+ fs.push(new Field(name,getSONType(structTypes,type),_code.getALIAS(),false));
+ }
+ SONType fref = structTypes.get(typeName);
+ if (fref != null) {
+ if (fref instanceof SONTypeStruct ts) {
+ ts._fields = fs.asAry();
+ }
+ else throw new CompilerException("");
+ }
+ else {
+ structTypes.put(typeName, new SONTypeStruct(typeName, fs.asAry()));
+ }
+// SONTypeStruct ts = SONTypeStruct.make(typeName, fs.asAry());
+// TYPES.put(typeName, SONTypeMemPtr.make(ts));
+ }
+
+ private String getSONTypeName(Type type) {
+ if (type instanceof Type.TypeFunction typeFunction) {
+ return typeFunction.name;
+ }
+ else if (type instanceof Type.TypeArray typeArray) {
+ return typeArray.name();
+ }
+ else if (type instanceof Type.TypeStruct typeStruct) {
+ return "*" + typeStruct.name;
+ }
+ else if (type instanceof Type.TypeInteger ||
+ type instanceof Type.TypeVoid) {
+ return SONTypeInteger.BOT.str();
+ }
+ else if (type instanceof Type.TypeNull) {
+ return SONTypeNil.NIL.str();
+ }
+ else if (type instanceof Type.TypeNullable typeNullable) {
+ return getSONTypeName(typeNullable.baseType)+"?";
+ }
+ else throw new CompilerException("Not yet implemented " + type.name());
+ }
+
+ private SONType getSONType(Map structTypes, Type type) {
+ String tname = getSONTypeName(type);
+ SONType t = structTypes.get(tname);
+ if (t != null) return t;
+ if (type instanceof Type.TypeStruct) {
+ SONType existing = structTypes.get(type.name);
+ SONTypeStruct ts;
+ if (existing instanceof SONTypeStruct tsExisting)
+ ts = tsExisting;
+ else {
+ ts = new SONTypeStruct(type.name, new Field[0]);
+ structTypes.put(ts.str(), ts);
+ }
+ SONTypeMemPtr ptr = new SONTypeMemPtr((byte)2,ts);
+ if (type.name().equals(ts.str())) {
+ structTypes.put(ptr.str(), ptr);
+ return ptr;
+ }
+ else throw new CompilerException("Unexpected error");
+ }
+ else if (type instanceof Type.TypeArray typeArray) {
+ SONType elementType = getSONType(structTypes,typeArray.getElementType());
+ SONTypeStruct ts = null;
+ SONType existing = structTypes.get(typeArray.name());
+ if (existing instanceof SONTypeStruct tsExisting)
+ ts = tsExisting;
+ else {
+ ts = SONTypeStruct.makeArray(SONTypeInteger.U32, _code.getALIAS(), elementType, _code.getALIAS());
+ structTypes.put(ts.str(), ts);
+ }
+ SONTypeMemPtr ptr = new SONTypeMemPtr((byte)2,ts);
+ structTypes.put(typeArray.name(), ptr); // Array type name is not same as ptr str()
+ return ptr;
+ }
+ else if (type instanceof Type.TypeNullable typeNullable) {
+ SONType baseType = getSONType(structTypes,typeNullable.baseType);
+ SONTypeMemPtr ptr = null;
+ if (baseType instanceof SONTypeMemPtr ptr1) {
+ if (ptr1.nullable())
+ ptr = ptr1;
+ else
+ ptr = new SONTypeMemPtr((byte)3,ptr1._obj);
+ }
+ else
+ ptr = new SONTypeMemPtr((byte)2,(SONTypeStruct) baseType);
+ structTypes.put(ptr.str(), ptr);
+ return ptr;
+ }
+ else if (type instanceof Type.TypeVoid) {
+ return structTypes.get("Int"); // Only allowed in return types
+ }
+ throw new CompilerException("Count not find type " + type.name());
+ }
+
+ private Node ctrl() { return _scope.ctrl(); }
+ private N ctrl(N n) { return _scope.ctrl(n); }
+
+ // TODO because first two slots are MEM and RPC
+ private int REGNUM = 2;
+ private void setVarIds(Scope scope, ScopeNode scopeNode, FunNode fun) {
+ for (Symbol symbol: scope.getLocalSymbols()) {
+ if (symbol instanceof Symbol.VarSymbol varSymbol) {
+ varSymbol.regNumber = REGNUM++;
+ String sonTypeName = getSONTypeName(varSymbol.type);
+ SONType sonType = TYPES.get(sonTypeName);
+ if (sonType == null) throw new CompilerException("Unknown SON Type "+sonTypeName);
+ Node init = null;
+
+ if (varSymbol instanceof Symbol.ParameterSymbol) {
+ init = new ParmNode(makeVarName(varSymbol),varSymbol.regNumber,sonType,fun,con(sonType)).peephole();
+ }
+ scopeNode.define(makeVarName(varSymbol), sonType, false, init);
+ }
+ }
+ for (Scope childScope: scope.children) {
+ setVarIds(childScope, scopeNode, fun);
+ }
+ }
+
+ private void generateFunction(Symbol.FunctionTypeSymbol functionTypeSymbol) {
+ _scope = new ScopeNode();
+ _scope.define(ScopeNode.CTRL, SONType.CONTROL , false, null);
+ _scope.define(ScopeNode.MEM0, SONTypeMem.BOT , false, null);
+
+ ctrl(XCTRL);
+ _scope.mem(new MemMergeNode(false));
+
+ var funType = (SONTypeFunPtr) TYPES.get(functionTypeSymbol.name);
+ if (funType == null) throw new CompilerException("Function " + functionTypeSymbol.name + " not found");
+
+ // Parse whole program, as-if function header "{ int arg -> body }"
+ generateFunctionBody(functionTypeSymbol,funType);
+ _code.link(funType)._name = functionTypeSymbol.name;
+
+ // Clean up and reset
+ //_xScopes.pop();
+ _scope.kill();
+// for( StructNode init : INITS.values() )
+// init.unkeep().kill();
+// INITS.clear();
+ _code._stop.peephole();
+// if( show ) showGraph();
+ showGraph();
+ }
+
+ /**
+ * Dumps out the node graph
+ * @return {@code null}
+ */
+ Node showGraph() {
+ System.out.println(new GraphVisualizer().generateDotOutput(_code._stop,_scope,_xScopes));
+ return null;
+ }
+
+ /**
+ * Parses a function body, assuming the header is parsed.
+ */
+ private ReturnNode generateFunctionBody(Symbol.FunctionTypeSymbol functionTypeSymbol,SONTypeFunPtr sig) {
+ // Stack parser state on the local Java stack, and unstack it later
+ Node oldctrl = ctrl().keep();
+ Node oldmem = _scope.mem().keep();
+ FunNode oldfun = _fun;
+ ScopeNode breakScope = _breakScope; _breakScope = null;
+ ScopeNode continueScope = _continueScope; _continueScope = null;
+
+ FunNode fun = _fun = (FunNode)peep(new FunNode(sig,null,_code._start));
+ // Once the function header is available, install in linker table -
+ // allowing recursive functions. Linker matches on declared args and
+ // exact fidx, and ignores the return (because the fidx will only match
+ // the exact single function).
+ _code.link(fun);
+
+ Node rpc = new ParmNode("$rpc",0,SONTypeRPC.BOT,fun,con(SONTypeRPC.BOT)).peephole();
+
+ // Build a multi-exit return point for all function returns
+ RegionNode r = new RegionNode(null,null).init();
+ assert r.inProgress();
+ PhiNode rmem = new PhiNode(ScopeNode.MEM0,SONTypeMem.BOT,r,null).init();
+ PhiNode rrez = new PhiNode(ScopeNode.ARG0,SONType.BOTTOM,r,null).init();
+ ReturnNode ret = new ReturnNode(r, rmem, rrez, rpc, fun).init();
+ fun.setRet(ret);
+ assert ret.inProgress();
+ _code._stop.addDef(ret);
+
+ // Pre-call the function from Start, with worse-case arguments. This
+ // represents all the future, yet-to-be-parsed functions calls and
+ // external calls.
+ _scope.push(ScopeNode.Kind.Function);
+ ctrl(fun); // Scope control from function
+ // Private mem alias tracking per function
+ MemMergeNode mem = new MemMergeNode(true);
+ mem.addDef(null); // Alias#0
+ mem.addDef(new ParmNode(ScopeNode.MEM0,1,SONTypeMem.BOT,fun,con(SONTypeMem.BOT)).peephole()); // All aliases
+ _scope.mem(mem);
+ // All args, "as-if" called externally
+ AST.FuncDecl funcDecl = (AST.FuncDecl) functionTypeSymbol.functionDecl;
+ setVarIds(funcDecl.scope,_scope,fun);
+
+ // Parse the body
+ Node last = compileStatement(funcDecl.block);
+
+ // Last expression is the return
+ if( ctrl()._type==SONType.CONTROL )
+ fun.addReturn(ctrl(), _scope.mem().merge(), last);
+
+ // Pop off the inProgress node on the multi-exit Region merge
+ assert r.inProgress();
+ r ._inputs.pop();
+ rmem._inputs.pop();
+ rrez._inputs.pop();
+ assert !r.inProgress();
+
+ // Force peeps, which have been avoided due to inProgress
+ ret.setDef(1,rmem.peephole());
+ ret.setDef(2,rrez.peephole());
+ ret.setDef(0,r.peephole());
+ ret = (ReturnNode)ret.peephole();
+
+ // Function scope ends
+ _scope.pop();
+ _fun = oldfun;
+ _breakScope = breakScope;
+ _continueScope = continueScope;
+ // Reset control and memory to pre-function parsing days
+ ctrl(oldctrl.unkeep());
+ _scope.mem(oldmem.unkeep());
+
+ return ret;
+ }
+
+ private Node compileExpr(AST.Expr expr) {
+ switch (expr) {
+ case AST.LiteralExpr constantExpr -> {
+ return compileConstantExpr(constantExpr);
+ }
+ case AST.BinaryExpr binaryExpr -> {
+ return compileBinaryExpr(binaryExpr);
+ }
+ case AST.UnaryExpr unaryExpr -> {
+ return compileUnaryExpr(unaryExpr);
+ }
+ case AST.NameExpr symbolExpr -> {
+ return compileSymbolExpr(symbolExpr);
+ }
+ case AST.NewExpr newExpr -> {
+ return compileNewExpr(newExpr);
+ }
+ case AST.InitExpr initExpr -> {
+ return compileInitExpr(initExpr);
+ }
+ case AST.ArrayLoadExpr arrayLoadExpr -> {
+ return compileArrayIndexExpr(arrayLoadExpr);
+ }
+ case AST.ArrayStoreExpr arrayStoreExpr -> {
+ return compileArrayStoreExpr(arrayStoreExpr);
+ }
+ case AST.GetFieldExpr getFieldExpr -> {
+ return compileFieldExpr(getFieldExpr);
+ }
+ case AST.SetFieldExpr setFieldExpr -> {
+ return compileSetFieldExpr(setFieldExpr);
+ }
+ case AST.CallExpr callExpr -> {
+ return compileCallExpr(callExpr);
+ }
+ default -> throw new IllegalStateException("Unexpected value: " + expr);
+ }
+ }
+
+ private Node compileFieldExpr(AST.GetFieldExpr getFieldExpr) {
+ Node objPtr = compileExpr(getFieldExpr.object).keep();
+ // Sanity check expr for being a reference
+ if( !(objPtr._type instanceof SONTypeMemPtr ptr) ) {
+ throw new CompilerException("Unexpected type " + objPtr._type.str());
+ }
+ String name = getFieldExpr.fieldName;
+ SONTypeStruct base = ptr._obj;
+ int fidx = base.find(name);
+ if( fidx == -1 ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'");
+ Field f = base._fields[fidx]; // Field from field index
+ SONType tf = f._type;
+ // Field offset; fixed for structs
+ Node off = con(base.offset(fidx)).keep();
+ Node load = new LoadNode(name, f._alias, tf, memAlias(f._alias), objPtr, off);
+ // Arrays include control, as a proxy for a safety range check
+ // Structs don't need this; they only need a NPE check which is
+ // done via the type system.
+ load = peep(load);
+ objPtr.unkeep();
+ off.unkeep();
+ return load;
+ }
+
+ private Node compileSetFieldExpr(AST.SetFieldExpr setFieldExpr) {
+ Node objPtr;
+ if (setFieldExpr instanceof AST.InitFieldExpr)
+ objPtr = ctorStack.getLast();
+ else
+ objPtr = compileExpr(setFieldExpr.object);
+ // Sanity check expr for being a reference
+ if( !(objPtr._type instanceof SONTypeMemPtr ptr) ) {
+ throw new CompilerException("Unexpected type " + objPtr._type.str());
+ }
+ Node val = compileExpr(setFieldExpr.value).keep();
+ String name = setFieldExpr.fieldName;
+ SONTypeStruct base = ptr._obj;
+ int fidx = base.find(name);
+ if( fidx == -1 ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'");
+ Field f = base._fields[fidx]; // Field from field index
+ SONType tf = f._type;
+ Node mem = memAlias(f._alias);
+ Node st = new StoreNode(f._fname,f._alias,tf,mem,objPtr,con(base.offset(fidx)),val.unkeep(),true).peephole();
+ memAlias(f._alias,st);
+ return objPtr;
+ }
+
+ private Node compileArrayIndexExpr(AST.ArrayLoadExpr arrayLoadExpr) {
+ Node objPtr = compileExpr(arrayLoadExpr.array).keep();
+ // Sanity check expr for being a reference
+ if( !(objPtr._type instanceof SONTypeMemPtr ptr) ) {
+ throw new CompilerException("Unexpected type " + objPtr._type.str());
+ }
+ String name = "[]";
+ SONTypeStruct base = ptr._obj;
+ Node index = compileExpr(arrayLoadExpr.expr).keep();
+ Node off = peep(new AddNode(con(base.aryBase()),peep(new ShlNode(index.unkeep(),con(base.aryScale()))))).keep();
+ int fidx = base.find(name);
+ if( fidx == -1 ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'");
+ Field f = base._fields[fidx]; // Field from field index
+ SONType tf = f._type;
+ Node load = new LoadNode(name, f._alias, tf, memAlias(f._alias), objPtr, off);
+ // Arrays include control, as a proxy for a safety range check
+ // Structs don't need this; they only need a NPE check which is
+ // done via the type system.
+ load.setDef(0,ctrl());
+ load = peep(load);
+ objPtr.unkeep();
+ off.unkeep();
+ return load;
+ }
+
+ private Node compileArrayStoreExpr(AST.ArrayStoreExpr arrayStoreExpr) {
+ Node objPtr;
+ if (arrayStoreExpr instanceof AST.ArrayInitExpr)
+ objPtr = ctorStack.getLast();
+ else
+ objPtr = compileExpr(arrayStoreExpr.array);
+ // Sanity check expr for being a reference
+ if( !(objPtr._type instanceof SONTypeMemPtr ptr) ) {
+ throw new CompilerException("Unexpected type " + objPtr._type.str());
+ }
+ String name = "[]";
+ SONTypeStruct base = ptr._obj;
+ Node index = compileExpr(arrayStoreExpr.expr).keep();
+ Node off = peep(new AddNode(con(base.aryBase()),peep(new ShlNode(index.unkeep(),con(base.aryScale()))))).keep();
+ Node val = compileExpr(arrayStoreExpr.value).keep();
+ int fidx = base.find(name);
+ if( fidx == -1 ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'");
+ Field f = base._fields[fidx]; // Field from field index
+ SONType tf = f._type;
+ Node mem = memAlias(f._alias);
+ Node st = new StoreNode(f._fname,f._alias,tf,mem,objPtr,off.unkeep(),val.unkeep(),true).peephole();
+ memAlias(f._alias,st);
+ return objPtr;
+ }
+
+ private Node compileInitExpr(AST.InitExpr initExpr) {
+ Node objPtr = compileExpr(initExpr.newExpr).keep();
+ ctorStack.add(objPtr);
+ // Sanity check expr for being a reference
+ if( !(objPtr._type instanceof SONTypeMemPtr ptr) ) {
+ throw new CompilerException("Unexpected type " + objPtr._type.str());
+ }
+ if (initExpr.initExprList != null && !initExpr.initExprList.isEmpty()) {
+ for (AST.Expr expr : initExpr.initExprList) {
+ compileExpr(expr);
+ }
+ }
+ ctorStack.removeLast();
+ return objPtr.unkeep();
+ }
+
+ private Node newArray(SONTypeStruct ary, Node len) {
+ int base = ary.aryBase ();
+ int scale= ary.aryScale();
+ Node size = peep(new AddNode(con(base),peep(new ShlNode(len.keep(),con(scale)))));
+ return newStruct(ary,size);
+ }
+ /**
+ * Return a NewNode initialized memory.
+ * @param obj is the declared type, with GLB fields
+ */
+ private Node newStruct( SONTypeStruct obj, Node size) {
+ Field[] fs = obj._fields;
+ if( fs==null )
+ throw error("Unknown struct type '" + obj._name + "'");
+ int len = fs.length;
+ Node[] ns = new Node[2+len];
+ ns[0] = ctrl(); // Control in slot 0
+ // Total allocated length in bytes
+ ns[1] = size;
+ // Memory aliases for every field
+ for( int i = 0; i < len; i++ )
+ ns[2+i] = memAlias(fs[i]._alias);
+ // FIXME make
+ Node nnn = new NewNode(SONTypeMemPtr.make(obj), ns).peephole().keep();
+ for( int i = 0; i < len; i++ )
+ memAlias(fs[i]._alias, new ProjNode(nnn,i+2,memName(fs[i]._alias)).peephole());
+ return new ProjNode(nnn.unkeep(),1,obj._name).peephole();
+ }
+
+ private Node compileNewExpr(AST.NewExpr newExpr) {
+ Type type = newExpr.type;
+ if (type instanceof Type.TypeArray typeArray) {
+ SONTypeMemPtr tarray = (SONTypeMemPtr) TYPES.get(getSONTypeName(typeArray));
+ return newArray(tarray._obj,ZERO);
+ }
+ else if (type instanceof Type.TypeStruct typeStruct) {
+ SONTypeStruct tstruct = (SONTypeStruct) TYPES.get(typeStruct.name());
+ return newStruct(tstruct,con(tstruct.offset(tstruct._fields.length)));
+ }
+ else
+ throw new CompilerException("Unexpected type: " + type);
+ }
+
+
+ private Node compileUnaryExpr(AST.UnaryExpr unaryExpr) {
+ String opCode = unaryExpr.op.str;
+ switch (opCode) {
+ case "-": return peep(new MinusNode(compileExpr(unaryExpr.expr)).widen());
+ // Maybe below we should explicitly set Int
+ case "!": return peep(new NotNode(compileExpr(unaryExpr.expr)));
+ default: throw new CompilerException("Invalid unary op");
+ }
+ }
+
+ private Node compileBinaryExpr(AST.BinaryExpr binaryExpr) {
+ String opCode = binaryExpr.op.str;
+ int idx=0;
+ boolean negate = false;
+ var lhs = compileExpr(binaryExpr.expr1);
+ switch (opCode) {
+ case "&&":
+ case "||":
+ throw new CompilerException("Not yet implemented");
+ case "==":
+ idx=2; lhs = new BoolNode.EQ(lhs, null);
+ break;
+ case "!=":
+ idx=2; lhs = new BoolNode.EQ(lhs, null); negate=true;
+ break;
+ case "<=":
+ idx=2; lhs = new BoolNode.LE(lhs, null);
+ break;
+ case "<":
+ idx=2; lhs = new BoolNode.LT(lhs, null);
+ break;
+ case ">=":
+ idx=1; lhs = new BoolNode.LE(null, lhs);
+ break;
+ case ">":
+ idx=1; lhs = new BoolNode.LT(null, lhs);
+ break;
+ case "+":
+ idx=2; lhs = new AddNode(lhs,null);
+ break;
+ case "-":
+ idx=2; lhs = new SubNode(lhs,null);
+ break;
+ case "*":
+ idx=2; lhs = new MulNode(lhs,null);
+ break;
+ case "/":
+ idx=2; lhs = new DivNode(lhs,null);
+ break;
+ default:
+ throw new CompilerException("Not yet implemented");
+ }
+ lhs.setDef(idx,compileExpr(binaryExpr.expr2));
+ lhs = peep(lhs.widen());
+ if( negate ) // Extra negate for !=
+ lhs = peep(new NotNode(lhs));
+ return lhs;
+ }
+
+ private Node compileConstantExpr(AST.LiteralExpr constantExpr) {
+ if (constantExpr.type instanceof Type.TypeInteger)
+ return con(constantExpr.value.num.intValue());
+ else if (constantExpr.type instanceof Type.TypeNull)
+ return NIL;
+ else throw new CompilerException("Invalid constant type");
+ }
+
+ private Node compileSymbolExpr(AST.NameExpr symbolExpr) {
+ if (symbolExpr.type instanceof Type.TypeFunction functionType)
+ return con(TYPES.get(functionType.name));
+ else {
+ Symbol.VarSymbol varSymbol = (Symbol.VarSymbol) symbolExpr.symbol;
+ Var v = _scope.lookup(makeVarName(varSymbol));
+ if (v == null) throw new CompilerException("Unknown variable name: " + varSymbol.name);
+ Node n = _scope.in(v._idx);
+ if (n == null) throw new CompilerException("Variable " + varSymbol.name + " not defined");
+ return n;
+ }
+ }
+
+ /**
+ * Parse function call arguments; caller will parse the surrounding "()"
+ *
+ * ( arg* )
+ *
+ */
+ private Node compileCallExpr(AST.CallExpr callExpr) {
+ Node expr = compileExpr(callExpr.callee);
+ if( expr._type == SONType.NIL )
+ throw error("Calling a null function pointer");
+ if( !(expr instanceof FRefNode) && !expr._type.isa(SONTypeFunPtr.BOT) )
+ throw error("Expected a function but got "+expr._type.glb().str());
+ expr.keep(); // Keep while parsing args
+
+ Ary args = new Ary(Node.class);
+ args.push(null); // Space for ctrl,mem
+ args.push(null);
+ for (AST.Expr e: callExpr.args) {
+ Node arg = compileExpr(e);
+ args.push(arg.keep());
+ }
+ // Control & memory after parsing args
+ args.set(0,ctrl().keep());
+ args.set(1,_scope.mem().merge().keep());
+ args.push(expr); // Function pointer
+ // Unkeep them all
+ for( Node arg : args )
+ arg.unkeep();
+ // Dead into the call? Skip all the node gen
+ if( ctrl()._type == SONType.XCONTROL ) {
+ for( Node arg : args )
+ if( arg.isUnused() )
+ arg.kill();
+ return con(SONType.TOP);
+ }
+
+ // Into the call
+ CallNode call = (CallNode)new CallNode(args.asAry()).peephole();
+
+ // Post-call setup
+ CallEndNode cend = (CallEndNode)new CallEndNode(call).peephole();
+ // Control from CallEnd
+ ctrl(new CProjNode(cend,0,ScopeNode.CTRL).peephole());
+ // Memory from CallEnd
+ MemMergeNode mem = new MemMergeNode(true);
+ mem.addDef(null); // Alias#0
+ mem.addDef(new ProjNode(cend,1,ScopeNode.MEM0).peephole());
+ _scope.mem(mem);
+ // Call result
+ return new ProjNode(cend,2,"#2").peephole();
+ }
+
+ private String makeVarName(Symbol.VarSymbol varSymbol) {
+ return varSymbol.name + "$" + varSymbol.regNumber;
+ }
+
+ private Node compileStatement(AST.Stmt statement) {
+ switch (statement) {
+ case AST.BlockStmt blockStmt -> {
+ return compileBlock(blockStmt);
+ }
+ case AST.VarStmt letStmt -> {
+ return compileLet(letStmt);
+ }
+ case AST.VarDeclStmt varDeclStmt -> {}
+ case AST.IfElseStmt ifElseStmt -> {
+ return compileIf(ifElseStmt);
+ }
+ case AST.WhileStmt whileStmt -> {
+ return compileWhile(whileStmt);
+ }
+ case AST.ContinueStmt continueStmt -> {
+ return compileContinue(continueStmt);
+ }
+ case AST.BreakStmt breakStmt -> {
+ return compileBreak(breakStmt);
+ }
+ case AST.ReturnStmt returnStmt -> {
+ return compileReturn(returnStmt);
+ }
+ case AST.AssignStmt assignStmt -> {
+ return compileAssign(assignStmt);
+ }
+ case AST.ExprStmt exprStmt -> {
+ return compileExprStmt(exprStmt);
+ }
+ default -> throw new IllegalStateException("Unexpected value: " + statement);
+ }
+ throw new CompilerException("Not yet implemented");
+ }
+
+ private ScopeNode jumpTo(ScopeNode toScope) {
+ ScopeNode cur = _scope.dup();
+ ctrl(XCTRL); // Kill current scope
+ // Prune nested lexical scopes that have depth > than the loop head
+ // We use _breakScope as a proxy for the loop head scope to obtain the depth
+ while( cur._lexSize.size() > _breakScope._lexSize.size() )
+ 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._lexSize.size() <= _breakScope._lexSize.size();
+ toScope.ctrl(toScope.mergeScopes(cur).peephole());
+ return toScope;
+ }
+ private void checkLoopActive() { if (_breakScope == null) throw error("No active loop for a break or continue"); }
+
+ private Node compileBreak(AST.BreakStmt breakStmt) {
+ 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 = jumpTo(_breakScope );
+ _breakScope.addGuards(_breakScope.ctrl(), null, false);
+ return ZERO;
+ }
+
+ private Node compileContinue(AST.ContinueStmt continueStmt) {
+ checkLoopActive(); _continueScope = jumpTo( _continueScope ); return ZERO;
+ }
+
+ private Node compileWhile(AST.WhileStmt whileStmt) {
+ 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(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 = compileExpr(whileStmt.condition);
+
+ // IfNode takes current control and predicate
+ Node ifNode = new IfNode(ctrl(), pred.keep()).peephole();
+ // Setup projection nodes
+ Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep();
+ Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole();
+
+ // 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
+ compileStatement(whileStmt.stmt); // Parse loop body
+ _scope.removeGuards(ifT);
+
+ // Merge the loop bottom into other continue statements
+ if (_continueScope != null) {
+ _continueScope = jumpTo(_continueScope);
+ _scope.kill();
+ _scope = _continueScope;
+ }
+
+
+ // The true branch loops back, so whatever is current _scope.ctrl gets
+ // added to head loop as input. endLoop() updates the head scope, and
+ // goes through all the phis that were created earlier. For each phi,
+ // it sets the second input to the corresponding input from the back
+ // edge. If the phi is redundant, it is replaced by its sole input.
+ var exit = _breakScope;
+ head.endLoop(_scope, exit);
+ head.unkeep().kill();
+
+ _xScopes.pop(); // Cleanup
+ _xScopes.pop(); // Cleanup
+
+ _continueScope = savedContinueScope;
+ _breakScope = savedBreakScope;
+
+ // At exit the false control is the current control, and
+ // the scope is the exit scope after the exit test.
+ _xScopes.pop();
+ _xScopes.push(exit);
+ _scope = exit;
+ return ZERO;
+ }
+
+ private Node compileIf(AST.IfElseStmt ifElseStmt) {
+ var pred = compileExpr(ifElseStmt.condition).keep();
+ pred.keep();
+
+ // IfNode takes current control and predicate
+ Node ifNode = new IfNode(ctrl(), pred).peephole();
+ // Setup projection nodes
+ Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep();
+ Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole().keep();
+ // In if true branch, the ifT proj node becomes the ctrl
+ // But first clone the scope and set it as current
+ ScopeNode fScope = _scope.dup(); // Duplicate current scope
+ _xScopes.push(fScope); // For graph visualization we need all scopes
+
+ // Parse the true side
+ ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection
+ _scope.addGuards(ifT,pred,false); // Up-cast predicate
+ Node lhs = compileStatement(ifElseStmt.ifStmt).keep(); // Parse true-side
+ _scope.removeGuards(ifT);
+
+ // See if a one-sided def was made: "if(pred) int x = 1;" and throw.
+ // See if any forward-refs were made, and copy them to the other side:
+ // "pred ? n*fact(n-1) : 1"
+ fScope.balanceIf(_scope);
+
+ ScopeNode tScope = _scope;
+
+ // Parse the false side
+ _scope = fScope; // Restore scope, then parse else block if any
+ ctrl(ifF.unkeep()); // Ctrl token is now set to ifFalse projection
+ // Up-cast predicate, even if not else clause, because predicate can
+ // remain true if the true clause exits: `if( !ptr ) return 0; return ptr.fld;`
+ _scope.addGuards(ifF,pred,true);
+ boolean doRHS = ifElseStmt.elseStmt != null;
+ Node rhs = (doRHS
+ ? compileStatement(ifElseStmt.elseStmt)
+ : con(lhs._type.makeZero())).keep();
+ _scope.removeGuards(ifF);
+ if( doRHS )
+ fScope = _scope;
+ pred.unkeep();
+
+ _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));
+ Node ret = peep(new PhiNode("",lhs._type.meet(rhs._type),r,lhs.unkeep(),rhs.unkeep()));
+ r.peephole();
+ return ret;
+ }
+
+ private Node compileExprStmt(AST.ExprStmt exprStmt) {
+ return compileExpr(exprStmt.expr);
+ }
+
+ private Node compileAssign(AST.AssignStmt assignStmt) {
+ if (!(assignStmt.lhs instanceof AST.NameExpr nameExpr))
+ throw new CompilerException("Assignment requires a nameExpr");
+ String name = nameExpr.name;
+ if (!(nameExpr.symbol instanceof Symbol.VarSymbol varSymbol))
+ throw new CompilerException("Assignment requires a variable name");
+ String scopedName = makeVarName(varSymbol);
+ // Parse rhs
+ Node expr = compileExpr(assignStmt.rhs);
+ Var def = _scope.lookup(scopedName);
+ if( def==null )
+ throw error("Undefined name '" + name + "'");
+ // Update
+ _scope.update(scopedName,expr);
+ return expr;
+ }
+
+ private Node compileLet(AST.VarStmt letStmt) {
+ String name = letStmt.varName;
+ String scopedName = makeVarName(letStmt.symbol);
+ // Parse rhs
+ Node expr = compileExpr(letStmt.expr);
+ Var def = _scope.lookup(scopedName);
+ if( def==null )
+ throw error("Undefined name '" + name + "'");
+ // Update
+ _scope.update(scopedName,expr);
+ return expr;
+ }
+
+ private Node compileReturn(AST.ReturnStmt returnStmt) {
+ var expr = compileExpr(returnStmt.expr);
+ // Need default memory, since it can be lazy, need to force
+ // a non-lazy Phi
+ _fun.addReturn(ctrl(), _scope.mem().merge(), expr);
+ ctrl(XCTRL); // Kill control
+ return expr;
+ }
+
+ private Node compileBlock(AST.BlockStmt block) {
+ Node last = ZERO;
+ for (AST.Stmt stmt: block.stmtList) {
+ last = compileStatement(stmt);
+ }
+ return last;
+ }
+
+ public static Node con(long con ) { return con==0 ? ZERO : con(SONTypeInteger.constant(con)); }
+ public static ConstantNode con( SONType t ) { return (ConstantNode)new ConstantNode(t).peephole(); }
+ public Node peep( Node n ) {
+ // Peephole, then improve with lexically scoped guards
+ return _scope.upcastGuard(n.peephole());
+ }
+
+ // We set up memory aliases by inserting special vars in the scope these
+ // variables are prefixed by $ so they cannot be referenced in Simple code.
+ // Using vars has the benefit that all the existing machinery of scoping
+ // and phis work as expected
+ private Node memAlias(int alias ) { return _scope.mem(alias ); }
+ private void memAlias(int alias, Node st) { _scope.mem(alias, st); }
+ public static String memName(int alias) { return ("$"+alias).intern(); }
+
+ CompilerException errorSyntax(String syntax) { return _errorSyntax("expected "+syntax); }
+ private CompilerException _errorSyntax(String msg) {
+ return error("Syntax error, "+msg);
+ }
+ public static CompilerException error(String msg) { return new CompilerException(msg); }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/IterPeeps.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/IterPeeps.java
new file mode 100644
index 0000000..f58ae9f
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/IterPeeps.java
@@ -0,0 +1,197 @@
+package com.compilerprogramming.ezlang.compiler;
+
+import com.compilerprogramming.ezlang.compiler.codegen.CodeGen;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+//import com.seaofnodes.simple.print.JSViewer;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Random;
+
+/**
+ * The IterPeeps runs after parsing. It iterates the peepholes to a fixed point
+ * so that no more peepholes apply. This should be linear because peepholes rarely
+ * (never?) increase code size. The graph should monotonically reduce in some
+ * dimension, which is usually size. It might also reduce in e.g. number of
+ * MulNodes or Load/Store nodes, swapping out more "expensive" Nodes for cheaper
+ * ones.
+ *
+ * The theoretical overall worklist is mindless just grabbing the next thing and
+ * doing it. If the graph changes, put the neighbors on the worklist.
+ *
+ * Lather, Rinse, Repeat until the worklist runs dry.
+ *
+ * The main issues we have to deal with:
+ *
+ *
+ * - 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.
+ *
+ * - 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.
+ *
+ * - Our strong invariant is that for all Nodes, either they are on the worklist
+ * OR no peephole applies. This invariant is easy to check, although expensive.
+ * Basically the normal "iterate peepholes to a fixed point" is linear, and this
+ * check is linear at each peephole step... so quadratic overall. Its a useful
+ * assert, but one we can disable once the overall algorithm is stable - and
+ * then turn it back on again when some new set of peepholes is misbehaving.
+ * The code for this is turned on in `IterPeeps.iterate` as `assert
+ * progressOnList(stop);`
+ *
+ */
+public class IterPeeps {
+
+ private final WorkList _work;
+
+ public IterPeeps( long seed ) { _work = new WorkList<>(seed); }
+
+ public N add( N n ) { return (N)_work.push(n); }
+
+ public void addAll( Ary ary ) { _work.addAll(ary); }
+
+ /**
+ * Iterate peepholes to a fixed point
+ */
+ public void iterate( CodeGen code ) {
+ assert progressOnList(code);
+ int cnt=0;
+
+ Node n;
+ while( (n=_work.pop()) != null ) {
+ if( n.isDead() ) continue;
+ cnt++; // Useful for debugging, searching which peephole broke things
+ Node x = n.peepholeOpt();
+ if( x != null ) {
+ if( x.isDead() ) continue;
+ // peepholeOpt can return brand-new nodes, needing an initial type set
+ if( x._type==null ) x.setType(x.compute());
+ // Changes require neighbors onto the worklist
+ if( x != n || !(x instanceof ConstantNode) ) {
+ // All outputs of n (changing node) not x (prior existing node).
+ for( Node z : n._outputs ) _work.push(z);
+ // Everybody gets a free "go again" in case they didn't get
+ // made in their final form.
+ _work.push(x);
+ // If the result is not self, revisit all inputs (because
+ // there's a new user), and replace in the graph.
+ if( x != n ) {
+ for( Node z : n. _inputs ) _work.push(z);
+ n.subsume(x);
+ }
+ }
+ // If there are distant neighbors, move to worklist
+ n.moveDepsToWorklist();
+ //JSViewer.show(); // Show again
+ assert progressOnList(code); // Very expensive assert
+ }
+ if( n.isUnused() && !(n instanceof StopNode) )
+ n.kill(); // Just plain dead
+ }
+
+ }
+
+ // Visit ALL nodes and confirm the invariant:
+ // Either you are on the _work worklist OR running `iter()` makes no progress.
+
+ // This invariant ensures that no progress is missed, i.e., when the
+ // worklist is empty we have indeed done all that can be done. To help
+ // with debugging, the {@code assert} is broken out in a place where it is easy to
+ // stop if a change is found.
+
+ // Also, the normal usage of `iter()` may attempt peepholes with distance
+ // neighbors and these should fail, but will then try to add dependencies
+ // {@link #Node.addDep} which is a side effect in an assert. The {@link
+ // #midAssert} is used to stop this side effect.
+ private boolean progressOnList(CodeGen code) {
+ code._midAssert = true;
+ Node changed = code._stop.walk( n -> {
+ Node m = n;
+ if( n.compute().isa(n._type) && (!n.iskeep() || n._nid<=6) ) { // Types must be forwards, even if on worklist
+ if( _work.on(n) ) return null;
+ m = n.peepholeOpt();
+ if( m==null ) return null;
+ }
+ System.err.println("BREAK HERE FOR BUG");
+ return m;
+ });
+ code._midAssert = false;
+ return changed==null;
+ }
+
+ /**
+ * Classic WorkList, with a fast add/remove, dup removal, random pull.
+ * The Node's nid is used to check membership in the worklist.
+ */
+ public static class WorkList {
+
+ 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 ary ) {
+ for( E n : ary )
+ push(n);
+ }
+
+ /**
+ * True if Node is on the WorkList
+ */
+ boolean on( E x ) { return _on.get(x._nid); }
+ boolean isEmpty() { return _len==0; }
+
+ /**
+ * Removes a random Node from the WorkList; null if WorkList is empty
+ */
+ public E pop() {
+ if( _len == 0 ) return null;
+ int idx = _R.nextInt(_len);
+ E x = (E)_es[idx];
+ _es[idx] = _es[--_len]; // Compress array
+ _on.clear(x._nid);
+ return x;
+ }
+
+ public void clear() {
+ _len = 0;
+ _on.clear();
+ _R.setSeed(_seed);
+ _totalWork = 0;
+ }
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/SB.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/SB.java
new file mode 100644
index 0000000..0ed652d
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/SB.java
@@ -0,0 +1,99 @@
+package com.compilerprogramming.ezlang.compiler;
+
+/** 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 void del(ArrayList 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 int find( ArrayList ary, E x ) {
+ for( int i=0; i= 0 on success, -1 on failure
+ */
+ public static int find( E[] ary, E x ) {
+ for( int i=0; i>>n); }
+
+ // Fold a 64bit hash into 32 bits
+ public static int fold( long x ) { return (int)((x>>32) ^ x); }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/Var.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/Var.java
new file mode 100644
index 0000000..6016939
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/Var.java
@@ -0,0 +1,52 @@
+package com.compilerprogramming.ezlang.compiler;
+
+import com.compilerprogramming.ezlang.compiler.sontypes.*;
+
+
+/**
+ * The tracked fields are now complex enough to deserve a array-of-structs layout
+ */
+public class Var {
+
+ public final String _name; // Declared name
+ public int _idx; // index in containing scope
+ private SONType _type; // Declared type
+ public boolean _final; // Final field
+ public boolean _fref; // Forward ref
+
+ public Var(int idx, String name, SONType type, boolean xfinal) {
+ this(idx,name,type,xfinal,false);
+ }
+ public Var(int idx, String name, SONType type, boolean xfinal, boolean fref) {
+ _idx = idx;
+ _name = name;
+ _type = type;
+ _final = xfinal;
+ _fref = fref;
+ }
+ public SONType type() {
+ if( !_type.isFRef() ) return _type;
+ // Update self to no longer use the forward ref type
+ SONType def = Compiler.TYPES.get(((SONTypeMemPtr)_type)._obj._name);
+ return (_type=_type.meet(def));
+ }
+ public SONType lazyGLB() {
+ SONType t = type();
+ return t instanceof SONTypeMemPtr ? t : t.glb();
+ }
+
+ // Forward reference variables (not types) must be BOTTOM and
+ // distinct from inferred variables
+ public boolean isFRef() { return _fref; }
+
+ public void defFRef(SONType type, boolean xfinal) {
+ assert isFRef() && xfinal;
+ _type = type;
+ _final = true;
+ _fref = false;
+ }
+
+ @Override public String toString() {
+ return _type.toString()+(_final ? " ": " !")+_name;
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/BuildLRG.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/BuildLRG.java
new file mode 100644
index 0000000..99c3fbd
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/BuildLRG.java
@@ -0,0 +1,91 @@
+package com.compilerprogramming.ezlang.compiler.codegen;
+
+import com.compilerprogramming.ezlang.compiler.Utils;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeMem;
+
+abstract public class BuildLRG {
+ // Compute live ranges in a single forwards pass. Every def is a new live
+ // range except live ranges are joined at Phi. Also, gather the
+ // intersection of register masks in each live range. Example: "free
+ // always zero" register forces register zero, and calling conventions
+ // typically force registers.
+
+ // Sets FAILED to the set of hard-conflicts (means no need for an IFG,
+ // since it will not color, just split the conflicted ranges now). Returns
+ // true if no hard-conflicts, although we still might not color.
+ public static boolean run(int round, RegAlloc alloc) {
+ for( Node bb : alloc._code._cfg )
+ for( Node n : bb.outs() ) {
+ if( n instanceof PhiNode phi && !(phi._type instanceof SONTypeMem) ) {
+ // All Phi inputs end up with the same LRG.
+ // Pass 1: find any pre-existing LRG, to avoid make-then-Union a LRG
+ LRG lrg = alloc.lrg(phi);
+ if( lrg == null )
+ for( int i=1; i=0; lrg=ifg.nextSetBit(lrg+1) ) {
+ LRG lrg1 = alloc._LRGS[lrg];
+ lrg0.addNeighbor(lrg1);
+ lrg1.addNeighbor(lrg0);
+ }
+ }
+ }
+
+ // Walk all the splits, looking for coalesce chances
+ boolean progress = false;
+ for( CFGNode bb : alloc._code._cfg ) {
+ outer:
+ for( int j=0; j (v2._adj==null ? 0 : v2._adj._len) )
+ { 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._adj==null ? 0 : v2._adj._len) >= mask.size() ) {
+ // Fails capacity, will not be trivial colorable
+ 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 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/CodeGen.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/CodeGen.java
new file mode 100644
index 0000000..522cfd3
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/CodeGen.java
@@ -0,0 +1,389 @@
+package com.compilerprogramming.ezlang.compiler.codegen;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+import com.compilerprogramming.ezlang.compiler.print.*;
+import com.compilerprogramming.ezlang.compiler.sontypes.*;
+import com.compilerprogramming.ezlang.exceptions.CompilerException;
+
+import java.io.IOException;
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+public class CodeGen {
+ // Last created CodeGen as a global; used all over to avoid passing about a
+ // "context".
+ public static CodeGen CODE;
+
+ public enum Phase {
+ Parse, // Parse ASCII text into Sea-of-Nodes IR
+ Opto, // Run ideal optimizations
+ TypeCheck, // Last check for bad programs
+ InstSelect, // Convert to target hardware nodes
+ Schedule, // Global schedule (code motion) nodes
+ LocalSched, // Local schedule
+ RegAlloc, // Register allocation
+ Encoding, // Encoding
+ Export, // Export
+ }
+ public Phase _phase;
+
+ // ---------------------------
+ // Compilation source code
+ public final String _src;
+ // Compile-time known initial argument type
+ public final SONTypeInteger _arg;
+
+ // ---------------------------
+ public CodeGen( String src ) { this(src, SONTypeInteger.BOT, 123L ); }
+ public CodeGen( String src, SONTypeInteger arg, long workListSeed ) {
+ CODE = this;
+ _phase = null;
+ _callingConv = null;
+ _start = new StartNode(arg);
+ _stop = new StopNode(src);
+ _src = src;
+ _arg = arg;
+ _iter = new IterPeeps(workListSeed);
+ P = new Compiler(this,arg);
+ }
+
+
+ // ---------------------------
+ /**
+ * A counter, for unique node id generation. Starting with value 1, to
+ * avoid bugs confusing node ID 0 with uninitialized values.
+ */
+ public int _uid = 1;
+ public int UID() { return _uid; }
+ public int getUID() {
+ return _uid++;
+ }
+
+ // Next available memory alias number
+ private int _alias = 2; // 0 is for control, 1 for memory
+ public int getALIAS() { return _alias++; }
+
+
+ // 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 _gvn = new HashMap<>();
+
+ // Compute "function indices": FIDX.
+ // Each new request at the same signature gets a new FIDX.
+ private final HashMap FIDXS = new HashMap<>();
+ public SONTypeFunPtr makeFun( SONTypeTuple sig, SONType ret ) {
+ Integer i = FIDXS.get(sig);
+ int fidx = i==null ? 0 : i;
+ FIDXS.put(sig,fidx+1); // Track count per sig
+ assert fidx<64; // TODO: need a larger FIDX space
+ return SONTypeFunPtr.make((byte)2,sig,ret, 1L< _linker = new HashMap<>();
+
+ // Parser object
+ public final Compiler P;
+
+ // Parse ASCII text into Sea-of-Nodes IR
+ public int _tParse;
+ public CodeGen parse() {
+ assert _phase == null;
+ _phase = Phase.Parse;
+ long t0 = System.currentTimeMillis();
+ P.parse();
+ _tParse = (int)(System.currentTimeMillis() - t0);
+ return this;
+ }
+
+
+ // ---------------------------
+ // Iterator peepholes.
+ public final IterPeeps _iter;
+ // Stop tracking deps while assertin
+ public boolean _midAssert;
+ // Statistics on peepholes
+ public int _iter_cnt, _iter_nop_cnt;
+ // Stat gathering
+ public void iterCnt() { if( !_midAssert ) _iter_cnt++; }
+ public void iterNop() { if( !_midAssert ) _iter_nop_cnt++; }
+
+ // Run ideal optimizations
+ public int _tOpto;
+ public CodeGen opto() {
+ assert _phase == Phase.Parse;
+ _phase = Phase.Opto;
+ long t0 = System.currentTimeMillis();
+
+ // Pessimistic peephole optimization on a worklist
+ _iter.iterate(this);
+ _tOpto = (int)(System.currentTimeMillis() - t0);
+
+ // TODO:
+ // Optimistic
+ // TODO:
+ // loop unroll, peel, RCE, etc
+ return this;
+ }
+ public N add( N n ) { return (N)_iter.add(n); }
+ public void addAll( Ary ary ) { _iter.addAll(ary); }
+
+
+ // ---------------------------
+ // Last check for bad programs
+ public int _tTypeCheck;
+ public CodeGen typeCheck() {
+ // Demand phase Opto for cleaning up dead control flow at least,
+ // required for the following GCM.
+ assert _phase.ordinal() <= Phase.Opto.ordinal();
+ _phase = Phase.TypeCheck;
+ long t0 = System.currentTimeMillis();
+
+ CompilerException err = _stop.walk( Node::err );
+ _tTypeCheck = (int)(System.currentTimeMillis() - t0);
+ if( err != null )
+ throw err;
+ return this;
+ }
+
+
+ // ---------------------------
+ // Code generation CPU target
+ public Machine _mach;
+ //
+ public String _callingConv;
+
+ // Convert to target hardware nodes
+ public int _tInsSel;
+ public CodeGen instSelect( String base, String cpu, String callingConv ) {
+ assert _phase.ordinal() <= Phase.TypeCheck.ordinal();
+ _phase = Phase.InstSelect;
+
+ _callingConv = callingConv;
+
+ // Not timing the class load...
+ // Look for CPU in fixed named place:
+ // com.compilerprogramming.ezlang.compiler.node.cpus."cpu"."cpu.class"
+ String clzFile = base+"."+cpu+"."+cpu;
+ try { _mach = ((Class) Class.forName( clzFile )).getDeclaredConstructor(new Class[]{CodeGen.class}).newInstance(this); }
+ catch( Exception e ) { throw new RuntimeException(e); }
+
+ // Convert to machine ops
+ long t0 = System.currentTimeMillis();
+ _uid = 1; // All new machine nodes reset numbering
+ var map = new IdentityHashMap();
+ _instSelect( _stop, map );
+ _stop = ( StopNode)map.get(_stop );
+ _start = (StartNode)map.get(_start);
+ _instOuts(_stop,visit());
+ _visit.clear();
+ _tInsSel = (int)(System.currentTimeMillis() - t0);
+ return this;
+ }
+
+ // Walk all ideal nodes, recursively mapping ideal to machine nodes, then
+ // make a machine node for "this".
+ private Node _instSelect( Node n, IdentityHashMap map ) {
+ if( n==null ) return null;
+ Node x = map.get(n);
+ if( x !=null ) return x; // Been there, done that
+
+ // If n is a MachNode already, then its part of a multi-node expansion.
+ // It does not need instruction selection (already selected!)
+ // but it does need its inputs walked.
+ if( n instanceof MachNode ) {
+ for( int i=0; i < n.nIns(); i++ )
+ n._inputs.set(i, _instSelect(n.in(i),map) );
+ return n;
+ }
+
+ // Produce a machine node from n; map it to flag as done so stops cycles.
+ map.put(n, x=_mach.instSelect(n) );
+ // Walk machine op and replace inputs with mapped inputs
+ for( int i=0; i < x.nIns(); i++ )
+ x._inputs.set(i, _instSelect(x.in(i),map) );
+ if( x instanceof MachNode mach )
+ mach.postSelect(this); // Post selection action
+
+ // Updates forward edges only.
+ n._outputs.clear();
+ return x;
+ }
+
+ // Walk all machine Nodes, and set their output edges
+ private void _instOuts( Node n, BitSet visit ) {
+ if( visit.get(n._nid) ) return;
+ visit.set(n._nid);
+ for( Node in : n._inputs )
+ if( in!=null ) {
+ in._outputs.push(n);
+ _instOuts(in,visit);
+ }
+ }
+
+
+ // ---------------------------
+ // Control Flow Graph in RPO order.
+ public int _tGCM;
+ public Ary _cfg = new Ary<>(CFGNode.class);
+
+ // Global schedule (code motion) nodes
+ public CodeGen GCM() { return GCM(false); }
+ public CodeGen GCM( boolean show) {
+ assert _phase.ordinal() <= Phase.InstSelect.ordinal();
+ _phase = Phase.Schedule;
+ long t0 = System.currentTimeMillis();
+
+ // Build the loop tree, fix never-exit loops
+ _start.buildLoopTree(_stop);
+ GlobalCodeMotion.buildCFG(this);
+ _tGCM = (int)(System.currentTimeMillis() - t0);
+ if( show )
+ System.out.println(new GraphVisualizer().generateDotOutput(_stop,null,null));
+ return this;
+ }
+
+ // ---------------------------
+ // Local (basic block) scheduler phase, a classic list scheduler
+ public int _tLocal;
+ public CodeGen localSched() {
+ assert _phase == Phase.Schedule;
+ _phase = Phase.LocalSched;
+ long t0 = System.currentTimeMillis();
+ ListScheduler.sched(this);
+ _tLocal = (int)(System.currentTimeMillis() - t0);
+ return this;
+ }
+
+
+ // ---------------------------
+ // Register Allocation
+ public int _tRegAlloc;
+ public RegAlloc _regAlloc;
+ public CodeGen regAlloc() {
+ assert _phase == Phase.LocalSched;
+ _phase = Phase.RegAlloc;
+ long t0 = System.currentTimeMillis();
+ _regAlloc = new RegAlloc(this);
+ _regAlloc.regAlloc();
+ _tRegAlloc = (int)(System.currentTimeMillis() - t0);
+ return this;
+ }
+
+ 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 int _tEncode;
+ public Encoding _encoding;
+ public CodeGen encode() {
+ assert _phase == Phase.RegAlloc;
+ _phase = Phase.Encoding;
+ long t0 = System.currentTimeMillis();
+ _encoding = new Encoding(this);
+ _encoding.encode();
+ _tEncode = (int)(System.currentTimeMillis() - t0);
+ return this;
+ }
+ // Encoded binary, no relocation info
+ public byte[] binary() { return _encoding.bits(); }
+
+ // ---------------------------
+ // Exporting to external formats
+ public CodeGen exportELF(String fname) throws IOException {
+ assert _phase == Phase.Encoding;
+ _phase = Phase.Export;
+ ElfFile obj = new ElfFile(this);
+ obj.export(fname);
+ return this;
+ }
+
+ // ---------------------------
+ SB asm(SB sb) { return ASMPrinter.print(sb,this); }
+ public String asm() { return asm(new SB()).toString(); }
+
+
+ // Testing shortcuts
+ public Node ctrl() { return _stop.ret().ctrl(); }
+ public Node expr() { return _stop.ret().expr(); }
+ public String print() { return _stop.print(); }
+
+ // Debugging helper
+ @Override public String toString() {
+ return _phase.ordinal() > Phase.Schedule.ordinal()
+ ? IRPrinter._prettyPrint( this )
+ : _stop.p(9999);
+ }
+
+ // Debugging helper
+ public Node f(int idx) { return _stop.find(idx); }
+
+ public static void print_as_hex(Encoding enc) {
+ for (byte b : enc._bits.toByteArray()) {
+ System.out.print(String.format("%02X", b));
+ }
+ System.out.println();
+ }
+
+ public void print_as_hex() {
+ for (byte b : _encoding._bits.toByteArray()) {
+ System.out.print(String.format("%02X", b));
+ }
+ System.out.println();
+ }
+
+ //// Debug purposes for now
+// public CodeGen printENCODING() {
+// ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+//
+// for(Node bb : CodeGen.CODE._cfg) {
+// for(Node n: bb.outs()) {
+// if(n instanceof MachNode) {
+// ((MachNode) n).encoding(E);
+// }
+// }
+// }
+//
+// print_as_hex(outputStream);
+// // Get the raw bytes from the output stream
+// return this;
+// }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/ElfExpr.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/ElfExpr.java
new file mode 100644
index 0000000..f24dc4f
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/ElfExpr.java
@@ -0,0 +1,87 @@
+package com.compilerprogramming.ezlang.compiler.codegen;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+public class ElfExpr {
+ public static void main(String[] args) {
+ try (FileOutputStream fos = new FileOutputStream("hello.o")) {
+ ByteBuffer buffer = ByteBuffer.allocate(512);
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+ // === ELF HEADER (64 bytes) ===
+ buffer.put(new byte[]{
+ 0x7F, 'E', 'L', 'F', // ELF Magic
+ 2, 1, 1, 0, // 64-bit, little-endian, version
+ 0, 0, 0, 0, 0, 0, 0, // ABI padding
+ 2, 0, // Type: Executable (ET_EXEC)
+ 0x3E, 0x00, // Machine: x86-64
+ 1, 0, 0, 0, // ELF version
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Entry point
+ 0, 0, 0, 0, 0, 0, 0, 0, // Program header offset
+ 64, 0, 0, 0, 0, 0, 0, 0, // Section header offset
+ 0, 0, 0, 0, // Flags
+ 64, 0, // ELF header size
+ 0, 0, // Program header entry size
+ 0, 0, // Number of program header entries
+ 40, 0, // Section header entry size
+ 2, 0, // Number of section headers
+ 1, 0 // Section header string table index
+ });
+
+ // === .TEXT SECTION (Executable Code) ===
+ byte[] textSection = {
+ // sys_write (write(1, message, 13))
+ (byte) 0x48, (byte) 0xC7, (byte) 0xC0, 0x01, 0x00, 0x00, 0x00, // mov rax, 1
+ (byte) 0x48, (byte) 0xC7, (byte) 0xC7, 0x01, 0x00, 0x00, 0x00, // mov rdi, 1
+ (byte) 0x48, (byte) 0xBE, (byte)0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rsi, message
+ (byte) 0x48, (byte) 0xC7, (byte) 0xC2, 0x0D, 0x00, 0x00, 0x00, // mov rdx, 13
+ (byte) 0x0F, (byte) 0x05, // syscall
+
+ // sys_exit (exit(0))
+ (byte) 0x48, (byte) 0xC7, (byte) 0xC0, 0x3C, 0x00, 0x00, 0x00, // mov rax, 60
+ (byte) 0x48, (byte) 0x31, (byte) 0xFF, // xor rdi, rdi
+ (byte) 0x0F, (byte) 0x05 // syscall
+ };
+
+ // Append section data
+ buffer.position(256);
+ buffer.put(textSection);
+
+ // === STRING MESSAGE (.data Section) ===
+ byte[] message = "Hello, World!\n".getBytes();
+ buffer.position(400);
+ buffer.put(message);
+
+ // === SECTION HEADERS ===
+ buffer.position(64);
+
+ // Null Section (required as first entry)
+ buffer.put(new byte[40]);
+
+ // .text Section
+ buffer.putInt(1); // Offset in section string table (".text")
+ buffer.putInt(1); // Type: SHT_PROGBITS
+ buffer.putLong(6); // Flags: Executable | Allocatable
+ buffer.putLong(0); // Virtual address
+ buffer.putLong(256); // Offset to section data
+ buffer.putLong(textSection.length); // Section size
+ buffer.putInt(0); // Link
+ buffer.putInt(0); // Info
+ buffer.putLong(4); // Address alignment
+ buffer.putLong(0); // No extra entries
+
+ // === SECTION HEADER STRING TABLE ===
+ buffer.position(500);
+ buffer.put(".text".getBytes());
+ buffer.put((byte) 0x00); // Null terminator
+
+ fos.write(buffer.array());
+ System.out.println("ELF object file 'hello.o' created.");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/ElfFile.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/ElfFile.java
new file mode 100644
index 0000000..eee43a1
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/ElfFile.java
@@ -0,0 +1,387 @@
+package com.compilerprogramming.ezlang.compiler.codegen;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+import com.compilerprogramming.ezlang.compiler.sontypes.*;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.io.IOException;
+import java.io.FileOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.BufferedOutputStream;
+
+public class ElfFile {
+
+ final CodeGen _code;
+ Ary _sections = new Ary<>(Section.class);
+
+ ElfFile( CodeGen code ) { _code = code; }
+
+ public static int writeCString(DataSection strtab, String name) {
+ // place name in the string table
+ int pos = strtab._contents.size();
+ byte[] name_bytes = name.getBytes();
+ strtab._contents.write(name_bytes, 0, name_bytes.length);
+ // the strings are null terminated
+ strtab._contents.write(0);
+ return pos;
+ }
+
+ public static void write4( ByteArrayOutputStream bits, int op ) {
+ bits.write(op );
+ bits.write(op>> 8);
+ bits.write(op>>16);
+ bits.write(op>>24);
+ }
+ public static void write8( ByteArrayOutputStream bits, long i64 ) {
+ write4(bits, (int) i64 );
+ write4(bits, (int)(i64>>32));
+ }
+
+ public static final int SYMBOL_SIZE = 4+1+1+2+8+8;
+
+ public static final int SYM_BIND_LOCAL = 0;
+ public static final int SYM_BIND_GLOBAL = 1;
+ public static final int SYM_BIND_WEAK = 2;
+
+ public static final int SYM_TYPE_NOTYPE = 0;
+ public static final int SYM_TYPE_OBJECT = 1;
+ public static final int SYM_TYPE_FUNC = 2;
+ public static final int SYM_TYPE_SECTION = 3;
+
+ public static class Symbol {
+ String _name;
+ int _name_pos;
+
+ // index in the symbol table
+ int _index;
+
+ // which section is it in
+ int _parent;
+
+ // depends on the symbol type
+ // TODO
+ int _value, _size;
+
+ // top 4bits are the "bind", bottom 4 are "type"
+ int _info;
+
+ Symbol(String name, int parent, int bind, int type) {
+ _name = name;
+ _parent = parent;
+ _info = (bind << 4) | (type & 0xF);
+ }
+
+ void writeHeader(ByteBuffer out) {
+ out.putInt(_name_pos); // name
+ out.put((byte) _info); // info
+ out.put((byte) 0); // other
+ out.putShort((short) _parent); // shndx
+ out.putLong(_value); // value
+ out.putLong(_size); // size
+ }
+ }
+
+ public static final int SHF_WRITE = 1;
+ public static final int SHF_ALLOC = 2;
+ public static final int SHF_EXECINSTR = 4;
+ public static final int SHF_STRINGS = 32;
+ public static final int SHF_INFO_LINK = 64;
+
+ public static final int SECTION_HDR_SIZE = 64;
+ abstract public static class Section {
+ String _name;
+ int _index;
+
+ int _type, _flags;
+ int _link, _info;
+
+ // where's the name in the string table
+ int _name_pos;
+ int _file_offset;
+
+ Section(String name, int type) {
+ _name = name;
+ _type = type;
+ }
+
+ void writeHeader(ByteBuffer out) {
+ out.putInt(_name_pos); // name
+ out.putInt(_type); // type
+ out.putLong(_flags); // flags
+ out.putLong(0); // addr
+ out.putLong(_file_offset); // offset
+ out.putLong(size()); // size
+ out.putInt(_link); // link
+ out.putInt(_info); // info
+ out.putLong(1); // addralign
+ if (_type == 2) {
+ out.putLong(SYMBOL_SIZE);// entsize
+ } else if (_type == 4) {
+ out.putLong(24); // entsize
+ } else {
+ out.putLong(0); // entsize
+ }
+ }
+
+ abstract void write(ByteBuffer out);
+ abstract int size();
+ }
+
+ public static class SymbolSection extends Section {
+ Ary _symbols = new Ary<>(Symbol.class);
+ Ary _loc = new Ary<>(Symbol.class);
+
+ SymbolSection(String name, int type) {
+ super(name, type);
+ }
+
+ @Override
+ void writeHeader(ByteBuffer out) {
+ // points to string table section
+ _link = 1;
+
+ // number of non-local symbols
+ _info = _loc.len() + 1;
+
+ super.writeHeader(out);
+ }
+
+ void push(Symbol s) {
+ if ((s._info >> 4) != SYM_BIND_LOCAL) {
+ _symbols.push(s);
+ } else {
+ _loc.push(s);
+ }
+ }
+
+ @Override
+ void write(ByteBuffer out) {
+ for( int i = 0; i < SYMBOL_SIZE/4; i++ ) {
+ out.putInt(0);
+ }
+ int num=1;
+ for( Symbol s : _loc ) {
+ s._index = num++;
+ s.writeHeader(out);
+ }
+ for( Symbol s : _symbols ) {
+ s._index = num++;
+ s.writeHeader(out);
+ }
+ }
+
+ @Override
+ int size() {
+ return (1 + _symbols.len() + _loc.len()) * SYMBOL_SIZE;
+ }
+ }
+
+ public static class DataSection extends Section {
+ ByteArrayOutputStream _contents;
+
+ DataSection(String name, int type) {
+ super(name, type);
+ _contents = new ByteArrayOutputStream();
+ }
+
+ DataSection(String name, int type, ByteArrayOutputStream contents) {
+ super(name, type);
+ _contents = contents;
+ }
+
+ @Override
+ void write(ByteBuffer out) {
+ out.put(_contents.toByteArray());
+ }
+
+ @Override
+ int size() {
+ return _contents.size();
+ }
+ }
+
+ private void pushSection(Section s) {
+ _sections.push(s);
+ // ELF is effectively "1-based", due to the NULL section
+ s._index = _sections._len;
+ }
+
+ private final HashMap _funcs = new HashMap<>();
+ private void encodeFunctions(SymbolSection symbols, DataSection text) {
+ int func_start = 0;
+ for( int i=0; i<_code._cfg._len; i++ ) {
+ Node bb = _code._cfg.at(i);
+ if( bb instanceof FunNode f ) {
+ // skip until the function ends
+ while( !(_code._cfg.at(i) instanceof ReturnNode) ) {
+ i++;
+ }
+
+ Node r = _code._cfg.at(i);
+ int end = _code._encoding._opStart[r._nid] + _code._encoding._opLen[r._nid];
+
+ Symbol func = new Symbol(f._name, text._index, SYM_BIND_GLOBAL, SYM_TYPE_FUNC);
+ func._size = end - func_start;
+ func._value = func_start;
+ symbols.push(func);
+ _funcs.put(f.sig(), func);
+
+ // next function starts where the last one ends
+ func_start = end;
+ }
+ }
+ }
+
+ public final HashMap _bigCons = new HashMap<>();
+ private void encodeConstants(SymbolSection symbols, DataSection rdata) {
+ int cnt = 0;
+ for (Map.Entry e : _code._encoding._bigCons.entrySet()) {
+ if (_bigCons.get(e.getValue()) != null) {
+ continue;
+ }
+
+ Symbol glob = new Symbol("GLOB$"+cnt, rdata._index, SYM_BIND_GLOBAL, SYM_TYPE_FUNC);
+ glob._value = rdata._contents.size();
+ symbols.push(glob);
+
+ SONType t = e.getValue();
+ if ( t instanceof SONTypeFloat tf ) {
+ write8(rdata._contents, Double.doubleToLongBits(tf._con));
+ } else {
+ throw Utils.TODO();
+ }
+
+ glob._size = rdata._contents.size() - glob._value;
+ _bigCons.put(e.getValue(), glob);
+ cnt++;
+ }
+ }
+
+ public void export(String fname) throws IOException {
+ DataSection strtab = new DataSection(".strtab", 3 /* SHT_SYMTAB */);
+ // first byte is reserved for an empty string
+ strtab._contents.write(0);
+ pushSection(strtab);
+
+ // place all the symbols
+ SymbolSection symbols = new SymbolSection(".symtab", 2 /* SHT_STRTAB */);
+ pushSection(symbols);
+
+ // we've already constructed this entire section in the encoding phase
+ DataSection text = new DataSection(".text", 1 /* SHT_PROGBITS */, _code._encoding._bits);
+ text._flags = SHF_ALLOC | SHF_WRITE | SHF_EXECINSTR;
+ pushSection(text);
+
+ DataSection rdata = new DataSection(".rodata", 1 /* SHT_PROGBITS */);
+ rdata._flags = SHF_ALLOC;
+ pushSection(rdata);
+
+ // populate function symbols
+ encodeFunctions(symbols, text);
+ // populate big constants
+ // encodeConstants(symbols, rdata);
+
+ // create .text relocations
+ DataSection text_rela = new DataSection(".rela.text", 4 /* SHT_RELA */);
+ for( Node n : _code._encoding._externals.keySet()) {
+ int nid = n._nid;
+ String extern = _code._encoding._externals.get(n);
+ int sym_id = _funcs.get(extern)._index;
+ int offset = _code._encoding._opStart[nid] + _code._encoding._opLen[nid] - 4;
+
+ // u64 offset
+ write8(text_rela._contents, offset);
+ // u64 info
+ write8(text_rela._contents, ((long)sym_id << 32L) | 4L /* PLT32 */);
+ // i64 addend
+ write8(text_rela._contents, -4);
+ }
+ // relocations to constants
+ if (false) for (Map.Entry e : _code._encoding._bigCons.entrySet()) {
+ int nid = e.getKey()._nid;
+ int sym_id = _bigCons.get(e.getValue())._index;
+ int offset = _code._encoding._opStart[nid] + _code._encoding._opLen[nid] - 4;
+
+ // u64 offset
+ write8(text_rela._contents, offset);
+ // u64 info
+ write8(text_rela._contents, ((long)sym_id << 32L) | 1L /* PC32 */);
+ // i64 addend
+ write8(text_rela._contents, -4);
+ }
+ text_rela._flags = SHF_INFO_LINK;
+ text_rela._link = 2;
+ text_rela._info = text._index;
+ pushSection(text_rela);
+
+ // populate string table
+ for( Section s : _sections ) { s._name_pos = writeCString(strtab, s._name); }
+ for( Symbol s : symbols._symbols ) { s._name_pos = writeCString(strtab, s._name); }
+ for( Symbol s : symbols._loc ) { s._name_pos = writeCString(strtab, s._name); }
+
+ int idx = 1;
+ for( Section s : _sections ) {
+ Symbol sym = new Symbol(s._name, idx++, SYM_BIND_LOCAL, SYM_TYPE_SECTION);
+ // we can reuse the same name pos from the actual section
+ sym._name_pos = s._name_pos;
+ sym._size = s.size();
+ symbols.push(sym);
+ }
+
+ int size = 64; // ELF header
+ // size of all section data
+ for( Section s : _sections ) {
+ s._file_offset = size;
+ size += s.size();
+ }
+ // headers go at the very end
+ int shdr_offset = size;
+ // there's a "null" section at the start of the section headers
+ size += SECTION_HDR_SIZE*(1 + _sections.len());
+
+ // System.out.printf("Hello %d, %d, %s\n", size, shdr_offset);
+ ByteBuffer out = ByteBuffer.allocate(size);
+ out.order(ByteOrder.LITTLE_ENDIAN);
+
+ // ELF header
+ out.put(new byte[]{ 0x7F, 'E', 'L', 'F', 2, 1, 1, 0 });
+ out.put(new byte[8]);
+ out.putShort((short)1 /* ET_REL */); // type
+ out.putShort((short)62 /* AMD64 */); // machine
+ out.putInt(1); // version
+ out.putLong(0); // entry
+ out.putLong(0); // phoff
+ out.putLong(shdr_offset); // shoff
+ out.putInt(0); // flags
+ out.putShort((short)64); // ehsize
+ out.putShort((short)0); // phentsize
+ out.putShort((short)0); // phnum
+ out.putShort((short)64); // shentsize
+ out.putShort((short) (1 + _sections.len())); // shnum
+ out.putShort((short)1); // shstrndx
+
+ // raw section data
+ for( Section s : _sections ) {
+ s.write(out);
+ }
+
+ // "null" section
+ for( int i = 0; i < 16; i++ ) {
+ out.putInt(0);
+ }
+
+ for( Section s : _sections ) {
+ s.writeHeader(out);
+ }
+ assert out.position() == size;
+
+ BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(fname));
+ bos.write(out.array());
+ bos.close();
+ }
+
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/Encoding.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/Encoding.java
new file mode 100644
index 0000000..a30447d
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/Encoding.java
@@ -0,0 +1,418 @@
+package com.compilerprogramming.ezlang.compiler.codegen;
+
+import com.compilerprogramming.ezlang.compiler.Ary;
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.Utils;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+import com.compilerprogramming.ezlang.compiler.sontypes.*;
+import java.io.ByteArrayOutputStream;
+import java.util.*;
+
+
+/**
+ * Instruction encodings
+ *
+ * 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).
+ *
+ * There are also a bunch of generic utilities for managing bits and bytes
+ * common in all encodings.
+ */
+public class Encoding {
+
+ // Top-level program graph structure
+ final CodeGen _code;
+
+ // Instruction bytes. The registers are encoded already. Relocatable data
+ // is in a fixed format depending on the kind of relocation.
+
+ // - RIP-relative offsets into the same chunk are just encoded with their
+ // check relative offsets; no relocation info is required.
+
+ // - Fixed address targets in the same encoding will be correct for a zero
+ // base; moving the entire encoding requires adding the new base address.
+
+ // - RIP-relative to external chunks have a zero offset; the matching
+ // relocation info will be used to patch the correct value.
+
+ public static class BAOS extends ByteArrayOutputStream {
+ public byte[] buf() { return buf; }
+ void set( byte[] buf0, int count0 ) { buf=buf0; count=count0; }
+ };
+ final public BAOS _bits = new BAOS();
+
+ public int [] _opStart; // Start of opcodes, by _nid
+ public byte[] _opLen; // Length of opcodes, by _nid
+
+ Encoding( CodeGen code ) { _code = code; }
+
+ // Shortcut to the defining register
+ public short reg(Node n) {
+ return _code._regAlloc.regnum(n);
+ }
+
+ public Encoding add1( int op ) { _bits.write(op); return this; }
+
+ public void add2( int op ) {
+ _bits.write(op );
+ _bits.write(op>> 8);
+ }
+ // Little endian write of a 32b opcode
+ public void add4( int op ) {
+ _bits.write(op );
+ _bits.write(op>> 8);
+ _bits.write(op>>16);
+ _bits.write(op>>24);
+ }
+ public void add8( long i64 ) {
+ add4((int) i64 );
+ add4((int)(i64>>32));
+ }
+ public int read4(int idx) {
+ byte[] buf = _bits.buf();
+ return buf[idx] | (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);
+ }
+
+ void pad8() {
+ while( (_bits.size()+7 & -8) > _bits.size() )
+ _bits.write(0);
+ }
+
+
+ // Nodes need "relocation" patching; things done after code is placed.
+ // Record src and dst Nodes.
+ private final HashMap _internals = new HashMap<>();
+ // Source is a Call, destination in the Fun.
+ public Encoding relo( CallNode call ) {
+ _internals.put(call,_code.link(call.tfp()));
+ return this;
+ }
+ public Encoding relo( ConstantNode con ) {
+ SONTypeFunPtr tfp = (SONTypeFunPtr)con._con;
+ _internals.put(con,_code.link(tfp));
+ return this;
+ }
+ public void jump( CFGNode jmp, CFGNode dst ) {
+ while( dst.nOuts() == 1 ) // Skip empty blocks
+ dst = dst.uctrl();
+ _internals.put(jmp,dst);
+ }
+
+
+ final HashMap _externals = new HashMap<>();
+ public Encoding external( Node call, String extern ) {
+ _externals.put(call,extern);
+ return this;
+ }
+
+ // Store t as a 32/64 bit constant in the code space; generate RIP-relative
+ // addressing to load it
+
+ public final HashMap _bigCons = new HashMap<>();
+ public final HashMap _cpool = new HashMap<>();
+ public void largeConstant( Node relo, SONType t ) {
+ assert t.isConstant();
+ _bigCons.put(relo,t);
+ }
+
+ void encode() {
+ // Basic block layout: invert 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 RPO but with more restrictions.
+ basicBlockLayout();
+
+ // Write encoding bits in order into a big byte array.
+ // Record opcode start and length.
+ writeEncodings();
+
+ // Write any large constants into a constant pool; they
+ // are accessed by RIP-relative addressing.
+ writeConstantPool();
+
+ // Short-form RIP-relative support: replace long encodings with short
+ // encodings and compact the code, changing all the offsets.
+ compactShortForm();
+
+ // Patch RIP-relative and local encodings now.
+ patchLocalRelocations();
+ }
+
+ // Basic block layout: invert 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 RPO but with more restrictions.
+ private void basicBlockLayout() {
+ Ary rpo = new Ary<>(CFGNode.class);
+ BitSet visit = _code.visit();
+ for( Node n : _code._start._outputs )
+ if( n instanceof FunNode fun )
+ _rpo_cfg(fun, visit, rpo);
+ rpo.add(_code._start);
+
+ // Reverse in-place
+ for( int i=0; i< rpo.size()>>1; i++ )
+ rpo.swap(i,rpo.size()-1-i);
+ visit.clear();
+ _code._cfg = rpo; // Save the new ordering
+ }
+
+ // Basic block layout. Now that RegAlloc is finished, no more spill code
+ // will appear. We can change our BB layout from RPO to something that
+ // minimizes actual branches, takes advantage of fall-through edges, and
+ // tries to help simple branch predictions: back branches are predicted
+ // taken, forward not-taken.
+ private void _rpo_cfg(CFGNode bb, BitSet visit, Ary rpo) {
+ if( visit.get(bb._nid) ) return;
+ visit.set(bb._nid);
+ if( bb.nOuts()==0 ) return; // StopNode
+ if( !(bb instanceof IfNode iff) ) {
+ CFGNode next = bb instanceof ReturnNode ? (CFGNode)bb.out(bb.nOuts()-1) : bb.uctrl();
+ // If the *next* BB has already been visited, we may need an
+ // unconditional jump here
+ if( visit.get(next._nid) && !(next instanceof StopNode) ) {
+ // If all the blocks, in RPO order, to our target
+ // are empty, we will fall in and not need a jump.
+ if( next instanceof LoopNode || !isEmptyBackwardsScan(rpo,next) ) {
+ CFGNode jmp = _code._mach.jump();
+ jmp.setDefX(0,bb);
+ next.setDef(next._inputs.find(bb),jmp);
+ rpo.add(jmp);
+ }
+ }
+ _rpo_cfg(next,visit,rpo);
+ } else {
+ boolean invert = false;
+ // Pick out the T/F projections
+ CProjNode t = iff.cproj(0);
+ CProjNode f = iff.cproj(1);
+ int tld = t.loopDepth(), fld = f.loopDepth(), bld = bb.loopDepth();
+ // Decide entering or exiting a loop
+ if( tld==bld ) {
+ // if T is empty, keep
+ if( t.nOuts()>1 &&
+ // Else swap so T is forward and exits, while F falls into next loop block
+ ((fld bld ) { // True enters a deeper loop
+ throw Utils.TODO();
+ } else if( fld == bld && f.out(0) instanceof LoopNode ) { // Else True exits a loop
+ // if false is the loop backedge, make sure its true/taken
+ invert = true;
+ } // always forward and good
+ // Invert test and Proj fields
+ if( invert ) {
+ iff.invert();
+ t.invert();
+ f.invert();
+ CProjNode tmp=f; f=t; t=tmp; // Swap t/f
+ }
+
+ // Always 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,rpo);
+ _rpo_cfg(f,visit,rpo);
+ }
+ rpo.add(bb);
+ }
+
+ private static boolean isEmptyBackwardsScan(Ary rpo, CFGNode next) {
+ for( int i=rpo._len-1; rpo.at(i)!=next; i-- )
+ if( rpo.at(i).nOuts()!=1 )
+ return false;
+ return true;
+ }
+
+ // Write encoding bits in order into a big byte array.
+ // Record opcode start and length.
+ public FunNode _fun; // Currently encoding function
+ private void writeEncodings() {
+ _opStart= new int [_code.UID()];
+ _opLen = new byte[_code.UID()];
+ for( CFGNode bb : _code._cfg ) {
+ if( !(bb instanceof MachNode mach0) )
+ _opStart[bb._nid] = _bits.size();
+ else if( bb instanceof FunNode fun ) {
+ _fun = fun; // Currently encoding function
+ _opStart[bb._nid] = _bits.size();
+ mach0.encoding( this );
+ _opLen[bb._nid] = (byte) (_bits.size() - _opStart[bb._nid]);
+ }
+ for( Node n : bb._outputs ) {
+ if( n instanceof MachNode mach && !(n instanceof FunNode) ) {
+ _opStart[n._nid] = _bits.size();
+ mach.encoding( this );
+ _opLen[n._nid] = (byte) (_bits.size() - _opStart[n._nid]);
+ }
+ }
+ }
+ pad8();
+ }
+
+ // Write the constant pool
+ private void writeConstantPool() {
+ // TODO: Check for cpool dups
+ HashSet ts = new HashSet<>();
+ for( SONType t : _bigCons.values() ) {
+ if( ts.contains(t) )
+ throw Utils.TODO(); // Dup! Compress!
+ ts.add(t);
+ }
+
+ // Write the 8-byte constants
+ for( Node relo : _bigCons.keySet() ) {
+ SONType t = _bigCons.get(relo);
+ if( t.log_size()==3 ) {
+ // Map from relo to constant start
+ _cpool.put(relo,_bits.size());
+ long x = t instanceof SONTypeInteger ti
+ ? ti.value()
+ : Double.doubleToRawLongBits(((SONTypeFloat)t).value());
+ add8(x);
+ }
+ }
+
+ // Write the 4-byte constants
+ for( Node relo : _bigCons.keySet() ) {
+ SONType t = _bigCons.get(relo);
+ if( t.log_size()==2 ) {
+ // Map from relo to constant start
+ _cpool.put(relo,_bits.size());
+ int x = t instanceof SONTypeInteger ti
+ ? (int)ti.value()
+ : Float.floatToRawIntBits((float)((SONTypeFloat)t).value());
+ add4(x);
+ }
+ }
+ }
+
+ // Short-form RIP-relative support: replace short encodings with long
+ // encodings and expand the code, changing all the offsets.
+ private void compactShortForm() {
+ int len = _code._cfg._len;
+ int[] oldStarts = new int[len];
+ for( int i=0; i 0;
+ int delta = _opStart[target._nid] - _opStart[bb._nid];
+ byte opLen = riprel.encSize(delta);
+ // Recorded size is smaller than the current size?
+ if( _opLen[bb._nid] < opLen ) {
+ // Start sliding the code down; record slide amount and new size
+ slide += opLen - _opLen[bb._nid];
+ _opLen[bb._nid] = opLen;
+ }
+ }
+ }
+
+ // CPool padding is non-linear; in rare cases padding can force a
+ // larger size... which will shrink the padding and allow the
+ // short form to work. Too bad.
+ if( !_cpool.isEmpty() )
+ pad8();
+ }
+
+
+ // Copy/slide the bits to make space for all the longer branches
+ int grow = _opStart[_code._cfg.at(len-1)._nid] - oldStarts[len-1];
+ if( grow > 0 ) { // If no short-form ops, nothing to do here
+ int end = _bits.size();
+ byte[] bits = new byte[end+grow];
+ for( int i=len-1; i>=0; i-- ) {
+ int start = oldStarts[i];
+ if( start==0 && i>1 ) continue;
+ int oldStart = oldStarts[i];
+ int newStart = _opStart[_code._cfg.at(i)._nid];
+ System.arraycopy(_bits.buf(),oldStart,bits,newStart,end-start);
+ end = start;
+ }
+ _bits.set(bits,bits.length);
+ }
+ }
+
+ // Patch local encodings now
+ private void patchLocalRelocations() {
+ // Walk the local code-address relocations
+ for( Node src : _internals.keySet() ) {
+ Node dst = _internals.get(src);
+ int target = _opStart[dst._nid];
+ int start = _opStart[src._nid];
+ ((RIPRelSize)src).patch(this, start, _opLen[src._nid], target - start);
+ }
+
+ for( Node src : _cpool.keySet() ) {
+ int target = _cpool.get(src);
+ int start = _opStart[src._nid];
+ ((RIPRelSize)src).patch(this, start, _opLen[src._nid], target - start);
+ }
+ }
+
+
+ // Actual stack layout is up to each CPU.
+ // X86, with too many args & spills:
+ // | CALLER |
+ // | argN | // slot 1, required by callER
+ // +--------+
+ // | RPC | // slot 0, required by callER
+ // | callee | // slot 3, callEE
+ // | callee | // slot 2, callEE
+ // | PAD16 |
+ // +--------+
+
+ // RISC/ARM, with too many args & spills:
+ // | CALLER |
+ // | argN | // slot 0, required by callER
+ // +--------+
+ // | callee | // slot 3, callEE: might be RPC
+ // | callee | // slot 2, callEE
+ // | callee | // slot 1, callEE
+ // | PAD16 |
+ // +--------+
+ private void frameSize() {
+ for( Node n : _code._start.outs() ) {
+ if( n instanceof FunNode fun ) {
+ throw Utils.TODO();
+ }
+ }
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/GlobalCodeMotion.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/GlobalCodeMotion.java
new file mode 100644
index 0000000..f0af74b
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/GlobalCodeMotion.java
@@ -0,0 +1,281 @@
+package com.compilerprogramming.ezlang.compiler.codegen;
+
+import com.compilerprogramming.ezlang.compiler.Ary;
+import com.compilerprogramming.ezlang.compiler.IterPeeps.WorkList;
+import com.compilerprogramming.ezlang.compiler.Utils;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+import com.compilerprogramming.ezlang.compiler.sontypes.*;
+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 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);
+
+ code._visit.clear();
+ schedLate (code);
+ }
+
+ // Post-Order of CFG
+ private static void _rpo_cfg(CFGNode def, Node use, BitSet visit, Ary rpo) {
+ if( use instanceof CallNode call ) call.unlink_all();
+ if( !(use instanceof CFGNode cfg) || visit.get(cfg._nid) )
+ return; // Been there, done that
+ if( def instanceof ReturnNode && use instanceof CallEndNode )
+ return;
+ assert !( def instanceof CallNode && use instanceof FunNode );
+ visit.set(cfg._nid);
+ for( Node useuse : cfg._outputs )
+ _rpo_cfg(cfg,useuse,visit,rpo);
+ rpo.add(cfg);
+ }
+
+ // Break up shared global constants by functions
+ private static void breakUpGlobalConstants( CodeGen code ) {
+ // For all global constants
+ for( int i=0; i< code._start.nOuts(); i++ ) {
+ Node con = code._start.out(i);
+ if( con instanceof MachNode mach && mach.isClone() ) {
+ // While constant has users in different functions
+ while( true ) {
+ // Find a function user, and another function
+ FunNode fun = null;
+ boolean done=true;
+ for( Node use : con.outs() ) {
+ FunNode fun2 = use.cfg0().fun();
+ if( fun==null || fun==fun2 ) fun=fun2;
+ else { done=false; break; }
+ }
+ // Single function user, so this constant is not shared
+ if( done ) { i--; con.setDef(0,fun); break; }
+ // Move function users to a private constant
+ Node con2 = mach.copy(); // Private constant clone
+ con2._inputs.set(0,null); // Preserve edge invariants from clone
+ con2.setDef(0,fun);
+ // Move function users to this private constant
+ for( int j=0; j early.idepth() )
+ early = n.in(i).cfg0(); // Latest/deepest input
+ n.setDef(0,early);
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ private static void schedLate( CodeGen code ) {
+ CFGNode[] late = new CFGNode[code.UID()];
+ Node[] ns = new Node[code.UID()];
+ // Breadth-first scheduling
+ breadth(code._stop,ns,late);
+
+ // Copy the best placement choice into the control slot
+ for( int i=0; i 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 LoadNode ld )
+ for( Node memuse : ld.mem()._outputs )
+ if( late[memuse._nid]==null &&
+ // New makes new memory, never crushes load memory
+ !(memuse instanceof NewNode) &&
+ // Load-use directly defines memory
+ (memuse._type instanceof SONTypeMem ||
+ // Load-use indirectly defines memory
+ (memuse._type instanceof SONTypeTuple tt && tt._types[ld._alias] instanceof SONTypeMem)) )
+ continue outer;
+
+ // All uses done, schedule
+ _doSchedLate(n,ns,late);
+ }
+
+ // Walk all inputs and put on worklist, as their last-use might now be done
+ for( Node def : n._inputs )
+ if( def!=null && late[def._nid]==null ) {
+ work.push(def);
+ // if the def has a load use, maybe the load can fire
+ for( Node ld : def._outputs )
+ if( ld instanceof LoadNode && late[ld._nid]==null )
+ work.push(ld);
+ }
+ }
+ }
+
+ private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late) {
+ // Walk uses, gathering the LCA (Least Common Ancestor) of uses
+ CFGNode early = n.in(0) instanceof CFGNode cfg ? cfg : n.in(0).cfg0();
+ assert early != null;
+ CFGNode lca = null;
+ for( Node use : n._outputs )
+ if( use != null )
+ lca = use_block(n,use, late)._idom(lca,null);
+
+ // Loads may need anti-dependencies, raising their LCA
+ if( n instanceof LoadNode load )
+ lca = find_anti_dep(lca,load,early,late);
+
+ // Walk up from the LCA to the early, looking for best place. This is
+ // the lowest execution frequency, approximated by least loop depth and
+ // deepest control flow.
+ CFGNode best = lca;
+ lca = lca.idom(); // Already found best for starting LCA
+ for( ; lca != early.idom(); lca = lca.idom() )
+ if( better(lca,best) )
+ best = lca;
+ assert !(best instanceof IfNode);
+ ns [n._nid] = n;
+ late[n._nid] = best;
+ }
+
+ // Block of use. Normally from late[] schedule, except for Phis, which go
+ // to the matching Region input.
+ private static CFGNode use_block(Node n, Node use, CFGNode[] late) {
+ if( !(use instanceof PhiNode phi) )
+ return late[use._nid];
+ CFGNode found=null;
+ for( int i=1; i best.idepth() || best instanceof IfNode);
+ }
+
+ private static CFGNode find_anti_dep(CFGNode lca, LoadNode load, CFGNode early, CFGNode[] late) {
+ // We could skip final-field loads here.
+ // Walk LCA->early, flagging Load's block location choices
+ for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ cfg._anti = load._nid;
+ // Walk load->mem uses, looking for Stores causing an anti-dep
+ for( Node mem : load.mem()._outputs ) {
+ switch( mem ) {
+ case StoreNode st:
+ assert late[st._nid]!=null;
+ lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st);
+ break;
+ case CallNode st:
+ assert late[st._nid]!=null;
+ lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st);
+ break;
+ case PhiNode phi:
+ // Repeat anti-dep for matching Phi inputs.
+ // No anti-dep edges but may raise the LCA.
+ for( int i=1; i> BBOUTS = new IdentityHashMap<>();
+ static void resetBBLiveOut() {
+ for( IdentityHashMap bbout : BBOUTS.values() )
+ bbout.clear();
+ }
+ static final IdentityHashMap TMP = new IdentityHashMap<>();
+
+ // Inteference Graph: Array of Bitsets
+ static final Ary 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 WORK = new Ary<>(CFGNode.class);
+ static void pushWork(CFGNode bb) {
+ if( WORK.find(bb) == -1 )
+ WORK.push(bb);
+ }
+
+ // ------------------------------------------------------------------------
+ // Visit all blocks, using the live-out LRGs per-block and doing a backwards
+ // walk over each block. At the end of the walk, push the live-out sets to
+ // prior blocks. Due to loops, no single visitation order suffices. This
+ // problem can be fairly efficiently solved with a proper LIVE calculation
+ // and bitsets. In the interests of simplicity (and assumption of smaller
+ // programs) I am skipping LIVE and directly computing it during the IFG
+ // building.
+
+ // Start by setting the live-out of the exit block (to empty), and putting
+ // it on a worklist.
+
+ // - Pull from worklist a block who's live-out has changed
+ // - Walk backwards, adding interferences and computing live.
+ // - At block head, "push" the new live-out to prior blocks.
+ // - - If they pick up new live-outs, put them on the worklist.
+
+ public static boolean build( int round, RegAlloc alloc ) {
+ // Reset all to empty
+ resetBBLiveOut();
+ resetIFG();
+
+ // Last block has nothing live out
+ assert WORK.isEmpty();
+ for( CFGNode bb : alloc._code._cfg )
+ if( bb.blockHead())
+ WORK.push( bb );
+
+ // Process blocks until no more changes
+ while( !WORK.isEmpty() )
+ do_block(round,alloc,WORK.pop());
+
+ return alloc.success();
+ }
+
+ // Walk one block backwards, compute live-in from live-out, and build IFG
+ static void do_block( int round, RegAlloc alloc, CFGNode bb ) {
+ assert bb.blockHead();
+ IdentityHashMap 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; i1 || // Has many users OR
+ // Only 1 user but effective use is remote block
+ effUseBlk != n.cfg0() ))
+ // Then fail the clonable; it should split or move
+ alloc.fail(alloc.lrg((Node)m));
+ // Else clonable cannot move
+ else if( !tlrg.sub(killMask) )
+ alloc.fail(tlrg);
+ }
+ }
+ }
+
+ // Check for self-conflict live ranges. These must split, and only happens
+ // during the first round a particular LRG splits.
+ private static void selfConflict(RegAlloc alloc, Node n, LRG lrg) {
+ selfConflict(alloc,n,lrg,TMP.get(lrg));
+ }
+ private static void selfConflict(RegAlloc alloc, Node n, LRG lrg, Node prior) {
+ if( prior!=null && prior != n ) {
+ lrg.selfConflict(prior);
+ lrg.selfConflict(n);
+ alloc.fail(lrg); // 2 unrelated values live at once same live range; self-conflict
+ }
+ }
+
+ // Merge TMP into bb's live-out set; if changes put bb on WORK
+ private static void mergeLiveOut( RegAlloc alloc, CFGNode priorbb, int i ) {
+ CFGNode bb = priorbb.cfg(i);
+ if( bb == null ) return; // Start has no prior
+ if( !bb.blockHead() ) bb = bb.cfg0();
+ //if( i==0 && !(bb instanceof StartNode) ) bb = bb.cfg0();
+ assert bb.blockHead();
+
+ // Lazy get live-out set for bb
+ IdentityHashMap lrgs = BBOUTS.computeIfAbsent( bb, k -> new IdentityHashMap<>() );
+
+ for( LRG lrg : TMP.keySet() ) {
+ Node def = TMP.get(lrg);
+ // Effective def comes from phi input from prior block
+ if( def instanceof PhiNode phi && phi.cfg0()==priorbb ) {
+ assert i!=0;
+ def = phi.in( i );
+ }
+ Node def_bb = lrgs.get(lrg);
+ if( def_bb==null ) {
+ lrgs.put(lrg,def);
+ pushWork(bb);
+ } else {
+ // Alive twice with different definitions; self-conflict
+ selfConflict(alloc,def,lrg,def_bb);
+ }
+ }
+ }
+
+
+ // ------------------------------------------------------------------------
+ // Color the inference graph.
+
+ // Coloring works by removing "trivial" live ranges from the IFG - those
+ // live ranges with fewer neighbors than available colors (registers).
+ // These are trivial because even if every neighbor takes a unique color,
+ // there's at least one more available to color this live range.
+
+ // If we hit a clique of non-trivial live ranges, we pick one to be "at
+ // risk" of not-coloring. Good choices include live ranges with a large
+ // area and few hot uses.
+
+ // Then we reverse and put-back live ranges - picking a color as we go.
+ // If there's no spare color we'll have to spill this at-risk live range.
+
+ public static boolean color(int round, RegAlloc alloc) {
+ int maxlrg = alloc._LRGS.length, nlrgs=0;
+ for( int i=1; i swork )
+ swork = sptr;
+
+ // Walk all neighbors and remove
+ if( lrg._adj != null ) {
+ for( LRG nlrg : lrg._adj ) {
+ // Remove and compress out neighbor
+ if( nlrg.removeCompress(lrg) ) {
+ // Neighbor is just exactly going trivial as 'lrg' is removed from IFG
+ // Find "j" position in the color_stack
+ int jx = swork; while( color_stack[jx] != nlrg ) jx++;
+ // Add trivial neighbor to trivial list. Pull lrg j out of
+ // unknown set, since its now in the trivial set
+ swap(color_stack, swork++, jx);
+ }
+ }
+ }
+ }
+
+ // Reverse simplify (unstack the color stack), and set colors (registers) for live ranges
+ while( sptr > 0 ) {
+ LRG lrg = color_stack[--sptr];
+ if( lrg==null ) continue;
+ RegMaskRW rmask = lrg._mask.copy();
+ // Walk neighbors and remove adjacent colors
+ if( lrg._adj!=null ) {
+ for( LRG nlrg : lrg._adj ) {
+ nlrg.reinsert(lrg);
+ int reg = nlrg._reg;
+ if( reg!= -1 ) // Failed neighbors do not count
+ rmask.clr(reg); // Remove neighbor from my choices
+ }
+ }
+ // At-risk live-range did not color?
+ if( rmask.isEmpty() ) {
+ alloc.fail(lrg);
+ lrg._reg = -1;
+ } else {
+ // Pick first available register
+ short reg = rmask.firstReg();
+ // Pick a "good" color from the choices. Typically, biased-coloring
+ // removes some over-spilling.
+ if( rmask.size() > 1 ) reg = biasColor(alloc,lrg,reg,rmask);
+ lrg._reg = reg; // Assign the register
+ }
+ }
+
+ return alloc.success();
+ }
+
+ // Pick LRG from color stack
+ private static void pickColor(LRG[] color_stack, int sptr, int swork) {
+ // Out of trivial colorable, pick an at-risk to pull
+ if( sptr==swork )
+ swap(color_stack,sptr,pickRisky(color_stack,sptr));
+ // When coloring, we'd like to give more choices; so when coloring we'd
+ // like to see the single-def first (since no choices anyway), then
+ // non-split related (so more live ranges get colored), then
+ // split-related last, so they have more colors to bias towards.
+
+ // Working in reverse, pick first split-related with many regs, then
+ // those with some regs, then single-def.
+ int bidx=sptr;
+ LRG best=color_stack[bidx];
+ for( int idx = sptr+1; idx < swork; idx++ )
+ if( betterLRG(best,color_stack[idx]) )
+ best = color_stack[bidx=idx];
+ if( bidx != sptr )
+ swap(color_stack,sptr,bidx); // Pick best at sptr
+ }
+
+ private static boolean betterLRG( LRG best, LRG lrg ) {
+ // If single-def varies, keep the not-single-def
+ if( best.size1() != lrg.size1() )
+ return best.size1();
+ // If hasSplit varies, keep the hasSplit
+ if( best.hasSplit() != lrg.hasSplit() )
+ return lrg.hasSplit();
+ // Keep large register count
+ return best.size() < lrg.size();
+ }
+
+ // Pick a live range that hasn't already spilled, or has a single-def-
+ // single-use that are not adjacent.
+ private static int pickRisky( LRG[] color_stack, int sptr ) {
+ int best=sptr;
+ int bestScore = pickRiskyScore(color_stack[best]);
+ for( int i=sptr+1; i bestScore )
+ { best = i; bestScore = iScore; }
+ }
+ return best;
+ }
+
+ // Pick a live range to pull, that might not color.
+ //
+ // Picking a live range with a very large span, with defs and uses outside
+ // loops means spilling a relative cheap live range and getting that
+ // register over a large area.
+ //
+ // Picking a live range that is very close to coloring might allow it to
+ // color despite being risky.
+ private static int pickRiskyScore( LRG lrg ) {
+ // Pick single-def clonables that are not right next to their single-use.
+ // Failing to color these will clone them closer to their uses.
+ if( !lrg._multiDef && lrg._machDef.isClone() ) {
+ Node def = ((Node)lrg._machDef);
+ Node use = ((Node)lrg._machUse);
+ CFGNode cfg = def.cfg0();
+ if( cfg != use.cfg0() || // Different blocks OR
+ // Same block, but not close
+ cfg._outputs.find(def) < cfg._outputs.find(use)+1 )
+ return 1000000;
+ }
+
+ // Always pick callee-save registers as being very large area recovered
+ // and very cheap to spill.
+ if( lrg._machDef instanceof CalleeSaveNode )
+ return 1000000-2;
+ if( lrg._splitDef != null && lrg._splitDef.in(1) instanceof CalleeSaveNode &&
+ lrg._splitUse != null && lrg._splitUse.out(0) instanceof ReturnNode )
+ return 1000000-1;
+
+ // TODO: cost/benefit model. Perhaps counting loop-depth (freq) of def/use for cost
+ // and "area" for benefit
+ return 1000;
+ }
+
+ private static short biasColor( RegAlloc alloc, LRG lrg, short reg, RegMask mask ) {
+ if( mask.size1() ) return reg;
+ // Check chain of splits up the def-chain. Take first allocated
+ // register, and if it's available in the mask, take it.
+ Node defSplit = lrg._splitDef, useSplit = lrg._splitUse;
+ int tidx, cnt=0;
+
+ while( (tidx=biasable(defSplit)) != 0 || biasable(useSplit) != 0 ) {
+ if( cnt++ > 10 ) break;
+
+ if( tidx != 0 ) {
+ short bias = biasColor( alloc, defSplit, mask );
+ if( bias >= 0 ) return bias; // Good bias
+ if( bias == -2 ) defSplit = null; // Kill this side, no more searching
+ } else defSplit = null;
+
+ if( biasable(useSplit) != 0 ) {
+ short bias = biasColor( alloc, useSplit, mask );
+ if( bias >= 0 ) return bias; // Good bias
+ if( bias == -2 ) useSplit = null; // Kill this side, no more searching
+ } else useSplit = null;
+
+ if( defSplit != null ) {
+ short bias = biasColorNeighbors( alloc, defSplit, mask );
+ if( bias >= 0 ) return bias;
+ // Advance def side
+ defSplit = defSplit.in(tidx);
+ }
+
+ if( useSplit != null ) {
+ short bias = biasColorNeighbors( alloc, useSplit, mask );
+ if( bias >= 0 ) return bias;
+ useSplit = useSplit.out(0);
+ }
+
+ }
+ return mask.firstReg();
+ }
+
+ private static int biasable(Node split) {
+ if( split instanceof SplitNode ) return 1; // Yes biasable, advance is slot 1
+ if( split instanceof PhiNode ) return 1; // Yes biasable, advance is slot 1
+ if( !(split instanceof MachNode mach) ) return 0; // Not biasable
+ return mach.twoAddress(); // Only biasable if 2-addr
+ }
+
+ // 3-way return:
+ // - good bias reg, take it & exit
+ // - this path is cutoff; do not search here anymore
+ // - advance this side
+ private static short biasColor( RegAlloc alloc, Node split, RegMask mask ) {
+ short bias = alloc.lrg(split)._reg;
+ if( bias != -1 ) {
+ if( mask.test(bias) ) return bias; // Good bias
+ else return -2; // Kill this side
+ } else return -1; // Advance this side
+ }
+
+ // Check if we can match "split" color, or else trim "mask" to colors
+ // "split" might get.
+ private static short biasColorNeighbors( RegAlloc alloc, Node split, RegMask mask ) {
+ LRG slrg = alloc.lrg(split);
+ if( slrg._adj == null ) return -1; // No trimming
+
+ // Can I limit my own choices to valid neighbor choices?
+ for( LRG alrg : slrg._adj ) {
+ int reg = alrg._reg;
+ if( reg == -1 && alrg._mask.size1() )
+ reg = alrg._mask.firstReg();
+ if( reg != -1 ) {
+ mask.clr(alrg._reg);
+ if( mask.size1() )
+ return mask.firstReg();
+ }
+ }
+ return -1; // No obvious color choice, but mask got trimmed
+ }
+
+ private static void swap( LRG[] ary, int x, int y ) {
+ LRG tmp = ary[x]; ary[x] = ary[y]; ary[y] = tmp;
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/LRG.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/LRG.java
new file mode 100644
index 0000000..0662c5f
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/LRG.java
@@ -0,0 +1,235 @@
+package com.compilerprogramming.ezlang.compiler.codegen;
+
+import com.compilerprogramming.ezlang.compiler.Ary;
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+import java.util.IdentityHashMap;
+
+/** A Live Range
+ *
+ * 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.
+ *
+ * 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 -
+ * (this is the Union-Find algorithm)
+ * - 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
+
+ // Mask set to empty via a kill-mask. This is usually a capacity kill,
+ // which means we need to split and spill into memory - which means even if
+ // the def-side has many registers, it MUST spill.
+ boolean _killed;
+
+ // Some splits used in biased coloring
+ MachConcreteNode _splitDef, _splitUse;
+
+ // All the self-conflicting defs for this live range
+ IdentityHashMap _selfConflicts;
+
+ // AND of all masks involved; null if none have been applied yet
+ RegMask _mask;
+
+ // Adjacent Live Range neighbors. Only valid during coloring
+ Ary _adj;
+
+ void addNeighbor(LRG lrg) {
+ if( _adj==null ) _adj = new Ary<>(LRG.class);
+ _adj.push(lrg);
+ }
+
+ // Remove and compress neighbor list; store ex-neighbor past
+ // end for unwinding Simplify. True if exactly going low-degree
+ boolean removeCompress( LRG lrg ) {
+ _adj.swap(_adj.find(lrg),_adj._len-1);
+ return _adj._len-- == _mask.size();
+ }
+
+ void reinsert( LRG lrg ) {
+ assert _adj._es[_adj._len]==lrg;
+ _adj._len++;
+ }
+
+ // More registers than neighbors
+ boolean lowDegree() { return (_adj==null ? 0 : _adj._len) < _mask.size(); }
+
+ LRG( short lrg ) { _lrg = lrg; _reg = -1; }
+
+ boolean leader() { return _leader == null; }
+
+ // Fast-path Find from the Union-Find algorithm
+ LRG find() {
+ if( _leader==null ) return this; // I am the leader
+ if( _leader._leader==null ) // I point to the leader
+ return _leader;
+ return _rollup();
+ }
+ // Slow-path rollup of U-F
+ private LRG _rollup() {
+ LRG ldr = _leader._leader;
+ // Roll-up
+ while( ldr._leader!=null ) ldr = ldr._leader;
+ LRG l2 = this;
+ while( l2 != ldr ) {
+ LRG l3 = l2._leader;
+ l2._leader = ldr;
+ l2 = l3;
+ }
+ return ldr;
+ }
+
+ // Union `this` and `lrg`, keeping the lower numbered _lrg.
+ // Includes a number of fast-path cutouts.
+ LRG union( LRG lrg ) {
+ assert leader();
+ if( lrg==null ) return this;
+ lrg = lrg.find();
+ if( lrg==this ) return this;
+ return _lrg < lrg._lrg ? _union(lrg) : lrg._union(this);
+ }
+ // Union `this` and `lrg`, folding together all stats.
+ private LRG _union( LRG lrg ) {
+ // Set U-F leader
+ lrg._leader = this;
+ // Fold together stats
+ if( _machDef==null ) {
+ _machDef = lrg._machDef;
+ } else if( lrg._machDef!=null ) {
+ if( _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;
+ } 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/ListScheduler.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/ListScheduler.java
new file mode 100644
index 0000000..220e7fb
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/ListScheduler.java
@@ -0,0 +1,258 @@
+package com.compilerprogramming.ezlang.compiler.codegen;
+
+import com.compilerprogramming.ezlang.compiler.Ary;
+import com.compilerprogramming.ezlang.compiler.IterPeeps.WorkList;
+import com.compilerprogramming.ezlang.compiler.Utils;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+import com.compilerprogramming.ezlang.compiler.sontypes.*;
+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 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 FREE = new Ary<>(XSched.class);
+ static void alloc(CFGNode bb, Node n) {
+ XSched x = FREE.pop();
+ XS.put(n, (x==null ? new XSched() : x).init(bb,n));
+ }
+ void free() { _n = null; FREE.push(this); }
+
+ static XSched get(Node n) { return n==null ? null : XS.get(n instanceof ProjNode && !(n.in(0) instanceof CallEndNode) ? n.in(0) : n); }
+
+ private XSched init(CFGNode bb, Node n) {
+ _n = n;
+ _bcnt = 0;
+ _ruse = _rdef = _single = false;
+
+ // Count block-locals and remote inputs
+ if( !(n instanceof PhiNode) )
+ for( Node def : n._inputs )
+ if( def != null && def != bb ) {
+ if( def.cfg0()==bb ) _bcnt++; // Raise block-local input count
+ else if( !def.isMem() ) _ruse = true; // Remote register user
+ }
+ // Count remote outputs and allowed registers
+ if( n instanceof MultiNode )
+ for( Node use : n._outputs )
+ computeSingleRDef(bb,use);
+ else
+ computeSingleRDef(bb,n);
+ 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 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 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 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 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; i1 && 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/Machine.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/Machine.java
new file mode 100644
index 0000000..273ac75
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/Machine.java
@@ -0,0 +1,48 @@
+package com.compilerprogramming.ezlang.compiler.codegen;
+
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+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; }
+ // Create a split op; any register to any register, including stack slots
+ public abstract SplitNode split( String kind, byte round, LRG lrg);
+ // List of caller-save registers
+ public abstract RegMask callerSave();
+ // List of callee-save registers
+ public abstract RegMask calleeSave();
+ // Return a MachNode unconditional branch
+ public abstract CFGNode jump();
+ // Break an infinite loop
+ public abstract IfNode never( CFGNode ctrl );
+ // Instruction select from ideal nodes
+ public abstract Node instSelect( Node n );
+
+ // Convert a register to a zero-based stack *slot*, or -1.
+ // Stack slots are assumed 8 bytes each.
+ // Actual stack layout is up to each CPU.
+ // X86, with too many args & spills:
+ // | CALLER |
+ // | argN | // slot 1, required by callER
+ // +--------+
+ // | RPC | // slot 0, required by callER
+ // | callee | // slot 3, callEE
+ // | callee | // slot 2, callEE
+ // | PAD16 |
+ // +--------+
+
+ // RISC/ARM, with too many args & spills:
+ // | CALLER |
+ // | argN | // slot 0, required by callER
+ // +--------+
+ // | callee | // slot 3, callEE: might be RPC
+ // | callee | // slot 2, callEE
+ // | callee | // slot 1, callEE
+ // | PAD16 |
+ // +--------+
+ public abstract int stackSlot( int reg );
+ // Human-readable name for a register number, e.g. "RAX" or "R0"
+ public abstract String reg( int reg );
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/RIPRelSize.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/RIPRelSize.java
new file mode 100644
index 0000000..0d35458
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/RIPRelSize.java
@@ -0,0 +1,14 @@
+package com.compilerprogramming.ezlang.compiler.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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/RegAlloc.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/RegAlloc.java
new file mode 100644
index 0000000..634ebf1
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/RegAlloc.java
@@ -0,0 +1,526 @@
+package com.compilerprogramming.ezlang.compiler.codegen;
+
+import com.compilerprogramming.ezlang.compiler.Ary;
+import com.compilerprogramming.ezlang.compiler.Utils;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+import java.util.Arrays;
+import java.util.IdentityHashMap;
+
+/**
+ * "Briggs/Chaitin/Click".
+ * Graph coloring.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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).
+ *
+ * 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.
+ *
+ * Liveness computation and interference graph built in the same one pass (one
+ * fewer passes per round of coloring).
+ *
+ * Single-register def or use live ranges deny neighbors their required
+ * register and thus do not interfere, vs interfering and denying the color
+ * and coloring time. Basically all function calls do this, but many oddball
+ * registers also, e.g. older div/mod/mul ops. (5x smaller IFG, the only
+ * O(n^2) part of this operation).
+ */
+
+public class RegAlloc {
+ // Main Coloring Algorithm:
+ // Repeat until colored:
+ // Build Live Ranges (LRGs)
+ // - Intersect allowed registers
+ // If hard conflicts (LRGs with no allowed registers)
+ // - Pre-split conflicted LRGs, and repeat
+ // Build Interference Graph (IFG)
+ // - Self conflicts split now
+ // Color (remove trivial first then any until empty; reverse assign colors
+ // - If color fails:
+ // - - Split uncolorable LRGs
+
+ // Top-level program graph structure
+ final CodeGen _code;
+
+ public int _spills, _spillScaled;
+
+ // -----------------------
+ // Live ranges with self-conflicts or no allowed registers
+ private final IdentityHashMap _failed = new IdentityHashMap<>();
+ void fail( LRG lrg ) {
+ assert lrg.leader();
+ _failed.put(lrg,"");
+ }
+ boolean success() { return _failed.isEmpty(); }
+
+
+ // -----------------------
+ // Map from Nodes to Live Ranges
+ private final IdentityHashMap _lrgs = new IdentityHashMap<>();
+ short _lrg_num;
+
+ // Define a new LRG, and assign n
+ LRG newLRG( Node n ) {
+ LRG lrg = lrg(n);
+ if( lrg!=null ) return lrg;
+ lrg = new LRG(_lrg_num++);
+ LRG old = _lrgs.put(n,lrg); assert old==null;
+ return lrg;
+ }
+
+ // LRG for n
+ LRG lrg( Node n ) {
+ LRG lrg = _lrgs.get(n);
+ if( lrg==null ) return null;
+ LRG lrg2 = lrg.find();
+ if( lrg != lrg2 )
+ _lrgs.put(n,lrg2);
+ return lrg2;
+ }
+
+ // Find LRG for n.in(idx), and also map n to it
+ LRG lrg2( Node n, int idx ) {
+ LRG lrg = lrg(n.in(idx));
+ return union(lrg,n);
+ }
+
+ // Union any lrg for n with lrg and map to the union
+ LRG union( LRG lrg, Node n ) {
+ LRG lrgn = _lrgs.get(n);
+ LRG lrg3 = lrg.union(lrgn);
+ _lrgs.put(n,lrg3);
+ return lrg3;
+ }
+
+ // Force all unified to roll up; collect live ranges
+ LRG[] _LRGS;
+ void unify() {
+ Ary lrgs = new Ary<>(LRG.class);
+ for( Node n : _lrgs.keySet() ) {
+ LRG lrg = lrg(n);
+ lrgs.setX(lrg._lrg,lrg);
+ }
+ _LRGS = lrgs.asAry();
+ // Remove unified lrgs from failed set also
+ for( LRG lrg : _failed.keySet().toArray(new LRG[0]) ) {
+ if( !lrg.leader() ) {
+ LRG lrg2 = lrg.find();
+ _failed.remove(lrg);
+ _failed.put(lrg2,"");
+ }
+ }
+ }
+
+
+ public short regnum( Node n ) {
+ LRG lrg = lrg(n);
+ return lrg==null ? -1 : lrg._reg;
+ }
+
+ // Printable register number for node n
+ String reg( Node n ) {
+ LRG lrg = lrg(n);
+ if( lrg==null ) return null;
+ if( lrg._reg == -1 ) return "V"+lrg._lrg;
+ return _code._mach.reg(lrg._reg);
+ }
+
+ // -----------------------
+ RegAlloc( CodeGen code ) { _code = code; }
+
+ public void regAlloc() {
+ // Insert callee-save registers
+ FunNode lastFun=null;
+ for( CFGNode bb : _code._cfg ) {
+ if( bb instanceof FunNode fun )
+ insertCalleeSave(lastFun=fun);
+ // Leaf routine, or not?
+ // X86 requires 16b RSP aligned if NOT leaf
+ if( bb instanceof CallNode ) lastFun._hasCalls = true;
+ }
+
+
+ // Top driver: repeated rounds of coloring and splitting.
+ byte round=0;
+ while( !graphColor(round) ) {
+ split(round);
+ if( round >= 7 ) // Really expect to be done soon
+ throw Utils.TODO("Allocator taking too long");
+ round++;
+ }
+ postColor(); // Remove no-op spills
+ }
+
+ private boolean graphColor(byte round) {
+ _failed.clear();
+ _lrgs.clear();
+ _lrg_num = 1;
+ _LRGS=null;
+
+ return
+ // Build Live Ranges
+ BuildLRG.run(round,this) && // if no hard register conflicts
+ // Build Interference Graph
+ IFG.build(round,this) && // If no self conflicts or uncolorable
+ // Conservative coalesce copies
+ Coalesce.coalesce(round,this) &&
+ // Color attempt
+ IFG.color(round,this); // If colorable
+ }
+
+ // Insert callee-save registers. Walk the callee-save RegMask ignoring any
+ // Parms, then insert a Parm and an edge from the Ret to the Parm with the
+ // callee-save register.
+ private void insertCalleeSave( FunNode fun ) {
+ RegMask saves = _code._mach.calleeSave();
+ ReturnNode ret = fun.ret();
+
+ for( short reg = saves.firstReg(); reg != -1; reg = saves.nextReg(reg) ) {
+ ret.addDef(new CalleeSaveNode(fun,reg,_code._mach.reg(reg)));
+ assert ((MachNode)ret).regmap(ret.nIns()-1).firstReg()==reg;
+ }
+ }
+
+
+ // -----------------------
+ // Split conflicted live ranges.
+ void split(byte round) {
+
+ // In C2, all splits are handling in one pass over the program. Here,
+ // in the name of clarity, we'll handle each failing live range
+ // independently... which generally requires a full pass over the
+ // program for each failing live range. i.e., might be a lot of
+ // passes.
+ for( LRG lrg : _failed.keySet() )
+ split(round,lrg);
+ }
+
+ // Split this live range
+ boolean split( byte round, LRG lrg ) {
+ assert lrg.leader(); // Already rolled up
+
+ if( lrg._selfConflicts != null )
+ return splitSelfConflict(round,lrg);
+
+ // Register mask when empty; split around defs and uses with limited
+ // register masks.
+ if( lrg._mask.isEmpty() && (!lrg._multiDef || lrg._1regUseCnt==1) ) {
+ if( lrg._1regDefCnt <= 1 &&
+ lrg._1regUseCnt <= 1 &&
+ (lrg._1regDefCnt + lrg._1regUseCnt) > 0 )
+ return splitEmptyMaskSimple(round,lrg);
+ // Default to splitByLoop
+ //return splitEmptyMask(round,lrg);
+ }
+
+ // Generic split-by-loop depth.
+ return splitByLoop(round,lrg);
+ }
+
+ // Split live range with an empty mask. Specifically forces splits at
+ // single-register defs or uses and not elsewhere.
+ boolean splitEmptyMaskSimple( byte round, LRG lrg ) {
+ // Live range has a single-def single-register, and/or a single-use
+ // single-register. Split after the def and before the use. Does not
+ // require a full pass.
+
+ // Split just after def
+ if( lrg._1regDefCnt==1 && !lrg._machDef.isClone() )
+ // Force must-split, even if a prior split same block because register
+ // conflicts. Example:
+ // alloc
+ // V1/rax - forced by alloc
+ // alloc
+ // V2/rax - kills prior RAX
+ // st4 [V1],len - No good, must split around
+ makeSplit("def/empty1",round,lrg).insertAfter((Node)lrg._machDef, false/*true*/);
+ // Split just before use
+ if( lrg._1regUseCnt==1 || (lrg._1regDefCnt==1 && ((Node)lrg._machDef).nOuts()==1) )
+ insertBefore((Node)lrg._machUse,lrg._uidx,"use/empty1",round,lrg,true);
+ return true;
+ }
+
+ // Split live range with an empty mask. Specifically forces splits at
+ // single-register defs or uses everywhere.
+ boolean splitEmptyMask( byte round, LRG lrg ) {
+ findAllLRG(lrg);
+ // If no single-use or single-def, assume this is a complete register
+ // kill and force spilling everywhere.
+ boolean all = lrg._killed || (lrg._1regDefCnt + lrg._1regUseCnt)==0;
+ for( Node n : _ns ) {
+ if( !(n instanceof MachNode mach) ) continue;
+ // Find def of spilling live range; spilling everywhere, OR
+ // single-register DEF and not cloneable (since these will clone
+ // before every use)
+ if( lrg(n)==lrg && (all || (!mach.isClone() && mach.outregmap().size1() )) )
+ makeSplit(n,"def/empty2",round,lrg).insertAfter(n,true);
+ // Find all uses
+ for( int i=1; i x._nid - y._nid );
+
+ // For all conflicts
+ for( Node def : conflicts ) {
+ assert lrg(def)==lrg; // Might be conflict use-side
+ // Split before each use that extends the live range; i.e. is a
+ // Phi or two-address
+ for( int i=0; i>32);
+
+
+ // If the minLoopDepth is less than the maxLoopDepth: for-all defs and
+ // uses, if at minLoopDepth or lower, split after def and before use.
+ for( Node n : _ns ) {
+ if( n instanceof SplitNode ) continue; // Ignoring splits; since spilling need to split in a deeper loop
+ if( n.isDead() ) continue; // Some Clonable went dead by other spill changes
+ // If this is a 2-address commutable op (e.g. AddX86, MulX86) and the rhs has only a single user,
+ // commute the inputs... which chops the LHS live ranges' upper bound to just the RHS.
+ if( n instanceof MachNode mach && lrg(n)==lrg && mach.twoAddress()==1 && mach.commutes() && n.in(2).nOuts()==1 )
+ n.swap12();
+
+ if( lrg(n)==lrg && // This is a LRG def
+ // At loop boundary, or splitting in inner loop
+ (min==max || n.cfg0().loopDepth() <= min) ) {
+ // Cloneable constants will be cloned at uses, not after def
+ if( !(n instanceof MachNode mach && mach.isClone()) &&
+ // Single user is already a split
+ !(n.nOuts()==1 && n.out(0) instanceof SplitNode) )
+ // Split after def in min loop nest
+ makeSplit("def/loop",round,lrg).insertAfter(n,false);
+ }
+
+ // PhiNodes check all CFG inputs
+ if( n instanceof PhiNode phi && !(n instanceof ParmNode)) {
+ for( int i=1; i>32);
+ int d = cfg.loopDepth();
+ min = Math.min(min,d);
+ max = Math.max(max,d);
+ return ((long)max<<32) | min;
+ }
+
+ // Find all members of a live range, both defs and uses
+ private final Ary _ns = new Ary<>(Node.class);
+ void findAllLRG( LRG lrg ) {
+ _ns.clear();
+ int wd = 0;
+ _ns.push((Node)lrg._machDef);
+ _ns.push((Node)lrg._machUse);
+ while( wd < _ns._len ) {
+ Node n = _ns.at(wd++);
+ if( lrg(n)!=lrg ) continue;
+ for( Node def : n._inputs )
+ if( lrg(def)==lrg && _ns.find(def)== -1 )
+ _ns.push(def);
+ for( Node use : n._outputs )
+ if( _ns.find(use)== -1 )
+ _ns.push(use);
+ }
+ for( Node n : _ns ) assert !n.isDead();
+ }
+
+ void insertBefore(Node n, int i, String kind, byte round, LRG lrg, boolean skip) {
+ Node def = n.in(i);
+ // Effective block for use
+ CFGNode cfg = n instanceof PhiNode phi ? phi.region().cfg(i) : n.cfg0();
+ // Def is a split ?
+ if( skip && def instanceof SplitNode ) {
+ boolean singleReg = n instanceof MachNode mach && mach.regmap(i).size1();
+ // Same block, multiple registers, split is only used by n,
+ // assume this is good enough and do not split again.
+ if( cfg==def.cfg0() && def.nOuts()==1 && !singleReg )
+ return;
+ }
+ makeSplit(def,kind,round,lrg).insertBefore(n, i);
+ // Skip split-of-split same block
+ if( 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);
+ }
+
+ private Node makeSplit( Node def, String kind, byte round, LRG lrg ) {
+ Node split = def instanceof MachNode mach && mach.isClone()
+ ? mach.copy()
+ : _code._mach.split(kind,round,lrg);
+ _lrgs.put(split,lrg);
+ return split;
+ }
+ private SplitNode makeSplit( String kind, byte round, LRG lrg ) {
+ SplitNode split = _code._mach.split(kind,round,lrg);
+ _lrgs.put(split,lrg);
+ return split;
+ }
+
+
+ // -----------------------
+ // POST PASS: Remove empty spills that biased-coloring made
+ private void postColor() {
+ int maxSlot = -1;
+ for( CFGNode bb : _code._cfg ) { // For all ops
+ if( bb instanceof FunNode )
+ maxSlot = -1;
+ if( bb instanceof ReturnNode ret )
+ ret.fun()._maxSlot = (short)maxSlot;
+ for( int j=0; j= 0;
+ }
+ }
+ }
+
+ private boolean splitBypass( CFGNode bb, int j, Node lo, int defreg ) {
+ // Attempt to bypass split
+ Node hi = lo.in(1);
+ while( true ) {
+ if( !(hi instanceof SplitNode && lo.cfg0() == hi.cfg0()) )
+ return false;
+ if( lrg(hi.in(1))._reg==defreg )
+ break;
+ hi = hi.in(1);
+ }
+ // Check no clobbers
+ for( int idx = j-1; bb.out(idx) != hi; idx++) {
+ Node n = bb.out(idx);
+ if( lrg(n)!=null && lrg(n)._reg == defreg )
+ return false; // Clobbered
+ }
+ lo.setDefOrdered(1,hi.in(1));
+ return true;
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/RegMask.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/RegMask.java
new file mode 100644
index 0000000..a566187
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/RegMask.java
@@ -0,0 +1,142 @@
+package com.compilerprogramming.ezlang.compiler.codegen;
+
+import com.compilerprogramming.ezlang.compiler.Utils;
+import com.compilerprogramming.ezlang.compiler.SB;
+
+/** RegMask
+ * A "register mask" - 1 bit set for each allowed register. In addition
+ * "stack slot" registers may be allowed, effectively making the set infinite.
+ *
+ * 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<> reg)&1)==0;
+ if( reg < 128 ) return ((_bits1 >> reg)&1)==0;
+ return true;
+ }
+
+
+ public short firstReg() {
+ return (short)(_bits0 != 0
+ ? Long.numberOfTrailingZeros(_bits0)
+ : Long.numberOfTrailingZeros(_bits1)+64);
+ }
+ public short nextReg(short reg) {
+ if( reg<64 ) {
+ long bits0 = _bits0 >> (reg+1);
+ if( bits0!=0 )
+ return (short)(reg+1+Long.numberOfTrailingZeros(bits0));
+ reg=64;
+ }
+ long bits1 = _bits1 >> (reg-64+1);
+ if( bits1!=0 )
+ return (short)(reg+1+Long.numberOfTrailingZeros(bits1));
+ return -1;
+ }
+
+ boolean isEmpty() { return _bits0==0 && _bits1==0; }
+
+ public boolean test( int reg ) {
+ return ((reg<64 ? (_bits0 >> reg) : (_bits1 >> (reg-64))) & 1) != 0;
+ }
+
+ // checks if the 2 masks have at least 1 bit in common
+ public boolean overlap( RegMask mask ) { return (_bits0 & mask._bits0)!=0 || (_bits1 & mask._bits1)!=0; }
+
+ // Defensive writable copy
+ public RegMaskRW copy() { return new RegMaskRW( _bits0, _bits1 ); }
+
+ // Has exactly 1 bit set
+ public boolean size1() {
+ return ((_bits0 & -_bits0)==_bits0 && _bits1==0) ||
+ ((_bits1 & -_bits1)==_bits1 && _bits0==0);
+ }
+
+ // Cardinality
+ public short size() { return (short)(Long.bitCount(_bits0)+Long.bitCount(_bits1)); }
+
+ @Override public String toString() { return toString(new SB()).toString(); }
+ public SB toString(SB sb) {
+ Machine mach = CodeGen.CODE._mach;
+ if( _bits0==0 && _bits1==0 ) return sb.p("[]");
+ sb.p("[");
+ for( int i=0; i<64; i++ )
+ if( ((_bits0 >> i)&1) != 0 )
+ sb.p(mach.reg(i)).p(",");
+ for( int i=0; i<64; i++ )
+ if( ((_bits1 >> i)&1) != 0 )
+ sb.p(mach.reg(i+64)).p(",");
+ return sb.unchar().p("]");
+ }
+}
+
+// Mutable regmask(writable)
+class RegMaskRW extends RegMask {
+ public RegMaskRW(long x, long y) { super(x,y); }
+ // clears bit at position r. Returns true if the mask is still not empty.
+ public boolean clr(int r) {
+ if( r < 64 ) _bits0 &= ~(1L<<(r ));
+ else _bits1 &= ~(1L<<(r-64));
+ return _bits0!=0 || _bits1!=0;
+ }
+ @Override RegMaskRW and( RegMask mask ) {
+ if( mask==null ) return this;
+ _bits0 &= mask._bits0;
+ _bits1 &= mask._bits1;
+ return this;
+ }
+ @Override RegMaskRW sub( RegMask mask ) {
+ if( mask==null ) return this;
+ _bits0 &= ~mask._bits0;
+ _bits1 &= ~mask._bits1;
+ return this;
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/AddFNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/AddFNode.java
new file mode 100644
index 0000000..d8ecc97
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/AddFNode.java
@@ -0,0 +1,44 @@
+package com.compilerprogramming.ezlang.compiler.nodes;
+
+import com.compilerprogramming.ezlang.compiler.sontypes.SONType;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFloat;
+
+import java.util.BitSet;
+
+public class AddFNode extends Node {
+ public AddFNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+
+ @Override public String label() { return "AddF"; }
+
+ @Override public String glabel() { return "+"; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("("), visited);
+ in(2)._print0(sb.append("+"), visited);
+ return sb.append(")");
+ }
+
+ @Override
+ public SONType compute() {
+ if (in(1)._type instanceof SONTypeFloat i0 &&
+ in(2)._type instanceof SONTypeFloat i1) {
+ if (i0.isConstant() && i1.isConstant())
+ return SONTypeFloat.constant(i0.value()+i1.value());
+ }
+ return in(1)._type.meet(in(2)._type);
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ SONType t2 = in(2)._type;
+
+ // Add of 0.
+ if ( t2.isConstant() && t2 instanceof SONTypeFloat i && i.value()==0 )
+ return lhs;
+
+ return null;
+ }
+ @Override Node copy(Node lhs, Node rhs) { return new AddFNode(lhs,rhs); }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/AddNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/AddNode.java
new file mode 100644
index 0000000..0098b61
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/AddNode.java
@@ -0,0 +1,200 @@
+package com.compilerprogramming.ezlang.compiler.nodes;
+
+import com.compilerprogramming.ezlang.compiler.Compiler;
+import com.compilerprogramming.ezlang.compiler.sontypes.*;
+
+import java.util.BitSet;
+
+import static com.compilerprogramming.ezlang.compiler.Compiler.con;
+
+public class AddNode extends Node {
+ public AddNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+
+ @Override public String label() { return "Add"; }
+
+ @Override public String glabel() { return "+"; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("("), visited);
+ in(2)._print0(sb.append("+"), visited);
+ return sb.append(")");
+ }
+
+
+ @Override
+ public SONType compute() {
+ SONType t1 = in(1)._type, t2 = in(2)._type;
+ if( t1.isHigh() || t2.isHigh() )
+ return SONTypeInteger.TOP;
+ if( t1 instanceof SONTypeInteger i1 &&
+ t2 instanceof SONTypeInteger i2 ) {
+ if (i1.isConstant() && i2.isConstant())
+ return SONTypeInteger.constant(i1.value()+i2.value());
+ // Fold ranges like {0-1} + {2-3} into {2-4}.
+ if( !overflow(i1._min,i2._min) &&
+ !overflow(i1._max,i2._max) )
+ return SONTypeInteger.make(i1._min+i2._min,i1._max+i2._max);
+ }
+ return SONTypeInteger.BOT;
+ }
+
+ 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 Node idealize () {
+ Node lhs = in(1);
+ Node rhs = in(2);
+ if( rhs instanceof AddNode add && add.err()!=null )
+ return null;
+ SONType t2 = rhs._type;
+
+ // Add of 0. We do not check for (0+x) because this will already
+ // canonicalize to (x+0)
+ if( t2 == SONTypeInteger.ZERO )
+ return lhs;
+
+ // Add of same to a multiply by 2
+ if( lhs==rhs )
+ return new ShlNode(lhs,con(1));
+
+ // Goal: a left-spine set of adds, with constants on the rhs (which then fold).
+
+ // Move non-adds to RHS
+ if( !(lhs instanceof AddNode) && rhs instanceof AddNode )
+ return swap12();
+
+ // x+(-y) becomes x-y
+ if( rhs instanceof MinusNode minus )
+ return new SubNode(lhs,minus.in(1));
+
+ // Now we might see (add add non) or (add non non) or (add add add) but never (add non add)
+
+ // Do we have x + (y + z) ?
+ // Swap to (x + y) + z
+ // Rotate (add add add) to remove the add on RHS
+ if( rhs instanceof AddNode add )
+ return new AddNode(new AddNode(lhs,add.in(1)).peephole(), add.in(2));
+
+ // Now we might see (add add non) or (add non non) but never (add non add) nor (add add add)
+ if( !(lhs instanceof AddNode) )
+ // Rotate; look for (add (phi cons) con/(phi cons))
+ return spine_cmp(lhs,rhs,this) ? swap12() : phiCon(this,true);
+
+ // Now we only see (add add non)
+
+ // Dead data cycle; comes about from dead infinite loops. Do nothing,
+ // the loop will peep as dead after a bit.
+ if( lhs.in(1) == lhs )
+ return null;
+
+ // Do we have (x + con1) + con2?
+ // Replace with (x + (con1+con2) which then fold the constants
+ // lhs.in(2) is con1 here
+ // If lhs.in(2) is not a constant, we add ourselves as a dependency
+ // because if it later became a constant then we could make this
+ // transformation.
+ if( lhs.in(2).addDep(this)._type.isConstant() && rhs._type.isConstant() )
+ return new AddNode(lhs.in(1),new AddNode(lhs.in(2),rhs).peephole());
+
+
+ // Do we have ((x + (phi cons)) + con) ?
+ // Do we have ((x + (phi cons)) + (phi cons)) ?
+ // Push constant up through the phi: x + (phi con0+con0 con1+con1...)
+ Node phicon = phiCon(this,true);
+ if( phicon!=null ) return phicon;
+
+ // Now we sort along the spine via rotates, to gather similar things together.
+
+ // Do we rotate (x + y) + z
+ // into (x + z) + y ?
+ if( spine_cmp(lhs.in(2),rhs,this) )
+ return new AddNode(new AddNode(lhs.in(1),rhs).peephole(),lhs.in(2));
+
+ return null;
+ }
+
+ // Rotation is only valid for associative ops, e.g. Add, Mul, And, Or, Xor.
+ // Do we have ((phi cons)|(x + (phi cons)) + con|(phi cons)) ?
+ // Push constant up through the phi: x + (phi con0+con0 con1+con1...)
+ static Node phiCon(Node op, boolean rotate) {
+ Node lhs = op.in(1);
+ Node rhs = op.in(2);
+ if( rhs._type== SONTypeInteger.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 hi._nid;
+ }
+
+ @Override Node copy(Node lhs, Node rhs) { return new AddNode(lhs,rhs); }
+ @Override Node copyF() { return new AddFNode(null,null); }
+// FIXME Dibyendu
+// @Override public Parser.ParseException err() {
+// if( in(1)._type.isHigh() || in(2)._type.isHigh() ) return null;
+// if( !(in(1)._type instanceof SONTypeInteger) ) return Parser.error("Cannot '"+label()+"' " + in(1)._type,null);
+// if( !(in(2)._type instanceof SONTypeInteger) ) return Parser.error("Cannot '"+label()+"' " + in(2)._type,null);
+// return null;
+// }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/AndNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/AndNode.java
new file mode 100644
index 0000000..f36997f
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/AndNode.java
@@ -0,0 +1,58 @@
+package com.compilerprogramming.ezlang.compiler.nodes;
+
+import com.compilerprogramming.ezlang.compiler.sontypes.SONType;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeInteger;
+
+public class AndNode extends LogicalNode {
+ public AndNode(Node lhs, Node rhs) { super(lhs, rhs); }
+
+ @Override public String label() { return "And"; }
+ @Override public String op() { return "&"; }
+
+ @Override public String glabel() { return "&"; }
+
+ @Override
+ public SONType compute() {
+ SONType t1 = in(1)._type, t2 = in(2)._type;
+ if( t1.isHigh() || t2.isHigh() )
+ return SONTypeInteger.TOP;
+ if( t1 instanceof SONTypeInteger i0 &&
+ t2 instanceof SONTypeInteger i1 ) {
+ if( i0.isConstant() && i1.isConstant() )
+ return SONTypeInteger.constant(i0.value()&i1.value());
+ // Sharpen allowed bits if either value is narrowed
+ long mask = i0.mask() & i1.mask();
+ return mask < 0 ? SONTypeInteger.BOT : SONTypeInteger.make(0,mask);
+ }
+ return SONTypeInteger.BOT;
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Node rhs = in(2);
+ SONType t1 = lhs._type;
+ SONType t2 = rhs._type;
+ if( !(t1 instanceof SONTypeInteger && t2 instanceof SONTypeInteger t2i) )
+ return null; // Malformed, e.g. (17 & 3.14)
+
+ // And of -1. We do not check for (-1&x) because this will already
+ // canonicalize to (x&-1). We do not check for zero, because
+ // the compute() call will return a zero already.
+ if( t2i.isConstant() && t2i.value()==-1 )
+ return lhs;
+
+ // Move constants to RHS: con*arg becomes arg*con
+ if( t1.isConstant() && !t2.isConstant() )
+ return swap12();
+
+ // Do we have ((x & (phi cons)) & con) ?
+ // Do we have ((x & (phi cons)) & (phi cons)) ?
+ // Push constant up through the phi: x & (phi con0&con0 con1&con1...)
+ Node phicon = AddNode.phiCon(this,true);
+ if( phicon!=null ) return phicon;
+
+ return null;
+ }
+ @Override Node copy(Node lhs, Node rhs) { return new AndNode(lhs,rhs); }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/BoolNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/BoolNode.java
new file mode 100644
index 0000000..9c9b744
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/BoolNode.java
@@ -0,0 +1,135 @@
+package com.compilerprogramming.ezlang.compiler.nodes;
+
+import com.compilerprogramming.ezlang.compiler.Compiler;
+import com.compilerprogramming.ezlang.compiler.Utils;
+import com.compilerprogramming.ezlang.compiler.sontypes.*;
+import java.util.BitSet;
+import static com.compilerprogramming.ezlang.compiler.sontypes.SONTypeInteger.*;
+
+abstract public class BoolNode extends Node {
+
+ public BoolNode(Node lhs, Node rhs) {
+ super(null, lhs, rhs);
+ }
+
+ abstract public String op(); // String opcode name
+
+ @Override
+ public String label() { return getClass().getSimpleName(); }
+
+ @Override
+ public String glabel() { return op(); }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("("), visited);
+ in(2)._print0(sb.append(op()), visited);
+ return sb.append(")");
+ }
+
+ @Override
+ public SONTypeInteger compute() {
+ SONType t1 = in(1)._type;
+ SONType t2 = in(2)._type;
+ // Exactly equals?
+ if( t1.isHigh() || t2.isHigh() )
+ return BOOL.dual();
+ if( in(1)==in(2) )
+ // LT fails, both EQ and LE succeed
+ return this instanceof LT ? FALSE : TRUE;
+ if( t1 instanceof SONTypeInteger i1 &&
+ t2 instanceof SONTypeInteger i2 )
+ return doOp(i1,i2);
+ if( t1 instanceof SONTypeFloat f1 &&
+ t2 instanceof SONTypeFloat f2 &&
+ f1.isConstant() && f2.isConstant() )
+ return doOp(f1.value(), f2.value()) ? TRUE : FALSE;
+ return BOOL;
+ }
+
+ SONTypeInteger doOp(SONTypeInteger t1, SONTypeInteger t2) { throw Utils.TODO(); }
+ boolean doOp(double lhs, double rhs) { throw Utils.TODO(); }
+ Node copyF(Node lhs, Node rhs) { return null; }
+ public boolean isFloat() { return false; }
+
+ @Override
+ public Node idealize() {
+ // Compare of same
+ if( in(1)==in(2) )
+ return this instanceof LT ? Compiler.ZERO : new ConstantNode(TRUE);
+
+ // Equals pushes constant to the right; 5==X becomes X==5.
+ if( this instanceof EQ ) {
+ if( !(in(2) instanceof ConstantNode) ) {
+ // con==noncon becomes noncon==con
+ if( in(1) instanceof ConstantNode || in(1)._nid > in(2)._nid )
+ // Equals sorts by NID otherwise: non.high == non.low becomes non.low == non.high
+ return in(1)._type instanceof SONTypeFloat ? new EQF(in(2),in(1)) : new EQ(in(2),in(1));
+ }
+ // Equals X==0 becomes a !X
+ if( (in(2)._type == ZERO || in(2)._type == SONType.NIL) )
+ return new NotNode(in(1));
+ }
+
+ // Do we have ((x * (phi cons)) * con) ?
+ // Do we have ((x * (phi cons)) * (phi cons)) ?
+ // Push constant up through the phi: x * (phi con0*con0 con1*con1...)
+ Node phicon = AddNode.phiCon(this,this instanceof EQ);
+ if( phicon!=null ) return phicon;
+
+ return null;
+ }
+
+ public static class EQ extends BoolNode {
+ public EQ(Node lhs, Node rhs) { super(lhs,rhs); }
+ public String op() { return "=="; }
+ SONTypeInteger doOp(SONTypeInteger i1, SONTypeInteger i2) {
+ if( i1==i2 && i1.isConstant() ) return TRUE;
+ if( i1._max < i2._min || i1._min > i2._max ) return FALSE;
+ return BOOL;
+ }
+ Node copy(Node lhs, Node rhs) { return new EQ(lhs,rhs); }
+ Node copyF() { return new EQF(null,null); }
+ }
+ public static class LT extends BoolNode {
+ public LT(Node lhs, Node rhs) { super(lhs,rhs); }
+ public String op() { return "<" ; }
+ public String glabel() { return "<"; }
+ SONTypeInteger doOp(SONTypeInteger i1, SONTypeInteger i2) {
+ if( i1._max < i2._min ) return TRUE;
+ if( i1._min >= i2._max ) return FALSE;
+ return BOOL;
+ }
+ Node copy(Node lhs, Node rhs) { return new LT(lhs,rhs); }
+ Node copyF() { return new LTF(null,null); }
+ }
+ public static class LE extends BoolNode {
+ public LE(Node lhs, Node rhs) { super(lhs,rhs); }
+ public String op() { return "<="; }
+ public String glabel() { return "<="; }
+ SONTypeInteger doOp(SONTypeInteger i1, SONTypeInteger i2) {
+ if( i1._max <= i2._min ) return TRUE;
+ if( i1._min > i2._max ) return FALSE;
+ return BOOL;
+ }
+ Node copy(Node lhs, Node rhs) { return new LE(lhs,rhs); }
+ Node copyF() { return new LEF(null,null); }
+ }
+
+ public static class EQF extends EQ { public EQF(Node lhs, Node rhs) { super(lhs,rhs); } boolean doOp(double lhs, double rhs) { return lhs == rhs; } public boolean isFloat() { return true; } }
+ public static class LTF extends LT { public LTF(Node lhs, Node rhs) { super(lhs,rhs); } boolean doOp(double lhs, double rhs) { return lhs < rhs; } public boolean isFloat() { return true; } }
+ public static class LEF extends LE { public LEF(Node lhs, Node rhs) { super(lhs,rhs); } boolean doOp(double lhs, double rhs) { return lhs <= rhs; } public boolean isFloat() { return true; } }
+
+ // Unsigned less that, for range checks. Not directly user writable.
+ public static class ULT extends BoolNode {
+ public ULT(Node lhs, Node rhs) { super(lhs,rhs); }
+ public String op() { return "u<" ; }
+ public String glabel() { return "u<"; }
+ SONTypeInteger doOp(SONTypeInteger i1, SONTypeInteger 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CFGNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CFGNode.java
new file mode 100644
index 0000000..3876fd2
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CFGNode.java
@@ -0,0 +1,181 @@
+package com.compilerprogramming.ezlang.compiler.nodes;
+
+import com.compilerprogramming.ezlang.compiler.Compiler;
+import com.compilerprogramming.ezlang.compiler.sontypes.*;
+import java.util.BitSet;
+
+/** Control Flow Graph Nodes
+ *
+ * CFG nodes have a immediate dominator depth (idepth) and a loop nesting
+ * depth(loop_depth).
+ *
+ * idepth is computed lazily upon first request, and is valid even in the
+ * Parser, and is used by peepholes during parsing and afterward.
+ *
+ * loop_depth is computed after optimization as part of scheduling.
+ *
+ * Start - Block head; has constants and trailing Funs
+ * Region - Block head; has Phis. Includes Fun and Parms.
+ * CallEnd - Block head; followed by CProj and Proj; also linked RPC Parms
+ * CProj - Block head
+ *
+ * If - Block tail; followed by CProj same block
+ * Call - Block tail; followed by CallEnd and linked Funs
+ * New/intrinsic - If followed by CProj: block tail else mid-block; followed by Proj
+ *
+ */
+public abstract class CFGNode extends Node {
+
+ public CFGNode(Node... nodes) { super(nodes); }
+ public CFGNode(CFGNode cfg) {
+ super(cfg);
+ if( cfg != null ) {
+ _idepth = cfg._idepth;
+ _ltree = cfg._ltree;
+ _pre = cfg._pre;
+ }
+ }
+
+ public CFGNode cfg(int idx) { return (CFGNode)in(idx); }
+
+ // Block head is Start, Region, CProj, but not e.g. If, Return, Stop
+ public boolean blockHead() { return false; }
+
+ // Get the one control following; error to call with more than one e.g. an
+ // IfNode or other multi-way branch.
+ public CFGNode uctrl() {
+ CFGNode c = null;
+ for( Node n : _outputs )
+ if( n instanceof CFGNode cfg )
+ { assert c==null; c = cfg; }
+ return c;
+ }
+
+
+ // ------------------------------------------------------------------------
+ /**
+ * 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.
+ *
+ * See {@link ...}
+ */
+ public int _idepth;
+ public int idepth() { return _idepth==0 ? (_idepth=idom().idepth()+1) : _idepth; }
+
+ // Return the immediate dominator of this Node and compute dom tree depth.
+ public CFGNode idom(Node dep) { return cfg(0); }
+ public final CFGNode idom() { return idom(null); }
+
+ // Return the LCA of two idoms
+ public CFGNode _idom(CFGNode rhs, Node dep) {
+ if( rhs==null ) return this;
+ CFGNode lhs = this;
+ while( lhs != rhs ) {
+ var comp = lhs.idepth() - rhs.idepth();
+ if( comp >= 0 ) lhs = ((CFGNode)lhs.addDep(dep)).idom();
+ if( comp <= 0 ) rhs = ((CFGNode)rhs.addDep(dep)).idom();
+ }
+ return lhs;
+ }
+
+ // Anti-dependence field support
+ public int _anti; // Per-CFG field to help find anti-deps
+
+ // Find nearest enclosing FunNode
+ public FunNode fun() {
+ CFGNode cfg = this;
+ while( !(cfg instanceof FunNode fun) )
+ cfg = cfg.idom();
+ return fun;
+ }
+
+ // ------------------------------------------------------------------------
+ // Loop nesting
+ public LoopNode loop() { return _ltree._head; }
+ public int loopDepth() { return _ltree==null ? 0 : _ltree.depth(); }
+
+ LoopTree _ltree;
+ public int _pre; // Pre-order numbers for loop tree finding
+ private static class LoopTree {
+ LoopTree _par;
+ final LoopNode _head;
+ int _depth;
+ LoopTree(LoopNode head) { _head = head; }
+ @Override public String toString() { return "LOOP"+_head._nid; }
+ int depth() {
+ return _depth==0 ? (_par==null ? 0 : (_depth = _par.depth()+1)) : _depth;
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Tag all CFG Nodes with their containing LoopNode; LoopNodes themselves
+ // also refer to *their* containing LoopNode, as well as have their depth.
+ // Start is a LoopNode which contains all at depth 1.
+ public void buildLoopTree(StopNode stop) {
+ _ltree = stop._ltree = Compiler.XCTRL._ltree = new LoopTree((StartNode)this);
+ _bltWalk(2,null,stop, new BitSet());
+ }
+ int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post ) {
+ // Pre-walked?
+ if( _pre!=0 ) return pre;
+ _pre = pre++;
+ // Pre-walk
+ for( Node use : _outputs )
+ if( use instanceof CFGNode usecfg && !skip( usecfg ) )
+ pre = usecfg._bltWalk( pre, use instanceof FunNode fuse ? fuse : fun, stop, post );
+
+ // Post-order work: find innermost loop
+ LoopTree inner = null, ltree;
+ for( Node use : _outputs ) {
+ if( !(use instanceof CFGNode usecfg) ) continue;
+ if( skip(usecfg) ) continue;
+ if( usecfg._type == SONType.XCONTROL || // Do not walk dead control
+ usecfg._type == SONTypeTuple.IF_NEITHER ) // Nor dead IFs
+ continue;
+ // Child visited but not post-visited?
+ if( !post.get(usecfg._nid) ) {
+ // Must be a backedge to a LoopNode then
+ ltree = usecfg._ltree = new LoopTree((LoopNode)usecfg);
+ } else {
+ // Take child's loop choice, which must exist
+ ltree = usecfg._ltree;
+ // If falling into a loop, use the target loop's parent instead
+ if( ltree._head == usecfg ) {
+ if( ltree._par == null )
+ // This loop never had an If test choose to take its
+ // exit, i.e. it is a no-exit infinite loop.
+ ltree._par = ltree._head.forceExit(fun,stop)._ltree;
+ ltree = ltree._par;
+ }
+ }
+ // Sort inner loops. The decision point is some branch far removed
+ // from either loop head OR either backedge so requires pre-order
+ // numbers to figure out innermost.
+ if( inner == null ) { inner = ltree; continue; }
+ if( inner == ltree ) continue; // No change
+ LoopTree outer = ltree._head._pre > inner._head._pre ? inner : ltree;
+ inner = ltree._head._pre > inner._head._pre ? ltree : inner;
+ inner._par = outer;
+ }
+ // Set selected loop
+ if( inner!=null )
+ _ltree = inner;
+ // Tag as post-walked
+ post.set(_nid);
+ return pre;
+ }
+
+ private boolean skip(CFGNode usecfg) {
+ // Only walk control users that are alive.
+ // Do not walk from a Call to linked Fun's.
+ return usecfg instanceof XCtrlNode ||
+ (this instanceof CallNode && usecfg instanceof FunNode) ||
+ (this instanceof ReturnNode && usecfg instanceof CallEndNode);
+ }
+
+
+ public String label( CFGNode target ) {
+ return (target instanceof LoopNode ? "LOOP" : "L")+target._nid;
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CProjNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CProjNode.java
new file mode 100644
index 0000000..9e2f384
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CProjNode.java
@@ -0,0 +1,66 @@
+package com.compilerprogramming.ezlang.compiler.nodes;
+
+import com.compilerprogramming.ezlang.compiler.Compiler;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONType;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeTuple;
+import java.util.BitSet;
+
+public class CProjNode extends CFGNode {
+
+ // Which slice of the incoming multipart value
+ public int _idx;
+
+ // Debugging label
+ public String _label;
+
+ public CProjNode(Node ctrl, int idx, String label) {
+ super(ctrl);
+ _idx = idx;
+ _label = label;
+ }
+ public CProjNode(CProjNode c) { super(c); _idx = c._idx; _label = c._label; }
+
+ @Override public String label() { return _label; }
+
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(_label); }
+
+ @Override public boolean blockHead() { return true; }
+
+ public CFGNode ctrl() { return cfg(0); }
+
+ @Override
+ public SONType compute() {
+ SONType t = ctrl()._type;
+ return t instanceof SONTypeTuple tt ? tt._types[_idx] : SONType.BOTTOM;
+ }
+
+ @Override
+ public Node idealize() {
+ if( ctrl()._type instanceof SONTypeTuple tt ) {
+ if( tt._types[_idx]== SONType.XCONTROL )
+ return Compiler.XCTRL; // We are dead
+ if( ctrl() instanceof IfNode && tt._types[1-_idx]== SONType.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().addDep(this) instanceof NotNode not )
+ return new CProjNode(new IfNode(iff.ctrl(),not.in(1)).peephole(),1-_idx,_idx==0 ? "False" : "True");
+
+ // Copy of some other input
+ return ((MultiNode)ctrl()).pcopy(_idx);
+ }
+
+ // Only called during basic-block layout, inverts a T/F CProj
+ public void invert() {
+ _label = _idx == 0 ? "False" : "True";
+ _idx = 1-_idx;
+ }
+
+ @Override
+ boolean eq( Node n ) { return _idx == ((CProjNode)n)._idx; }
+
+ @Override
+ int hash() { return _idx; }
+
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CallEndNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CallEndNode.java
new file mode 100644
index 0000000..c705f53
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CallEndNode.java
@@ -0,0 +1,100 @@
+package com.compilerprogramming.ezlang.compiler.nodes;
+
+import com.compilerprogramming.ezlang.compiler.Compiler;
+import com.compilerprogramming.ezlang.compiler.codegen.CodeGen;
+import com.compilerprogramming.ezlang.compiler.sontypes.*;
+import java.util.BitSet;
+
+/**
+ * CallEnd
+ */
+public class CallEndNode extends CFGNode implements MultiNode {
+
+ // When set true, this Call/CallEnd/Fun/Return is being trivially inlined
+ private boolean _folding;
+ public final SONTypeRPC _rpc;
+
+ public CallEndNode(CallNode call) { super(new Node[]{call}); _rpc = SONTypeRPC.constant(_nid); }
+ public CallEndNode(CallEndNode cend) { super(cend); _rpc = cend._rpc; }
+
+ @Override public String label() { return "CallEnd"; }
+ @Override public boolean blockHead() { return true; }
+
+ public CallNode call() { return (CallNode)in(0); }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("cend( ");
+ sb.append( in(0) instanceof CallNode ? "Call, " : "----, ");
+ for( int i=1; i1 ) {
+ assert nIns()==2; // Linked exactly once for a constant
+ ret = ((SONTypeTuple)in(1)._type).ret(); // Return type
+ }
+ }
+ return SONTypeTuple.make(call._type, SONTypeMem.BOT,ret);
+ }
+
+ @Override
+ public Node idealize() {
+
+ // Trivial inlining: call site calls a single function; single function
+ // is only called by this call site.
+ if( !_folding && nIns()==2 && in(0) instanceof CallNode call ) {
+ Node fptr = call.fptr();
+ if( fptr.nOuts() == 1 && // Only user is this call
+ fptr instanceof ConstantNode && // We have an immediate call
+ // Function is being called, and its not-null
+ fptr._type instanceof SONTypeFunPtr tfp && tfp.notNull() &&
+ // Arguments are correct
+ call.err()==null ) {
+ ReturnNode ret = (ReturnNode)in(1);
+ FunNode fun = ret.fun();
+ // Expecting Start, and the Call
+ if( fun.nIns()==3 ) {
+ assert fun.in(1) instanceof StartNode && fun.in(2)==call;
+ // Disallow self-recursive inlining (loop unrolling by another name)
+ CFGNode idom = call;
+ while( !(idom instanceof FunNode fun2) )
+ idom = idom.idom();
+ if( idom != fun ) {
+ // Trivial inline: rewrite
+ _folding = true;
+ // Rewrite Fun so the normal RegionNode ideal collapses
+ fun._folding = true;
+ fun.setDef(1, Compiler.XCTRL); // No default/unknown StartNode caller
+ fun.setDef(2,call.ctrl()); // Bypass the Call;
+ fun.ret().setDef(3,null); // Return is folding also
+ CodeGen.CODE.addAll(fun._outputs);
+ return this;
+ }
+ } else {
+ fun.addDep(this);
+ }
+ } else { // Function ptr has multiple users (so maybe multiple call sites)
+ fptr.addDep(this);
+ }
+ }
+
+ return null;
+ }
+
+ @Override public Node pcopy(int idx) {
+ return _folding ? in(1).in(idx) : null;
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CallNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CallNode.java
new file mode 100644
index 0000000..52b3bf1
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CallNode.java
@@ -0,0 +1,165 @@
+package com.compilerprogramming.ezlang.compiler.nodes;
+
+import com.compilerprogramming.ezlang.compiler.codegen.CodeGen;
+import com.compilerprogramming.ezlang.compiler.IterPeeps;
+import com.compilerprogramming.ezlang.compiler.Compiler;
+import com.compilerprogramming.ezlang.compiler.Utils;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONType;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFunPtr;
+import com.compilerprogramming.ezlang.exceptions.CompilerException;
+
+import java.util.BitSet;
+
+/**
+ * Call
+ */
+public class CallNode extends CFGNode {
+
+ public CallNode(Node... nodes) { super(nodes); }
+ public CallNode(CallNode call) { super(call); }
+
+ @Override public String label() { return "Call"; }
+
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ String fname = name();
+ if( fname == null ) fptr()._print0(sb,visited);
+ else sb.append(fname);
+ sb.append("( ");
+ for( int i=2; i0 && 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 SONType compute() {
+ return ctrl()._type;
+ }
+
+ @Override
+ public Node idealize() {
+ CallEndNode cend = cend();
+ if( cend==null ) return null; // Still building
+
+ // Link: call calls target function. Linking makes the target FunNode
+ // point to this Call, and all his Parms point to the call arguments;
+ // also the CallEnd points to the Return.
+ Node progress = null;
+ if( fptr()._type instanceof SONTypeFunPtr tfp && tfp.nargs() == nargs() ) {
+ // If fidxs is negative, then infinite unknown functions
+ long fidxs = tfp.fidxs();
+ if( fidxs > 0 ) {
+ // Wipe out the return which matching in the linker table
+ // Walk the (63 max) bits and link
+ for( ; fidxs!=0; fidxs = SONTypeFunPtr.nextFIDX(fidxs) ) {
+ int fidx = Long.numberOfTrailingZeros(fidxs);
+ SONTypeFunPtr tfp0 = tfp.makeFrom(fidx);
+ FunNode fun = CodeGen.CODE.link(tfp0);
+ if( fun!=null && !fun._folding && !linked(fun) )
+ progress = link(fun);
+ }
+ }
+ }
+
+ return progress;
+ }
+
+ // True if Fun is linked to this Call
+ boolean linked( FunNode fun ) {
+ for( Node n : fun._inputs )
+ if( n == this )
+ return true;
+ return false;
+ }
+
+
+ // Link so this calls fun
+ private Node link( FunNode fun ) {
+ assert !linked(fun);
+ fun.addDef(this);
+ for( Node use : fun._outputs )
+ if( use instanceof ParmNode parm )
+ parm.addDef(parm._idx==0 ? new ConstantNode(cend()._rpc).peephole() : arg(parm._idx));
+ // Call end points to function return
+ CodeGen.CODE.add(cend()).addDef(fun.ret());
+ return this;
+ }
+
+ // Unlink all linked functions
+ public void unlink_all() {
+ for( int i=0; i<_outputs._len; i++ )
+ if( out(i) instanceof FunNode fun ) {
+ assert linked(fun);
+ int idx = fun._inputs.find(this);
+ for( Node use : fun._outputs )
+ if( use instanceof ParmNode parm )
+ use.delDef(idx);
+ fun.delDef(idx);
+ cend().delDef(cend()._inputs.find(fun.ret()));
+ assert !linked(fun);
+ i--;
+ }
+ }
+
+ @Override
+ public CompilerException err() {
+ if( !(fptr()._type instanceof SONTypeFunPtr tfp) )
+ throw Utils.TODO();
+ if( !tfp.notNull() )
+ return Compiler.error( "Might be null calling "+tfp);
+ if( nargs() != tfp.nargs() )
+ return Compiler.error( "Expecting "+tfp.nargs()+" arguments, but found "+nargs());
+
+ // Check for args
+ for( int i=0; i
+ * 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 solely to allow visitation.
+ *
+ * The Constant's value is the value stored in it.
+ */
+
+public class ConstantNode extends Node {
+ public final SONType _con;
+ public ConstantNode( SONType type ) {
+ super(new Node[]{CodeGen.CODE._start});
+ _con = _type = type;
+ }
+ public ConstantNode( Node con, SONType t ) { super(con); _con = t; }
+ public ConstantNode( ConstantNode con ) { super(con); _con = con._type; }
+
+ public static Node make( SONType type ) {
+ if( type== SONType. CONTROL ) return new CtrlNode();
+ if( type== SONType.XCONTROL ) return new XCtrlNode();
+ return new ConstantNode(type);
+ }
+
+ @Override public String label() { return "#"+_con; }
+ @Override public String glabel() { return _con.gprint(new SB().p("#")).toString(); }
+
+ @Override
+ public String uniqueName() { return "Con_" + _nid; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ if( _con instanceof SONTypeFunPtr tfp && tfp.isConstant() ) {
+ FunNode fun = CodeGen.CODE.link(tfp);
+ if( fun._name != null )
+ return sb.append("{ ").append(fun._name).append("}");
+ }
+ return sb.append(_con.print(new SB()));
+ }
+
+ @Override public boolean isConst() { return true; }
+
+ @Override
+ public SONType compute() { return _con; }
+
+ @Override
+ public Node idealize() { return null; }
+
+ @Override
+ boolean eq(Node n) {
+ ConstantNode con = (ConstantNode)n; // Contract
+ return _con==con._con;
+ }
+
+ @Override
+ int hash() { return _con.hashCode(); }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CtrlNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CtrlNode.java
new file mode 100644
index 0000000..6450cdd
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CtrlNode.java
@@ -0,0 +1,14 @@
+package com.compilerprogramming.ezlang.compiler.nodes;
+
+import com.compilerprogramming.ezlang.compiler.codegen.CodeGen;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONType;
+import java.util.BitSet;
+
+public class CtrlNode extends CFGNode {
+ public CtrlNode() { super(CodeGen.CODE._start); }
+ @Override public String label() { return "Ctrl"; }
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append("Cctrl"); }
+ @Override public boolean isConst() { return true; }
+ @Override public SONType compute() { return SONType.CONTROL; }
+ @Override public Node idealize() { return null; }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/DivFNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/DivFNode.java
new file mode 100644
index 0000000..0813fa8
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/DivFNode.java
@@ -0,0 +1,41 @@
+package com.compilerprogramming.ezlang.compiler.nodes;
+
+import com.compilerprogramming.ezlang.compiler.sontypes.SONType;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFloat;
+
+import java.util.BitSet;
+
+public class DivFNode extends Node {
+ public DivFNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+
+ @Override public String label() { return "DivF"; }
+
+ @Override public String glabel() { return "/"; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("("), visited);
+ in(2)._print0(sb.append("/"), visited);
+ return sb.append(")");
+ }
+
+ @Override
+ public SONType compute() {
+ if (in(1)._type instanceof SONTypeFloat i0 &&
+ in(2)._type instanceof SONTypeFloat i1) {
+ if (i0.isConstant() && i1.isConstant())
+ return SONTypeFloat.constant(i0.value()/i1.value());
+ }
+ return in(1)._type.meet(in(2)._type);
+ }
+
+ @Override
+ public Node idealize() {
+ // Div of constant
+ if( in(2)._type instanceof SONTypeFloat f && f.isConstant() )
+ return new MulFNode(in(1),new ConstantNode(SONTypeFloat.constant(1.0/f.value())).peephole());
+
+ return null;
+ }
+ @Override Node copy(Node lhs, Node rhs) { return new DivFNode(lhs,rhs); }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/DivNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/DivNode.java
new file mode 100644
index 0000000..9c51b6e
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/DivNode.java
@@ -0,0 +1,46 @@
+package com.compilerprogramming.ezlang.compiler.nodes;
+
+import com.compilerprogramming.ezlang.compiler.sontypes.SONType;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeInteger;
+import java.util.BitSet;
+
+public class DivNode extends Node {
+ public DivNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+
+ @Override public String label() { return "Div"; }
+
+ @Override public String glabel() { return "//"; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("("), visited);
+ in(2)._print0(sb.append("/"), visited);
+ return sb.append(")");
+ }
+
+ @Override
+ public SONType compute() {
+ SONType t1 = in(1)._type, t2 = in(2)._type;
+ if( t1.isHigh() || t2.isHigh() )
+ return SONTypeInteger.TOP;
+ if( t1 instanceof SONTypeInteger i1 &&
+ t2 instanceof SONTypeInteger i2 ) {
+ if (i1.isConstant() && i2.isConstant())
+ return i2.value() == 0
+ ? SONTypeInteger.ZERO
+ : SONTypeInteger.constant(i1.value()/i2.value());
+ }
+ return SONTypeInteger.BOT;
+ }
+
+ @Override
+ public Node idealize() {
+ // Div of 1.
+ if( in(2)._type == SONTypeInteger.TRUE )
+ return in(1);
+ return null;
+ }
+
+ @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs); }
+ @Override Node copyF() { return new DivFNode(null,null); }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/FRefNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/FRefNode.java
new file mode 100644
index 0000000..a48814f
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/FRefNode.java
@@ -0,0 +1,37 @@
+package com.compilerprogramming.ezlang.compiler.nodes;
+
+import com.compilerprogramming.ezlang.compiler.Compiler;
+import com.compilerprogramming.ezlang.compiler.Var;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONType;
+import com.compilerprogramming.ezlang.exceptions.CompilerException;
+
+import java.util.BitSet;
+
+/**
+ * A Forward Reference. Its any final constant, including functions. When
+ * the Def finally appears its plugged into the forward reference, which then
+ * peepholes to the Def.
+ */
+public class FRefNode extends ConstantNode {
+ public static final SONType FREF_TYPE = SONType.BOTTOM;
+ public final Var _n;
+ public FRefNode( Var n ) { super(FREF_TYPE); _n = n; }
+
+ @Override public String label() { return "FRef"+_n; }
+
+ @Override public String uniqueName() { return "FRef_" + _nid; }
+
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return sb.append("FRef_").append(_n);
+ }
+
+ @Override public Node idealize() {
+ // When FRef finds its definition, idealize to it
+ return nIns()==1 ? null : in(1);
+ }
+
+ public CompilerException err() { return Compiler.error("Undefined name '"+_n._name+"'"); }
+
+ @Override boolean eq(Node n) { return this==n; }
+ @Override int hash() { return _n._idx; }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/FunNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/FunNode.java
new file mode 100644
index 0000000..a0eb0be
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/FunNode.java
@@ -0,0 +1,199 @@
+package com.compilerprogramming.ezlang.compiler.nodes;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONType;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFunPtr;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeTuple;
+
+import java.util.BitSet;
+import static com.compilerprogramming.ezlang.compiler.codegen.CodeGen.CODE;
+
+public class FunNode extends RegionNode {
+
+ // When set true, this Call/CallEnd/Fun/Return is being trivially inlined
+ boolean _folding;
+
+ private SONTypeFunPtr _sig; // Initial signature
+ private ReturnNode _ret; // Return pointer
+
+ public String _name; // Debug name
+
+ public FunNode(SONTypeFunPtr sig, Node... nodes ) { super(nodes); _sig = sig; }
+ public FunNode( FunNode fun ) {
+ super( fun );
+ if( fun!=null ) {
+ _sig = fun.sig();
+ _name = fun._name;
+ } else {
+ _sig = SONTypeFunPtr.BOT;
+ _name = "";
+ }
+ }
+
+ @Override
+ public String label() { return _name == null ? "$fun"+_sig.fidx() : _name; }
+
+ // Find the one CFG user from Fun. It's not always the Return, but always
+ // the Return *is* a CFG user of Fun.
+ @Override public CFGNode uctrl() {
+ for( Node n : _outputs )
+ if( n instanceof CFGNode cfg &&
+ (cfg instanceof RegionNode || cfg.cfg0()==this) )
+ return cfg;
+ return null;
+ }
+
+ public ParmNode rpc() {
+ ParmNode rpc = null;
+ for( Node n : _outputs )
+ if( n instanceof ParmNode parm && parm._idx==0 )
+ { assert rpc==null; rpc=parm; }
+ return rpc;
+ }
+
+ // Cannot create the Return and Fun at the same time; one has to be first.
+ // So setting the return requires a second step.
+ public void setRet(ReturnNode ret) { _ret=ret; }
+ public ReturnNode ret() { assert _ret!=null; return _ret; }
+
+ // Signature can improve over time
+ public SONTypeFunPtr sig() { return _sig; }
+ public void setSig( SONTypeFunPtr sig ) {
+ assert sig.isa(_sig);
+ if( _sig != sig ) {
+ CODE.add(this);
+ _sig = sig;
+ }
+ }
+
+ @Override
+ public SONType compute() {
+ // Only dead if no callers after SCCP
+ return SONType.CONTROL;
+ }
+
+ @Override
+ public Node idealize() {
+
+ // Some linked path dies
+ Node progress = deadPath();
+ if( progress!=null ) {
+ if( nIns()==3 && in(2) instanceof CallNode call )
+ CODE.add(call.cend()); // If Start and one call, check for inline
+ return progress;
+ }
+
+ // Upgrade inferred or user-written return type to actual
+ if( _ret!=null && _ret._type instanceof SONTypeTuple tt && tt.ret() != _sig.ret() )
+ //throw Utils.TODO();
+ return null;
+
+ // When can we assume no callers? Or no other callers (except main)?
+ // In a partial compilation, we assume Start gets access to any/all
+ // top-level public structures and recursively what they point to.
+ // This in turn is valid arguments to every callable function.
+ //
+ // In a total compilation, we can start from Start and keep things
+ // more contained.
+
+ // If no default/unknown caller, use the normal RegionNode ideal rules
+ // to collapse
+ if( unknownCallers() ) return null;
+
+ // If down to a single input, become that input
+ if( nIns()==2 && !hasPhi() ) {
+ CODE.add( CODE._stop ); // Stop will remove dead path
+ CODE.add( _ret ); // Return will compute to TOP control
+ return in(1); // Collapse if no Phis; 1-input Phis will collapse on their own
+ }
+
+ return null;
+ }
+
+ // Bypass Region idom, always assume depth == 1, one more than Start
+ @Override public int idepth() { return (_idepth=1); }
+ // Bypass Region idom, always assume idom is Start
+ @Override public CFGNode idom(Node dep) { return cfg(1); }
+
+ // Always in-progress until we run out of unknown callers
+ public boolean unknownCallers() { return in(1) instanceof StartNode; }
+
+ @Override public boolean inProgress() { return unknownCallers(); }
+
+ // Add a new function exit point.
+ public void addReturn(Node ctrl, Node mem, Node rez) { _ret.addReturn(ctrl,mem,rez); }
+
+ // Build the function body
+ public BitSet body() {
+
+ // Reverse up (stop to start) CFG only, collect bitmap.
+ BitSet cfgs = new BitSet();
+ cfgs.set(_nid);
+ walkUp(ret(),cfgs );
+
+ // Top down (start to stop) all flavors. CFG limit to bitmap.
+ // If data use bottoms out in wrong CFG, returns false - but tries all outputs.
+ // If any output hits an in-CFG use (e.g. phi), then keep node.
+ BitSet body = new BitSet();
+ walkDown(this, cfgs, body, new BitSet());
+ return body;
+ }
+
+ private static void walkUp(CFGNode n, BitSet cfgs) {
+ if( cfgs.get(n._nid) ) return;
+ cfgs.set(n._nid);
+ if( n instanceof RegionNode r )
+ for( int i=1; i ">=";
+ case "<=" -> ">" ;
+ case "==" -> "!=";
+ case "!=" -> "==";
+ case ">" -> "<=";
+ case ">=" -> "<" ;
+ default -> throw Utils.TODO();
+ };
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/LoadNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/LoadNode.java
new file mode 100644
index 0000000..fdec37a
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/LoadNode.java
@@ -0,0 +1,209 @@
+package com.compilerprogramming.ezlang.compiler.nodes;
+
+import com.compilerprogramming.ezlang.compiler.Utils;
+import com.compilerprogramming.ezlang.compiler.sontypes.*;
+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(String name, int alias, SONType glb, Node mem, Node ptr, Node off) {
+ super(name, alias, glb, mem, ptr, off);
+ }
+
+ // GraphVis DOT code (must be valid Java identifiers) and debugger labels
+ @Override public String label() { return "ld_"+mlabel(); }
+ // GraphVis node-internal labels
+ @Override public String glabel() { return "." +_name; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); }
+
+ @Override
+ public SONType compute() {
+ if( mem()._type instanceof SONTypeMem mem ) {
+ // Update declared forward ref to the actual
+ if( _declaredType.isFRef() && mem._t instanceof SONTypeMemPtr tmp && !tmp.isFRef() )
+ _declaredType = tmp;
+ // No lifting if ptr might null-check
+ if( err()==null )
+ return _declaredType.join(mem._t);
+ }
+ return _declaredType;
+ }
+
+ @Override
+ public Node idealize() {
+ Node ptr = ptr();
+ Node mem = mem();
+
+ // Simple Load-after-Store on same address.
+ if( mem instanceof StoreNode st &&
+ ptr == st.ptr() && off() == st.off() ) { // Must check same object
+ assert _name.equals(st._name); // Equiv class aliasing is perfect
+ return extend(st.val());
+ }
+
+ // Simple Load-after-New on same address.
+ if( mem instanceof ProjNode p && p.in(0) instanceof NewNode nnn &&
+ ptr == nnn.proj(1) ) // Must check same object
+ return zero(nnn); // Load zero from new
+
+ // Uplift control to a prior dominating load.
+ for( Node memuse : mem._outputs )
+ // Find a prior load, has same mem,ptr,off but higher ctrl
+ if( memuse != this && memuse instanceof LoadNode ld && ptr==ld.ptr() && off()==ld.off() &&
+ cfg0()!=null && cfg0()._idom(ld.cfg0(),this) == ld.cfg0() ) // Higher control means load is legal earlier
+ return ld;
+
+ // Load-after-Store on same address, but bypassing provably unrelated
+ // stores. This is a more complex superset of the above two peeps.
+ // "Provably unrelated" is really weak.
+ if( ptr instanceof ReadOnlyNode ro )
+ ptr = ro.in(1);
+ outer:
+ while( true ) {
+ switch( mem ) {
+ case StoreNode st:
+ if( ptr == st.ptr().addDep(this) && off() == st.off() )
+ return extend(castRO(st.val())); // Proved equal
+ // Can we prove unequal? Offsets do not overlap?
+ if( !off()._type.join(st.off()._type).isHigh() && // Offsets overlap
+ !neverAlias(ptr,st.ptr()) ) // And might alias
+ break outer; // Cannot tell, stop trying
+ // Pointers cannot overlap
+ mem = st.mem(); // Proved never equal
+ break;
+ case PhiNode phi:
+ // Assume related
+ phi.addDep(this);
+ break outer;
+ case ConstantNode top: break outer; // Assume shortly dead
+ case ProjNode mproj: // Memory projection
+ switch( mproj.in(0) ) {
+ case NewNode nnn1:
+ if( ptr instanceof ProjNode pproj && pproj.in(0) == mproj.in(0) )
+ return zero(nnn1);
+ if( !(ptr instanceof ProjNode pproj && pproj.in(0) instanceof NewNode) )
+ break outer; // Cannot tell, ptr not related to New
+ mem = nnn1.in(nnn1.findAlias(_alias));// Bypass unrelated New
+ break;
+ case StartNode start: break outer;
+ case CallEndNode cend: break outer; // TODO: Bypass no-alias call
+ default: throw Utils.TODO();
+ }
+ break;
+ case MemMergeNode merge: mem = merge.alias(_alias); break;
+
+ default:
+ throw Utils.TODO();
+ }
+ }
+
+ // Push a Load up through a Phi, as long as it collapses on at least
+ // one arm. If at a Loop, the backedge MUST collapse - else we risk
+ // spinning the same transform around the loop indefinitely.
+ // BEFORE (2 Sts, 1 Ld): AFTER (1 St, 0 Ld):
+ // if( pred ) ptr.x = e0; val = pred ? e0
+ // else ptr.x = e1; : e1;
+ // val = ptr.x; ptr.x = val;
+ if( mem() instanceof PhiNode memphi && memphi.region()._type == SONType.CONTROL && memphi.nIns()== 3 &&
+ // Offset can be hoisted
+ off() instanceof ConstantNode &&
+ // Pointer can be hoisted
+ hoistPtr(ptr,memphi) ) {
+
+ // Profit on RHS/Loop backedge
+ if( profit(memphi,2) ||
+ // Else must not be a loop to count profit on LHS.
+ (!(memphi.region() instanceof LoopNode) && profit(memphi,1)) ) {
+ Node ld1 = ld(1);
+ Node ld2 = ld(2);
+ return new PhiNode(_name,_declaredType,memphi.region(),ld1,ld2);
+ }
+ }
+
+ return null;
+ }
+
+ // Load a flavored zero from a New
+ private Node zero(NewNode nnn) {
+ SONTypeStruct ts = nnn._ptr._obj;
+ SONType zero = ts._fields[ts.findAlias(_alias)]._type.makeZero();
+ return castRO(new ConstantNode(zero).peephole());
+ }
+
+ private Node ld( int idx ) {
+ Node mem = mem(), ptr = ptr();
+ return new LoadNode(_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr, off()).peephole();
+ }
+
+ private static boolean neverAlias( Node ptr1, Node ptr2 ) {
+ return ptr1.in(0) != ptr2.in(0) &&
+ // Unrelated allocations
+ ptr1 instanceof ProjNode && ptr1.in(0) instanceof NewNode &&
+ ptr2 instanceof ProjNode && ptr2.in(0) instanceof NewNode;
+ }
+
+ private static boolean hoistPtr(Node ptr, PhiNode memphi ) {
+ // Can I hoist ptr above the Region?
+ if( !(memphi.region() instanceof RegionNode r) )
+ return false; // Dead or dying Region/Phi
+ // If ptr from same Region, then yes, just use hoisted split pointers
+ if( ptr instanceof PhiNode pphi && pphi.region() == r )
+ return true;
+
+ // No, so can we lift this ptr?
+ CFGNode cptr = ptr.cfg0();
+ if( cptr != null )
+ // Pointer is controlled high
+ // TODO: Really needs to be the LCA of all inputs is high
+ return cptr.idepth() <= r.idepth();
+
+ // Dunno without a longer walk
+ return false;
+ }
+
+ // Profitable if we find a matching Store on this Phi arm.
+ private boolean profit(PhiNode phi, int idx) {
+ Node px = phi.in(idx);
+ if( px==null ) return false;
+ if( px._type instanceof SONTypeMem mem && mem._t.isHighOrConst() ) return true;
+ if( px instanceof StoreNode st1 && ptr()==st1.ptr() && off()==st1.off() ) return true;
+ px.addDep(this);
+ return false;
+ }
+
+ // Read-Only is a deep property, and cannot be cast-away
+ private Node castRO(Node rez) {
+ if( ptr()._type.isFinal() && !rez._type.isFinal() )
+ return new ReadOnlyNode(rez).peephole();
+ return rez;
+ }
+
+ // When a load bypasses a store, the store might truncate bits - and the
+ // load will need to zero/sign-extend.
+ private Node extend(Node val) {
+ if( !(_declaredType instanceof SONTypeInteger ti) ) return val;
+ if( ti._min==0 ) // Unsigned
+ return new AndNode(val,con(ti._max));
+ // Signed extension
+ int shift = Long.numberOfLeadingZeros(ti._max)-1;
+ Node shf = con(shift);
+ if( shf._type== SONTypeInteger.ZERO )
+ return val;
+ Node shl = new ShlNode(val,shf.keep()).peephole();
+ return new SarNode(shl,shf.unkeep());
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/LogicalNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/LogicalNode.java
new file mode 100644
index 0000000..1b93813
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/LogicalNode.java
@@ -0,0 +1,28 @@
+package com.compilerprogramming.ezlang.compiler.nodes;
+
+import com.compilerprogramming.ezlang.compiler.Compiler;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeInteger;
+import com.compilerprogramming.ezlang.exceptions.CompilerException;
+
+import java.util.BitSet;
+
+public abstract class LogicalNode extends Node {
+
+ public LogicalNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+ abstract String op();
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("("), visited);
+ in(2)._print0(sb.append(op()), visited);
+ return sb.append(")");
+ }
+
+
+ @Override public CompilerException err() {
+ if( in(1)._type.isHigh() || in(2)._type.isHigh() ) return null;
+ if( !(in(1)._type instanceof SONTypeInteger) ) return Compiler.error("Cannot '"+op()+"' " + in(1)._type);
+ if( !(in(2)._type instanceof SONTypeInteger) ) return Compiler.error("Cannot '"+op()+"' " + in(2)._type);
+ return null;
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/LoopNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/LoopNode.java
new file mode 100644
index 0000000..721926c
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/LoopNode.java
@@ -0,0 +1,80 @@
+package com.compilerprogramming.ezlang.compiler.nodes;
+
+import com.compilerprogramming.ezlang.compiler.sontypes.SONType;
+
+public class LoopNode extends RegionNode {
+ public LoopNode( Node entry ) { super(null,entry,null); }
+ public LoopNode( LoopNode loop ) { super(loop); }
+
+ public CFGNode entry() { return cfg(1); }
+ public CFGNode back () { return cfg(2); }
+
+ @Override
+ public String label() { return "Loop"; }
+
+ @Override
+ public SONType compute() {
+ if( inProgress() ) return SONType.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 _idepth==0 ? (_idepth=idom().idepth()+1) : _idepth; }
+ // Bypass Region idom, same as the default idom() using use in(1) instead of in(0)
+ @Override public CFGNode idom(Node dep) { return entry(); }
+
+ // If this is an unreachable loop, it may not have an exit. If it does not
+ // (i.e., infinite loop), force an exit to make it reachable.
+ public StopNode forceExit( FunNode fun, StopNode stop ) {
+ // Walk the backedge, then immediate dominator tree util we hit this
+ // Loop again. If we ever hit a CProj from an If (as opposed to
+ // directly on the If) we found our exit.
+ CFGNode x = back();
+ while( x != this ) {
+ if( x instanceof CProjNode exit && exit.in(0) instanceof IfNode iff ) {
+ CFGNode other = iff.cproj(1-exit._idx);
+ if( other!=null && other.loopDepth() < loopDepth() )
+ return stop; // Found an exit, not an infinite loop
+ }
+ x = x.idom();
+ }
+ // Found a no-exit loop. Insert an exit
+ NeverNode iff = new NeverNode(back());
+ for( Node use : _outputs )
+ if( use instanceof PhiNode )
+ iff.addDef(use);
+ CProjNode t = new CProjNode(iff,0,"True" ).init();
+ CProjNode f = new CProjNode(iff,1,"False").init();
+ setDef(2,f);
+
+ // Now fold control into the exit. Might have 1 valid exit, or an
+ // XCtrl or a bunch of prior NeverNode exits.
+ Node top = new ConstantNode(SONType.TOP).peephole();
+ ReturnNode ret = fun.ret();
+ Node ctrl = ret.ctrl(), mem = ret.mem(), expr = ret.expr();
+ if( ctrl._type != SONType.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
+ ctrl = new RegionNode(null,ctrl).init();
+ mem = new PhiNode((RegionNode)ctrl,mem ).init();
+ expr = new PhiNode((RegionNode)ctrl,expr).init();
+ }
+ // Append new Never exit
+ ctrl.addDef(t );
+ mem .addDef(top);
+ expr.addDef(top);
+ } else {
+ ctrl = t;
+ mem = top;
+ expr = top;
+ }
+ ret.setDef(0,ctrl);
+ ret.setDef(1,mem );
+ ret.setDef(2,expr);
+
+ return stop;
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/MachConcreteNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/MachConcreteNode.java
new file mode 100644
index 0000000..82aa6e0
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/MachConcreteNode.java
@@ -0,0 +1,27 @@
+package com.compilerprogramming.ezlang.compiler.nodes;
+
+import com.compilerprogramming.ezlang.compiler.sontypes.SONType;
+import com.compilerprogramming.ezlang.compiler.Utils;
+
+import java.util.BitSet;
+
+// Generic machine-specific class, has a few Node implementations that have to
+// exist (abstract) but are not useful past the optimizer.
+public abstract class MachConcreteNode extends Node implements MachNode {
+
+ public MachConcreteNode(Node node) { super(node); }
+ public MachConcreteNode(Node[]nodes) { super(nodes); }
+
+ @Override public String label() { return op(); }
+ @Override public SONType compute () { throw Utils.TODO(); }
+ @Override public Node idealize() { throw Utils.TODO(); }
+
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("(").append(op()).append(",");
+ for( int i=1; i= nIns() ) addDef(null);
+ return setDef(alias,st);
+ }
+
+ // Read or update from memory.
+ // A shared implementation allows us to create lazy phis both during
+ // lookups and updates; the lazy phi creation is part of chapter 8.
+ Node _mem( int alias, Node st ) {
+ // Memory projections are made lazily; if one does not exist
+ // then it must be START.proj(1)
+ Node old = alias(alias);
+ if( old instanceof ScopeNode loop ) {
+ MemMergeNode loopmem = loop.mem();
+ Node memdef = loopmem.alias(alias);
+ // Lazy phi!
+ old = memdef instanceof PhiNode phi && loop.ctrl()==phi.region()
+ // Loop already has a real Phi, use it
+ ? memdef
+ // Set real Phi in the loop head
+ // The phi takes its one input (no backedge yet) from a recursive
+ // lookup, which might have insert a Phi in every loop nest.
+ : loopmem.alias(alias, new PhiNode(Compiler.memName(alias), SONTypeMem.BOT,loop.ctrl(),loopmem._mem(alias,null),null).peephole() );
+ alias(alias,old);
+ }
+ // Memory projections are made lazily; expand as needed
+ return st==null ? old : alias(alias,st); // Not lazy, so this is the answer
+ }
+
+
+ void _merge( MemMergeNode that, RegionNode r) {
+ int len = Math.max(nIns(),that.nIns());
+ for( int i = 2; i < len; i++)
+ if( alias(i) != that.alias(i) ) { // No need for redundant Phis
+ // If we are in lazy phi mode we need to a lookup
+ // by alias as it will trigger a phi creation
+ Node lhs = this._mem(i,null);
+ Node rhs = that._mem(i,null);
+ alias(i, new PhiNode(Compiler.memName(i), lhs._type.glb().meet(rhs._type.glb()), r, lhs, rhs).peephole());
+ }
+ }
+
+ // Fill in the backedge of any inserted Phis
+ void _endLoopMem( ScopeNode scope, MemMergeNode back, MemMergeNode exit ) {
+ Node exit_def = exit.alias(1);
+ for( int i=1; i outs();
+
+ // Find a projection by index
+ default ProjNode proj( int idx ) {
+ for( Node out : outs() )
+ if( out instanceof ProjNode prj && prj._idx==idx )
+ return prj;
+ return null;
+ }
+
+ // Find a projection by index
+ default CProjNode cproj( int idx ) {
+ for( Node out : outs() )
+ if( out instanceof CProjNode prj && prj._idx==idx )
+ return prj;
+ return null;
+ }
+
+ // Return not-null if this projection index is a ideal copy.
+ // Called by ProjNode ideal and used to collapse Multis.
+ default Node pcopy(int idx) { return null; }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/NeverNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/NeverNode.java
new file mode 100644
index 0000000..b60f7a1
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/NeverNode.java
@@ -0,0 +1,20 @@
+package com.compilerprogramming.ezlang.compiler.nodes;
+
+import com.compilerprogramming.ezlang.compiler.Compiler;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONType;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeTuple;
+
+import java.util.BitSet;
+
+// "Never true" for infinite loop exits
+public class NeverNode extends IfNode {
+ public NeverNode(Node ctrl) { super(ctrl,Compiler.ZERO); }
+
+ @Override public String label() { return "Never"; }
+
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append("Never"); }
+
+ @Override public SONType compute() { return SONTypeTuple.IF_BOTH; }
+
+ @Override public Node idealize() { return null; }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/NewNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/NewNode.java
new file mode 100644
index 0000000..364cea0
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/NewNode.java
@@ -0,0 +1,83 @@
+package com.compilerprogramming.ezlang.compiler.nodes;
+
+import com.compilerprogramming.ezlang.compiler.sontypes.*;
+
+import java.util.BitSet;
+
+/**
+ * Allocation! Allocate a chunk of memory, and pre-zero it.
+ * The inputs include control and size, and ALL aliases being set.
+ * The output is large tuple, one for every alias plus the created pointer.
+ * New is expected to be followed by projections for every alias.
+ */
+public class NewNode extends Node implements MultiNode {
+
+ public final SONTypeMemPtr _ptr;
+ public final int _len;
+
+ public NewNode(SONTypeMemPtr ptr, Node... nodes) {
+ super(nodes);
+ assert !ptr.nullable();
+ _ptr = ptr;
+ _len = ptr._obj._fields.length;
+ // Control in slot 0
+ assert nodes[0]._type== SONType.CONTROL || nodes[0]._type == SONType.XCONTROL;
+ // Malloc-length in slot 1
+ assert nodes[1]._type instanceof SONTypeInteger || nodes[1]._type== SONType.NIL;
+ for( int i=0; i<_len; i++ )
+ // Memory slices for all fields.
+ assert nodes[2+i]._type.isa(SONTypeMem.BOT);
+ }
+
+ public NewNode(NewNode nnn) { super(nnn); _ptr = nnn._ptr; _len = nnn._len; }
+
+ public Node mem (int idx) { return in(idx+2); }
+
+ @Override public String label() { return "new_"+glabel(); }
+ @Override public String glabel() {
+ return _ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._type.str() : _ptr._obj.str();
+ }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("new ");
+ return sb.append(_ptr._obj.str());
+ }
+
+ int findAlias(int alias) {
+ int idx = _ptr._obj.findAlias(alias);
+ assert idx!= -1; // Error, caller should be calling
+ return idx+2; // Skip ctrl, size
+ }
+
+ // 0 - ctrl
+ // 1 - byte size
+ // 2-len+2 - aliases, one per field
+ // len+2 - 2*len+2 - initial values, one per field
+ public Node size() { return in(1); }
+
+ @Override
+ public SONTypeTuple compute() {
+ Field[] fs = _ptr._obj._fields;
+ SONType[] ts = new SONType[fs.length+2];
+ ts[0] = SONType.CONTROL;
+ ts[1] = _ptr;
+ for( int i=0; i
+ * 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 final Ary _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.
+ *
+ * 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 final Ary _outputs;
+
+
+ /**
+ * Current computed type for this Node. This value changes as the graph
+ * changes and more knowledge is gained about the program.
+ */
+ public SONType _type;
+
+
+ Node(Node... inputs) {
+ _nid = CODE.getUID(); // allocate unique dense ID
+ _inputs = new Ary<>(Node.class);
+ Collections.addAll(_inputs,inputs);
+ _outputs = new Ary<>(Node.class);
+ for( Node n : _inputs )
+ if( n != null )
+ n.addUse( this );
+ }
+
+ // Make a Node using the existing arrays of nodes.
+ // Used by any pass rewriting all Node classes but not the edges.
+ Node( Node n ) {
+ assert CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.InstSelect.ordinal();
+ _nid = CODE.getUID(); // allocate unique dense ID
+ _inputs = new Ary<>(n==null ? new Node[0] : n._inputs.asAry());
+ _outputs = new Ary<>(Node.class);
+ _type = n==null ? SONType.BOTTOM : n._type;
+ _deps = null;
+ _hash = 0;
+ }
+
+ // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ"
+ public abstract String label();
+
+ // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99"
+ public String uniqueName() {
+ // Get rid of $ as graphviz doesn't like it
+ String label = label().replaceAll("\\$", "");
+ return label + _nid;
+ }
+
+ // Graphical label, e.g. "+" or "Region" or "=="
+ public String glabel() { return label(); }
+
+ // Extra fun stuff, for assembly printing. Jump labels, parser locations,
+ // variable types, etc.
+ public String comment() { return null; }
+
+ // ------------------------------------------------------------------------
+
+ // Debugger Printing.
+
+ // {@code toString} is what you get in the debugger. It has to print 1
+ // line (because this is what a debugger typically displays by default) and
+ // has to be robust with broken graph/nodes.
+ @Override
+ public final String toString() { return print(); }
+
+ // This is a *deep* print. We print with a mutually recursive tik-tok
+ // style; the common _print0 calls the per-Node _print1, which calls back
+ // to _print0;
+ public final String print() {
+ return _print0(new StringBuilder(), new BitSet()).toString();
+ }
+
+ // This is the common print: check for repeats, check for DEAD and print
+ // "DEAD" else call the per-Node print1.
+ public final StringBuilder _print0(StringBuilder sb, BitSet visited) {
+ if (visited.get(_nid) && !(this instanceof ConstantNode) )
+ return sb.append(label());
+ visited.set(_nid);
+ return isDead()
+ ? sb.append(uniqueName()).append(":DEAD")
+ : _print1(sb, visited);
+ }
+ // Every Node implements this; a partial-line recursive print
+ abstract public StringBuilder _print1(StringBuilder sb, BitSet visited);
+
+ public String p(int depth) { return IRPrinter.prettyPrint(this,depth); }
+
+ public boolean isConst() { return false; }
+
+ // ------------------------------------------------------------------------
+ // Graph Node & Edge manipulation
+
+ /**
+ * Gets the ith input node
+ * @param i Offset of the input node
+ * @return Input node or null
+ */
+ public Node in(int i) { return _inputs.get(i); }
+ public Node out(int i) { return _outputs.get(i); }
+ public final Ary 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 def into a Node. Keeps the edges correct, by removing
+ * the corresponding use->def edge. This may make the original
+ * def go dead. This function is co-recursive with {@link #kill}.
+ *
+
+ * This method is the normal path for altering a Node, because it does the
+ * proper default edge maintenance. It also immediately 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 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 setDefX(int idx, N new_def ) {
+ while( nIns() <= idx ) addDef(null);
+ return setDef(idx,new_def);
+ }
+
+ // Remove the numbered input, compressing the inputs in-place. This
+ // shuffles the order deterministically - which is suitable for Region and
+ // Phi, but not for every Node. If the def goes dead, it is recursively
+ // killed, which may include 'this' Node.
+ public Node delDef(int idx) {
+ unlock();
+ Node old_def = in(idx);
+ _inputs.del(idx);
+ if( old_def.delUse(this) ) // If we removed the last use, the old def is now dead
+ old_def.kill(); // Kill old def
+ old_def.moveDepsToWorklist();
+ return this;
+ }
+
+ // Insert the numbered input, sliding other inputs to the right
+ Node insertDef(int idx, Node new_def) {
+ _inputs.add(idx,null);
+ return setDef(idx,new_def);
+ }
+
+
+ /**
+ * Add a new def to an existing Node. Keep the edges correct by
+ * adding the corresponding def->use edge.
+ *
+ * @param new_def the new definition, appended to the end of existing definitions
+ * @return new_def for flow coding
+ */
+ public Node addDef(Node new_def) {
+ unlock();
+ // Add use->def edge
+ _inputs.add(new_def);
+ // If new def is not null, add the corresponding def->use edge
+ if( new_def != null )
+ new_def.addUse(this);
+ return new_def;
+ }
+
+ // Breaks the edge invariants, used temporarily
+ protected N addUse(Node n) { _outputs.add(n); return (N)this; }
+
+ // Remove node 'use' from 'def's (i.e. our) output list, by compressing the list in-place.
+ // Return true if the output list is empty afterward.
+ // Error is 'use' does not exist; ok for 'use' to be null.
+ protected boolean delUse( Node use ) {
+ _outputs.del(_outputs.find(use));
+ return _outputs.isEmpty();
+ }
+
+ // Shortcut for "popping" until n nodes. A "pop" is basically a
+ // setDef(last,null) followed by lowering the nIns() count.
+ void popUntil(int n) {
+ unlock();
+ while( nIns() > n ) {
+ Node old_def = _inputs.pop();
+ if( old_def != null && // If it exists and
+ old_def.delUse(this) ) // If we removed the last use, the old def is now dead
+ old_def.kill(); // Kill old def
+ }
+ }
+
+ /**
+ * Kill a Node with no uses, by setting all of its defs
+ * to null. This may recursively kill more Nodes, and is basically dead
+ * code elimination.
+ */
+ public void kill( ) {
+ unlock();
+ moveDepsToWorklist();
+ assert isUnused(); // Has no uses, so it is dead
+ _type=null; // Flag as dead
+ while( nIns()>0 ) { // Set all inputs to null, recursively killing unused Nodes
+ Node old_def = _inputs.removeLast();
+ // Revisit neighbor because removed use
+ if( old_def != null && CODE.add(old_def).delUse(this) )
+ old_def.kill(); // If we removed the last use, the old def is now dead
+ }
+ assert isDead(); // Really dead now
+ }
+
+ // 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 keep() { return addUse(null); }
+ // Remove bogus null.
+ public 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();
+ }
+
+
+ // 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.addAll(n._outputs);
+ }
+ kill();
+ }
+
+ // Replace uses of `def` with `this`, and insert `this` immediately after
+ // `def` in the basic block.
+ public void insertAfter( Node def, boolean must ) {
+ CFGNode cfg = def.cfg0();
+ int i = cfg._outputs.find(def)+1;
+ if( cfg instanceof CallEndNode ) {
+ cfg = cfg.uctrl(); i=0;
+ } else if( def.in(0) instanceof MultiNode ) {
+ assert i==0;
+ i = cfg._outputs.find(def.in(0))+1;
+ }
+
+ while( cfg.out(i) instanceof PhiNode || cfg.out(i) instanceof CalleeSaveNode ) i++;
+ cfg._outputs.insert(this,i);
+ _inputs.set(0,cfg);
+ for( int j=def.nOuts()-1; j>=0; j-- ) {
+ // Can we avoid a split of a split? 'this' split is used by
+ // another split in the same block.
+ if( !must && def.out(j) instanceof SplitNode split && def.out(j).cfg0()==cfg &&
+ !split._kind.contains("self") )
+ continue;
+ Node use = def._outputs.del(j);
+ use.unlock();
+ int idx = use._inputs.find(def);
+ use._inputs.set(idx,this);
+ addUse(use);
+ }
+ if( nIns()>1 ) setDef(1,def);
+ }
+
+ // Insert this in front of use.in(uidx) with this, and insert this
+ // immediately before use in the basic block.
+ public void insertBefore( Node use, int uidx ) {
+ CFGNode cfg = use.cfg0();
+ int i;
+ if( use instanceof PhiNode phi ) {
+ cfg = phi.region().cfg(uidx);
+ i = cfg.nOuts()-1;
+ } else {
+ i = cfg._outputs.find(use);
+ }
+ cfg._outputs.insert(this,i);
+ _inputs.set(0,cfg);
+ if( _inputs._len > 1 && this instanceof SplitNode )
+ setDefOrdered(1,use.in(uidx));
+ use.setDefOrdered(uidx,this);
+ }
+
+ public void setDefOrdered(int idx, Node def) {
+ // If old is dying, remove from CFG ordered
+ Node old = in(idx);
+ if( old!=null && old.nOuts()==1 ) {
+ CFGNode cfg = old.cfg0();
+ if( cfg!=null ) {
+ cfg._outputs.remove(cfg._outputs.find(old));
+ old._inputs.set(0,null);
+ }
+ }
+ setDef(idx,def);
+ }
+
+ public void removeSplit() {
+ CFGNode cfg = cfg0();
+ cfg._outputs.remove(cfg._outputs.find(this));
+ _inputs.set(0,null);
+ if( _inputs._len > 1 ) subsume(in(1));
+ }
+
+ // ------------------------------------------------------------------------
+ // Graph-based optimizations
+
+ /**
+ * Try to peephole at this node and return a better replacement Node.
+ * Always returns some not-null Node (often this).
+ */
+ public final Node peephole( ) {
+// if( _type==null ) // Brand-new node, never peeped before
+// JSViewer.show();
+ Node n = peepholeOpt();
+// if( n!=null ) // Made progress?
+// JSViewer.show(); // Show again
+ return n==null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n); // Cannot return null for no-progress
+ }
+
+ /**
+ * Try to peephole at this node and return a better replacement Node if
+ * possible. We compute a {@link SONType} and then check and replace:
+ *
+ * - if the Type {@link SONType#isConstant}, we replace with a {@link ConstantNode}
+ * - in a future chapter we will look for a
+ * Common Subexpression
+ * to eliminate.
+ * - 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.
+ *
+ * Unlike peephole above, this explicitly returns null for no-change, or not-null
+ * for a better replacement (which can be this).
+ *
+ */
+ public final Node peepholeOpt( ) {
+ CODE.iterCnt();
+ // Compute initial or improved Type
+ SONType old = setType(compute());
+
+ // Replace constant computations from non-constants with a constant
+ // node. If peeps are disabled, still allow high Phis to collapse;
+ // they typically come from dead Regions, and we want the Region to
+ // collapse, which requires the Phis to die first.
+ if( _type.isHighOrConst() && !isConst() )
+ 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
+ * Monotonic
+ * as it is part of a Monotone Analysis Framework.
+ * See for example this set of slides.
+ *
+ * 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.
+ *
+ * {@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 SONType compute();
+
+ // Set the type. Assert monotonic progress.
+ // If changing, add users to worklist.
+ public SONType setType(SONType type) {
+ SONType old = _type;
+ assert old==null || type.isa(old); // Since _type not set, can just re-run this in assert in the debugger
+ if( old == type ) return old;
+ _type = type; // Set _type late for easier assert debugging
+ CODE.addAll(_outputs);
+ moveDepsToWorklist();
+ return old;
+ }
+
+ public 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:
+ * ary[idx+1]
+ * might turn into Sea-of-Nodes IR:
+ * (ary+12)+((idx+1) * 4)
+ * This expression can then be idealized into:
+ * ary + ((idx*4) + (12 + (1*4)))
+ * And more folding:
+ * ary + ((idx<<2) + 16)
+ * And during code-gen:
+ * MOV4 Rary,[Ridx<<2 +16] // or some such hardware-specific notation
+ *
+ * {@link #idealize} has a very specific calling convention:
+ *
+ * - If NO change is made, return {@code null}
+ *
- If ANY change is made, return not-null; this can be {@code this}
+ *
- The returned Node does NOT call {@link #peephole} on itself; the {@link #peephole} call will recursively peephole it.
+ *
- Any NEW nodes that are not directly returned DO call {@link #peephole}.
+ *
+ *
+ * Examples:
+ *
+ * | before | after | return | comment |
+ * | {@code (x+5) } | {@code (x+5) } | {@code null } | No change |
+ * | {@code (5+x) } | {@code (x+5) } | {@code this } | Swapped arguments |
+ * | {@code ((x+1)+2)} | {@code (x+(1+2))} | {@code (x+_) } | Returns 2 new Nodes |
+ *
+ *
+ * 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.
+ *
+ * Since idealize calls peephole and peephole calls idealize, you must be
+ * careful that all idealizations are monotonic: 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 _deps;
+
+ /**
+ * Add a node to the list of dependencies. Only add it if its not an input
+ * or output of this node, that is, it is at least one step away. The node
+ * being added must benefit from this node being peepholed.
+ */
+ Node addDep( Node dep ) {
+ // Running peepholes during the big assert cannot have side effects
+ // like adding dependencies.
+ if( CODE._midAssert ) return this;
+ if( dep == null ) return this;
+ if( _deps==null ) _deps = new Ary<>(Node.class);
+ if( _deps .find(dep) != -1 ) return this; // Already on list
+ if( _inputs .find(dep) != -1 ) return this; // No need for deps on immediate neighbors
+ if( _outputs.find(dep) != -1 ) return this;
+ _deps.add(dep);
+ return this;
+ }
+
+ // Move the dependents onto a worklist, and clear for future dependents.
+ public void moveDepsToWorklist( ) {
+ if( _deps==null ) return;
+ CODE.addAll(_deps);
+ _deps.clear();
+ }
+
+
+ // Two nodes are equal if they have the same inputs and the same "opcode"
+ // which means the same Java class, plus same internal parts.
+ @Override public final boolean equals( Object o ) {
+ if( o==this ) return true;
+ if( o.getClass() != getClass() ) return false;
+ Node n = (Node)o;
+ int len = _inputs.size();
+ if( len != n._inputs.size() ) return false;
+ for( int i=0; i>13) ^ n._nid;
+ if( hash==0 ) hash = 0xDEADBEEF; // Bad hash, so use some junky thing
+ return (_hash=hash);
+ }
+ // Subclasses add extra hash info (such as ConstantNodes constant)
+ int hash() { return 0; }
+
+ // ------------------------------------------------------------------------
+ //
+
+ /** Is this Node Memory related */
+ public boolean isMem() { return false; }
+
+ /** Pinned in the schedule; these are data nodes whose input#0 is not allowed to change */
+ public boolean isPinned() { return false; }
+
+ // Semantic change to the graph (so NOT a peephole), used by the Parser.
+ // If any input is a float, flip to a float-flavored opcode and widen any
+ // non-float input.
+ public final Node widen() {
+ if( !hasFloatInput() ) return this;
+ Node flt = copyF();
+ if( flt==null ) return this;
+ for( int i=1; i E walk( Function pred ) {
+ assert CODE._visit.isEmpty();
+ E rez = _walk(pred);
+ CODE._visit.clear();
+ return rez;
+ }
+
+ private E _walk( Function pred ) {
+ if( CODE._visit.get(_nid) ) return null; // Been there, done that
+ CODE._visit.set(_nid);
+ E x = pred.apply(this);
+ if( x != null ) return x;
+ for( Node def : _inputs ) if( def != null && (x = def._walk(pred)) != null ) return x;
+ for( Node use : _outputs ) if( use != null && (x = use._walk(pred)) != null ) return x;
+ return null;
+ }
+
+ /**
+ * Debugging utility to find a Node by index
+ */
+ public Node find(int nid) { return _find(nid, new BitSet()); }
+ private Node _find(int nid, BitSet bs) {
+ if( bs.get(_nid) ) return null; // Been there, done that
+ bs.set(_nid);
+ if( _nid==nid ) return this;
+ Node x;
+ for( Node def : _inputs ) if( def != null && (x = def._find(nid,bs)) != null ) return x;
+ for( Node use : _outputs ) if( use != null && (x = use._find(nid,bs)) != null ) return x;
+ return null;
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/NotNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/NotNode.java
new file mode 100644
index 0000000..5649814
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/NotNode.java
@@ -0,0 +1,31 @@
+package com.compilerprogramming.ezlang.compiler.nodes;
+
+import com.compilerprogramming.ezlang.compiler.sontypes.*;
+import java.util.BitSet;
+
+public class NotNode extends Node {
+ public NotNode(Node in) { super(null, in); }
+
+ @Override public String label() { return "Not"; }
+
+ @Override public String glabel() { return "!"; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("(!"), visited);
+ return sb.append(")");
+ }
+
+ @Override
+ public SONTypeInteger compute() {
+ SONType t0 = in(1)._type;
+ if( t0.isHigh() ) return SONTypeInteger.BOOL.dual();
+ if( t0 == SONType.NIL || t0 == SONTypeInteger.ZERO ) return SONTypeInteger.TRUE;
+ if( t0 instanceof SONTypeNil tn && tn.notNull() ) return SONTypeInteger.FALSE;
+ if( t0 instanceof SONTypeInteger i && (i._min > 0 || i._max < 0) ) return SONTypeInteger.FALSE;
+ return SONTypeInteger.BOOL;
+ }
+
+ @Override
+ public Node idealize() { return null; }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/OrNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/OrNode.java
new file mode 100644
index 0000000..fc76f23
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/OrNode.java
@@ -0,0 +1,53 @@
+package com.compilerprogramming.ezlang.compiler.nodes;
+
+import com.compilerprogramming.ezlang.compiler.Compiler;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONType;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeInteger;
+
+public class OrNode extends LogicalNode {
+ public OrNode(Node lhs, Node rhs) { super(lhs, rhs); }
+
+ @Override public String label() { return "Or"; }
+ @Override public String op() { return "|"; }
+
+ @Override public String glabel() { return "|"; }
+
+ @Override
+ public SONType compute() {
+ SONType t1 = in(1)._type, t2 = in(2)._type;
+ if( t1.isHigh() || t2.isHigh() )
+ return SONTypeInteger.TOP;
+ if( t1 instanceof SONTypeInteger i0 &&
+ t2 instanceof SONTypeInteger i1 ) {
+ if( i0.isConstant() && i1.isConstant() )
+ return SONTypeInteger.constant(i0.value()|i1.value());
+ }
+ return SONTypeInteger.BOT;
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Node rhs = in(2);
+ SONType t1 = lhs._type;
+ SONType 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 SONTypeInteger i && i.value()==0 )
+ return lhs;
+
+ // Move constants to RHS: con*arg becomes arg*con
+ if ( t1.isConstant() && !t2.isConstant() )
+ return swap12();
+
+ // Do we have ((x | (phi cons)) | con) ?
+ // Do we have ((x | (phi cons)) | (phi cons)) ?
+ // Push constant up through the phi: x | (phi con0|con0 con1|con1...)
+ Node phicon = AddNode.phiCon(this,true);
+ if( phicon!=null ) return phicon;
+
+ return null;
+ }
+ @Override Node copy(Node lhs, Node rhs) { return new OrNode(lhs,rhs); }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ParmNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ParmNode.java
new file mode 100644
index 0000000..b755949
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ParmNode.java
@@ -0,0 +1,47 @@
+package com.compilerprogramming.ezlang.compiler.nodes;
+
+import com.compilerprogramming.ezlang.compiler.sontypes.SONType;
+import java.util.BitSet;
+
+public class ParmNode extends PhiNode {
+
+ // Argument indices are mapped one-to-one on CallNode inputs
+ public final int _idx; // Argument index
+
+ public ParmNode(String label, int idx, SONType declaredType, Node... inputs) {
+ super(label,declaredType,inputs);
+ _idx = idx;
+ }
+ public ParmNode(ParmNode parm) { super(parm, parm._label, parm._declaredType); _idx = parm._idx; }
+
+ @Override public String label() { return MemOpNode.mlabel(_label); }
+
+ @Override public String glabel() { return _label; }
+
+ public FunNode fun() { return (FunNode)in(0); }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ if( "main".equals(fun()._name) && _label.equals("arg") )
+ return sb.append("arg");
+ sb.append("Parm_").append(_label).append("(");
+ for( Node in : _inputs ) {
+ if (in == null) sb.append("____");
+ else in._print0(sb, visited);
+ sb.append(",");
+ }
+ sb.setLength(sb.length()-1);
+ sb.append(")");
+ return sb;
+ }
+
+
+ @Override
+ public Node idealize() {
+ if( inProgress() ) return null;
+ return super.idealize();
+ }
+
+ // Always in-progress until we run out of unknown callers
+ @Override public boolean inProgress() { return fun().inProgress(); }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/PhiNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/PhiNode.java
new file mode 100644
index 0000000..5523b17
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/PhiNode.java
@@ -0,0 +1,205 @@
+package com.compilerprogramming.ezlang.compiler.nodes;
+
+import com.compilerprogramming.ezlang.compiler.sontypes.*;
+import com.compilerprogramming.ezlang.exceptions.CompilerException;
+
+import java.util.BitSet;
+
+public class PhiNode extends Node {
+
+ public final String _label;
+
+ // The Phi type we compute must stay within the domain of the Phi.
+ // Example Int stays Int, Ptr stays Ptr, Control stays Control, Mem stays Mem.
+ final SONType _declaredType;
+
+ public PhiNode(String label, SONType declaredType, Node... inputs) { super(inputs); _label = label; assert declaredType!=null; _declaredType = declaredType; }
+ public PhiNode(PhiNode phi, String label, SONType declaredType) { super(phi); _label = label; _declaredType = declaredType; }
+ public PhiNode(PhiNode phi) { super(phi); _label = phi._label; _declaredType = phi._declaredType; }
+
+ public PhiNode(RegionNode r, Node sample) {
+ super(new Node[]{r});
+ _label = "";
+ _declaredType = sample._type;
+ while( nIns() < r.nIns() )
+ addDef(sample);
+ }
+
+ @Override public String label() { return "Phi_"+MemOpNode.mlabel(_label); }
+
+ @Override public String glabel() { return "φ_"+_label; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ if( !(region() instanceof RegionNode r) || r.inProgress() )
+ sb.append("Z");
+ sb.append("Phi(");
+ for( Node in : _inputs ) {
+ if (in == null) sb.append("____");
+ else in._print0(sb, visited);
+ sb.append(",");
+ }
+ sb.setLength(sb.length()-1);
+ sb.append(")");
+ return sb;
+ }
+
+ public CFGNode region() { return (CFGNode)in(0); }
+ @Override public boolean isMem() { return _declaredType instanceof SONTypeMem; }
+ @Override public boolean isPinned() { return true; }
+
+ @Override
+ public SONType compute() {
+ if( !(region() instanceof RegionNode r) )
+ return region()._type== SONType.XCONTROL ? (_type instanceof SONTypeMem ? SONTypeMem.TOP : SONType.TOP) : _type;
+ // During parsing Phis have to be computed type pessimistically.
+ if( r.inProgress() ) return _declaredType;
+ // Set type to local top of the starting type
+ SONType t = _declaredType.glb().dual();//Type.TOP;
+ for (int i = 1; i < nIns(); i++)
+ // If the region's control input is live, add this as a dependency
+ // to the control because we can be peeped should it become dead.
+ if( r.in(i).addDep(this)._type != SONType.XCONTROL )
+ t = t.meet(in(i)._type);
+ return t;
+ }
+
+ @Override
+ public Node idealize() {
+ if( !(region() instanceof RegionNode r ) )
+ return in(1); // Input has collapse to e.g. starting control.
+ if( r.inProgress() || r.nIns()<=1 )
+ return null; // Input is in-progress
+
+ // If we have only a single unique input, become it.
+ Node live = singleUniqueInput();
+ if (live != null)
+ return live;
+
+ // No bother if region is going to fold dead paths soon
+ for( int i=1; i 0 && idom.in(0) != iff ) idom = idom.idom();
+ if( idom instanceof CProjNode proj && proj._idx==1 )
+ return val;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private boolean same_op() {
+ for( int i=2; i1 && in(nIns()-1) == null; }
+
+ // Never equal if inProgress
+ @Override boolean eq( Node n ) { return !inProgress(); }
+
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ReturnNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ReturnNode.java
new file mode 100644
index 0000000..b44328e
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ReturnNode.java
@@ -0,0 +1,131 @@
+package com.compilerprogramming.ezlang.compiler.nodes;
+
+import com.compilerprogramming.ezlang.compiler.Compiler;
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.sontypes.*;
+import com.compilerprogramming.ezlang.exceptions.CompilerException;
+
+import java.util.BitSet;
+
+/**
+ * The Return node has two inputs. The first input is a control node and the
+ * second is the data node that supplies the return value.
+ *
+ * In this presentation, Return functions as a Stop node, since multiple return statements are not possible.
+ * The Stop node will be introduced in Chapter 6 when we implement if statements.
+ *
+ * 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); }
+ public FunNode fun() { return _fun; }
+
+ @Override
+ public String label() { return "Return"; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("return ");
+ if( expr()==null ) sb.append("----");
+ else expr()._print0(sb, visited);
+ return sb.append(";");
+ }
+
+ // No one unique control follows; can be many call end sites
+ @Override public CFGNode uctrl() { return null; }
+
+ @Override
+ public SONType compute() {
+ if( inProgress () ) return SONTypeTuple.RET; // In progress
+ if( _fun.isDead() ) return SONTypeTuple.RET.dual(); // Dead another way
+ return SONTypeTuple.make(ctrl()._type,mem()._type,expr()._type);
+ }
+
+ @Override public Node idealize() {
+ if( inProgress () ) return null;
+ if( _fun.isDead() ) return null;
+
+// // Upgrade signature based on return type
+// SONType ret = expr()._type;
+// SONTypeFunPtr fcn = _fun.sig();
+// assert ret.isa(fcn.ret());
+// if( ret != fcn.ret() )
+// _fun.setSig(fcn.makeFrom(ret));
+
+ // If dead (cant be reached; infinite loop), kill the exit values
+ if( ctrl()._type== SONType.XCONTROL &&
+ !(mem() instanceof ConstantNode && expr() instanceof ConstantNode) ) {
+ Node top = new ConstantNode(SONType.TOP).peephole();
+ setDef(1,top);
+ setDef(2,top);
+ return this;
+ }
+
+ return null;
+ }
+
+ public boolean inProgress() {
+ return ctrl().getClass() == RegionNode.class && ((RegionNode)ctrl()).inProgress();
+ }
+
+ // Gather parse-time return types for error reporting
+ private SONType mt = SONType.TOP;
+ private boolean ti=false, tf=false, tp=false, tn=false;
+
+ // Add a return exit to the current parsing function
+ void addReturn( Node ctrl, Node rmem, Node expr ) {
+ assert inProgress();
+
+ // Gather parse-time return types for error reporting
+ SONType t = expr._type;
+ mt = mt.meet(t);
+ ti |= t instanceof SONTypeInteger x;
+ tf |= t instanceof SONTypeFloat x;
+ tp |= t instanceof SONTypeMemPtr x;
+ tn |= t== SONType.NIL;
+
+ // Merge path into the One True Return
+ RegionNode r = (RegionNode)ctrl();
+ // Assert that the Phis are in particular outputs; not reordered or shuffled
+ PhiNode mem = (PhiNode)r.out(0); assert mem._declaredType == SONTypeMem.BOT;
+ PhiNode rez = (PhiNode)r.out(1); assert rez._declaredType == SONType.BOTTOM;
+ // Pop "inProgress" null off
+ r ._inputs.pop();
+ mem._inputs.pop();
+ rez._inputs.pop();
+ // Add new return point
+ r .addDef(ctrl);
+ mem.addDef(rmem);
+ rez.addDef(expr);
+ // Back to being inProgress
+ r .addDef(null);
+ mem.addDef(null);
+ rez.addDef(null);
+ }
+
+ @Override public CompilerException err() {
+ return expr()._type/*mt*/== SONType.BOTTOM ? mixerr(ti,tf,tp,tn) : null;
+ }
+
+ static CompilerException mixerr( boolean ti, boolean tf, boolean tp, boolean tn) {
+ if( !ti && !tf && !tp && !tn )
+ return Compiler.error("No defined return type");
+ SB sb = new SB().p("No common type amongst ");
+ if( ti ) sb.p("int and ");
+ if( tf ) sb.p("f64 and ");
+ if( tp || tn ) sb.p("reference and ");
+ return Compiler.error(sb.unchar(5).toString());
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/RoundF32Node.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/RoundF32Node.java
new file mode 100644
index 0000000..aa9a698
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/RoundF32Node.java
@@ -0,0 +1,38 @@
+package com.compilerprogramming.ezlang.compiler.nodes;
+
+import com.compilerprogramming.ezlang.compiler.sontypes.SONType;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFloat;
+
+import java.util.BitSet;
+
+public class RoundF32Node extends Node {
+ public RoundF32Node(Node lhs) { super(null, lhs); }
+
+ @Override public String label() { return "RoundF32"; }
+
+ @Override public String glabel() { return "(f32)"; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return in(1)._print0(sb.append("((f32)"), visited).append(")");
+ }
+
+ @Override
+ public SONType compute() {
+ if (in(1)._type instanceof SONTypeFloat i0 && i0.isConstant() )
+ return SONTypeFloat.constant((float)i0.value());
+ return in(1)._type;
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ SONType t1 = lhs._type;
+
+ // RoundF32 of float
+ if( t1 instanceof SONTypeFloat tf && tf._sz==32 )
+ return lhs;
+
+ return null;
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/SarNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/SarNode.java
new file mode 100644
index 0000000..1a823dd
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/SarNode.java
@@ -0,0 +1,48 @@
+package com.compilerprogramming.ezlang.compiler.nodes;
+
+import com.compilerprogramming.ezlang.compiler.sontypes.SONType;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeInteger;
+
+public class SarNode extends LogicalNode {
+ public SarNode(Node lhs, Node rhs) { super(lhs, rhs); }
+
+ @Override public String label() { return "Sar"; }
+ @Override public String op() { return ">>"; }
+
+ @Override public String glabel() { return ">>"; }
+
+ @Override
+ public SONType compute() {
+ SONType t1 = in(1)._type, t2 = in(2)._type;
+ if( t1.isHigh() || t2.isHigh() )
+ return SONTypeInteger.TOP;
+ if (t1 instanceof SONTypeInteger i0 &&
+ t2 instanceof SONTypeInteger i1) {
+ if( i0 == SONTypeInteger.ZERO )
+ return SONTypeInteger.ZERO;
+ if( i0.isConstant() && i1.isConstant() )
+ return SONTypeInteger.constant(i0.value()>>i1.value());
+ if( i1.isConstant() ) {
+ int log = (int)i1.value();
+ return SONTypeInteger.make(-1L<<(63-log),(1L<<(63-log))-1);
+ }
+ }
+ return SONTypeInteger.BOT;
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Node rhs = in(2);
+ SONType t2 = rhs._type;
+
+ // Sar of 0.
+ if( t2.isConstant() && t2 instanceof SONTypeInteger i && (i.value()&63)==0 )
+ return lhs;
+
+ // TODO: x >> 3 >> (y ? 1 : 2) ==> x >> (y ? 4 : 5)
+
+ return null;
+ }
+ @Override Node copy(Node lhs, Node rhs) { return new SarNode(lhs,rhs); }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ScopeNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ScopeNode.java
new file mode 100644
index 0000000..c3109cc
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ScopeNode.java
@@ -0,0 +1,429 @@
+package com.compilerprogramming.ezlang.compiler.nodes;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.CodeGen;
+import com.compilerprogramming.ezlang.compiler.sontypes.*;
+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
+ public final Ary _vars;
+
+ // Size of each nested lexical scope
+ public final Ary _lexSize;
+
+ // Lexical scope is one of normal Block, constructor or function
+ public enum Kind { Block, Constructor, Function };
+ public final Ary _kinds;
+
+ // Extra guards; tested predicates and casted results
+ private final Ary _guards;
+
+ // A new ScopeNode
+ public ScopeNode() {
+ super(true);
+ _vars = new Ary<>(Var .class);
+ _lexSize= new Ary<>(Integer.class);
+ _kinds = new Ary<>(Kind .class);
+ _guards = new Ary<>(Node .class);
+ }
+
+ @Override public String label() { return "Scope"; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("Scope[ ");
+ int j=1;
+ for( int i=0; i N ctrl(N n) { return setDef(0,n); }
+ public Node mem(Node n) { return setDef(1,n); }
+
+ public void push(Kind kind) {
+ assert _lexSize._len==_kinds._len;
+ _lexSize.push(_vars.size());
+ _kinds .push(kind);
+ }
+
+ // Pop a lexical scope
+ public void pop() {
+ assert _lexSize._len==_kinds._len;
+ promote();
+ int n = _lexSize.pop();
+ _kinds.pop();
+ popUntil(n);
+ _vars.setLen(n);
+ }
+
+
+ // 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() {
+ int n = _lexSize.last();
+ for( int i=n; i=0 && v._idx<_lexSize.at(i); i-- )
+ if( _kinds.at(i)==Kind.Function )
+ return true;
+ return false;
+ }
+
+
+ // 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.
+ int find( String name ) {
+ for( int i=_vars.size()-1; i>=0; i-- )
+ if( _vars.at(i)._name.equals(name) )
+ return i;
+ return -1;
+ }
+
+ /**
+ * Create a new variable name in the current scope
+ */
+ public boolean define(String name, SONType declaredType, boolean xfinal, Node init) {
+ assert _lexSize.isEmpty() || name.charAt(0)!='$' ; // Later scopes do not define memory
+ if( _lexSize._len > 0 )
+ for( int i=_vars.size()-1; i>=_lexSize.last(); i-- ) {
+ Var n = _vars.at(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); // Declare full correct type, final, source location
+ setDef(n._idx,fref.addDef(init)); // Set FRef to defined; tell parser also
+ }
+ }
+ Var v = new Var(nIns(),name,declaredType,xfinal,init==Compiler.XCTRL);
+ _vars.add(v);
+ // Creating a forward reference
+ if( init==Compiler.XCTRL )
+ init = new FRefNode(v).init();
+ addDef(init);
+ return true;
+ }
+
+ // Read from memory
+ public Node mem( int alias ) { return mem()._mem(alias,null); }
+ // Write to memory
+ public void mem( int alias, Node st ) { mem()._mem(alias,st); }
+
+
+ /**
+ * Lookup a name in all scopes starting from most deeply nested.
+ *
+ * @param name Name to be looked up
+ * @return null if not found, or the implementing Node
+ */
+ public Var lookup( String name ) {
+ int idx = find(name);
+ // -1 is missed in all scopes, not found
+ return idx == -1 ? null : update(_vars.at(idx),null);
+ }
+
+ /**
+ * If the name is present in any scope, then redefine else null
+ *
+ * @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(_vars.at(idx),n);
+ }
+
+ public Var update( Var v, Node st ) {
+ Node old = in(v._idx);
+ if( old instanceof ScopeNode loop ) {
+ // Lazy Phi!
+ Node def = loop.in(v._idx);
+ old = def instanceof PhiNode phi && loop.ctrl()==phi.region()
+ // Loop already has a real Phi, use it
+ ? def
+ // Set real Phi in the loop head
+ // The phi takes its one input (no backedge yet) from a recursive
+ // lookup, which might have insert a Phi in every loop nest.
+ : loop.setDef(v._idx,new PhiNode(v._name, v.lazyGLB(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).peephole());
+ setDef(v._idx,old);
+ }
+ assert !v._final || st==null;
+ if( st!=null ) setDef(v._idx,st); // Set new value
+ return v;
+ }
+
+ /**
+ * Duplicate a ScopeNode; including all levels, up to Nodes. So this is
+ * neither shallow (would dup the Scope but not the internal HashMap
+ * tables), nor deep (would dup the Scope, the HashMap tables, but then
+ * also the program Nodes).
+ *
+ * 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.
+ *
+ * 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._lexSize.addAll(_lexSize);
+ dup._kinds .addAll(_kinds );
+ dup._guards .addAll(_guards );
+ // The dup'd guards all need dup'd keepers, to keep proper accounting
+ // when later removing all guards
+ for( Node n : _guards )
+ if( !(n instanceof CFGNode) )
+ n.keep();
+ dup.addDef(ctrl()); // Control input is just copied
+
+ // Memory input is a shallow copy
+ MemMergeNode memdup = new MemMergeNode(true), mem = mem();
+ memdup.addDef(null);
+ memdup.addDef(loop ? this : mem.in(1));
+ for( int i=2; i 0 ) {
+ Node cast = _guards.at(--i);
+ if( cast instanceof CFGNode ) continue; // Marker between guard sets
+ Node xpred = _guards.at(--i);
+ if( xpred == pred )
+ return cast;
+ }
+ return pred;
+ }
+
+ // Kill guards also
+ @Override public void kill() {
+ for( Node n : _guards )
+ if( !(n instanceof CFGNode) )
+ n.unkill();
+ _guards.clear();
+ // Can have lazy uses remaining
+ if( isUnused() )
+ super.kill();
+ }
+
+
+ private void replace( Node old, Node cast ) {
+ assert old!=null && old!=cast;
+ for( int i=0; i (x << i) + (c << i)
+ if( lhs instanceof AddNode add && add.addDep(this).in(2)._type instanceof SONTypeInteger c && c.isConstant() ) {
+ long sum = c.value() << shl.value();
+ if( Integer.MIN_VALUE <= sum && sum <= Integer.MAX_VALUE )
+ return new AddNode(new ShlNode(add.in(1),rhs).peephole(), Compiler.con(sum) );
+ }
+ }
+
+ // TODO: x << 3 << (y ? 1 : 2) ==> x << (y ? 4 : 5)
+
+ return null;
+ }
+ @Override Node copy(Node lhs, Node rhs) { return new ShlNode(lhs,rhs); }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ShrNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ShrNode.java
new file mode 100644
index 0000000..c4b2a9b
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ShrNode.java
@@ -0,0 +1,45 @@
+package com.compilerprogramming.ezlang.compiler.nodes;
+
+import com.compilerprogramming.ezlang.compiler.Compiler;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONType;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeInteger;
+
+public class ShrNode extends LogicalNode {
+ public ShrNode(Node lhs, Node rhs) { super(lhs, rhs); }
+
+ @Override public String label() { return "Shr"; }
+ @Override public String op() { return ">>>"; }
+
+ @Override public String glabel() { return ">>>"; }
+
+ @Override
+ public SONType compute() {
+ SONType t1 = in(1)._type, t2 = in(2)._type;
+ if( t1.isHigh() || t2.isHigh() )
+ return SONTypeInteger.TOP;
+ if (t1 instanceof SONTypeInteger i0 &&
+ t2 instanceof SONTypeInteger i1 ) {
+ if( i0 == SONTypeInteger.ZERO )
+ return SONTypeInteger.ZERO;
+ if( i0.isConstant() && i1.isConstant() )
+ return SONTypeInteger.constant(i0.value()>>>i1.value());
+ }
+ return SONTypeInteger.BOT;
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Node rhs = in(2);
+ SONType t2 = rhs._type;
+
+ // Shr of 0.
+ if( t2.isConstant() && t2 instanceof SONTypeInteger i && (i.value()&63)==0 )
+ return lhs;
+
+ // TODO: x >>> 3 >>> (y ? 1 : 2) ==> x >>> (y ? 4 : 5)
+
+ return null;
+ }
+ @Override Node copy(Node lhs, Node rhs) { return new ShrNode(lhs,rhs); }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/SplitNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/SplitNode.java
new file mode 100644
index 0000000..9a980ec
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/SplitNode.java
@@ -0,0 +1,22 @@
+package com.compilerprogramming.ezlang.compiler.nodes;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.codegen.CodeGen;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONType;
+import java.util.BitSet;
+
+public abstract class SplitNode extends MachConcreteNode {
+ public final String _kind; // Kind of split
+ public final byte _round;
+ public SplitNode(String kind, byte round, Node[] nodes) { super(nodes); _kind = kind; _round = round; }
+ @Override public String op() { return "mov"; }
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return in(1)._print0(sb.append("mov("),visited).append(")");
+ }
+ @Override public SONType 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/StartNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/StartNode.java
new file mode 100644
index 0000000..ac9053f
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/StartNode.java
@@ -0,0 +1,57 @@
+package com.compilerprogramming.ezlang.compiler.nodes;
+
+import com.compilerprogramming.ezlang.compiler.codegen.CodeGen;
+import com.compilerprogramming.ezlang.compiler.Compiler;
+import com.compilerprogramming.ezlang.compiler.sontypes.*;
+
+import java.util.BitSet;
+
+import static com.compilerprogramming.ezlang.compiler.Utils.TODO;
+
+/**
+ * The Start node represents the start of the function.
+ *
+ * Start initially has 1 input (arg) from outside and the initial control.
+ * In ch10 we also add mem aliases as structs get defined; each field in struct
+ * adds a distinct alias to Start's tuple.
+ */
+public class StartNode extends LoopNode implements MultiNode {
+
+ final SONType _arg;
+
+ public StartNode(SONType arg) { super((Node)null); _arg = arg; _type = compute(); }
+ public StartNode(StartNode start) { super(start); _arg = start==null ? null : start._arg; }
+
+ @Override public String label() { return "Start"; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return sb.append(label());
+ }
+
+ @Override public boolean blockHead() { return true; }
+ @Override public CFGNode cfg0() { return null; }
+
+ // Get the one control following; error to call with more than one e.g. an
+ // IfNode or other multi-way branch. For Start, its "main"
+ @Override public CFGNode uctrl() {
+ // Find "main", its the start.
+ CFGNode C = null;
+ for( Node use : _outputs )
+ if( use instanceof FunNode fun && fun.sig().isa(CodeGen.CODE._main) )
+ { assert C==null; C = fun; }
+ return C;
+ }
+
+
+ @Override public SONTypeTuple compute() {
+ return SONTypeTuple.make(SONType.CONTROL, SONTypeMem.TOP,_arg);
+ }
+
+ @Override public Node idealize() { return null; }
+
+ // No immediate dominator, and idepth==0
+ @Override public int idepth() { return 0; }
+ @Override public CFGNode idom(Node dep) { return null; }
+
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/StopNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/StopNode.java
new file mode 100644
index 0000000..1d13a7f
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/StopNode.java
@@ -0,0 +1,63 @@
+package com.compilerprogramming.ezlang.compiler.nodes;
+
+import com.compilerprogramming.ezlang.compiler.sontypes.SONType;
+import java.util.BitSet;
+
+public class StopNode extends CFGNode {
+
+ public final String _src;
+
+ public StopNode(String src) {
+ super();
+ _src = src;
+ _type = compute();
+ }
+ public StopNode(StopNode stop) { super(stop); _src = stop==null ? null : stop._src; }
+
+ @Override
+ public String label() {
+ return "Stop";
+ }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ // For the sake of many old tests, and single value prints as "return val"
+ if( ret()!=null ) return ret()._print0(sb,visited);
+ sb.append("Stop[ ");
+ for( Node ret : _inputs )
+ ret._print0(sb, visited).append(" ");
+ return sb.append("]");
+ }
+
+ @Override public boolean blockHead() { return true; }
+
+
+ // If a single Return, return it.
+ // Otherwise, null because ambiguous.
+ public ReturnNode ret() {
+ return nIns()==1 && in(0) instanceof ReturnNode ret ? ret : null;
+ }
+
+ @Override
+ public SONType compute() {
+ return SONType.BOTTOM;
+ }
+
+ @Override
+ public Node idealize() {
+ int len = nIns();
+ for( int i=0; i> ").p(code.reg(in(2)));
+ }
+
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/AsrIARM.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/AsrIARM.java
new file mode 100644
index 0000000..05a508c
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/AsrIARM.java
@@ -0,0 +1,31 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.arm;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.codegen.CodeGen;
+import com.compilerprogramming.ezlang.compiler.codegen.Encoding;
+import com.compilerprogramming.ezlang.compiler.codegen.RegMask;
+import com.compilerprogramming.ezlang.compiler.nodes.MachConcreteNode;
+import com.compilerprogramming.ezlang.compiler.nodes.MachNode;
+import com.compilerprogramming.ezlang.compiler.nodes.Node;
+
+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.RMASK; }
+ @Override public void encoding( Encoding enc ) {
+ short rd = enc.reg(this);
+ short rn = enc.reg(in(1));
+ assert _imm > 0;
+ enc.add4(arm.imm_shift(0b1001001101,_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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/BranchARM.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/BranchARM.java
new file mode 100644
index 0000000..12ce1c2
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/BranchARM.java
@@ -0,0 +1,65 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.arm;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+// Jump on flags, uses flags
+public class BranchARM extends IfNode implements MachNode, RIPRelSize {
+ String _bop;
+ BranchARM(IfNode iff, String bop ) {
+ super(iff);
+ _bop = bop;
+ }
+ @Override public String op() { return "j"+_bop; }
+ @Override public String label() { return op(); }
+
+ @Override public void postSelect(CodeGen code) {
+ Node set = in(1);
+ 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 invert() { _bop = invert(_bop); }
+
+ // Encoding is appended into the byte array; size is returned
+ @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
+ enc.jump(this,cproj(0));
+ // B.cond
+ enc.add4( arm.b_cond(0b01010100, 0, arm.make_condition(_bop)) );
+ }
+
+ // Delta is from opcode start
+ @Override public byte encSize(int delta) {
+ 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 ) {
+ if( opLen==4 ) {
+ enc.patch4(opStart,arm.b_cond(0b01010100, delta, arm.make_condition(_bop)));
+ } else {
+ throw Utils.TODO();
+ }
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ String src = code.reg(in(1));
+ if( src!="flags" ) sb.p(src).p(" ");
+ CFGNode prj = cproj(0);
+ while( prj.nOuts() == 1 )
+ prj = prj.uctrl(); // Skip empty blocks
+ sb.p(label(prj));
+ }
+ @Override public String comment() { return "L"+cproj(1)._nid; }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/CallARM.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/CallARM.java
new file mode 100644
index 0000000..d16a25f
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/CallARM.java
@@ -0,0 +1,47 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.arm;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFunPtr;
+
+public class CallARM extends CallNode implements MachNode, RIPRelSize {
+ final SONTypeFunPtr _tfp;
+ final String _name;
+
+ CallARM(CallNode call, SONTypeFunPtr tfp) {
+ super(call);
+ _inputs.pop(); // Pop constant target
+ assert tfp.isConstant();
+ _tfp = tfp;
+ _name = CodeGen.CODE.link(tfp)._name;
+ }
+
+ @Override public String op() { return "call"; }
+ @Override public String label() { return op(); }
+ @Override public String name() { return _name; }
+ @Override public SONTypeFunPtr tfp() { return _tfp; }
+ @Override public RegMask regmap(int i) { return arm.callInMask(_tfp,i); }
+ @Override public RegMask outregmap() { return null; }
+
+ @Override public void encoding( Encoding enc ) {
+ enc.relo(this);
+ // BL
+ enc.add4(arm.b(0b100101,0)); // Target patched at link time
+ }
+
+ // Delta is from opcode start, but X86 measures from the end of the 5-byte encoding
+ @Override public byte encSize(int delta) { return 4; }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ enc.patch4(opStart,arm.b(0b100101,delta));
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(_name);
+ for( int i=0; i> i) & 0xFFFF;
+ if (block == 0) nb0++;
+ if (block == 0xFFFF) nb1++;
+ }
+ int pattern;
+ int op;
+ if(nb0 >= nb1) {
+ // More 0 blocks then F blocks, use movz
+ pattern = 0;
+ op = 0b110100101;
+ } else {
+ // More F blocks then 0 blocks, use movn
+ pattern = 0xFFFF;
+ op = 0b100100101;
+ }
+ int invert = pattern;
+ for (int i=0; i<4; i++) {
+ int block = (int)x & 0xFFFF;
+ x >>= 16;
+ if (block != pattern) {
+ enc.add4(arm.mov(op, i, block ^ invert, self));
+ op = 0b111100101;
+ invert = 0;
+ }
+ }
+ if (op != 0b111100101) {
+ // All blocks are the same, special case
+ enc.add4(arm.mov(op, 0, 0, self));
+ }
+ }
+
+ // Human-readable form appended to the SB. Things like the encoding,
+ // indentation, leading address or block labels not printed here.
+ // Just something like "ld4\tR17=[R18+12] // Load array base".
+ // General form: "op\tdst=src+src"
+ @Override public void asm(CodeGen code, SB sb) {
+ String reg = code.reg(this);
+ _con.print(sb.p(reg).p(" = #"));
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LoadARM.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LoadARM.java
new file mode 100644
index 0000000..6d11b2f
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LoadARM.java
@@ -0,0 +1,33 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.arm;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.LoadNode;
+import com.compilerprogramming.ezlang.compiler.nodes.Node;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFloat;
+
+// Load memory addressing on ARM
+// Support imm, reg(direct), or reg+off(indirect) addressing
+// Base = base - base pointer, offset is added to base
+// idx = null
+// off = off - offset added to base
+
+public class LoadARM extends MemOpARM{
+ LoadARM(LoadNode ld,Node base, Node idx, int off) {
+ super(ld, base, idx, off, 0);
+ }
+ @Override public String op() { return "ld"+_sz; }
+ @Override public RegMask outregmap() { return arm.MEM_MASK; }
+ // ldr(immediate - unsigned offset) | ldr(register)
+ @Override public void encoding( Encoding enc ) {
+ if(_declaredType == SONTypeFloat.F32 || _declaredType == SONTypeFloat.F64) {
+ ldst_encode(enc, 0b1111110101, 0b11111100011, this, true);
+ } else {
+ ldst_encode(enc, 0b1111100101, 0b11111000011, this, false);
+ }
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(",");
+ asm_address(code,sb);
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LslARM.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LslARM.java
new file mode 100644
index 0000000..0596fc2
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LslARM.java
@@ -0,0 +1,19 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.arm;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+// 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.RMASK; }
+
+ @Override public void encoding( Encoding enc ) { arm.shift_reg(enc,this,0b1000); }
+ @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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LslIARM.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LslIARM.java
new file mode 100644
index 0000000..9201580
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LslIARM.java
@@ -0,0 +1,33 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.arm;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.codegen.CodeGen;
+import com.compilerprogramming.ezlang.compiler.codegen.Encoding;
+import com.compilerprogramming.ezlang.compiler.codegen.RegMask;
+import com.compilerprogramming.ezlang.compiler.nodes.MachConcreteNode;
+import com.compilerprogramming.ezlang.compiler.nodes.MachNode;
+import com.compilerprogramming.ezlang.compiler.nodes.Node;
+
+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.RMASK; }
+ @Override public void encoding( Encoding enc ) {
+ short rd = enc.reg(this);
+ short rn = enc.reg(in(1));
+ assert _imm > 0;
+ // UBFM , , #(- MOD 64), #(63-)
+ // immr must be (- MOD 64) = 64 - shift
+ enc.add4(arm.imm_shift(0b1101001101, 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LsrARM.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LsrARM.java
new file mode 100644
index 0000000..afd84ba
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LsrARM.java
@@ -0,0 +1,19 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.arm;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+// 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.RMASK; }
+
+ @Override public void encoding( Encoding enc ) { arm.shift_reg(enc,this,0b1001); }
+ @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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LsrIARM.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LsrIARM.java
new file mode 100644
index 0000000..72629ff
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LsrIARM.java
@@ -0,0 +1,29 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.arm;
+
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+// 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.RMASK; }
+ @Override public void encoding( Encoding enc ) {
+ short rd = enc.reg(this);
+ short rn = enc.reg(in(1));
+ assert _imm > 0;
+ enc.add4(arm.imm_shift(0b1101001101,_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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/MemOpARM.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/MemOpARM.java
new file mode 100644
index 0000000..70dfdee
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/MemOpARM.java
@@ -0,0 +1,67 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.arm;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+import com.compilerprogramming.ezlang.compiler.sontypes.*;
+
+import java.lang.StringBuilder;
+import java.util.BitSet;
+
+
+public abstract class MemOpARM extends MemOpNode implements MachNode {
+ final int _off; // Limit 9 bits sized, or (13 bits< 0 ? "addi" : "ret";
+ }
+ // Correct Nodes outside the normal edges
+ @Override public void postSelect(CodeGen code) {
+ FunNode fun = (FunNode)rpc().in(0);
+ _fun = fun;
+ fun.setRet(this);
+ }
+ @Override public RegMask regmap(int i) { return arm.retMask(_fun.sig(),i); }
+ @Override public RegMask outregmap() { return null; }
+ // RET
+ @Override public void encoding( Encoding enc ) {
+ int frameAdjust = ((FunARM)fun())._frameAdjust;
+ if( frameAdjust > 0 )
+ enc.add4(arm.imm_inst(0b1001000100, (frameAdjust*-8)&0xFFF, arm.RSP, arm.RSP));
+ enc.add4(arm.ret(0b1101011001011111000000));
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ int frameAdjust = ((FunARM)fun())._frameAdjust;
+ if( frameAdjust>0 )
+ sb.p("rsp += #").p(frameAdjust*-8).p("\nret");
+ // Post code-gen, just print the "ret"
+ if( code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() )
+ // Prints return reg (either X0 or D0), RPC (always R30) and
+ // then the callee-save registers.
+ for( int i=2; i= arm.D_OFFSET;
+ boolean srcX = src >= arm.D_OFFSET;
+
+ if (dst >= arm.MAX_REG) {
+ // store to sp
+ if(src >= arm.MAX_REG) {
+ throw Utils.TODO();
+ }
+ int off = enc._fun.computeStackSlot(dst - arm.MAX_REG)*8;
+ enc.add4(arm.load_str_imm(0b1111100100, off, src, dst));
+ }
+
+ if(src >= arm.MAX_REG) {
+ // Load from SP
+ int off = enc._fun.computeStackSlot(src - arm.MAX_REG) * 8;
+ enc.add4(arm.load_str_imm(0b1111100101, off, src, dst));
+ }
+
+ // pick opcode based on regs
+ if(!dstX && !srcX) {
+ // GPR->GPR
+ enc.add4(arm.mov_reg(0b10101010000, src, dst));
+ } else if(dstX && srcX) {
+ // FPR->FPR
+ // fmov reg
+ enc.add4(arm.f_mov_reg(0b00011110, src,dst));
+ } else if(dstX && !srcX) {
+ // GPR->FPR
+ // FMOV(general) 64 bits to DOUBLE-PRECISION
+ enc.add4(arm.f_mov_general(0b10011110, 0b01, 0, 0b111, src, dst));
+ } else if(!dstX && srcX) {
+ //FPF->GPR
+ // FMOV(general) DOUBLE-PRECISION to 64 bits
+ enc.add4(arm.f_mov_general(0b10011110, 0b01, 0, 0b110, src, dst));
+ }
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/StoreARM.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/StoreARM.java
new file mode 100644
index 0000000..69fc9f1
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/StoreARM.java
@@ -0,0 +1,37 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.arm;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.StoreNode;
+import com.compilerprogramming.ezlang.compiler.nodes.Node;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFloat;
+
+// 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; }
+ @Override public void encoding( Encoding enc ) {
+ if(_declaredType == SONTypeFloat.F32 || _declaredType == SONTypeFloat.F64) {
+ ldst_encode(enc, 0b1111110100,0b11111100001, val(), true);
+ } else {
+ ldst_encode(enc, 0b1111100100, 0b11111000001, val(), false);
+ }
+ }
+ @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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/SubARM.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/SubARM.java
new file mode 100644
index 0000000..847a0b4
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/SubARM.java
@@ -0,0 +1,19 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.arm;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+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.RMASK; }
+
+ @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,0b11001011); }
+
+ // 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/SubFARM.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/SubFARM.java
new file mode 100644
index 0000000..a1326f2
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/SubFARM.java
@@ -0,0 +1,18 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.arm;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+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,0b1110); }
+ // 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/SubIARM.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/SubIARM.java
new file mode 100644
index 0000000..2ada6af
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/SubIARM.java
@@ -0,0 +1,27 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.arm;
+
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.MachConcreteNode;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeInteger;
+
+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.RMASK; }
+
+ @Override public void encoding( Encoding enc ) { arm.imm_inst(enc,this,0b1101000100,_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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/TFPARM.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/TFPARM.java
new file mode 100644
index 0000000..1be5a4e
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/TFPARM.java
@@ -0,0 +1,52 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.arm;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.ConstantNode;
+import com.compilerprogramming.ezlang.compiler.nodes.MachNode;
+import com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv.riscv;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFunPtr;
+
+public class TFPARM extends ConstantNode implements MachNode, RIPRelSize {
+ TFPARM( ConstantNode con ) { super(con); }
+ @Override public String op() { return "ldx"; }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public boolean isClone() { return true; }
+ @Override public TFPARM copy() { return new TFPARM(this); }
+ @Override public void encoding( Encoding enc ) {
+ enc.relo(this);
+ short self = enc.reg(this);
+ // adrp x0, 0
+ int adrp = arm.adrp(1,0, 0b10000, 0,self);
+ // add x0, x0, 0
+ arm.imm_inst(enc,this,0b1001000100,0);
+ enc.add4(adrp);
+ }
+
+ @Override public byte encSize(int delta) {
+ if( -(1L<<12) <= delta && delta < (1L<<12) ) return 4;
+ throw Utils.TODO();
+ }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ short rpc = enc.reg(this);
+ if(opLen ==4 ) {
+ // opstart of add
+ int next = opStart + opLen;
+ int adrp_delta = delta >> 12;
+ // patch upper 20 bits via adrp
+ enc.patch4(opStart, arm.adrp(1, adrp_delta & 0b11, 0b10000, adrp_delta >> 2, rpc));
+ // low 12 bits via add
+ enc.patch4(next, arm.imm_inst_l(enc, this, 0b1001000100, delta & 0xfff));
+ } else {
+ throw Utils.TODO();
+ }
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ String reg = code.reg(this);
+ _con.print(sb.p(reg).p(" #"));
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/UJmpARM.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/UJmpARM.java
new file mode 100644
index 0000000..fa566b5
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/UJmpARM.java
@@ -0,0 +1,48 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.arm;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONType;
+import java.util.BitSet;
+
+// unconditional jump
+public class UJmpARM extends CFGNode implements MachNode, RIPRelSize {
+ UJmpARM() { }
+ @Override public String op() { return "jmp"; }
+ @Override public String label() { return op(); }
+ @Override public StringBuilder _print1( StringBuilder sb, BitSet visited ) {
+ return sb.append("jmp ");
+ }
+ @Override public RegMask regmap(int i) {return null; }
+ @Override public RegMask outregmap() { return null; }
+ @Override public SONType compute() { throw Utils.TODO(); }
+ @Override public Node idealize() { throw Utils.TODO(); }
+ @Override public void encoding( Encoding enc ) {
+ enc.jump(this,uctrl());
+ int body = arm.b(0b01010100, 0);
+ enc.add4(body);
+ }
+
+ // Delta is from opcode start
+ @Override public byte encSize(int delta) {
+ if( -(1<<26) <= delta && delta < (1<<26) ) return 4;
+ // 2 word encoding needs a tmp register, must teach RA
+ throw Utils.TODO();
+ }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ if( opLen==4 ) {
+ enc.patch4(opStart,arm.b(0b01010100, 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/XorARM.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/XorARM.java
new file mode 100644
index 0000000..ac9e38b
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/XorARM.java
@@ -0,0 +1,19 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.arm;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+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.RMASK; }
+ @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,0b11001010); }
+ // 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/XorIARM.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/XorIARM.java
new file mode 100644
index 0000000..2dc314a
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/XorIARM.java
@@ -0,0 +1,27 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.arm;
+
+import com.compilerprogramming.ezlang.compiler.nodes.MachConcreteNode;
+import com.compilerprogramming.ezlang.compiler.nodes.MachNode;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+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.RMASK; }
+
+ // General form: "xori rd = rs1 ^ imm"
+ @Override public void encoding( Encoding enc ) { arm.imm_inst_n(enc,this,0b110100100,_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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/arm.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/arm.java
new file mode 100644
index 0000000..4f01b8d
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/arm.java
@@ -0,0 +1,662 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.arm;
+
+import com.compilerprogramming.ezlang.compiler.Utils;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+import com.compilerprogramming.ezlang.compiler.sontypes.*;
+import java.io.ByteArrayOutputStream;
+
+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)
+ static final int X0 = 0, X1 = 1, X2 = 2, X3 = 3, X4 = 4, X5 = 5, X6 = 6, X7 = 7;
+ static final int X8 = 8, X9 = 9, X10 = 10, X11 = 11, X12 = 12, X13 = 13, X14 = 14, X15 = 15;
+ static final int X16 = 16, X17 = 17, X18 = 18, X19 = 19, X20 = 20, X21 = 21, X22 = 22, X23 = 23;
+ static final int X24 = 24, X25 = 25, X26 = 26, X27 = 27, X28 = 28, X29 = 29, X30 = 30, RSP = 31;
+
+ // Floating point registers
+ static final int D0 = 32, D1 = 33, D2 = 34, D3 = 35, D4 = 36, D5 = 37, D6 = 38, D7 = 39;
+ static final int D8 = 40, D9 = 41, D10 = 42, D11 = 43, D12 = 44, D13 = 45, D14 = 46, D15 = 47;
+ static final int D16 = 48, D17 = 49, D18 = 50, D19 = 51, D20 = 52, D21 = 53, D22 = 54, D23 = 55;
+ static final int D24 = 56, D25 = 57, D26 = 58, D27 = 59, D28 = 60, D29 = 61, D30 = 62, D31 = 63;
+
+ static final int MAX_REG = 64;
+ static final int D_OFFSET = 31;
+ static final int FLAGS = 64;
+
+ static final String[] REGS = new String[] {
+ "X0", "X1", "X2", "X3", "X4", "X5", "X6", "X7",
+ "X8", "X9", "X10", "X11", "X12", "X13", "X14", "X15",
+ "X16", "X17", "X18", "X19", "X20", "X21", "X22", "X23",
+ "X24", "X25", "X26", "X27", "X28", "X29", "RPC", "RSP",
+ "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7",
+ "D8", "D9", "D10", "D11", "D12", "D13", "D14", "D15",
+ "D16", "D17", "D18", "D19", "D20", "D21", "D22", "D23",
+ "D24", "D25", "D26", "D27", "D28", "D29", "D30", "D31",
+ "flags"
+ };
+ @Override public String reg( int reg ) {
+ return reg < REGS.length ? REGS[reg] : "[rsp+"+(reg-REGS.length)*8+"]";
+ }
+ // Stack slots, in units of 8 bytes.
+ @Override public int stackSlot( int reg ) {
+ return reg < REGS.length ? -1 : reg-REGS.length;
+ }
+
+ // 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< "!=";
+ case "!=" -> "==";
+ case ">" -> "<=";
+ case "<" -> ">=";
+ case ">=" -> "<";
+ case "<=" -> ">";
+ default -> throw new IllegalStateException("Unexpected value: " + op);
+ };
+ }
+ public enum COND {
+ EQ,
+ NE,
+ CS,
+ CC,
+ MI,
+ PL,
+ VS,
+ VC,
+ HI,
+ LS,
+ GE,
+ LT,
+ GT,
+ LE,
+ AL,
+ NV
+ }
+
+ // True if signed 9-bit immediate
+ private static boolean imm9(SONTypeInteger ti) {
+ // 55 = 64-9
+ return ti.isConstant() && ((ti.value()<<55)>>55) == ti.value();
+ }
+ // True if signed 12-bit immediate
+ private static boolean imm12(SONTypeInteger 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(SONTypeInteger ti) {
+ if( !ti.isConstant() ) return -1;
+ if( !ti.isConstant() ) return -1;
+ long val = ti.value();
+ if (val == 0 || val == -1) return -1; // Special cases are not allowed
+ int immr = 0;
+ // Rotate until we have 0[...]1
+ while (val < 0 || (val & 1)==0) {
+ val = (val >>> 63) | (val << 1);
+ immr++;
+ }
+ int size = 32;
+ long pattern = val;
+ // Is upper half of pattern the same as the lower?
+ while ((pattern & ((1L<> size)) {
+ // Then only take one half
+ pattern >>= size;
+ size >>= 1;
+ }
+ size <<= 1;
+ int imms = Long.bitCount(pattern);
+ // Pattern should now be zeros followed by ones 0000011111
+ if (pattern != (1L<=0 && imm12 >= 0 && rn >=0 && rd>=0; // Caller zeros high order bits
+ return (opcode << 22) | (imm12 << 10) | (rn << 5) | rd;
+ }
+
+ public static int imm_shift(int opcode, int imm, int imms, int rn, int rd) {
+ return (opcode << 22) | (1 << 22) | (imm << 16) | (imms << 10) | (rn << 5) | rd;
+ }
+
+ public static void imm_inst(Encoding enc, Node n, int opcode, int imm12) {
+ short self = enc.reg(n);
+ short reg1 = enc.reg(n.in(1));
+ int body = imm_inst(opcode, imm12&0xFFF, reg1, self);
+ enc.add4(body);
+ }
+
+ public static void imm_inst_n(Encoding enc, Node n, int opcode, int imm13) {
+ short self = enc.reg(n);
+ short reg1 = enc.reg(n.in(1));
+
+ int body = imm_inst_n(opcode, imm13, reg1, self);
+ enc.add4(body);
+ }
+
+ // nth bit comes from immediate and not opcode
+ public static int imm_inst_n(int opcode, int imm13, int rn, int rd) {
+ assert 0 <= imm13 && imm13 <= 0x1FFF;
+ return (opcode << 23) | (imm13 << 10) | (rn << 5) | rd;
+ }
+
+ public static int imm_inst_l(Encoding enc, Node n, int opcode, int imm12) {
+ short self = enc.reg(n);
+ short reg1 = enc.reg(n.in(1));
+ int body = imm_inst(opcode, imm12&0xFFF, reg1, self);
+ return body;
+ }
+
+ // for normal add, reg1, reg2 cases (reg-to-reg)
+ // using shifted-reg form
+ public static int r_reg(int opcode, int shift, int rm, int imm6, int rn, int rd) {
+ return (opcode << 24) | (shift << 21) | (rm << 16) | (imm6 << 10) << (rn << 5) | rd;
+ }
+ public static void r_reg(Encoding enc, Node n, int opcode) {
+ short self = enc.reg(n);
+ short reg1 = enc.reg(n.in(1));
+ short reg2 = enc.reg(n.in(2));
+ int body = r_reg(opcode, 0, reg2, 0, reg1, self);
+ enc.add4(body);
+ }
+
+ public static int shift_reg(int opcode, int rm, int op2, int rn, int rd) {
+ return (opcode << 21) | (rm << 16) | (op2 << 10) | (rn << 5) | rd;
+ }
+ public static void shift_reg(Encoding enc, Node n, int op2) {
+ short self = enc.reg(n);
+ short reg1 = enc.reg(n.in(1));
+ short reg2 = enc.reg(n.in(2));
+ int body = shift_reg(0b10011010110, reg2, op2, reg1, self);
+ enc.add4(body);
+ }
+
+ // MUL can be considered an alias for MADD with the third operand Ra being set to 0
+ public static int madd(int opcode, int rm, int ra, int rn, int rd) {
+ return (opcode << 21) | (rm << 16) | (ra << 10) | (rn << 5) | rd;
+ }
+ public static void madd(Encoding enc, Node n, int opcode, int ra) {
+ short self = enc.reg(n);
+ short reg1 = enc.reg(n.in(1));
+ short reg2 = enc.reg(n.in(2));
+ int body = madd(opcode, reg2, ra, reg1, self);
+ enc.add4(body);
+ }
+
+ // encodes movk, movn, and movz
+ public static int mov(int opcode, int shift, int imm16, int rd) {
+ return (opcode << 23) | (shift << 21) | (imm16 << 5) | rd;
+ }
+
+ public static int mov_reg(int opcode, int src, int dst) {
+ return (opcode << 21) | (src << 16) | 0b11111 << 5 | dst;
+ }
+
+ public static int ret(int opcode) {
+ return (opcode << 10);
+ }
+
+ // FMOV (scalar, immediate)
+ public static int f_mov(int opcode, int ftype, int imm8, int rd) {
+ return (opcode << 24) | (ftype << 21) |(imm8 << 13) | (128 << 5) | rd;
+ }
+
+ public static int f_scalar(int opcode, int ftype, int rm, int op, int rn, int rd) {
+ return (opcode << 24) | (ftype << 22) | (1 << 21) | (rm << 16) | (op << 10) | (rn << 5) | rd;
+ }
+
+ public static int f_mov_reg(int opcode, int rn, int rd) {
+ return (opcode << 24) | (0b01100000010000 << 10) | (rn << 5) | rd;
+ }
+ public static int f_mov_general(int opcode, int ftype, int rmode, int opcode1, int rn, int rd) {
+ return (opcode << 24) |(ftype << 22) | (1 << 21) | (rmode << 19) | (opcode1 << 16) | (rn << 5) | rd;
+ }
+ public static void f_scalar(Encoding enc, Node n, int op ) {
+ short self = (short)(enc.reg(n) -D_OFFSET);
+ short reg1 = (short)(enc.reg(n.in(1))-D_OFFSET);
+ short reg2 = (short)(enc.reg(n.in(2))-D_OFFSET);
+ int body = f_scalar(0b00011110, 1, reg2, op, reg1, self);
+ enc.add4(body);
+ }
+
+ // share same encoding with LDR (literal, SIMD&FP) flavour
+ public static int load_pc(int opcode, int offset, int rt) {
+ return (opcode << 24) | (offset << 5) | rt;
+ }
+ // int l
+ public static int adrp(int op, int imlo,int opcode, int imhi, int rd) {
+ return (op << 31) | (imlo << 29) |(opcode << 24) | (imhi << 5) | rd;
+ }
+
+ public static int load_adr(int opcode, int offset, int base, int rt) {
+ return (opcode << 22) | (offset << 10) | (base << 5) | rt;
+ }
+
+ // [Rptr+Roff]
+ public static int indr_adr(int opcode, int off, STORE_LOAD_OPTION option, int s, int ptr, int rt) {
+ return (opcode << 21) | (off << 16) | (option.ordinal() << 13) | (s << 12) | (2 << 10) | (ptr << 5) | rt;
+ }
+ // [Rptr+imm9]
+ public static int load_str_imm(int opcode, int imm12, int ptr, int rt) {
+ return (opcode << 22) | (imm12 << 10) |(ptr << 5) | rt;
+ }
+
+ // encoding for vcvt, size is encoded in operand
+ // ,
+ // F32.S32
+ //encoded as op = 0b00, size = 0b10.
+ // VCVT.. ,
+ // opcode is broken down into 4 pieces
+ public static int f_convert(int opcode_1, int opcode_2, int opcode_3, int opcode_4, int vd, int vm) {
+ return (opcode_1 << 28) | (opcode_2 << 24) | (opcode_3 << 20) | (opcode_4 << 16) |
+ (vd << 12) | (0x01100010 << 4) | vm;
+ }
+ public static int float_cast(int opcode, int ftype, int rn, int rd) {
+ return (opcode << 24) | (ftype << 22) | (2176 << 10) | (rn << 5) | rd;
+ }
+
+
+ // ftype = 3
+ public static int f_cmp(int opcode, int ftype, int rm, int rn) {
+ return (opcode << 24) | (ftype << 21) | (rm << 16) | (8 << 10) | (rn << 5) | 8;
+ }
+ public static void f_cmp(Encoding enc, Node n) {
+ short reg1 = (short)(enc.reg(n.in(1))-D_OFFSET);
+ short reg2 = (short)(enc.reg(n.in(2))-D_OFFSET);
+ int body = f_cmp(0b00011110, 3, reg1, reg2);
+ enc.add4(body);
+ }
+
+ public static COND make_condition(String bop) {
+ return switch (bop) {
+ case "==" -> COND.EQ;
+ case "!=" -> COND.NE;
+ case "<" -> COND.LE;
+ case "<=" -> COND.LT;
+ case ">=" -> COND.GT;
+ case ">" -> COND.GE;
+ default -> throw Utils.TODO();
+ };
+ }
+ // Todo: maybe missing zero here after delta << 5
+ public static int b_cond(int opcode, int delta, COND cond) {
+ // 24-5 == 19bits offset range
+ assert -(1<<19) <= delta && delta < (1<<19);
+ delta &= (1L<<19)-1; // Zero extend
+ return (opcode << 24) | (delta << 5) | cond.ordinal();
+ }
+
+ public static int cond_set(int opcode, int rm, COND cond, int rn, int rd) {
+ return (opcode << 21) | (rm << 16) | (cond.ordinal() << 12) | (rn << 5) | rd;
+ }
+
+ // Branch with Link to Register calls a subroutine at an address in a register, setting register X30 to PC+4.
+ public static int blr(int opcode, int rd) {
+ return opcode << 10 | rd << 5;
+ }
+ public static int b(int opcode, int delta) {
+ assert -(1<<26) <= delta && delta < (1<<26);
+ delta &= (1L<<26)-1; // Zero extend
+ return (opcode << 26) | delta;
+ }
+
+ static RegMask callInMask(SONTypeFunPtr tfp, int idx ) {
+ if( idx==0 ) return RPC_MASK;
+ if( idx==1 ) return null;
+ // Count floats in signature up to index
+ int fcnt=0;
+ for( int i=2; i= XMMS.length )
+ throw Utils.TODO();
+ RegMask[] cargs = CALLINMASK;
+ if( tfp.nargs()-fcnt >= cargs.length )
+ throw Utils.TODO();
+ return 0; // No stack args
+ }
+
+ static final long CALLEE_SAVE =
+ 1L< new AddFARM(addf);
+ case AddNode add -> add(add);
+ case AndNode and -> and(and);
+ case BoolNode bool -> cmp(bool);
+ case CallNode call -> call(call);
+ case CastNode cast -> new CastNode(cast);
+ case CallEndNode cend -> new CallEndARM(cend);
+ case CProjNode c -> new CProjNode(c);
+ case ConstantNode con -> con(con);
+ case DivFNode divf -> new DivFARM(divf);
+ case DivNode div -> new DivARM(div);
+ case FunNode fun -> new FunARM(fun);
+ case IfNode iff -> jmp(iff);
+ case LoadNode ld -> ld(ld);
+ case MemMergeNode mem -> new MemMergeNode(mem);
+ case MulFNode mulf -> new MulFARM(mulf);
+ case MulNode mul -> new MulARM(mul);
+ case NewNode nnn -> new NewARM(nnn);
+ case NotNode not -> new NotARM(not);
+ case OrNode or -> or(or);
+ case ParmNode parm -> new ParmARM(parm);
+ case PhiNode phi -> new PhiNode(phi);
+ case ProjNode prj -> new ProjARM(prj);
+ case ReadOnlyNode read -> new ReadOnlyNode(read);
+ case ReturnNode ret -> new RetARM(ret,ret.fun());
+ case SarNode sar -> asr(sar);
+ case ShlNode shl -> lsl(shl);
+ case ShrNode shr -> lsr(shr);
+ case StartNode start -> new StartNode(start);
+ case StopNode stop -> new StopNode(stop);
+ case StoreNode st -> st(st);
+ case SubFNode subf -> new SubFARM(subf);
+ case SubNode sub -> sub(sub);
+ case ToFloatNode tfn-> new I2F8ARM(tfn);
+ case XorNode xor -> xor(xor);
+
+ case LoopNode loop -> new LoopNode(loop);
+ case RegionNode region-> new RegionNode(region);
+ default -> throw Utils.TODO();
+ };
+ }
+
+ private Node cmp(BoolNode bool){
+ Node cmp = _cmp(bool);
+ return new SetARM(cmp, invert(bool.op()));
+ }
+ private Node _cmp(BoolNode bool) {
+ if( bool instanceof BoolNode.EQF ||
+ bool instanceof BoolNode.LTF ||
+ bool instanceof BoolNode.LEF )
+ return new CmpFARM(bool);
+ return bool.in(2) instanceof ConstantNode con && con._con instanceof SONTypeInteger 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
+ if( !(iff.in(1) instanceof BoolNode) )
+ iff.setDef(1,new BoolNode.EQ(iff.in(1),new ConstantNode(SONTypeInteger.ZERO)));
+ return new BranchARM(iff, invert(((BoolNode)iff.in(1)).op()));
+ }
+
+ private Node add(AddNode add) {
+ return add.in(2) instanceof ConstantNode off && off._con instanceof SONTypeInteger 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 SONTypeInteger ti && imm12(ti)
+ ? new SubIARM(sub, (int)ti.value())
+ : new SubARM(sub);
+ }
+
+ private Node con(ConstantNode con) {
+ if( !con._con.isConstant() ) return new ConstantNode( con ); // Default unknown caller inputs
+ return switch( con._con ) {
+ case SONTypeInteger ti -> new IntARM(con);
+ case SONTypeFloat tf -> new FloatARM(con);
+ case SONTypeFunPtr tfp -> new TFPARM(con);
+ case SONTypeMemPtr tmp -> new ConstantNode(con);
+ case SONTypeNil tn -> throw Utils.TODO();
+ // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code.
+ case SONType t -> new ConstantNode(con);
+ };
+ }
+
+ private Node call(CallNode call){
+ return call.fptr() instanceof ConstantNode con && con._con instanceof SONTypeFunPtr tfp
+ ? new CallARM(call, tfp)
+ : new CallRRARM(call);
+ }
+
+ private Node or(OrNode or) {
+ int imm12;
+ return or.in(2) instanceof ConstantNode off && off._con instanceof SONTypeInteger 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 SONTypeInteger 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 SONTypeInteger 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 SONTypeInteger 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 SONTypeInteger 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 SONTypeInteger ti && ti.value() >= 0 && ti.value() < 63
+ ? new LsrIARM(lsr, (int)ti.value())
+ : new LsrARM(lsr);
+ }
+
+
+ private static int off;
+ private static Node idx;
+ private Node st(StoreNode st) {
+ Node xval = st.val() instanceof ConstantNode con && con._con == SONTypeInteger.ZERO ? null : st.val();
+ return new StoreARM(address(st),st.ptr(),idx,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 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 SONTypeMemPtr; // Base ptr always, not some derived
+ if( mop.off() instanceof ConstantNode con && con._con instanceof SONTypeInteger ti && imm9(ti) ) {
+ off = (int)ti.value();
+ assert off == ti.value(); // In 32-bit range
+ } else {
+ idx = mop.off();
+ }
+ return mop;
+ }
+
+
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AUIPC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AUIPC.java
new file mode 100644
index 0000000..5a848a1
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AUIPC.java
@@ -0,0 +1,41 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.ConstantNode;
+import com.compilerprogramming.ezlang.compiler.nodes.MachNode;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFunPtr;
+
+// Add upper 20bits to PC. Immediate comes from the relocation info.
+public class AUIPC extends ConstantNode implements MachNode, RIPRelSize {
+ AUIPC( SONTypeFunPtr tfp ) { super(tfp); }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public boolean isClone() { return true; }
+ @Override public AUIPC copy() { return new AUIPC((SONTypeFunPtr)_con); }
+ @Override public String op() { return "auipc"; }
+ @Override public void encoding( Encoding enc ) {
+ enc.relo(this);
+ short dst = enc.reg(this);
+ enc.add4(riscv.u_type(0x17, dst, 0));
+ }
+
+ // Delta is from opcode start, but X86 measures from the end of the 5-byte encoding
+ @Override public byte encSize(int delta) { return 4; }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ //short rpc = enc.reg(this);
+ //// High half is where the TFP constant used to be, the last input
+ //short auipc = enc.reg(in(_inputs._len-1));
+ //enc.patch4(opStart,riscv.i_type(0x67, rpc, 0, auipc, delta));
+ throw Utils.TODO();
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ String reg = code.reg(this);
+ sb.p(reg).p(" = PC+#");
+ if( _con == null ) sb.p("---");
+ else _con.print(sb);
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AddFRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AddFRISC.java
new file mode 100644
index 0000000..7e9058a
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AddFRISC.java
@@ -0,0 +1,17 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+// 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AddIRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AddIRISC.java
new file mode 100644
index 0000000..1de9018
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AddIRISC.java
@@ -0,0 +1,21 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv;
+
+import com.compilerprogramming.ezlang.compiler.Utils;
+import com.compilerprogramming.ezlang.compiler.nodes.Node;
+
+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.I_TYPE; }
+ @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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AddRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AddRISC.java
new file mode 100644
index 0000000..4b163a2
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AddRISC.java
@@ -0,0 +1,21 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AndIRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AndIRISC.java
new file mode 100644
index 0000000..b37f960
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AndIRISC.java
@@ -0,0 +1,12 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv;
+
+import com.compilerprogramming.ezlang.compiler.Utils;
+import com.compilerprogramming.ezlang.compiler.nodes.Node;
+
+public class AndIRISC extends ImmRISC {
+ AndIRISC( Node and, int imm) { super(and,imm); }
+ @Override public String op() { return "andi"; }
+ @Override public String glabel() { return "&"; }
+ @Override int opcode() { return riscv.I_TYPE; }
+ @Override int func3() {return 7;}
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AndRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AndRISC.java
new file mode 100644
index 0000000..01677f0
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AndRISC.java
@@ -0,0 +1,16 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+public class AndRISC extends MachConcreteNode implements MachNode {
+ 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,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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/BranchRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/BranchRISC.java
new file mode 100644
index 0000000..a6bfb29
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/BranchRISC.java
@@ -0,0 +1,76 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+import java.util.BitSet;
+
+// Conditional branch such as: BEQ
+public class BranchRISC extends IfNode implements MachNode, RIPRelSize {
+ String _bop;
+ // label is obtained implicitly
+ public BranchRISC( IfNode iff, String bop, Node n1, Node n2 ) {
+ super(iff);
+ _bop = bop;
+ _inputs.setX(1,n1);
+ _inputs.setX(2,n2);
+ }
+
+ @Override public String op() { return "b"+_bop; }
+ @Override public String label() { return op(); }
+ @Override public String comment() { return "L"+cproj(1)._nid; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return null; }
+ @Override public void invert() {
+ if( _bop.equals("<") || _bop.equals("<=") )
+ swap12(); // Cannot invert the test, so swap the operands
+ else
+ _bop = invert(_bop);
+ }
+
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("if( ");
+ if( in(1)==null ) sb.append("0");
+ else in(1)._print0(sb,visited);
+ sb.append(_bop);
+ if( in(2)==null ) sb.append("0");
+ else in(2)._print0(sb,visited);
+ return sb.append(" )");
+ }
+
+ @Override public void encoding( Encoding enc ) {
+ enc.jump(this,cproj(0));
+ // 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( -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 ) {
+ 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) {
+ 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);
+ while( prj.nOuts() == 1 )
+ prj = prj.uctrl(); // Skip empty blocks
+ sb.p(label(prj));
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/CallEndRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/CallEndRISC.java
new file mode 100644
index 0000000..32cbb5b
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/CallEndRISC.java
@@ -0,0 +1,23 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.CallEndNode;
+import com.compilerprogramming.ezlang.compiler.nodes.MachNode;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFunPtr;
+
+public class CallEndRISC extends CallEndNode implements MachNode {
+ final SONTypeFunPtr _tfp;
+ CallEndRISC( CallEndNode cend ) {
+ super(cend);
+ _tfp = (SONTypeFunPtr)(cend.call().fptr()._type);
+ }
+ @Override public String op() { return "cend"; }
+ @Override public String label() { return op(); }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return null; }
+ @Override public RegMask outregmap(int idx) { return idx == 2 ? riscv.retMask(_tfp,2) : null; }
+ @Override public RegMask killmap() { return riscv.riscCallerSave(); }
+ @Override public void encoding( Encoding enc ) { }
+ @Override public void asm(CodeGen code, SB sb) { }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/CallRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/CallRISC.java
new file mode 100644
index 0000000..67ff05d
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/CallRISC.java
@@ -0,0 +1,62 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFunPtr;
+
+public class CallRISC extends CallNode implements MachNode, RIPRelSize {
+ final SONTypeFunPtr _tfp;
+ final String _name;
+ CallRISC( CallNode call, SONTypeFunPtr tfp ) {
+ super(call);
+ assert tfp.isConstant();
+ _inputs.pop(); // Pop constant target
+ _tfp = tfp;
+ _name = CodeGen.CODE.link(tfp)._name;
+ }
+
+ @Override public String op() { return "call"; }
+ @Override public String label() { return op(); }
+ @Override public RegMask regmap(int i) {
+ // Last call input is AUIPC
+ if( i == nIns()-1 ) return riscv.RMASK;
+ return riscv.callInMask(_tfp,i);
+ }
+ @Override public RegMask outregmap() { return riscv.RPC_MASK; }
+ @Override public String name() { return _name; }
+ @Override public SONTypeFunPtr tfp() { return _tfp; }
+
+ @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.relo(this);
+ short rpc = enc.reg(this);
+ enc.add4(riscv.j_type(riscv.J_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.J_JAL, rpc, delta));
+ } else {
+ throw Utils.TODO();
+ }
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(_name).p(" ");
+ for( int i=0; i>20; // Sign extend 12 bits
+ in(1)._print0(sb.append("( "), visited);
+ return sb.append(String.format(" %s #%d )",glabel(),imm12));
+ }
+
+ abstract int opcode();
+ abstract int func3();
+
+ @Override public void encoding( Encoding enc ) {
+ short dst = enc.reg(this );
+ short src = enc.reg(in(1));
+ int body = riscv.i_type(opcode(), dst, func3(), src, _imm12 & 0xFFF);
+ enc.add4(body);
+ }
+
+ // 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)(((SONTypeInteger)lui._con).value()) + imm12);
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/IntRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/IntRISC.java
new file mode 100644
index 0000000..635155e
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/IntRISC.java
@@ -0,0 +1,29 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.ConstantNode;
+import com.compilerprogramming.ezlang.compiler.nodes.MachNode;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeInteger;
+
+// 12-bit integer constant. Larger constants are made up in the instruction
+// selection by adding with a LUI.
+public class IntRISC extends ConstantNode implements MachNode {
+ public IntRISC(ConstantNode con) { super(con); }
+ @Override public String op() { return "ldi"; }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public boolean isClone() { return true; }
+ @Override public IntRISC copy() { return new IntRISC(this); }
+ @Override public void encoding( Encoding enc ) {
+ short dst = enc.reg(this);
+ SONTypeInteger ti = (SONTypeInteger)_con;
+ // Explicit truncation of larger immediates; this will sign-extend on
+ // load and this is handled during instruction selection.
+ enc.add4(riscv.i_type(riscv.I_TYPE, dst, 0, riscv.ZERO, (int)(ti.value() & 0xFFF)));
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ String reg = code.reg(this);
+ _con.print(sb.p(reg).p(" = #"));
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/LUI.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/LUI.java
new file mode 100644
index 0000000..a85eba9
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/LUI.java
@@ -0,0 +1,32 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.ConstantNode;
+import com.compilerprogramming.ezlang.compiler.nodes.MachNode;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeInteger;
+
+// Load upper 20bits.
+public class LUI extends ConstantNode implements MachNode {
+ public LUI( int imm20 ) {
+ super(SONTypeInteger.constant(imm20));
+ assert riscv.imm20Exact((SONTypeInteger)_con);
+ }
+ @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)((SONTypeInteger)_con).value()); }
+ @Override public String op() { return "lui"; }
+ @Override public void encoding( Encoding enc ) {
+ long x = ((SONTypeInteger)_con).value();
+ int imm20 = (int)(x>>12) & 0xFFFFF;
+ short dst = enc.reg(this);
+ int lui = riscv.u_type(0x17, dst, imm20);
+ enc.add4(lui);
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ String reg = code.reg(this);
+ sb.p(reg).p(" = #").hex4((int)(((SONTypeInteger)_con).value()));
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/LoadRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/LoadRISC.java
new file mode 100644
index 0000000..d3e6479
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/LoadRISC.java
@@ -0,0 +1,28 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+// 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 {
+ 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 = xreg(enc);
+ short ptr = enc.reg(ptr());
+ int body = riscv.i_type(opcode(enc), dst, func3(), ptr, _off);
+ enc.add4(body);
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(",");
+ asm_address(code,sb);
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/MemOpRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/MemOpRISC.java
new file mode 100644
index 0000000..23988e6
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/MemOpRISC.java
@@ -0,0 +1,76 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.Utils;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+import com.compilerprogramming.ezlang.compiler.sontypes.*;
+import java.util.BitSet;
+
+public abstract class MemOpRISC extends MemOpNode implements MachNode {
+ final int _off; // Limit 12 bits
+ final char _sz = (char)('0'+(1<<_declaredType.log_size()));
+ MemOpRISC(MemOpNode mop, Node base, int off, Node val) {
+ super(mop,mop);
+ assert base._type instanceof SONTypeMemPtr;
+ _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;
+ }
+
+ @Override public String label() { return op(); }
+ Node val() { return in(4); } // Only for stores
+
+ @Override public StringBuilder _printMach(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); }
+
+ @Override public SONType compute() { throw Utils.TODO(); }
+ @Override public Node idealize() { throw Utils.TODO(); }
+
+ // func3 is based on load/store size and extend
+ int func3() {
+ int func3 = -1;
+ if( _declaredType == SONTypeInteger. I8 ) func3=0; // LB SB
+ if( _declaredType == SONTypeInteger.I16 ) func3=1; // LH SH
+ if( _declaredType == SONTypeInteger.I32 ) func3=2; // LW SW
+ if( _declaredType == SONTypeInteger.BOT ) func3=3; // LD SD
+ if( _declaredType == SONTypeInteger. U8 ) func3=4; // LBU
+ if( _declaredType == SONTypeInteger.BOOL) func3=4; // LBU
+ if( _declaredType == SONTypeInteger.U16 ) func3=5; // LHU
+ if( _declaredType == SONTypeInteger.U32 ) func3=6; // LWU
+ // float
+ if(_declaredType == SONTypeFloat.F32) func3 = 2; // fLW fSW
+ if(_declaredType == SONTypeFloat.F64) func3 = 3; // fLD fSD
+ if( _declaredType instanceof SONTypeMemPtr ) func3=6; // 4 byte pointers, assumed unsigned?
+ if( func3 == -1 ) throw Utils.TODO();
+ return func3;
+ }
+
+ // 7 bits, 00 000 11 or 00 001 11 for FP
+ int opcode(Encoding enc) {
+ if( enc.reg(this) < riscv.F_OFFSET ) return 3;
+ else if( this instanceof StoreRISC )
+ // opcode is the same for 32 bit and 64 bits
+ return 39;
+ return 7;
+ }
+ short xreg(Encoding enc) {
+ short xreg = enc.reg(this );
+ return xreg < riscv.F_OFFSET ? xreg : (short)(xreg-riscv.F_OFFSET);
+ }
+
+ // 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/MulFRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/MulFRISC.java
new file mode 100644
index 0000000..56cbf7b
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/MulFRISC.java
@@ -0,0 +1,17 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+// 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/MulRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/MulRISC.java
new file mode 100644
index 0000000..1930da7
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/MulRISC.java
@@ -0,0 +1,17 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+// mulh signed multiply instruction(no-imm form)
+public class MulRISC extends MachConcreteNode implements MachNode{
+ 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/NewRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/NewRISC.java
new file mode 100644
index 0000000..a561f3b
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/NewRISC.java
@@ -0,0 +1,34 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+import com.compilerprogramming.ezlang.compiler.print.ASMPrinter;
+
+public class NewRISC extends NewNode implements MachNode {
+ // A pre-zeroed chunk of memory.
+ NewRISC( NewNode nnn ) { super(nnn); }
+ @Override public String op() { return "alloc"; }
+ @Override public RegMask regmap(int i) {
+ return i==1 ? riscv.A0_MASK : null; // Size input or mem aliases
+ }
+ @Override public RegMask outregmap(int i) { return i == 1 ? riscv.A0_MASK : null; }
+ @Override public RegMask outregmap() { return null; }
+ @Override public RegMask killmap() { return riscv.riscCallerSave(); }
+
+ // Encoding is appended into the byte array; size is returned
+ @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.u_type(0x17, riscv.A1, 0));
+ enc.add4(riscv.i_type(0x67, riscv.RPC, 0, riscv.A1, 0));
+ }
+
+ // General form: "alloc #bytes PC"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p("auipc a1=#calloc\n");
+ sb.p("call a1+#calloc, a0");
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/NotRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/NotRISC.java
new file mode 100644
index 0000000..94d2a92
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/NotRISC.java
@@ -0,0 +1,16 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.NotNode;
+
+public class NotRISC extends ImmRISC {
+ NotRISC(NotNode not) { super(not,1); }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.RMASK; }
+ @Override public String op() { return "not"; }
+ // sltiu: x 0 ? "addi" : "ret";
+ }
+ // Correct Nodes outside the normal edges
+ @Override public void postSelect(CodeGen code) {
+ FunNode fun = (FunNode)rpc().in(0);
+ _fun = fun;
+ fun.setRet(this);
+ }
+ @Override public RegMask regmap(int i) { return riscv.retMask(_fun.sig(),i); }
+ @Override public RegMask outregmap() { return null; }
+ @Override public void encoding( Encoding enc ) {
+ int frameAdjust = fun()._frameAdjust;
+ if( frameAdjust > 0 )
+ enc.add4(riscv.i_type(riscv.I_TYPE, riscv.SP, 0, riscv.SP, (frameAdjust*-8) & 0xFFF));
+ short rpc = enc.reg(rpc());
+ enc.add4(riscv.i_type(0x67, riscv.ZERO, 0, rpc, 0));
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ int frameAdjust = fun()._frameAdjust;
+ if( frameAdjust>0 )
+ sb.p("rsp += #").p(frameAdjust*-8).p("\nret");
+ // Post code-gen, just print the "ret"
+ if( code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() )
+ // Prints return reg (either A0 or FA0), RPC (always R1) and then
+ // the callee-save registers.
+ for( int i=2; i= riscv.F_OFFSET;
+ boolean srcX = src >= riscv.F_OFFSET;
+
+ if( dst >= riscv.MAX_REG ) {
+ // Store to SP
+ if( src >= riscv.MAX_REG ) {
+ throw Utils.TODO(); // Very rare stack-stack move
+ }
+
+ int off = enc._fun.computeStackSlot(dst - riscv.MAX_REG)*8;
+ // store 64 bit values
+ enc.add4(riscv.s_type(39, 3, riscv.SP, dst, off));
+ }
+ if( src >= riscv.MAX_REG ) {
+ // Load from SP
+ int off = enc._fun.computeStackSlot(src - riscv.MAX_REG)*8;
+ enc.add4(riscv.i_type(7, dst, 3,riscv.SP, off));
+ }
+ // 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
+ // Can do: FPR->GPR->FPR
+ enc.add4(riscv.r_type(0b1010011, dst, 0, src, 0, 0b1110001));
+ enc.add4(riscv.r_type(0b1010011, dst, 0, src, 0, 0b0100000));
+ } else if( dstX && !srcX ) {
+ //GPR->FPR
+ // fmv.d.x
+ enc.add4(riscv.r_type(0b1010011, dst, 0, src, 0, 0b0100000));
+ } else if( !dstX && srcX ) {
+ //FPR->GPR
+ //fmv.x.d
+ enc.add4(riscv.r_type(0b1010011, dst, 0, src, 0, 0b1110001));
+ }
+
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SraIRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SraIRISC.java
new file mode 100644
index 0000000..95b66c7
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SraIRISC.java
@@ -0,0 +1,12 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv;
+
+import com.compilerprogramming.ezlang.compiler.Utils;
+import com.compilerprogramming.ezlang.compiler.nodes.Node;
+
+public class SraIRISC extends ImmRISC {
+ SraIRISC( Node and, int imm) { super(and,imm); }
+ @Override int opcode() { return riscv.I_TYPE; }
+ @Override int func3() { return 5;}
+ @Override public String glabel() { return ">>"; }
+ @Override public String op() { return "srai"; }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SraRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SraRISC.java
new file mode 100644
index 0000000..9d919cb
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SraRISC.java
@@ -0,0 +1,17 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+// Right Shift Arithmetic
+public class SraRISC extends MachConcreteNode implements MachNode {
+ 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,5,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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SrlIRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SrlIRISC.java
new file mode 100644
index 0000000..a19c72b
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SrlIRISC.java
@@ -0,0 +1,12 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv;
+
+import com.compilerprogramming.ezlang.compiler.Utils;
+import com.compilerprogramming.ezlang.compiler.nodes.Node;
+
+public class SrlIRISC extends ImmRISC {
+ SrlIRISC( Node and, int imm, boolean pop) { super(and,imm,pop); }
+ @Override int opcode() { return riscv.I_TYPE; }
+ @Override int func3() { return 5; }
+ @Override public String glabel() { return ">>>"; }
+ @Override public String op() { return "srli"; }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SrlRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SrlRISC.java
new file mode 100644
index 0000000..37b7059
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SrlRISC.java
@@ -0,0 +1,17 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+// Right Shift Logical
+public class SrlRISC extends MachConcreteNode implements MachNode {
+ 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/StoreRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/StoreRISC.java
new file mode 100644
index 0000000..7f16a1b
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/StoreRISC.java
@@ -0,0 +1,46 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.Utils;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.codegen.RegMask;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+// Store memory addressing on ARM
+// Support imm, reg(direct), or reg+off(indirect) addressing
+// Base = base - base pointer, offset is added to base
+// idx = null
+// off = off - offset added to base)
+// imm = imm - immediate value to store
+// val = Node of immediate value to store(null if its a constant immediate)
+
+//e.g s.cs[0] = 67; // C
+// base = s.cs, off = 4, imm = 67, val = null
+
+// sw rs2,offset(rs1)
+public class StoreRISC extends MemOpRISC {
+ StoreRISC( StoreNode st, Node base, int off, Node val ) { super(st, base, off, val); }
+ @Override public String op() { return "st"+_sz; }
+ @Override public RegMask regmap(int i) {
+ // 0 - ctrl
+ if( i==1 ) return null; // mem
+ if( i==2 ) return riscv.RMASK; // ptr
+ // 2 - index
+ if( i==4 ) return riscv.MEM_MASK; // Wide mask to store GPR and FPR
+ throw Utils.TODO();
+ }
+ @Override public RegMask outregmap() { return null; }
+
+ @Override public void encoding( Encoding enc ) {
+ short val = xreg(enc);
+ short ptr = enc.reg(ptr());
+ int body = riscv.s_type(opcode(enc), func3()&3, ptr, val, _off);
+ enc.add4(body);
+ }
+
+ @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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SubFRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SubFRISC.java
new file mode 100644
index 0000000..dd20d7a
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SubFRISC.java
@@ -0,0 +1,17 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+// 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SubRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SubRISC.java
new file mode 100644
index 0000000..652210f
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SubRISC.java
@@ -0,0 +1,16 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+public class SubRISC extends MachConcreteNode implements MachNode {
+ 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/TFPRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/TFPRISC.java
new file mode 100644
index 0000000..e0cf2df
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/TFPRISC.java
@@ -0,0 +1,54 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.ConstantNode;
+import com.compilerprogramming.ezlang.compiler.nodes.MachNode;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFunPtr;
+
+public class TFPRISC extends ConstantNode implements MachNode, RIPRelSize {
+ TFPRISC(ConstantNode con) { super(con); }
+ @Override public String op() { return "ldx"; }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public boolean isClone() { return true; }
+ @Override public TFPRISC copy() { return new TFPRISC(this); }
+ @Override public void encoding( Encoding enc ) {
+ enc.relo(this);
+ // TODO: 1 op encoding, plus a TODO if it does not fit
+ short dst = enc.reg(this);
+ SONTypeFunPtr tfp = (SONTypeFunPtr)_con;
+ // auipc t0,0
+ int auipc = riscv.u_type(0b0010111, dst, 0);
+ // addi t1,t0 + #0
+ int addi = riscv.i_type(0b0010011, dst, 0, dst, 0);
+ enc.add4(auipc);
+ enc.add4(addi);
+ }
+
+ @Override public byte encSize(int delta) {
+ if( -(1L<<11) <= delta && delta < (1L<<11) ) return 4;
+ throw Utils.TODO();
+ }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ short rpc = enc.reg(this);
+ if( opLen==4 ) {
+ // AUIPC (upper 20 bits)
+ // opstart of add
+ int next = opStart + opLen;
+ enc.patch4(opStart,riscv.u_type(0b0010111, rpc, delta));
+ // addi(low 12 bits)
+ enc.patch4(next,riscv.i_type(0b0010011, rpc, 0, rpc, delta & 0xFFF));
+ // addi
+ } else {
+ throw Utils.TODO();
+ }
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ _con.print(sb.p(code.reg(this)).p(" #"));
+ }
+
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/UJmpRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/UJmpRISC.java
new file mode 100644
index 0000000..ca5087c
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/UJmpRISC.java
@@ -0,0 +1,49 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONType;
+import java.util.BitSet;
+
+// unconditional jump
+public class UJmpRISC extends CFGNode implements MachNode, RIPRelSize {
+ //UJmpRISC() { }
+ @Override public String op() { return "jmp"; }
+ @Override public String label() { return op(); }
+ @Override public StringBuilder _print1( StringBuilder sb, BitSet visited ) {
+ return sb.append("jmp ");
+ }
+ @Override public RegMask regmap(int i) {return null; }
+ @Override public RegMask outregmap() { return null; }
+ @Override public SONType 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.J_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.J_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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/XorIRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/XorIRISC.java
new file mode 100644
index 0000000..0f2338c
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/XorIRISC.java
@@ -0,0 +1,12 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv;
+
+import com.compilerprogramming.ezlang.compiler.Utils;
+import com.compilerprogramming.ezlang.compiler.nodes.Node;
+
+public class XorIRISC extends ImmRISC {
+ XorIRISC( Node and, int imm) { super(and,imm); }
+ @Override int opcode() { return riscv.I_TYPE; }
+ @Override int func3() { return 4; }
+ @Override public String glabel() { return "^"; }
+ @Override public String op() { return "xori"; }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/XorRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/XorRISC.java
new file mode 100644
index 0000000..2542201
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/XorRISC.java
@@ -0,0 +1,16 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+public class XorRISC extends MachConcreteNode implements MachNode {
+ 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/riscv.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/riscv.java
new file mode 100644
index 0000000..7cead1f
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/riscv.java
@@ -0,0 +1,557 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv;
+
+import com.compilerprogramming.ezlang.compiler.Utils;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+import com.compilerprogramming.ezlang.compiler.sontypes.*;
+
+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, SP = 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
+ static int F0 = 32, F1 = 33, F2 = 34, F3 = 35, F4 = 36, F5 = 37, F6 = 38, F7 = 39;
+ static int FS0 = 40, FS1 = 41, FA0 = 42, FA1 = 43, FA2 = 44, FA3 = 45, FA4 = 46, FA5 = 47;
+ static int FA6 = 48, FA7 = 49, FS2 = 50, FS3 = 51, FS4 = 52, FS5 = 53, FS6 = 54, FS7 = 55;
+ static int FS8 = 56, FS9 = 57, FS10 = 58, FS11 = 59, FT8 = 60, FT9 = 61, FT10 = 62, FT11 = 63;
+
+ static final int MAX_REG = 64;
+
+ static final int F_OFFSET = 32;
+
+ static final String[] REGS = new String[] {
+ "zero","rpc" , "sp" , "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"
+ };
+
+ // General purpose register mask: pointers and ints, not floats
+ static final long RD_BITS = 0b11111111111111111111111111111111L; // All the GPRs
+ static final RegMask RMASK = new RegMask(RD_BITS);
+
+ static final long WR_BITS = 0b11111111111111111111111111111010L; // All the GPRs, minus ZERO and SP
+ static final RegMask WMASK = new RegMask(WR_BITS);
+ // Float mask from(f0-ft10). TODO: ft10,ft11 needs a larger RegMask
+ static final long FP_BITS = 0b11111111111111111111111111111111L<> 1) & 0x3FF;
+ int imm11 = (delta>>11) & 1;
+ int imm12_19 = (delta>>12) & 0xFF;
+ int imm20 = (delta>>19) & 1;
+ int bits = imm20<<20 | imm12_19 << 12 | imm11 << 11 | imm10_01;
+ return bits << 12 | rd << 7 | opcode;
+ }
+
+
+ public static int i_type(int opcode, int rd, int func3, int rs1, int imm12) {
+ assert opcode >= 0 && rd >=0 && func3 >=0 && rs1 >=0 && imm12 >= 0; // Zero-extend by caller
+ return (imm12 << 20) | (rs1 << 15) | (func3 << 12) | (rd << 7) | opcode;
+ }
+
+
+ // S-type instructions(store)
+ public static int s_type(int opcode, int offset1, int func3, int rs1, int rs2, int offset2) {
+ return (offset2 << 25) | (rs2 << 20) | (rs1 << 15) | (func3 << 12) | (offset1 << 7) | opcode;
+ }
+ public static int s_type(int opcode, int func3, int rs1, int rs2, int imm12) {
+ assert imm12 >= 0; // Masked to high zero bits by caller
+ int imm_lo = imm12 & 0x1F;
+ int imm_hi = imm12 >> 5;
+ return (imm_hi << 25) | (rs2 << 20) | (rs1 << 15) | (func3 << 12) | (imm_lo << 7) | opcode;
+ }
+
+ // BRANCH
+ public static int b_type(int opcode, int func3, short rs1, short rs2, int delta) {
+ assert -4*1024 <= delta && delta < 4*1024;
+ assert (delta&1)==0; // Low bit is always zero, not encoded
+ // Messy branch offset encoding
+ // 31 30 29 28 27 26 25 24-20 19-15 14-12 11 10 9 8 7 6-0
+ // 12 10 9 8 7 6 5 SRC2 SRC1 FUNC3 4 3 2 1 11 OP
+ int imm4_1 = (delta>> 1) & 0xF;
+ int imm10_5= (delta>> 5) &0x3F;
+ int imm11 = (delta>>11) & 1;
+ int imm12 = (delta>>12) & 1;
+ int imm5 = imm4_1<<1 | imm11;
+ int imm7 = imm12<<6 | imm10_5;
+ return (imm7 << 25 ) | (rs2 << 20) | (rs1 << 15) | (func3 << 12) | (imm5 << 7) | opcode;
+ }
+
+ public enum RM {
+ RNE, // Round to Nearest, ties to Even
+ RTZ, // Round towards Zero
+ RDN, // Round Down
+ RUP, // Round Up
+ DIRECT, // Round to Nearest, ties to Max Magnitude
+ RESERVED1, // Reserved for futue use
+ RESERVED2, // Reserved for future use
+ DYN, // In instruction’s rm field, selects dynamic rounding mode; In Rounding Mode register, reserved
+ }
+
+ // Since opcode is the same just return back func3
+ static public int jumpop(String op) {
+ return switch(op) {
+ case "==" -> 0x0;
+ case "!=" -> 0x1;
+ case "<" -> 0x4;
+ case "<=" -> 0x5;
+ case "u<" -> 0x6;
+ case "u<=" -> 0x7;
+ default -> throw Utils.TODO();
+ };
+ }
+
+ // Since opcode is the same just return back func3
+ static public int fsetop(String op) {
+ return switch(op) {
+ case "<" -> 1;
+ case "<=" -> 0;
+ case "==" -> 2;
+ default -> throw Utils.TODO();
+ };
+ }
+
+ static RegMask callInMask( SONTypeFunPtr tfp, int idx ) {
+ if( idx==0 ) return RPC_MASK;
+ if( idx==1 ) return null;
+ // Count floats in signature up to index
+ int fcnt=0;
+ for( int i=2; i= XMMS.length )
+ throw Utils.TODO();
+ RegMask[] cargs = CALLINMASK;
+ if( tfp.nargs()-fcnt >= cargs.length )
+ throw Utils.TODO();
+ return 0; // No stack args
+ }
+
+ // callee saved(riscv)
+ static final long CALLEE_SAVE =
+ (1L<< S0) | (1L<< S1) | (1L<< S2 ) | (1L<< S3 ) |
+ (1L<< S4) | (1L<< S5) | (1L<< S6 ) | (1L<< S7 ) |
+ (1L<< S8) | (1L<< S9) | (1L<< S10) | (1L<< S11) |
+ (1L<>52) == ti.value();
+ }
+ // True if HIGH 20-bit signed immediate, with all zeros low.
+ public static boolean imm20Exact(SONTypeInteger ti) {
+ // shift left 32 to clear out the upper 32 bits.
+ // shift right SIGNED to sign-extend upper 32 bits; then shift 12 more to clear out lower 12 bits.
+ // shift left 12 to re-center the bits.
+ return ti.isConstant() && (((ti.value()<<32)>>>44)<<12) == ti.value();
+ }
+
+ @Override public Node instSelect( Node n ) {
+ return switch (n) {
+ case AddFNode addf -> addf(addf);
+ case AddNode add -> add(add);
+ case AndNode and -> and(and);
+ case BoolNode bool -> cmp(bool);
+ case CallNode call -> call(call);
+ case CastNode cast -> new CastRISC(cast);
+ case CallEndNode cend -> new CallEndRISC(cend);
+ case CProjNode c -> new CProjNode(c);
+ case ConstantNode con -> con(con);
+ case DivFNode divf -> new DivFRISC(divf);
+ case DivNode div -> new DivRISC(div);
+ case FunNode fun -> new FunRISC(fun);
+ case IfNode iff -> jmp(iff);
+ case LoadNode ld -> ld(ld);
+ case MemMergeNode mem -> new MemMergeNode(mem);
+ case MulFNode mulf -> new MulFRISC(mulf);
+ case MulNode mul -> new MulRISC(mul);
+ case NewNode nnn -> nnn(nnn);
+ case NotNode not -> new NotRISC(not);
+ case OrNode or -> or(or);
+ case ParmNode parm -> new ParmRISC(parm);
+ case PhiNode phi -> new PhiNode(phi);
+ case ProjNode prj -> prj(prj);
+ case ReadOnlyNode read -> new ReadOnlyNode(read);
+ case ReturnNode ret -> new RetRISC(ret, ret.fun());
+ case SarNode sar -> sra(sar);
+ case ShlNode shl -> sll(shl);
+ case ShrNode shr -> srl(shr);
+ case StartNode start -> new StartNode(start);
+ case StopNode stop -> new StopNode(stop);
+ case StoreNode st -> st(st);
+ case SubFNode subf -> new SubFRISC(subf);
+ case SubNode sub -> sub(sub);
+ case ToFloatNode tfn -> i2f8(tfn);
+ case XorNode xor -> xor(xor);
+
+ case LoopNode loop -> new LoopNode(loop);
+ case RegionNode region -> new RegionNode(region);
+ default -> throw Utils.TODO();
+ };
+ }
+
+ private Node addf(AddFNode addf) {
+ return new AddFRISC(addf);
+ }
+
+ private Node add(AddNode add) {
+ if( add.in(2) instanceof ConstantNode off && off._con instanceof SONTypeInteger 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 SONTypeInteger ti ) {
+ if( imm12(ti) )
+ return new AndIRISC(and, (int)ti.value());
+ // Could be any size low bit mask
+ if( ti.value() == 0xFFFFFFFFL )
+ return new SrlIRISC(new SllIRISC(and,32),32,false);
+ }
+ return new AndRISC(and);
+ }
+
+ private Node call(CallNode call) {
+ return call.fptr() instanceof ConstantNode con && con._con instanceof SONTypeFunPtr tfp
+ ? new CallRISC(call, tfp)
+ : new CallRRISC(call);
+ }
+ private Node nnn(NewNode nnn) {
+ // TODO: pass in the TFP for alloc
+ return new NewRISC(nnn);
+ }
+
+ private Node cmp(BoolNode bool) {
+ // Float variant directly implemented in hardware
+ if( bool.isFloat() )
+ return new SetFRISC(bool);
+
+ // Only < and y - swap; y < x
+ // x >= y - swap and invert; !(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 SONTypeInteger ti && imm12(ti)
+ ? new SetIRISC(bool, (int)ti.value(),false)
+ : new SetRISC(bool);
+ // x <= y - flip and invert; !(y < x); `slt tmp=y,x; xori dst=tmp,#1`
+ case "<=" -> new XorIRISC(new SetRISC(bool.swap12()),1);
+ // x == y - sub and vs0 == `sub tmp=x-y; sltu dst=tmp,#1`
+ case "==" -> new SetIRISC(new SubRISC(bool),1,true);
+ default -> throw Utils.TODO();
+ };
+ }
+
+ private Node con( ConstantNode con ) {
+ if( !con._con.isConstant() ) return new ConstantNode( con ); // Default unknown caller inputs
+ return switch( con._con ) {
+ case SONTypeInteger ti -> {
+ if( imm12(ti) ) yield new IntRISC(con);
+ long x = ti.value();
+ if( imm20Exact(ti) ) yield new LUI((int)x);
+ if( (x<<32)>>32 == x ) { // Signed lower 32-bit immediate
+ // Here, the low 12 bits get sign-extended, which means if
+ // bit11 is set, the value is negative and lowers the LUI
+ // value. Add a bit 12 to compensate
+ if( ((x>>11)&1)==1 ) x += 0x1000;
+ yield new AddIRISC(new LUI((int)(x & ~0xFFF)), (int)(x & 0xFFF),false);
+ }
+ // Need more complex sequence for larger constants... or a load
+ // from a constant pool, which does not need an extra register
+ throw Utils.TODO();
+ }
+ case SONTypeFloat tf -> new FltRISC(con);
+ case SONTypeFunPtr tfp -> new TFPRISC(con);
+ case SONTypeMemPtr tmp -> throw Utils.TODO();
+ case SONTypeNil tn -> throw Utils.TODO();
+ // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code.
+ case SONType t -> t==SONType.NIL ? new IntRISC(con) : new ConstantNode(con);
+ };
+ }
+
+ private Node jmp( IfNode iff ) {
+ if( iff.in(1) instanceof BoolNode bool && !bool.isFloat() ) {
+ // if less than or equal switch inputs
+ String bop = bool.op();
+ if( bop.equals(">=") || bop.equals(">") )
+ return new BranchRISC(iff, IfNode.invert(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);
+ }
+
+ private Node or(OrNode or) {
+ if( or.in(2) instanceof ConstantNode con && con._con instanceof SONTypeInteger 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 SONTypeInteger 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 SONTypeInteger 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 SONTypeInteger 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 SONTypeInteger 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 SONTypeInteger ti && imm12(ti)
+ ? new AddIRISC(sub, (int)(-ti.value()),true)
+ : new SubRISC(sub);
+ }
+
+ private Node i2f8(ToFloatNode tfn) {
+ assert tfn.in(1)._type instanceof SONTypeInteger ti;
+ return new I2F8RISC(tfn);
+ }
+
+ private Node prj(ProjNode prj) {
+ return new ProjRISC(prj);
+ }
+
+ private Node ld(LoadNode ld) {
+ return new LoadRISC(ld,address(ld),off);
+ }
+
+ private Node st(StoreNode st) {
+ Node xval = st.val() instanceof ConstantNode con && con._con == SONTypeInteger.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 SONTypeMemPtr; // Base ptr always, not some derived
+ if( mop.off() instanceof ConstantNode con && con._con instanceof SONTypeInteger ti && imm12(ti) ) {
+ off = (int)ti.value();
+ } else {
+ base = new AddRISC(base,mop.off());
+ base._type = mop.ptr()._type;
+ }
+ return base;
+ }
+
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddFMemX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddFMemX86.java
new file mode 100644
index 0000000..92c8f15
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddFMemX86.java
@@ -0,0 +1,44 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.Utils;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+public class AddFMemX86 extends MemOpX86 {
+ AddFMemX86( AddFNode add, LoadNode ld , Node base, Node idx, int off, int scale, Node val ) {
+ super(add,ld, base, idx, off, scale, 0, val );
+ }
+ @Override public String op() { return "addf"+_sz; }
+ @Override public RegMask regmap(int i) {
+ if( i==1 ) return null; // Memory
+ if( i==2 ) return x86_64_v2.RMASK; // base
+ if( i==3 ) return x86_64_v2.RMASK; // index
+ if( i==4 ) return x86_64_v2.XMASK; // value
+ throw Utils.TODO();
+ }
+ @Override public RegMask outregmap() { return x86_64_v2.XMASK; }
+ @Override public int twoAddress() { return 4; }
+ @Override public void encoding( Encoding enc ) {
+ // addsd xmm0, DWORD PTR [rdi+0xc]
+ short dst = enc.reg(this );
+ short ptr = enc.reg(ptr());
+ short idx = enc.reg(idx());
+ // F opcode
+ enc.add1(0xF2);
+ // rex prefix must come next (REX.W is not set)
+ x86_64_v2.rexF(dst, ptr, idx, false, enc);
+
+ // FP ADD
+ enc.add1(0x0F).add1(0x58);
+
+ x86_64_v2.indirectAdr(_scale, idx, ptr, _off, dst, enc);
+ }
+
+ // General form: "add dst = src + [base + idx<<2 + 12]"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ");
+ sb.p(val()==null ? "#"+_imm : code.reg(val())).p(" + ");
+ asm_address(code,sb);
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddFX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddFX86.java
new file mode 100644
index 0000000..6681c1c
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddFX86.java
@@ -0,0 +1,33 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+public class AddFX86 extends MachConcreteNode implements MachNode {
+ AddFX86( Node addf ) { super(addf); }
+ @Override public String op() { return "addf"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.XMASK; }
+ @Override public int twoAddress() { return 1; }
+ @Override public boolean commutes() { return true; }
+
+ @Override public void encoding( Encoding enc ) {
+ // F2 0F 58 /r ADDSD xmm1, xmm2/m64
+ short dst = (short)(enc.reg(this ) - x86_64_v2.XMM_OFFSET);
+ short src = (short)(enc.reg(in(2)) - x86_64_v2.XMM_OFFSET);
+ // Fopcode
+ enc.add1(0xF2);
+ // rex prefix must come next (REX.W is not set)
+ x86_64_v2.rexF(dst, src, 0, false, enc);
+
+ enc.add1(0x0F).add1(0x58);
+
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src));
+ }
+
+ // General form: "addf dst += src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" += ").p(code.reg(in(2)));
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddIX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddIX86.java
new file mode 100644
index 0000000..7659e35
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddIX86.java
@@ -0,0 +1,13 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.nodes.Node;
+
+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; }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddMemX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddMemX86.java
new file mode 100644
index 0000000..89dbcd9
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddMemX86.java
@@ -0,0 +1,35 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+public class AddMemX86 extends MemOpX86 {
+ AddMemX86( AddNode add, LoadNode ld , Node base, Node idx, int off, int scale, int imm, Node val ) {
+ super(add,ld, base, idx, off, scale, imm, val );
+ }
+ @Override public String op() { return "add"+_sz; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ @Override public int twoAddress() { return 4; }
+ @Override public void encoding( Encoding enc ) {
+ // add something to register from memory
+ // add eax,DWORD PTR [rdi+0xc]
+ // REX.W + 03 /r ADD r64, r/m64
+ short dst = enc.reg(this );
+ short ptr = enc.reg(ptr());
+ short idx = enc.reg(idx());
+
+ enc.add1(x86_64_v2.rex(dst, ptr, idx));
+ // opcode
+ enc.add1(0x03);
+
+ x86_64_v2.indirectAdr(_scale, idx, ptr, _off, dst, enc);
+ }
+
+ // General form: "add dst = 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddX86.java
new file mode 100644
index 0000000..fe1808b
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddX86.java
@@ -0,0 +1,11 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.nodes.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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AndIX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AndIX86.java
new file mode 100644
index 0000000..446da90
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AndIX86.java
@@ -0,0 +1,11 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.nodes.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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AndX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AndX86.java
new file mode 100644
index 0000000..3c09cc6
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AndX86.java
@@ -0,0 +1,11 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.nodes.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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/CallEndX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/CallEndX86.java
new file mode 100644
index 0000000..4cf1f22
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/CallEndX86.java
@@ -0,0 +1,23 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.CallEndNode;
+import com.compilerprogramming.ezlang.compiler.nodes.MachNode;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFunPtr;
+
+public class CallEndX86 extends CallEndNode implements MachNode {
+ final SONTypeFunPtr _tfp;
+ CallEndX86( CallEndNode cend ) {
+ super(cend);
+ _tfp = (SONTypeFunPtr)(cend.call().fptr()._type);
+ }
+ @Override public String op() { return "cend"; }
+ @Override public String label() { return op(); }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return null; }
+ @Override public RegMask outregmap(int idx) { return idx == 2 ? x86_64_v2.retMask(_tfp,2) : null; }
+ @Override public RegMask killmap() { return x86_64_v2.x86CallerSave(); }
+ @Override public void encoding( Encoding enc ) { }
+ @Override public void asm(CodeGen code, SB sb) { }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/CallRX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/CallRX86.java
new file mode 100644
index 0000000..82a9999
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/CallRX86.java
@@ -0,0 +1,33 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.CallNode;
+import com.compilerprogramming.ezlang.compiler.nodes.MachNode;
+
+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); // Normal argument
+ }
+ @Override public RegMask outregmap() { return null; }
+ @Override public void encoding( Encoding enc ) {
+ // FF /2 CALL r/m64
+ // calls the function in the register
+ short src = enc.reg(fptr());
+ enc.add1(0xFF);
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.INDIRECT,2,src));
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(fptr())).p(" ");
+ for( int i=0; i= 8 ) enc.add1(x86_64_v2.rex(dst, dst, 0));
+ enc.add1(0x33); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, dst));
+ return;
+ }
+
+ // Simply move the constant into a GPR
+ // Conditional encoding based on 64 or 32 bits
+ //REX.W + C7 /0 id MOV r/m64, imm32
+
+ long imm = ((SONTypeInteger)_con).value();
+ if (Integer.MIN_VALUE <= imm && imm < 0) {
+ // We need sign extension, so use imm32 into 64 bit register
+ // REX.W + C7 /0 id MOV r/m64, imm32
+ enc.add1(x86_64_v2.rex(0, dst, 0));
+ enc.add1(0xC7); // 32 bits encoding
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 0x00, dst));
+ enc.add4((int)imm);
+ } else if (0 <= imm && imm <= 0xFFFFFFFFL) {
+ // We want zero extension into 64 bit register
+ // so move 32 bit into 32 bit register which zeros
+ // the upper bits in the 64 bit register.
+ // B8+ rd id MOV r32, imm32
+ if (dst >= 8) enc.add1(0x41);
+ enc.add1(0xB8 + (dst & 0x07));
+ enc.add4((int)imm);
+ } else {
+ // Just write the full 64 bit constant
+ // REX.W + B8+ rd io MOV r64, imm64
+ enc.add1(x86_64_v2.rex(0, dst, 0));
+ enc.add1(0xB8 + (dst & 0x07));
+ enc.add8(imm); // 64 bits encoding
+ }
+ }
+
+ // Human-readable form appended to the SB. Things like the encoding,
+ // indentation, leading address or block labels not printed here.
+ // Just something like "ld4\tR17=[R18+12] // Load array base".
+ // General form: "op\tdst=src+src"
+ @Override public void asm(CodeGen code, SB sb) {
+ String reg = code.reg(this);
+ if( _con == SONType.NIL || _con == SONTypeInteger.ZERO )
+ sb.p(reg).p(",").p(reg);
+ else
+ _con.print(sb.p(reg).p(" = #"));
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/JmpX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/JmpX86.java
new file mode 100644
index 0000000..426dde7
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/JmpX86.java
@@ -0,0 +1,69 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+// 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);
+ 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 invert() { _bop = invert(_bop); }
+
+ @Override public void encoding( Encoding enc ) {
+ enc.jump(this,cproj(0));
+ int op = x86_64_v2.jumpop(_bop);
+ enc.add1(op-16); // Short form jump
+ enc.add1(0); // Offset
+ //enc.add1(0x0F).add1(x86_64_v2.jumpop(_bop));
+ //enc.add4(0); // Offset patched later
+ }
+
+ // Delta is from opcode start, but X86 measures from the end of the 2-byte encoding
+ @Override public byte encSize(int delta) { return (byte)(x86_64_v2.imm8(delta-2) ? 2 : 6); }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ byte[] bits = enc.bits();
+ if( opLen==2 ) {
+ assert bits[opStart] == x86_64_v2.jumpop(_bop)-16;
+ delta -= 2; // Offset from opcode END
+ assert (byte)delta==delta;
+ bits[opStart+1] = (byte)delta;
+ } else {
+ assert bits[opStart] == x86_64_v2.jumpop(_bop)-16;
+ delta -= 6; // Offset from opcode END
+ bits[opStart] = 0x0F;
+ bits[opStart+1] = (byte)x86_64_v2.jumpop(_bop);
+ enc.patch4(opStart+2,delta);
+ }
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ String src = code.reg(in(1));
+ if( src!="flags" ) sb.p(src).p(" ");
+ CFGNode prj = cproj(0); // 0 is True is jump target
+ while( prj.nOuts() == 1 && !(prj instanceof ReturnNode) )
+ prj = prj.uctrl(); // Skip empty blocks
+ sb.p(label(prj));
+ }
+
+ @Override public String comment() { return "L"+cproj(1)._nid; }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/LeaX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/LeaX86.java
new file mode 100644
index 0000000..3050699
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/LeaX86.java
@@ -0,0 +1,55 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+public class LeaX86 extends MachConcreteNode implements MachNode {
+ final int _scale;
+ final int _offset;
+ LeaX86( Node add, Node base, Node idx, int scale, int offset ) {
+ super(add);
+ assert scale==0 || scale==1 || scale==2 || scale==3;
+ _inputs.pop();
+ _inputs.pop();
+ _inputs.push(base);
+ _inputs.push(idx);
+ _scale = scale;
+ _offset = offset;
+ }
+
+ @Override public String op() { return "lea"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+
+ @Override public void encoding( Encoding enc ) {
+ // REX.W + 8D /r LEA r64,m
+ short dst = enc.reg(this);
+ short ptr = enc.reg(in(1));
+ short idx = enc.reg(in(2));
+ // ptr is null
+ // just do: [(index * s) + disp32]
+ if( ptr == -1 ) {
+ enc.add1(x86_64_v2.rex(dst, 0, idx));
+ enc.add1(0x8D); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.INDIRECT, dst, 0x04));
+ enc.add1(x86_64_v2.sib(_scale, idx, x86_64_v2.RBP));
+ enc.add4(_offset);
+ return;
+ }
+
+ enc.add1(x86_64_v2.rex(dst, ptr, idx));
+ enc.add1(0x8D); // opcode
+ // rsp is hard-coded here(0x04)
+ x86_64_v2.indirectAdr(_scale, idx, ptr, _offset, dst, enc);
+ }
+
+ // General form: "lea dst = base + 4*idx + 12"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ");
+ if( in(1) != null )
+ sb.p(code.reg(in(1))).p(" + ");
+ sb.p(code.reg(in(2))).p("<<").p(_scale);
+ if( _offset!=0 ) sb.p(" + #").p(_offset);
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/LoadX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/LoadX86.java
new file mode 100644
index 0000000..1f5dc56
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/LoadX86.java
@@ -0,0 +1,80 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.LoadNode;
+import com.compilerprogramming.ezlang.compiler.nodes.Node;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONType;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFloat;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeInteger;
+
+public class LoadX86 extends MemOpX86 {
+ LoadX86( LoadNode ld, Node base, Node idx, int off, int scale ) {
+ super(ld,ld, base, idx, off, scale, 0);
+ }
+ @Override public String op() { return "ld"+_sz; }
+ @Override public RegMask outregmap() { return x86_64_v2.MEM_MASK; }
+ @Override public void encoding( Encoding enc ) {
+ // REX.W + 8B /r MOV r64, r/m64
+ // Zero extension for u8, u16 and u32 but sign extension i8, i16, i32
+ // Use movsx and movzx
+
+ short dst = enc.reg(this );
+ short ptr = enc.reg(ptr());
+ short idx = enc.reg(idx());
+
+ if (_declaredType != SONTypeInteger.U32 && _declaredType != SONTypeFloat.F32 && _declaredType != SONTypeFloat.F64) {
+ enc.add1(x86_64_v2.rex(dst, ptr, idx));
+ }
+
+ if(_declaredType == SONTypeFloat.F32) {
+ // F3 0F 10 /r MOVSS xmm1, m32
+ enc.add1(0xF3);
+ enc.add1(0x0F);
+ enc.add1(0x10);
+ dst -= (short)x86_64_v2.XMM_OFFSET;
+ }
+ if(_declaredType == SONTypeFloat.F64) {
+ // F2 0F 10 /r MOVSD xmm1, m64
+ enc.add1(0xF2);
+ enc.add1(0x0F);
+ enc.add1(0x10);
+ dst -= (short)x86_64_v2.XMM_OFFSET;
+ }
+ if(_declaredType == SONTypeInteger.I8) {
+ // sign extend: REX.W + 0F BE /r MOVSX r64, r/m8
+ enc.add1(0x0F);
+ enc.add1(0xBE);
+ } else if(_declaredType == SONTypeInteger.I16) {
+ // sign extend: REX.W + 0F BF /r MOVSX r64, r/m16
+ enc.add1(0x0F);
+ enc.add1(0xBF);
+ } else if(_declaredType == SONTypeInteger.I32) {
+ // sign extend: REX.W + 63 /r MOVSXD r64, r/m32
+ enc.add1(0x63);
+ } else if(_declaredType == SONTypeInteger.U8) {
+ // zero extend: REX.W + 0F B6 /r MOVZX r64, r/m8
+ enc.add1(0x0F);
+ enc.add1(0xB6);
+ } else if(_declaredType == SONTypeInteger.U16) {
+ // zero extend: REX.W + 0F B7 /r MOVZX r64, r/m16
+ enc.add1(0x0F);
+ enc.add1(0xB7);
+ } else if(_declaredType == SONTypeInteger.U32) {
+ // zero extend: 8B /r MOV r32, r/m32
+ enc.add1(0x8B);
+ } else if(_declaredType == SONTypeInteger.BOT) {
+ // REX.W + 8B /r MOV r64, r/m64
+ enc.add1(0x8B);
+ }
+
+ // includes modrm internally
+ x86_64_v2.indirectAdr(_scale, idx, ptr, _off, dst, enc);
+ }
+
+ // General form: "ldN dst,[base + idx<<2 + 12]"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(",");
+ asm_address(code,sb);
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MemAddX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MemAddX86.java
new file mode 100644
index 0000000..1096341
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MemAddX86.java
@@ -0,0 +1,39 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+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 void encoding( Encoding enc ) {
+ // add something to memory
+ // REX.W + 01 /r | REX.W + 81 /0 id
+ // ADD [mem], imm32/reg
+ short ptr = enc.reg(ptr());
+ short idx = enc.reg(idx());
+ short src = enc.reg(val());
+
+ enc.add1(x86_64_v2.rex(src, ptr, idx));
+ // opcode
+ enc.add1( src == -1 ? 0x01 : 0x81 );
+
+ // includes modrm
+ x86_64_v2.indirectAdr(_scale, idx, ptr, _off, src == -1 ? 0 : src, enc);
+ if( src == -1 ) enc.add4(_imm);
+ }
+ // General form: "add [base + idx<<2 + 12] += src"
+ @Override public void asm(CodeGen code, SB sb) {
+ asm_address(code,sb);
+ if( val()==null ) {
+ if( _imm != 1 && _imm != -1 ) sb.p(" += #").p(_imm);
+ } else sb.p(" += ").p(code.reg(val()));
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MemOpX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MemOpX86.java
new file mode 100644
index 0000000..f511e97
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MemOpX86.java
@@ -0,0 +1,85 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+import com.compilerprogramming.ezlang.compiler.sontypes.*;
+
+import java.lang.StringBuilder;
+import java.util.BitSet;
+
+// Generic X86 memory operand base.
+// inputs:
+// ctrl - will appear for possibly RCE (i.e. array ops)
+// mem - memory dependence edge
+// base - Basic object pointer
+// idx/null - Scaled index offset, or null if none
+// val/null - Value to store, as part of a 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 = (char)('0'+(1<<_declaredType.log_size()));
+ MemOpX86( Node op, MemOpNode mop, Node base, Node idx, int off, int scale, int imm ) {
+ super(op,mop);
+ assert base._type instanceof SONTypeMemPtr && !(base instanceof AddNode);
+ assert (idx==null && scale==0) || (idx!=null && 0<= scale && scale<=3);
+
+ // Copy memory parts from eg 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;
+ }
+
+ // Store-based flavors have a value edge
+ MemOpX86( Node op, MemOpNode mop, Node base, Node idx, int off, int scale, int imm, Node val ) {
+ this(op,mop,base,idx,off,scale,imm);
+ _inputs.setX(4,val);
+ }
+
+ Node idx() { return in(3); }
+ Node val() { return in(4); } // Only for stores, including op-to-memory
+
+ @Override public StringBuilder _printMach(StringBuilder sb, BitSet visited) {
+ return sb.append(".").append(_name);
+ }
+
+ @Override public String label() { return op(); }
+ @Override public SONType compute() { throw Utils.TODO(); }
+ @Override public Node idealize() { throw Utils.TODO(); }
+
+ // Register mask allowed on input i.
+ @Override public RegMask regmap(int i) {
+ if( i==1 ) return null; // Memory
+ if( i==2 ) return x86_64_v2.RMASK; // base in GPR
+ if( i==3 ) return x86_64_v2.RMASK; // index in GPR
+ if( i==4 ) return x86_64_v2.MEM_MASK; // value in GPR or XMM
+ throw Utils.TODO();
+ }
+
+ // "[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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MulFX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MulFX86.java
new file mode 100644
index 0000000..73bca29
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MulFX86.java
@@ -0,0 +1,32 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+public class MulFX86 extends MachConcreteNode implements MachNode {
+ MulFX86( Node mulf) { super(mulf); }
+ @Override public String op() { return "mulf"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.XMASK; }
+ @Override public int twoAddress() { return 1; }
+ @Override public boolean commutes() { return true; }
+
+ @Override public void encoding( Encoding enc ) {
+ // F2 0F 59 /r MULSD xmm1,xmm2/m64
+ short dst = (short)(enc.reg(this ) - x86_64_v2.XMM_OFFSET);
+ short src = (short)(enc.reg(in(2)) - x86_64_v2.XMM_OFFSET);
+
+ // Fopcode
+ enc.add1(0xF2);
+ // rex prefix must come next (REX.W is not set)
+ x86_64_v2.rexF(dst, src, 0, false, enc);
+
+ enc.add1(0x0F).add1(0x59).add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src));
+ }
+
+ // General form: "mulf dst *= src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" *= ").p(code.reg(in(2)));
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MulIX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MulIX86.java
new file mode 100644
index 0000000..c53599d
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MulIX86.java
@@ -0,0 +1,11 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.nodes.Node;
+
+public class MulIX86 extends ImmX86 {
+ MulIX86( Node add, int imm ) { super(add,imm); }
+ @Override public String op() { return "muli"; }
+ @Override public String glabel() { return "*"; }
+ @Override int opcode() { return 0x69; }
+ @Override int mod() { return 0; }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MulX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MulX86.java
new file mode 100644
index 0000000..04c9a51
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MulX86.java
@@ -0,0 +1,29 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+public class MulX86 extends MachConcreteNode implements MachNode {
+ MulX86( Node mul ) { super(mul); }
+ @Override public String op() { return "mul"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ @Override public int twoAddress() { return 1; }
+ @Override public boolean commutes() { return true; }
+
+ @Override public void encoding( Encoding enc ) {
+ // REX.W + 0F AF /r IMUL r64, r/m64
+ short dst = enc.reg(this ); // src1
+ short src = enc.reg(in(2)); // src2
+ enc.add1(x86_64_v2.rex(dst, src, 0));
+ enc.add1(0x0F); // opcode
+ enc.add1(0xAF); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src));
+ }
+
+ // General form: "mul dst *= src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2)));
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/NewX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/NewX86.java
new file mode 100644
index 0000000..6d5639b
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/NewX86.java
@@ -0,0 +1,30 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+public class NewX86 extends NewNode implements MachNode {
+ // A pre-zeroed chunk of memory.
+ NewX86( NewNode nnn ) { super(nnn); }
+ @Override public String op() { return "alloc"; }
+ // Size and pointer result in standard calling convention; null for all the
+ // memory aliases edges
+ @Override public RegMask regmap(int i) { return i == 1 ? x86_64_v2.RDI_MASK : null; }
+ @Override public RegMask outregmap(int i) { return i == 1 ? x86_64_v2.RAX_MASK : null; }
+ @Override public RegMask outregmap() { return null; }
+ @Override public RegMask killmap() { return x86_64_v2.x86CallerSave(); }
+
+ @Override public void encoding( Encoding enc ) {
+ enc.external(this,"calloc");
+ // E8 cd CALL rel32;
+ enc.add1(0xE8);
+ enc.add4(0); // offset
+ }
+
+ // General form: "alloc #bytes"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p("#calloc, ").p(code.reg(size()));
+ }
+
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/NotX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/NotX86.java
new file mode 100644
index 0000000..3a270d9
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/NotX86.java
@@ -0,0 +1,39 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+public class NotX86 extends MachConcreteNode implements MachNode {
+ NotX86(NotNode not) { super(not); }
+ @Override public String op() { return "not"; }
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.RMASK; }
+ @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; }
+
+ @Override public void encoding( Encoding enc ) {
+ assert !(in(1) instanceof NotNode); // Cleared out by peeps
+ assert !(in(1) instanceof BoolNode); // Cleared out by peeps
+ short dst = enc.reg(this );
+ short src = enc.reg(in(1));
+
+ // Pre-zero using XOR dst,dst; since zero'd will not have a
+ // byte-dependency from the setz. Can skip REX is dst is low 8, makes
+ // this a 32b xor, which will also zero the high bits.
+ if( dst >= 8 ) enc.add1(x86_64_v2.rex(dst, dst, 0));
+ enc.add1(0x33); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, dst));
+
+ // test rdi,rdi
+ enc.add1(x86_64_v2.rex(src, src, 0));
+ enc.add1(0x85);
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, src, src));
+
+ // setz (sete dil)
+ enc.add1(x86_64_v2.rex(dst, 0, 0));
+ enc.add1(0x0F);
+ enc.add1(0x94);
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, 0));
+ }
+ @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/OrIX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/OrIX86.java
new file mode 100644
index 0000000..cac195d
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/OrIX86.java
@@ -0,0 +1,11 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.nodes.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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/OrX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/OrX86.java
new file mode 100644
index 0000000..99a73db
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/OrX86.java
@@ -0,0 +1,11 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.nodes.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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ParmX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ParmX86.java
new file mode 100644
index 0000000..33995f1
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ParmX86.java
@@ -0,0 +1,17 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.codegen.Encoding;
+import com.compilerprogramming.ezlang.compiler.codegen.RegMask;
+import com.compilerprogramming.ezlang.compiler.nodes.MachNode;
+import com.compilerprogramming.ezlang.compiler.nodes.ParmNode;
+
+public class ParmX86 extends ParmNode implements MachNode {
+ final RegMask _rmask;
+ ParmX86( ParmNode parm ) {
+ super(parm);
+ _rmask = x86_64_v2.callInMask(fun().sig(),_idx);
+ }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return _rmask; }
+ @Override public void encoding( Encoding enc ) { }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ProjX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ProjX86.java
new file mode 100644
index 0000000..f5c765c
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ProjX86.java
@@ -0,0 +1,15 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.codegen.Encoding;
+import com.compilerprogramming.ezlang.compiler.codegen.RegMask;
+import com.compilerprogramming.ezlang.compiler.nodes.MachNode;
+import com.compilerprogramming.ezlang.compiler.nodes.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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/RegX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/RegX86.java
new file mode 100644
index 0000000..fc49513
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/RegX86.java
@@ -0,0 +1,25 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+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 int twoAddress() { return 1; }
+ abstract int opcode();
+ @Override public void encoding( Encoding enc ) {
+ // REX.W + 01 /r
+ short dst = enc.reg(this ); // src1
+ short src = enc.reg(in(2)); // src2
+ enc.add1(x86_64_v2.rex(dst, src, 0));
+ enc.add1(opcode()); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src));
+ }
+ // General form: "add dst += src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" ").p(glabel()).p("= ").p(code.reg(in(2)));
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/RetX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/RetX86.java
new file mode 100644
index 0000000..ce4d36f
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/RetX86.java
@@ -0,0 +1,41 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+public class RetX86 extends ReturnNode implements MachNode {
+ RetX86( ReturnNode ret, FunNode fun ) { super(ret, fun); fun.setRet(this); }
+ @Override public String op() {
+ return ((FunX86)fun())._frameAdjust > 0 ? "addi" : "ret";
+ }
+ // Correct Nodes outside the normal edges
+ @Override public void postSelect(CodeGen code) {
+ FunNode fun = (FunNode)rpc().in(0);
+ _fun = fun;
+ fun.setRet(this);
+ }
+ @Override public RegMask regmap(int i) { return x86_64_v2.retMask(_fun.sig(),i); }
+ @Override public RegMask outregmap() { return null; }
+ @Override public void encoding( Encoding enc ) {
+ int frameAdjust = ((FunX86)fun())._frameAdjust;
+ if( frameAdjust > 0 ) {
+ enc.add1( 0x83 );
+ enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 0, x86_64_v2.RSP) );
+ enc.add1(frameAdjust*-8);
+ }
+ enc.add1(0xC3);
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ int frameAdjust = ((FunX86)fun())._frameAdjust;
+ if( frameAdjust>0 )
+ sb.p("rsp += #").p(frameAdjust*-8).p("\nret");
+ // Post code-gen, just print the "ret"
+ if( code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() )
+ // Prints return reg (either RAX or XMM0), RPC (always [rsp-4]) and
+ // then the callee-save registers.
+ for( int i=2; i>"; }
+ @Override int opcode() { return 0xC1; }
+ @Override int mod() { return 7; }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SarX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SarX86.java
new file mode 100644
index 0000000..7b91b69
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SarX86.java
@@ -0,0 +1,24 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.Utils;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.Node;
+
+public class SarX86 extends RegX86 {
+ SarX86( Node add ) { super(add); }
+ @Override public String op() { return "sar"; }
+ @Override public String glabel() { return ">>"; }
+ @Override public RegMask regmap(int i) {
+ if (i == 1) return x86_64_v2.WMASK;
+ if (i == 2) return x86_64_v2.RCX_MASK;
+ throw Utils.TODO();
+ }
+ @Override int opcode() { return 0xD3; }
+ @Override public void encoding( Encoding enc ) {
+ short dst = enc.reg(this ); // src1
+ short src = enc.reg(in(2)); // src2
+ enc.add1(x86_64_v2.rex(0, dst, 0));
+ enc.add1(opcode()); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 7, dst));
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SetX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SetX86.java
new file mode 100644
index 0000000..7ee2305
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SetX86.java
@@ -0,0 +1,50 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+// 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 <,<=,==
+ // Constructor expects input is an X86 and not an Ideal node.
+ SetX86( Node cmp, String bop ) {
+ super(cmp);
+ _inputs.setLen(1); // Pop the cmp inputs
+ // Replace with the matched cmp
+ _inputs.push(cmp);
+ _bop = bop;
+ }
+ @Override public String op() { return "set"+_bop; }
+ @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.FLAGS_MASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+
+ @Override public void encoding( Encoding enc ) {
+ // REX + 0F 94
+ short dst = enc.reg(this );
+
+ // Optional rex, for dst
+ if( dst >= 8 ) enc.add1(x86_64_v2.rex(0, dst, 0));
+ enc.add1(0x0F); // opcode
+ enc.add1(switch (_bop) {
+ case "==" -> 0x94; // SETE
+ case "<" -> 0x9C; // SETL
+ case "<=" -> 0X9E; // SETLE
+ default -> throw Utils.TODO();
+ });
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 0, dst));
+
+ // low 8 bites are set, now zero extend for next instruction
+ if( dst >= 8 ) enc.add1(x86_64_v2.rex(0, dst, 0));
+ enc.add1(0x0F); // opcode
+ enc.add1(0xB6); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, dst));
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this));
+ String src = code.reg(in(1));
+ if( src!="flags" ) sb.p(" = ").p(src);
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ShlIX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ShlIX86.java
new file mode 100644
index 0000000..ee8dfb6
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ShlIX86.java
@@ -0,0 +1,11 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.nodes.Node;
+
+public class ShlIX86 extends ImmX86 {
+ ShlIX86( Node add, int imm ) { super(add,imm); }
+ @Override public String op() { return "shli"; }
+ @Override public String glabel() { return "<<"; }
+ @Override int opcode() { return 0xC1; }
+ @Override int mod() { return 4; }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ShlX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ShlX86.java
new file mode 100644
index 0000000..8aeb594
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ShlX86.java
@@ -0,0 +1,24 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.Utils;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.Node;
+
+public class ShlX86 extends RegX86 {
+ ShlX86( Node add ) { super(add); }
+ @Override public String op() { return "shl"; }
+ @Override public String glabel() { return "<<"; }
+ @Override public RegMask regmap(int i) {
+ if (i == 1) return x86_64_v2.WMASK;
+ if (i == 2) return x86_64_v2.RCX_MASK;
+ throw Utils.TODO();
+ }
+ @Override int opcode() { return 0xD3; }
+ @Override public final void encoding( Encoding enc ) {
+ short dst = enc.reg(this ); // src1
+ short src = enc.reg(in(2)); // src2
+ enc.add1(x86_64_v2.rex(0, dst, 0));
+ enc.add1(opcode()); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 4, dst));
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ShrIX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ShrIX86.java
new file mode 100644
index 0000000..de39108
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ShrIX86.java
@@ -0,0 +1,11 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.nodes.Node;
+
+public class ShrIX86 extends ImmX86 {
+ ShrIX86( Node add, int imm ) { super(add,imm); }
+ @Override public String op() { return "shri"; }
+ @Override public String glabel() { return ">>>"; }
+ @Override int opcode() { return 0xC1; }
+ @Override int mod() { return 5; }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ShrX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ShrX86.java
new file mode 100644
index 0000000..d0a42f5
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ShrX86.java
@@ -0,0 +1,24 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.Utils;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.Node;
+
+public class ShrX86 extends RegX86 {
+ ShrX86( Node add ) { super(add); }
+ @Override public String op() { return "shr"; }
+ @Override public String glabel() { return ">>>"; }
+ @Override public RegMask regmap(int i) {
+ if (i == 1) return x86_64_v2.WMASK;
+ if (i == 2) return x86_64_v2.RCX_MASK;
+ throw Utils.TODO();
+ }
+ @Override int opcode() { return 0xD3; }
+ @Override public final void encoding( Encoding enc ) {
+ short dst = enc.reg(this ); // src1
+ short src = enc.reg(in(2)); // src2
+ enc.add1(x86_64_v2.rex(0, dst, 0));
+ enc.add1(opcode()); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 5, dst));
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SplitX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SplitX86.java
new file mode 100644
index 0000000..49129a2
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SplitX86.java
@@ -0,0 +1,99 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.Utils;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.Node;
+import com.compilerprogramming.ezlang.compiler.nodes.SplitNode;
+
+public class SplitX86 extends SplitNode {
+ SplitX86( String kind, byte round ) { super(kind,round, new Node[2]); }
+ @Override public String op() { return "mov"; }
+ @Override public RegMask regmap(int i) { return x86_64_v2.SPLIT_MASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.SPLIT_MASK; }
+
+ // Need to handle 8 cases: . reg->reg, reg->xmm, reg->flags, xmm->reg, xmm->xmm, xmm->flags, flags->reg, flags->xmm,
+ // flags->flags.
+ @Override public void encoding( Encoding enc ) {
+ // REX.W + 8B /r MOV r64, r/m64
+ short dst = enc.reg(this );
+ short src = enc.reg(in(1));
+
+ if( dst == x86_64_v2.FLAGS ) {
+ // mov reg, flags
+ // push rcx
+ // popf (Pop the top of the stack into the FLAGS register)
+ // 50+rd PUSH r64
+ enc.add1(0x50 + src);
+ // popf
+ enc.add1(0x9D);
+ return;
+ }
+ if( src == x86_64_v2.FLAGS ) {
+ // mov flags, reg
+ // pushf; pop reg
+ enc.add1(0x9C);
+ // 58+ rd POP r64
+ enc.add1(0x58 + dst);
+ return;
+ }
+
+ // Flag for being either XMM or stack
+ boolean dstX = dst >= x86_64_v2.XMM_OFFSET;
+ boolean srcX = src >= x86_64_v2.XMM_OFFSET;
+
+ // Stack spills
+ if( dst >= x86_64_v2.MAX_REG ) {
+ if( src >= x86_64_v2.MAX_REG )
+ throw Utils.TODO(); // Very rare stack-stack move
+ // TODO: Missing FP 0x66 prefix
+ if( srcX ) { src -= (short)x86_64_v2.XMM_OFFSET; enc.add1(0x66); }
+ int off = enc._fun.computeStackSlot(dst - x86_64_v2.MAX_REG)*8;
+ enc.add1(x86_64_v2.rex(src, x86_64_v2.RSP, -1));
+ enc.add1( 0x89 );
+ x86_64_v2.indirectAdr(0, (short)-1, (short)x86_64_v2.RSP, off, src, enc);
+ return;
+ }
+ if( src >= x86_64_v2.MAX_REG ) {
+ if( dstX ) { dst -= (short)x86_64_v2.XMM_OFFSET; enc.add1(0x66); }
+ int off = enc._fun.computeStackSlot(src - x86_64_v2.MAX_REG)*8;
+ enc.add1(x86_64_v2.rex(dst, x86_64_v2.RSP, -1));
+ enc.add1( 0x8B );
+ x86_64_v2.indirectAdr(0, (short)-1, (short)x86_64_v2.RSP, off, dst, enc);
+ return;
+ }
+
+ // reg-reg move. Adjust numbering for GPR vs FPR reg set
+ if( dstX ) dst -= (short) x86_64_v2.XMM_OFFSET;
+ if( srcX ) src -= (short) x86_64_v2.XMM_OFFSET;
+
+ // 0x66 if moving between register classes
+ if( dstX ^ srcX ) enc.add1(0x66);
+ enc.add1(x86_64_v2.rex(dst, src, 0));
+
+ // pick opcode based on regs
+ if( !dstX && !srcX ) {
+ // reg->reg (MOV r64, r/m64)
+ enc.add1(0x8B);
+ } else if( dstX && srcX ) {
+ // xmm->xmm (NP 0F 28 /r MOVAPS xmm1, xmm2/m128)
+ enc.add1(0x0F);
+ enc.add1(0x28);
+ } else if( dstX && !srcX ) {
+ // xmm->reg (66 REX.W 0F 6E /r MOVQ xmm, r/m64)
+ enc.add1(0x0F);
+ enc.add1(0x6E);
+ } else if( !dstX && srcX ) {
+ // reg->xmm(66 REX.W 0F 7E /r MOVQ r/m64, xmm)
+ enc.add1(0x0F);
+ enc.add1(0x7E);
+ }
+
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src));
+ }
+
+ // General form: "mov dst = src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1)));
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/StoreX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/StoreX86.java
new file mode 100644
index 0000000..501121f
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/StoreX86.java
@@ -0,0 +1,63 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.ConstantNode;
+import com.compilerprogramming.ezlang.compiler.nodes.Node;
+import com.compilerprogramming.ezlang.compiler.nodes.StoreNode;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFloat;
+
+import java.util.BitSet;
+
+public class StoreX86 extends MemOpX86 {
+ StoreX86( StoreNode st, Node base, Node idx, int off, int scale, int imm, Node val ) {
+ super(st,st, base, idx, off, scale, imm, val);
+ }
+ @Override public String op() { return "st"+_sz; }
+ @Override public StringBuilder _printMach(StringBuilder sb, BitSet visited) {
+ Node val = val();
+ sb.append(".").append(_name).append("=");
+ if( val==null ) sb.append(_imm);
+ else val._print0(sb,visited);
+ return sb.append(";");
+ }
+ // Register mask allowed as a result. 0 for no register.
+ @Override public RegMask outregmap() { return null; }
+ @Override public void encoding( Encoding enc ) {
+ // REX.W + C7 /0 id MOV r/m64, imm32 |
+ // REX.W + 89 /r MOV r/m64, r64
+ short ptr = enc.reg(ptr());
+ short idx = enc.reg(idx());
+ short src = enc.reg(val());
+
+ int imm_op = x86_64_v2.selectOpcodeForImmStore(_imm);
+ if(src == -1 && _imm != 0) {
+ if(imm_op == -1) {enc.add1(x86_64_v2.rex(src, ptr, idx));enc.add1(0xC7); }
+ else enc.add1(imm_op);
+ } else {
+ if(_declaredType == SONTypeFloat.F32) {src -= (short)x86_64_v2.XMM_OFFSET; enc.add1(0xF3); enc.add1(0x0F); enc.add1(0x11);}
+ else if(_declaredType == SONTypeFloat.F64) {src -= (short)x86_64_v2.XMM_OFFSET; enc.add1(0xF2); enc.add1(0x0F); enc.add1(0x11);}
+ else if(_declaredType.log_size() == 0) enc.add1(0x88);
+ else if(_declaredType.log_size() == 1) enc.add1(0x89);
+ else if(_declaredType.log_size() == 2) enc.add1(0x89);
+ else if(_declaredType.log_size() == 3) {enc.add1(x86_64_v2.rex(src, ptr, idx)); enc.add1(0x89);}
+ }
+
+ x86_64_v2.indirectAdr(_scale, idx, ptr, _off, src, enc);
+ if( src == -1 ) {
+ switch (x86_64_v2.imm_size(_imm)) {
+ case 8: enc.add1(_imm); break;
+ case 16: enc.add2(_imm); break;
+ case 32: enc.add4(_imm); break;
+ case 64: enc.add8(_imm); break;
+ }
+ }
+ }
+
+ // 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SubFX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SubFX86.java
new file mode 100644
index 0000000..2b03bc0
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SubFX86.java
@@ -0,0 +1,31 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+
+public class SubFX86 extends MachConcreteNode implements MachNode {
+ SubFX86( Node subf) { super(subf); }
+ @Override public String op() { return "subf"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.XMASK; }
+ @Override public int twoAddress() { return 1; }
+
+ @Override public void encoding( Encoding enc ) {
+ // F2 0F 5C /r SUBSD xmm1, xmm2/m64
+ short dst = (short)(enc.reg(this ) - x86_64_v2.XMM_OFFSET);
+ short src = (short)(enc.reg(in(2)) - x86_64_v2.XMM_OFFSET);
+ // Fopcode
+ enc.add1(0xF2);
+ // rex prefix must come next (REX.W is not set)
+ x86_64_v2.rexF(dst, src, 0, false, enc);
+
+ enc.add1(0x0F).add1(0x5C);
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src));
+ }
+
+ // General form: "subf dst -= src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" -= ").p(code.reg(in(2)));
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SubX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SubX86.java
new file mode 100644
index 0000000..e5156a4
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SubX86.java
@@ -0,0 +1,10 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.nodes.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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/TFPX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/TFPX86.java
new file mode 100644
index 0000000..4861b8d
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/TFPX86.java
@@ -0,0 +1,66 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+import com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv.riscv;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONType;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFunPtr;
+
+// Function constants
+public class TFPX86 extends ConstantNode implements MachNode, RIPRelSize {
+ TFPX86( ConstantNode con ) { super(con); }
+ @Override public String op() {
+ return _con == SONType.NIL ? "xor" : "ldx";
+ }
+ @Override public boolean isClone() { return true; }
+ @Override public Node copy() { return new TFPX86(this); }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ // Zero-set uses XOR kills flags
+ @Override public RegMask killmap() {
+ return _con == SONType.NIL ? x86_64_v2.FLAGS_MASK : null;
+ }
+
+ @Override public void encoding( Encoding enc ) {
+ short dst = enc.reg(this);
+ // Short form for zero
+ if( _con==SONType.NIL ) {
+ // XOR dst,dst. Can skip REX is dst is low 8, makes this a 32b
+ // xor, which will also zero the high bits.
+ if( dst >= 8 ) enc.add1(x86_64_v2.rex(dst, dst, 0));
+ enc.add1(0x33); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, dst));
+ return;
+ }
+
+ // lea to load function pointer address
+ // lea rax, [rip+disp32]
+ enc.relo(this);
+ enc.add1(x86_64_v2.rex(dst, 0, 0));
+ enc.add1(0x8D); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.INDIRECT, dst, 0b101));
+ enc.add4(0);
+ }
+
+ @Override public byte encSize(int delta) {
+ return 7;
+ }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ enc.patch4(opStart + 3, delta - 7);
+ }
+
+ // Human-readable form appended to the SB. Things like the encoding,
+ // indentation, leading address or block labels not printed here.
+ // Just something like "ld4\tR17=[R18+12] // Load array base".
+ // General form: "op\tdst=src+src"
+ @Override public void asm(CodeGen code, SB sb) {
+ String reg = code.reg(this);
+ if( _con == SONType.NIL )
+ sb.p(reg).p(",").p(reg);
+ else
+ _con.print(sb.p(reg).p(" = #"));
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/UJmpX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/UJmpX86.java
new file mode 100644
index 0000000..c70a884
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/UJmpX86.java
@@ -0,0 +1,53 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.*;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONType;
+import java.util.BitSet;
+
+// unconditional jump
+public class UJmpX86 extends CFGNode implements MachNode, RIPRelSize {
+ UJmpX86() { }
+ @Override public String op() { return "jmp"; }
+ @Override public String label() { return op(); }
+ @Override public StringBuilder _print1( StringBuilder sb, BitSet visited ) {
+ return sb.append("jmp ");
+ }
+ @Override public RegMask regmap(int i) {return null; }
+ @Override public RegMask outregmap() { return null; }
+ @Override public SONType compute() { throw Utils.TODO(); }
+ @Override public Node idealize() { throw Utils.TODO(); }
+ @Override public void encoding( Encoding enc ) {
+ enc.jump(this,uctrl());
+ // Short-form jump
+ enc.add1(0xEB).add1(0);
+ //// E9 cd JMP rel32
+ //enc.add1(0xE9).add4(0);
+ }
+
+ // Delta is from opcode start, but X86 measures from the end of the 2-byte encoding
+ @Override public byte encSize(int delta) { return (byte)(x86_64_v2.imm8(delta-2) ? 2 : 5); }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ byte[] bits = enc.bits();
+ if( opLen==2 ) {
+ assert bits[opStart] == (byte)0xEB;
+ delta -= 2; // Offset from opcode END
+ assert (byte)delta==delta;
+ bits[opStart+1] = (byte)delta;
+ } else {
+ assert bits[opStart] == (byte)0xEB;
+ bits[opStart] = (byte)0xE9; // Long form
+ delta -= 5; // Offset from opcode END
+ enc.patch4(opStart+1,delta);
+ }
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ CFGNode target = uctrl();
+ assert target.nOuts() > 1; // Should optimize jmp to empty targets
+ sb.p(label(target));
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/XorIX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/XorIX86.java
new file mode 100644
index 0000000..ab7ceb8
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/XorIX86.java
@@ -0,0 +1,11 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.nodes.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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/XorX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/XorX86.java
new file mode 100644
index 0000000..155c5ba
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/XorX86.java
@@ -0,0 +1,11 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.nodes.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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/x86_64_v2.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/x86_64_v2.java
new file mode 100644
index 0000000..a7691d7
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/x86_64_v2.java
@@ -0,0 +1,678 @@
+package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2;
+
+import com.compilerprogramming.ezlang.compiler.Utils;
+import com.compilerprogramming.ezlang.compiler.codegen.*;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+import com.compilerprogramming.ezlang.compiler.sontypes.*;
+
+import java.io.ByteArrayOutputStream;
+
+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;
+
+ 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);
+
+ static final long SPILLS = -(1L << MAX_REG);
+ static final RegMask SPLIT_MASK = new RegMask(WR_BITS | FP_BITS | (1L< 0x84;
+ case "!=" -> 0x85;
+ case ">" -> 0x8F;
+ case "<" -> 0x8C;
+ case "<=" -> 0x8E;
+ case ">=" -> 0X8D;
+ default -> throw new IllegalArgumentException("Too many arguments");
+ };
+ }
+
+ public static int modrm(MOD mod, int reg, int m_r) {
+ // combine all the bits
+ return (mod.ordinal() << 6) | ((reg & 0x07) << 3) | m_r & 0x07;
+ }
+
+ // 00 000 000
+ // same bit-layout as modrm
+ public static int sib(int scale, int index, int base) {
+ return (scale << 6) | ((index & 0x07) << 3) | base & 0x07;
+ }
+
+ // reg1 is reg(R)
+ // reg2 is r/mem(B)
+ // reg3 is X(index)
+ // reg4 is X(base)
+
+ // 0 denotes no direct register
+ public static int rex(int reg, int ptr, int idx, boolean wide) {
+ // assuming 64 bit by default so: 0100 1000
+ int rex = wide ? REX_W : REX;
+ if( 8 <= reg && reg <= 15 ) rex |= 0b00000100; // REX.R
+ if( 8 <= ptr && ptr <= 15 ) rex |= 0b00000001; // REX.B
+ if( 8 <= idx && idx <= 15 ) rex |= 0b00000010; // REX.X
+ return rex;
+ }
+ // return opcode for optimised immediate store
+ public static int selectOpcodeForImmStore(long imm) {
+ if(imm8(imm)) return 0xC6;
+ if(imm16(imm)) return 0xC7;
+ if(imm32(imm)) return 0xC7;
+ return -1;
+ }
+ // return the size of the immediate
+ public static int imm_size(long imm) {
+ if(imm8(imm)) return 8;
+ if(imm16(imm)) return 16;
+ if(imm32(imm)) return 32;
+ return 64;
+ }
+
+ public static int rex(int reg, int ptr, int idx) {
+ return rex(reg, ptr, idx, true);
+ }
+
+ // rex for floats. Return size (0 or 1)
+ // don't need to use REX if 0x40.
+ public static byte rexF(int reg, int ptr, int idx, boolean wide, Encoding enc) {
+ int rex = rex(reg, ptr, idx, wide);
+ if (rex == REX) return 0;
+ enc.add1(rex);
+ return 1;
+ }
+ // Function used for encoding indirect memory addresses
+ // Does not always generate SIB byte e.g index == -1.
+ // -1 denotes empty value, not set - note 0 is different from -1 as it can represent rax.
+ // Looks for best mod locally
+ public static void indirectAdr( int scale, short index, short base, int offset, int reg, Encoding enc ) {
+ // Assume indirect
+ assert 0 <= base && base < 16;
+ assert index != RSP;
+
+ MOD mod = MOD.INDIRECT;
+ // is 1 byte enough or need more?
+ if( offset != 0 )
+ mod = imm8(offset)
+ ? MOD.INDIRECT_disp8
+ : MOD.INDIRECT_disp32;
+
+ // needs to pick optimal displacement mod if we want to encode base
+ if( mod == MOD.INDIRECT && (base == RBP || base == R13) )
+ mod = MOD.INDIRECT_disp8;
+
+ // special encoding for [base +offset]
+ if( index == -1 ) {
+ // Case for mov reg, [disp] (load)
+ enc.add1(modrm(mod, reg == -1 ? 0 : reg, base));
+ } else {
+ // rsp is hard-coded here(0x04)
+ enc.add1(modrm(mod, reg, 0x04));
+ enc.add1(sib(scale, index, base));
+ }
+
+ if( mod == MOD.INDIRECT_disp8 ) {
+ enc.add1(offset);
+ } else if( mod == MOD.INDIRECT_disp32 ) {
+ enc.add4(offset);
+ }
+ }
+
+ // Calling conv metadata
+ public int GPR_COUNT_CONV_WIN64 = 4; // RCX, RDX, R9, R9
+ public int XMM_COUNT_CONV_WIN64 = 4; // XMM0L, XMM1L, XMM2L, XMM3L
+
+ public int GPR_COUNT_CONV_SYSTEM_V = 6; // RDI, RSI, RDX, RCX, R8, R9
+ public int XMM_COUNT_CONV_SYSTEM_V = 4; // XMM0, XMM1, XMM2, XMM3 ....
+ // Human-readable name for a register number, e.g. "RAX".
+ // Hard crash for bad register number, fix yer bugs!
+ public static final String[] REGS = new String[]{
+ "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",
+ };
+ @Override public String reg( int reg ) {
+ return reg < REGS.length ? REGS[reg] : "[rsp+"+(reg-REGS.length)*8+"]";
+ }
+
+ // Stack slots, in units of 8 bytes.
+ @Override public int stackSlot( int reg ) {
+ return reg < REGS.length ? -1 : reg-REGS.length;
+ }
+
+
+ // WIN64(param passing)
+ static RegMask[] CALLINMASK_WIN64 = new RegMask[] {
+ RCX_MASK,
+ RDX_MASK,
+ R08_MASK,
+ R09_MASK,
+ };
+
+ // SystemV(param passing)
+ static RegMask[] CALLINMASK_SYSTEMV = new RegMask[] {
+ RDI_MASK,
+ RSI_MASK,
+ RDX_MASK,
+ RCX_MASK,
+ R08_MASK,
+ R09_MASK,
+ };
+
+ // Limit of float args passed in registers
+ static RegMask[] XMMS4 = new RegMask[]{
+ new RegMask(XMM0), new RegMask(XMM1), new RegMask(XMM2), new RegMask(XMM3),
+ };
+
+ // Map from function signature and argument index to register.
+ // Used to set input registers to CallNodes, and ParmNode outputs.
+ static RegMask callInMask( SONTypeFunPtr tfp, int idx ) {
+ if( idx==0 ) return RPC_MASK;
+ if( idx==1 ) return null;
+ // Count floats in signature up to index
+ int fcnt=0;
+ for( int i=2; i CALLINMASK_SYSTEMV;
+ case "Win64" -> CALLINMASK_WIN64;
+ default -> throw new IllegalArgumentException("Unknown calling convention: "+CodeGen.CODE._callingConv);
+ };
+ if( idx-2-fcnt < cargs.length )
+ return cargs[idx-2-fcnt];
+ }
+ throw Utils.TODO(); // Pass on stack slot
+ }
+
+ // Return the max stack slot used by this signature, or 0
+ static short maxSlot( SONTypeFunPtr tfp ) {
+ // Count floats in signature up to index
+ int fcnt=0;
+ for( int i=0; i= XMMS4.length )
+ throw Utils.TODO();
+ RegMask[] cargs = switch( CodeGen.CODE._callingConv ) {
+ case "SystemV" -> CALLINMASK_SYSTEMV;
+ case "Win64" -> CALLINMASK_WIN64;
+ default -> throw new IllegalArgumentException("Unknown calling convention: "+CodeGen.CODE._callingConv);
+ };
+ if( tfp.nargs()-fcnt >= cargs.length )
+ throw Utils.TODO();
+ return 0; // No stack args
+ }
+
+ // Return single int/ptr register. Used by CallEnd output and Return input.
+ static RegMask retMask( SONTypeFunPtr tfp ) {
+ return tfp.ret() instanceof SONTypeFloat ? XMM0_MASK : RAX_MASK;
+ }
+
+
+ // caller saved(systemv)
+ static final long SYSTEM5_CALLER_SAVE =
+ (1L << RAX) | (1L << RCX) | (1L << RDX) |
+ (1L << RDI) | (1L << RSI) |
+ (1L << R08) | (1L << R09) | (1L << R10) | (1L << R11) |
+ (1L << FLAGS) | // Flags are killed
+ // All FP regs are killed
+ FP_BITS;
+ static final RegMask SYSTEM5_CALLER_SAVE_MASK = new RegMask(SYSTEM5_CALLER_SAVE);
+
+ // caller saved(win64)
+ static final long WIN64_CALLER_SAVE =
+ (1L<< RAX) | (1L<< RCX) | (1L<< RDX) |
+ (1L<< R08) | (1L<< R09) | (1L<< R10) | (1L<< R11) |
+ (1L< SYSTEM5_CALLER_SAVE_MASK;
+ case "Win64" -> WIN64_CALLER_SAVE_MASK;
+ default -> throw new IllegalArgumentException("Unknown calling convention: " + CodeGen.CODE._callingConv);
+ };
+ }
+
+ @Override public RegMask callerSave() { return x86CallerSave(); }
+
+ static final RegMask SYSTEM5_CALLEE_SAVE_MASK;
+ static final RegMask WIN64_CALLEE_SAVE_MASK;
+
+ static {
+ long callee = ~SYSTEM5_CALLER_SAVE;
+ // Remove the spills
+ callee &= (1L< SYSTEM5_CALLEE_SAVE_MASK;
+ case "Win64" -> WIN64_CALLEE_SAVE_MASK;
+ default -> throw new IllegalArgumentException("Unknown calling convention: "+CodeGen.CODE._callingConv);
+ };
+ }
+ @Override public RegMask calleeSave() { return x86CalleeSave(); }
+
+ static final RegMask[] WIN64_RET_MASKS, SYS5_RET_MASKS;
+ static {
+ WIN64_RET_MASKS = makeRetMasks( WIN64_CALLEE_SAVE_MASK);
+ SYS5_RET_MASKS = makeRetMasks(SYSTEM5_CALLEE_SAVE_MASK);
+ }
+ private static RegMask[] makeRetMasks(RegMask mask) {
+ int nSaves = mask.size();
+ RegMask[] masks = new RegMask[4 + nSaves];
+ masks[0] = null;
+ masks[1] = null; // Memory
+ masks[2] = null; // Varies, either XMM0 or RAX
+ masks[3] = RPC_MASK;
+ short reg = mask.firstReg();
+ for( int i=0; i SYS5_RET_MASKS;
+ case "Win64" -> WIN64_RET_MASKS;
+ default -> throw new IllegalArgumentException("Unknown calling convention: "+CodeGen.CODE._callingConv);
+ };
+ return masks[i];
+ }
+
+ // Create a split op; any register to any register, including stack slots
+ @Override public SplitNode split(String kind, byte round, LRG lrg) {
+ return new SplitX86(kind, round);
+ }
+
+ // Return a MachNode unconditional branch
+ @Override public CFGNode jump() {
+ return new UJmpX86();
+ }
+
+ // Break an infinite loop
+ @Override
+ public IfNode never(CFGNode ctrl) {
+ throw Utils.TODO();
+ }
+
+ // Instruction selection
+ @Override
+ public Node instSelect(Node n) {
+ return switch (n) {
+ case AddFNode addf -> addf(addf);
+ case AddNode add -> add(add);
+ case AndNode and -> and(and);
+ case BoolNode bool -> cmp(bool);
+ case CallEndNode cend -> new CallEndX86(cend);
+ case CallNode call -> call(call);
+ case CastNode cast -> new CastX86(cast);
+ case CProjNode c -> new CProjNode(c);
+ case ConstantNode con -> con(con);
+ case DivFNode divf -> new DivFX86(divf);
+ case DivNode div -> new DivX86(div);
+ case FunNode fun -> new FunX86(fun);
+ case IfNode iff -> jmp(iff);
+ case LoadNode ld -> ld(ld);
+ case MemMergeNode mem -> new MemMergeNode(mem);
+ case MulFNode mulf -> new MulFX86(mulf);
+ case MulNode mul -> mul(mul);
+ case NewNode nnn -> new NewX86(nnn);
+ case NotNode not -> new NotX86(not);
+ case OrNode or -> or(or);
+ case ParmNode parm -> new ParmX86(parm);
+ case PhiNode phi -> new PhiNode(phi);
+ case ProjNode prj -> prj(prj);
+ case ReadOnlyNode read -> new ReadOnlyNode(read);
+ case ReturnNode ret -> new RetX86(ret, ret.fun());
+ case SarNode sar -> sar(sar);
+ case ShlNode shl -> shl(shl);
+ case ShrNode shr -> shr(shr);
+ case StartNode start -> new StartNode(start);
+ case StopNode stop -> new StopNode(stop);
+ case StoreNode st -> st(st);
+ case SubFNode subf -> new SubFX86(subf);
+ case SubNode sub -> sub(sub);
+ case ToFloatNode tfn -> i2f8(tfn);
+ case 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 imm16(long imm) {return -32768 <= imm && imm <= 32767; }
+ public static boolean imm32( long imm ) { return (int)imm==imm; }
+
+ // Attempt a full LEA-style break down.
+ private Node add(AddNode add) {
+ Node lhs = add.in(1);
+ Node rhs = add.in(2);
+ if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && ld._declaredType.log_size() >= 3)
+ return new AddMemX86(add, address(ld), ld.ptr(), idx, off, scale, imm(rhs), val);
+
+// if(rhs instanceof LoadNode ld && ld.nOuts() == 1 && ld._declaredType.log_size() >= 3) {
+// throw Utils.TODO(); // Swap load sides
+// }
+
+ // Attempt a full LEA-style break down.
+ // Returns one of AddX86, AddIX86, LeaX86, or LHS
+ if( rhs instanceof ConstantNode off && off._con instanceof SONTypeInteger toff ) {
+ long imm = toff.value();
+ assert imm!=0; // Folded in peeps
+ if( (int)imm != imm ) // Full 64bit immediate
+ return new AddX86(add);
+ // Now imm <= 32bits
+ if( lhs instanceof AddNode ladd )
+ // ((base + (idx << scale)) + off)
+ return _lea(add, ladd.in(1), ladd.in(2), (int)imm);
+ if( lhs instanceof ShlNode shift )
+ // (idx << scale) + off; no base
+ return _lea(add, null, shift, (int)imm);
+
+ // lhs + rhs1
+ return new AddIX86(add, (int)imm);
+ }
+ return _lea(add, lhs, rhs, 0);
+ }
+
+
+ private Node addf(AddFNode addf) {
+ if(addf.in(1) instanceof LoadNode ld && ld.nOuts() == 1)
+ return new AddFMemX86(addf, address(ld), ld.ptr(), idx, off, scale, addf.in(2));
+
+// if(addf.in(2) instanceof LoadNode ld && ld.nOuts() == 1)
+// throw Utils.TODO(); // Swap load sides
+
+ return new AddFX86(addf);
+ }
+
+
+ private Node _lea(Node add, Node base, Node idx, int off) {
+ int scale = 0;
+ if( base instanceof ShlNode && !(idx instanceof ShlNode) )
+ throw Utils.TODO(); // Bug in canonicalization, should on RHS
+ if( idx instanceof ShlNode shift && shift.in(2) instanceof ConstantNode shfcon &&
+ shfcon._con instanceof SONTypeInteger 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 SONTypeInteger ti && imm32(ti.value()) )
+ return new AndIX86(and, (int)ti.value());
+ return new AndX86(and);
+ }
+
+ private Node call(CallNode call) {
+ return call.fptr() instanceof ConstantNode con && con._con instanceof SONTypeFunPtr tfp
+ ? new CallX86(call, tfp)
+ : new CallRX86(call);
+ }
+
+ // Because X86 flags, a normal ideal Bool is 2 X86 ops: a "cmp" and at "setz".
+ // Ideal If reading from a setz will skip it and use the "cmp" instead.
+ private static boolean invert;
+ private Node cmp( BoolNode bool ) {
+ invert = false;
+ Node cmp = _cmp(bool);
+ return new SetX86(cmp, invert ? IfNode.invert(bool.op()) : bool.op());
+ }
+
+ private Node _cmp(BoolNode bool) {
+ // Float variant
+ if( bool.isFloat() )
+ return new CmpFX86(bool);
+ Node lhs = bool.in(1);
+ Node rhs = bool.in(2);
+
+ // Vs memory
+ if( lhs instanceof LoadNode ld && ld.nOuts() == 1 )
+ return new CmpMemX86(bool, address(ld), ld.ptr(), idx, off, scale, imm(rhs), val, false);
+
+ if( rhs instanceof LoadNode ld && ld.nOuts() == 1 )
+ 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 SONTypeInteger ti && imm32(ti.value()) )
+ return new CmpIX86(bool, (int)ti.value());
+
+ if( lhs instanceof ConstantNode con && con._con instanceof SONTypeInteger ti && imm32(ti.value()) ) {
+ invert = true;
+ return new CmpIX86(bool, (int)ti.value(), 0.5);
+ }
+
+ // x vs y
+ return new CmpX86(bool);
+ }
+
+ private Node con(ConstantNode con) {
+ if(!con._con.isConstant()) return new ConstantNode(con); // Default unknown caller inputs
+ return switch (con._con) {
+ case SONTypeInteger ti -> new IntX86(con);
+ case SONTypeFloat tf -> new FltX86(con);
+ case SONTypeFunPtr tfp -> new TFPX86(con);
+ case SONTypeMemPtr tmp -> throw Utils.TODO();
+ case SONTypeNil tn -> throw Utils.TODO();
+ // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code.
+ case SONType t -> t == SONType.NIL ? new IntX86(con) : new ConstantNode(con);
+ };
+ }
+
+ private Node i2f8(ToFloatNode tfn) {
+ assert tfn.in(1)._type instanceof SONTypeInteger 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
+ BoolNode bool;
+ if( iff.in(1) instanceof BoolNode bool0 ) bool = bool0;
+ else iff.setDef(1, bool=new BoolNode.EQ(iff.in(1), new ConstantNode(SONTypeInteger.ZERO)));
+ return new JmpX86(iff, bool.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 SONTypeInteger 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 SONTypeInteger ti && imm32(ti.value()) )
+ return new OrIX86(or, (int)ti.value());
+ return new OrX86(or);
+ }
+
+ private Node prj( ProjNode prj ) {
+ return new ProjX86(prj);
+ }
+
+ private Node sar(SarNode sar) {
+ if( sar.in(2) instanceof ConstantNode con && con._con instanceof SONTypeInteger ti )
+ return new SarIX86(sar, (int)(ti.value() & 0x03f) );
+ return new SarX86(sar);
+ }
+
+ private Node shl(ShlNode shl) {
+ if( shl.in(2) instanceof ConstantNode con && con._con instanceof SONTypeInteger ti )
+ return new ShlIX86(shl, (int)(ti.value() & 0x03f) );
+ return new ShlX86(shl);
+ }
+
+ private Node shr( ShrNode shr ) {
+ if( shr.in(2) instanceof ConstantNode con && con._con instanceof SONTypeInteger ti )
+ return new ShrIX86(shr, (int)(ti.value() & 0x03f) );
+ 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 &&
+ ld.in(0) == st.in(0) &&
+ ld.mem() == st.mem() &&
+ ld.ptr() == st.ptr() &&
+ ld.off() == st.off()) {
+ if( op instanceof AddNode )
+ return new MemAddX86(address(st), st.ptr(), idx, off, scale, imm(op.in(2)), val);
+ throw Utils.TODO();
+ }
+ }
+
+ return new StoreX86(address(st), st.ptr(), idx, off, scale, imm(st.val()), val);
+ }
+
+ private Node sub (SubNode sub ){
+ return sub.in(2) instanceof ConstantNode con && con._con instanceof SONTypeInteger 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 SONTypeInteger 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 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 SONTypeMemPtr; // Base ptr always, not some derived
+ if(mop.off() instanceof AddNode add && add.in(2) instanceof ConstantNode con && con._con instanceof SONTypeInteger 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 SONTypeInteger 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 SONTypeInteger 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 SONTypeInteger ti) {
+ val = null;
+ imm = (int) ti.value();
+ assert imm == ti.value(); // In 32-bit range
+ } else {
+ val = xval;
+ }
+ return imm;
+ }
+
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/print/ASMPrinter.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/print/ASMPrinter.java
new file mode 100644
index 0000000..3c6df73
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/print/ASMPrinter.java
@@ -0,0 +1,254 @@
+package com.compilerprogramming.ezlang.compiler.print;
+
+import com.compilerprogramming.ezlang.compiler.codegen.CodeGen;
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.codegen.Encoding;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONType;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFunPtr;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeMem;
+import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeRPC;
+
+public abstract class ASMPrinter {
+
+ public static SB print(SB sb, CodeGen code) {
+ if( code._cfg==null )
+ return sb.p("Need _cfg set, run after GCM");
+
+ // instruction address
+ int iadr = 0;
+ for( int i=0; i iadr )
+ iadr++;
+
+ // constant pool
+ Encoding enc = code._encoding;
+ if( enc!=null && !enc._bigCons.isEmpty() ) {
+ sb.p("--- Constant Pool ------").nl();
+ for( Node relo : enc._bigCons.keySet() ) {
+ SONType t = enc._bigCons.get(relo);
+ if( t.log_size()==3 ) {
+ sb.hex2(iadr).p(" ").hex8(enc.read8(iadr)).p(" ");
+ t.print(sb).nl();
+ iadr += 8;
+ }
+ }
+ for( Node relo : enc._bigCons.keySet() ) {
+ SONType t = enc._bigCons.get(relo);
+ if( t.log_size()==2 ) {
+ sb.hex2(iadr).p(" ").hex4(enc.read4(iadr)).fix(9,"");
+ t.print(sb).nl();
+ iadr += 4;
+ }
+ }
+ }
+
+
+ return sb;
+ }
+
+ private static int print(int iadr, SB sb, CodeGen code, FunNode fun, int cfgidx) {
+ // Function header
+ sb.nl().p("---");
+ if( fun._name != null ) sb.p(fun._name).p(" ");
+ fun.sig().print(sb);
+ sb.p("---------------------------").nl();
+
+ if( fun._frameAdjust != 0 )
+ iadr = doInst(iadr,sb,code,cfgidx,fun,true,true);
+ while( !(code._cfg.at(cfgidx) instanceof ReturnNode) )
+ iadr = doBlock(iadr,sb,code,fun,cfgidx++);
+
+ // Function separator
+ sb.p("---");
+ fun.sig().print(sb);
+ sb.p("---------------------------").nl();
+ return iadr;
+ }
+
+ static private final int opWidth = 5;
+ static private final int argWidth = 30;
+ static int doBlock(int iadr, SB sb, CodeGen code, FunNode fun, int cfgidx) {
+ final int encWidth = code._mach.defaultOpSize()*2;
+ CFGNode bb = code._cfg.at(cfgidx);
+ if( bb != fun && !(bb instanceof IfNode) && !(bb instanceof CallEndNode) && !(bb instanceof CallNode) && !(bb instanceof CProjNode && bb.in(0) instanceof CallEndNode ))
+ sb.p(label(bb)).p(":").nl();
+ if( bb instanceof CallNode ) return iadr;
+ final boolean postAlloc = code._phase.ordinal() > CodeGen.Phase.RegAlloc.ordinal();
+ final boolean postEncode= code._phase.ordinal() >=CodeGen.Phase.Encoding.ordinal();
+
+ boolean once=false;
+ for( Node n : bb.outs() ) {
+ if( !(n instanceof PhiNode phi) ) continue;
+ if( phi._type instanceof SONTypeMem || phi._type instanceof SONTypeRPC ) continue; // Nothing for the hidden ones
+ // Post-RegAlloc phi prints all on one line
+ if( postAlloc ) {
+ if( !once ) { once=true; sb.fix(4," ").p(" ").fix(encWidth,"").p(" "); }
+ sb.p(phi._label).p(':').p(code.reg(phi)).p(',');
+ } else {
+ // Pre-RegAlloc phi prints one line per
+ sb.fix(4," ").p(" ").fix(encWidth,"").p(" ").fix(opWidth,phi._label).p(" ").p(code.reg(phi));
+ if( phi.getClass() == PhiNode.class ) {
+ sb.p(" = phi( ");
+ for( int i=1; i1 )
+ break; // Has code in the block, need to jump around
+ // No code in the block, can fall through it
+ }
+ sb.hex2(iadr++).p(" ").fix(encWidth,"??").p(" ").fix(opWidth,"JMP").p(" ").fix(argWidth,label(cfg)).nl();
+ return iadr;
+ }
+
+ // ProjNodes following a multi (e.g. Call or New results),
+ // get indent slightly and just print their index & node#
+ if( n instanceof ProjNode proj ) {
+ if( proj._type instanceof SONTypeMem ) return iadr; // Nothing for the hidden ones
+ sb.fix(4," ").p(" ").fix(encWidth,"").p(" ").fix(opWidth,proj._label==null ? "---" : proj._label).p(" ").p(code.reg(n)).nl();
+ return iadr;
+ }
+
+ // ADDR ENCODING Op--- dst = src op src // Comment
+ // 1234 abcdefgh ld4 RAX = [Rbase + off] // Comment
+ sb.hex2(iadr).p(" ");
+
+ // Encoding
+ int fatEncoding = 0;
+ if( code._encoding != null ) {
+ int size = code._encoding._opLen[n._nid];
+ for( int i=0; i>1); i++ )
+ sb.hex1(code._encoding._bits.buf()[iadr++]);
+ for( int i=size*2; i>1); // Not-printed parts of encoding
+ } else
+ sb.fix(encWidth,"");
+ sb.p(" ");
+
+ // Op; generally "ld4" or "call"
+ sb.fix(opWidth, n instanceof MachNode mach ? mach.op() : n.label()).p(" ");
+
+ // General asm args
+ String isMultiOp = null;
+ if( n instanceof MachNode mach ) {
+ int old = sb.len();
+ mach.asm(code,sb);
+ int len = sb.len();
+ isMultiOp = isMultiOp(sb,old,len);
+ sb.fix(argWidth-(len-old),""); // Pad out
+
+ } else if( !(n._type instanceof SONTypeMem) ) {
+ // Room for some inputs
+ sb.fix(5, n._nid+":" );
+ int off = 0;
+ for( int i=0; i= argWidth ) break;
+ }
+ if( off < argWidth ) sb.fix(argWidth-off,"");
+ }
+ sb.p(" ");
+
+ // Comment
+ String comment = n.comment();
+ if( comment!=null ) sb.p("// ").p(comment);
+
+ sb.nl();
+
+ // Printing more op bits than fit
+ if( isMultiOp != null && code._encoding != null ) {
+ // Multiple ops, template style, no RA, no scheduling. Print out
+ // one-line-per-newline, with encoding bits up front.
+ int size = code._encoding._opLen[n._nid];
+ int off = Math.min(size,dopz);
+ while( isMultiOp!=null ) {
+ sb.hex2(iadr).p(" ");
+ int len = Math.min(size-off,dopz);
+ for( int i=0; i 0 ) {
+ // Extra bytes past the default encoding width, all put on a line by
+ // themselves. X86 special for super long encodings
+ sb.hex2(iadr).p(" ");
+ for( int i=0; i xScopes) {
+
+ // Since the graph has cycles, we need to create a flat list of all the
+ // nodes in the graph.
+ Collection all = findAll(xScopes, stop, scope);
+ StringBuilder sb = new StringBuilder();
+ sb.append("digraph chapter18 {\n");
+ sb.append("/*\n");
+ sb.append(stop._src);
+ sb.append("\n*/\n");
+
+ // To keep the Scopes below the graph and pointing up into the graph we
+ // need to group the Nodes in a subgraph cluster, and the scopes into a
+ // different subgraph cluster. THEN we can draw edges between the
+ // scopes and nodes. If we try to cross subgraph cluster borders while
+ // still making the subgraphs DOT gets confused.
+ sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes
+
+ // CNC - turned off Apr/8/2024, gives more flex in the layout and
+ // removes some of the more ludicrous layout choices.
+ // Preserve node input order
+ //sb.append("\tordering=\"in\";\n");
+
+ // Merge multiple edges hitting the same node. Makes common shared
+ // nodes much prettier to look at.
+ sb.append("\tconcentrate=\"true\";\n");
+
+ // Force nested scopes to order
+ sb.append("\tcompound=\"true\";\n");
+
+ // Just the Nodes first, in a cluster no edges
+ nodes(sb, all);
+
+ // Now the scopes, in a cluster no edges
+ if( xScopes != null )
+ for( ScopeNode xscope : xScopes )
+ if( !xscope.isDead() )
+ scope( sb, xscope );
+
+ // Walk the Node edges
+ nodeEdges(sb, all);
+
+ // Walk the active Scope edges
+ if( xScopes != null )
+ for( ScopeNode xscope : xScopes )
+ if( !xscope.isDead() )
+ scopeEdges( sb, xscope );
+
+ sb.append("}\n");
+ return sb.toString();
+ }
+
+ private void nodesByCluster(StringBuilder sb, boolean doCtrl, Collection all) {
+ if (!_separateControlCluster && doCtrl) // all nodes in 1 cluster
+ return;
+ // Just the Nodes first, in a cluster no edges
+ sb.append(doCtrl ? "\tsubgraph cluster_Controls {\n" : "\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name
+ for (Node n : all) {
+ if (n instanceof ProjNode || n instanceof CProjNode || n instanceof MemMergeNode || n==Compiler.XCTRL)
+ continue; // Do not emit, rolled into MultiNode or Scope cluster already
+ if( _separateControlCluster && doCtrl && !(n instanceof CFGNode) ) continue;
+ if( _separateControlCluster && !doCtrl && (n instanceof CFGNode) ) continue;
+ sb.append("\t\t").append(n.uniqueName()).append(" [ ");
+ String lab = n.glabel();
+ if (n instanceof MultiNode) {
+ // Make a box with the MultiNode on top, and all the projections on the bottom
+ sb.append("shape=plaintext label=<\n");
+ sb.append("\t\t\t\n");
+ sb.append("\t\t\t| ").append(lab).append(" | \n");
+ sb.append("\t\t\t");
+ boolean doProjTable = false;
+ n._outputs.sort((x,y) -> x instanceof ProjNode xp && y instanceof ProjNode yp ? (xp._idx - yp._idx) : (x._nid - y._nid));
+ for (Node use : n._outputs) {
+ if (use instanceof ProjNode proj) {
+ if (!doProjTable) {
+ doProjTable = true;
+ sb.append("").append("\n");
+ sb.append("\t\t\t\t").append("\n");
+ sb.append("\t\t\t\t");
+ }
+ sb.append("| ").append(proj.glabel()).append(" | ");
+ }
+ if (use instanceof CProjNode proj) {
+ if (!doProjTable) {
+ doProjTable = true;
+ sb.append("").append("\n");
+ sb.append("\t\t\t\t").append("\n");
+ sb.append("\t\t\t\t");
+ }
+ sb.append("| ").append(proj.glabel()).append(" | ");
+ }
+ }
+ if (doProjTable) {
+ sb.append(" ").append("\n");
+ sb.append("\t\t\t\t ").append("\n");
+ sb.append("\t\t\t | ");
+ }
+ sb.append(" \n");
+ sb.append("\t\t\t >\n\t\t");
+
+ } else {
+ // control nodes have box shape
+ // other nodes are ellipses, i.e. default shape
+ if( n instanceof CFGNode ) sb.append("shape=box style=filled fillcolor=yellow ");
+ else if (n instanceof PhiNode) sb.append("style=filled fillcolor=lightyellow ");
+ sb.append("label=\"").append(lab).append("\" ");
+ }
+ sb.append("];\n");
+ }
+ if (!_separateControlCluster) {
+ // Force Region & Phis to line up
+ for (Node n : all) {
+ if (n instanceof RegionNode region) {
+ sb.append("\t\t{ rank=same; ");
+ sb.append(region.uniqueName()).append(";");
+ for (Node phi : region._outputs)
+ if (phi instanceof PhiNode) sb.append(phi.uniqueName()).append(";");
+ sb.append("}\n");
+ }
+ }
+ }
+ sb.append("\t}\n"); // End Node cluster
+ }
+
+ private void nodes(StringBuilder sb, Collection all) {
+ nodesByCluster(sb, true, all);
+ nodesByCluster(sb, false, all);
+ }
+
+ // Build a nested scope display, walking the _prev edge
+ private void scope( StringBuilder sb, ScopeNode scope ) {
+ sb.append("\tnode [shape=plaintext];\n");
+ int last = scope.nIns();
+ int max = scope._lexSize.size();
+ for( int i = 0; i < max; i++ ) {
+ int level = max-i-1;
+ String scopeName = makeScopeName(scope, level);
+ sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name
+ // Special for memory ScopeMinNode
+ if( level==0 ) {
+ MemMergeNode n = scope.mem();
+ sb.append("\t\t").append(n.uniqueName()).append(" [label=\"").append(n.glabel()).append("\"];\n");
+ }
+ sb.append("\t\t").append(scopeName).append(" [label=<\n");
+ sb.append("\t\t\t\n");
+ // Add the scope level
+ sb.append("\t\t\t| ").append(level).append(" | ");
+ int lexStart=scope._lexSize.at(level);
+ for( int j=lexStart; j").append(v._name).append("");
+ }
+ last = lexStart;
+ sb.append(" \n");
+ sb.append("\t\t\t >];\n");
+ }
+ // Scope clusters nest, so the graphics shows the nested scopes, so
+ // they are not closed as they are printed; so they just keep nesting.
+ // We close them all at once here.
+ sb.append( "\t}\n".repeat( max ) ); // End all Scope clusters
+ }
+
+ private String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; }
+ private String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; }
+
+ // Walk the node edges
+ private void nodeEdges(StringBuilder sb, Collection all) {
+ // All them edge labels
+ sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n");
+ for( Node n : all ) {
+ // Do not display the Constant->Start edge;
+ // ProjNodes handled by Multi;
+ // ScopeNodes are done separately
+ if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeNode || n==Compiler.XCTRL )
+ continue;
+ for( int i=0; idef edge from Phi to Region.
+ sb.append('\t').append(n.uniqueName());
+ sb.append(" -> ");
+ sb.append(def.uniqueName());
+ sb.append(" [style=dotted taillabel=").append(i).append("];\n");
+ } else if( def != null ) {
+ // Most edges land here use->def
+ sb.append('\t').append(n.uniqueName()).append(" -> ");
+ if( def instanceof CProjNode proj ) {
+ String mname = proj.ctrl().uniqueName();
+ sb.append(mname).append(":p").append(proj._idx);
+ } else if( def instanceof ProjNode proj ) {
+ String mname = proj.in(0).uniqueName();
+ sb.append(mname).append(":p").append(proj._idx);
+ } else sb.append(def.uniqueName());
+ // Number edges, so we can see how they track
+ sb.append("[taillabel=").append(i);
+ // The edge from New to ctrl is just for anchoring the New
+ if ( n instanceof NewNode )
+ sb.append(" color=green");
+ // control edges are colored red
+ else if( def instanceof CFGNode )
+ sb.append(" color=red");
+ else if( def.isMem() )
+ sb.append(" color=blue");
+ // Backedges do not add a ranking constraint
+ if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) )
+ sb.append(" constraint=false");
+ sb.append("];\n");
+ }
+ }
+ }
+ }
+
+ // Walk the scope edges
+ private void scopeEdges( StringBuilder sb, ScopeNode scope ) {
+ sb.append("\tedge [style=dashed color=cornflowerblue];\n");
+ int level=0;
+ for( var v : scope._vars ) {
+ Node def = scope.in(v._idx);
+ while( def instanceof ScopeNode lazy )
+ def = lazy.in(v._idx);
+ if( def==null ) continue;
+ while( level < scope._lexSize.size() && v._idx >= scope._lexSize.at(level) )
+ level++;
+ String scopeName = makeScopeName(scope, level-1);
+ sb.append("\t")
+ .append(scopeName).append(":")
+ .append('"').append(makePortName(scopeName, v._name)).append('"') // wrap port name with quotes because $ctrl is not valid unquoted
+ .append(" -> ");
+ if( def instanceof CProjNode proj ) {
+ sb.append(def.in(0).uniqueName()).append(":p").append(proj._idx);
+ } else if( def instanceof ProjNode proj ) {
+ sb.append(def.in(0).uniqueName()).append(":p").append(proj._idx);
+ } else sb.append(def.uniqueName());
+ sb.append(";\n");
+ }
+ }
+
+ /**
+ * Finds all nodes in the graph.
+ */
+ public static Collection findAll(Stack ss, Node... ns) {
+ final HashMap all = new HashMap<>();
+ for( Node n : ns )
+ walk(all, n);
+ if( ss != null )
+ for( Node n : ss )
+ walk(all, n);
+ return all.values();
+ }
+
+ /**
+ * Walk a subgraph and populate distinct nodes in the all list.
+ */
+ private static void walk(HashMap all, Node n) {
+ if(n == null ) return;
+ if (all.get(n._nid) != null) return; // Been there, done that
+ all.put(n._nid, n);
+ for (Node c : n._inputs)
+ walk(all, c);
+ for( Node c : n._outputs )
+ walk(all, c);
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/print/IRPrinter.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/print/IRPrinter.java
new file mode 100644
index 0000000..a69581d
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/print/IRPrinter.java
@@ -0,0 +1,327 @@
+package com.compilerprogramming.ezlang.compiler.print;
+
+import com.compilerprogramming.ezlang.compiler.Ary;
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.Utils;
+import com.compilerprogramming.ezlang.compiler.codegen.CodeGen;
+import com.compilerprogramming.ezlang.compiler.nodes.*;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.HashMap;
+
+public abstract class IRPrinter {
+
+ // Print a node on 1 line, columnar aligned, as:
+ // NNID NNAME DDEF DDEF [[ UUSE UUSE ]] TYPE
+ // 1234 sssss 1234 1234 1234 1234 1234 1234 tttttt
+ public static SB printLine( Node n, SB sb ) {
+ sb.p("%4d %-7.7s ".formatted(n._nid,n.label()));
+ if( n._inputs==null )
+ return sb.p("DEAD\n");
+ for( Node def : n._inputs )
+ sb.p(def==null ? "____" : "%4d".formatted(def._nid))
+ // Lazy Phi indicator
+ .p(n instanceof MemMergeNode && def instanceof MemMergeNode ? "^" : " ");
+ for( int i = n._inputs.size(); i<4; i++ )
+ sb.p(" ");
+ sb.p(" [[ ");
+ for( Node use : n._outputs )
+ sb.p(use==null ? "____ " : "%4d ".formatted(use._nid));
+ int lim = 6 - Math.max(n._inputs.size(),4);
+ for( int i = n._outputs.size(); i CodeGen.Phase.Schedule.ordinal()
+ ? _prettyPrintScheduled( node, depth )
+ : _prettyPrint( node, depth );
+ }
+
+ // Another bulk pretty-printer. Makes more effort at basic-block grouping.
+ private static String _prettyPrint( Node node, int depth ) {
+ // First, a Breadth First Search at a fixed depth.
+ BFS bfs = new BFS(node,depth);
+ // Convert just that set to a post-order
+ ArrayList rpos = new ArrayList<>();
+ BitSet visit = new BitSet();
+ for( int i=bfs._lim; i< bfs._bfs.size(); i++ )
+ postOrd( bfs._bfs.get(i), null, rpos, visit, bfs._bs);
+ // Reverse the post-order walk
+ SB sb = new SB();
+ boolean gap=false;
+ for( int i=rpos.size()-1; i>=0; i-- ) {
+ Node n = rpos.get(i);
+ if( n instanceof CFGNode || n instanceof MultiNode ) {
+ if( !gap ) sb.p("\n"); // Blank before multihead
+ if( n instanceof FunNode fun )
+ fun.sig().print(sb.p("--- ").p(fun._name==null ? "" : fun._name).p(" "),false).p("----------------------\n");
+ printLine( n, sb ); // Print head
+ while( --i >= 0 ) {
+ Node t = rpos.get(i);
+ if( !(t.in(0) instanceof MultiNode) ) { i++; break; }
+ printLine( t, sb );
+ }
+ if( n instanceof ReturnNode ret ) {
+ FunNode fun = ret.fun();
+ sb.p("--- ");
+ if( fun != null )
+ fun.sig().print(sb.p(fun._name==null ? "" : fun._name).p(" "),false);
+ sb.p("----------------------\n");
+ }
+ if( !(n instanceof CallNode) ) {
+ sb.p("\n"); // Blank after multitail
+ gap = true;
+ }
+ } else {
+ printLine( n, sb );
+ gap = false;
+ }
+ }
+ return sb.toString();
+ }
+
+ private static void postOrd(Node n, Node prior, ArrayList rpos, BitSet visit, BitSet bfs) {
+ if( !bfs.get(n._nid) )
+ return; // Not in the BFS visit
+ if( n instanceof FunNode && !(prior instanceof StartNode) )
+ return; // Only visit Fun from Start
+ if( visit.get(n._nid) ) return; // Already post-order walked
+ visit.set(n._nid);
+ // First walk the CFG, then everything
+ if( n instanceof CFGNode ) {
+ for( Node use : n._outputs )
+ // Follow CFG, not across call/function borders, and not around backedges
+ if( use instanceof CFGNode && !(n instanceof CallNode && use instanceof FunNode) &&
+ use.nOuts() >= 1 && !(use._outputs.get(0) instanceof LoopNode) )
+ postOrd(use, n, rpos,visit,bfs);
+ for( Node use : n._outputs )
+ // Follow CFG, not across call/function borders
+ if( use instanceof CFGNode && !(n instanceof CallNode && use instanceof FunNode) )
+ postOrd(use,n,rpos,visit,bfs);
+ }
+ // Follow all outputs
+ for( Node use : n._outputs )
+ if( use != null &&
+ !(n instanceof CallNode && use instanceof FunNode) &&
+ (n instanceof FunNode || !(use instanceof ParmNode)) )
+ postOrd(use, n, rpos,visit,bfs);
+ // Post-order
+ rpos.add(n);
+ }
+
+ // Breadth-first search, broken out in a class to keep in more independent.
+ // Maintains a root-set of Nodes at the limit (or past by 1 if MultiHead).
+ public static class BFS {
+ // A breadth first search, plus MultiHeads for any MultiTails
+ public final ArrayList _bfs;
+ public final BitSet _bs; // Visited members by node id
+ public final int _depth; // Depth limit
+ public final int _lim; // From here to _bfs._len can be roots for a reverse search
+ public BFS( Node base, int d ) {
+ _depth = d;
+ _bfs = new ArrayList<>();
+ _bs = new BitSet();
+
+ add(base); // Prime the pump
+ int idx=0, lim=1; // Limit is where depth counter changes
+ while( idx < _bfs.size() ) { // Ran out of nodes below depth
+ Node n = _bfs.get(idx++);
+ for( Node def : n._inputs )
+ if( def!=null && !_bs.get(def._nid) )
+ add(def);
+ if( idx==lim ) { // Depth counter changes at limit
+ if( --d < 0 )
+ break; // Ran out of depth
+ lim = _bfs.size(); // New depth limit
+ }
+ }
+ // Toss things past the limit except multi-heads
+ while( idx < _bfs.size() ) {
+ Node n = _bfs.get(idx);
+ if( n instanceof MultiNode ) idx++;
+ else del(idx);
+ }
+ // Root set is any node with no inputs in the visited set
+ lim = _bfs.size();
+ for( int i=_bfs.size()-1; i>=0; i-- )
+ if( !any_visited(_bfs.get(i)) )
+ swap( i,--lim);
+ _lim = lim;
+ }
+ void swap( int x, int y ) {
+ if( x==y ) return;
+ Node tx = _bfs.get(x);
+ Node ty = _bfs.get(y);
+ _bfs.set(x,ty);
+ _bfs.set(y,tx);
+ }
+ void add(Node n) {
+ _bfs.add(n);
+ _bs.set(n._nid);
+ }
+ void del(int idx) {
+ _bs.clear(_bfs.get(idx)._nid);
+ Utils.del(_bfs, idx);
+ }
+ boolean any_visited( Node n ) {
+ for( Node def : n._inputs )
+ if( def!=null && _bs.get(def._nid) )
+ return true;
+ return false;
+ }
+ }
+
+ public static String _prettyPrint( CodeGen code ) {
+ SB sb = new SB();
+ // Print the Start "block"
+ printLine(code._start,sb);
+ for( Node n : code._start._outputs )
+ printLine(n,sb);
+ sb.nl();
+
+ // Skip start, stop
+ for( int i=1; i ds = new HashMap<>();
+ Ary ns = new Ary<>(Node.class);
+ _walk(ds,ns,node,depth);
+ // Remove data projections, these are force-printed behind their multinode head
+ for( int i=0; i bns = new Ary<>(Node.class);
+ while( !ds.isEmpty() ) {
+ CFGNode blk = null;
+ for( Node n : ns ) {
+ CFGNode cfg = n instanceof CFGNode cfg0 && cfg0.blockHead() ? cfg0 : n.cfg0();
+ if( blk==null || cfg.idepth() < blk.idepth() )
+ blk = cfg;
+ }
+ Integer d = ds.remove(blk._nid);
+ ns.del(ns.find(blk));
+
+ // Print block header
+ sb.p("%-13.13s".formatted(label(blk)+":"));
+ sb.p( " ".repeat(4) ).p(" [[ ");
+ if( blk instanceof StartNode ) ;
+ else if( blk instanceof RegionNode || blk instanceof StopNode )
+ for( int i=(blk instanceof StopNode ? 3 : 1); i ds, Ary ns, Node node, int d ) {
+ Integer nd = ds.get(node._nid);
+ if( nd!=null && d <= nd ) return; // Been there, done that
+ Integer old = ds.put(node._nid,d) ;
+ if( old == null )
+ ns.add(node);
+ if( d == 0 ) return; // Depth cutoff
+ for( Node def : node._inputs )
+ if( def != null &&
+ !(node instanceof LoopNode loop && loop.back()==def) &&
+ // Don't walk into or out of functions
+ !(node instanceof CallEndNode && def instanceof ReturnNode) &&
+ !(node instanceof FunNode && def instanceof CallNode) &&
+ !(node instanceof ParmNode && !(def instanceof FunNode))
+ )
+ _walk(ds,ns,def,d-1);
+ }
+
+ static String label( CFGNode blk ) {
+ if( blk instanceof StartNode ) return "START";
+ return (blk instanceof LoopNode ? "LOOP" : "L")+blk._nid;
+ }
+ static void label( SB sb, CFGNode blk ) {
+ if( !blk.blockHead() ) blk = blk.cfg(0);
+ sb.p( "%-9.9s ".formatted( label( blk ) ) );
+ }
+ static void printLine( Node n, SB sb, Ary bns, int i, HashMap ds, Ary ns ) {
+ printLine( n, sb );
+ if( i != -1 ) bns.del(i);
+ ds.remove(n._nid);
+ int idx = ns.find(n);
+ if( idx!=-1 ) ns.del(idx);
+ }
+
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/Field.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/Field.java
new file mode 100644
index 0000000..d4cf783
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/Field.java
@@ -0,0 +1,74 @@
+package com.compilerprogramming.ezlang.compiler.sontypes;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+
+import java.util.ArrayList;
+
+/**
+ * Represents a field in a struct. This is not a Type in the type system.
+ */
+public class Field extends SONType {
+
+ // The pair {fieldName,type} uniquely identifies a field.
+
+ // Field name
+ public final String _fname;
+ // Type of the field
+ public final SONType _type;
+ // Unique memory alias, not sensibly part of a "type" but very convenient here.
+ public final int _alias;
+ // Field must be written to exactly once, no more, no less
+ public final boolean _final;
+
+ public Field(String fname, SONType type, int alias, boolean xfinal ) {
+ super(TFLD);
+ _fname = fname;
+ _type = type;
+ _alias = alias;
+ _final = xfinal;
+ }
+ // Make with existing alias
+ public static Field make(String fname, SONType type, int alias, boolean xfinal ) {
+ return new Field(fname,type,alias,xfinal).intern();
+ }
+ public Field makeFrom( SONType type ) {
+ return type == _type ? this : new Field(_fname,type,_alias,_final).intern();
+ }
+ @Override public Field makeRO() { return _final ? this : make(_fname,_type.makeRO(),_alias,true); }
+ @Override public boolean isFinal() { return _final && _type.isFinal(); }
+
+ public static final Field TEST = make("test", SONType.NIL,-2,false);
+ public static final Field TEST2= make("test", SONType.NIL,-2,true);
+ public static void gather(ArrayList ts) { ts.add(TEST); ts.add(TEST2); }
+
+ @Override Field xmeet( SONType that ) {
+ Field fld = (Field)that; // Invariant
+ assert _fname.equals(fld._fname);
+ assert _alias==fld._alias;
+ return make(_fname,_type.meet(fld._type),_alias,_final | fld._final);
+ }
+
+ @Override
+ public Field dual() { return make(_fname,_type.dual(),_alias,!_final); }
+
+ @Override public Field glb() {
+ SONType glb = _type.glb();
+ return (glb==_type && _final) ? this : make(_fname,glb,_alias,true);
+ }
+
+ // Override in subclasses
+ int hash() { return _fname.hashCode() ^ _type.hashCode() ^ _alias ^ (_final ? 1024 : 0); }
+
+ boolean eq(SONType t) {
+ Field f = (Field)t;
+ return _fname.equals(f._fname) && _type==f._type && _alias==f._alias && _final==f._final;
+ }
+
+
+ @Override
+ public SB print( SB sb ) {
+ return _type.print(sb.p(_final?"":"!").p(_fname).p(":").p(_alias).p(" : "));
+ }
+
+ @Override public String str() { return _fname; }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONType.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONType.java
new file mode 100644
index 0000000..ba7eaac
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONType.java
@@ -0,0 +1,235 @@
+package com.compilerprogramming.ezlang.compiler.sontypes;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.Utils;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * These types are part of a Monotone Analysis Framework,
+ * @see see for example this set of slides.
+ *
+ * The types form a lattice; @see a symmetric complete bounded (ranked) lattice.
+ *
+ * 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.
+ * 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 SONType {
+ static final HashMap INTERN = new HashMap<>();
+
+ // ----------------------------------------------------------
+ // Simple types are implemented fully here. "Simple" means: the code and
+ // type hierarchy are simple, not that the Type is conceptually simple.
+ static final byte TBOT = 0; // Bottom (ALL)
+ static final byte TTOP = 1; // Top (ANY)
+ static final byte TCTRL = 2; // Ctrl flow bottom
+ static final byte TXCTRL = 3; // Ctrl flow top (mini-lattice: any-xctrl-ctrl-all)
+ static final byte TNIL = 4; // low null of all flavors
+ static final byte TXNIL = 5; // high or choice null
+ static final byte TSIMPLE = 6; // End of the Simple Types
+
+ static final byte TPTR = 7; // All nil-able scalar values
+ static final byte TINT = 8; // All Integers; see TypeInteger
+ static final byte TFLT = 9; // All Floats ; see TypeFloat
+ static final byte TMEMPTR =10; // Memory pointer to a struct type
+ static final byte TFUNPTR =11; // Function pointer; unique signature and code address (just a bit)
+ static final byte TTUPLE =12; // Tuples; finite collections of unrelated Types, kept in parallel
+ static final byte TMEM =13; // All memory (alias 0) or A slice of memory - with specific alias
+ static final byte TSTRUCT =14; // Structs; tuples with named fields
+ static final byte TFLD =15; // Named fields in structs
+ static final byte TRPC =16; // Return Program Control (Return PC or RPC)
+
+ public final byte _type;
+
+ public boolean is_simple() { return _type < TSIMPLE; }
+ private static final String[] STRS = new String[]{"Bot","Top","Ctrl","~Ctrl","null","~nil"};
+ protected SONType(byte type) { _type = type; }
+
+ public static final SONType BOTTOM = new SONType( TBOT ).intern(); // ALL
+ public static final SONType TOP = new SONType( TTOP ).intern(); // ANY
+ public static final SONType CONTROL = new SONType( TCTRL ).intern(); // Ctrl
+ public static final SONType XCONTROL = new SONType( TXCTRL ).intern(); // ~Ctrl
+ public static final SONType NIL = new SONType( TNIL ).intern(); // low null of all flavors
+ public static final SONType XNIL = new SONType( TXNIL ).intern(); // high or choice null
+ public static SONType[] gather() {
+ ArrayList ts = new ArrayList<>();
+ ts.add(BOTTOM);
+ ts.add(CONTROL);
+ ts.add(NIL);
+ ts.add(XNIL);
+ SONTypeNil.gather(ts);
+ SONTypePtr.gather(ts);
+ SONTypeInteger.gather(ts);
+ SONTypeFloat.gather(ts);
+ SONTypeMemPtr.gather(ts);
+ SONTypeFunPtr.gather(ts);
+ SONTypeMem.gather(ts);
+ Field.gather(ts);
+ SONTypeStruct.gather(ts);
+ SONTypeTuple.gather(ts);
+ SONTypeRPC.gather(ts);
+ int sz = ts.size();
+ for( int i = 0; i < sz; i++ )
+ ts.add(ts.get(i).dual());
+ return ts.toArray(new SONType[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(); }
+
+ // Strict constant values, things on the lattice centerline.
+ // Excludes both high and low values
+ public boolean isConstant() { return _type==TNIL; }
+
+ // ----------------------------------------------------------
+
+ // 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"
+ public T intern() {
+ T nnn = (T)INTERN.get(this);
+ if( nnn==null )
+ INTERN.put(nnn=(T)this,this);
+ return nnn;
+ }
+
+ private 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 SONType 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(SONType t) { return true; }
+
+
+ // ----------------------------------------------------------
+ public final SONType meet(SONType t) {
+ // Shortcut for the self case
+ if( t == this ) return this;
+ // Same-type is always safe in the subclasses
+ if( _type==t._type ) return xmeet(t);
+ // TypeNil vs TypeNil meet
+ if( this instanceof SONTypeNil ptr0 && t instanceof SONTypeNil ptr1 )
+ return ptr0.nmeet(ptr1);
+ // Reverse; xmeet 2nd arg is never "is_simple" and never equal to "this".
+ if( is_simple() ) return this.xmeet(t );
+ if( t.is_simple() ) return t .xmeet(this);
+ return SONType.BOTTOM; // Mixing 2 unrelated types
+ }
+
+ // Compute meet right now. Overridden in subclasses.
+ // Handle cases where 'this.is_simple()' and unequal to 't'.
+ // Subclassed xmeet calls can assert that '!t.is_simple()'.
+ SONType xmeet(SONType t) {
+ assert is_simple(); // Should be overridden in subclass
+ // ANY meet anything is thing; thing meet ALL is ALL
+ if( _type==TBOT || t._type==TTOP ) return this;
+ if( _type==TTOP || t._type==TBOT ) return t;
+
+ // RHS TypeNil vs NIL/XNIL
+ if( _type== TNIL ) return t instanceof SONTypeNil ptr ? ptr.meet0() : (t._type==TXNIL ? SONTypePtr.PTR : BOTTOM);
+ if( _type== TXNIL ) return t instanceof SONTypeNil ptr ? ptr.meetX() : (t._type== TNIL ? SONTypePtr.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;
+ }
+
+ public SONType dual() {
+ return switch( _type ) {
+ case TBOT -> TOP;
+ case TTOP -> BOTTOM;
+ case TCTRL ->XCONTROL;
+ case TXCTRL-> CONTROL;
+ case TNIL ->XNIL;
+ case TXNIL -> NIL;
+ default -> throw Utils.TODO(); // Should not reach here
+ };
+ }
+
+ // ----------------------------------------------------------
+ // Our lattice is defined with a MEET and a DUAL.
+ // JOIN is dual of meet of both duals.
+ public final SONType join(SONType 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( SONType t ) { return meet(t)==t; }
+
+ // True if this "isa" t up to named structures
+ public boolean shallowISA( SONType t ) { return isa(t); }
+
+ public SONType nonZero() { return SONTypePtr.NPTR; }
+
+ // Make a zero version of this type, 0 for integers and null for pointers.
+ public SONType makeZero() { return SONType.NIL; }
+
+ /** Compute greatest lower bound in the lattice */
+ public SONType glb() { return SONType.BOTTOM; }
+
+ // Is forward-reference
+ public boolean isFRef() { return false; }
+
+ // All reachable struct Fields are final
+ public boolean isFinal() { return true; }
+
+ // Make all reachable struct Fields final
+ public SONType makeRO() { 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() { throw Utils.TODO(); }
+
+ // ----------------------------------------------------------
+ // Useful in the debugger, which calls toString everywhere.
+ // This is a more verbose dev-friendly print.
+ @Override
+ public final String toString() {
+ return str();
+ }
+
+ public SB print(SB sb) { return sb.p(str()); }
+ public SB gprint(SB sb) { return print(sb); }
+
+ // This is used by error messages, and is a shorted print.
+ public String str() { return STRS[_type]; }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeFloat.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeFloat.java
new file mode 100644
index 0000000..c4d2dba
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeFloat.java
@@ -0,0 +1,94 @@
+package com.compilerprogramming.ezlang.compiler.sontypes;
+
+import com.compilerprogramming.ezlang.compiler.Utils;
+import java.util.ArrayList;
+
+/**
+ * Float Type
+ */
+public class SONTypeFloat extends SONType {
+
+ public final static SONTypeFloat ONE = constant(1.0);
+ public final static SONTypeFloat FZERO = constant(0.0);
+ public final static SONTypeFloat F32 = make(32, 0);
+ public final static SONTypeFloat F64 = make(64, 0);
+
+ // - high -64, high -32, con 0, low +32, low +64
+ public final byte _sz;
+
+ /**
+ * The constant value or 0
+ */
+ public final double _con;
+
+ private SONTypeFloat(byte sz, double con) {
+ super(TFLT);
+ _sz = sz;
+ _con = con;
+ }
+ private static SONTypeFloat make(int sz, double con) {
+ return new SONTypeFloat((byte)sz,con).intern();
+ }
+
+ public static SONTypeFloat constant(double con) { return make(0, con); }
+
+ public static void gather(ArrayList ts) { ts.add(F64); ts.add(F32); ts.add(constant(3.141592653589793)); }
+
+ @Override public String str() {
+ return switch( _sz ) {
+ case -64 -> "~flt";
+ case -32 -> "~f32";
+ case 0 -> ""+_con+((float)_con==_con ? "f" : "");
+ case 32 -> "f32";
+ case 64 -> "flt";
+ default -> throw Utils.TODO();
+ };
+ }
+ private boolean isF32() { return ((float)_con)==_con; }
+
+ @Override public boolean isHigh () { return _sz< 0; }
+ @Override public boolean isConstant() { return _sz==0; }
+ @Override public int log_size() { return _sz==32 || _sz==-32 ? 2 : 3; }
+
+ public double value() { return _con; }
+
+ @Override
+ public SONTypeFloat xmeet(SONType other) {
+ SONTypeFloat f = (SONTypeFloat)other;
+ // Invariant from caller: 'this' != 'other' and same class (TypeFloat).
+ SONTypeFloat i = (SONTypeFloat)other; // Contract
+ // Larger size in i1, smaller in i0
+ SONTypeFloat i0 = _sz < i._sz ? this : i;
+ SONTypeFloat 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
+ public SONType dual() {
+ return isConstant() ? this : make(-_sz,0); // Constants are a self-dual
+ }
+
+ @Override public SONType makeZero() { return FZERO; }
+ @Override public SONType glb() { return F64; }
+
+ @Override
+ int hash() { return (int)(Double.hashCode(_con) ^ _sz ^ (1<<17)); }
+ @Override
+ public boolean eq( SONType t ) {
+ SONTypeFloat i = (SONTypeFloat)t; // Contract
+ // Allow NaN to check for equality
+ return _sz==i._sz && Double.doubleToLongBits(_con)==Double.doubleToLongBits(i._con);
+ }
+
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeFunPtr.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeFunPtr.java
new file mode 100644
index 0000000..2d81a60
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeFunPtr.java
@@ -0,0 +1,106 @@
+package com.compilerprogramming.ezlang.compiler.sontypes;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.Utils;
+import java.util.ArrayList;
+
+/**
+ * Represents a Pointer to a function.
+ *
+ * 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).
+ *
+ * Functions with different signatures cannot be mixed, and will result in a
+ * bottom function type, which can only be null-checked.
+ */
+public class SONTypeFunPtr extends SONTypeNil {
+ // A TypeFunPtr is Signature and a set of functions.
+ public final SONTypeTuple _sig;
+ public final SONType _ret;
+ // Cheesy easy implementation for a small set; 1 bit per unique function
+ // within the TypeTuple. Can be upgraded to a BitSet for larger classes of
+ // functions. Negative means "these 63 concrete bits plus infinite unknown more"
+ public final long _fidxs; // 63 unique functions per signature
+
+ public SONTypeFunPtr(byte nil, SONTypeTuple sig, SONType ret, long fidxs) {
+ super(TFUNPTR,nil);
+ assert sig != null;
+ _sig = sig;
+ _ret = ret;
+ _fidxs = fidxs;
+ }
+
+ public static SONTypeFunPtr make(byte nil, SONTypeTuple sig, SONType ret, long fidxs ) { return new SONTypeFunPtr(nil,sig,ret,fidxs).intern(); }
+ public static SONTypeFunPtr make(boolean nil, SONTypeTuple sig, SONType ret ) { return make((byte)(nil ? 3 : 2),sig,ret,-1); }
+ @Override
+ SONTypeFunPtr makeFrom(byte nil ) { return nil ==_nil ? this : make( nil,_sig,_ret, _fidxs); }
+ public SONTypeFunPtr makeFrom(SONType ret ) { return ret ==_ret ? this : make( _nil,_sig, ret, _fidxs); }
+ public SONTypeFunPtr makeFrom(int fidx ) { return make((byte)2, _sig,_ret,1L< ts) { ts.add(TEST); ts.add(TEST0); ts.add(BOT); ts.add(MAIN); }
+
+ @Override
+ SONType xmeet(SONType t) {
+ SONTypeFunPtr that = (SONTypeFunPtr) t;
+ return SONTypeFunPtr.make(xmeet0(that),(SONTypeTuple)_sig.meet(that._sig), _ret.meet(that._ret), _fidxs | that._fidxs);
+ }
+
+ @Override
+ public SONTypeFunPtr dual() { return SONTypeFunPtr.make(dual0(), _sig.dual(), _ret.dual(), ~_fidxs); }
+
+ // RHS is NIL; do not deep-dual when crossing the centerline
+ @Override public SONType meet0() { return _nil==3 ? this : make((byte)3,_sig,_ret,_fidxs); }
+
+ @Override public SONTypeFunPtr glb() { return make((byte)3,_sig,_ret,-1L); }
+
+ @Override public boolean isHigh () { return _nil <= 1 || (_nil==2 && _fidxs==0); }
+ @Override public boolean isConstant() { return (_nil==2 && Long.bitCount(_fidxs)==1) || (_nil==3 && _fidxs==0); }
+
+ @Override public int log_size() { return 2; } // (1<<2)==4-byte pointers
+
+ public SONType arg(int i) { return _sig._types[i]; }
+ public long fidxs() { return _fidxs; }
+ public SONType ret() { return _ret; }
+ public int nargs() { return _sig._types.length; }
+ public int fidx() { assert Long.bitCount(_fidxs)==1; return Long.numberOfTrailingZeros(_fidxs); }
+
+ @Override
+ int hash() { return Utils.fold(_sig.hashCode() ^ _ret.hashCode() ^ _fidxs ^ super.hash()); }
+
+ @Override
+ boolean eq(SONType t) {
+ SONTypeFunPtr ptr = (SONTypeFunPtr)t; // Invariant
+ return _sig == ptr._sig && _ret == ptr._ret && _fidxs == ptr._fidxs && super.eq(ptr);
+ }
+
+ @Override public String str() { return print(new SB()).toString(); }
+ @Override public SB print(SB sb) { return _print(sb,false,true); }
+ public SB print(SB sb, boolean n) { return _print(sb,false,n); }
+ @Override public SB gprint(SB sb) { return _print(sb,true ,true); }
+ private static SB _print(SB sb, boolean g, SONType t) { return g ? t.gprint(sb) : t.print(sb); }
+ private SB _print(SB sb, boolean g, boolean n) {
+ sb.p(x()).p("{ ");
+ if( _sig._types!=null )
+ for( SONType t : _sig._types )
+ _print(sb,g,t).p(" ");
+ _print(sb.p(g ? "→ " : "-> "),g,_ret).p(" #");
+ if( isHigh() ) sb.p("~");
+ long fidxs = isHigh() ? ~_fidxs : _fidxs;
+ String fidx = fidxs==0 ? ""
+ : Long.bitCount(fidxs) == 1 ? ""+Long.numberOfTrailingZeros(fidxs)
+ : fidxs == -1 ? "ALL"
+ : "b"+Long.toBinaryString(fidxs); // Just some function bits
+ return sb.p(fidx).p("}").p(q());
+ }
+
+ // Usage: for( long fidxs=fidxs(); fidxs!=0; fidxs=nextFIDX(fidxs) { int fidxs = Long.numberOfTrailingZeros(fidxs); ... }
+ public static long nextFIDX(long fidxs) { return fidxs & (fidxs-1); }
+
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeInteger.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeInteger.java
new file mode 100644
index 0000000..5030b57
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeInteger.java
@@ -0,0 +1,127 @@
+package com.compilerprogramming.ezlang.compiler.sontypes;
+
+import com.compilerprogramming.ezlang.compiler.Utils;
+import java.util.ArrayList;
+
+/**
+ * Integer Type
+ */
+public class SONTypeInteger extends SONType {
+
+ public final static SONTypeInteger ZERO= make(0,0);
+ public final static SONTypeInteger FALSE=ZERO;
+ public final static SONTypeInteger TRUE= make(1,1);
+
+ public final static SONTypeInteger I1 = make(-1,0);
+ public final static SONTypeInteger I8 = make(-128,127);
+ public final static SONTypeInteger I16 = make(-32768,32767);
+ public final static SONTypeInteger I32 = make(-1L<<31,(1L<<31)-1);
+ public final static SONTypeInteger BOT = make(Long.MIN_VALUE,Long.MAX_VALUE);
+ public final static SONTypeInteger TOP = BOT.dual();
+
+ public final static SONTypeInteger U1 = make(0,1);
+ public final static SONTypeInteger BOOL= U1;
+ public final static SONTypeInteger U8 = make(0,255);
+ public final static SONTypeInteger U16 = make(0,65535);
+ public final static SONTypeInteger U32 = make(0,(1L<<32)-1);
+
+ /**
+ * Describes an integer *range* - everything from min to max; both min and
+ * max are inclusive. If min==max, this is a constant.
+ *
+ * If min <= max, this is a below center (towards bottom).
+ * If min > max, this is an above center (towards top).
+ */
+ public final long _min, _max;
+
+ private SONTypeInteger(long min, long max) {
+ super(TINT);
+ _min = min;
+ _max = max;
+ }
+
+ // Strict non-zero contract
+ public static SONTypeInteger make(long lo, long hi) { return new SONTypeInteger(lo,hi).intern(); }
+
+ public static SONTypeInteger constant(long con) { return make(con, con); }
+
+ public static void gather(ArrayList ts) { ts.add(I32); ts.add(BOT); ts.add(U1); ts.add(I1); ts.add(U8); }
+
+ @Override public String str() {
+ if( isConstant() ) return ""+_min;
+ long lo = _min, hi = _max;
+ String x = "";
+ if( hi < lo ) {
+ lo = _max; hi = _min;
+ x = "~";
+ }
+ return x+_str(lo,hi);
+ }
+ private static String _str(long lo, long hi) {
+ if( lo==Long.MIN_VALUE && hi==Long.MAX_VALUE ) return "int";
+ 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 boolean isConstant() { return _min == _max; }
+
+ @Override public int log_size() {
+ 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( isHighOrConst() ) return 0;
+ throw Utils.TODO();
+ }
+
+ public long value() { assert isConstant(); return _min; }
+
+ // AND-mask of forced zeros. e.g. unsigned types will return their mask;
+ // u8 will return 0xFF. But also a range of 16-18 (0x10-0x12) will return
+ // 0x13 - no value in the range {16,17,18} will allow bit 0x04 to be set.
+ public long mask() {
+ if( isHigh() ) return 0;
+ if( isConstant() ) return _min;
+ // Those bit positions which differ min to max
+ long x = _min ^ _max;
+ // Highest '1' bit in the differ set. Since the range is from min to
+ // max, all values below here are possible.
+ long ff1 = Long.highestOneBit(x);
+ // Make a all-1's mask from ff1, and set over the same bits (either min
+ // or max is ok).
+ long mask = _min | (ff1-1) | ff1;
+ return mask;
+ }
+
+ @Override
+ public SONType xmeet(SONType other) {
+ // Invariant from caller: 'this' != 'other' and same class (TypeInteger)
+ SONTypeInteger i = (SONTypeInteger)other; // Contract
+ return make(Math.min(_min,i._min), Math.max(_max,i._max));
+ }
+
+ @Override public SONTypeInteger dual() { return make(_max,_min); }
+
+ @Override public SONTypeInteger nonZero() {
+ if( isHigh() ) return this;
+ if( this==ZERO ) return null; // No sane answer
+ if( _min==0 ) return make(1,Math.max(_max,1)); // specifically good on BOOL
+ if( _max==0 ) return make(_min,-1);
+ return this;
+ }
+ @Override public SONType makeZero() { return ZERO; }
+ @Override public SONTypeInteger glb() { return BOT; }
+ @Override int hash() { return Utils.fold(_min) * Utils.fold(_max); }
+ @Override public boolean eq( SONType t ) {
+ SONTypeInteger i = (SONTypeInteger)t; // Contract
+ return _min==i._min && _max==i._max;
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeMem.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeMem.java
new file mode 100644
index 0000000..aed4569
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeMem.java
@@ -0,0 +1,72 @@
+package com.compilerprogramming.ezlang.compiler.sontypes;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+
+import java.util.ArrayList;
+
+
+/**
+ * Represents a slice of memory corresponding to a set of aliases
+ */
+public class SONTypeMem extends SONType {
+
+ // Which slice of memory?
+ // 0 means TOP, no slice.
+ // 0 means BOT, all memory.
+ // N means slice#N.
+ public final int _alias;
+ public final SONType _t; // Memory contents, some scalar type
+
+ private SONTypeMem(int alias, SONType t) {
+ super(TMEM);
+ assert alias!=0 || (t== SONType.TOP || t== SONType.BOTTOM);
+ _alias = alias;
+ _t = t;
+ }
+
+ public static SONTypeMem make(int alias, SONType t) { return new SONTypeMem(alias,t).intern(); }
+ public static final SONTypeMem TOP = make(0, SONType.TOP );
+ public static final SONTypeMem BOT = make(0, SONType.BOTTOM);
+
+ public static void gather(ArrayList ts) { ts.add(make(1, SONType.NIL)); ts.add(make(1, SONTypeInteger.ZERO)); ts.add(BOT); }
+
+ @Override
+ SONTypeMem xmeet(SONType t) {
+ SONTypeMem that = (SONTypeMem) t; // Invariant: TypeMem and unequal
+ if( this==TOP ) return that;
+ if( that==TOP ) return this;
+ if( this==BOT ) return BOT;
+ if( that==BOT ) return BOT;
+ int alias = _alias==that._alias ? _alias : 0;
+ SONType mt = _t.meet(that._t);
+ return make(alias,mt);
+ }
+
+ @Override
+ public SONType dual() {
+ return make(_alias,_t.dual());
+ }
+
+ @Override public boolean isHigh() { return _t.isHigh(); }
+ @Override public SONType glb() { return make(_alias,_t.glb()); }
+
+ @Override int hash() { return 9876543 + _alias + _t.hashCode(); }
+
+ @Override boolean eq(SONType t) {
+ SONTypeMem that = (SONTypeMem) t; // Invariant
+ return _alias == that._alias && _t == that._t;
+ }
+
+ @Override public SB print(SB sb) {
+ sb.p("#");
+ if( _alias==0 ) return sb.p(_t._type==TTOP ? "TOP" : "BOT");
+ return _t.print(sb.p(_alias).p(":"));
+ }
+ @Override public SB gprint(SB sb) {
+ sb.p("#");
+ if( _alias==0 ) return sb.p(_t._type==TTOP ? "TOP" : "BOT");
+ return _t.gprint(sb.p(_alias).p(":"));
+ }
+
+ @Override public String str() { return print(new SB()).toString(); }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeMemPtr.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeMemPtr.java
new file mode 100644
index 0000000..7b0f1c8
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeMemPtr.java
@@ -0,0 +1,107 @@
+package com.compilerprogramming.ezlang.compiler.sontypes;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.Utils;
+import java.util.ArrayList;
+
+/**
+ * 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 SONTypeMemPtr extends SONTypeNil {
+ // 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 final SONTypeStruct _obj;
+
+ public SONTypeMemPtr(byte nil, SONTypeStruct obj) {
+ super(TMEMPTR,nil);
+ assert obj!=null;
+ _obj = obj;
+ }
+ static SONTypeMemPtr make(byte nil, SONTypeStruct obj) { return new SONTypeMemPtr(nil, obj).intern(); }
+ public static SONTypeMemPtr makeNullable(SONTypeStruct obj) { return make((byte)3, obj); }
+ public static SONTypeMemPtr make(SONTypeStruct obj) { return new SONTypeMemPtr((byte)2, obj).intern(); }
+ public SONTypeMemPtr makeFrom(SONTypeStruct obj) { return obj==_obj ? this : make(_nil, obj); }
+ public SONTypeMemPtr makeNullable() { return makeFrom((byte)3); }
+ @Override
+ SONTypeMemPtr makeFrom(byte nil) { return nil==_nil ? this : make(nil,_obj); }
+ @Override public SONTypeMemPtr makeRO() { return makeFrom(_obj.makeRO()); }
+ @Override public boolean isFinal() { return _obj.isFinal(); }
+
+ // 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 SONTypeMemPtr BOT = make((byte)3, SONTypeStruct.BOT);
+ public static SONTypeMemPtr TOP = BOT.dual();
+ public static SONTypeMemPtr NOTBOT = make((byte)2, SONTypeStruct.BOT);
+
+ public static SONTypeMemPtr TEST= make((byte)2, SONTypeStruct.TEST);
+ public static void gather(ArrayList ts) { ts.add(NOTBOT); ts.add(BOT); ts.add(TEST); }
+
+ @Override
+ public SONTypeNil xmeet(SONType t) {
+ SONTypeMemPtr that = (SONTypeMemPtr) t;
+ return SONTypeMemPtr.make(xmeet0(that), (SONTypeStruct)_obj.meet(that._obj));
+ }
+
+ @Override
+ public SONTypeMemPtr dual() { return SONTypeMemPtr.make( dual0(), _obj.dual()); }
+
+ // RHS is NIL; do not deep-dual when crossing the centerline
+ @Override
+ SONType meet0() { return _nil==3 ? this : make((byte)3,_obj); }
+
+
+ // True if this "isa" t up to named structures
+ @Override public boolean shallowISA( SONType t ) {
+ if( !(t instanceof SONTypeMemPtr that) ) return false;
+ if( this==that ) return true;
+ if( xmeet0(that)!=that._nil ) return false;
+ if( _obj==that._obj ) return true;
+ if( _obj._name.equals(that._obj._name) )
+ return true; // Shallow, do not follow matching names, just assume ok
+ throw Utils.TODO(); // return _obj.shallowISA(that._obj);
+ }
+
+ @Override public SONTypeMemPtr glb() { return make((byte)3,_obj.glb()); }
+ // Is forward-reference
+ @Override public boolean isFRef() { return _obj.isFRef(); }
+
+ @Override public int log_size() { return 2; } // (1<<2)==4-byte pointers
+
+ @Override int hash() { return _obj.hashCode() ^ super.hash(); }
+
+ @Override boolean eq(SONType t) {
+ SONTypeMemPtr ptr = (SONTypeMemPtr)t; // Invariant
+ return _obj == ptr._obj && super.eq(ptr);
+ }
+
+ @Override public String str() {
+ if( this== NOTBOT) return "*void";
+ if( this== BOT) return "*void?";
+ return x()+"*"+_obj.str()+q();
+ }
+
+ @Override public SB print(SB sb) {
+ if( this== NOTBOT) return sb.p("*void");
+ if( this== BOT) return sb.p("*void?");
+ return _obj.print(sb.p(x()).p("*")).p(q());
+ }
+ @Override public SB gprint(SB sb) {
+ if( this== NOTBOT) return sb.p("*void");
+ if( this== BOT) return sb.p("*void?");
+ return _obj.gprint(sb.p(x()).p("*")).p(q());
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeNil.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeNil.java
new file mode 100644
index 0000000..3193c0c
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeNil.java
@@ -0,0 +1,60 @@
+package com.compilerprogramming.ezlang.compiler.sontypes;
+
+import java.util.ArrayList;
+
+/**
+ * Nil-able Scalar types
+ */
+public abstract class SONTypeNil extends SONType {
+ // 0 = high-subclass choice nil
+ // 1 = high-subclass no nil
+ // 2 = low -subclass no nil
+ // 3 = low -subclass also nil
+ final byte _nil;
+
+ SONTypeNil(byte t, byte nil ) { super(t); _nil = nil; }
+
+ public static void gather(ArrayList ts) { }
+
+ abstract SONTypeNil makeFrom(byte nil);
+
+ byte xmeet0(SONTypeNil that) { return (byte)Math.max(_nil,that._nil); }
+
+ byte dual0() { return (byte)(3-_nil); }
+
+ // RHS is NIL
+ abstract SONType meet0();
+
+ // RHS is XNIL
+ SONType meetX() {
+ return _nil==0 ? XNIL : (_nil<=2 ? SONTypePtr.NPTR : SONTypePtr.PTR);
+ }
+
+ SONType nmeet(SONTypeNil 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 SONTypePtr 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 SONTypePtr.NPTR;
+ return SONTypePtr.PTR;
+ }
+
+ @Override public boolean isHigh () { return _nil <= 1; }
+ @Override public boolean isConstant () { return false; }
+ @Override public boolean isHighOrConst() { return isHigh() || isConstant(); }
+
+ @Override public SONType glb() { return SONType.NIL; }
+
+ public boolean notNull() { return _nil==1 || _nil==2; }
+ public boolean nullable() { return _nil==3; }
+
+ final String q() { return _nil == 1 || _nil == 2 ? "" : "?"; }
+ final String x() { return isHigh() ? "~" : ""; }
+
+ int hash() { return _nil<<17; }
+
+ boolean eq(SONTypeNil ptr) { return _nil == ptr._nil; }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypePtr.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypePtr.java
new file mode 100644
index 0000000..dda8dd2
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypePtr.java
@@ -0,0 +1,56 @@
+package com.compilerprogramming.ezlang.compiler.sontypes;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.Utils;
+import java.util.ArrayList;
+
+/**
+ * Represents a Scalar; a single register-sized value.
+ */
+public class SONTypePtr extends SONTypeNil {
+
+ private SONTypePtr(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 SONTypePtr XPTR = new SONTypePtr((byte)0).intern();
+ public static SONTypePtr XNPTR= new SONTypePtr((byte)1).intern();
+ public static SONTypePtr NPTR = new SONTypePtr((byte)2).intern();
+ public static SONTypePtr PTR = new SONTypePtr((byte)3).intern();
+ private static final SONTypePtr[] PTRS = new SONTypePtr[]{XPTR,XNPTR,NPTR,PTR};
+
+ public static void gather(ArrayList ts) { ts.add(PTR); ts.add(NPTR); }
+
+ SONTypeNil makeFrom(byte nil) { throw Utils.TODO(); }
+
+ @Override public SONTypeNil xmeet(SONType t) {
+ SONTypePtr that = (SONTypePtr) t;
+ return PTRS[xmeet0(that)];
+ }
+
+ @Override public SONTypePtr dual() { return PTRS[dual0()]; }
+
+ // High scalar loses, low scalar wins
+ @Override
+ SONTypeNil nmeet(SONTypeNil 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
+ SONType meet0() { return isHigh() ? NIL : PTR; }
+ // RHS is XNIL
+ // 0->xscalar, 1->nscalar, 2->nscalar, 3->scalar
+ @Override
+ SONType meetX() { return _nil==0 ? XNIL : (_nil==3 ? PTR : NPTR); }
+
+ @Override public SONTypePtr glb() { return PTR; }
+
+ private static final String[] STRS = new String[]{"~ptr","~nptr","nptr","ptr"};
+ @Override public String str() { return STRS[_nil]; }
+ @Override public SB print(SB sb) { return sb.p(str()); }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeRPC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeRPC.java
new file mode 100644
index 0000000..82f0e2f
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeRPC.java
@@ -0,0 +1,97 @@
+package com.compilerprogramming.ezlang.compiler.sontypes;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+/**
+ * Return Program Control or Return PC or RPC
+ */
+public class SONTypeRPC extends SONType {
+
+ // A set of CallEndNode IDs (or StopNode); commonly just one.
+ // Basically a sparse bit set
+ final HashSet _rpcs;
+
+ // If true, invert the meaning of the bits
+ final boolean _any;
+
+ private SONTypeRPC(boolean any, HashSet rpcs) {
+ super(TRPC);
+ _any = any;
+ _rpcs = rpcs;
+ }
+ private static SONTypeRPC make(boolean any, HashSet rpcs) {
+ return new SONTypeRPC(any,rpcs).intern();
+ }
+
+ public static SONTypeRPC constant(int cend) {
+ HashSet rpcs = new HashSet<>();
+ rpcs.add(cend);
+ return make(false,rpcs);
+ }
+
+ public static final SONTypeRPC BOT = make(true,new HashSet<>());
+ private static final SONTypeRPC TEST2 = constant(2);
+ private static final SONTypeRPC TEST3 = constant(2);
+
+ public static void gather(ArrayList ts) { ts.add(BOT); ts.add(TEST2); ts.add(TEST3); }
+
+ @Override public String str() {
+ if( _rpcs.isEmpty() )
+ return _any ? "$[ALL]" : "$[]";
+ if( _rpcs.size()==1 )
+ for( Integer rpc : _rpcs )
+ return _any ? "$[-"+rpc+"]" : "$["+rpc+"]";
+ return "$["+(_any ? "-" : "")+_rpcs+"]";
+ }
+
+ @Override public boolean isConstant() {
+ return !_any && _rpcs.size()==1;
+ }
+
+ @Override
+ public SONTypeRPC xmeet(SONType other) {
+ SONTypeRPC rpc = (SONTypeRPC)other;
+ // If the two sets are equal, the _any must be unequal (invariant),
+ // so they cancel and all bits are set.
+ if( _rpcs.equals(rpc._rpcs) )
+ return BOT;
+ // Classic union of bit sets (which might be infinite).
+ HashSet lhs = _rpcs, rhs = rpc._rpcs;
+ // Smaller on left
+ if( lhs.size() > rhs.size() ) { lhs = rpc._rpcs; rhs = _rpcs; }
+
+ HashSet rpcs = new HashSet<>();
+ boolean any = true;
+ // If both sets are infinite, intersect.
+ if( _any && rpc._any ) {
+ for( Integer i : lhs ) if( rhs.contains(i) ) rpcs.add(i);
+
+ } else if( !_any && !rpc._any ) {
+ // if neither set is infinite, union.
+ rpcs.addAll( lhs );
+ rpcs.addAll( rhs );
+ any = false;
+ } else {
+ // If one is infinite, subtract the other from it.
+ HashSet sub = _any ? rpc._rpcs : _rpcs;
+ HashSet inf = _any ? _rpcs : rpc._rpcs;
+ for( Integer i : inf )
+ if( inf.contains(i) && !sub.contains(i) )
+ rpcs.add(i);
+ }
+ return make(any,rpcs);
+ }
+
+ @Override
+ public SONType dual() { return make(!_any,_rpcs); }
+
+ @Override
+ int hash() { return _rpcs.hashCode() ^ (_any ? -1 : 0) ; }
+ @Override
+ public boolean eq( SONType t ) {
+ SONTypeRPC rpc = (SONTypeRPC)t; // Contract
+ return _any==rpc._any && _rpcs.equals(rpc._rpcs);
+ }
+
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeStruct.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeStruct.java
new file mode 100644
index 0000000..f1f15ea
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeStruct.java
@@ -0,0 +1,259 @@
+package com.compilerprogramming.ezlang.compiler.sontypes;
+
+import com.compilerprogramming.ezlang.compiler.SB;
+import com.compilerprogramming.ezlang.compiler.Utils;
+import java.util.ArrayList;
+
+/**
+ * Represents a struct type.
+ */
+public class SONTypeStruct extends SONType {
+
+ // A Struct has a name and a set of fields; the fields themselves have
+ // names, types and aliases. The name has no semantic meaning, but is
+ // useful for debugging. Briefly during parsing a Struct can be a
+ // forward-ref; in this case the _fields array is null.
+
+ // Its illegal to attempt to load a field from a forward-ref struct.
+
+ // Example: "int rez = new S.x; struct S { int x; } return rez;" // Error, S not defined
+ // Rewrite: "struct S { int x; } int rez = new S.x; return rez;" // Ok, no forward ref
+ //
+ // During the normal optimization run, struct types "bottom out" at further
+ // struct references, so we don't have to handle e.g. cyclic types. The
+ // "bottom out" is again the forward-ref struct.
+ public final String _name;
+ public Field[] _fields;
+
+ public SONTypeStruct(String name, Field[] fields) {
+ super(TSTRUCT);
+ _name = name;
+ _fields = fields;
+ }
+
+ // All fields directly listed
+ public static SONTypeStruct make(String name, Field... fields) { return new SONTypeStruct(name, fields).intern(); }
+ public static final SONTypeStruct TOP = make("$TOP",new Field[0]);
+ public static final SONTypeStruct BOT = make("$BOT",new Field[0]);
+ public static final SONTypeStruct TEST = make("test",new Field[]{Field.TEST});
+ // Forward-ref version
+ public static SONTypeStruct makeFRef(String name) { return make(name, (Field[])null); }
+ // Make a read-only version
+ @Override public SONTypeStruct makeRO() {
+ if( isFinal() ) return this;
+ Field[] flds = new Field[_fields.length];
+ for( int i=0; i ts) { ts.add(TEST); ts.add(BOT); ts.add(S1); ts.add(S2); ts.add(ARY); }
+
+ // Find field index by name
+ public int find(String fname) {
+ for( int i=0; i<_fields.length; i++ )
+ if( _fields[i]._fname.equals(fname) )
+ return i;
+ return -1;
+ }
+ public Field field(String fname) {
+ int idx = find(fname);
+ return idx== -1 ? null : _fields[idx];
+ }
+
+ // Find field index by alias
+ public int findAlias( int alias ) {
+ for( int i=0; i<_fields.length; i++ )
+ if( _fields[i]._alias==alias )
+ return i;
+ return -1;
+ }
+
+
+ @Override
+ SONType xmeet(SONType t) {
+ SONTypeStruct that = (SONTypeStruct) t;
+ if( this==TOP ) return that;
+ if( that==TOP ) return this;
+ if( this==BOT ) return BOT;
+ if( that==BOT ) return BOT;
+
+ // Within the same compilation unit, struct names are unique. If the
+ // names differ, its different structs. Across many compilation units,
+ // structs with the same name but different field layouts can be
+ // interned... which begs the question:
+ // "What is the meet of structs from two different compilation units?"
+ // And the answer is: "don't ask".
+ if( !_name.equals(that._name) )
+ return BOT; // It's a struct; that's about all we know
+ if( this._fields==null ) return that;
+ if( that._fields==null ) return this;
+ if( _fields.length != that._fields.length ) return BOT;
+ // Just do field meets
+ Field[] flds = new Field[_fields.length];
+ for( int i=0; i<_fields.length; i++ ) {
+ Field f0 = _fields[i], f1 = that._fields[i];
+ if( !f0._fname.equals(f1._fname) || f0._alias != f1._alias )
+ return BOT;
+ flds[i] = (Field)f0.meet(f1);
+ }
+ return make(_name,flds);
+ }
+
+ @Override
+ public SONTypeStruct dual() {
+ if( this==TOP ) return BOT;
+ if( this==BOT ) return TOP;
+ if( _fields == null ) return this;
+ Field[] flds = new Field[_fields.length];
+ for( int i=0; i<_fields.length; i++ )
+ flds[i] = _fields[i].dual();
+ return make(_name,flds);
+ }
+
+ // Keeps the same struct, but lower-bounds all fields.
+ @Override public SONTypeStruct glb() {
+ if( _glb() ) return this;
+ // Need to glb each field
+ Field[] flds = new Field[_fields.length];
+ for( int i=0; i<_fields.length; i++ )
+ flds[i] = _fields[i].glb();
+ return make(_name,flds);
+ }
+ private boolean _glb() {
+ if( _fields!=null )
+ for( Field f : _fields )
+ if( f.glb() != f )
+ return false;
+ return true;
+ }
+
+ // Is forward-reference
+ @Override public boolean isFRef() { return _fields==null; }
+ // All fields are final
+ @Override public boolean isFinal() {
+ if( _fields==null ) return true;
+ for( Field fld : _fields )
+ if( !fld.isFinal() )
+ return false;
+ return true;
+ }
+
+ @Override
+ boolean eq(SONType t) {
+ SONTypeStruct ts = (SONTypeStruct)t; // Invariant
+ if( !_name.equals(ts._name) )
+ return false;
+// if( _fields == ts._fields ) return true;
+// if( _fields==null || ts._fields==null ) return false;
+// if( _fields.length!=ts._fields.length )
+// return false;
+// for( int i = 0; i < _fields.length; i++ )
+// if( _fields[i] != ts._fields[i] )
+// return false;
+ return true;
+ }
+
+ @Override
+ int hash() {
+ long hash = _name.hashCode();
+// if( _fields != null )
+// for( Field f : _fields )
+// hash = Utils.rot(hash,13) ^ f.hashCode();
+// return Utils.fold(hash);
+ return (int) hash;
+ }
+
+ @Override
+ public SB print(SB sb) {
+ sb.p(_name);
+ if( _fields == null ) return sb; // Forward reference struct, just print the name
+ if( isAry() ) return sb; // Skip printing generic array fields
+ sb.p(" {");
+ for( Field f : _fields )
+ f._type.print(sb).p(f._final ? " " : " !").p(f._fname).p("; ");
+ return sb.p("}");
+ }
+ @Override public SB gprint( SB sb ) { return sb.p(_name); }
+
+ @Override public String str() { return _name; }
+
+
+ public boolean isAry() { return _fields.length==2 && _fields[1]._fname.equals("[]"); }
+
+ public int aryBase() {
+ assert isAry();
+ if( _offs==null ) _offs = offsets();
+ return _offs[1];
+ }
+ public int aryScale() {
+ assert isAry();
+ return _fields[1]._type.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
+ // Compute a layout for a collection of fields
+ assert _fields != null; // No forward refs
+
+ // Array layout is different: len,[pad],body...
+ if( isAry() )
+ return _offs = new int[]{ 0, _fields[1]._type.log_size() < 3 ? 4 : 8 };
+
+ // Compute a layout
+ int[] cnts = new int[4]; // Count of fields at log field size
+ for( Field f : _fields )
+ cnts[f._type.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]< ts) { ts.add(BOT); ts.add(TEST); ts.add(START); ts.add(MAIN); ts.add(IF_TRUE); }
+
+ @Override
+ SONType xmeet(SONType other) {
+ SONTypeTuple tt = (SONTypeTuple)other; // contract from xmeet
+ if( this==TOP ) return other;
+ if( tt ==TOP ) return this ;
+ if( _types.length != tt._types.length )
+ return BOT;
+ SONType[] ts = new SONType[_types.length];
+ for( int i=0; i<_types.length; i++ )
+ ts[i] = _types[i].meet(tt._types[i]);
+ return make(ts);
+ }
+
+ @Override public SONTypeTuple dual() {
+ if( this==TOP ) return BOT;
+ if( this==BOT ) return TOP;
+ SONType[] ts = new SONType[_types.length];
+ for( int i=0; i<_types.length; i++ )
+ ts[i] = _types[i].dual();
+ return make(ts);
+ }
+
+ @Override
+ public SONType glb() {
+ SONType[] ts = new SONType[_types.length];
+ for( int i=0; i<_types.length; i++ )
+ ts[i] = _types[i].glb();
+ return make(ts);
+ }
+
+ public SONType ret() { assert _types.length==3; return _types[2]; }
+
+ @Override public String str() { return print(new SB()).toString(); }
+
+ @Override public SB print(SB sb) {
+ if( this==TOP ) return sb.p("[TOP]");
+ if( this==BOT ) return sb.p("[BOT]");
+ sb.p("[ ");
+ for( SONType t : _types )
+ t.print(sb).p(", ");
+ return sb.unchar(2).p("]");
+ }
+ @Override public SB gprint(SB sb) {
+ if( this==TOP ) return sb.p("[TOP]");
+ if( this==BOT ) return sb.p("[BOT]");
+ sb.p("[ ");
+ for( SONType t : _types )
+ t.gprint(sb).p(", ");
+ return sb.unchar(2).p("]");
+ }
+
+ @Override
+ int hash() {
+ int sum = 0;
+ if( _types!=null ) for( SONType type : _types ) sum ^= type.hashCode();
+ return sum;
+ }
+
+ @Override
+ boolean eq( SONType t ) {
+ SONTypeTuple tt = (SONTypeTuple)t; // Contract
+ if( _types==null && tt._types==null ) return true;
+ if( _types==null || tt._types==null ) return false;
+ if( _types.length != tt._types.length ) return false;
+ for( int i=0; i<_types.length; i++ )
+ if( _types[i]!=tt._types[i] )
+ return false;
+ return true;
+ }
+
+
+}
diff --git a/seaofnodes/src/test/java/com/compilerprogramming/ezlang/compiler/Main.java b/seaofnodes/src/test/java/com/compilerprogramming/ezlang/compiler/Main.java
new file mode 100644
index 0000000..8b8400f
--- /dev/null
+++ b/seaofnodes/src/test/java/com/compilerprogramming/ezlang/compiler/Main.java
@@ -0,0 +1,19 @@
+package com.compilerprogramming.ezlang.compiler;
+
+import com.compilerprogramming.ezlang.compiler.codegen.CodeGen;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+public class Main {
+ public static final String PORTS = "com.compilerprogramming.ezlang.compiler.nodes.cpus";
+ // Compile and run a simple program
+ public static void main( String[] args ) throws Exception {
+ // First arg is file, 2nd+ args are program args
+ String src = Files.readString(Path.of(args[0]));
+ CodeGen code = new CodeGen(src);
+ code.parse().opto().typeCheck().GCM().localSched();
+ System.out.println(code._stop);
+ //System.out.println(Eval2.eval(code,arg,100000));
+ }
+}
diff --git a/seaofnodes/src/test/java/com/compilerprogramming/ezlang/compiler/Simple.java b/seaofnodes/src/test/java/com/compilerprogramming/ezlang/compiler/Simple.java
new file mode 100644
index 0000000..eb5c5d2
--- /dev/null
+++ b/seaofnodes/src/test/java/com/compilerprogramming/ezlang/compiler/Simple.java
@@ -0,0 +1,304 @@
+package com.compilerprogramming.ezlang.compiler;
+
+import com.compilerprogramming.ezlang.compiler.codegen.CodeGen;
+import com.compilerprogramming.ezlang.compiler.print.*;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.io.InputStreamReader;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.stream.Collectors;
+
+public class Simple {
+ public static final String PORTS = "com.seaofnodes.simple.node.cpus";
+
+ static final int DUMP_AFTER_PARSE = 1<<0;
+ static final int DUMP_AFTER_OPTO = 1<<1;
+ static final int DUMP_AFTER_TYPE_CHECK = 1<<2;
+ static final int DUMP_AFTER_INSTR_SELECT = 1<<3;
+ static final int DUMP_AFTER_GCM = 1<<4;
+ static final int DUMP_AFTER_LOCAL_SCHED = 1<<5;
+ static final int DUMP_AFTER_REG_ALLOC = 1<<6;
+ static final int DUMP_AFTER_ENCODE = 1<<7;
+
+ static final int DUMP_FINAL = 1<<16;
+
+ static final int DUMP_DOT = 1<<29;
+ static final int DUMP_PASS_NAME = 1<<30;
+ static final int DUMP_AFTER_ALL = 0xff | DUMP_PASS_NAME;
+ static final int DUMP_DOT_AFTER_ALL = 0xff | DUMP_DOT;
+
+ static final int DUMP_DISASSEMBLE = 1<<31;
+
+ static String system_cpu = null;
+ static String system_abi = null;
+
+ static void usage() {
+ System.out.println(
+"""
+simple [options] [--eval|--run ...]
+Options:
+ --dump - dump final intermediate representation
+ --dump-after-parse - dump intermediate representation after parse
+ --dump-after-opto - dump intermediate representation after opto pass
+ --dump-after-type-check - dump intermediate representation after type check pass
+ --dump-after-instr-select - dump intermediate representation after instrution selection pass
+ --dump-after-gcm - dump intermediate representation after GCM pass
+ --dump-after-local-sched - dump intermediate representation after local scheduling pass
+ --dump-after-reg-alloc - dump intermediate representation after register allocation pass
+ --dump-after-encode - dump intermediate representation after encoding pass
+ --dump-after-all - dump intermediate representation after all passes
+ --dot - dump grapical representation of intermediate code into *.dot file(s)
+ -S - dump generated assembler code
+ --eval ... - evaluate the compiled code in emulator
+ --run ... - run the compiled code natively
+ --dump-size - print the size of generated code
+ --dump-time - print compilation and execution times
+ --cpu - use specific CPU (x86_64_v2, riscv, arm)
+ --abi - use speific ABI variant (SystemV)
+ --target - print native CPU and ABI
+ --version
+ --help
+"""
+);
+ System.exit(0);
+ }
+
+ static void bad_usage() {
+ System.err.println("ERROR: Invalid usage (use --help)");
+ System.exit(1);
+ }
+
+ static void target() {
+ if (system_cpu == null || system_abi == null) {
+ System.err.println("ERROR: Unknown target");
+ System.exit(1);
+ }
+ System.out.print(system_cpu);
+ System.out.print("-");
+ System.out.println(system_abi);
+ System.exit(0);
+ }
+
+ static void dump(CodeGen code, int dump, int pass) {
+ if ((dump & pass) != 0) {
+ if ((dump & DUMP_DOT) != 0) {
+ String fn = switch (pass) {
+ case DUMP_AFTER_PARSE -> "01-parse.dot";
+ case DUMP_AFTER_OPTO -> "02-opto.dot";
+ case DUMP_AFTER_TYPE_CHECK -> "03-type_check.dot";
+ case DUMP_AFTER_INSTR_SELECT -> "04-instr_select.dot";
+ case DUMP_AFTER_GCM -> "05-gcm.dot";
+ case DUMP_AFTER_LOCAL_SCHED -> "06-local_sched.dot";
+ case DUMP_AFTER_REG_ALLOC -> "07-reg_allos.dot";
+ case DUMP_AFTER_ENCODE -> "08-local_sched.dot";
+ case DUMP_FINAL -> "09-final.dot";
+ default -> throw Utils.TODO();
+ };
+
+ try {
+ Files.writeString(Path.of(fn),
+ new GraphVisualizer().generateDotOutput(code._stop, null, null));
+ } catch(IOException e) {
+ System.err.println("ERROR: Cannot write DOT file");
+ System.exit(1);
+ }
+ } else {
+ if ((dump & DUMP_PASS_NAME) != 0) {
+ System.err.println(switch (pass) {
+ case DUMP_AFTER_PARSE -> "After Parse:";
+ case DUMP_AFTER_OPTO -> "After OPTO:";
+ case DUMP_AFTER_TYPE_CHECK -> "After Type Check:";
+ case DUMP_AFTER_INSTR_SELECT -> "After Instruction Selection:";
+ case DUMP_AFTER_GCM -> "After GCM:";
+ case DUMP_AFTER_LOCAL_SCHED -> "After Local Scheduling:";
+ case DUMP_AFTER_REG_ALLOC -> "After Register Allocation:";
+ case DUMP_AFTER_ENCODE -> "After Instruction Encoding:";
+ case DUMP_FINAL -> "Final:";
+ default -> throw Utils.TODO();
+ });
+ }
+
+ System.err.println(IRPrinter.prettyPrint(code._stop, 9999));
+ }
+ }
+ }
+
+ static void print_compilation_times(CodeGen code) {
+ double t, total = 0;
+
+ total += t = code._tParse / 1e3;
+ System.out.println(String.format("Parsing Time: %.3f sec", t));
+ total += t = code._tOpto / 1e3;
+ System.out.println(String.format("Optimization Time: %.3f sec", t));
+ total += t = code._tTypeCheck / 1e3;
+ System.out.println(String.format("Type Checking Time: %.3f sec", t));
+ total += t = code._tInsSel / 1e3;
+ System.out.println(String.format("Instruction Selection Time: %.3f sec", t));
+ total += t = code._tGCM / 1e3;
+ System.out.println(String.format("GCM Time: %.3f sec", t));
+ total += t = code._tLocal / 1e3;
+ System.out.println(String.format("Local Scheduling Time: %.3f sec", t));
+ total += t = code._tRegAlloc / 1e3;
+ System.out.println(String.format("Register Allocation Time: %.3f sec", t));
+ total += t = code._tEncode / 1e3;
+ System.out.println(String.format("Instruction Encoding Time: %.3f sec", t));
+ System.out.println(String.format("TOTAL COMPILATION TIME: %.3f sec", total));
+ }
+
+ static String getInput() {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
+ return reader.lines().collect(Collectors.joining("\n"));
+ }
+
+ public static void main(String[] args) throws Exception {
+ String input_filename = null;
+ boolean use_stdin = false;
+ boolean do_eval = false;
+ boolean do_run = false;
+ boolean do_codegen = false;
+ boolean do_print_size = false;
+ boolean do_print_time = false;
+ int dump = 0;
+ int first_arg = 0;
+ String src = null;
+ String cpu = null;
+ String abi = null;
+
+ // TODO: autodetect
+ system_cpu = "x86_64_v2";
+ system_abi = "SystemV";
+
+ // Parse command line
+loop: for (int i = 0; i < args.length; i++) {
+ String arg = args[i];
+ if (arg.charAt(0) == '-') {
+ switch (arg) {
+ case "-": use_stdin = true; break;
+ case "--dump": dump |= DUMP_FINAL; break;
+ case "--dump-after-parse": dump |= DUMP_AFTER_PARSE; break;
+ case "--dump-after-opto": dump |= DUMP_AFTER_OPTO; break;
+ case "--dump-after-type-check": dump |= DUMP_AFTER_TYPE_CHECK; break;
+ case "--dump-after-instr-select": dump |= DUMP_AFTER_INSTR_SELECT; break;
+ case "--dump-after-gcm": dump |= DUMP_AFTER_GCM; break;
+ case "--dump-after-local-sched": dump |= DUMP_AFTER_LOCAL_SCHED; break;
+ case "--dump-after-reg-alloc": dump |= DUMP_AFTER_REG_ALLOC; break;
+ case "--dump-after-encode": dump |= DUMP_AFTER_ENCODE; break;
+ case "--dump-after-all": dump |= DUMP_AFTER_ALL; break;
+ case "--dot": dump |= DUMP_DOT; break;
+ case "-S": dump |= DUMP_DISASSEMBLE; break;
+ case "--eval": do_eval = true; first_arg = i + 1; break loop;
+ case "--run": do_run = true; first_arg = i + 1; break loop;
+ case "--dump-size": do_print_size = true; break;
+ case "--dump-time": do_print_time = true; break;
+ case "--cpu": if (cpu != null
+ || i + 1 >= args.length
+ || args[i + 1].charAt(0) == '-') bad_usage();
+ cpu = args[i + 1];
+ i++;
+ break;
+ case "--abi": if (abi != null
+ || i + 1 >= args.length
+ || args[i + 1].charAt(0) == '-') bad_usage();
+ abi = args[i + 1];
+ i++;
+ break;
+ case "--target": target();
+ case "--version": throw Utils.TODO();
+ case "--help": usage();
+ default: bad_usage();
+ }
+ } else {
+ if (input_filename != null || use_stdin) bad_usage();
+ input_filename = arg;
+ }
+ }
+
+ if (input_filename == null && !use_stdin) {
+ System.err.println("ERROR: no input file' (use --help)");
+ System.exit(1);
+ }
+
+ if (do_run || (dump & DUMP_DISASSEMBLE) != 0 || do_print_size) {
+ if (cpu == null) cpu = system_cpu;
+ if (abi == null) abi = system_abi;
+ if (cpu == null || abi == null) {
+ System.err.println("ERROR: Cannot compile for unknown target");
+ System.exit(1);
+ }
+ do_codegen = true;
+ }
+
+ // Read input file
+ try {
+ src = use_stdin ? getInput() : Files.readString(Path.of(input_filename));
+ } catch(IOException e) {
+ System.err.println("ERROR: Cannot read input file");
+ System.exit(1);
+ }
+
+ // Compilation pipeline
+ CodeGen code = new CodeGen(src);
+
+ code.parse();
+ dump(code, dump, DUMP_AFTER_PARSE);
+
+ code.opto();
+ dump(code, dump, DUMP_AFTER_OPTO);
+
+ code.typeCheck();
+ dump(code, dump, DUMP_AFTER_TYPE_CHECK);
+
+ if (do_codegen) {
+ code.instSelect(PORTS, cpu, abi);
+ dump(code, dump, DUMP_AFTER_INSTR_SELECT);
+ }
+
+ code.GCM();
+ dump(code, dump, DUMP_AFTER_GCM);
+
+ code.localSched();
+ dump(code, dump, DUMP_AFTER_LOCAL_SCHED);
+
+ if (do_codegen) {
+ code.regAlloc();
+ dump(code, dump, DUMP_AFTER_REG_ALLOC);
+
+ code.encode();
+ dump(code, dump, DUMP_AFTER_ENCODE);
+ }
+
+ dump(code, dump, DUMP_FINAL);
+
+ if (do_codegen && (dump & DUMP_DISASSEMBLE) != 0) {
+ System.out.println(code.asm());
+ }
+
+ if (do_print_size) {
+ System.out.println(String.format("Code Size: %d", code._encoding._bits.size()));
+ }
+
+ if (do_print_time) {
+ print_compilation_times(code);
+ }
+
+// if (do_eval) {
+// // TODO: Support for evaluation of functions with different argument numbers and types
+// long t = System.currentTimeMillis();
+// long arg = (first_arg < args.length) ? Integer.valueOf(args[first_arg]) : 0;
+// System.out.println(Eval2.eval(code, arg, 100000));
+// if (do_print_time) {
+// System.out.println(String.format("EXECUTION TIME: %.3f sec",
+// (System.currentTimeMillis() - t) / 1e3));
+// }
+// } else
+ if (do_run) {
+ if (cpu != system_cpu || abi != system_abi) {
+ System.err.println("ERROR: cannot run code on not native target");
+ System.exit(1);
+ }
+ throw Utils.TODO();
+ }
+ }
+}
diff --git a/seaofnodes/src/test/java/com/compilerprogramming/ezlang/compiler/TestSONTypes.java b/seaofnodes/src/test/java/com/compilerprogramming/ezlang/compiler/TestSONTypes.java
new file mode 100644
index 0000000..6584c71
--- /dev/null
+++ b/seaofnodes/src/test/java/com/compilerprogramming/ezlang/compiler/TestSONTypes.java
@@ -0,0 +1,221 @@
+package com.compilerprogramming.ezlang.compiler;
+
+import com.compilerprogramming.ezlang.compiler.codegen.CodeGen;
+import org.junit.Test;
+
+import static com.compilerprogramming.ezlang.compiler.Main.PORTS;
+import static org.junit.Assert.assertEquals;
+
+public class TestSONTypes {
+
+ String compileSrc(String src) {
+ var compiler = new CodeGen(src);
+ compiler.parse();
+ return null;
+ }
+
+ @Test
+ public void test1() {
+ String src = """
+struct T { var i: Int }
+""";
+ compileSrc(src);
+ }
+
+ @Test
+ public void test2() {
+ String src = """
+struct T { var i: S }
+struct S { var i: Int }
+""";
+ compileSrc(src);
+ }
+
+ @Test
+ public void test3() {
+ String src = """
+struct T { var next: T? }
+""";
+ compileSrc(src);
+ }
+
+ @Test
+ public void test4() {
+ String src = """
+struct T { var arr: [Int] }
+""";
+ compileSrc(src);
+ }
+
+ @Test
+ public void test5() {
+ String src = """
+func foo() {}
+""";
+ compileSrc(src);
+ }
+
+ @Test
+ public void test6() {
+ String src = """
+func foo(a: Int) {}
+""";
+ compileSrc(src);
+ }
+
+
+ @Test
+ public void test7() {
+ String src = """
+func foo(a: Int, b: Int)->Int {}
+""";
+ compileSrc(src);
+ }
+
+ @Test
+ public void test8() {
+ String src = """
+func foo(a: Int, b: Int)->[Int] {}
+""";
+ compileSrc(src);
+ }
+
+ @Test
+ public void test9() {
+ String src = """
+func foo(a: Int, b: Int)->[Int]? {}
+""";
+ compileSrc(src);
+ }
+
+ @Test
+ public void test10() {
+ String src = """
+func foo(a: Int, b: Int)->Int {
+ return a+b;
+}
+""";
+ compileSrc(src);
+ }
+
+ @Test
+ public void test11() {
+ String src = """
+func foo()->[Int] {
+ return new [Int]{};
+}
+""";
+ compileSrc(src);
+ }
+
+ @Test
+ public void test12() {
+ String src = """
+func foo()->[Int] {
+ return new [Int]{1};
+}
+""";
+ compileSrc(src);
+ }
+
+ @Test
+ public void test13() {
+ String src = """
+func foo()->[Int] {
+ return new [Int]{42,84};
+}
+""";
+ compileSrc(src);
+ }
+
+ @Test
+ public void test14() {
+ String src = """
+struct T { var i: Int }
+func foo()->T {
+ return new T{i=1};
+}
+""";
+ compileSrc(src);
+ }
+
+ @Test
+ public void test15() {
+ String src = """
+struct T { var i: Int; var j: Int }
+func foo()->T {
+ return new T{i=23, j=32};
+}
+""";
+ compileSrc(src);
+ }
+
+ @Test
+ public void test16() {
+ String src = """
+func foo()->Int {
+ return new [Int]{42,84}[1];
+}
+""";
+ compileSrc(src);
+ }
+
+ @Test
+ public void test17() {
+ String src = """
+struct T { var i: Int; var j: Int }
+func foo()->Int {
+ return new T{i=23, j=32}.j;
+}
+""";
+ compileSrc(src);
+ }
+
+ @Test
+ public void test18() {
+ String src = """
+func bar()->Int { return 1 }
+func foo()->Int {
+ return bar();
+}
+""";
+ compileSrc(src);
+ }
+
+ @Test
+ public void test19() {
+ String src = """
+func foo()->Int {
+ var x = 1
+ return x
+}
+""";
+ compileSrc(src);
+ }
+
+ static void testCPU( String src, String cpu, String os, int spills, String stop ) {
+ CodeGen code = new CodeGen(src);
+ code.parse().opto().typeCheck().instSelect(PORTS,cpu,os).GCM().localSched().regAlloc().encode();
+ int delta = spills>>3;
+ if( delta==0 ) delta = 1;
+ assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta);
+ if( stop != null )
+ assertEquals(stop, code._stop.toString());
+ System.out.println(code.asm());
+ }
+
+ private static void testAllCPUs( String src, int spills, String stop ) {
+ testCPU(src,"x86_64_v2", "SystemV",spills,stop);
+ testCPU(src,"riscv" , "SystemV",spills,stop);
+ testCPU(src,"arm" , "SystemV",spills,stop);
+ }
+
+ @Test
+ public void test101() {
+ testAllCPUs("""
+ func main()->Int {
+ return 42
+ }
+ """, 0, null);
+ }
+}
diff --git a/types/src/main/java/com/compilerprogramming/ezlang/types/Type.java b/types/src/main/java/com/compilerprogramming/ezlang/types/Type.java
index 1233a64..05cbca6 100644
--- a/types/src/main/java/com/compilerprogramming/ezlang/types/Type.java
+++ b/types/src/main/java/com/compilerprogramming/ezlang/types/Type.java
@@ -140,6 +140,7 @@ public int getFieldIndex(String name) {
return fieldNames.indexOf(name);
}
public int numFields() { return fieldNames.size(); }
+ public String getFieldName(int index) { return fieldNames.get(index); }
public void complete() { pending = false; }
}
| |