summaryrefslogtreecommitdiff
path: root/chapter29
diff options
context:
space:
mode:
Diffstat (limited to 'chapter29')
-rw-r--r--chapter29/README.ja.md19
-rw-r--r--chapter29/README.md42
-rw-r--r--chapter29/docs/29-grammar.ja.md8
-rw-r--r--chapter29/docs/29-grammar.md8
-rw-r--r--chapter29/docs/construction-groups.md131
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/Iter.java19
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java9
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/Serialize.java9
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java3
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/ArithNode.java13
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/BoolNode.java18
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java14
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/ConstructNode.java28
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/ConvertNode.java12
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/LoadNode.java31
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/MemOpNode.java3
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/MinusNode.java4
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/MulNode.java8
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/NewNode.java6
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/NotNode.java3
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/ParmNode.java19
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/PhiNode.java13
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/RegionNode.java2
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/ReturnNode.java16
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/ScopeNode.java6
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/StoreNode.java13
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/Allocations.java12
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/Construction.java145
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/Declarations.java4
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/Functions.java57
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/Parser.java4
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/README.md7
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/References.java129
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/Structs.java162
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/Types.java1
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/Var.java5
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/type/TypeMem.java9
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java60
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/type/TypeStruct.java13
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/ArithNodeTest.java31
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/BoundaryTest.java5
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/Chapter10Test.java14
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/Chapter12Test.java2
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/Chapter18Test.java2
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/Chapter19Test.java2
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/ConstructionGroupTest.java135
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/ConstructionRequirementsTest.java222
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/ConstructorCompletionTest.java42
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/FinalConstructionTest.java9
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/TypeTest.java4
50 files changed, 1162 insertions, 371 deletions
diff --git a/chapter29/README.ja.md b/chapter29/README.ja.md
index 54c7b5a6..8a3ff493 100644
--- a/chapter29/README.ja.md
+++ b/chapter29/README.ja.md
@@ -4,8 +4,8 @@
[前の章: 第28b章 — 分離コンパイル](../chapter28b/README.ja.md)
-この章では、第28b章の言語と分離コンパイルの動作を保ちながら、コンパイラを
-読みやすく、変更しやすく整理します。最初の変更は、3,000行を超えたパーサを
+この章では、コンパイラを読みやすく、変更しやすく整理します。
+最初の変更は、3,000行を超えたパーサを
文法のまとまりに沿って `parser/` パッケージへ分割することです。
`Parser` がカーソル、スコープ、型の登録情報を持ち、各部品は `Parser P` を
@@ -14,10 +14,21 @@
[パーサの構成(英語)](src/main/java/com/seaofnodes/simple/parser/README.md)と
[文法と実装の対応表(英語)](docs/29-grammar.md)からコードをたどれます。
-初期化完了チェックは、構文解析後のヘルパー関数の Return を調べます。
-未初期化の必須フィールドを private memory に残すため、終了経路の別の一覧は不要です。
+インスタンスのフィールドは、初期化中も通常の Load と Store を使います。
+各終了経路のメモリは Return に統合し、最後に全フィールドを再格納する処理は行いません。
+未初期化のフィールドは `TypeScalar.BOT` で表し、meet でも未初期化の分岐が残ります。
+通常のメモリ型の前向き解析で初期化を確認します。詳細は `Construction.java` の
+クラスコメントに記載しています。クラスのシングルトン初期化は従来の方式を維持します。
ソース上のプライバシー制約は今回は追加せず、[ロードマップ](../docs/roadmap.md)に残します。
+[構築グループの実験(英語)](docs/construction-groups.md)では、コンストラクタの
+`C ^arg` 引数で未完成の参照を渡せます。未完成の引数を受け取る new の結果も未完成です。
+`TypeMemPtr` の修飾子を通常の型解析で伝播し、一時的な `ConstructNode` が
+各 new の実引数から結果の修飾子を計算します。
+未完成のオブジェクトのフィールドは self 以外から読めず、通常の関数呼び出しには
+完成した参照が必要です。フィールド要件の推論は後回しにし、対応するテストを保存しています。
+`^` を使うコンストラクタの分離コンパイルは未対応です。
+
リポジトリのルートからビルドとテストを実行します。
```sh
diff --git a/chapter29/README.md b/chapter29/README.md
index 15ddc1a9..28925606 100644
--- a/chapter29/README.md
+++ b/chapter29/README.md
@@ -4,8 +4,7 @@ English | [日本語](README.ja.md)
[Previous: Chapter 28b — Separate Compilation](../chapter28b/README.md)
-This chapter makes the compiler easier to read and change while preserving
-Chapter 28b's language and separate-compilation behavior. It starts by breaking
+This chapter makes the compiler easier to read and change. It starts by breaking
the parser into grammar-sized pieces. Further cleanup belongs here; earlier
chapters retain their teaching snapshots.
@@ -35,21 +34,30 @@ other packages. `IterPeeps` now lives in `codegen/` beside the compilation passe
## 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.
+Instance fields use ordinary Loads and Stores during declaration initialization
+and constructor execution. One hidden private-memory value flows through scope
+merges and `addReturn`; field values are no longer copied into memory at exit.
+
+Fresh fields start at `TypeScalar.BOT`, which preserves a missing live arm at a
+merge. Forward memory types supply the initialization proof at Loads and merged
+Returns. Checks run after parsing and an optimistic type solve; there is no
+separate initialization walk or field-state map. Class singleton initialization
+retains its existing store-at-exit path.
+
+`ConstructorCompletionTest` covers partial assignments, early returns, loops,
+declaration defaults and immediate field access, and the formerly disabled
+required-number/nullable-default regression. The large comment on
+[`Construction`](src/main/java/com/seaofnodes/simple/parser/Construction.java)
+explains the type proof and the separate yes/no restriction on references.
+
+The [construction-group experiment](docs/construction-groups.md) adds `C ^arg`
+constructor parameters. A new result is incomplete iff an actual argument is
+incomplete. A `TypeMemPtr` qualifier propagates this through normal type flow;
+a temporary `ConstructNode` types each new result from that call's arguments.
+Only self may be read while incomplete; ordinary calls require completed
+objects. Field sharpening and inferred requirements are deferred,
+with their litmus tests preserved in `ConstructionRequirementsTest`. Separate
+compilation of constructors using `^` remains unsupported.
## Further cleanup
diff --git a/chapter29/docs/29-grammar.ja.md b/chapter29/docs/29-grammar.ja.md
index 48251d9b..b7794410 100644
--- a/chapter29/docs/29-grammar.ja.md
+++ b/chapter29/docs/29-grammar.ja.md
@@ -41,7 +41,13 @@ structDeclaration : 'struct' IDENTIFIER '{' structMember* '}' ';' ;
structMember : constructorDeclaration | methodDeclaration | statement ;
// The constructor name must match the enclosing struct's name.
-constructorDeclaration : 'new' IDENTIFIER '=' functionExpression ';' ;
+constructorDeclaration : 'new' IDENTIFIER '=' constructorExpression ';' ;
+constructorExpression : '{' constructorParameterList? '->' statement* '}' ;
+constructorParameterList : (constructorParameter ','?)+ ;
+constructorParameter : type '^'? bindingModifier? IDENTIFIER ;
+// ^ accepts an unfinished struct reference; ? independently permits null.
+// Only self may be read while incomplete; ^ arguments may be stored/forwarded.
+// A new result is incomplete iff an actual argument is. See construction-groups.md.
// A method is an ordinary fixed function binding in a struct definition.
// The compiler supplies self; it is not written in parameterList.
diff --git a/chapter29/docs/29-grammar.md b/chapter29/docs/29-grammar.md
index 58c9b30c..cb231ce0 100644
--- a/chapter29/docs/29-grammar.md
+++ b/chapter29/docs/29-grammar.md
@@ -47,7 +47,13 @@ structDeclaration : 'struct' IDENTIFIER '{' structMember* '}' ';' ;
structMember : constructorDeclaration | methodDeclaration | statement ;
// The constructor name must match the enclosing struct's name.
-constructorDeclaration : 'new' IDENTIFIER '=' functionExpression ';' ;
+constructorDeclaration : 'new' IDENTIFIER '=' constructorExpression ';' ;
+constructorExpression : '{' constructorParameterList? '->' statement* '}' ;
+constructorParameterList : (constructorParameter ','?)+ ;
+constructorParameter : type '^'? bindingModifier? IDENTIFIER ;
+// ^ accepts an unfinished struct reference; ? independently permits null.
+// Only self may be read while incomplete; ^ arguments may be stored/forwarded.
+// A new result is incomplete iff an actual argument is. See construction-groups.md.
// A method is an ordinary fixed function binding in a struct definition.
// The compiler supplies self; it is not written in parameterList.
diff --git a/chapter29/docs/construction-groups.md b/chapter29/docs/construction-groups.md
new file mode 100644
index 00000000..c817626e
--- /dev/null
+++ b/chapter29/docs/construction-groups.md
@@ -0,0 +1,131 @@
+# Construction: field types and incomplete references
+
+Instance constructors use ordinary field Loads and Stores. There are two checks:
+whether a field has a value, and whether an object reference is usable outside
+construction. Neither requires a collection of constructor-group identities.
+
+## Field initialization
+
+New produces private `TypeMem` containing the object's `TypeStruct`, with
+`TypeScalar.BOT` for fresh fields. Defaults emit ordinary Stores: zero for an
+ordinary integer, null for a nullable pointer, no Store for a required field.
+An assignment strongly updates its field in private memory. Phi meets the
+incoming memories; a missing live arm survives because `BOT.meet(7) == BOT`.
+An initialized unknown integer has `TypeInteger.BOT`, not `TypeScalar.BOT`.
+Arrays retain their language-level zero filling.
+
+One `#selfMem` binding carries this memory through scopes and returns. A Load
+checks its incoming field type. Constructor completion checks the merged Return
+memory; the explicit constructor receives the declaration initializer's merged
+result, including early exits. There is no backwards initialization query,
+per-field parser state, call-site snapshot, or store-all instance exit.
+
+After parsing closes loops and resolves names, `Iter.types` computes an
+optimistic fixed point using the existing node types, compute methods, and
+worklist, without graph rewrites. Optimism matters: an initialized field carried
+unchanged around a loop must not remain stuck at a pessimistic BOT seed. Numeric
+fields in loop memory widen as scalar loop values do. Constructor Loads remain
+kept until checking, so an unused or folded result cannot hide an illegal read.
+BOT describes the incoming field contents, not a taint on later computations.
+Conversions and numeric Stores retain their existing declared-type bounds;
+their narrowing cannot excuse the original Load, which is checked independently.
+
+Internal construction memory parameters use actual allocation/initializer call
+edges; the hypothetical external caller does not supply their memory. They
+retain their memory family when there are no live callers. Function-valued
+fields retain their declared signatures in private summaries, avoiding repeated
+unfolding of a constructor whose returned memory contains its own function field.
+Class singleton initialization keeps its older store-at-exit implementation.
+
+## A yes/no rule for references
+
+`self` and incoming `C ^arg` constructor parameters are incomplete references.
+`C? ^arg` also accepts null. The qualifier is permitted only on struct parameters
+of explicit constructors, not on arrays or ordinary function parameters.
+
+- An allocation result is incomplete if any actual argument is incomplete.
+- With only completed arguments, including null, the result is complete.
+- An incomplete reference may be stored into self or passed to a `^` parameter.
+- Ordinary calls and stores into other objects require completed references.
+- Field lookup on an incomplete object is forbidden, except through self.
+ Exact local aliases of self share its current private memory.
+- An object-reference field read from incomplete self is conservatively
+ incomplete. There is no inference of initialized-field requirements on `^`
+ arguments, nor permission to traverse a constructed member before its root
+ construction finishes. Arrays are already initialized and cannot receive
+ incomplete elements, so an initialized array field can be used normally.
+
+For example:
+
+```simple
+struct Child {
+ Test.Parent? parent;
+ new Child = { Test.Parent? ^par -> parent = par; };
+};
+struct Parent {
+ Child child;
+ int~ payload;
+ new Parent = { int n ->
+ child = new Child(self); // Incomplete result: self is incomplete.
+ // child.parent is forbidden here.
+ payload = n;
+ };
+};
+val parent = new Parent(arg);
+val local = new Child(parent); // Complete argument, complete result.
+return local.parent==parent; // Lookup is allowed.
+```
+
+Here `Test` names the source unit. After the root Parent constructor completes,
+the entire cycle is usable: all members have initialized their own fields, and
+none could expose an incomplete reference to ordinary code during construction.
+The Child constructor still treats its `^par` as incomplete when the actual
+argument is complete; it may retain or forward it, but cannot read `par.payload`.
+Field sharpening and inferred contracts are deliberately deferred.
+
+## Aliases and loops
+
+Incomplete is a property of a value, not just the current contents of a parser
+Var. Consider `while (n--) { use(p); p = self; }`: the backedge introduces an
+incomplete value after `use(p)` has been parsed. `TypeMemPtr._partial` carries
+that restriction through ordinary forward typing. It is an independent
+two-point lattice: meet ORs the restriction and dual complements it. Copies,
+conversions, Stores, and Phis preserve it; a merge is incomplete if any live
+input is incomplete. A `^` formal stays incomplete even when its known callers
+are complete. The receiver's private memory identifies the own-self exception;
+another incomplete pointer of the same class has no such permission.
+
+A small `ConstructNode` wraps each source `new`. Its runtime value is the
+allocator result; its type adds the OR of that call's actual argument qualifiers.
+This distinguishes `new Child(self)` from `new Child(null)` even when both call
+the same constructor. Looking only at the shared callee Return would lose this
+distinction. The node disappears after source checking, before lowering.
+
+A completed receiver gives a completed view of its pointer fields. Construction
+may retain incomplete references but cannot publish them, so reaching the root
+`new` boundary with complete arguments makes the entire reachable cycle usable.
+There is no group identity or traversal to mark members complete. Conversely,
+object-reference fields read through incomplete self remain conservative, even
+if the particular stored reference was complete.
+
+The optimistic `Iter.types` solve also handles qualifier cycles. For
+`p = null; while (n--) p = new Link(p);`, no input introduces incompleteness,
+so `p` stays complete and a subsequent field read is allowed. A backedge carrying
+self instead makes the Phi and its uses incomplete.
+
+`Construction` retains source operands and diagnostic sites until checking;
+it stores no flow facts and performs no recursive Load/Phi/Call query. The old
+root and allocation bitsets are gone. There are no field-requirement sets,
+hidden `^` memory arguments, or runtime group objects.
+
+`ConstructionGroupTest` exercises cycles, complete/null arguments, prohibited
+lookups/publication, aliases, branch merges, complete and incomplete loop
+backedges, and declared restrictions with only complete callers, including an
+uninlined constructor through machine encoding. `ConstructorCompletionTest`,
+`FinalConstructionTest`, and `BoundaryTest` exercise field-memory semantics.
+The former inferred-requirement litmus tests are preserved, explicitly deferred,
+in `ConstructionRequirementsTest`. Separate compilation of `^` constructors is
+still deliberately unsupported. The pointer qualifier has an object-file
+representation, but importing and checking the full construction contract is
+outside this experiment. Rebuild chapter 29 object files after the type-format
+change.
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/Iter.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/Iter.java
index 4085ebff..1f53d3e4 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/codegen/Iter.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/Iter.java
@@ -29,6 +29,25 @@ public abstract class Iter {
assert !CodeGen.expensiveAssert() || Opto.fixedPointCheck(code);
}
+ // Stabilize existing node types before releasing source error obligations.
+ // No rewrites: even an unused illegal Load must still be checked here.
+ public static void types(CodeGen code) {
+ // Definite initialization needs the optimistic solution around loops:
+ // an unchanged initialized field must not be stuck at its old BOT seed.
+ Ary<Node> typed = new Ary<>(Node.class);
+ code._start.walk(n -> { typed.add(n); n._type = Type.TOP; code.add(n); return null; });
+ Node n;
+ while( (n = code._iter._work.pop())!=null ) {
+ if( n.isDead() ) continue;
+ Type t = n._type.meet(n.compute());
+ if( t==n._type ) continue;
+ n._type = t;
+ code.addAll(n._outputs);
+ n.moveDepsToWorklist();
+ }
+ code.addAll(typed);
+ }
+
private static Type iterEscapeReset(Type t) {
if( t instanceof TypeMem mem )
return mem._one ? mem : mem.makeFrom(XInt.FULL,XInt.FULL);
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 7fa4c662..2958332b 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java
@@ -164,6 +164,7 @@ public abstract class ParseAll {
// The complete public interface is now known. Keep this independent
// of later graph/linker liveness so SCCP computations remain local and
// monotonic.
+ code.P.checkConstruction();
code.freezePublicInterface();
}
@@ -337,9 +338,11 @@ public abstract class ParseAll {
// Add the new top-level loaded class
Parser.TYPES.put(cunit._clz._name,cunit._clz);
- Field inst = cunit._clz.field(cunit._name);
- if( inst != null && inst._t instanceof TypeFunPtr tfp && tfp._ret instanceof TypeMem mem )
- Parser.TYPES.put(cunit._cname,mem._t);
+ // Layout belongs to the allocator's object result. An initializer's
+ // private-memory return no longer promises initialized field values.
+ Field inst = cunit._clz.field("<new>");
+ if( inst != null && inst._t instanceof TypeFunPtr tfp && tfp._ret instanceof TypeMemPtr ptr )
+ Parser.TYPES.put(cunit._cname,ptr._obj);
}
// Symbol not found in source code (or we would not be here).
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 4bb94183..ae4ecb74 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
@@ -38,6 +38,7 @@ public class Serialize {
}
static void serialize( CodeGen code ) {
+ code.P.checkConstructionSerializable();
// Anonymous functions also need names when another object refers to them.
for( FunNode fun : code._linker )
if( fun!=null && !fun.isDead() && fun._name==null )
@@ -421,11 +422,9 @@ public class Serialize {
if( !(type instanceof TypeStruct ts) || !TypeStruct.startsClzPrefix(ts._name) )
continue;
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());
- if( inst != null && inst._t instanceof TypeFunPtr tfp && tfp._ret instanceof TypeMem mem && mem._t instanceof TypeStruct its )
- Parser.TYPES.put(cname,its);
+ Field inst = ts.field("<new>");
+ if( inst != null && inst._t instanceof TypeFunPtr tfp && tfp._ret instanceof TypeMemPtr ptr )
+ Parser.TYPES.put(cname,ptr._obj);
}
elf._pubTypes = new TypeStruct[npubTypes];
for( int i=0; i<npubTypes; i++ ) {
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java
index c9e53311..abd8628d 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java
@@ -3,6 +3,7 @@ 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.TypeScalar;
import com.seaofnodes.simple.util.Ary;
// Reject invalid optimized programs before lowering erases their source-level
@@ -52,7 +53,7 @@ abstract class TypeCheck {
private static boolean noBottomInputs(Node n) {
for( Node def : n._inputs )
- if( def!=null && !(def instanceof FldOffNode) && def._type==Type.BOTTOM )
+ if( def!=null && !(def instanceof FldOffNode) && (def._type==Type.BOTTOM || def._type==TypeScalar.BOT) )
return false;
return true;
}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/ArithNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/ArithNode.java
index 16e9ec8d..2c6610e1 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/ArithNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/ArithNode.java
@@ -117,10 +117,13 @@ public abstract class ArithNode extends Node implements ModeNode {
if( t2 instanceof TypeConAry ary ) t2 = ary.elem();
if( t1==null || t2==null ) return Type.TOP;
if( t1.isHigh() || t2.isHigh() ) return Type.TOP;
+ // Scalar.BOT is an unknown/uninitialized scalar, not an integer.
+ // Keep that family through arithmetic, including annihilators like *0.
// Without numeric inputs, stay below both possible result families.
// Pointer modes belong to comparisons, not arithmetic.
if( !(t1 instanceof TypeInteger || t1 instanceof TypeFloat) ||
- !(t2 instanceof TypeInteger || t2 instanceof TypeFloat) ) return Type.BOTTOM;
+ !(t2 instanceof TypeInteger || t2 instanceof TypeFloat) )
+ return t1.isa(TypeScalar.BOT) && t2.isa(TypeScalar.BOT) ? TypeScalar.BOT : Type.BOTTOM;
byte mode = _mode==0 ? mode(t1,t2) : _mode;
if( mode==1 ) {
if( t1 instanceof TypeInteger x &&
@@ -128,7 +131,7 @@ public abstract class ArithNode extends Node implements ModeNode {
return x.isConstant() && y.isConstant()
? con(x,y)
: doOp(x,y).makeWide( (byte)Math.max(x._widen,y._widen) );
- return TypeInteger.BOT;
+ return TypeScalar.BOT;
}
// FP handling
if( t1 instanceof TypeFloat f1 && t2 instanceof TypeFloat f2 &&
@@ -182,9 +185,9 @@ public abstract class ArithNode extends Node implements ModeNode {
Node[] ns = new Node[lhs.nIns()];
ns[0] = lhs.in(0);
for( int i=1; i<lhs.nIns(); i++ ) {
- ns[i] = isCon(lhs,rhs,i)
- ? ConstantNode.make(con(lhs.in(i)._type, rhs.in(i)._type)).peephole()
- : copy(lhs.in(i), rhs.in(i)).peephole();
+ // Let the copied operation's compute handle constants
+ // in its actual numeric family, including floats.
+ ns[i] = copy(lhs.in(i), rhs.in(i)).peephole();
}
String label = lhs._label==rhs._label ? lhs._label : lhs._label + rhs._label;
return new PhiNode(label, ns).peephole();
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/BoolNode.java
index 02d3e22e..203e2266 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/BoolNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/BoolNode.java
@@ -29,7 +29,7 @@ abstract public class BoolNode extends Node implements ModeNode {
}
@Override
- public TypeInteger compute() {
+ public Type compute() {
Type t1 = in(1)._type;
Type t2 = in(2)._type;
if( t1==null || t2==null )
@@ -37,9 +37,11 @@ abstract public class BoolNode extends Node implements ModeNode {
// Exactly equals?
if( t1.isHigh() || t2.isHigh() )
return (TypeInteger)BOOL.dual();
- if( in(1)==in(2) )
- // LT fails, both EQ and LE succeed
- return this instanceof LT ? FALSE : TRUE;
+ if( t1==Type.BOTTOM || t2==Type.BOTTOM ) return Type.BOTTOM;
+ if( t1==TypeScalar.BOT || t2==TypeScalar.BOT ) return TypeScalar.BOT;
+ if( in(1)==in(2) && (t1 instanceof TypeInteger || t1 instanceof TypeNil) )
+ // Floating x==x is not true when x may be NaN.
+ return this instanceof EQ || this instanceof LE ? TRUE : FALSE;
byte mode = _mode==0 ? ArithNode.mode(t1,t2) : _mode;
if( mode==0 )
return BOOL;
@@ -71,9 +73,7 @@ abstract public class BoolNode extends Node implements ModeNode {
@Override
public Node idealize() {
- // Compare of same
- if( in(1)==in(2) )
- return this instanceof LT ? CodeGen.CODE.ZERO : ConstantNode.make(TRUE);
+ // Constant comparisons, including compare-of-same, belong in compute().
// Can we decide int vs flt?
Type t1 = in(1)._type, t2 = in(2)._type;
if( t1==null || t2==null )
@@ -136,8 +136,8 @@ abstract public class BoolNode extends Node implements ModeNode {
@Override public Tag serialTag() { return Tag.NE; }
public String op() { return "!="; }
TypeInteger doOp(TypeInteger i1, TypeInteger i2) {
- if( i1==i2 && i1.isConstant() ) return TRUE;
- if( i1._max < i2._min || i1._min > i2._max ) return FALSE;
+ if( i1==i2 && i1.isConstant() ) return FALSE;
+ if( i1._max < i2._min || i1._min > i2._max ) return TRUE;
return BOOL;
}
@Override boolean doOp(double lhs, double rhs) { return lhs!=rhs; }
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 4024c5a9..9e130bb4 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java
@@ -23,8 +23,8 @@ public class ConFldOffNode extends ConstantNode {
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();
+ public static Node make( TypeStruct ts, String fname ) {
+ return new ConFldOffNode(ts,fname).peephole();
}
@Override public Tag serialTag() { throw Utils.TODO("should not reach here"); }
@@ -37,20 +37,22 @@ public class ConFldOffNode extends ConstantNode {
return p.p(label());
}
- @Override public Node idealize() {
+ @Override public boolean isConst() { return false; }
+
+ @Override public Type compute() {
// Some array offsets are known early
if( _ts.isAry() ) {
// Length always at offset zero
if( _fname==" len" )
- return CodeGen.CODE.ZERO;
+ return TypeInteger.ZERO;
// The shift depends on the element
if( _fname=="<<" )
- return Node.con(_ts.aryScale());
+ return TypeInteger.constant(_ts.aryScale());
// The base depends on the layout (alignment for large elements may
// involve padding) and MAY depend on the length if I go for
// smaller array lengths.
}
- return null;
+ return _con;
}
// Convert field offset to an integer
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/ConstructNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/ConstructNode.java
new file mode 100644
index 00000000..52e3eb54
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/ConstructNode.java
@@ -0,0 +1,28 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.print.ExprPrinter;
+
+/** The source new boundary. Own fields have been constructed, but an incomplete
+ * actual argument keeps this result incomplete. Unlike a callee's merged Return,
+ * this transfer describes one allocation site. Erased after source checks. */
+public class ConstructNode extends Node {
+ public ConstructNode(Node value, Ary<Node> args) {
+ super(null,value);
+ for( Node arg : args ) addDef(arg);
+ }
+ @Override public String label() { return "Construct"; }
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("construct(").n(in(1)).p(")"); }
+ @Override public Type compute() {
+ Type t = in(1)._type;
+ if( !(t instanceof TypeMemPtr ptr) ) return t;
+ boolean partial = false;
+ for( int i = 2; i<nIns(); i++ ) partial |= TypeMemPtr.mayBePartial(in(i)._type);
+ return ptr.withPartial(partial);
+ }
+ @Override public Node idealize() {
+ return CodeGen.CODE._phase==CodeGen.Phase.Parse ? null : in(1);
+ }
+}
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 81f46097..ee911398 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
@@ -39,6 +39,7 @@ public class ConvertNode extends Node {
@Override public Type compute() {
Type src = val()._type;
if( src.isHigh() ) return Type.TOP;
+ if( _dst instanceof TypeMemPtr ptr ) return ptr.withPartial(TypeMemPtr.mayBePartial(src));
if( !(_dst instanceof TypeMemPtr) && src.isa(_dst) ) return src;
return _dst;
}
@@ -46,7 +47,8 @@ public class ConvertNode extends Node {
@Override public Node idealize() {
Type src = val()._type;
if( src==Type.BOTTOM || src==Type.TOP ) return null;
- if( (src.isa(_dst) && src.accessISA(_dst)) ) return val();
+ Type dst = _dst instanceof TypeMemPtr ptr ? ptr.withPartial(TypeMemPtr.mayBePartial(src)) : _dst;
+ if( (src.isa(dst) && src.accessISA(dst)) ) return val();
// Managed arrays are represented by an object pointer, while C wants
// the address of the first element. The representation cast itself is
@@ -63,10 +65,10 @@ public class ConvertNode extends Node {
return new ConvertNode(_dst,new ToIntegerNode(val()).peephole());
// Narrow integers produce the declared sign/zero extension.
- if( src instanceof TypeInteger && _dst instanceof TypeInteger dst ) {
- if( dst._min==0 )
- return new AndNode(null,val(),con(dst._max));
- int shift = Long.numberOfLeadingZeros(dst._max)-1;
+ if( src instanceof TypeInteger && _dst instanceof TypeInteger ti ) {
+ if( ti._min==0 )
+ return new AndNode(null,val(),con(ti._max));
+ int shift = Long.numberOfLeadingZeros(ti._max)-1;
Node shf = con(shift);
if( shf._type==TypeInteger.ZERO ) return val();
return new SarNode(null,new ShlNode(null,val(),shf.keep()).peephole(),shf.unkeep());
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/LoadNode.java
index 9aa8b311..4b09d2e9 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/LoadNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -76,6 +76,7 @@ public class LoadNode extends MemOpNode {
return Type.BOTTOM; // Error in is error out
if( !(ptr0 instanceof TypeMemPtr tmp) )
return scalar(mem._t);
+ if( tmp._partial && (!mem._one || mem._clz) ) return TypeScalar.BOT;
if( tmp.nullable() )
return Type.BOTTOM; // Load from nil-able is error
@@ -91,13 +92,19 @@ public class LoadNode extends MemOpNode {
// Load member of constant array
if( t instanceof TypeConAry ary )
t = ary.elem(); // TODO: if offset is known, can peek the constant
- if( mem._alias==_alias )
+ if( mem._one && !mem._clz && !tmp._obj.isAry() ) {
+ Field actual = mem._t instanceof TypeStruct ts ? ts.field(_name) : null;
+ t = mem._alias==_alias ? mem._t : actual==null ? TypeScalar.BOT : actual._t;
+ } else if( mem._alias==_alias )
t = t.join(mem._t);
// A deeply read-only base produces a deeply read-only value. The
// generic declared field type must not cast this information away.
if( ptr0.isFinal() || readonly )
t = t.makeRO();
+ // A completed receiver gives a completed view of its reachable fields.
+ // Through self, keep object references restricted until the root new.
+ if( t instanceof TypeMemPtr value ) t = value.withPartial(tmp._partial && !value._obj.isAry());
return scalar(t);
}
@@ -110,6 +117,7 @@ public class LoadNode extends MemOpNode {
@Override
public Node idealize() {
+ if( _type==TypeScalar.BOT ) return null;
Node mem = mem();
Node ptr = ptr();
@@ -169,7 +177,9 @@ public class LoadNode extends MemOpNode {
// if( pred ) ptr.x = e0; val = pred ? e0
// else ptr.x = e1; : e1;
// val = ptr.x; ptr.x = val;
- if( mem() instanceof MemPhiNode memphi && memphi.region()._type == Type.CONTROL && memphi.nIns()== 3 &&
+ if( mem() instanceof PhiNode memphi && !(memphi instanceof ParmNode) &&
+ (memphi instanceof MemPhiNode || (memphi._type instanceof TypeMem tm && tm._one)) &&
+ memphi.region()._type == Type.CONTROL && memphi.nIns()== 3 &&
// Retain control on both copies only if it dominates both paths.
// The Region itself, or a later range check, cannot move to its inputs.
(in(0)==null || cfg0().domLCA(memphi.region().idom(this),this)==cfg0()) &&
@@ -203,6 +213,9 @@ public class LoadNode extends MemOpNode {
// Load a flavored zero from a New
private Node zero(NewNode nnn) {
+ // Object fields acquire values from their initializer's Stores.
+ // Only arrays expose calloc's zero-fill as a language-level default.
+ if( !nnn._ts.isAry() ) return new ConstantNode(TypeScalar.BOT).peephole();
Type zero = declaredType().makeZero();
assert zero.isa(_type); // Catch an uninitialized non-null field
return castRO(new ConstantNode(zero).peephole());
@@ -246,7 +259,8 @@ public class LoadNode extends MemOpNode {
if( esc.self()==ptr ) { mem=esc.priv(); break; }
if( neverAlias(ptr,esc.self()) ) { mem=esc.pub(); break; }
return null;
- case MemPhiNode phi:
+ case PhiNode phi:
+ if( phi instanceof ParmNode || (!(phi instanceof MemPhiNode) && !(phi._type instanceof TypeMem tm && tm._one)) ) return null;
addDep(phi.region());
// Only the original loop closes a successful cycle. Another
// loop or an unfinished merge needs a separate proof.
@@ -273,7 +287,7 @@ public class LoadNode extends MemOpNode {
Node win=find(mem,ptr,stop,new BitSet());
if( win!=null ) {
if( win==stop ) return value;
- if( win instanceof MemPhiNode mp ) {
+ if( win instanceof PhiNode mp ) {
PhiNode phi=new PhiNode(_name,mp.region()).keep();
for( int i=1; i<mp.nIns(); i++ ) phi.addDef(load(mp.in(i),ptr,stop,value));
return CodeGen.CODE.add(phi.unkeep().peephole());
@@ -311,6 +325,15 @@ public class LoadNode extends MemOpNode {
return false;
}
+ @Override public Parser.ParseException err() {
+ Parser.ParseException err = super.err();
+ if( err!=null ) return err;
+ if( (in(0)==null || !in(0)._type.isHigh()) && _type==TypeScalar.BOT &&
+ mem()._type instanceof TypeMem mem && mem._one && !mem._clz )
+ return Parser.error("Field '"+_name+"' might not be initialized",_loc);
+ return null;
+ }
+
// Read-Only is a deep property, and cannot be cast-away
private Node castRO(Node rez) {
if( (ptr()._type.isFinal() || declaredType().isFinal()) && !rez._type.isFinal() )
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
index 24ca6157..b453550d 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
@@ -123,6 +123,9 @@ public abstract class MemOpNode extends TypeNode {
// Already an error, but better error messages come from elsewhere
if( ptr == Type.BOTTOM ) return null;
if( ptr.isHigh() ) return null; // Assume it will fall to not-null
+ if( ptr instanceof TypeMemPtr p && p._partial &&
+ !(mem()._type instanceof TypeMem m && m._one && !m._clz) )
+ return Parser.error("Cannot access field '"+_name+"' through an incomplete object",_loc);
// Better be a not-nil TMP
if( !(ptr instanceof TypeMemPtr tmp && tmp.notNull()) )
return Parser.error( "Might be null accessing '" + _name + "'",_loc);
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/MinusNode.java
index 4f4343d4..63d413d0 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/MinusNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/MinusNode.java
@@ -47,12 +47,12 @@ public class MinusNode extends Node implements ModeNode {
return TypeInteger.BOT;
}
if( t instanceof TypeFloat f ) {
- if( _mode==1 ) return TypeInteger.BOT;
+ if( _mode==1 ) return TypeScalar.BOT;
return f.isConstant() ? TypeFloat.constant(-f.value()) : f;
}
// No numeric family is known. This must be below both integer and
// float results, in either analysis direction and with any mode.
- return Type.BOTTOM;
+ return t.isa(TypeScalar.BOT) ? TypeScalar.BOT : Type.BOTTOM;
}
@Override
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/MulNode.java
index bb99cad5..04fcf1c2 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/MulNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/MulNode.java
@@ -16,6 +16,7 @@ public class MulNode extends ArithNode {
@Override long doOp( long x, long y ) { return x * y; }
@Override double doOp(double x, double y ) { return x * y; }
@Override TypeInteger doOp(TypeInteger x, TypeInteger y) {
+ if( x==TypeInteger.ZERO || y==TypeInteger.ZERO ) return TypeInteger.ZERO;
return TypeInteger.BOT;
}
@@ -26,6 +27,11 @@ public class MulNode extends ArithNode {
Type t1 = lhs._type;
Type t2 = rhs._type;
+ // Integer strength reductions require two integer operands. In
+ // particular Scalar.BOT * 0 must retain its unresolved input.
+ if( _mode!=1 || !(t1 instanceof TypeInteger && t2 instanceof TypeInteger) )
+ return super.idealize();
+
// Move constants to RHS: con*arg becomes arg*con
if ( t1.isConstant() && !t2.isConstant() )
return swap12();
@@ -36,7 +42,7 @@ public class MulNode extends ArithNode {
// canonicalize to (x*1)
long c = i.value();
if( c==1 ) return lhs._type.isa(compute()) ? lhs : super.idealize();
- if( c==0 ) return CodeGen.CODE.ZERO;
+ // Zero is handled by compute(), before idealize().
// Mul by a power of 2, +/-1. Bit patterns more complex than this
// are unlikely to win on an X86 vs the normal "imul", and so
// become machine-specific.
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/NewNode.java
index d4df7a0b..96a237e1 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/NewNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/NewNode.java
@@ -44,9 +44,9 @@ public class NewNode extends Node implements MultiNode {
TypeStruct ts = _ts.isAry() ? _ts : _ts.makeInit();
return TypeTuple.make(TypeMemPtr.make(_ts),
// Arrays get pre-zeroed by constructor.
- // Objects start with default-zero memory for
- // fields that can be zero-initialized. This is
- // still private memory, so no aliases escape yet.
+ // Object fields start at Scalar.BOT; declaration
+ // defaults are explicit Stores. This is private
+ // memory, so no aliases escape yet.
TypeMem.make(1,ts,true,false,false,null,null));
}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/NotNode.java
index 2328c287..c3a795c9 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/NotNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/NotNode.java
@@ -11,9 +11,10 @@ public class NotNode extends Node {
@Override protected String format() { return "(!%1)"; }
@Override
- public TypeInteger compute() {
+ public Type compute() {
Type t0 = in(1)._type;
if( t0.isHigh() ) return (TypeInteger)TypeInteger.BOOL.dual();
+ if( t0==Type.BOTTOM || t0==TypeScalar.BOT ) return t0;
if( t0 == Type.NIL || t0 == TypeInteger.ZERO ) return TypeInteger.TRUE;
if( t0 instanceof TypeNil tn && tn.notNull() ) return TypeInteger.FALSE;
if( t0 instanceof TypeInteger i && (i._min > 0 || i._max < 0) ) return TypeInteger.FALSE;
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/ParmNode.java
index 91c471c0..1d8bd484 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/ParmNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/ParmNode.java
@@ -4,6 +4,7 @@ import com.seaofnodes.print.ExprPrinter;
import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.codegen.Serialize;
+import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.util.BAOS;
import java.util.HashMap;
@@ -64,13 +65,18 @@ public class ParmNode extends PhiNode {
@Override
public Type compute() {
if( !(region() instanceof RegionNode r) )
- return region()._type==Type.XCONTROL || region()._type==Type.TOP ? (_type instanceof TypeMem ? TypeMem.TOP : Type.TOP) : _type;
+ return region()._type==Type.XCONTROL || region()._type==Type.TOP ? (_declaredType instanceof TypeMem ? TypeMem.TOP : Type.TOP) : _type;
// During parsing Phis have to be computed type pessimistically.
- if( r.inProgress() || in(nIns()-1)==null )
+ boolean privateMem = _idx==3 && _declaredType instanceof TypeMem mem && mem._one && !mem._clz;
+ if( privateMem && fun().unknownCallers() && nIns()==2 && CodeGen.CODE._phase!=CodeGen.Phase.Parse )
+ return TypeMem.TOP;
+ if( (!privateMem && r.inProgress()) || in(nIns()-1)==null || (privateMem && fun().unknownCallers() && nIns()==2) )
return _declaredType;
// Set type to local top of the starting type
Type t = Type.TOP;
- for( int i = 1; i < nIns(); i++ ) {
+ // Internal construction memory is supplied by allocation/initializer
+ // calls, not by the fictitious external caller used to keep a body live.
+ for( int i = privateMem && fun().unknownCallers() ? 2 : 1; i < nIns(); i++ ) {
// If the region's control input is live, add this as a dependency
// to the control because we can be peeped should it become dead.
Type ctrl = addDep(r.in(i))._type;
@@ -87,7 +93,12 @@ public class ParmNode extends PhiNode {
// widening this Parm alone cannot stop f(x-1)+1 from growing its result.
if( t instanceof TypeInteger ti && !ti.isHigh() && !ti.isConstant() )
t = ti.same_but_slightly_wider_than();
- return t.join(_declaredType);
+ t = t.join(_declaredType);
+ // ^ is a restriction inside the constructor even if every currently
+ // known caller passes a complete object. Inlining may later remove it.
+ if( t instanceof TypeMemPtr ptr && _declaredType instanceof TypeMemPtr decl && decl._partial )
+ t = ptr.withPartial(true);
+ return t==Type.TOP && _declaredType instanceof TypeMem ? TypeMem.TOP : t;
}
@Override
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/PhiNode.java
index 75d3ff13..a4aaaa07 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/PhiNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/PhiNode.java
@@ -19,6 +19,13 @@ public class PhiNode extends Node {
_label = label;
}
public static PhiNode make(String label, Type minType, Node... inputs) {
+ if( minType instanceof TypeMem mem && mem._one ) {
+ // One object's memory travels as one value. Its unfinished loop
+ // Phi still belongs to the private-memory family.
+ PhiNode phi = new PhiNode(label,inputs);
+ phi._type = TypeMem.makePrivate(Type.BOTTOM);
+ return phi;
+ }
return minType instanceof TypeMem mem
? mem._alias==1
? new BulkMemPhiNode(label,inputs)
@@ -67,7 +74,7 @@ public class PhiNode extends Node {
return Type.TOP;
// During parsing Phis have to be computed type pessimistically.
if( r.inProgress() || in(nIns()-1)==null )
- return Type.BOTTOM;
+ return _type instanceof TypeMem mem && mem._one ? TypeMem.makePrivate(Type.BOTTOM) : Type.BOTTOM;
// Set type to local top of the starting type
Type t = Type.TOP;
for( int i = 1; i < nIns(); i++ ) {
@@ -94,7 +101,7 @@ public class PhiNode extends Node {
return ti.same_but_slightly_wider_than();
}
- return t;
+ return r instanceof LoopNode && t instanceof TypeMem mem ? mem.widen() : t;
}
@Override
@@ -208,7 +215,7 @@ public class PhiNode extends Node {
t = t.meet(ins[i]._type);
}
// Do not introduce an operand Phi mixing incompatible type families.
- if( t==Type.BOTTOM ) {
+ if( t==Type.BOTTOM || t==TypeScalar.BOT ) {
for( int i=1; i<ins.length; i++ ) addDep(ins[i]);
cp.kill();
return null;
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/RegionNode.java
index 8d0d56e9..eee1c150 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/RegionNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/RegionNode.java
@@ -142,7 +142,7 @@ public class RegionNode extends CFGNode {
{ addDep(use); if( data!=null ) addDep(data); return true; }
}
} else
- { addDep(use); return true; } // Control user
+ { if( use!=null ) addDep(use); return true; } // Control user, including an external keep
}
return false;
}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
index 182adb19..f2972c91 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
@@ -77,22 +77,6 @@ public class ReturnNode extends CFGNode {
if( ctrl()._type == Type.CONTROL &&
expr()._type == TypeScalar.BOT )
return Parser.error("Cannot return generic scalar",null);
- // With no user constructor, <init> doubles as the public no-arg
- // constructor. Required fields remain TOP in its private memory: a
- // typed parser poison which deliberately emitted no Store or code.
- if( ctrl()._type == Type.CONTROL && _fun.isInstance() ) {
- TypeStruct self = ((TypeMemPtr)_fun.sig().arg(0))._obj;
- if( !CodeGen.hasUserConstructor(self) && expr()._type instanceof TypeMem mem )
- for( Field fld : self._fields ) {
- if( !(fld._t instanceof TypeNil tn && tn.notNull()) ) continue;
- Type actual = mem._alias==1 && mem._t instanceof TypeStruct ts
- ? ts.field(fld._fname)._t
- : mem._alias==fld._alias ? mem._t : Type.TOP;
- if( actual==Type.TOP || actual instanceof TypeNil atn && atn.nullable() )
- return Parser.error("'"+self._name+"' is not fully initialized, field '"+
- fld._fname+"' is only partially set in the constructor",null);
- }
- }
return null;
}
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 5fc2b8cd..1130cf44 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
@@ -209,9 +209,8 @@ public class ScopeNode extends MemMergeNode {
return kx == -1 ? null : _kinds.at(kx);
}
- // Final fields are writable local values inside their own initializer or
- // constructor. Final parameters, local variables, and captured bindings
- // remain final. Use the same rule when deciding which loop Phis to build.
+ // Final fields are writable inside their own initializer or constructor.
+ // Final parameters, local variables, and captured bindings remain final.
public boolean canInit(Var v) {
int fx=enclosingFuncOrDecl();
Kind.Func fun=(Kind.Func)_kinds.at(fx);
@@ -428,7 +427,6 @@ public class ScopeNode extends MemMergeNode {
Var n = scope.var(i);
if( n.isFRef() ) { // Loop body has forward refs
Var v = new Var(nIns(),n._name,n.type(),n._final,n._loc,true);
- v._uninit = n._uninit;
_vars.add(v);
addDef(scope.in(i));
}
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 fe7d428c..47984ed6 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/StoreNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/StoreNode.java
@@ -88,15 +88,23 @@ public class StoreNode extends MemOpNode {
// Sharpen memory value; required for narrowing stores where the parser
// inserts zero/sign masking and somebody reads the TypeMem type.
+ // A source Load of an unwritten field is kept and checked separately;
+ // this declared bound does not excuse that earlier read.
Type decl = declaredType();
+ if( decl instanceof TypeMemPtr ptrType ) decl = ptrType.withPartial(TypeMemPtr.mayBePartial(val));
if( decl != Type.BOTTOM )
{ assert decl != Type.TOP; val = val.join(decl); }
+ // A constructor can itself be a field. Its result memory contains
+ // that field again; retain the declared signature instead of unfolding
+ // successively sharper copies of its private-memory return type.
+ if( mem._one && decl instanceof TypeFunPtr ) val = decl;
// A singleton class's final constant field lives in the static image.
// Its precise memory slice is a constant too; ordinary peepholes can
// replace this Store before scheduling or instruction selection.
// 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( fld==null && off() instanceof ConFldOffNode off ) fld = off._ts.field(_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);
@@ -106,7 +114,7 @@ public class StoreNode extends MemOpNode {
// which is never does in a constructor.
if( !_name.equals("[]") && mem._one )
// Just track the stored value
- return TypeMem.make(_alias,val,true,false,_init,null,null).escapesFrom(val);
+ return TypeMem.make(_alias,val,true,false,fld==null ? _init : fld._final,null,null).escapesFrom(val);
// Normal aliasing Store.
assert mem._alias==1 || mem._alias==_alias; // Perfect aliasing
@@ -319,6 +327,9 @@ public class StoreNode extends MemOpNode {
public Parser.ParseException err() {
Parser.ParseException err = super.err();
if( err != null ) return err;
+ if( TypeMemPtr.mayBePartial(val()._type) &&
+ !(mem()._type instanceof TypeMem mem && mem._one && !mem._clz) )
+ return Parser.error("Cannot store an incomplete object into an ordinary object",_loc);
assert _size>0 || CodeGen.CODE._phase.ordinal() <= CodeGen.Phase.FinalizeIR.ordinal();
if( ptr()._type == Type.TOP ) // Dead store?
return null;
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 8405278c..b8db4f4c 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/parser/Allocations.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/Allocations.java
@@ -47,6 +47,7 @@ final class Allocations {
if( clz==null )
throw P.error("Unknown struct type '" + ts._name + "'");
Field afld = clz.field("<new>");
+ P.construction.allocation(afld==null ? null : (TypeFunPtr)afld._t,ctorArgs,P.loc());
Node alloc;
if( afld != null ) {
TypeFunPtr tfp = (TypeFunPtr)afld._t;
@@ -67,7 +68,9 @@ final class Allocations {
args.add(P.ctrl()); args.add(P.mem());
for( Node arg : ctorArgs ) args.add(arg.unkeep());
args.add(alloc);
- return P.functions.functionCall(args);
+ Node result = P.functions.functionCall(args);
+ if( P.ctrl()._type==Type.XCONTROL ) return result;
+ return new ConstructNode(result,ctorArgs).peephole();
}
private Ary<Node> constructorArgs() {
@@ -83,7 +86,7 @@ final class Allocations {
Field lenFld = ts.field("#" );
Field bodyFld = ts.field("[]");
- ConFldOffNode base = ConFldOffNode.make(ts, "[]");
+ Node base = ConFldOffNode.make(ts, "[]");
int scale = ts.aryScale();
Node size = P.peep(new AddNode(base,P.peep(new ShlNode(null,len,P.con(scale)))));
@@ -127,6 +130,11 @@ final class Allocations {
// call this function; all layout, initialization and publication choices
// are made here, where the complete struct and constructor are known.
TypeFunPtr makeAllocator(TypeStruct ts, ReturnNode initRet, Field ctorFld) {
+ if( ctorFld==null ) {
+ Field legacy = ts.field(ts._name.substring(ts._name.lastIndexOf('.')+1).intern());
+ if( legacy!=null && legacy._t instanceof TypeFunPtr sig && sig.nargs()>=2 && sig.arg(1) instanceof TypeMem )
+ ctorFld = legacy;
+ }
TypeFunPtr ctor = ctorFld==null ? null : (TypeFunPtr)ctorFld._t;
int nargs = ctor==null ? 0 : ctor.nargs()-2; // Hide self and selfMem
Type[] atypes = new Type[nargs];
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/Construction.java b/chapter29/src/main/java/com/seaofnodes/simple/parser/Construction.java
new file mode 100644
index 00000000..799ca6fc
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/Construction.java
@@ -0,0 +1,145 @@
+package com.seaofnodes.simple.parser;
+
+import com.seaofnodes.simple.codegen.Iter;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Ary;
+
+/**
+ * Field initialization is ordinary forward typing: New supplies Scalar.BOT
+ * fields in private memory, Store replaces a field, and Phi meets the incoming
+ * memories. Scalar.BOT survives a missing live arm; Integer.BOT describes an
+ * initialized integer. Loads and merged Returns inspect these types after the
+ * parser closes loops and compute() stabilizes. No backwards initialization walk.
+ *
+ * References have just a yes/no restriction. Constructor self and ^ parameters
+ * are incomplete. An allocation result is incomplete iff an actual argument is
+ * incomplete. Copies and Phis preserve this restriction. Only self may be read
+ * while incomplete; object-reference fields read from self conservatively remain
+ * incomplete. Arrays are born initialized and cannot contain incomplete refs.
+ * Ordinary calls and stores outside self require complete references.
+ *
+ * This permits cycles to be assembled but not traversed until the root new
+ * returns. A new with only complete arguments, including null, returns complete.
+ * A ^ formal is always restricted inside its constructor, even when a caller
+ * passes a complete argument. No field requirements are inferred.
+ *
+ * Incompleteness lives in TypeMemPtr, not a parser provenance graph. Ordinary
+ * Phis carry it around loops, conversions preserve it, and ConstructNode types
+ * each allocation result from its actual arguments. Source obligations retain
+ * their operands until the type fixed point, but hold no separate flow facts.
+ *
+ * The qualifier is a two-point lattice: complete meets incomplete to incomplete.
+ * Constructor self and ^ formals seed incomplete; a ^ formal stays restricted
+ * even if its known callers are complete. A ConstructNode wraps each source new
+ * and ORs the actual argument qualifiers. Thus new Child(self) is incomplete,
+ * while new Child(null) and new Child(oldParent) are complete, even though all
+ * three calls share the same constructor Return. The raw allocator result is
+ * complete after its own fields have been checked; ConstructNode adds the
+ * caller's restriction and disappears after source checking.
+ *
+ * Reading a pointer field through a completed receiver gives a completed view
+ * of that field too: its object's construction could only retain incomplete
+ * references from the enclosing construction. There is no group ID to clear.
+ * A read through incomplete self conservatively returns an incomplete reference
+ * (except initialized arrays), even when the stored value happens to be complete.
+ * Own-self access is identified by the receiver and its private memory, not by
+ * the nominal class: a ^ argument of the same class cannot use that exception.
+ *
+ * Iter.types also solves qualifier cycles optimistically. For p = null followed
+ * by p = new Link(p) in a loop, nothing introduces incompleteness, so the loop
+ * stays complete. A backedge carrying self makes the Phi and its uses incomplete.
+ * Both cases use the ordinary worklist; no recursive Load/Phi/Call query remains.
+ */
+final class Construction {
+ private final Parser P;
+ boolean _borrowed; // Source ^ parameters; separate compilation is still deferred.
+ private final Ary<Node> _kept = new Ary<>(Node.class);
+ private final Ary<Use> _uses = new Ary<>(Use.class);
+ private final Ary<LoadNode> _reads = new Ary<>(LoadNode.class);
+ Frame _frame;
+
+ Construction(Parser P) { this.P = P; }
+
+ static final class Frame {
+ final boolean _owner;
+ final Node _self;
+ Frame(boolean owner, Node self) { _owner = owner; _self = self; }
+ }
+ private record Use(Node ctrl, Node value, String operation, Lexer loc) {}
+
+ Frame enter(FunNode fun, boolean owner) {
+ Frame old = _frame;
+ _frame = new Frame(owner,fun.parm(2));
+ return old;
+ }
+
+ private Node keep(Node n) { _kept.add(n.keep()); return n; }
+
+ void complete(Node value, String operation, Lexer loc) {
+ if( !_frame._owner ) return;
+ _uses.add(new Use(keep(P.ctrl()),keep(value),operation,loc));
+ }
+
+ void ordinaryCall(Ary<Node> args, Lexer loc) {
+ TypeFunPtr sig = args.last()._type instanceof TypeFunPtr tfp ? tfp : null;
+ for( int i = 2; i<args.size()-1; i++ ) {
+ if( i==2 && sig!=null && sig.nargs()>0 && sig.arg(0)==TypePtr.PTR ) continue;
+ complete(args.at(i),"pass to an ordinary function",loc);
+ }
+ }
+
+ Node memory(Node ptr, String field, Lexer loc) {
+ if( own(ptr) ) return selfMem();
+ complete(ptr,"read field '"+field+"'",loc);
+ return P.mem();
+ }
+
+ Node loaded(LoadNode load) {
+ if( _frame._owner ) _reads.add((LoadNode)keep(load));
+ return P.peep(load);
+ }
+
+ void allocation(TypeFunPtr sig, Ary<Node> args, Lexer loc) {
+ if( !_frame._owner ) return;
+ for( int i = 0; i<args.size(); i++ )
+ if( sig==null || i>=sig.nargs() || !(sig.arg(i) instanceof TypeMemPtr ptr && ptr._partial) )
+ complete(args.at(i),"pass to a constructor parameter without '^'",loc);
+ }
+
+ Node selfMem() {
+ Var mem = P._scope.lookup("#selfMem");
+ return P._scope.in(P._scope.update(mem,null));
+ }
+
+ boolean own(Node ptr) {
+ while( ptr instanceof ConvertNode || ptr instanceof ReadOnlyNode || ptr instanceof GuardNode ) ptr = ptr.in(1);
+ return _frame._owner && ptr==_frame._self;
+ }
+
+ Node storeMemory(Node ptr) { return own(ptr) ? selfMem() : P.mem(); }
+
+ void store(Node ptr, int alias, Node st, Node prior) {
+ if( own(ptr) ) P._scope.update("#selfMem",P.mergeAlias(prior,alias,st));
+ else P.storeMem(st,alias,prior);
+ }
+
+ void check() {
+ Iter.types(P._code);
+ for( LoadNode load : _reads )
+ if( load.err()!=null ) throw load.err();
+ for( Use use : _uses )
+ if( !use.ctrl._type.isHigh() && TypeMemPtr.mayBePartial(use.value._type) )
+ throw Parser.error("Cannot "+use.operation+" with an incomplete object",use.loc);
+ P.structs.checkInitFields();
+ for( Node n : _kept ) n.unkeep();
+ _kept.clear();
+ _uses.clear();
+ _reads.clear();
+ }
+
+ void checkSerializable() {
+ if( _borrowed )
+ throw P.error("Separate compilation of constructors with '^' parameters is not supported yet");
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/Declarations.java b/chapter29/src/main/java/com/seaofnodes/simple/parser/Declarations.java
index 92e978b6..91955197 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/parser/Declarations.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/Declarations.java
@@ -65,7 +65,7 @@ final class Declarations {
expr = externDecl(name,t);
t = expr._type; // Upgrade declared type to the exact extern decl type
} else if( P.structs.isConstructorDecl(name) ) {
- expr = P.functions.parseConstructor(name);
+ expr = P.functions.parseConstructor(name,false);
} else {
expr = P.expressions.parseAssignment();
}
@@ -126,7 +126,7 @@ final class Declarations {
// Define a new name
if( !P._scope.define(name,t,xfinal || fld_final,lift, loc) )
throw P.error("Redefining name '" + name + "'", loc);
- P._scope.lookup(name)._uninit = uninit;
+ P.structs.declareField(P._scope.lookup(name),lift,uninit);
return lift;
}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/Functions.java b/chapter29/src/main/java/com/seaofnodes/simple/parser/Functions.java
index d848b0e3..4d597b8d 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/parser/Functions.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/Functions.java
@@ -35,32 +35,32 @@ final class Functions {
// Parse other arguments
P._lexer.skipWhiteSpace();
Lexer loc = P.loc(); // First argument location
- parameters(ts,ids,finals);
+ 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));
ReturnNode ret = parseFunctionBody(tfp,loc,finals,ids.asAry());
return new FunPtrNode(tfp,P._code._start,ret).peephole();
}
- Node parseConstructor(String name) {
+ Node parseConstructor(String name, boolean explicit) {
P.require("{");
Ary<Type> ts = new Ary<>(Type.class);
Ary<String> ids = new Ary<>(String.class);
BitSet finals = new BitSet();
TypeStruct self = P._scope.constructorSelf();
assert self != null;
- ts.push(TypeMemPtr.make(Structs.constructorRecv(self)));
+ ts.push(TypeMemPtr.make(Structs.constructorRecv(self)).withPartial(true));
ids.push("self");
// Explicit constructors use the same hidden allocation arguments as
// <init>: public memory is the call memory edge (index 1), followed by
// self (index 2) and the private, unescaped object memory (index 3).
- ts.push(TypeMem.makePrivate(self));
+ ts.push(TypeMem.makePrivate(Type.BOTTOM));
ids.push("#selfMem");
P._lexer.skipWhiteSpace();
Lexer loc = P.loc();
- parameters(ts,ids,finals);
- TypeFunPtr tfp = TypeFunPtr.make1((byte)2,true,ts.asAry(),TypeMem.makePrivate(self),P._code.fidx(P._ref._cname));
+ parameters(ts,ids,finals,explicit);
+ TypeFunPtr tfp = TypeFunPtr.make1((byte)2,true,ts.asAry(),TypeMem.makePrivate(Type.BOTTOM),P._code.fidx(P._ref._cname));
Node[] preParse = P._scope.save();
ReturnNode ret = parseBody((self._name+"."+name).intern(),tfp,loc,finals,ids.asAry());
P._scope.restore(preParse);
@@ -69,10 +69,16 @@ final class Functions {
}
// parameterList followed by the function arrow; callers seed hidden parameters.
- private void parameters(Ary<Type> ts, Ary<String> ids, BitSet finals) {
+ private void parameters(Ary<Type> ts, Ary<String> ids, BitSet finals, boolean constructor) {
while( true ) {
Type t = P.types.parseType(); // Arg type
if( t==null ) break;
+ if( P.match("^") ) {
+ if( !constructor || !(t instanceof TypeMemPtr ptr) || ptr._obj.isAry() )
+ throw P.error("'^' requires a struct parameter of a constructor");
+ t = ptr.withPartial(true);
+ P.construction._borrowed = true;
+ }
finals.set(ids.size(),P.declarations.bindingFinal(t));
String id = P.requireId();
ts .push(t ); // Push type/arg pairs
@@ -108,6 +114,7 @@ final class Functions {
// Unkeep them all
for( Node arg : args )
arg.unkeep();
+ P.construction.ordinaryCall(args,site);
return functionCall(args,site);
}
@@ -177,8 +184,8 @@ final class Functions {
// - - If isInit, also incrementally extend the self-type.
// If isInit
// - upgrade and close the self-type
- // - Gather fields and store values
- // - <init> returns self, not last
+ // - Store class singleton fields; instance Stores already exist
+ // - <init> and user constructors return their current private memory
// Unstack the {break,continue,return} scopes.
ReturnNode parseBody( String funName, TypeFunPtr sig, Lexer loc, String[] ids) {
@@ -200,8 +207,8 @@ final class Functions {
int oldUID = P._code.UID(); // Used to approximate function size
FunNode fun = enterFunction(funName,sig,loc,finals,ids);
+ Construction.Frame outerConstruction = P.construction.enter(fun,isCtor && !fun.isClz());
Node rpc = fun.parm(0);
- if( isCtor && !isInit ) P.structs.constructorDefaults(typeName,fun,loc);
if( fun.isClz() ) P.structs.beginClassInit(fun,loc);
// Parse the function body.
@@ -228,13 +235,13 @@ final class Functions {
if( fun.isClz() ) {
assert last!=null;
} else if( isCtor ) {
- last = fun.parm(3);
+ last = P.construction.selfMem();
}
addReturn(last);
// exit path is now dead (nothing live after a "return")
}
- if( isCtor )
- P.structs.upgradeSelfTypeAndStoreFields( typeName, fun.isClz(), self, fun.parm(3), isInit );
+ if( isInit )
+ P.structs.finishInitializer(typeName,fun.isClz(),self);
// Build a return from the _returnScope.
// Can be no returns for never-exit functions
@@ -258,6 +265,7 @@ final class Functions {
P.statements._continueScope = continueScope;
P.statements._breakScope = breakScope;
P._scope.pop();
+ P.construction._frame = outerConstruction;
return ret;
}
@@ -290,17 +298,19 @@ final class Functions {
return fun;
}
Node addReturn(Node expr) {
+ if( P.construction._frame._owner ) expr = P.construction.selfMem();
expr.keep();
// Force the pending lazy bulk-memory Phi.
P.mem(); // Force a pending lazy bulk-memory Phi
- // For <init> and <clinit> - all fields in scope will be live-on-exit
- // and stored into `self` and need to have lazy-phi's inserted.
+ // Class fields are still stored at exit. Instance fields already
+ // travel in expr's private memory; only their scope layout is retained.
int lexN = P._scope.enclosingFuncOrDecl();
Kind.Func k = (Kind.Func)P._scope._kinds.at(lexN);
if( FunNode.isInit(k._name) ) {
int nestedLexSize = lexN+1 < P._scope.depth() ? P._scope._kinds.at(lexN+1)._lexSize : P._scope.nIns();
- for( int i = k._lexSize; i<nestedLexSize; i++ )
- P._scope.update(P._scope.var(i),null);
+ if( k._name.endsWith(".<clinit>") )
+ for( int i = k._lexSize; i<nestedLexSize; i++ )
+ P._scope.update(P._scope.var(i),null);
if( _returnScope != null ) {
Var vexpr = _returnScope._vars.pop();
Node oldX = _returnScope.removeLast();
@@ -312,12 +322,6 @@ final class Functions {
_returnScope._vars.add(vexpr);
_returnScope.addDef(oldX);
}
- } else if( P.structs.isExplicitConstructorName(k._name) ) {
- // Field bindings live in the enclosing declaration, not among the
- // constructor's arguments/locals. Force lazy Phis on every exit.
- int first = P._scope._kinds.at(lexN-1)._lexSize+2;
- for( int i = first; i<k._lexSize; i++ )
- P._scope.update(P._scope.var(i),null);
}
// No prior merge point? Just clone and hang on to it
@@ -328,15 +332,12 @@ final class Functions {
_returnScope.define("$expr", Conversions.inferredReturnType(expr._type), true, expr, null);
} else {
- // For <init> and <clinit> - ALL FIELDS IN LAST SCOPE are live
- // and need to have lazy-phi's inserted.
-
- // And fields might not match.... will need matching Var/define with default values
+ // The initializer scopes have already been aligned above.
int rlen = _returnScope.nIns()-1;
RegionNode r = P.ctrl(new RegionNode(null, null,_returnScope.ctrl(), P._scope.ctrl()).init().keep());
_returnScope._merge(P._scope, r, rlen);
Node oldExpr = _returnScope.in(rlen);
- _returnScope.setDef(rlen,new PhiNode("$expr", r, oldExpr, expr).peephole());
+ _returnScope.setDef(rlen,PhiNode.make("$expr",expr._type, r, oldExpr, expr).peephole());
P._code.add(r);
_returnScope.ctrl(r.unkeep());
}
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 ccc9ebdc..93c3932b 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/parser/Parser.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/Parser.java
@@ -23,6 +23,10 @@ public class Parser {
final Allocations allocations = new Allocations(this);
final References references = new References(this);
final Conversions conversions = new Conversions(this);
+ final Construction construction = new Construction(this);
+
+ public void checkConstruction() { construction.check(); }
+ public void checkConstructionSerializable() { construction.checkSerializable(); }
// Compile driver
public final CodeGen _code;
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 e3e94b97..d1125842 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/parser/README.md
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/README.md
@@ -15,11 +15,12 @@ productions to their entry points.
| `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. |
| `Structs.java` | Struct and constructor declarations, field discovery, and initialization checks. |
+| `Construction.java` | Source checks for field initialization and incomplete references; its comment explains BOT fields, the TypeMemPtr qualifier, and allocation boundaries. |
| `References.java` | Bare-name and field resolution, implicit receivers, assignments, and forward references. |
| `Allocations.java` | Allocation expressions, strings, and declaration-side allocation helpers. |
| `ControlFlow.java` | Guarded scope merging shared by `if`, `?:`, `&&`, and `||`. |
| `Conversions.java` | Shared binding, field, and result conversions. |
-| `Var.java` | Lexical binding metadata: name, type, scope slot, mutability, and initialization state. |
+| `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
@@ -29,3 +30,7 @@ bodies after field discovery; `Lexer.skipTo` scans these deferred fragments.
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`.
+
+Instance constructor fields live in ordinary memory, with one hidden `#selfMem`
+binding per activation. Forward field types use `TypeScalar.BOT` for missing
+initialization; there are no per-field snapshots or store-all constructor exits.
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 a22383cf..4851d8b5 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/parser/References.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/References.java
@@ -6,7 +6,6 @@ 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.BitSet;
/** Name and field access, receiver selection, and lvalue checks. */
final class References {
@@ -63,20 +62,10 @@ final class References {
return parseField(rvalue,id,pre);
if( var._fref )
return P.expressions.parsePostfix(rvalue);
- // A constructor may read the unfinished object, but each field it
- // reads must already be initialized on every path. Read the current
- // local value; other fields may still be waiting for their first write.
- if( P.structs.inExplicitConstructor() && var._uninit && missingInit(rvalue,new BitSet()) )
- throw Parser.error("Field '"+id+"' might not be initialized",var._loc);
-
- // Check for an instance field load
- int kx = P._scope.kindx(var); // Declaration scope
- int fx = P._scope.enclosingFunction(); // Enclosing function scope
- Kind kk = P._scope._kinds.at(kx);
- Kind fk = P._scope._kinds.at(fx);
- // Access instance field from 'self'
- if( kx+1 == fx && kk instanceof Kind.Func inst && FunNode.isInstance(inst._name) && fk instanceof Kind.Func method && !P.structs.inExplicitConstructor() )
- return parseField(P._scope.in(method._lexSize),id,pre);
+ Node fieldSelf = fieldSelf(var);
+ if( fieldSelf!=null ) return parseField(fieldSelf,id,pre);
+ int kx = P._scope.kindx(var);
+ int fx = P._scope.enclosingFunction();
// Check for a function-escaping variable; these require true
// closures. Final constants are OK; final vars require a hidden var
@@ -130,6 +119,19 @@ final class References {
}
Node assign(Var def, Node expr) {
+ Node self = fieldSelf(def);
+ if( self!=null ) {
+ TypeStruct ts = P.types.latestStruct(((TypeMemPtr)self._type)._obj);
+ Field fld = ts.field(def._name);
+ Node val = expr.keep();
+ Node lift = P.conversions.convertExpr(val,def.type());
+ Node mem = P.construction.storeMemory(self).keep();
+ Node st = new StoreNode(P.loc(),fld._fname,fld._alias,fld._t,P.ctrl(),mem,self,
+ ConFldOffNode.make(ts,fld._fname),lift,P.construction.own(self)).peephole();
+ P.construction.store(self,fld._alias,st,mem);
+ mem.unkeep();
+ return val.unkeep();
+ }
String name = def._name;
// Lift expression, based on type
Type decl = def.type(); // Declared type
@@ -141,23 +143,9 @@ final class References {
int kx = P._scope.kindx(def); // Declaration scope
int fx = P._scope.enclosingFunction(); // Enclosing function scope
Kind kk = P._scope._kinds.at(kx);
- Kind fk = P._scope._kinds.at(fx);
- // Access instance field from 'self'
- if( kx+1 == fx && kk instanceof Kind.Func inst && FunNode.isInstance(inst._name) &&
- fk instanceof Kind.Func method && !P.structs.inExplicitConstructor() ) {
- // Named field offset on an unknown struct type
- TypeMemPtr self = (TypeMemPtr)P._scope.var(fk._lexSize).type();
- TypeStruct sobj = (TypeStruct)Parser.TYPES.get(self._obj._name);
- Field fld = sobj.field(name);
- int alias = fld._alias;
- Node ptr = P._scope.in(method._lexSize);
- Node off = P.fldoff(ptr,name);
- Node prior = P.mem().keep();
- Node st = new StoreNode(P.loc(), name, alias, decl, P.ctrl(), prior, ptr, off, lift, P.structs.inExplicitConstructor()).peephole().keep();
- P.storeMem(st,alias,prior);
- st.unkeep();
- prior.unkeep();
- }
+ if( kx<fx && !(P.structs.inExplicitConstructor() && kx+1==fx &&
+ kk instanceof Kind.Func owner && FunNode.isInstance(owner._name)) )
+ P.construction.complete(lift,"store outside the construction group",P.loc());
lift.unkill();
// Return un-lifted expr
@@ -181,7 +169,6 @@ final class References {
}
Node parseField(Node expr, String name, int pre, Lexer site) {
- if( P.structs.inExplicitConstructor() && privateSelf(expr) ) return privateField(name,pre);
// Keep expr across possible updates
expr.keep();
@@ -246,16 +233,20 @@ final class References {
if( pre==0 && P.matchOpx('=','=') ) {
// Field assignment
Node val = P.expressions.parseAssignment().keep();
+ if( !P.construction.own(expr) ) {
+ P.construction.complete(expr,"write through an unfinished reference",site);
+ P.construction.complete(val,"store into an ordinary object",site);
+ }
// Lift value for store
Node lift = fld==null ? val : new ConvertNode(fld._t,val).peephole();
// Memory for store, post assignment expression
- Node mem = P.mem();
+ Node mem = P.construction.storeMemory(expr);
// Store to field
mem.keep();
- if( expr._type instanceof TypeMemPtr view && view._ro && P.ctrl()._type!=Type.XCONTROL )
+ if( !P.construction.own(expr) && expr._type instanceof TypeMemPtr view && view._ro && P.ctrl()._type!=Type.XCONTROL )
throw P.error("Cannot modify final field '"+name+"'");
- Node st = new StoreNode(P.loc(), name, alias, decl, P.ctrl(), mem, expr.unkeep(), off.unkeep(), lift, false).peephole().keep();
- P.storeMem(st,alias,mem);
+ Node st = new StoreNode(P.loc(), name, alias, decl, P.ctrl(), mem, expr.unkeep(), off.unkeep(), lift, P.construction.own(expr)).peephole().keep();
+ P.construction.store(expr,alias,st,mem);
st.unkeep();
mem.unkeep();
@@ -263,26 +254,27 @@ final class References {
}
// Memory for load
- Node mem = P.mem();
+ Node mem = P.construction.memory(expr,name,site);
// Loading from a constant array, the declared type is the
// meet-over-elements and not the array itself.
if( name=="[]" && decl instanceof TypeConAry conary )
decl = conary.elem();
// Load field
- Node load = P.peep(new LoadNode(P.loc(),name, alias, P.ctrl(), mem, expr, off));
+ Node load = P.construction.loaded(new LoadNode(P.loc(),name,alias,P.ctrl(),mem,expr,off));
// Check for assign-update, "ptr.fld += expr" or "ary[idx]++"
char ch = pre==0 ? P._lexer.matchOperAssign() : (char)pre;
if( ch!=0 ) {
+ if( !P.construction.own(expr) ) P.construction.complete(expr,"write through an unfinished reference",site);
if( fld!=null && decl==Type.BOTTOM )
throw P.error( "'" + ts._name + "' is not fully initialized, field '" + fld._fname + "' needs to be set in a constructor" );
Node op = P.expressions.opAssign(ch,load, decl ).keep();
mem.keep();
- if( expr._type instanceof TypeMemPtr view && view._ro && P.ctrl()._type!=Type.XCONTROL )
+ if( !P.construction.own(expr) && expr._type instanceof TypeMemPtr view && view._ro && P.ctrl()._type!=Type.XCONTROL )
throw P.error("Cannot modify final field '"+name+"'");
- Node st = new StoreNode(P.loc(), name, alias, decl, P.ctrl(), mem, expr.unkeep(), off.unkeep(), op, false).peephole().keep();
- P.storeMem(st,alias,mem);
+ Node st = new StoreNode(P.loc(), name, alias, decl, P.ctrl(), mem, expr.unkeep(), off.unkeep(), op, P.construction.own(expr)).peephole().keep();
+ P.construction.store(expr,alias,st,mem);
st.unkeep();
mem.unkeep();
@@ -300,37 +292,14 @@ final class References {
return P._scope.in(P._scope.update(P._scope.var(selfx),null));
}
- private boolean privateSelf(Node ptr) {
- while( ptr instanceof ConvertNode || ptr instanceof ReadOnlyNode || ptr instanceof GuardNode ) ptr = ptr.in(1);
- Node self = defaultSelf();
- while( self instanceof ConvertNode || self instanceof ReadOnlyNode || self instanceof GuardNode ) self = self.in(1);
- return ptr==self;
- }
-
- // An explicit self.x read sees the same current value as bare x. Loading
- // the allocation here would miss updates still held in local SSA values.
- private Node privateField(String name, int pre) {
- int idx = P.structs.fieldVarIdxBefore(P._scope._kinds.at(P._scope.enclosingFuncOrDecl())._lexSize,name);
- if( idx<0 ) throw P.error("Cannot resolve field '"+name+"'");
- Var v = P._scope.var(idx);
- if( pre==0 && P.matchOpx('=','=') ) {
- checkSharedFunction(v);
- Node value = P.expressions.parseAssignment().keep();
- P._scope.update(v,P.conversions.convertExpr(value,v.type()));
- return value.unkeep();
- }
- Node value = P._scope.in(P._scope.update(v,null));
- if( v._uninit && missingInit(value,new BitSet()) )
- throw Parser.error("Field '"+name+"' might not be initialized",v._loc);
- char ch = pre==0 ? P._lexer.matchOperAssign() : (char)pre;
- if( ch!=0 ) {
- checkSharedFunction(v);
- Node op = P.expressions.opAssign(ch,value,v.type());
- P._scope.update(v,op);
- if( pre!=0 && Lexer.postfix(ch) ) value.unkeep();
- return pre==0 && Lexer.postfix(ch) ? value.unkeep() : op;
- }
- return P.expressions.parsePostfixMethod(value,defaultSelf());
+ // 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();
+ 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();
+ return null;
}
private void checkSharedFunction(Var v) {
@@ -338,20 +307,6 @@ final class References {
throw P.error("Cannot reassign shared function '"+v._name+"'");
}
- // A loop Phi is temporarily bottom before its backedge is parsed. That is
- // not an unwritten field: follow its live inputs to the actual defaults.
- private boolean missingInit(Node value, BitSet seen) {
- if( value==null || seen.get(value._nid) ) return false;
- seen.set(value._nid);
- if( value instanceof PhiNode phi && !(value instanceof ParmNode) ) {
- for( int i = 1; i<phi.nIns(); i++ )
- if( phi.region().in(i)!=null && phi.region().in(i)._type!=Type.XCONTROL && missingInit(phi.in(i),seen) )
- return true;
- return false;
- }
- return !Structs.initialized(value._type);
- }
-
Ary<FRefNode> resolve() {
// All still-unresolved forward refs
// should immediately resolve to a file-based name which is a Simple
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 57b43715..3360d85e 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/parser/Structs.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/Structs.java
@@ -61,6 +61,8 @@ final class Structs {
// This is basically sugar for "val CLZNAME = <init>"
// and is a nicer version of updateSelfAsFieldsDiscovered.
P._scope.define(typeName, sig, true, new FunPtrNode(sig,P._code._start,ret).peephole(), P.loc());
+ Var binding = P._scope.lookup(typeName);
+ declareField(binding,P._scope.in(binding),false);
P.require("}");
return P.require(P._code.ZERO,";");
@@ -81,7 +83,7 @@ final class Structs {
if( _pendingCtor!=null ) throw P.error("Redefining constructor '"+name+"'");
_pendingCtor = new Constructor(name,P.loc());
// Parse the body after all field declarations are known, so self.x
- // and x share a local slot even when x is declared below this body.
+ // and x resolve the same field even when x is declared below this body.
P.require("{");
P._lexer.skipTo('{','}',"constructor body");
P.require("}");
@@ -101,7 +103,7 @@ final class Structs {
private void defineConstructor(String name) {
TypeStruct self = P._scope.constructorSelf();
- Node ctor = P.functions.parseConstructor(name);
+ Node ctor = P.functions.parseConstructor(name,true);
TypeFunPtr sig = (TypeFunPtr)ctor._type;
String clzName = TypeStruct.addClzPrefix(self._name);
TypeStruct clz = (TypeStruct)Parser.TYPES.get(clzName);
@@ -139,7 +141,6 @@ final class Structs {
// Struct decls look like function bodies. Parse function body normally.
ReturnNode ret = P.functions.parseBody(fname,sig,P.loc(),ids);
- if( !isClz ) checkInitFields(ret);
// Unwind global state.
P.ctrl(oldCtrl.unkeep());
P.mem (oldMem .unkeep());
@@ -149,13 +150,13 @@ final class Structs {
// <cl/init> signature: { self arg/selfMem -> BOT/selfMem }
// Self-memory is the rare *private* (never aliased) memory for the new object.
private TypeFunPtr structInitSig( boolean isClz, TypeStruct tself ) {
- // Private uninitialized memory from new object, no escaped values.
- TypeMem privMem = isClz ? null : TypeMem.make(1,tself,true,isClz,false,null,null);
- Type targ = isClz ? TypeInteger.BOT : privMem;
+ // One object, unknown contents. The signature must not promise that
+ // its declared field values have already been supplied.
+ Type targ = isClz ? TypeInteger.BOT : TypeMem.makePrivate(Type.BOTTOM);
// Return for private memory has to name all escaped fields, which are not known yet.
// Escape ALL fields, and sharpen later.
- Type tret = isClz ? Type.BOTTOM : TypeMem.makePrivate(tself);
- Type[] targs = new Type[]{ TypeMemPtr.make((byte)2,tself,isClz), targ };
+ Type tret = isClz ? Type.BOTTOM : TypeMem.makePrivate(Type.BOTTOM);
+ Type[] targs = new Type[]{ TypeMemPtr.make((byte)2,tself,isClz).withPartial(!isClz), targ };
// FIDX for FREFs is in the global namespace, needs the correct clz name.
String clzBare = tself._name;
int order = 3; // 3 is always the <init> order, 2nd in the file just behind the <clinit>
@@ -188,72 +189,58 @@ final class Structs {
return nvar;
}
- void upgradeSelfTypeAndStoreFields( String typeName, boolean isClz, ParmNode self, ParmNode smem, boolean upgrade ) {
+ void finishInitializer( String typeName, boolean isClz, ParmNode self ) {
int lex = P._scope.klast()._lexSize;
assert P._scope.var(lex)._name=="self";
TypeStruct tself = (TypeStruct)Parser.TYPES.get(typeName);
int base = lex+2; // Skip ctrl, mem
- if( upgrade ) {
- // Default <init>/<clinit> defines the struct and can sharpen fields.
- // A named constructor only initializes the already-closed struct.
- for( int i = 0; i<tself.nkids(); i++ ) {
- Field tfld = tself._fields[i];
- int vidx = fieldVarIdx(base,tfld._fname);
- if( vidx == -1 )
- continue;
- Var v = P._scope.var(vidx);
- // Forward-ref fields where placed in tself but will get promoted
- // to an outer scope - and should not be in tself.
- if( v._fref )
- tself = tself.remove(i--);
- else if( v.type() != tfld._t ) {
- assert v.type().isa(tfld._t);
- tself = tself.replace( tfld.makeFrom( v.type() ) );
- }
+ // Default <init>/<clinit> defines the struct and can sharpen fields.
+ // A named constructor only initializes the already-closed struct.
+ for( int i = 0; i<tself.nkids(); i++ ) {
+ Field tfld = tself._fields[i];
+ int vidx = fieldVarIdx(base,tfld._fname);
+ if( vidx == -1 )
+ continue;
+ Var v = P._scope.var(vidx);
+ // Forward-ref fields where placed in tself but will get promoted
+ // to an outer scope - and should not be in tself.
+ if( v._fref )
+ tself = tself.remove(i--);
+ else if( v.type() != tfld._t ) {
+ assert v.type().isa(tfld._t);
+ tself = tself.replace( tfld.makeFrom( v.type() ) );
}
-
- // Close Struct type after parsing struct body.
- Parser.TYPES.put( tself._name, tself = tself.close() );
- // Improve self, selfMem types in scope.
- // Class self-type is a singleton pointer. Instance <init> receives the
- // broad allocation pointer, while private memory tracks initialized fields.
- self._declaredType = self._type = isClz ? TypeMemPtr.make((byte)2,tself,true) : TypeMemPtr.make(constructorRecv(tself));
- FunNode fun = self.fun();
- fun.setSig(fun.sig().makeFrom(self._type,0));
- if( !isClz ) // <init> returns upgraded private memory
- smem._declaredType = smem._type = TypeMem.makePrivate(tself);
}
+ // Close Struct type after parsing struct body.
+ Parser.TYPES.put( tself._name, tself = tself.close() );
+ // Improve the receiver type in scope.
+ // Class self-type is a singleton pointer. Instance <init> receives the
+ // broad allocation pointer, while private memory tracks initialized fields.
+ self._declaredType = self._type = isClz ? TypeMemPtr.make((byte)2,tself,true) : TypeMemPtr.make(constructorRecv(tself)).withPartial(true);
+ FunNode fun = self.fun();
+ fun.setSig(fun.sig().makeFrom(self._type,0));
+
// 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
if( P.functions._returnScope==null )
return;
- // A MemMerge to gather field updates as stores. Classes update the
- // normal public memory. <init> updates the private memory that got
- // passed in - is treated like a normal argument and not like memory.
- Node mmm = isClz ? P.functions._returnScope.mem(1) : smem;
+ // Class singleton fields retain their store-at-exit path. Instance
+ // initializers already returned their current private memory.
+ if( !isClz ) return; // Instance fields already have their Stores.
+ Node mmm = P.functions._returnScope.mem(1);
// Store constructor results into fields
for( int i = 0; i<tself.nkids(); i++ ) {
Field fld = tself._fields[i];
- int vidx = upgrade
- ? fieldVarIdx(base,fld._fname)
- : fieldVarIdxBefore(lex,fld._fname);
+ int vidx = fieldVarIdx(base,fld._fname);
if( vidx == -1 )
continue;
Var v = P._scope.var(vidx);
if( v._fref || v._extern!=0 ) continue; // C storage has no Simple initializer
Node val = P.functions._returnScope.in(vidx);
- // 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 )
@@ -266,18 +253,18 @@ final class Structs {
}
mmm = mmm.peephole();
- if( !isClz )
- // Stuff private mem into normal expression return value.
- P.functions._returnScope.setDef(P.functions._returnScope.nIns()-1,mmm);
- else
- // Parser memory is one opaque whole-memory edge. Publish the
- // completed class-field aggregate as the <clinit> return memory.
- P.functions._returnScope.mem(mmm);
- //_code.add(mmm);
+ P.functions._returnScope.mem(mmm);
}
// addReturn already merged every exit. Inspect the completed helpers,
// after parsing has discovered all fields and torn down their scopes.
+ void checkInitFields() {
+ P._code._stop.walk(n -> {
+ if( n instanceof ReturnNode ret && ret.fun().isInstance() ) checkInitFields(ret);
+ return null;
+ });
+ }
+
private void checkInitFields(ReturnNode init) {
if( init.ctrl()._type.isHigh() || init.expr()._type.isHigh() ) return;
TypeStruct ts = ((TypeMemPtr)init.fun().sig().arg(0))._obj;
@@ -294,32 +281,33 @@ final class Structs {
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 )
+ Type mem = ret.expr()._type;
+ Field actual = mem instanceof TypeMem tm && tm._t instanceof TypeStruct shape ? shape.field(fld._fname) : null;
+ if( actual==null || actual._t==TypeScalar.BOT || actual._t==Type.BOTTOM )
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);
}
}
- // Seed the constructor-local SSA slots from the declaration initializer.
- void constructorDefaults(String typeName, FunNode fun, Lexer loc) {
- int lex = P._scope.klast()._lexSize;
- TypeStruct shape = (TypeStruct)Parser.TYPES.get(typeName);
- for( Field fld : shape._fields ) {
- int idx = fieldVarIdxBefore(lex,fld._fname);
- if( idx<0 ) continue;
- 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),ConFldOffNode.make(shape,fld._fname)).peephole(),v.type());
- P._scope.update(v,value);
- }
+ // Instance declarations establish field metadata and emit their default
+ // Stores immediately. Required fields emit no Store until assigned.
+ void declareField(Var var, Node value, boolean uninit) {
+ if( !(P._scope.klast() instanceof Kind.Func kind) || !FunNode.isInstance(kind._name) ) return;
+ TypeStruct self = P._scope.constructorSelf();
+ TypeStruct shape = (TypeStruct)Parser.TYPES.get(self._name);
+ Field old = shape.field(var._name);
+ Field fld = old==null ? Field.make(var._name,var.type(),P._code.alias(P._ref._cname),var._final) : old.makeFrom(var.type());
+ Parser.TYPES.put(shape._name,shape = shape.addOrUpdate(fld));
+ if( uninit || var._extern!=0 ) return;
+ Node ptr = P.references.defaultSelf();
+ Node mem = P.construction.selfMem().keep();
+ Node st = new StoreNode(var._loc,fld._fname,fld._alias,fld._t,P.ctrl(),mem,ptr,
+ ConFldOffNode.make(shape,fld._fname),value,true).peephole();
+ P.construction.store(ptr,fld._alias,st,mem);
+ mem.unkeep();
}
void beginClassInit(FunNode fun, Lexer loc) {
@@ -343,14 +331,6 @@ final class Structs {
prior.unkeep();
}
- // 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)
- // Can't be left null and declared not-null.
- return t!=Type.BOTTOM && !t.isHigh() && (!(t instanceof TypeNil tn) || tn.notNull());
- }
-
private int fieldVarIdx( int base, String name ) {
for( int i = base; i<P._scope.nIns(); i++ )
if( P._scope.var(i)._name == name )
@@ -358,16 +338,6 @@ final class Structs {
return -1;
}
- // Named constructors update field Vars from the enclosing initializer
- // scope. Constructor arguments and locals begin at limit and must not
- // shadow the field state gathered into private memory.
- int fieldVarIdxBefore( int limit, String name ) {
- for( int i = limit-1; i>=0; i-- )
- if( P._scope.var(i)._name == name )
- return i;
- return -1;
- }
-
boolean isConstructorDecl(String name) {
TypeStruct self = P._scope.constructorSelf();
return self != null && name == initFieldName(self._name);
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 970b5a50..3cb474b1 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/parser/Types.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/Types.java
@@ -73,6 +73,7 @@ final class Types {
// Check valid forward ref, after parsing all the type extra bits.
// Cannot check earlier, because cannot find required 'id' until after "[]?" syntax
int old2 = P.pos();
+ P.match("^"); // Constructor parameter qualifier; parsed by Functions.
if( !P.match("!") ) P.match("~");
String id = P._lexer.matchId();
if( !(P.peek(',') || P.peek(';') || P.peek('=') || P.match("->")) || id==null )
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/Var.java b/chapter29/src/main/java/com/seaofnodes/simple/parser/Var.java
index 7d3be3c2..ddbb91d8 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/parser/Var.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/Var.java
@@ -16,9 +16,6 @@ public class Var {
private Type _type; // Declared type
public boolean _final; // Final field
public boolean _fref; // Forward ref
- // Parser-only definite-initialization marker. The current binding's
- // nullable type carries the path-sensitive state through SSA merges.
- public boolean _uninit;
public int _extern; // C storage alias; zero for ordinary variables
public Lexer _loc; // Source location
@@ -49,6 +46,6 @@ public class Var {
}
@Override public String toString() {
- return (_fref ? "FREF " : "")+(_uninit ? "UNINIT " : "")+_type.toString()+(_final ? " ": " !")+_name;
+ return (_fref ? "FREF " : "")+_type.toString()+(_final ? " ": " !")+_name;
}
}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/type/TypeMem.java b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeMem.java
index 278a3ea0..c72740a9 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/type/TypeMem.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeMem.java
@@ -79,6 +79,15 @@ public class TypeMem extends Type {
public static TypeMem makePrivate(Type t) { return make(1,t,true,false,false,escapeFIDX(XInt.EMPTY,t), escapeAlias(XInt.EMPTY,t)); }
public TypeMem makeFrom(int alias) { return make(alias,_t,_one,_clz,_final,_escFs,_escAs); }
+ // Widen numeric fields at cyclic memory merges just as scalar Phis do.
+ public TypeMem widen() {
+ if( !_one || !(_t instanceof TypeStruct ts) ) return this;
+ TypeStruct wide = ts;
+ for( Field fld : ts._fields )
+ if( fld._t instanceof TypeInteger ti && !ti.isHigh() && !ti.isConstant() )
+ wide = wide.replace(fld.makeFrom(ti.same_but_slightly_wider_than()));
+ return wide==ts ? this : make(_alias,wide,_one,_clz,_final,_escFs,_escAs);
+ }
public TypeMem makeFrom(int[] fidxes, int[] aliases) {
return make(_alias,_t,_one,_clz,_final,fidxes,aliases);
}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
index 175b1c63..943710d1 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
@@ -29,13 +29,16 @@ public class TypeMemPtr extends TypeNil {
public TypeStruct _obj;
// Deep read-only access through this pointer; independent of slot finality.
public boolean _ro;
+ // This reference may still depend on unfinished construction. Independent
+ // two-point lattice: meet ORs the restriction, dual complements it.
+ public boolean _partial;
public boolean _one; // Singleton pointer value; the pointer itself is constant.
public boolean _pub; // Escaped/public pointer. False means constructor-private.
private static final Ary<TypeMemPtr> FREE = new Ary<>(TypeMemPtr.class);
private TypeMemPtr(byte nil, TypeStruct obj, boolean one, boolean pub) { super(TMEMPTR,nil); init(nil,obj,one,pub); }
- private TypeMemPtr init(byte nil, TypeStruct obj, boolean one, boolean pub) { _nil=nil; _obj=obj; _ro=obj==TypeStruct.BOT; _one=one; _pub=pub; return this; }
+ private TypeMemPtr init(byte nil, TypeStruct obj, boolean one, boolean pub) { _nil = nil; _obj = obj; _ro = obj==TypeStruct.BOT; _partial = obj==TypeStruct.BOT; _one = one; _pub = pub; return this; }
// Return a filled-in TypeMemPtr; either from free list or alloc new.
static TypeMemPtr malloc(byte nil, TypeStruct obj, boolean one, boolean pub) {
return FREE.isEmpty() ? new TypeMemPtr(nil,obj,one,pub) : FREE.pop().init(nil,obj,one,pub);
@@ -46,10 +49,10 @@ public class TypeMemPtr extends TypeNil {
return make(nil,obj,one,true);
}
public static TypeMemPtr make(byte nil, TypeStruct obj, boolean one, boolean pub) {
- return make(nil,obj,one, pub,obj==TypeStruct.BOT);
+ return make(nil,obj,one,pub,obj==TypeStruct.BOT,obj==TypeStruct.BOT);
}
- private static TypeMemPtr make(byte nil, TypeStruct obj, boolean one, boolean pub, boolean ro) {
- TypeMemPtr tmp = malloc(nil, obj, one, pub).access(ro);
+ private static TypeMemPtr make(byte nil, TypeStruct obj, boolean one, boolean pub, boolean ro, boolean partial) {
+ TypeMemPtr tmp = malloc(nil, obj, one, pub).access(ro).partial(partial);
TypeMemPtr t2 = tmp.intern();
if( t2==tmp ) return tmp;
return VISIT.isEmpty() ? t2.free(tmp) : t2.delayFree(tmp);
@@ -72,21 +75,33 @@ public class TypeMemPtr extends TypeNil {
public static TypeMemPtr makeNullable(TypeStruct obj) { return make((byte)3, obj); }
public static TypeMemPtr makePrivate (TypeStruct obj) { return make((byte)2, obj, false, false); }
- public TypeMemPtr makeFrom(TypeStruct obj) { return obj==_obj ? this : make(_nil, obj, _one, _pub,_ro); }
+ public TypeMemPtr makeFrom(TypeStruct obj) { return obj==_obj ? this : make(_nil, obj, _one, _pub,_ro,_partial); }
public TypeMemPtr makeNullable() { return makeFrom((byte)3); }
/** Inferred mutable local: nullable, public and non-singleton. */
public TypeMemPtr makeVar() { return (TypeMemPtr)makeStorage(); }
/** Public-memory shape: nullable and non-singleton, preserving the referent. */
- @Override TypeMemPtr _makeStorage() { return make(_nil,_obj._makeStorage(),false,true,_ro); }
- @Override TypeMemPtr makeFrom(byte nil) { return nil==_nil ? this : make(nil, _obj, _one, _pub,_ro); }
+ @Override TypeMemPtr _makeStorage() { return make(_nil,_obj._makeStorage(),false,true,_ro,_partial); }
+ @Override TypeMemPtr makeFrom(byte nil) { return nil==_nil ? this : make(nil, _obj, _one, _pub,_ro,_partial); }
@Override public Type nonZero() { return makeFrom((byte)(_nil <= 1 ? 1 : 2)); }
private TypeMemPtr access(boolean ro) { _ro=ro; return this; }
+ private TypeMemPtr partial(boolean partial) { _partial = partial; return this; }
+
+ public TypeMemPtr withPartial(boolean partial) {
+ return partial==_partial ? this : make(_nil,_obj,_one,_pub,_ro,partial);
+ }
+
+ // Conservative on unresolved scalar/pointer families; null is complete.
+ public static boolean mayBePartial(Type t) {
+ return t instanceof TypeMemPtr ptr ? ptr._partial
+ : t==Type.BOTTOM || t==TypeScalar.BOT || (t instanceof TypePtr && !t.isHigh());
+ }
public TypeMemPtr withAccess(boolean ro) {
if( _ro==ro ) return this;
TypeMemPtr ptr = malloc(_nil, _obj, _one, _pub);
ptr._ro = ro;
+ ptr._partial = _partial;
TypeMemPtr res = ptr.intern();
if( res==ptr ) return ptr;
return VISIT.isEmpty() ? res.free(ptr) : res.delayFree(ptr);
@@ -109,7 +124,7 @@ public class TypeMemPtr extends TypeNil {
public static final TypeMemPtr NOTBOT = make((byte)2,TypeStruct.BOT);
public static final TypeMemPtr TEST= make((byte)2, TypeStruct.TEST);
- public static void gather(ArrayList<Type> ts) { ts.add(NOTBOT); ts.add(BOT); ts.add(TEST); ts.add(TEST.makeRO()); }
+ public static void gather(ArrayList<Type> ts) { ts.add(NOTBOT); ts.add(BOT); ts.add(TEST); ts.add(TEST.makeRO()); ts.add(TEST.withPartial(true)); ts.add(TEST.withPartial(true).makeRO()); }
@Override
public TypeNil xmeet(Type t) {
@@ -118,28 +133,28 @@ public class TypeMemPtr extends TypeNil {
// Can't keep _one if mixing two unequal singletons; the result is not
// either singleton. Once either side is public, the meet is public.
boolean one = _one && that._one && (_obj==that._obj || !(_obj.isConAry() || that._obj.isConAry()));
- return make(xmeet0(that), (TypeStruct)_obj.meet(that._obj), one, _pub | that._pub,_ro | that._ro);
+ return make(xmeet0(that), (TypeStruct)_obj.meet(that._obj), one, _pub | that._pub,_ro | that._ro, _partial | that._partial);
}
@Override
- TypeMemPtr xdual() { return malloc( dual0(), _obj.dual(), !_one, !_pub).access(!_ro); }
+ TypeMemPtr xdual() { return malloc( dual0(), _obj.dual(), !_one, !_pub).access(!_ro).partial(!_partial); }
@Override TypeMemPtr rdual() {
if( _dual!=null ) return dual();
assert !_terned;
- TypeMemPtr d = malloc(dual0(), null, !_one, !_pub).access(!_ro);
+ TypeMemPtr d = malloc(dual0(), null, !_one, !_pub).access(!_ro).partial(!_partial);
(_dual = d)._dual = this; // Cross link duals
d._obj = _obj._terned ? _obj.dual() : _obj.rdual();
return d;
}
// RHS is NIL; do not deep-dual when crossing the center line
- @Override Type meet0() { return _nil==3 ? this : make((byte)3,_obj,false,true,_ro); }
+ @Override Type meet0() { return _nil==3 ? this : make((byte)3,_obj,false,true,_ro,_partial); }
@Override boolean _isConstant() { return _one; }
@Override boolean _isFinal() { return _ro; }
@Override TypeMemPtr _makeRO() { return withAccess(true); }
- @Override TypeMemPtr _close( String name, HashMap<String, Type> TYPES ) { return malloc(_nil,_obj._close(name, TYPES ),_one,_pub).access(_ro); }
+ @Override TypeMemPtr _close( String name, HashMap<String, Type> TYPES ) { return malloc(_nil,_obj._close(name, TYPES ),_one,_pub).access(_ro).partial(_partial); }
@Override Type _upgradeType(HashMap<String,Type> TYPES) {
return makeFrom((TypeStruct)_obj._upgradeType(TYPES));
@@ -147,47 +162,48 @@ public class TypeMemPtr extends TypeNil {
@Override public int log_size() { return 3; } // (1<<3)==8-byte pointers
- @Override int hash() { return (_ro ? 8192 : 0) ^ _obj.hashCode() ^ super.hash() ^ (_one ? 2048 : 0) ^ (_pub ? 4096 : 0); }
+ @Override int hash() { return (_partial ? 16384 : 0) ^ (_ro ? 8192 : 0) ^ _obj.hashCode() ^ super.hash() ^ (_one ? 2048 : 0) ^ (_pub ? 4096 : 0); }
@Override boolean eq(Type t) {
TypeMemPtr ptr = (TypeMemPtr)t; // Invariant
- return _ro==ptr._ro && super.eq(ptr) && _one == ptr._one && _pub == ptr._pub && _obj == ptr._obj;
+ return _partial==ptr._partial && _ro==ptr._ro && super.eq(ptr) && _one == ptr._one && _pub == ptr._pub && _obj == ptr._obj;
}
@Override boolean cycle_eq(Type t) {
if( t._type != TMEMPTR ) return false;
TypeMemPtr ptr = (TypeMemPtr)t; // Invariant
- return _ro==ptr._ro && super.eq(ptr) && _one == ptr._one && _pub == ptr._pub && _obj.cycle_eq(ptr._obj);
+ return _partial==ptr._partial && _ro==ptr._ro && super.eq(ptr) && _one == ptr._one && _pub == ptr._pub && _obj.cycle_eq(ptr._obj);
}
@Override public int nkids() { return 1; }
@Override public Type at( int idx ) { return _obj; }
@Override public void set( int idx, Type t ) { _obj = (TypeStruct)t; }
- // Reserve tags for null/not, one/general, public/private and read-only access
- @Override int TAGOFF() { return 16; }
+ // Reserve tags for null/not, one/general, public/private and read-only and incomplete access
+ @Override int TAGOFF() { return 32; }
@Override public void packed( BAOS baos, HashMap<String,Integer> strs ) {
assert _nil>=2;
baos.write(TAGOFFS[_type]
+ (_nil==2 ? 0 : 1)
+ (!_one ? 0 : 2)
+ (!_pub ? 0 : 4)
- + (!_ro ? 0 : 8) );
+ + (!_ro ? 0 : 8)
+ + (!_partial ? 0 : 16) );
}
static TypeMemPtr packed( int tag, BAOS bais ) {
- return malloc((byte)((tag&1)+2),null,(tag&2)==2,(tag&4)==4).access((tag&8)==8);
+ return malloc((byte)((tag&1)+2),null,(tag&2)==2,(tag&4)==4).access((tag&8)==8).partial((tag&16)==16);
}
@Override public String str() {
if( this==BOT ) return "PtrBot";
if( this==TOP ) return "PtrTop";
if( this== NOTBOT) return "*void";
- return x()+"*"+(_obj==null?"---":_obj.str())+q();
+ return (_partial ? "^" : "")+x()+"*"+(_obj==null?"---":_obj.str())+q();
}
@Override SB _print(SB sb, BitSet visit, boolean html ) {
if( this==BOT ) return sb.p("PtrBot");
if( this==TOP ) return sb.p("PtrTop");
if( this== NOTBOT) return sb.p("*void");
- return _obj.print(sb.p(x()).p("*"),visit,html).p(q());
+ return _obj.print(sb.p(_partial ? "^" : "").p(x()).p("*"),visit,html).p(q());
}
}
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 9c0c9693..d0ac55aa 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
@@ -103,7 +103,7 @@ public class TypeStruct extends Type {
for( Field f : _fields )
if( f._fname.charAt(0) != '_' && // Skip private fields.
!hiddenInit(f) && // Skip private allocation helper.
- f._t != Type.TOP ) // Skip uninit fields
+ f._t != Type.TOP && f._t != TypeScalar.TOP ) // Skip unreachable values
xs[XInt.idx(f._alias)] |= XInt.mask(f._alias);
return (_aliases=XInt.intern(xs));
}
@@ -116,10 +116,10 @@ public class TypeStruct extends Type {
assert _terned;
int[] xs = XInt.EMPTY;
for( Field f : _fields )
- if( f._fname.charAt(0) != '_' && // Skip private fields.
- !hiddenInit(f) && // Skip private allocation helper.
- f._t instanceof TypeFunPtr tfp ) // Looking for TFP
- xs = XInt.meet(xs,tfp._fidxs);
+ if( f._fname.charAt(0) != '_' && !hiddenInit(f) ) {
+ if( f._t==TypeScalar.BOT || f._t==Type.BOTTOM ) return (_fidxs = XInt.FULL);
+ if( f._t instanceof TypeFunPtr tfp ) xs = XInt.meet(xs,tfp._fidxs);
+ }
return (_fidxs = xs);
}
@@ -156,8 +156,7 @@ public class TypeStruct extends Type {
Field[] fs = new Field[_fields.length];
for( int i=0; i<_fields.length; i++ ) {
if( _fields[i]._extern ) { fs[i] = _fields[i]; continue; }
- Type zero = _fields[i]._t.makeZero();
- fs[i] = _fields[i].makeFrom(_fields[i]._t != Type.BOTTOM && zero.isa(_fields[i]._t) ? zero : Type.TOP);
+ fs[i] = _fields[i].makeFrom(TypeScalar.BOT);
}
return make(_name,_open,_fref,fs);
}
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/ArithNodeTest.java b/chapter29/src/test/java/com/seaofnodes/simple/ArithNodeTest.java
index e912f224..b28a6af3 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/ArithNodeTest.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/ArithNodeTest.java
@@ -9,6 +9,29 @@ import org.junit.Test;
import static org.junit.Assert.*;
public class ArithNodeTest {
+ @Test public void testConstantFoldsRequireInitializedInputs() {
+ new CodeGen("return 0;");
+ Node x = ConstantNode.seed(TypeScalar.BOT);
+ Node zero = ConstantNode.seed(TypeInteger.ZERO);
+ for( Node op : new Node[]{new MulNode(x,zero,(byte)1),new AndNode(null,x,zero),
+ new SubNode(x,x,(byte)1),new BoolNode.EQ(x,x,(byte)1),
+ new BoolNode.NE(x,x,(byte)1),new BoolNode.ULT(x,x),new NotNode(x)} ) {
+ assertSame(op.label(),TypeScalar.BOT,op.compute());
+ Node replacement = op.idealize();
+ assertTrue(op.label(),replacement==null || !replacement.compute().isConstant());
+ }
+ // Once the family is known, compute owns the annihilator fold.
+ x._type = TypeInteger.BOT;
+ assertSame(TypeInteger.ZERO,new MulNode(x,zero,(byte)1).compute());
+ assertSame(TypeInteger.TRUE,new BoolNode.EQ(x,x,(byte)1).compute());
+ assertSame(TypeInteger.FALSE,new BoolNode.NE(x,x,(byte)1).compute());
+ assertSame(TypeInteger.FALSE,new BoolNode.ULT(x,x).compute());
+ x._type = TypeFloat.F64;
+ assertSame(TypeInteger.BOOL,new BoolNode.EQ(x,x,(byte)2).compute());
+ x._type = TypeFloat.constant(Double.NaN);
+ assertSame(TypeInteger.FALSE,new BoolNode.EQ(x,x,(byte)2).compute());
+ }
+
@Test public void testUnresolvedInput() {
new CodeGen("return 0;").driver(CodeGen.Phase.Opto);
Node lhs = ConstantNode.seed(TypeInteger.TRUE);
@@ -16,9 +39,9 @@ public class ArithNodeTest {
ArithNode add = new AddNode(lhs,rhs);
assertSame(TypeInteger.constant(2),add.compute());
lhs._type = TypeScalar.BOT;
- assertSame(Type.BOTTOM,add.compute());
+ assertSame(TypeScalar.BOT,add.compute());
add.setMode((byte)1);
- assertSame(Type.BOTTOM,add.compute());
+ assertSame(TypeScalar.BOT,add.compute());
}
@Test public void testFloatPrecision() {
@@ -26,6 +49,10 @@ public class ArithNodeTest {
assertEquals("1.6777217E7",Eval2.eval(code,16777216));
code = new CodeGen("f32 x = arg; f32 y = x+1.0; return y;").driver(CodeGen.Phase.TypeCheck);
assertEquals("1.6777216E7",Eval2.eval(code,16777216));
+ code = new CodeGen("flt x = 0.0; flt y = 0.0; if(arg) { x = 1.5; y = 3.5; } else { x = 2.5; y = 4.5; } return x+y;")
+ .driver(CodeGen.Phase.TypeCheck);
+ assertEquals("7.0",Eval2.eval(code,0));
+ assertEquals("5.0",Eval2.eval(code,1));
}
@Test public void testMonotoneCompute() {
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/BoundaryTest.java b/chapter29/src/test/java/com/seaofnodes/simple/BoundaryTest.java
index de502371..10b7517e 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/BoundaryTest.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/BoundaryTest.java
@@ -24,10 +24,7 @@ public class BoundaryTest {
CodeGen code=new CodeGen(src).driver(CodeGen.Phase.LocalSched);
assertEquals("2018",Eval2.eval(code,19));
assertEquals("0",Eval2.eval(code,0));
- code._stop.walk(n -> {
- if( n instanceof StoreNode st ) assertEquals(0,st.cfg0().loopDepth());
- return null;
- });
+ // Private memory follows the loop; stores need not move to its exit.
code=new CodeGen("struct Byte { new Byte={int n -> self.x=n; self.x+=3;}; u8 x; }; return new Byte(255).x;").driver(CodeGen.Phase.TypeCheck);
assertEquals("2",Eval2.eval(code,0));
// data is still unset when the constructor reads and updates x.
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter29/src/test/java/com/seaofnodes/simple/Chapter10Test.java
index 2c9a69a7..1a954110 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/Chapter10Test.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/Chapter10Test.java
@@ -158,7 +158,7 @@ else
return v;
""");
code.driver(CodeGen.Phase.FinalizeIR);
- assertEquals("Stop[ return Test.Vector2D; return MEM[ 2:___ 3:.x=0; 4:.y=0;]; return Test.Vector2D; ]", code.print());
+ assertEquals("Stop[ return Test.Vector2D; return MemTop; return Test.Vector2D; ]", code.print());
}
@Test
@@ -196,7 +196,7 @@ while (arg) {
return bar.a;
""");
code.driver(CodeGen.Phase.FinalizeIR);
- assertEquals("Stop[ return Phi(Loop,0,(Phi_a+2)); return MEM[ 2:___ 3:.a=0;]; return Test.Bar; ]", code.print());
+ assertEquals("Stop[ return Phi(Loop,0,(Phi_a+2)); return MemTop; return Test.Bar; ]", code.print());
}
@Test
@@ -318,7 +318,7 @@ while( i.x < i.len ) {
return sum;
""");
code.driver(CodeGen.Phase.FinalizeIR);
- assertEquals("Stop[ return Phi(Loop,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum)); return MEM[ 2:___ 3:.x=0; 4:.len=0;]; return Test.Iter; ]", code.print());
+ assertEquals("Stop[ return Phi(Loop,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum)); return MemTop; return Test.Iter; ]", code.print());
}
@@ -337,7 +337,7 @@ while(arg) {
return ret;
""");
code.driver(CodeGen.Phase.FinalizeIR);
- assertEquals("Stop[ return Phi(Loop,Test.s0,Phi(Region,Test.s0,Phi_ret)); return MEM[ 2:___ 3:.v0=0;]; return Test.s0; ]", code.print());
+ assertEquals("Stop[ return Phi(Loop,Test.s0,Phi(Region,Test.s0,Phi_ret)); return MemTop; return Test.s0; ]", code.print());
}
@Test
@@ -390,7 +390,7 @@ s0 b = new s0();
return a.x * 100 + b.x * 10 + cnt;
""");
code.driver(CodeGen.Phase.FinalizeIR);
- assertEquals("Stop[ return 112; return MEM[ 2:___ 3:___ 4:.x=1;]; return Test.s0; ]", code.print());
+ assertEquals("Stop[ return 112; return MemTop; return Test.s0; ]", code.print());
}
@@ -433,7 +433,7 @@ else return new s0;
if(new s0.f0) return 0;
""");
code.driver(CodeGen.Phase.TypeCheck);
- assertEquals("Stop[ return Test.s0; return MEM[ 2:___ 3:.f0=0;]; return Test.s0; ]", code.print());
+ assertEquals("Stop[ return Test.s0; return MemTop; return Test.s0; ]", code.print());
}
@Test
@@ -461,7 +461,7 @@ s0 v1 = v0;
return v1;
""");
code.driver(CodeGen.Phase.FinalizeIR);
- assertEquals("Stop[ return (const)Test.s0; return MEM[ 2:___ 3:.f0=0;]; return Test.s0; ]", code.print());
+ assertEquals("Stop[ return (const)Test.s0; return MemTop; return Test.s0; ]", code.print());
}
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter29/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index fae7611d..156e091d 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -101,7 +101,7 @@ N n = new N(new M);
return n.next;
""");
code.driver(CodeGen.Phase.FinalizeIR);
- assertEquals("Stop[ return (const)Test.M; return MEM[ 2:___ 3:.m=0;]; return Test.M; return MEM[ 2:___ 3:___ 4:___ 5:___ 6:___ 7:.next=Parm_m(Test.N.N,*Test.M {i64 !m; }); 8:.i=0;]; return MEM[ 2:___ 3:___ 4:___ 5:___ 6:___ 7:___ 8:.i=0;]; return Test.N; ]", code.print());
+ assertEquals("Stop[ return (const)Test.M; return MemTop; return Test.M; return MemTop; return MemTop; return Test.N; ]", code.print());
}
@Test
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/Chapter18Test.java b/chapter29/src/test/java/com/seaofnodes/simple/Chapter18Test.java
index e5813eae..ecb2b934 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/Chapter18Test.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/Chapter18Test.java
@@ -347,7 +347,7 @@ for(;;) {
}
""");
code.driver(Phase.LocalSched);
- assertEquals("Stop[ return Top; return Parm_i(x,i64); ]", code.print());
+ assertEquals("return Top;", code.print());
assertNull( Eval2.eval( code, 0 ) ); // Infinite loop, <clinit> never exits
}
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/Chapter19Test.java b/chapter29/src/test/java/com/seaofnodes/simple/Chapter19Test.java
index d5fe207a..e6249d82 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/Chapter19Test.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/Chapter19Test.java
@@ -118,7 +118,7 @@ val hashCode = { !String self ->
hashCode(new String("Hello, World!"));
""";
CodeGen code = new CodeGen(src).driver(Phase.LocalSched);
- assertEquals("Stop[ return Phi(Region,123456789,(!=0)Phi(Loop,0,(.[]+((Phi_hash<<5)-Phi_hash)))); return MEM[ 2:___ 3:___ 4:___ 5:.cs=Parm_s(Test.String.String,*[]u8[final]); 6:._hashCode=0;]; return MEM[ 2:___ 3:___ 4:___ 5:___ 6:._hashCode=0;]; return Test.String; return Phi(Region,0,1,0,1); return Phi(Region,(!=0)._hashCode,Phi(Region,123456789,(!=0)Phi(Loop,0,(.[]+((Phi_hash<<5)-Phi_hash))))); ]", code.print());
+ assertEquals("Stop[ return Phi(Region,123456789,(!=0)Phi(Loop,0,(.[]+((Phi_hash<<5)-Phi_hash)))); return MemTop; return MemTop; return Test.String; return Phi(Region,0,1,0,1); return Phi(Region,(!=0)._hashCode,Phi(Region,123456789,(!=0)Phi(Loop,0,(.[]+((Phi_hash<<5)-Phi_hash))))); ]", code.print());
assertEquals("4029215624828139541", Eval2.eval(code, 2));
}
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/ConstructionGroupTest.java b/chapter29/src/test/java/com/seaofnodes/simple/ConstructionGroupTest.java
new file mode 100644
index 00000000..cea6ca91
--- /dev/null
+++ b/chapter29/src/test/java/com/seaofnodes/simple/ConstructionGroupTest.java
@@ -0,0 +1,135 @@
+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 ConstructionGroupTest {
+ private static final String CHILD = """
+ struct Child {
+ Test.Parent? parent;
+ int value = 5;
+ new Child = { Test.Parent? ^par -> parent = par; };
+ };
+ val read = { Child c -> return c.value; };
+ """;
+
+ private String parent(String body) {
+ return CHILD+"struct Parent { Child child; int~ payload; new Parent = { int n -> "+body+" }; };";
+ }
+
+ private void rejects(String src, String message) {
+ try {
+ new CodeGen(src).parse();
+ fail("Accepted: "+src);
+ } catch( Parser.ParseException e ) {
+ assertTrue(e.getMessage(),e.getMessage().contains(message));
+ }
+ }
+
+ @Test public void testCyclesAndIndependentAllocations() {
+ String src = parent("val detached = new Child(null); payload = n+read(detached); child = new Child(self);")+
+ "val p = new Parent(arg); val local = new Child(p); val par = local.parent; return (par ? par.payload : 0)+p.child.value;";
+ for( long seed : new long[]{1,123,987654321} ) {
+ CodeGen code = new CodeGen(src,seed,true).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("17",Eval2.eval(code,7));
+ assertEquals("10",Eval2.eval(code,0));
+ }
+ new CodeGen(src.replace("Child","Child_noInline"))
+ .driver(CodeGen.Phase.Encoding,TestC.CPU_PORT,TestC.CALL_CONVENTION,true,false,0);
+ }
+
+ @Test public void testNoIncompleteLookupOrEscape() {
+ // This lookup supplies a live result. Inlining must not turn self
+ // complete inside the body before checking the use of child.
+ rejects(parent("child = new Child(self); payload = child.parent==null;")+
+ "return new Parent(arg).payload;","incomplete object");
+ rejects(parent("payload = 1; child = new Child(self); int x = child.parent==null;")+"return 0;","incomplete object");
+ rejects(parent("payload = 1; child = new Child(self); read(child);")+"return 0;","ordinary function");
+ rejects(parent("payload = 1; child = new Child(self);").replace("parent = par;","parent = par; value = par.payload;")+
+ "return 0;","incomplete object");
+ rejects("""
+ struct Box { Test.C? value; }; val box = new Box;
+ struct C { int~ x; new C = { -> box.value = self; x = 1; }; };
+ return 0;
+ ""","ordinary object");
+ rejects("struct C { C next = self; int~ x; new C = { -> int y = next.x; x = 7; }; }; return 0;",
+ "incomplete object");
+ }
+
+ @Test public void testAliasesAndLoopBackedges() {
+ String src = parent("val alias = self; alias.payload = n; child = new Child(alias);")+"return new Parent(arg).child.value;";
+ assertEquals("5",Eval2.eval(new CodeGen(src).driver(CodeGen.Phase.TypeCheck),3));
+ rejects(parent("payload = 1; child = new Child(self); Child? !c = null; while(n--) { if(c) read(c); c = child; }")+
+ "return 0;","ordinary function");
+ rejects(parent("payload = 1; child = new Child(self); Parent? !p = null; while(n--) { val c = new Child(p); read(c); p = self; }")+
+ "return 0;","ordinary function");
+ rejects(parent("payload = 1; child = new Child(self); val c = n ? new Child(null) : child; read(c);")+
+ "return 0;","ordinary function");
+ }
+
+ @Test public void testConstructorOnlyQualifier() {
+ rejects("struct C {}; val f = { C ^c -> return 0; }; return 0;","constructor");
+ rejects("struct C { new C = { int ^x -> }; }; return 0;","struct parameter");
+ rejects(parent("payload = 1; child = new Child(self);").replace("^par","par")+"return 0;","without '^'");
+ CodeGen code = new CodeGen(parent("payload = 1; child = new Child(self);")+"return 0;")
+ .driver(CodeGen.Phase.TypeCheck);
+ try {
+ code.serialize();
+ fail("Exported a constructor qualifier without its contract");
+ } catch( Parser.ParseException e ) {
+ assertTrue(e.getMessage(),e.getMessage().contains("Separate compilation"));
+ }
+ }
+
+ @Test public void testCompleteLoopAllocations() {
+ // Optimistic types prove the cycle p -> new Link(p) -> p complete.
+ String src = """
+ struct Link {
+ Link? next;
+ int value = 5;
+ new Link = { Link? ^n -> next = n; };
+ };
+ struct Root {
+ Link head;
+ int value;
+ new Root = { int n ->
+ Link? !p = null;
+ while(n--) p = new Link(p);
+ if(!p) p = new Link(null);
+ value = p.value;
+ head = p;
+ };
+ };
+ val root = new Root(arg);
+ return root.value + root.head.value;
+ """;
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("10",Eval2.eval(code,0));
+ assertEquals("10",Eval2.eval(code,3));
+ // Dead calls discard their arguments before a construction result exists.
+ assertEquals("0",Eval2.eval(new CodeGen(CHILD+"struct Parent {}; return 0; new Child(null);")
+ .driver(CodeGen.Phase.TypeCheck),0));
+ }
+
+ @Test public void testDeclaredQualifierAndOwnReceiver() {
+ // A complete caller does not grant permission to read a ^ formal.
+ rejects("""
+ struct Parent { int payload = 7; };
+ struct Child {
+ int value;
+ new Child = { Parent ^par -> value = par.payload; };
+ };
+ return new Child(new Parent).value;
+ ""","incomplete object");
+ // The self exception belongs to the receiver, not every pointer of its class.
+ rejects("""
+ struct C {
+ int !value;
+ new C = { !C? ^other -> if(other) other.value = 1; value = 2; };
+ };
+ return new C(new C(null)).value;
+ ""","incomplete object");
+ }
+}
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/ConstructionRequirementsTest.java b/chapter29/src/test/java/com/seaofnodes/simple/ConstructionRequirementsTest.java
new file mode 100644
index 00000000..c570e643
--- /dev/null
+++ b/chapter29/src/test/java/com/seaofnodes/simple/ConstructionRequirementsTest.java
@@ -0,0 +1,222 @@
+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.*;
+
+@org.junit.Ignore("Deferred: inferred field requirements and incomplete-object traversal")
+public class ConstructionRequirementsTest {
+ private static final String CHILD = """
+ struct Child {
+ Test.Parent parent;
+ int~ width;
+ new Child = { Test.Parent ^par ->
+ parent = par;
+ width = par.width;
+ };
+ };
+ """;
+
+ private String parent(String body) {
+ return CHILD+"struct Parent { Child child; int~ width; new Parent = { int n -> "+body.replace("arg","n")+" }; };";
+ }
+
+ private void rejects(String src, String message) {
+ try {
+ new CodeGen(src).parse();
+ fail("Accepted: "+src);
+ } catch( Parser.ParseException e ) {
+ assertTrue(e.getMessage(),e.getMessage().contains(message));
+ }
+ }
+
+ @Test public void testCycleAndRequiredField() {
+ String src = parent("width = arg+10; child = new Child(self);")+"return new Parent(arg).child.parent.child.width;";
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("17",Eval2.eval(code,7));
+ assertEquals("10",Eval2.eval(code,0));
+ code = new CodeGen(parent("width = arg+10; child = new Child(self);")+
+ "val p = new Parent(arg); return new Child(p).parent.child.width;").driver(CodeGen.Phase.TypeCheck);
+ assertEquals("17",Eval2.eval(code,7));
+ code = new CodeGen(CHILD+"struct Parent { int width = 10; Child child = new Child(self); }; return new Parent().child.width;")
+ .driver(CodeGen.Phase.TypeCheck);
+ assertEquals("10",Eval2.eval(code,0));
+ rejects(parent("child = new Child(self); width = 10;")+"return 0;","requires initialized field 'width'");
+ rejects(parent("if(arg) width = 10; child = new Child(self); width = 20;")+"return 0;","requires initialized field 'width'");
+ code = new CodeGen(parent("child = new Child(self); width = 10;").replace("width = par.width;","width = 5;")+
+ "return new Parent(arg).child.parent.width;").driver(CodeGen.Phase.TypeCheck);
+ assertEquals("10",Eval2.eval(code,0));
+ // Even if the comparison folds using the non-null declared type,
+ // accessing child still requires that field to have been initialized.
+ rejects(parent("width = 10; child = new Child(self);").replace("width = par.width;","width = par.child==null;")+
+ "return 0;","requires initialized field 'child'");
+ }
+
+ @Test public void testNoEscapeOrFieldLaundering() {
+ rejects("struct C { C next = self; int~ payload; new C = { -> payload = next.payload; }; }; return 0;",
+ "might not be initialized");
+ CodeGen code = new CodeGen("struct C { C next = self; int payload; new C = { -> payload = 17; payload = next.payload; }; }; return new C().next.payload;")
+ .driver(CodeGen.Phase.TypeCheck);
+ assertEquals("17",Eval2.eval(code,0));
+ rejects("""
+ struct C {
+ C next; int~ payload;
+ new C = { -> next = self; payload = next.payload; };
+ }; return 0;
+ ""","might not be initialized");
+ rejects(parent("width = 10; child = new Child(self); use(child);")+
+ "val use = { Child c -> return c.parent.width; }; return 0;","ordinary function");
+ rejects(CHILD+"""
+ struct Parent {
+ Child child; int~ width;
+ new Parent = { -> width = 10; child = new Child(self); int x = child.parent.width; };
+ }; return 0;
+ ""","Traversing an unfinished reference field");
+ rejects("""
+ struct C {
+ int x;
+ val get = { -> return x; };
+ new C = { -> x = get(); };
+ }; return 0;
+ ""","ordinary function");
+ rejects("""
+ struct Box { Test.C? value; }; val box = new Box;
+ struct C { new C = { -> box.value = self; }; }; return 0;
+ ""","ordinary object");
+ }
+
+ @Test public void testConstructorOnlyQualifier() {
+ rejects("struct C {}; val f = { C ^c -> return 0; }; return 0;","constructor");
+ rejects("struct C { new C = { int ^x -> }; }; return 0;","struct parameter");
+ String src = parent("width = 10; child = new Child(self);")+"return 0;";
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
+ try {
+ code.serialize();
+ fail("Exported an unrecorded construction contract");
+ } catch( Parser.ParseException e ) {
+ assertTrue(e.getMessage(),e.getMessage().contains("Separate compilation"));
+ }
+ }
+
+ @Test public void testTransitiveRequirementsAndAliases() {
+ String child = """
+ struct Leaf {
+ int~ width;
+ new Leaf = { Test.Parent ^par -> val alias = par; width = alias.width; };
+ };
+ struct Child {
+ Test.Parent parent; Leaf leaf;
+ new Child = { Test.Parent ^par -> parent = par; leaf = new Leaf(par); };
+ };
+ """;
+ String parent = "struct Parent { Child child; int~ width; new Parent = { int n -> %s }; };";
+ String good = child+parent.formatted("width = n+10; val alias = self; child = new Child(alias);");
+ CodeGen code = new CodeGen(good+"return new Parent(arg).child.leaf.width;").driver(CodeGen.Phase.TypeCheck);
+ assertEquals("17",Eval2.eval(code,7));
+ rejects(child+parent.formatted("child = new Child(self); width = 10;")+"return 0;",
+ "requires initialized field 'width'");
+ }
+
+ @Test public void testLoopsAndUninlinedConstructor() {
+ String src = parent("width = 1; for(int i = 0; i < arg; i++) width += 2; child = new Child(self);")+
+ "return new Parent(arg).child.width;";
+ for( long seed : new long[]{1,123,987654321} ) {
+ CodeGen code = new CodeGen(src.replace("Child","Child_noInline"),seed,true).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("7",Eval2.eval(code,3));
+ assertEquals("1",Eval2.eval(code,0));
+ }
+ new CodeGen(src.replace("Child","Child_noInline"))
+ .driver(CodeGen.Phase.Encoding,TestC.CPU_PORT,TestC.CALL_CONVENTION,true,false,0);
+ rejects(parent("for(int i = 0; i < arg; i++) width = 2; child = new Child(self); width = 10;")+"return 0;",
+ "requires initialized field 'width'");
+ // The assignment on the backedge introduces the unfinished alias
+ // after the call was parsed. It must not pass as an ordinary Child.
+ rejects(parent("width = 10; child = new Child(self); Child? !c = null; while(arg--) { use(c); c = child; }")+
+ "val use = { Child? c -> return 0; }; return 0;","ordinary function");
+ }
+
+ @Test public void testNullableFieldAndDuplicateArguments() {
+ String src = """
+ struct Child {
+ Test.Parent parent; int~ width;
+ new Child = { Test.Parent ^a, Test.Parent ^b ->
+ parent = a; width = a.width+b.width+(a.optional==null);
+ };
+ };
+ struct Parent {
+ Child child; int width; Child? optional;
+ new Parent = { -> width = 10; child = new Child(self,self); };
+ };
+ return new Parent().child.width;
+ """;
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("21",Eval2.eval(code,0));
+ }
+
+ @Test public void testNullableUnfinishedParameter() {
+ String child = """
+ struct Child {
+ Test.Parent? parent; int width;
+ new Child = { Test.Parent? ^par ->
+ parent = par; width = par ? par.width : 0;
+ };
+ };
+ val read = { Child c -> return c.width; };
+ """;
+ String parent = """
+ struct Parent {
+ Child child; int~ width;
+ new Parent = { int n ->
+ val detached = new Child(null);
+ width = n+read(detached);
+ child = new Child(self);
+ };
+ };
+ """;
+ CodeGen code = new CodeGen(child+parent+"""
+ val p = new Parent(arg);
+ val leaf = new Child(p);
+ return read(leaf)+read(p.child);
+ """).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("14",Eval2.eval(code,7));
+ assertEquals("0",Eval2.eval(code,0));
+ rejects(child+parent.replace("width = n+read(detached);","").replace("child = new Child(self);",
+ "child = new Child(self); width = n;"),"requires initialized field 'width'");
+ rejects(child+parent.replace("child = new Child(self);","child = new Child(self); read(child);"),
+ "ordinary function");
+ try {
+ new CodeGen(child.replace("par ? par.width : 0","par.width")+parent+
+ "return new Child(null).width;").driver(CodeGen.Phase.TypeCheck);
+ fail("Dereferenced null constructor argument");
+ } catch( Parser.ParseException e ) {
+ assertTrue(e.getMessage(),e.getMessage().toLowerCase().contains("null"));
+ }
+ }
+
+ @Test public void testDistinctCallTimeMemory() {
+ String src = CHILD+"""
+ struct Parent {
+ Child child; Child second; int~ width;
+ new Parent = { int n ->
+ width = n+1; child = new Child(self);
+ width = n+2; second = new Child(self);
+ width = n+3;
+ };
+ };
+ val p = new Parent(arg); return p.child.width*100+p.second.width*10+p.width;
+ """;
+ for( long seed : new long[]{1,123,987654321} ) {
+ CodeGen code = new CodeGen(src.replace("Child","Child_noInline"),seed,true).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("123",Eval2.eval(code,0));
+ assertEquals("456",Eval2.eval(code,3));
+ }
+ new CodeGen(src.replace("Child","Child_noInline"))
+ .driver(CodeGen.Phase.Encoding,TestC.CPU_PORT,TestC.CALL_CONVENTION,true,false,0);
+ String loop = parent("width = 1; child = new Child(self); for(int i = 0; i < arg; i++) { width += 1; child = new Child(self); } width += 10;")+
+ "val p = new Parent(arg); return p.child.width*100+p.width;";
+ CodeGen code = new CodeGen(loop.replace("Child","Child_noInline")).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("414",Eval2.eval(code,3));
+ assertEquals("111",Eval2.eval(code,0));
+ }
+}
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/ConstructorCompletionTest.java b/chapter29/src/test/java/com/seaofnodes/simple/ConstructorCompletionTest.java
index 6a229a4f..cbb9ee37 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/ConstructorCompletionTest.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/ConstructorCompletionTest.java
@@ -6,6 +6,48 @@ import org.junit.Test;
import static org.junit.Assert.*;
public class ConstructorCompletionTest {
+ @Test public void testFieldMemoryDuringInitialization() {
+ CodeGen code = new CodeGen("""
+ struct A {
+ int x = 1;
+ x = 2;
+ int y = self.x;
+ new A = { -> self.x = 3; y += x; };
+ };
+ val a = new A; return a.x*10+a.y;
+ """).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("35",Eval2.eval(code,0));
+ code = new CodeGen("""
+ struct A {
+ int[] data = new int[3];
+ new A = { -> for(int i = 0; i < 3; i++) data[i] = i+1; };
+ };
+ return new A().data[2];
+ """).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("3",Eval2.eval(code,0));
+ for( String body : new String[]{
+ "int~ x; int y = x; new A = { -> x = 7; };",
+ "int~ x; int y = x*0; new A = { -> x = 7; };",
+ // Constructor entry includes the initializer's early return.
+ // Its later Store of 2 must not excuse the earlier read of x.
+ "int~ x; if(arg) return 0; x = 1; new A = { -> int y = x; x = 2; };"
+ } )
+ try {
+ new CodeGen("struct A { "+body+" }; return 0;").parse();
+ fail("Read a field before its constructor wrote it");
+ } catch( Parser.ParseException e ) {
+ assertTrue(e.getMessage(),e.getMessage().contains("might not be initialized"));
+ }
+ }
+
+ @Test public void testRequiredNumberBesideNullableDefault() {
+ CodeGen code = new CodeGen("""
+ struct A { int~ x; A? p; new A = { -> x = 10; }; };
+ return new A().x;
+ """).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("10",Eval2.eval(code,0));
+ }
+
private void rejects(String fields, String field, boolean explicit) {
String src = "struct B {}; struct A { "+fields+" }; return 0;";
try {
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/FinalConstructionTest.java b/chapter29/src/test/java/com/seaofnodes/simple/FinalConstructionTest.java
index 38b94693..66fdacc3 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/FinalConstructionTest.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/FinalConstructionTest.java
@@ -2,7 +2,6 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.parser.Parser;
import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.node.StoreNode;
import org.junit.Test;
import static org.junit.Assert.*;
@@ -20,16 +19,12 @@ public class FinalConstructionTest {
return new C(arg).get();
""";
- @Test public void testFinalConstructorLocals() throws java.io.IOException {
+ @Test public void testFinalConstructorFields() throws java.io.IOException {
var code=new CodeGen(LOOP).driver(CodeGen.Phase.LocalSched);
assertEquals("1",Eval2.eval(code,0));
assertEquals("15",Eval2.eval(code,3));
assertEquals("63",Eval2.eval(code,5));
- code._stop.walk(n -> {
- if( n instanceof StoreNode st && st._name.equals("x") )
- assertEquals("Final field writes belong after the loop",0,st.cfg0().loopDepth());
- return null;
- });
+ // Writes are ordinary Stores and may remain inside the loop.
String[] sources={
"struct C { val x=1; new C={->x=1; x=2;}; }; return new C().x;",
"struct C { int ~x; new C={int n -> self.x=1; for(int i=0;i<n;i++) self.x*=2;}; }; return new C(arg).x;",
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/TypeTest.java b/chapter29/src/test/java/com/seaofnodes/simple/TypeTest.java
index b4055eef..68239396 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/TypeTest.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/TypeTest.java
@@ -147,7 +147,7 @@ public class TypeTest {
Assert.assertNotEquals(ptr1, ptr2);
Assert.assertEquals(ptr1, ptr1.dual().dual());
- Assert.assertEquals(TypeMemPtr.makeNullable(TypeStruct.BOT).withAccess(false), ptr1.meet(ptr2nil));
+ Assert.assertEquals(TypeMemPtr.makeNullable(TypeStruct.BOT).withAccess(false).withPartial(false), ptr1.meet(ptr2nil));
TypeMemPtr TOP = TypeMemPtr.TOP;
TypeMemPtr BOT = TypeMemPtr.makeNullable(TypeStruct.BOT);
@@ -156,7 +156,7 @@ public class TypeTest {
Type PTR_meet_NULL = NULL.meet(PTR);
Assert.assertEquals(BOT, PTR_meet_NULL);
Type ptr1_meet_ptr2 = ptr1.meet(ptr2);
- Assert.assertEquals(PTR.withAccess(false), ptr1_meet_ptr2);
+ Assert.assertEquals(PTR.withAccess(false).withPartial(false), ptr1_meet_ptr2);
Type NULL_join_ptr1 = NULL.join(ptr1);
Assert.assertEquals(TypePtr.XNPTR, NULL_join_ptr1);
Type NULL_join_PTR = PTR.join(NULL);