summaryrefslogtreecommitdiff
path: root/chapter29
diff options
context:
space:
mode:
Diffstat (limited to 'chapter29')
-rw-r--r--chapter29/README.ja.md3
-rw-r--r--chapter29/README.md27
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java14
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java3
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/Encoding.java5
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java16
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/Serialize.java8
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/StaticData.java6
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java2
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/CLInitNode.java9
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java6
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/ConvertNode.java2
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/ScopeNode.java4
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/StoreNode.java2
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/Allocations.java12
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/Conversions.java2
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/Expressions.java2
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/Lexer.java5
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/Parser.java148
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/README.md5
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/References.java22
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/Structs.java106
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/Types.java2
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/type/TypeStruct.java19
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/ConstructorCompletionTest.java77
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/Eval2.java3
26 files changed, 300 insertions, 210 deletions
diff --git a/chapter29/README.ja.md b/chapter29/README.ja.md
index f4bb10b6..54c7b5a6 100644
--- a/chapter29/README.ja.md
+++ b/chapter29/README.ja.md
@@ -14,7 +14,8 @@
[パーサの構成(英語)](src/main/java/com/seaofnodes/simple/parser/README.md)と
[文法と実装の対応表(英語)](docs/29-grammar.md)からコードをたどれます。
-コンストラクタの初期化完了チェックの整理は今後の課題です。
+初期化完了チェックは、構文解析後のヘルパー関数の Return を調べます。
+未初期化の必須フィールドを private memory に残すため、終了経路の別の一覧は不要です。
ソース上のプライバシー制約は今回は追加せず、[ロードマップ](../docs/roadmap.md)に残します。
リポジトリのルートからビルドとテストを実行します。
diff --git a/chapter29/README.md b/chapter29/README.md
index 9ddf69ce..15ddc1a9 100644
--- a/chapter29/README.md
+++ b/chapter29/README.md
@@ -33,14 +33,25 @@ are parsed after their bodies, and declared constructors after field discovery.
The [source layout](src/main/java/com/seaofnodes/simple/README.md) describes the
other packages. `IterPeeps` now lives in `codegen/` beside the compilation passes.
-## Cleanup still to do
-
-Constructor completion is a useful next target. `addReturn` already merges field
-values across exit paths, but `_ctorExits` saves those types until all fields
-are known. Checking the final `Return` alone is insufficient today: field
-conversions can erase uninitialized state, and the declaration initializer is
-separate from the user constructor. Simplify that bookkeeping without weakening
-the initialization checks.
+## Checking constructor completion
+
+`addReturn` merges field values across exit paths. After `parseBody` finishes,
+`Structs.parseInitializer` checks the completed helper Returns; no separate exit
+maps survive scope teardown. Required fields without an assignment on every
+returning path retain `TOP` in private memory. They emit no Store, so numeric
+conversions cannot hide the missing assignment.
+
+The declaration initializer and user constructor have separate Returns. Check
+the user constructor when present, or the declaration initializer for the default
+constructor. The older same-named constructor binding can precede field
+declarations; fields absent from its Return use the declaration initializer's
+result. Nonreturning helpers require no completed object. Completion errors
+point to the relevant helper's source location.
+
+`ConstructorCompletionTest` covers partial numeric assignments, early returns,
+nonreturning paths, declaration defaults, and fields declared after constructors.
+
+## Further cleanup
Source privacy is deferred. Leading underscores retain the existing export
filtering behavior; this chapter does not yet enforce access restrictions on
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
index bdb510b4..6e1c0af5 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
@@ -114,13 +114,13 @@ public class CodeGen {
}
public static boolean hasUserConstructor(TypeStruct self) {
- Type t = Parser.TYPES.get(Parser.addClzPrefix(self._name));
+ Type t = Parser.TYPES.get(TypeStruct.addClzPrefix(self._name));
return t instanceof TypeStruct clz && clz.field("<ctor>") != null;
}
// A forward reference has a placeholder class, not a definition yet.
private static boolean resolvedClass(TypeStruct clz) {
- Type t = Parser.TYPES.get(clz._name.substring(Parser.CLZ.length()));
+ Type t = Parser.TYPES.get(clz._name.substring(TypeStruct.CLZ.length()));
return !(t instanceof TypeStruct ts) || !ts._fref;
}
@@ -139,13 +139,13 @@ public class CodeGen {
_publicFIDXs = _publicAliases = XInt.EMPTY;
for( Type t : Parser.TYPES.values() )
if( t instanceof TypeStruct clz &&
- Parser.startsClzPrefix(clz._name) &&
- Parser.isPublicClassName(clz._name) &&
+ TypeStruct.startsClzPrefix(clz._name) &&
+ TypeStruct.isPublicClassName(clz._name) &&
resolvedClass(clz) ) {
// Gather function pointers from public fields of both class
// and instance objects.
gatherPublicFields(clz,true);
- Type self = Parser.TYPES.get(clz._name.substring(Parser.CLZ.length()));
+ Type self = Parser.TYPES.get(clz._name.substring(TypeStruct.CLZ.length()));
if( self instanceof TypeStruct ts ) gatherPublicFields(ts,false);
}
// Limit broad function types to known FIDXs, excluding reserved bits.
@@ -309,7 +309,7 @@ public class CodeGen {
}
public String entryClinitName() {
- return Parser.addClzPrefix(_srcName==null ? "Test" : _srcName.replace('/','.'))+".<clinit>";
+ return TypeStruct.addClzPrefix(_srcName==null ? "Test" : _srcName.replace('/','.'))+".<clinit>";
}
// Verbose printing during compilation
@@ -790,7 +790,7 @@ public class CodeGen {
// Publish definitions only; referenced classes belong to other objects.
for( String fname : elf._units )
- _externSymbols.putIfAbsent(Parser.addClzPrefix(fname.replace('/','.')),elf);
+ _externSymbols.putIfAbsent(TypeStruct.addClzPrefix(fname.replace('/','.')),elf);
// A -L object can name dependencies alongside itself. They
// need metadata discovery even when no directory was supplied.
for( String dep : elf._deps ) {
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java
index 2b091b3c..995301c3 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java
@@ -1,6 +1,5 @@
package com.seaofnodes.simple.codegen;
-import com.seaofnodes.simple.parser.Parser;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeStruct;
@@ -135,7 +134,7 @@ public class CompUnit {
static File dependencyObj(String fname, boolean local) {
File obj = new File(CODE._buildDir+"/"+fname+".o");
if( local && (obj.isFile() || new File(CODE._modDir+"/"+fname+".smp").isFile()) ) return obj;
- ElfReader elf = CODE.findExternalSimple(Parser.addClzPrefix(fname.replace('/','.')));
+ ElfReader elf = CODE.findExternalSimple(TypeStruct.addClzPrefix(fname.replace('/','.')));
return elf==null ? null : elf._file;
}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/Encoding.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/Encoding.java
index 8b868218..b07a8fec 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/codegen/Encoding.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/Encoding.java
@@ -1,6 +1,5 @@
package com.seaofnodes.simple.codegen;
-import com.seaofnodes.simple.parser.Parser;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.print.IRPrinter;
import com.seaofnodes.simple.type.*;
@@ -66,7 +65,7 @@ public class Encoding implements com.seaofnodes.isa.CodeSink {
// Class initializers still write their fields, even when every
// final value is constant. Their storage must remain writable.
return !(t instanceof TypeStruct ts) ||
- (!Parser.startsClzPrefix(ts._name) && ts.isConstant());
+ (!TypeStruct.startsClzPrefix(ts._name) && ts.isConstant());
}
}
@@ -201,7 +200,7 @@ public class Encoding implements com.seaofnodes.isa.CodeSink {
// addressing to load it. Type is stored in either the .rodata or .data.
public void largeConstant( Node relo, Type t, int off, int elf ) {
// TODO: Any-old struct in the cpool
- assert t.isConstant() || (t instanceof TypeStruct ts && Parser.startsClzPrefix(ts._name));
+ assert t.isConstant() || (t instanceof TypeStruct ts && TypeStruct.startsClzPrefix(ts._name));
assert (byte)off == off;
assert (byte)elf == elf;
_bigCons.put(relo,new Relo(relo,t,(byte)off,(byte)elf));
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java
index 1218c956..7fa4c662 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java
@@ -117,7 +117,7 @@ public abstract class ParseAll {
Parser.TYPES.put(ts._name,ts);
for( CompUnit cu : code._compunits.values() )
if( cu._ext==null )
- cu._clz = (TypeStruct)Parser.TYPES.get(Parser.addClzPrefix(cu._cname));
+ cu._clz = (TypeStruct)Parser.TYPES.get(TypeStruct.addClzPrefix(cu._cname));
// Walk over all Nodes, and upgrade the internal constants to the
// closed-over types.
@@ -135,8 +135,8 @@ public abstract class ParseAll {
if( alloc ) {
Field ctor = clz.field("<new>");
if( ctor == null || !(ctor._t instanceof TypeFunPtr tfp) ) {
- String name = clzName.startsWith(Parser.CLZ)
- ? clzName.substring(Parser.CLZ.length()) : clzName;
+ String name = clzName.startsWith(TypeStruct.CLZ)
+ ? clzName.substring(TypeStruct.CLZ.length()) : clzName;
int dot = name.lastIndexOf('.');
throw Parser.error("Unknown struct type '"+(dot==-1 ? name : name.substring(dot+1))+"'",fref._loc);
}
@@ -184,7 +184,7 @@ public abstract class ParseAll {
xcunit = findCUnitExternalSimple(code,fref._name);
if( xcunit != null ) {
// Replace the FRef with the discovered class name.
- fref._name = Parser.addClzPrefix(xcunit._cname);
+ fref._name = TypeStruct.addClzPrefix(xcunit._cname);
cunit.addDep(xcunit);
} else {
throw new RuntimeException("Undefined name '" + fref._name +"'");
@@ -261,7 +261,7 @@ public abstract class ParseAll {
private static File externalObj(CodeGen code, CompUnit par, String name) {
String cname = par==null ? name : par._cname + "." + name;
- ElfReader elf = code.findExternalSimple(Parser.addClzPrefix(cname));
+ ElfReader elf = code.findExternalSimple(TypeStruct.addClzPrefix(cname));
return elf == null ? null : elf._file;
}
@@ -286,7 +286,7 @@ public abstract class ParseAll {
ProjNode oldMem = oldStart==null ? null : oldStart.proj(1);
if( oldMem!=null ) code.releaseReplacedNode(oldMem);
elf.loadSimple(code);
- cunit._clz = (TypeStruct)Parser.TYPES.get(Parser.addClzPrefix(cunit._cname));
+ cunit._clz = (TypeStruct)Parser.TYPES.get(TypeStruct.addClzPrefix(cunit._cname));
StartNode lstart = (StartNode)elf._nodes.at(0);
StopNode lstop = ( StopNode)elf._nodes.last();
@@ -419,8 +419,8 @@ public abstract class ParseAll {
// Search external Simple objects only. C linkage uses explicit "C"
// declarations, which produce ExternNodes and are resolved by the linker.
public static CompUnit findCUnitExternalSimple( CodeGen code, String symbol ) {
- String clzName = symbol.startsWith(Parser.CLZ) ? symbol : Parser.addClzPrefix(symbol);
- String cname = clzName.substring(Parser.CLZ.length());
+ String clzName = symbol.startsWith(TypeStruct.CLZ) ? symbol : TypeStruct.addClzPrefix(symbol);
+ String cname = clzName.substring(TypeStruct.CLZ.length());
String fname = cname.replace('.','/');
CompUnit cunit = code._compunits.get(fname);
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/Serialize.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
index 76829151..4bb94183 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
@@ -313,8 +313,8 @@ public class Serialize {
// A nested declaration belongs to its nearest enclosing file.
private static CompUnit classOwner(CodeGen code, TypeStruct ts) {
- if( !Parser.startsClzPrefix(ts._name) ) return null;
- String name = ts._name.substring(Parser.CLZ.length()).replace('.','/');
+ if( !TypeStruct.startsClzPrefix(ts._name) ) return null;
+ String name = ts._name.substring(TypeStruct.CLZ.length()).replace('.','/');
while( true ) {
CompUnit cu = code._compunits.get(name);
if( cu!=null ) return cu;
@@ -418,9 +418,9 @@ public class Serialize {
if( type instanceof TypeStruct ts )
Parser.TYPES.put(ts._name,ts);
for( Type type : types ) {
- if( !(type instanceof TypeStruct ts) || !Parser.startsClzPrefix(ts._name) )
+ if( !(type instanceof TypeStruct ts) || !TypeStruct.startsClzPrefix(ts._name) )
continue;
- String cname = ts._name.substring(Parser.CLZ.length());
+ String cname = ts._name.substring(TypeStruct.CLZ.length());
int dot = cname.lastIndexOf('.');
String name = dot == -1 ? cname : cname.substring(dot+1);
Field inst = ts.field(name.intern());
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/StaticData.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/StaticData.java
index f813fab5..5d1a519b 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/codegen/StaticData.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/StaticData.java
@@ -41,11 +41,11 @@ public final class StaticData {
}
static boolean isClass(Type t) {
- return t instanceof TypeStruct ts && Parser.startsClzPrefix(ts._name);
+ return t instanceof TypeStruct ts && TypeStruct.startsClzPrefix(ts._name);
}
static CompUnit owner(CodeGen code,TypeStruct ts) {
- return owner(code,ts._name.substring(Parser.CLZ.length()));
+ return owner(code,ts._name.substring(TypeStruct.CLZ.length()));
}
static CompUnit owner(CodeGen code,String name) {
if(name==null) return null;
@@ -65,7 +65,7 @@ public final class StaticData {
if( clz && Parser.TYPES.get(((TypeStruct)t)._name) instanceof TypeStruct canonical ) t=canonical;
CompUnit owner=clz ? owner(enc._code,(TypeStruct)t)
: t instanceof TypeFunPtr fp ? owner(enc._code,enc._code._fidxs.owner(fp.fidx())) : null;
- String symbol=clz ? ((TypeStruct)t)._name.substring(Parser.CLZ.length())+".$class"
+ String symbol=clz ? ((TypeStruct)t)._name.substring(TypeStruct.CLZ.length())+".$class"
: t instanceof TypeFunPtr fp ? functionSymbol(fp) : prefix+objects.size();
ObjectData obj=new ObjectData(t,symbol,!clz && (!(t instanceof TypeStruct ts) || ts.isConstant()),
owner!=null && !enc._code.emits(owner));
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
index 6a902f3c..130b631c 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
@@ -258,7 +258,7 @@ public class BulkMemPhiNode extends PhiNode {
MemPhiNode mphi = _findPhi(alias);
if( mphi!=null ) return mphi;
- mphi = new MemPhiNode("$"+alias,alias);
+ 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
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/CLInitNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/CLInitNode.java
index f381e57a..f510be39 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/CLInitNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/CLInitNode.java
@@ -2,7 +2,6 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.simple.parser.Lexer;
import com.seaofnodes.print.ExprPrinter;
-import com.seaofnodes.simple.parser.Parser;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.codegen.CompUnit;
import com.seaofnodes.simple.codegen.Iter;
@@ -43,14 +42,14 @@ public class CLInitNode extends CFGNode implements MultiNode {
// The field/alias can be unresolved until forward references are loaded.
public Field flag() {
- return ptr()._type instanceof TypeMemPtr tmp && Parser.startsClzPrefix(tmp._obj._name)
+ return ptr()._type instanceof TypeMemPtr tmp && TypeStruct.startsClzPrefix(tmp._obj._name)
? tmp._obj.field("$init") : null;
}
@Override public String label() { return "CLInit"; }
// Best effort dst compunit, based on ptr - or null
public CompUnit cu() {
- if( !(ptr()._type instanceof TypeMemPtr ptr && Parser.startsClzPrefix(ptr._obj._name)) )
+ if( !(ptr()._type instanceof TypeMemPtr ptr && TypeStruct.startsClzPrefix(ptr._obj._name)) )
return null;
// Convert "class:sys.ary" into "sys/ary" and lookup CompUnit
String fname = ptr._obj._name.substring(6).replace(".","/");
@@ -70,7 +69,7 @@ public class CLInitNode extends CFGNode implements MultiNode {
return ptr()._type==Type.NIL ||
(ptr()._type instanceof TypeMemPtr tmp && tmp._obj!=TypeStruct.BOT &&
!tmp._obj.isHigh() && !tmp._obj._open && !tmp._obj._fref &&
- !Parser.startsClzPrefix(tmp._obj._name));
+ !TypeStruct.startsClzPrefix(tmp._obj._name));
}
public boolean bypass() { return instance() || ready()._type==TypeInteger.TRUE; }
@@ -205,6 +204,6 @@ public class CLInitNode extends CFGNode implements MultiNode {
dep.addDep(init.ptr());
Field flag = init.flag();
if( flag==null || flag._alias!=alias ) return mem;
- return new ProjNode(init,2,Parser.memName(alias)).peephole();
+ return new ProjNode(init,2,ScopeNode.memName(alias)).peephole();
}
}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java
index cb99b4dd..4024c5a9 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java
@@ -21,6 +21,12 @@ public class ConFldOffNode extends ConstantNode {
public TypeStruct _ts; // Struct holding field
public final String _fname; // Field name
public ConFldOffNode( TypeStruct ts, String fname ) { super(TypeInteger.BOT); _ts = ts; _fname = fname; }
+
+ // Field offset for a known type (but unknown offset).
+ public static ConFldOffNode make( TypeStruct ts, String fname ) {
+ return (ConFldOffNode)new ConFldOffNode(ts,fname).peephole();
+ }
+
@Override public Tag serialTag() { throw Utils.TODO("should not reach here"); }
@Override public String label() {
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/ConvertNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
index a969fb4e..81f46097 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
@@ -52,7 +52,7 @@ public class ConvertNode extends Node {
// the address of the first element. The representation cast itself is
// zero-code; the Add skips the array header.
if( _dst==TypeInteger.BOT && src instanceof TypeMemPtr tmp && tmp._obj.isAry() )
- return new AddNode(new PtrToIntNode(val()).peephole(),Parser.off(tmp._obj,"[]"));
+ return new AddNode(new PtrToIntNode(val()).peephole(),ConFldOffNode.make(tmp._obj,"[]"));
// Integer (and nil-as-zero) to floating point.
if( (src instanceof TypeInteger || src==Type.NIL) && _dst instanceof TypeFloat )
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
index ff3d8b4d..5fc2b8cd 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
@@ -26,6 +26,10 @@ public class ScopeNode extends MemMergeNode {
public static final String ARG0 = "arg";
public static final String MEM0 = "$mem";
+ // Structural label for a precise memory alias. The '$' prefix cannot be
+ // referenced in Simple source.
+ public static String memName(int alias) { return ("$"+alias).intern(); }
+
// All active/live variables in all nested scopes, all run together.
// Maps 1-to-1 to the _inputs array.
public final Ary<Var> _vars;
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/StoreNode.java
index 96e15a58..fe7d428c 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/StoreNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/StoreNode.java
@@ -97,7 +97,7 @@ public class StoreNode extends MemOpNode {
// A constant runtime value alone is not enough: mutable fields (notably
// $init) still need their writes, and instance fields need allocation.
Field fld = ptr._obj.field(_name);
- if( _alias!=1 && ptr._one && Parser.startsClzPrefix(ptr._obj._name) &&
+ if( _alias!=1 && ptr._one && TypeStruct.startsClzPrefix(ptr._obj._name) &&
fld!=null && fld._final && decl.isConstant() )
return TypeMem.make(_alias,val,true,true,true,mem._escFs,mem._escAs).escapesFrom(val);
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/Allocations.java b/chapter29/src/main/java/com/seaofnodes/simple/parser/Allocations.java
index c5b8566b..8405278c 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/parser/Allocations.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/Allocations.java
@@ -43,7 +43,7 @@ final class Allocations {
String typeName = ts._name.indexOf('.') == -1 ? P.types.fullTypeName(ts._name) : ts._name;
Type resolved = Parser.TYPES.get(typeName);
if( resolved instanceof TypeStruct rts ) ts = rts;
- TypeStruct clz = (TypeStruct)Parser.TYPES.get(Parser.addClzPrefix(typeName));
+ TypeStruct clz = (TypeStruct)Parser.TYPES.get(TypeStruct.addClzPrefix(typeName));
if( clz==null )
throw P.error("Unknown struct type '" + ts._name + "'");
Field afld = clz.field("<new>");
@@ -52,7 +52,7 @@ final class Allocations {
TypeFunPtr tfp = (TypeFunPtr)afld._t;
FunNode fun = P._code.link(tfp);
alloc = fun==null
- ? new FRefNode((Parser.addClzPrefix(typeName)+".<new>").intern(),P.loc())
+ ? new FRefNode((TypeStruct.addClzPrefix(typeName)+".<new>").intern(),P.loc())
: new FunPtrNode(tfp,P._code._start,fun.ret()).peephole();
} else {
if( !ts._fref )
@@ -60,7 +60,7 @@ final class Allocations {
Type[] sig = new Type[ctorArgs._len];
for( int i = 0; i<ctorArgs._len; i++ )
sig[i] = ctorArgs.at(i)._type;
- alloc = new FRefNode((Parser.addClzPrefix(typeName)+".<new>").intern(),P.loc(),
+ alloc = new FRefNode((TypeStruct.addClzPrefix(typeName)+".<new>").intern(),P.loc(),
TypeFunPtr.make(false,sig,TypeMemPtr.make(ts)));
}
Ary<Node> args = new Ary<>(Node.class);
@@ -83,7 +83,7 @@ final class Allocations {
Field lenFld = ts.field("#" );
Field bodyFld = ts.field("[]");
- ConFldOffNode base = Parser.off(ts, "[]");
+ ConFldOffNode base = ConFldOffNode.make(ts, "[]");
int scale = ts.aryScale();
Node size = P.peep(new AddNode(base,P.peep(new ShlNode(null,len,P.con(scale)))));
@@ -95,7 +95,7 @@ final class Allocations {
// Store length. Rest of array is zero'd via CALLOC during CodeGen.
// Length is casted to sanity.
// TODO: Needs runtime check.
- Node st = P.peep(new StoreNode(null, "#", 1, TypeInteger.U32, null, smem, self, Parser.off(ts,"#"), len.unkeep(), true));
+ 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
@@ -145,7 +145,7 @@ final class Allocations {
for( int i = 0; i<nargs; i++ )
parms[i] = new ParmNode("arg"+i,i+2,atypes[i],fun,ConstantNode.seed(atypes[i]).peephole()).peephole();
- NewNode nnn = new NewNode(ts,fun,Parser.off(ts," len")).init();
+ NewNode nnn = new NewNode(ts,fun,ConFldOffNode.make(ts," len")).init();
Node self = new ProjNode(nnn,0,ts.str()).init();
Node priv = new ProjNode(nnn,1,"#selfMem").init();
Node[] initArgs = new Node[]{fun,pub,self,priv,
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/Conversions.java b/chapter29/src/main/java/com/seaofnodes/simple/parser/Conversions.java
index d50e2031..fa11311d 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/parser/Conversions.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/Conversions.java
@@ -22,7 +22,7 @@ final class Conversions {
return expr;
// Auto-widen array to i64 (cast ptr to raw int bits)
if( t == TypeInteger.BOT && expr._type instanceof TypeMemPtr tmp && tmp._obj.isAry() )
- return P.peep(new AddNode(P.peep(new PtrToIntNode(expr)),Parser.off(tmp._obj,"[]")));
+ return P.peep(new AddNode(P.peep(new PtrToIntNode(expr)),ConFldOffNode.make(tmp._obj,"[]")));
// Widen ints to floats, narrow wide types
return P.peep(new ConvertNode(t,expr));
}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/Expressions.java b/chapter29/src/main/java/com/seaofnodes/simple/parser/Expressions.java
index c3971c63..93c07d8a 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/parser/Expressions.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/Expressions.java
@@ -314,7 +314,7 @@ final class Expressions {
(byte)ch == 1 ? P.con( 1) : // var++
(byte)ch == -1 ? P.con(-1) : // var--
parseAssignment(); // var op= rhs
- if( !Parser.postfix(ch) ) lhs.unkeep(); // Allow to die in next peep
+ if( !Lexer.postfix(ch) ) lhs.unkeep(); // Allow to die in next peep
// 4 cases:
// int + int ==>> narrow int
// int + flt ==>> error, caller must fail assigning flt into int
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/Lexer.java b/chapter29/src/main/java/com/seaofnodes/simple/parser/Lexer.java
index f2857c9f..ec88738b 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/parser/Lexer.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/Lexer.java
@@ -298,6 +298,11 @@ public class Lexer {
// Distinct tags for the multi-character compound operators.
static final char SHL = 2, SAR = 3, SHR = 4;
+ // ch is +/- 1, means oper++ or oper-- means postfix.
+ static boolean postfix(char ch) {
+ return (byte)ch== 1 || (byte)ch== -1;
+ }
+
public char matchOperAssign() {
skipWhiteSpace();
if( match("<<=" ) ) return SHL;
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/Parser.java b/chapter29/src/main/java/com/seaofnodes/simple/parser/Parser.java
index 913fd2b2..ccc9ebdc 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/parser/Parser.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/Parser.java
@@ -30,9 +30,6 @@ public class Parser {
// The Lexer. Thin wrapper over a byte[] buffer with a cursor.
Lexer _lexer;
- // Class-object name, distinct from its instance type.
- public static final String CLZ = "class:";
-
// Source file for compilation
public CompUnit _ref;
@@ -142,7 +139,7 @@ public class Parser {
// File-level struct declaration
_ref = ref;
_nestedType = ref._cname;
- String typeName = addClzPrefix(ref._cname);
+ String typeName = TypeStruct.addClzPrefix(ref._cname);
// Parse whole file, which is a struct-level class initializer
structs.parseInitializer( true, typeName );
@@ -163,32 +160,58 @@ public class Parser {
return frefs;
}
- @Override
- public String toString() { return _lexer==null ? "" : _lexer.toString(); }
+ // Cursor and token helpers
- // Debugging utility to find a Node by index
- public Node f(int nid) { return _code.f(nid); }
- public static String addClzPrefix( String x ) {
- assert !x.startsWith(CLZ);
- return (CLZ+x).intern();
+ // Return true and skip if "syntax" is next in the stream.
+ boolean match (String syntax) { return _lexer.match (syntax); }
+ // Match must be "exact", not be followed by more id letters
+ boolean matchx(String syntax) { return _lexer.matchx(syntax); }
+ boolean matchOp(char c0 ) { return _lexer.matchOp(c0); }
+ boolean matchOpx(char c0, char c1) { return _lexer.matchOpx(c0,c1); }
+ // Skip whitespace, but leave the next token unread.
+ boolean peek(char ch) { return _lexer.peek(ch); }
+
+ public int pos() { return _lexer._position; }
+ int pos(int pos) {
+ int old = _lexer._position;
+ _lexer._position = pos;
+ return old;
+ }
+ Type posT(int pos) { _lexer._position = pos; return null; }
+ // Source code location
+ Lexer loc() { return new Lexer(_lexer); }
+
+ // Require and return an identifier
+ String requireId() {
+ String id = _lexer.matchId();
+ if (id != null && !KEYWORDS.contains(id) ) return id.intern();
+ throw error("Expected an identifier, found '"+id+"'");
}
- public static boolean startsClzPrefix( String s ) {
- return s.startsWith(CLZ);
+
+ String matchId() {
+ int old = pos();
+ String id = _lexer.matchId();
+ if( id==null ) return null;
+ if( !KEYWORDS.contains(id) ) return id;
+ pos(old);
+ return null;
}
- public static boolean isPublicClassName( String name ) {
- if( startsClzPrefix(name) ) name = name.substring(CLZ.length());
- return !name.isEmpty() && name.charAt(0)!='_' && name.indexOf("._")==-1;
+
+ // Require an exact match
+ Parser require(String syntax) { require(null, syntax); return this; }
+ <N> N require(N n, String syntax) {
+ if (match(syntax)) return n;
+ throw errorSyntax(syntax);
}
+ // Scope, control, and memory
+
// Read and set CTRL
Node ctrl() { return _scope.ctrl(); }
<N extends Node> N ctrl(N n) { return _scope.ctrl(n); }
// Read and set Memory
Node mem() { return _scope.mem(1); }
void mem(Node mem) { _scope.mem(mem); }
- // Structural label for precise memory Phis. The '$' prefix cannot be
- // referenced in Simple source.
- public static String memName(int alias) { return ("$"+alias).intern(); }
// Ordinary parsing carries one whole-memory value. Alias partitioning is
// graph optimization, not parser state.
@@ -196,16 +219,6 @@ public class Parser {
mem(alias==1 ? st : mergeAlias(prior,alias,st));
}
- // A semantic graph partition representing the complete memory after a
- // precise alias update. ScopeNode treats this as one opaque value.
- Node mergeAlias(Node bulk, int alias, Node precise) {
- assert bulk != null : "Missing bulk memory for alias #"+alias;
- MemMergeNode merge = new MemMergeNode(null,bulk);
- merge.alias(alias,precise);
- return peep(merge);
- }
- ConstantNode con( long con ) { return _code.con(con); }
- ConstantNode con( Type t ) { return _code.con(t ); }
void classInit(Lexer site, Node ptr) {
// Ordinary load machinery resolves the flag alias and offset when
// the receiver is still a forward reference.
@@ -217,14 +230,23 @@ public class Parser {
Node bulk = new ProjNode(init,1,ScopeNode.MEM0).peephole().keep();
flag = init.flag();
mem(flag==null ? bulk : mergeAlias(bulk,flag._alias,
- new ProjNode(init,2,memName(flag._alias)).peephole()));
+ new ProjNode(init,2,ScopeNode.memName(flag._alias)).peephole()));
bulk.unkeep();
init.unkeep();
}
- // Field offset for a known type (but unknown offset)
- public static ConFldOffNode off( TypeStruct ts, String fname ) {
- return (ConFldOffNode)(new ConFldOffNode(ts,fname).peephole());
+ // IR construction using the current parser context
+
+ ConstantNode con( long con ) { return _code.con(con); }
+ ConstantNode con( Type t ) { return _code.con(t ); }
+
+ // A semantic graph partition representing the complete memory after a
+ // precise alias update. ScopeNode treats this as one opaque value.
+ Node mergeAlias(Node bulk, int alias, Node precise) {
+ assert bulk != null : "Missing bulk memory for alias #"+alias;
+ MemMergeNode merge = new MemMergeNode(null,bulk);
+ merge.alias(alias,precise);
+ return peep(merge);
}
// Field offset for an unknown type
public Node fldoff( Node n, String name ) {
@@ -237,57 +259,7 @@ public class Parser {
return _scope.upcastGuard(n.peephole());
}
- // Parse a string or null
-
- //////////////////////////////////
- // Utilities for lexical analysis
-
- // Return true and skip if "syntax" is next in the stream.
- boolean match (String syntax) { return _lexer.match (syntax); }
- // Match must be "exact", not be followed by more id letters
- boolean matchx(String syntax) { return _lexer.matchx(syntax); }
- boolean matchOp(char c0 ) { return _lexer.matchOp(c0); }
- boolean matchOpx(char c0, char c1) { return _lexer.matchOpx(c0,c1); }
- // Return true and do NOT skip if 'ch' is next
- boolean peek(char ch) { return _lexer.peek(ch); }
-
- // ch is +/- 1, means oper++ or oper-- means postfix
- static boolean postfix(char ch) {
- return (byte)ch== 1 || (byte)ch== -1;
- }
-
- public int pos() { return _lexer._position; }
- int pos(int pos) {
- int old = _lexer._position;
- _lexer._position = pos;
- return old;
- }
- Type posT(int pos) { _lexer._position = pos; return null; }
- // Source code location
- Lexer loc() { return new Lexer(_lexer); }
-
- // Require and return an identifier
- String requireId() {
- String id = _lexer.matchId();
- if (id != null && !KEYWORDS.contains(id) ) return id.intern();
- throw error("Expected an identifier, found '"+id+"'");
- }
-
- String matchId() {
- int old = pos();
- String id = _lexer.matchId();
- if( id==null ) return null;
- if( !KEYWORDS.contains(id) ) return id;
- pos(old);
- return null;
- }
-
- // Require an exact match
- Parser require(String syntax) { require(null, syntax); return this; }
- <N> N require(N n, String syntax) {
- if (match(syntax)) return n;
- throw errorSyntax(syntax);
- }
+ // Diagnostics
ParseException errorSyntax(String syntax) { return _errorSyntax("expected `"+syntax+"`"); }
private ParseException _errorSyntax(String msg) {
@@ -295,4 +267,12 @@ public class Parser {
}
ParseException error(String msg) { return error(msg,_lexer); }
public static ParseException error(String msg, Lexer loc) { return new ParseException(msg,loc); }
+
+ // Debugging
+
+ @Override
+ public String toString() { return _lexer==null ? "" : _lexer.toString(); }
+
+ // Debugging utility to find a Node by index
+ public Node f(int nid) { return _code.f(nid); }
}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/README.md b/chapter29/src/main/java/com/seaofnodes/simple/parser/README.md
index 02a1a2d0..e3e94b97 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/parser/README.md
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/README.md
@@ -24,3 +24,8 @@ productions to their entry points.
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.
+
+`Parser` keeps the short cursor, scope/memory, IR-building, and diagnostic helpers
+used by these components. Independent helpers live with their data: class-object
+names in `TypeStruct`, postfix operator encoding in `Lexer`, known field-offset
+creation in `ConFldOffNode.make`, and memory-alias labels in `ScopeNode.memName`.
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/References.java b/chapter29/src/main/java/com/seaofnodes/simple/parser/References.java
index 4b474cb8..a22383cf 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/parser/References.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/References.java
@@ -46,7 +46,7 @@ final class References {
CompUnit cu = ParseAll.findCompUnit(P._code,P._ref,id);
if( cu!=null ) {
// Must be a final class-type
- String clzName = Parser.addClzPrefix(cu._cname);
+ String clzName = TypeStruct.addClzPrefix(cu._cname);
TypeStruct clz = (TypeStruct)Parser.TYPES.get(clzName);
if( clz == null )
Parser.TYPES.put(clzName,clz = TypeStruct.make(clzName,true));
@@ -113,8 +113,8 @@ final class References {
checkSharedFunction(var);
Node op = P.expressions.opAssign(ch,rvalue, var.type() );
P._scope.update(var,op);
- if( Parser.postfix(ch) ) rvalue.unkeep();
- return pre==0 && Parser.postfix(ch) ? rvalue : op;
+ if( Lexer.postfix(ch) ) rvalue.unkeep();
+ return pre==0 && Lexer.postfix(ch) ? rvalue : op;
}
void checkAssignment(Var def) {
@@ -210,8 +210,8 @@ final class References {
}
Field fld = ts==null ? null : ts.field(name);
- if( fld==null && ts!=null && !Parser.startsClzPrefix(ts._name) ) {
- Type clz0 = Parser.TYPES.get(Parser.addClzPrefix(ts._name));
+ if( fld==null && ts!=null && !TypeStruct.startsClzPrefix(ts._name) ) {
+ Type clz0 = Parser.TYPES.get(TypeStruct.addClzPrefix(ts._name));
if( clz0 instanceof TypeStruct clz && (fld = P.types.latestStruct(clz).field(name)) != null ) {
expr.unkeep();
ts = P.types.latestStruct(clz);
@@ -227,7 +227,7 @@ final class References {
// need to force class initialization. Basically, we insert an CLInit,
// and if it does not fold away it'll turn into a runtime check.
Kind.Func func = (Kind.Func)P._scope._kinds.at(P._scope.enclosingFuncOrDecl());
- if( (ts==null || ts==TypeStruct.BOT || ts.isHigh() || Parser.startsClzPrefix(ts._name)) &&
+ if( (ts==null || ts==TypeStruct.BOT || ts.isHigh() || TypeStruct.startsClzPrefix(ts._name)) &&
!(ts!=null && (ts._name+".<clinit>").equals(func._name)) )
P.classInit(site,expr);
@@ -287,8 +287,8 @@ final class References {
mem.unkeep();
op.unkeep(); // A narrow Store may have folded away the expression's conversion.
- if( Parser.postfix(ch) ) load.unkeep();
- return pre==0 && Parser.postfix(ch) ? load : op;
+ if( Lexer.postfix(ch) ) load.unkeep();
+ return pre==0 && Lexer.postfix(ch) ? load : op;
}
off.unkill();
// Might be a method call, so pass expr as 'self'
@@ -327,8 +327,8 @@ final class References {
checkSharedFunction(v);
Node op = P.expressions.opAssign(ch,value,v.type());
P._scope.update(v,op);
- if( pre!=0 && Parser.postfix(ch) ) value.unkeep();
- return pre==0 && Parser.postfix(ch) ? value.unkeep() : op;
+ if( pre!=0 && Lexer.postfix(ch) ) value.unkeep();
+ return pre==0 && Lexer.postfix(ch) ? value.unkeep() : op;
}
return P.expressions.parsePostfixMethod(value,defaultSelf());
}
@@ -360,7 +360,7 @@ final class References {
for( FRefNode fref : _frefs ) {
if( fref.nIns() != 1 && fref._con == FRefNode.FREF_TYPE ) continue;
CompUnit cu = ParseAll.findCompUnitOrThrow(P._code,P._ref,fref._name);
- fref._con = TypeMemPtr.make((byte)2,TypeStruct.make(Parser.addClzPrefix(cu._cname),true),true);
+ fref._con = TypeMemPtr.make((byte)2,TypeStruct.make(TypeStruct.addClzPrefix(cu._cname),true),true);
frefs.push(fref);
}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/Structs.java b/chapter29/src/main/java/com/seaofnodes/simple/parser/Structs.java
index db17cea3..57b43715 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/parser/Structs.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/Structs.java
@@ -3,17 +3,11 @@ 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;
-import java.util.IdentityHashMap;
/** Struct and constructor declarations, field discovery, and initialization checks. */
final class Structs {
private final Parser P;
- // Constructor exit types survive scope teardown until all fields are
- // known. A null entry denotes a constructor with no returning path.
- Ary<IdentityHashMap<Var,Type>> _ctorExits;
-
// Constructor declaration: new TypeName = { args -> body };
record Constructor(String name, Lexer body) {}
Constructor _pendingCtor;
@@ -39,12 +33,12 @@ final class Structs {
ReturnNode ret = parseInitializer( false, fullName );
- P._nestedType = old;
+ P._nestedType = old; // Pop nested stack
// Insert a field into class:NESTED.typeName of "val typeName = fcn-ptr-to-init"
// Function does malloc internally.
// Future self: means subtyping has to deal with "who does the malloc"
- String clzName = Parser.addClzPrefix(fullName);
+ String clzName = TypeStruct.addClzPrefix(fullName);
TypeFunPtr sig = ret.fun().sig();
TypeStruct ts = (TypeStruct)Parser.TYPES.get(clzName);
String initName = initFieldName(fullName);
@@ -109,7 +103,7 @@ final class Structs {
TypeStruct self = P._scope.constructorSelf();
Node ctor = P.functions.parseConstructor(name);
TypeFunPtr sig = (TypeFunPtr)ctor._type;
- String clzName = Parser.addClzPrefix(self._name);
+ String clzName = TypeStruct.addClzPrefix(self._name);
TypeStruct clz = (TypeStruct)Parser.TYPES.get(clzName);
Field old = clz==null ? null : clz.field("<ctor>");
if( old != null )
@@ -122,8 +116,6 @@ final class Structs {
// Parse a struct declaration (not an allocation); file-level is a
// class-init, and scope structs are normal
ReturnNode parseInitializer( boolean isClz, String typeName ) {
- var oldExits = _ctorExits;
- _ctorExits = new Ary(IdentityHashMap.class);
// Record & restore global state set during parsing the <init> code
Node oldCtrl= P._scope.ctrl().keep();
Node oldMem = P._scope.mem ().keep();
@@ -147,7 +139,7 @@ final class Structs {
// Struct decls look like function bodies. Parse function body normally.
ReturnNode ret = P.functions.parseBody(fname,sig,P.loc(),ids);
- _ctorExits = oldExits;
+ if( !isClz ) checkInitFields(ret);
// Unwind global state.
P.ctrl(oldCtrl.unkeep());
P.mem (oldMem .unkeep());
@@ -168,8 +160,8 @@ final class Structs {
String clzBare = tself._name;
int order = 3; // 3 is always the <init> order, 2nd in the file just behind the <clinit>
if( isClz ) {
- assert Parser.startsClzPrefix( clzBare );
- clzBare = clzBare.substring(Parser.CLZ.length()).intern();
+ assert TypeStruct.startsClzPrefix( clzBare );
+ clzBare = clzBare.substring(TypeStruct.CLZ.length()).intern();
order = 2; // 2 is always the <clinit> order, first in file and skipping reserved #1
}
return TypeFunPtr.make1((byte)2, true, targs, tret, P._code.fidx(clzBare,order));
@@ -232,20 +224,6 @@ final class Structs {
smem._declaredType = smem._type = TypeMem.makePrivate(tself);
}
- if( !upgrade ) {
- IdentityHashMap<Var,Type> exit = null;
- if( P.functions._returnScope!=null && !P.functions._returnScope.ctrl()._type.isHigh() ) {
- exit = new IdentityHashMap<>();
- int first = P._scope._kinds.at(P._scope.depth()-2)._lexSize+2;
- for( int i = first; i<lex; i++ )
- exit.put(P._scope.var(i),P.functions._returnScope.in(i)._type);
- }
- _ctorExits.add(exit);
- } else if( !isClz ) {
- // Check that all fields are initialized
- checkInitFields(tself,base);
- }
-
// When can _returnScope be null here? A never-exit constructor will
// not have any returns, and thus no need to gather values and store
// them into the (never) constructed object
@@ -267,19 +245,22 @@ final class Structs {
continue;
Var v = P._scope.var(vidx);
if( v._fref || v._extern!=0 ) continue; // C storage has no Simple initializer
- // NewNode's TOP field is the zero-code poison for a required
- // field. Leave it alone unless the declaration body supplied a
- // value; otherwise a user constructor must initialize it.
Node val = P.functions._returnScope.in(vidx);
- if( upgrade && v._uninit &&
- !initialized(val._type) ) continue;
+ // Preserve a missing field in the returned private memory. A
+ // numeric conversion would turn BOTTOM into an ordinary number
+ // type, hiding a path that never assigned the field.
+ if( !isClz && v._uninit && !initialized(val._type) ) {
+ TypeStruct missing = (TypeStruct)((TypeMem)smem._declaredType)._t;
+ smem._declaredType = smem._type = TypeMem.makePrivate(missing.addOrUpdate(fld.makeFrom(Type.TOP)));
+ continue;
+ }
// A numeric merge can still be untyped while parsing. Retain its
// declared conversion at the helper boundary, as assignments do.
if( fld._t instanceof TypeInteger || fld._t instanceof TypeFloat )
val = P.conversions.convertExpr(val,fld._t);
// Store value into extended struct
Node prior = mmm.keep();
- Node st = P.peep(new StoreNode(null, fld._fname, fld._alias, fld._t, null, prior, self, Parser.off(tself,fld._fname), val, fld._final));
+ Node st = P.peep(new StoreNode(null, fld._fname, fld._alias, fld._t, null, prior, self, ConFldOffNode.make(tself,fld._fname), val, fld._final));
mmm = P.mergeAlias(prior,fld._alias,st);
prior.unkeep();
}
@@ -295,24 +276,34 @@ final class Structs {
//_code.add(mmm);
}
- // Check that all fields are initialized on all constructor exit paths.
- private void checkInitFields(TypeStruct ts, int base) {
- if( P.functions._returnScope==null || P.functions._returnScope.ctrl()._type.isHigh() ) return;
- boolean implicit = _ctorExits.isEmpty();
- if( implicit ) _ctorExits.add(new IdentityHashMap<>());
- for( IdentityHashMap<Var,Type> exit : _ctorExits ) {
- if( exit==null ) continue;
- for( Field fld : ts._fields ) {
- int idx = fieldVarIdx(base,fld._fname);
- if( idx == -1 ) continue;
- Var v = P._scope.var(idx);
- if( !v._uninit || v._fref ) continue;
- Type t = exit.getOrDefault(v,P.functions._returnScope.in(idx)._type);
- if( !initialized(t) )
- throw Parser.error("'"+ts._name+"' is not fully initialized, field '"+fld._fname+
- (implicit ? "' needs to be set in a constructor" :
- "' is only partially set in the constructor"),v._loc);
- }
+ // addReturn already merged every exit. Inspect the completed helpers,
+ // after parsing has discovered all fields and torn down their scopes.
+ private void checkInitFields(ReturnNode init) {
+ if( init.ctrl()._type.isHigh() || init.expr()._type.isHigh() ) return;
+ TypeStruct ts = ((TypeMemPtr)init.fun().sig().arg(0))._obj;
+ TypeStruct clz = (TypeStruct)Parser.TYPES.get(TypeStruct.addClzPrefix(ts._name));
+ Field ctor = clz==null ? null : clz.field("<ctor>");
+ // The older same-named function binding is parsed immediately, so
+ // fields declared after it are supplied by the declaration initializer.
+ Field legacy = ts.field(initFieldName(ts._name));
+ if( ctor!=null ) checkInitFields(ts,init,P._code.link((TypeFunPtr)ctor._t).ret());
+ if( legacy!=null && legacy._t instanceof TypeFunPtr sig )
+ checkInitFields(ts,init,P._code.link(sig).ret());
+ else if( ctor==null ) checkInitFields(ts,init,init);
+ }
+
+ private void checkInitFields(TypeStruct ts, ReturnNode init, ReturnNode ret) {
+ if( ret.ctrl()._type.isHigh() || ret.expr()._type.isHigh() ) return;
+ TypeStruct fields = (TypeStruct)((TypeMem)ret.expr()._type)._t;
+ TypeStruct defaults = (TypeStruct)((TypeMem)init.expr()._type)._t;
+ for( Field fld : ts._fields ) {
+ if( fld._extern ) continue;
+ Field actual = fields.field(fld._fname);
+ if( actual==null ) actual = defaults.field(fld._fname);
+ if( actual._t==Type.TOP )
+ throw Parser.error("'"+ts._name+"' is not fully initialized, field '"+fld._fname+
+ (ret==init ? "' needs to be set in a constructor" :
+ "' is only partially set in the constructor"),ret.fun()._loc);
}
}
@@ -326,7 +317,7 @@ final class Structs {
Var v = P._scope.var(idx);
if( v._fref || v._extern!=0 || v._uninit || P._scope.in(v)._type.isConstant() ) continue;
Node value = P.conversions.convertExpr(
- new LoadNode(loc,fld._fname,fld._alias,P.ctrl(),fun.parm(3),fun.parm(2),Parser.off(shape,fld._fname)).peephole(),v.type());
+ new LoadNode(loc,fld._fname,fld._alias,P.ctrl(),fun.parm(3),fun.parm(2),ConFldOffNode.make(shape,fld._fname)).peephole(),v.type());
P._scope.update(v,value);
}
}
@@ -336,7 +327,7 @@ final class Structs {
// run-once flag. Keep this in the body so every execution strategy
// shares the same ordering, including direct entry into a child.
if( P._ref._par!=null ) {
- String pname = Parser.addClzPrefix(P._ref._par._cname);
+ String pname = TypeStruct.addClzPrefix(P._ref._par._cname);
TypeStruct parent = (TypeStruct)Parser.TYPES.get(pname);
if( parent==null ) parent = TypeStruct.make(pname,true);
Node ptr = P.con(TypeMemPtr.make((byte)2,parent,true)).keep();
@@ -352,8 +343,7 @@ final class Structs {
prior.unkeep();
}
- // Validate each user constructor, or an implicit empty one, before the
- // declaration scope goes away. Later fields use their declaration defaults.
+ // Required-field values must be initialized before conversion for storage.
static boolean initialized(Type t) {
// Can't be BOTTOM, that means explicit not initialized.
// Can't be high (initialized to an error)
@@ -403,8 +393,8 @@ final class Structs {
}
private static String initFieldName( String typeName ) {
- if( Parser.startsClzPrefix(typeName) )
- typeName = typeName.substring(Parser.CLZ.length());
+ if( TypeStruct.startsClzPrefix(typeName) )
+ typeName = typeName.substring(TypeStruct.CLZ.length());
int idx = typeName.lastIndexOf('.');
return (idx == -1 ? typeName : typeName.substring(idx+1)).intern();
}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/Types.java b/chapter29/src/main/java/com/seaofnodes/simple/parser/Types.java
index 79ac9d12..970b5a50 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/parser/Types.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/Types.java
@@ -86,7 +86,7 @@ final class Types {
TypeMemPtr forwardRefType( String tname, TypeStruct tself ) {
Parser.TYPES.put(tname,tself);
- String clzName = Parser.addClzPrefix( tname );
+ String clzName = TypeStruct.addClzPrefix( tname );
// Only a struct declaration publishes an instance initializer. A
// forward type may name a file with just <clinit>, so do not invent
// a constructor field (or overwrite an already parsed class).
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
index 4e73e148..9c0c9693 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
@@ -126,9 +126,9 @@ public class TypeStruct extends Type {
// With a user constructor, the same-named function field is the hidden
// default <init> allocation helper, not part of the public class surface.
public boolean hiddenInit(Field fld) {
- if( field("<ctor>") == null || !_name.startsWith("class:") ) return false;
+ if( field("<ctor>") == null || !startsClzPrefix(_name) ) return false;
int idx = _name.lastIndexOf('.');
- String name = _name.substring(idx<0 ? "class:".length() : idx+1);
+ String name = _name.substring(idx<0 ? CLZ.length() : idx+1);
return fld._fname.equals(name);
}
@@ -653,4 +653,19 @@ public class TypeStruct extends Type {
return is;
}
+
+ // Class-object name, distinct from its instance type.
+ public static final String CLZ = "class:";
+
+ public static String addClzPrefix( String x ) {
+ assert !x.startsWith(CLZ);
+ return (CLZ+x).intern();
+ }
+ public static boolean startsClzPrefix( String s ) {
+ return s.startsWith(CLZ);
+ }
+ public static boolean isPublicClassName( String name ) {
+ if( startsClzPrefix(name) ) name = name.substring(CLZ.length());
+ return !name.isEmpty() && name.charAt(0)!='_' && name.indexOf("._")==-1;
+ }
}
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/ConstructorCompletionTest.java b/chapter29/src/test/java/com/seaofnodes/simple/ConstructorCompletionTest.java
new file mode 100644
index 00000000..6a229a4f
--- /dev/null
+++ b/chapter29/src/test/java/com/seaofnodes/simple/ConstructorCompletionTest.java
@@ -0,0 +1,77 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.parser.Parser;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class ConstructorCompletionTest {
+ private void rejects(String fields, String field, boolean explicit) {
+ String src = "struct B {}; struct A { "+fields+" }; return 0;";
+ try {
+ new CodeGen(src).parse();
+ fail(src);
+ } catch( Parser.ParseException e ) {
+ assertEquals("'Test.A' is not fully initialized, field '"+field+
+ (explicit ? "' is only partially set in the constructor" :
+ "' needs to be set in a constructor"),e.getMessage());
+ }
+ }
+
+ @Test public void testMissingNumericFields() {
+ for( String type : new String[]{"int", "u8", "f32"} ) {
+ rejects(type+"~ x;","x",false);
+ rejects(type+"~ x; new A = { -> };","x",true);
+ // Converting the merged BOTTOM to the numeric storage type used
+ // to hide the missing assignment from the returned memory.
+ rejects(type+"~ x; new A = { -> if(arg) x = 1; };","x",true);
+ rejects(type+"~ x; new A = { -> if(arg) return 0; x = 1; };","x",true);
+ rejects(type+"~ x; new A = { -> while(arg--) x = 1; };","x",true);
+ }
+ }
+
+ @Test public void testInitializerAndConstructorReturns() {
+ rejects("B b;","b",false);
+ rejects("new A = { -> if(arg) b = new B; }; B b;","b",true);
+ String[] fields = {
+ "int~ x; new A = { -> if(arg) { x = 3; return 0; } x = 7; };",
+ // <init> has no value for x; the user constructor supplies it.
+ "new A = { -> x = 7; }; int~ x;",
+ // The user constructor inherits the declaration's default.
+ "new A = { -> }; int~ x = 7;",
+ "B? b; int x;",
+ };
+ String[][] values = {{"7","3"}, {"7","7"}, {"7","7"}, {"0","0"}};
+ for( int i = 0; i<fields.length; i++ ) {
+ CodeGen code = new CodeGen("struct B {}; struct A { "+fields[i]+" }; return new A().x;")
+ .driver(CodeGen.Phase.TypeCheck);
+ assertEquals(values[i][0],Eval2.eval(code,0));
+ assertEquals(values[i][1],Eval2.eval(code,1));
+ }
+ }
+
+ @Test public void testNonreturningPaths() {
+ // An initializer parsed in dead enclosing code can have TOP memory
+ // rather than a struct-shaped result.
+ new CodeGen("while(false) { struct A { int x; }; } return 0;").parse();
+ new CodeGen("struct A { int~ x; new A = { -> while(true) {} }; }; return 0;").parse();
+ rejects("int~ x; new A = { -> if(arg) return 0; while(true) {} };","x",true);
+ CodeGen code = new CodeGen("""
+ struct A {
+ int~ x;
+ new A = { -> if(arg) { x = 7; return 0; } while(true) {} };
+ };
+ return new A().x;
+ """).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("7",Eval2.eval(code,1));
+ }
+
+ @Test public void testEarlierConstructorBinding() {
+ // The older binding spelling is parsed before the following field.
+ // Its Return lacks that field, so completion must consult <init> too.
+ rejects("val A = { -> }; B b;","b",true);
+ rejects("int~ x; val A = { -> if(arg) x = 1; };","x",true);
+ new CodeGen("struct B {}; struct A { val A = { -> }; B b = new B; }; return 0;").parse();
+ new CodeGen("struct A { val A = { -> while(true) {} }; int~ x; }; return 0;").parse();
+ }
+}
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/Eval2.java b/chapter29/src/test/java/com/seaofnodes/simple/Eval2.java
index 6e5331b2..8d0e3d5b 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/Eval2.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/Eval2.java
@@ -1,6 +1,5 @@
package com.seaofnodes.simple;
-import com.seaofnodes.simple.parser.Parser;
import com.seaofnodes.simple.codegen.*;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.print.IRPrinter;
@@ -372,7 +371,7 @@ public abstract class Eval2 {
yield new Object[0];
}
- } else if( Parser.startsClzPrefix(tmp._obj._name) ) {
+ } else if( TypeStruct.startsClzPrefix(tmp._obj._name) ) {
yield classObject(tmp._obj);
} else {
// Constant object with all zeros