diff options
Diffstat (limited to 'chapter25/src')
51 files changed, 1006 insertions, 886 deletions
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/Parser.java b/chapter25/src/main/java/com/seaofnodes/simple/Parser.java index 19894357..42c83f93 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/Parser.java @@ -217,13 +217,17 @@ public class Parser { * Parses a function body, assuming the header is parsed. */ private ReturnNode parseFunctionBody( TypeFunPtr sig, Lexer loc, String[] ids) { + return parseFunctionBody(sig, loc, null, ids); + } + + private ReturnNode parseFunctionBody( TypeFunPtr sig, Lexer loc, java.util.BitSet finals, String[] ids) { // Record & restore the existing scope vars. The function parse might // update fields, so when the parse is done reset back to the pre-parse // state. Node[] preParse = _scope.save(); // Parse function body normally - ReturnNode ret = _parseFunctionBody(null, sig,loc,ids); + ReturnNode ret = _parseFunctionBody(null, sig,loc,finals,ids); // Reset all fields to pre-parse days _scope.restore(preParse); @@ -246,6 +250,10 @@ public class Parser { // Unstack the {break,continue,return} scopes. private ReturnNode _parseFunctionBody( String funName, TypeFunPtr sig, Lexer loc, String[] ids) { + return _parseFunctionBody(funName, sig, loc, null, ids); + } + + private ReturnNode _parseFunctionBody( String funName, TypeFunPtr sig, Lexer loc, java.util.BitSet finals, String[] ids) { boolean isInit = FunNode.isInit(funName); boolean isCtor = isInit || isExplicitConstructorName(funName); String typeName = isCtor ? ((TypeMemPtr)sig._sig[0])._obj._name : null; @@ -278,7 +286,7 @@ public class Parser { for( int i=0; i<ids.length; i++ ) { Type t = sig.arg(i); // Args take a default input if called from Start/external-world - _scope.define(ids[i], t, i==0 && ids[0]=="self", new ParmNode(ids[i],i+2,t,fun,ConstantNode.seed(t).peephole()).peephole(), loc); + _scope.define(ids[i], t, (i==0 && ids[0]=="self") || (finals!=null && finals.get(i)), new ParmNode(ids[i],i+2,t,fun,ConstantNode.seed(t).peephole()).peephole(), loc); } // Parse the function body. @@ -434,7 +442,7 @@ public class Parser { // value; otherwise a user constructor must initialize it. Node val = _returnScope.in(vidx); if( upgrade && v._uninit && - !(val._type instanceof TypeNil tn && tn.notNull() && !val._type.isHigh()) ) continue; + !initialized(val._type) ) continue; // Store value into extended struct Node prior = mmm.keep(); Node st = peep(new StoreNode(null, fld._fname, fld._alias, fld._t, null, prior, self, off(tself,fld._fname), val, fld._final)); @@ -455,6 +463,10 @@ public class Parser { // Validate each user constructor, or an implicit empty one, before the // declaration scope goes away. Later fields use their declaration defaults. + private static boolean initialized(Type t) { + return t!=Type.BOTTOM && !t.isHigh() && (!(t instanceof TypeNil tn) || tn.notNull()); + } + private void checkInitFields(TypeStruct ts, int base) { if( _returnScope==null || _returnScope.ctrl()._type.isHigh() ) return; boolean implicit = _ctorExits.isEmpty(); @@ -467,7 +479,7 @@ public class Parser { Var v = _scope.var(idx); if( !v._uninit || v._fref ) continue; Type t = exit.getOrDefault(v,_returnScope.in(idx)._type); - if( !(t instanceof TypeNil tn && tn.notNull() && !t.isHigh()) ) + if( !initialized(t) ) throw error("'"+ts._name+"' is not fully initialized, field '"+fld._fname+ (implicit ? "' needs to be set in a constructor" : "' is only partially set in the constructor"),v._loc); @@ -1025,6 +1037,10 @@ public class Parser { // Widen ints to floats; narrow wide int types. // Early error if types do not match variable. private Node convertExpr( Node expr, Type t ) { + if( !expr._type.accessISA(t) ) + throw error("Type "+expr._type.str()+" is not of declared type "+t.str()); + if( t instanceof TypeMemPtr tmp && tmp.isFinal() ) + expr = peep(new ReadOnlyNode(expr)); // A FunPtr carries the semantic edge to its function's Return. Do not // hide that edge behind a Convert which can fold to a plain constant. if( expr instanceof FunPtrNode && t instanceof TypeFunPtr ) @@ -1090,17 +1106,24 @@ public class Parser { return require(n,";"); } - /** Parse final: [!]var['=' asgn] + // The binding modifier sits immediately before the name. It never + // changes permissions on the referenced object. + private boolean bindingFinal(Type t) { + if( match("!") ) return false; + if( match("~") ) return true; + return t==Type.TOP || t instanceof TypeNil; + } + + /** Parse final: [!|~]var['=' asgn] */ private Node parseDeclaration(Type t) { assert t!=null; // Has var/val instead of a user-declared type boolean inferType = t==Type.TOP || t==Type.BOTTOM; - boolean hasBang = match("!"); + boolean xfinal = bindingFinal(t); Lexer loc = loc(); String name = requireId(); // Optional initializing expression follows - boolean xfinal = false; boolean fld_final = false; // Field is final, but not deeply final boolean uninit = false; // Required non-null constructor field Node expr; @@ -1121,18 +1144,6 @@ public class Parser { } else { expr = parseAsgn(); } - // TOP means val and val is always final - xfinal = (t==Type.TOP) || - expr instanceof ExternNode || - // BOTTOM is var and var is always not-final - (t!=Type.BOTTOM && - // no Bang AND - !hasBang && - // not-null (expecting null to be set to not-null) - expr._type != Type.NIL && - // Pointers are final by default; int/flt are not-final by default. - (t instanceof TypeNil)); - // var/val, then type comes from expression if( inferType ) { if( expr._type==Type.NIL ) @@ -1141,12 +1152,6 @@ public class Parser { if( !xfinal ) t = mutableInferredType(t); } - // Final is deep on ptrs - if( xfinal && t instanceof TypeMemPtr tmp ) { - t = tmp.makeRO(); - expr = peep(new ReadOnlyNode(expr)); - } - // expr is a constant function if( t instanceof TypeFunPtr decl && expr._type instanceof TypeFunPtr tfp && tfp.isConstant() ) { t = decl.makeFrom(tfp.fidx()); @@ -1168,8 +1173,8 @@ public class Parser { // Required pointers retain their declared shape, but include // null until every constructor path assigns them. case TypeNil tn -> tn.nullable() ? con(Type.NIL) : con(t.meet(Type.NIL)); - case TypeInteger ti -> _code.ZERO; - case TypeFloat tf -> con(TypeFloat.FZERO); + case TypeInteger ti -> xfinal ? con(Type.BOTTOM) : _code.ZERO; + case TypeFloat tf -> con(xfinal ? Type.BOTTOM : TypeFloat.FZERO); // Bottom signals type inference: they must be initialized in // the constructor and that's when we'll discover the type. case Type tt -> { assert tt==Type.BOTTOM; yield con(tt); } @@ -1177,9 +1182,9 @@ public class Parser { // Every non-null pointer without a default is constructor-required. // Without `!` it is also shallow-final; mutability and definite // initialization are independent properties. - if( t instanceof TypeNil tn && !tn.nullable() ) { + if( (xfinal && !(t instanceof TypeNil)) || (t instanceof TypeNil tn && !tn.nullable()) ) { uninit = true; - if( !hasBang ) fld_final = true; + if( xfinal ) fld_final = true; } } @@ -1430,8 +1435,10 @@ public class Parser { // Otherwise you get a type name int old1 = pos(); + boolean writable = match("!"); + boolean readonly = !writable && match("~"); String tname = _lexer.matchId(); - if( tname==null ) return null; + if( tname==null ) return posT(old1); // Convert the type name to a type. Type t0 = TYPES.get(tname), t1 = t0; @@ -1544,6 +1551,13 @@ public class Parser { if( t1 instanceof TypeStruct ts1 ) t1 = TypeMemPtr.make(ts1); + // A qualifier before the name applies to the struct reference, not + // to an enclosing array. Arrays have their own [] / [~] qualifier. + if( t1 instanceof TypeMemPtr tmp ) { + if( readonly || !writable ) t1 = tmp.makeRO(); + } else if( writable || readonly ) + return posT(old1); + // Nest arrays and '?' as needed Type t2 = t1; while( true ) { @@ -1554,7 +1568,7 @@ public class Parser { } else throw error("Type "+t0+" cannot be null"); } else if( match("[~]") ) { - t2 = TypeMemPtr.make(typeAry(t2,true)); + t2 = TypeMemPtr.make(typeAry(t2,true)).makeRO(); } else if( match("[]") ) { t2 = TypeMemPtr.make(typeAry(t2,false)); } else @@ -1566,7 +1580,7 @@ public class Parser { // Check valid forward ref, after parsing all the type extra bits. // Cannot check earlier, because cannot find required 'id' until after "[]?" syntax int old2 = pos(); - match("!"); + if( !match("!") ) match("~"); String id = _lexer.matchId(); if( !(peek(',') || peek(';') || peek('=') || match("->")) || id==null ) return posT(old1); // Reset lexer to reparse @@ -1616,9 +1630,8 @@ public class Parser { ta = TypeStruct.makeAry(tname,TypeInteger.U32,_code.alias(rcname),t,_code.alias(rcname), false ); TYPES.put(tname,ta); } - if( !efinal ) return ta; // Already have the aliases, just efinal is wrong - return TypeStruct.makeAry(tname,TypeInteger.U32,ta._fields[0]._alias,t,ta._fields[1]._alias,true ); + return TypeStruct.makeAry(tname,TypeInteger.U32,ta._fields[0]._alias,t,ta._fields[1]._alias,efinal ); } private Type implicitSelfType() { @@ -2187,7 +2200,7 @@ public class Parser { // array body. TypeMemPtr str = TypeMemPtr.make((byte)2,TypeStruct.makeAry("[]u8", slen, lenAlias, body, elemAlias, true),true); assert str.isConstant(); - return con(str); + return con(str.makeRO()); } /** @@ -2305,6 +2318,8 @@ public class Parser { Node mem = mem(); // Store to field mem.keep(); + if( expr._type instanceof TypeMemPtr view && view._ro && ctrl()._type!=Type.XCONTROL ) + throw error("Cannot modify final field '"+name+"'"); Node st = new StoreNode(loc(), name, alias, decl, ctrl(), mem, expr.unkeep(), off.unkeep(), lift, false).peephole().keep(); storeMem(st,alias,mem); st.unkeep(); @@ -2330,6 +2345,8 @@ public class Parser { throw error( "'" + ts._name + "' is not fully initialized, field '" + fld._fname + "' needs to be set in a constructor" ); Node op = opAssign(ch,load, decl ).keep(); mem.keep(); + if( expr._type instanceof TypeMemPtr view && view._ro && ctrl()._type!=Type.XCONTROL ) + throw error("Cannot modify final field '"+name+"'"); Node st = new StoreNode(loc(), name, alias, decl, ctrl(), mem, expr.unkeep(), off.unkeep(), op, false).peephole().keep(); storeMem(st,alias,mem); st.unkeep(); @@ -2375,6 +2392,7 @@ public class Parser { private Node func() { Ary<Type> ts = new Ary<>(Type.class); Ary<String> ids = new Ary<>(String.class); + java.util.BitSet finals = new java.util.BitSet(); Type self = implicitSelfType(); ts.push(self); ids.push(self == TypePtr.PTR ? "#self" : "self"); @@ -2385,6 +2403,7 @@ public class Parser { while( true ) { Type t = type(); // Arg type if( t==null ) break; + finals.set(ids.size(),bindingFinal(t)); String id = requireId(); ts .push(t ); // Push type/arg pairs ids.push(id); @@ -2393,7 +2412,7 @@ public class Parser { require("->"); // Make a concrete function type, with a fidx TypeFunPtr tfp = TypeFunPtr.make1((byte)2,true,ts.asAry(),Type.BOTTOM,_code.fidx(_ref._cname)); - ReturnNode ret = parseFunctionBody(tfp,loc,ids.asAry()); + ReturnNode ret = parseFunctionBody(tfp,loc,finals,ids.asAry()); return new FunPtrNode(tfp,_code._start,ret).peephole(); } @@ -2401,6 +2420,7 @@ public class Parser { require("{"); Ary<Type> ts = new Ary<>(Type.class); Ary<String> ids = new Ary<>(String.class); + java.util.BitSet finals = new java.util.BitSet(); TypeStruct self = _scope.constructorSelf(); assert self != null; ts.push(TypeMemPtr.make(constructorRecv(self))); @@ -2416,6 +2436,7 @@ public class Parser { while( true ) { Type t = type(); if( t==null ) break; + finals.set(ids.size(),bindingFinal(t)); String id = requireId(); ts .push(t ); ids.push(id); @@ -2424,7 +2445,7 @@ public class Parser { require("->"); TypeFunPtr tfp = TypeFunPtr.make1((byte)2,true,ts.asAry(),TypeMem.makePrivate(self),_code.fidx(_ref._cname)); Node[] preParse = _scope.save(); - ReturnNode ret = _parseFunctionBody((self._name+"."+name).intern(),tfp,loc,ids.asAry()); + ReturnNode ret = _parseFunctionBody((self._name+"."+name).intern(),tfp,loc,finals,ids.asAry()); _scope.restore(preParse); require("}"); return new FunPtrNode(tfp,_code._start,ret).peephole(); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java index ee5c48df..a8cd315f 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java @@ -63,8 +63,9 @@ abstract public class Serialize { Type.TAGOFFS(); BAOS baos = new BAOS(); + // C0D2 adds independent pointer access permissions to serialized types. // A - Print a header - baos.write('C').write('0').write('D').write('E'); + baos.write('C').write('0').write('D').write('2'); // Count unique Types var types = new HashMap<Type,Integer>(); @@ -217,8 +218,8 @@ abstract public class Serialize { Type.TAGOFFS(); // A - Read a header - if( bais.read()!='C' || bais.read()!='0' || bais.read()!='D' || bais.read()!='E' ) - throw new IllegalArgumentException("Missing magic word"); + if( bais.read()!='C' || bais.read()!='0' || bais.read()!='D' || bais.read()!='2' ) + throw new IllegalArgumentException("Unsupported serialized IR format; rebuild the object file"); // B - Packed read of #strings, then strings int ndependents = bais.packed4(); // Number of dependent object files @@ -246,8 +247,8 @@ abstract public class Serialize { String[] strs = elf._strs; if( strs == null ) { // A - Read a header - if( bais.read() != 'C' || bais.read() != '0' || bais.read() != 'D' || bais.read() != 'E' ) - throw new IllegalArgumentException( "Missing magic word" ); + if( bais.read() != 'C' || bais.read() != '0' || bais.read() != 'D' || bais.read() != '2' ) + throw new IllegalArgumentException( "Unsupported serialized IR format; rebuild the object file" ); // B - Packed read of #strings, then strings int ndependents = bais.packed4(); // Number of dependent object files diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java index 2e921301..162f9c24 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java @@ -227,7 +227,7 @@ public class CallEndNode extends CFGNode implements MultiNode { // references here. Compare against its recursively upgraded form, // so deep mutability requirements participate before inlining. Type formal = tfp.arg(i).upgradeType(Parser.TYPES); - if( !arg._type.isa(formal) ) + if( !arg._type.isa(formal) || !arg._type.accessISA(formal) ) { addDep(arg); return false; } } return true; diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java index 84f1e705..4372e2d5 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java @@ -187,7 +187,7 @@ public class CallNode extends CFGNode { // Check for args for( int i=0; i<tfp.nargs(); i++ ) - if( !arg(i+2)._type.isa(tfp.arg(i)) ) { + if( !arg(i+2)._type.isa(tfp.arg(i)) || !arg(i+2)._type.accessISA(tfp.arg(i)) ) { // Constructors return private memory and carry hidden self and // selfMem arguments ahead of the source-level arguments. int argno = i - (tfp.ret() instanceof TypeMem ? 2 : 0); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java index 4ee5521a..b038366a 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java @@ -35,7 +35,7 @@ public class ConvertNode extends Node { Type src = val()._type; if( CodeGen.CODE._phase == CodeGen.Phase.Opto ) { if( src.isHigh() ) return Type.TOP; - if( src.isa(_dst) ) return src; + if( (src.isa(_dst) && src.accessISA(_dst)) ) return src; } return _dst; } @@ -43,7 +43,7 @@ public class ConvertNode extends Node { @Override public Node idealize() { Type src = val()._type; if( src==Type.BOTTOM || src==Type.TOP ) return null; - if( src.isa(_dst) ) return val(); + if( (src.isa(_dst) && src.accessISA(_dst)) ) return val(); // Managed arrays are represented by an object pointer, while C wants // the address of the first element. The representation cast itself is diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java index a2345d92..a557f97c 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -85,6 +85,7 @@ public class LoadNode extends MemOpNode { return tmp.isHigh() || tmp._obj.isHigh() ? TypeScalar.TOP : TypeScalar.BOT; Type t = pfld._t; + boolean readonly = t instanceof TypeMemPtr view && view._ro; // Load member of constant array if( t instanceof TypeConAry ary ) t = ary.elem(); // TODO: if offset is known, can peek the constant @@ -93,7 +94,7 @@ public class LoadNode extends MemOpNode { // A deeply read-only base produces a deeply read-only value. The // generic declared field type must not cast this information away. - if( ptr0.isFinal() ) + if( ptr0.isFinal() || readonly ) t = t.makeRO(); return scalar(t); } @@ -306,7 +307,7 @@ public class LoadNode extends MemOpNode { // Read-Only is a deep property, and cannot be cast-away private Node castRO(Node rez) { - if( ptr()._type.isFinal() && !rez._type.isFinal() ) + if( (ptr()._type.isFinal() || declaredType().isFinal()) && !rez._type.isFinal() ) return new ReadOnlyNode(rez).peephole(); return rez; } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java index 7e2963f2..415ade34 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -269,7 +269,7 @@ public class StoreNode extends MemOpNode { return null; TypeMemPtr tmp = (TypeMemPtr)ptr()._type; Field f = tmp._obj.field(_name); - if( f!=null && f._final && !_init ) + if( f!=null && (tmp._ro || f._final) && !_init ) return Parser.error("Cannot modify final field '"+_name+"'",_loc); return null; } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter25/src/main/java/com/seaofnodes/simple/type/Type.java index d1af6ca8..980daf0f 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/type/Type.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/type/Type.java @@ -468,6 +468,10 @@ public class Type /*implements Cloneable*/ { boolean _isConstant() { return _type==TNIL; } // Are all reachable struct Fields are final? + // Writable array slots are invariant in their element access permissions. + // Otherwise an alias could insert a readonly pointer into a writable slot. + public boolean accessISA(Type dst) { return true; } + public final boolean isFinal() { return recurClose(recurOpen()._isFinal()); } boolean _isFinal() { assert is_nokids(); return true; } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java index cfd213b7..175b1c63 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java @@ -27,12 +27,15 @@ public class TypeMemPtr extends TypeNil { // (TOP ,true ) - a nil public TypeStruct _obj; + // Deep read-only access through this pointer; independent of slot finality. + public boolean _ro; + public boolean _one; // Singleton pointer value; the pointer itself is constant. public boolean _pub; // Escaped/public pointer. False means constructor-private. private static final Ary<TypeMemPtr> FREE = new Ary<>(TypeMemPtr.class); private TypeMemPtr(byte nil, TypeStruct obj, boolean one, boolean pub) { super(TMEMPTR,nil); init(nil,obj,one,pub); } - private TypeMemPtr init(byte nil, TypeStruct obj, boolean one, boolean pub) { _nil=nil; _obj=obj; _one=one; _pub=pub; return this; } + private TypeMemPtr init(byte nil, TypeStruct obj, boolean one, boolean pub) { _nil=nil; _obj=obj; _ro=obj==TypeStruct.BOT; _one=one; _pub=pub; return this; } // Return a filled-in TypeMemPtr; either from free list or alloc new. static TypeMemPtr malloc(byte nil, TypeStruct obj, boolean one, boolean pub) { return FREE.isEmpty() ? new TypeMemPtr(nil,obj,one,pub) : FREE.pop().init(nil,obj,one,pub); @@ -43,7 +46,10 @@ public class TypeMemPtr extends TypeNil { return make(nil,obj,one,true); } public static TypeMemPtr make(byte nil, TypeStruct obj, boolean one, boolean pub) { - TypeMemPtr tmp = malloc(nil, obj, one, pub); + return make(nil,obj,one, pub,obj==TypeStruct.BOT); + } + private static TypeMemPtr make(byte nil, TypeStruct obj, boolean one, boolean pub, boolean ro) { + TypeMemPtr tmp = malloc(nil, obj, one, pub).access(ro); TypeMemPtr t2 = tmp.intern(); if( t2==tmp ) return tmp; return VISIT.isEmpty() ? t2.free(tmp) : t2.delayFree(tmp); @@ -66,15 +72,36 @@ public class TypeMemPtr extends TypeNil { public static TypeMemPtr makeNullable(TypeStruct obj) { return make((byte)3, obj); } public static TypeMemPtr makePrivate (TypeStruct obj) { return make((byte)2, obj, false, false); } - public TypeMemPtr makeFrom(TypeStruct obj) { return obj==_obj ? this : make(_nil, obj, _one, _pub); } + public TypeMemPtr makeFrom(TypeStruct obj) { return obj==_obj ? this : make(_nil, obj, _one, _pub,_ro); } public TypeMemPtr makeNullable() { return makeFrom((byte)3); } /** Inferred mutable local: nullable, public and non-singleton. */ public TypeMemPtr makeVar() { return (TypeMemPtr)makeStorage(); } /** Public-memory shape: nullable and non-singleton, preserving the referent. */ - @Override TypeMemPtr _makeStorage() { return make(_nil,_obj._makeStorage(),false,true); } - @Override TypeMemPtr makeFrom(byte nil) { return nil==_nil ? this : make(nil, _obj, _one, _pub); } + @Override TypeMemPtr _makeStorage() { return make(_nil,_obj._makeStorage(),false,true,_ro); } + @Override TypeMemPtr makeFrom(byte nil) { return nil==_nil ? this : make(nil, _obj, _one, _pub,_ro); } @Override public Type nonZero() { return makeFrom((byte)(_nil <= 1 ? 1 : 2)); } + private TypeMemPtr access(boolean ro) { _ro=ro; return this; } + + public TypeMemPtr withAccess(boolean ro) { + if( _ro==ro ) return this; + TypeMemPtr ptr = malloc(_nil, _obj, _one, _pub); + ptr._ro = ro; + TypeMemPtr res = ptr.intern(); + if( res==ptr ) return ptr; + return VISIT.isEmpty() ? res.free(ptr) : res.delayFree(ptr); + } + + // Writable slots can load and store references, so their element access + // must agree in both directions. A read-only outer view can weaken access. + @Override public boolean accessISA(Type dst) { + if( !(dst instanceof TypeMemPtr ptr) ) return true; + if( _ro && !ptr._ro ) return false; + if( ptr._ro || _obj._fields==null || ptr._obj._fields==null || !_obj.isAry() || !ptr._obj.isAry() ) return true; + Type a = _obj._fields[1]._t, b = ptr._obj._fields[1]._t; + return a.accessISA(b) && b.accessISA(a); + } + // An abstract pointer, pointing to either a Struct or an Array. // Can also be null or not, so 4 choices {TOP,BOT} x {nil,not} public static final TypeMemPtr BOT = make((byte)3, TypeStruct.BOT); @@ -82,7 +109,7 @@ public class TypeMemPtr extends TypeNil { public static final TypeMemPtr NOTBOT = make((byte)2,TypeStruct.BOT); public static final TypeMemPtr TEST= make((byte)2, TypeStruct.TEST); - public static void gather(ArrayList<Type> ts) { ts.add(NOTBOT); ts.add(BOT); ts.add(TEST); } + public static void gather(ArrayList<Type> ts) { ts.add(NOTBOT); ts.add(BOT); ts.add(TEST); ts.add(TEST.makeRO()); } @Override public TypeNil xmeet(Type t) { @@ -91,28 +118,28 @@ public class TypeMemPtr extends TypeNil { // Can't keep _one if mixing two unequal singletons; the result is not // either singleton. Once either side is public, the meet is public. boolean one = _one && that._one && (_obj==that._obj || !(_obj.isConAry() || that._obj.isConAry())); - return make(xmeet0(that), (TypeStruct)_obj.meet(that._obj), one, _pub | that._pub); + return make(xmeet0(that), (TypeStruct)_obj.meet(that._obj), one, _pub | that._pub,_ro | that._ro); } @Override - TypeMemPtr xdual() { return malloc( dual0(), _obj.dual(), !_one, !_pub); } + TypeMemPtr xdual() { return malloc( dual0(), _obj.dual(), !_one, !_pub).access(!_ro); } @Override TypeMemPtr rdual() { if( _dual!=null ) return dual(); assert !_terned; - TypeMemPtr d = malloc(dual0(), null, !_one, !_pub); + TypeMemPtr d = malloc(dual0(), null, !_one, !_pub).access(!_ro); (_dual = d)._dual = this; // Cross link duals d._obj = _obj._terned ? _obj.dual() : _obj.rdual(); return d; } // RHS is NIL; do not deep-dual when crossing the center line - @Override Type meet0() { return _nil==3 ? this : make((byte)3,_obj); } + @Override Type meet0() { return _nil==3 ? this : make((byte)3,_obj,false,true,_ro); } @Override boolean _isConstant() { return _one; } - @Override boolean _isFinal() { return _obj._isFinal(); } - @Override TypeMemPtr _makeRO() { return makeFrom(_obj._makeRO()); } - @Override TypeMemPtr _close( String name, HashMap<String, Type> TYPES ) { return malloc(_nil,_obj._close(name, TYPES ),_one,_pub); } + @Override boolean _isFinal() { return _ro; } + @Override TypeMemPtr _makeRO() { return withAccess(true); } + @Override TypeMemPtr _close( String name, HashMap<String, Type> TYPES ) { return malloc(_nil,_obj._close(name, TYPES ),_one,_pub).access(_ro); } @Override Type _upgradeType(HashMap<String,Type> TYPES) { return makeFrom((TypeStruct)_obj._upgradeType(TYPES)); @@ -120,33 +147,34 @@ public class TypeMemPtr extends TypeNil { @Override public int log_size() { return 3; } // (1<<3)==8-byte pointers - @Override int hash() { return _obj.hashCode() ^ super.hash() ^ (_one ? 2048 : 0) ^ (_pub ? 4096 : 0); } + @Override int hash() { return (_ro ? 8192 : 0) ^ _obj.hashCode() ^ super.hash() ^ (_one ? 2048 : 0) ^ (_pub ? 4096 : 0); } @Override boolean eq(Type t) { TypeMemPtr ptr = (TypeMemPtr)t; // Invariant - return super.eq(ptr) && _one == ptr._one && _pub == ptr._pub && _obj == ptr._obj; + return _ro==ptr._ro && super.eq(ptr) && _one == ptr._one && _pub == ptr._pub && _obj == ptr._obj; } @Override boolean cycle_eq(Type t) { if( t._type != TMEMPTR ) return false; TypeMemPtr ptr = (TypeMemPtr)t; // Invariant - return super.eq(ptr) && _one == ptr._one && _pub == ptr._pub && _obj.cycle_eq(ptr._obj); + return _ro==ptr._ro && super.eq(ptr) && _one == ptr._one && _pub == ptr._pub && _obj.cycle_eq(ptr._obj); } @Override public int nkids() { return 1; } @Override public Type at( int idx ) { return _obj; } @Override public void set( int idx, Type t ) { _obj = (TypeStruct)t; } - // Reserve tags for null/not, one/general and public/private - @Override int TAGOFF() { return 8; } + // Reserve tags for null/not, one/general, public/private and read-only access + @Override int TAGOFF() { return 16; } @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) { assert _nil>=2; baos.write(TAGOFFS[_type] + (_nil==2 ? 0 : 1) + (!_one ? 0 : 2) - + (!_pub ? 0 : 4) ); + + (!_pub ? 0 : 4) + + (!_ro ? 0 : 8) ); } static TypeMemPtr packed( int tag, BAOS bais ) { - return malloc((byte)((tag&1)+2),null,(tag&2)==2,(tag&4)==4); + return malloc((byte)((tag&1)+2),null,(tag&2)==2,(tag&4)==4).access((tag&8)==8); } @Override public String str() { diff --git a/chapter25/src/main/smp/sys/aryi64.smp b/chapter25/src/main/smp/sys/aryi64.smp index 22ee082e..98b72605 100644 --- a/chapter25/src/main/smp/sys/aryi64.smp +++ b/chapter25/src/main/smp/sys/aryi64.smp @@ -53,7 +53,7 @@ struct aryi64 { // parseAryI64 & aryi64.str are bijections modulo whitespace. // Returns null if the parse failed, without giving a clue as to why. val parseAryI64 = { u8[~] str -> - scan !s = new scan(str); + !scan !s = new scan(str); if( !s.match('[') ) return null; // Array of parsed results var nums = new aryi64(new i64[1]); diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter10Test.java index f41a1904..cdafb43b 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -24,7 +24,7 @@ public class Chapter10Test { private static final String NULLABLE_POINT_SOURCE = """ struct Point { int x; new Point = { int v -> x = v; }; }; - Point?[] !points = new Point?[2]; + !Point?[] !points = new !Point?[2]; points[arg] = new Point(42); Point? p = points[1]; """; @@ -117,7 +117,7 @@ struct _Foo { int x; }; _Foo? foo = null; -_Bar !bar = new _Bar; +!_Bar !bar = new _Bar; bar.a = 1; bar.a = 2; return bar.a; @@ -130,7 +130,7 @@ return bar.a; public void testExample() { CodeGen code = new CodeGen(""" struct Vector2D { int x; int y; }; -Vector2D !v = new Vector2D; +!Vector2D !v = new Vector2D; v.x = 1; if (arg) v.y = 2; @@ -169,7 +169,7 @@ arg=0+new s0.0; public void testLoop() { CodeGen code = new CodeGen(""" struct Bar { int a; }; -Bar !bar = new Bar; +!Bar !bar = new Bar; while (arg) { bar.a = bar.a + 2; arg = arg + 1; @@ -184,7 +184,7 @@ return bar.a; public void testIf() { CodeGen code = new CodeGen(""" struct Bar { int a; }; -Bar !bar = new Bar; +!Bar !bar = new Bar; if (arg) bar = null; bar.a = 1; return bar.a; @@ -197,7 +197,7 @@ return bar.a; public void testIf2() { CodeGen code = new CodeGen(""" struct Bar { int a; }; -Bar? !bar = null; +!Bar? !bar = null; if (arg) bar = new Bar; bar.a = 1; return bar.a; @@ -210,7 +210,7 @@ return bar.a; public void testIf3() { CodeGen code = new CodeGen(""" struct Bar { int a; }; -Bar bar = null; +!Bar !bar = null; if (arg) bar = null; bar.a = 1; return bar.a; @@ -223,7 +223,7 @@ return bar.a; public void testIfOrNull() { CodeGen code = new CodeGen(""" struct _Bar { int a; }; -_Bar? !bar = new _Bar; +!_Bar? !bar = new _Bar; if (arg) bar = null; if( bar ) bar.a = 1; return bar.a; @@ -237,7 +237,7 @@ return bar.a; CodeGen code = new CodeGen( """ struct _Bar { int a; }; -_Bar? !bar = new _Bar; +!_Bar? !bar = new _Bar; if (arg) bar = null; int rez = 3; if( !bar ) rez=4; @@ -252,7 +252,7 @@ return rez; public void testWhileWithNullInside() { CodeGen code = new CodeGen(""" struct s0 {int v0;}; -s0? !v0 = new s0; +!s0? !v0 = new s0; int ret = 0; while(arg) { ret = v0.v0; @@ -273,7 +273,7 @@ struct s0 { int v0; }; s0? v1=new s0; -s0? v1; +!s0? !v1; v1=new s0; """); try { code.parse(); fail(); } @@ -289,7 +289,7 @@ struct Iter { int x; int len; }; -Iter !i = new Iter; +!Iter !i = new Iter; i.len = arg; int sum=0; while( i.x < i.len ) { @@ -307,9 +307,9 @@ return sum; public void test1() { CodeGen code = new CodeGen(""" struct s0 {int v0;}; -s0 !ret = new s0; +!s0 !ret = new s0; while(arg) { - s0 !v0 = new s0; + !s0 !v0 = new s0; v0.v0 = arg; arg = arg-1; if (arg==5) ret=v0; @@ -325,8 +325,8 @@ return ret; public void test2() { CodeGen code = new CodeGen(""" struct _s0 {int v0;}; -_s0 !ret = new _s0; -_s0 !v0 = new _s0; +!_s0 !ret = new _s0; +!_s0 !v0 = new _s0; while(arg) { v0.v0 = arg; arg = arg-1; @@ -343,9 +343,9 @@ return ret.v0; public void test3() { CodeGen code = new CodeGen(""" struct _s0 {int v0;}; -_s0 !ret = new _s0; +!_s0 !ret = new _s0; while(arg < 10) { - _s0 !v1 = new _s0; + !_s0 !v1 = new _s0; if (arg == 5) ret=v1; arg = arg + 1; } diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter11Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter11Test.java deleted file mode 100644 index d515df8d..00000000 --- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter11Test.java +++ /dev/null @@ -1,284 +0,0 @@ -package com.seaofnodes.simple; -import com.seaofnodes.simple.codegen.CodeGen; - - -import org.junit.Test; -import static org.junit.Assert.assertEquals; - -public class Chapter11Test { - - @Test - public void testPrimes() { - CodeGen code = new CodeGen( -""" -if( arg < 2 ) return 0; -int primeCount = 1; -int prime = 3; -while( prime <= arg ) { - int isPrime = 1; - // Check for even case, so the next loop need only check odds - if( (prime/2)*2 == prime ) - continue; - // Check odds up to sqrt of prime - int j = 3; - while( j*j <= prime ) { - if( (prime/j)*j == prime ) { - isPrime = 0; - break; - } - j = j + 2; - } - if( isPrime ) - primeCount = primeCount + 1; - prime = prime + 2; -} -return primeCount; -"""); - code.parse().opto(); - assertEquals("return Phi(Region,0,Phi(Loop,1,Phi(Region,Phi_primeCount,Phi_primeCount,(Phi_primeCount+1))));", code.print()); - assertEquals("0", Eval2.eval(code, 1)); // No primes 1 or below - assertEquals("1", Eval2.eval(code, 2)); // 2 - assertEquals("2", Eval2.eval(code, 3)); // 2, 3 - assertEquals("2", Eval2.eval(code, 4)); // 2, 3 - assertEquals("3", Eval2.eval(code, 5)); // 2, 3, 5 - assertEquals("4", Eval2.eval(code, 10)); // 2, 3, 5, 7 - } - - @Test - public void testClassField() { - CodeGen code = new CodeGen( -""" -if( arg < 2 ) return 0; -int primeCount = 1; -while( arg-- ) - primeCount++; -return primeCount; -"""); - code.driver(CodeGen.Phase.LocalSched); - assertEquals("0", Eval2.eval(code, 1)); // No primes 1 or below - assertEquals("3", Eval2.eval(code, 2)); // -} - - - @Test - public void testAntiDeps1() { - CodeGen code = new CodeGen( -""" -struct _S { int f; }; -_S !v=new _S; -v.f = 2; -_S q=new _S; -int i=q.f; -i=v.f; -if (arg) v.f=1; -return i; -"""); - code.parse().opto(); - assertEquals("return 2;", code.print()); - assertEquals("2", Eval2.eval(code, 0)); - assertEquals("2", Eval2.eval(code, 1)); - } - - @Test - public void testAntiDeps2() { - CodeGen code = new CodeGen( -""" -struct _S { int f; }; -_S !v = new _S; -v.f = arg; -_S !t = new _S; -int i = 0; -if (arg) { - if (arg+1) v = t; - i = v.f; -} else { - v.f = 2; -} -return i; -"""); - code.parse().opto(); - assertEquals("return Phi(Region,.f,0);", code.print()); - } - - @Test - public void testAntiDeps3() { - CodeGen code = new CodeGen( -""" -struct _S { int f; }; -_S !v0 = new _S; -_S? v1; -if (arg) v1 = new _S; -if (v1) { - v0.f = v1.f; -} else { - v0.f = 2; -} -return v0.f; -"""); - code.parse().opto(); - assertEquals("return Phi(Region,.f,2);", code.print()); - } - - - @Test - public void testAntiDeps4() { - CodeGen code = new CodeGen( -""" -struct _S { int f; }; -_S !v = new _S; -v.f = arg; -_S t = new _S; -int i = v.f; -if (arg+1) arg= 0; -while (arg) v.f = 2; -return i; -"""); - code.parse().opto(); - assertEquals("return arg;", code.print()); - } - - @Test - public void testAntiDeps5() { - CodeGen code = new CodeGen( -""" -struct _S { int f; }; -_S !v = new _S; -while(1) { - while(arg+1) { arg=arg-1; } - if (arg) break; - v.f = 2; -} -return v.f; -"""); - code.parse().opto(); - assertEquals("return Phi(Loop,0,2);", code.print()); - for( int arg : new int[]{-1,0,1,3,100} ) - assertEquals("0",Eval2.eval(code,arg)); - } - - @Test - public void testAntiDeps6() { - CodeGen code = new CodeGen( -""" -struct _S { int v; }; -_S !ptr=new _S; -while( -arg ) - ptr = new _S; -while(1) - arg = arg+ptr.v; -"""); - code.parse().opto(); - assertEquals("return Top;", code.print()); - } - - @Test - public void testAntiDeps7() { - CodeGen code = new CodeGen( -""" -struct _S { int f; }; -_S !v = new _S; -_S t = new _S; -int i = v.f; -while (arg) { - v.f = arg; - arg = arg-1; -} -return i; -"""); - code.parse().opto(); - assertEquals("return 0;", code.print()); - } - - @Test - public void testAntiDeps8() { - CodeGen code = new CodeGen( -""" -struct _S { int f; }; -_S !v = new _S; -_S t = new _S; -while(arg) { - arg=arg-1; - int f = v.f; - v.f = 2; - if (arg) arg = f; - else v.f = 3; -} -return arg; -"""); - code.parse().opto(); - assertEquals("return 0;", code.print()); - } - - @Test - public void testAntiDeps9() { - CodeGen code = new CodeGen( -""" -struct _S { int f; }; -_S !v = new _S; -_S t = new _S; -if (arg) { - v.f=2; - int i=t.f; - v.f=i; -} -return v.f; -"""); - code.parse().opto(); - assertEquals("return 0;", code.print()); - } - - @Test - public void testExample2() { - CodeGen code = new CodeGen( -""" -struct _S { int f; }; -_S !v = new _S; -int i = arg; -while (arg > 0) { - int j = i/3; - if (arg == 5) - v.f = j; - arg = arg - 1; -} -return v; - """); - code.parse().opto(); - //assertEquals("return new _S;", code.print()); - } - - @Test - public void testScheduleUse() { - CodeGen code = new CodeGen( -""" -int v0=0; -while(0>=0) { - u1 v1=0; - v1=v0; - if(v1*0) - v0=-v1; -} -return 0; -"""); - code.parse().opto(); - assertEquals("return Top;", code.print()); - } - - @Test - public void testLoopCarriedDep() { - CodeGen code = new CodeGen( -""" -u32 v0=0; -{ - int v1=0; - while(v1) { - v1=1>>>v0!=0; - v0=v1/0; - } - return v1; -} -"""); - code.parse().opto(); - assertEquals("return 0;", code.print()); - } - -} diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter12Test.java index 0f4bb0b4..a71cc167 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -3,45 +3,149 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.codegen.CodeGen; import org.junit.Test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; public class Chapter12Test { - @Test public void testSubZeroFloat() { - var code = new CodeGen("flt x = arg; return 0-x;").parse().opto().typeCheck(); - // Compare strings so +0.0 and -0.0 remain distinct. - assertEquals("0.0", Eval2.eval(code,0)); - assertEquals("-1.0", Eval2.eval(code,1)); + @Test + public void testJig() { + CodeGen code = new CodeGen( +""" +return 3; +"""); + code.parse().opto(); + assertEquals("return 3;", code.print()); + assertEquals("3", Eval2.eval(code, 0)); } - @Test public void testDeadNumericReturns() { - for( String[] test : new String[][] { - {"if(1) return 7; else return 2.5;", "7"}, - {"if(0) return 7; else return 2.5;", "2.5"}, - {"return 7; return 2.5;", "7"}, - {"return 2.5; return 7;", "2.5"} - } ) { - String src = test[0]; - var code = new CodeGen(src).parse().opto().typeCheck(); - assertEquals(src,test[1],Eval2.eval(code,0)); - } + @Test + public void testLinkedList0() { + CodeGen code = new CodeGen( +""" +struct LLI { !LLI? !next; int i; }; +!LLI? !head = null; +while( arg ) { + !LLI !x = new LLI; + x.next = head; + x.i = arg; + head = x; + arg = arg-1; +} +return head.next.i; +"""); + try { code.parse().opto().typeCheck(); fail(); } + catch( Exception e ) { assertEquals("Might be null accessing 'next'",e.getMessage()); } } + @Test + public void testLinkedList1() { + CodeGen code = new CodeGen( +""" +struct _LLI { !_LLI? !next; int q; }; +!_LLI? !head = null; +while( arg ) { + !_LLI !x = new _LLI; + x.next = head; + x.q = arg; + head = x; + arg = arg-1; +} +if( !head ) return 0; +_LLI? next = head.next; +if( next==null ) return 1; +return next.q; +"""); + code.parse().opto().typeCheck(); + assertEquals("return Phi(Region,1,.q,0);", code.print()); + assertEquals("2", Eval2.eval(code, 3)); + } @Test - public void testJig() { + public void testCoRecur() { CodeGen code = new CodeGen( """ -return 0; +struct _int0 { int i; !Test._flt0? !f; }; +struct _flt0 { flt f; !_int0? !i; }; +!_int0 !i0 = new _int0; +i0.i = 17; +!_flt0 !f0 = new _flt0; +f0.f = 3.14; +i0.f = f0; +f0.i = i0; +return f0.i.f.i.i; +"""); + code.parse().opto().typeCheck(); + assertEquals("return 17;", code.print()); + } + + @Test + public void testNullRef0() { + CodeGen code = new CodeGen( +""" +struct _N { !_N? !next; int i; }; +_N n = new _N; +return n.next; """); code.parse().opto(); - assertEquals("return 0;", code.print()); - assertEquals("0", Eval2.eval(code, 0)); + assertEquals("return null;", code.print()); } @Test - public void testFloat() { + public void testNullRef1() { CodeGen code = new CodeGen( """ -flt x = 3.14; +struct M { int m; }; +struct N { !M !next; int i; new N = { !M m -> next = m; }; }; +N n = new N(new M); +return n.next; +"""); + code.parse().opto(); + assertEquals("Stop[ return (const)Test.M; return MEM[ 2:.m=0;]; return Test.M; return MEM[ 2:___ 3:___ 4:___ 5:___ 6:.next=Parm_m(Test.N.N,*Test.M {i64 !m; }); 7:.i=0;]; return MEM[ 2:___ 3:___ 4:___ 5:___ 6:___ 7:.i=0;]; return Test.N; ]", code.print()); + } + + @Test + public void testNullRef2() { + CodeGen code = new CodeGen( +""" +struct M { int m; }; +struct N { !M !next; int i; new N = { !M m -> next = m; }; }; +N n = new N(null); +return n.next; +"""); + try { code.parse().opto().typeCheck(); fail(); } + catch( Exception e ) { assertEquals("Argument #0 isa null, but must be a *Test.M {i64 !m; }",e.getMessage()); } + } + + @Test + public void testNullRef3() { + CodeGen code = new CodeGen( +""" +struct N { !N? !next; int i; }; +!N !n = new N; +n.i = 3.14; +return n.i; +"""); + try { code.parse().opto().typeCheck(); fail(); } + catch( Exception e ) { assertEquals("Type 3.14 is not of declared type i64",e.getMessage()); } + } + + @Test + public void testNullRef4() { + CodeGen code = new CodeGen("return -null-5/null-5;"); + try { code.parse().opto().typeCheck(); fail(); } + catch( Exception e ) { assertEquals("Cannot 5 // null",e.getMessage()); } + } + + @Test public void testNullRef5() { + CodeGen code = new CodeGen("return null+42;"); + try { code.parse().opto().typeCheck(); fail(); } + catch( Exception e ) { assertEquals("Cannot null + 42",e.getMessage()); } + } + + @Test + public void testEmpty() { + CodeGen code = new CodeGen( +""" +struct _S{}; return 0; """); code.parse().opto(); @@ -50,34 +154,128 @@ return 0; } @Test - public void testSquareRoot() { + public void testForwardRef0() { CodeGen code = new CodeGen( """ -flt guess = arg; -while( 1 ) { - flt next = (arg/guess + guess)/2; - if( next == guess ) break; - guess = next; -} -return guess; +struct S1 { S2? s; }; +return new S2; """); - // TODO: One of the problems with <clinit> returning an OS error code, - // is it chops all results to int. - code.parse().opto(); - assertEquals("return Phi(Loop,(flt)arg,((Phi_guess+(ToFloat/Phi_guess))*0.5f));", code.print()); - assertEquals("3.0", Eval2.eval(code, 9)); - assertEquals("1.414213562373095", Eval2.eval(code, 2)); + try { code.parse().opto().typeCheck(); fail(); } + catch( Exception e ) { assertEquals("Unknown struct type 'S2'",e.getMessage()); } } @Test - public void testFPOps() { + public void testForwardRef1() { CodeGen code = new CodeGen( """ -flt x = arg; -return x+1==x; +struct _S1 { !_S2? !s; }; +struct _S2 { int x; }; +return (new _S1).s=new _S2; """); code.parse().opto(); - assertEquals("return ((flt)arg==(ToFloat+1.0f));", code.print()); - assertEquals("0", Eval2.eval(code, 1)); + assertEquals("return Test._S2;", code.print()); + } + + @Test + public void testcheckNull() { + CodeGen code = new CodeGen( +""" +struct I { int i; }; +struct P { !I? !pi; }; +!P !p1 = new P; +!P !p2 = new P; +p2.pi = new I; +p2.pi.i = 2; +if (arg) p1 = new P; +return p1.pi.i + 1; +"""); + try { code.parse().opto().typeCheck(); fail(); } + catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); } + } + + @Test + public void testCoRecur2() { + CodeGen code = new CodeGen( +""" +struct _A { !Test._B? !f0; !Test._C? !f1; }; !_A !a = new _A; +struct _B { !Test._C? !f0; !Test._A? !f1; }; !_B !b = new _B; +struct _C { !Test._A? !f0; !Test._B? !f1; }; !_C !c = new _C; + +a.f0=b; a.f1=c; +b.f0=c; b.f1=a; +c.f0=a; c.f1=b; + +return a.f0.f1.f0.f1.f0; + +"""); + code.parse().opto().typeCheck().GCM().localSched(); + assertEquals("return Test._B;", code.print()); + assertEquals("Test._B{f0=Test._C{f0=Test._A{f0=$cyclic,f1=$cyclic},f1=$cyclic},f1=$cyclic}", Eval2.eval(code, 0)); + } + + @Test + public void testCoRecur3() { + CodeGen code = new CodeGen( +""" +struct _A { !Test._L? !a; !Test._T? !b; !Test._F? !c; }; !_A !a = new _A; +struct _B { !Test._M? !a; !Test._U? !b; !Test._G? !c; }; !_B !b = new _B; +struct _C { !Test._N? !a; !Test._V? !b; !Test._H? !c; }; !_C !c = new _C; +struct _D { !Test._O? !a; !Test._W? !b; !Test._I? !c; }; !_D !d = new _D; +struct _E { !Test._P? !a; !Test._X? !b; !Test._J? !c; }; !_E !e = new _E; +struct _F { !Test._Q? !a; !Test._Y? !b; !Test._K? !c; }; !_F !f = new _F; +struct _G { !Test._R? !a; !Test._Z? !b; !Test._L? !c; }; !_G !g = new _G; +struct _H { !Test._S? !a; !Test._A? !b; !Test._M? !c; }; !_H !h = new _H; +struct _I { !Test._T? !a; !Test._B? !b; !Test._N? !c; }; !_I !i = new _I; +struct _J { !Test._U? !a; !Test._C? !b; !Test._O? !c; }; !_J !j = new _J; +struct _K { !Test._V? !a; !Test._D? !b; !Test._P? !c; }; !_K !k = new _K; +struct _L { !Test._W? !a; !Test._E? !b; !Test._Q? !c; }; !_L !l = new _L; +struct _M { !Test._X? !a; !Test._F? !b; !Test._R? !c; }; !_M !m = new _M; +struct _N { !Test._Y? !a; !Test._G? !b; !Test._S? !c; }; !_N !n = new _N; +struct _O { !Test._Z? !a; !Test._H? !b; !Test._T? !c; }; !_O !o = new _O; +struct _P { !Test._A? !a; !Test._I? !b; !Test._U? !c; }; !_P !p = new _P; +struct _Q { !Test._B? !a; !Test._J? !b; !Test._V? !c; }; !_Q !q = new _Q; +struct _R { !Test._C? !a; !Test._K? !b; !Test._W? !c; }; !_R !r = new _R; +struct _S { !Test._D? !a; !Test._L? !b; !Test._X? !c; }; !_S !s = new _S; +struct _T { !Test._E? !a; !Test._M? !b; !Test._Y? !c; }; !_T !t = new _T; +struct _U { !Test._F? !a; !Test._N? !b; !Test._Z? !c; }; !_U !u = new _U; +struct _V { !Test._G? !a; !Test._O? !b; !Test._A? !c; }; !_V !v = new _V; +struct _W { !Test._H? !a; !Test._P? !b; !Test._B? !c; }; !_W !w = new _W; +struct _X { !Test._I? !a; !Test._Q? !b; !Test._C? !c; }; !_X !x = new _X; +struct _Y { !Test._J? !a; !Test._R? !b; !Test._D? !c; }; !_Y !y = new _Y; +struct _Z { !Test._K? !a; !Test._S? !b; !Test._E? !c; }; !_Z !z = new _Z; + +a.a=l; a.b=t; a.c=f; +b.a=m; b.b=u; b.c=g; +c.a=n; c.b=v; c.c=h; +d.a=o; d.b=w; d.c=i; +e.a=p; e.b=x; e.c=j; +f.a=q; f.b=y; f.c=k; +g.a=r; g.b=z; g.c=l; +h.a=s; h.b=a; h.c=m; +i.a=t; i.b=b; i.c=n; +j.a=u; j.b=c; j.c=o; +k.a=v; k.b=d; k.c=p; +l.a=w; l.b=e; l.c=q; +m.a=x; m.b=f; m.c=r; +n.a=y; n.b=g; n.c=s; +o.a=z; o.b=h; o.c=t; +p.a=a; p.b=i; p.c=u; +q.a=b; q.b=j; q.c=v; +r.a=c; r.b=k; r.c=w; +s.a=d; s.b=l; s.c=x; +t.a=e; t.b=m; t.c=y; +u.a=f; u.b=n; u.c=z; +v.a=g; v.b=o; v.c=a; +w.a=h; w.b=p; w.c=b; +x.a=i; x.b=q; x.c=c; +y.a=j; y.b=r; y.c=d; +z.a=k; z.b=s; z.c=e; + +return a.b.c.a.b.c.a.b.c.a.b.c.a.b.c.a.b.c; + +"""); + code.parse().opto().typeCheck().GCM().localSched(); + assertEquals("return Test._R;", code.print()); + assertEquals("Test._R{a=Test._C{a=Test._N{a=Test._Y{a=Test._J{a=Test._U{a=Test._F{a=Test._Q{a=Test._B{a=Test._M{a=Test._X{a=Test._I{a=Test._T{a=Test._E{a=Test._P{a=Test._A{a=Test._L{a=Test._W{a=Test._H{a=Test._S{a=Test._D{a=Test._O{a=Test._Z{a=Test._K{a=Test._V{a=Test._G{a=$cyclic,b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic}", Eval2.eval(code, 0)); } } diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter13Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter13Test.java index fedd59ce..7cfa5817 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter13Test.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter13Test.java @@ -1,281 +1,284 @@ package com.seaofnodes.simple; - import com.seaofnodes.simple.codegen.CodeGen; + + import org.junit.Test; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; public class Chapter13Test { + @Test - public void testJig() { + public void testPrimes() { CodeGen code = new CodeGen( """ -return 3; +if( arg < 2 ) return 0; +int primeCount = 1; +int prime = 3; +while( prime <= arg ) { + int isPrime = 1; + // Check for even case, so the next loop need only check odds + if( (prime/2)*2 == prime ) + continue; + // Check odds up to sqrt of prime + int j = 3; + while( j*j <= prime ) { + if( (prime/j)*j == prime ) { + isPrime = 0; + break; + } + j = j + 2; + } + if( isPrime ) + primeCount = primeCount + 1; + prime = prime + 2; +} +return primeCount; """); code.parse().opto(); - assertEquals("return 3;", code.print()); - assertEquals("3", Eval2.eval(code, 0)); + assertEquals("return Phi(Region,0,Phi(Loop,1,Phi(Region,Phi_primeCount,Phi_primeCount,(Phi_primeCount+1))));", code.print()); + assertEquals("0", Eval2.eval(code, 1)); // No primes 1 or below + assertEquals("1", Eval2.eval(code, 2)); // 2 + assertEquals("2", Eval2.eval(code, 3)); // 2, 3 + assertEquals("2", Eval2.eval(code, 4)); // 2, 3 + assertEquals("3", Eval2.eval(code, 5)); // 2, 3, 5 + assertEquals("4", Eval2.eval(code, 10)); // 2, 3, 5, 7 } @Test - public void testLinkedList0() { + public void testClassField() { CodeGen code = new CodeGen( """ -struct LLI { LLI? next; int i; }; -LLI? !head = null; -while( arg ) { - LLI !x = new LLI; - x.next = head; - x.i = arg; - head = x; - arg = arg-1; -} -return head.next.i; +if( arg < 2 ) return 0; +int primeCount = 1; +while( arg-- ) + primeCount++; +return primeCount; """); - try { code.parse().opto().typeCheck(); fail(); } - catch( Exception e ) { assertEquals("Might be null accessing 'next'",e.getMessage()); } - } + code.driver(CodeGen.Phase.LocalSched); + assertEquals("0", Eval2.eval(code, 1)); // No primes 1 or below + assertEquals("3", Eval2.eval(code, 2)); // +} + @Test - public void testLinkedList1() { + public void testAntiDeps1() { CodeGen code = new CodeGen( """ -struct _LLI { _LLI? next; int q; }; -_LLI? !head = null; -while( arg ) { - _LLI !x = new _LLI; - x.next = head; - x.q = arg; - head = x; - arg = arg-1; -} -if( !head ) return 0; -_LLI? next = head.next; -if( next==null ) return 1; -return next.q; +struct _S { int f; }; +!_S !v=new _S; +v.f = 2; +_S q=new _S; +int i=q.f; +i=v.f; +if (arg) v.f=1; +return i; """); - code.parse().opto().typeCheck(); - assertEquals("return Phi(Region,1,.q,0);", code.print()); - assertEquals("2", Eval2.eval(code, 3)); + code.parse().opto(); + assertEquals("return 2;", code.print()); + assertEquals("2", Eval2.eval(code, 0)); + assertEquals("2", Eval2.eval(code, 1)); } @Test - public void testCoRecur() { + public void testAntiDeps2() { CodeGen code = new CodeGen( """ -struct _int0 { int i; Test._flt0? f; }; -struct _flt0 { flt f; _int0? i; }; -_int0 !i0 = new _int0; -i0.i = 17; -_flt0 !f0 = new _flt0; -f0.f = 3.14; -i0.f = f0; -f0.i = i0; -return f0.i.f.i.i; +struct _S { int f; }; +!_S !v = new _S; +v.f = arg; +!_S !t = new _S; +int i = 0; +if (arg) { + if (arg+1) v = t; + i = v.f; +} else { + v.f = 2; +} +return i; """); - code.parse().opto().typeCheck(); - assertEquals("return 17;", code.print()); + code.parse().opto(); + assertEquals("return Phi(Region,.f,0);", code.print()); } @Test - public void testNullRef0() { + public void testAntiDeps3() { CodeGen code = new CodeGen( """ -struct _N { _N? next; int i; }; -_N n = new _N; -return n.next; +struct _S { int f; }; +!_S !v0 = new _S; +!_S? !v1; +if (arg) v1 = new _S; +if (v1) { + v0.f = v1.f; +} else { + v0.f = 2; +} +return v0.f; """); code.parse().opto(); - assertEquals("return null;", code.print()); + assertEquals("return Phi(Region,.f,2);", code.print()); } + @Test - public void testNullRef1() { + public void testAntiDeps4() { CodeGen code = new CodeGen( """ -struct M { int m; }; -struct N { M next; int i; new N = { M m -> next = m; }; }; -N n = new N(new M); -return n.next; +struct _S { int f; }; +!_S !v = new _S; +v.f = arg; +_S t = new _S; +int i = v.f; +if (arg+1) arg= 0; +while (arg) v.f = 2; +return i; """); code.parse().opto(); - assertEquals("Stop[ return (const)Test.M; return MEM[ 2:.m=0;]; return Test.M; return MEM[ 2:___ 3:___ 4:___ 5:___ 6:.next=Parm_m(Test.N.N,*Test.M {i64 !m; }); 7:.i=0;]; return MEM[ 2:___ 3:___ 4:___ 5:___ 6:___ 7:.i=0;]; return Test.N; ]", code.print()); + assertEquals("return arg;", code.print()); } @Test - public void testNullRef2() { + public void testAntiDeps5() { CodeGen code = new CodeGen( """ -struct M { int m; }; -struct N { M next; int i; new N = { M m -> next = m; }; }; -N n = new N(null); -return n.next; +struct _S { int f; }; +!_S !v = new _S; +while(1) { + while(arg+1) { arg=arg-1; } + if (arg) break; + v.f = 2; +} +return v.f; """); - try { code.parse().opto().typeCheck(); fail(); } - catch( Exception e ) { assertEquals("Argument #0 isa null, but must be a *Test.M {i64 !m; }",e.getMessage()); } + code.parse().opto(); + assertEquals("return Phi(Loop,0,2);", code.print()); + for( int arg : new int[]{-1,0,1,3,100} ) + assertEquals("0",Eval2.eval(code,arg)); } @Test - public void testNullRef3() { + public void testAntiDeps6() { CodeGen code = new CodeGen( """ -struct N { N? next; int i; }; -N !n = new N; -n.i = 3.14; -return n.i; +struct _S { int v; }; +!_S !ptr=new _S; +while( -arg ) + ptr = new _S; +while(1) + arg = arg+ptr.v; """); - try { code.parse().opto().typeCheck(); fail(); } - catch( Exception e ) { assertEquals("Type 3.14 is not of declared type i64",e.getMessage()); } - } - - @Test - public void testNullRef4() { - CodeGen code = new CodeGen("return -null-5/null-5;"); - try { code.parse().opto().typeCheck(); fail(); } - catch( Exception e ) { assertEquals("Cannot 5 // null",e.getMessage()); } - } - - @Test public void testNullRef5() { - CodeGen code = new CodeGen("return null+42;"); - try { code.parse().opto().typeCheck(); fail(); } - catch( Exception e ) { assertEquals("Cannot null + 42",e.getMessage()); } + code.parse().opto(); + assertEquals("return Top;", code.print()); } @Test - public void testEmpty() { + public void testAntiDeps7() { CodeGen code = new CodeGen( """ -struct _S{}; -return 0; +struct _S { int f; }; +!_S !v = new _S; +_S t = new _S; +int i = v.f; +while (arg) { + v.f = arg; + arg = arg-1; +} +return i; """); code.parse().opto(); assertEquals("return 0;", code.print()); - assertEquals("0", Eval2.eval(code, 0)); } @Test - public void testForwardRef0() { + public void testAntiDeps8() { CodeGen code = new CodeGen( """ -struct S1 { S2? s; }; -return new S2; +struct _S { int f; }; +!_S !v = new _S; +_S t = new _S; +while(arg) { + arg=arg-1; + int f = v.f; + v.f = 2; + if (arg) arg = f; + else v.f = 3; +} +return arg; """); - try { code.parse().opto().typeCheck(); fail(); } - catch( Exception e ) { assertEquals("Unknown struct type 'S2'",e.getMessage()); } + code.parse().opto(); + assertEquals("return 0;", code.print()); } @Test - public void testForwardRef1() { + public void testAntiDeps9() { CodeGen code = new CodeGen( """ -struct _S1 { _S2? s; }; -struct _S2 { int x; }; -return (new _S1).s=new _S2; +struct _S { int f; }; +!_S !v = new _S; +_S t = new _S; +if (arg) { + v.f=2; + int i=t.f; + v.f=i; +} +return v.f; """); code.parse().opto(); - assertEquals("return Test._S2;", code.print()); + assertEquals("return 0;", code.print()); } @Test - public void testcheckNull() { + public void testExample2() { CodeGen code = new CodeGen( """ -struct I { int i; }; -struct P { I? pi; }; -P !p1 = new P; -P !p2 = new P; -p2.pi = new I; -p2.pi.i = 2; -if (arg) p1 = new P; -return p1.pi.i + 1; -"""); - try { code.parse().opto().typeCheck(); fail(); } - catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); } +struct _S { int f; }; +!_S !v = new _S; +int i = arg; +while (arg > 0) { + int j = i/3; + if (arg == 5) + v.f = j; + arg = arg - 1; +} +return v; + """); + code.parse().opto(); + //assertEquals("return new _S;", code.print()); } @Test - public void testCoRecur2() { + public void testScheduleUse() { CodeGen code = new CodeGen( """ -struct _A { Test._B? f0; Test._C? f1; }; _A !a = new _A; -struct _B { Test._C? f0; Test._A? f1; }; _B !b = new _B; -struct _C { Test._A? f0; Test._B? f1; }; _C !c = new _C; - -a.f0=b; a.f1=c; -b.f0=c; b.f1=a; -c.f0=a; c.f1=b; - -return a.f0.f1.f0.f1.f0; - +int v0=0; +while(0>=0) { + u1 v1=0; + v1=v0; + if(v1*0) + v0=-v1; +} +return 0; """); - code.parse().opto().typeCheck().GCM().localSched(); - assertEquals("return Test._B;", code.print()); - assertEquals("Test._B{f0=Test._C{f0=Test._A{f0=$cyclic,f1=$cyclic},f1=$cyclic},f1=$cyclic}", Eval2.eval(code, 0)); + code.parse().opto(); + assertEquals("return Top;", code.print()); } @Test - public void testCoRecur3() { + public void testLoopCarriedDep() { CodeGen code = new CodeGen( """ -struct _A { Test._L? a; Test._T? b; Test._F? c; }; _A !a = new _A; -struct _B { Test._M? a; Test._U? b; Test._G? c; }; _B !b = new _B; -struct _C { Test._N? a; Test._V? b; Test._H? c; }; _C !c = new _C; -struct _D { Test._O? a; Test._W? b; Test._I? c; }; _D !d = new _D; -struct _E { Test._P? a; Test._X? b; Test._J? c; }; _E !e = new _E; -struct _F { Test._Q? a; Test._Y? b; Test._K? c; }; _F !f = new _F; -struct _G { Test._R? a; Test._Z? b; Test._L? c; }; _G !g = new _G; -struct _H { Test._S? a; Test._A? b; Test._M? c; }; _H !h = new _H; -struct _I { Test._T? a; Test._B? b; Test._N? c; }; _I !i = new _I; -struct _J { Test._U? a; Test._C? b; Test._O? c; }; _J !j = new _J; -struct _K { Test._V? a; Test._D? b; Test._P? c; }; _K !k = new _K; -struct _L { Test._W? a; Test._E? b; Test._Q? c; }; _L !l = new _L; -struct _M { Test._X? a; Test._F? b; Test._R? c; }; _M !m = new _M; -struct _N { Test._Y? a; Test._G? b; Test._S? c; }; _N !n = new _N; -struct _O { Test._Z? a; Test._H? b; Test._T? c; }; _O !o = new _O; -struct _P { Test._A? a; Test._I? b; Test._U? c; }; _P !p = new _P; -struct _Q { Test._B? a; Test._J? b; Test._V? c; }; _Q !q = new _Q; -struct _R { Test._C? a; Test._K? b; Test._W? c; }; _R !r = new _R; -struct _S { Test._D? a; Test._L? b; Test._X? c; }; _S !s = new _S; -struct _T { Test._E? a; Test._M? b; Test._Y? c; }; _T !t = new _T; -struct _U { Test._F? a; Test._N? b; Test._Z? c; }; _U !u = new _U; -struct _V { Test._G? a; Test._O? b; Test._A? c; }; _V !v = new _V; -struct _W { Test._H? a; Test._P? b; Test._B? c; }; _W !w = new _W; -struct _X { Test._I? a; Test._Q? b; Test._C? c; }; _X !x = new _X; -struct _Y { Test._J? a; Test._R? b; Test._D? c; }; _Y !y = new _Y; -struct _Z { Test._K? a; Test._S? b; Test._E? c; }; _Z !z = new _Z; - -a.a=l; a.b=t; a.c=f; -b.a=m; b.b=u; b.c=g; -c.a=n; c.b=v; c.c=h; -d.a=o; d.b=w; d.c=i; -e.a=p; e.b=x; e.c=j; -f.a=q; f.b=y; f.c=k; -g.a=r; g.b=z; g.c=l; -h.a=s; h.b=a; h.c=m; -i.a=t; i.b=b; i.c=n; -j.a=u; j.b=c; j.c=o; -k.a=v; k.b=d; k.c=p; -l.a=w; l.b=e; l.c=q; -m.a=x; m.b=f; m.c=r; -n.a=y; n.b=g; n.c=s; -o.a=z; o.b=h; o.c=t; -p.a=a; p.b=i; p.c=u; -q.a=b; q.b=j; q.c=v; -r.a=c; r.b=k; r.c=w; -s.a=d; s.b=l; s.c=x; -t.a=e; t.b=m; t.c=y; -u.a=f; u.b=n; u.c=z; -v.a=g; v.b=o; v.c=a; -w.a=h; w.b=p; w.c=b; -x.a=i; x.b=q; x.c=c; -y.a=j; y.b=r; y.c=d; -z.a=k; z.b=s; z.c=e; - -return a.b.c.a.b.c.a.b.c.a.b.c.a.b.c.a.b.c; - +u32 v0=0; +{ + int v1=0; + while(v1) { + v1=1>>>v0!=0; + v0=v1/0; + } + return v1; +} """); - code.parse().opto().typeCheck().GCM().localSched(); - assertEquals("return Test._R;", code.print()); - assertEquals("Test._R{a=Test._C{a=Test._N{a=Test._Y{a=Test._J{a=Test._U{a=Test._F{a=Test._Q{a=Test._B{a=Test._M{a=Test._X{a=Test._I{a=Test._T{a=Test._E{a=Test._P{a=Test._A{a=Test._L{a=Test._W{a=Test._H{a=Test._S{a=Test._D{a=Test._O{a=Test._Z{a=Test._K{a=Test._V{a=Test._G{a=$cyclic,b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic}", Eval2.eval(code, 0)); + code.parse().opto(); + assertEquals("return 0;", code.print()); } + } diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java new file mode 100644 index 00000000..20274581 --- /dev/null +++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java @@ -0,0 +1,83 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import org.junit.Test; +import static org.junit.Assert.assertEquals; + +public class Chapter14FloatTest { + @Test public void testSubZeroFloat() { + var code = new CodeGen("flt x = arg; return 0-x;").parse().opto().typeCheck(); + // Compare strings so +0.0 and -0.0 remain distinct. + assertEquals("0.0", Eval2.eval(code,0)); + assertEquals("-1.0", Eval2.eval(code,1)); + } + + @Test public void testDeadNumericReturns() { + for( String[] test : new String[][] { + {"if(1) return 7; else return 2.5;", "7"}, + {"if(0) return 7; else return 2.5;", "2.5"}, + {"return 7; return 2.5;", "7"}, + {"return 2.5; return 7;", "2.5"} + } ) { + String src = test[0]; + var code = new CodeGen(src).parse().opto().typeCheck(); + assertEquals(src,test[1],Eval2.eval(code,0)); + } + } + + + @Test + public void testJig() { + CodeGen code = new CodeGen( +""" +return 0; +"""); + code.parse().opto(); + assertEquals("return 0;", code.print()); + assertEquals("0", Eval2.eval(code, 0)); + } + + @Test + public void testFloat() { + CodeGen code = new CodeGen( +""" +flt x = 3.14; +return 0; +"""); + code.parse().opto(); + assertEquals("return 0;", code.print()); + assertEquals("0", Eval2.eval(code, 0)); + } + + @Test + public void testSquareRoot() { + CodeGen code = new CodeGen( +""" +flt guess = arg; +while( 1 ) { + flt next = (arg/guess + guess)/2; + if( next == guess ) break; + guess = next; +} +return guess; +"""); + // TODO: One of the problems with <clinit> returning an OS error code, + // is it chops all results to int. + code.parse().opto(); + assertEquals("return Phi(Loop,(flt)arg,((Phi_guess+(ToFloat/Phi_guess))*0.5f));", code.print()); + assertEquals("3.0", Eval2.eval(code, 9)); + assertEquals("1.414213562373095", Eval2.eval(code, 2)); + } + + @Test + public void testFPOps() { + CodeGen code = new CodeGen( +""" +flt x = arg; +return x+1==x; +"""); + code.parse().opto(); + assertEquals("return ((flt)arg==(ToFloat+1.0f));", code.print()); + assertEquals("0", Eval2.eval(code, 1)); + } +} diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter14Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter14Test.java index 807905a8..f0ad6c2d 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter14Test.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter14Test.java @@ -93,7 +93,7 @@ return b; CodeGen code = new CodeGen( """ struct _Foo { u1 b; }; -_Foo !f = new _Foo; +!_Foo !f = new _Foo; f.b = 123; return f.b; """); diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter15Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter15Test.java index 24552062..85dc7b47 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter15Test.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter15Test.java @@ -25,8 +25,8 @@ return 3.14; public void testCyclic() { CodeGen code = new CodeGen( """ -struct _C { _C? l; }; -_C !c = new _C; +struct _C { !_C? !l; }; +!_C !c = new _C; c.l = c; return c; """); @@ -98,7 +98,7 @@ return a[0]; CodeGen code = new CodeGen( """ struct _A { int i; }; -_A?[] !a = new _A?[2]; +!_A?[] !a = new !_A?[2]; return a; """); code.parse().opto(); @@ -112,16 +112,16 @@ return a; """ struct _S { int x; flt y; }; // A new _S -_S !s = new _S; s.x=99; s.y = 3.14; +!_S !s = new _S; s.x=99; s.y = 3.14; // Double-d array of _Ss. Fill in one row. -_S?[]?[] !iss = new _S?[]?[2]; -iss[0] = new _S?[7]; +!_S?[]?[] !iss = new !_S?[]?[2]; +iss[0] = new !_S?[7]; iss[0][2] = s; // Now pull out the filled-in value, with null checks flt rez; -_S?[]? is = iss[arg]; +!_S?[]? is = iss[arg]; if( !is ) rez = 1.2; else { _S? i = is[2]; @@ -142,11 +142,11 @@ return rez; """ struct _S { int x; flt y; }; // A new _S -_S !s = new _S; s.x=99; s.y = 3.14; +!_S !s = new _S; s.x=99; s.y = 3.14; // Double-d array of _Ss. Fill in one row. -_S?[]?[] !iss = new _S?[]?[2]; -iss[0] = new _S?[7]; +!_S?[]?[] !iss = new !_S?[]?[2]; +iss[0] = new !_S?[7]; iss[0][2] = s; // Now pull out the filled-in value, with null checks @@ -167,9 +167,9 @@ return rez;"""); CodeGen code = new CodeGen( """ // Can we define a forward-reference array? -struct _Tree { _Tree?[]? _kids; }; -_Tree !root = new _Tree; -root._kids = new _Tree?[2]; // NO BANG SO ARRAY IS OF IMMUTABLE TREES???? +struct _Tree { !_Tree?[]? !_kids; }; +!_Tree !root = new _Tree; +root._kids = new !_Tree?[2]; // NO BANG SO ARRAY IS OF IMMUTABLE TREES???? root._kids[0] = new _Tree; return root; """); @@ -182,7 +182,7 @@ return root; public void testNestedStructAddMemProj() { CodeGen code = new CodeGen( """ -struct _S { int a; int[] b; new _S = { int[] ary -> b=ary; }; }; +struct _S { int a; int[] !b; new _S = { int[] ary -> b=ary; }; }; return 0; """); code.parse().opto(); @@ -227,8 +227,8 @@ return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6 CodeGen code = new CodeGen( """ struct _S {int i; flt f;}; -_S !s1 = new _S; -_S !s2 = new _S; +!_S !s1 = new _S; +!_S !s2 = new _S; s2.i = 3; s2.f = 2.0; if (arg) s1 = new _S; diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter16Test.java index 80b0a2bf..23ec549d 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter16Test.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter16Test.java @@ -42,31 +42,6 @@ return x+y; assertEquals("5", Eval2.eval(code, 0)); } -// @Test -// public void testFinal0() { -// CodeGen code = new CodeGen( -//""" -//int !x=2; -//x=3; -//return x; -//"""); -// try { code.parse().opto(); fail(); } -// catch( Exception e ) { assertEquals("Cannot reassign final 'x'",e.getMessage()); } -// } - - @Test - public void testFinal1() { - CodeGen code = new CodeGen( -""" -int x=2, y=3; -if( arg ) { int x = y; x = x*x; y=x; } // Shadow final x -return y; -"""); - code.parse().opto(); - assertEquals("return Phi(Region,9,3);", code.print()); - assertEquals("3", Eval2.eval(code, 0)); - assertEquals("9", Eval2.eval(code, 1)); - } @Test public void testConstruct0() { @@ -81,18 +56,6 @@ return z.x; } @Test - public void testConstruct1() { - CodeGen code = new CodeGen(""" -struct _X { int !x; new _X = { int xx -> x=xx; }; }; -_X z = new _X(3); -return z.x; -"""); - code.parse().opto(); - assertEquals("return 3;", code.print()); - assertEquals("3", Eval2.eval(code, 0)); - } - - @Test public void testConstruct2() { CodeGen code = new CodeGen(""" struct _X { int x=3; new _X = { int xx -> x=xx; }; }; @@ -106,86 +69,11 @@ return z.x; @Test - public void testStructFinal0() { - CodeGen code = new CodeGen(""" -struct _Point { int !x, !y; new _Point = { int xx, int yy -> x=xx; y=yy; }; }; -_Point p = new _Point(3,4); -return p; -"""); - code.parse().opto(); - assertEquals("return (const)Test._Point;", code.print()); - assertEquals("Test._Point{x=3,y=4}", Eval2.eval(code, 0)); - } - - @Test - public void testStructFinal1() { - CodeGen code = new CodeGen(""" -struct _Point { int x=3, y=4; new _Point = { int xx, int yy -> x=xx; y=yy; }; }; -val p = new _Point(5,6); -p.x++; -return p; -"""); - try { code.parse().opto().typeCheck(); fail(); } - catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); } - } - - @Test - public void testStructFinal2() { - CodeGen code = new CodeGen(""" -struct _Point { int x=3, y=4; }; -val p = new _Point; -p.x++; -return p; -"""); - try { code.parse().opto().typeCheck(); fail(); } - catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); } - } - - @Test - public void testStructFinal3() { - CodeGen code = new CodeGen(""" -struct _Point { var x; var y; }; -_Point p = new _Point; -p.x++; -return p; -"""); - try { code.parse().opto(); fail(); } - catch( Exception e ) { assertEquals("'Test._Point' is not fully initialized, field 'x' needs to be set in a constructor",e.getMessage()); } - } - - @Test - public void testStructFinal4() { - CodeGen code = new CodeGen(""" -struct _Point { val x=2; val y=4; }; -_Point p = new _Point; -p.x++; -return p; -"""); - try { code.parse().opto().typeCheck(); fail(); } - catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); } - } - - @Test - public void testStructFinal5() { - CodeGen code = new CodeGen(""" -struct _Point { var x=3; var y=4; }; -_Point !p = new _Point; -p.x++; -return p; -"""); - code.parse().opto(); - assertEquals("return Test._Point;", code.print()); - assertEquals("Test._Point{x=4,y=4}", Eval2.eval(code, 0)); - } - - // Same as the Chapter13 test with the same name, but using the new - // constructor syntax - @Test public void testLinkedList1() { CodeGen code = new CodeGen( """ -struct _LLI { _LLI? next; int i; new _LLI = { _LLI? n, int ii -> next=n; i=ii; }; }; -_LLI? !head = null; +struct _LLI { !_LLI? !next; int i; new _LLI = { !_LLI? !n, int ii -> next=n; i=ii; }; }; +!_LLI? !head = null; while( arg ) { head = new _LLI(head,arg); arg = arg-1; @@ -206,7 +94,7 @@ return next.i; public void testLinkedList2() { CodeGen code = new CodeGen( """ -struct _LLI { _LLI? next; int i; new _LLI = { _LLI? n, int a -> +struct _LLI { !_LLI? !next; int i; new _LLI = { !_LLI? !n, int a -> next=n; int !tmp=a; while( a > 10 ) { @@ -215,7 +103,7 @@ struct _LLI { _LLI? next; int i; new _LLI = { _LLI? n, int a -> } i=tmp; }; }; -_LLI? !head = null; +!_LLI? !head = null; while( arg ) { head = new _LLI(head,arg); arg = arg-1; diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter17aTest.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter17aTest.java new file mode 100644 index 00000000..52acff57 --- /dev/null +++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter17aTest.java @@ -0,0 +1,109 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class Chapter17aTest { + + @Test + public void testFinal1() { + CodeGen code = new CodeGen( +""" +int x=2, y=3; +if( arg ) { int x = y; x = x*x; y=x; } // Shadow final x +return y; +"""); + code.parse().opto(); + assertEquals("return Phi(Region,9,3);", code.print()); + assertEquals("3", Eval2.eval(code, 0)); + assertEquals("9", Eval2.eval(code, 1)); + } + + @Test + public void testConstruct1() { + CodeGen code = new CodeGen(""" +struct _X { int ~x; new _X = { int xx -> x=xx; }; }; +_X z = new _X(3); +return z.x; +"""); + code.parse().opto(); + assertEquals("return 3;", code.print()); + assertEquals("3", Eval2.eval(code, 0)); + } + + @Test + public void testStructFinal0() { + CodeGen code = new CodeGen(""" +struct _Point { int ~x, ~y; new _Point = { int xx, int yy -> x=xx; y=yy; }; }; +_Point p = new _Point(3,4); +return p; +"""); + code.parse().opto(); + assertEquals("return (const)Test._Point;", code.print()); + assertEquals("Test._Point{x=3,y=4}", Eval2.eval(code, 0)); + } + + @Test + public void testStructFinal1() { + CodeGen code = new CodeGen(""" +struct _Point { int x=3, y=4; new _Point = { int xx, int yy -> x=xx; y=yy; }; }; +~_Point p = new _Point(5,6); +p.x++; +return p; +"""); + try { code.parse().opto().typeCheck(); fail(); } + catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); } + } + + @Test + public void testStructFinal2() { + CodeGen code = new CodeGen(""" +struct _Point { int x=3, y=4; }; +~_Point p = new _Point; +p.x++; +return p; +"""); + try { code.parse().opto().typeCheck(); fail(); } + catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); } + } + + @Test + public void testStructFinal3() { + CodeGen code = new CodeGen(""" +struct _Point { var x; var y; }; +_Point p = new _Point; +p.x++; +return p; +"""); + try { code.parse().opto(); fail(); } + catch( Exception e ) { assertEquals("'Test._Point' is not fully initialized, field 'x' needs to be set in a constructor",e.getMessage()); } + } + + @Test + public void testStructFinal4() { + CodeGen code = new CodeGen(""" +struct _Point { val x=2; val y=4; }; +_Point p = new _Point; +p.x++; +return p; +"""); + try { code.parse().opto().typeCheck(); fail(); } + catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); } + } + + @Test + public void testStructFinal5() { + CodeGen code = new CodeGen(""" +struct _Point { var x=3; var y=4; }; +!_Point !p = new _Point; +p.x++; +return p; +"""); + code.parse().opto(); + assertEquals("return Test._Point;", code.print()); + assertEquals("Test._Point{x=4,y=4}", Eval2.eval(code, 0)); + } + +} diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter17Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter17bTest.java index 79aecbff..ab1a59e1 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter17Test.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter17bTest.java @@ -5,7 +5,7 @@ import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; -public class Chapter17Test { +public class Chapter17bTest { @Test public void testJig() { @@ -80,7 +80,7 @@ return xs[0]; public void testInc5() { CodeGen code = new CodeGen(""" struct _S { u16 x; }; -_S !s = new _S; +!_S !s = new _S; s.x--; return s.x; """); @@ -156,33 +156,33 @@ return s.x; } @Test public void testVar4() { - CodeGen code = new CodeGen("struct _S{int x;}; _S? s; s=new _S; s.x++; return s.x; // Ok, no initializer so x is mutable "); + CodeGen code = new CodeGen("struct _S{int x;}; !_S? !s; s=new _S; s.x++; return s.x; // Ok, no initializer so x is mutable "); code.parse().opto(); assertEquals("return 1;", code.print()); assertEquals("1", Eval2.eval(code, 0)); } @Test public void testVar5() { - CodeGen code = new CodeGen("struct _S{int x; new _S={ int xx -> x=xx; };}; _S? s; s=new _S(3); s.x++; return s.x; // Ok, no initializer so x is mutable "); + CodeGen code = new CodeGen("struct _S{int x; new _S={ int xx -> x=xx; };}; !_S? !s; s=new _S(3); s.x++; return s.x; // Ok, no initializer so x is mutable "); code.parse().opto(); assertEquals("return 4;", code.print()); assertEquals("4", Eval2.eval(code, 0)); } @Test public void testVar6() { - CodeGen code = new CodeGen("struct S{int x;}; val s = new S; s.x++; return s.x; // Error initializer so x is immutable "); + CodeGen code = new CodeGen("struct S{int x;}; ~S s = new S; s.x++; return s.x; // Error initializer so x is immutable "); try { code.parse().opto().typeCheck(); fail(); } catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); } } @Test public void testVar7() { - CodeGen code = new CodeGen("struct S{int x; new S={ int xx -> x=xx; };}; val s = new S(3); s.x++; return s.x; // Error initializer so x is immutable "); + CodeGen code = new CodeGen("struct S{int x; new S={ int xx -> x=xx; };}; ~S s = new S(3); s.x++; return s.x; // Error initializer so x is immutable "); try { code.parse().opto().typeCheck(); fail(); } catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); } } @Test public void testVar8() { - CodeGen code = new CodeGen("struct _S{int x;}; _S !s=new _S; s.x++; return s.x; // Ok, has '!' so s.x is mutable "); + CodeGen code = new CodeGen("struct _S{int x;}; !_S !s=new _S; s.x++; return s.x; // Ok, has '!' so s.x is mutable "); code.parse().opto(); assertEquals("return 1;", code.print()); assertEquals("1", Eval2.eval(code, 0)); @@ -196,7 +196,7 @@ return s.x; } @Test public void testVar10() { - CodeGen code = new CodeGen("struct S{int x;}; val s=new S; s.x++; return s.x; // Error, has val so x is immutable "); + CodeGen code = new CodeGen("struct S{int x;}; ~S s=new S; s.x++; return s.x; // Error, has val so x is immutable "); try { code.parse().opto().typeCheck(); fail(); } catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); } } @@ -204,15 +204,15 @@ return s.x; @Test public void testVar11() { CodeGen code = new CodeGen(""" struct Bar { int x; }; -Bar !bar = new Bar; +!Bar !bar = new Bar; bar.x = 3; // Ok, bar is mutable -struct Foo { Bar? !bar; int y; new Foo = { Bar? b -> bar = b; }; }; -Foo !foo = new Foo(bar); +struct Foo { !Bar? !bar; int y; new Foo = { !Bar? b -> bar = b; }; }; +!Foo !foo = new Foo(bar); foo.bar = bar; // Ok foo is mutable foo.bar.x++; // Ok foo and foo.bar and foo.bar.x are all mutable -val xfoo = foo; // Throw away mutability +~Foo xfoo = foo; // Throw away mutability xfoo.bar.x++; // Error, cannot mutate through xfoo return xfoo.bar.x; """); @@ -223,15 +223,15 @@ return xfoo.bar.x; @Test public void testVar12() { CodeGen code = new CodeGen(""" struct _Bar { int x; }; -_Bar !bar = new _Bar; +!_Bar !bar = new _Bar; bar.x = 3; // Ok, bar is mutable -struct _Foo { _Bar? !bar; int y; }; -_Foo !foo = new _Foo; +struct _Foo { !_Bar? !bar; int y; }; +!_Foo !foo = new _Foo; foo.bar = bar; // Ok bar is mutable foo.bar.x++; // Ok foo and foo.bar and foo.bar.x are all mutable -val xfoo = foo; // Throw away mutability +~_Foo xfoo = foo; // Throw away mutability int x4 = xfoo.bar.x; // Ok to read through xfoo, gets 4 foo.bar.x++; // Bumps to 5 int x5 = xfoo.bar.x; // Ok to read through xfoo, gets 5 @@ -256,7 +256,7 @@ int i,i++; public void testVar14() { CodeGen code = new CodeGen(""" struct B {}; -struct A { B b; new A = { -> +struct A { !B !b; new A = { -> return b; // read before init b = new B; }; }; @@ -269,10 +269,10 @@ A x = new A; @Test public void testVar15() { for( String fields : new String[] { - "B b; new A = { -> if (arg) b = new B; };", - "B b; new A = { -> if (arg) return 0; b = new B; };", - "B b; new A = { B b -> b = new B; };", // Argument shadows field - "new A = { -> }; B b;", // Field declared later + "!B !b; new A = { -> if (arg) b = new B; };", + "!B !b; new A = { -> if (arg) return 0; b = new B; };", + "!B !b; new A = { !B b -> b = new B; };", // Argument shadows field + "new A = { -> }; !B !b;", // Field declared later } ) { CodeGen code = new CodeGen("struct B {}; struct A { "+fields+" }; return 0;"); try { code.parse(); fail(fields); } @@ -282,16 +282,16 @@ A x = new A; @Test public void testDefaultConstructorFields() { - for( String field : new String[] { "B b;", "B !b;" } ) { + for( String field : new String[] { "!B !b;", "!B !b;" } ) { CodeGen code = new CodeGen("struct B {}; struct A { "+field+" }; return new A;"); try { code.parse(); fail(field); } catch( Parser.ParseException e ) { assertEquals("'Test.A' is not fully initialized, field 'b' needs to be set in a constructor",e.getMessage()); } } for( String fields : new String[] { "B b = new B;", - "B b; b = new B;", + "!B !b; b = new B;", "new A = { -> }; B b = new B;", - "B b; new A = { -> if (arg) { b = new B; return 0; } b = new B; };", + "!B !b; new A = { -> if (arg) { b = new B; return 0; } b = new B; };", } ) { CodeGen code = new CodeGen("struct B {}; struct A { "+fields+" }; return new A.b != null;"); code.parse().opto().typeCheck(); @@ -299,7 +299,7 @@ A x = new A; assertEquals("1",Eval2.eval(code,1)); } // No returning path means no object can escape uninitialized. - new CodeGen("struct B {}; struct A { B b; new A = { -> while (true) {} }; }; return 0;").parse(); + new CodeGen("struct B {}; struct A { !B !b; new A = { -> while (true) {} }; }; return 0;").parse(); } @Test @@ -308,7 +308,7 @@ A x = new A; """ struct S{ new S = { -> x = 2; }; }; val x = 1; -val s = new S; +~S s = new S; return x; """ ); @@ -323,10 +323,10 @@ return x; struct Person { bool focused; }; -val focus = { Person p -> +val focus = { !Person p -> p.focused = true; }; -Person me; // Person !me = new Person; +!Person !me; // !Person !me = new Person; focus(me); return me; """ @@ -361,8 +361,8 @@ return arg ? 0 : arg; public void testShortCircuitGuard() { CodeGen code = new CodeGen(""" struct S { int !x; }; -val get = { S s -> s.x; }; -S? !s = arg ? new S; +val get = { !S s -> s.x; }; +!S? !s = arg ? new S; if( !s || !arg ) return 0; return get(s); """); @@ -400,7 +400,7 @@ return b ? b.x++ + b.x++ : -1; public void testTrinary4() { // This test case will benefit from an unzipping transformation CodeGen code = new CodeGen(""" -struct _Bar { _Bar? next; int x; new _Bar = { _Bar? n, int xx -> next=n; x=xx; }; }; +struct _Bar { !_Bar? !next; int x; new _Bar = { !_Bar? !n, int xx -> next=n; x=xx; }; }; var b = arg ? new _Bar((arg==2) ? new _Bar(null,2), 1); return b ? b.next ? b.next.x : b.x; // parses "b ? (b.next ? b.next.x : b.x) : 0" """); @@ -515,8 +515,8 @@ for(;;arg++;) {} public void testForward0() { CodeGen code = new CodeGen(""" struct _A { - B? f1; - B? f2; + !B? !f1; + !B? !f2; }; return new _A; """); @@ -529,11 +529,11 @@ return new _A; public void testForward1() { CodeGen code = new CodeGen(""" struct _A { - B?[]? nil_array_of_b; - B?[] array_of_b; - new _A = { B?[] bs -> array_of_b = bs; }; + !B?[]? !nil_array_of_b; + !B?[] !array_of_b; + new _A = { !B?[] bs -> array_of_b = bs; }; }; -return new _A(new B?[0]).array_of_b; +return new _A(new !B?[0]).array_of_b; """); code.parse().opto(); assertEquals("return []*B?;", code.print()); @@ -544,8 +544,8 @@ return new _A(new B?[0]).array_of_b; @Test public void testLinkedList2() { CodeGen code = new CodeGen(""" -struct _LLI { _LLI? next; int i; new _LLI = { _LLI? n, int ii -> next=n; i=ii; }; }; -_LLI? !head = null; +struct _LLI { !_LLI? !next; int i; new _LLI = { !_LLI? !n, int ii -> next=n; i=ii; }; }; +!_LLI? !head = null; while( arg-- ) head = new _LLI(head,arg); int sum=0; diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter18Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter18Test.java index 2c61fb44..adc05f6f 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter18Test.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter18Test.java @@ -318,7 +318,7 @@ return 2; public void testFcn8() { CodeGen code = new CodeGen( """ -{int -> int}? _i2i = null; +{int -> int}? !_i2i = null; var _id = {{int->int} f-> return f;}; for(;;) { if (_i2i) return _i2i(arg); @@ -336,7 +336,7 @@ for(;;) { public void testFcn9() { CodeGen code = new CodeGen( """ -{int -> int}? i2i = null; +{int -> int}? !i2i = null; for(;;) { if (i2i) return i2i(arg); var x = {int i-> return i;}; @@ -357,13 +357,13 @@ struct _Person { int age; }; -val _fcn = { _Person?[] ps, int x -> +val _fcn = { !_Person?[] !ps, int x -> val tmp = ps[x]; if( ps[x] ) ps[x].age++; }; -var ps = new _Person?[2]; +var ps = new !_Person?[2]; ps[0] = new _Person; ps[1] = new _Person; return _fcn(ps,1); @@ -475,7 +475,7 @@ struct S { { int } f = { -> return x(); }; }; val x = { -> return 1; }; -S? s = null; +!S? !s = null; for(;;) { if (s) return s.f; } @@ -509,7 +509,7 @@ return _f2f(_o)(1); struct _Person { int coffee_count; }; -_Person !p = new _Person; +!_Person !p = new _Person; p.coffee_count += 1; return p.coffee_count; """); diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter19Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter19Test.java index 5ab7ba97..8cd5137c 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter19Test.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter19Test.java @@ -46,13 +46,13 @@ return 0; String src = """ struct String { - u8[~] cs; + u8[~] !cs; int _hashCode; new String = { u8[~] s -> cs=s; }; }; // Compare two Strings -val equals = { String self, String s -> +val equals = { !String !self, !String s -> if( self == s ) return true; if( self.cs# != s.cs# ) return false; for( int i=0; i< self.cs#; i++ ) @@ -62,7 +62,7 @@ val equals = { String self, String s -> }; -val _hashCodeString = { String self -> +val _hashCodeString = { !String self -> int hash=0; for( int i=0; i< self.cs#; i++ ) hash = hash*31 + self.cs[i]; @@ -71,7 +71,7 @@ val _hashCodeString = { String self -> }; // Return the String hashCode (cached, and never 0) -val hashCode = { String self -> +val hashCode = { !String self -> self._hashCode ? self._hashCode : (self._hashCode = _hashCodeString(self)); @@ -89,14 +89,14 @@ hashCode(new String("Hello, World!")); String src = """ struct String { - u8[~] cs; + u8[~] !cs; int _hashCode; new String = { u8[~] s -> cs=s; }; }; -val f = { String self -> 7; }; +val f = { !String self -> 7; }; -val hashCode = { String self -> +val hashCode = { !String self -> self._hashCode ? 3 : (self._hashCode = f(self)); @@ -283,7 +283,7 @@ return sum;"""); public void testAlloc1() { CodeGen code = new CodeGen( """ -struct _S { int a; _S? c; }; +struct _S { int a; !_S? !c; }; return new _S;"""); code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return Test._S;", code.print()); diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java index 245ea745..262b8604 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java @@ -15,7 +15,7 @@ public class Chapter20Test { // A real integer return merged with a synthetic exit. "if(arg) while(1) {} return arg+1;", // TOP must not widen a non-null pointer or constrain a float register. - "struct S { int x; }; S !s=new S; if(arg) while(1) {} return s;", + "struct S { int x; }; !S !s=new S; if(arg) while(1) {} return s;", "if(arg) while(1) {} return 1.5;", // Several synthetic exits into the same return. "if(arg==1) while(1) {} if(arg==2) while(1) {} return arg+3;", @@ -23,7 +23,7 @@ public class Chapter20Test { "while(1) { arg+=1; }", "if(arg) while(1) {} while(1) {}", // Loop-carried memory and scalar self-conflicts exercise spilling. - "struct S { int x; int y; }; S !a=new S; if(arg) while(1) { a.x+=arg; a.y+=a.x; } return a.x+a.y;" + "struct S { int x; int y; }; !S !a=new S; if(arg) while(1) { a.x+=arg; a.y+=a.x; } return a.x+a.y;" }; for( String target : new String[]{"x86_64_v2","riscv","arm"} ) for( String src : sources ) { @@ -44,7 +44,7 @@ public class Chapter20Test { @Test public void testNarrowStoreMasks() { for( String type : new String[]{"i8","u8","i16","u16"} ) { - CodeGen code = new CodeGen("struct S { "+type+" x; }; S !s=new S; s.x=arg; return 0;") + CodeGen code = new CodeGen("struct S { "+type+" x; }; !S !s=new S; s.x=arg; return 0;") .driver(CodeGen.Phase.RegAlloc,"x86_64_v2","SystemV"); int stores=0; for( var bb : code._cfg ) @@ -60,9 +60,9 @@ public class Chapter20Test { @Test public void testInlinedReturnValue() { String src = """ struct S { int x; }; -val f = { S s -> s.x = g(); }; +val f = { !S s -> s.x = g(); }; val g = { -> 123; }; -S !s = new S; +!S !s = new S; return f(s); """; // The entry of g can disappear before its Return is folded into f. @@ -225,13 +225,13 @@ return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6 String src = """ struct String { - u8[] cs; + u8[] !cs; int _hashCode; new String = { u8[] data -> cs=data; }; }; // Compare two Strings -val equals = { String self, String s -> +val equals = { !String !self, !String s -> if( self == s ) return true; if( self.cs# != s.cs# ) return false; for( int i=0; i< self.cs#; i++ ) @@ -241,13 +241,13 @@ val equals = { String self, String s -> }; // Return the String hashCode (cached, and never 0) -val hashCode = { String self -> +val hashCode = { !String self -> self._hashCode ? self._hashCode : (self._hashCode = _hashCodeString(self)); }; -val _hashCodeString = { String self -> +val _hashCodeString = { !String self -> int hash=0; for( int i=0; i< self.cs#; i++ ) hash = hash*31 + self.cs[i]; diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java index 93a9e63f..bfce93b9 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java @@ -17,8 +17,8 @@ public class Chapter21Test { @Test public void testReadBeforeConditionalStore() { var code = new CodeGen(""" - struct S { int x; }; S !a=new S; S !b=new S; - a.x=10; b.x=20; S !p=a; if(arg&1) p=b; + struct S { int x; }; !S !a=new S; !S !b=new S; + a.x=10; b.x=20; !S !p=a; if(arg&1) p=b; int sum=0; for(int i=0; i<6; i++) { sum+=a.x; @@ -281,7 +281,7 @@ return new _s0.v1; } @Test public void testInfinite() { - String src = "struct _S { int i; }; _S !s = new _S; while(1) s.i++; return s.i;"; + String src = "struct _S { int i; }; !_S !s = new _S; while(1) s.i++; return s.i;"; testCPU(src,"x86_64_v2", "SystemV",0,"return Top;"); testCPU(src,"riscv" , "SystemV",2,"return Top;"); testCPU(src,"arm" , "SystemV",2,"return Top;"); @@ -311,7 +311,7 @@ return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6 """ struct _S { int f; }; var v0 = new _S; -_S? v1; +!_S? !v1; if (arg) v1 = new _S; if (v1) { v0.f = v1.f; @@ -330,13 +330,13 @@ return v0; String src = """ struct String { - u8[] cs; + u8[] !cs; int _hashCode; new String = { u8[] data -> cs=data; }; }; // Compare two Strings -val equals = { String self, String s -> +val equals = { !String !self, !String s -> if( self == s ) return true; if( self.cs# != s.cs# ) return false; for( int i=0; i< self.cs#; i++ ) @@ -346,13 +346,13 @@ val equals = { String self, String s -> }; // Return the String hashCode (cached, and never 0) -val hashCode = { String self -> +val hashCode = { !String self -> self._hashCode ? self._hashCode : (self._hashCode = _hashCodeString(self)); }; -val _hashCodeString = { String self -> +val _hashCodeString = { !String self -> int hash=0; for( int i=0; i< self.cs#; i++ ) hash = hash*31 + self.cs[i]; @@ -369,13 +369,13 @@ val _hashCodeString = { String self -> String src = """ struct String { - u8[] cs; + u8[] !cs; int _hashCode; new String = { u8[] data -> cs=data; }; }; // Compare two Strings -val equals = { String self, String s -> +val equals = { !String !self, !String s -> if( self == s ) return true; if( self.cs# != s.cs# ) return false; for( int i=0; i< self.cs#; i++ ) @@ -385,13 +385,13 @@ val equals = { String self, String s -> }; // Return the String hashCode (cached, and never 0) -val hashCode = { String self -> +val hashCode = { !String self -> self._hashCode ? self._hashCode : (self._hashCode = _hashCodeString(self)); }; -val _hashCodeString = { String self -> +val _hashCodeString = { !String self -> int hash=0; for( int i=0; i< self.cs#; i++ ) hash = hash*31 + self.cs[i]; @@ -570,7 +570,7 @@ val fib = { int n -> i32 age; }; - val fcn = { Person?[] ps, int x -> + val fcn = { !Person?[] !ps, int x -> if( ps[x] ) ps[x].age++; }; diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java index 82211985..ed678334 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java @@ -141,7 +141,7 @@ return new s0.v1; @Test public void testSextFail() throws IOException { String src = """ struct _Person { i32 age;}; -_Person !p = new _Person; +!_Person !p = new _Person; p.age = (arg<<17)>>17; return 0; """; @@ -164,7 +164,7 @@ return 0; @Test public void testSextFail2() throws IOException { String src = """ struct _Person { i32 age;}; - _Person !p = new _Person; + !_Person !p = new _Person; p.age = (arg<<48)>>48; return 0; """; @@ -188,7 +188,7 @@ return 0; String src = """ // Should fold away sign extend struct _Person { i8 age;}; -_Person !p = new _Person; +!_Person !p = new _Person; p.age = (arg<<48)>>48; return 0; """; @@ -216,7 +216,7 @@ struct Person { // Exports a field typed singleton+final *class:Person{age:i32} i32 age; }; -val fcn = { Person?[] ps, int x -> // exports a field with a constant fcn ptr; exports the fcn also +val fcn = { !Person?[] !ps, int x -> // exports a field with a constant fcn ptr; exports the fcn also if( ps[x] ) ps[x].age++; }; @@ -270,12 +270,12 @@ val fcn = { Person?[] ps, int x -> // exports a field with a constant fcn ptr; e public void testCoRecur() { String src = """ val x = 5; // aa.az; // Error to self-define forward ref -struct _A { Test._B? b; Test._C? c; i64 ax; val az = x*2; new _A = { i64 x -> ax=x; }; }; -struct _B { Test._A? a; Test._C? c; f32 bx; val bz = x*3; new _B = { f32 x, Test._A? aa -> bx=x; a=aa; }; }; -struct _C { Test._A? a; Test._B? b; f64 cx; val cz = x*x; new _C = { f64 x, Test._A? aa, Test._B? bb -> cx=x; a=aa; b=bb; }; }; -_A !aa = new _A(17); -_B !bb = new _B(3.14f, aa); -_C !cc = new _C(2.73, aa, bb); +struct _A { !Test._B? !b; !Test._C? !c; i64 ax; val az = x*2; new _A = { i64 x -> ax=x; }; }; +struct _B { !Test._A? !a; !Test._C? !c; f32 bx; val bz = x*3; new _B = { f32 x, !Test._A? aa -> bx=x; a=aa; }; }; +struct _C { !Test._A? !a; !Test._B? !b; f64 cx; val cz = x*x; new _C = { f64 x, !Test._A? aa, !Test._B? bb -> cx=x; a=aa; b=bb; }; }; +!_A !aa = new _A(17); +!_B !bb = new _B(3.14f, aa); +!_C !cc = new _C(2.73, aa, bb); aa.b = bb; aa.c = cc; bb.c = cc; diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter23Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter23Test.java index dad1dde4..7c46567a 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter23Test.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter23Test.java @@ -142,7 +142,7 @@ return !(!s || !arg) ? 1 : 0; public void testAndPtr() throws IOException { // Todo: have one src here String src = """ -struct S { S? fld; new S = { S? f -> fld=f; }; }; +struct S { !S? !fld; new S = { !S? f -> fld=f; }; }; val ptr = arg == 1 ? null : new S(arg==1 ? null : new S(null)); return ptr && ptr.fld ? "true" : "false"; """; @@ -257,7 +257,7 @@ return (a && sq_noInline(0)) // A hypothetical scanner class struct _Scan { int !x; - u8[~] buf; + u8[~] !buf; new _Scan = { u8[~] b -> buf=b; }; // Skip whitespace val skip = { -> @@ -272,7 +272,7 @@ struct _Scan { return true; }; }; -_Scan !_s = new _Scan(" q"); +!_Scan !_s = new _Scan(" q"); return _s.peek('q'); """; @@ -288,7 +288,7 @@ return _s.peek('q'); // A hypothetical scanner class struct _Scan { int !x; - u8[~] buf; + u8[~] !buf; new _Scan = { u8[~] b -> buf=b; }; // Skip whitespace val skip = { -> @@ -297,14 +297,14 @@ struct _Scan { return self; }; }; -val s = new _Scan(" q"); +~_Scan s = new _Scan(" q"); return s.skip().x; """; try { new CodeGen(src).parse().opto().typeCheck(); fail(); } catch( Exception e ) { // Bad error message, but basically requires a mutable 'x' field. - assertEquals("Argument #0 isa *Test._Scan {i64 x; *[]u8[final] buf; { *Test._Scan -> *Test._Scan {i64 !x; *[]u8 buf; {[ 5]} skip; } #[ 5]} skip; }, but must be a *Test._Scan {i64 !x; *[]u8[final] buf; { *Test._Scan -> *Test._Scan #[ 5]} skip; }",e.getMessage()); + org.junit.Assert.assertTrue(e.getMessage().startsWith("Argument #0 isa")); } }; @@ -315,7 +315,7 @@ return s.skip().x; // A hypothetical scanner class struct _Scan { int !x; - u8[~] buf; + u8[~] !buf; new _Scan = { u8[~] b -> buf=b; }; // Skip whitespace val skip = { -> @@ -327,7 +327,7 @@ struct _Scan { buf[x++]==ch; }; }; -_Scan !_s = new _Scan(" [1,2]"); +!_Scan !_s = new _Scan(" [1,2]"); return _s.require('['); """; diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java index cad16f2b..b06f90d8 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java @@ -25,10 +25,20 @@ import org.junit.Test; import static org.junit.Assert.*; public class Chapter25Test { + + @Ignore + @Test + public void testJig() { + String src = "int x = arg/17; if( arg == 0 ) return arg + x; return arg + x;"; + CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck); + assertEquals("return (arg+3);", code.print()); + assertEquals("3",Eval2.eval(code,0)); + } + @Test public void testNeverReturnPointerRiscV() { new CodeGen(""" struct _S { int x; }; - _S !s = new _S; + !_S !s = new _S; if(arg) while(1) { s.x += arg; } return s; """).driver(CodeGen.Phase.Encoding,"riscv","SystemV"); @@ -119,17 +129,6 @@ public class Chapter25Test { com.seaofnodes.simple.codegen.PrintRegTestSupport.checkConstantPool(); } - - @Ignore - @Test - public void testJig() { - String src = "struct I { int x; }; struct F { I p; }; return new F.p.x;"; - CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck); - assertEquals("2",Eval2.eval(code,0)); - } - - - private static final String SYS_BLDDIR = "build/objs/lib_"+TestC.CPU_ABI; private static final File SYS_FILE = new File(SYS_BLDDIR+"/sys.o"); diff --git a/chapter25/src/test/java/com/seaofnodes/simple/CompoundAssignmentTest.java b/chapter25/src/test/java/com/seaofnodes/simple/CompoundAssignmentTest.java index 0d67d3c5..48f739cf 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/CompoundAssignmentTest.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/CompoundAssignmentTest.java @@ -73,7 +73,7 @@ public class CompoundAssignmentTest { @Test public void testFields() { for (String op : OPS) { long expected = apply(op, -8, 3); - check("struct S { int x; }; S !s=new S; s.x=arg; int r=(s.x " + + check("struct S { int x; }; !S !s=new S; s.x=arg; int r=(s.x " + op + " 3); return s.x*10+r;", -8, expected*11); } } diff --git a/chapter25/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java b/chapter25/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java index 2786b403..9406f480 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java @@ -22,8 +22,8 @@ public class MemoryEncodingTest { """, // One possibly aliasing arm must reject the unchanged-memory proof. """ - struct S { int x; }; S !a=new S; S !b=new S; - a.x=10; b.x=20; S !p=a; if(arg&1) p=b; + struct S { int x; }; !S !a=new S; !S !b=new S; + a.x=10; b.x=20; !S !p=a; if(arg&1) p=b; int sum=0; for(int i=0; i<6; i++) { if(i==2) p.x=30; @@ -34,8 +34,8 @@ public class MemoryEncodingTest { """, // Unchanged memory does not imply an unchanged loop-carried pointer. """ - struct S { int x; }; S !a=new S; S !b=new S; - a.x=10; b.x=20; S !p=a; int sum=0; + struct S { int x; }; !S !a=new S; !S !b=new S; + a.x=10; b.x=20; !S !p=a; int sum=0; for(int i=0; i<6; i++) { if(i==2) a.x=30; sum+=p.x; @@ -45,7 +45,7 @@ public class MemoryEncodingTest { """, // All arms fold, but their values differ: build a value Phi. """ - struct S { int x; }; S !a=new S; a.x=1; int sum=0; + struct S { int x; }; !S !a=new S; a.x=1; int sum=0; for(int i=0; i<6; i++) { sum+=a.x; if(i&1) a.x=2; @@ -78,9 +78,9 @@ public class MemoryEncodingTest { @Test public void testKeepReadsBeforeWrites() { var code = new CodeGen(""" struct S { int x; int y; }; - S !a = new S; S !b = new S; + !S !a = new S; !S !b = new S; a.x=arg; b.x=arg+1; - S !p=a; S !q=b; + !S !p=a; !S !q=b; if (arg<0) { p=b; q=a; } int v; if (arg>1) { v=p.x; p.x=41; p.y=5; } diff --git a/chapter25/src/test/java/com/seaofnodes/simple/MergeSortTest.java b/chapter25/src/test/java/com/seaofnodes/simple/MergeSortTest.java index ba8fc178..f5d3d146 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/MergeSortTest.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/MergeSortTest.java @@ -22,7 +22,7 @@ val merge_sort = { int[] a -> _split_merge(a, 0, a#, b); }; -val _split_merge = { int[] b, int begin, int end, int[] a -> +val _split_merge = { int[] !b, int begin, int end, int[] a -> if (end - begin <= 1) return 0; int middle = (end + begin) / 2; @@ -32,7 +32,7 @@ val _split_merge = { int[] b, int begin, int end, int[] a -> return 0; }; -val _merge = { int[] b, int begin, int middle, int end, int[] a -> +val _merge = { int[] !b, int begin, int middle, int end, int[] a -> int i = begin, j = middle; for (int k = begin; k < end; k++) { @@ -47,7 +47,7 @@ val _merge = { int[] b, int begin, int middle, int end, int[] a -> } }; -val _copy_array = { int[] a, int begin, int end, int[] b -> +val _copy_array = { int[] !a, int begin, int end, int[] b -> for (int k = begin; k < end; k++) b[k] = a[k]; }; diff --git a/chapter25/src/test/java/com/seaofnodes/simple/MutabilityTest.java b/chapter25/src/test/java/com/seaofnodes/simple/MutabilityTest.java new file mode 100644 index 00000000..60e4c5aa --- /dev/null +++ b/chapter25/src/test/java/com/seaofnodes/simple/MutabilityTest.java @@ -0,0 +1,67 @@ +package com.seaofnodes.simple; + +import org.junit.Test; +import static org.junit.Assert.*; +import com.seaofnodes.simple.codegen.CodeGen; + +public class MutabilityTest { + private static void ok(String src, String result) { + var code = new CodeGen(src).parse().opto().typeCheck(); + assertEquals(result,Eval2.eval(code,0)); + } + private static void bad(String src) { + try { + new CodeGen(src).parse().opto().typeCheck(); + } catch( RuntimeException expected ) { + String msg = expected.getMessage(); + assertTrue("Expected a permission error, got: "+expected, + msg!=null && (msg.contains("final") || msg.contains("not of declared type") || msg.contains("Cannot store") || msg.contains("Argument #"))); + return; + } + fail("Accepted forbidden write: "+src); + } + + @Test public void testIndependentBindingsAndViews() { + ok("struct P { int x; new P = { int v -> x=v; }; }; !P ~p=new P(0); p.x=7; return p.x;", "7"); + ok("struct P { int x; new P = { int v -> x=v; }; }; ~P !p=new P(3); p=new P(7); return p.x;", "7"); + bad("struct P { int x; new P = { int v -> x=v; }; }; !P ~p=new P(0); p=new P(0); return 0;"); + bad("struct P { int x; new P = { int v -> x=v; }; }; ~P !p=new P(0); p.x=7; return 0;"); + bad("int ~x=3; x=7; return x;"); + ok("int !x=3; x=7; return x;", "7"); + } + + @Test public void testDeepViewAndAliases() { + ok("struct P { int x; new P = { int v -> x=v; }; }; !P ~p=new P(0); ~P ~r=p; p.x=7; return r.x;", "7"); + bad("struct P { int x; new P = { int v -> x=v; }; }; struct Box { !P p; new Box = { !P v -> p=v; }; }; !P p=new P(0); Box b=new Box(p); b.p.x=7; return 0;"); + bad("struct P { int x; new P = { int v -> x=v; }; }; P p=new P(0); !P q=p; q.x=7; return 0;"); + bad("struct P { P? next; int x; new P = { P? v -> next=v; }; }; !P p=new P(new P(null)); p.next.x=7; return 0;"); + bad("struct P { !P? next; int x; new P = { !P? v -> next=v; }; }; P p=new P(new P(null)); p.next.x=7; return 0;"); + } + + @Test public void testInferencePreservesAccess() { + ok("struct P { int x; }; val p=new P; p.x=7; return p.x;", "7"); + bad("struct P { int x; }; val p=new P; p=new P; return 0;"); + bad("struct P { int x; }; P p=new P; var q=p; q.x=7; return 0;"); + } + + @Test public void testParameterPermissions() { + ok("struct P { int x; }; val f={ !P p -> p.x=7; return p.x; }; return f(new P);", "7"); + ok("val f={ int ~x -> return x; }; return f(7);", "7"); + bad("val f={ int ~x -> x=7; return x; }; return f(3);"); + bad("struct P { int x; }; val f={ P p -> p.x=7; return p.x; }; return f(new P);"); + bad("struct P { int x; }; val f={ !P p -> p.x=7; return p.x; }; P p=new P; return f(p);"); + bad("struct P { int x; }; val f={ P?[] a -> return a#; }; !P?[] a=new !P?[1]; return f(a);"); + } + + @Test public void testArrayLayers() { + bad("struct P { int x; }; !P?[] rw=new !P?[1]; P?[] ro=rw; rw[0]=new P; ro[0].x=7; return 0;"); + ok("u8[] a=new u8[2]; a[0]=7; return a[0];", "7"); + ok("struct P { int x; new P = { int v -> x=v; }; }; P?[] a=new P?[1]; a[0]=new P(7); return a[0].x;", "7"); + bad("struct P { int x; new P = { int v -> x=v; }; }; P?[] a=new P?[1]; a[0]=new P(0); a[0].x=7; return 0;"); + ok("struct P { int x; new P = { int v -> x=v; }; }; !P?[] a=new !P?[1]; a[0]=new P(0); a[0].x=7; return a[0].x;", "7"); + bad("u8[~] a=new u8[1]; a[0]=7; return 0;"); + ok("u8[~]?[] rows=new u8[~]?[1]; rows[0]=new u8[1]; return rows#;", "1"); + bad("u8[~]?[] rows=new u8[~]?[1]; rows[0]=new u8[1]; rows[0][0]=7; return 0;"); + bad("u8[]?[~] rows=new u8[]?[1]; rows[0]=new u8[1]; return 0;"); + } +} diff --git a/chapter25/src/test/java/com/seaofnodes/simple/TypeTest.java b/chapter25/src/test/java/com/seaofnodes/simple/TypeTest.java index b9cba5e8..f6099607 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/TypeTest.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/TypeTest.java @@ -90,7 +90,7 @@ public class TypeTest { Assert.assertNotEquals(ptr1, ptr2); Assert.assertEquals(ptr1, ptr1.dual().dual()); - Assert.assertEquals(TypeMemPtr.makeNullable(TypeStruct.BOT), ptr1.meet(ptr2nil)); + Assert.assertEquals(TypeMemPtr.makeNullable(TypeStruct.BOT).withAccess(false), ptr1.meet(ptr2nil)); TypeMemPtr TOP = TypeMemPtr.TOP; TypeMemPtr BOT = TypeMemPtr.makeNullable(TypeStruct.BOT); @@ -99,7 +99,7 @@ public class TypeTest { Type PTR_meet_NULL = NULL.meet(PTR); Assert.assertEquals(BOT, PTR_meet_NULL); Type ptr1_meet_ptr2 = ptr1.meet(ptr2); - Assert.assertEquals(PTR, ptr1_meet_ptr2); + Assert.assertEquals(PTR.withAccess(false), ptr1_meet_ptr2); Type NULL_join_ptr1 = NULL.join(ptr1); Assert.assertEquals(TypePtr.XNPTR, NULL_join_ptr1); Type NULL_join_PTR = PTR.join(NULL); diff --git a/chapter25/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java b/chapter25/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java index 7ecde7c0..c5e94047 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java @@ -445,7 +445,7 @@ public class ScriptGenerator { for (int i=0; i<fields.length; i++) { var fieldName = getRandomName(); var type = getType(true); - printIndentation().append(type.name).append(" ").append(fieldName).append(";\n"); + printIndentation().append(declType(type)).append(" !").append(fieldName).append(";\n"); fields[i] = new TypeStruct.Field(fieldName.toString(), type, struct); } indentation -= INDENTATION; @@ -642,15 +642,17 @@ public class ScriptGenerator { return 0; } - /** - * Generate a declaration statement. - * @return 0 - */ + // This generator mutates references and fields; request both permissions. + private static String declType(Type type) { + return (type instanceof TypeStruct || type instanceof TypeNullable ? "!" : "") + type.name; + } + + /** Generate a declaration statement. */ public int genDecl() { var type = getType(); var name = getVarName(); // Always make them mutable - sb.append(generateInvalid() ? getRandomName() : type.name).append(" !").append(name); + sb.append(generateInvalid() ? getRandomName() : declType(type)).append(" !").append(name); if (!(type instanceof TypeNullable) || random.nextBoolean()) { sb.append("="); genExpression(type, true, type==TYPE_BOOL); diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testString.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testString.smp index 8c56636f..f66ad621 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testString.smp +++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testString.smp @@ -4,7 +4,7 @@ struct String { int _hashCode; }; -val equals = { String self, String s -> +val equals = { !String !self, !String s -> if( self == s ) return true; if( self.cs# != s.cs# ) return false; for( int i=0; i< self.cs#; i++ ) @@ -13,13 +13,13 @@ val equals = { String self, String s -> return true; }; -val hashCode = { String self -> +val hashCode = { !String self -> self._hashCode ? self._hashCode : (self._hashCode = _hashCodeString(self)); }; -val _hashCodeString = { String self -> +val _hashCodeString = { !String self -> int hash=0; if( self.cs ) { for( int i=0; i< self.cs#; i++ ) @@ -29,7 +29,7 @@ val _hashCodeString = { String self -> return hash; }; -String !s = new String(new u8[17]); +!String !s = new String(new u8[17]); s.cs[0] = 67; // C s.cs[1] = 108; // l hashCode(s); diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21AllocTest-testString.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21AllocTest-testString.smp index 86220b7a..586ccb85 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21AllocTest-testString.smp +++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21AllocTest-testString.smp @@ -6,7 +6,7 @@ struct String { }; // Compare two Strings -val equals = { String self, String s -> +val equals = { !String !self, !String s -> if( self == s ) return true; if( self.cs# != s.cs# ) return false; for( int i=0; i< self.cs#; i++ ) @@ -16,13 +16,13 @@ val equals = { String self, String s -> }; // Return the String hashCode (cached, and never 0) -val hashCode = { String self -> +val hashCode = { !String self -> self._hashCode ? self._hashCode : (self._hashCode = _hashCodeString(self)); }; -val _hashCodeString = { String self -> +val _hashCodeString = { !String self -> int hash=0; if( self.cs ) for( int i=0; i< self.cs#; i++ ) diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testAntiDeps1.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testAntiDeps1.smp index baf77c92..950e282d 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testAntiDeps1.smp +++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testAntiDeps1.smp @@ -1,6 +1,6 @@ struct S { int f; }; var v0 = new S; -S? v1; +!S? !v1; if (arg) v1 = new S; if (v1) { v0.f = v1.f; diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testInfinite.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testInfinite.smp index 1e4557d3..5e517b61 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testInfinite.smp +++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testInfinite.smp @@ -1 +1 @@ -struct S { int i; }; S !s = new S; while(1) s.i++; +struct S { int i; }; !S !s = new S; while(1) s.i++; diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testPerson.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testPerson.smp index 06c2861e..09285034 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testPerson.smp +++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testPerson.smp @@ -3,7 +3,7 @@ struct Person { int age; }; -val fcn = { Person?[] ps, int x -> +val fcn = { !Person?[] ps, int x -> if( ps[x] ) ps[x].age++; }; diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testStringExport.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testStringExport.smp index 3a696d66..f3d34b0a 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testStringExport.smp +++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testStringExport.smp @@ -5,7 +5,7 @@ struct String { }; // Compare two Strings -val equals = { String self, String s -> +val equals = { !String !self, !String s -> if( self == s ) return true; if( self.cs# != s.cs# ) return false; for( int i=0; i< self.cs#; i++ ) @@ -15,13 +15,13 @@ val equals = { String self, String s -> }; // Return the String hashCode (cached, and never 0) -val hashCode = { String self -> +val hashCode = { !String self -> self._hashCode ? self._hashCode : (self._hashCode = _hashCodeString(self)); }; -val _hashCodeString = { String self -> +val _hashCodeString = { !String self -> int hash=0; for( int i=0; i< self.cs#; i++ ) hash = hash*31 + self.cs[i]; diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testInfiniteReturn.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testInfiniteReturn.smp index 523fc6f9..f9866025 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testInfiniteReturn.smp +++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testInfiniteReturn.smp @@ -1 +1 @@ -struct S { int i; }; S !s = new S; while(1) s.i++; return s.i; +struct S { int i; }; !S !s = new S; while(1) s.i++; return s.i; diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testPerson.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testPerson.smp index a3cafc9a..f860091d 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testPerson.smp +++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testPerson.smp @@ -2,7 +2,7 @@ struct Person { i32 age; }; -val fcn = { Person?[] ps, int x -> +val fcn = { !Person?[] ps, int x -> if( ps[x] ) ps[x].age++; }; diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextFail.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextFail.smp index 2d431705..9558bca4 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextFail.smp +++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextFail.smp @@ -1,4 +1,4 @@ struct Person { i32 age;}; -Person !p = new Person; +!Person !p = new Person; p.age = (arg<<17)>>17; return 0; diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextFail2.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextFail2.smp index 01e070c8..d3a27319 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextFail2.smp +++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextFail2.smp @@ -1,5 +1,5 @@ struct Person { i32 age;}; - Person !p = new Person; + !Person !p = new Person; p.age = (arg<<48)>>48; return 0; diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextSuccess.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextSuccess.smp index 6d11a431..fdf4a7e9 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextSuccess.smp +++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextSuccess.smp @@ -1,5 +1,5 @@ // Should fold away sign extend struct Person { i8 age;}; -Person !p = new Person; +!Person !p = new Person; p.age = (arg<<48)>>48; return 0; diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23AllocTest-testString.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23AllocTest-testString.smp index 1a4d55c1..4ef375e9 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23AllocTest-testString.smp +++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23AllocTest-testString.smp @@ -6,7 +6,7 @@ struct String { }; // Compare two Strings -val equals = { String self, String s -> +val equals = { !String !self, !String s -> if( self == s ) return true; if( self.cs# != s.cs# ) return false; for( int i=0; i< self.cs#; i++ ) @@ -16,13 +16,13 @@ val equals = { String self, String s -> }; // Return the String hashCode (cached, and never 0) -val hashCode = { String self -> +val hashCode = { !String self -> self._hashCode ? self._hashCode : (self._hashCode = _hashCodeString(self)); }; -val _hashCodeString = { String self -> +val _hashCodeString = { !String self -> int hash=0; for( int i=0; i< self.cs#; i++ ) hash = hash*31 + self.cs[i]; diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testAndPtr.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testAndPtr.smp index 5d300ede..556a1375 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testAndPtr.smp +++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testAndPtr.smp @@ -1,6 +1,6 @@ {i32 i64 u32 -> u32} write="C"; val _p={u8[~] str -> i64 ptr=str; return write(1,ptr,str#);}; -struct S { S? fld; new S={ S? f -> fld=f; }; }; +struct S { !S? !fld; new S={ !S? f -> fld=f; }; }; val ptr = arg == 1 ? null : new S(arg==1 ? null : new S(null)); if( ptr && ptr.fld ) { diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testFRefFields.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testFRefFields.smp index 8eb86522..00fcb589 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testFRefFields.smp +++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testFRefFields.smp @@ -16,5 +16,5 @@ struct Scan { return true; }; }; -Scan !s = new Scan(" q"); +!Scan !s = new Scan(" q"); return s.peek('q'); diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testMethod.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testMethod.smp index 3ba33216..27a19988 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testMethod.smp +++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testMethod.smp @@ -13,5 +13,5 @@ struct Scan { buf[x++]==ch; }; }; -Scan !s = new Scan(" [1,2]"); +!Scan !s = new Scan(" [1,2]"); return s.require('['); diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOrRhsLoopLocal.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOrRhsLoopLocal.smp index a4c8a9f5..cf6d93ab 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOrRhsLoopLocal.smp +++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOrRhsLoopLocal.smp @@ -2,7 +2,7 @@ struct S { int f; }; int x=0; int result=arg&1; if(!result) { - S !s=new S; + !S !s=new S; while(x<3) x++; s.f=++x; result=!!s.f; diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOrRhsLoopMemory.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOrRhsLoopMemory.smp index 775b92b5..370bfb8d 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOrRhsLoopMemory.smp +++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOrRhsLoopMemory.smp @@ -1,8 +1,8 @@ struct S { int f; }; -S !s=new S; +!S !s=new S; int result=arg&1; if(!result) { - S !t=new S; + !S !t=new S; while(s.f<3) s.f++; s.f++; t.f=s.f; diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testBubbleSort.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testBubbleSort.smp index a61ca332..7aeeaa35 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testBubbleSort.smp +++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testBubbleSort.smp @@ -137,7 +137,7 @@ struct aryi64 { }; }; val parseAryI64 = { u8[~] str -> - Scan !s = new Scan(str); + !Scan !s = new Scan(str); if( !s.match('[') ) return null; // Array of parsed results var nums = new aryi64(new i64[1]); @@ -185,7 +185,7 @@ struct aryi64 { // // Bubble sort, with reading (parsing) and writing (formatting) the data. -val sort = { aryi64 nums -> +val sort = { !aryi64 nums -> var done = false; while( !done ) { done = true; |
