From fb80ada35ce2806c2f617a0e336d2ca4ab1d5d12 Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Tue, 22 Sep 2026 15:50:22 -0700 Subject: Graph cleanup Phis stay with regions, stop sinks to bottom, --- .../main/java/com/seaofnodes/simple/Parser.java | 2 +- .../simple/print/SimpleGraphAdapter.java | 1 + .../main/java/com/seaofnodes/simple/Parser.java | 2 +- .../simple/print/SimpleGraphAdapter.java | 1 + .../main/java/com/seaofnodes/simple/Parser.java | 2 +- .../simple/print/SimpleGraphAdapter.java | 1 + .../main/java/com/seaofnodes/simple/Parser.java | 2 +- .../simple/print/SimpleGraphAdapter.java | 1 + .../main/java/com/seaofnodes/simple/Parser.java | 3 +- .../simple/print/SimpleGraphAdapter.java | 1 + .../main/java/com/seaofnodes/simple/Parser.java | 3 +- .../simple/print/SimpleGraphAdapter.java | 1 + .../main/java/com/seaofnodes/simple/Parser.java | 3 +- .../simple/print/SimpleGraphAdapter.java | 1 + .../main/java/com/seaofnodes/simple/Parser.java | 3 +- .../simple/print/SimpleGraphAdapter.java | 1 + .../main/java/com/seaofnodes/simple/Parser.java | 3 +- .../simple/print/SimpleGraphAdapter.java | 1 + .../main/java/com/seaofnodes/simple/Parser.java | 3 +- .../simple/print/SimpleGraphAdapter.java | 1 + .../main/java/com/seaofnodes/simple/Parser.java | 3 +- .../simple/print/SimpleGraphAdapter.java | 1 + .../main/java/com/seaofnodes/simple/Parser.java | 3 +- .../simple/print/SimpleGraphAdapter.java | 1 + .../main/java/com/seaofnodes/simple/Parser.java | 3 +- .../simple/print/SimpleGraphAdapter.java | 1 + .../main/java/com/seaofnodes/simple/Parser.java | 3 +- .../simple/print/SimpleGraphAdapter.java | 1 + .../main/java/com/seaofnodes/simple/Parser.java | 3 +- .../simple/print/SimpleGraphAdapter.java | 1 + .../main/java/com/seaofnodes/simple/Parser.java | 3 +- .../simple/print/SimpleGraphAdapter.java | 1 + .../main/java/com/seaofnodes/simple/Parser.java | 3 +- .../simple/print/SimpleGraphAdapter.java | 1 + .../main/java/com/seaofnodes/simple/Parser.java | 3 +- .../simple/print/SimpleGraphAdapter.java | 1 + .../main/java/com/seaofnodes/simple/Parser.java | 3 +- .../simple/print/SimpleGraphAdapter.java | 1 + .../main/java/com/seaofnodes/simple/Parser.java | 3 +- .../simple/print/SimpleGraphAdapter.java | 1 + .../main/java/com/seaofnodes/simple/Parser.java | 3 +- .../simple/print/SimpleGraphAdapter.java | 3 +- graph/README.md | 9 ++++ .../java/com/seaofnodes/graph/GraphSnapshot.java | 2 +- graph/web/layout.js | 63 +++++++++++++++++----- graph/web/render.js | 13 +++-- graph/web/viewer.js | 8 ++- 47 files changed, 135 insertions(+), 42 deletions(-) diff --git a/chapter05/src/main/java/com/seaofnodes/simple/Parser.java b/chapter05/src/main/java/com/seaofnodes/simple/Parser.java index dbaa093d..359d0175 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/Parser.java @@ -166,6 +166,7 @@ public class Parser { // But first clone the scope and set it as current int ndefs = _scope.nIns(); ScopeNode fScope = _scope.dup(); // Duplicate current scope + fScope.ctrl(ifF); // The pending arm already starts at False _xScopes.push(fScope); // For graph visualization we need all scopes // Parse the true side @@ -175,7 +176,6 @@ public class Parser { // Parse the false side _scope = fScope; // Restore scope, then parse else block if any - ctrl(ifF); // Ctrl token is now set to ifFalse projection if (matchx("else")) { parseStatement(); fScope = _scope; diff --git a/chapter05/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter05/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index 2ec62125..0c2ac4b0 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -34,6 +34,7 @@ public class SimpleGraphAdapter extends GraphAdapter { } private Kind kind(Node n) { + if( n instanceof StopNode ) return Kind.STOP; if( n instanceof ScopeNode ) return Kind.SCOPE; if( n instanceof StartNode ) return Kind.CTRL; if( n instanceof RegionNode ) return Kind.REGION; diff --git a/chapter06/src/main/java/com/seaofnodes/simple/Parser.java b/chapter06/src/main/java/com/seaofnodes/simple/Parser.java index 9b79c195..b9fbf26f 100644 --- a/chapter06/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter06/src/main/java/com/seaofnodes/simple/Parser.java @@ -167,6 +167,7 @@ public class Parser { // But first clone the scope and set it as current int ndefs = _scope.nIns(); ScopeNode fScope = _scope.dup(); // Duplicate current scope + fScope.ctrl(ifF); // The pending arm already starts at False _xScopes.push(fScope); // For graph visualization we need all scopes // Parse the true side @@ -176,7 +177,6 @@ public class Parser { // Parse the false side _scope = fScope; // Restore scope, then parse else block if any - ctrl(ifF); // Ctrl token is now set to ifFalse projection if (matchx("else")) { parseStatement(); fScope = _scope; diff --git a/chapter06/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter06/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index cee75f6a..84ef99f2 100644 --- a/chapter06/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter06/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -34,6 +34,7 @@ public class SimpleGraphAdapter extends GraphAdapter { } private Kind kind(Node n) { + if( n instanceof StopNode ) return Kind.STOP; if( n instanceof ScopeNode ) return Kind.SCOPE; if( n instanceof StartNode ) return Kind.CTRL; if( n instanceof RegionNode ) return Kind.REGION; diff --git a/chapter07/src/main/java/com/seaofnodes/simple/Parser.java b/chapter07/src/main/java/com/seaofnodes/simple/Parser.java index f40eb3a3..2e043538 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/Parser.java @@ -237,6 +237,7 @@ public class Parser { // But first clone the scope and set it as current int ndefs = _scope.nIns(); ScopeNode fScope = _scope.dup(); // Duplicate current scope + fScope.ctrl(ifF); // The pending arm already starts at False _xScopes.push(fScope); // For graph visualization we need all scopes // Parse the true side @@ -246,7 +247,6 @@ public class Parser { // Parse the false side _scope = fScope; // Restore scope, then parse else block if any - ctrl(ifF); // Ctrl token is now set to ifFalse projection if (matchx("else")) { parseStatement(); fScope = _scope; diff --git a/chapter07/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter07/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index e907e744..9f33520e 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -34,6 +34,7 @@ public class SimpleGraphAdapter extends GraphAdapter { } private Kind kind(Node n) { + if( n instanceof StopNode ) return Kind.STOP; if( n instanceof ScopeNode ) return Kind.SCOPE; if( n instanceof StartNode ) return Kind.CTRL; if( n instanceof LoopNode ) return Kind.LOOP; diff --git a/chapter08/src/main/java/com/seaofnodes/simple/Parser.java b/chapter08/src/main/java/com/seaofnodes/simple/Parser.java index 5c699a54..d7055f48 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/Parser.java @@ -284,6 +284,7 @@ public class Parser { // But first clone the scope and set it as current int ndefs = _scope.nIns(); ScopeNode fScope = _scope.dup(); // Duplicate current scope + fScope.ctrl(ifF); // The pending arm already starts at False _xScopes.push(fScope); // For graph visualization we need all scopes // Parse the true side @@ -293,7 +294,6 @@ public class Parser { // Parse the false side _scope = fScope; // Restore scope, then parse else block if any - ctrl(ifF); // Ctrl token is now set to ifFalse projection if (matchx("else")) { parseStatement(); fScope = _scope; diff --git a/chapter08/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter08/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index 585592d4..3c4dd7dd 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -34,6 +34,7 @@ public class SimpleGraphAdapter extends GraphAdapter { } private Kind kind(Node n) { + if( n instanceof StopNode ) return Kind.STOP; if( n instanceof ScopeNode ) return Kind.SCOPE; if( n instanceof StartNode ) return Kind.CTRL; if( n instanceof LoopNode ) return Kind.LOOP; diff --git a/chapter09/src/main/java/com/seaofnodes/simple/Parser.java b/chapter09/src/main/java/com/seaofnodes/simple/Parser.java index 393b5358..3c1eab96 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/Parser.java @@ -282,6 +282,7 @@ public class Parser { // But first clone the scope and set it as current int ndefs = _scope.nIns(); ScopeNode fScope = _scope.dup(); // Duplicate current scope + fScope.ctrl(ifF); // The pending arm already starts at False _xScopes.push(fScope); // For graph visualization we need all scopes // Parse the true side @@ -291,7 +292,7 @@ public class Parser { // Parse the false side _scope = fScope; // Restore scope, then parse else block if any - ctrl(ifF.unkeep()); // Ctrl token is now set to ifFalse projection + ifF.unkeep(); // fScope already owns the false control if (matchx("else")) { parseStatement(); fScope = _scope; diff --git a/chapter09/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter09/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index a3372f08..df46cc6b 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -36,6 +36,7 @@ public class SimpleGraphAdapter extends GraphAdapter { } private Kind kind(Node n) { + if( n instanceof StopNode ) return Kind.STOP; if( n instanceof ScopeNode ) return Kind.SCOPE; if( n instanceof StartNode ) return Kind.CTRL; if( n instanceof LoopNode ) return Kind.LOOP; diff --git a/chapter10/src/main/java/com/seaofnodes/simple/Parser.java b/chapter10/src/main/java/com/seaofnodes/simple/Parser.java index 01c7da29..ddbbdf82 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter10/src/main/java/com/seaofnodes/simple/Parser.java @@ -331,6 +331,7 @@ public class Parser { // But first clone the scope and set it as current int ndefs = _scope.nIns(); ScopeNode fScope = _scope.dup(); // Duplicate current scope + fScope.ctrl(ifF); // The pending arm already starts at False _xScopes.push(fScope); // For graph visualization we need all scopes // Parse the true side @@ -341,7 +342,7 @@ public class Parser { // Parse the false side _scope = fScope; // Restore scope, then parse else block if any - ctrl(ifF.unkeep()); // Ctrl token is now set to ifFalse projection + ifF.unkeep(); // fScope already owns the false control if (matchx("else")) { _scope.upcast(ifF,pred,true); // Up-cast predicate parseStatement(); diff --git a/chapter10/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter10/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index 545897d8..600e9c73 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter10/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -37,6 +37,7 @@ public class SimpleGraphAdapter extends GraphAdapter { } private Kind kind(Node n) { + if( n instanceof StopNode ) return Kind.STOP; if( n instanceof ScopeNode ) return Kind.SCOPE; if( n instanceof StartNode ) return Kind.CTRL; if( n instanceof LoopNode ) return Kind.LOOP; diff --git a/chapter11/src/main/java/com/seaofnodes/simple/Parser.java b/chapter11/src/main/java/com/seaofnodes/simple/Parser.java index 7008512f..5f4922f4 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/Parser.java @@ -342,6 +342,7 @@ public class Parser { // But first clone the scope and set it as current int ndefs = _scope.nIns(); ScopeNode fScope = _scope.dup(); // Duplicate current scope + fScope.ctrl(ifF); // The pending arm already starts at False _xScopes.push(fScope); // For graph visualization we need all scopes // Parse the true side @@ -352,7 +353,7 @@ public class Parser { // Parse the false side _scope = fScope; // Restore scope, then parse else block if any - ctrl(ifF.unkeep()); // Ctrl token is now set to ifFalse projection + ifF.unkeep(); // fScope already owns the false control if (matchx("else")) { _scope.upcast(ifF,pred,true); // Up-cast predicate parseStatement(); diff --git a/chapter11/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter11/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index 38acb8cd..9ddf0de6 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -38,6 +38,7 @@ public class SimpleGraphAdapter extends GraphAdapter { } private Kind kind(Node n) { + if( n instanceof StopNode ) return Kind.STOP; if( n instanceof ScopeNode ) return Kind.SCOPE; if( n instanceof StartNode ) return Kind.CTRL; if( n instanceof LoopNode ) return Kind.LOOP; diff --git a/chapter12/src/main/java/com/seaofnodes/simple/Parser.java b/chapter12/src/main/java/com/seaofnodes/simple/Parser.java index 2bcd8da7..f5c63cc7 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/Parser.java @@ -345,6 +345,7 @@ public class Parser { // But first clone the scope and set it as current int ndefs = _scope.nIns(); ScopeNode fScope = _scope.dup(); // Duplicate current scope + fScope.ctrl(ifF); // The pending arm already starts at False _xScopes.push(fScope); // For graph visualization we need all scopes // Parse the true side @@ -355,7 +356,7 @@ public class Parser { // Parse the false side _scope = fScope; // Restore scope, then parse else block if any - ctrl(ifF.unkeep()); // Ctrl token is now set to ifFalse projection + ifF.unkeep(); // fScope already owns the false control if (matchx("else")) { _scope.upcast(ifF,pred,true); // Up-cast predicate parseStatement(); diff --git a/chapter12/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter12/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index 8e909e1c..8af87a2b 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -38,6 +38,7 @@ public class SimpleGraphAdapter extends GraphAdapter { } private Kind kind(Node n) { + if( n instanceof StopNode ) return Kind.STOP; if( n instanceof ScopeNode ) return Kind.SCOPE; if( n instanceof StartNode ) return Kind.CTRL; if( n instanceof LoopNode ) return Kind.LOOP; diff --git a/chapter13/src/main/java/com/seaofnodes/simple/Parser.java b/chapter13/src/main/java/com/seaofnodes/simple/Parser.java index e41293bd..b3bd1ada 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/Parser.java @@ -346,6 +346,7 @@ public class Parser { // But first clone the scope and set it as current int ndefs = _scope.nIns(); ScopeNode fScope = _scope.dup(); // Duplicate current scope + fScope.ctrl(ifF); // The pending arm already starts at False _xScopes.push(fScope); // For graph visualization we need all scopes // Parse the true side @@ -356,7 +357,7 @@ public class Parser { // Parse the false side _scope = fScope; // Restore scope, then parse else block if any - ctrl(ifF.unkeep()); // Ctrl token is now set to ifFalse projection + ifF.unkeep(); // fScope already owns the false control _scope.upcast(ifF,pred,true); // Up-cast predicate if (matchx("else")) { parseStatement(); diff --git a/chapter13/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter13/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index 60eae682..3778a6d9 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -38,6 +38,7 @@ public class SimpleGraphAdapter extends GraphAdapter { } private Kind kind(Node n) { + if( n instanceof StopNode ) return Kind.STOP; if( n instanceof ScopeNode ) return Kind.SCOPE; if( n instanceof StartNode ) return Kind.CTRL; if( n instanceof LoopNode ) return Kind.LOOP; diff --git a/chapter14/src/main/java/com/seaofnodes/simple/Parser.java b/chapter14/src/main/java/com/seaofnodes/simple/Parser.java index 5d7d67da..34fac183 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/Parser.java @@ -370,6 +370,7 @@ public class Parser { // But first clone the scope and set it as current int ndefs = _scope.nIns(); ScopeNode fScope = _scope.dup(); // Duplicate current scope + fScope.ctrl(ifF); // The pending arm already starts at False _xScopes.push(fScope); // For graph visualization we need all scopes // Parse the true side @@ -380,7 +381,7 @@ public class Parser { // Parse the false side _scope = fScope; // Restore scope, then parse else block if any - ctrl(ifF.unkeep()); // Ctrl token is now set to ifFalse projection + ifF.unkeep(); // fScope already owns the false control _scope.upcast(ifF,pred,true); // Up-cast predicate if (matchx("else")) { parseStatement(); diff --git a/chapter14/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter14/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index 39f79e69..4b1cc065 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -38,6 +38,7 @@ public class SimpleGraphAdapter extends GraphAdapter { } private Kind kind(Node n) { + if( n instanceof StopNode ) return Kind.STOP; if( n instanceof ScopeNode ) return Kind.SCOPE; if( n instanceof StartNode ) return Kind.CTRL; if( n instanceof LoopNode ) return Kind.LOOP; diff --git a/chapter15/src/main/java/com/seaofnodes/simple/Parser.java b/chapter15/src/main/java/com/seaofnodes/simple/Parser.java index 71bf1b07..965bd146 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/Parser.java @@ -381,6 +381,7 @@ public class Parser { // But first clone the scope and set it as current int ndefs = _scope.nIns(); ScopeNode fScope = _scope.dup(); // Duplicate current scope + fScope.ctrl(ifF); // The pending arm already starts at False _xScopes.push(fScope); // For graph visualization we need all scopes // Parse the true side @@ -391,7 +392,7 @@ public class Parser { // Parse the false side _scope = fScope; // Restore scope, then parse else block if any - ctrl(ifF.unkeep()); // Ctrl token is now set to ifFalse projection + ifF.unkeep(); // fScope already owns the false control _scope.upcast(ifF,pred,true); // Up-cast predicate if (matchx("else")) { parseStatement(); diff --git a/chapter15/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter15/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index 639108ae..f5efd3e8 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -38,6 +38,7 @@ public class SimpleGraphAdapter extends GraphAdapter { } private Kind kind(Node n) { + if( n instanceof StopNode ) return Kind.STOP; if( n instanceof ScopeNode ) return Kind.SCOPE; if( n instanceof StartNode ) return Kind.CTRL; if( n instanceof LoopNode ) return Kind.LOOP; diff --git a/chapter16/src/main/java/com/seaofnodes/simple/Parser.java b/chapter16/src/main/java/com/seaofnodes/simple/Parser.java index b52767cc..1f20a913 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/Parser.java @@ -355,6 +355,7 @@ public class Parser { // But first clone the scope and set it as current int ndefs = _scope.nIns(); ScopeNode fScope = _scope.dup(); // Duplicate current scope + fScope.ctrl(ifF); // The pending arm already starts at False _xScopes.push(fScope); // For graph visualization we need all scopes // Parse the true side @@ -365,7 +366,7 @@ public class Parser { // Parse the false side _scope = fScope; // Restore scope, then parse else block if any - ctrl(ifF.unkeep()); // Ctrl token is now set to ifFalse projection + ifF.unkeep(); // fScope already owns the false control _scope.upcast(ifF,pred,true); // Up-cast predicate if (matchx("else")) { parseStatement(); diff --git a/chapter16/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter16/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index 6d281535..da54e8c8 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -38,6 +38,7 @@ public class SimpleGraphAdapter extends GraphAdapter { } private Kind kind(Node n) { + if( n instanceof StopNode ) return Kind.STOP; if( n instanceof ScopeNode ) return Kind.SCOPE; if( n instanceof StartNode ) return Kind.CTRL; if( n instanceof LoopNode ) return Kind.LOOP; diff --git a/chapter17/src/main/java/com/seaofnodes/simple/Parser.java b/chapter17/src/main/java/com/seaofnodes/simple/Parser.java index a8157b41..07312396 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/Parser.java @@ -446,6 +446,7 @@ public class Parser { // But first clone the scope and set it as current int ndefs = _scope.nIns(); ScopeNode fScope = _scope.dup(); // Duplicate current scope + fScope.ctrl(ifF); // The pending arm already starts at False _xScopes.push(fScope); // For graph visualization we need all scopes // Parse the true side @@ -458,7 +459,7 @@ public class Parser { // Parse the false side _scope = fScope; // Restore scope, then parse else block if any - ctrl(ifF.unkeep()); // Ctrl token is now set to ifFalse projection + ifF.unkeep(); // fScope already owns the false control // Up-cast predicate, even if not else clause, because predicate can // remain true if the true clause exits: `if( !ptr ) return 0; return ptr.fld;` _scope.addGuards(ifF,pred,true); diff --git a/chapter17/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter17/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index 5a45aa9d..a88987d5 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -38,6 +38,7 @@ public class SimpleGraphAdapter extends GraphAdapter { } private Kind kind(Node n) { + if( n instanceof StopNode ) return Kind.STOP; if( n instanceof ScopeNode ) return Kind.SCOPE; if( n instanceof StartNode ) return Kind.CTRL; if( n instanceof LoopNode ) return Kind.LOOP; diff --git a/chapter18/src/main/java/com/seaofnodes/simple/Parser.java b/chapter18/src/main/java/com/seaofnodes/simple/Parser.java index 1ed0c02a..3e914bbd 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/Parser.java @@ -519,6 +519,7 @@ public class Parser { // In if true branch, the ifT proj node becomes the ctrl // But first clone the scope and set it as current ScopeNode fScope = _scope.dup(); // Duplicate current scope + fScope.ctrl(ifF); // The pending arm already starts at False _xScopes.push(fScope); // For graph visualization we need all scopes // Parse the true side @@ -536,7 +537,7 @@ public class Parser { // Parse the false side _scope = fScope; // Restore scope, then parse else block if any - ctrl(ifF.unkeep()); // Ctrl token is now set to ifFalse projection + ifF.unkeep(); // fScope already owns the false control // Up-cast predicate, even if not else clause, because predicate can // remain true if the true clause exits: `if( !ptr ) return 0; return ptr.fld;` _scope.addGuards(ifF,pred,true); diff --git a/chapter18/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter18/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index 23085c9c..442bfd50 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -38,6 +38,7 @@ public class SimpleGraphAdapter extends GraphAdapter { } private Kind kind(Node n) { + if( n instanceof StopNode ) return Kind.STOP; if( n instanceof ScopeNode ) return Kind.SCOPE; if( n instanceof FunNode ) return Kind.FUN; if( n instanceof StartNode ) return Kind.CTRL; diff --git a/chapter19/src/main/java/com/seaofnodes/simple/Parser.java b/chapter19/src/main/java/com/seaofnodes/simple/Parser.java index 29738a31..1a775050 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/Parser.java @@ -502,6 +502,7 @@ public class Parser { // In if true branch, the ifT proj node becomes the ctrl // But first clone the scope and set it as current ScopeNode fScope = _scope.dup(); // Duplicate current scope + fScope.ctrl(ifF); // The pending arm already starts at False _xScopes.push(fScope); // For graph visualization we need all scopes // Parse the true side @@ -519,7 +520,7 @@ public class Parser { // Parse the false side _scope = fScope; // Restore scope, then parse else block if any - ctrl(ifF.unkeep()); // Ctrl token is now set to ifFalse projection + ifF.unkeep(); // fScope already owns the false control // Up-cast predicate, even if not else clause, because predicate can // remain true if the true clause exits: `if( !ptr ) return 0; return ptr.fld;` _scope.addGuards(ifF,pred,true); diff --git a/chapter19/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter19/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index 170dcb7d..d61f4771 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -38,6 +38,7 @@ public class SimpleGraphAdapter extends GraphAdapter { } private Kind kind(Node n) { + if( n instanceof StopNode ) return Kind.STOP; if( n instanceof ScopeNode ) return Kind.SCOPE; if( n instanceof FunNode ) return Kind.FUN; if( n instanceof StartNode ) return Kind.CTRL; diff --git a/chapter20/src/main/java/com/seaofnodes/simple/Parser.java b/chapter20/src/main/java/com/seaofnodes/simple/Parser.java index 0809a6f3..af047850 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/Parser.java @@ -503,6 +503,7 @@ public class Parser { // In if true branch, the ifT proj node becomes the ctrl // But first clone the scope and set it as current ScopeNode fScope = _scope.dup(); // Duplicate current scope + fScope.ctrl(ifF); // The pending arm already starts at False _xScopes.push(fScope); // For graph visualization we need all scopes // Parse the true side @@ -520,7 +521,7 @@ public class Parser { // Parse the false side _scope = fScope; // Restore scope, then parse else block if any - ctrl(ifF.unkeep()); // Ctrl token is now set to ifFalse projection + ifF.unkeep(); // fScope already owns the false control // Up-cast predicate, even if not else clause, because predicate can // remain true if the true clause exits: `if( !ptr ) return 0; return ptr.fld;` _scope.addGuards(ifF,pred,true); diff --git a/chapter20/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter20/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index 15afe5d1..5c277abc 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -38,6 +38,7 @@ public class SimpleGraphAdapter extends GraphAdapter { } private Kind kind(Node n) { + if( n instanceof StopNode ) return Kind.STOP; if( n instanceof ScopeNode ) return Kind.SCOPE; if( n instanceof FunNode ) return Kind.FUN; if( n instanceof StartNode ) return Kind.CTRL; diff --git a/chapter21/src/main/java/com/seaofnodes/simple/Parser.java b/chapter21/src/main/java/com/seaofnodes/simple/Parser.java index adddbaa6..fb108447 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/Parser.java @@ -521,6 +521,7 @@ public class Parser { // In if true branch, the ifT proj node becomes the ctrl // But first clone the scope and set it as current ScopeNode fScope = _scope.dup(); // Duplicate current scope + fScope.ctrl(ifF); // The pending arm already starts at False _xScopes.push(fScope); // For graph visualization we need all scopes // Parse the true side @@ -538,7 +539,7 @@ public class Parser { // Parse the false side _scope = fScope; // Restore scope, then parse else block if any - ctrl(ifF.unkeep()); // Ctrl token is now set to ifFalse projection + ifF.unkeep(); // fScope already owns the false control // Up-cast predicate, even if not else clause, because predicate can // remain true if the true clause exits: `if( !ptr ) return 0; return ptr.fld;` _scope.addGuards(ifF,pred,true); diff --git a/chapter21/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter21/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index 73967e05..69403674 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -38,6 +38,7 @@ public class SimpleGraphAdapter extends GraphAdapter { } private Kind kind(Node n) { + if( n instanceof StopNode ) return Kind.STOP; if( n instanceof ScopeNode ) return Kind.SCOPE; if( n instanceof FunNode ) return Kind.FUN; if( n instanceof StartNode ) return Kind.CTRL; diff --git a/chapter22/src/main/java/com/seaofnodes/simple/Parser.java b/chapter22/src/main/java/com/seaofnodes/simple/Parser.java index 60a36574..72fe6a71 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/Parser.java @@ -534,6 +534,7 @@ public class Parser { // In if true branch, the ifT proj node becomes the ctrl // But first clone the scope and set it as current ScopeNode fScope = _scope.dup(); // Duplicate current scope + fScope.ctrl(ifF); // The pending arm already starts at False _xScopes.push(fScope); // For graph visualization we need all scopes // Parse the true side @@ -551,7 +552,7 @@ public class Parser { // Parse the false side _scope = fScope; // Restore scope, then parse else block if any - ctrl(ifF.unkeep()); // Ctrl token is now set to ifFalse projection + ifF.unkeep(); // fScope already owns the false control // Up-cast predicate, even if not else clause, because predicate can // remain true if the true clause exits: `if( !ptr ) return 0; return ptr.fld;` _scope.addGuards(ifF,pred,true); diff --git a/chapter22/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter22/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index 8dcee566..2bcda3ef 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -38,6 +38,7 @@ public class SimpleGraphAdapter extends GraphAdapter { } private Kind kind(Node n) { + if( n instanceof StopNode ) return Kind.STOP; if( n instanceof ScopeNode ) return Kind.SCOPE; if( n instanceof FunNode ) return Kind.FUN; if( n instanceof StartNode ) return Kind.CTRL; diff --git a/chapter23/src/main/java/com/seaofnodes/simple/Parser.java b/chapter23/src/main/java/com/seaofnodes/simple/Parser.java index 41d8fcb5..be5c8932 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/Parser.java @@ -548,6 +548,7 @@ public class Parser { // In if true branch, the ifT proj node becomes the ctrl // But first clone the scope and set it as current ScopeNode fScope = _scope.dup(); // Duplicate current scope + fScope.ctrl(ifF); // The pending arm already starts at False _xScopes.push(fScope); // For graph visualization we need all scopes // Parse the true side @@ -573,7 +574,7 @@ public class Parser { // Parse the false side _scope = fScope; // Restore scope, then parse else block if any - ctrl(ifF.unkeep()); // Ctrl token is now set to ifFalse projection + ifF.unkeep(); // fScope already owns the false control // Up-cast predicate, even if not else clause, because predicate can // remain true if the true clause exits: `if( !ptr ) return 0; return ptr.fld;` _scope.addGuards(ifF,pred,true); diff --git a/chapter23/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter23/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index 38a4778f..f9b940a9 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -38,6 +38,7 @@ public class SimpleGraphAdapter extends GraphAdapter { } private Kind kind(Node n) { + if( n instanceof StopNode ) return Kind.STOP; if( n instanceof ScopeNode ) return Kind.SCOPE; if( n instanceof FunNode ) return Kind.FUN; if( n instanceof StartNode ) return Kind.CTRL; diff --git a/chapter24/src/main/java/com/seaofnodes/simple/Parser.java b/chapter24/src/main/java/com/seaofnodes/simple/Parser.java index c9e53b4b..cd4d5afb 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/Parser.java @@ -555,6 +555,7 @@ public class Parser { // In if true branch, the ifT proj node becomes the ctrl // But first clone the scope and set it as current ScopeNode fScope = _scope.dup(); // Duplicate current scope + fScope.ctrl(ifF); // The pending arm already starts at False _xScopes.push(fScope); // For graph visualization we need all scopes // Parse the true side @@ -580,7 +581,7 @@ public class Parser { // Parse the false side _scope = fScope; // Restore scope, then parse else block if any - ctrl(ifF.unkeep()); // Ctrl token is now set to ifFalse projection + ifF.unkeep(); // fScope already owns the false control // Up-cast predicate, even if not else clause, because predicate can // remain true if the true clause exits: `if( !ptr ) return 0; return ptr.fld;` _scope.addGuards(ifF,pred,true); diff --git a/chapter24/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter24/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index bf8f4181..374197c0 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -38,6 +38,7 @@ public class SimpleGraphAdapter extends GraphAdapter { } private Kind kind(Node n) { + if( n instanceof StopNode ) return Kind.STOP; if( n instanceof ScopeNode ) return Kind.SCOPE; if( n instanceof FunNode ) return Kind.FUN; if( n instanceof StartNode ) return Kind.CTRL; diff --git a/chapter25/src/main/java/com/seaofnodes/simple/Parser.java b/chapter25/src/main/java/com/seaofnodes/simple/Parser.java index ffd8bf64..05685418 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/Parser.java @@ -778,6 +778,7 @@ public class Parser { // In if true branch, the ifT proj node becomes the ctrl // But first clone the scope and set it as current ScopeNode fScope = _scope.dup(); // Duplicate current scope + fScope.ctrl(ifF); // The pending arm already starts at False _xScopes.push(fScope); // For graph visualization we need all scopes // Parse the true side @@ -803,7 +804,7 @@ public class Parser { // Parse the false side _scope = fScope; // Restore scope, then parse else block if any - ctrl(ifF.unkeep()); // Ctrl token is now set to ifFalse projection + ifF.unkeep(); // fScope already owns the false control // Up-cast predicate, even if not else clause, because predicate can // remain true if the true clause exits: `if( !ptr ) return 0; return ptr.fld;` _scope.addGuards(ifF,pred,true); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index e9b1dc6d..506ac3e2 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -38,8 +38,9 @@ public class SimpleGraphAdapter extends GraphAdapter { } private Kind kind(Node n) { + if( n instanceof StopNode ) return Kind.STOP; if( n instanceof ScopeNode ) return Kind.SCOPE; - if( n instanceof StartCUNode || n instanceof StopCUNode ) return Kind.UNIT; + if( n instanceof StartCUNode ) return Kind.UNIT; if( n instanceof FunNode ) return Kind.FUN; // Start inherits Loop for whole-program analysis, but is not a source loop. if( n instanceof StartNode ) return Kind.CTRL; diff --git a/graph/README.md b/graph/README.md index a2145220..b98ca9d3 100644 --- a/graph/README.md +++ b/graph/README.md @@ -147,6 +147,15 @@ the internal parent/projection edge is represented by the shared box. A projection whose parent is absent is drawn on its own. Node kinds use color and shape rather than a KIND label. +Phi slot 0 uses a short left-facing arrow at the middle of the box's left side, +instead of a full edge to its Region. The tooltip and node details retain the +definition ID. Value inputs stay along the top. +Each Region (including a loop header) and its Phis form one layout row, with +the Region on the left. Their boxes, ports, selections and node IDs stay separate. +This also keeps a newly created Phi on its Region's row while the parser holds it. +Stops occupy the bottom row, beside the Parser box when it is present, even +before return edges attach. Chapter adapters identify Stops with `Kind.STOP`. + Scopes have named slots across the top, including control, memory and variable bindings. Edges leave these slots upward toward their definitions. They stay visible even when **Associations** is unchecked, and do not constrain CFG layout. diff --git a/graph/src/main/java/com/seaofnodes/graph/GraphSnapshot.java b/graph/src/main/java/com/seaofnodes/graph/GraphSnapshot.java index 6836693e..53d74706 100644 --- a/graph/src/main/java/com/seaofnodes/graph/GraphSnapshot.java +++ b/graph/src/main/java/com/seaofnodes/graph/GraphSnapshot.java @@ -14,7 +14,7 @@ public record GraphSnapshot(int ver, String comp, long step, @Override public int[] roots() { return roots.clone(); } - public enum Kind { DATA, CTRL, MEM, SCOPE, PHI, REGION, LOOP, FUN, UNIT } + public enum Kind { DATA, CTRL, MEM, SCOPE, PHI, REGION, LOOP, FUN, UNIT, STOP } public enum Role { DATA, CTRL, MEM, ASSOC } /** IDs are stable within one compilation. Labels and types are plain text. */ diff --git a/graph/web/layout.js b/graph/web/layout.js index a277cb53..b192de87 100644 --- a/graph/web/layout.js +++ b/graph/web/layout.js @@ -22,7 +22,7 @@ class GraphLayout { if (scope) w = Math.ceil(Math.max(120, this.ctx.measureText(label).width + 24, cols.reduce((sum, w) => sum + w, 0))); return {id: "n" + n.id, width: w, height: 62, - ports: [], n, label, type, scope, cols, ox: 0, oy: 0}; + ports: [], n, label, type, scope, cols, stop: n.kind === "STOP", ox: 0, oy: 0}; }); const byId = new Map(nodes.map(n => [n.n.id, n])); const parsing = evt?.phase === "Parse" && evt.kind !== "PHASE"; @@ -46,7 +46,16 @@ class GraphLayout { projs.get(par).push(n); n.par = par; } - const groups = nodes.filter(n => !n.par).map(n => { + const phis = new Map(); + for (const n of nodes) { + if (n.n.kind !== "PHI") continue; + const reg = byId.get(n.n.edges.find(e => e.idx === 0)?.def); + if (!reg || !["REGION", "LOOP", "FUN"].includes(reg.n.kind)) continue; + if (!phis.has(reg)) phis.set(reg, []); + phis.get(reg).push(n); + n.reg = reg; + } + const groups = nodes.filter(n => !n.par && !n.reg).map(n => { const slots = (projs.get(n) || []).sort((a, b) => a.n.proj.idx - b.n.proj.idx || a.n.id - b.n.id); const parts = [n, ...slots]; if (slots.length) { @@ -61,15 +70,24 @@ class GraphLayout { x += p.width; } } + // ELK sees one row; the renderer keeps each Region/Phi as a separate box. + let width = n.width; + for (const p of phis.get(n) || []) { + p.ox = width + 32; + width = p.ox + p.width; + parts.push(p); + } const ports = []; for (const p of parts) { - p.cell = slots.length > 0; + p.cell = slots.length > 0 && !p.reg; const extra = p.scope && p.cols.length ? (p.width - p.cols.reduce((sum, w) => sum + w, 0)) / p.cols.length : 0; let x = 0; for (const [idx, e] of p.n.edges.entries()) { // The parent/projection link is represented by the shared box. if (p.par && e.idx === 0 && e.def === p.par.n.id) continue; + // Phi's Region input is a local left-facing marker, not a routed edge. + if (p.n.kind === "PHI" && e.idx === 0) continue; if (p.scope) { const span = p.cols[idx] + extra; ports.push({id: p.id + "i" + e.idx, x: x + span / 2 - 3, y: -3, span, @@ -77,7 +95,8 @@ class GraphLayout { x += span; continue; } - ports.push({id: p.id + "i" + e.idx, x: p.ox + p.width * (e.idx + 1) / (p.n.edges.length + 1) - 3, + const off = p.n.kind === "PHI" ? 0 : 1; + ports.push({id: p.id + "i" + e.idx, x: p.ox + p.width * (e.idx + off) / (p.n.edges.length + off) - 3, y: p.oy - 3, width: 6, height: 6, layoutOptions: {"elk.port.side": "NORTH"}}); } // Direct uses of the MultiNode attach to its header, projections below. @@ -86,12 +105,13 @@ class GraphLayout { y: p.oy + (side === "EAST" ? p.height / 2 : p.height) - 3, width: 6, height: 6, layoutOptions: {"elk.port.side": side}}); } - return {id: n.id, width: n.width, height: n.height * (slots.length ? 2 : 1), ports, parts}; + return {id: n.id, width, height: n.height * (slots.length ? 2 : 1), ports, parts}; }); const edges = []; for (const use of snap.nodes) { for (const e of use.edges) { if (!e.def) continue; + if (use.kind === "PHI" && e.idx === 0) continue; if (byId.get(use.id).par && e.idx === 0 && e.def === use.proj.par) continue; const id = "e" + use.id + "i" + e.idx; const reg = byId.get(use.edges[0]?.def)?.n; @@ -106,13 +126,14 @@ class GraphLayout { } } const graph = await this.elk.layout({ - id: "root", children: groups.filter(g => !g.parts[0].scope).map(({parts, ...box}) => ({...box, + id: "root", children: groups.filter(g => !g.parts[0].scope && !g.parts[0].stop).map(({parts, ...box}) => ({...box, layoutOptions: {"elk.portConstraints": "FIXED_POS", // Keep a projection with its parent even when that parent has real uses. "elk.layered.layering.layerConstraint": parts.length === 1 && parts[0].held ? "LAST_SEPARATE" : "NONE"} })), // Scope bindings and lifetime associations must not impose CFG ranks. - edges: edges.filter(e => e.role !== "ASSOC" && !e.bind && !byId.get(e.def).scope).map(e => ({ + edges: edges.filter(e => e.role !== "ASSOC" && !e.bind && !byId.get(e.def).scope && + !byId.get(e.use).stop && !byId.get(e.def).stop).map(e => ({ id: e.id, sources: e.sources, targets: e.targets, layoutOptions: e.layoutOptions })), layoutOptions: { @@ -124,8 +145,9 @@ class GraphLayout { "elk.randomSeed": 1 } }); - // Scope ports use the same local geometry; place their boxes after CFG nodes. - for (const g of groups.filter(g => g.parts[0].scope)) graph.children.push({...g, x: 0, y: 0}); + // Place scopes and Stops after the flow graph; their ports stay local. + for (const g of groups.filter(g => g.parts[0].scope || g.parts[0].stop)) + graph.children.push({...g, x: 0, y: 0}); const boxes = new Map(graph.children.map(n => [n.id, n])); const routes = new Map((graph.edges || []).map(e => [e.id, e])); for (const group of groups) { @@ -135,6 +157,8 @@ class GraphLayout { n.x = box.x + n.ox; n.y = box.y + n.oy; // Keep semantic nodes/slots intact for selection and neighborhood marks. n.ports = n.n.edges.map(e => { + if (n.n.kind === "PHI" && e.idx === 0) + return {id: n.id + "i0", x: -6, y: n.height / 2 - 3, width: 6, height: 6, reg: true, edge: e}; const p = ports.get(n.id + "i" + e.idx); return p && {...p, x: p.x - n.ox, y: p.y - n.oy, edge: e}; }).filter(Boolean); @@ -169,11 +193,21 @@ class GraphLayout { } // Parser ownership is an overlay, never an IR node or an ELK flow edge. const parser = owned.length ? {width: Math.max(180, (owned.length + 1) * 18), height: 36} : null; + // Stop belongs at the bottom even before any Return has attached to it. + const foot = [...nodes.filter(n => n.stop), ...(parser ? [parser] : [])]; + if (foot.length) { + const width = foot.reduce((sum, n) => sum + n.width, 0) + (foot.length - 1) * 32; + const height = Math.max(...foot.map(n => n.height)); + graph.width = Math.max(graph.width, width + 48); + let x = (graph.width - width) / 2; + for (const n of foot) { + n.x = x; + n.y = graph.height + 24 + height - n.height; + x += n.width + 32; + } + graph.height += height + 48; + } if (parser) { - graph.width = Math.max(graph.width, parser.width + 48); - parser.x = (graph.width - parser.width) / 2; - parser.y = graph.height + 24; - graph.height = parser.y + parser.height + 24; owned.sort((a, b) => a.x - b.x || a.n.id - b.n.id); parser.ports = owned.map((n, i) => ({id: "parser-i" + n.n.id, x: parser.width * (i + 1) / (owned.length + 1) - 3, y: -3})); @@ -181,7 +215,8 @@ class GraphLayout { use: 0, def: n.n.id, role: "PARSER", sources: [n.id + "o"], targets: ["parser-i" + n.n.id]}); } for (const e of edges) { - if (e.role === "ASSOC" || e.role === "PARSER" || e.bind || byId.get(e.def).scope) { + if (e.role === "ASSOC" || e.role === "PARSER" || e.bind || byId.get(e.def).scope || + byId.get(e.use)?.stop || byId.get(e.def).stop) { const def = byId.get(e.def), use = e.role === "PARSER" ? parser : byId.get(e.use); const a = def.ports.find(p => p.id === e.sources[0]); const b = use.ports.find(p => p.id === e.targets[0]); diff --git a/graph/web/render.js b/graph/web/render.js index 34c66a02..883475f5 100644 --- a/graph/web/render.js +++ b/graph/web/render.js @@ -77,7 +77,7 @@ class GraphView { .on("click", (event, n) => { event.stopPropagation(); this.select(n.n.id); }); boxes.select("path.box").attr("d", n => this.shape(n)) .attr("fill", n => ({CTRL: "#fff1c4", REGION: "#fff1c4", LOOP: "#ffe0ab", FUN: "#ffe0ab", - UNIT: "#ffe0ab", MEM: "#dcecff", PHI: "#f5e4fa", SCOPE: "#e5e0ff", DATA: "#edf3f7"})[n.n.kind]); + UNIT: "#ffe0ab", STOP: "#fff1c4", MEM: "#dcecff", PHI: "#f5e4fa", SCOPE: "#e5e0ff", DATA: "#edf3f7"})[n.n.kind]); boxes.select("text.head").attr("y", n => n.scope ? 47 : 15) .text(n => `#${n.n.id}${n.n.proj ? " · p" + n.n.proj.idx : ""}`); boxes.select("text.label").attr("x", n => n.width / 2).attr("y", n => n.scope ? 51 : 33) @@ -97,8 +97,15 @@ class GraphView { group.selectAll("circle.port").data(ports, p => p.id).join("circle") .attr("class", "port").attr("cx", p => p.x + 3).attr("cy", p => p.y + 3) .attr("r", 3).attr("fill", p => p.edge.def ? "#536477" : "white"); + group.selectAll("path.reg").data(ports.filter(p => p.reg && p.edge.def), p => p.id).join("path") + .attr("class", "reg").attr("fill", "none").attr("stroke", this.colors.CTRL) + .attr("d", p => `M-3,${p.y + 3}H-18m4,-3l-4,3l4,3`) + .each(function(p) { + d3.select(this).selectAll("title").data([p]).join("title") + .text(`[0] → #${p.edge.def}`); + }); group.selectAll("text.slot").data(ports, p => p.id).join("text") - .attr("class", "slot").attr("x", p => p.x + 3).attr("y", p => p.y - 4) + .attr("class", "slot").attr("x", p => p.reg ? -10 : p.x + 3).attr("y", p => p.y - 4) .attr("text-anchor", "middle").text(p => p.edge.idx); }); this.assocs(document.getElementById("assocs").checked); @@ -110,7 +117,7 @@ class GraphView { shape(n) { const w = n.width, h = n.height; // A MultiNode and its projection cells tile one rectangular box. - if (n.cell || n.n.kind === "CTRL" || n.n.kind === "UNIT") return `M0,0H${w}V${h}H0Z`; + if (n.cell || ["CTRL", "UNIT", "STOP"].includes(n.n.kind)) return `M0,0H${w}V${h}H0Z`; if (n.n.kind === "REGION") return `M12,0H${w - 12}L${w},${h}H0Z`; if (n.n.kind === "PHI") return `M12,0H${w - 12}L${w},${h / 2}L${w - 12},${h}H12L0,${h / 2}Z`; if (n.n.kind === "MEM" || n.n.kind === "FUN") diff --git a/graph/web/viewer.js b/graph/web/viewer.js index 63ee195e..4a88245b 100644 --- a/graph/web/viewer.js +++ b/graph/web/viewer.js @@ -87,9 +87,13 @@ async function render(index) { if (generation === frameGeneration) { renderer.show(frame.snap, frame.layout, frame.evt); if (frame.pos >= 0) { - // Stepping focuses a button; focus the source so its selection is visible. + // A newline or EOF has no visible character to highlight. Show the last + // parsed character there, rather than losing the position indicator. + let pos = Math.min(frame.pos, program.value.length); + while (pos > 0 && (!program.value[pos] || /\s/.test(program.value[pos]))) pos--; + program.setSelectionRange(pos, pos + 1); + // Focus after selecting, so the browser brings the new position into view. program.focus({preventScroll: true}); - program.setSelectionRange(frame.pos, frame.pos + 1); } } rendering = false; -- cgit v1.2.3