summaryrefslogtreecommitdiff
path: root/src/main/java/com
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-08-10 11:11:18 -0700
committerCliff Click <cliffc@acm.org>2026-11-14 16:23:39 -0800
commit207febef9924c8fa5391185e75d42a7ea480bb38 (patch)
tree68ec6225a89fa1178c32fdf659b9b4f2c3327288 /src/main/java/com
parent259f60b105eb770bc6f7e3bd36d91f2b217e9315 (diff)
downloadSimple-207febef9924c8fa5391185e75d42a7ea480bb38.tar.gz
Simple-207febef9924c8fa5391185e75d42a7ea480bb38.zip
Chapter 31: Software Drawing, Time, and Graphical Gameslinear
Co-authored-by: Hsiang-Ying Fu <66062782+fuhsnn@users.noreply.github.com> Co-authored-by: fuhsnn <66062782+fuhsnn@users.noreply.github.com> Co-authored-by: Symious <14933944+symious@users.noreply.github.com> Co-authored-by: Jun Kawasaki <jun784@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'src/main/java/com')
-rw-r--r--src/main/java/com/seaofnodes/simple/README.md2
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/CodeGen.java6
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java15
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/Encoding.java10
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/IterPeeps.java4
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/ParseAll.java5
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/Serialize.java16
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/StaticData.java10
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java6
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ArrayInitNode.java70
-rw-r--r--src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java24
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CallEndNode.java4
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CallNode.java12
-rw-r--r--src/main/java/com/seaofnodes/simple/node/EscapeNode.java6
-rw-r--r--src/main/java/com/seaofnodes/simple/node/Intrinsic.java119
-rw-r--r--src/main/java/com/seaofnodes/simple/node/LoadNode.java26
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MemMergeNode.java16
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MemOpNode.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/node/Node.java4
-rw-r--r--src/main/java/com/seaofnodes/simple/node/StoreNode.java4
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/BitCastARM.java23
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java1
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/BitCastRISC.java23
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java1
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/BitCastX86.java23
-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/NegFX86.java25
-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.java23
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/Allocations.java17
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/ArrayInitializers.java187
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/Conversions.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/Declarations.java4
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/Expressions.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/Functions.java64
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/Lexer.java35
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/MethodAccess.java130
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/Parser.java12
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/README.md15
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/References.java13
-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/Types.java11
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/Var.java5
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/package-info.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/print/ASMPrinter.java15
-rw-r--r--src/main/java/com/seaofnodes/simple/type/Type.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeConAry.java147
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeConAryB.java20
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeConAryI.java16
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeFloat.java8
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeMem.java9
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeStruct.java11
59 files changed, 1097 insertions, 176 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/CodeGen.java b/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
index b4c93e1e..8618b47b 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
@@ -786,7 +786,11 @@ public class CodeGen {
public boolean _asmLittle=true;
public String asm() { return asm(new SB()).toString(); }
SB asm(SB sb) {
- ASMPrinter.print(sb,this);
+ if( _encodings.isEmpty() ) ASMPrinter.print(sb,this);
+ else for( var entry : _encodings.entrySet() ) {
+ sb.p("\n=== Compilation unit: ").p(entry.getKey()._cname).p(" ===\n");
+ ASMPrinter.print(sb,this,entry.getValue());
+ }
return sb;
}
diff --git a/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java b/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java
index fcf5c287..c5b25be0 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java
@@ -318,11 +318,15 @@ public class ElfWriter {
_strtab = new StringSection();
// place all the symbols
+ Encoding enc = _code._encoding;
int nlocals = 1/*null*/+9/*number of sections*/+1/*Simple section*/;
+ int debugLocals=0;
+ for( StaticData.ObjectData obj : enc._data.objects.values() )
+ if( !obj.external ) debugLocals+=obj.debugNames.size();
+ nlocals+=debugLocals;
SymbolSection symbols = new SymbolSection(nlocals);
// we've already constructed these entire sections in the encoding phase
- Encoding enc = _code._encoding;
// Append ELF-only thunks to a copy. Export must not change instruction
// offsets or the image used by the in-memory linker and emulators.
BAOS textBits = new BAOS(enc._bits.toByteArray(),enc._bits.size());
@@ -402,10 +406,17 @@ public class ElfWriter {
SimpleSection simple = new SimpleSection(_code._serial);
// Write the local section header symbols
- assert nlocals == 1+_sections._len;
+ assert nlocals == 1+_sections._len+debugLocals;
symbols.symbol("",0,0,0); // Null symbol at index 0
for( Section s : _sections )
symbols.symbol(s._name, s._index, SYM_BIND_LOCAL, SYM_TYPE_SECTION, 0, s.size());
+ // Local aliases describe the pooled object without changing linkage.
+ // Several source constants may name the same bytes.
+ for( StaticData.ObjectData obj : enc._data.objects.values() )
+ if( !obj.external )
+ for( String name : obj.debugNames )
+ symbols.symbol(name,obj.readOnly ? rodata._index : rwdata._index,
+ SYM_BIND_LOCAL,SYM_TYPE_OBJECT,obj.offset,obj.size);
int size = 64; // ELF header size
// size of all section data
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/IterPeeps.java b/src/main/java/com/seaofnodes/simple/codegen/IterPeeps.java
index 985cd4d2..f4acb729 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/IterPeeps.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/IterPeeps.java
@@ -65,8 +65,10 @@ public class IterPeeps {
* Iterate peepholes and inlining to a fixed point
*/
public void iterate( CodeGen code ) {
+ assert !CodeGen.expensiveAssert(0) || (progressOnList(code, _work) && schedulableUses(code));
boolean didInline = false;
Ary<Node> defer = new Ary<>(Node.class);
+
while( true ) {
// Clean up everything that does not grow the code
iteratePeeps(code);
@@ -97,8 +99,6 @@ public class IterPeeps {
// Run all the code-reduction and type-lifting peeps as possible
private void iteratePeeps( CodeGen code ) {
- assert !CodeGen.expensiveAssert(0) || (progressOnList(code, _work) && schedulableUses(code));
-
Node n;
while( (n = _work.pop()) != null ) {
if( n.isDead() ) continue;
diff --git a/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java b/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java
index 3306ff8f..882a0f8a 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java
@@ -150,7 +150,8 @@ public abstract class ParseAll {
}
private static void addRequiredTypes(CodeGen code, CompUnit cunit) {
- for( String typeName : code.P._requiredTypes ) {
+ for( var required : code.P._requiredTypes.entrySet() ) {
+ String typeName = required.getKey();
// A forward use can have been resolved later in this source file.
if( Parser.TYPES.get(typeName) instanceof TypeStruct ts && !ts._fref ) continue;
try {
@@ -158,7 +159,7 @@ public abstract class ParseAll {
if( xcunit != null )
cunit.addDep(xcunit);
else
- assert false : "expected to find required type";
+ throw Parser.error("Unknown type '"+typeName+"'",required.getValue());
} catch( IOException ioe ) {
throw new RuntimeException(ioe);
}
diff --git a/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java b/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java
index 3991cd48..1b335c50 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java
@@ -215,7 +215,7 @@ public class RegAlloc {
_deferredLoopSelf.clear();
for( byte round=0; !graphColor(round); round++ ) {
split(round);
- if( round >= 7 ) {
+ if( round >= 10 ) {
FunNode fun = _cfg.at(0).fun();
throw new IllegalStateException("Register allocation failed after eight rounds in "+
(fun==null ? "<root>" : fun.label()));
diff --git a/src/main/java/com/seaofnodes/simple/codegen/Serialize.java b/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
index ae4ecb74..b168c4af 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,18 @@ 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.isConAry() ) {
+ TypeConAry ary=(TypeConAry)ts.field("[]")._t;
+ ts=TypeStruct.makeAry(ts._name,TypeInteger.U32,ts.field("#")._alias,
+ ary.storageType(),ts.field("[]")._alias,false);
+ } else 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/StaticData.java b/src/main/java/com/seaofnodes/simple/codegen/StaticData.java
index 5d1a519b..3d3a40dd 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/StaticData.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/StaticData.java
@@ -14,7 +14,10 @@ public final class StaticData {
public final String symbol;
public final boolean readOnly, external;
public final int alignment, size;
+ // Source names are aliases, not part of the object's pooling identity.
+ final TreeSet<String> debugNames = new TreeSet<>();
public int offset;
+ public String label() { return debugNames.isEmpty() ? symbol : String.join(", ",debugNames); }
ObjectData(Type t, String name, boolean ro, boolean ext) {
type=t; symbol=name; readOnly=ro; external=ext;
alignment=t.alignment();
@@ -73,7 +76,12 @@ public final class StaticData {
// Register before following fields: class objects may refer to each other.
if( !obj.external && t instanceof TypeStruct ts )
for( Field f : ts._fields )
- if( !f._extern && f._t.isConstant() ) target(f._t);
+ if( !f._extern && f._t.isConstant() ) {
+ target(f._t);
+ if( clz && f._final && f._t instanceof TypeMemPtr ptr &&
+ pointerConstant(ptr) && !isClass(ptr._obj) )
+ object(ptr._obj).debugNames.add(ts._name.substring(TypeStruct.CLZ.length())+"."+f._fname);
+ }
return obj;
}
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..c6ae90ea
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/ArrayInitNode.java
@@ -0,0 +1,70 @@
+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() {
+ if( in(0)._type != Type.CONTROL ) return Type.TOP;
+ Type ptr = in(2)._type;
+ if( _count < 0 ) return ptr;
+ // A list is strict in all its inputs. Wait for every input before
+ // choosing a constant payload or falling to the ordinary array type.
+ // Otherwise one SCCP worklist order can return PtrTop (or an array)
+ // before another unresolved input sends the result back up to Top.
+ if( ptr.isHigh() || in(3)._type.isHigh() ) return Type.TOP;
+ for( int i = 4; i < nIns(); i++ )
+ if( in(i)._type.isHigh() ) return Type.TOP;
+ if( !(ptr instanceof TypeMemPtr tmp) ) return ptr;
+ Field body = tmp._obj.field("[]");
+ if( body == null || !body._final || !(body._t instanceof TypeInteger storage) ) return ptr;
+ if( !(in(3)._type instanceof TypeInteger length) || !length.isConstant() || length.value() != _count ) return ptr;
+ long[] values = new long[_count];
+ for( int i = 0; i < _count; i++ ) {
+ Type value = in(i+4)._type;
+ if( !(value instanceof TypeInteger integer) || !integer.isConstant() ) return ptr;
+ values[i] = integer.value();
+ }
+ TypeConAry contents = TypeConAry.make(storage,values);
+ TypeStruct array = TypeStruct.makeAry(tmp._obj._name,tmp._obj.field("#")._alias,body._alias,contents);
+ // The surrounding ReadOnlyNode supplies the public view. Keep this
+ // node's access flags consistent when its contents cease to be constant.
+ return TypeMemPtr.make((byte)2,array,true).withAccess(tmp._ro);
+ }
+ @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/BulkMemPhiNode.java b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
index 130b631c..b49163e7 100644
--- a/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
@@ -261,15 +261,35 @@ public class BulkMemPhiNode extends PhiNode {
mphi = new MemPhiNode(ScopeNode.memName(alias),alias);
mphi.addDef(region());
// Due to cycles, must set before calling peephole
- // Splitting preserves the bulk Phi's established escape bound. Starting
- // at BOT would temporarily invent escapes when the new slice is merged.
+ // An escaped slice must preserve the bulk Phi's established bound.
+ // An unescaped slice was excluded from that summary: include the
+ // facts hidden in predecessor slices before wiring any cyclic Phis.
TypeMem old = (TypeMem)_type;
+ if( !XInt.bit(old._escAs,alias) )
+ old = (TypeMem)old.meet(sliceBound(this,alias,new BitSet()));
mphi.setType(TypeMem.make(alias,old._t,old._one,old._clz,false,old._escFs,old._escAs));
for( int i=1; i<nIns(); i++ )
mphi.addDef(CodeGen.CODE.add(preciseInput(in(i),alias)));
return CodeGen.CODE.add(mphi);
}
+ // A bulk summary ignores unescaped slots in MemMerge inputs. Follow the
+ // same slots that splitting will expose, including across loop backedges.
+ // A visited cycle contributes TOP; its incoming definitions supply the bound.
+ private static TypeMem sliceBound(Node mem, int alias, BitSet visit) {
+ if( visit.get(mem._nid) ) return TypeMem.TOP;
+ visit.set(mem._nid);
+ if( mem instanceof MemMergeNode merge )
+ return sliceBound(merge.alias(alias),alias,visit);
+ if( mem instanceof BulkMemPhiNode bulk ) {
+ TypeMem bound = TypeMem.TOP;
+ for( int i = 1; i < bulk.nIns(); i++ )
+ bound = (TypeMem)bound.meet(sliceBound(bulk.in(i),alias,visit));
+ return bound;
+ }
+ return mem._type instanceof TypeMem bound ? bound : TypeMem.BOT;
+ }
+
MemPhiNode _findPhi(int alias) {
for( Node use : region().outs() )
if( use instanceof MemPhiNode mphi && mphi._alias==alias )
diff --git a/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
index b5156c2d..108dd024 100644
--- a/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
@@ -85,6 +85,8 @@ public class CallEndNode extends CFGNode implements MultiNode {
return ftype.isHigh() ? TypeTuple.STATE.dual() : TypeTuple.STATE;
// Mid-fold, just take the inlined callee's return.
if( _folding ) return in(1)._type;
+ TypeTuple intrinsic=Intrinsic.compute(call,this);
+ if( intrinsic!=null ) return intrinsic;
// The outside continuation reads Start directly: Call can remain
// CONTROL while its possible returned identities change.
@@ -135,6 +137,8 @@ public class CallEndNode extends CFGNode implements MultiNode {
}
public byte maybeInline() {
+ byte intrinsic=Intrinsic.tryInline(this);
+ if( intrinsic>0 ) return intrinsic;
byte inline = inlineCandidate(false);
if( CodeGen.CODE._midAssert )
return inline;
diff --git a/src/main/java/com/seaofnodes/simple/node/CallNode.java b/src/main/java/com/seaofnodes/simple/node/CallNode.java
index ac5e4eee..9e9c5224 100644
--- a/src/main/java/com/seaofnodes/simple/node/CallNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/CallNode.java
@@ -97,6 +97,7 @@ public class CallNode extends CFGNode {
if( _type!=Type.CONTROL ) return TypeMem.BOT;
if( !(fptr()._type instanceof TypeFunPtr) )
return fptr()._type.isHigh() ? TypeMem.START : TypeMem.BOT;
+ if( Intrinsic.pure(this) ) return TypeMem.START;
if( !external() ) return TypeMem.START;
Type input = dep.addDepForwards(mem())._type;
TypeMem escaped = input instanceof TypeMem m ? m : input.isHigh() ? TypeMem.TOP : TypeMem.BOT;
@@ -241,6 +242,8 @@ public class CallNode extends CFGNode {
return Parser.error("Expected a function, but found " + fptr()._type, _loc);
if( !tfp.notNull() )
return Parser.error( "Might be null calling "+tfp, _loc);
+ Parser.ParseException intrinsic=Intrinsic.err(this);
+ if( intrinsic!=null ) return intrinsic;
if( nargs() != tfp.nargs() )
return Parser.error( "Expecting "+(tfp.nargs()-1)+" arguments, but found "+(nargs()-1), _loc);
@@ -253,6 +256,15 @@ public class CallNode extends CFGNode {
// Constructors return private memory and carry hidden self and
// selfMem arguments ahead of the source-level arguments.
int argno = i - (tfp.ret() instanceof TypeMem ? 2 : 0);
+ // Access qualifiers are not shown by the structural type dump.
+ // Report the differing permission instead of two huge shapes.
+ if( arg(i+2)._type instanceof TypeMemPtr actual && actual._ro &&
+ tfp.arg(i) instanceof TypeMemPtr formal && !formal._ro ) {
+ String who = i == 0 ? name() == null ? "Receiver" : "Receiver for method '"+name()+"'"
+ : "Argument #"+argno;
+ return Parser.error(who+" is read-only "+actual._obj.str()+
+ "; expected writable "+formal._obj.str(),_loc);
+ }
return Parser.error( "Argument #" + argno + " isa " + arg(i+2)._type + ", but must be a " + tfp.arg(i), _loc );
}
diff --git a/src/main/java/com/seaofnodes/simple/node/EscapeNode.java b/src/main/java/com/seaofnodes/simple/node/EscapeNode.java
index 57340609..a8a6151c 100644
--- a/src/main/java/com/seaofnodes/simple/node/EscapeNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/EscapeNode.java
@@ -64,6 +64,10 @@ public class EscapeNode extends TypeNode {
@Override
public Type compute() {
+ // A static constant array has no private heap writes to publish.
+ // This depends on its type, regardless of which node produced it.
+ if( self()!=null && self()._type instanceof TypeMemPtr ptr &&
+ ptr.isConstant() && ptr._obj.isConAry() ) return pub()._type;
if( priv()._type.isHigh() ) return TypeMem.TOP;
if( pub ()._type.isHigh() ) return TypeMem.TOP;
TypeMem mpriv = (TypeMem)priv()._type;
@@ -100,6 +104,8 @@ public class EscapeNode extends TypeNode {
}
@Override public Node idealize() {
+ if( self()._type instanceof TypeMemPtr ptr && ptr.isConstant() && ptr._obj.isConAry() )
+ return pub();
// Sharpen private memory input
if( priv() instanceof MemMergeNode mmm ) {
setDef(2,mmm.alias(fld()._alias));
diff --git a/src/main/java/com/seaofnodes/simple/node/Intrinsic.java b/src/main/java/com/seaofnodes/simple/node/Intrinsic.java
new file mode 100644
index 00000000..82626507
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/Intrinsic.java
@@ -0,0 +1,119 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.type.*;
+
+// Fixed-semantics C functions. Recognition, effects and expansion live here;
+// Call/CallEnd only delegate. These symbols also have ordinary C definitions
+// for calls whose target is not known at compile time.
+public final class Intrinsic {
+ private Intrinsic() {}
+
+ private enum Op {
+ F64_BITS("simple_f64_bits",TypeFloat.F64,TypeInteger.BOT,true),
+ F64_FROM_BITS("simple_f64_from_bits",TypeInteger.BOT,TypeFloat.F64,true);
+
+ final String symbol;
+ final Type arg, ret;
+ final boolean pure;
+ Op(String symbol, Type arg, Type ret, boolean pure) {
+ this.symbol=symbol; this.arg=arg; this.ret=ret; this.pure=pure;
+ }
+ boolean signature(TypeFunPtr fp) {
+ // Slot zero is the implicit receiver, omitted by the C ABI.
+ return fp.nargs()==2 && fp.arg(1)==arg && fp.ret()==ret;
+ }
+ Expansion expand(CallNode call) {
+ Node value=switch(this) {
+ case F64_BITS -> new F64BitsNode(call.arg(3)).peephole();
+ case F64_FROM_BITS -> new F64FromBitsNode(call.arg(3)).peephole();
+ };
+ return new Expansion(call.ctrl(),call.mem(),value);
+ }
+ }
+
+ private static Op lookup(CallNode call) {
+ if( !(call.fptr()._type instanceof TypeFunPtr fp) || !fp.notNull() || !fp.isConstant() ) return null;
+ String symbol=CodeGen.CODE.externFunc(fp.fidx());
+ if( symbol==null ) return null; // A Simple function with this name is ordinary code.
+ for( Op op : Op.values() ) if( op.symbol.equals(symbol) ) return op;
+ return null;
+ }
+
+ public static Parser.ParseException err(CallNode call) {
+ Op op=lookup(call);
+ return op==null || op.signature(call.tfp()) ? null : Parser.error(
+ "Intrinsic '"+op.symbol+"' requires { "+op.arg.str()+" -> "+op.ret.str()+" }",call._loc);
+ }
+
+ // Both initial operations are pure and expose no references. Future atomic
+ // operations need their own memory/escape semantics, not this passthrough.
+ public static boolean pure(CallNode call) {
+ Op op=lookup(call);
+ return op!=null && op.pure && op.signature(call.tfp());
+ }
+
+ public static TypeTuple compute(CallNode call, Node dep) {
+ if( !pure(call) ) return null;
+ return TypeTuple.make(Type.CONTROL,dep.addDep(call.mem())._type,call.tfp().ret());
+ }
+
+ // An expansion replaces the complete call state, even for a pure value op.
+ private record Expansion(Node control, Node memory, Node value) {
+ void replace(CallEndNode end) {
+ Node[] state={control,memory,value};
+ for( Node n : state ) n.keep();
+ end.keep();
+ for( Node use : end._outputs.asAry() ) {
+ if( use==null ) continue; // Our temporary keep.
+ assert use instanceof Proj;
+ use.subsume(state[((Proj)use).idx()]);
+ }
+ end.unkill();
+ for( Node n : state ) n.unkill();
+ CodeGen.CODE.invalidateIDepthCaches();
+ }
+ }
+
+ // Same cheap-inlining worklist as ordinary functions, but no body/Return,
+ // cloning, size heuristic, or import loading is needed.
+ public static byte tryInline(CallEndNode end) {
+ if( end.folding() || !(end.in(0) instanceof CallNode call) ) return 0;
+ Op op=lookup(call);
+ if( op==null || !op.signature(call.tfp()) || call.nargs()!=call.tfp().nargs() ||
+ !end.goodArgs(call,call.tfp()) ) return 0;
+ if( !CodeGen.CODE._midAssert ) op.expand(call).replace(end);
+ return 1;
+ }
+
+ public abstract static class BitCastNode extends Node {
+ BitCastNode(Node value) { super(null,value); }
+ public abstract boolean toBits();
+ @Override public Type compute() {
+ Type input=in(1)._type;
+ Type result=toBits() ? TypeInteger.BOT : TypeFloat.F64;
+ if( input.isHigh() ) return result.dual();
+ if( toBits() && input instanceof TypeFloat f && f.isConstant() )
+ return TypeInteger.constant(Double.doubleToRawLongBits(f.value()));
+ if( !toBits() && input instanceof TypeInteger i && i.isConstant() )
+ return TypeFloat.constant(Double.longBitsToDouble(i.value()));
+ return result;
+ }
+ @Override public Node idealize() {
+ return in(1) instanceof BitCastNode cast && cast.toBits()!=toBits() ? cast.in(1) : null;
+ }
+ }
+ public static final class F64BitsNode extends BitCastNode {
+ public F64BitsNode(Node value) { super(value); }
+ @Override public boolean toBits() { return true; }
+ @Override public Tag serialTag() { return Tag.F64Bits; }
+ @Override protected String format() { return "f64Bits(%1)"; }
+ }
+ public static final class F64FromBitsNode extends BitCastNode {
+ public F64FromBitsNode(Node value) { super(value); }
+ @Override public boolean toBits() { return false; }
+ @Override public Tag serialTag() { return Tag.F64FromBits; }
+ @Override protected String format() { return "f64FromBits(%1)"; }
+ }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/src/main/java/com/seaofnodes/simple/node/LoadNode.java
index eb78ed1f..14efa315 100644
--- a/src/main/java/com/seaofnodes/simple/node/LoadNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -98,7 +98,15 @@ public class LoadNode extends MemOpNode {
t = mem._alias==_alias ? mem._t : actual==null ? TypeScalar.BOT : actual._t;
} else if( mem._alias==_alias )
t = t.join(mem._t);
- if( !mem._one ) t = mem.outside(t);
+ // A final method's identity comes from its declaration, not from an
+ // unknown outside writer. It remains callable on a local object even
+ // when that function has never escaped to outside code.
+ if( !mem._one ) {
+ Type declared = Parser.TYPES.get(tmp._obj._name);
+ Field field = declared instanceof TypeStruct obj ? obj.field(_name) : null;
+ if( !(field != null && field._final && field._t instanceof TypeFunPtr fun && fun.isConstant()) )
+ t = mem.outside(t);
+ }
// Store forwarding is also a value fact during SCCP. Waiting for the
// later rewrite can turn a known non-null stored pointer into a
// nullable declaration, then BOTTOM at the next dereference.
@@ -364,15 +372,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/MemOpNode.java b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
index 35183380..bce11b50 100644
--- a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
@@ -94,7 +94,8 @@ public abstract class MemOpNode extends TypeNode {
if( !(ptr()._type instanceof TypeMemPtr tmp) )
return Type.BOTTOM;
Field fld = tmp._obj.field(_name);
- return fld == null || fld._t == null ? Type.BOTTOM : fld._t;
+ return fld == null || fld._t == null ? Type.BOTTOM :
+ fld._t instanceof TypeConAry ary ? ary.storageType() : fld._t;
}
@Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return _printMach(p); }
diff --git a/src/main/java/com/seaofnodes/simple/node/Node.java b/src/main/java/com/seaofnodes/simple/node/Node.java
index 2815dfba..d4c0794c 100644
--- a/src/main/java/com/seaofnodes/simple/node/Node.java
+++ b/src/main/java/com/seaofnodes/simple/node/Node.java
@@ -77,7 +77,7 @@ public abstract class Node extends BaseNode<Node> implements Cloneable {
New,Never,Not,Or,Parm,Phi,Proj,PtrToInt,
ReadOnly,Return,Region,RoundF32,
Sar,Shl,Shr,Start,StartCU,StopCU,Stop,Store,Sub,
- ToInteger,ToFloat,XCtrl,Xor;
+ ToInteger,ToFloat,XCtrl,Xor,F64Bits,F64FromBits;
public static final Tag[] VALS = values();
public Node make( BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases ) {
return switch(this) {
@@ -103,6 +103,8 @@ public abstract class Node extends BaseNode<Node> implements Cloneable {
case Sub -> new SubNode(null,null,(byte)bais.packed1());
case ToFloat-> new ToFloatNode(null);
case ToInteger-> new ToIntegerNode(null);
+ case F64Bits-> new Intrinsic.F64BitsNode(null);
+ case F64FromBits-> new Intrinsic.F64FromBitsNode(null);
case XCtrl -> new XCtrlNode();
case Xor -> { int mode=bais.packed1(); assert mode==1; yield new XorNode(null,null,null); }
diff --git a/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/src/main/java/com/seaofnodes/simple/node/StoreNode.java
index 010d5f61..204e0c4b 100644
--- a/src/main/java/com/seaofnodes/simple/node/StoreNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/StoreNode.java
@@ -243,6 +243,10 @@ public class StoreNode extends MemOpNode {
private byte storageType() {
if( !(ptr()._type instanceof TypeMemPtr tmp) )
return 0;
+ // Completing a forward declaration changes TYPES without changing
+ // this Store's inputs. Wait for the pointer's type upgrade, which
+ // queues its users, before consulting the completed declaration.
+ if( tmp._obj._fref ) return 0;
TypeStruct obj = (TypeStruct)Parser.TYPES.get(tmp._obj._name);
Field fld = obj.field(_name);
if( fld == null || fld._t == null )
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/BitCastARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/BitCastARM.java
new file mode 100644
index 00000000..ec57b3f8
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/BitCastARM.java
@@ -0,0 +1,23 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// A representation move, with no arithmetic, rounding, or flag changes.
+public class BitCastARM extends MachConcreteNode implements MachNode {
+ private final boolean _toBits;
+ BitCastARM(Intrinsic.BitCastNode cast) { super(cast); _toBits=cast.toBits(); }
+ @Override public String op() { return _toBits ? "f64bits" : "f64frombits"; }
+ @Override public RegMask regmap(int i) { assert i==1; return _toBits ? arm.DMASK : arm.RMASK; }
+ @Override public RegMask outregmap() { return _toBits ? arm.WMASK : arm.DMASK; }
+ @Override public void encoding(Encoding enc) {
+ enc.add4(Arm64.f_mov_general(Arm64.OP_FMOV,0b01,0,_toBits ? 0b110 : 0b111,
+ enc.reg(in(1))-(_toBits ? arm.D_OFFSET : 0),
+ enc.reg(this)-(_toBits ? 0 : arm.D_OFFSET)));
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = bits ").p(code.reg(in(1)));
+ }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
index ee7b0fc3..2d47edf5 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
@@ -350,6 +350,7 @@ public class arm extends Machine {
case StoreNode st -> st(st);
case SubNode sub -> sub.mode()==2 ? new SubFARM(sub) : sub(sub);
case ToFloatNode tfn -> new I2F8ARM(tfn);
+ case Intrinsic.BitCastNode cast -> new BitCastARM(cast);
case ToIntegerNode cvt -> new F8ToIARM(cvt);
case XorNode xor -> xor(xor);
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BitCastRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BitCastRISC.java
new file mode 100644
index 00000000..141ff7d5
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BitCastRISC.java
@@ -0,0 +1,23 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// A representation move, with no arithmetic, rounding, or flag changes.
+public class BitCastRISC extends MachConcreteNode implements MachNode {
+ private final boolean _toBits;
+ BitCastRISC(Intrinsic.BitCastNode cast) { super(cast); _toBits=cast.toBits(); }
+ @Override public String op() { return _toBits ? "f64bits" : "f64frombits"; }
+ @Override public RegMask regmap(int i) { assert i==1; return _toBits ? riscv.FMASK : riscv.RMASK; }
+ @Override public RegMask outregmap() { return _toBits ? riscv.WMASK : riscv.FMASK; }
+ @Override public void encoding(Encoding enc) {
+ enc.add4(RiscV.r_type(RiscV.OP_FP,
+ enc.reg(this)-(_toBits ? 0 : riscv.F_OFFSET),0,
+ enc.reg(in(1))-(_toBits ? riscv.F_OFFSET : 0),0,_toBits ? 0b1110001 : 0b1111001));
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = bits ").p(code.reg(in(1)));
+ }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java
index d455337b..de754393 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java
@@ -47,7 +47,7 @@ public class SplitRISC extends SplitNode {
// FPR->FPR
src -= riscv.F_OFFSET;
dst -= riscv.F_OFFSET;
- enc.add4(RiscV.r_type(RiscV.OP_FP, dst, 0b000, src, src, 0b0010101)); // dst = FMIN(src,src)
+ enc.add4(RiscV.r_type(RiscV.OP_FP, dst, 0b000, src, src, 0b0010001)); // FSGNJ.D preserves NaN bits.
} else if(!srcX && dstX) {
//GPR->FPR
// fmv.d.x
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
index 378955a5..976d1a52 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
@@ -254,6 +254,7 @@ public class riscv extends Machine {
case StoreNode st -> st(st);
case SubNode sub -> sub.mode()==2 ? new SubFRISC(sub) : sub(sub);
case ToFloatNode tfn -> i2f8(tfn);
+ case Intrinsic.BitCastNode cast -> new BitCastRISC(cast);
case ToIntegerNode cvt -> new F8ToIRISC(cvt);
case XorNode xor -> xor(xor);
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/BitCastX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/BitCastX86.java
new file mode 100644
index 00000000..bf550032
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/BitCastX86.java
@@ -0,0 +1,23 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// A representation move, with no arithmetic, rounding, or flag changes.
+public class BitCastX86 extends MachConcreteNode implements MachNode {
+ private final boolean _toBits;
+ BitCastX86(Intrinsic.BitCastNode cast) { super(cast); _toBits=cast.toBits(); }
+ @Override public String op() { return _toBits ? "f64bits" : "f64frombits"; }
+ @Override public RegMask regmap(int i) { assert i==1; return _toBits ? x86_64_v2.XMASK : x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return _toBits ? x86_64_v2.WMASK : x86_64_v2.XMASK; }
+ @Override public void encoding(Encoding enc) {
+ X86.move(enc,!_toBits,_toBits,
+ enc.reg(this)-(_toBits ? 0 : x86_64_v2.XMM_OFFSET),
+ enc.reg(in(1))-(_toBits ? x86_64_v2.XMM_OFFSET : 0));
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = bits ").p(code.reg(in(1)));
+ }
+}
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/NegFX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegFX86.java
new file mode 100644
index 00000000..61662fd2
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegFX86.java
@@ -0,0 +1,25 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeFloat;
+import com.seaofnodes.simple.util.SB;
+
+// Flip the sign bit, preserving signed zero, infinities, and NaN payloads.
+public class NegFX86 extends MachConcreteNode implements MachNode {
+ NegFX86(MinusNode neg) {
+ super(neg);
+ addDef(new FltX86(new ConstantNode(TypeFloat.constant(-0.0)),null));
+ }
+ @Override public String op() { return "negf"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.XMASK; }
+ @Override public int twoAddress() { return 1; }
+ @Override public void encoding(Encoding enc) {
+ X86.sse(enc,0x66,0x0F57,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET);
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" ^= ").p(code.reg(in(2))).p(" // sign bit");
+ }
+}
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..dc15b185 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[]{
@@ -282,6 +294,7 @@ public class x86_64_v2 extends Machine {
public Node instSelect(Node n) {
return switch (n) {
case AddNode add -> add(add);
+ case Intrinsic.BitCastNode cast -> new BitCastX86(cast);
case AndNode and -> and(and);
case BoolNode bool -> cmp(bool);
case CLInitNode init -> new CLInitNode(init);
@@ -298,10 +311,7 @@ public class x86_64_v2 extends Machine {
case IfNode iff -> jmp(iff);
case LoadNode ld -> ld(ld);
case MemMergeNode mem -> new MemMergeNode(mem);
- case MinusNode neg -> {
- if( neg.mode()==2 ) throw Utils.TODO();
- yield new NegX86(neg);
- }
+ case MinusNode neg -> neg.mode()==2 ? new NegFX86(neg) : new NegX86(neg);
case MulNode mul -> mul.mode()==2 ? new MulFX86(mul) : mul(mul);
case NewNode nnn -> new NewX86(nnn);
case NotNode not -> new NotX86(not);
@@ -370,8 +380,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..64234045 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));
@@ -117,7 +124,7 @@ final class Allocations {
TypeStruct ts = P.types.typeAry(TypeInteger.U8,true);
int lenAlias = ts.field("#" )._alias;
int elemAlias = ts.field("[]")._alias;
- TypeConAryB body = TypeConAryB.make(s);
+ TypeConAry body = TypeConAry.make(s);
TypeInteger slen = TypeInteger.constant(s.length);
// Make a TMP, not-null (byte)2, singleton (true) (requires text
// strings are hash-interned), with a TypeStruct having a constant
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..079853cd
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/parser/ArrayInitializers.java
@@ -0,0 +1,187 @@
+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 result=new ArrayInitNode(P.ctrl(),mem,self,len,
+ generator ? -1 : values.size(),site,values).peephole().keep();
+ Node prior=P.mem();
+ P.mem(P.mergeAlias(prior,length._alias,new EscapeNode(length,result,slen.unkeep(),prior).peephole()));
+ prior=P.mem();
+ P.mem(P.mergeAlias(prior,body._alias,new EscapeNode(body,result,mem,prior).peephole()));
+ mem.unkeep(); self.unkeep(); len.unkeep();
+ for( Node value : values ) value.unkeep();
+ result.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/Conversions.java b/src/main/java/com/seaofnodes/simple/parser/Conversions.java
index fa11311d..72247ca6 100644
--- a/src/main/java/com/seaofnodes/simple/parser/Conversions.java
+++ b/src/main/java/com/seaofnodes/simple/parser/Conversions.java
@@ -37,7 +37,7 @@ final class Conversions {
case TypeFloat tf -> TypeFloat.F64;
case TypeMemPtr tmp -> tmp.makeVar();
case TypeFunPtr tfp -> tfp;
- case TypeConAry<?> a -> a;
+ case TypeConAry a -> a;
case TypePtr ptr -> TypePtr.PTR;
case TypeScalar ts -> TypeScalar.BOT;
default -> t;
diff --git a/src/main/java/com/seaofnodes/simple/parser/Declarations.java b/src/main/java/com/seaofnodes/simple/parser/Declarations.java
index 13fa59b8..473a7c24 100644
--- a/src/main/java/com/seaofnodes/simple/parser/Declarations.java
+++ b/src/main/java/com/seaofnodes/simple/parser/Declarations.java
@@ -91,6 +91,10 @@ final class Declarations {
// expr is a constant function
if( t instanceof TypeFunPtr decl && expr._type instanceof TypeFunPtr tfp && tfp.isConstant() ) {
t = decl.makeFrom(tfp.fidx());
+ // The explicit signature specifies source parameters/return;
+ // the receiver of a method body is still inferred.
+ if( expr instanceof FunPtrNode && P.structs.inStructDeclaration() )
+ t = ((TypeFunPtr)t).makeFrom(tfp.arg(0),0);
FunNode fun = P._code.link(tfp);
if( fun != null )
fun.setName(name); // Assign debug name to Simple function
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..84bd509e 100644
--- a/src/main/java/com/seaofnodes/simple/parser/Functions.java
+++ b/src/main/java/com/seaofnodes/simple/parser/Functions.java
@@ -14,6 +14,11 @@ final class Functions {
// Merge all function exits here.
ScopeNode _returnScope;
+ // Instance method headers are available immediately; their bodies need
+ // the complete field scope, just like explicitly declared constructors.
+ private record Method(FunPtrNode ptr, Lexer body, Lexer loc, BitSet finals, String[] ids) {}
+ private Ary<Method> _methods;
+
Functions(Parser P) { this.P = P; }
/**
@@ -29,6 +34,7 @@ final class Functions {
Ary<String> ids = new Ary<>(String.class);
BitSet finals = new BitSet();
Type self = P.types.implicitSelfType();
+ if( P.structs.inStructDeclaration() ) self = self.makeRO();
ts.push(self);
ids.push(self == TypePtr.PTR ? "#self" : "self");
@@ -38,10 +44,49 @@ final class Functions {
parameters(ts,ids,finals,false);
// Make a concrete function type, with a fidx
TypeFunPtr tfp = TypeFunPtr.make1((byte)2,true,ts.asAry(),Type.BOTTOM,P._code.fidx(P._ref._cname));
+ if( P.structs.inStructDeclaration() ) {
+ FunPtrNode ptr = (FunPtrNode)new FunPtrNode(tfp,P._code._start,null).peephole().keep();
+ _methods.push(new Method(ptr,P.loc(),loc,finals,ids.asAry()));
+ P._lexer.skipTo('{','}',"method body");
+ return ptr;
+ }
ReturnNode ret = parseFunctionBody(tfp,loc,finals,ids.asAry());
return new FunPtrNode(tfp,P._code._start,ret).peephole();
}
+ private void finishMethods() {
+ if( _methods == null ) return;
+ Ary<Method> methods = _methods;
+ Lexer after = P._lexer;
+ for( Method method : methods ) {
+ P._lexer = new Lexer(method.body());
+ TypeFunPtr sig = (TypeFunPtr)method.ptr()._con;
+ ReturnNode ret = parseFunctionBody(sig,method.loc(),method.finals(),method.ids());
+ method.ptr().setDef(1,ret);
+ P.require("}");
+ }
+ Ary<FunNode> functions = new Ary<>(FunNode.class);
+ for( Method method : methods ) functions.add(method.ptr().fun());
+ MethodAccess.infer(functions);
+ for( Method method : methods ) {
+ ReturnNode ret = method.ptr().ret();
+ TypeFunPtr sig = ret.fun().sig();
+ method.ptr().peephole();
+ // Declarations saw the header before its FunNode existed.
+ for( Var var : P._scope._vars )
+ if( var.type() instanceof TypeFunPtr tfp && tfp.isConstant() && tfp.fidx() == sig.fidx() ) {
+ ret.fun().setName(var._name);
+ var.refineType(var.type().join(method.ptr()._type));
+ TypeStruct self = P._scope.constructorSelf();
+ TypeStruct shape = (TypeStruct)Parser.TYPES.get(self._name);
+ Parser.TYPES.put(shape._name,shape.replace(shape.field(var._name).makeFrom(var.type())));
+ break;
+ }
+ method.ptr().unkeep();
+ }
+ P._lexer = after;
+ }
+
Node parseConstructor(String name, boolean explicit) {
P.require("{");
Ary<Type> ts = new Ary<>(Type.class);
@@ -198,9 +243,13 @@ 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);
+ Ary<Method> outerMethods = _methods;
+ _methods = FunNode.isInstance(funName) ? new Ary<>(Method.class) : null;
boolean isCtor = isInit || P.structs.isExplicitConstructorName(funName);
String typeName = isCtor ? ((TypeMemPtr)sig._sig[0])._obj._name : null;
@@ -233,7 +282,11 @@ final class Functions {
nvar = P.structs.updateSelfAsFieldsDiscovered( typeName, nvar );
}
- if( isInit ) P.structs.finishConstructor();
+ if( isInit ) {
+ finishMethods();
+ P.structs.finishConstructor();
+ }
+ _methods = outerMethods;
P.structs._pendingCtor = outerCtor;
ParmNode self = fun.parm(2);
@@ -275,6 +328,7 @@ final class Functions {
P.statements._breakScope = breakScope;
P._scope.pop();
P.construction._frame = outerConstruction;
+ P.arrays._frame = outerArray;
return ret;
}
@@ -300,8 +354,14 @@ final class Functions {
// All args, "as-if" called externally
for( int i = 0; i<ids.length; i++ ) {
Type t = sig.arg(i);
+ // Parse an implicit receiver with write access, then infer the
+ // public requirement from its uses once every method is present.
+ if( i == 0 && ids[0].equals("self") && !FunNode.isInit(funName) &&
+ !(sig.ret() instanceof TypeMem) && t instanceof TypeMemPtr ptr )
+ t = ptr.withAccess(false);
// Args take a default input if called from Start/external-world
- Node parm = new ParmNode(ids[i],i+2,t,fun,ConstantNode.seed(fun.defaultArg(i)).peephole()).peephole();
+ Type seed = i == 0 && t instanceof TypeMemPtr ? t : fun.defaultArg(i);
+ Node parm = new ParmNode(ids[i],i+2,t,fun,ConstantNode.seed(seed).peephole()).peephole();
if( t instanceof TypeMemPtr ptr && ptr._partial ) parm = new ConstructNode(parm).peephole();
P._scope.define(ids[i], t, (i==0 && ids[0]=="self") || (finals!=null && finals.get(i)), parm, loc);
}
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/MethodAccess.java b/src/main/java/com/seaofnodes/simple/parser/MethodAccess.java
new file mode 100644
index 00000000..099ba036
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/parser/MethodAccess.java
@@ -0,0 +1,130 @@
+package com.seaofnodes.simple.parser;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Ary;
+import java.util.ArrayDeque;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.Set;
+
+/** Infer the access required through an instance method's receiver. */
+final class MethodAccess {
+ private final FunNode _fun;
+ private final Set<Node> _aliases = Collections.newSetFromMap(new IdentityHashMap<>());
+ private final Set<MethodAccess> _calls = new HashSet<>();
+ private boolean _writes;
+
+ private MethodAccess(FunNode fun) { _fun = fun; }
+
+ static void infer(Ary<FunNode> functions) {
+ HashMap<Integer,MethodAccess> methods = new HashMap<>();
+ for( FunNode fun : functions ) methods.put(fun.sig().fidx(),new MethodAccess(fun));
+ for( MethodAccess method : methods.values() ) method.scan(methods);
+ boolean changed;
+ do {
+ changed = false;
+ for( MethodAccess method : methods.values() )
+ if( !method._writes )
+ for( MethodAccess callee : method._calls )
+ if( callee._writes ) { method._writes = true; changed = true; break; }
+ } while( changed );
+ for( MethodAccess method : methods.values() ) method.finish();
+ }
+
+ private void scan(HashMap<Integer,MethodAccess> methods) {
+ if( _fun.parm(2) == null ) return; // Unused receiver.
+ ArrayDeque<Node> work = new ArrayDeque<>();
+ work.add(_fun.parm(2));
+ while( !work.isEmpty() ) {
+ Node value = work.remove();
+ if( !_aliases.add(value) ) continue;
+ for( Node use : value._outputs ) {
+ if( use == null || use instanceof ReadOnlyNode ) continue;
+ if( use instanceof StoreNode store ) {
+ if( store.ptr() == value || store.val() == value ) _writes = true;
+ } else if( use instanceof CallNode call ) {
+ for( int i = 2; i < call.nIns()-1; i++ )
+ if( call.in(i) == value ) call(call,i-2,methods);
+ } else if( use instanceof ReturnNode ret ) {
+ // Returning a writable alias must not grant write access
+ // to an object reached through a read-only receiver.
+ if( ret.expr() == value ) _writes = true;
+ } else if( use instanceof LoadNode load && load.ptr() == value ) {
+ Type type = type(load);
+ if( type instanceof TypeMemPtr ptr ) {
+ if( !ptr._ro ) work.add(load);
+ } else if( type == Type.BOTTOM || type == TypeScalar.BOT ) _writes = true;
+ } else if( use instanceof PhiNode || use instanceof GuardNode ) {
+ work.add(use);
+ } else if( use instanceof ConvertNode convert ) {
+ if( !(convert.dst() instanceof TypeMemPtr ptr && ptr._ro) ) _writes = true;
+ } else if( use instanceof BoolNode || use instanceof NotNode ||
+ use instanceof FldOffNode || use instanceof ScopeNode || use instanceof CLInitNode ) {
+ // Comparisons and field offsets do not expose write access.
+ } else {
+ // Includes raw-address casts and unrecognized escapes.
+ _writes = true;
+ }
+ }
+ }
+ }
+
+ private void call(CallNode call, int arg, HashMap<Integer,MethodAccess> methods) {
+ // Every member of this struct is now declared. An unresolved bare
+ // function name belongs to an outer scope, whose hidden slot is not self.
+ if( arg == 0 && call.fptr() instanceof FRefNode ) return;
+ Type type = type(call.fptr());
+ if( !(type instanceof TypeFunPtr sig) || arg >= sig.nargs() ) { _writes = true; return; }
+ if( arg == 0 && sig.arg(0) == TypePtr.PTR ) return;
+ if( arg == 0 ) {
+ if( XInt.isHigh(sig.fidxs()) ) { _writes = true; return; }
+ for( int fidx = XInt.next(sig.fidxs(),-1); fidx >= 0; fidx = XInt.next(sig.fidxs(),fidx) ) {
+ MethodAccess local = methods.get(fidx);
+ if( local != null ) _calls.add(local);
+ else {
+ FunNode target = CodeGen.CODE.link(fidx);
+ Type receiver = target == null ? sig.arg(0) : target.sig().arg(0);
+ if( !(receiver instanceof TypeMemPtr ptr && ptr._ro) ) _writes = true;
+ }
+ }
+ return;
+ }
+ if( !(sig.arg(arg) instanceof TypeMemPtr ptr && ptr._ro) ) _writes = true;
+ }
+
+ private static Type type(Node value) {
+ // Flow types can still be BOTTOM while the parser discovers fields.
+ if( value instanceof LoadNode load ) {
+ TypeStruct obj = load.off() instanceof ConFldOffNode off ? off._ts :
+ type(load.ptr()) instanceof TypeMemPtr ptr ? ptr._obj : null;
+ if( obj != null ) {
+ if( !obj.isAry() && Parser.TYPES.get(obj._name) instanceof TypeStruct latest ) obj = latest;
+ Field field = obj.field(load._name);
+ if( field != null ) return field._t;
+ }
+ }
+ return value._type;
+ }
+
+ private void finish() {
+ ParmNode self = _fun.parm(2);
+ if( self == null ) return;
+ TypeMemPtr receiver = ((TypeMemPtr)self._declaredType).withAccess(!_writes);
+ _fun.setSig(_fun.sig().makeFrom(receiver,0));
+ if( _writes ) return;
+ self._declaredType = receiver;
+ self.setDef(1,ConstantNode.seed(receiver).peephole());
+ // These parser-time assumptions are settled before Iter/SCCP. No
+ // writable alias escapes a method classified as read-only.
+ for( Node alias : _aliases )
+ if( alias._type instanceof TypeMemPtr ptr ) {
+ alias._type = ptr.makeRO();
+ CodeGen.CODE.add(alias);
+ CodeGen.CODE.addAll(alias._outputs);
+ }
+ }
+}
diff --git a/src/main/java/com/seaofnodes/simple/parser/Parser.java b/src/main/java/com/seaofnodes/simple/parser/Parser.java
index 0cf3c0e7..9c18385d 100644
--- a/src/main/java/com/seaofnodes/simple/parser/Parser.java
+++ b/src/main/java/com/seaofnodes/simple/parser/Parser.java
@@ -7,7 +7,7 @@ import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.Ary;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.LinkedHashSet;
+import java.util.LinkedHashMap;
import java.util.Stack;
/** Source-unit entry point and shared cursor, scope, and IR utilities.
@@ -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);
@@ -91,7 +92,7 @@ public class Parser {
public final Stack<ScopeNode> _xScopes = new Stack<>();
// Layouts needed by field accesses in this source file. Resolve after
// parsing, since a later local struct declaration can satisfy the use.
- public final LinkedHashSet<String> _requiredTypes = new LinkedHashSet<>();
+ public final LinkedHashMap<String,Lexer> _requiredTypes = new LinkedHashMap<>();
// Mapping from a type name to a Type. The string name matches
// `type.str()` call.
@@ -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 ) {
@@ -265,7 +271,7 @@ public class Parser {
// Field offset for an unknown type
public Node fldoff( Node n, String name ) {
if( n._type instanceof TypeMemPtr ptr && ptr._obj._fref )
- _requiredTypes.add(ptr._obj._name);
+ _requiredTypes.putIfAbsent(ptr._obj._name,loc());
return new FldOffNode(n,name,loc()).peephole();
}
public Node peep( Node n ) {
diff --git a/src/main/java/com/seaofnodes/simple/parser/README.md b/src/main/java/com/seaofnodes/simple/parser/README.md
index cee1d64a..48cf13c8 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 |
@@ -14,6 +14,7 @@ productions to their entry points.
| `Declarations.java` | Bindings, initializers, mutability modifiers, and native declarations. |
| `Types.java` | Type syntax, qualified-name lookup, arrays, function types, and forward types. |
| `Functions.java` | Function expressions, parameters, arguments, calls, and function entry/return graphs. |
+| `MethodAccess.java` | Infers whether an instance method needs writable access through its receiver. |
| `Structs.java` | Struct and constructor declarations, field discovery, and initialization Stores. |
| `Construction.java` | Selects private self memory during parsing; its comment explains BOT fields, the TypeMemPtr qualifier, and checks deferred to TypeCheck. |
| `References.java` | Bare-name and field resolution, implicit receivers, assignments, and forward references. |
@@ -23,8 +24,16 @@ productions to their entry points.
| `Var.java` | Lexical binding metadata: name, type, scope slot, and mutability. |
Start with `Statements.parseStatement` or `Expressions.parseAssignment` to follow
-the grammar. Loop updates are parsed after their bodies, and declared constructor
-bodies after field discovery; `Lexer.skipTo` scans these deferred fragments.
+the grammar. Loop updates are parsed after their bodies. Instance method headers
+are parsed immediately, but their bodies and declared constructor bodies wait
+until all fields are known. Methods can therefore use fields declared later in
+the struct. `Lexer.skipTo` scans these deferred fragments.
+
+Method receivers accept read-only access unless the body needs writable access.
+The check follows aliases, child references, array elements, and calls between
+methods. Writing an independent argument does not require a writable receiver.
+Returning or storing a writable receiver alias also requires writable access;
+an explicit read-only view can be exposed without that requirement.
`Parser` keeps the short cursor, scope/memory, IR-building, and diagnostic helpers
used by these components. Independent helpers live with their data: class-object
diff --git a/src/main/java/com/seaofnodes/simple/parser/References.java b/src/main/java/com/seaofnodes/simple/parser/References.java
index 6335bad2..7724103e 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 &&
@@ -278,7 +280,7 @@ final class References {
// Check for assign-update, "ptr.fld += expr" or "ary[idx]++"
char ch = pre==0 ? P._lexer.matchOperAssign() : (char)pre;
if( ch!=0 ) {
- if( fld!=null && decl==Type.BOTTOM )
+ if( fld!=null && decl==Type.BOTTOM && !ts._fref )
throw Parser.error( "'" + ts._name + "' is not fully initialized, field '" + fld._fname + "' needs to be set in a constructor",site );
Node op = P.expressions.opAssign(ch,load, decl ).keep();
mem.keep();
@@ -306,10 +308,15 @@ final class References {
// Fields are lvalues in memory, including inside their initializer and
// explicit constructor. Scope slots retain declaration metadata only.
private Node fieldSelf(Var var) {
- int kx = P._scope.kindx(var), fx = P._scope.enclosingFuncOrDecl();
+ int kx = P._scope.kindx(var);
ScopeNode.Kind kind = P._scope._kinds.at(kx);
if( !(kind instanceof Kind.Func inst) || !FunNode.isInstance(inst._name) || var._idx<inst._lexSize+2 ) return null;
- if( kx==fx || kx+1==fx ) return defaultSelf();
+ // Nested functions have their own receiver parameter. Resolve fields
+ // through it even across intervening function and block scopes.
+ Node self = defaultSelf();
+ Type owner = P._scope.var(inst._lexSize).type();
+ if( self._type instanceof TypeMemPtr ptr && owner instanceof TypeMemPtr declared &&
+ ptr._obj._name.equals(declared._obj._name) ) return self;
return null;
}
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/Types.java b/src/main/java/com/seaofnodes/simple/parser/Types.java
index 827518fd..501924f0 100644
--- a/src/main/java/com/seaofnodes/simple/parser/Types.java
+++ b/src/main/java/com/seaofnodes/simple/parser/Types.java
@@ -37,6 +37,12 @@ final class Types {
if( t0==Type.BOTTOM || t0==Type.TOP ) return t0; // var/val
tname = typeName(tname).intern();
t0 = Parser.TYPES.get(tname);
+ // Bare forward types use the same file-relative name as `new`.
+ // A later sibling declaration must complete this very type.
+ if( t0 == null && tname.indexOf('.') == -1 ) {
+ tname = fullTypeName(tname);
+ t0 = Parser.TYPES.get(tname);
+ }
}
Type t1 = t0==null ? TypeStruct.forward(tname) : t0;
@@ -135,9 +141,8 @@ final class Types {
Type implicitSelfType() {
TypeStruct self = P._scope.constructorSelf();
- // Ordinary instance methods accept the declared object shape. The
- // constructor-only receiver starts at the low/default field values,
- // and is built explicitly by parseConstructor().
+ // Function types without a body must allow for writes. Function
+ // expressions can infer a weaker requirement in MethodAccess.
return self == null ? TypePtr.PTR : TypeMemPtr.make(self);
}
diff --git a/src/main/java/com/seaofnodes/simple/parser/Var.java b/src/main/java/com/seaofnodes/simple/parser/Var.java
index ddbb91d8..5361148f 100644
--- a/src/main/java/com/seaofnodes/simple/parser/Var.java
+++ b/src/main/java/com/seaofnodes/simple/parser/Var.java
@@ -33,6 +33,11 @@ public class Var {
public Type type() { return _type; }
public Type _type() { return _type; } // ScopeNode printer needs to have no side effects
+ void refineType(Type type) {
+ assert type.isa(_type) : _name+": "+_type+" -> "+type;
+ _type = type;
+ }
+
// Forward reference variables (not types) must be BOTTOM and
// distinct from inferred variables
public boolean isFRef() { return _fref; }
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..e4d1659d 100644
--- a/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java
+++ b/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java
@@ -10,19 +10,22 @@ 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> {
private final CodeGen _code;
private final Encoding _enc;
- private ASMPrinter(CodeGen code) {
+ private ASMPrinter(CodeGen code, Encoding enc) {
super(new IRPrinter());
_code=code;
- _enc=code._encoding;
+ _enc=enc;
}
public static SB print(SB sb, CodeGen code) {
- return sb.p(new com.seaofnodes.print.ASMPrinter<>(new ASMPrinter(code)).print());
+ return print(sb,code,code._encoding);
+ }
+ public static SB print(SB sb, CodeGen code, Encoding enc) {
+ return sb.p(new com.seaofnodes.print.ASMPrinter<>(new ASMPrinter(code,enc)).print());
}
@Override public ArrayList<Node> blocks() {
var blocks=new ArrayList<Node>();
@@ -89,11 +92,11 @@ public final class ASMPrinter extends AssemblyAdapter<Node> {
var data=new ArrayList<Data>();
for( var obj : _enc._data.entries() )
if( !obj.external && obj.readOnly==ro )
- data.add(new Data(obj,obj.symbol+": "+obj.type.str(),obj.alignment,obj.size));
+ data.add(new Data(obj,obj.label()+": "+obj.type.str(),obj.alignment,obj.size));
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/Type.java b/src/main/java/com/seaofnodes/simple/type/Type.java
index 0f655f49..2e228494 100644
--- a/src/main/java/com/seaofnodes/simple/type/Type.java
+++ b/src/main/java/com/seaofnodes/simple/type/Type.java
@@ -531,7 +531,7 @@ public class Type /*implements Cloneable*/ {
// tag-space except for the first tag-space which uses 1 tag for
// multiple Types instead of 1 Type having multiple tags. Has to
// be ordered to match _type enum.
- for( Type t : new Type[]{Type.XNIL,TypeScalar.BOT,TypePtr.PTR,TypeInteger.BOT,TypeFloat.F64,TypeConAryB.ABC,TypeRPC.BOT,TypeMemPtr.BOT,TypeFunPtr.BOT,TypeMem.BOT,Field.BOT,TypeStruct.BOT,TypeTuple.BOT} )
+ for( Type t : new Type[]{Type.XNIL,TypeScalar.BOT,TypePtr.PTR,TypeInteger.BOT,TypeFloat.F64,TypeConAry.ABC,TypeRPC.BOT,TypeMemPtr.BOT,TypeFunPtr.BOT,TypeMem.BOT,Field.BOT,TypeStruct.BOT,TypeTuple.BOT} )
TAGOFFS[t._type+1] = TAGOFFS[t._type] + t.TAGOFF();
assert TAGOFFS[TMAX] <= 255;
return TAGOFFS;
diff --git a/src/main/java/com/seaofnodes/simple/type/TypeConAry.java b/src/main/java/com/seaofnodes/simple/type/TypeConAry.java
index 9cb23e09..c10f09aa 100644
--- a/src/main/java/com/seaofnodes/simple/type/TypeConAry.java
+++ b/src/main/java/com/seaofnodes/simple/type/TypeConAry.java
@@ -1,33 +1,48 @@
package com.seaofnodes.simple.type;
import com.seaofnodes.simple.util.BAOS;
-import com.seaofnodes.simple.util.Utils;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
/**
- * Represents a constant array of primitives
+ * Constant integer contents with an explicit storage width and signedness.
*/
-public abstract class TypeConAry<A> extends TypeScalar {
+public final class TypeConAry extends TypeScalar {
boolean _any;
byte _widen;
- // One of byte or int array
- public final A _ary;
+ private final TypeInteger _storage;
+ private final long[] _ary;
- TypeConAry( boolean any, byte widen, A ary ) { super(TCONARY); _any = any; _widen = widen; _ary = ary; }
+ private TypeConAry(boolean any, byte widen, TypeInteger storage, long[] ary) {
+ super(TCONARY);
+ _any=any; _widen=widen; _storage=storage; _ary=ary;
+ }
+ static final TypeConAry ABC=make("abc");
+ static final TypeConAry ABCD=make("abcd");
public static void gather(ArrayList<Type> ts) {
- ts.add(TypeConAryB.make(""));
- ts.add(TypeConAryB.ABC);
- ts.add(TypeConAryB.ABCD);
- ts.add(TypeConAryI.I123);
+ ts.add(make(""));
+ ts.add(ABC);
+ ts.add(ABCD);
+ ts.add(make(TypeInteger.I32,new long[]{1,2,3}));
+ ts.add(make(TypeInteger.U16,new long[]{0,65535}));
+ ts.add(make(TypeInteger.BOT,new long[]{Long.MIN_VALUE,Long.MAX_VALUE}));
+ for( TypeInteger storage : STORAGE ) {
+ ts.add(make(storage,new long[0]));
+ ts.add(make(storage,new long[]{0,1}));
+ }
}
// Fresh, uninterned type sharing this array; xdual must not intern.
- abstract TypeConAry<A> _make(boolean any, byte widen);
+ private TypeConAry _make(boolean any, byte widen) {
+ return new TypeConAry(any,widen,_storage,_ary);
+ }
- @Override TypeConAry<A> xdual() { return _make( !_any, (byte)(3-_widen) ); }
+ @Override TypeConAry xdual() { return _make( !_any, (byte)(3-_widen) ); }
@Override Type xmeet(Type t) {
TypeConAry ary = (TypeConAry)t; // Invariant
+ if( len()==0 ) return emptyMeet(ary);
+ if( ary.len()==0 ) return ary.emptyMeet(this);
// Same base array but different?
if( _equals(ary) )
// Return array with larger widen
@@ -37,15 +52,7 @@ public abstract class TypeConAry<A> extends TypeScalar {
}
Type ymeet( TypeInteger ti ) {
- // Empty arrays have no observed elements: use the storage bounds.
- // Check both endpoints just as the loop checks a nonempty body;
- // retaining high integer inputs here preserves lattice duality.
- if( len()==0 ) {
- TypeInteger bound=emptyElem();
- if( !ti.isa(TypeInteger.make(bound._min,bound._min,ti._widen)) ||
- !ti.isa(TypeInteger.make(bound._max,bound._max,ti._widen)) )
- return ti.meet(bound);
- }
+ if( len()==0 ) return emptyMeet(ti);
// if i can isa *each* element, then maybe can keep.
// no good to i.isa(elem()) because fails the dual
for( int j=0; j<len(); j++ )
@@ -56,12 +63,21 @@ public abstract class TypeConAry<A> extends TypeScalar {
}
private boolean _equals(TypeConAry ary) {
- int len = len();
- if( len != ary.len() ) return false;
- for( int i=0; i<len; i++ )
- if( at8(i) != ary.at8(i) )
- return false;
- return true;
+ return _storage==ary._storage && (len()!=0 || _any==ary._any) && Arrays.equals(_ary,ary._ary);
+ }
+ @Override boolean eq(Type t) { return t instanceof TypeConAry ary && _widen==ary._widen && _equals(ary); }
+ @Override int hash() { return Arrays.hashCode(_ary)^_storage.hashCode()^(len()==0 && _any ? 1231 : 0); }
+ @Override public String str() {
+ boolean text=_storage==TypeInteger.U8;
+ for( long value : _ary )
+ if( value<32 || value>126 ) { text=false; break; }
+ if( !text ) {
+ String range=elem().str();
+ return range.startsWith("[") ? range : "["+range+"]";
+ }
+ byte[] bytes = new byte[len()];
+ for( int i=0; i<bytes.length; i++ ) bytes[i]=(byte)_ary[i];
+ return "[\""+new String(bytes)+"\"]";
}
@Override public boolean isHigh() { return this==TOP; }
@@ -70,8 +86,6 @@ public abstract class TypeConAry<A> extends TypeScalar {
// Meet-over-elements type
public Type elem() {
- if( _ary==null )
- return _any ? Type.TOP : BOTTOM;
int len = len();
long min = Long.MAX_VALUE;
if( len==0 ) return emptyElem();
@@ -83,23 +97,80 @@ public abstract class TypeConAry<A> extends TypeScalar {
return TypeInteger.make(min,max,_widen);
}
private TypeInteger emptyElem() {
- return log_size()==0 ? TypeInteger.make(0,255,_widen)
- : TypeInteger.make(Integer.MIN_VALUE,Integer.MAX_VALUE,_widen);
+ TypeInteger storage=storageType();
+ return _any ? TypeInteger.make(storage._min,storage._max,_widen)
+ : TypeInteger.make(storage._max,storage._min,_widen);
+ }
+ // Keep the empty payload distinct from its element bound. It sits just
+ // above the storage dual; its own dual sits just below the storage bound.
+ // This makes it a subtype of every constant array within those bounds,
+ // while preserving meet/join symmetry with ordinary integer ranges.
+ private Type emptyMeet(Type t) {
+ Type bound=emptyElem();
+ if( t.isa(bound) && (_any || t!=bound) ) return this;
+ return bound.meet(t);
}
- public abstract long at8(int idx);
- public abstract int len();
- @Override public abstract int log_size();
- public void write( BAOS baos ) { throw Utils.TODO("Should not reach here: abstract constant array cannot be written"); }
+ public TypeInteger storageType() { return _storage; }
+ public long at8(int idx) { return _ary[idx]; }
+ public int len() { return _ary.length; }
+ @Override public int log_size() { return _storage.log_size(); }
+ public void write( BAOS baos ) {
+ for( int i=0; i<len(); i++ ) {
+ long value=at8(i);
+ for( int b=0; b<(1<<log_size()); b++ ) baos.write((byte)(value >>> (8*b)));
+ }
+ }
+
+ /** Canonical contents, independent of the node which discovered them. */
+ public static TypeConAry make(TypeInteger storage, long[] values) {
+ long[] copy=values.clone();
+ int shift=64-(8<<storage.log_size());
+ for( int i=0; i<copy.length; i++ )
+ copy[i]=storage._min<0 ? (copy[i]<<shift)>>shift : copy[i]&storage._max;
+ return new TypeConAry(false,(byte)0,storage,copy).intern();
+ }
+ public static TypeConAry make(byte[] bytes) {
+ long[] values=new long[bytes.length];
+ for( int i=0; i<bytes.length; i++ ) values[i]=bytes[i]&255;
+ return new TypeConAry(false,(byte)0,TypeInteger.U8,values).intern();
+ }
+ public static TypeConAry make(String s) { return make(s.getBytes()); }
+
+ // Keep the existing byte-array tag and encoding. An impossible Java array
+ // length escapes to a typed payload, without renumbering later type tags.
+ private static final int TYPED = -1;
+ private static final TypeInteger[] STORAGE={TypeInteger.BOOL,TypeInteger.I8,TypeInteger.U8,
+ TypeInteger.I16,TypeInteger.U16,TypeInteger.I32,TypeInteger.U32,TypeInteger.BOT};
// Reserve tags for u8 array
@Override int TAGOFF() { return 1; }
@Override public void packed( BAOS baos, HashMap<String,Integer> strs ) {
- assert log_size()==0 && !_any;
baos.write(TAGOFFS[TCONARY]+0);
+ if( storageType()!=TypeInteger.U8 || _any || _widen!=0 ) {
+ baos.packed4(TYPED);
+ int kind=0;
+ while( kind<STORAGE.length && STORAGE[kind]!=storageType() ) kind++;
+ assert kind<STORAGE.length;
+ baos.packed1(kind);
+ baos.packed1((_any ? 4 : 0)|_widen);
+ }
baos.packed4(len());
- baos.write((byte[])_ary);
+ write(baos);
}
static TypeConAry packed( int tag, BAOS bais ) {
- return TypeConAryB.make(bais.read(new byte[bais.packed4()]));
+ int len=bais.packed4();
+ if( len!=TYPED ) return make(bais.read(new byte[len]));
+ TypeInteger storage=STORAGE[bais.packed1()];
+ int flags=bais.packed1();
+ long[] values=new long[bais.packed4()];
+ int size=1<<storage.log_size();
+ for( int i=0; i<values.length; i++ ) {
+ long value=0;
+ for( int b=0; b<size; b++ ) value|=(long)bais.read() << (8*b);
+ if( storage._min<0 && size<8 ) value=(value << (64-size*8)) >> (64-size*8);
+ values[i]=value;
+ }
+ TypeConAry ary=make(storage,values);
+ return flags==0 ? ary : ary._make((flags&4)!=0,(byte)(flags&3)).intern();
}
}
diff --git a/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java b/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java
deleted file mode 100644
index 84be5fb2..00000000
--- a/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.seaofnodes.simple.type;
-
-import com.seaofnodes.simple.util.BAOS;
-import java.util.Arrays;
-
-public class TypeConAryB extends TypeConAry<byte[]> {
- private TypeConAryB(boolean any, byte widen, byte[] bs) { super(any,widen,bs); }
- @Override TypeConAryB _make( boolean any, byte widen ) { return new TypeConAryB(any,widen,_ary); }
- public static TypeConAryB make( byte[] bs ) { return new TypeConAryB(false,(byte)0,bs).intern(); }
- public static TypeConAryB make( String s ) { return make(s.getBytes()); }
- static final TypeConAryB ABC = make("abc");
- static final TypeConAryB ABCD = make("abcd");
- @Override public long at8(int idx) { return _ary[idx]&255; }
- @Override public int len() { return _ary.length; }
- @Override public int log_size() { return 0; }
- @Override public String str() { return "[\""+new String(_ary)+"\"]"; }
- @Override boolean eq(Type t) { return t instanceof TypeConAryB ary && _widen==ary._widen && Arrays.equals(_ary,ary._ary); }
- @Override int hash() { return Arrays.hashCode(_ary); }
- @Override public void write( BAOS baos ) { baos.write(_ary); }
-}
diff --git a/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java b/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java
deleted file mode 100644
index b1b061de..00000000
--- a/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.seaofnodes.simple.type;
-
-import java.util.Arrays;
-
-public class TypeConAryI extends TypeConAry<int[]> {
- private TypeConAryI(boolean any, byte widen, int[] bs) { super(any,widen,bs); }
- public static TypeConAryI make( int[] bs ) { return new TypeConAryI(false,(byte)0,bs).intern(); }
- @Override TypeConAryI _make( boolean any, byte widen ) { return new TypeConAryI(any,widen,_ary); }
- static final TypeConAryI I123 = make(new int[]{1,2,3});
- @Override public long at8(int idx) { return _ary[idx]; }
- @Override public int len() { return _ary.length; }
- @Override public int log_size() { return 2; }
- @Override public String str() { return "[i32]"; }
- @Override boolean eq(Type t) { return t instanceof TypeConAryI ary && _widen==ary._widen && Arrays.equals(_ary,ary._ary); }
- @Override int hash() { return Arrays.hashCode(_ary); }
-}
diff --git a/src/main/java/com/seaofnodes/simple/type/TypeFloat.java b/src/main/java/com/seaofnodes/simple/type/TypeFloat.java
index d410e9cf..8d04fcbf 100644
--- a/src/main/java/com/seaofnodes/simple/type/TypeFloat.java
+++ b/src/main/java/com/seaofnodes/simple/type/TypeFloat.java
@@ -107,7 +107,7 @@ public class TypeFloat extends TypeScalar {
else {
assert isConstant();
baos.write(TAGOFFS[_type] + 2);
- baos.packed8(Double.doubleToLongBits(_con));
+ baos.packed8(Double.doubleToRawLongBits(_con));
}
}
@@ -121,12 +121,12 @@ public class TypeFloat extends TypeScalar {
}
@Override
- int hash() { return (int)(Double.hashCode(_con) ^ _sz ^ (1<<17)); }
+ int hash() { return Long.hashCode(Double.doubleToRawLongBits(_con)) ^ _sz ^ (1<<17); }
@Override
public boolean eq( Type t ) {
TypeFloat i = (TypeFloat)t; // Contract
- // Allow NaN to check for equality
- return _sz==i._sz && Double.doubleToLongBits(_con)==Double.doubleToLongBits(i._con);
+ // Representation conversions observe NaN signs/payloads and signed zero.
+ return _sz==i._sz && Double.doubleToRawLongBits(_con)==Double.doubleToRawLongBits(i._con);
}
}
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;
diff --git a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
index 7c9758bd..11ba3cc4 100644
--- a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
+++ b/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
@@ -146,8 +146,7 @@ public class TypeStruct extends Type {
Type len = TypeInteger.constant(con.len());
return make(name,false,
Field.make("#" ,len , lenAlias,true),
- Field.make("[]" ,con.elem(),bodyAlias,true),
- Field.make("[con]",con ,bodyAlias,true));
+ Field.make("[]" ,con ,bodyAlias,true));
}
public TypeStruct makeInit() {
@@ -190,6 +189,9 @@ public class TypeStruct extends Type {
}
@Override TypeStruct _close( String name, HashMap<String, Type> TYPES ) {
assert (name==null) != (TYPES==null); // One or the other
+ // Primitive constant contents are already closed. Looking up their
+ // shared array name would discard the length and payload on import.
+ if( isConAry() ) return this;
String namePlusFinal = isAry() ? (_name+(_fields[1]._final ? "~" : "!")).intern() : _name;
TypeStruct ts = (TypeStruct)VISIT.get(namePlusFinal);
if( ts!=null ) return ts;
@@ -251,7 +253,7 @@ public class TypeStruct extends Type {
public static final TypeStruct TEST= make("test",false,Field.TEST);
private static final TypeStruct ARY = makeAry("[]i64",TypeInteger.U32,-1,TypeInteger.BOT,-2,false);
private static final TypeStruct STR = makeAry("[]u8" ,TypeInteger.U32,-1,TypeInteger.U8 ,-4,false);
- private static final TypeStruct ABC = makeAry("[]u8",-1,-4,TypeConAryB.ABC);
+ private static final TypeStruct ABC = makeAry("[]u8",-1,-4,TypeConAry.ABC);
// Representative unresolved forms for the exhaustive lattice tests:
// bare forward ref, forward ref constrained by a field use, and definition.
private static final TypeStruct FREF0 = forward("%FREF");
@@ -462,6 +464,7 @@ public class TypeStruct extends Type {
@Override public int log_size() { throw Utils.TODO("Should not reach here: structs have no scalar log size"); }
@Override public int size() { return offset(_fields.length); }
@Override public int alignment() {
+ if( isAry() ) return Math.max(2,aryScale()); // The length is always u32.
int align = 0;
for( Field f : _fields )
if( !f._extern ) align = Math.max(align, f._t.alignment());
@@ -607,7 +610,7 @@ public class TypeStruct extends Type {
int align = _fields[1]._t.alignment();
_offs[1] = (4 + ((1<<align)-1)) & -(1<<align);
if( _fields[1]._t instanceof TypeConAry ary ) // Array length is well-defined for constants, not defined for runtime arrays
- _offs[2] = _offs[1]+ary.len();
+ _offs[2] = _offs[1]+(ary.len()<<ary.log_size());
} else {
int[] cnts = new int[5]; // Count of fields at log field size