summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2024-09-12 11:13:31 -0700
committerCliff Click <cliffc@acm.org>2026-10-04 13:30:41 -0700
commiteab779cdd6453215354725b114442e221fa48c3d (patch)
tree959d3db20389a3e770556bbc76d584cb4754c836
parent7f933387c55187c17a2958e96ea039eff343fed4 (diff)
downloadSimple-eab779cdd6453215354725b114442e221fa48c3d.tar.gz
Simple-eab779cdd6453215354725b114442e221fa48c3d.zip
Chapter 15: Arrays
Co-authored-by: XmiliaH <xmilia.hermit@gmail.com> Co-authored-by: Thalia Archibald <thalia@archibald.dev> Co-authored-by: Tamás Hadházy <85063808+Hels15@users.noreply.github.com> Co-authored-by: Hsiang-Ying Fu <66062782+fuhsnn@users.noreply.github.com> Co-authored-by: tannal <tannal2409@gmail.com> Co-authored-by: hadha <helsgithub15@gmail.com> Co-authored-by: fuhsnn <66062782+fuhsnn@users.noreply.github.com> Co-authored-by: Symious <14933944+symious@users.noreply.github.com> Co-authored-by: RobertObkircher <robert.obkircher@gmail.com> Co-authored-by: Robert Obkircher <robert.obkircher@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
-rw-r--r--README.md2
-rw-r--r--chapter14.iml28
-rw-r--r--docs/chapter15/15-grammar.md129
-rw-r--r--docs/chapter15/Makefile12
-rw-r--r--docs/chapter15/README.md260
-rw-r--r--src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java46
-rw-r--r--src/main/java/com/seaofnodes/simple/Parser.java241
-rw-r--r--src/main/java/com/seaofnodes/simple/Utils.java11
-rw-r--r--src/main/java/com/seaofnodes/simple/node/AddNode.java5
-rw-r--r--src/main/java/com/seaofnodes/simple/node/AndNode.java1
-rw-r--r--src/main/java/com/seaofnodes/simple/node/BoolNode.java22
-rw-r--r--src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java16
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CFGNode.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CProjNode.java17
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ConstantNode.java1
-rw-r--r--src/main/java/com/seaofnodes/simple/node/DivNode.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/node/LoadNode.java127
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MemMergeNode.java36
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MemOpNode.java23
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MemPhiNode.java12
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MinusNode.java4
-rw-r--r--src/main/java/com/seaofnodes/simple/node/NewNode.java37
-rw-r--r--src/main/java/com/seaofnodes/simple/node/Node.java16
-rw-r--r--src/main/java/com/seaofnodes/simple/node/OrNode.java1
-rw-r--r--src/main/java/com/seaofnodes/simple/node/PhiNode.java10
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ProjNode.java5
-rw-r--r--src/main/java/com/seaofnodes/simple/node/RegionNode.java59
-rw-r--r--src/main/java/com/seaofnodes/simple/node/SarNode.java16
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ScopeNode.java82
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ShlNode.java18
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ShrNode.java17
-rw-r--r--src/main/java/com/seaofnodes/simple/node/StartNode.java4
-rw-r--r--src/main/java/com/seaofnodes/simple/node/StoreNode.java26
-rw-r--r--src/main/java/com/seaofnodes/simple/node/SubNode.java7
-rw-r--r--src/main/java/com/seaofnodes/simple/node/XorNode.java1
-rw-r--r--src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java9
-rw-r--r--src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/type/Field.java24
-rw-r--r--src/main/java/com/seaofnodes/simple/type/Type.java10
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeFloat.java6
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeInteger.java17
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeMem.java36
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java15
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeStruct.java114
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeTuple.java9
-rw-r--r--src/test/java/com/seaofnodes/simple/BrainFuckTest.java80
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter06Test.java2
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter10Test.java28
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter12Test.java29
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter13Test.java37
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java12
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter14Test.java127
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter15Test.java415
-rw-r--r--src/test/java/com/seaofnodes/simple/TypeTest.java30
-rw-r--r--src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java184
-rw-r--r--src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java71
-rw-r--r--src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java6
57 files changed, 1907 insertions, 652 deletions
diff --git a/README.md b/README.md
index 00881464..61f77cf4 100644
--- a/README.md
+++ b/README.md
@@ -58,7 +58,7 @@ The Simple language is styled after a subset of C or Java.
* [Chapter 12](docs/chapter12/README.md): Reference fields, forward references and recursive structs.
* [Chapter 13](docs/chapter13/README.md): Global Code Motion - Scheduling.
* [Chapter 14](docs/chapter14/README.md): Numeric types: floats, narrow integers, ranges and rounding to `f32`.
-* Chapter 15: One dimensional static length array type, with array loads and stores.
+* [Chapter 15](docs/chapter15/README.md): One dimensional static length array type, with array loads and stores.
* Chapter 16: Constructors
* Chapter 17a: Binding mutability, reference permissions, and deep read-only views.
* Chapter 17b: Syntax sugar: `var`, `val`, `x+=y`, `for(init; test; next) body`
diff --git a/chapter14.iml b/chapter14.iml
deleted file mode 100644
index 4c719b6d..00000000
--- a/chapter14.iml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module type="JAVA_MODULE" version="4">
- <component name="NewModuleRootManager" inherit-compiler-output="false">
- <output url="file://$MODULE_DIR$/../build/idea/production/chapter14" />
- <output-test url="file://$MODULE_DIR$/../build/idea/test/chapter14" />
- <exclude-output />
- <content url="file://$MODULE_DIR$">
- <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
- <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
- <excludeFolder url="file://$MODULE_DIR$/build" />
- <excludeFolder url="file://$MODULE_DIR$/target" />
- </content>
- <orderEntry type="inheritedJdk" />
- <orderEntry type="sourceFolder" forTests="false" />
- <orderEntry type="module" module-name="graph" />
- <orderEntry type="module" module-name="print" />
- <orderEntry type="module-library" scope="TEST">
- <library name="JUnit4">
- <CLASSES>
- <root url="jar://$MODULE_DIR$/lib/junit-4.12.jar!/" />
- <root url="jar://$MODULE_DIR$/lib/hamcrest-core-1.3.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- </component>
-</module>
diff --git a/docs/chapter15/15-grammar.md b/docs/chapter15/15-grammar.md
new file mode 100644
index 00000000..76ea0cab
--- /dev/null
+++ b/docs/chapter15/15-grammar.md
@@ -0,0 +1,129 @@
+# Grammar for Chapter 15
+
+```antlrv4
+grammar SimpleLanguage;
+
+program : block EOF ;
+
+block : statement+ ;
+
+
+statement
+ : '{' block '}'
+ | returnStatement
+ | ifStatement
+ | whileStatement
+ | breakStatement
+ | continueStatement
+ | structDeclaration
+ | expressionStatement
+ ;
+
+
+returnStatement : 'return' expression ';' ;
+
+ifStatement : 'if' '(' expression ')' statement ('else' statement)? ;
+
+whileStatement : 'while' '(' expression ')' statement ;
+
+breakStatement : 'break' ';' ;
+
+continueStatement : 'continue' ';' ;
+
+
+structDeclaration : 'struct' IDENTIFIER '{' fields '}' ;
+
+fields : field+ ;
+
+field : type IDENTIFIER ';' ;
+
+
+expressionStatement
+ : type IDENTIFIER ';'
+ | type IDENTIFIER '=' expression ';'
+ | IDENTIFIER '=' expression ';'
+ | expression
+ ;
+
+PRIMTYPE
+ : 'int'
+ | 'i8'
+ | 'i16'
+ | 'i32'
+ | 'i64'
+ | 'u8'
+ | 'u16'
+ | 'u32'
+ | 'flt'
+ | 'f32'
+ | 'f64'
+ | 'bool'
+ ;
+
+type
+ : PRIMTYPE
+ | typeName '?'?
+ | typeName nestedArray+
+ ;
+
+nestedArray : '?'? '[]'* ;
+
+typeName : IDENTIFIER ;
+
+
+expression : bitWiseExpression ;
+
+bitWiseExpression
+ : comparisonExpression ( '&' | '|' | '^' ) comparisonExpression)*
+ ;
+
+comparisonExpression
+ : shiftExpression (('==' | '!='| '>'| '<'| '>='| '<=') shiftExpression)*
+ ;
+
+shiftExpression
+ : additiveExpression ( '<<' | '>>>' | '>>' ) additiveExpression)*
+ ;
+
+additiveExpression
+ : multiplicativeExpression (('+' | '-') multiplicativeExpression)*
+ ;
+
+multiplicativeExpression
+ : unaryExpression (('*' | '/') unaryExpression)*
+ ;
+
+unaryExpression
+ : ('-') unaryExpression
+ | '!' unaryExpression
+ | primaryExpression postFix*
+ ;
+
+primaryExpression
+ : INTEGER_LITERAL
+ | '(' expression ')'
+ | 'true'
+ | 'false'
+ | 'null'
+ | newExpression
+ | IDENTIFIER
+ ;
+
+newExpression : 'new' IDENTIFIER [ '[' expression ']' ] ;
+
+postFix
+ : '.' IDENTIFIER [ '=' expression ]
+ | '[' expression ']' [ '=' expression ]
+ | '#'
+ ;
+
+INTEGER_LITERAL
+ : [1-9]DIGIT*
+ | [0]
+ ;
+
+IDENTIFIER : NON_DIGIT (NON_DIGIT | DIGIT)* ;
+
+NON_DIGIT: [a-zA-Z_];
+DIGIT: [0-9];
+```
diff --git a/docs/chapter15/Makefile b/docs/chapter15/Makefile
new file mode 100644
index 00000000..12659b8f
--- /dev/null
+++ b/docs/chapter15/Makefile
@@ -0,0 +1,12 @@
+GV := $(wildcard *.gv)
+SVG := $(patsubst %.gv,%.svg,$(GV))
+
+.PHONY: all
+all: $(SVG)
+
+%.svg: %.gv
+ dot -Tsvg $< > $@
+
+.PHONY: clean
+clean:
+ @rm -f $(SVG)
diff --git a/docs/chapter15/README.md b/docs/chapter15/README.md
new file mode 100644
index 00000000..db0f2f1c
--- /dev/null
+++ b/docs/chapter15/README.md
@@ -0,0 +1,260 @@
+# Chapter 15: Arrays
+
+[Previous: Chapter 14](../chapter14/README.md) |
+[Next: Chapter 16](../chapter16/README.md)
+
+# Table of Contents
+
+1. [Parser changes](#parser)
+2. [TypeArray is TypeStruct](#type-array-is-typestruct)
+3. [Allocation and partial memory](#allocation-and-partial-memory)
+4. [New Address Math](#new-address-math)
+5. [Struct Layout](#struct-layout)
+6. [Some Simple Address Math Peeps](#some-simple-address-math-peeps)
+7. [Discussion](#discussion)
+
+
+At long last we introduce arrays! These require more complex addressing math,
+which in turn means we need to change how Loads and Stores do addressing -
+with true field *offsets* instead of field *names*. This means we also
+will do a struct field *layout* in this chapter.
+
+Arrays are created with the standard `new int[len]` syntax, and can be any base
+type and any integer expression length. Arrays are read and written in the usual
+style: `Person p = persons[idx];` and `persons[idx] = new Person;`. The length
+is accessed with post-fix `#`; e.g. `persons#`.
+
+Like Java, arrays are always safety checked, and failing a runtime check
+will panic the Evaluator.
+
+- Indexing out of bounds:
+- `ary[-1]`
+- `ary[ary#]`
+- Creating an array with a bad length:
+- `new int[-1]; // Negative length`
+- `new int[1<<63]; // Too large`
+- `new int[3.14]; // Not an integer`
+
+
+You can also read [this chapter](https://github.com/SeaOfNodes/Simple/tree/linear-chapter15) in a linear Git revision history on the [linear](https://github.com/SeaOfNodes/Simple/tree/linear) branch and [compare](https://github.com/SeaOfNodes/Simple/compare/linear-chapter14...linear-chapter15) it to the previous chapter.
+
+
+## Parser
+
+The Parser changes to support arrays are fairly minimal. The main change is
+when parsing a `type()` we look for array syntax: `int[]` is now a valid type.
+Any dimension of arrays are allowed, and the base type can be any type so
+e.g. `Person[][]?` is a two-dimensional array of `Person`s which might be null.
+
+Arrays are always created with zero/null bodies, so implicitly always allow
+`null` values.
+
+We make a new array with much the same syntax as making a new object: `new
+int[99]`. The length is required and is any integer expression, so
+e.g. `new int[arg]` is an integer array of length `arg`. Multi-dimensional arrays
+only make the outer dimension; the elements themselves are all null and typed
+as the next lower dimension array.
+
+Arrays implicitly have two fields: the length and the body. The length is a
+read-only field set when the array is allocated; it is referenced with the
+post-fix `#` operator; e.g. `ary#`. Example to sum an array:
+
+```java
+int[] ary = new int[arg];
+int sum = 0;
+int i = 0;
+while( i < ary# ) {
+ sum = sum + ary[i];
+ i = i + 1;
+}
+```
+
+Because arrays have two implicit fields, they get 2 unique alias numbers per
+base array type. This means that `int[]` and `flt[]` types never alias, but
+all `int[]` types all alias - and all references into an array body all alias
+with each other. This can lead to some conservative code (no optimization)
+with mixing loads and stores in an array in a loop. Some of this can be
+addressed in a later chapter - simple 1-d stencil calculations can be heavily
+optimized - but Simple the compiler is not targeting complex vectorization.
+
+
+## Type Array is TypeStruct
+
+Arrays have a unique type, which is actually just a `TypeStruct` with fields
+named `#` and `[]`. Since these are not available as field names from within
+the Parser, users cannot write their own structs which get confused as arrays.
+
+These `TypeStruct` arrays otherwise behave exactly as a `TypeStruct`.
+
+
+## Allocation and partial memory
+
+This chapter keeps Chapter 11's single hidden `$mem` variable and lazy
+`BulkMemPhi`/`MemPhi` partitioning. Start produces `{ctrl, $mem, arg}` and
+Return consumes `{ctrl, $mem, value}`. Array initialization changes New:
+instead of separate initializing Stores, New allocates and zeros the object.
+
+New consumes `{ctrl, $mem, size}` and produces just `{ptr, $mem}`. The pointer
+is result 0 because New has no control result. Its memory input and result
+cover only the field aliases in New's internal `TypeStruct`, also available
+through its `TypeMemPtr`. Alias numbers are identities, not projection indices.
+
+For an allocation affecting aliases `A`, the parser builds:
+
+```text
+input = MemMerge(default: absent, each a in A: before[a])
+new = New(ctrl, input, size)
+ptr = Proj(new, 0)
+after = MemMerge(default: before, each a in A: Proj(new, 1))
+```
+
+All affected entries share the same memory projection. The input MemMerge is
+partial: a null default means an unlisted alias is **not covered**. The output
+aggregate retains the previous memory for unrelated aliases. An empty struct
+has an empty partial input and leaves whole memory unchanged.
+
+Memory types now pair an alias with the type of values stored in that slice.
+An alias query through New combines the incoming contents with the field's
+initializer: integer zero, floating zero, or null. The query follows aggregates
+and allocations, stops at cached Phi types, and records optimizer dependencies.
+MemPhi merges these value types for its one alias. No per-alias New projections
+or new lattice domain are needed.
+
+A Load using New's own pointer can fold to the initialized value. A pointer
+from a distinct New can bypass the allocation to its memory input. Mere node
+inequality is insufficient: a Phi or cast can still denote the allocated object.
+Array length is set by a subsequent Store; the array body is zeroed by New.
+Array accesses retain their existing control input as the bounds-check proxy.
+
+Both schedulers follow an alias through MemMerge to find New's memory effect.
+MemMerge itself does not overwrite memory, but must not hide the ordering of
+a read before allocation and a later write to an existing object.
+
+## New Address Math
+
+Since arrays need addressing math to index into them, Loads and Stores need a
+way to compute an offset. Previously Loads and Stores only worked on structs,
+and included the field name within them - thus the implicit addressing was field
+name (e.g. a string lookup!). New in this chapter we create a struct *layout*
+and compute field offsets to all structs.
+
+Loads and Stores now take an `off` instead of a field name, and the field
+name is included for debugging only.
+
+```java
+public MemOpNode(String name, int alias, Type glb, Node mem, Node ptr, Node off) {
+ super(null, mem, ptr, off);
+ _name = name;
+ _alias = alias;
+ _declaredType = glb;
+}
+```
+
+For arrays, the `off` is computed via the normal array math sequence:
+
+ `off = base + (idx << scale)`
+
+The base and index are computed for arrays from the element type, and used
+by the parser to build the offset. For structs, the layout is used.
+
+```java
+// Get field type and layout offset from base type and field index fidx
+Field f = base._fields[fidx]; // Field from field index
+Node off;
+if( name.equals("[]") ) { // If field is an array body
+ // Array index math
+ Node idx = require(parseExpression(),"]");
+ off = new AddNode(con(base.aryBase()),new ShlNode(idx,con(base.aryScale())).peephole()).peephole();
+} else { // Else normal struct field
+ // Hardwired field offset
+ off = con(base.offset(fidx));
+}
+```
+
+
+## Struct Layout
+
+New in this chapter is struct *layouts*: a fixed offset inside a block of
+memory where a particular field is stored. The offset is computed and cached
+in `TypeStruct`. Every field type now has a `log_size`. These fields are
+packed by size, with alignment padding and the whole struct is padded out to
+mod 8. This means the actual field offset and the field declaration order are
+*unrelated*: large fields pack low, and smaller fields pack next.
+
+```java
+struct s {
+ i8 b; // log_size=0
+ u16 char; // log_size=1
+ Person p; // log_size=2
+ flt pi; // log_size=3
+}
+// Layout will be:
+struct s {
+ flt pi; // offset= 0, log_size=3
+ Person p; // offset= 8, log_size=2
+ u16 char; // offset=12, log_size=1
+ i8 b; // offset=14, log_size=0
+ void pad; // offset=15, size = 1
+} // size =16
+```
+
+Field size is mostly obvious except perhaps for `TypeInteger`. Floats are
+either 32 bits or 64 bits. Pointers are set at 4 bytes in `TypeMemPtr` and
+this could be changed at a later date to support a larger range of pointers.
+`TypeInteger` only has valid `log_size` fields for a fixed set of user-visible
+integer ranges. Other ranges are possible in Simple via `compute` calls, but
+these will never show up as base field types:
+
+ `struct S { i16 x; }`
+
+Here `x` will have offset 0 and size 2 bytes, and `S` will be padded to size 8.
+
+ `S s = new S; s.x = 3;`
+
+The same `x` field now holds either a 0 or a 3, so the `TypeInteger` for `x`
+will be a range `[0-3]` which could be represented in just 2 bits ... but this is not used to compute the field size.
+
+
+## Some Simple Address Math Peeps
+
+Shift-left by 0 is an identity: `(x<<0)` becomes `x`.
+
+Shift-left with a constant add distributes, to allow more constant folding:
+`(x + c) << i` becomes `(x << i) + (c << i)`.
+
+This means the IR for:
+
+ `x[i]` becomes `16/*base*/ + (i << 3/*scale*/)`
+ becomes `Load(mem,x,Add(Shl(i,3),16))`
+
+
+ `x[i+1]` becomes `16/*base*/ + ((i+1) << 3/*scale*/)` becomes
+ `Load(mem,x,Add(Shl(i,3),24))`
+
+Notice the `idx+1` folds the `+1` into the base math.
+
+Also notice this form is perfect for the X86 addressing modes.
+
+## Discussion
+
+Included in the test cases is a simple rolling sum, and a Sieve of Eratosthenes.
+
+```java
+int i=0;
+while( i < ary# ) {
+ ary[i] = i;
+ i = i+1;
+}
+```
+
+Here we really see the lack of an `i++` operator and a `for` loop. You can
+imagine another syntax:
+
+```java
+for( int i=0; i < ary#; i++ )
+ ary[i] = i;
+```
+
+that would be entirely syntactic-sugar over the existing parser, but would
+really help make array looping syntax clearer. There are lots of options
+here, which should really be the focus of another chapter!
diff --git a/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java b/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java
index 78a0dcde..d17ec3b6 100644
--- a/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java
+++ b/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java
@@ -93,11 +93,12 @@ public abstract class GlobalCodeMotion {
private static void _schedEarly(Node n, BitSet visit) {
if( n==null || visit.get(n._nid) ) return; // Been there, done that
+ assert !(n instanceof CFGNode);
visit.set(n._nid);
// Schedule inputs first, except Phis: following their backedges would
// enter a data cycle before its control has been scheduled.
for( Node def : n._inputs )
- if( def!=null && !(def instanceof PhiNode) )
+ if( def!=null )
_schedEarly(def,visit);
// An existing edge 0 already supplies control (or a Phi/Proj binding).
if( n.in(0)==null ) {
@@ -122,7 +123,7 @@ public abstract class GlobalCodeMotion {
// Copy the best placement choice into the control slot
for( int i=0; i<late.length; i++ )
- if( ns[i] != null )
+ if( ns[i] != null && !(ns[i] instanceof ProjNode) )
ns[i].setDef(0,late[i]);
}
@@ -137,8 +138,7 @@ public abstract class GlobalCodeMotion {
// These I know the late schedule of, and need to set early for loops
if( n instanceof CFGNode cfg ) late[n._nid] = cfg.blockHead() ? cfg : cfg.cfg(0);
else if( n instanceof PhiNode phi ) late[n._nid] = phi.region();
- // These nodes have a fixed late placement at their original control.
- else if( n instanceof ProjNode || n instanceof NewNode || n==Parser.ZERO || n instanceof CastNode ) late[n._nid] = n.cfg0();
+ else if( n instanceof ProjNode && n.in(0) instanceof CFGNode cfg ) late[n._nid] = cfg;
else {
// All uses done?
@@ -148,8 +148,8 @@ public abstract class GlobalCodeMotion {
// Loads need their memory inputs' uses also done
if( n instanceof LoadNode ld )
- for( Node memuse : ld.mem()._outputs )
- if( antiUse(ld,memuse) && late[memuse._nid]==null )
+ for( Node memuse : ld.antiDeps() )
+ if( late[memuse._nid]==null )
continue outer;
// All uses done, schedule
@@ -161,9 +161,7 @@ public abstract class GlobalCodeMotion {
for( Node def : n._inputs ) {
if( def==null ) continue;
if( late[def._nid]==null ) work.push(def);
- for( Node out : def._outputs )
- if( out instanceof LoadNode ld && late[ld._nid]==null )
- work.push(ld);
+ wakeLoads(def,late,work,new BitSet());
}
if( n instanceof LoopNode loop )
for( Node phi : loop._outputs )
@@ -174,11 +172,12 @@ public abstract class GlobalCodeMotion {
private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late, int[] anti) {
// Walk uses, gathering the LCA (Least Common Ancestor) of uses
- CFGNode early = (CFGNode)n.in(0);
+ CFGNode early = n.in(0) instanceof CFGNode cfg ? cfg : n.in(0).cfg0();
assert early != null;
CFGNode lca = null;
for( Node use : n._outputs )
- lca = use_block(n,use, late).domLCA(lca,null);
+ if( use != null )
+ lca = use_block(n,use, late).domLCA(lca,null);
// Loads may need anti-dependencies, raising their LCA
if( n instanceof LoadNode load )
@@ -219,15 +218,15 @@ public abstract class GlobalCodeMotion {
best instanceof IfNode;
}
- // Only a store or memory Phi covering this alias can constrain a load.
- // MemMerge packages slices without overwriting them.
- private static boolean antiUse(LoadNode load, Node use) {
- return switch( use ) {
- case StoreNode st -> st._alias==load._alias;
- case MemPhiNode phi -> phi._alias==load._alias;
- case BulkMemPhiNode phi -> !phi.isSplit(load._alias);
- default -> false;
- };
+ // An allocation may consume a load's memory through a partial aggregate.
+ private static void wakeLoads(Node def, CFGNode[] late, WorkList<Node> work, BitSet visit) {
+ if( visit.get(def._nid) ) return;
+ visit.set(def._nid);
+ for( Node out : def._outputs )
+ if( out instanceof LoadNode ld && late[ld._nid]==null ) work.push(ld);
+ if( def instanceof MemMergeNode )
+ for( int i=1; i<def.nIns(); i++ )
+ if( def.in(i)!=null ) wakeLoads(def.in(i),late,work,visit);
}
private static CFGNode find_anti_dep(CFGNode lca, LoadNode load, CFGNode early, CFGNode[] late, int[] anti) {
@@ -236,13 +235,16 @@ public abstract class GlobalCodeMotion {
for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
anti[cfg._nid] = load._nid;
// Walk load->mem uses, looking for Stores causing an anti-dep
- for( Node mem : load.mem()._outputs ) {
- if( !antiUse(load,mem) ) continue;
+ for( Node mem : load.antiDeps() ) {
switch( mem ) {
case StoreNode st:
assert late[st._nid]!=null;
lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st,anti);
break;
+ case NewNode st:
+ assert late[st._nid]!=null;
+ lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st,anti);
+ break;
case PhiNode phi:
// Repeat anti-dep for matching Phi inputs.
// No anti-dep edges but may raise the LCA.
diff --git a/src/main/java/com/seaofnodes/simple/Parser.java b/src/main/java/com/seaofnodes/simple/Parser.java
index 4ab4d9ac..a0c15ae3 100644
--- a/src/main/java/com/seaofnodes/simple/Parser.java
+++ b/src/main/java/com/seaofnodes/simple/Parser.java
@@ -31,14 +31,13 @@ public class Parser {
*/
public static StartNode START;
- public static ConstantNode ZERO;
- public static XCtrlNode XCTRL;
+ public static ConstantNode ZERO; // Very common node, cached here
+ public static XCtrlNode XCTRL; // Very common node, cached here
- public StopNode STOP;
+ // Next available memory alias number
+ static int ALIAS;
- // Field identities are independent of the memory graph and Start's tuple.
- private final HashMap<String,Integer> _aliases = new HashMap<>();
- private int _alias = 2;
+ public StopNode STOP;
// Debugger Printing.
public static boolean SCHEDULED; // True if debug printer can use schedule info
@@ -101,7 +100,9 @@ public class Parser {
ScopeNode _continueScope;
ScopeNode _breakScope;
- // Mapping from a type name to a Type.
+ // Mapping from a type name to a Type. The string name matches
+ // `type.str()` call. No TypeMemPtrs are in here, because Simple does not
+ // have C-style '*ptr' references.
public static HashMap<String, Type> TYPES = new HashMap<>();
public Parser(String source, TypeInteger arg) {
@@ -129,8 +130,9 @@ public class Parser {
_continueScope = _breakScope = null;
START = new StartNode(new Type[]{ Type.CONTROL, TypeMem.BOT, arg });
STOP = new StopNode(source);
- ZERO = new ConstantNode(TypeInteger.constant(0)).peephole().keep();
+ ZERO = con(0).keep();
XCTRL= new XCtrlNode().peephole().keep();
+ ALIAS = 2; // alias 0 for the control, 1 for memory
}
public Parser(String source) {
@@ -157,8 +159,9 @@ public class Parser {
_scope.define(ScopeNode.ARG0, TypeInteger.BOT, new ProjNode(START, 2, ScopeNode.ARG0).peephole());
_scope.define("$mem", TypeMem.BOT, new ProjNode(START, 1, "$mem").peephole());
parseBlock();
+
if( ctrl()._type==Type.CONTROL )
- STOP.addReturn(new ReturnNode(ctrl(), new ConstantNode(TypeInteger.constant(0)).peephole(), _scope).peephole());
+ STOP.addReturn(new ReturnNode(ctrl(), ZERO, _scope).peephole());
_scope.pop();
_xScopes.pop();
if (!_lexer.isEOF()) throw error("Syntax error, unexpected " + _lexer.getAnyNextToken());
@@ -218,7 +221,7 @@ public class Parser {
Type t = type();
if( t==null )
throw errorSyntax("Requires a field type, found '"+_lexer.getAnyNextToken()+"'");
- return require(Field.make(requireId().intern(),t),";");
+ return require(Field.make(requireId().intern(),ALIAS++,t),";");
}
/**
@@ -232,7 +235,9 @@ public class Parser {
if (_xScopes.size() > 1) throw errorSyntax("struct declarations can only appear in top level scope");
String typeName = requireId();
Type t = TYPES.get(typeName);
- if( t!=null && !(t instanceof TypeStruct ts && ts._fields==null) ) throw errorSyntax("struct '" + typeName + "' cannot be redefined");
+ if( t!=null && !(t instanceof TypeMemPtr tmp && tmp._obj._fields==null) )
+ throw errorSyntax("struct '" + typeName + "' cannot be redefined");
+ // Parse a collection of fields
ArrayList<Field> fields = new ArrayList<>();
require("{");
while (!peek('}') && !_lexer.isEOF()) {
@@ -242,10 +247,8 @@ public class Parser {
}
require("}");
// Build and install the TypeStruct
- TypeStruct ts = TypeStruct.make(typeName, fields);
- TYPES.put(typeName, ts); // Insert the struct name in the collection of all struct names
- _aliases.put(typeName,_alias);
- _alias += ts._fields.length;
+ TypeStruct ts = TypeStruct.make(typeName, fields.toArray(new Field[fields.size()]));
+ TYPES.put(typeName, TypeMemPtr.make(ts));
return parseStatement();
}
@@ -337,7 +340,7 @@ public class Parser {
ctrl(XCTRL); // Kill current scope
// Prune nested lexical scopes that have depth > than the loop head
// We use _breakScope as a proxy for the loop head scope to obtain the depth
- while( cur._scopes.size() > _breakScope._scopes.size() )
+ while( cur._idxs.size() > _breakScope._idxs.size() )
cur.pop();
// If this is a continue then first time the target is null
// So we just use the pruned current scope as the base for the
@@ -345,7 +348,7 @@ public class Parser {
if (toScope == null)
return cur;
// toScope is either the break scope, or a scope that was created here
- assert toScope._scopes.size() <= _breakScope._scopes.size();
+ assert toScope._idxs.size() <= _breakScope._idxs.size();
toScope.ctrl(toScope.mergeScopes(cur));
return toScope;
}
@@ -469,33 +472,69 @@ public class Parser {
// Auto-deepen forward ref types
Type e = expr._type;
if( e instanceof TypeMemPtr tmp && tmp._obj._fields==null )
- e = tmp.make_from((TypeStruct)TYPES.get(tmp._obj._name));
+ e = TYPES.get(tmp._obj._name);
// Type is sane
if( !e.isa(t) )
throw error("Type " + e.str() + " is not of declared type " + t.str());
return _scope.update(name,expr);
}
- // Parse a "type id" and return "type" (and re-parse "id" in caller) or
- // return null.
+ // Parse and return a type or null. Valid types always are followed by an
+ // 'id' which the caller must parse. This lets us distinguish forward ref
+ // types (which ARE valid here) from local vars in an (optional) forward
+ // ref type position.
private Type type() {
- int old = _lexer._position;
+ int old1 = _lexer._position;
String tname = _lexer.matchId();
if( tname==null ) return null;
- boolean nullable = match("?");
- Type t = TYPES.get(tname);
- // Assume a forward-reference type
- if( t == null ) {
- int old2 = _lexer._position;
- String id = _lexer.matchId();
- if( id==null ) {
- _lexer._position = old;
- return null;
+ // Convert the type name to a type.
+ Type t0 = TYPES.get(tname);
+ Type t1 = t0 == null ? TypeMemPtr.make(TypeStruct.make(tname)) : t0; // Null: assume a forward ref type
+ // Nest arrays and '?' as needed
+ while( true ) {
+ assert !(t1 instanceof TypeStruct);
+ if( match("?") ) {
+ if( !(t1 instanceof TypeMemPtr tmp) )
+ throw error("Type "+t0+" cannot be null");
+ if( tmp._nil ) throw error("Type "+t1+" already allows null");
+ t1 = TypeMemPtr.make(tmp._obj,true);
+ continue;
+ }
+ if( match("[]") ) {
+ t1 = typeAry(t1);
+ continue;
}
- TYPES.put(tname,t = TypeStruct.make(tname));
- _lexer._position = old2; // Reparse ID in caller
+ break;
}
- return t instanceof TypeStruct obj ? TypeMemPtr.make(obj,nullable) : t;
+
+ // Check no forward ref
+ if( t0 != null ) return t1;
+ // Check valid forward ref, after parsing all the type extra bits.
+ // Cannot check earlier, because cannot find required 'id' until after "[]?" syntax
+ int old2 = _lexer._position;
+ String id = _lexer.matchId();
+ _lexer._position = old2; // Reset lexer to reparse
+ if( id==null ) {
+ _lexer._position = old1; // Reset lexer to reparse
+ return null; // Not a type
+ }
+ // Yes a forward ref, so declare it
+ TYPES.put(tname,t1);
+ return t1;
+ }
+
+ // Make an array type of t
+ private TypeMemPtr typeAry( Type t ) {
+ if( t instanceof TypeMemPtr tmp && !tmp._nil )
+ throw error("Arrays of reference types must always be nullable");
+ String tname = t.str()+"[]";
+ Type ta = TYPES.get(tname);
+ if( ta != null ) return (TypeMemPtr)ta;
+ // Need make an array type.
+ TypeStruct ts = TypeStruct.makeAry(TypeInteger.BOT,ALIAS++,t,ALIAS++);
+ TypeMemPtr tary = TypeMemPtr.make(ts);
+ TYPES.put(tname,tary);
+ return tary;
}
@@ -643,22 +682,36 @@ public class Parser {
* Parse a primary expression:
*
* <pre>
- * primaryExpr : integerLiteral | Identifier | true | false | null | new Identifier | '(' expression ')'
+ * primaryExpr : integerLiteral | Identifier | true | false | null | new Type | '(' expression ')'
* </pre>
* @return a primary {@link Node}, never {@code null}
*/
private Node parsePrimary() {
if( _lexer.isNumber(_lexer.peek()) ) return parseLiteral();
if( match("(") ) return require(parseExpression(), ")");
- if( matchx("true" ) ) return new ConstantNode(TypeInteger.constant(1)).peephole();
+ if( matchx("true" ) ) return con(1);
if( matchx("false") ) return ZERO;
if( matchx("null" ) ) return new ConstantNode(TypeMemPtr.NULLPTR).peephole();
if( matchx("new") ) {
- String structName = requireId();
- Type t = TYPES.get(structName);
- if( !(t instanceof TypeStruct obj) || obj._fields==null )
- throw error("Unknown struct type '" + structName + "'");
- return newStruct(obj);
+ Type t = type();
+ if( t==null ) throw error("Expected a type");
+ if( match("[") ) {
+ Node len = parseExpression().keep();
+ if( !(len._type instanceof TypeInteger) )
+ throw error("Cannot allocate an array with length "+len._type);
+ require("]");
+ TypeMemPtr tmp = typeAry(t);
+ return newArray(tmp._obj,len);
+ }
+ if( t instanceof TypeMemPtr tmp ) {
+ TypeStruct obj = tmp._obj;
+ if( obj._fields==null )
+ throw error("Unknown struct type '" + obj._name + "'");
+ if( obj.isAry() )
+ throw Utils.TODO(); // Missing array length
+ return newStruct(obj,con(obj.offset(obj._fields.length)));
+ }
+ throw error("Cannot allocate a "+t.str());
}
String name = _lexer.matchId();
if( name == null) throw errorSyntax("an identifier or expression");
@@ -668,25 +721,46 @@ public class Parser {
}
/**
- * Return a NewNode but also generate instructions to initialize it.
+ * Return a NewNode with pre-zeroed memory
*/
- private Node newStruct(TypeStruct obj) {
- Node n = new NewNode(TypeMemPtr.make(obj), ctrl()).peephole().keep();
- int alias = _aliases.get(obj._name);
- for( Field field : obj._fields ) {
- store(field._fname,alias,field._type,n,new ConstantNode(field._type.makeInit()).peephole(),true);
- alias++;
- }
- return n.unkeep();
+ private Node newStruct(TypeStruct obj, Node size) {
+ Node prior = mem().keep();
+ // No default: only the aliases initialized by this allocation.
+ MemMergeNode input = new MemMergeNode(null);
+ for( Field f : obj._fields )
+ input.alias(f._alias,prior);
+ Node nnn = new NewNode(TypeMemPtr.make(obj),ctrl(),input.peephole(),size).peephole().keep();
+ Node ptr = new ProjNode(nnn,0,obj._name).peephole().keep();
+ Node out = new ProjNode(nnn,1,"$mem").peephole().keep();
+ MemMergeNode after = new MemMergeNode(prior);
+ for( Field f : obj._fields )
+ after.alias(f._alias,out);
+ mem(after.peephole());
+ out.unkeep();
+ nnn.unkeep();
+ if( prior.unkeep().isUnused() ) prior.kill();
+ return ptr.unkeep();
+ }
+
+ private Node newArray(TypeStruct ary, Node len) {
+ int base = ary.aryBase ();
+ int scale= ary.aryScale();
+ Node size = new AddNode(con(base),new ShlNode(len,con(scale)).peephole()).peephole();
+ Node ptr = newStruct(ary,size);
+ int alias = ary._fields[0]._alias; // Length alias
+ store("#",alias,TypeInteger.BOT,ptr,con(ary.offset(0)),len.unkeep(),true,null);
+ return ptr;
}
// Memory is one hidden SSA variable, including across branches and loops.
private Node mem() { return _scope.lookup("$mem"); }
private Node mem(Node n) { return _scope.update("$mem",n); }
- private void store(String name, int alias, Type glb, Node ptr, Node val, boolean init) {
+ private void store(String name, int alias, Type glb, Node ptr, Node off, Node val, boolean init, Node ctrl) {
Node prior = mem().keep();
- Node st = new StoreNode(name,alias,glb,ctrl(),prior,ptr,val,init).peephole();
+ Node st = new StoreNode(name,alias,glb,prior,ptr,off,val,init);
+ st.setDef(0,ctrl);
+ st = st.peephole();
mem(new MemMergeNode(prior,alias,st).peephole());
if( prior.unkeep().isUnused() ) prior.kill();
}
@@ -697,39 +771,66 @@ public class Parser {
*
* <pre>
* expr ('.' IDENTIFIER)* [ = expr ]
+ * expr #
+ * expr ('[' expr ']')* = [ = expr ]
* </pre>
*/
private Node parsePostfix(Node expr) {
- if( !match(".") ) return expr;
+ String name = null;
+ if( match(".") ) name = requireId().intern();
+ else if( match("#") ) name = "#";
+ else if( match("[") ) name = "[]";
+ else return expr; // No postfix
+ // Sanity check expr for being a reference
if( !(expr._type instanceof TypeMemPtr ptr) )
- throw error("Expected struct reference but got " + expr._type.str());
-
- String name = requireId().intern();
- if( expr._type == TypeMemPtr.TOP ) throw error("Accessing field '" + name + "' from null");
+ throw error("Expected reference but got " + expr._type.str());
+ if( ptr == TypeMemPtr.TOP ) throw error("Accessing field '" + name + "' from null");
if( ptr._obj == null ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'");
- TypeStruct base = (TypeStruct)TYPES.get(ptr._obj._name);
- int idx = base==null ? -1 : base.find(name);
- if( idx == -1 ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'");
- int alias = _aliases.get(ptr._obj._name)+idx;
+ // Sanity check field name for existing
+ TypeMemPtr tmp = (TypeMemPtr)TYPES.get(ptr._obj._name);
+ if( tmp == null ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'");
+ TypeStruct base = tmp._obj;
+ int fidx = base.find(name);
+ if( fidx == -1 ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'");
+ expr.keep();
+
+ // Get field type and layout offset from base type and field index fidx
+ Field f = base._fields[fidx]; // Field from field index
+ Node off;
+ if( name.equals("[]") ) { // If field is an array body
+ // Array index math
+ Node idx = require(parseExpression(),"]");
+ Node shl = new ShlNode(idx,con(base.aryScale())).peephole();
+ off = new AddNode(con(base.aryBase()),shl).peephole();
+ } else { // Else normal struct field
+ // Hardwired field offset
+ off = con(base.offset(fidx));
+ }
if( match("=") ) {
// Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment
if( peek('=') ) _lexer._position--;
else {
+ off.keep();
Node val = parseExpression();
- Type glb = base._fields[idx]._type;
// Auto-truncate when storing to narrow fields
- val = zsMask(val,glb);
- store(name,alias,glb,expr,val,false);
- return expr; // "obj.a = expr" returns the expression while updating memory
+ val = zsMask(val,f._type).keep();
+ // Array control stands in for the future bounds check.
+ store(name,f._alias,f._type,expr.unkeep(),off.unkeep(),val,false,base.isAry() ? ctrl() : null);
+ return val.unkeep(); // "obj.a = expr" returns the expression while updating memory
}
}
- Type declaredType = base._fields[idx]._type;
- return parsePostfix(new LoadNode(name, alias, declaredType.glb(), mem(), expr).peephole());
+ Node load = new LoadNode(name, f._alias, f._type.glb(), mem(), expr.unkeep(), off);
+ // Arrays include control, as a proxy for a safety range check
+ // Structs don't need this; they only need a NPE check which is
+ // done via the type system.
+ if( base.isAry() ) load.setDef(0,ctrl());
+ return parsePostfix(load.peephole());
}
+
// zero/sign extend. "i" is limited to either classic unsigned (min==0) or
// classic signed (min=minus-power-of-2); max=power-of-2-minus-1.
private Node zsMask(Node val, Type t ) {
@@ -740,10 +841,10 @@ public class Parser {
return new RoundF32Node(val).peephole();
}
if( t0._min==0 ) // Unsigned
- return new AndNode(val,new ConstantNode(TypeInteger.constant(t0._max)).peephole()).peephole();
+ return new AndNode(val,con(t0._max)).peephole();
// Signed extension
int shift = Long.numberOfLeadingZeros(t0._max)-1;
- Node shf = new ConstantNode(TypeInteger.constant(shift)).peephole();
+ Node shf = con(shift);
if( shf._type==TypeInteger.ZERO )
return val;
return new SarNode(new ShlNode(val,shf.keep()).peephole(),shf.unkeep()).peephole();
@@ -762,6 +863,10 @@ public class Parser {
return (ConstantNode) new ConstantNode(_lexer.parseNumber()).peephole();
}
+ public static Node con( long con ) {
+ return new ConstantNode(TypeInteger.constant(con)).peephole();
+ }
+
//////////////////////////////////
// Utilities for lexical analysis
diff --git a/src/main/java/com/seaofnodes/simple/Utils.java b/src/main/java/com/seaofnodes/simple/Utils.java
index f3ac25b1..1346c3ea 100644
--- a/src/main/java/com/seaofnodes/simple/Utils.java
+++ b/src/main/java/com/seaofnodes/simple/Utils.java
@@ -13,14 +13,9 @@ public class Utils {
* @param i element to be removed
* @return element removed
*/
- public static <E> E del(ArrayList<E> array, int i) {
- if ( i >= 0 && i < array.size() ) {
- E tmp = array.get(i);
- E last = array.removeLast();
- if (i < array.size()) array.set(i, last);
- return tmp;
- }
- return null;
+ public static <E> void del(ArrayList<E> array, int i) {
+ E last = array.removeLast();
+ if (i < array.size()) array.set(i, last);
}
/**
diff --git a/src/main/java/com/seaofnodes/simple/node/AddNode.java b/src/main/java/com/seaofnodes/simple/node/AddNode.java
index 5e54100d..b08f749a 100644
--- a/src/main/java/com/seaofnodes/simple/node/AddNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/AddNode.java
@@ -15,7 +15,8 @@ public class AddNode extends Node {
@Override
public Type compute() {
Type t1 = in(1)._type, t2 = in(2)._type;
- if( t1.isHigh() || t2.isHigh() ) return TypeInteger.TOP;
+ if( t1.isHigh() || t2.isHigh() )
+ return TypeInteger.TOP;
if( t1 instanceof TypeInteger i1 &&
t2 instanceof TypeInteger i2 ) {
if (i1.isConstant() && i2.isConstant())
@@ -29,7 +30,7 @@ public class AddNode extends Node {
}
private static boolean overflow( long x, long y ) {
- if( (x ^ y ) < 0 ) return false; // unequal signs, never overflow
+ if( (x ^ y ) < 0 ) return false; // unequal signs, never overflow
return (x ^ (x + y)) < 0; // sum has unequal signs, so overflow
}
@Override
diff --git a/src/main/java/com/seaofnodes/simple/node/AndNode.java b/src/main/java/com/seaofnodes/simple/node/AndNode.java
index 5e51e248..1f223bcb 100644
--- a/src/main/java/com/seaofnodes/simple/node/AndNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/AndNode.java
@@ -14,7 +14,6 @@ public class AndNode extends Node {
@Override
public Type compute() {
- if( in(1)._type.isHigh() || in(2)._type.isHigh() ) return TypeInteger.TOP;
if( in(1)._type instanceof TypeInteger i0 &&
in(2)._type instanceof TypeInteger i1 ) {
if( i0.isConstant() && i1.isConstant() )
diff --git a/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/src/main/java/com/seaofnodes/simple/node/BoolNode.java
index 8a25e8d9..6efde623 100644
--- a/src/main/java/com/seaofnodes/simple/node/BoolNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/BoolNode.java
@@ -32,9 +32,6 @@ abstract public class BoolNode extends Node {
Type t2 = in(2)._type;
if( t1.isHigh() || t2.isHigh() )
return BOOL.dual();
- // Compare of same
- if( in(1)==in(2) )
- return doOp(ZERO,ZERO);
if( t1 instanceof TypeInteger i1 &&
t2 instanceof TypeInteger i2 )
return doOp(i1,i2);
@@ -50,6 +47,10 @@ abstract public class BoolNode extends Node {
@Override
public Node idealize() {
+ // Compare of same
+ if( in(1)==in(2) )
+ return new ConstantNode(doOp(ZERO,ZERO));
+
// Equals pushes constant to the right; 5==X becomes X==5.
if( this instanceof EQ ) {
if( !(in(2) instanceof ConstantNode) ) {
@@ -61,11 +62,14 @@ abstract public class BoolNode extends Node {
// Equals X==0 becomes a !X
if( (in(2)._type == ZERO || in(2)._type == TypeMemPtr.NULLPTR) )
return new NotNode(in(1));
- // Equals bool == 1 becomes bool
- if( in(2)._type == TRUE && in(1)._type == BOOL )
- return in(1);
}
+ // Do we have ((phi cons) cmp con) ?
+ // Do we have ((phi cons) cmp (phi cons)) ?
+ // Push the compare up through the phi: (phi con0 cmp con, con1 cmp con...)
+ Node phicon = AddNode.phiCon(this,this instanceof EQ);
+ if( phicon!=null ) return phicon;
+
return null;
}
@@ -103,8 +107,8 @@ abstract public class BoolNode extends Node {
Node copyF() { return new LEF(null,null); }
}
- public static class EQF extends EQ { public EQF(Node lhs, Node rhs) { super(lhs,rhs); } boolean doOp(double lhs, double rhs) { return lhs == rhs; } Node copy(Node lhs, Node rhs) { return new EQF(lhs,rhs); } }
- public static class LTF extends LT { public LTF(Node lhs, Node rhs) { super(lhs,rhs); } boolean doOp(double lhs, double rhs) { return lhs < rhs; } Node copy(Node lhs, Node rhs) { return new LTF(lhs,rhs); } }
- public static class LEF extends LE { public LEF(Node lhs, Node rhs) { super(lhs,rhs); } boolean doOp(double lhs, double rhs) { return lhs <= rhs; } Node copy(Node lhs, Node rhs) { return new LEF(lhs,rhs); } }
+ public static class EQF extends EQ { public EQF(Node lhs, Node rhs) { super(lhs,rhs); } boolean doOp(double lhs, double rhs) { return lhs == rhs; } }
+ public static class LTF extends LT { public LTF(Node lhs, Node rhs) { super(lhs,rhs); } boolean doOp(double lhs, double rhs) { return lhs < rhs; } }
+ public static class LEF extends LE { public LEF(Node lhs, Node rhs) { super(lhs,rhs); } boolean doOp(double lhs, double rhs) { return lhs <= rhs; } }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
index 8d512f21..84f97189 100644
--- a/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
@@ -87,7 +87,7 @@ public class BulkMemPhiNode extends PhiNode {
// True if the MemMerge has no precise slice still covered by this Phi.
private boolean canPeek(MemMergeNode mmm) {
- return missingAlias(mmm,false)==0;
+ return mmm.in(1)!=null && missingAlias(mmm)==0;
}
// Return an alias required by an input but still covered by this Phi, or 0.
@@ -95,7 +95,7 @@ public class BulkMemPhiNode extends PhiNode {
return switch(n) {
case ProjNode proj -> 0;
case ConstantNode con -> 0;
- case MemMergeNode mmm -> missingAlias(mmm,false);
+ case MemMergeNode mmm -> missingAlias(mmm);
case MemOpNode mem -> unsplit(mem._alias);
case BulkMemPhiNode bulk -> missingAlias(bulk);
default -> throw new AssertionError("Unexpected memory input "+n);
@@ -111,7 +111,7 @@ public class BulkMemPhiNode extends PhiNode {
use.addDepForwards(this);
return switch(use) {
case ScopeNode scope -> 0;
- case MemMergeNode mmm -> missingAlias(mmm,true);
+ case MemMergeNode mmm -> missingAlias(mmm);
case BulkMemPhiNode bulk -> missingAlias(bulk);
case MemOpNode mem -> unsplit(mem._alias);
case MemPhiNode phi -> unsplit(phi._alias);
@@ -139,14 +139,14 @@ public class BulkMemPhiNode extends PhiNode {
return 0;
}
- // First precise MemMerge slice still covered by this Phi. When inspecting
- // a user, ignore slots which point back to this Phi.
- private int missingAlias(MemMergeNode mmm, boolean user) {
+ // Every explicit slot requests that alias, including a partial allocation
+ // input whose slot still points to this bulk Phi.
+ private int missingAlias(MemMergeNode mmm) {
for( int alias=2; alias<mmm.nIns(); alias++ )
if( mmm.in(alias)!=null &&
mmm.alias(alias)!=mmm.in(1) &&
!_aliases.get(alias) )
- { assert (!user || mmm.in(alias)!=this); return alias; }
+ return alias;
return 0;
}
@@ -198,7 +198,7 @@ public class BulkMemPhiNode extends PhiNode {
mphi = new MemPhiNode("$"+alias,alias);
mphi.addDef(region());
// Due to cycles, must set before calling peephole
- mphi.setType(TypeMem.make(alias));
+ mphi.setType(TypeMem.make(alias,Type.BOTTOM));
for( int i=1; i<nIns(); i++ )
mphi.addDef(IterPeeps.add(preciseInput(in(i),alias)));
return IterPeeps.add(mphi);
diff --git a/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/src/main/java/com/seaofnodes/simple/node/CFGNode.java
index c690a83c..06596ce0 100644
--- a/src/main/java/com/seaofnodes/simple/node/CFGNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/CFGNode.java
@@ -23,8 +23,6 @@ public abstract class CFGNode extends Node {
public CFGNode(Node... nodes) { super(nodes); }
- @Override public boolean isCFG() { return true; }
-
public CFGNode cfg(int idx) { return (CFGNode)in(idx); }
// Block head is Start, Region, CProj, but not e.g. If, Return, Stop
diff --git a/src/main/java/com/seaofnodes/simple/node/CProjNode.java b/src/main/java/com/seaofnodes/simple/node/CProjNode.java
index ff5d1a76..cba63db8 100644
--- a/src/main/java/com/seaofnodes/simple/node/CProjNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/CProjNode.java
@@ -38,14 +38,17 @@ public class CProjNode extends CFGNode {
@Override
public Node idealize() {
- if ( ctrl() instanceof IfNode iff ) {
- if ( iff._type instanceof TypeTuple tt && tt._types[1 - _idx] == Type.XCONTROL )
- return iff.in(0); // We become our input control
-
- // Flip a negating if-test, to remove the not
- if( iff.pred().addDep(this) instanceof NotNode not )
- return new CProjNode(new IfNode(iff.ctrl(),not.in(1)).peephole(),1-_idx,_idx==0 ? "False" : "True");
+ if( ctrl()._type instanceof TypeTuple tt ) {
+ if( tt._types[_idx]==Type.XCONTROL )
+ return Parser.XCTRL; // We are dead
+ if( ctrl() instanceof IfNode && tt._types[1-_idx]==Type.XCONTROL ) // Only true for IfNodes
+ return ctrl().in(0); // We become our input control
}
+
+ // Flip a negating if-test, to remove the not
+ if( ctrl() instanceof IfNode iff && iff.pred().addDep(this) instanceof NotNode not )
+ return new CProjNode(new IfNode(iff.ctrl(),not.in(1)).peephole(),1-_idx,_idx==0 ? "False" : "True");
+
return null;
}
diff --git a/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
index 9ecd1152..426736ed 100644
--- a/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
@@ -38,7 +38,6 @@ public class ConstantNode extends Node {
}
@Override public boolean isMultiTail() { return true; }
- // The special forever-zero is pinned, all others can sink
@Override
public Type compute() { return _con; }
diff --git a/src/main/java/com/seaofnodes/simple/node/DivNode.java b/src/main/java/com/seaofnodes/simple/node/DivNode.java
index 5c7ba42c..cd749a03 100644
--- a/src/main/java/com/seaofnodes/simple/node/DivNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/DivNode.java
@@ -30,7 +30,7 @@ public class DivNode extends Node {
@Override
public Node idealize() {
// Div of 1.
- if( in(2)._type.isConstant() && in(2)._type instanceof TypeInteger i && i.value()==1 )
+ if( in(2)._type == TypeInteger.TRUE )
return in(1);
return null;
}
diff --git a/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/src/main/java/com/seaofnodes/simple/node/LoadNode.java
index 3191061c..b0517a4c 100644
--- a/src/main/java/com/seaofnodes/simple/node/LoadNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -2,10 +2,11 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.print.ExprPrinter;
-import com.seaofnodes.simple.Utils;
import com.seaofnodes.simple.IterPeeps;
import com.seaofnodes.simple.type.Type;
-
+import com.seaofnodes.simple.type.TypeMem;
+import java.util.BitSet;
+import java.util.ArrayList;
/**
* Load represents extracting a value from inside a memory object,
@@ -17,11 +18,12 @@ public class LoadNode extends MemOpNode {
* Load a value from a ptr.field.
*
* @param name The field we are loading
- * @param memSlice The memory alias node - this is updated after a Store
- * @param memPtr The ptr to the struct from where we load a field
+ * @param mem The memory alias node - this is updated after a Store
+ * @param ptr The ptr to the struct base from where we load a field
+ * @param off The offset inside the struct base
*/
- public LoadNode(String name, int alias, Type glb, Node memSlice, Node memPtr) {
- super(name, alias, glb, null, memSlice, memPtr);
+ public LoadNode(String name, int alias, Type glb, Node mem, Node ptr, Node off) {
+ super(name, alias, glb, mem, ptr, off);
}
@Override boolean canDrop(MemOpNode other, Node dep) {
@@ -37,18 +39,22 @@ public class LoadNode extends MemOpNode {
if( use==null ) continue;
use.addDepForwards(dep);
if( use instanceof StoreNode || use instanceof PhiNode ||
- use instanceof MemMergeNode ) return true;
+ use instanceof MemMergeNode ||
+ use instanceof NewNode ) return true;
}
return false;
}
- @Override public String label() { return _name; }
+ // Debugger label
+ @Override public String label() { return "ld_"+mlabel(); }
@Override
protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p(".").p(_name); }
@Override
public Type compute() {
- return _declaredType;
+ Type t = MemMergeNode.contents(mem(),_alias,this);
+ // No constant folding if the pointer still needs a null check.
+ return err()==null ? _declaredType.join(t) : _declaredType;
}
@Override
@@ -57,15 +63,57 @@ public class LoadNode extends MemOpNode {
setDef(1,IterPeeps.add(merge.alias(_alias)));
return this;
}
-
+ Node ptr = ptr();
// Simple Load-after-Store on same address.
if( mem() instanceof StoreNode st &&
- ptr() == st.ptr() && _alias==st._alias ) { // Must check same object
+ _alias==st._alias && ptr == st.ptr() && off() == st.off() ) { // Must check same object
assert _name.equals(st._name); // Equiv class aliasing is perfect
return st.val();
}
+ // Simple Load-after-New on same address.
+ if( mem() instanceof ProjNode p && p.in(0) instanceof NewNode nnn &&
+ ptr == nnn.proj(0) ) // Must check same object
+ return new ConstantNode(_declaredType.makeInit());
+
+ // Load-after-Store on same address, but bypassing provably unrelated
+ // stores. This is a more complex superset of the above two peeps.
+ // "Provably unrelated" is really weak.
+ Node mem = mem();
+ outer:
+ while( true ) {
+ mem.addDep(this);
+ switch( mem ) {
+ case MemMergeNode merge:
+ mem = merge.alias(_alias);
+ break;
+ case StoreNode st:
+ if( _alias==st._alias && ptr == st.ptr() && off() == st.off() )
+ return st.val(); // Proved equal
+ // Can we prove unequal? Offsets do not overlap?
+ if( _alias==st._alias && !off()._type.join(st.off()._type).isHigh() && // Offsets overlap
+ !neverAlias(ptr,st.ptr()) ) // And might alias
+ break outer; // Cannot tell, stop trying
+ // Pointers cannot overlap
+ mem = st.mem(); // Proved never equal
+ break;
+ case PhiNode phi:
+ break outer; // Assume related
+ case ProjNode mproj:
+ if( mproj.in(0) instanceof NewNode nnn1 ) {
+ if( ptr instanceof ProjNode pproj && pproj.in(0) == mproj.in(0) )
+ return new ConstantNode(_declaredType.makeInit()); // Load from a New
+ if( !(ptr instanceof ProjNode pproj && pproj.in(0) instanceof NewNode nnn2) )
+ break outer; // Cannot tell, ptr not related to New
+ mem = nnn1.mem();// Bypass unrelated New
+ break;
+ } else break outer;
+ default:
+ break outer;
+ }
+ }
+
// Push a Load up through a Phi, as long as it collapses on at least
// one arm. If at a Loop, the backedge MUST collapse - else we risk
// spinning the same transform around the loop indefinitely.
@@ -73,31 +121,66 @@ 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 MemPhiNode memphi && memphi.region()._type == Type.CONTROL && memphi.nIns()== 3 &&
+ off() instanceof ConstantNode ) {
// Profit on RHS/Loop backedge
if( profit(memphi,2) ||
// Else must not be a loop to count profit on LHS.
(!(memphi.region() instanceof LoopNode) && profit(memphi,1)) ) {
- if( ptr() instanceof NewNode || !(ptr() instanceof CastNode) ) {
- if( !(ptr() instanceof NewNode) ) throw Utils.TODO(); // Validate
-
- Node ld1 = new LoadNode(_name,_alias,_declaredType,memphi.in(1),ptr()).peephole();
- Node ld2 = new LoadNode(_name,_alias,_declaredType,memphi.in(2),ptr()).peephole();
- return new PhiNode(_name,_type,memphi.region(),ld1,ld2);
- }
+ Node ld1 = ld(1);
+ Node ld2 = ld(2);
+ return new PhiNode(_name,_declaredType,memphi.region(),ld1,ld2);
}
}
return null;
}
+ private Node ld( int idx ) {
+ Node mem = mem(), ptr = ptr();
+ LoadNode ld = new LoadNode(_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr,off());
+ ld.setDef(0,in(0));
+ return ld.peephole();
+ }
+ private static boolean neverAlias( Node ptr1, Node ptr2 ) {
+ return ptr1.in(0) != ptr2.in(0) &&
+ // Unrelated allocations
+ ptr1 instanceof ProjNode && ptr1.in(0) instanceof NewNode &&
+ ptr2 instanceof ProjNode && ptr2.in(0) instanceof NewNode;
+ }
+
// Profitable if we find a matching Store on this Phi arm.
private boolean profit(PhiNode phi, int idx) {
Node px = phi.in(idx);
- return px!=null && px.addDep(this) instanceof StoreNode st1 && ptr()==st1.ptr() && _alias==st1._alias;
+ if( px==null ) return false;
+ px.addDep(this);
+ if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() ) return true;
+ if( px instanceof StoreNode st1 && _alias==st1._alias && ptr()==st1.ptr() && off()==st1.off() ) return true;
+ return false;
+ }
+
+ // Memory writers which must follow this read. A packaging node is not a
+ // write, but can hide New's input, so follow this alias through aggregates.
+ public ArrayList<Node> antiDeps() {
+ ArrayList<Node> deps = new ArrayList<>();
+ antiDeps(mem(),deps,new BitSet());
+ return deps;
}
- Node copy(Node mem, Node ptr) {
- return new LoadNode(_name,_alias,_declaredType,mem,ptr);
+ private void antiDeps(Node mem, ArrayList<Node> deps, BitSet visit) {
+ if( visit.get(mem._nid) ) return;
+ visit.set(mem._nid);
+ for( Node use : mem._outputs ) {
+ if( use instanceof MemMergeNode merge ) {
+ Node slice = _alias<merge.nIns() ? merge.in(_alias) : null;
+ if( (slice==mem) || (slice==null && merge.in(1)==mem) )
+ antiDeps(merge,deps,visit);
+ } else if( (use instanceof StoreNode st && st._alias==_alias) ||
+ (use instanceof NewNode nnn && nnn.field(_alias)!=null) ||
+ (use instanceof MemPhiNode phi && phi._alias==_alias) ||
+ (use instanceof BulkMemPhiNode phi && !phi.isSplit(_alias)) ) {
+ if( !deps.contains(use) ) deps.add(use);
+ }
+ }
}
}
diff --git a/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
index f3d5b01e..a4b105c9 100644
--- a/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
@@ -6,7 +6,9 @@ import com.seaofnodes.simple.IterPeeps;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeMem;
-/** All memory: a default slice and explicit overrides for individual aliases. */
+/** A default slice and explicit alias overrides. A null default means partial
+ * memory: absent aliases are not covered, rather than having unknown contents.
+ */
public class MemMergeNode extends Node {
public MemMergeNode(Node bulk) { super(null,bulk); _type = TypeMem.BOT; }
public MemMergeNode(Node bulk, int alias, Node precise) {
@@ -17,7 +19,9 @@ public class MemMergeNode extends Node {
public Node alias(int alias) {
Node n = alias < nIns() ? in(alias) : null;
assert n!=null || !(in(1) instanceof BulkMemPhiNode bulk) || !bulk.isSplit(alias);
- return n==null ? in(1) : n;
+ n = n==null ? in(1) : n;
+ assert n!=null : "Alias not covered: "+alias;
+ return n;
}
public void alias(int alias, Node mem) {
@@ -26,6 +30,26 @@ public class MemMergeNode extends Node {
setDef(alias,mem);
}
+ // Scalar contents of one alias. New's struct supplies the initialized type;
+ // join it with the incoming contents for all previously allocated objects.
+ // Phis use their cached types, so this query does not recurse around loops.
+ static Type contents(Node mem, int alias, Node dep) {
+ mem.addDep(dep);
+ if( mem instanceof MemMergeNode merge )
+ return contents(merge.alias(alias),alias,dep);
+ if( mem instanceof ProjNode proj && proj.in(0) instanceof NewNode nnn ) {
+ assert proj._idx==1 && nnn.field(alias)!=null;
+ nnn.addDep(dep);
+ return contents(nnn.mem(),alias,dep).meet(nnn.field(alias)._type.makeInit());
+ }
+ // There are no heap arguments in this chapter. Before the first New,
+ // the set of allocated objects (and hence stored values) is empty.
+ if( mem instanceof ProjNode proj && proj.in(0) instanceof StartNode ) return Type.TOP;
+ if( mem._type==Type.TOP || mem._type==TypeMem.TOP ) return Type.TOP;
+ if( mem._type instanceof TypeMem mt && mt._alias==alias ) return mt._t;
+ return Type.BOTTOM;
+ }
+
@Override public String label() { return "MemMerge"; }
@Override public boolean isMem() { return true; }
@Override public Type compute() { return TypeMem.BOT; }
@@ -42,10 +66,14 @@ public class MemMergeNode extends Node {
@Override public Node idealize() {
boolean progress=false, allDefault=true;
for( int i=2; i<nIns(); i++ ) {
- if( in(i)==in(1) ) { setDef(i,null); progress=true; }
+ if( in(i)!=null && in(i)==in(1) ) { setDef(i,null); progress=true; }
+ if( in(i) instanceof MemMergeNode mem ) {
+ setDef(i,IterPeeps.add(mem.alias(i)));
+ progress=true;
+ }
if( in(i)!=null ) allDefault=false;
}
- if( allDefault ) return in(1);
+ if( allDefault && in(1)!=null ) return in(1);
if( in(1) instanceof MemMergeNode mem ) {
for( int i=2; i<mem.nIns(); i++ )
diff --git a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
index 29d302cb..9c222ad2 100644
--- a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
@@ -13,16 +13,24 @@ public abstract class MemOpNode extends Node {
public final int _alias;
public final Type _declaredType;
-
- public MemOpNode(String name, int alias, Type glb, Node... nodes) {
- super(nodes);
+ public MemOpNode(String name, int alias, Type glb, Node mem, Node ptr, Node off) {
+ super(null, mem, ptr, off);
_name = name;
_alias = alias;
_declaredType = glb;
}
+ public MemOpNode(String name, int alias, Type glb, Node mem, Node ptr, Node off, Node value) {
+ this(name, alias, glb, mem, ptr, off);
+ addDef(value);
+ }
+
+ //
+ static String mlabel(String name) { return name.equals("[]") ? "ary" : (name.equals("#") ? "len" : name); }
+ String mlabel() { return mlabel(_name); }
public Node mem() { return in(1); }
public Node ptr() { return in(2); }
+ public Node off() { return in(3); }
// Extra conditions for factoring matching memory operations through a Phi.
// The caller has already checked the opcode, input shape and control.
@@ -43,8 +51,11 @@ public abstract class MemOpNode extends Node {
@Override
String err() {
Type ptr = ptr()._type;
- return (ptr==Type.BOTTOM || (ptr instanceof TypeMemPtr tmp && tmp._nil) )
- ? "Might be null accessing '" + _name + "'"
- : null;
+ // Already an error, but better error messages come from elsewhere
+ if( ptr == Type.BOTTOM ) return null;
+ // Better be a not-nil TMP
+ if( ptr instanceof TypeMemPtr tmp && !tmp._nil )
+ return null;
+ return "Might be null accessing '" + _name + "'";
}
}
diff --git a/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
index c806bede..6aae00f9 100644
--- a/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
@@ -9,7 +9,7 @@ public class MemPhiNode extends PhiNode {
public final int _alias;
public MemPhiNode(String label, int alias, Node... inputs) {
- super(label,TypeMem.make(alias),inputs);
+ super(label,TypeMem.make(alias,Type.BOTTOM),inputs);
assert alias > 1;
_alias = alias;
}
@@ -18,8 +18,14 @@ public class MemPhiNode extends PhiNode {
@Override public Type compute() {
assert BulkMemPhiNode.checkMem(this,_alias);
- Type t = super.compute();
- return t==Type.TOP || t==TypeMem.TOP ? TypeMem.TOP : TypeMem.make(_alias);
+ if( !(region() instanceof RegionNode r) )
+ return region()._type==Type.XCONTROL ? TypeMem.TOP : _type;
+ if( r.inProgress() ) return _declaredType;
+ Type t = Type.TOP;
+ for( int i=1; i<nIns(); i++ )
+ if( r.in(i).addDep(this)._type!=Type.XCONTROL )
+ t = t.meet(MemMergeNode.contents(in(i),_alias,this));
+ return TypeMem.make(_alias,t);
}
@Override public Node idealize() {
diff --git a/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/src/main/java/com/seaofnodes/simple/node/MinusNode.java
index f56c423e..bd8f71ad 100644
--- a/src/main/java/com/seaofnodes/simple/node/MinusNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/MinusNode.java
@@ -16,9 +16,7 @@ public class MinusNode extends Node {
public Type compute() {
if( in(1)._type instanceof TypeInteger i0 ) {
if( i0.isHigh() ) return TypeInteger.TOP;
- // Catch overflow wrapping on `-Long.MIN_VALUE`
- if( i0 == TypeInteger.BOT || i0._min == Long.MIN_VALUE ) return TypeInteger.BOT;
- return TypeInteger.make(-i0._max,-i0._min);
+ return i0==TypeInteger.BOT ? TypeInteger.BOT : TypeInteger.make(-i0._max,-i0._min);
}
return in(1)._type.isHigh() ? TypeInteger.TOP : TypeInteger.BOT;
}
diff --git a/src/main/java/com/seaofnodes/simple/node/NewNode.java b/src/main/java/com/seaofnodes/simple/node/NewNode.java
index 74ba5184..f92d3cdf 100644
--- a/src/main/java/com/seaofnodes/simple/node/NewNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/NewNode.java
@@ -2,29 +2,44 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.print.ExprPrinter;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeMemPtr;
+import com.seaofnodes.simple.type.*;
+/** Allocate and zero an object. Inputs {ctrl, $mem, size}; results {ptr, $mem}.
+ * The memory input and result cover only the aliases in the allocated struct.
+ */
+public class NewNode extends Node implements MultiNode {
-public class NewNode extends Node {
+ public final TypeMemPtr _ptr;
- TypeMemPtr _ptr;
+ public NewNode(TypeMemPtr ptr, Node ctrl, Node mem, Node size) {
+ super(ctrl,mem,size);
+ assert ctrl._type==Type.CONTROL || ctrl._type==Type.XCONTROL;
+ assert mem._type instanceof TypeMem;
+ assert size._type instanceof TypeInteger;
+ _ptr = ptr;
+ }
+
+ public Node mem() { return in(1); }
+ public Node size() { return in(2); }
- public NewNode(TypeMemPtr ptr, Node ctrl) {
- super(ctrl);
- this._ptr = ptr;
+ public Field field(int alias) {
+ for( Field f : _ptr._obj._fields )
+ if( f._alias==alias ) return f;
+ return null;
}
- @Override
- public String label() { return "new"; }
+ @Override public String label() {
+ return "new_" + (_ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._type.str() : _ptr._obj.str());
+ }
@Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
return p.p("new ").p(_ptr._obj.str());
}
-
@Override
- public Type compute() { return _ptr; }
+ public TypeTuple compute() {
+ return TypeTuple.make(_ptr,TypeMem.BOT);
+ }
@Override
public Node idealize() { return null; }
diff --git a/src/main/java/com/seaofnodes/simple/node/Node.java b/src/main/java/com/seaofnodes/simple/node/Node.java
index 4ee82187..541c8eb1 100644
--- a/src/main/java/com/seaofnodes/simple/node/Node.java
+++ b/src/main/java/com/seaofnodes/simple/node/Node.java
@@ -92,7 +92,7 @@ public abstract class Node extends BaseNode<Node> implements OutNode, Cloneable
public boolean isUnused() { return nOuts() == 0; }
- public CFGNode cfg0() { return (CFGNode)in(0); }
+ public CFGNode cfg0() { return (CFGNode)in(0); }
/**
* Change a <em>def</em> into a Node. Keeps the edges correct, by removing
@@ -136,11 +136,10 @@ public abstract class Node extends BaseNode<Node> implements OutNode, Cloneable
Node delDef(int idx) {
unlock();
Node old_def = in(idx);
- if( old_def != null && // If the old def exists, remove a def->use edge
- old_def.delUse(this) ) // If we removed the last use, the old def is now dead
+ Utils.del(_inputs, idx);
+ if( old_def.delUse(this) ) // If we removed the last use, the old def is now dead
old_def.kill(); // Kill old def
old_def.moveDepsToWorklist();
- Utils.del(_inputs, idx);
return this;
}
@@ -489,8 +488,6 @@ public abstract class Node extends BaseNode<Node> implements OutNode, Cloneable
// ------------------------------------------------------------------------
//
- /** Is this Node control-flow-graph related */
-
/** Is this Node Memory related */
public boolean isMem() { return false; }
@@ -593,12 +590,7 @@ public abstract class Node extends BaseNode<Node> implements OutNode, Cloneable
E x = pred.apply(this);
if( x != null ) return x;
for( Node def : _inputs ) if( def != null && (x = def._walk(pred)) != null ) return x;
- // Unroll iterator to survive junk CME
- for( int i=0; i<_outputs.size(); i++ ) {
- Node use = _outputs.get(i);
- if( use != null && (x = use._walk(pred)) != null )
- return x;
- }
+ for( Node use : _outputs ) if( use != null && (x = use._walk(pred)) != null ) return x;
return null;
}
diff --git a/src/main/java/com/seaofnodes/simple/node/OrNode.java b/src/main/java/com/seaofnodes/simple/node/OrNode.java
index 4088fde6..5badd015 100644
--- a/src/main/java/com/seaofnodes/simple/node/OrNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/OrNode.java
@@ -14,7 +14,6 @@ public class OrNode extends Node {
@Override
public Type compute() {
- if( in(1)._type.isHigh() || in(2)._type.isHigh() ) return TypeInteger.TOP;
if( in(1)._type instanceof TypeInteger i0 &&
in(2)._type instanceof TypeInteger i1 ) {
if( i0.isConstant() && i1.isConstant() )
diff --git a/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/src/main/java/com/seaofnodes/simple/node/PhiNode.java
index 309cd8b5..ea5eeb89 100644
--- a/src/main/java/com/seaofnodes/simple/node/PhiNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/PhiNode.java
@@ -12,7 +12,7 @@ public class PhiNode extends Node {
final String _label;
// The Phi type we compute must stay within the domain of the Phi.
- // Example Int stays Int, Ptr stays Ptr, Mem stays Mem.
+ // Example Int stays Int, Ptr stays Ptr, Control stays Control, Mem stays Mem.
final Type _declaredType;
public PhiNode(String label, Type declaredType, Node... inputs) { super(inputs); _label = label; assert declaredType!=null; _declaredType = declaredType; }
@@ -21,7 +21,7 @@ public class PhiNode extends Node {
return type instanceof TypeMem ? new BulkMemPhiNode(label,inputs) : new PhiNode(label,type,inputs);
}
- @Override public String label() { return "Phi_"+_label; }
+ @Override public String label() { return "Phi_"+MemOpNode.mlabel(_label); }
@Override
protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
@@ -82,9 +82,9 @@ public class PhiNode extends Node {
if( in(2)._type == in(2)._type.makeInit() ) nullx = 2;
if( nullx != -1 ) {
Node val = in(3-nullx);
- if( region().idom(this) instanceof IfNode iff && iff.pred().addDep(this)==val ) {
+ if( r.idom(this) instanceof IfNode iff && iff.pred().addDep(this)==val ) {
// Must walk the idom on the null side to make sure we hit False.
- CFGNode idom = (CFGNode)region().in(nullx);
+ CFGNode idom = (CFGNode)r.in(nullx);
while( idom.nIns() > 0 && idom.in(0) != iff ) idom = idom.idom();
if( idom instanceof CProjNode proj && proj._idx==1 )
return val;
@@ -97,7 +97,7 @@ public class PhiNode extends Node {
private boolean same_op() {
Node op = in(1);
- if( op.isCFG() || op instanceof ConstantNode || op instanceof PhiNode ||
+ if( op instanceof CFGNode || op instanceof ConstantNode || op instanceof PhiNode ||
op instanceof ProjNode || op instanceof NewNode || op instanceof ScopeNode || op instanceof MemMergeNode ) return false;
// A bulk Phi must split its aliases before factoring precise Stores.
if( this instanceof BulkMemPhiNode ) return false;
diff --git a/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/src/main/java/com/seaofnodes/simple/node/ProjNode.java
index e82c69b3..668e20ad 100644
--- a/src/main/java/com/seaofnodes/simple/node/ProjNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ProjNode.java
@@ -21,12 +21,13 @@ public class ProjNode extends Node {
_label = label;
}
- @Override
- public String label() { return _label; }
+ @Override public String label() { return _label; }
@Override
protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p(_label); }
+ @Override public CFGNode cfg0() { return in(0).cfg0(); }
+
@Override public boolean isMultiTail() { return in(0).isMultiHead(); }
@Override public boolean isMem() { return _type instanceof TypeMem; }
diff --git a/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/src/main/java/com/seaofnodes/simple/node/RegionNode.java
index cdfd10ad..d4f73ddf 100644
--- a/src/main/java/com/seaofnodes/simple/node/RegionNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/RegionNode.java
@@ -67,15 +67,70 @@ public class RegionNode extends CFGNode {
// If a CFG diamond with no merging, delete: "if( pred ) {} else {};"
if( !hasPhi() && // No Phi users, just a control user
- in(1) instanceof ProjNode p1 &&
- in(2) instanceof ProjNode p2 &&
+ in(1) instanceof CProjNode p1 &&
+ in(2) instanceof CProjNode p2 &&
p1.addDepForwards(this).in(0)==p2.addDepForwards(this).in(0) &&
p1.in(0) instanceof IfNode iff )
return iff.ctrl();
+
+ // Look for direct uses of a guarded expression, and replace with the guard result.
+ if( !(idom() instanceof StartNode) && // Dead or dying
+ !(this instanceof LoopNode) ) {
+ VISIT.clear();
+ boolean progress = false;
+ outer:
+ for( int i=1; i<nIns(); i++ ) {
+ CFGNode idom = cfg(i);
+ while( !(idom instanceof CProjNode cproj) ) {
+ idom = idom.idom();
+ if( idom==null || idom.idepth() < idom().idepth() )
+ continue outer;
+ }
+ if( cproj.cfg0() instanceof IfNode iff && cproj.compute()==Type.CONTROL ) {
+ VISIT.set(iff.pred()._nid);
+ for( Node out : _outputs )
+ if( out instanceof PhiNode phi )
+ progress |= walk(iff,cproj,phi.in(i));
+ }
+ }
+ if( progress )
+ return this;
+ }
+
return null;
}
+ // A walk that can be cutoff early
+ private static final BitSet VISIT = new BitSet();
+ private static boolean walk( IfNode iff, CProjNode cproj, Node n ) {
+ if( VISIT.get(n._nid) ) return false;
+ VISIT.set(n._nid);
+ if( n instanceof CFGNode ) return false;
+ if( n.in(0)!=null && n.cfg0().idepth() < iff.idepth() )
+ return false;
+ boolean progress = false;
+ Node pred = iff.pred();
+ for( int i=1; i<n.nIns(); i++ ) {
+ // Using a tested value
+ if( n.in(i) == pred && !(n instanceof CastNode) ) {
+ // Checking pred for T/F
+ Type t = pred._type;
+ Type tcast = cproj._idx==0 ? t.nonZero() : t.makeInit();
+ if( t!=tcast ) { // Progress to cast
+ Node cast = new CastNode( tcast, cproj, pred ).peephole();
+ n.setDef( i, cast );
+ IterPeeps.add(n);
+ progress = true;
+ }
+ }
+ if( n.in(i)!=null )
+ progress |= walk(iff,cproj,n.in(i));
+ }
+ return progress;
+ }
+
+
private int findDeadInput() {
for( int i=1; i<nIns(); i++ )
if( in(i)._type==Type.XCONTROL )
diff --git a/src/main/java/com/seaofnodes/simple/node/SarNode.java b/src/main/java/com/seaofnodes/simple/node/SarNode.java
index 31d96ada..65151598 100644
--- a/src/main/java/com/seaofnodes/simple/node/SarNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/SarNode.java
@@ -14,14 +14,14 @@ public class SarNode extends Node {
@Override
public Type compute() {
- if( in(1)._type.isHigh() || in(2)._type.isHigh() ) return TypeInteger.TOP;
- if (in(1)._type instanceof TypeInteger i1 &&
- in(2)._type instanceof TypeInteger i2) {
- if( i1.isConstant() && i2.isConstant() )
- return TypeInteger.constant(i1.value()>>i2.value());
- if( i2._min < 0 || i2._max >= 64 )
- return TypeInteger.BOT;
- return TypeInteger.make(i1._min>>i2._min,i1._max>>i2._min);
+ if (in(1)._type instanceof TypeInteger i0 &&
+ in(2)._type instanceof TypeInteger i1) {
+ if( i0.isConstant() && i1.isConstant() )
+ return TypeInteger.constant(i0.value()>>i1.value());
+ if( i1.isConstant() ) {
+ int log = (int)i1.value();
+ return TypeInteger.make(-1L<<(63-log),(1L<<(63-log))-1);
+ }
}
return TypeInteger.BOT;
}
diff --git a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
index b408eb2f..36a5060b 100644
--- a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
@@ -3,10 +3,9 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.print.ExprPrinter;
import com.seaofnodes.simple.IterPeeps;
-import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.Utils;
-import com.seaofnodes.simple.type.TypeMemPtr;
-
+import com.seaofnodes.simple.type.*;
import java.util.*;
/**
@@ -23,24 +22,22 @@ public class ScopeNode extends Node {
public static final String ARG0 = "arg";
/**
- * Names for every input edge
+ * Map variable names to Scope input edge
*/
- public final Stack<HashMap<String, Integer>> _scopes;
+ public final Stack<HashMap<String, Integer>> _idxs;
/**
- * Tracks declared types for every name
+ * Map variable names to declared Type
*/
- public Stack<HashMap<String, Type>> _types;
-
+ final Stack<HashMap<String, Type>> _decls;
// A new ScopeNode
public ScopeNode() {
- _scopes = new Stack<>();
- _types = new Stack<>();
+ _idxs = new Stack<>();
+ _decls= new Stack<>();
_type = Type.BOTTOM;
}
-
@Override public String label() { return "Scope"; }
@Override
@@ -59,6 +56,20 @@ public class ScopeNode extends Node {
return p.unchar(' ').p("]");
}
+ public Node ctrl() { return in(0); }
+
+ /**
+ * The ctrl of a ScopeNode is always bound to the currently active
+ * control node in the graph, via a special name '$ctrl' that is not
+ * a valid identifier in the language grammar and hence cannot be
+ * referenced in Simple code.
+ *
+ * @param n The node to be bound to '$ctrl'
+ *
+ * @return Node that was bound
+ */
+ public Node ctrl(Node n) { return setDef(0,n); }
+
/**
* Recover the names for all variable bindings.
* The result is an array of names that is aligned with the
@@ -68,7 +79,7 @@ public class ScopeNode extends Node {
*/
public String[] reverseNames() {
String[] names = new String[nIns()];
- for( HashMap<String,Integer> syms : _scopes )
+ for( HashMap<String,Integer> syms : _idxs )
for( String name : syms.keySet() )
names[syms.get(name)] = name;
return names;
@@ -78,33 +89,34 @@ public class ScopeNode extends Node {
@Override public Node idealize() { return null; }
- public void push() { _scopes.push(new HashMap<>()); _types.push(new HashMap<>()); }
- public void pop() { popN(_scopes.pop().size()); _types.pop(); }
+ public void push() { _idxs.push(new HashMap<>()); _decls.push(new HashMap<>()); }
+ public void pop() { popN(_idxs.pop().size()); _decls.pop(); }
/**
- * Create a new name in the current scope
+ * Create a new variable name in the current scope
*/
public Node define( String name, Type declaredType, Node n ) {
- HashMap<String,Integer> syms = _scopes.lastElement();
- _types.lastElement().put(name,declaredType);
+ HashMap<String,Integer> syms = _idxs.lastElement();
+ _decls.lastElement().put(name,declaredType);
if( syms.put(name,nIns()) != null )
return null; // Double define
return addDef(n);
}
+
/**
* Lookup a name in all scopes starting from most deeply nested.
*
* @param name Name to be looked up
* @see #update(String, Node, int)
*/
- public Node lookup( String name ) { return update(name,null,_scopes.size()-1); }
+ public Node lookup( String name ) { return update(name,null,_idxs.size()-1); }
/**
* If the name is present in any scope, then redefine else null
*
* @param name Name being redefined
* @param n The node to bind to the name
*/
- public Node update( String name, Node n ) { return update(name,n,_scopes.size()-1); }
+ public Node update( String name, Node n ) { return update(name,n,_idxs.size()-1); }
/**
* Both recursive lookup and update.
* <p>
@@ -118,7 +130,7 @@ public class ScopeNode extends Node {
*/
private Node update( String name, Node n, int nestingLevel ) {
if( nestingLevel<0 ) return null; // Missed in all scopes, not found
- var syms = _scopes.get(nestingLevel); // Get the symbol table for nesting level
+ var syms = _idxs.get(nestingLevel); // Get the symbol table for nesting level
var idx = syms.get(name);
if( idx == null ) return update(name,n,nestingLevel-1); // Not found in this scope, recursively look in parent scope
Node old = in(idx);
@@ -136,29 +148,15 @@ public class ScopeNode extends Node {
return n==null ? old : setDef(idx,n); // Not lazy, so this is the answer
}
- // Return declared type
+ // Return declared type for a variable
public Type lookupDeclaredType( String name ) {
- for( int i=_types.size(); i>0; i-- ) {
- Type t = _types.get(i-1).get(name);
+ for( int i=_decls.size(); i>0; i-- ) {
+ Type t = _decls.get(i-1).get(name);
if( t != null ) return t;
}
return null;
}
- public Node ctrl() { return in(0); }
-
- /**
- * The ctrl of a ScopeNode is always bound to the currently active
- * control node in the graph, via a special name '$ctrl' that is not
- * a valid identifier in the language grammar and hence cannot be
- * referenced in Simple code.
- *
- * @param n The node to be bound to '$ctrl'
- *
- * @return Node that was bound
- */
- public Node ctrl(Node n) { return setDef(0,n); }
-
/**
* Duplicate a ScopeNode; including all levels, up to Nodes. So this is
* neither shallow (would dup the Scope but not the internal HashMap
@@ -166,7 +164,7 @@ public class ScopeNode extends Node {
* also the program Nodes).
* <p>
* If the {@code loop} flag is set, the edges are filled in as the original
- * Scope, as a indication of Lazy Phis at loop heads. The goal here is to
+ * Scope, as an indication of Lazy Phis at loop heads. The goal here is to
* not make Phis at loop heads for variables which are never touched in the
* loop body.
* <p>
@@ -179,10 +177,10 @@ public class ScopeNode extends Node {
// 1) duplicate the name bindings of the ScopeNode across all stack levels
// 2) Make the new ScopeNode a user of all the nodes bound
// 3) Ensure that the order of defs is the same to allow easy merging
- for( HashMap<String,Integer> syms : _scopes )
- dup._scopes.push(new HashMap<>(syms));
- for( HashMap<String,Type> ts : _types )
- dup._types.push(ts); // Types don't change, just keep stacks aligned
+ for( HashMap<String,Integer> syms : _idxs )
+ dup._idxs.push(new HashMap<>(syms));
+ for( HashMap<String,Type> ts : _decls )
+ dup._decls.push(ts); // Types don't change, just keep stacks aligned
dup.addDef(ctrl()); // Control input is just copied
for( int i=1; i<nIns(); i++ )
diff --git a/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/src/main/java/com/seaofnodes/simple/node/ShlNode.java
index 98331d08..278c8390 100644
--- a/src/main/java/com/seaofnodes/simple/node/ShlNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ShlNode.java
@@ -1,10 +1,10 @@
package com.seaofnodes.simple.node;
+import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeInteger;
-
public class ShlNode extends Node {
public ShlNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
@@ -14,7 +14,6 @@ public class ShlNode extends Node {
@Override
public Type compute() {
- if( in(1)._type.isHigh() || in(2)._type.isHigh() ) return TypeInteger.TOP;
if (in(1)._type instanceof TypeInteger i0 &&
in(2)._type instanceof TypeInteger i1) {
if( i0.isConstant() && i1.isConstant() )
@@ -27,11 +26,18 @@ public class ShlNode extends Node {
public Node idealize() {
Node lhs = in(1);
Node rhs = in(2);
- Type t2 = rhs._type;
- // Shl of 0.
- if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 )
- return lhs;
+ if( rhs._type instanceof TypeInteger shl && shl.isConstant() ) {
+ // Shl of 0.
+ if( (shl.value()&63)==0 )
+ return lhs;
+ // (x + c) << i => (x << i) + (c << i)
+ if( lhs instanceof AddNode add && add.in(2)._type instanceof TypeInteger c && c.isConstant() ) {
+ long sum = c.value() << shl.value();
+ if( Integer.MIN_VALUE <= sum && sum <= Integer.MAX_VALUE )
+ return new AddNode( new ShlNode(add.in(1),rhs).peephole(), Parser.con(sum) );
+ }
+ }
// TODO: x << 3 << (y ? 1 : 2) ==> x << (y ? 4 : 5)
diff --git a/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/src/main/java/com/seaofnodes/simple/node/ShrNode.java
index 97ffc7cd..70fd630d 100644
--- a/src/main/java/com/seaofnodes/simple/node/ShrNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ShrNode.java
@@ -14,19 +14,10 @@ public class ShrNode extends Node {
@Override
public Type compute() {
- if( in(1)._type.isHigh() || in(2)._type.isHigh() ) return TypeInteger.TOP;
- if (in(1)._type instanceof TypeInteger i1 &&
- in(2)._type instanceof TypeInteger i2) {
- if( i1.isConstant() && i2.isConstant() )
- return TypeInteger.constant(i1.value()>>>i2.value());
- if( i2._min < 0 || i2._max >= 64 )
- return TypeInteger.BOT;
- // Zero shifting a negative makes a larger positive
- // so get the endpoints correct.
- long s1 = i1._min>>>i2._min;
- long s2 = i1._max>>>i2._min;
- boolean wrap = i1._min < 0 && i1._max >=0;
- return TypeInteger.make(wrap ? 0 : Math.min(s1,s2),Math.max(s1,s2));
+ if (in(1)._type instanceof TypeInteger i0 &&
+ in(2)._type instanceof TypeInteger i1) {
+ if( i0.isConstant() && i1.isConstant() )
+ return TypeInteger.constant(i0.value()>>>i1.value());
}
return TypeInteger.BOT;
}
diff --git a/src/main/java/com/seaofnodes/simple/node/StartNode.java b/src/main/java/com/seaofnodes/simple/node/StartNode.java
index a5d4923f..812305d5 100644
--- a/src/main/java/com/seaofnodes/simple/node/StartNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/StartNode.java
@@ -16,8 +16,7 @@ public class StartNode extends CFGNode implements MultiNode {
_type = _args = TypeTuple.make(args);
}
- @Override
- public String label() { return "Start"; }
+ @Override public String label() { return "Start"; }
@Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
return p.p(label());
@@ -25,6 +24,7 @@ public class StartNode extends CFGNode implements MultiNode {
@Override public boolean isMultiHead() { return true; }
@Override public boolean blockHead() { return true; }
+ @Override public CFGNode cfg0() { return this; }
@Override
public TypeTuple compute() { return _args; }
diff --git a/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/src/main/java/com/seaofnodes/simple/node/StoreNode.java
index e9eaaf2e..485491c0 100644
--- a/src/main/java/com/seaofnodes/simple/node/StoreNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/StoreNode.java
@@ -16,24 +16,26 @@ public class StoreNode extends MemOpNode {
private final boolean _init; // Initializing writes are allowed to write null
/**
- * @param name The struct field we are assigning to
- * @param memSlice The memory alias node - this is updated after a Store
- * @param memPtr The ptr to the struct where we will store a value
+ * @param name The struct field we are assigning to
+ * @param mem The memory alias node - this is updated after a Store
+ * @param ptr The ptr to the struct base where we will store a value
+ * @param off The offset inside the struct base
* @param value Value to be stored
*/
- public StoreNode(String name, int alias, Type glb, Node ctrl, Node memSlice, Node memPtr, Node value, boolean init) {
- super(name, alias, glb, ctrl, memSlice, memPtr, value);
+ public StoreNode(String name, int alias, Type glb, Node mem, Node ptr, Node off, Node value, boolean init) {
+ super(name, alias, glb, mem, ptr, off, value);
_init = init;
}
+ // Debugger label
@Override boolean canDrop(MemOpNode other, Node dep) {
return super.canDrop(other,dep) && _init==((StoreNode)other)._init;
}
- @Override public String label() { return "." +_name+"="; }
+ @Override public String label() { return "st_"+mlabel(); }
@Override public boolean isMem() { return true; }
- public Node val() { return in(3); }
+ public Node val() { return in(4); }
@Override
protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
@@ -41,7 +43,10 @@ public class StoreNode extends MemOpNode {
}
@Override
- public Type compute() { return TypeMem.make(_alias); }
+ public Type compute() {
+ Type t = val()._type.meet(MemMergeNode.contents(mem(),_alias,this));
+ return TypeMem.make(_alias,t);
+ }
@Override
public Node idealize() {
@@ -55,6 +60,7 @@ public class StoreNode extends MemOpNode {
// required init-store being stomped by a first user store.
if( mem() instanceof StoreNode st &&
ptr()==st.ptr() && _alias==st._alias && // Must check same object
+ off()==st.off() && // And same offset
ptr()._type instanceof TypeMemPtr && // No bother if weird dead pointers
// Must have exactly one use of "this" or you get weird
// non-serializable memory effects in the worse case.
@@ -82,7 +88,7 @@ public class StoreNode extends MemOpNode {
String err() {
String err = super.err();
if( err != null ) return err;
- Type ptr = val()._type;
- return _init || ptr.isa(_declaredType) ? null : "Cannot store "+ptr+" into field "+_declaredType+" "+_name;
+ Type t = val()._type;
+ return _init || t.isa(_declaredType) ? null : "Cannot store "+t+" into field "+_declaredType+" "+_name;
}
}
diff --git a/src/main/java/com/seaofnodes/simple/node/SubNode.java b/src/main/java/com/seaofnodes/simple/node/SubNode.java
index d5e54e73..134f7cd6 100644
--- a/src/main/java/com/seaofnodes/simple/node/SubNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -15,10 +15,6 @@ public class SubNode extends Node {
@Override
public Type compute() {
- // Sub of same is 0
- if( in(1)==in(2) )
- return TypeInteger.ZERO;
-
Type t1 = in(1)._type, t2 = in(2)._type;
if( t1.isHigh() || t2.isHigh() )
return TypeInteger.TOP;
@@ -27,6 +23,9 @@ public class SubNode extends Node {
if (i1.isConstant() && i2.isConstant())
return TypeInteger.constant(i1.value()-i2.value());
}
+ // Sub of same is 0
+ if( in(1)==in(2) )
+ return TypeInteger.ZERO;
return TypeInteger.BOT;
}
diff --git a/src/main/java/com/seaofnodes/simple/node/XorNode.java b/src/main/java/com/seaofnodes/simple/node/XorNode.java
index 7490032c..0521cc52 100644
--- a/src/main/java/com/seaofnodes/simple/node/XorNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/XorNode.java
@@ -14,7 +14,6 @@ public class XorNode extends Node {
@Override
public Type compute() {
- if( in(1)._type.isHigh() || in(2)._type.isHigh() ) return TypeInteger.TOP;
if( in(1)._type instanceof TypeInteger i0 &&
in(2)._type instanceof TypeInteger i1 ) {
if( i0.isConstant() && i1.isConstant() )
diff --git a/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java
index 26897a7c..e2bf366d 100644
--- a/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java
+++ b/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java
@@ -6,7 +6,7 @@ import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.node.Node;
import com.seaofnodes.simple.type.TypeMem;
-/** Chapter 14's view of the IR; browser, transport and layout are shared. */
+/** Chapter 15's view of the IR; browser, transport and layout are shared. */
public class SimpleGraphAdapter extends GraphAdapter<Node> {
@Override protected String[] edgeNames(Node n) {
@@ -21,16 +21,17 @@ public class SimpleGraphAdapter extends GraphAdapter<Node> {
if( n instanceof LoopNode ) return Kind.LOOP;
if( n instanceof RegionNode ) return Kind.REGION;
if( n instanceof PhiNode ) return Kind.PHI;
- return n.isCFG() ? Kind.CTRL : isMem(n) ? Kind.MEM : Kind.DATA;
+ return n instanceof CFGNode ? Kind.CTRL : isMem(n) ? Kind.MEM : Kind.DATA;
}
@Override protected Role role(Node n, int i) {
if( n instanceof ScopeNode || n instanceof ConstantNode || n instanceof XCtrlNode ) return Role.ASSOC;
if( n instanceof PhiNode ) return i == 0 ? Role.ASSOC : isMem(n) ? Role.MEM : Role.DATA;
- if( n instanceof ProjNode || n instanceof CProjNode ) return n.isCFG() ? Role.CTRL : isMem(n) ? Role.MEM : Role.DATA;
+ if( n instanceof ProjNode || n instanceof CProjNode )
+ return n instanceof CFGNode ? Role.CTRL : isMem(n) ? Role.MEM : Role.DATA;
if( n instanceof RegionNode && i == 0 ) return Role.ASSOC;
if( i == 0 || n instanceof RegionNode || n instanceof StopNode ) return Role.CTRL;
- if( i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode) ) return Role.MEM;
+ if( i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode || n instanceof NewNode) ) return Role.MEM;
Node def = n.in(i);
if( def != null && isMem(def) ) return Role.MEM;
return Role.DATA;
diff --git a/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java
index 202aa9d9..450922d7 100644
--- a/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java
+++ b/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java
@@ -7,7 +7,7 @@ import com.seaofnodes.simple.node.Node;
import java.util.ArrayList;
import java.io.IOException;
-/** Chapter 14's compilation context; capture and event bookkeeping are shared. */
+/** Chapter 15's compilation context; capture and event bookkeeping are shared. */
public class SimpleGraphObserver extends GraphCapture<Node> {
private Parser _parser;
private Node _ret;
diff --git a/src/main/java/com/seaofnodes/simple/type/Field.java b/src/main/java/com/seaofnodes/simple/type/Field.java
index b2c006dd..5e674132 100644
--- a/src/main/java/com/seaofnodes/simple/type/Field.java
+++ b/src/main/java/com/seaofnodes/simple/type/Field.java
@@ -12,48 +12,52 @@ public class Field extends Type {
// Field name
public final String _fname;
+ // Unique memory alias
+ public final int _alias;
// Type of the field
public final Type _type;
- private Field(String fname, Type type ) {
+ private Field(String fname, int alias, Type type ) {
super(TFLD);
_fname = fname;
+ _alias = alias;
_type = type;
}
// Make with existing alias
- public static Field make( String fname, Type type ) {
- return new Field(fname,type).intern();
+ public static Field make( String fname, int alias, Type type ) {
+ return new Field(fname,alias,type).intern();
}
- public static final Field TEST = make("test",TypeInteger.ZERO);
+ public static final Field TEST = make("test",-2,TypeInteger.ZERO);
public static void gather(ArrayList<Type> ts) { ts.add(TEST); }
@Override Field xmeet( Type that ) {
Field fld = (Field)that; // Invariant
assert _fname.equals(fld._fname);
- return make(_fname,_type.meet(fld._type));
+ assert _alias==fld._alias;
+ return make(_fname,_alias,_type.meet(fld._type));
}
@Override
- public Field dual() { return make(_fname,_type.dual()); }
+ public Field dual() { return make(_fname,_alias,_type.dual()); }
@Override
public Field glb() {
- return make(_fname,_type.glb());
+ return make(_fname,_alias,_type.glb());
}
// Override in subclasses
- int hash() { return _fname.hashCode() ^ _type.hashCode(); }
+ int hash() { return _fname.hashCode() ^ _type.hashCode() ^ _alias; }
boolean eq(Type t) {
Field f = (Field)t;
- return _fname.equals(f._fname) && _type==f._type;
+ return _fname.equals(f._fname) && _alias==f._alias && _type==f._type;
}
@Override
public StringBuilder print( StringBuilder sb ) {
- return _type.print(sb.append(_fname).append(" :"));
+ return _type.print(sb.append(_fname).append(":").append(_alias).append(" : "));
}
@Override public String str() { return _fname; }
diff --git a/src/main/java/com/seaofnodes/simple/type/Type.java b/src/main/java/com/seaofnodes/simple/type/Type.java
index 49dc090a..989eff9e 100644
--- a/src/main/java/com/seaofnodes/simple/type/Type.java
+++ b/src/main/java/com/seaofnodes/simple/type/Type.java
@@ -39,6 +39,7 @@ public class Type {
static final byte TMEMPTR = 9; // Memory pointer type
static final byte TSTRUCT =10; // Structs; tuples with named fields
static final byte TFLD =11; // Fields into struct
+ static final byte TARRAY =12; // Array
public final byte _type;
@@ -81,6 +82,7 @@ public class Type {
public StringBuilder typeName( StringBuilder sb) { return print(sb); }
public Type makeInit() { return null; }
+ public Type nonZero() { return glb(); }
// ----------------------------------------------------------
@@ -178,6 +180,14 @@ public class Type {
public Type glb() { return _type==TCTRL ? XCONTROL : BOTTOM; }
// ----------------------------------------------------------
+
+ // Size in bits to hold an instance of this type.
+ // Sizes are expected to be between 1 and 64 bits.
+ // Size 0 means this either takes no space (such as a known-zero field)
+ // or isn't a scalar to be stored in memory.
+ public int log_size() { throw Utils.TODO(); }
+
+ // ----------------------------------------------------------
// Useful in the debugger, which calls toString everywhere.
// This is a more verbose dev-friendly print.
@Override
diff --git a/src/main/java/com/seaofnodes/simple/type/TypeFloat.java b/src/main/java/com/seaofnodes/simple/type/TypeFloat.java
index 975c1a9e..dc36c082 100644
--- a/src/main/java/com/seaofnodes/simple/type/TypeFloat.java
+++ b/src/main/java/com/seaofnodes/simple/type/TypeFloat.java
@@ -70,6 +70,11 @@ public class TypeFloat extends Type {
@Override public boolean isHighOrConst() { return _sz<=0; }
@Override public boolean isConstant() { return _sz==0; }
+ @Override public int log_size() {
+ int sz = _sz==0 ? (isF32() ? 32 : 64) : Math.abs(_sz);
+ return _sz==32 ? 2 : 3;
+ }
+
public double value() { return _con; }
@Override
@@ -111,4 +116,5 @@ public class TypeFloat extends Type {
TypeFloat i = (TypeFloat)t; // Contract
return _con==i._con && _sz==i._sz;
}
+
}
diff --git a/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/src/main/java/com/seaofnodes/simple/type/TypeInteger.java
index aca3e06b..042b4ba8 100644
--- a/src/main/java/com/seaofnodes/simple/type/TypeInteger.java
+++ b/src/main/java/com/seaofnodes/simple/type/TypeInteger.java
@@ -1,5 +1,6 @@
package com.seaofnodes.simple.type;
+import com.seaofnodes.simple.Utils;
import java.util.ArrayList;
/**
@@ -75,6 +76,14 @@ public class TypeInteger extends Type {
@Override public boolean isHighOrConst() { return _min >= _max; }
@Override public boolean isConstant () { return _min == _max; }
+ @Override public int log_size() {
+ if( this==I8 || this==U8 || this==BOOL ) return 0; // 1<<0 == 1 bytes
+ if( this==I16 || this==U16 ) return 1; // 1<<1 == 2 bytes
+ if( this==I32 || this==U32 ) return 2; // 1<<2 == 4 bytes
+ if( this==BOT ) return 3; // 1<<3 == 8 bytes
+ throw Utils.TODO();
+ }
+
public long value() { assert isConstant(); return _min; }
// AND-mask of forced zeros. e.g. unsigned types will return their mask;
@@ -104,8 +113,14 @@ public class TypeInteger extends Type {
}
@Override public TypeInteger dual() { return make(_max,_min); }
- @Override public Type glb() { return BOT; }
+ @Override public TypeInteger glb() { return BOT; }
@Override public TypeInteger makeInit() { return ZERO; }
+ @Override public TypeInteger nonZero() {
+ if( isHigh() ) return this;
+ if( _min==0 ) return make(1,_max); // specifically good on BOOL
+ if( _max==0 ) return make(_min,-1);
+ return this;
+ }
@Override int hash() { return (int)((_min ^ (_min>>32)) *(_max ^ (_max>>32))); }
@Override
public boolean eq( Type t ) {
diff --git a/src/main/java/com/seaofnodes/simple/type/TypeMem.java b/src/main/java/com/seaofnodes/simple/type/TypeMem.java
index 38b3635a..76288d3e 100644
--- a/src/main/java/com/seaofnodes/simple/type/TypeMem.java
+++ b/src/main/java/com/seaofnodes/simple/type/TypeMem.java
@@ -16,49 +16,55 @@ public class TypeMem extends Type {
// -1 means BOT, all memory.
// N means slice#N.
public final int _alias;
+ public final Type _t; // Memory contents, some scalar type
- private TypeMem(int alias) { super(TMEM); _alias = alias; }
+ private TypeMem(int alias, Type t) { super(TMEM); _alias = alias; _t = t; }
- public static TypeMem make(int alias) { return new TypeMem(alias).intern(); }
- public static final TypeMem TOP = make( 0);
- public static final TypeMem BOT = make(-1);
+ public static TypeMem make(int alias, Type t) { return new TypeMem(alias,t).intern(); }
+ public static final TypeMem TOP = make( 0, Type.TOP);
+ public static final TypeMem BOT = make(-1, Type.BOTTOM);
- public static void gather(ArrayList<Type> ts) { ts.add(make(1)); ts.add(BOT); }
+ public static void gather(ArrayList<Type> ts) { ts.add(make(1,TypeInteger.ZERO)); ts.add(BOT); }
@Override
TypeMem xmeet(Type t) {
TypeMem that = (TypeMem) t; // Invariant: TypeMem and unequal
- return _alias==0 ? that : (that._alias==0 ? this : BOT);
+ if( this==TOP ) return that;
+ if( that==TOP ) return this;
+ if( this==BOT ) return BOT;
+ if( that==BOT ) return BOT;
+ int alias = _alias==that._alias ? _alias : -1;
+ Type mt = _t.meet(that._t);
+ return make(alias,mt);
}
@Override
public Type dual() {
if( _alias== 0 ) return BOT;
if( _alias==-1 ) return TOP;
- return this;
+ return make(_alias,_t.dual());
}
@Override
- public Type glb() { return make(_alias); }
+ public Type glb() { return make(_alias,_t.glb()); }
@Override
- int hash() { return 9876543 + _alias; }
+ int hash() { return 9876543 + _alias + _t.hashCode(); }
@Override
boolean eq(Type t) {
TypeMem that = (TypeMem) t; // Invariant
- return _alias == that._alias;
+ return _alias == that._alias && _t == that._t;
}
@Override
public StringBuilder print(StringBuilder sb) {
if( this==BOT ) return sb.append("MemBot");
if( this==TOP ) return sb.append("MemTop");
- return sb.append("MEM#").append( switch(_alias) {
- case 0 -> "TOP";
- case -1 -> "BOT";
- default -> ""+_alias;
- });
+ sb.append("MEM#");
+ if( _alias== 0 ) return sb.append("TOP");
+ if( _alias==-1 ) return sb.append("BOT");
+ return _t.print(sb.append(_alias).append(":"));
}
@Override public String str() { return toString(); }
diff --git a/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
index e6e40fe2..ceba733d 100644
--- a/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
+++ b/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
@@ -6,8 +6,8 @@ import java.util.ArrayList;
* Represents a Pointer to memory.
*
* Null is generic pointer to non-existent memory.
- * *void is a non-Null pointer to all possible structs.
- * Pointers can be to specific struct types, or a union with Null.
+ * *void is a non-Null pointer to all possible refs, both structs and arrays.
+ * Pointers can be to specific struct and array types, or a union with Null.
* The distinguished *$BOT ptr represents union of *void and Null.
* The distinguished *$TOP ptr represents the dual of *$BOT.
*/
@@ -33,12 +33,16 @@ public class TypeMemPtr extends Type {
}
public static TypeMemPtr make(TypeStruct obj, boolean nil) { return new TypeMemPtr(obj, nil).intern(); }
public static TypeMemPtr make(TypeStruct obj) { return make(obj, false); }
- public TypeMemPtr make_from(TypeStruct obj) { return make(obj, _nil); }
+ public TypeMemPtr makeFrom(TypeStruct obj) { return make(obj, _nil); }
+ // An abstract pointer, pointing to either a Struct or an Array.
+ // Can also be null or not.
public static final TypeMemPtr BOT = make(TypeStruct.BOT,true);
public static final TypeMemPtr TOP = BOT.dual();
+ // An abstract null (can be Struct or Array) or not-null C void*
public static final TypeMemPtr NULLPTR = make(TypeStruct.TOP,true);
public static final TypeMemPtr VOIDPTR = NULLPTR.dual(); // A bottom mix of not-null ptrs, like C's void* but not null
+
public static final TypeMemPtr TEST= make(TypeStruct.TEST,false);
public static void gather(ArrayList<Type> ts) { ts.add(NULLPTR); ts.add(BOT); ts.add(TEST); }
@@ -52,11 +56,14 @@ public class TypeMemPtr extends Type {
public TypeMemPtr dual() { return TypeMemPtr.make(_obj.dual(), !_nil); }
@Override
- public Type glb() { return make(_obj.glb(),true); }
+ public TypeMemPtr glb() { return make(_obj.glb(),true); }
@Override public TypeMemPtr makeInit() { return NULLPTR; }
+ @Override public Type nonZero() { return VOIDPTR; }
@Override public boolean isHigh() { return this==TOP; }
+ @Override public int log_size() { return 2; } // (1<<2)==4-byte pointers
+
@Override
int hash() { return _obj.hashCode() ^ (_nil ? 1024 : 0); }
diff --git a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
index 8ad59a39..922b7914 100644
--- a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
+++ b/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
@@ -8,38 +8,53 @@ import java.util.ArrayList;
*/
public class TypeStruct extends Type {
- // Immutable shallow references, resolved by name in Parser.TYPES when needed.
- // A name-only struct has _fields == null, even after its definition is known.
- // For LLI { LLI? next; int i; }, L0 is name-only and L1.next points to L0;
- // TYPES["LLI"] holds L1. We deliberately stop recursive type expansion here,
- // keeping interning and lattice operations finite and acyclic. Never patch
- // L0 into L1: field access looks up the complete declaration by name instead.
+ // A Struct has a name and a set of fields; the fields themselves have
+ // names, types and aliases. The name has no semantic meaning, but is
+ // useful for debugging. Briefly during parsing a Struct can be a
+ // forward-ref; in this case the _fields array is null.
+
+ // Its illegal to attempt to load a field from a forward-ref struct.
+
+ // Example: "int rez = new S.x; struct S { int x; } return rez;" // Error, S not defined
+ // Rewrite: "struct S { int x; } int rez = new S.x; return rez;" // Ok, no forward ref
+ //
+ // During the normal optimization run, struct types "bottom out" at further
+ // struct references, so we don't have to handle e.g. cyclic types. The
+ // "bottom out" is again the forward-ref struct.
public final String _name;
public final Field[] _fields;
- private TypeStruct(String name, Field[] fields) {
- super(Type.TSTRUCT);
+ TypeStruct(String name, Field[] fields) {
+ super(TSTRUCT);
_name = name;
_fields = fields;
}
- // Forward-ref version
- public static TypeStruct make(String name) { return new TypeStruct(name, null).intern(); }
// All fields directly listed
- public static TypeStruct make(String name, Field[] fields) { return new TypeStruct(name, fields).intern(); }
- // Parser uses an ArrayList to gather the fields
- public static TypeStruct make(String name, ArrayList<Field> fields) { return make(name,fields.toArray(new Field[fields.size()])); }
-
- public static final TypeStruct TOP = make("$TOP",new Field [0]);
- public static final TypeStruct BOT = make("$BOT",new Field [0]);
+ public static TypeStruct make(String name, Field... fields) { return new TypeStruct(name, fields).intern(); }
+ public static final TypeStruct TOP = make("$TOP",new Field[0]);
+ public static final TypeStruct BOT = make("$BOT",new Field[0]);
public static final TypeStruct TEST = make("test",new Field[]{Field.TEST});
+ // Forward-ref version
+ public static TypeStruct make(String name) { return make(name, (Field[])null); }
+
+ // Array
+ public static TypeStruct makeAry(TypeInteger len, int lenAlias, Type body, int bodyAlias) {
+ assert !(body instanceof TypeMemPtr tmp && !tmp._nil);
+ return make(body.str() + "[]",
+ Field.make("#" , lenAlias,len ),
+ Field.make("[]",bodyAlias,body));
+ }
// A pair of self-cyclic types
private static final TypeStruct S1F = make("S1");
private static final TypeStruct S2F = make("S2");
- public static final TypeStruct S1 = make("S1", new Field[]{ Field.make("a", TypeInteger.BOT), Field.make("s2",TypeMemPtr.make(S2F,false)) });
- private static final TypeStruct S2 = make("S2", new Field[]{ Field.make("b", TypeFloat .BOT), Field.make("s1",TypeMemPtr.make(S1F,false)) });
- public static void gather(ArrayList<Type> ts) { ts.add(TEST); ts.add(BOT); ts.add(S1); ts.add(S2); }
+ public static final TypeStruct S1 = make("S1", new Field[]{ Field.make("a", -1, TypeInteger.BOT), Field.make("s2",-2,TypeMemPtr.make(S2F,false)) });
+ private static final TypeStruct S2 = make("S2", new Field[]{ Field.make("b", -3, TypeFloat .BOT), Field.make("s1",-4,TypeMemPtr.make(S1F,false)) });
+
+ private static final TypeStruct ARY = makeAry(TypeInteger.BOT,-1,TypeFloat.BOT,-2);
+
+ public static void gather(ArrayList<Type> ts) { ts.add(TEST); ts.add(BOT); ts.add(S1); ts.add(S2); ts.add(ARY); }
public int find(String fname) {
for( int i=0; i<_fields.length; i++ )
@@ -66,11 +81,15 @@ public class TypeStruct extends Type {
return BOT; // It's a struct; that's about all we know
if( this._fields==null ) return this;
if( that._fields==null ) return that;
- // Now all fields should be the same, so just do field meets
- assert _fields.length == that._fields.length;
+ if( _fields.length != that._fields.length ) return BOT;
+ // Just do field meets
Field[] flds = new Field[_fields.length];
- for( int i=0; i<_fields.length; i++ )
- flds[i] = (Field)_fields[i].meet(that._fields[i]);
+ for( int i=0; i<_fields.length; i++ ) {
+ Field f0 = _fields[i], f1 = that._fields[i];
+ if( !f0._fname.equals(f1._fname) || f0._alias != f1._alias )
+ return BOT;
+ flds[i] = (Field)f0.meet(f1);
+ }
return make(_name,flds);
}
@@ -109,6 +128,7 @@ public class TypeStruct extends Type {
if( !_name.equals(ts._name) )
return false;
if( _fields == ts._fields ) return true;
+ if( _fields==null || ts._fields==null ) return false;
if( _fields.length!=ts._fields.length )
return false;
for( int i = 0; i < _fields.length; i++ )
@@ -145,4 +165,52 @@ public class TypeStruct extends Type {
if( this==TOP ) return "StructTop";
return _name;
}
+
+
+ public boolean isAry() { return _fields.length==2 && _fields[1]._fname.equals("[]"); }
+
+ public int aryBase() {
+ assert isAry();
+ if( _offs==null ) _offs = offsets();
+ return _offs[1];
+ }
+ public int aryScale() {
+ assert isAry();
+ return _fields[1]._type.log_size();
+ }
+
+
+ // Field offsets as packed byte offsets
+ private int[] _offs; // Lazily computed and not part of the type semantics
+ public int offset(int idx) {
+ if( _offs==null ) _offs = offsets();
+ return _offs[idx];
+ }
+ private int[] offsets() { // Field byte offsets
+ // Compute a layout for a collection of fields
+ assert _fields != null; // No forward refs
+ // Compute a layout
+ int[] cnts = new int[4]; // Count of fields at log field size
+ for( Field f : _fields )
+ cnts[f._type.log_size()]++; // Log size is 0(byte), 1(i16/u16), 2(i32/f32), 3(i64/dbl)
+ int off = 0, idx = 0; // Base common struct fields go here, e.g. Mark/Klass
+ // Compute offsets to the start of each power-of-2 aligned fields.
+ int[] offs = new int[4];
+ for( int i=3; i>=0; i-- ) {
+ offs[i] = off;
+ off += cnts[i]<<i;
+ }
+ // Assign offsets to all fields.
+ // Really a hidden radix sort.
+ _offs = new int[_fields.length+1];
+ for( Field f : _fields ) {
+ int log = f._type.log_size();
+ _offs[idx++] = offs[log]; // Field offset
+ offs[log] += 1<<log; // Next field offset at same alignment
+ cnts[log]--; // Count down, should be all zero at end
+ }
+ _offs[_fields.length] = (off+7)& ~7; // Round out to max alignment
+ return _offs;
+ }
+
}
diff --git a/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/src/main/java/com/seaofnodes/simple/type/TypeTuple.java
index 783f3f60..59ddf34b 100644
--- a/src/main/java/com/seaofnodes/simple/type/TypeTuple.java
+++ b/src/main/java/com/seaofnodes/simple/type/TypeTuple.java
@@ -46,7 +46,14 @@ public class TypeTuple extends Type {
return sb;
}
- @Override public String str() { return print(new StringBuilder()).toString(); }
+ @Override public String str() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("[ ");
+ for( Type t : _types )
+ sb.append(t.str()).append(", ");
+ sb.setLength(sb.length()-2);
+ return sb.append("]").toString();
+ }
/**
* Display Type name in a format that's good for IR printer
diff --git a/src/test/java/com/seaofnodes/simple/BrainFuckTest.java b/src/test/java/com/seaofnodes/simple/BrainFuckTest.java
new file mode 100644
index 00000000..29ef8732
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/BrainFuckTest.java
@@ -0,0 +1,80 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.evaluator.Evaluator;
+import com.seaofnodes.simple.node.StopNode;
+import org.junit.Test;
+import org.junit.Ignore;
+
+import java.nio.charset.StandardCharsets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public class BrainFuckTest {
+
+ @Test
+ public void testBrainfuck() {
+ var program = "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.".getBytes(StandardCharsets.UTF_8);
+ var encoded = new StringBuilder("u8[] program = new u8[").append(program.length).append("];");
+ for (int i = 0; i < program.length; i++) {
+ int value = program[i] & 0xFF;
+ encoded.append("program[").append(i).append("] = ").append(value).append(";");
+ }
+
+ Parser parser = new Parser(
+ encoded + """
+int pc = 0;
+int d = 0;
+u8[] data = new u8[100];
+u8[] output = new u8[0];
+
+while (pc < program#) {
+ int command = program[pc];
+ if (command == 62) {
+ d = d + 1;
+ } else if (command == 60) {
+ d = d - 1;
+ } else if (command == 43) {
+ data[d] = data[d] + 1;
+ } else if (command == 45) {
+ data[d] = data[d] - 1;
+ } else if (command == 46) {
+ u8[] old = output;
+ output = new u8[output# + 1];
+ int i = 0;
+ while (i < old#) {
+ output[i] = old[i];
+ i = i + 1;
+ }
+ output[i] = data[d];
+ } else if (command == 44) {
+ data[d] = 42;
+ } else if (command == 91) {
+ if (data[d] == 0) {
+ int d = 1;
+ while (d > 0) {
+ pc = pc + 1;
+ command = program[pc];
+ if (command == 91) d = d + 1;
+ if (command == 93) d = d - 1;
+ }
+ }
+ } else if (command == 93) {
+ if (data[d] != 0) {
+ int d = 1;
+ while (d > 0) {
+ pc = pc - 1;
+ command = program[pc];
+ if (command == 93) d = d + 1;
+ if (command == 91) d = d - 1;
+ }
+ }
+ }
+ pc = pc + 1;
+}
+return output;
+ """);
+ StopNode stop = parser.parse().iterate();
+ assertEquals("Hello World!\n", Evaluator.evaluate(stop, 0, 10000).toString());
+ }
+}
diff --git a/src/test/java/com/seaofnodes/simple/Chapter06Test.java b/src/test/java/com/seaofnodes/simple/Chapter06Test.java
index c5664c9d..d2c46df5 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter06Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter06Test.java
@@ -65,7 +65,7 @@ else
b=5;
return b;""", TypeInteger.BOT);
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Region35,42,5);", stop.toString());
+ assertEquals("return Phi(Region40,42,5);", stop.toString());
}
@Test
diff --git a/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/src/test/java/com/seaofnodes/simple/Chapter10Test.java
index 6649c0b0..a5e3a9e0 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter10Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter10Test.java
@@ -28,10 +28,11 @@ public class Chapter10Test {
// Issue #246: null-check guards start in Chapter 10; arrays arrive in Chapter 15.
private static final String NULLABLE_POINT_SOURCE = """
struct Point { int x; };
+ Point?[] points = new Point?[2];
Point point = new Point;
point.x = 42;
- Point? p = null;
- if (arg) p = point;
+ points[arg] = point;
+ Point? p = points[1];
""";
@Test
@@ -54,7 +55,7 @@ public class Chapter10Test {
for( String body : new String[] {
"return p.x;",
"if (p == null) return p.x; return -1;",
- "if (!!point) return p.x; return -1;",
+ "if (!!points) return p.x; return -1;",
"if (!!p) { int x = p.x; } return p.x;"
} ) {
try {
@@ -127,7 +128,7 @@ else
return v;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return new Vector2D;", stop.toString());
+ assertEquals("return Vector2D;", stop.toString());
}
@Test
@@ -165,7 +166,7 @@ while (arg) {
return bar.a;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Loop13,0,(Phi_a+2));", stop.toString());
+ assertEquals("return Phi(Loop15,0,(Phi_a+2));", stop.toString());
}
@Test
@@ -217,7 +218,7 @@ if( bar ) bar.a = 1;
return bar;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Region33,(*void)Phi(Region20,null,new Bar),null);", stop.toString());
+ assertEquals("return Phi(Region36,(*void)Phi(Region22,null,Bar),null);", stop.toString());
}
@Test
@@ -233,7 +234,7 @@ else bar.a = 1;
return rez;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Region39,4,3);", stop.toString());
+ assertEquals("return Phi(Region42,4,3);", stop.toString());
}
@Test
@@ -308,7 +309,7 @@ while(arg) {
return ret;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Loop13,new s0,Phi(Region37,new s0,Phi_ret));", stop.toString());
+ assertEquals("return Phi(Loop15,s0,Phi(Region42,s0,Phi_ret));", stop.toString());
}
@Test
@@ -326,7 +327,7 @@ while(arg) {
return ret;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Loop17,new s0,Phi(Region38,new s0,Phi_ret));", stop.toString());
+ assertEquals("return Phi(Loop21,s0,Phi(Region43,s0,Phi_ret));", stop.toString());
}
@@ -343,7 +344,7 @@ while(arg < 10) {
return ret;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Loop13,new s0,Phi(Region35,new s0,Phi_ret));", stop.toString());
+ assertEquals("return Phi(Loop15,s0,Phi(Region39,s0,Phi_ret));", stop.toString());
}
@Test
@@ -387,7 +388,7 @@ else return new s0;
if(new s0.f0) return 0;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return new s0;", stop.toString());
+ assertEquals("return s0;", stop.toString());
}
@Test
@@ -415,7 +416,7 @@ s0 v1 = v0;
return v1;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return new s0;", stop.toString());
+ assertEquals("return s0;", stop.toString());
}
@@ -547,8 +548,7 @@ return 0;
if (arg) s.x=arg+1; else s.x=arg+2;
return s;
""").parse().iterate();
- // These chapters still bind struct Stores to branch control.
- assertEquals(3,countMemoryNodes(stop,StoreNode.class,new BitSet()));
+ assertEquals(1,countMemoryNodes(stop,StoreNode.class,new BitSet()));
assertEquals(2L,((Evaluator.Obj)Evaluator.evaluate(stop,0)).fields()[0]);
assertEquals(4L,((Evaluator.Obj)Evaluator.evaluate(stop,3)).fields()[0]);
}
diff --git a/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index e84da36a..e885deb3 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -2,14 +2,10 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.evaluator.Evaluator;
import com.seaofnodes.simple.node.StopNode;
-import com.seaofnodes.simple.type.TypeStruct;
-import com.seaofnodes.simple.type.TypeMemPtr;
import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
public class Chapter12Test {
@Test
@@ -105,7 +101,7 @@ n.next = new N;
return n.next;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return new N;", stop.toString());
+ assertEquals("return N;", stop.toString());
}
@Test
@@ -178,7 +174,7 @@ struct S2 { int x; }
return new S1.s=new S2;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return new S1;", stop.toString());
+ assertEquals("return S2;", stop.toString());
}
@Test
@@ -198,25 +194,4 @@ return p1.pi.i + 1;
catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); }
}
-
- @Test
- public void testShallowRecursiveType() {
- new Parser("struct LLI { LLI? next; int i; } return 0;").parse().iterate();
- TypeStruct l1 = (TypeStruct)Parser.TYPES.get("LLI");
- TypeStruct l0 = ((TypeMemPtr)l1._fields[0]._type)._obj;
- assertEquals("LLI", l0._name);
- assertNull(l0._fields);
- assertEquals(2, l1._fields.length);
- assertSame(l0, l1.meet(l0));
- assertSame(l0, l0.dual());
- assertSame(l0, l0.glb());
- }
-
- @Test
- public void testUnusedForwardReference() {
- StopNode stop = new Parser("struct Holder { Missing? ref; } Holder h = new Holder; return h.ref;")
- .parse().iterate();
- assertEquals("return null;", stop.toString());
- assertNull(((TypeStruct)Parser.TYPES.get("Missing"))._fields);
- }
}
diff --git a/src/test/java/com/seaofnodes/simple/Chapter13Test.java b/src/test/java/com/seaofnodes/simple/Chapter13Test.java
index 34d71d9b..3373a1aa 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter13Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter13Test.java
@@ -347,7 +347,9 @@ if (arg) v.f=1;
return i;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return .f;", stop.toString());
+ assertEquals("return 2;", stop.toString());
+ assertEquals(2L, Evaluator.evaluate(stop, 0));
+ assertEquals(2L, Evaluator.evaluate(stop, 1));
}
@Test
@@ -356,6 +358,7 @@ return i;
"""
struct S { int f; }
S v = new S;
+v.f = arg;
S t = new S;
int i = 0;
if (arg) {
@@ -367,7 +370,7 @@ if (arg) {
return i;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Region37,.f,0);", stop.toString());
+ assertEquals("return Phi(Region46,.f,0);", stop.toString());
}
@Test
@@ -386,7 +389,7 @@ if (v1) {
return v0;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return new S;", stop.toString());
+ assertEquals("return S;", stop.toString());
}
@@ -396,6 +399,7 @@ return v0;
"""
struct S { int f; }
S v = new S;
+v.f = arg;
S t = new S;
int i = v.f;
if (arg+1) arg= 0;
@@ -403,7 +407,7 @@ while (arg) v.f = 2;
return i;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return .f;", stop.toString());
+ assertEquals("return arg;", stop.toString());
}
@Test
@@ -420,7 +424,7 @@ while(1) {
return v;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return new S;", stop.toString());
+ assertEquals("return S;", stop.toString());
}
@Test
@@ -453,7 +457,7 @@ while (arg) {
return i;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return .f;", stop.toString());
+ assertEquals("return 0;", stop.toString());
}
@Test
@@ -473,7 +477,7 @@ while(arg) {
return arg;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Loop17,arg,Phi(Region41,.f,0));", stop.toString());
+ assertEquals("return Phi(Loop21,arg,Phi(Region48,.f,0));", stop.toString());
}
@Test
@@ -491,7 +495,7 @@ if (arg) {
return v;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return new S;", stop.toString());
+ assertEquals("return S;", stop.toString());
}
@Test
@@ -512,21 +516,4 @@ return v;
StopNode stop = parser.parse().iterate();
//assertEquals("return new S;", stop.toString());
}
-
- @Test
- public void testSplit() {
- Parser parser = new Parser(
- """
-struct S { int f; }
-S s = new S;
-if( arg==0 ) s.f = 1;
-else if (arg == 1) s.f = 1;
-return s.f;
-""");
- StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Region43,1,Phi(Region38,1,0));", stop.toString());
- assertEquals(1L, Evaluator.evaluate(stop, 0));
- assertEquals(1L, Evaluator.evaluate(stop, 1));
- assertEquals(0L, Evaluator.evaluate(stop, 2));
- }
}
diff --git a/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
index f2b51da2..f4323fe0 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
@@ -80,16 +80,4 @@ return x+1==x;
assertEquals("return ((flt)arg==(ToFloat+1.0));", stop.toString());
assertEquals(0L, Evaluator.evaluate(stop, 1));
}
- @Test
- public void testReferenceFieldRejectsFloat() {
- Parser parser = new Parser(
-"""
-struct N { N next; int i; }
-N n = new N;
-n.i = 3.14;
-return n.i;
-""");
- try { parser.parse().iterate(); fail(); }
- catch( Exception e ) { assertEquals("Cannot store 3.14 into field int i",e.getMessage()); }
- }
}
diff --git a/src/test/java/com/seaofnodes/simple/Chapter14Test.java b/src/test/java/com/seaofnodes/simple/Chapter14Test.java
index 23c8fed7..d64423de 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter14Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter14Test.java
@@ -125,7 +125,8 @@ return b;
""");
StopNode stop = parser.parse().iterate();
assertEquals("return (((((arg<<56)>>56)+1)<<56)>>56);", stop.toString());
- assertEquals(1L, Evaluator.evaluate(stop, 0));
+ assertEquals(1L, Evaluator.evaluate(stop, 0));
+ assertEquals(-128L, Evaluator.evaluate(stop, 127));
}
@Test
@@ -142,35 +143,6 @@ return c; //
}
@Test
- public void testWrapMinus() {
- Parser parser = new Parser(
-"""
-int MAX = 9223372036854775807; //0x7FFFFFFFFFFFFFFF;
-int i = (arg&MAX) + -MAX + -1; // Basically (e0) + Long.MIN_VALUE
-int j = -i; // Negating Long.MIN_VALUE wraps, cannot constant fold
-if (arg) j = 1;
-return j;
-""");
- StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Region29,1,(-((arg&9223372036854775807)+-9223372036854775808)));", stop.toString());
- assertEquals(-9223372036854775808L, Evaluator.evaluate(stop, 0));
- assertEquals(1L, Evaluator.evaluate(stop, 1));
- }
-
- @Test
- public void testWrapShr() {
- Parser parser = new Parser(
-"""
-return (arg >>> 1)==0;
-""");
- StopNode stop = parser.parse().iterate();
- assertEquals("return (!(arg>>>1));", stop.toString());
- assertEquals(1L, Evaluator.evaluate(stop, 0));
- assertEquals(1L, Evaluator.evaluate(stop, 1));
- assertEquals(0L, Evaluator.evaluate(stop, 2));
- }
-
- @Test
public void testOr() {
Parser parser = new Parser(
"""
@@ -194,101 +166,6 @@ return arg;
}
@Test
- public void testLoadBug() {
- Parser parser = new Parser(
-"""
-struct A { int i; }
-struct B { int i; }
-A a = new A;
-A t1 = new A;
-B b = new B;
-B t2 = new B;
-int i;
-if (arg) i = a.i;
-else i = b.i;
-return i;
-""");
- StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Region34,.i,.i);", stop.toString());
- assertEquals(0L, Evaluator.evaluate(stop, 0));
- }
-
- @Test
- public void testBug2() {
- Parser parser = new Parser(
-"""
-int z = 0;
-while (1) {
- int j;
- if (arg&3) {
- j = arg >> 2;
- } else {
- j = (arg >> 3)+z;
- }
- return j+1;
-}
-""");
- StopNode stop = parser.parse().iterate();
- assertEquals("return ((arg>>Phi(Region33,2,3))+1);", stop.toString());
- assertEquals(1L, Evaluator.evaluate(stop, 0));
- assertEquals(1L, Evaluator.evaluate(stop, 1));
- assertEquals(2L, Evaluator.evaluate(stop, 7));
- assertEquals(2L, Evaluator.evaluate(stop, 8));
- }
-
- @Test
- public void testBug3() {
- Parser parser = new Parser(
-"""
-flt f = arg;
-bool b;
-if (arg&3) b = f == 1.0;
-else b = f == 2.0;
-if (arg&5) b = arg == 1;
-return b;
-""");
- StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Region35,(arg==1),((flt)arg==Phi(Region24,1.0,2.0)));", stop.toString());
- assertEquals(1L, Evaluator.evaluate(stop, 1));
- assertEquals(0L, Evaluator.evaluate(stop, 2));
- assertEquals(0L, Evaluator.evaluate(stop, 3));
- }
-
- @Test
- public void testBug4() {
- Parser parser = new Parser(
-"""
-int i;
-if (arg&7) i=3;
-else i=2;
-return (arg == i) == 1;
-""");
- StopNode stop = parser.parse().iterate();
- assertEquals("return (arg==Phi(Region19,3,2));", stop.toString());
- assertEquals(0L, Evaluator.evaluate(stop, 0));
- assertEquals(0L, Evaluator.evaluate(stop, 1));
- assertEquals(0L, Evaluator.evaluate(stop, 2));
- assertEquals(1L, Evaluator.evaluate(stop, 3));
- }
-
- @Test
- public void testBug5() {
- Parser parser = new Parser(
-"""
-int d = -1;
-while (1) {
- int i = (arg & 7) + (9223372036854775807 - 7);
- if (arg & 1) i = ((arg&3) + (9223372036854775807 - 3)) & d;
- return i + -1;
-}
-""");
- StopNode stop = parser.parse().iterate();
- assertEquals("return (Phi(Region44,((arg&3)+9223372036854775804),((arg&7)+9223372036854775800))+-1);", stop.toString());
- assertEquals(0x7FFFFFFFFFFFFFF7L, Evaluator.evaluate(stop, 0));
- assertEquals(0x7FFFFFFFFFFFFFFCL, Evaluator.evaluate(stop, 1));
- }
-
- @Test
public void testTypes() {
Parser parser = new Parser(
"""
diff --git a/src/test/java/com/seaofnodes/simple/Chapter15Test.java b/src/test/java/com/seaofnodes/simple/Chapter15Test.java
new file mode 100644
index 00000000..8f9d2d14
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/Chapter15Test.java
@@ -0,0 +1,415 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.evaluator.Evaluator;
+import com.seaofnodes.simple.node.StopNode;
+import org.junit.Test;
+import org.junit.Ignore;
+
+import java.nio.charset.StandardCharsets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public class Chapter15Test {
+
+ @Test public void testPartialAllocationMemory() {
+ StopNode stop = new Parser("""
+ struct Other { int a; int b; int c; }
+ struct S { int x; flt y; S? next; }
+ Other other = new Other;
+ other.c = 7;
+ S a = new S;
+ a.x = arg;
+ a.y = 1.5;
+ S b = new S;
+ // S's aliases are global identities, not tuple or input positions.
+ // Bypass b's zeroing for a, preserving the unrelated Other slice.
+ return a.x + a.y + b.x + b.y + other.c;
+ """).parse().iterate();
+ assertEquals(8.5, Evaluator.evaluate(stop,0));
+ assertEquals(12.5, Evaluator.evaluate(stop,4));
+
+ stop = new Parser("""
+ struct S { int x; flt y; S? next; }
+ S a = new S; S b = new S;
+ S p = a; if (arg) p = b;
+ // The memory result carries distinct int, float and pointer facts.
+ if (p.next != null) return 99;
+ return p.x + p.y;
+ """).parse().iterate();
+ assertEquals("return 0.0;",stop.toString());
+ assertEquals(0.0,Evaluator.evaluate(stop,1));
+ }
+
+ @Test public void testReadAcrossAllocation() {
+ StopNode stop = new Parser("""
+ struct S { int x; }
+ S a = new S; S b = new S;
+ a.x = 11; b.x = 22;
+ S p = a; if (arg) p = b;
+ int before = p.x;
+ S c = new S;
+ a.x = 33; b.x = 44;
+ // The partial input to c must not hide the read/write ordering.
+ // p can alias either object, so node inequality cannot bypass New.
+ return before*100 + p.x + c.x;
+ """).parse().iterate();
+ assertEquals(1133L,Evaluator.evaluate(stop,0));
+ assertEquals(2244L,Evaluator.evaluate(stop,1));
+ }
+
+ @Test public void testAllocationInLoop() {
+ StopNode stop = new Parser("""
+ struct S { int x; flt y; S? next; }
+ S old = new S; old.x = 7;
+ S last = old;
+ while (arg > 0) {
+ S fresh = new S;
+ fresh.x = last.x + 1;
+ fresh.next = last;
+ last = fresh;
+ arg = arg - 1;
+ }
+ return old.x*100 + last.x;
+ """).parse().iterate();
+ assertEquals(707L,Evaluator.evaluate(stop,0));
+ assertEquals(712L,Evaluator.evaluate(stop,5));
+ }
+
+ @Test
+ public void testJig() {
+ Parser parser = new Parser("""
+return 3.14;
+""");
+ StopNode stop = parser.parse().iterate();
+ assertEquals("return 3.14;", stop.toString());
+ assertEquals(3.14, Evaluator.evaluate(stop, 0));
+ }
+
+ @Test
+ public void testLifetime() {
+ Parser parser = new Parser("""
+u8[] b = new u8[5];
+if (arg) {
+ b[0] = b[0] + 1;
+} else {
+ b[1] = b[1] * 2;
+}
+return b[0] + b[1];
+""");
+ StopNode stop = parser.parse().iterate();
+ assertEquals("return Phi(Region72,1,0);", stop.toString());
+ assertEquals(0L, Evaluator.evaluate(stop, 0));
+ assertEquals(1L, Evaluator.evaluate(stop, 1));
+ }
+
+ @Test
+ public void testCyclic() {
+ Parser parser = new Parser(
+"""
+struct C {
+ C? l;
+}
+C c = new C;
+c.l = c;
+return c;
+""");
+ StopNode stop = parser.parse().iterate();
+ assertEquals("return C;", stop.toString());
+ assertEquals("Obj<C>@1{l=obj@1}", Evaluator.evaluate(stop, 0).toString());
+ }
+
+ @Test
+ public void testSafetyCheck() {
+ Parser parser = new Parser(
+"""
+u8[] old = new u8[0];
+u8[] output = new u8[1];
+int i = 0;
+while (i < old#) {
+ output[i] = old[i];
+ i = i + 1;
+}
+output[i] = 1;
+return output;
+""");
+ StopNode stop = parser.parse().iterate();
+ assertEquals("return u8[];", stop.toString());
+ assertEquals("\u0001", Evaluator.evaluate(stop, 0).toString());
+ }
+
+ @Test
+ public void testBasic1() {
+ Parser parser = new Parser(
+"""
+int[] is = new int[2];
+return is[1];
+""");
+ StopNode stop = parser.parse().iterate();
+ assertEquals("return 0;", stop.toString());
+ assertEquals(0L, Evaluator.evaluate(stop, 0));
+ }
+
+ @Test
+ public void testBasic2() {
+ Parser parser = new Parser(
+"""
+int[] is = new int[2];
+int[] is2 = new int[2];
+return is[1];
+""");
+ StopNode stop = parser.parse().iterate();
+ assertEquals("return 0;", stop.toString());
+ assertEquals(0L, Evaluator.evaluate(stop, 0));
+ }
+
+ @Test
+ public void testBasic3() {
+ Parser parser = new Parser(
+"""
+int[] a = new int[2];
+a[0] = 1;
+a[1] = 2;
+return a[0];
+""");
+ StopNode stop = parser.parse().iterate();
+ assertEquals("return 1;", stop.toString());
+ assertEquals(1L, Evaluator.evaluate(stop, 0));
+ }
+
+ @Test
+ public void testBasic4() {
+ Parser parser = new Parser(
+"""
+struct A { int i; }
+A?[] a = new A?[2];
+return a;
+""");
+ StopNode stop = parser.parse().iterate();
+ assertEquals("return *A?[];", stop.toString());
+ assertEquals("Obj<*A?[]>{#=2,[]=[null,null]}", Evaluator.evaluate(stop, 0).toString());
+ }
+
+ @Test
+ public void testBasic5() {
+ Parser parser = new Parser(
+"""
+struct S { int x; flt y; }
+// A new S
+S s = new S; s.x=99; s.y = 3.14;
+
+// Double-d array of Ss. Fill in one row.
+S?[]?[] iss = new S?[]?[2];
+iss[0] = new S?[7];
+iss[0][2] = s;
+
+// Now pull out the filled-in value, with null checks
+flt rez;
+S?[]? is = iss[arg];
+if( !is ) rez = 1.2;
+else {
+ S? i = is[2];
+ if( !i ) rez = 2.3;
+ else rez = i.y;
+}
+return rez;
+""");
+ StopNode stop = parser.parse().iterate();
+ assertEquals("return Phi(Region122,1.2,Phi(Region119,2.3,.y));", stop.toString());
+ assertEquals(3.14, Evaluator.evaluate(stop, 0));
+ }
+
+ @Ignore
+ @Test
+ public void testBasic6() {
+ Parser parser = new Parser(
+"""
+struct S { int x; flt y; }
+// A new S
+S s = new S; s.x=99; s.y = 3.14;
+
+// Double-d array of Ss. Fill in one row.
+S?[]?[] iss = new S?[]?[2];
+iss[0] = new S?[7];
+iss[0][2] = s;
+
+// Now pull out the filled-in value, with null checks
+flt rez = 1.2;
+if( iss[arg] )
+ if( iss[arg][2] )
+ rez = iss[arg][2].y;
+return rez;
+""");
+ StopNode stop = parser.parse().iterate();
+ assertEquals("return Phi(Region122,Phi(Region118,.y,1.2),1.2);", stop.toString());
+ assertEquals(3.14, Evaluator.evaluate(stop, 0));
+ }
+
+ @Test
+ public void testTree() {
+ Parser parser = new Parser(
+"""
+// Can we define a forward-reference array?
+struct Tree { Tree?[] _kids; }
+Tree root = new Tree;
+root._kids = new Tree?[2];
+root._kids[0] = new Tree;
+return root;
+""");
+ StopNode stop = parser.parse().iterate();
+ assertEquals("return Tree;", stop.toString());
+ assertEquals("Obj<Tree>{_kids=Obj<*Tree?[]>{#=2,[]=[Obj<Tree>{_kids=null},null]}}", Evaluator.evaluate(stop, 0).toString());
+ }
+
+ @Test
+ public void testNestedStructAddMemProj() {
+ Parser parser = new Parser(
+"""
+struct S {
+ int a;
+ int[] b;
+}
+return 0;
+""");
+ StopNode stop = parser.parse();
+ assertEquals("return 0;", stop.toString());
+ }
+
+ @Test
+ public void testRollingSum() {
+ Parser parser = new Parser(
+"""
+int[] ary = new int[arg];
+// Fill [0,1,2,3,4,...]
+int i=0;
+while( i < ary# ) {
+ ary[i] = i;
+ i = i+1;
+}
+// Fill [0,1,3,6,10,...]
+i=0;
+while( i < ary# - 1 ) {
+ ary[i+1] = ary[i+1] + ary[i];
+ i = i+1;
+}
+return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6
+""");
+ StopNode stop = parser.parse().iterate();
+ assertEquals("return (.[]+(.[]*1000));", stop.toString());
+ assertEquals(1006L, Evaluator.evaluate(stop, 4));
+ }
+
+ @Test
+ public void sieveOEratosthenes() {
+ Parser parser = new Parser(
+"""
+int[] ary = new int[arg];
+int[] primes = new int[arg];
+int nprimes = 0;
+// Find primes
+int j=2;
+while( j*j < arg ) {
+ while( ary[j]==1 ) j = j + 1;
+ // j is now a prime
+ primes[nprimes] = j; nprimes = nprimes + 1;
+ // Mark out the rest non-primes
+ int i = j + j;
+ while( i < ary# ) {
+ ary[i] = 1;
+ i = i + j;
+ }
+ j = j + 1;
+}
+// Now just collect the remaining primes
+while( j < arg ) {
+ if( ary[j] == 0 ) {
+ primes[nprimes] = j; nprimes = nprimes + 1;
+ }
+ j = j + 1;
+}
+// Shrink the result array to size
+int[] rez = new int[nprimes];
+j = 0;
+while( j < nprimes ) {
+ rez[j] = primes[j];
+ j = j + 1;
+}
+
+return rez;
+""");
+ StopNode stop = parser.parse().iterate();
+ assertEquals("return int[];", stop.toString());
+ Evaluator.Obj obj = (Evaluator.Obj)Evaluator.evaluate(stop, 20);
+ assertEquals("int[] {\n # :int;\n [] :int;\n}",obj.struct().toString());
+ long nprimes = (Long)obj.fields()[0];
+ long[] primes = new long[]{2,3,5,7,11,13,17,19};
+ for( int i=0; i<nprimes; i++ )
+ assertEquals(primes[i],(long)(Long)obj.fields()[i+1]);
+ }
+
+ @Test
+ public void testNewNodeInit() {
+ Parser parser = new Parser(
+"""
+struct S {int i; flt f;}
+S s1 = new S;
+S s2 = new S;
+s2.i = 3;
+s2.f = 2.0;
+if (arg) s1 = new S;
+return s1.i + s1.f;
+""");
+ StopNode stop = parser.parse().iterate();
+ assertEquals("return ((flt).i+.f);", stop.toString());
+ assertEquals(0.0, Evaluator.evaluate(stop, 0));
+ }
+
+
+ @Test
+ public void testBad0() {
+ Parser parser = new Parser(
+"""
+return new flt;
+""");
+ try { parser.parse().iterate(); fail(); }
+ catch( Exception e ) { assertEquals("Cannot allocate a flt",e.getMessage()); }
+ }
+
+ @Test
+ public void testBad1() {
+ Parser parser = new Parser(
+"""
+int is = new int[2];
+""");
+ try { parser.parse().iterate(); fail(); }
+ catch( Exception e ) { assertEquals("Type *int[] is not of declared type int",e.getMessage()); }
+ }
+
+ @Test
+ public void testBad2() {
+ Parser parser = new Parser(
+"""
+int[] is = new int[3.14];
+return is[1];
+""");
+ try { parser.parse().iterate(); fail(); }
+ catch( Exception e ) { assertEquals("Cannot allocate an array with length 3.14",e.getMessage()); }
+ }
+
+ @Test
+ public void testBad3() {
+ Parser parser = new Parser(
+"""
+int[] is = new int[arg];
+return is[1];
+""");
+ StopNode stop = parser.parse().iterate();
+ assertEquals("return 0;", stop.toString());
+ assertEquals(0L, Evaluator.evaluate(stop, 4));
+ try { Evaluator.evaluate(stop,0); }
+ catch( ArrayIndexOutOfBoundsException e ) { assertEquals("Array index 1 out of bounds for array length 0",e.getMessage()); }
+ try { Evaluator.evaluate(stop,-1); }
+ catch( NegativeArraySizeException e ) { assertEquals("-1",e.getMessage()); }
+ }
+
+}
diff --git a/src/test/java/com/seaofnodes/simple/TypeTest.java b/src/test/java/com/seaofnodes/simple/TypeTest.java
index 9e8a1418..cb45a0d7 100644
--- a/src/test/java/com/seaofnodes/simple/TypeTest.java
+++ b/src/test/java/com/seaofnodes/simple/TypeTest.java
@@ -13,32 +13,32 @@ public class TypeTest {
@Test
public void testTypeAdHoc() {
TypeStruct s1 = TypeStruct.make("s1", new Field[]{
- Field.make("a", TypeInteger.BOT),
- Field.make("b", TypeInteger.BOT) });
+ Field.make("a",-1, TypeInteger.BOT),
+ Field.make("b",-2, TypeInteger.BOT) });
TypeStruct s2 = TypeStruct.make("s2", new Field[]{
- Field.make("a", TypeInteger.BOT),
- Field.make("b", TypeInteger.BOT) });
+ Field.make("a",-3, TypeInteger.BOT),
+ Field.make("b",-4, TypeInteger.BOT) });
Assert.assertEquals(s1, s1.glb());
Assert.assertNotEquals(s1, s1.dual());
Assert.assertEquals(s1, s1.dual().glb());
- TypeMem m1 = TypeMem.make(1);
- TypeMem m2 = TypeMem.make(2);
- TypeMem m3 = TypeMem.make(3);
- TypeMem m4 = TypeMem.make(4);
+ TypeMem m1 = TypeMem.make(1,TypeInteger.ZERO);
+ TypeMem m2 = TypeMem.make(2,TypeInteger.U16);
+ TypeMem m3 = TypeMem.make(3,TypeFloat.BOT);
+ TypeMem m4 = TypeMem.make(4,TypeInteger.BOT);
Assert.assertNotEquals(m1, m2);
Assert.assertNotEquals(m2, m3);
Assert.assertNotEquals(m3, m4);
Assert.assertEquals(TypeStruct.BOT, s1.meet(s2));
- Assert.assertEquals(TypeMem .BOT, m1.meet(m2));
+ Assert.assertEquals(TypeMem.make(-1,TypeInteger.U16), m1.meet(m2));
Assert.assertEquals(TypeMem .BOT, m2.meet(m3));
Assert.assertEquals(TypeMem .BOT, m3.meet(m4));
- Assert.assertEquals(m1, m1.glb());
- Assert.assertEquals(TypeMem.make(1), m1.dual());
- Assert.assertEquals(m1.dual(), m1.glb().dual());
+ Assert.assertEquals(TypeMem.make(1,TypeInteger.BOT), m1.glb());
+ Assert.assertEquals(TypeMem.make(1,TypeInteger.ZERO), m1.dual());
+ Assert.assertEquals(m4.dual(), m4.glb().dual());
TypeMemPtr ptr1 = TypeMemPtr.make(s1);
Assert.assertEquals(s1, ptr1._obj);
@@ -58,12 +58,12 @@ public class TypeTest {
Assert.assertEquals(ptr1, ptr1.dual().dual());
Assert.assertEquals(ptr1.glb(), ptr1.dual().glb());
- Assert.assertEquals(TypeMemPtr.BOT, ptr1.meet(ptr2nil));
+ Assert.assertEquals(TypeMemPtr.make(TypeStruct.BOT,true), ptr1.meet(ptr2nil));
Assert.assertEquals(ptr1.glb(), ptr1.meet(TypeMemPtr.NULLPTR));
TypeMemPtr TOP = TypeMemPtr.TOP;
- TypeMemPtr BOT = TypeMemPtr.BOT;
- TypeMemPtr PTR = TypeMemPtr.VOIDPTR;
+ TypeMemPtr BOT = TypeMemPtr.make(TypeStruct.BOT,true );
+ TypeMemPtr PTR = TypeMemPtr.make(TypeStruct.BOT,false);
TypeMemPtr NULL = TypeMemPtr.NULLPTR;
Type PTR_meet_NULL = NULL.meet(PTR);
Assert.assertEquals(BOT, PTR_meet_NULL);
diff --git a/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java
index afe4cfe3..9eed23de 100644
--- a/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java
+++ b/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java
@@ -6,27 +6,119 @@ import com.seaofnodes.simple.type.*;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
-import java.util.ArrayList;
-import java.util.BitSet;
-import java.util.Objects;
+import java.util.*;
public class Evaluator {
private static final Object MEMORY = new Object();
public record Obj(TypeStruct struct, Object[] fields) {
+ private void init(IdentityHashMap<Obj, Integer> objs) {
+ var oid = objs.get(this);
+ if (oid != null) {
+ if (oid != 0) objs.put(this, 0);
+ return;
+ }
+ objs.put(this, -1);
+ for (var obj : fields) {
+ if (obj instanceof Obj o) o.init(objs);
+ }
+ }
+ private static int p(StringBuilder sb, IdentityHashMap<Obj, Integer> objs, int id, String indentation, String step, String sep, Object obj) {
+ if (obj instanceof Obj o) return o.p(sb, objs, id, indentation, step, sep);
+ sb.append(obj);
+ return id;
+ }
+ private int p(StringBuilder sb, IdentityHashMap<Obj, Integer> objs, int id, String indentation, String step, String sep) {
+ var cid = objs.get(this);
+ assert cid != null;
+ if (cid > 0) {
+ sb.append("obj@").append(cid);
+ return id;
+ }
+ if (struct._name.equals("u8[]")) {
+ sb.append('"');
+ for (int i=struct._fields.length-1; i<fields.length; i++) {
+ var v = fields[i];
+ if (v == null) v = 0;
+ assert v instanceof Number;
+ var n = ((Number)v).byteValue() & 0xFF;
+ if (n >= 0x20 && n < 0x80) {
+ if (n == 0x22 || n == 0x5C) sb.append("\\");
+ sb.append((char)n);
+ } else {
+ sb.append("\\x").append("0123456789abcdef".charAt(n >> 4)).append("0123456789abcdef".charAt(n & 0xF));
+ }
+ }
+ sb.append('"');
+ return id;
+ }
+ sb.append("Obj<").append(struct._name).append(">");
+ if (cid == 0) {
+ cid = id++;
+ objs.put(this, cid);
+ sb.append("@").append(cid);
+ }
+ sb.append("{");
+ if (struct._fields.length == 0) {
+ assert !struct.isAry();
+ sb.append("}");
+ return id;
+ }
+ String nextIndent = indentation + step;
+ int e = struct._fields.length - 1;
+ for(int i=0; i<e; i++) {
+ sb.append(nextIndent).append(struct._fields[i]._fname).append("=");
+ id = p(sb, objs, id, nextIndent, step, sep, fields[i]);
+ sb.append(sep);
+ }
+ sb.append(nextIndent).append(struct._fields[e]._fname).append("=");
+ if (struct.isAry()) {
+ sb.append("[");
+ if (fields.length > e) {
+ String innerIndent = nextIndent + step;
+ sb.append(innerIndent);
+ id = p(sb, objs, id, nextIndent, step, sep, fields[e]);
+ for (int i = e+1; i < fields.length; i++) {
+ sb.append(sep).append(innerIndent);
+ id = p(sb, objs, id, nextIndent, step, sep, fields[i]);
+ }
+ sb.append(nextIndent);
+ }
+ sb.append("]");
+ } else {
+ id = p(sb, objs, id, nextIndent, step, sep, fields[e]);
+ }
+ sb.append(indentation).append("}");
+ return id;
+ }
+ private StringBuilder p(StringBuilder sb, String indentation, String step, String sep) {
+ var objs = new IdentityHashMap<Obj, Integer>();
+ init(objs);
+ p(sb, objs, 1, indentation, step, sep);
+ return sb;
+ }
@Override
public String toString() {
- var sb = new StringBuilder();
- sb.append("Obj<").append(struct._name).append("> {");
- for(int i=0; i<struct._fields.length; i++) {
- sb.append("\n ").append(struct._fields[i]._fname).append("=").append(fields[i]);
+ if (struct._name.equals("u8[]")) {
+ var sb = new StringBuilder();
+ for (int i=struct._fields.length-1; i<fields.length; i++) {
+ var v = fields[i];
+ if (v == null) v = 0;
+ assert v instanceof Number;
+ var n = ((Number)v).byteValue() & 0xFF;
+ sb.append((char)n);
+ }
+ return sb.toString();
}
- return sb.append("\n}").toString();
+ return p(new StringBuilder(), "", "", ",").toString();
+ }
+ public String pretty() {
+ return p(new StringBuilder(), "\n", " ", "").toString();
}
}
- private static int getFieldIndex(TypeStruct struct, MemOpNode memop) {
+ private static int getFieldIndex(TypeStruct struct, MemOpNode memop, long off) {
int idx = struct.find(memop._name);
if( idx >= 0 ) return idx;
throw new AssertionError("Field "+memop._name+" not found in struct " + struct._name);
@@ -82,22 +174,78 @@ public class Evaluator {
return in2 == 0 ? 0 : vald(div.in(1)) / in2;
}
+ private static long offToIdx(long off, TypeStruct t) {
+ off -= t.aryBase();
+ int scale = t.aryScale();
+ long mask = (1L << scale)-1;
+ assert (off & mask) == 0;
+ return off>>scale;
+ }
+
private Object alloc(NewNode alloc) {
- var type = ((TypeMemPtr)alloc.compute())._obj;
- return new Obj(type, new Object[type._fields.length]);
+ TypeStruct type = alloc._ptr._obj;
+ Object[] body=null;
+ int num;
+ if( type.isAry() ) {
+ long sz = (Long)val(alloc.size());
+ long n = offToIdx(sz, type);
+ if( n < 0 )
+ throw new NegativeArraySizeException(""+n);
+ body = new Object[(int)n+1]; // Array body
+ var c = type._fields[type._fields.length-1]._type;
+ if (c instanceof TypeInteger) {
+ for (int i=0; i<n; i++) body[i+type._fields.length-1] = 0L;
+ } else if (c instanceof TypeFloat) {
+ for (int i = 0; i < n; i++) body[i+type._fields.length-1] = 0D;
+ } else {
+ assert c instanceof TypeMemPtr;
+ }
+ num = type._fields.length-1;
+ } else {
+ body = new Object[type._fields.length];
+ num = type._fields.length;
+ }
+ for (int i=0; i<num; i++) {
+ var c = type._fields[i]._type;
+ if (c instanceof TypeInteger) {
+ body[i] = 0L;
+ } else if (c instanceof TypeFloat) {
+ body[i] = 0D;
+ } else {
+ assert c instanceof TypeMemPtr;
+ }
+ }
+ return new Object[]{new Obj(type,body),MEMORY};
}
private Object load(LoadNode load) {
- var from = (Obj)val(load.in(2));
- var idx = getFieldIndex(from.struct, load);
- return from.fields[idx];
+ var from = (Obj)val(load.ptr());
+ var off = vall(load.off());
+ var idx = getFieldIndex(from.struct, load, (Long)off);
+ if( idx==from.struct._fields.length-1 && from.struct.isAry() ) {
+ long len = from.fields.length - from.struct._fields.length + 1;
+ long i = offToIdx(off, from.struct);
+ if( i < 0 || i >= len )
+ throw new ArrayIndexOutOfBoundsException("Array index out of bounds " + i + " < " + len);
+ return from.fields[(int)i+from.struct._fields.length-1];
+
+ } else
+ return from.fields[idx];
}
private Object store(StoreNode store) {
- var to = (Obj)val(store.in(2));
- var val = val(store.in(3));
- var idx = getFieldIndex(to.struct, store);
- to.fields[idx] = val;
+ var to = (Obj)val(store.ptr());
+ var off = vall(store.off());
+ var val = val(store.val());
+ var idx = getFieldIndex(to.struct, store, (Long)off);
+ if( idx==to.struct._fields.length-1 && to.struct.isAry() ) {
+ long len = to.fields.length - to.struct._fields.length + 1;
+ long i = offToIdx(off, to.struct);
+ if( i < 0 || i >= len )
+ throw new RuntimeException("Array index out of bounds " + off + " <= " + len);
+ to.fields[(int)i+to.struct._fields.length-1] = val;
+ } else
+ to.fields[idx] = val;
return null;
}
diff --git a/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java b/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java
index d16f4300..125f2f5e 100644
--- a/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java
+++ b/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java
@@ -31,6 +31,8 @@ import java.util.*;
*/
public class Scheduler {
+ private final HashMap<Node,ArrayList<NodeData>> readers = new HashMap<>();
+
/**
* A basic block with a schedule of the containing nodes.
*
@@ -155,7 +157,10 @@ public class Scheduler {
* @return true if all placed inputs are before this node.
*/
private boolean isValid(NodeData data) {
- return data.node._inputs.stream().map(i->i==null?null:d(i)).map(d->d==null||d.users>0?null:d.block).allMatch(d->d==null||dom(d, data.block)==d);
+ return data.node._inputs.stream()
+ .map(i->i==null?null:d(i))
+ .map(d->d==null||d.users>0?null:d.block)
+ .allMatch(d->d==null||dom(d, data.block)==d);
}
/**
@@ -204,7 +209,7 @@ public class Scheduler {
* @return true if node is placed during the control flow graph build.
*/
private static boolean isPinnedNode(NodeData data) {
- return data.node.isCFG() || data.node instanceof PhiNode;
+ return data.node instanceof CFGNode || data.node instanceof PhiNode;
}
/**
@@ -249,13 +254,7 @@ public class Scheduler {
// but they did not refine the placement
// so do that now.
var mem = l.in(1);
- for(var out : mem._outputs) {
- // Aggregates describe partitions; they do not overwrite
- // memory. Only users of this alias constrain the load.
- if (out instanceof MemMergeNode || out instanceof ReturnNode) continue;
- if (out instanceof StoreNode s && s._alias != l._alias) continue;
- if (out instanceof MemPhiNode p && p._alias != l._alias) continue;
- if (out instanceof BulkMemPhiNode p && p.isSplit(l._alias)) continue;
+ for(var out : l.antiDeps()) {
if (out instanceof PhiNode p) {
var r = p.in(0);
for (int i = 1; i < p.nIns(); i++) {
@@ -273,13 +272,9 @@ public class Scheduler {
for(var in:data.node._inputs) {
if (in!=null) update(d(in), data.block);
}
- if (data.node instanceof StoreNode s) {
- // Store nodes have anti-deps to load nodes.
- // So decrease the uses of these loads when the store is placed.
- for (var out: s.in(1)._outputs) {
- if (out instanceof LoadNode l && l._alias==s._alias) od(out).ifPresent(this::decUsers);
- }
- }
+ var loads = readers.get(data.node);
+ if (loads != null)
+ for (var load : loads) decUsers(load);
}
// Now all nodes should be placed and have a block assigned
@@ -365,7 +360,7 @@ public class Scheduler {
while (!queue.isEmpty()) {
var data = queue.pop();
var node = data.node;
- assert node.isCFG();
+ assert node instanceof CFGNode;
BasicBlock block;
switch (node) {
case StartNode s:
@@ -405,7 +400,9 @@ public class Scheduler {
}
}
if (!(node instanceof ReturnNode))
- for (Node n:node._outputs) if (n!=null && n.isCFG() && isCFGNodeReady(n) && d(n).block == null) queue.push(d(n));
+ for (Node n : node._outputs)
+ if(n instanceof CFGNode && isCFGNodeReady(n) && d(n).block == null)
+ queue.push(d(n));
var b = block;
for(var in:data.node._inputs) od(in).ifPresent(d->update(d, b));
}
@@ -427,7 +424,7 @@ public class Scheduler {
if (nd.users>0) nd.users++;
return;
}
- assert node.isCFG() == cfg;
+ assert node instanceof CFGNode == cfg;
assert isNotXCtrl(node);
nd = new NodeData(node);
if (cfg) nd.users=0;
@@ -443,42 +440,40 @@ public class Scheduler {
private void doMarkAlive(Node node) {
var cfgQueue = new Stack<NodeData>();
var dataQueue = new Stack<NodeData>();
- var mem = new Stack<NodeData>();
markAlive(cfgQueue, node, true);
// Mark all CFG nodes.
while (!cfgQueue.isEmpty()) {
var data = cfgQueue.pop();
node = data.node;
- assert node.isCFG();
+ assert node instanceof CFGNode;
if (!(node instanceof ReturnNode)) {
- for (var out : node._outputs) if (out!=null && out.isCFG() && isNotXCtrl(out)) markAlive(cfgQueue, out, true);
+ for (var out : node._outputs) if (out!=null && out instanceof CFGNode && isNotXCtrl(out)) markAlive(cfgQueue, out, true);
}
- for (var in : node._inputs) if(in!=null && !in.isCFG() && isNotXCtrl(in)) markAlive(dataQueue, in, false);
+ for (var in : node._inputs) if(in!=null && !(in instanceof CFGNode) && isNotXCtrl(in)) markAlive(dataQueue, in, false);
}
// Mark all other nodes.
while (!dataQueue.isEmpty()) {
var data = dataQueue.pop();
node = data.node;
- assert !node.isCFG();
+ assert !(node instanceof CFGNode);
if (node instanceof PhiNode phi) {
var r = phi.in(0);
for (int i=1; i<phi.nIns(); i++) {
if (od(r.in(i)).isPresent()) markAlive(dataQueue, phi.in(i), false);
}
} else {
- for (var in : node._inputs) if (in != null && !in.isCFG()) markAlive(dataQueue, in, false);
- }
- if (node instanceof StoreNode) mem.push(data);
- }
- // Handle store nodes and increase load with an anti-dep to the store.
- while (!mem.isEmpty()) {
- var data = mem.pop();
- node = data.node;
- for(var out:node.in(1)._outputs) {
- if (out instanceof LoadNode l && l._alias==((StoreNode)node)._alias)
- od(out).ifPresent(d->d.users++);
+ for (var in : node._inputs) if (in != null && !(in instanceof CFGNode)) markAlive(dataQueue, in, false);
}
}
+ // Writes, including New behind an aggregate, schedule before their
+ // anti-dependent reads in this reverse scheduling pass.
+ for (var load : data.values())
+ if (load.node instanceof LoadNode l)
+ for (var use : l.antiDeps())
+ if ((use instanceof StoreNode || use instanceof NewNode) && data.containsKey(use)) {
+ load.users++;
+ readers.computeIfAbsent(use,k->new ArrayList<>()).add(load);
+ }
}
/**
@@ -508,8 +503,8 @@ public class Scheduler {
for(var n:node._outputs) if (n instanceof CProjNode p && p._idx==0) return p;
return null;
}
- assert node._outputs.stream().filter(Node::isCFG).limit(2).count()<=1;
- for(var n:node._outputs) if(n.isCFG()) return n;
+ assert node._outputs.stream().filter(x -> x instanceof CFGNode).limit(2).count()<=1;
+ for(var n:node._outputs) if(n instanceof CFGNode) return n;
return null;
}
@@ -553,7 +548,7 @@ public class Scheduler {
}
if (last instanceof ReturnNode) break;
if (last instanceof IfNode if_) {
- for(var out:if_._outputs) if (out.isCFG() && blocks.get(out) == null) queue.push(d(out));
+ for(var out:if_._outputs) if (out instanceof CFGNode && blocks.get(out) == null) queue.push(d(out));
break;
}
data = d(last);
diff --git a/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java b/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java
index ec6e7816..7cd854bf 100644
--- a/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java
+++ b/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java
@@ -25,7 +25,7 @@ public class ScriptGenerator {
private static final int MAX_BLOCK_DEPTH = 4;
private static final int MAX_EXPRESSION_DEPTH = 10;
private static final int MAX_SUFFIX_RECURSIVE_DEPTH = 10;
-
+
/**
* Number of spaces per indentation
*/
@@ -109,8 +109,8 @@ public class ScriptGenerator {
static {
new Parser("", TypeInteger.BOT);
- for(var e:Parser.TYPES.entrySet()) {
- if (e.getValue() instanceof TypeInteger) {
+ for( var e : Parser.TYPES.entrySet() ) {
+ if( e.getValue() instanceof TypeInteger ) {
INTTYPES.add(new TypeInt(e.getKey()));
}
}