Skip to content

Commit

Permalink
Graph pattern matching prototype
Browse files Browse the repository at this point in the history
  • Loading branch information
mirkosertic committed Apr 26, 2024
1 parent 1e61aa9 commit 8b43299
Show file tree
Hide file tree
Showing 10 changed files with 444 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,10 @@ private void visitDominationTreeOf(final ControlTokenConsumer startNode, final S
}
} catch (final IllegalStateException e) {
throw e;
} catch (final SequencerException e) {
throw e;
} catch (final RuntimeException e) {
throw new RuntimeException("Error processing node #" + graph.nodes().indexOf(current) + " " + current.nodeType + current.additionalDebugInfo(), e);
throw new SequencerException("Error processing node #" + graph.nodes().indexOf(current) + " " + current.nodeType + current.additionalDebugInfo(), e);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2024 Mirko Sertic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.mirkosertic.bytecoder.core.backend.sequencer;

public class SequencerException extends RuntimeException{

public SequencerException(final String message, final Exception cause) {
super(message, cause);
}
}
4 changes: 4 additions & 0 deletions core/src/main/java/de/mirkosertic/bytecoder/core/ir/Goto.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,8 @@ public class Goto extends ControlTokenConsumer {
public Goto stampInto(final Graph target) {
return target.newGoto();
}

public void deleteFromControlFlow() {
owner.deleteFromControlFlowInternally(this);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import de.mirkosertic.bytecoder.core.ir.ControlTokenConsumer;
import de.mirkosertic.bytecoder.core.ir.EdgeType;
import de.mirkosertic.bytecoder.core.ir.FrameDebugInfo;
import de.mirkosertic.bytecoder.core.ir.Goto;
import de.mirkosertic.bytecoder.core.ir.Graph;
import de.mirkosertic.bytecoder.core.ir.LineNumberDebugInfo;
import de.mirkosertic.bytecoder.core.ir.NodeType;
Expand All @@ -33,7 +34,7 @@ public class DropDebugData implements Optimizer {
public boolean optimize(final BackendType backendType, final CompileUnit compileUnit, final ResolvedMethod method) {
final Graph g = method.methodBody;
boolean changed = false;
for (final ControlTokenConsumer t : g.nodes().stream().filter(t -> t.nodeType == NodeType.FrameDebugInfo || t.nodeType == NodeType.LineNumberDebugInfo).map(t -> (ControlTokenConsumer) t).collect(Collectors.toList())) {
for (final ControlTokenConsumer t : g.nodes().stream().filter(t -> t.nodeType == NodeType.FrameDebugInfo || t.nodeType == NodeType.LineNumberDebugInfo || t.nodeType == NodeType.Goto).map(t -> (ControlTokenConsumer) t).collect(Collectors.toList())) {
if (t.controlComingFrom.size() == 1 && t.controlFlowsTo.size() == 1 && t.controlFlowsTo.keySet().iterator().next().edgeType() == EdgeType.FORWARD) {
if (t.nodeType == NodeType.FrameDebugInfo) {
((FrameDebugInfo) t).deleteFromControlFlow();
Expand All @@ -43,6 +44,10 @@ public boolean optimize(final BackendType backendType, final CompileUnit compile
((LineNumberDebugInfo) t).deleteFromControlFlow();
changed = true;
}
if (t.nodeType == NodeType.Goto) {
((Goto) t).deleteFromControlFlow();
changed = true;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
/*
* Copyright 2024 Mirko Sertic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.mirkosertic.bytecoder.core.optimizer;

import de.mirkosertic.bytecoder.core.ir.ControlTokenConsumer;
import de.mirkosertic.bytecoder.core.ir.Graph;
import de.mirkosertic.bytecoder.core.ir.Node;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.stream.Collectors;

public class GraphPatternMatcher {

private interface Context {
Node[] outgoingDataFlowsFor(final Node node);
}

private static class Path {

private final String path;

public Path() {
this("R");
}

private Path(final String path) {
this.path = path;
}

public Path addIncoming(final int i) {
return new Path(path + ".i[" + i + "]");
}

public Path addOutgoing(final int i) {
return new Path(path + ".o[" + i + "]");
}

public Path controlComingFrom(final int nodeIndex) {
return new Path(path + ".cin[" + nodeIndex + "]");
}

@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final Path path1 = (Path) o;
return Objects.equals(path, path1.path);
}

@Override
public int hashCode() {
return Objects.hashCode(path);
}

public Node resolve(final Node root, final Context context) {
Node node = null;
final StringTokenizer st = new StringTokenizer(path, ".");
while (st.hasMoreTokens()) {
final String token = st.nextToken();
if ("R".equals(token)) {
node = root;
} else if (token.startsWith("i[")) {
int index = Integer.parseInt(token.substring(2, token.length() - 1));
final Node[] incoming = node.incomingDataFlows;
if (incoming.length > index) {
node = incoming[index];
} else {
return null;
}
} else if (token.startsWith("o[")) {
int index = Integer.parseInt(token.substring(2, token.length() - 1));
final Node[] outgoing = context.outgoingDataFlowsFor(node);
if (outgoing.length > index) {
node = outgoing[index];
} else {
return null;
}
} else {
throw new IllegalStateException("what do do with " + token);
}
}
return node;
}
}

private static class CompiledPattern {

private final Map<Node, List<Path>> nodeToPaths = new HashMap<>();
private final List<Node> nodeIndex = new ArrayList<>();

private boolean registerToIndex(final Node n) {
if (!nodeIndex.contains(n)) {
nodeIndex.add(n);
return true;
}
return false;
}

public int nodeIndexOf(final Node n) {
return nodeIndex.indexOf(n);
}

public boolean matchesTo(final Node analysisPoint, final Context c) {
// Check for all nodes and paths if they resolve according to their path
for (final Map.Entry<Node, List<Path>> entry : nodeToPaths.entrySet()) {
Node singularValue = null;
for (final Path p : entry.getValue()) {
final Node r = p.resolve(analysisPoint, c);
if (r != null) {
if (singularValue == null) {
singularValue = r;
} else {
if (singularValue != r) {
return false;
}
}
} else {
return false;
}
}
// todo: check index of singular value in template and at analysispoint??
}
return true;
}
}

private final Node pattern;
private final CompiledPattern compiledPattern;

public GraphPatternMatcher(final Node patternToCompile) {
this.pattern = patternToCompile;
this.compiledPattern = compile(patternToCompile);
}

private static class PathAnalysisState {
private final Node node;
private final Path path;

public PathAnalysisState(final Node node, final Path path) {
this.node = node;
this.path = path;
}
}

private void registerPaths(final Node pivot, final CompiledPattern compiledPattern) {

final Path root = new Path();

final Stack<PathAnalysisState> workingQueue = new Stack<>();
workingQueue.add(new PathAnalysisState(pivot, root));

compiledPattern.registerToIndex(pivot);

while (!workingQueue.isEmpty()) {
final PathAnalysisState workingItem = workingQueue.pop();
final Path workingItemPath = workingItem.path;

final List<Path> workingItemPaths = compiledPattern.nodeToPaths.computeIfAbsent(workingItem.node, key -> new ArrayList<>());
if (!workingItemPaths.contains(workingItemPath)) {
workingItemPaths.add(workingItemPath);
}

final Node[] incomingDataFlow = workingItem.node.incomingDataFlows;
for (int i = 0; i < incomingDataFlow.length; i++) {
final Node n = incomingDataFlow[i];
final boolean registered = compiledPattern.registerToIndex(n);
final Path newPath = workingItemPath.addIncoming(i);

final List<Path> paths = compiledPattern.nodeToPaths.computeIfAbsent(n, key -> new ArrayList<>());
if (!paths.contains(newPath)) {
paths.add(newPath);
}

if (registered) {
workingQueue.add(new PathAnalysisState(n, newPath));
}
}
final Node[] outgoing = workingItem.node.outgoingDataFlows();
for (int i = 0; i < outgoing.length; i++) {
final Node n = outgoing[i];
final boolean registered = compiledPattern.registerToIndex(n);
final Path newPath = workingItemPath.addOutgoing(i);

final List<Path> paths = compiledPattern.nodeToPaths.computeIfAbsent(n, key -> new ArrayList<>());
if (!paths.contains(newPath)) {
paths.add(newPath);
}

if (registered) {
workingQueue.add(new PathAnalysisState(n, newPath));
}
}

if (workingItem.node instanceof ControlTokenConsumer) {
final ControlTokenConsumer control = (ControlTokenConsumer) workingItem.node;
for (final ControlTokenConsumer inc : control.controlComingFrom) {
final boolean registered = compiledPattern.registerToIndex(inc);
final Path newPath = workingItemPath.controlComingFrom(compiledPattern.nodeIndexOf(inc));

final List<Path> paths = compiledPattern.nodeToPaths.computeIfAbsent(inc, key -> new ArrayList<>());
if (!paths.contains(newPath)) {
paths.add(newPath);
}

if (registered) {
workingQueue.add(new PathAnalysisState(inc, newPath));
}
}
}
}
}

private CompiledPattern compile(final Node patternRoot) {
final CompiledPattern pattern = new CompiledPattern();

registerPaths(patternRoot, pattern);

return pattern;
}

public List<Node> findMatches(final Graph source) {
final List<Node> result = new ArrayList<>();

final Context c = new Context() {

private final Map<Node, Node[]> outgoingFlows = new HashMap<>();

@Override
public Node[] outgoingDataFlowsFor(final Node node) {
return outgoingFlows.computeIfAbsent(node, Node::outgoingDataFlows);
}
};

// We search for all analysis candidates in source with the same nodetype as pivot
for (final Node analysisPoint : source.nodes().stream().filter(t -> t.nodeType == pattern.nodeType).collect(Collectors.toList())) {

if (compiledPattern.matchesTo(analysisPoint, c)) {
result.add(analysisPoint);
}
}

return result;
}
}
Loading

0 comments on commit 8b43299

Please sign in to comment.