Skip to content

Commit 7023f8b

Browse files
committed
Working on CFG sequencing
1 parent 7bbc3b9 commit 7023f8b

File tree

9 files changed

+544
-19
lines changed

9 files changed

+544
-19
lines changed

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
[![Java CI with Maven](https://github.com/mirkosertic/MetaIR/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/mirkosertic/MetaIR/actions/workflows/maven.yml)
44

55
MetaIR is a graph-based intermediate representation (IR) for JVM bytecode, built on Cliff Click's Sea-of-Nodes concept.
6-
The framework leverages the Java Class-File API introduced in Java 24 (JEP 484).
6+
The framework leverages the Java Class-File API introduced in Java 24 (JEP 484).
7+
8+
Most parts of the IR are taken from [Bytecoder - Framework to interpret and transpile JVM bytecode to JavaScript, OpenCL or WebAssembly. ](https://github.com/mirkosertic/Bytecoder),
9+
but were rewritten to be more flexible and extensible.
710

811
## Key Features
912

@@ -12,6 +15,10 @@ The framework leverages the Java Class-File API introduced in Java 24 (JEP 484).
1215
- **Optimization**: Built-in peephole optimizations for graph reduction
1316
- **Integration**: Built-in integration with JUnit Platform
1417
- **Cross-Compilation**: Foundation framework for cross-compiler development
18+
- **MetaIR** covers all aspects of JVM bytecode, including:
19+
- **Control Flow**: Branching, loops, exception handling
20+
- **Data Flow**: Local variables, stack, memory
21+
- **Memory aliasing**: Memory allocation, memory access
1522

1623
## Technical Details
1724
- Built on [Java Class-File API (JEP 484)](https://openjdk.org/jeps/484)
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
package de.mirkosertic.metair.ir;
2+
3+
import java.util.ArrayList;
4+
import java.util.Collections;
5+
import java.util.Comparator;
6+
import java.util.HashMap;
7+
import java.util.HashSet;
8+
import java.util.List;
9+
import java.util.Map;
10+
import java.util.Set;
11+
12+
public class CFGDominatorTree {
13+
14+
final List<Node> preOrder;
15+
final Map<Node, Node> idom;
16+
17+
final List<Node> rpo;
18+
19+
public CFGDominatorTree(final Node start) {
20+
preOrder = new DFS2(start, true).getTopologicalOrder();
21+
idom = new HashMap<>();
22+
rpo = new ArrayList<>();
23+
computeDominators();
24+
computeRPO(start);
25+
}
26+
27+
private void computeRPO(final Node consumer) {
28+
final List<Node> finished = new ArrayList<>();
29+
final Set<Node> visited = new HashSet<>();
30+
computeRPO(consumer, finished, visited);
31+
32+
Collections.reverse(finished);
33+
rpo.addAll(finished);
34+
}
35+
36+
private void computeRPO(final Node current, final List<Node> finished, final Set<Node> visited) {
37+
if (visited.add(current)) {
38+
for (final Node user : current.usedBy.stream().sorted(Comparator.comparing((Node o) -> o.getClass().getSimpleName())).toList()) {
39+
for (final Node.UseEdge edge : user.uses) {
40+
if (edge.node() == current) {
41+
if (edge.use() instanceof final ControlFlowUse cfu) {
42+
if (cfu.type == FlowType.FORWARD) {
43+
computeRPO(user, finished, visited);
44+
}
45+
}
46+
}
47+
}
48+
}
49+
finished.add(current);
50+
}
51+
}
52+
53+
public List<Node> getPreOrder() {
54+
return preOrder;
55+
}
56+
57+
public List<Node> getRpo() {
58+
return rpo;
59+
}
60+
61+
private void computeDominators() {
62+
final Node firstElement = preOrder.getFirst();
63+
idom.put(firstElement, firstElement);
64+
65+
boolean changed;
66+
do {
67+
changed = false;
68+
for (final Node v : preOrder) {
69+
if (v.equals(firstElement))
70+
continue;
71+
final Node oldIdom = getIDom(v);
72+
Node newIdom = null;
73+
for (final Node.UseEdge edge : v.uses) {
74+
if (edge.use() instanceof ControlFlowUse) {
75+
if (getIDom(edge.node()) == null)
76+
/* not yet analyzed */ continue;
77+
if (newIdom == null) {
78+
/* If we only have one (defined) predecessor pre, IDom(v) = pre */
79+
newIdom = edge.node();
80+
} else {
81+
/* compute the intersection of all defined predecessors of v */
82+
newIdom = intersectIDoms(edge.node(), newIdom);
83+
}
84+
}
85+
}
86+
if (newIdom == null) {
87+
throw new AssertionError("newIDom == null !, for " + v);
88+
}
89+
if (!newIdom.equals(oldIdom)) {
90+
changed = true;
91+
this.idom.put(v, newIdom);
92+
}
93+
}
94+
} while (changed);
95+
}
96+
97+
public Node getIDom(final Node node) {
98+
return idom.get(node);
99+
}
100+
101+
private Node intersectIDoms(Node v1, Node v2) {
102+
while (v1 != v2) {
103+
if (preOrder.indexOf(v1) < preOrder.indexOf(v2)) {
104+
v2 = getIDom(v2);
105+
} else {
106+
v1 = getIDom(v1);
107+
}
108+
}
109+
return v1;
110+
}
111+
112+
/**
113+
* Check wheter a node dominates another one.
114+
*
115+
* @return true, if <code>dominator</code> dominates <code>dominated</code> w.r.t to the entry node
116+
*/
117+
public boolean dominates(final Node dominator, final Node dominated) {
118+
if(dominator.equals(dominated)) {
119+
return true; // Domination is reflexive ;)
120+
}
121+
Node dom = getIDom(dominated);
122+
// as long as dominated >= dominator
123+
while(dom != null && preOrder.indexOf(dom) >= preOrder.indexOf(dominator) && ! dom.equals(dominator)) {
124+
dom = getIDom(dom);
125+
}
126+
return dominator.equals(dom);
127+
}
128+
129+
public Set<Node> getStrictDominators(final Node n) {
130+
final Set<Node> strictDoms = new HashSet<>();
131+
Node dominated = n;
132+
Node iDom = getIDom(n);
133+
while(iDom != dominated) {
134+
strictDoms.add(iDom);
135+
dominated = iDom;
136+
iDom = getIDom(dominated);
137+
}
138+
return strictDoms;
139+
}
140+
141+
public Set<Node> immediatelyDominatedNodesOf(final Node n) {
142+
final Set<Node> result = new HashSet<>();
143+
for (final Map.Entry<Node, Node> entry : idom.entrySet()) {
144+
if (entry.getValue() == n) {
145+
result.add(entry.getKey());
146+
}
147+
}
148+
return result;
149+
}
150+
151+
public Set<Node> domSetOf(final Node n) {
152+
final Set<Node> theDomSet = new HashSet<>();
153+
addToDomSet(n, theDomSet);
154+
return theDomSet;
155+
}
156+
157+
private void addToDomSet(final Node n, final Set<Node> domset) {
158+
domset.add(n);
159+
for (final Map.Entry<Node, Node> idomEntry : idom.entrySet()) {
160+
if (idomEntry.getValue() == n && preOrder.indexOf(idomEntry.getKey()) > preOrder.indexOf(n)) {
161+
addToDomSet(idomEntry.getKey(), domset);
162+
}
163+
}
164+
}
165+
}

src/main/java/de/mirkosertic/metair/ir/DFS2.java

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,15 @@ public class DFS2 {
1515
private final Set<Node> visited;
1616
private final Map<Node, Set<Node>> precomputedPredecessors;
1717
private final Map<Node, List<Node>> precomputedForwards;
18+
private final boolean onlyControlFlow;
1819

1920
public DFS2(final Node node) {
21+
this(node, false);
22+
}
23+
24+
public DFS2(final Node node, final boolean onlyControlFlow) {
2025

26+
this.onlyControlFlow = onlyControlFlow;
2127
this.nodesInOrder = new ArrayList<>();
2228
this.workList = new ArrayList<>();
2329
this.visited = new HashSet<>();
@@ -77,16 +83,22 @@ private List<Node> getForwardNodesFor(final Node currentNode) {
7783
for (final Node user : key.usedBy) {
7884
for (final Node.UseEdge edge : user.uses) {
7985
if (edge.node() == key) {
80-
if (edge.use() instanceof final ControlFlowUse cfu && cfu.type == FlowType.FORWARD) {
81-
forwardNodes.add(user);
82-
} else if (edge.use() instanceof final PHIUse pu && pu.type == FlowType.FORWARD) {
83-
forwardNodes.add(user);
84-
} else if (edge.use() instanceof DefinedByUse) {
85-
forwardNodes.add(user);
86-
} else if (edge.use() instanceof DataFlowUse) {
87-
forwardNodes.add(user);
88-
} else if (edge.use() instanceof MemoryUse) {
89-
forwardNodes.add(user);
86+
if (onlyControlFlow) {
87+
if (edge.use() instanceof final ControlFlowUse cfu && cfu.type == FlowType.FORWARD) {
88+
forwardNodes.add(user);
89+
}
90+
} else {
91+
if (edge.use() instanceof final ControlFlowUse cfu && cfu.type == FlowType.FORWARD) {
92+
forwardNodes.add(user);
93+
} else if (edge.use() instanceof final PHIUse pu && pu.type == FlowType.FORWARD) {
94+
forwardNodes.add(user);
95+
} else if (edge.use() instanceof DefinedByUse) {
96+
forwardNodes.add(user);
97+
} else if (edge.use() instanceof DataFlowUse) {
98+
forwardNodes.add(user);
99+
} else if (edge.use() instanceof MemoryUse) {
100+
forwardNodes.add(user);
101+
}
90102
}
91103
}
92104
}
@@ -102,15 +114,23 @@ public List<Node> getTopologicalOrder() {
102114
private Set<Node> predecessorsOf(final Node node) {
103115
final Set<Node> predecessors = new HashSet<>();
104116
for (final Node.UseEdge edge : node.uses) {
105-
if (edge.use() instanceof final ControlFlowUse cfu) {
106-
if (cfu.type == FlowType.FORWARD) {
107-
predecessors.add(edge.node());
108-
}
109-
} else if (edge.use() instanceof final PHIUse pu) {
110-
if (pu.type == FlowType.FORWARD) {
111-
predecessors.add(edge.node());
117+
if (onlyControlFlow) {
118+
if (edge.use() instanceof final ControlFlowUse cfu) {
119+
if (cfu.type == FlowType.FORWARD) {
120+
predecessors.add(edge.node());
121+
}
112122
}
113-
} else predecessors.add(edge.node());
123+
} else {
124+
if (edge.use() instanceof final ControlFlowUse cfu) {
125+
if (cfu.type == FlowType.FORWARD) {
126+
predecessors.add(edge.node());
127+
}
128+
} else if (edge.use() instanceof final PHIUse pu) {
129+
if (pu.type == FlowType.FORWARD) {
130+
predecessors.add(edge.node());
131+
}
132+
} else predecessors.add(edge.node());
133+
}
114134
}
115135
return predecessors;
116136
}

src/main/java/de/mirkosertic/metair/ir/DOTExporter.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,55 @@ public static void writeTo(final DominatorTree tree, final PrintStream ps) {
297297
ps.flush();
298298
}
299299

300+
public static void writeTo(final CFGDominatorTree tree, final PrintStream ps) {
301+
ps.println("digraph debugoutput {");
302+
ps.println(" ordering=\"in\";");
303+
for (final Node n : tree.preOrder) {
304+
ps.print(" node" + tree.preOrder.indexOf(n) + "[");
305+
printNode(tree.preOrder.indexOf(n), n, ps, " Order : " + tree.rpo.indexOf(n));
306+
ps.println("];");
307+
308+
final Node id = tree.idom.get(n);
309+
if (id != n) {
310+
ps.print(" node" + tree.preOrder.indexOf(n) + " -> node" + tree.preOrder.indexOf(id) + "[dir=\"forward\"");
311+
ps.print(" color=\"green\" penwidth=\"2\"");
312+
ps.println("];");
313+
}
314+
315+
for (final Node.UseEdge useEdge : n.uses) {
316+
final Node usedNode = useEdge.node();
317+
318+
if (useEdge.use() instanceof final ControlFlowUse cfu) {
319+
ps.print(" node" + tree.preOrder.indexOf(usedNode));
320+
ps.print(" -> node" + tree.preOrder.indexOf(n));
321+
ps.print("[labeldistance=2, color=red, fontcolor=red");
322+
if (cfu.type == FlowType.BACKWARD) {
323+
ps.print(", style=dashed");
324+
}
325+
ps.print("]");
326+
ps.println(";");
327+
}
328+
}
329+
}
330+
ps.println("""
331+
subgraph cluster_000 {
332+
label = "Legend";
333+
node [shape=point]
334+
{
335+
rank=same;
336+
c0 [style = invis];
337+
c1 [style = invis];
338+
c2 [style = invis];
339+
c3 [style = invis];
340+
}
341+
c0 -> c1 [label="Control flow", style=solid, color=red]
342+
c2 -> c3 [label="Control flow back edge", style=dashed, color=red]
343+
}
344+
""");
345+
ps.println("}");
346+
ps.flush();
347+
}
348+
300349
public static void writeBytecodeCFGTo(final MethodAnalyzer analyzer, final PrintStream ps) {
301350
ps.println("digraph {");
302351
ps.println(" ordering=\"in\";");

0 commit comments

Comments
 (0)