summaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/com/seaofnodes/simple/README.md2
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/Encoding.java10
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/Serialize.java12
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java6
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ArrayInitNode.java44
-rw-r--r--src/main/java/com/seaofnodes/simple/node/LoadNode.java16
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MemMergeNode.java16
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java27
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java6
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java18
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java17
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/Allocations.java15
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/ArrayInitializers.java185
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/Expressions.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/Functions.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/Lexer.java35
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/Parser.java6
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/README.md2
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/References.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/Statements.java1
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/Strings.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/package-info.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/print/ASMPrinter.java4
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeMem.java9
27 files changed, 394 insertions, 55 deletions
diff --git a/src/main/java/com/seaofnodes/simple/README.md b/src/main/java/com/seaofnodes/simple/README.md
index af86f21e..a8916aa8 100644
--- a/src/main/java/com/seaofnodes/simple/README.md
+++ b/src/main/java/com/seaofnodes/simple/README.md
@@ -2,7 +2,7 @@
| Directory | Contents |
| --- | --- |
-| `parser/` | Recursive-descent grammar routines, lexing, name resolution, and construction of the initial IR. `Var` describes a lexical binding. See the [grammar map](../../../../../../docs/chapter30/30-grammar.md). |
+| `parser/` | Recursive-descent grammar routines, lexing, name resolution, and construction of the initial IR. `Var` describes a lexical binding. See the [grammar map](../../../../../../docs/chapter31/31-grammar.md). |
| `node/` | Sea-of-Nodes IR nodes: graph edges, type computation, and local peephole rewrites. |
| `node/cpus/` | Machine-specific nodes and instruction selection support for `arm`, `riscv`, and `x86_64_v2`. |
| `codegen/` | Compilation driver and passes: module loading, optimization, scheduling, register allocation, serialization, and machine-code emission. `IterPeeps` runs the peephole worklist. |
diff --git a/src/main/java/com/seaofnodes/simple/codegen/Encoding.java b/src/main/java/com/seaofnodes/simple/codegen/Encoding.java
index b07a8fec..eb62c36a 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/Encoding.java
+++ b/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/src/main/java/com/seaofnodes/simple/codegen/Serialize.java b/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
index ae4ecb74..79bebe1f 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
@@ -417,7 +417,7 @@ public class Serialize {
assert elf._clz._name==strs[1/*skip null ptr*/];
for( Type type : types )
if( type instanceof TypeStruct ts )
- Parser.TYPES.put(ts._name,ts);
+ installType(ts);
for( Type type : types ) {
if( !(type instanceof TypeStruct ts) || !TypeStruct.startsClzPrefix(ts._name) )
continue;
@@ -429,7 +429,7 @@ public class Serialize {
elf._pubTypes = new TypeStruct[npubTypes];
for( int i=0; i<npubTypes; i++ ) {
TypeStruct ts = elf._pubTypes[i] = (TypeStruct)types[pubTypeIds[i]];
- Parser.TYPES.put(ts._name,ts);
+ installType(ts);
}
elf._functions = new FunctionSummary[bais.packed4()];
for( int i = 0; i<elf._functions.length; i++ ) {
@@ -444,6 +444,14 @@ public class Serialize {
return elf._types = new TypeRead(types,strs,fileAliases,fileFidxs,fileRpcs);
}
+ private static void installType(TypeStruct ts) {
+ // TYPES names the mutable array declaration. Read-only arrays in
+ // imported signatures keep their own types, but must not replace that
+ // declaration: closeOver looks up its mutable variant by name.
+ if( ts.isAry() ) ts = ts.replace(ts.field("[]").makeFrom(false));
+ Parser.TYPES.put(ts._name,ts);
+ }
+
static void readAll( CodeGen code, ElfReader elf, GlobalBits aliases, GlobalBits fidxs, GlobalBits rpcs ) {
BAOS bais = elf._bais;
TypeRead tread = readTypes(code, elf, aliases, fidxs, rpcs);
diff --git a/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java b/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java
index a86cd18a..418edaf6 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java
+++ b/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/src/main/java/com/seaofnodes/simple/node/ArrayInitNode.java b/src/main/java/com/seaofnodes/simple/node/ArrayInitNode.java
new file mode 100644
index 00000000..30f86ac6
--- /dev/null
+++ b/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/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/src/main/java/com/seaofnodes/simple/node/LoadNode.java
index eb78ed1f..35db4c2f 100644
--- a/src/main/java/com/seaofnodes/simple/node/LoadNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -364,15 +364,13 @@ public class LoadNode extends MemOpNode {
if( ptr instanceof PhiNode pphi && pphi.region() == r )
return true;
- // No, so can we lift this ptr?
- CFGNode cptr = ptr.cfg0();
- if( cptr != null )
- // Pointer is controlled high
- // TODO: Really needs to be the LCA of all inputs is high
- return cptr.idepth() <= r.idepth();
-
- // Dunno without a longer walk
- return false;
+ // Each copied Load needs its pointer before the Region, including
+ // a loop's entry arm. input 0 alone misses floating Loads whose own
+ // pointer was produced inside the loop.
+ CFGNode pinned = ptr.cfg0();
+ if( pinned==null || pinned.idepth()>r.idepth() ) return false;
+ CFGNode cptr = ptr instanceof PhiNode phi ? phi.region() : CFGNode.earlyCFG(ptr);
+ return cptr==null || cptr.dominates(r.idom(memphi),memphi);
}
@Override public Parser.ParseException err() {
diff --git a/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
index aa3f6d57..19420839 100644
--- a/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
+++ b/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/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java
index 10cee059..ae725659 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java
+++ b/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/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java
index fcca7f8f..b0ecd685 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java
+++ b/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/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java
index 86610f6d..4494985d 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java
+++ b/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/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java
index 1e4e7c26..1291cf78 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java
+++ b/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/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java
index 864c1b55..bcc4880b 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java
+++ b/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/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java
index 9ffa25fc..8dfd31e7 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java
+++ b/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/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
index 3d71b4ea..aed8ee4c 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
+++ b/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[]{
@@ -370,8 +382,9 @@ public class x86_64_v2 extends Machine {
private Node _lea(Node add, Node base, Node idx, int off) {
int scale = 0;
- if( base instanceof ShlNode && !(idx instanceof ShlNode) )
- throw Utils.TODO(); // Bug in canonicalization, should on RHS
+ if( base instanceof ShlNode && !(idx instanceof ShlNode) ) {
+ Node tmp = base; base = idx; idx = tmp;
+ }
if( idx instanceof ShlNode shift && shift.in(2) instanceof ConstantNode shfcon &&
shfcon._con instanceof TypeInteger tscale && 0 <= tscale.value() && tscale.value() <= 3 ) {
idx = shift.in(1);
diff --git a/src/main/java/com/seaofnodes/simple/parser/Allocations.java b/src/main/java/com/seaofnodes/simple/parser/Allocations.java
index 861f95b6..5c6b5765 100644
--- a/src/main/java/com/seaofnodes/simple/parser/Allocations.java
+++ b/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/src/main/java/com/seaofnodes/simple/parser/ArrayInitializers.java b/src/main/java/com/seaofnodes/simple/parser/ArrayInitializers.java
new file mode 100644
index 00000000..70eeae1f
--- /dev/null
+++ b/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/src/main/java/com/seaofnodes/simple/parser/Expressions.java b/src/main/java/com/seaofnodes/simple/parser/Expressions.java
index 247d974e..6486aba8 100644
--- a/src/main/java/com/seaofnodes/simple/parser/Expressions.java
+++ b/src/main/java/com/seaofnodes/simple/parser/Expressions.java
@@ -400,7 +400,7 @@ final class Expressions {
* Parse integer literal
*
* <pre>
- * integerLiteral: [1-9][0-9]* | [0]
+ * integerLiteral: [1-9][0-9]* | 0[0-7]* | 0[xX][0-9a-fA-F]+
* floatLiteral: [digits].[digits]?[e [digits]]?[f]?
* </pre>
*/
diff --git a/src/main/java/com/seaofnodes/simple/parser/Functions.java b/src/main/java/com/seaofnodes/simple/parser/Functions.java
index 5175acc6..8047fabc 100644
--- a/src/main/java/com/seaofnodes/simple/parser/Functions.java
+++ b/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/src/main/java/com/seaofnodes/simple/parser/Lexer.java b/src/main/java/com/seaofnodes/simple/parser/Lexer.java
index 5e4b7311..62dd054e 100644
--- a/src/main/java/com/seaofnodes/simple/parser/Lexer.java
+++ b/src/main/java/com/seaofnodes/simple/parser/Lexer.java
@@ -239,6 +239,7 @@ public class Lexer {
// Return a constant Type, either TypeInteger or TypeFloat
Type parseNumber() {
Lexer start=new Lexer(this);
+ if( isHex() ) return parseInteger(parseNumberString(),16,2,start);
int old = _position;
int len = isLongOrDouble();
boolean f32 = peek()=='f';
@@ -250,7 +251,7 @@ public class Lexer {
return TypeFloat.constant(Float.parseFloat(literal));
if( len > 0 ) {
if( len > 1 && _input[old]=='0' )
- throw Parser.error("Syntax error: integer values cannot start with '0'",start);
+ return parseInteger(literal,8,0,start);
long i = Long.parseLong(literal);
return TypeInteger.constant(i);
}
@@ -262,6 +263,11 @@ public class Lexer {
}
private String parseNumberString() {
int old = _position;
+ if( isHex() ) {
+ _position += 2;
+ while( isIdLetter(peek()) ) inc();
+ return new String(_input,old,_position-old);
+ }
int len = Math.abs(isLongOrDouble());
if( peek()=='f' ) {
nextChar();
@@ -270,6 +276,24 @@ public class Lexer {
return new String(_input,old,len);
}
+ private boolean isHex() {
+ return peek()=='0' && _position+1<_input.length && (peek(1)=='x' || peek(1)=='X');
+ }
+
+ private TypeInteger parseInteger(String literal, int radix, int prefix, Lexer start) {
+ String kind = radix==16 ? "Hexadecimal" : "Octal";
+ if( literal.length()==prefix )
+ throw Parser.error(kind+" integer literal requires at least one digit",start);
+ for( int i=prefix; i<literal.length(); i++ )
+ if( Character.digit(literal.charAt(i),radix)<0 )
+ throw Parser.error("Invalid "+kind.toLowerCase()+" integer literal '"+literal+"'",start);
+ try {
+ return TypeInteger.constant(Long.parseUnsignedLong(literal.substring(prefix),radix));
+ } catch( NumberFormatException ex ) {
+ throw Parser.error(kind+" integer literal exceeds 64 bits",start);
+ }
+ }
+
// Return +len that ends a long
// Return -len that ends a double
private int isLongOrDouble() {
@@ -381,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/src/main/java/com/seaofnodes/simple/parser/Parser.java b/src/main/java/com/seaofnodes/simple/parser/Parser.java
index 0cf3c0e7..3e7bb6b8 100644
--- a/src/main/java/com/seaofnodes/simple/parser/Parser.java
+++ b/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/src/main/java/com/seaofnodes/simple/parser/README.md b/src/main/java/com/seaofnodes/simple/parser/README.md
index cee1d64a..d0220761 100644
--- a/src/main/java/com/seaofnodes/simple/parser/README.md
+++ b/src/main/java/com/seaofnodes/simple/parser/README.md
@@ -2,7 +2,7 @@
The parser builds Sea-of-Nodes IR directly. Each component shares the cursor,
active scope, and compilation state through its `Parser P` reference; there is no intermediate
-AST. The [grammar map](../../../../../../../docs/chapter30/30-grammar.md) connects language
+AST. The [grammar map](../../../../../../../docs/chapter31/31-grammar.md) connects language
productions to their entry points.
| File | Responsibility |
diff --git a/src/main/java/com/seaofnodes/simple/parser/References.java b/src/main/java/com/seaofnodes/simple/parser/References.java
index 6335bad2..f170cae2 100644
--- a/src/main/java/com/seaofnodes/simple/parser/References.java
+++ b/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/src/main/java/com/seaofnodes/simple/parser/Statements.java b/src/main/java/com/seaofnodes/simple/parser/Statements.java
index 707202ab..0c2a25f7 100644
--- a/src/main/java/com/seaofnodes/simple/parser/Statements.java
+++ b/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/src/main/java/com/seaofnodes/simple/parser/Strings.java b/src/main/java/com/seaofnodes/simple/parser/Strings.java
index 5bf76585..be913371 100644
--- a/src/main/java/com/seaofnodes/simple/parser/Strings.java
+++ b/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/src/main/java/com/seaofnodes/simple/parser/package-info.java b/src/main/java/com/seaofnodes/simple/parser/package-info.java
index c886f72a..73ebec2e 100644
--- a/src/main/java/com/seaofnodes/simple/parser/package-info.java
+++ b/src/main/java/com/seaofnodes/simple/parser/package-info.java
@@ -7,7 +7,7 @@
* use-side name/field resolution; Allocations, ControlFlow, and Conversions
* contain their shared IR construction. Lexer also supplies location snapshots.
*
- * <p>See {@code chapter30/docs/30-grammar.md} for the production-to-method map.
+ * <p>See {@code chapter31/docs/31-grammar.md} for the production-to-method map.
* Evaluation order and scope lifetimes are explicit: loop updates and declared
* constructor bodies are the two deferred source fragments. There is no AST.
*/
diff --git a/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java b/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java
index e8218afd..bdd887e6 100644
--- a/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java
+++ b/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/src/main/java/com/seaofnodes/simple/type/TypeMem.java b/src/main/java/com/seaofnodes/simple/type/TypeMem.java
index 5faa7107..1c83ddd7 100644
--- a/src/main/java/com/seaofnodes/simple/type/TypeMem.java
+++ b/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;