diff --git a/optvm/src/test/java/com/compilerprogramming/ezlang/interpreter/TestInterpreter.java b/optvm/src/test/java/com/compilerprogramming/ezlang/interpreter/TestInterpreter.java
index bae0b11..9636be9 100644
--- a/optvm/src/test/java/com/compilerprogramming/ezlang/interpreter/TestInterpreter.java
+++ b/optvm/src/test/java/com/compilerprogramming/ezlang/interpreter/TestInterpreter.java
@@ -1008,7 +1008,7 @@ func quicksort(arr: [Float], low: Int, high: Int) {
}
}
-func eq(a: [Int], b: [Int], n: Int)->Int
+func eq(a: [Float], b: [Float], n: Int)->Int
{
var result = 1
var i = 0
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/Compiler.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/Compiler.java
index 9f43261..bdf2632 100644
--- a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/Compiler.java
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/Compiler.java
@@ -364,9 +364,13 @@ private ReturnNode generateFunctionBody(Symbol.FunctionTypeSymbol functionTypeSy
// Parse the body
Node last = compileStatement(funcDecl.block);
- // Last expression is the return
- if( ctrl()._type== Type.CONTROL )
- fun.addReturn(ctrl(), _scope.mem().merge(), last);
+ // Add an implicit return only for reachable fall-through. Void functions
+ // use an integer sentinel in the backend signature.
+ if( ctrl()._type== Type.CONTROL ) {
+ EZType.EZTypeFunction functionType = (EZType.EZTypeFunction) functionTypeSymbol.type;
+ Node result = functionType.returnType instanceof EZType.EZTypeVoid ? ZERO : last;
+ fun.addReturn(ctrl(), _scope.mem().merge(), result);
+ }
// Pop off the inProgress node on the multi-exit Region merge
assert r.inProgress();
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/IterPeeps.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/IterPeeps.java
deleted file mode 100644
index 1cea2e2..0000000
--- a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/IterPeeps.java
+++ /dev/null
@@ -1,201 +0,0 @@
-package com.compilerprogramming.ezlang.compiler;
-
-import com.compilerprogramming.ezlang.compiler.codegen.CodeGen;
-import com.compilerprogramming.ezlang.compiler.node.*;
-import com.compilerprogramming.ezlang.compiler.util.Ary;
-//import com.seaofnodes.simple.print.JSViewer;
-import java.util.Arrays;
-import java.util.BitSet;
-import java.util.Random;
-
-/**
- * The IterPeeps runs after parsing. It iterates the peepholes to a fixed point
- * so that no more peepholes apply. This should be linear because peepholes rarely
- * (never?) increase code size. The graph should monotonically reduce in some
- * dimension, which is usually size. It might also reduce in e.g. number of
- * MulNodes or Load/Store nodes, swapping out more "expensive" Nodes for cheaper
- * ones.
- *
- * The theoretical overall worklist is mindless just grabbing the next thing and
- * doing it. If the graph changes, put the neighbors on the worklist.
- *
- * Lather, Rinse, Repeat until the worklist runs dry.
- *
- * The main issues we have to deal with:
- *
- *
- * - Nodes have uses; replacing some set of Nodes with another requires more graph
- * reworking. Not rocket science, but it can be fiddly. Its helpful to have a
- * small set of graph munging utilities, and the strong invariant that the graph
- * is stable and correct between peepholes. In our case `Node.subsume` does
- * most of the munging, building on our prior stable Node utilities.
- *
- * - Changing a Node also changes the graph "neighborhood". The neighbors need to
- * be checked to see if THEY can also peephole, and so on. After any peephole
- * or graph update we put a Nodes uses and defs on the worklist.
- *
- * - Our strong invariant is that for all Nodes, either they are on the worklist
- * OR no peephole applies. This invariant is easy to check, although expensive.
- * Basically the normal "iterate peepholes to a fixed point" is linear, and this
- * check is linear at each peephole step... so quadratic overall. Its a useful
- * assert, but one we can disable once the overall algorithm is stable - and
- * then turn it back on again when some new set of peepholes is misbehaving.
- * The code for this is turned on in `IterPeeps.iterate` as `assert
- * progressOnList(stop);`
- *
- */
-public class IterPeeps {
-
- private final WorkList _work;
-
- public IterPeeps( long seed ) { _work = new WorkList<>(seed); }
-
- @SuppressWarnings("unchecked")
- public N add( N n ) { return (N)_work.push(n); }
-
- public void addAll( Ary ary ) { _work.addAll(ary); }
-
- /**
- * Iterate peepholes to a fixed point
- */
- public void iterate( CodeGen code ) {
- assert progressOnList(code);
- int cnt=0;
-
- Node n;
- while( (n=_work.pop()) != null ) {
- if( n.isDead() ) continue;
- cnt++; // Useful for debugging, searching which peephole broke things
- Node x = n.peepholeOpt();
- if( x != null ) {
- if( x.isDead() ) continue;
- // peepholeOpt can return brand-new nodes, needing an initial type set
- if( x._type==null ) x.setType(x.compute());
- // Changes require neighbors onto the worklist
- if( x != n || !(x instanceof ConstantNode) ) {
- // All outputs of n (changing node) not x (prior existing node).
- for( Node z : n._outputs ) _work.push(z);
- // Everybody gets a free "go again" in case they didn't get
- // made in their final form.
- _work.push(x);
- // If the result is not self, revisit all inputs (because
- // there's a new user), and replace in the graph.
- if( x != n ) {
- for( Node z : n. _inputs ) _work.push(z);
- for( Node z : x._outputs ) _work.push(z);
- n.subsume(x);
- }
- }
- // If there are distant neighbors, move to worklist
- n.moveDepsToWorklist();
- //JSViewer.show(); // Show again
- assert progressOnList(code); // Very expensive assert
- }
- if( n.isUnused() && !(n instanceof StopNode) )
- n.kill(); // Just plain dead
- }
-
- }
-
- // Visit ALL nodes and confirm the invariant:
- // Either you are on the _work worklist OR running `iter()` makes no progress.
-
- // This invariant ensures that no progress is missed, i.e., when the
- // worklist is empty we have indeed done all that can be done. To help
- // with debugging, the {@code assert} is broken out in a place where it is easy to
- // stop if a change is found.
-
- // Also, the normal usage of `iter()` may attempt peepholes with distance
- // neighbors and these should fail, but will then try to add dependencies
- // {@link #Node.addDep} which is a side effect in an assert. The {@link
- // #midAssert} is used to stop this side effect.
- private boolean progressOnList(CodeGen code) {
- code._midAssert = true;
- Node changed = code._stop.walk( n -> {
- Node m = n;
- if( n.compute().isa(n._type) && (!n.iskeep() || n._nid<=6) ) { // Types must be forwards, even if on worklist
- if( _work.on(n) ) return null;
- m = n.peepholeOpt();
- if( m==null ) return null;
- }
- System.err.println("BREAK HERE FOR BUG");
- return m;
- });
- code._midAssert = false;
- return changed==null;
- }
-
- /**
- * Classic WorkList, with a fast add/remove, dup removal, random pull.
- * The Node's nid is used to check membership in the worklist.
- */
- @SuppressWarnings("unchecked")
- public static class WorkList {
-
- private Node[] _es;
- private int _len;
- private final BitSet _on; // Bit set if Node._nid is on WorkList
- private final Random _R; // For randomizing pull from the WorkList
- private final long _seed;
-
- /* Useful stat - how many nodes are processed in the post parse iterative opt */
- private long _totalWork = 0;
-
- public WorkList() { this(123); }
- WorkList(long seed) {
- _es = new Node[1];
- _len=0;
- _on = new BitSet();
- _seed = seed;
- _R = new Random();
- _R.setSeed(_seed);
- }
-
- /**
- * Pushes a Node on the WorkList, ensuring no duplicates
- * If Node is null it will not be added.
- */
- public E push( E x ) {
- if( x==null ) return null;
- int idx = x._nid;
- if( !_on.get(idx) ) {
- _on.set(idx);
- if( _len==_es.length )
- _es = Arrays.copyOf(_es,_len<<1);
- _es[_len++] = x;
- _totalWork++;
- }
- return x;
- }
-
- public void addAll( Ary ary ) {
- for( E n : ary )
- push(n);
- }
-
- /**
- * True if Node is on the WorkList
- */
- boolean on( E x ) { return _on.get(x._nid); }
- boolean isEmpty() { return _len==0; }
-
- /**
- * Removes a random Node from the WorkList; null if WorkList is empty
- */
- public E pop() {
- if( _len == 0 ) return null;
- int idx = _R.nextInt(_len);
- E x = (E)_es[idx];
- _es[idx] = _es[--_len]; // Compress array
- _on.clear(x._nid);
- return x;
- }
-
- public void clear() {
- _len = 0;
- _on.clear();
- _R.setSeed(_seed);
- _totalWork = 0;
- }
- }
-}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/IterPeeps2.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/IterPeeps2.java
new file mode 100644
index 0000000..d53514a
--- /dev/null
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/IterPeeps2.java
@@ -0,0 +1,62 @@
+package com.compilerprogramming.ezlang.compiler;
+
+import com.compilerprogramming.ezlang.compiler.codegen.CodeGen;
+import com.compilerprogramming.ezlang.compiler.node.Node;
+import com.compilerprogramming.ezlang.compiler.node.StopNode;
+
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.List;
+import java.util.function.Consumer;
+
+public class IterPeeps2 {
+
+ static void dfs(Node root, Consumer consumer, BitSet visited) {
+ visited.set(root._nid);
+ /* For each successor node */
+ for (int i = 0; i < root.nOuts(); i++) {
+ Node S = root.out(i);
+ if (S == null)
+ continue;
+ if (!visited.get(S._nid))
+ dfs(S, consumer, visited);
+ }
+ consumer.accept(root);
+ }
+
+ public static List rpo(Node root) {
+ List nodes = new ArrayList<>();
+ // note add below prepends
+ dfs(root, (n)->nodes.add(0,n), new BitSet());
+ return nodes;
+ }
+
+ public void iterate(CodeGen code) {
+ final Node startNode = code._start; // We need the start node to get to the full graph
+ int iter = 1;
+ int count = 0; // Count of nodes we peepholed
+ for (;;iter++) {
+ List rpos = rpo(startNode);
+ count += rpos.size();
+ boolean progress = false;
+ for (int i = 0; i < rpos.size(); i++) {
+ Node n = rpos.get(i);
+ if (n.isDead()) continue;
+ Node x = n.peepholeOpt();
+ if (x != null) {
+ progress = true;
+ if (x.isDead()) continue;
+ // peepholeOpt can return brand-new nodes, needing an initial type set
+ if( x._type==null ) x.setType(x.compute());
+ if (x != n) n.subsume(x);
+ }
+ if( n.isUnused() && !(n instanceof StopNode) )
+ n.kill(); // Just plain dead
+ }
+ if (!progress) break;
+ }
+ System.out.println("Completed in " + iter + " iterations; peepholed " + count + " nodes");
+// if( show )
+// System.out.println(new GraphVisualizer().generateDotOutput(stopNode,null,null));
+ }
+}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/CodeGen.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/CodeGen.java
index b0b4ac7..f07979a 100644
--- a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/CodeGen.java
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/CodeGen.java
@@ -51,7 +51,7 @@ public CodeGen( String src, TypeInteger arg, long workListSeed ) {
_stop = new StopNode(src);
_src = src;
_arg = arg;
- _iter = new IterPeeps(workListSeed);
+ _iter = new IterPeeps2();
_main = makeFun(TypeTuple.MAIN, Type.BOTTOM);
P = new Compiler(this,arg);
}
@@ -180,7 +180,7 @@ public CodeGen parse() {
// ---------------------------
// Iterator peepholes.
- public final IterPeeps _iter;
+ public final IterPeeps2 _iter;
// Stop tracking deps while assertin
public boolean _midAssert;
// Statistics on peepholes
@@ -206,8 +206,6 @@ public CodeGen opto() {
// loop unroll, peel, RCE, etc
return this;
}
- public N add( N n ) { return _iter.add(n); }
- public void addAll( Ary ary ) { _iter.addAll(ary); }
// ---------------------------
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/GlobalCodeMotion.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/GlobalCodeMotion.java
index 2e8aed4..d9cdf76 100644
--- a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/GlobalCodeMotion.java
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/GlobalCodeMotion.java
@@ -1,7 +1,7 @@
package com.compilerprogramming.ezlang.compiler.codegen;
import com.compilerprogramming.ezlang.compiler.util.Ary;
-import com.compilerprogramming.ezlang.compiler.IterPeeps.WorkList;
+import com.compilerprogramming.ezlang.compiler.util.WorkList;
import com.compilerprogramming.ezlang.compiler.util.Utils;
import com.compilerprogramming.ezlang.compiler.node.*;
import com.compilerprogramming.ezlang.compiler.type.*;
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/RegAlloc.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/RegAlloc.java
index 68ddc32..e14b3a1 100644
--- a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/RegAlloc.java
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/RegAlloc.java
@@ -397,7 +397,7 @@ boolean splitByLoop( byte round, LRG lrg ) {
// If the minLoopDepth is less than the maxLoopDepth: for-all defs and
// uses, if at minLoopDepth or lower, split after def and before use.
for( Node n : _ns ) {
- if( n instanceof SplitNode && min!=max ) continue; // Ignoring splits; since spilling need to split in a deeper loop
+ if( n instanceof SplitNode ) continue; // Ignoring splits; since spilling need to split in a deeper loop
if( n.isDead() ) continue; // Some Cloneable went dead by other spill changes
// If this is a 2-address commutable op (e.g. AddX86, MulX86) and the rhs has only a single user,
// commute the inputs... which chops the LHS live ranges' upper bound to just the RHS.
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/node/CallEndNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/node/CallEndNode.java
index 35e410b..f1422e4 100644
--- a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/node/CallEndNode.java
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/node/CallEndNode.java
@@ -89,11 +89,6 @@ public Node idealize() {
fun.setDef(1, Compiler.XCTRL); // No default/unknown StartNode caller
fun.setDef(2,call.ctrl()); // Bypass the Call;
fun.ret().setDef(3,null); // Return is folding also
- CodeGen.CODE.addAll(fun._outputs);
- // Repeat defs 1 layer down, for users of Parm (Phis)
- for( Node parm : fun._outputs )
- if( parm instanceof ParmNode )
- CodeGen.CODE.addAll(parm._outputs);
// Inlining immediately blows all cache idepth fields past the inline point.
// Bump the global version number invalidating them en-masse.
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/node/CallNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/node/CallNode.java
index 1a4f3d1..6c94ac9 100644
--- a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/node/CallNode.java
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/node/CallNode.java
@@ -129,7 +129,7 @@ private Node link( FunNode fun ) {
if( use instanceof ParmNode parm )
parm.addDef(parm._idx==0 ? new ConstantNode(cend()._rpc).peephole() : arg(parm._idx));
// Call end points to function return
- CodeGen.CODE.add(cend()).addDef(fun.ret());
+ cend().addDef(fun.ret());
return this;
}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/node/FunNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/node/FunNode.java
index b34d491..92fbca0 100644
--- a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/node/FunNode.java
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/node/FunNode.java
@@ -65,7 +65,6 @@ public ParmNode rpc() {
public void setSig( TypeFunPtr sig ) {
assert sig.isa(_sig);
if( _sig != sig ) {
- CODE.add(this);
_sig = sig;
}
}
@@ -89,8 +88,6 @@ public Node idealize() {
// Some linked path dies
Node progress = deadPath();
if( progress!=null ) {
- if( nIns()==3 && in(2) instanceof CallNode call )
- CODE.add(call.cend()); // If Start and one call, check for inline
return progress;
}
@@ -114,8 +111,6 @@ public Node idealize() {
// If down to a single input, become that input
if( nIns()==2 && !hasPhi() ) {
- CODE.add( CODE._stop ); // Stop will remove dead path
- CODE.add( _ret ); // Return will compute to TOP control
return in(1); // Collapse if no Phis; 1-input Phis will collapse on their own
}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/node/IfNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/node/IfNode.java
index c5f92a0..fd6cd7f 100644
--- a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/node/IfNode.java
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/node/IfNode.java
@@ -9,7 +9,6 @@ public class IfNode extends CFGNode implements MultiNode {
public IfNode(Node ctrl, Node pred) {
super(ctrl, pred);
- CodeGen.CODE.add(this); // Because idoms are complex, just add it
}
public IfNode(IfNode iff) { super(iff); }
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/node/LoadNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/node/LoadNode.java
index 6b15f64..ea917ec 100644
--- a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/node/LoadNode.java
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/node/LoadNode.java
@@ -64,9 +64,6 @@ public Node idealize() {
// Simple load-after-MemMerge to a known alias can bypass. Happens when inlining.
if( mem instanceof MemMergeNode mem2 ) {
Node memA = mem2.alias(_alias);
- for( Node ld : memA._outputs )
- if( ld instanceof LoadNode )
- CodeGen.CODE.add(ld);
setDef(1,memA);
return this;
}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/node/MemMergeNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/node/MemMergeNode.java
index 885683d..0265941 100644
--- a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/node/MemMergeNode.java
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/node/MemMergeNode.java
@@ -163,7 +163,6 @@ void _useless( ) {
if( in(i) instanceof PhiNode phi ) {
// Do an eager useless-phi removal
Node in = phi.peephole();
- CodeGen.CODE.addAll(phi._outputs);
phi.moveDepsToWorklist();
if( in != phi ) {
if( !phi.iskeep() ) // Keeping phi around for parser elsewhere
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/node/Node.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/node/Node.java
index 92664b6..b91ac45 100644
--- a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/node/Node.java
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/node/Node.java
@@ -178,7 +178,6 @@ public N setDef(int idx, N new_def ) {
if( old_def != null ) { // If the old def exists, remove a def->use edge
if( old_def.delUse(this) ) // If we removed the last use, the old def is now dead
old_def.kill(); // Kill old def
- else CODE.add(old_def); // Else old lost a use, so onto worklist
}
moveDepsToWorklist();
// Return new_def for easy flow-coding
@@ -264,7 +263,7 @@ public void kill( ) {
while( nIns()>0 ) { // Set all inputs to null, recursively killing unused Nodes
Node old_def = _inputs.removeLast();
// Revisit neighbor because removed use
- if( old_def != null && CODE.add(old_def).delUse(this) )
+ if( old_def != null && old_def.delUse(this) )
old_def.kill(); // If we removed the last use, the old def is now dead
}
assert isDead(); // Really dead now
@@ -312,8 +311,6 @@ public void subsume( Node nnn ) {
int idx = n._inputs.find(this);
n._inputs.set(idx,nnn);
nnn.addUse(n);
- CODE.add(n);
- CODE.addAll(n._outputs);
}
kill();
}
@@ -488,7 +485,6 @@ public Type setType(Type type) {
assert old==null || type.isa(old); // Since _type not set, can just re-run this in assert in the debugger
if( old == type ) return old;
_type = type; // Set _type late for easier assert debugging
- CODE.addAll(_outputs);
moveDepsToWorklist();
return old;
}
@@ -571,7 +567,6 @@ N addDep( N dep ) {
// Move the dependents onto a worklist, and clear for future dependents.
public void moveDepsToWorklist( ) {
if( _deps==null ) return;
- CODE.addAll(_deps);
_deps.clear();
}
diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/node/RegionNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/node/RegionNode.java
index db58fd3..4e20611 100644
--- a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/node/RegionNode.java
+++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/node/RegionNode.java
@@ -64,7 +64,6 @@ public Node idealize() {
PhiNode phi2 = phi.in(i) instanceof PhiNode phi2x && phi2x.region()==region ? phi2x : null;
for( int j=1; j {
+ private Node[] _es = new Node[1];
+ private int _len;
+ private final BitSet _on = new BitSet();
+ private final Random _random = new Random(123);
+
+ public E push(E node) {
+ if (node == null) return null;
+ if (!_on.get(node._nid)) {
+ _on.set(node._nid);
+ if (_len == _es.length)
+ _es = Arrays.copyOf(_es, _len << 1);
+ _es[_len++] = node;
+ }
+ return node;
+ }
+
+ @SuppressWarnings("unchecked")
+ public E pop() {
+ if (_len == 0) return null;
+ int idx = _random.nextInt(_len);
+ E node = (E)_es[idx];
+ _es[idx] = _es[--_len];
+ _on.clear(node._nid);
+ return node;
+ }
+}
\ No newline at end of file
diff --git a/seaofnodes/src/test/java/com/compilerprogramming/ezlang/compiler/EvalRisc5.java b/seaofnodes/src/test/java/com/compilerprogramming/ezlang/compiler/EvalRisc5.java
new file mode 100644
index 0000000..6b83f86
--- /dev/null
+++ b/seaofnodes/src/test/java/com/compilerprogramming/ezlang/compiler/EvalRisc5.java
@@ -0,0 +1,356 @@
+package com.compilerprogramming.ezlang.compiler;
+
+/** Simple RISC5 CPU Emulator
+ *
+ * Original works credit Charles Lohr.
+ * This is a complete rewrite in Java.
+ *
+ * Load code into memory and go!
+ */
+
+
+import com.compilerprogramming.ezlang.compiler.codegen.Encoding;
+import com.compilerprogramming.ezlang.compiler.node.cpus.riscv.riscv;
+import com.compilerprogramming.ezlang.compiler.util.Utils;
+
+import java.util.Arrays;
+
+public class EvalRisc5 {
+
+ // Memory image, always 0-based
+ public final byte[] _buf;
+
+ // GPRs
+ final long[] regs;
+ // FRs
+ final double[] fregs;
+ // PC
+ int _pc;
+
+ // Start of free memory for allocation
+ int _heap;
+
+ // Cycle counters
+ int _cycle;
+
+ EvalRisc5( byte[] buf, int stackSize ) {
+ _buf = buf;
+ regs = new long[32];
+ fregs = new double[32];
+ // Stack grows down, heap grows up
+ regs[riscv.RSP] = _heap = stackSize;
+ _pc = 0;
+ }
+
+ // Little endian
+ public int ld1s(int x) { return _buf[x ] ; }
+ public int ld1z(int x) { return _buf[x ]&0xFF; }
+ public int ld2s(int x) { return ld1z(x) | ld1s(x+1)<< 8; }
+ public int ld2z(int x) { return ld1z(x) | ld1z(x+1)<< 8; }
+ public int ld4s(int x) { return ld2z(x) | ld2s(x+2)<<16; }
+ public long ld8 (int x) { return (long)ld4s(x)&0xFFFFFFFFL | ((long)ld4s(x+4))<<32; }
+
+ public float ld4f(int x) { return Float.intBitsToFloat(ld4s(x)); }
+ public double ld8f(int x) { return Double.longBitsToDouble(ld8(x)); }
+
+
+ public void st1(int x, int val) { _buf[x] = (byte)val; }
+ public void st2(int x, int val) { st1(x,val); st1(x+1,val>> 8); }
+ public void st4(int x, int val) {st2(x,val); st2(x+2,val>>16); }
+ public void st8(int x, long val) { st4(x,(int)val); st4(x+4,(int)(val>>32)); }
+
+ // Note: only a few bits are used. (Machine = 3, User = 0)
+ // Bits 0..1 = privilege.
+ // Bit 2 = WFI (Wait for interrupt)
+ // Bit 3+ = Load/Store reservation LSBs.
+ int _extraflags;
+
+ public int step( int maxops ) {
+ int trap = 0;
+ long rval = 0;
+ double frval = 0;
+ int pc = _pc;
+ int cycle = _cycle;
+ boolean is_f = false;
+ outer:
+ for( int icount = 0; icount < maxops; icount++ ) {
+ int ir = 0;
+ rval = 0;
+ cycle++;
+
+ if( pc >= _buf.length ) {
+ trap = 1 + 1; // Handle access violation on instruction read.
+ break;
+ }
+ if( (pc & 3)!=0 ) {
+ trap = 1; // Handle PC-misaligned access
+ break;
+ }
+ // Load instruction from image buffer at PC
+ ir = ld4s( pc );
+ int rdid = (ir >> 7) & 0x1f;
+
+ switch( ir & 0x7f ) {
+ case 0x37: // LUI (0b0110111)
+ rval = ( ir & 0xfffff000 );
+ break;
+ case 0x17: // AUIPC (0b0010111)
+ rval = pc + ( ir & 0xfffff000 );
+ break;
+ case 0x6F: { // JAL (0b1101111)
+ int reladdy = ((ir & 0x80000000)>>11) | ((ir & 0x7fe00000)>>20) | ((ir & 0x00100000)>>9) | ((ir&0x000ff000));
+ if( (reladdy & 0x00100000)!=0 ) reladdy |= 0xffe00000; // Sign extension.
+ rval = pc + 4;
+ pc = pc + reladdy - 4;
+ break;
+ }
+ case 0x67: { // JALR (0b1100111)
+ int imm_se = ir >> 20; // Sign extend 12 bits
+ rval = pc + 4;
+ pc = ((int)regs[ (ir >> 15) & 0x1f ] + imm_se) & ~1;
+ // Return from top-level ; exit sim
+ if( pc==0 ) {
+ if( rdid!=0 ) regs[rdid] = rval; // Write PC into register before exit
+ break outer;
+ }
+ // Inline CALLOC effect
+ if( pc == Encoding.SENTINEL_CALLOC ) {
+ int size = (int)(regs[10]*regs[11]);
+ regs[10] = _heap; // Return next free address
+ // Pre-zeroed; epsilon (null) collector, never recycles memory so always zero
+ // Arrays.fill(_buf,_heap,_heap+size, (byte) 0 );
+ _heap += size;
+ pc = (int)(rval - 4); // Unwind PC, as-if returned from calloc
+ } else
+ pc -= 4;
+ break;
+ }
+ case 0x63: { // Branch (0b1100011)
+ int immm4 = ((ir & 0xf00)>>7) | ((ir & 0x7e000000)>>20) | ((ir & 0x80) << 4) | ((ir >> 31)<<12);
+ if( (immm4 & 0x1000)!=0 ) immm4 |= 0xffffe000;
+ long rs1 = regs[(ir >> 15) & 0x1f];
+ long rs2 = regs[(ir >> 20) & 0x1f];
+ immm4 = pc + immm4 - 4;
+ rdid = 0;
+ if( switch( (ir >> 12) & 0x7 ) {
+ case 0 -> rs1 == rs2;
+ case 1 -> rs1 != rs2;
+ case 4 -> rs1 < rs2;
+ case 5 -> rs1 >= rs2;
+ case 6 -> Long.compareUnsigned(rs1,rs2) < 0; //BLTU
+ case 7 -> Long.compareUnsigned(rs1,rs2) >= 0; //BGEU
+ default -> { trap = (2+1); yield false; }
+ } ) pc = immm4;
+ break;
+ }
+ case 0x03: { // Load (0b0000011)
+ int rs1 = (int)regs[(ir >> 15) & 0x1f]; // Address chop to 32b
+ int imm = ir >> 20;
+ int imm_se = imm | (( (imm & 0x800)!=0 ) ? 0xfffff000 : 0 );
+ int rsval = rs1 + imm_se;
+
+ if( rsval >= _buf.length-3 ) {
+ trap = (5+1);
+ rval = rsval;
+ } else {
+ switch( ( ir >> 12 ) & 0x7 ) {
+ //LB, LH, LW, LBU, LHU
+ case 0: rval = ld1s( rsval ); break;
+ case 1: rval = ld2s( rsval ); break;
+ case 2: rval = ld4s( rsval ); break;
+ case 3: rval = ld8 ( rsval ); break;
+ case 4: rval = ld1z( rsval ); break;
+ case 5: rval = ld2z( rsval ); break;
+ case 6: rval = ld4s( rsval ); break;
+ default: trap = (2+1);
+ }
+ }
+ break;
+ }
+ // floats
+ case 0x7: {
+ is_f = true;
+ int rs1 = (int)regs[(ir >> 15) & 0x1f]; // Address chop to 32b
+ int imm = ir >> 20;
+ int imm_se = imm | (( (imm & 0x800)!=0 ) ? 0xfffff000 : 0 );
+ int rsval = rs1 + imm_se;
+
+ if( rsval >= _buf.length-3 ) {
+ trap = (5+1);
+ rval = rsval;
+ } else {
+ switch( ( ir >> 12 ) & 0x7 ) {
+ case 2: frval = ld4f( rsval ); break;
+ case 3: frval = ld8f( rsval ); break;
+ default: trap = (2+1);
+ }
+ }
+ break;
+ }
+
+ case 0x23: { // Store 0b0100011
+ int rs1 = (int)regs[(ir >> 15) & 0x1f]; // Address chop to 32b
+ long rs2 = regs[(ir >> 20) & 0x1f];
+ int addy = ( ( ir >> 7 ) & 0x1f ) | ( ( ir & 0xfe000000 ) >> 20 );
+ if( (addy & 0x800)!=0 ) addy |= 0xfffff000;
+ addy += rs1;
+ rdid = 0;
+ if( addy >= _buf.length-3 ) {
+ trap = (7+1); // Store access fault.
+ rval = addy;
+ } else {
+ switch( ( ir >> 12 ) & 0x7 ) { //SB, SH, SW
+ case 0: st1( addy, (int)rs2 ); break;
+ case 1: st2( addy, (int)rs2 ); break;
+ case 2: st4( addy, (int)rs2 ); break;
+ case 3: st8( addy, rs2 ); break;
+ default: trap = (2+1);
+ }
+ }
+ break;
+ }
+ case 0x13: // Op-immediate 0b0010011
+ case 0x33: { // Op 0b0110011
+ int imm = ir >> 20;
+ imm = imm | (( (imm & 0x800)!=0 ) ? 0xfffff000 : 0);
+ long rs1 = regs[(ir >> 15) & 0x1f];
+ boolean is_reg = (ir & 0x20)!=0;
+ long rs2 = is_reg ? regs[imm & 0x1f] : imm;
+
+ // Insert the detection here
+ int rs1id = (ir >> 15) & 0x1f;
+ int funct3 = (ir >> 12) & 0x7;
+
+ if( is_reg && ( ir & 0x02000000 )!=0 ) {
+ switch( (ir>>12)&7 ) { //0x02000000 = RV32M
+ case 0: rval = rs1 * rs2; break; // MUL
+ case 1: rval = (int)((long)((int)rs1) * (long)((int)rs2)) >> 32; break; // MULH
+ case 2: rval = (int)((long)((int)rs1) * (long)rs2) >> 32; break; // MULHSU
+ case 3: rval = (int)((long)rs1 * (long)rs2) >> 32; break; // MULHU
+ case 4: if( rs2 == 0 ) rval = -1; else rval = ((int)rs1 == Integer.MIN_VALUE && (int)rs2 == -1) ? rs1 : ((int)rs1 / (int)rs2); break; // DIV
+ case 5: if( rs2 == 0 ) rval = 0xffffffff; else rval = rs1 / rs2; break; // DIVU
+ case 6: if( rs2 == 0 ) rval = rs1; else rval = ((int)rs1 == Integer.MIN_VALUE && (int)rs2 == -1) ? 0 : ((int)((int)rs1 % (int)rs2)); break; // REM
+ case 7: if( rs2 == 0 ) rval = rs1; else rval = rs1 % rs2; break; // REMU
+ }
+ } else {
+ rval = switch( (ir >> 12) & 7 ) { // These could be either op-immediate or op commands. Be careful.
+ case 0 -> (is_reg && (ir & 0x40000000) != 0) ? (rs1 - rs2) : (rs1 + rs2);
+ case 1 -> rs1 << (rs2 & 0x1F);
+ case 2 -> (rs1 < rs2) ? 1 : 0;
+ case 3 -> (rs1 < rs2) ? 1 : 0;
+ case 4 -> rs1 ^ rs2;
+ case 5 -> (ir & 0x40000000) != 0 ? (((int) rs1) >> (rs2 & 0x1F)) : (rs1 >> (rs2 & 0x1F));
+ case 6 -> rs1 | rs2;
+ case 7 -> rs1 & rs2;
+ default -> rval;
+ };
+ }
+ break;
+ }
+ case 0x0f: // 0b0001111
+ rdid = 0; // fencetype = (ir >> 12) & 0b111; We ignore fences in this impl.
+ break;
+ case 0x73: // Zifencei+Zicsr (0b1110011)
+ trap = (2+1); // Note micrrop 0b100 == undefined.
+ break;
+
+ case 0x2f: { // RV32A (0b00101111)
+ long rs1 = regs[(ir >> 15) & 0x1f];
+ long rs2 = regs[(ir >> 20) & 0x1f];
+ int irmid = ( ir>>27 ) & 0x1f;
+
+ // We don't implement load/store from UART or CLNT with RV32A here.
+ if( rs1 >= _buf.length-3 ) {
+ trap = (7+1); //Store/AMO access fault
+ rval = rs1;
+ } else {
+ rval = ld4s( (int)rs1 );
+
+ // Referenced a little bit of https://github.com/franzflasch/riscv_em/blob/master/src/core/core.c
+ boolean dowrite = true;
+ switch( irmid ) {
+ case 2: //LR.W (0b00010)
+ dowrite = false;
+ _extraflags = (_extraflags & 0x07) | (int)(rs1<<3);
+ break;
+ case 3: { //SC.W (0b00011) (Make sure we have a slot, and, it's valid)
+ boolean xflag = _extraflags >> 3 != ( rs1 & 0x1fffffff );
+ rval = xflag ? 1 : 0; // Validate that our reservation slot is OK.
+ dowrite = !xflag; // Only write if slot is valid.
+ break;
+ }
+ case 0: rs2 += rval; break; // AMOADD.W (0b00000)
+ case 1: break; // AMOSWAP.W (0b00001)
+ case 4: rs2 ^= rval; break; // AMOXOR.W (0b00100)
+ case 8: rs2 |= rval; break; // AMOOR.W (0b01000)
+ case 12: rs2 &= rval; break; // AMOAND.W (0b01100)
+ case 16: rs2 = Math.min( rs2, rval ); break; //AMOMIN.W (0b10000)
+ case 20: rs2 = Math.max( rs2, rval ); break; //AMOMAX.W (0b10100)
+ case 24: rs2 = Math.min( rs2, rval ); break; //AMOMINU.W (0b11000)
+ case 28: rs2 = Math.max( rs2, rval ); break; //AMOMAXU.W (0b11100)
+ default: trap = (2+1); dowrite = false; break; //Not supported.
+ }
+ if( dowrite ) st4( (int)rs1, (int)rs2 );
+ }
+ break;
+ }
+
+ case 0x53: {
+ is_f = true;
+ int rs1 = (ir >> 15) & 0x1F;
+ int rs2 = (ir >> 20) & 0x1F;
+ double lhs = fregs[rs1];
+ double rhs = fregs[rs2];
+ int func5 = (ir >> 27) & 0x1F;
+ int func2 = (ir >> 25) & 0x3;
+ if( func2 != 1 ) throw Utils.TODO(); // 1==double
+ switch( func5 ) {
+ case 0: frval = lhs + rhs; break; // fadd.d
+ case 1: frval = lhs - rhs; break; // fsub.d
+ case 2: frval = lhs * rhs; break; // fmul.d
+ case 3: frval = lhs / rhs; break; // fdiv.d
+ case 5: frval = Math.min(lhs,rhs); break; // fmin.d, also used for move
+ case 20:
+ is_f = false; // Output into a GPR
+ rval = switch( (ir >> 12) & 7 ) {
+ case 0 -> lhs <= rhs ? 1 : 0;
+ case 1 -> lhs == rhs ? 1 : 0;
+ case 2 -> lhs < rhs ? 1 : 0;
+ default -> throw Utils.TODO();
+ };
+ break;
+ case 26: frval = (double)regs[rs1]; break; // fcvt.w.d
+ default:
+ trap = (2+1); // Fault: Invalid opcode.
+ throw Utils.TODO();
+ }
+ break;
+ }
+
+ default: trap = (2+1); // Fault: Invalid opcode.
+ }
+
+ // If there was a trap, do NOT allow register writeback.
+ if( trap!=0 ) {
+ _pc = pc;
+ break;
+ }
+
+ if( rdid!=0 || is_f) {
+ if(is_f) {fregs[rdid] = frval; is_f = false;} // Write back register.
+ else regs[rdid] = rval; // Write back register.
+ }
+
+ pc += 4;
+ }
+
+ // Handle traps and interrupts.
+ if( trap!=0 )
+ return trap;
+
+ _cycle = cycle;
+ _pc = pc;
+ return 0;
+ }
+}
+
diff --git a/seaofnodes/src/test/java/com/compilerprogramming/ezlang/compiler/TestRisc5.java b/seaofnodes/src/test/java/com/compilerprogramming/ezlang/compiler/TestRisc5.java
new file mode 100644
index 0000000..0799433
--- /dev/null
+++ b/seaofnodes/src/test/java/com/compilerprogramming/ezlang/compiler/TestRisc5.java
@@ -0,0 +1,53 @@
+package com.compilerprogramming.ezlang.compiler;
+
+import com.compilerprogramming.ezlang.compiler.codegen.CodeGen;
+import com.compilerprogramming.ezlang.compiler.node.FunNode;
+import com.compilerprogramming.ezlang.compiler.node.Node;
+import com.compilerprogramming.ezlang.compiler.node.cpus.riscv.riscv;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import static org.junit.Assert.assertEquals;
+
+// Runs 32-bit R5 code in an emulator
+public class TestRisc5 {
+
+ // Compile and run a simple program
+ public static EvalRisc5 build( String dir, String file, String main, int arg, int spills, boolean print ) throws IOException {
+ String src = Files.readString(Path.of(dir+"/"+file+".ez"));
+ return buildSrc(src, main, arg, spills, print);
+ }
+
+ public static EvalRisc5 buildSrc( String src, String main, int arg, int spills, boolean print ) throws IOException {
+ // Compile and export Simple
+ CodeGen code = new CodeGen(src).driver("riscv", "SystemV",null);
+ if( print ) { code.print_as_hex(); System.out.print(code.asm()); }
+
+ // Allocation quality not degraded when a baseline was supplied
+ if( spills >= 0 ) {
+ int delta = spills>>3;
+ if( delta==0 ) delta = 1;
+ assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta);
+ }
+
+ // Image
+ byte[] image = new byte[1<<20]; // A megabyte (1024*1024 bytes)
+ EvalRisc5 R5 = new EvalRisc5(image, 1<<16);
+ // Code at offset 0
+ System.arraycopy(code._encoding.bits(), 0, image, 0, code._encoding.bits().length);
+ // Initial incoming int arg
+ R5.regs[riscv.A0] = arg;
+ // malloc memory starts at 64K and goes up
+
+ // Look up starting PC or zero if not given
+ if( main!=null )
+ for( Node use : code._start._outputs )
+ if( use instanceof FunNode fun && main.equals(fun._name) )
+ R5._pc = code._encoding._opStart[fun._nid];
+
+ return R5;
+ }
+
+}
diff --git a/seaofnodes/src/test/java/com/compilerprogramming/ezlang/compiler/TestSONTypes.java b/seaofnodes/src/test/java/com/compilerprogramming/ezlang/compiler/TestSONTypes.java
index b2166ae..6b929c4 100644
--- a/seaofnodes/src/test/java/com/compilerprogramming/ezlang/compiler/TestSONTypes.java
+++ b/seaofnodes/src/test/java/com/compilerprogramming/ezlang/compiler/TestSONTypes.java
@@ -1,6 +1,7 @@
package com.compilerprogramming.ezlang.compiler;
import com.compilerprogramming.ezlang.compiler.codegen.CodeGen;
+import com.compilerprogramming.ezlang.compiler.node.cpus.riscv.riscv;
import com.compilerprogramming.ezlang.lexer.Lexer;
import com.compilerprogramming.ezlang.parser.Parser;
import com.compilerprogramming.ezlang.parser.ShortCircuitLowerer;
@@ -9,6 +10,8 @@
import org.junit.Ignore;
import org.junit.Test;
+import java.io.IOException;
+
import static com.compilerprogramming.ezlang.compiler.Main.PORTS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -98,7 +101,7 @@ func foo(a: Int, b: Int)->[Int]? {}
@Test
public void test10() {
String src = """
-func foo(a: Int, b: Int)->Int {
+func foo(a: Int, b: Int)->Int {
return a+b;
}
""";
@@ -108,7 +111,7 @@ func foo(a: Int, b: Int)->Int {
@Test
public void test11() {
String src = """
-func foo()->[Int] {
+func foo()->[Int] {
return new [Int]{};
}
""";
@@ -118,7 +121,7 @@ func foo()->[Int] {
@Test
public void test12() {
String src = """
-func foo()->[Int] {
+func foo()->[Int] {
return new [Int]{1};
}
""";
@@ -128,7 +131,7 @@ func foo()->[Int] {
@Test
public void test13() {
String src = """
-func foo()->[Int] {
+func foo()->[Int] {
return new [Int]{42,84};
}
""";
@@ -139,7 +142,7 @@ func foo()->[Int] {
public void test14() {
String src = """
struct T4 { var i: Int }
-func foo()->T4 {
+func foo()->T4 {
return new T4{i=1};
}
""";
@@ -210,7 +213,7 @@ func foo(a: Int)->Int {
public void test15() {
String src = """
struct T5 { var i: Int; var j: Int }
-func foo()->T5 {
+func foo()->T5 {
return new T5{i=23, j=32};
}
""";
@@ -220,7 +223,7 @@ func foo()->T5 {
@Test
public void test16() {
String src = """
-func foo()->Int {
+func foo()->Int {
return new [Int]{42,84}[1];
}
""";
@@ -228,7 +231,7 @@ func foo()->Int {
}
@Test
- public void testArrayLengthUnaryOperator() {
+ public void testArrayLengthUnaryOperatorLiteral() {
String src = """
func foo()->Int {
return #new [Int]{42,84};
@@ -240,7 +243,7 @@ func foo()->Int {
public void test17() {
String src = """
struct T5 { var i: Int; var j: Int }
-func foo()->Int {
+func foo()->Int {
return new T5{i=23, j=32}.j;
}
""";
@@ -251,7 +254,7 @@ func foo()->Int {
public void test18() {
String src = """
func bar()->Int { return 1 }
-func foo()->Int {
+func foo()->Int {
return bar();
}
""";
@@ -261,7 +264,7 @@ func foo()->Int {
@Test
public void test19() {
String src = """
-func foo()->Int {
+func foo()->Int {
var x = 1
return x
}
@@ -361,4 +364,962 @@ func main()->Int {
}
""", 0, null);
}
+
+ @Test
+ public void testMergeSortUsingRisc5Emulator() throws IOException {
+ EvalRisc5 R5 = TestRisc5.build("src/test/cases/mergsort", "sort", "main", 0, 59, false);
+ int trap = R5.step(100000);
+ assertEquals(0,trap);
+ assertEquals(1L, R5.regs[riscv.A0]);
+ }
+
+ @Test
+ public void testFibUsingRisc5Emulator() throws IOException {
+ EvalRisc5 R5 = TestRisc5.build("src/test/cases/fib", "fib", "foo", 0, 19, false);
+ int trap = R5.step(100000);
+ assertEquals(0,trap);
+ assertEquals(89L, R5.regs[riscv.A0]);
+ }
+
+ @Test
+ public void testSieveUsingRisc5Emulator() throws IOException {
+ EvalRisc5 R5 = TestRisc5.build("src/test/cases/sieve", "sieve", "main", 0, 106, false);
+ int trap = R5.step(100000);
+ assertEquals(0,trap);
+ assertEquals(1L, R5.regs[riscv.A0]);
+ }
+
+ private static void runRisc5(String src, String entry, long expected) throws IOException {
+ EvalRisc5 risc5 = TestRisc5.buildSrc(src, entry, 0, -1, false);
+ assertEquals(0, risc5.step(1_000_000));
+ assertEquals(expected, risc5.regs[riscv.A0]);
+ }
+ @Test
+ public void testFunction1() throws IOException {
+ String src = """
+ func foo()->Int {
+ return 42;
+ }
+ """;
+ runRisc5(src, "foo", 42L);
+ }
+
+ @Test
+ public void testFunction2() throws IOException {
+ String src = """
+ func bar()->Int {
+ return 42;
+ }
+ func foo()->Int {
+ return bar();
+ }
+ """;
+ runRisc5(src, "foo", 42L);
+ }
+
+ @Test
+ @Ignore("RISC5 backend does not yet compile or execute this interpreter case")
+ public void testFunction3() throws IOException {
+ String src = """
+ func negate(n: Int)->Int {
+ return -n;
+ }
+ func foo()->Int {
+ return negate(42);
+ }
+ """;
+ runRisc5(src, "foo", -42L);
+ }
+
+ @Test
+ public void testFunction4() throws IOException {
+ String src = """
+ func foo(x: Int, y: Int)->Int { return x+y; }
+ func bar()->Int { var t = foo(1,2); return t+1; }
+ """;
+ runRisc5(src, "bar", 4L);
+ }
+
+ @Test
+ public void testFunction5() throws IOException {
+ String src = """
+ func bar()->Int { var t = new [Int] {1,21,3}; return t[1]; }
+ """;
+ runRisc5(src, "bar", 21L);
+ }
+
+ @Test
+ public void testFunction6() throws IOException {
+ String src = """
+ struct Test
+ {
+ var field: Int
+ }
+ func foo()->Test
+ {
+ var test = new Test{ field = 42 }
+ return test
+ }
+ func bar()->Int { return foo().field }
+ """;
+ runRisc5(src, "bar", 42L);
+ }
+
+ @Test
+ public void testFunction7() throws IOException {
+ String src = """
+ func bar(arg: Int)->Int {
+ if (arg)
+ return 42;
+ return 0;
+ }
+ func foo()->Int {
+ return bar(1);
+ }
+ """;
+ runRisc5(src, "foo", 42L);
+ }
+
+ @Test
+ public void testFunction8() throws IOException {
+ String src = """
+ func factorial(num: Int)->Int {
+ var result = 1
+ while (num > 1)
+ {
+ result = result * num
+ num = num - 1
+ }
+ return result
+ }
+ func foo()->Int {
+ return factorial(5);
+ }
+ """;
+ runRisc5(src, "foo", 120L);
+ }
+
+ @Test
+ public void testFunction9() throws IOException {
+ String src = """
+ func fib(n: Int)->Int {
+ var f1=1
+ var f2=1
+ var i=n
+ while( i>1 ){
+ var temp = f1+f2
+ f1=f2
+ f2=temp
+ i=i-1
+ }
+ return f2
+ }
+ func foo()->Int {
+ return fib(10)
+ }
+ """;
+ runRisc5(src, "foo", 89L);
+ }
+
+ @Test
+ public void testFunction10() throws IOException {
+ String src = """
+ func foo()->Int {
+ if (1)
+ return 2;
+ return 3;
+ }
+ """;
+ runRisc5(src, "foo", 2L);
+ }
+
+ @Test
+ @Ignore("RISC5 backend does not yet compile or execute this interpreter case")
+ public void testFunction11() throws IOException {
+ String src = """
+ func bar(data: [Int]) {
+ var j = 1
+ var k = 2
+ var m = 4
+ var n = k * m
+ j = n + j
+ data[0] = j
+ }
+ func foo()->Int {
+ var data = new [Int] {0}
+ bar(data)
+ return data[0]
+ }
+ """;
+ runRisc5(src, "foo", 9L);
+ }
+
+ @Test
+ @Ignore("RISC5 backend does not yet compile or execute this interpreter case")
+ public void testFunction12() throws IOException {
+ String src = """
+ func bar(data: [Int]) {
+ var j = 12345
+ if (data[0])
+ data[1] = 1 + j - 1234
+ else
+ data[2] = 123 + j + 10
+ }
+ func foo()->Int {
+ var data = new [Int] {0,0,0}
+ bar(data)
+ return data[0]+data[1]+data[2];
+ }
+ """;
+ runRisc5(src, "foo", 12478L);
+ }
+
+ @Test
+ @Ignore("RISC5 backend does not yet compile or execute this interpreter case")
+ public void testFunction13() throws IOException {
+ String src = """
+ func bar(data: [Int]) {
+ var j = 12345
+ if (data[0])
+ data[1] = 1 + j - 1234
+ else
+ data[2] = 123 - j + 10
+ }
+ func foo()->Int {
+ var data = new [Int] {0,0,0}
+ bar(data)
+ return data[0]+data[1]+data[2];
+ }
+ """;
+ runRisc5(src, "foo", -12212L);
+ }
+
+ @Test
+ @Ignore("RISC5 backend does not yet compile or execute this interpreter case")
+ public void testFunction14() throws IOException {
+ String src = """
+ func bar(data: [Int]) {
+ var j = 5
+ if (data[0])
+ data[1] = 10
+ else
+ data[2] = 15
+ data[3] = j + 21
+ }
+ func foo()->Int {
+ var data = new [Int] {0,0,0,0}
+ bar(data)
+ return data[0]+data[1]+data[2]+data[3];
+ }
+ """;
+ runRisc5(src, "foo", 5L);
+ }
+
+ @Test
+ @Ignore("RISC5 backend does not yet compile or execute this interpreter case")
+ public void testFunction15() throws IOException {
+ String src = """
+ func bar(data: [Int]) {
+ var j = 5
+ if (data[0])
+ data[1] = j * 10
+ else
+ data[2] = j * 15
+ data[3] = j * 21
+ }
+ func foo()->Int {
+ var data = new [Int] {0,0,0,0}
+ bar(data)
+ return data[0]+data[1]+data[2]+data[3];
+ }
+ """;
+ runRisc5(src, "foo", 5L);
+ }
+
+ @Test
+ @Ignore("RISC5 backend does not yet compile or execute this interpreter case")
+ public void testFunction16() throws IOException {
+ String src = """
+ func bar(data: [Int]) {
+ var j: Int
+ if (data[0]) {
+ j = 5
+ data[1] = 10
+ }
+ else {
+ data[2] = 15
+ j = 5
+ }
+ data[3] = j + 21
+ }
+ func foo()->Int {
+ var data = new [Int] {1,0,0,0}
+ bar(data)
+ return data[0]+data[1]+data[2]+data[3]
+ }
+ """;
+ runRisc5(src, "foo", 1L);
+ }
+
+ @Test
+ @Ignore("RISC5 backend does not yet compile or execute this interpreter case")
+ public void testFunction17() throws IOException {
+ String src = """
+ func bar(data: [Int]) {
+ var j: Int
+ var k: Int
+ if (data[0]) {
+ j = 4
+ k = 6
+ data[1] = j
+ }
+ else {
+ j = 7
+ k = 3
+ data[2] = k
+ }
+ data[3] = (j+k) * 21
+ }
+ func foo()->Int {
+ var data = new [Int] {1,0,0,0}
+ bar(data)
+ return data[0]+data[1]+data[2]+data[3]
+ }
+ """;
+ runRisc5(src, "foo", 1L);
+ }
+
+ @Test
+ @Ignore("RISC5 backend does not yet compile or execute this interpreter case")
+ public void testFunction18() throws IOException {
+ String src = """
+ func bar(data: [Int]) {
+ var i: Int
+ var stop = data[0]
+ var j = 21
+ i = 1
+ while ( i < stop ) {
+ j = (j - 20) * 21;
+ i = i + 1
+ }
+ data[1] = j
+ data[2] = i
+ }
+ func foo()->Int {
+ var data = new [Int] {2,0,0}
+ bar(data)
+ return data[0]+data[1]+data[2];
+ }
+ """;
+ runRisc5(src, "foo", 2L);
+ }
+
+ @Test
+ @Ignore("RISC5 backend does not yet compile or execute this interpreter case")
+ public void testFunction19() throws IOException {
+ String src = """
+ func bar(data: [Int]) {
+ var j = 1
+ if (j) j = 10
+ else j = data[0]
+ data[0] = j * 21 + data[1]
+ }
+ func foo()->Int {
+ var data = new [Int] {2,3}
+ bar(data)
+ return data[0]+data[1];
+ }
+ """;
+ runRisc5(src, "foo", 213L);
+ }
+
+ @Test
+ @Ignore("RISC5 backend does not yet compile or execute this interpreter case")
+ public void testFunction20() throws IOException {
+ String src = """
+ func bar(data: [Int]) {
+ var j = data[0]
+ if (j == 5)
+ j = j * 21 + 25 / j
+ data[1] = j
+ }
+ func foo()->Int {
+ var data = new [Int] {5,3}
+ bar(data)
+ return data[0]+data[1];
+ }
+ """;
+ runRisc5(src, "foo", 5L);
+ }
+
+ @Test
+ public void testFunction101() throws IOException {
+ String src = """
+ func foo()->Int
+ {
+ return null == null;
+ }
+
+ """;
+ runRisc5(src, "foo", 1L);
+ }
+
+ @Test
+ @Ignore("RISC5 backend does not yet compile or execute this interpreter case")
+ public void testFunction102() throws IOException {
+ String src = """
+ struct Foo
+ {
+ var next: Foo?
+ }
+ func foo()->Int
+ {
+ var f = new Foo{ next = null }
+ return null == f.next
+ }
+
+ """;
+ runRisc5(src, "foo", 1L);
+ }
+
+ @Test
+ public void testFunction103() throws IOException {
+ String src = """
+ struct Foo
+ {
+ var i: Int
+ }
+ func foo()->Int
+ {
+ var f = new [Foo?] { new Foo{i = 1}, null }
+ var f0 = f[0]
+ return null == f[1] && f0 != null && 1 == f0.i
+ }
+
+ """;
+ runRisc5(src, "foo", 1L);
+ }
+
+ @Test
+ public void testFunction104() throws IOException {
+ String src = """
+ func foo()->Int
+ {
+ return 1 == 1 && 2 == 2
+ }
+
+ """;
+ runRisc5(src, "foo", 1L);
+ }
+
+ @Test
+ public void testFunction105() throws IOException {
+ String src = """
+ func bar(a: Int, b: Int)->Int
+ {
+ return a+1 == b-1 && b / a == 2
+ }
+ func foo()->Int
+ {
+ return bar(3,5)
+ }
+ """;
+ runRisc5(src, "foo", 0L);
+ }
+
+ @Test
+ public void testFunction106() throws IOException {
+ String src = """
+ func bar(a: Int, b: Int)->Int
+ {
+ return a+1 == b-1 || b / a == 2
+ }
+ func foo()->Int
+ {
+ return bar(3,5)
+ }
+ """;
+ runRisc5(src, "foo", 1L);
+ }
+
+ @Test
+ public void testFunction107() throws IOException {
+ String src = """
+ func bar(a: [Int])->Int
+ {
+ return a[0]+a[2] == a[1]-a[2] || a[1] / a[0] == 2
+ }
+ func foo()->Int
+ {
+ return bar(new [Int] {3,5,1})
+ }
+ """;
+ runRisc5(src, "foo", 1L);
+ }
+
+ @Test
+ public void testMergeSort() throws IOException {
+ String src = """
+// based on the top-down version from https://en.wikipedia.org/wiki/Merge_sort
+// via https://github.com/SeaOfNodes/Simple
+func merge_sort(a: [Int], b: [Int], n: Int)
+{
+ copy_array(a, 0, n, b)
+ split_merge(a, 0, n, b)
+}
+
+func split_merge(b: [Int], begin: Int, end: Int, a: [Int])
+{
+ if (end - begin <= 1)
+ return;
+ var middle = (end + begin) / 2
+ split_merge(a, begin, middle, b)
+ split_merge(a, middle, end, b)
+ merge(b, begin, middle, end, a)
+}
+
+func merge(b: [Int], begin: Int, middle: Int, end: Int, a: [Int])
+{
+ var i = begin
+ var j = middle
+ var k = begin
+ while (k < end) {
+ // && and ||
+ var cond = 0
+ if (i < middle) {
+ if (j >= end) cond = 1;
+ else if (a[i] <= a[j]) cond = 1;
+ }
+ if (cond)
+ {
+ b[k] = a[i]
+ i = i + 1
+ }
+ else
+ {
+ b[k] = a[j]
+ j = j + 1
+ }
+ k = k + 1
+ }
+}
+
+func copy_array(a: [Int], begin: Int, end: Int, b: [Int])
+{
+ var k = begin
+ while (k < end)
+ {
+ b[k] = a[k]
+ k = k + 1
+ }
+}
+
+func eq(a: [Int], b: [Int], n: Int)->Int
+{
+ var result = 1
+ var i = 0
+ while (i < n)
+ {
+ if (a[i] != b[i])
+ {
+ result = 0
+ break
+ }
+ i = i + 1
+ }
+ return result
+}
+
+func main()->Int
+{
+ var a = new [Int]{10,9,8,7,6,5,4,3,2,1}
+ var b = new [Int]{ 0,0,0,0,0,0,0,0,0,0}
+ var expect = new [Int]{1,2,3,4,5,6,7,8,9,10}
+ merge_sort(a, b, 10)
+ return eq(a,expect,10)
+}
+""";
+ runRisc5(src, "main", 1L);
+ }
+
+ @Test
+ @Ignore("RISC5 backend does not yet compile or execute this interpreter case")
+ public void testFunction108() throws IOException {
+ String src = """
+ func make(len: Int, val: Int)->[Int]
+ {
+ return new [Int]{len=len, value=val}
+ }
+ func main()->Int
+ {
+ var arr = make(3,3);
+ var i = 0
+ while (i < 3) {
+ if (arr[i] != 3)
+ return 1
+ i = i + 1
+ }
+ return 0
+ }
+ """;
+ runRisc5(src, "main", 0L);
+ }
+
+ @Test
+ public void testFunction109() throws IOException {
+ String src = """
+func sieve(N: Int)->[Int]
+{
+ // The main Sieve array
+ var ary = new [Int]{len=N,value=0}
+ // The primes less than N
+ var primes = new [Int]{len=N/2,value=0}
+ // Number of primes so far, searching at index p
+ var nprimes = 0
+ var p=2
+ // Find primes while p^2 < N
+ while( p*p < N ) {
+ // skip marked non-primes
+ while( ary[p] ) {
+ p = p + 1
+ }
+ // p is now a prime
+ primes[nprimes] = p
+ nprimes = nprimes+1
+ // Mark out the rest non-primes
+ var i = p + p
+ while( i < N ) {
+ ary[i] = 1
+ i = i + p
+ }
+ p = p + 1
+ }
+
+ // Now just collect the remaining primes, no more marking
+ while ( p < N ) {
+ if( !ary[p] ) {
+ primes[nprimes] = p
+ nprimes = nprimes + 1
+ }
+ p = p + 1
+ }
+
+ // Copy/shrink the result array
+ var rez = new [Int]{len=nprimes,value=0}
+ var j = 0
+ while( j < nprimes ) {
+ rez[j] = primes[j]
+ j = j + 1
+ }
+ return rez
+}
+func eq(a: [Int], b: [Int], n: Int)->Int
+{
+ var result = 1
+ var i = 0
+ while (i < n)
+ {
+ if (a[i] != b[i])
+ {
+ result = 0
+ break
+ }
+ i = i + 1
+ }
+ return result
+}
+
+func main()->Int
+{
+ var rez = sieve(20)
+ var expected = new [Int]{2,3,5,7,11,13,17,19}
+ return eq(rez,expected,8)
+}
+""";
+ runRisc5(src, "main", 1L);
+ }
+
+ @Test
+ public void testFunction110() throws IOException {
+ String src = """
+func swap(arr: [Int], i: Int, j: Int) {
+ var tmp = arr[i];
+ arr[i] = arr[j];
+ arr[j] = tmp;
+}
+
+func partition(arr: [Int], low: Int, high: Int)->Int {
+ var pivot = arr[high];
+ var i = low;
+ var j = low;
+ while (j < high) {
+ if (arr[j] < pivot) {
+ swap(arr, i, j);
+ i = i + 1;
+ }
+ j = j + 1;
+ }
+ swap(arr, i, high);
+ return i;
+}
+
+func quicksort(arr: [Int], low: Int, high: Int) {
+ if (low < high) {
+ var p = partition(arr, low, high);
+ quicksort(arr, low, p - 1);
+ quicksort(arr, p + 1, high);
+ }
+}
+
+func eq(a: [Int], b: [Int], n: Int)->Int
+{
+ var result = 1
+ var i = 0
+ while (i < n)
+ {
+ if (a[i] != b[i])
+ {
+ result = 0
+ break
+ }
+ i = i + 1
+ }
+ return result
+}
+
+func main()->Int
+{
+ var nums = new [Int]{33, 10, 55, 71, 29, 3};
+ var expected = new [Int]{3,10,29,33,55,71}
+ quicksort(nums, 0, 5);
+ return eq(nums,expected,6)
+}
+""";
+ runRisc5(src, "main", 1L);
+ }
+
+ @Test
+ public void testRecursiveTwoCallsWithLiveValues() throws IOException {
+ String src = """
+func recurse(low: Int, high: Int)->Int {
+ if (low < high) {
+ var p = low;
+ recurse(low, p - 1);
+ return recurse(p + 1, high);
+ }
+ return 1;
+}
+
+func main()->Int {
+ return recurse(0, 5);
+}
+""";
+ runRisc5(src, "main", 1L);
+ }
+ @Test
+ public void testRecursiveTwoCallsWithArrayArgument() throws IOException {
+ String src = """
+func recurse(arr: [Int], low: Int, high: Int)->Int {
+ if (low < high) {
+ var p = low;
+ recurse(arr, low, p - 1);
+ return recurse(arr, p + 1, high);
+ }
+ return 1;
+}
+
+func main()->Int {
+ var nums = new [Int]{33, 10, 55, 71, 29, 3};
+ return recurse(nums, 0, 5);
+}
+""";
+ runRisc5(src, "main", 1L);
+ }
+ @Test
+ public void testRecursiveTwoCallsWithComputedPivot() throws IOException {
+ String src = """
+func choosePivot(arr: [Int], low: Int, high: Int)->Int {
+ return low;
+}
+
+func recurse(arr: [Int], low: Int, high: Int)->Int {
+ if (low < high) {
+ var p = choosePivot(arr, low, high);
+ recurse(arr, low, p - 1);
+ return recurse(arr, p + 1, high);
+ }
+ return 1;
+}
+
+func main()->Int {
+ var nums = new [Int]{33, 10, 55, 71, 29, 3};
+ return recurse(nums, 0, 5);
+}
+""";
+ runRisc5(src, "main", 1L);
+ }
+ @Test
+ public void testFunction110ReducedPartition() throws IOException {
+ String src = """
+func swap(arr: [Int], i: Int, j: Int) {
+ var tmp = arr[i];
+ arr[i] = arr[j];
+ arr[j] = tmp;
+}
+
+func partition(arr: [Int], low: Int, high: Int)->Int {
+ var pivot = arr[high];
+ var i = low;
+ var j = low;
+ while (j < high) {
+ if (arr[j] < pivot) {
+ swap(arr, i, j);
+ i = i + 1;
+ }
+ j = j + 1;
+ }
+ swap(arr, i, high);
+ return i;
+}
+
+func main()->Int {
+ var nums = new [Int]{33, 10, 55, 71, 29, 3};
+ return partition(nums, 0, 4);
+}
+""";
+ runRisc5(src, "main", 1L);
+ }
+ @Test
+ public void testFunction110ReducedRecursive() throws IOException {
+ String src = """
+func swap(arr: [Int], i: Int, j: Int) {
+ var tmp = arr[i];
+ arr[i] = arr[j];
+ arr[j] = tmp;
+}
+
+func partition(arr: [Int], low: Int, high: Int)->Int {
+ var pivot = arr[high];
+ var i = low;
+ var j = low;
+ while (j < high) {
+ if (arr[j] < pivot) {
+ swap(arr, i, j);
+ i = i + 1;
+ }
+ j = j + 1;
+ }
+ swap(arr, i, high);
+ return i;
+}
+
+func quicksort(arr: [Int], low: Int, high: Int) {
+ if (low < high) {
+ var p = partition(arr, low, high);
+ quicksort(arr, low, p - 1);
+ quicksort(arr, p + 1, high);
+ }
+}
+
+func main()->Int {
+ var nums = new [Int]{33, 10, 55, 71, 29, 3};
+ quicksort(nums, 0, 5);
+ return nums[0] * 100 + nums[5];
+}
+""";
+ runRisc5(src, "main", 371L);
+ }
+ @Test
+ public void testNullPhiThroughSSADestruction() throws IOException {
+ String src = """
+ struct Foo
+ {
+ var i: Int
+ }
+ func choose(c: Int)->Foo? {
+ var x: Foo?
+ x = new Foo{ i = 7 }
+ if (c == 0)
+ x = null
+ return x
+ }
+ func foo()->Int {
+ return choose(0) == null && choose(1) != null
+ }
+ """;
+ runRisc5(src, "foo", 1L);
+ }
+
+ @Test
+ @Ignore("RISC5 backend does not yet compile or execute this interpreter case")
+ public void testFunction111() throws IOException {
+ String src = """
+func swap(arr: [Float], i: Int, j: Int) {
+ var tmp = arr[i];
+ arr[i] = arr[j];
+ arr[j] = tmp;
+}
+
+func partition(arr: [Float], low: Int, high: Int)->Int {
+ var pivot = arr[high];
+ var i = low;
+ var j = low;
+ while (j < high) {
+ if (arr[j] < pivot) {
+ swap(arr, i, j);
+ i = i + 1;
+ }
+ j = j + 1;
+ }
+ swap(arr, i, high);
+ return i;
+}
+
+func quicksort(arr: [Float], low: Int, high: Int) {
+ if (low < high) {
+ var p = partition(arr, low, high);
+ quicksort(arr, low, p - 1);
+ quicksort(arr, p + 1, high);
+ }
+}
+
+func eq(a: [Float], b: [Float], n: Int)->Int
+{
+ var result = 1
+ var i = 0
+ while (i < n)
+ {
+ if (a[i] != b[i])
+ {
+ result = 0
+ break
+ }
+ i = i + 1
+ }
+ return result
+}
+
+func main()->Int
+{
+ var nums = new [Float]{33.4, 10.2, 55.1, 71.9, 29.9, 3.5};
+ var expected = new [Float]{3.5,10.2,29.9,33.4,55.1,71.9}
+ quicksort(nums, 0, 5);
+ return eq(nums,expected,6)
+}
+""";
+ runRisc5(src, "main", 1L);
+ }
+
+ @Test
+ public void testArrayLengthUnaryOperator() throws IOException {
+ String src = """
+ func foo()->Int {
+ var a = new [Int] {1,2,3,4}
+ var b = new [Int] {len=0,value=0}
+ return #a + #b
+ }
+ """;
+ runRisc5(src, "foo", 4L);
+ }
+
}
diff --git a/semantic/src/main/java/com/compilerprogramming/ezlang/semantic/SemaAssignTypes.java b/semantic/src/main/java/com/compilerprogramming/ezlang/semantic/SemaAssignTypes.java
index 605c6de..6e09fff 100644
--- a/semantic/src/main/java/com/compilerprogramming/ezlang/semantic/SemaAssignTypes.java
+++ b/semantic/src/main/java/com/compilerprogramming/ezlang/semantic/SemaAssignTypes.java
@@ -179,6 +179,13 @@ public void exit(AST.CallExpr callExpr) {
return;
validType(callExpr.callee.type, false, callExpr.lineNumber);
if (callExpr.callee.type instanceof EZType.EZTypeFunction f) {
+ if (callExpr.args.size() != f.args.size())
+ throw new CompilerException("Expected " + f.args.size() + " arguments but got " + callExpr.args.size(), callExpr.lineNumber);
+ for (int i = 0; i < callExpr.args.size(); i++) {
+ EZType argumentType = callExpr.args.get(i).type;
+ validType(argumentType, true, callExpr.lineNumber);
+ checkAssignmentCompatible(f.args.get(i).type, argumentType, callExpr.lineNumber);
+ }
callExpr.type = f.returnType;
}
else
diff --git a/semantic/src/test/java/com/compilerprogramming/ezlang/semantic/TestSemaAssignTypes.java b/semantic/src/test/java/com/compilerprogramming/ezlang/semantic/TestSemaAssignTypes.java
index 03cd68a..3eaf66d 100644
--- a/semantic/src/test/java/com/compilerprogramming/ezlang/semantic/TestSemaAssignTypes.java
+++ b/semantic/src/test/java/com/compilerprogramming/ezlang/semantic/TestSemaAssignTypes.java
@@ -453,7 +453,7 @@ func foo(arg: Foo?) {
}
// Nullable Bug call should fail
- @Test
+ @Test(expected = CompilerException.class)
public void test25() {
String src = """
struct Foo { var bar: Int }
@@ -529,4 +529,16 @@ func foo()->Int
""";
analyze(src, "foo", "func foo()->Int");
}
+ @Test(expected = CompilerException.class)
+ public void testCallRejectsMismatchedArrayElementType() {
+ String src = """
+ func eq(a: [Int]) {
+ }
+ func main() {
+ var values = new [Float] {1.0, 2.0}
+ eq(values)
+ }
+""";
+ analyze(src, "main", "func main()");
+ }
}