diff options
Diffstat (limited to 'chapter31/src/main/java/com/seaofnodes')
21 files changed, 345 insertions, 37 deletions
diff --git a/chapter31/src/main/java/com/seaofnodes/simple/codegen/Encoding.java b/chapter31/src/main/java/com/seaofnodes/simple/codegen/Encoding.java index b07a8fec..eb62c36a 100644 --- a/chapter31/src/main/java/com/seaofnodes/simple/codegen/Encoding.java +++ b/chapter31/src/main/java/com/seaofnodes/simple/codegen/Encoding.java @@ -39,7 +39,9 @@ public class Encoding implements com.seaofnodes.isa.CodeSink { public final BAOS _bits = new BAOS(); // Instructions / encodings public final BAOS _cpool = new BAOS(); // Constant r/o pool public final BAOS _sdata = new BAOS(); // Static r/w pool - public final HashMap<Node,Relo> _bigCons = new HashMap<>(); + // Relocations belong to instruction occurrences, including equal constant + // loads cloned by scheduling/register allocation. Structural keys lose sites. + public final IdentityHashMap<Node,Relo> _bigCons = new IdentityHashMap<>(); public final StaticData _data; public final HashMap<String,Integer> _externalAddresses = new HashMap<>(); @@ -157,7 +159,7 @@ public class Encoding implements com.seaofnodes.isa.CodeSink { // Nodes need "relocation" patching; things done after code is placed. // Record src and dst Nodes. - private final HashMap<Node,CFGNode> _internals = new HashMap<>(); + private final IdentityHashMap<Node,CFGNode> _internals = new IdentityHashMap<>(); // Source is a Call, destination in the Fun. public Encoding relo( CallNode call ) { FunNode fun = _code.link(call.tfp()); @@ -187,13 +189,13 @@ public class Encoding implements com.seaofnodes.isa.CodeSink { } // External references only located by Strings a link-time - public final HashMap<Node,String> _externals = new HashMap<>(); + public final IdentityHashMap<Node,String> _externals = new IdentityHashMap<>(); public Encoding external( Node n, String extern ) { _externals.put(n,extern); return this; } - public final HashMap<Node,String> _externData = new HashMap<>(); + public final IdentityHashMap<Node,String> _externData = new IdentityHashMap<>(); public void externalData(Node n, String name) { _externData.put(n,name); } // Store t as a 32/64 bit constant in the code space; generate RIP-relative diff --git a/chapter31/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java b/chapter31/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java index a86cd18a..418edaf6 100644 --- a/chapter31/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java +++ b/chapter31/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java @@ -21,12 +21,14 @@ abstract class TypeCheck { Ary<Node> errs = new Ary<>(Node.class); Ary<ConstructNode> constructs = new Ary<>(ConstructNode.class); Ary<InitViewNode> views = new Ary<>(InitViewNode.class); + Ary<ArrayInitNode> arrays = new Ary<>(ArrayInitNode.class); code._stop.walk( n -> { if( n.err() != null ) errs.add(n); if( n instanceof CLInitNode ) code.add(n); if( n instanceof ConstructNode con ) constructs.add(con); if( n instanceof InitViewNode view ) views.add(view); + if( n instanceof ArrayInitNode array ) arrays.add(array); return null; }); @@ -57,13 +59,15 @@ abstract class TypeCheck { if( !con.isDead() ) con.subsume(con.in(1)); for( InitViewNode view : views ) if( !view.isDead() ) view.subsume(view.in(1)); + for( ArrayInitNode array : arrays ) + if( !array.isDead() ) array.subsume(array.in(2)); } private static int errorRank(Node n) { // Bad memory access originates the error. Missing construction fields // in turn explain generic-scalar arithmetic further downstream. if( n instanceof MemOpNode && noBottomInputs(n) ) return 0; - if( n instanceof ConstructNode ) return 1; + if( n instanceof ConstructNode || n instanceof ArrayInitNode ) return 1; return noBottomInputs(n) ? 2 : 3; } diff --git a/chapter31/src/main/java/com/seaofnodes/simple/node/ArrayInitNode.java b/chapter31/src/main/java/com/seaofnodes/simple/node/ArrayInitNode.java new file mode 100644 index 00000000..30f86ac6 --- /dev/null +++ b/chapter31/src/main/java/com/seaofnodes/simple/node/ArrayInitNode.java @@ -0,0 +1,44 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; +import com.seaofnodes.simple.parser.Lexer; +import com.seaofnodes.simple.parser.Parser; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.Ary; + +/** Source checks for array construction and its yielded elements. Erased by + * TypeCheck before serialization and scheduling. */ +public final class ArrayInitNode extends Node { + private final int _count; // List size, -1 for a generator, -2 for a yielded element. + private final Lexer _loc; + public ArrayInitNode(Node ctrl, Node mem, Node ptr, Node len, int count, Lexer loc, Ary<Node> values) { + super(ctrl,mem,ptr,len); + _count=count; _loc=loc; + for( Node value : values ) addDef(value); + } + public ArrayInitNode(Node ctrl, Node value, Lexer loc) { + super(ctrl,null,value); + _count=-2; _loc=loc; + } + @Override public String label() { return "ArrayInit"; } + @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("arrayInit(").n(in(2)).p(")"); } + @Override public Type compute() { return in(0)._type==Type.CONTROL ? in(2)._type : Type.TOP; } + @Override public Node idealize() { return null; } + @Override public Parser.ParseException err() { + if( in(0)._type==Type.XCONTROL ) return null; + if( _count==-2 ) + return TypeMemPtr.mayBePartial(in(2)._type) + ? Parser.error("Array initializer requires fully initialized elements",_loc) : null; + if( !(in(3)._type instanceof TypeInteger len) ) + return Parser.error("Array length must be an integer",_loc); + if( len.isConstant() && (len.value()<0 || len.value()>TypeInteger.U32._max) ) + return Parser.error("Array length is outside the u32 range",_loc); + if( _count>=0 && (!len.isConstant() || len.value()!=_count) ) + return Parser.error("Array initializer has "+_count+" elements; length must equal that constant",_loc); + for( int i=4; i<nIns(); i++ ) + if( TypeMemPtr.mayBePartial(in(i)._type) ) + return Parser.error("Array initializer requires fully initialized elements",_loc); + return null; + } + @Override public boolean eq(Node n) { return this==n; } +} diff --git a/chapter31/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter31/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java index aa3f6d57..19420839 100644 --- a/chapter31/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java +++ b/chapter31/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java @@ -20,8 +20,20 @@ public class MemMergeNode extends Node { public MemMergeNode( Node ...nodes) { super(nodes); _type = TypeMem.BOT; } public MemMergeNode(MemMergeNode mem) { super(mem); } @Override public Tag serialTag() { return Tag.MemMerge; } - public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { baos.packed1(nIns()); } - static Node make( BAOS bais ) { Node mem = new MemMergeNode(); mem.setDefX(bais.packed1()-1,null); return mem; } + public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { + // A memory merge always has inputs. Zero escapes a wide alias table, + // preserving every existing nonzero single-byte count. + assert nIns()>0; + baos.packed1(nIns()<=255 ? nIns() : 0); + if( nIns()>255 ) baos.packed4(nIns()); + } + static Node make( BAOS bais ) { + int count=bais.packed1(); + if( count==0 ) count=bais.packed4(); + Node mem=new MemMergeNode(); + mem.setDefX(count-1,null); + return mem; + } @Override public String label() { return "ALLMEM"; } @Override public boolean isMem() { return true; } diff --git a/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java b/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java index 10cee059..ae725659 100644 --- a/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java +++ b/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java @@ -9,6 +9,7 @@ import com.seaofnodes.simple.util.Utils; // Jump on flags, uses flags public class JmpX86 extends IfNode implements MachNode, RIPRelSize { String _bop; + boolean _unsigned, _floating, _unorderedTaken; JmpX86( IfNode iff, String bop ) { super(iff); _bop = bop; @@ -23,29 +24,47 @@ public class JmpX86 extends IfNode implements MachNode, RIPRelSize { if( set instanceof SetX86 setx && (cmp instanceof CmpX86 || cmp instanceof CmpIX86 || cmp instanceof CmpMemX86 || cmp instanceof CmpFX86) ) { _inputs.set( 1, cmp ); _bop = setx._bop; + _unsigned = setx._unsigned; + _floating = cmp instanceof CmpFX86; + _unorderedTaken = _bop.equals("!="); } else throw Utils.TODO(); } @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.FLAGS_MASK; } @Override public RegMask outregmap() { return null; } - @Override public void negate() { _bop = negate(_bop); } + @Override public void negate() { + _bop = negate(_bop); + if( _floating ) _unorderedTaken = !_unorderedTaken; + } + + private int condition() { return x86_64_v2.jumpop(_bop,_unsigned)&15; } @Override public void encoding( Encoding enc ) { if( in(1)==null && _bop=="!=" ) return; - X86.branch(enc,in(1)==null ? -1 : x86_64_v2.jumpop(_bop)&15); + // UCOMISD sets CF/ZF/PF, not signed-integer SF/OF. Handle unordered + // separately, including when layout has negated the original test. + if( _floating ) { + if( _unorderedTaken ) enc.add1(0x0F).add1(0x8A).add4(0); // JP target + else enc.add1(0x7A).add1(0); // JP past the ordered jump + } + X86.branch(enc,in(1)==null ? -1 : condition()); enc.jump(this,cproj(0)); } // Delta is from opcode start, but X86 measures from the end of the 2-byte encoding @Override public byte encSize(int delta) { if( in(1)==null && _bop=="!=" ) return 0; - return X86.branchSize(delta,in(1)!=null); + int prefix = !_floating ? 0 : _unorderedTaken ? 6 : 2; + return (byte)(prefix + X86.branchSize(delta-prefix,in(1)!=null)); } // Delta is from opcode start @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { assert !(in(1)==null && _bop=="!="); - X86.patchBranch(enc.bits(),opStart,opLen,delta,in(1)==null ? -1 : x86_64_v2.jumpop(_bop)&15); + int prefix = !_floating ? 0 : _unorderedTaken ? 6 : 2; + if( _floating ) + X86.patchBranch(enc.bits(),opStart,prefix,_unorderedTaken ? delta : opLen,10); + X86.patchBranch(enc.bits(),opStart+prefix,opLen-prefix,delta-prefix,in(1)==null ? -1 : condition()); } @Override public void asm(CodeGen code, SB sb) { diff --git a/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java b/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java index fcca7f8f..b0ecd685 100644 --- a/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java +++ b/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java @@ -67,9 +67,9 @@ public abstract class MemOpX86 extends MemOpNode implements MachNode { if( i==1 ) return null; // Memory if( i==2 ) return x86_64_v2.RMASK; // base in GPR if( i==3 ) return x86_64_v2.RMASK; // index in GPR - if( i==4 ) return _sz >= '4' - ? x86_64_v2.MEM_MASK // value in GPR or XMM - : x86_64_v2.RMASK; // Bytes and shorts in GPR only + // Integer arithmetic and comparisons require a GPR value operand. + // Only StoreX86 has alternate encodings that accept XMM values. + if( i==4 ) return x86_64_v2.RMASK; return null; // Anti-dependence } diff --git a/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java b/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java index 86610f6d..4494985d 100644 --- a/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java +++ b/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java @@ -9,7 +9,7 @@ public class NegX86 extends MachConcreteNode implements MachNode { NegX86(MinusNode not) { super(not); } @Override public String op() { return "neg"; } @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } - @Override public RegMask outregmap() { return x86_64_v2.RMASK; } + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; } @Override public int twoAddress() { return 1; } diff --git a/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java b/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java index 1e4e7c26..1291cf78 100644 --- a/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java +++ b/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java @@ -9,7 +9,7 @@ public class NotX86 extends MachConcreteNode implements MachNode { NotX86(NotNode not) { super(not); } @Override public String op() { return "not"; } @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } - @Override public RegMask outregmap() { return x86_64_v2.RMASK; } + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; } @Override public void encoding( Encoding enc ) { diff --git a/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java b/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java index 864c1b55..bcc4880b 100644 --- a/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java +++ b/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java @@ -4,7 +4,6 @@ import com.seaofnodes.isa.X86; import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; // Corresponds to the x86 instruction "sete && setne". // Use result of comparison without jump. @@ -25,15 +24,14 @@ public class SetX86 extends MachConcreteNode implements MachNode { @Override public RegMask outregmap() { return x86_64_v2.WMASK; } @Override public void encoding( Encoding enc ) { - int op=switch (_bop) { - case "==" -> 0x94; // SETE - case "<" -> _unsigned ? 0x92 : 0x9C; // SETB /SETL - case "<=" -> _unsigned ? 0x96 : 0x9E; // SETBE/SETLE - case ">=" -> _unsigned ? 0x93 : 0x9D; // SETAE/SETGE - case ">" -> _unsigned ? 0x97 : 0x9F; // SETA /SETG - default -> throw Utils.TODO(); - }; - X86.set(enc,op&15,enc.reg(this)); + int dst=enc.reg(this); + if( in(1) instanceof CmpFX86 ) { + // MOV preserves comparison flags; unordered is true only for !=. + X86.unary(enc,0xC7,0,dst); + enc.add4(_bop.equals("!=") ? 1 : 0); + enc.add1(0x7A).add1(dst>=4 ? 8 : 6); // JP past SETcc/MOVZX + } + X86.set(enc,x86_64_v2.jumpop(_bop,_unsigned)&15,dst); } @Override public void asm(CodeGen code, SB sb) { diff --git a/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java b/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java index 9ffa25fc..8dfd31e7 100644 --- a/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java +++ b/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java @@ -25,6 +25,9 @@ public class StoreX86 extends MemOpX86 { } // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return null; } + @Override public RegMask regmap(int i) { + return i==4 && _sz>='4' ? x86_64_v2.MEM_MASK : super.regmap(i); + } @Override public void encoding( Encoding enc ) { short ptr=enc.reg(ptr()), idx=enc.reg(idx()), src=enc.reg(val()); if( src==-1 ) X86.storeImm(enc,Integer.numberOfTrailingZeros(_sz-'0'),_imm,ptr,idx,_off,_scale); diff --git a/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java index 673339a9..aed8ee4c 100644 --- a/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ b/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -87,6 +87,18 @@ public class x86_64_v2 extends Machine { default -> throw new IllegalArgumentException("Too many arguments"); }; } + static int jumpop(String op, boolean unsigned) { + if( !unsigned ) return jumpop(op); + return switch( op ) { + case "==" -> 0x84; + case "!=" -> 0x85; + case "<" -> 0x82; + case "<=" -> 0x86; + case ">" -> 0x87; + case ">=" -> 0x83; + default -> throw new IllegalArgumentException(op); + }; + } // Limit of float args passed in registers static RegMask[] XMMS8 = new RegMask[]{ diff --git a/chapter31/src/main/java/com/seaofnodes/simple/parser/Allocations.java b/chapter31/src/main/java/com/seaofnodes/simple/parser/Allocations.java index 861f95b6..5c6b5765 100644 --- a/chapter31/src/main/java/com/seaofnodes/simple/parser/Allocations.java +++ b/chapter31/src/main/java/com/seaofnodes/simple/parser/Allocations.java @@ -25,11 +25,20 @@ final class Allocations { } // Parse ary[ length_expr ] if( P.match("[") ) { - if( !t.makeZero().isa(t) ) - throw Parser.error("Cannot allocate a non-nullable, since arrays are always zero/null filled",site); + boolean readonly = P.match("~"); + if( P.match("]") ) { + P.require("{"); + return P.arrays.parse(t,readonly,null,site); + } Node len = P.expressions.parseAssignment().keep(); P.require("]"); + if( P.match("{") ) return P.arrays.parse(t,readonly,len,site); + if( !t.makeZero().isa(t) ) + throw Parser.error("Cannot allocate a non-nullable, since arrays are always zero/null filled",site); + if( readonly ) return new ReadOnlyNode(allocArray(P.types.typeAry(t,true),len)).peephole(); return allocArray(P.types.typeAry(t,false),len); + } else if( t instanceof TypeMemPtr ary && ary._obj.isAry() && P.match("{") ) { + return P.arrays.parse(ary._obj.field("[]")._t,ary._ro,null,site); } else { if( !(t instanceof TypeMemPtr tmp) ) throw Parser.error("Cannot allocate a "+t.str(),site); @@ -101,8 +110,6 @@ final class Allocations { // TODO: Needs runtime check. Node st = P.peep(new StoreNode(null, "#", 1, TypeInteger.U32, null, smem, self, ConFldOffNode.make(ts,"#"), len.unkeep(), true)); - // TODO: Allow add-on "constructor" to init the array elements - Node prior = P.mem(); Node esc = P.peep(new EscapeNode(lenFld,self,st,prior)); P.mem(P.mergeAlias(prior,lenFld._alias,esc)); diff --git a/chapter31/src/main/java/com/seaofnodes/simple/parser/ArrayInitializers.java b/chapter31/src/main/java/com/seaofnodes/simple/parser/ArrayInitializers.java new file mode 100644 index 00000000..70eeae1f --- /dev/null +++ b/chapter31/src/main/java/com/seaofnodes/simple/parser/ArrayInitializers.java @@ -0,0 +1,185 @@ +package com.seaofnodes.simple.parser; + +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.ScopeNode.Kind; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.Ary; + +/** Array construction keeps the destination private until all elements exist. + * A generator is a scoped, immediately executed loop body, not an escaping closure. */ +final class ArrayInitializers { + private final Parser P; + Frame _frame; + + static final class Frame { + final Kind.Block _kind; + final int _depth; + final Var _value; + ScopeNode _returns; + Frame(Kind.Block kind, int depth, Var value) { + _kind=kind; _depth=depth; _value=value; + } + } + + ArrayInitializers(Parser P) { this.P=P; } + + // Opening brace consumed; an absent length means a list with inferred size. + Node parse(Type elem, boolean readonly, Node len, Lexer site) { + int pos=P.pos(); + Type indexType=P.types.parseType(); + String index=indexType==null ? null : P._lexer.matchId(); + boolean generator=index!=null && P.match("->"); + if( !generator ) P.pos(pos); + if( generator && Parser.KEYWORDS.contains(index) ) + throw Parser.error("Expected an array index name",site); + if( generator && (indexType!=TypeInteger.BOT && indexType!=TypeInteger.I32 && indexType!=TypeInteger.U32) ) + throw Parser.error("Array generator index must be int, i64, or u32",site); + if( generator && len==null ) + throw Parser.error("Array generator requires a length",site); + + Ary<Node> values=new Ary<>(Node.class); + if( !generator ) { + if( !P.peek('}') ) do { + values.add(P.conversions.convertExpr(P.expressions.parseAssignment(),elem).keep()); + } while( P.match(",") && !P.peek('}') ); + P.require("}"); + if( len==null ) len=P.con(values.size()).keep(); + } + + TypeStruct ts=P.types.typeAry(elem,readonly); + Field body=ts.field("[]"), length=ts.field("#"); + Node size=new AddNode(ConFldOffNode.make(ts,"[]"), + new ShlNode(null,len,P.con(ts.aryScale())).peephole()).peephole(); + NewNode alloc=new NewNode(ts,P.ctrl(),size).init(); + Node self=new ProjNode(alloc,0,ts.str()).init().keep(); + Node mem=new ProjNode(alloc,1,"#arrayMem").init().keep(); + Node slen=new StoreNode(site,"#",length._alias,TypeInteger.U32,null,mem,self, + ConFldOffNode.make(ts,"#"),len,true).peephole().keep(); + + if( generator ) { + mem=generator(ts,self,mem,len,index,indexType,site); + } else { + for( int i=0; i<values.size(); i++ ) { + Node st=store(ts,self,mem,P.con(i),values.at(i),site).keep(); + mem.unkeep(); + mem=st; + } + } + + Node prior=P.mem(); + P.mem(P.mergeAlias(prior,length._alias,new EscapeNode(length,self,slen.unkeep(),prior).peephole())); + prior=P.mem(); + P.mem(P.mergeAlias(prior,body._alias,new EscapeNode(body,self,mem,prior).peephole())); + Node result=new ArrayInitNode(P.ctrl(),mem.unkeep(),self.unkeep(),len.unkeep(), + generator ? -1 : values.size(),site,values).peephole(); + for( Node value : values ) value.unkeep(); + return readonly ? new ReadOnlyNode(result).peephole() : result; + } + + private Node store(TypeStruct ts, Node self, Node mem, Node idx, Node value, Lexer site) { + Field fld=ts.field("[]"); + Node off=new AddNode(ConFldOffNode.make(ts,"[]"), + new ShlNode(null,idx,P.con(ts.aryScale())).peephole()).peephole(); + return new StoreNode(site,"[]",fld._alias,fld._t,P.ctrl(),mem,self,off,value,true).peephole(); + } + + private Var define(String name, Type type, boolean fin, Node value, Lexer site) { + if( !P._scope.define(name,type,fin,value,site) ) throw Parser.error("Duplicate name '"+name+"'",site); + return P._scope.lookup(name); + } + + private Node read(Var var) { return P._scope.in(P._scope.update(var,null)); } + + private Node generator(TypeStruct ts, Node self, Node mem, Node len, String name, + Type indexType, Lexer site) { + Frame outer=_frame; + ScopeNode oldBreak=P.statements._breakScope, oldContinue=P.statements._continueScope; + P.statements._breakScope=P.statements._continueScope=null; + Kind.Block kind=new Kind.Block(); + P._scope.push(kind); + // The counter stays wide even when the source index is a narrow int. + // The private memory and result slots cannot be named in source. + Var counter=define("$arrayIndex",TypeInteger.BOT,false,P.con(0),site); + Var memory=define("$arrayMem",TypeMem.makePrivate(Type.BOTTOM),false,mem.unkeep(),site); + Var result=define("$arrayValue",ts.field("[]")._t,false,P.con(Type.TOP),site); + _frame=new Frame(kind,P._scope.depth(),result); + + P.ctrl(new LoopNode(site,null,P.ctrl()).peephole()); + ScopeNode head=P._scope.keep(); + P._xScopes.push(P._scope=P._scope.dup(true)); + Node idx=read(counter).keep(); + Node pred=new BoolNode.LT(idx,len).peephole().keep(); + Node iff=new IfNode(P.ctrl(),pred).peephole().keep(); + CFGNode yes=new CProjNode(iff,0,"True").peephole().keep(); + CFGNode no=(CFGNode)new CProjNode(iff.unkeep(),1,"False").peephole(); + P.ctrl(no); + ScopeNode exit=P._scope.dup(); + P._xScopes.push(exit); + exit.addGuards(no,pred,true); + P.ctrl(yes.unkeep()); + P._scope.addGuards(yes,pred.unkeep(),false); + + P._scope.push(new Kind.Block()); + define(name,indexType,true,new ConvertNode(indexType,idx).peephole(),site); + Node last=P._code.ZERO; + while( !P.peek('}') && !P._lexer.isEOF() ) { + P._code.add(last); + last=P.statements.parseStatement(); + } + P.require("}"); + if( P.ctrl()._type!=Type.XCONTROL ) addReturn(last); + P._scope.pop(); + if( _frame._returns!=null ) { + P._scope.kill(); + P._scope=_frame._returns; + } + Node value=read(result); + // The loop starts with the whole private array memory. Keep that shape + // on the backedge too: mixing it with an element slice yields BOTTOM, + // which would falsely publish every function when the array escapes. + Node prior=read(memory).keep(); + Node st=store(ts,self,prior,idx,value,site); + P._scope.update(memory,P.mergeAlias(prior.unkeep(),ts.field("[]")._alias,st)); + P._scope.update(counter,new AddNode(idx.unkeep(),P.con(1)).peephole()); + P._scope.removeGuards(yes); + head.balanceLoopFRefs(P._scope); + exit.balanceLoopFRefs(P._scope); + head.endLoop(P._scope,exit); + head.unkeep().kill(); + P._xScopes.pop(); + P._xScopes.pop(); + if( !P._xScopes.isEmpty() ) { P._xScopes.pop(); P._xScopes.push(exit); } + P._scope=exit; + Node done=read(memory).keep(); + P._scope.pop(); + _frame=outer; + P.statements._breakScope=oldBreak; + P.statements._continueScope=oldContinue; + return done; + } + + Node addReturn(Node expr) { + Node value=P.conversions.convertExpr(expr,_frame._value.type()); + value=new ArrayInitNode(P.ctrl(),value,P.loc()).peephole().keep(); + P.mem(); // Materialize lazy memory before cloning a return path. + ScopeNode cur=P._scope.dup(); + // Saved paths share Var records with the active scope. Drop locals + // without promoting them, then copy forward references explicitly. + while( cur.depth()>_frame._depth ) cur._pop(); + cur.balanceLoopFRefs(P._scope); + cur.update(_frame._value,value.unkeep()); + if( _frame._returns==null ) _frame._returns=cur; + else { + _frame._returns.balanceLoopFRefs(cur); + cur.balanceLoopFRefs(_frame._returns); + _frame._returns.ctrl(_frame._returns.mergeScopes(cur,P.loc()).peephole()); + } + P.ctrl(P._code.XCTRL); + return P._code.ZERO; + } + + void checkAssignment(Var var, Lexer site) { + if( _frame!=null && var._idx<_frame._kind._lexSize ) + throw Parser.error("Cannot assign captured local '"+var._name+"' in an array generator",site); + } +} diff --git a/chapter31/src/main/java/com/seaofnodes/simple/parser/Functions.java b/chapter31/src/main/java/com/seaofnodes/simple/parser/Functions.java index 5175acc6..8047fabc 100644 --- a/chapter31/src/main/java/com/seaofnodes/simple/parser/Functions.java +++ b/chapter31/src/main/java/com/seaofnodes/simple/parser/Functions.java @@ -198,6 +198,8 @@ final class Functions { } ReturnNode parseBody( String funName, TypeFunPtr sig, Lexer loc, BitSet finals, String[] ids) { + ArrayInitializers.Frame outerArray = P.arrays._frame; + P.arrays._frame = null; Structs.Constructor outerCtor = P.structs._pendingCtor; P.structs._pendingCtor = null; boolean isInit = FunNode.isInit(funName); @@ -275,6 +277,7 @@ final class Functions { P.statements._breakScope = breakScope; P._scope.pop(); P.construction._frame = outerConstruction; + P.arrays._frame = outerArray; return ret; } diff --git a/chapter31/src/main/java/com/seaofnodes/simple/parser/Lexer.java b/chapter31/src/main/java/com/seaofnodes/simple/parser/Lexer.java index 9ab678ca..62dd054e 100644 --- a/chapter31/src/main/java/com/seaofnodes/simple/parser/Lexer.java +++ b/chapter31/src/main/java/com/seaofnodes/simple/parser/Lexer.java @@ -405,14 +405,19 @@ public class Lexer { precision = formatNumber(site); } char kind=nextChar(); - if( "diuoxXfFeEgGaAcsb".indexOf(kind)<0 ) + if( "diuoxXfFeEgGaAcsbt".indexOf(kind)<0 ) throw Parser.error("Expected a string format: %(expression), %d(expression), or %%",site); boolean number="diuoxXfFeEgGaA".indexOf(kind)>=0; if( !number && (flags&~1)!=0 || "uoxX".indexOf(kind)>=0 && (flags&6)!=0 || "diu".indexOf(kind)>=0 && (flags&8)!=0 ) throw Parser.error("Invalid flags for %"+kind,site); - if( precision>=0 && (kind=='c' || kind=='b') ) + if( precision>=0 && (kind=='c' || kind=='b' || kind=='t') ) throw Parser.error("Precision is not supported for %"+kind,site); + if( kind=='t' && peek()!='(' ) { + int style="FrRMDT".indexOf(nextChar()); + if( style<0 ) throw Parser.error("Expected a time format: %t, %tF, %tr, %tR, %tM, %tD, or %tT",site); + flags |= style<<8; + } if( peek()!='(' ) throw Parser.error("Expected '(' after string format %"+kind,site); inc(); return new Format(kind,flags,width,precision,site); diff --git a/chapter31/src/main/java/com/seaofnodes/simple/parser/Parser.java b/chapter31/src/main/java/com/seaofnodes/simple/parser/Parser.java index 0cf3c0e7..3e7bb6b8 100644 --- a/chapter31/src/main/java/com/seaofnodes/simple/parser/Parser.java +++ b/chapter31/src/main/java/com/seaofnodes/simple/parser/Parser.java @@ -21,6 +21,7 @@ public class Parser { final Functions functions = new Functions(this); final Structs structs = new Structs(this); final Allocations allocations = new Allocations(this); + final ArrayInitializers arrays = new ArrayInitializers(this); final References references = new References(this); final Conversions conversions = new Conversions(this); final Construction construction = new Construction(this); @@ -120,6 +121,11 @@ public class Parser { public final Lexer _loc; ParseException( String msg, Lexer loc ) { super(msg); _loc = loc==null ? null : new Lexer(loc); } public String diagnostic() { return _loc==null ? getMessage() : _loc.diagnostic(getMessage()); } + // JUnit and uncaught-exception reporters use these entry points rather + // than the CLI's diagnostic(). Source errors should retain their source + // excerpt and caret there too. getMessage() stays stable for callers. + @Override public void printStackTrace(java.io.PrintStream out) { out.println(diagnostic()); } + @Override public void printStackTrace(java.io.PrintWriter out) { out.println(diagnostic()); } } public Parser(CodeGen code ) { diff --git a/chapter31/src/main/java/com/seaofnodes/simple/parser/References.java b/chapter31/src/main/java/com/seaofnodes/simple/parser/References.java index 6335bad2..f170cae2 100644 --- a/chapter31/src/main/java/com/seaofnodes/simple/parser/References.java +++ b/chapter31/src/main/java/com/seaofnodes/simple/parser/References.java @@ -120,6 +120,7 @@ final class References { return P.expressions.parsePostfixMethod(P.construction.view(rvalue),P._scope.in(P._scope.update(P._scope.var(selfx),null))); } // Assign-update direct into Scope + P.arrays.checkAssignment(var,site); if( var._final && !P._scope.canInit(var) ) throw Parser.error("Cannot reassign final '"+var._name+"'",site); checkSharedFunction(var); @@ -130,6 +131,7 @@ final class References { } void checkAssignment(Var def, Lexer site) { + if( fieldSelf(def)==null ) P.arrays.checkAssignment(def,site); String name = def._name; // A final field may receive its first value in its constructor. if( P._scope.in(def._idx)._type!=Type.TOP && def._final && diff --git a/chapter31/src/main/java/com/seaofnodes/simple/parser/Statements.java b/chapter31/src/main/java/com/seaofnodes/simple/parser/Statements.java index 707202ab..0c2a25f7 100644 --- a/chapter31/src/main/java/com/seaofnodes/simple/parser/Statements.java +++ b/chapter31/src/main/java/com/seaofnodes/simple/parser/Statements.java @@ -78,6 +78,7 @@ final class Statements { */ private Node parseReturn() { var expr = P.require(P.expressions.parseAssignment(), ";"); + if( P.arrays._frame!=null ) return P.arrays.addReturn(expr); return P.functions.addReturn(expr); } diff --git a/chapter31/src/main/java/com/seaofnodes/simple/parser/Strings.java b/chapter31/src/main/java/com/seaofnodes/simple/parser/Strings.java index 5bf76585..be913371 100644 --- a/chapter31/src/main/java/com/seaofnodes/simple/parser/Strings.java +++ b/chapter31/src/main/java/com/seaofnodes/simple/parser/Strings.java @@ -32,7 +32,7 @@ final class Strings { Node strings=P.references.member(text,"string",fmt.site()).keep(); fptr=new FormatNode(value,ints.unkeep(),floats.unkeep(),strings.unkeep(),fmt.site()).peephole(); } else { - String family="diuoxXcb".indexOf(fmt.kind())>=0 ? "integer" : fmt.kind()=='s' ? "string" : "floating"; + String family="diuoxXcb".indexOf(fmt.kind())>=0 ? "integer" : fmt.kind()=='s' ? "string" : fmt.kind()=='t' ? "time" : "floating"; fptr=P.references.member(text,family,fmt.site()); } call(text,fptr,fmt.site(),builder,value.unkeep(),P.con(fmt.kind() | fmt.flags()<<8), diff --git a/chapter31/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java b/chapter31/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java index e8218afd..bdd887e6 100644 --- a/chapter31/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java +++ b/chapter31/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java @@ -10,7 +10,7 @@ import com.seaofnodes.simple.type.*; import java.util.ArrayList; import com.seaofnodes.simple.codegen.Encoding; import com.seaofnodes.simple.util.BAOS; -import java.util.HashMap; +import java.util.Map; /** Machine-specific facts; traversal, columns and byte display live in print/. */ public final class ASMPrinter extends AssemblyAdapter<Node> { @@ -93,7 +93,7 @@ public final class ASMPrinter extends AssemblyAdapter<Node> { return data; } // Retain this diagnostic entry point for checking that pool printing never lays out types. - static int printConstantPool(int address, SB sb, BAOS bits, HashMap<Node,Encoding.Relo> relos, + static int printConstantPool(int address, SB sb, BAOS bits, Map<Node,Encoding.Relo> relos, boolean ro, String name) { sb.p(com.seaofnodes.print.ASMPrinter.pool(new Pool(name,bits.buf(),0,address,entries(relos.values(),ro)))); return address+bits.size(); diff --git a/chapter31/src/main/java/com/seaofnodes/simple/type/TypeMem.java b/chapter31/src/main/java/com/seaofnodes/simple/type/TypeMem.java index 5faa7107..1c83ddd7 100644 --- a/chapter31/src/main/java/com/seaofnodes/simple/type/TypeMem.java +++ b/chapter31/src/main/java/com/seaofnodes/simple/type/TypeMem.java @@ -211,14 +211,19 @@ public class TypeMem extends Type { + (!_one ? 0 : (_clz ? 4 : 8))); if( _alias!=1 ) { // generic alias - assert _alias <= 255; - baos.packed1( _alias ); + // Zero is reserved for alias-set flags, never a memory slice. + // Use it as an escape for large libraries, retaining the existing + // single-byte encoding (including 255) for older objects. + assert _alias>1; + baos.packed1(_alias<=255 ? _alias : 0); + if( _alias>255 ) baos.packed4(_alias); } XInt.packed(baos,_escFs); XInt.packed(baos,_escAs); } static TypeMem packed( int tag, BAOS bais ) { int alias = (tag&1)==0 ? 1 : bais.packed1(); + if( alias==0 ) alias=bais.packed4(); boolean xfinal= (tag&2)==2; boolean one = (tag&12)!=0; boolean clz = (tag&4)==4; |
