summaryrefslogtreecommitdiff
path: root/chapter31
diff options
context:
space:
mode:
Diffstat (limited to 'chapter31')
-rw-r--r--chapter31/README.md25
-rw-r--r--chapter31/docs/31-grammar.md11
-rw-r--r--chapter31/docs/examples/Dijkstra.smp9
-rw-r--r--chapter31/docs/examples/TicTacToe.smp3
-rw-r--r--chapter31/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java6
-rw-r--r--chapter31/src/main/java/com/seaofnodes/simple/node/ArrayInitNode.java44
-rw-r--r--chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java6
-rw-r--r--chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java3
-rw-r--r--chapter31/src/main/java/com/seaofnodes/simple/parser/Allocations.java15
-rw-r--r--chapter31/src/main/java/com/seaofnodes/simple/parser/ArrayInitializers.java185
-rw-r--r--chapter31/src/main/java/com/seaofnodes/simple/parser/Functions.java3
-rw-r--r--chapter31/src/main/java/com/seaofnodes/simple/parser/Parser.java6
-rw-r--r--chapter31/src/main/java/com/seaofnodes/simple/parser/References.java2
-rw-r--r--chapter31/src/main/java/com/seaofnodes/simple/parser/Statements.java1
-rw-r--r--chapter31/src/main/smp/sys/aryu8.smp11
-rw-r--r--chapter31/src/main/smp/sys/draw/Canvas.smp90
-rw-r--r--chapter31/src/main/smp/sys/text.smp5
-rw-r--r--chapter31/src/test/java/com/seaofnodes/simple/ArrayInitTest.java175
-rw-r--r--chapter31/src/test/java/com/seaofnodes/simple/Chapter22Test.java4
-rw-r--r--chapter31/src/test/java/com/seaofnodes/simple/Chapter25Test.java2
-rw-r--r--chapter31/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java31
-rw-r--r--chapter31/src/test/java/com/seaofnodes/simple/Eval2.java18
-rw-r--r--chapter31/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java25
-rw-r--r--chapter31/src/test/java/com/seaofnodes/simple/ParserDiagnosticTest.java18
24 files changed, 589 insertions, 109 deletions
diff --git a/chapter31/README.md b/chapter31/README.md
index b82d9612..356b9cdb 100644
--- a/chapter31/README.md
+++ b/chapter31/README.md
@@ -24,6 +24,31 @@ animation and the reusable `sys.draw.Button` widget:
`make -C chapter31 run-clock CTAGS=`. Click its button or press Space to switch
between green seven-segment digits and an analog dial.
+## Array initializers
+
+Arrays can be filled with a list or an index-based initializer:
+
+```simple
+val glyphs = new u16[~]{075557,026227,061247}; // Read-only, inferred length.
+val squares = new int[10]{int i -> i*i;};
+val names = new u8[~][~2]{"Alice","Bob"}; // Read-only, non-null strings.
+```
+
+An explicit list length must be a constant equal to the number of elements.
+A generator evaluates its length once and runs in index order, once per element
+(zero times for an empty array). Its body supports statements and `return`, or
+uses its last expression as the result. It can read surrounding locals but
+cannot reassign them. Use `int`, `i64`, or `u32` for the index.
+
+`[~n]` (or `[~]` with a list) makes the array read-only; `val` alone only fixes
+the binding. Initializers can construct arrays of non-null references because
+every element is supplied before the array becomes accessible. Read-only access
+propagates to referenced elements, following the existing view rules. Initialization runs at runtime;
+read-only does not require compile-time evaluation. Canvas's font now uses a
+read-only `u16` table, and `sys.text.end` uses a generator for its byte copy.
+
+## Running the examples
+
```sh
make -C chapter31 libs CTAGS= # Once while online (also: init or lib).
make -C chapter31 run-graphical-tictactoe CTAGS=
diff --git a/chapter31/docs/31-grammar.md b/chapter31/docs/31-grammar.md
index 3083fcc2..f4ca76f1 100644
--- a/chapter31/docs/31-grammar.md
+++ b/chapter31/docs/31-grammar.md
@@ -185,7 +185,14 @@ argumentList : assignment (',' assignment)* ','? ;
// Allocation invokes the declared constructor; inline initializer blocks from
// earlier chapters are replaced by constructor arguments.
-newExpression : 'new' type ('[' assignment ']' | callSuffix)? ;
+newExpression
+ : 'new' type ('[' '~'? assignment ']' arrayInitializer? | arrayInitializer | callSuffix)?
+ ;
+// With no explicit length, type must end in [] or [~] and the initializer is a list.
+arrayInitializer
+ : '{' (assignment (',' assignment)* ','?)? '}'
+ | '{' ('int' | 'i32' | 'i64' | 'u32') IDENTIFIER '->' statement* '}'
+ ;
INTEGER_LITERAL : [1-9] DIGIT* | '0' [0-7]* | '0' [xX] HEX_DIGIT+ ;
// The f suffix also accepts an integer-shaped token, e.g. 12f.
@@ -231,7 +238,7 @@ there is no inheritance chain, AST, or additional compiler pass.
| `assignment` through `primaryExpression` | [`Expressions`](../src/main/java/com/seaofnodes/simple/parser/Expressions.java): `parseAssignment`, `parseExpression`, `parseLogical`, `parseBitwise`, `parseEquality`, `parseComparison`, `parseShift`, `parseAddition`, `parseMultiplication`, `parseUnary`, `parsePrimary` |
| `postfixExpression`, member selection and updates | `Expressions.parsePostfix` / `parsePostfixMethod` dispatch to [`References.parseField`](../src/main/java/com/seaofnodes/simple/parser/References.java); `References.parseName` handles bare names, implicit receivers, and forward references |
| `functionExpression`, `parameterList`, calls, `argumentList` | [`Functions`](../src/main/java/com/seaofnodes/simple/parser/Functions.java): `parseFunction`, `parseConstructor`, shared `parameters` and `arguments`, then `parseBody` |
-| `newExpression` | [`Allocations.parseNew`](../src/main/java/com/seaofnodes/simple/parser/Allocations.java); the allocation-helper graph builder follows the expression routines |
+| `newExpression`, `arrayInitializer` | [`Allocations.parseNew`](../src/main/java/com/seaofnodes/simple/parser/Allocations.java), [`ArrayInitializers`](../src/main/java/com/seaofnodes/simple/parser/ArrayInitializers.java); private initialization, list checks, and generator loops |
| `stringExpression`, `interpolation` | [`Strings.parse`](../src/main/java/com/seaofnodes/simple/parser/Strings.java), using `Lexer.stringPart` / `stringFormat`; normal expression parsing resumes inside each interpolation |
| Literals, identifiers, comments, token matching | [`Lexer`](../src/main/java/com/seaofnodes/simple/parser/Lexer.java); it also supplies source-location snapshots used by diagnostics and serialized IR |
diff --git a/chapter31/docs/examples/Dijkstra.smp b/chapter31/docs/examples/Dijkstra.smp
index 768bdd08..a8fcf3da 100644
--- a/chapter31/docs/examples/Dijkstra.smp
+++ b/chapter31/docs/examples/Dijkstra.smp
@@ -3,13 +3,10 @@
// https://sampleprograms.io/projects/dijkstra/
val shortestPath = { !sys.aryi64 matrix, int nodes, int src, int dst ->
- i64 infinity = (1 << 63)-1;
- var dist = new i64[nodes];
+ i64 infinity = sys.integer.MAX_I64;
var visited = new sys.adt.bitset();
- // All nodes are infinitely far from each other
- for( int i=0; i<nodes; i++ ) dist[i] = infinity;
- // Starting point is zero far from starting point
- dist[src] = 0;
+ // All nodes are infinitely far from each other, except the starting point
+ var dist = new i64[nodes]{ int i -> i==src ? 0 : infinity; };
i64 result = -1;
int count = 0;
diff --git a/chapter31/docs/examples/TicTacToe.smp b/chapter31/docs/examples/TicTacToe.smp
index a710e440..1e0f3ca0 100644
--- a/chapter31/docs/examples/TicTacToe.smp
+++ b/chapter31/docs/examples/TicTacToe.smp
@@ -3,8 +3,7 @@ val main = { u8[~][] args ->
sys.io.p("Tic-tac-toe!\nYou are X; the CPU is O. You go first.\n");
sys.io.p("Press a square's number (no Enter needed), or q/Esc to quit.\n");
sys.io.p("Squares:\n 1 | 2 | 3 \n---+---+---\n 4 | 5 | 6 \n---+---+---\n 7 | 8 | 9 \n\n");
- val board = new u8[9];
- for( int i=0; i<board#; i++ ) board[i]=' ';
+ val board = new u8[9]{ int i -> ' '; }; // Initialize the board
// Draw empty board
_draw(board);
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/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/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/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/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/smp/sys/aryu8.smp b/chapter31/src/main/smp/sys/aryu8.smp
index 88574522..76d47ec5 100644
--- a/chapter31/src/main/smp/sys/aryu8.smp
+++ b/chapter31/src/main/smp/sys/aryu8.smp
@@ -69,10 +69,7 @@ val atoi = { u8[~] str ->
// Zero-terminate a string for libc, reserving one array element for the zero.
{ u8[~] -> u8[] } str0 = { u8[~] str ->
- if( str# == sys.integer.MAX_U32 ) libc._exit(1);
- u8[] !strz = new u8[str#+1];
- u32 len=str#;
- ary.copy(strz,0,str,0,len);
- strz[str#] = '\0';
- strz;
-};
+ if( str# == sys.integer.MAX_U32 ) libc._exit(1);
+ // Copy, terminate and return
+ return new u8[str#+1]{i64 i -> i<str# ? str[i] : '\0'; };
+}; \ No newline at end of file
diff --git a/chapter31/src/main/smp/sys/draw/Canvas.smp b/chapter31/src/main/smp/sys/draw/Canvas.smp
index c736e4e2..5bd7d68e 100644
--- a/chapter31/src/main/smp/sys/draw/Canvas.smp
+++ b/chapter31/src/main/smp/sys/draw/Canvas.smp
@@ -112,79 +112,25 @@ struct Canvas {
};
};
-// An old-school 3x5 font
+// An old-school 3x5 font, ASCII 32..127. Five octal row digits need u16.
+// Lowercase uses uppercase shapes; unsupported characters use '?'.
+val _glyphs = new u16[~96]{
+ 000000,022202,055000,057575,061202,051245,061202,022000, // space..'
+ 012221,042224,005250,002720,000024,000700,000002,011244, // (../
+ 075557,026227,061247,061216,055711,074616,034757,071222, // 0..7
+ 075757,075716,002020,002024,012421,007070,042124,061202, // 8..?
+ 061202,025755,065656,034443,065556,074647,074644,034553, // @..G
+ 055755,072227,011152,055655,044447,057755,057775,025552, // H..O
+ 065644,025573,065655,034216,072222,055557,055552,055775, // P..W
+ 055255,055222,071247,032223,044211,062226,025000,000007, // X.._
+ 042000,025755,065656,034443,065556,074647,074644,034553, // `..g
+ 055755,072227,011152,055655,044447,057755,057775,025552, // h..o
+ 065644,025573,065655,034216,072222,055557,055552,055775, // p..w
+ 055255,055222,071247,032423,022222,062126,003600,061202, // x..DEL
+};
+
val _glyph = { u8 c ->
- // Only space for upper-case letters in a 3x5 font
- if( 'a' <= c <= 'z' )
- c -= 'a'-'A';
- // Five octal digits: one 3-bit row per digit, top to bottom.
- if( c==' ' ) return 000000;
- if( c=='0' ) return 075557;
- if( c=='1' ) return 026227;
- if( c=='2' ) return 061247;
- if( c=='3' ) return 061216;
- if( c=='4' ) return 055711;
- if( c=='5' ) return 074616;
- if( c=='6' ) return 034757;
- if( c=='7' ) return 071222;
- if( c=='8' ) return 075757;
- if( c=='9' ) return 075716;
- if( c=='A' ) return 025755;
- if( c=='B' ) return 065656;
- if( c=='C' ) return 034443;
- if( c=='D' ) return 065556;
- if( c=='E' ) return 074647;
- if( c=='F' ) return 074644;
- if( c=='G' ) return 034553;
- if( c=='H' ) return 055755;
- if( c=='I' ) return 072227;
- if( c=='J' ) return 011152;
- if( c=='K' ) return 055655;
- if( c=='L' ) return 044447;
- if( c=='M' ) return 057755;
- if( c=='N' ) return 057775;
- if( c=='O' ) return 025552;
- if( c=='P' ) return 065644;
- if( c=='Q' ) return 025573;
- if( c=='R' ) return 065655;
- if( c=='S' ) return 034216;
- if( c=='T' ) return 072222;
- if( c=='U' ) return 055557;
- if( c=='V' ) return 055552;
- if( c=='W' ) return 055775;
- if( c=='X' ) return 055255;
- if( c=='Y' ) return 055222;
- if( c=='Z' ) return 071247;
- if( c=='-' ) return 000700;
- if( c=='.' ) return 000002;
- if( c==':' ) return 002020;
- if( c==',' ) return 000024;
- if( c==';' ) return 002024;
- if( c=='\'' ) return 022000;
- if( c=='"' ) return 055000;
- if( c=='`' ) return 042000;
- if( c=='/' ) return 011244;
- if( c=='\\' ) return 044211;
- if( c=='(' ) return 012221;
- if( c==')' ) return 042224;
- if( c=='[' ) return 032223;
- if( c==']' ) return 062226;
- if( c=='{' ) return 032423;
- if( c=='}' ) return 062126;
- if( c=='+' ) return 002720;
- if( c=='=' ) return 007070;
- if( c=='_' ) return 000007;
- if( c=='<' ) return 012421;
- if( c=='>' ) return 042124;
- if( c=='|' ) return 022222;
- if( c=='^' ) return 025000;
- if( c=='~' ) return 003600;
- if( c=='*' ) return 005250;
- if( c=='#' ) return 057575;
- if( c=='%' ) return 051245;
- if( c=='!' ) return 022202;
- if( c=='?' ) return 061202;
- return 061202;
+ return 32<=c<128 ? _glyphs[c-32] : 061202;
};
// Pixel coverage for a round-ended segment, separate from traversal.
diff --git a/chapter31/src/main/smp/sys/text.smp b/chapter31/src/main/smp/sys/text.smp
index a28edf6d..2c4b44ff 100644
--- a/chapter31/src/main/smp/sys/text.smp
+++ b/chapter31/src/main/smp/sys/text.smp
@@ -5,10 +5,7 @@
val begin = { -> new aryu8(); };
val end = { aryu8 b ->
- val bytes = new u8[b.len];
- ary.copy(bytes,0,b.buf,0,b.len);
- u8[~] result = bytes;
- result;
+ new u8[~b.len]{ i64 i -> b.buf[i]; };
};
val _pad = { !aryu8 b, i64 n, u8 c ->
diff --git a/chapter31/src/test/java/com/seaofnodes/simple/ArrayInitTest.java b/chapter31/src/test/java/com/seaofnodes/simple/ArrayInitTest.java
new file mode 100644
index 00000000..6cba5ee3
--- /dev/null
+++ b/chapter31/src/test/java/com/seaofnodes/simple/ArrayInitTest.java
@@ -0,0 +1,175 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class ArrayInitTest {
+ private static void value(String src, long expected) {
+ CodeGen code=new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
+ assertEquals(Long.toString(expected),Eval2.eval(code,0,100000));
+ }
+
+ private static void bad(String src, String message) {
+ try {
+ new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
+ fail("Expected: "+message);
+ } catch( com.seaofnodes.simple.parser.Parser.ParseException e ) {
+ assertTrue(e.getMessage(),e.getMessage().contains(message));
+ }
+ }
+
+ @Test public void lists() {
+ value("val a=new u16[3]{025755,1,65538}; return a#*100000+a[0]+a[1]+a[2];",311248);
+ value("val a=new int[]{4,7,9,}; a[1]=2; return a#+a[0]+a[1]+a[2];",18);
+ value("val a=new int[]{}; return a#;",0);
+ bad("val a=new int[2]{1}; return a[0];","initializer has 1 elements");
+ }
+
+ @Test public void generators() {
+ value("int n=6; val a=new int[n]{int i -> i*i;}; return a[5]+a#;",31);
+ value("int base=7; val a=new int[3]{int i -> return base+i;}; return a[2];",9);
+ value("val a=new int[3]{int i -> if(i==1) return 9; i+1;}; return a[0]+a[1]+a[2];",13);
+ value("val a=new int[0]{int i -> 1/i;}; return a#;",0);
+ bad("val a=new int[]{int i -> i;}; return a#;","requires a length");
+ bad("val f={-> int x=1; val a=new int[2]{int i -> x++;}; return a[0];}; return f();","captured local");
+ }
+
+ @Test public void referencesAndReadonly() {
+ value("struct S { int x; new S={i64 v -> x=v;}; }; val a=new S[~3]{int i -> new S(i+4);}; return a[2].x;",6);
+ value("val a=new u8[~]{1,2,3}; return a[1];",2);
+ value("val names=new u8[~][~2]{\"Alice\",\"Bob\"}; return names[1][0];",66);
+ value("val a=new int[ ~ ]{4,5}; return a[1];",5);
+ value("val a=new {i64 -> i64}[]{ {i64 x -> x+1;}, {i64 x -> x*2;} }; return a[1](7);",14);
+ value("struct S { int x=1; }; val a=new !S[1]{new S}; a[0].x=8; return a[0].x;",8);
+ bad("struct S { int x=1; }; val a=new !S[~1]{new S}; a[0].x=8; return a[0].x;","final field");
+ value("struct S { int x; }; val a=new S?[~2]{u32 i -> null;}; return a[1]==null;",1);
+ bad("val a=new int[~2]{1,2}; a[0]=3; return a[0];","final field");
+ bad("struct S { int x; }; val a=new S[1]{null}; return a#;","declared type");
+ }
+
+ @Test public void nativeInitializers() throws Exception {
+ TestC.runSF("""
+ val main = { ->
+ int n=8;
+ val a=new u16[~n]{int i -> i*i+1;};
+ val b=new u8[]{257,2,3};
+ return a[7]==50 && b[0]==1 && a#==8 ? 0 : 1;
+ };
+ ""","arrayInitializers","",-1);
+ }
+
+ @Test public void nestedBodies() {
+ value("""
+ val f={ ->
+ int offset=5;
+ val a=new int[4]{int i ->
+ int sum=offset;
+ for(int j=0;j<i;j++) { if(j==1) continue; sum+=j; }
+ if(i==2) return 20;
+ return sum;
+ };
+ return a[0]+a[1]+a[2]+a[3];
+ };
+ return f();
+ """,37);
+ value("""
+ val a=new int[][3]{int i ->
+ new int[2]{int j -> i*10+j;};
+ };
+ return a[2][1]+a[0][0];
+ """,21);
+ value("""
+ val a=new int[2]{int i ->
+ val twice={i64 x -> return x*2;};
+ twice(i);
+ };
+ return a[1];
+ """,2);
+ }
+
+ @Test public void orderAndIdentity() throws Exception {
+ TestC.runSF("""
+ struct Counter {
+ int calls=0;
+ val next={->calls++;};
+ };
+ struct Item {
+ int x;
+ new Item={i64 v -> x=v;};
+ };
+ val build_noInline={u32 n, !Counter c ->
+ new Item[~n]{u32 i -> new Item(c.next());};
+ };
+ val main={->
+ val c=new Counter;
+ val empty=build_noInline(0,c);
+ if(empty#!=0 || c.calls!=0) return 1;
+ val a=build_noInline(4,c);
+ if(c.calls!=4 || a[0].x!=0 || a[3].x!=3 || a[0]==a[1]) return 2;
+ val b=new int[]{c.next(),c.next(),c.next()};
+ if(b[0]!=4 || b[2]!=6) return 3;
+ val d=new int[c.next()]{i64 i -> c.next();};
+ if(d#!=7 || d[0]!=8 || d[6]!=14 || c.calls!=15) return 4;
+ return 0;
+ };
+ ""","arrayInitOrder","",-1);
+ }
+
+ @Test public void diagnostics() {
+ bad("val f={u32 n -> new int[n]{1,2};}; return f(arg)#;","length must equal that constant");
+ bad("val a=new int[2]{u8 i -> i;}; return a[0];","generator index");
+ bad("val a=new int[2]{int return -> 0;}; return a[0];","index name");
+ bad("val a=new int[2]{int i -> i++;}; return a[0];","final 'i'");
+ bad("val f={->int x=0; return new int[2]{int i -> x=i;};}; return f()#;","captured local");
+ bad("val a=new int[2]{int i -> break;}; return a[0];","No active loop");
+ bad("val a=new int[-1]{}; return a#;","outside the u32 range");
+ bad("val a=new int[1.5]{int i -> i;}; return a#;","integer");
+ bad("val a=new int[~]{1,2}; val b=a; b[0]++; return b[0];","final field");
+ for( String init : new String[]{"self","int i -> self;"} )
+ bad("struct C { int x; new C={-> val a=new C[1]{"+init+"}; x=a#;}; }; return new C().x;",
+ "fully initialized elements");
+ }
+
+ @Test public void forwardReferencesAndLoopScopes() {
+ value("""
+ val f={ ->
+ int sum=0;
+ for(int k=0;k<3;k++) {
+ val a=new int[2]{int i ->
+ if(i) return later(i);
+ later(3);
+ };
+ if(k==1) continue;
+ sum+=a[0]+a[1];
+ }
+ return sum;
+ };
+ val later={i64 i -> i*2;};
+ return f();
+ """,16);
+ }
+
+ @Test public void optimizationOrder() {
+ String src="""
+ struct Box { int x; new Box={i64 v -> x=v;}; };
+ val f={u32 n ->
+ int base=7;
+ val a=new Box[~n]{u32 i ->
+ int x=base;
+ for(int j=0;j<i;j++) x+=j;
+ if(i==2) return new Box(20);
+ new Box(x);
+ };
+ return n==0 ? 0 : a[n-1].x;
+ };
+ return f(arg&7);
+ """;
+ for( long seed : new long[]{1,123,987654321} ) {
+ CodeGen code=new CodeGen(src,seed,true).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("0",Eval2.eval(code,0));
+ assertEquals("20",Eval2.eval(code,3));
+ assertEquals("13",Eval2.eval(code,5));
+ }
+ }
+}
diff --git a/chapter31/src/test/java/com/seaofnodes/simple/Chapter22Test.java b/chapter31/src/test/java/com/seaofnodes/simple/Chapter22Test.java
index 9bebee23..c9791de4 100644
--- a/chapter31/src/test/java/com/seaofnodes/simple/Chapter22Test.java
+++ b/chapter31/src/test/java/com/seaofnodes/simple/Chapter22Test.java
@@ -332,9 +332,7 @@ return 0;
public void testFinalArray() {
String src = """
int N=4;
-i32[] !is = new i32[N];
-for( int i=0; i<N; i++ )
- is[i] = i*i;
+i32[~] !is = new i32[~N]{ int i -> i*i; };
val _sum = { i32[~] is -> // final array
int sum=0;
for( int i=0; i<is#; i++ )
diff --git a/chapter31/src/test/java/com/seaofnodes/simple/Chapter25Test.java b/chapter31/src/test/java/com/seaofnodes/simple/Chapter25Test.java
index 6b892c65..055ef518 100644
--- a/chapter31/src/test/java/com/seaofnodes/simple/Chapter25Test.java
+++ b/chapter31/src/test/java/com/seaofnodes/simple/Chapter25Test.java
@@ -477,7 +477,7 @@ public class Chapter25Test {
// Software drawing and the optional native graphics interface.
assertArrayEquals(new String[]{"sys/adt", "sys/ary",
- "sys/aryi64", "sys/aryu8", "sys/audio", "sys/char", "sys/draw", "sys/gfx", "sys/io", "sys/libc", "sys/rt", "sys/scan", "sys/text", "sys/time"},sys_elf._deps);
+ "sys/aryi64", "sys/aryu8", "sys/audio", "sys/char", "sys/draw", "sys/gfx", "sys/integer", "sys/io", "sys/libc", "sys/rt", "sys/scan", "sys/text", "sys/time"},sys_elf._deps);
assertArrayEquals(new String[]{"sys"},sys_elf._units);
assertSame("class:sys" ,sys_elf._clz._name);
}
diff --git a/chapter31/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java b/chapter31/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java
index 5a20388b..12634f24 100644
--- a/chapter31/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java
+++ b/chapter31/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java
@@ -7,6 +7,37 @@ import org.junit.Test;
import static org.junit.Assert.*;
public class EscapeFlowTest {
+ @Test public void testArrayGeneratorDoesNotPublishUnrelatedFunctions() {
+ for( long seed : new long[]{1,123,987654321} )
+ for( String access : new String[]{"","~"} ) {
+ CodeGen code=new CodeGen("""
+ val _hidden_noInline={i64 x -> x+1;};
+ val data=new i32[%s(arg&7)]{int i -> i*i;};
+ return data#;
+ """.formatted(access),seed,true).parse();
+ int hidden=named(code,"_hidden_noInline").sig().fidx();
+ code.driver(CodeGen.Phase.TypeCheck);
+ TypeMem outside=((TypeTuple)code._start._type).mem();
+ assertFalse(XInt.bit(outside._escFs,hidden));
+ assertEquals("0",Eval2.eval(code,0));
+ assertEquals("4",Eval2.eval(code,4));
+ }
+ }
+
+ @Test public void testArrayGeneratorPublishesItsCallbacks() {
+ CodeGen code=new CodeGen("""
+ val _callback_noInline={i64 x -> x+1;};
+ val callbacks=new {i64 -> i64}[~3]{int i -> _callback_noInline;};
+ return callbacks;
+ """).parse();
+ FunNode callback=named(code,"_callback_noInline");
+ int fidx=callback.sig().fidx();
+ code.driver(CodeGen.Phase.TypeCheck);
+ assertTrue(XInt.bit(((TypeTuple)code._start._type).mem()._escFs,fidx));
+ assertFalse(callback.isDead());
+ assertTrue(callback.unknownCallers());
+ }
+
// A cofinite outside set must not revive private allocation helpers.
@Test public void testOpaqueRoundTrip() {
for( long seed : new long[]{1,123,987654321} ) {
diff --git a/chapter31/src/test/java/com/seaofnodes/simple/Eval2.java b/chapter31/src/test/java/com/seaofnodes/simple/Eval2.java
index 8d0e3d5b..b6228666 100644
--- a/chapter31/src/test/java/com/seaofnodes/simple/Eval2.java
+++ b/chapter31/src/test/java/com/seaofnodes/simple/Eval2.java
@@ -388,7 +388,7 @@ public abstract class Eval2 {
TypeMem mem = (TypeMem)con._type;
Object val = con(mem._t);
TypeMemPtr tmp = (TypeMemPtr)esc.self()._type;
- int idx = tmp._obj.findAlias(esc.fld()._alias);
+ int idx = layout(tmp._obj).findAlias(esc.fld()._alias);
Object[] fs = (Object[])val(esc.self());
if( tmp._obj.isAry() ) {
assert idx==0; // length field
@@ -443,10 +443,20 @@ public abstract class Eval2 {
private static Object[] classObject(TypeStruct type) {
Object[] ptr = CLASSES.get(type._name);
- if( ptr==null ) CLASSES.put(type._name,ptr=alloc(type));
+ if( ptr==null ) CLASSES.put(type._name,ptr=alloc(layout(type)));
return ptr;
}
+ // Imported pointer views may retain parsing-only forward-reference fields.
+ // Like native field offsets, evaluator slots use the module's final layout.
+ private static TypeStruct layout(TypeStruct type) {
+ if( TypeStruct.startsClzPrefix(type._name) ) {
+ CompUnit cu=CODE._compunits.get(type._name.substring(6).replace('.','/'));
+ if( cu!=null ) return cu._clz;
+ }
+ return type;
+ }
+
private static Object not( NotNode not ) {
Object n = val(not.in(1));
return ( n==null || (n instanceof Double D && D==0.0) || n instanceof Long L && L==0 )
@@ -465,7 +475,7 @@ public abstract class Eval2 {
TypeMemPtr tmp = (TypeMemPtr)ld.ptr()._type;
int idx = tmp._obj.isAry()
? offToIdx(x(ld.off()),tmp._obj)
- : tmp._obj.find(ld._name);
+ : layout(tmp._obj).find(ld._name);
return fs[idx];
}
@@ -476,7 +486,7 @@ public abstract class Eval2 {
Object[] fs = (Object[])val(st.ptr());
int idx = tmp._obj.isAry()
? offToIdx(x(st.off()),tmp._obj)
- : tmp._obj.find(st._name);
+ : layout(tmp._obj).find(st._name);
Object val = val(st.val());
// Idealization can remove conversions already performed by the store.
Type type = st.declaredType();
diff --git a/chapter31/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java b/chapter31/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java
index adeba136..6d81af85 100644
--- a/chapter31/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java
+++ b/chapter31/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java
@@ -8,6 +8,31 @@ import static org.junit.Assert.*;
// Execution regressions are separate from the fixed spill-measurement cohorts.
public class MemoryEncodingTest {
+ @Test public void testIntegerMemoryOperandsAcrossCalls() throws java.io.IOException {
+ TestC.runSF("""
+ val touch_noInline={i64[] a, i64 i -> a[i]+=1;};
+ val scan_noInline={i64[] a, i64 limit, i64 delta ->
+ int below=0, above=0;
+ for(int i=0;i<a#;i++) {
+ touch_noInline(a,i);
+ if(a[i]<limit) below++;
+ if(limit<a[i]) above++;
+ a[i]+=delta;
+ }
+ return below*10+above;
+ };
+ val main={->
+ val a=new i64[]{0x7ffffffffffffffe,-2,0};
+ if(scan_noInline(a,0x7fffffffffffffff,0)!=20) return 1;
+ if(a[0]!=0x7fffffffffffffff || a[1]!=-1 || a[2]!=1) return 2;
+ val b=new i64[]{-3,-2,0};
+ if(scan_noInline(b,-1,0x100000000)!=11) return 3;
+ if(b[0]!=0xfffffffe || b[1]!=0xffffffff || b[2]!=0x100000001) return 4;
+ return 0;
+ };
+ ""","integerMemoryAcrossCalls","",-1);
+ }
+
@Test public void testLoopLoadSearch() {
String[] sources = {
"""
diff --git a/chapter31/src/test/java/com/seaofnodes/simple/ParserDiagnosticTest.java b/chapter31/src/test/java/com/seaofnodes/simple/ParserDiagnosticTest.java
index ef26a943..a1253d05 100644
--- a/chapter31/src/test/java/com/seaofnodes/simple/ParserDiagnosticTest.java
+++ b/chapter31/src/test/java/com/seaofnodes/simple/ParserDiagnosticTest.java
@@ -134,6 +134,24 @@ public class ParserDiagnosticTest {
+"\n return x;\n ^",error.diagnostic());
}
+ @Test public void testJUnitReportsSourceDiagnostic() {
+ var error=rejected("val dist=new i64[3]{int i -> i;}\ndist[0]=0;");
+ String expected="Test.smp:2:1: error: Syntax error, expected `;` but found `dist`"
+ +"\n dist[0]=0;\n ^"+System.lineSeparator();
+ var test=org.junit.runner.Description.createTestDescription(getClass(),"missingSemicolon");
+ assertEquals(expected,new org.junit.runner.notification.Failure(test,error).getTrace());
+
+ var bytes=new java.io.ByteArrayOutputStream();
+ error.printStackTrace(new java.io.PrintStream(bytes,true,StandardCharsets.UTF_8));
+ assertEquals(expected,bytes.toString(StandardCharsets.UTF_8));
+ assertEquals("Syntax error, expected `;` but found `dist`",error.getMessage());
+
+ // Compiler implementation failures must still include Java frames.
+ String trace=new org.junit.runner.notification.Failure(test,new IllegalStateException("compiler bug")).getTrace();
+ assertTrue(trace,trace.contains("java.lang.IllegalStateException: compiler bug"));
+ assertTrue(trace,trace.contains("ParserDiagnosticTest.testJUnitReportsSourceDiagnostic"));
+ }
+
@Test public void testEOF() {
var error=rejected("return 1");
assertEquals("Test.smp:1:9: error: Syntax error, expected `;` but found `EOF`"