diff options
Diffstat (limited to 'chapter26/src')
| -rw-r--r-- | chapter26/src/main/java/com/seaofnodes/simple/Parser.java | 76 | ||||
| -rw-r--r-- | chapter26/src/test/java/com/seaofnodes/simple/PrefixUpdateTest.java | 53 |
2 files changed, 95 insertions, 34 deletions
diff --git a/chapter26/src/main/java/com/seaofnodes/simple/Parser.java b/chapter26/src/main/java/com/seaofnodes/simple/Parser.java index 45c429d8..520d7e3a 100644 --- a/chapter26/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter26/src/main/java/com/seaofnodes/simple/Parser.java @@ -1475,18 +1475,17 @@ public class Parser { // Pre-dec/pre-inc int old = pos(); if( match("--") || match("++") ) { - int delta = _lexer.peek(-1)=='+' ? 1 : -1; // Pre vs post + int delta = _lexer.peek(-1)=='+' ? 1 : -1; // Inc vs Dec + int start = pos(); String name = _lexer.matchId(); - if( name!=null ) { - Var n = _scope.lookup(name); - if( n != null && !(n.type() instanceof TypeMemPtr) ) { - if( n._final && !_scope.canInit(n) ) - throw error("Cannot reassign final '"+n._name+"'"); - checkSharedField(n); - Node expr = liftExpr(peep(new AddNode(_scope.in(n),con(delta))),n.type(),true); - _scope.update(n,expr); - return expr; - } + Var var = name==null ? null : _scope.lookup(name); + if( var!=null && !(var.type() instanceof TypeMemPtr) ) { + if( var._final && !_scope.canInit(var) ) + throw error("Cannot reassign final '"+var._name+"'"); + // Resolve fields just as a read or postfix update does. Updating + // their declaration's local slot would silently lose the Store. + pos(start); + return parsePrimary(delta); } // Reset, try again pos(old); @@ -1507,7 +1506,10 @@ public class Parser { * </pre> * @return a primary {@link Node}, never {@code null} */ - private Node parsePrimary() { + private Node parsePrimary() { return parsePrimary(0); } + + // pre is +/-1 for a prefix update of a known name, zero otherwise. + private Node parsePrimary(int pre) { if( _lexer.isNumber(_lexer.peek()) ) return parseLiteral(); if( _lexer.peek('"') ) return newString(parseString()); if( matchx("true" ) ) return con(1); @@ -1555,7 +1557,7 @@ public class Parser { if( instance(var) ) { Var selfVar = _scope.lookup("self"); Node self = liftExpr(_scope.in(selfVar),selfVar.type(),true); - return parsePostfixName(self,id); + return parsePostfixName(self,id,pre); } // Load local value @@ -1570,7 +1572,7 @@ public class Parser { else throw error("Cannot read uninitialized field '"+id+"'"); // Check for assign-update, x += e0; - char ch = _lexer.matchOperAssign(); + char ch = pre==0 ? _lexer.matchOperAssign() : (char)pre; if( ch==0 ) // Normal primary, check for postfix updates return parsePostfix(rvalue); // Assign-update direct into Scope @@ -1580,7 +1582,8 @@ public class Parser { checkCaptureWrite(var); Node op = opAssign(ch,rvalue, var.type() ); _scope.update(var,op); - return postfix(ch) ? rvalue.unkeep() : op; + if( postfix(ch) ) rvalue.unkeep(); + return pre==0 && postfix(ch) ? rvalue : op; } // true if instance variable lookup (var is in any number of nested blocks, @@ -1877,8 +1880,10 @@ public class Parser { * expr ('[' expr ']')* [ [op]= expr ] // Array reference * </pre> */ - private Node parsePostfixName(Node expr, String name) { - if( privateSelf(expr) ) return parsePrivateField(name); + private Node parsePostfixName(Node expr, String name) { return parsePostfixName(expr,name,0); } + + private Node parsePostfixName(Node expr, String name, int pre) { + if( privateSelf(expr) ) return parsePrivateField(name,pre); if( !privateSelf(expr) && mayBeSelf(expr,new java.util.BitSet()) ) throw error("Cannot access self through a merged alias during construction"); @@ -1887,7 +1892,7 @@ public class Parser { // Sanity check expr for being a reference if( !(expr._type instanceof TypeMemPtr ptr) ) - return parseUnknownField(expr,name); + return parseUnknownField(expr,name,pre); // Find the field from the Type. Lookup in the base object field names. TypeMemPtr ptr2 = (TypeMemPtr)TYPES.get(ptr._obj._name); @@ -1895,7 +1900,7 @@ public class Parser { ptr = ptr.makeFrom(ptr2._obj); // The declaration fixes the field shape; retain this view's access. TypeStruct base = ptr._obj; int fidx = base.find(name); - if( fidx==-1 && base._open ) return parseUnknownField(expr,name); + if( fidx==-1 && base._open ) return parseUnknownField(expr,name,pre); if( fidx == -1 ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'"); @@ -1918,7 +1923,7 @@ public class Parser { : off(base,name)).keep(); // Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment - if( matchOpx('=','=') ) { + if( pre==0 && matchOpx('=','=') ) { Node val = parseAsgn().keep(); Node lift = liftExpr( val, tf, false ); @@ -1937,14 +1942,16 @@ public class Parser { load = peep(load); // Check for assign-update, "ptr.fld += expr" or "ary[idx]++" - char ch = _lexer.matchOperAssign(); + char ch = pre==0 ? _lexer.matchOperAssign() : (char)pre; if( ch!=0 ) { Node op = opAssign(ch,load,tf); + // A narrow Store may drop its conversion; prefix still returns it. + if( pre!=0 ) op.keep(); store(name,f._alias,tf,expr.unkeep(),off.unkeep(),op,false,base.isAry() ? ctrl() : null); - load = postfix(ch) ? load.unkeep() : op; - // And use the original loaded value as the result - return load; - //throw Utils.TODO(); // NO POSTFIX AFTER STORE, JUST RETURN + if( pre!=0 ) op.unkeep(); + if( postfix(ch) ) load.unkeep(); + // Prefix returns the new value; postfix returns the old load. + return pre==0 && postfix(ch) ? load : op; } else { off.unkill(); // Method call: Loaded a function from a named field, AND the base @@ -1969,7 +1976,7 @@ public class Parser { // Syntax fixes the access even when a forward definition or a loop // leaves the receiver untyped. Resolve its alias and layout in the graph. - private Node parseUnknownField(Node expr, String name) { + private Node parseUnknownField(Node expr, String name, int pre) { expr.keep(); Node off = new FldOffNode(expr,name).peephole(); if( name.equals("[]") ) { @@ -1978,7 +1985,7 @@ public class Parser { off = peep(new AddNode(off,peep(new ShlNode(loc(),index,scale)))); } off.keep(); - if( matchOpx('=','=') ) { + if( pre==0 && matchOpx('=','=') ) { Node value = parseAsgn().keep(); unknownStore(name,expr,off,value); expr.unkill(); @@ -1988,7 +1995,7 @@ public class Parser { Node load = new LoadNode(loc(),name,1,mem(),expr,off); load.setDef(0,ctrl()); load = peep(load); - char ch = _lexer.matchOperAssign(); + char ch = pre==0 ? _lexer.matchOperAssign() : (char)pre; if( ch!=0 ) { Node op = opAssign(ch,load,Type.BOTTOM).keep(); unknownStore(name,expr,off,op); @@ -1996,7 +2003,7 @@ public class Parser { // the stored value after conversion; postfix returns the old load. // Reading our own Store folds once its alias and width are known. if( postfix(ch) ) load.unkeep(); - else { + if( pre!=0 || !postfix(ch) ) { load = new LoadNode(loc(),name,1,mem(),expr,off); load.setDef(0,ctrl()); load = peep(load); @@ -2010,7 +2017,7 @@ public class Parser { } else load.keep(); expr.unkill(); off.unkill(); - return parsePostfix(load.unkeep()); + return pre==0 ? parsePostfix(load.unkeep()) : load.unkeep(); } private void unknownStore(String name, Node ptr, Node off, Node value) { @@ -2022,12 +2029,12 @@ public class Parser { // Explicit self.x and an unqualified field name use the same local slot. // The map also keeps self.x distinct from a parameter/local named x. - private Node parsePrivateField(String name) { + private Node parsePrivateField(String name, int pre) { Var v=_fieldVars.get(name); if( v==null ) throw error("Cannot resolve field '"+name+"'"); if( v.type() instanceof TypeFunPtr ctor && ctor.ret() instanceof TypeMem ) throw error("A constructor is only called by new"); - if( matchOpx('=','=') ) { + if( pre==0 && matchOpx('=','=') ) { checkPrivateWrite(name); Node value=parseAsgn().keep(); checkPublication(value); @@ -2037,13 +2044,14 @@ public class Parser { Node value=_scope.in(_scope.update(v,null)); if( value instanceof ConstantNode && (value._type==Type.BOTTOM || value._type==Type.TOP) ) throw error("Cannot read uninitialized field '"+name+"'"); - char ch=_lexer.matchOperAssign(); + char ch=pre==0 ? _lexer.matchOperAssign() : (char)pre; if( ch!=0 ) { checkPrivateWrite(name); Node op=opAssign(ch,value,v.type()); checkPublication(op); _scope.update(v,op); - return postfix(ch) ? value.unkeep() : op; + if( postfix(ch) ) value.unkeep(); + return pre==0 && postfix(ch) ? value : op; } if( value._type instanceof TypeFunPtr && match("(") ) return parsePostfix(require(functionCall(value,self()),")")); diff --git a/chapter26/src/test/java/com/seaofnodes/simple/PrefixUpdateTest.java b/chapter26/src/test/java/com/seaofnodes/simple/PrefixUpdateTest.java new file mode 100644 index 00000000..56d553b3 --- /dev/null +++ b/chapter26/src/test/java/com/seaofnodes/simple/PrefixUpdateTest.java @@ -0,0 +1,53 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.type.TypeInteger; +import org.junit.Test; +import static org.junit.Assert.*; + +public class PrefixUpdateTest { + @Test public void testMethodFieldWrites() { + String[] fields = {"int x=0", "int x=3", "u8 x=0", "f32 x=0"}; + String[] updates = {"++x", "--x", "++x", "++x"}; + String[] results = {"221", "112", "221", "221.0"}; + for(int i=0;i<fields.length;i++) + for(int seed:new int[]{2,13,123}) { + String src="struct C { "+fields[i]+"; val change_noInline={ -> "+updates[i]+"; }; }; " + +"var c=new C; val a=c.change_noInline(); val b=c.change_noInline(); return a+10*b+100*c.x;"; + CodeGen code=new CodeGen(src,TypeInteger.BOT,seed,true).driver(CodeGen.Phase.TypeCheck); + assertEquals(fields[i],results[i],Eval2.eval(code,0)); + } + } + + @Test public void testLocalAndFinalUpdates() { + CodeGen code=new CodeGen("int x=arg; return ++x*100 + --x*10 + x;").driver(CodeGen.Phase.TypeCheck); + assertEquals("433",Eval2.eval(code,3)); + code=new CodeGen("struct C { int x=7; val f={int x -> ++x;}; }; var c=new C; return c.f(3)+10*c.x;") + .driver(CodeGen.Phase.TypeCheck); + assertEquals("74",Eval2.eval(code,0)); + for(String src:new String[]{"val x=1; return ++x;", + "struct C { val x=1; val f={ -> ++x; }; }; var c=new C; return c.f();"}) { + try { + new CodeGen(src).driver(CodeGen.Phase.TypeCheck); + fail("final update"); + } catch(RuntimeException e) { + assertTrue(e.getMessage(),e.getMessage().contains("final")); + } + } + } + + @Test public void testNativeByteOverflow() throws java.io.IOException { + String src="val prefixUpdate={int arg -> " + +"struct C { u8 x=255; val inc_noInline={ -> ++x; }; }; " + +"var c=new C; val a=c.inc_noInline(); val b=c.inc_noInline(); return a+10*b+100*c.x; };"; + var dir=java.nio.file.Path.of("build/objs"); + java.nio.file.Files.createDirectories(dir); + var driver=dir.resolve("prefixUpdate.c"); + java.nio.file.Files.writeString(driver,""" + __attribute__((CALL_CONV)) extern long long prefixUpdate(long long); + int main(void) { return prefixUpdate(0)!=110; } + """); + String abi=TestC.OS.startsWith("Windows") ? "ms_abi" : "sysv_abi"; + TestC.run(src,TestC.CALL_CONVENTION,null,abi,driver.toString(),"build/objs/prefixUpdate","S","",-1); + } +} |
