diff options
Diffstat (limited to 'chapter29/src/test/java/com/seaofnodes/simple/Eval2.java')
| -rw-r--r-- | chapter29/src/test/java/com/seaofnodes/simple/Eval2.java | 567 |
1 files changed, 567 insertions, 0 deletions
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/Eval2.java b/chapter29/src/test/java/com/seaofnodes/simple/Eval2.java new file mode 100644 index 00000000..6e5331b2 --- /dev/null +++ b/chapter29/src/test/java/com/seaofnodes/simple/Eval2.java @@ -0,0 +1,567 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.parser.Parser; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.print.IRPrinter; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.Ary; +import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; +import java.util.*; + +public abstract class Eval2 { + + // A mapping from Node ID to evaluation data Object. + // Data Objects 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<Node,Object> _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; + } + } + + // A Classic Continuation; the suspended caller and the control point to + // resume after a return. + private static class Continuation { + // Resume a CallEnd or a CLInit's successor, or exit at Stop. + final CFGNode _cc; + + // Stack frame at the execution point. Includes Continuations which + // recursively include other stack frames + final Frame _frame; + + Continuation(CFGNode cc, Frame frame) { + _cc = cc; + _frame = frame; + assert _frame._refcnt >=0; + _frame._refcnt++; + } + + boolean isStop() { return _cc instanceof StopNode; } + + @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; + + // Class pointers and initializer self parameters share one object per run. + private static final HashMap<String,Object[]> CLASSES = new HashMap<>(); + + public static String eval( CodeGen code, long arg ) { return eval(code,arg,1000); } + public static String eval( CodeGen code, long arg, int timeout ) { + code.driver(CodeGen.Phase.TypeCheck); + if( code._start.uctrl()==null ) return ""; // The empty program + SB trace = null; // new SB(); // TRACE, set to null for off, new SB() for on + // Set global, so don't have to pass everywhere + CODE = code; + // The normal driver schedules ideal IR without Lower or instruction + // selection. Objects loaded later are scheduled independently. + code.driver(CodeGen.Phase.LocalSched); + CLASSES.clear(); + + // 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); + + FunNode entry = (FunNode)code._start.uctrl(); + VALUE_CACHE.setX(0,"$mem"); + VALUE_CACHE.setX(1,classObject(((TypeMemPtr)entry.sig().arg(0))._obj)); + VALUE_CACHE.setX(2,arg); + CFGNode BB = enter(entry,code._stop); + CFGNode prior = null; + + + // ------------ + // 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,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 CallNode call : BB = call(call); break; + case CLInitNode init: BB = call(init); 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( continuation(ret).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("Eval control: "+prior.getClass().getSimpleName()); + } + if( BB==null ) return null; + assert !BB.isDead(); + } + } + + // Scratch values for parallel Phi assignment and call arguments. Each use + // is consumed before the next block executes; loading only schedules code. + private static final Ary<Object> VALUE_CACHE = new Ary<>(Object.class); + static int parallelAssignPhis(RegionNode r, CFGNode prior, SB trace) { + + // enter() assigned parameters, including the return continuation, + // from runtime arguments rather than the compiler's call graph. + if( r instanceof FunNode ) { + int i = 0; + while( i<r.nOuts() && r.out(i) instanceof ParmNode ) i++; + return i; + } + + // Compute input path on Phis for this Region + int path = r._inputs.find( prior ); + + // 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; + + VALUE_CACHE.setX(i,F.get(phi.in(path))); + } + // Parallel assign; might assign before read, so read from cache + for( int j=0; j < i; j++ ) + traceData(F.put0(r.out(j),VALUE_CACHE.at(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 ) { + int nargs = call.nargs()+1; // Include memory. + for( int i = 0; i<nargs; i++ ) VALUE_CACHE.setX(i,val(call.in(i+1))); + return enter(CODE.link(tfp(call.fptr())),call.cend()); + } + + // Interpret the flag-test/call diamond that Lower would emit. The taken + // arm uses the ordinary call entry and RPC continuation. The initializer + // itself initializes its parent and sets its own $init flag. + private static CFGNode call(CLInitNode init) { + CompUnit cu = init.cu(); + //if( cu==null ) return init.uctrl(); + assert cu != null; // Post-optimizer the remaining CLInits refer to real classes + Object[] obj = classObject(cu._clz); + if( obj[cu._clz.find("$init")].equals(1L) ) + return init.uctrl(); // Class already initialized! + VALUE_CACHE.setX(0,"$mem"); + VALUE_CACHE.setX(1,obj); + VALUE_CACHE.setX(2,0L); + return enter(CODE.link(cu),init.uctrl()); + } + + + private static FunNode enter(FunNode fun, CFGNode continuation) { + // If we find a Summary, then this Fun's code never got loaded. + // Load it now. + if( fun.summary()!=null ) { + int fidx = fun.sig().fidx(); + // Load the matching .o, and all its CompUnits, and all the + // included FunNodes, including a replacement for this one. + ElfReader elf = SummaryNode.load(CODE,fun); + // Object files contain checked ideal IR, before scheduling. This is + // the first schedule for this object's bodies, even though the caller + // is already executing. Schedule only the new graph: revisiting the + // caller could change nodes whose values live in an active Frame. + // No optimization, CLInit lowering, or finalization. + ListScheduler.sched(GlobalCodeMotion.forEval(CODE,(StartNode)elf._nodes.at(0),(StopNode)elf._nodes.last())); + // Get the new replacement FunNode + fun = CODE.link(fidx); + } + Continuation cont = new Continuation(continuation,F); + F=new Frame(F); + for( Node use : fun._outputs ) + if( use instanceof ParmNode parm ) + F.put0(parm,parm._idx==0 ? cont : VALUE_CACHE.at(parm._idx-1)); + return fun; + } + + private static CFGNode ret( ReturnNode ret ) { + // Fetch return expression value and continuation from current frame + Object rez = val(ret.expr()); + Continuation cont = continuation(ret); + // Lower Frame reference count; might delete frame + F.dec(); // Lower ref count + // Install replacement Frame; set return result over the CallEnd + F = cont._frame; + if( cont._cc instanceof CallEndNode ) F.put0(cont._cc,rez); + return cont._cc; // Resume after the call. + } + + private static Continuation continuation(ReturnNode ret) { + return (Continuation)val(ret.rpc()); + } + + 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 RoundF32Node rnd -> (double)(float)d(rnd.in(1)); + case ToIntegerNode cvt -> (long)d(cvt.in(1)); + 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); + } + + // 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<con.len(); i++ ) + xs[i] = con.at8(i); + yield xs; + } else { + // Generic constant array, used as a default input to a + // function; should never execute. + yield new Object[0]; + } + + } else if( Parser.startsClzPrefix(tmp._obj._name) ) { + yield classObject(tmp._obj); + } else { + // Constant object with all zeros + yield alloc(tmp._obj); + } + } + default -> 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<num; i++ ) + ptr[i] = con(type._fields[i]._t.makeZero()); + return ptr; + } + + private static Object[] classObject(TypeStruct type) { + Object[] ptr = CLASSES.get(type._name); + if( ptr==null ) CLASSES.put(type._name,ptr=alloc(type)); + return ptr; + } + + private static Object not( NotNode not ) { + Object n = val(not.in(1)); + return ( n==null || (n instanceof Double D && D==0.0) || n instanceof Long L && L==0 ) + ? 1L : 0L; + } + + private static Object load( LoadNode ld ) { + Object f = val(ld.ptr()); + // Check for dense constant array + if( f instanceof TypeMemPtr tmp ) { + throw Utils.TODO(); + } + Object[] fs = (Object[])f; + if( ld._name.equals("#") ) + return (long)fs.length; + TypeMemPtr tmp = (TypeMemPtr)ld.ptr()._type; + int idx = tmp._obj.isAry() + ? offToIdx(x(ld.off()),tmp._obj) + : tmp._obj.find(ld._name); + return fs[idx]; + } + + private static Object store( StoreNode st ) { + if( st._name.equals("#") ) + return "$mem"; // Eval stores the length in the java object[], no need to set it now + TypeMemPtr tmp = (TypeMemPtr)st.ptr()._type; + Object[] fs = (Object[])val(st.ptr()); + int idx = tmp._obj.isAry() + ? offToIdx(x(st.off()),tmp._obj) + : tmp._obj.find(st._name); + Object val = val(st.val()); + // Idealization can remove conversions already performed by the store. + Type type = st.declaredType(); + if( type instanceof TypeInteger ti ) { + int shift = 64-(8 << st.log_size()); + long x = (Long)val << shift; + val = ti._min < 0 ? x >> shift : x >>> shift; + } else if( type instanceof TypeFloat && st.log_size()==2 ) + val = (double)(float)(double)(Double)val; + fs[idx] = val; + return "$mem"; + } + + + // ------------------------------------------------------------------------ + private static final IdentityHashMap<Object,Object> 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<Object,Object> 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<flds.length; i++ ) + _print( flds[i]._t,xs[i], sb.p(flds[i]._fname).p("="), visit ).p(","); + sb.unchar().p("}"); + } + yield sb; + } + case TypeFunPtr tfp -> 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(); + } + }; + } +} |
