package com.seaofnodes.simple; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.print.IRPrinter; import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; import java.util.*; public abstract class Eval2 { // A mapping from Node ID to evaluation data Object. // Data Objsts are a boxed Long or Double; or an Object[] of fields, which // themselves are just more Objects. private static class Frame { private static int UID=1; private final IdentityHashMap _data; private Frame _prior; // Prior frame in this sequence, means when making a new frame we can find old stuff private int _refcnt; // TODO: Use this in a sentence (free/recycle unused stack frames) final int _uid = UID++; // Debugging aid Frame(Frame prior) { _prior = prior; if( prior!=null ) prior._refcnt++; _data = new IdentityHashMap<>(); _refcnt = 1; } // Dec ref count void dec() { assert _refcnt>0; if( --_refcnt == 0 ) { Frame prior = _prior; _prior = null; _data.clear(); // TODO: FREE LIST? if( prior!=null ) prior.dec(); } } // Put a mapping from Node->Object Node put(Node n, Object d) { assert d!=null; return put0(n,d); } // Put a mapping from Node->Object? Node put0(Node n, Object d) { assert _refcnt>0; _data.put(n,d); return n; } // Get a mapped Node, perhaps using the prior stack frame crawl Object get(Node n) { assert _refcnt>0; Object d = _data.get(n); return d != null ? d : _prior == null ? null : _prior.get(n); } // Short debugging print public String p() { return "#"+_uid + (_prior==null ? "" : " >> " + _prior.p()); } // Long debugging print @Override public String toString() { String s = ""; if( _prior != null ) s+= _prior+" << "; s += "#"+_uid+"[ "; for( Node n : _data.keySet() ) s += n + ":" + _data.get(n) + ","; s += "]"; return s; } } // Classic Closure; an environment (stack of frames) and an execution point private static class Closure { // Where to return too; either a CallEnd or a Function final CFGNode _cc; // Stack frame at the execution point. Includes Closures which // recursively include other stack frames private Frame _frame; Closure(CFGNode cc, Frame frame) { _cc = cc; // One of Fun, CallEnd or Stop (null) _frame = frame; assert _frame._refcnt >=0; _frame._refcnt++; } FunNode fun() { return (FunNode)_cc; } CallEndNode cend() { return (CallEndNode)_cc; } boolean isStop() { return _cc instanceof StopNode; } // or Stop // void pushFrame() { _frame = new Frame(_frame); _frame._refcnt++; } @Override public String toString() { return _cc.toString(); } } // CodeGen has the linker tables public static CodeGen CODE; // Current active Frame, to avoid having to pass it everywhere public static Frame F; public static String eval( CodeGen code, long arg ) { return eval(code,arg,1000); } public static String eval( CodeGen code, long arg, int timeout ) { if( code._start.uctrl()==null ) return ""; // The empty program SB trace = null; // new SB(); // TRACE, set to null for off, new SB() for on // Force local scheduling phase code.driver(CodeGen.Phase.LocalSched); // Set global, so don't have to pass everywhere CODE = code; // Timer for when we run too long. Ticks once per backedge or function call int loopCnt = 0; // Current value for each Node; boxed Long or Double; or an Object[] of // fields, which themselves are just more Objects. F = new Frame(null); // Start at the START CFGNode BB = code._start, prior = null; // Return from main hits Stop F.put(BB,new Closure(code._stop,F)); // For all / Clazzes, install their uninitialized memory for( FunNode fun : code._linker ) if( fun!=null && fun.isClz() ) { TypeStruct clz = ((TypeMemPtr)fun.sig().arg(0))._obj; // Map from the self parameter to the // singleton class structure. F.put(fun.parm(2),alloc(clz)); } // ------------ // Evaluate until exit or timeout. Each outer loop step computes // all data nodes under some new Control. while( true ) { if( BB==null ) return null; traceCtrl(BB,trace); if( trace!=null ) System.out.println(F.p()); // Parallel assign Phis int i = BB instanceof RegionNode r ? parallelAssignPhis(r,prior,arg,trace) : 0; // Compute the data nodes until BB control for( ; i < BB.nOuts(); i++ ) if( !(BB.out(i) instanceof CFGNode) ) step(BB.out(i), trace); // Find the next control target prior = BB; BB = BB.uctrl(); // Next unique control target switch( prior ) { case StartNode start: F = new Frame(F); break; // Frame for the call to main, as-if Called case CallNode call : BB = call(call); break; case NeverNode never: BB = never.cproj(0); break; // Synthetic exit is never taken. case IfNode iff : Object p = val(iff.pred()); BB = iff.cproj( p==null || (p instanceof Long x && x==0L) ? 1 : 0); break; case ReturnNode ret : if( clj(ret.rpc()).isStop() ) return exit(ret); else BB = ret(ret); break; case FunNode fun : if( loopCnt++ > timeout ) return null; break; // Timeout case LoopNode loop : if( loopCnt++ > timeout ) return null; break; // Timeout case CallEndNode cend: break; case CProjNode cproj: break; case RegionNode r : break; default: throw Utils.TODO(); } if( BB==null ) return null; assert !BB.isDead(); } } // Phi cache; size is limited to number of Phis at a merge point private static final Object[] PHICACHE = new Object[256]; static int parallelAssignPhis(RegionNode r, CFGNode prior, long arg, SB trace) { // Compute input path on Phis for this Region int path = r._inputs.find( prior ); // Parameters read from prior frame, Phis from local frame Frame frame = (r instanceof FunNode ? F._prior : F); boolean isClz = r instanceof FunNode fun && fun.isClz(); // Parallel assign Phis. First parallel read and cache int i; for( i = 0; i < r.nOuts(); i++ ) { // Due to scheduling, all Phis are first in the outputs; first // non-Phi is the end of all Phis. if( !(r.out(i) instanceof PhiNode phi) ) break; // Parms read from Call args - // - except RPC is the actual prior frame // - except takes the singleton class object and external args Object val; if( isClz && phi instanceof ParmNode parm && parm._idx==3 ) PHICACHE[i] = arg; // Reading the initial arguments to else { Node n = phi instanceof ParmNode parm // RPC uses the prior Call directly; // Parms may not be linked, so read call args directly ? (parm._idx==0 ? prior : prior.in(parm._idx)) // Phis read from path input : phi.in(path); // Read the frame before overwriting PHICACHE[i] = frame.get(n); } } // Parallel assign; might assign before read, so read from cache for( int j=0; j < i; j++ ) traceData(F.put0(r.out(j),PHICACHE[j]),trace); // Return point in basic block past last Phi; the Phis have been // evaluated here, and now we need to evaluate the rest of the block return i; } // Make a call. private static CFGNode call( CallNode call ) { // Set up the Return PC and return frame (which is just the current frame). // Really making a Continuation, but it's just a Closure Closure cont = new Closure(call.cend(),F); // Set the return continuation into the current frame RPC F.put(call, cont ); // Next Frame to use F = new Frame(F); // Target function and environment return CODE.link(tfp(call.fptr())); } private static CFGNode ret( ReturnNode ret ) { // Fetch return expression value and continuation from current frame Object rez = val(ret.expr()); Closure cont = clj(ret.rpc()); // Lower Frame reference count; might delete frame F.dec(); // Lower ref count // Install replacement Frame; set return result over the CallEnd F = cont._frame; F.put0(cont._cc,rez); // Set return value into CallEnd return cont._cc; // Return CallEnd as the new control } private static String exit( ReturnNode ret ) { return prettyPrint(ret.expr()._type,val(ret.expr())); } // Take a worklist step: one data op from the current Control is updated. private static void step(Node n, SB trace) { if( n instanceof PhiNode ) return; // Compute new value F.put0(n,compute(n)); traceData(n,trace); // Also do any following projections, which are not in the local schedule otherwise if( n instanceof MultiNode ) for( Node use : n._outputs ) step(use,trace); } // From here down shamelessly copied from Evaluator, written by @Xmilia private static Object compute( Node n ) { return switch( n ) { case AddNode add -> add.mode()==1 ? (Object)(x(add.in(1)) + x(add.in(2))) : (Object)(d(add.in(1)) + d(add.in(2))); case AndNode and -> x(and.in(1)) & x(and.in(2)); case BoolNode.EQ eq -> Objects.equals(val(eq.in(1)), val(eq.in(2))) ? 1L : 0L; // Bool EQ supports pointers, nil, and integers case BoolNode.LE le -> le.mode()==1 ? (x(le.in(1)) <= x(le.in(2)) ? 1L : 0L) : (d(le.in(1)) <= d(le.in(2)) ? 1L : 0L); case BoolNode.LT lt -> lt.mode()==1 ? (x(lt.in(1)) < x(lt.in(2)) ? 1L : 0L) : (d(lt.in(1)) < d(lt.in(2)) ? 1L : 0L); case GuardNode guard -> val(guard.in(1)); case ConstantNode con -> con(con._con); case DivNode div -> div.mode()==1 ? (Object)(x(div.in(2))==0 ? 0L : x(div.in(1)) / x(div.in(2))) : (Object)((d(div.in(2))==0 ? 0D : d(div.in(1))) / d(div.in(2))); case EscapeNode esc -> esc(esc); case FunPtrNode fptr -> con(fptr._con); case LoadNode ld -> load(ld); case MemMergeNode merge-> "$mem"; case MinusNode sub -> sub.mode()==1 ? (Object)(- x(sub.in(1))) : (Object)(- d(sub.in(1))); case MulNode mul -> mul.mode()==1 ? (Object)(x(mul.in(1)) * x(mul.in(2))) : (Object)(d(mul.in(1)) * d(mul.in(2))); case NewNode alloc-> alloc(alloc); case NotNode not -> not(not); case OrNode or -> x(or .in(1)) | x(or .in(2)); case ProjNode proj -> proj._type instanceof TypeMem ? "$mem" : val(proj.in(0)); case ReadOnlyNode read -> val(read.in(1)); case SarNode sar -> x(sar.in(1)) >> x(sar.in(2)); case ShlNode shl -> x(shl.in(1)) << x(shl.in(2)); case ShrNode shr -> x(shr.in(1)) >>>x(shr.in(2)); case StoreNode st -> store(st); case SubNode sub -> sub.mode()==1 ? (Object)(x(sub.in(1)) - x(sub.in(2))) : (Object)(d(sub.in(1)) - d(sub.in(2))); case ToFloatNode toflt-> (double)x(toflt.in(1)); case XorNode xor -> x(xor.in(1)) ^ x(xor.in(2)); default -> throw Utils.TODO(); }; } // Fetch without unboxing, searching up-Frame static Object val( Node n ) { return F.get(n); } // Fetch and unbox as primitive long static long x( Node n ) { Object d = F.get(n); return d==null ? 0 : (Long)d; } // Fetch and unbox as primitive double static double d( Node n ) { Object d = F.get(n); return d==null ? 0 : (Double)d; } // Fetch and unbox a function constant static TypeFunPtr tfp(Node n) { return (TypeFunPtr)F.get(n); } // Fetch and unbox a closure static Closure clj(Node n) { return (Closure)F.get(n); } // Java box ints and floats. Other things can just be null or some informative string private static Object con( Type t ) { return switch ( t ) { case TypeInteger i -> i.isConstant() ? (Long )i.value() : "INT"; case TypeFloat f -> f.isConstant() ? (Double)f.value() : "FLT"; case TypeFunPtr tfp -> tfp; case TypeMem mem -> "MEM"; case TypeMemPtr tmp -> { if( tmp._obj.isAry() ) { // Constant array ptr Type elem = tmp._obj.field("[]")._t; if( elem instanceof TypeConAry con ) { Object[] xs = new Object[con.len()]; for( int i=0; i null; }; } // Escapes of constant memory actually needs to set the memory private static Object esc(EscapeNode esc) { if( esc.priv() instanceof ConstantNode con ) { TypeMem mem = (TypeMem)con._type; Object val = con(mem._t); TypeMemPtr tmp = (TypeMemPtr)esc.self()._type; int idx = tmp._obj.findAlias(esc.fld()._alias); Object[] fs = (Object[])val(esc.self()); if( tmp._obj.isAry() ) { assert idx==0; // length field assert (Long)val==fs.length; } else { fs[idx] = val; } } return "$mem"; } // Convert array size to array element count private static int offToIdx( long off, TypeStruct t) { off -= t.aryBase(); int scale = t.aryScale(); assert (off & ((1L << scale)-1)) == 0; return (int)(off>>scale); } // Builds and returns a pointer Object private static Object[] alloc(NewNode alloc) { TypeStruct type = alloc._ts; if( type.isAry() ) { long sz = (Long)val(alloc.in(1)); long x = offToIdx(sz, type); int n = (int)x; if( n!=x || n<0 ) throw new NegativeArraySizeException(""+n); Object[] ary = new Object[n]; // Array body var elem = type._fields[1]._t; if (elem instanceof TypeInteger) { Arrays.fill( ary, 0L ); } else if (elem instanceof TypeFloat) { Arrays.fill( ary, 0D ); } else { assert elem instanceof TypeMemPtr || elem instanceof TypeFunPtr; } return ary; } return alloc(type); } private static Object[] alloc(TypeStruct type) { assert !type.isAry(); int num = type._fields.length; Object[] ptr = new Object[num]; for( int i=0; i VISIT = new IdentityHashMap<>(); static void traceCtrl( CFGNode BB, SB sb ) { if( sb==null ) return; IRPrinter.printLine(BB,sb.p("--- ")); System.out.print(sb); sb.clear(); } static void traceData( Node n, SB sb ) { if( sb==null ) return; IRPrinter.printLine(n,sb).unchar(); _print( n._type, F.get(n), sb.p('\t'), VISIT ); System.out.println(sb); sb.clear(); VISIT.clear(); } // Use type to print x as a string. Uses a new VISIT table so the debugger // doesn't try to reuse VISIT if stopping mid-print. The trace code reuses // VISIT endlessly for some minor speed hope static String prettyPrint( Type t, Object x ) { return _print(t,x,new SB(), new IdentityHashMap<>()).toString(); } static SB _print( Type t, Object x, SB sb, IdentityHashMap visit ) { if( x instanceof String s ) return sb.p(s); if( x == null ) return sb.p("null"); return switch( t ) { case TypeInteger i -> sb.p( (Long)x); case TypeFloat f -> sb.p((Double)x); case TypeMemPtr tmp -> { if( visit.containsKey(x) ) yield sb.p("$cyclic"); visit.put(x,x); Object[] xs = (Object[])x; // Array of fields if( tmp._obj.isAry() ) { Type elem = tmp._obj._fields[1]._t; if( elem.isa(TypeInteger.U8) ) { // Shortcut u8[] as a String for( Object o : xs ) sb.p((char)(long)(Long)o); } else { // Array of elements elem.print(sb).p("[ "); if( elem instanceof TypeConAry ) elem = TypeInteger.ZERO; for( Object o : xs ) _print( elem, o, sb, visit ).p( "," ); sb.unchar().p("]"); } } else { sb.p(tmp._obj._name).p("{"); Field[] flds = tmp._obj._fields; for( int i=0; i sb.p(x.toString()); case TypeRPC rpc -> sb.p(x.toString()); case TypeMem mem -> sb.p("$mem"); case TypeTuple tt -> { if( tt._types.length>1 && tt._types[1] instanceof TypeMemPtr ) // Assume a NewNode yield _print( tt._types[1], x, sb, visit ); throw Utils.TODO(); } case Type tt -> { if( tt == Type.NIL ) yield sb.p("null"); throw Utils.TODO(); } }; } }