diff --git a/compiler/mx.compiler/suite.py b/compiler/mx.compiler/suite.py index 9b661cf9f99a..86c0b21d60cb 100644 --- a/compiler/mx.compiler/suite.py +++ b/compiler/mx.compiler/suite.py @@ -60,9 +60,10 @@ "digest" : "sha512:40c505dd03ca0bb102f1091b89b90672126922f290bd8370eef9a7afc5d9c1e7b5db08c448a0948ef46bf57d850e166813e2d68bf7b1c88a46256d839b6b0201", "packedResource": True, }, + "IDEALGRAPHVISUALIZER_DIST" : { - "urls" : ["https://lafo.ssw.uni-linz.ac.at/pub/idealgraphvisualizer/idealgraphvisualizer-1.22-6cb0d3acbb1.zip"], - "digest" : "sha512:8c4795fae203bfa84c40b041fe6d0f46a89bd8b975120d28aea9483eef1c1b63ab685716c1258387c12a255560904284fd0bf9aa947f2efabc4a629148000b5d", + "urls" : ["https://lafo.ssw.uni-linz.ac.at/pub/idealgraphvisualizer/idealgraphvisualizer-1.23-4543ff4a3e5.zip"], + "digest" : "sha512:2c779c8a01ab4cc7b77e1497ca97641799bb934309ac1306ae8383ab4efdd7af50bbd1bf55438c742e4159b581e69476a2dd8023af0f105433992a35454bdbf0", "packedResource": True, }, diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graphio/parsing/BinaryReader.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graphio/parsing/BinaryReader.java index 300ac259a2db..accdfb8370c8 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graphio/parsing/BinaryReader.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graphio/parsing/BinaryReader.java @@ -91,6 +91,8 @@ public class BinaryReader implements GraphParser, ModelControl { private static final Logger LOG = Logger.getLogger(BinaryReader.class.getName()); + static final boolean TRACE_PARSE_TIME = false; + private final Logger instLog; private final DataSource dataSource; @@ -472,15 +474,33 @@ public boolean equals(Object obj) { public static final class EnumKlass extends Klass { public final String[] values; + public final EnumValue[] enums; + private volatile int hashCode = 0; public EnumKlass(String name, String[] values) { super(name); this.values = values; + this.enums = new EnumValue[values.length]; + for (int i = 0; i < values.length; i++) { + this.enums[i] = new EnumValue(this, i); + } + } + + EnumValue get(int ordinal) { + if (ordinal >= 0 && ordinal < enums.length) { + return enums[ordinal]; + } + return new EnumValue(this, ordinal); } @Override public int hashCode() { - return super.hash * 31 + Arrays.hashCode(values); + int h = hashCode; + if (h == 0) { + h = Objects.hash(super.hashCode(), Arrays.hashCode(values)); + hashCode = h; + } + return h; } @Override @@ -646,7 +666,7 @@ private Object addPoolEntry(Class klass) throws IOException { case POOL_ENUM: { EnumKlass enumClass = readPoolObject(EnumKlass.class); int ordinal = dataSource.readInt(); - obj = new EnumValue(enumClass, ordinal); + obj = enumClass.get(ordinal); break; } case POOL_NODE_CLASS: { @@ -808,6 +828,7 @@ public GraphDocument parse() throws IOException { hashStack.push(null); boolean restart = false; + long start = System.nanoTime(); try { while (true) { // TERMINATION ARGUMENT: finite length of data source will result in // EOFException eventually. @@ -831,6 +852,11 @@ public GraphDocument parse() throws IOException { } finally { // also terminates the builder closeDanglingGroups(); + if (TRACE_PARSE_TIME) { + long end = System.nanoTime(); + System.err.println(((System.nanoTime() - timeStart) / 1_000_000) + " Parsed file in " + ((end - start) / 1_000_000) + " ms"); + } + } return builder.rootDocument(); } @@ -990,7 +1016,10 @@ private InputGraph parseGraph(String title, boolean toplevel) throws IOException } } + private static final long timeStart = System.nanoTime(); + private InputGraph parseGraph(int dumpId, String format, Object[] args, boolean toplevel) throws IOException { + long start = System.nanoTime(); InputGraph g = builder.startGraph(dumpId, format, args); try { parseProperties(); @@ -1014,6 +1043,10 @@ private InputGraph parseGraph(int dumpId, String format, Object[] args, boolean // we have to finish the graph. reporter.popContext(); g = builder.endGraph(); + if (TRACE_PARSE_TIME) { + long end = System.nanoTime(); + System.err.println(((System.nanoTime() - timeStart) / 1_000_000) + " Parsed " + dumpId + " " + String.format(format, args) + " in " + ((end - start) / 1000000) + " ms"); + } } return g; } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graphio/parsing/Builder.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graphio/parsing/Builder.java index 2e1e46713004..ef760bf76c0f 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graphio/parsing/Builder.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graphio/parsing/Builder.java @@ -33,6 +33,7 @@ import jdk.graal.compiler.graphio.parsing.BinaryReader.EnumValue; import jdk.graal.compiler.graphio.parsing.BinaryReader.Method; +import jdk.graal.compiler.graphio.parsing.TemplateParser.TemplatePart; import jdk.graal.compiler.graphio.parsing.model.GraphDocument; import jdk.graal.compiler.graphio.parsing.model.Group; import jdk.graal.compiler.graphio.parsing.model.InputBlock; @@ -219,16 +220,23 @@ public String toString() { final class NodeClass { public final String className; public final String nameTemplate; + private final List templateParts; public final List inputs; public final List sux; + private String shortString; NodeClass(String className, String nameTemplate, List inputs, List sux) { this.className = className; this.nameTemplate = nameTemplate; + this.templateParts = jdk.graal.compiler.graphio.parsing.TemplateParser.parseTemplate(nameTemplate); this.inputs = inputs; this.sux = sux; } + public List getTemplateParts() { + return templateParts; + } + @Override public String toString() { return className; @@ -265,13 +273,18 @@ public boolean equals(Object obj) { } String toShortString() { - int lastDot = className.lastIndexOf('.'); - String localShortName = className.substring(lastDot + 1); - if (localShortName.endsWith("Node") && !localShortName.equals("StartNode") && !localShortName.equals(CLASS_ENDNODE)) { - return localShortName.substring(0, localShortName.length() - 4); - } else { - return localShortName; + String s = shortString; + if (s == null) { + int lastDot = className.lastIndexOf('.'); + String localShortName = className.substring(lastDot + 1); + if (localShortName.endsWith("Node") && !localShortName.equals("StartNode") && !localShortName.equals(CLASS_ENDNODE)) { + s = localShortName.substring(0, localShortName.length() - 4); + } else { + s = localShortName; + } + shortString = s; } + return shortString; } } } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graphio/parsing/ModelBuilder.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graphio/parsing/ModelBuilder.java index d13062bdf003..1b590b2b5b72 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graphio/parsing/ModelBuilder.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graphio/parsing/ModelBuilder.java @@ -47,8 +47,6 @@ import java.util.Objects; import java.util.Set; import java.util.logging.Logger; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import jdk.graal.compiler.graphio.parsing.BinaryReader.EnumValue; import jdk.graal.compiler.graphio.parsing.BinaryReader.Method; @@ -662,6 +660,14 @@ public void endNode(int nodeId) { PROPNAME_ID, PROPNAME_IDX, PROPNAME_BLOCK))); + private static boolean isSystemProperty(String key) { + return switch (key) { + case PROPNAME_HAS_PREDECESSOR, PROPNAME_NAME, PROPNAME_CLASS, PROPNAME_ID, PROPNAME_IDX, PROPNAME_BLOCK -> + true; + default -> false; + }; + } + @Override public void setGroupName(String name, String shortName) { assert folder instanceof Group; @@ -684,7 +690,7 @@ protected final void reportProgress() { @Override public void setNodeName(NodeClass nodeClass) { assert currentNode != null; - getProperties().setProperty(PROPNAME_NAME, createName(nodeClass, nodeEdges, nodeClass.nameTemplate)); + getProperties().setProperty(PROPNAME_NAME, createName(nodeClass, nodeEdges)); getProperties().setProperty(PROPNAME_CLASS, nodeClass.className); switch (nodeClass.className) { case "BeginNode": @@ -696,35 +702,38 @@ public void setNodeName(NodeClass nodeClass) { } } - static final Pattern TEMPLATE_PATTERN = Pattern.compile("\\{([pi])#([a-zA-Z0-9$_]+)(/([lms]))?}"); - - private String createName(NodeClass nodeClass, List edges, String template) { - if (template.isEmpty()) { + private String createName(NodeClass nodeClass, List edges) { + if (nodeClass.nameTemplate.isEmpty()) { return nodeClass.toShortString(); } - Matcher m = TEMPLATE_PATTERN.matcher(template); - StringBuilder sb = new StringBuilder(); + + StringBuilder sb = new StringBuilder(nodeClass.nameTemplate.length()); Properties p = getProperties(); - while (m.find()) { - String name = m.group(2); - String type = m.group(1); - String result; + List templateParts = nodeClass.getTemplateParts(); + for (TemplateParser.TemplatePart template : templateParts) { + if (!template.isReplacement) { + sb.append(template.value); + continue; + } + String name = template.name; + String type = template.type; switch (type) { case "i": - StringBuilder inputString = new StringBuilder(); + boolean first = true; for (EdgeInfo edge : edges) { if (edge.label.startsWith(name) && (name.length() == edge.label.length() || edge.label.charAt(name.length()) == '[')) { - if (inputString.length() > 0) { - inputString.append(", "); + if (!first) { + sb.append(", "); } - inputString.append(edge.from); + first = false; + sb.append(edge.from); } } - result = inputString.toString(); break; case "p": Object prop = p.get(name); - String length = m.group(4); + String length = template.length; + String result; if (prop == null) { result = "?"; } else if (length != null && prop instanceof LengthToString lengthProp) { @@ -735,36 +744,21 @@ private String createName(NodeClass nodeClass, List edges, String temp case "m": result = lengthProp.toString(Length.M); break; - default: case "l": + default: result = lengthProp.toString(Length.L); break; } } else { result = prop.toString(); } + sb.append(result); break; default: - result = "#?#"; + sb.append("#?#"); break; } - - // Escape '\' and '$' to not interfere with the regular expression. - StringBuilder newResult = new StringBuilder(); - for (int i = 0; i < result.length(); ++i) { - char c = result.charAt(i); - if (c == '\\') { - newResult.append("\\\\"); - } else if (c == '$') { - newResult.append("\\$"); - } else { - newResult.append(c); - } - } - result = newResult.toString(); - m.appendReplacement(sb, result); } - m.appendTail(sb); return sb.toString(); } @@ -775,7 +769,7 @@ public void setNodeProperty(String key, Object value) { assert currentNode != null; String k = key; if (!(value instanceof InputGraph)) { - if (SYSTEM_PROPERTIES.contains(key)) { + if (isSystemProperty(key)) { k = NOT_DATA + k; } setProperty(k, value); diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graphio/parsing/TemplateParser.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graphio/parsing/TemplateParser.java new file mode 100644 index 000000000000..91757ac91cb4 --- /dev/null +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graphio/parsing/TemplateParser.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.graal.compiler.graphio.parsing; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class TemplateParser { + private static final Pattern TEMPLATE_PATTERN = Pattern.compile("\\{([pi])#([a-zA-Z0-9$_]+)(/([lms]))?}"); + + public static List parseTemplate(String template) { + List parts = new ArrayList<>(); + Matcher m = TEMPLATE_PATTERN.matcher(template); + int lastEnd = 0; + while (m.find()) { + if (m.start() > lastEnd) { + parts.add(new TemplatePart(template.substring(lastEnd, m.start()))); + } + String name = m.group(2); + String type = m.group(1); + switch (type) { + case "i": + parts.add(new TemplatePart(name, type, null)); + break; + case "p": + String length = m.group(4); + parts.add(new TemplatePart(name, type, length)); + break; + default: + parts.add(new TemplatePart("#?#")); + break; + } + lastEnd = m.end(); + } + if (lastEnd < template.length()) { + parts.add(new TemplatePart(template.substring(lastEnd))); + } + return parts; + } + + public static class TemplatePart { + public final String value; + public final boolean isReplacement; + public final String name; + public final String type; + public final String length; + + public TemplatePart(String name, String type, String length) { + this.name = name; + this.type = type; + this.length = length; + this.value = null; + this.isReplacement = true; + } + + public TemplatePart(String value) { + this.value = value; + this.isReplacement = false; + name = null; + type = null; + length = null; + } + } +} diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graphio/parsing/model/GraphDocumentVisitor.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graphio/parsing/model/GraphDocumentVisitor.java new file mode 100644 index 000000000000..fa99503c576e --- /dev/null +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graphio/parsing/model/GraphDocumentVisitor.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.graal.compiler.graphio.parsing.model; + +import java.util.function.Consumer; + +/** + * Visitor interface to traverse the various elements of the {@link GraphDocument} tree. Traversal + * can be stopped early by returning {@code false} from any of the {@code visit} methods. + */ +public interface GraphDocumentVisitor { + + static void visitAll(GraphDocument doc, Consumer visit) { + GraphDocumentVisitor visitor = inputGraph -> { + visit.accept(inputGraph); + return true; + }; + visitor.visit(doc); + } + + /** + * Visits a {@link Folder} in the graph document, which could be the top-level + * {@link GraphDocument} or a nested {@link Group}. + *

+ * The default implementation visits all elements in order, stopping if + * {@link #visit(FolderElement)} returns false for any of them. + */ + default boolean visit(Folder folder) { + for (FolderElement elem : folder.getElements()) { + if (!visit(elem)) { + return false; + } + } + return true; + } + + /** + * Visits an {@link InputGraph}. Implementors can choose whether to stop at the outermost level + * or also consider nested subgraphs. + */ + boolean visit(InputGraph inputGraph); + + /** + * Visits a {@link Group}. The default implementation delegates to {@link #visit(Folder)}. + */ + default boolean visit(Group g) { + return visit((Folder) g); + } + + /** + * Visits a {@link GraphDocument}. The default implementation delegates to + * {@link #visit(Folder)}. + */ + default boolean visit(GraphDocument document) { + return visit((Folder) document); + } + + /** + * Visits a {@link FolderElement}. The default implementation delegates to the {@code visit} + * version corresponding to the concrete type of {@code element}. + */ + default boolean visit(FolderElement element) { + if (element instanceof Group group) { + return visit(group); + } + if (element instanceof InputGraph inputGraph) { + return visit(inputGraph); + } + if (element instanceof Folder folder) { + return visit(folder); + } + return true; + } +} diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graphio/parsing/model/Properties.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graphio/parsing/model/Properties.java index 654daab68feb..61ad2ab8fcd7 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graphio/parsing/model/Properties.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graphio/parsing/model/Properties.java @@ -288,9 +288,13 @@ public LinkedHashMapProperties() { } public LinkedHashMapProperties(Properties p) { - this(p.size()); - for (Property prop : p) { - map.put(prop.getName(), prop.getValue()); + if (p instanceof LinkedHashMapProperties hash) { + map = new LinkedHashMap<>(hash.map); + } else { + map = new LinkedHashMap<>(p.size()); + for (Property prop : p) { + map.put(prop.getName(), prop.getValue()); + } } } @@ -303,6 +307,49 @@ public int size() { return map.size(); } + @Override + public boolean equals(Object o) { + if (o instanceof LinkedHashMapProperties hash) { + if (size() != hash.size()) { + return false; + } + /* + * Most Properties comparisons are for cases where they are the same or very similar + * so efficiency is fairly important to loading speed. They are commonly ordered in + * the same fashion so take advantage of that in the comparison + */ + Set> set1 = map.entrySet(); + Iterator> iter1 = set1.iterator(); + + Set> set2 = hash.map.entrySet(); + Iterator> iter2 = set2.iterator(); + while (iter1.hasNext()) { + var entry = iter1.next(); + // They are the same length so hasNext must be true + Map.Entry next = iter2.next(); + if (entry.getKey().equals(next.getKey())) { + if (!Objects.deepEquals(entry.getValue(), next.getValue())) { + return false; + } + continue; + } + // Different key encountered so must resort to contains for all following values + if (!Objects.deepEquals(entry.getValue(), hash.map.get(entry.getKey()))) { + return false; + } + while (iter1.hasNext()) { + entry = iter1.next(); + if (!Objects.deepEquals(entry.getValue(), hash.map.get(entry.getKey()))) { + return false; + } + } + return true; + } + return true; + } + return super.equals(o); + } + @Override public Property atIndex(int index) { if (index >= size() || index < 0) { @@ -469,10 +516,10 @@ protected final int makeHash() { int hash = 5; for (Property prop : this) { hash = hash ^ (Property.makeHash(prop.getName(), prop.getValue())); // position affected - // hash would - // violate - // equal/hash - // contract + // hash would + // violate + // equal/hash + // contract } return hash; } diff --git a/visualizer/IdealGraphVisualizer/Bytecodes/pom.xml b/visualizer/IdealGraphVisualizer/Bytecodes/pom.xml index 332dcf666dae..be15f16956fe 100644 --- a/visualizer/IdealGraphVisualizer/Bytecodes/pom.xml +++ b/visualizer/IdealGraphVisualizer/Bytecodes/pom.xml @@ -3,7 +3,7 @@ org.graalvm.visualizer IdealGraphVisualizer-parent - 1.23-SNAPSHOT + 1.24-SNAPSHOT Bytecodes nbm diff --git a/visualizer/IdealGraphVisualizer/ControlFlow/pom.xml b/visualizer/IdealGraphVisualizer/ControlFlow/pom.xml index 5e332656f34b..0712076399bd 100644 --- a/visualizer/IdealGraphVisualizer/ControlFlow/pom.xml +++ b/visualizer/IdealGraphVisualizer/ControlFlow/pom.xml @@ -3,7 +3,7 @@ org.graalvm.visualizer IdealGraphVisualizer-parent - 1.23-SNAPSHOT + 1.24-SNAPSHOT ControlFlow nbm diff --git a/visualizer/IdealGraphVisualizer/Coordinator/pom.xml b/visualizer/IdealGraphVisualizer/Coordinator/pom.xml index e14e119a607c..f65dab9cacf7 100644 --- a/visualizer/IdealGraphVisualizer/Coordinator/pom.xml +++ b/visualizer/IdealGraphVisualizer/Coordinator/pom.xml @@ -3,7 +3,7 @@ org.graalvm.visualizer IdealGraphVisualizer-parent - 1.23-SNAPSHOT + 1.24-SNAPSHOT Coordinator nbm diff --git a/visualizer/IdealGraphVisualizer/Data/pom.xml b/visualizer/IdealGraphVisualizer/Data/pom.xml index 615bc2db1273..acee3955ce2f 100644 --- a/visualizer/IdealGraphVisualizer/Data/pom.xml +++ b/visualizer/IdealGraphVisualizer/Data/pom.xml @@ -3,7 +3,7 @@ org.graalvm.visualizer IdealGraphVisualizer-parent - 1.23-SNAPSHOT + 1.24-SNAPSHOT Data nbm diff --git a/visualizer/IdealGraphVisualizer/Data/src/main/java/jdk/graal/compiler/graphio/parsing/BinaryReader.java b/visualizer/IdealGraphVisualizer/Data/src/main/java/jdk/graal/compiler/graphio/parsing/BinaryReader.java index 300ac259a2db..accdfb8370c8 100644 --- a/visualizer/IdealGraphVisualizer/Data/src/main/java/jdk/graal/compiler/graphio/parsing/BinaryReader.java +++ b/visualizer/IdealGraphVisualizer/Data/src/main/java/jdk/graal/compiler/graphio/parsing/BinaryReader.java @@ -91,6 +91,8 @@ public class BinaryReader implements GraphParser, ModelControl { private static final Logger LOG = Logger.getLogger(BinaryReader.class.getName()); + static final boolean TRACE_PARSE_TIME = false; + private final Logger instLog; private final DataSource dataSource; @@ -472,15 +474,33 @@ public boolean equals(Object obj) { public static final class EnumKlass extends Klass { public final String[] values; + public final EnumValue[] enums; + private volatile int hashCode = 0; public EnumKlass(String name, String[] values) { super(name); this.values = values; + this.enums = new EnumValue[values.length]; + for (int i = 0; i < values.length; i++) { + this.enums[i] = new EnumValue(this, i); + } + } + + EnumValue get(int ordinal) { + if (ordinal >= 0 && ordinal < enums.length) { + return enums[ordinal]; + } + return new EnumValue(this, ordinal); } @Override public int hashCode() { - return super.hash * 31 + Arrays.hashCode(values); + int h = hashCode; + if (h == 0) { + h = Objects.hash(super.hashCode(), Arrays.hashCode(values)); + hashCode = h; + } + return h; } @Override @@ -646,7 +666,7 @@ private Object addPoolEntry(Class klass) throws IOException { case POOL_ENUM: { EnumKlass enumClass = readPoolObject(EnumKlass.class); int ordinal = dataSource.readInt(); - obj = new EnumValue(enumClass, ordinal); + obj = enumClass.get(ordinal); break; } case POOL_NODE_CLASS: { @@ -808,6 +828,7 @@ public GraphDocument parse() throws IOException { hashStack.push(null); boolean restart = false; + long start = System.nanoTime(); try { while (true) { // TERMINATION ARGUMENT: finite length of data source will result in // EOFException eventually. @@ -831,6 +852,11 @@ public GraphDocument parse() throws IOException { } finally { // also terminates the builder closeDanglingGroups(); + if (TRACE_PARSE_TIME) { + long end = System.nanoTime(); + System.err.println(((System.nanoTime() - timeStart) / 1_000_000) + " Parsed file in " + ((end - start) / 1_000_000) + " ms"); + } + } return builder.rootDocument(); } @@ -990,7 +1016,10 @@ private InputGraph parseGraph(String title, boolean toplevel) throws IOException } } + private static final long timeStart = System.nanoTime(); + private InputGraph parseGraph(int dumpId, String format, Object[] args, boolean toplevel) throws IOException { + long start = System.nanoTime(); InputGraph g = builder.startGraph(dumpId, format, args); try { parseProperties(); @@ -1014,6 +1043,10 @@ private InputGraph parseGraph(int dumpId, String format, Object[] args, boolean // we have to finish the graph. reporter.popContext(); g = builder.endGraph(); + if (TRACE_PARSE_TIME) { + long end = System.nanoTime(); + System.err.println(((System.nanoTime() - timeStart) / 1_000_000) + " Parsed " + dumpId + " " + String.format(format, args) + " in " + ((end - start) / 1000000) + " ms"); + } } return g; } diff --git a/visualizer/IdealGraphVisualizer/Data/src/main/java/jdk/graal/compiler/graphio/parsing/Builder.java b/visualizer/IdealGraphVisualizer/Data/src/main/java/jdk/graal/compiler/graphio/parsing/Builder.java index 2e1e46713004..ef760bf76c0f 100644 --- a/visualizer/IdealGraphVisualizer/Data/src/main/java/jdk/graal/compiler/graphio/parsing/Builder.java +++ b/visualizer/IdealGraphVisualizer/Data/src/main/java/jdk/graal/compiler/graphio/parsing/Builder.java @@ -33,6 +33,7 @@ import jdk.graal.compiler.graphio.parsing.BinaryReader.EnumValue; import jdk.graal.compiler.graphio.parsing.BinaryReader.Method; +import jdk.graal.compiler.graphio.parsing.TemplateParser.TemplatePart; import jdk.graal.compiler.graphio.parsing.model.GraphDocument; import jdk.graal.compiler.graphio.parsing.model.Group; import jdk.graal.compiler.graphio.parsing.model.InputBlock; @@ -219,16 +220,23 @@ public String toString() { final class NodeClass { public final String className; public final String nameTemplate; + private final List templateParts; public final List inputs; public final List sux; + private String shortString; NodeClass(String className, String nameTemplate, List inputs, List sux) { this.className = className; this.nameTemplate = nameTemplate; + this.templateParts = jdk.graal.compiler.graphio.parsing.TemplateParser.parseTemplate(nameTemplate); this.inputs = inputs; this.sux = sux; } + public List getTemplateParts() { + return templateParts; + } + @Override public String toString() { return className; @@ -265,13 +273,18 @@ public boolean equals(Object obj) { } String toShortString() { - int lastDot = className.lastIndexOf('.'); - String localShortName = className.substring(lastDot + 1); - if (localShortName.endsWith("Node") && !localShortName.equals("StartNode") && !localShortName.equals(CLASS_ENDNODE)) { - return localShortName.substring(0, localShortName.length() - 4); - } else { - return localShortName; + String s = shortString; + if (s == null) { + int lastDot = className.lastIndexOf('.'); + String localShortName = className.substring(lastDot + 1); + if (localShortName.endsWith("Node") && !localShortName.equals("StartNode") && !localShortName.equals(CLASS_ENDNODE)) { + s = localShortName.substring(0, localShortName.length() - 4); + } else { + s = localShortName; + } + shortString = s; } + return shortString; } } } diff --git a/visualizer/IdealGraphVisualizer/Data/src/main/java/jdk/graal/compiler/graphio/parsing/ModelBuilder.java b/visualizer/IdealGraphVisualizer/Data/src/main/java/jdk/graal/compiler/graphio/parsing/ModelBuilder.java index d13062bdf003..1b590b2b5b72 100644 --- a/visualizer/IdealGraphVisualizer/Data/src/main/java/jdk/graal/compiler/graphio/parsing/ModelBuilder.java +++ b/visualizer/IdealGraphVisualizer/Data/src/main/java/jdk/graal/compiler/graphio/parsing/ModelBuilder.java @@ -47,8 +47,6 @@ import java.util.Objects; import java.util.Set; import java.util.logging.Logger; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import jdk.graal.compiler.graphio.parsing.BinaryReader.EnumValue; import jdk.graal.compiler.graphio.parsing.BinaryReader.Method; @@ -662,6 +660,14 @@ public void endNode(int nodeId) { PROPNAME_ID, PROPNAME_IDX, PROPNAME_BLOCK))); + private static boolean isSystemProperty(String key) { + return switch (key) { + case PROPNAME_HAS_PREDECESSOR, PROPNAME_NAME, PROPNAME_CLASS, PROPNAME_ID, PROPNAME_IDX, PROPNAME_BLOCK -> + true; + default -> false; + }; + } + @Override public void setGroupName(String name, String shortName) { assert folder instanceof Group; @@ -684,7 +690,7 @@ protected final void reportProgress() { @Override public void setNodeName(NodeClass nodeClass) { assert currentNode != null; - getProperties().setProperty(PROPNAME_NAME, createName(nodeClass, nodeEdges, nodeClass.nameTemplate)); + getProperties().setProperty(PROPNAME_NAME, createName(nodeClass, nodeEdges)); getProperties().setProperty(PROPNAME_CLASS, nodeClass.className); switch (nodeClass.className) { case "BeginNode": @@ -696,35 +702,38 @@ public void setNodeName(NodeClass nodeClass) { } } - static final Pattern TEMPLATE_PATTERN = Pattern.compile("\\{([pi])#([a-zA-Z0-9$_]+)(/([lms]))?}"); - - private String createName(NodeClass nodeClass, List edges, String template) { - if (template.isEmpty()) { + private String createName(NodeClass nodeClass, List edges) { + if (nodeClass.nameTemplate.isEmpty()) { return nodeClass.toShortString(); } - Matcher m = TEMPLATE_PATTERN.matcher(template); - StringBuilder sb = new StringBuilder(); + + StringBuilder sb = new StringBuilder(nodeClass.nameTemplate.length()); Properties p = getProperties(); - while (m.find()) { - String name = m.group(2); - String type = m.group(1); - String result; + List templateParts = nodeClass.getTemplateParts(); + for (TemplateParser.TemplatePart template : templateParts) { + if (!template.isReplacement) { + sb.append(template.value); + continue; + } + String name = template.name; + String type = template.type; switch (type) { case "i": - StringBuilder inputString = new StringBuilder(); + boolean first = true; for (EdgeInfo edge : edges) { if (edge.label.startsWith(name) && (name.length() == edge.label.length() || edge.label.charAt(name.length()) == '[')) { - if (inputString.length() > 0) { - inputString.append(", "); + if (!first) { + sb.append(", "); } - inputString.append(edge.from); + first = false; + sb.append(edge.from); } } - result = inputString.toString(); break; case "p": Object prop = p.get(name); - String length = m.group(4); + String length = template.length; + String result; if (prop == null) { result = "?"; } else if (length != null && prop instanceof LengthToString lengthProp) { @@ -735,36 +744,21 @@ private String createName(NodeClass nodeClass, List edges, String temp case "m": result = lengthProp.toString(Length.M); break; - default: case "l": + default: result = lengthProp.toString(Length.L); break; } } else { result = prop.toString(); } + sb.append(result); break; default: - result = "#?#"; + sb.append("#?#"); break; } - - // Escape '\' and '$' to not interfere with the regular expression. - StringBuilder newResult = new StringBuilder(); - for (int i = 0; i < result.length(); ++i) { - char c = result.charAt(i); - if (c == '\\') { - newResult.append("\\\\"); - } else if (c == '$') { - newResult.append("\\$"); - } else { - newResult.append(c); - } - } - result = newResult.toString(); - m.appendReplacement(sb, result); } - m.appendTail(sb); return sb.toString(); } @@ -775,7 +769,7 @@ public void setNodeProperty(String key, Object value) { assert currentNode != null; String k = key; if (!(value instanceof InputGraph)) { - if (SYSTEM_PROPERTIES.contains(key)) { + if (isSystemProperty(key)) { k = NOT_DATA + k; } setProperty(k, value); diff --git a/visualizer/IdealGraphVisualizer/Data/src/main/java/jdk/graal/compiler/graphio/parsing/TemplateParser.java b/visualizer/IdealGraphVisualizer/Data/src/main/java/jdk/graal/compiler/graphio/parsing/TemplateParser.java new file mode 100644 index 000000000000..91757ac91cb4 --- /dev/null +++ b/visualizer/IdealGraphVisualizer/Data/src/main/java/jdk/graal/compiler/graphio/parsing/TemplateParser.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.graal.compiler.graphio.parsing; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class TemplateParser { + private static final Pattern TEMPLATE_PATTERN = Pattern.compile("\\{([pi])#([a-zA-Z0-9$_]+)(/([lms]))?}"); + + public static List parseTemplate(String template) { + List parts = new ArrayList<>(); + Matcher m = TEMPLATE_PATTERN.matcher(template); + int lastEnd = 0; + while (m.find()) { + if (m.start() > lastEnd) { + parts.add(new TemplatePart(template.substring(lastEnd, m.start()))); + } + String name = m.group(2); + String type = m.group(1); + switch (type) { + case "i": + parts.add(new TemplatePart(name, type, null)); + break; + case "p": + String length = m.group(4); + parts.add(new TemplatePart(name, type, length)); + break; + default: + parts.add(new TemplatePart("#?#")); + break; + } + lastEnd = m.end(); + } + if (lastEnd < template.length()) { + parts.add(new TemplatePart(template.substring(lastEnd))); + } + return parts; + } + + public static class TemplatePart { + public final String value; + public final boolean isReplacement; + public final String name; + public final String type; + public final String length; + + public TemplatePart(String name, String type, String length) { + this.name = name; + this.type = type; + this.length = length; + this.value = null; + this.isReplacement = true; + } + + public TemplatePart(String value) { + this.value = value; + this.isReplacement = false; + name = null; + type = null; + length = null; + } + } +} diff --git a/visualizer/IdealGraphVisualizer/Data/src/main/java/jdk/graal/compiler/graphio/parsing/model/GraphDocumentVisitor.java b/visualizer/IdealGraphVisualizer/Data/src/main/java/jdk/graal/compiler/graphio/parsing/model/GraphDocumentVisitor.java new file mode 100644 index 000000000000..fa99503c576e --- /dev/null +++ b/visualizer/IdealGraphVisualizer/Data/src/main/java/jdk/graal/compiler/graphio/parsing/model/GraphDocumentVisitor.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.graal.compiler.graphio.parsing.model; + +import java.util.function.Consumer; + +/** + * Visitor interface to traverse the various elements of the {@link GraphDocument} tree. Traversal + * can be stopped early by returning {@code false} from any of the {@code visit} methods. + */ +public interface GraphDocumentVisitor { + + static void visitAll(GraphDocument doc, Consumer visit) { + GraphDocumentVisitor visitor = inputGraph -> { + visit.accept(inputGraph); + return true; + }; + visitor.visit(doc); + } + + /** + * Visits a {@link Folder} in the graph document, which could be the top-level + * {@link GraphDocument} or a nested {@link Group}. + *

+ * The default implementation visits all elements in order, stopping if + * {@link #visit(FolderElement)} returns false for any of them. + */ + default boolean visit(Folder folder) { + for (FolderElement elem : folder.getElements()) { + if (!visit(elem)) { + return false; + } + } + return true; + } + + /** + * Visits an {@link InputGraph}. Implementors can choose whether to stop at the outermost level + * or also consider nested subgraphs. + */ + boolean visit(InputGraph inputGraph); + + /** + * Visits a {@link Group}. The default implementation delegates to {@link #visit(Folder)}. + */ + default boolean visit(Group g) { + return visit((Folder) g); + } + + /** + * Visits a {@link GraphDocument}. The default implementation delegates to + * {@link #visit(Folder)}. + */ + default boolean visit(GraphDocument document) { + return visit((Folder) document); + } + + /** + * Visits a {@link FolderElement}. The default implementation delegates to the {@code visit} + * version corresponding to the concrete type of {@code element}. + */ + default boolean visit(FolderElement element) { + if (element instanceof Group group) { + return visit(group); + } + if (element instanceof InputGraph inputGraph) { + return visit(inputGraph); + } + if (element instanceof Folder folder) { + return visit(folder); + } + return true; + } +} diff --git a/visualizer/IdealGraphVisualizer/Data/src/main/java/jdk/graal/compiler/graphio/parsing/model/Properties.java b/visualizer/IdealGraphVisualizer/Data/src/main/java/jdk/graal/compiler/graphio/parsing/model/Properties.java index 654daab68feb..61ad2ab8fcd7 100644 --- a/visualizer/IdealGraphVisualizer/Data/src/main/java/jdk/graal/compiler/graphio/parsing/model/Properties.java +++ b/visualizer/IdealGraphVisualizer/Data/src/main/java/jdk/graal/compiler/graphio/parsing/model/Properties.java @@ -288,9 +288,13 @@ public LinkedHashMapProperties() { } public LinkedHashMapProperties(Properties p) { - this(p.size()); - for (Property prop : p) { - map.put(prop.getName(), prop.getValue()); + if (p instanceof LinkedHashMapProperties hash) { + map = new LinkedHashMap<>(hash.map); + } else { + map = new LinkedHashMap<>(p.size()); + for (Property prop : p) { + map.put(prop.getName(), prop.getValue()); + } } } @@ -303,6 +307,49 @@ public int size() { return map.size(); } + @Override + public boolean equals(Object o) { + if (o instanceof LinkedHashMapProperties hash) { + if (size() != hash.size()) { + return false; + } + /* + * Most Properties comparisons are for cases where they are the same or very similar + * so efficiency is fairly important to loading speed. They are commonly ordered in + * the same fashion so take advantage of that in the comparison + */ + Set> set1 = map.entrySet(); + Iterator> iter1 = set1.iterator(); + + Set> set2 = hash.map.entrySet(); + Iterator> iter2 = set2.iterator(); + while (iter1.hasNext()) { + var entry = iter1.next(); + // They are the same length so hasNext must be true + Map.Entry next = iter2.next(); + if (entry.getKey().equals(next.getKey())) { + if (!Objects.deepEquals(entry.getValue(), next.getValue())) { + return false; + } + continue; + } + // Different key encountered so must resort to contains for all following values + if (!Objects.deepEquals(entry.getValue(), hash.map.get(entry.getKey()))) { + return false; + } + while (iter1.hasNext()) { + entry = iter1.next(); + if (!Objects.deepEquals(entry.getValue(), hash.map.get(entry.getKey()))) { + return false; + } + } + return true; + } + return true; + } + return super.equals(o); + } + @Override public Property atIndex(int index) { if (index >= size() || index < 0) { @@ -469,10 +516,10 @@ protected final int makeHash() { int hash = 5; for (Property prop : this) { hash = hash ^ (Property.makeHash(prop.getName(), prop.getValue())); // position affected - // hash would - // violate - // equal/hash - // contract + // hash would + // violate + // equal/hash + // contract } return hash; } diff --git a/visualizer/IdealGraphVisualizer/Difference/pom.xml b/visualizer/IdealGraphVisualizer/Difference/pom.xml index 4f1a24ebf11f..ba8218b76655 100644 --- a/visualizer/IdealGraphVisualizer/Difference/pom.xml +++ b/visualizer/IdealGraphVisualizer/Difference/pom.xml @@ -3,7 +3,7 @@ org.graalvm.visualizer IdealGraphVisualizer-parent - 1.23-SNAPSHOT + 1.24-SNAPSHOT Difference nbm diff --git a/visualizer/IdealGraphVisualizer/Filter/pom.xml b/visualizer/IdealGraphVisualizer/Filter/pom.xml index c73b69511f59..687291827aab 100644 --- a/visualizer/IdealGraphVisualizer/Filter/pom.xml +++ b/visualizer/IdealGraphVisualizer/Filter/pom.xml @@ -3,7 +3,7 @@ org.graalvm.visualizer IdealGraphVisualizer-parent - 1.23-SNAPSHOT + 1.24-SNAPSHOT Filter nbm diff --git a/visualizer/IdealGraphVisualizer/Filter/src/main/java/org/graalvm/visualizer/filter/CachedLanguageFilter.java b/visualizer/IdealGraphVisualizer/Filter/src/main/java/org/graalvm/visualizer/filter/CachedLanguageFilter.java new file mode 100644 index 000000000000..39d5b0c9a89a --- /dev/null +++ b/visualizer/IdealGraphVisualizer/Filter/src/main/java/org/graalvm/visualizer/filter/CachedLanguageFilter.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.graalvm.visualizer.filter; + +import org.graalvm.visualizer.graph.Diagram; + +import java.util.ArrayList; + +/** + * This represents a cacheable filter generated by {@link CustomFilter}. It permits a {@link CustomFilter} to build the + * filter once instead of having to construct the filter for each invocation of the filter. + */ +public class CachedLanguageFilter extends AbstractFilter { + private final ArrayList filters = new ArrayList<>(); + private String name = "CachedLanguageFilter"; + + public CachedLanguageFilter() { + } + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public void apply(Diagram d) { + for (var filter : filters) { + filter.apply(d); + } + } + + public void add(AbstractFilter filter) { + filters.add(filter); + } +} diff --git a/visualizer/IdealGraphVisualizer/Filter/src/main/java/org/graalvm/visualizer/filter/CustomFilter.java b/visualizer/IdealGraphVisualizer/Filter/src/main/java/org/graalvm/visualizer/filter/CustomFilter.java index f6b5edc0d9f6..e6f6646b2a3d 100644 --- a/visualizer/IdealGraphVisualizer/Filter/src/main/java/org/graalvm/visualizer/filter/CustomFilter.java +++ b/visualizer/IdealGraphVisualizer/Filter/src/main/java/org/graalvm/visualizer/filter/CustomFilter.java @@ -77,6 +77,13 @@ public class CustomFilter extends AbstractFilter { private String code; private String name; + /** + * A cached version of the filter chain built by the filter invocation. This permits reusing the filter instead of + * constructor it from scratch each time. If the filter invocation returns an instance of {@link CachedLanguageFilter} + * then that object is saved and used for all future invocations. + */ + private CachedLanguageFilter cachedFilter; + public CustomFilter(String name, String code) { this(name, code, MIME_JAVASCRIPT, Lookup.EMPTY); } @@ -113,6 +120,7 @@ public void setCode(String s) { return; } code = s; + cachedFilter = null; fireChangedEvent(); } @@ -265,6 +273,11 @@ private PreparedScript executeHelperScripts(FilterEnvironment env, Map org.graalvm.visualizer IdealGraphVisualizer-parent - 1.23-SNAPSHOT + 1.24-SNAPSHOT FilterProfiles nbm diff --git a/visualizer/IdealGraphVisualizer/FilterWindow/pom.xml b/visualizer/IdealGraphVisualizer/FilterWindow/pom.xml index ba9fd2815d0c..d7711ab9911f 100644 --- a/visualizer/IdealGraphVisualizer/FilterWindow/pom.xml +++ b/visualizer/IdealGraphVisualizer/FilterWindow/pom.xml @@ -3,7 +3,7 @@ org.graalvm.visualizer IdealGraphVisualizer-parent - 1.23-SNAPSHOT + 1.24-SNAPSHOT FilterWindow nbm diff --git a/visualizer/IdealGraphVisualizer/Graal/pom.xml b/visualizer/IdealGraphVisualizer/Graal/pom.xml index 73a7c121f089..8a22a9ee9cd3 100644 --- a/visualizer/IdealGraphVisualizer/Graal/pom.xml +++ b/visualizer/IdealGraphVisualizer/Graal/pom.xml @@ -3,7 +3,7 @@ org.graalvm.visualizer IdealGraphVisualizer-parent - 1.23-SNAPSHOT + 1.24-SNAPSHOT Graal nbm diff --git a/visualizer/IdealGraphVisualizer/Graal/src/main/java/org/graalvm/visualizer/graal/filters/PropertyColorFilter.java b/visualizer/IdealGraphVisualizer/Graal/src/main/java/org/graalvm/visualizer/graal/filters/PropertyColorFilter.java new file mode 100644 index 000000000000..a369d3c2647d --- /dev/null +++ b/visualizer/IdealGraphVisualizer/Graal/src/main/java/org/graalvm/visualizer/graal/filters/PropertyColorFilter.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.graalvm.visualizer.graal.filters; + +import org.graalvm.visualizer.filter.AbstractFilter; +import org.graalvm.visualizer.graph.Diagram; +import org.graalvm.visualizer.graph.Figure; + +import java.awt.Color; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + + +/** + * A more efficient bulk coloring implementation. + */ +public class PropertyColorFilter extends AbstractFilter { + Map> stringColorMap = new HashMap<>(); + + public PropertyColorFilter() { + } + + @Override + public String getName() { + return getClass().getSimpleName(); + } + + @Override + public void apply(Diagram diagram) { + applyColorMap(diagram); + } + + private void applyColorMap(Diagram diagram) { + Set keys = stringColorMap.keySet(); + for (Figure f : diagram.getFigures()) { + for (String property : keys) { + Object value = f.getProperties().get(property); + if (value == null) { + continue; + } + Color color = stringColorMap.get(property).get(value.toString()); + if (color != null) { + f.setColor(color); + } + } + } + } + + /** + * Color any {@link Figure} with the {@code property} equals to {@code value} with the color {@code color}. + */ + public void addRule(String property, String value, Color color) { + stringColorMap.computeIfAbsent(property, x -> new HashMap<>()).put(value, color); + } +} diff --git a/visualizer/IdealGraphVisualizer/Graal/src/main/resources/org/graalvm/visualizer/graal/filters/color.filter b/visualizer/IdealGraphVisualizer/Graal/src/main/resources/org/graalvm/visualizer/graal/filters/color.filter index 62d498a7f3f4..e9426526099b 100644 --- a/visualizer/IdealGraphVisualizer/Graal/src/main/resources/org/graalvm/visualizer/graal/filters/color.filter +++ b/visualizer/IdealGraphVisualizer/Graal/src/main/resources/org/graalvm/visualizer/graal/filters/color.filter @@ -8,23 +8,44 @@ var gray = new Color(220, 220, 220); var violet = new Color(201, 148, 199); var black = new Color(0, 0, 0); -colorize("category", "controlSink", red); -colorize("category", "controlSplit", red); -colorize("category", "merge", red); -colorize("category", "begin", orange); -colorize("category", "end", orange); -colorize("category", "fixed", yellow); -colorize("category", "state", lightGreen); -colorize("category", "phi", middleBlue); -colorize("category", "proxy", middleBlue); -colorize("category", "floating", lightBlue); -colorize("class", ".*\\.nodes\\..*ParameterNode$", gray); -colorize("class", ".*\\.nodes\\..*ConstantNode$", gray); -colorize("class", ".*\\.nodes\\..*GuardNode$", violet); -colorize("class", ".*\\.nodes\\.debug\\.BlackholeNode$", black); +var cachedFilter = new CachedLanguageFilter(); + +var f = new PropertyColorFilter(); +f.addRule("category", "controlSink", red); +f.addRule("category", "controlSplit", red); +f.addRule("category", "merge", red); +f.addRule("category", "begin", orange); +f.addRule("category", "end", orange); +f.addRule("category", "fixed", yellow); +f.addRule("category", "state", lightGreen); +f.addRule("category", "phi", middleBlue); +f.addRule("category", "proxy", middleBlue); +f.addRule("category", "floating", lightBlue); + +f.addRule("class", "com.oracle.graal.nodes.ParameterNode", gray); +f.addRule("class", "jdk.graal.compiler.nodes.ParameterNode", gray); +f.addRule("class", "org.graalvm.compiler.nodes.ParameterNode", gray); + +f.addRule("class", "com.oracle.graal.nodes.ConstantNode", gray); +f.addRule("class", "jdk.graal.compiler.nodes.ConstantNode", gray); +f.addRule("class", "org.graalvm.compiler.nodes.ConstantNode", gray); + +f.addRule("class", "com.oracle.graal.nodes.GuardNode", violet); +f.addRule("class", "jdk.graal.compiler.nodes.GuardNode", violet); +f.addRule("class", "org.graalvm.compiler.nodes.GuardNode", violet); + +f.addRule("class", "com.oracle.graal.nodes.debug.BlackholeNode", black); +f.addRule("class", "jdk.graal.compiler.nodes.debug.BlackholeNode", black); +f.addRule("class", "org.graalvm.compiler.nodes.debug.BlackholeNode", black); + +cachedFilter.add(f); var f = new GraalEdgeColorFilter(); f.setUsageColor("Successor", red); f.setUsageColor("Value", blue); f.setUsageColor("Memory", new Color(0, 128, 0)); -f.apply(graph); + +cachedFilter.add(f); + +// return the cached filter +cachedFilter diff --git a/visualizer/IdealGraphVisualizer/Graal/src/main/resources/org/graalvm/visualizer/graal/filters/graalFilters.properties b/visualizer/IdealGraphVisualizer/Graal/src/main/resources/org/graalvm/visualizer/graal/filters/graalFilters.properties index 7a4660ff1915..aa206c09d917 100644 --- a/visualizer/IdealGraphVisualizer/Graal/src/main/resources/org/graalvm/visualizer/graal/filters/graalFilters.properties +++ b/visualizer/IdealGraphVisualizer/Graal/src/main/resources/org/graalvm/visualizer/graal/filters/graalFilters.properties @@ -4,3 +4,4 @@ GraalCFGFilter=org.graalvm.visualizer.graal.filters.GraalCFGFilter GraalColoringFilter=org.graalvm.visualizer.graal.filters.GraalColoringFilter GraalEdgeColorFilter=org.graalvm.visualizer.graal.filters.GraalEdgeColorFilter +PropertyColorFilter=org.graalvm.visualizer.graal.filters.PropertyColorFilter \ No newline at end of file diff --git a/visualizer/IdealGraphVisualizer/Graal/src/main/resources/org/graalvm/visualizer/graal/filters/removeState.filter b/visualizer/IdealGraphVisualizer/Graal/src/main/resources/org/graalvm/visualizer/graal/filters/removeState.filter index 5990b13b19fc..1d2e3397076c 100644 --- a/visualizer/IdealGraphVisualizer/Graal/src/main/resources/org/graalvm/visualizer/graal/filters/removeState.filter +++ b/visualizer/IdealGraphVisualizer/Graal/src/main/resources/org/graalvm/visualizer/graal/filters/removeState.filter @@ -1 +1,3 @@ -remove("category", "state"); \ No newline at end of file +// Removing orphans also removes any nodes which no longer have any uses. +// This cleans up Constants which were only referenced by FrameStates +removeIncludingOrphans("category", "state") \ No newline at end of file diff --git a/visualizer/IdealGraphVisualizer/Graph/pom.xml b/visualizer/IdealGraphVisualizer/Graph/pom.xml index 01b7e1365220..22f44dfadfd2 100644 --- a/visualizer/IdealGraphVisualizer/Graph/pom.xml +++ b/visualizer/IdealGraphVisualizer/Graph/pom.xml @@ -3,7 +3,7 @@ org.graalvm.visualizer IdealGraphVisualizer-parent - 1.23-SNAPSHOT + 1.24-SNAPSHOT Graph nbm diff --git a/visualizer/IdealGraphVisualizer/Graph/src/main/java/org/graalvm/visualizer/graph/Connection.java b/visualizer/IdealGraphVisualizer/Graph/src/main/java/org/graalvm/visualizer/graph/Connection.java index 06707dc83664..35308e3b0f4d 100644 --- a/visualizer/IdealGraphVisualizer/Graph/src/main/java/org/graalvm/visualizer/graph/Connection.java +++ b/visualizer/IdealGraphVisualizer/Graph/src/main/java/org/graalvm/visualizer/graph/Connection.java @@ -29,6 +29,7 @@ import java.awt.Point; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; public final class Connection implements Link { @@ -141,6 +142,22 @@ public void remove() { outputSlot.connections.remove(this); } + /** + * When removing large numbers of figures, repeatedly removing individual edges becomes n squared because + * of the use of ArrayList. If the {@code cleaned} map is passed in then the Figures to be removed have been marked + * with {@link Figure#isDeleted()}. The lists of successor, predecessors and connections are then just cleaned once. + */ + public void remove(HashSet cleaned) { + if (cleaned == null) { + remove(); + return; + } + + inputSlot.cleanDeletedFigures(cleaned); + outputSlot.cleanDeletedFigures(cleaned); + } + + public String getToolTipText() { StringBuilder builder = new StringBuilder(); if (label != null) { diff --git a/visualizer/IdealGraphVisualizer/Graph/src/main/java/org/graalvm/visualizer/graph/Diagram.java b/visualizer/IdealGraphVisualizer/Graph/src/main/java/org/graalvm/visualizer/graph/Diagram.java index 238f619c268a..203c46e4da3c 100644 --- a/visualizer/IdealGraphVisualizer/Graph/src/main/java/org/graalvm/visualizer/graph/Diagram.java +++ b/visualizer/IdealGraphVisualizer/Graph/src/main/java/org/graalvm/visualizer/graph/Diagram.java @@ -22,24 +22,38 @@ */ package org.graalvm.visualizer.graph; -import static jdk.graal.compiler.graphio.parsing.model.KnownPropertyNames.PROPNAME_NAME; -import static jdk.graal.compiler.graphio.parsing.model.KnownPropertyValues.NAME_ROOT; -import static jdk.graal.compiler.graphio.parsing.model.KnownPropertyValues.NAME_START; +import jdk.graal.compiler.graphio.parsing.model.InputBlock; +import jdk.graal.compiler.graphio.parsing.model.InputEdge; +import jdk.graal.compiler.graphio.parsing.model.InputGraph; +import jdk.graal.compiler.graphio.parsing.model.InputNode; +import jdk.graal.compiler.graphio.parsing.model.Properties; +import jdk.graal.compiler.graphio.parsing.model.Properties.EqualityPropertyMatcher; +import org.graalvm.visualizer.data.Source; -import java.awt.*; -import java.util.*; +import java.awt.Dimension; +import java.awt.Font; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Consumer; import java.util.stream.Stream; -import org.graalvm.visualizer.data.Source; - -import jdk.graal.compiler.graphio.parsing.model.*; -import jdk.graal.compiler.graphio.parsing.model.Properties; -import jdk.graal.compiler.graphio.parsing.model.Properties.EqualityPropertyMatcher; +import static jdk.graal.compiler.graphio.parsing.model.KnownPropertyNames.PROPNAME_NAME; +import static jdk.graal.compiler.graphio.parsing.model.KnownPropertyValues.NAME_ROOT; +import static jdk.graal.compiler.graphio.parsing.model.KnownPropertyValues.NAME_START; /** * Visual model of an {@link InputGraph}. Captures positions, sizes, routing @@ -319,20 +333,18 @@ public static Diagram createDiagram(InputGraph graph, String nodeText) { return d; } - public void removeAllFigures(Collection
figuresToRemove) { - if (figuresToRemove instanceof Set) { - removeAllFigures(((Set) figuresToRemove)); - } - Set s = new HashSet<>(figuresToRemove); - removeAllFigures(s); - } - public void removeAllFigures(Set
figuresToRemove) { if (!figuresToRemove.isEmpty()) { invalidateSlotMap(); } + // First mark all Figures to be deleted + for (Figure f : figuresToRemove) { + f.setDeleted(); + } + // Now remove the edges associated with the deleted figures + HashSet cleaned = new HashSet<>(); for (Figure f : figuresToRemove) { - freeFigure(f); + freeFigure(f, cleaned); } for (Figure f : figuresToRemove) { @@ -348,16 +360,21 @@ private Set collectFigureIds(Figure succ) { return representedIds; } - private void freeFigure(Figure succ) { + private void freeFigure(Figure succ, HashSet cleaned) { Set representedIds = sourceMap == null ? null : collectFigureIds(succ); List inputSlots = new ArrayList<>(succ.getInputSlots()); for (InputSlot s : inputSlots) { - succ.removeInputSlot(s); + succ.removeInputSlot(s, cleaned); } List outputSlots = new ArrayList<>(succ.getOutputSlots()); for (OutputSlot s : outputSlots) { - succ.removeOutputSlot(s); + succ.removeOutputSlot(s, cleaned); + } + + if (cleaned != null) { + // When doing a bulk cleaning, the edges of deleted figures aren't directly updated so clear then here. + succ.clear(); } assert succ.getInputSlots().isEmpty(); @@ -372,7 +389,7 @@ private void freeFigure(Figure succ) { public void removeFigure(Figure succ) { assert this.figureMap.containsValue(succ); - freeFigure(succ); + freeFigure(succ, null); this.figureMap.remove(succ.getId()); invalidateSlotMap(); } @@ -385,6 +402,57 @@ public InputGraph getGraph() { return graph; } + + public Iterable iterateConnections() { + return new Iterable<>() { + @Override + public Iterator iterator() { + return new Iterator<>() { + + final Iterator
figureMapIterator = figureMap.values().iterator(); + Iterator inputSlotIterator = Collections.emptyIterator(); + Iterator connectionIterator = Collections.emptyIterator(); + + @Override + public boolean hasNext() { + while (true) { + if (connectionIterator.hasNext()) { + return true; + } + if (inputSlotIterator.hasNext()) { + connectionIterator = inputSlotIterator.next().getConnections().iterator(); + continue; + } + if (figureMapIterator.hasNext()) { + inputSlotIterator = figureMapIterator.next().getInputSlots().iterator(); + continue; + } + return false; + } + } + + @Override + public Connection next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + return connectionIterator.next(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public void forEachRemaining(Consumer action) { + throw new UnsupportedOperationException(); + } + }; + } + }; + } + public Set getConnections() { Set connections = new HashSet<>(); diff --git a/visualizer/IdealGraphVisualizer/Graph/src/main/java/org/graalvm/visualizer/graph/Figure.java b/visualizer/IdealGraphVisualizer/Graph/src/main/java/org/graalvm/visualizer/graph/Figure.java index e621c39350c2..55a273825f1a 100644 --- a/visualizer/IdealGraphVisualizer/Graph/src/main/java/org/graalvm/visualizer/graph/Figure.java +++ b/visualizer/IdealGraphVisualizer/Graph/src/main/java/org/graalvm/visualizer/graph/Figure.java @@ -22,22 +22,31 @@ */ package org.graalvm.visualizer.graph; -import static jdk.graal.compiler.graphio.parsing.model.KnownPropertyNames.PROPNAME_NAME; -import static jdk.graal.compiler.graphio.parsing.model.KnownPropertyValues.NAME_ROOT; - -import java.awt.*; -import java.awt.image.BufferedImage; -import java.util.*; -import java.util.List; - -import org.graalvm.visualizer.data.Source; -import org.graalvm.visualizer.layout.Cluster; -import org.graalvm.visualizer.layout.Vertex; - import jdk.graal.compiler.graphio.parsing.model.InputBlock; import jdk.graal.compiler.graphio.parsing.model.InputGraph; import jdk.graal.compiler.graphio.parsing.model.InputNode; import jdk.graal.compiler.graphio.parsing.model.Properties; +import org.graalvm.visualizer.data.Source; +import org.graalvm.visualizer.layout.Cluster; +import org.graalvm.visualizer.layout.Vertex; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static jdk.graal.compiler.graphio.parsing.model.KnownPropertyNames.PROPNAME_NAME; +import static jdk.graal.compiler.graphio.parsing.model.KnownPropertyValues.NAME_ROOT; public class Figure extends Properties.Entity implements Source.Provider, Vertex, DiagramItem { public static final int INSET = 8; @@ -46,14 +55,14 @@ public class Figure extends Properties.Entity implements Source.Provider, Vertex public static final int SLOT_START = 4; public static final int SLOT_OFFSET = 8; - protected List inputSlots; + protected ArrayList inputSlots; private OutputSlot singleOutput; - private List outputSlots; + private ArrayList outputSlots; private final Source source; private final Diagram diagram; private Point position; - private final List
predecessors; - private final List
successors; + private final ArrayList
predecessors; + private final ArrayList
successors; private List subgraphs; private Color color; private final int id; @@ -64,6 +73,39 @@ public class Figure extends Properties.Entity implements Source.Provider, Vertex private final int hash; private boolean boundary; + /** + * Marked for bulk deletion. + */ + private boolean deleted; + + void setDeleted() { + this.deleted = true; + } + + boolean isDeleted() { + return deleted; + } + + /** + * Drop all edges when deleting a Figure. + */ + void clear() { + assert isDeleted() : this; + if (inputSlots != null) { + inputSlots.clear(); + } + if (outputSlots != null) { + outputSlots.clear(); + } + if (predecessors != null) { + predecessors.clear(); + } + if (successors != null) { + successors.clear(); + } + } + + /** * Visible flag */ @@ -110,6 +152,17 @@ public static int getSlotsWidth(Collection slots) { return result; } + public int getSlotsWidthBefore(InputSlot inputSlot) { + int result = Figure.SLOT_OFFSET; + for (Slot s : inputSlots) { + if (s == inputSlot) { + return result; + } + result += s.getWidth() + Figure.SLOT_OFFSET; + } + return result; + } + public static int getSlotsWidth(Slot s) { if (s == null) { return Figure.SLOT_OFFSET; @@ -213,6 +266,14 @@ protected void addSuccessor(Figure f) { this.successors.add(f); } + /** + * Clean up all edges which reference an {@link Figure#isDeleted()} figure. + */ + public void cleanDeletedFigures() { + predecessors.removeIf(Figure::isDeleted); + successors.removeIf(Figure::isDeleted); + } + protected void removePredecessor(Figure f) { assert predecessors.contains(f); predecessors.remove(f); @@ -355,6 +416,10 @@ public List getInputSlots() { return Collections.unmodifiableList(inputSlots); } + int getInputSlotsWidth() { + return Figure.getSlotsWidth(inputSlots); + } + public Set getSlots() { Set result = new HashSet<>(); result.addAll(getInputSlots()); @@ -372,13 +437,24 @@ public List getOutputSlots() { } } - void removeInputSlot(InputSlot s) { - s.removeAllConnections(); + + public int getOutputSlotsWidth() { + if (outputSlots != null) { + return Figure.getSlotsWidth(outputSlots); + } else if (singleOutput != null) { + return Figure.getSlotsWidth(Collections.singletonList(singleOutput)); + } else { + return 0; + } + } + + void removeInputSlot(InputSlot s, HashSet cleaned) { + s.removeAllConnections(cleaned); inputSlots.remove(s); } - void removeOutputSlot(OutputSlot s) { - s.removeAllConnections(); + void removeOutputSlot(OutputSlot s, HashSet cleaned) { + s.removeAllConnections(cleaned); doRemoveOutputSlot(s); } @@ -459,11 +535,16 @@ public Cluster getCluster() { } } + Boolean isRoot; + @Override public boolean isRoot() { - List sourceNodes = source.getSourceNodes(); - //Get property value just once - return sourceNodes.size() > 0 && NAME_ROOT.equals(sourceNodes.get(0).getProperties().get(PROPNAME_NAME, String.class)); + if (isRoot == null) { + List sourceNodes = source.getSourceNodes(); + // Get property value just once + isRoot = !sourceNodes.isEmpty() && NAME_ROOT.equals(sourceNodes.get(0).getProperties().get(PROPNAME_NAME, String.class)); + } + return isRoot; } @Override @@ -478,6 +559,7 @@ public Rectangle getBounds() { void sourcesChanged(Source s) { diagram.invalidateSlotMap(); + isRoot = null; } @Override @@ -545,4 +627,5 @@ private static void replaceFromTo(Figure from, Figure to) { public void replaceFrom(Figure source) { replaceFromTo(source, this); } + } diff --git a/visualizer/IdealGraphVisualizer/Graph/src/main/java/org/graalvm/visualizer/graph/InputSlot.java b/visualizer/IdealGraphVisualizer/Graph/src/main/java/org/graalvm/visualizer/graph/InputSlot.java index 5fd91347f267..77c404d41089 100644 --- a/visualizer/IdealGraphVisualizer/Graph/src/main/java/org/graalvm/visualizer/graph/InputSlot.java +++ b/visualizer/IdealGraphVisualizer/Graph/src/main/java/org/graalvm/visualizer/graph/InputSlot.java @@ -43,13 +43,13 @@ protected Slot copyInto(Figure f) { @Override public Point getRelativePosition() { - int gap = getFigure().getWidth() - Figure.getSlotsWidth(getFigure().getInputSlots()); + int gap = getFigure().getWidth() - getFigure().getInputSlotsWidth(); if (gap < 0) { gap = 0; } double gapRatio = (double) gap / (double) (getFigure().getInputSlots().size() + 1); int gapAmount = (int) ((getPosition() + 1) * gapRatio); - return new Point(gapAmount + Figure.getSlotsWidth(Figure.getAllBefore(getFigure().getInputSlots(), this)) + getWidth() / 2, -Figure.SLOT_START); + return new Point(gapAmount + getFigure().getSlotsWidthBefore(this) + getWidth() / 2, -Figure.SLOT_START); } @Override diff --git a/visualizer/IdealGraphVisualizer/Graph/src/main/java/org/graalvm/visualizer/graph/OutputSlot.java b/visualizer/IdealGraphVisualizer/Graph/src/main/java/org/graalvm/visualizer/graph/OutputSlot.java index 89b162b7b77a..65adb755490c 100644 --- a/visualizer/IdealGraphVisualizer/Graph/src/main/java/org/graalvm/visualizer/graph/OutputSlot.java +++ b/visualizer/IdealGraphVisualizer/Graph/src/main/java/org/graalvm/visualizer/graph/OutputSlot.java @@ -42,7 +42,7 @@ protected Slot copyInto(Figure f) { @Override public Point getRelativePosition() { - int gap = getFigure().getWidth() - Figure.getSlotsWidth(getFigure().getOutputSlots()); + int gap = getFigure().getWidth() - getFigure().getOutputSlotsWidth(); if (gap < 0) { gap = 0; } diff --git a/visualizer/IdealGraphVisualizer/Graph/src/main/java/org/graalvm/visualizer/graph/Slot.java b/visualizer/IdealGraphVisualizer/Graph/src/main/java/org/graalvm/visualizer/graph/Slot.java index c74e334f3e65..cea3e3c99dca 100644 --- a/visualizer/IdealGraphVisualizer/Graph/src/main/java/org/graalvm/visualizer/graph/Slot.java +++ b/visualizer/IdealGraphVisualizer/Graph/src/main/java/org/graalvm/visualizer/graph/Slot.java @@ -22,22 +22,27 @@ */ package org.graalvm.visualizer.graph; -import static jdk.graal.compiler.graphio.parsing.model.KnownPropertyNames.*; +import jdk.graal.compiler.graphio.parsing.model.InputNode; +import jdk.graal.compiler.graphio.parsing.model.Properties; +import org.graalvm.visualizer.data.Source; +import org.graalvm.visualizer.layout.Port; +import org.graalvm.visualizer.layout.Vertex; +import org.graalvm.visualizer.util.StringUtils; -import java.awt.*; +import java.awt.Color; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.HashSet; import java.util.List; -import org.graalvm.visualizer.data.Source; -import org.graalvm.visualizer.layout.Port; -import org.graalvm.visualizer.layout.Vertex; -import org.graalvm.visualizer.util.StringUtils; - -import jdk.graal.compiler.graphio.parsing.model.InputNode; -import jdk.graal.compiler.graphio.parsing.model.Properties; +import static jdk.graal.compiler.graphio.parsing.model.KnownPropertyNames.PROPNAME_CONNECTION_COUNT; +import static jdk.graal.compiler.graphio.parsing.model.KnownPropertyNames.PROPNAME_FIGURE; +import static jdk.graal.compiler.graphio.parsing.model.KnownPropertyNames.PROPNAME_NAME; public abstract class Slot implements Port, Source.Provider, Properties.Provider { @@ -201,9 +206,13 @@ public List getConnections() { } public void removeAllConnections() { + removeAllConnections(null); + } + + public void removeAllConnections(HashSet cleaned) { List connectionsCopy = new ArrayList<>(this.connections); for (Connection c : connectionsCopy) { - c.remove(); + c.remove(cleaned); } } @@ -212,4 +221,18 @@ public Vertex getVertex() { return figure; } + /** + * Clean up all edges which reference an {@link Figure#isDeleted()} figure. + */ + void cleanDeletedFigures(HashSet cleaned) { + Figure input = getFigure(); + if (!input.isDeleted()) { + if (cleaned.add(input)) { + input.cleanDeletedFigures(); + } + } + if (cleaned.add(this)) { + this.connections.removeIf(x -> x.getInputSlot().getFigure().isDeleted() || x.getOutputSlot().getFigure().isDeleted()); + } + } } diff --git a/visualizer/IdealGraphVisualizer/GraphSearch/pom.xml b/visualizer/IdealGraphVisualizer/GraphSearch/pom.xml index 73e3a995eeb3..758e661390ae 100644 --- a/visualizer/IdealGraphVisualizer/GraphSearch/pom.xml +++ b/visualizer/IdealGraphVisualizer/GraphSearch/pom.xml @@ -3,7 +3,7 @@ org.graalvm.visualizer IdealGraphVisualizer-parent - 1.23-SNAPSHOT + 1.24-SNAPSHOT GraphSearch nbm diff --git a/visualizer/IdealGraphVisualizer/HierarchicalLayout/pom.xml b/visualizer/IdealGraphVisualizer/HierarchicalLayout/pom.xml index 7a55ac4f27dc..69e2910c7722 100644 --- a/visualizer/IdealGraphVisualizer/HierarchicalLayout/pom.xml +++ b/visualizer/IdealGraphVisualizer/HierarchicalLayout/pom.xml @@ -3,7 +3,7 @@ org.graalvm.visualizer IdealGraphVisualizer-parent - 1.23-SNAPSHOT + 1.24-SNAPSHOT HierarchicalLayout nbm diff --git a/visualizer/IdealGraphVisualizer/HierarchicalLayout/src/main/java/org/graalvm/visualizer/hierarchicallayout/HierarchicalLayoutManager.java b/visualizer/IdealGraphVisualizer/HierarchicalLayout/src/main/java/org/graalvm/visualizer/hierarchicallayout/HierarchicalLayoutManager.java index 3aa2a7e98994..e1f8cdb61f87 100644 --- a/visualizer/IdealGraphVisualizer/HierarchicalLayout/src/main/java/org/graalvm/visualizer/hierarchicallayout/HierarchicalLayoutManager.java +++ b/visualizer/IdealGraphVisualizer/HierarchicalLayout/src/main/java/org/graalvm/visualizer/hierarchicallayout/HierarchicalLayoutManager.java @@ -53,8 +53,8 @@ import java.util.TreeSet; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.IntUnaryOperator; +import java.util.logging.Level; import java.util.logging.Logger; -import java.util.stream.Collectors; import static org.graalvm.visualizer.settings.layout.LayoutSettings.BOTH_SORT; import static org.graalvm.visualizer.settings.layout.LayoutSettings.CENTER_CROSSING_X; @@ -98,7 +98,7 @@ public class HierarchicalLayoutManager implements LayoutManager { private static final Logger LOG = Logger.getLogger(HierarchicalLayoutManager.class.getName()); - public static final boolean TRACE = false; + public static final boolean TRACE_DEFAULT = false; public static final boolean CHECK = false; public static final int SWEEP_ITERATIONS = 1; public static final int CROSSING_ITERATIONS = 2; @@ -110,6 +110,16 @@ public class HierarchicalLayoutManager implements LayoutManager { public static final int VIP_BONUS = 10; private final AtomicBoolean cancelled; + private final LayoutSettingBean settings; + + private boolean trace = TRACE_DEFAULT; + + /** + * Enable or disable tracing of time spent in each step of the algorithm. + */ + public void setTrace(boolean t) { + trace = t; + } @Override public boolean cancel() { @@ -123,17 +133,51 @@ public enum Combine { SAME_OUTPUTS } - private final LayoutSettingBean setting; + // Cached settings fields + private boolean bothSort; + private boolean centerCrossingX; + private boolean centerSimpleNodes; + private boolean crossingSort; + private boolean crossPositionDuring; + private boolean crossReduceRouting; + private boolean crossResetXFromMiddle; + private boolean crossResetXFromNode; + private boolean decreaseLayerWidthDeviation; + private boolean decreaseLayerWidthDeviationQuick; + private boolean decreaseLayerWidthDeviationUp; + private boolean dummyFirstSort; + private boolean edgeBending; + private boolean irrelevantLayoutCode; + private boolean lastDownSweep; + private boolean lastUpCrossingSweep; + private boolean meanNotMedian; + private boolean noCrossingLayerReassign; + private boolean noDummyLongEdges; + private boolean noVip; + private boolean optimalUpVip; + private boolean properCrossingClosestNode; + private boolean reverseSort; + private boolean spanByAngle; + private boolean squashPosition; + private boolean standalones; + private boolean unreverseVips; + private boolean unknownCrossingNumber; + + private int xAssignSweepCount; + private int crossingSweepCount; + private int minEdgeAngle; + private float crossFactor; + // Options private final Combine combine; private final int dummyWidth; private final int offset; private int maxLayerLength; // Algorithm global datastructures - private final Set reversedLinks; - private final Set longEdges; - private final List nodes; - private final List standAlones; + private final HashSet reversedLinks; + private final HashSet longEdges; + private final ArrayList nodes; + private final HashSet standAlones; private final HashMap vertexToLayoutNode; private final HashMap> reversedLinkStartPoints; private final HashMap> reversedLinkEndPoints; @@ -148,6 +192,41 @@ public enum Combine { private final boolean isDelayDanglingNodes; private final boolean isDrawLongEdges; + /** + * Helper class to track the number of VIP edges. + */ + static class VIPArrayList extends ArrayList { + int vips; + + public int getVips() { + return vips; + } + + VIPArrayList() { + } + + public boolean add(LayoutEdge e) { + super.add(e); + if (e.vip) { + vips++; + } + return true; + } + + public boolean remove(Object object) { + boolean remove = super.remove(object); + if (remove && ((LayoutEdge) object).vip) { + vips--; + } + return remove; + } + + public void clear() { + super.clear(); + vips = 0; + } + } + private class LayoutNode { public int x; @@ -160,10 +239,42 @@ private class LayoutNode { public int bottomYOffset; public final Vertex vertex; // Only used for non-dummy nodes, otherwise null - public final List preds = new ArrayList<>(); - public final List succs = new ArrayList<>(); + public final VIPArrayList preds = new VIPArrayList(); + public final VIPArrayList succs = new VIPArrayList(); public int pos = -1; // Position within layer + NodeRow dangling; + + boolean isDangling() { + return dangling != null; + } + + private NodeRow getDanglingRow() { + return this.dangling; + } + + private void setDangling(NodeRow r) { + assert this.dangling == null : this.dangling; + this.dangling = r; + } + + private void removeDangling() { + assert this.dangling != null : this; + this.dangling = null; + } + + public int getPos() { + return pos; + } + + public int getPredsVips() { + return preds.getVips(); + } + + public int getSuccsVips() { + return succs.getVips(); + } + public float crossingNumber = 0; public void loadCrossingNumber(boolean up) { @@ -179,9 +290,13 @@ public void loadCrossingNumber(boolean up) { public int loadCrossingNumber(boolean up, LayoutNode source) { int count = 0; if (up) { - count = succs.stream().map((e) -> e.loadCrossingNumber(up, source)).reduce(count, Integer::sum); + for (LayoutEdge e : succs) { + count = count + e.loadCrossingNumber(true, source); + } } else { - count = preds.stream().map((e) -> e.loadCrossingNumber(up, source)).reduce(count, Integer::sum); + for (LayoutEdge e : preds) { + count = count + e.loadCrossingNumber(false, source); + } } return count; } @@ -269,7 +384,7 @@ private class LayoutEdge { public int relativeFrom; public int relativeTo; public Link link; - public boolean vip; + public final boolean vip; public int loadCrossingNumber(boolean up, LayoutNode source) { if (isDefaultLayout || !isDummyCrossing || !(up ? to.isDummy() : from.isDummy())) { @@ -307,14 +422,10 @@ public int getEndPoint() { return relativeTo + to.getLeftSide(); } - public LayoutEdge(LayoutNode from, LayoutNode to) { + public LayoutEdge(LayoutNode from, LayoutNode to, int relativeFrom, int relativeTo, Link link, boolean vip) { assert from != null && to != null : "Dangling LayoutEdge."; this.from = from; this.to = to; - } - - public LayoutEdge(LayoutNode from, LayoutNode to, int relativeFrom, int relativeTo, Link link, boolean vip) { - this(from, to); this.relativeFrom = relativeFrom; this.relativeTo = relativeTo; this.link = link; @@ -322,22 +433,46 @@ public LayoutEdge(LayoutNode from, LayoutNode to, int relativeFrom, int relative } } + static final ThreadLocal indent = ThreadLocal.withInitial(() -> 0); + + private void traceEnd(long start, Class theClass) { + if (trace) { + for (int i = 0; i < indent.get(); i++) { + System.out.print(' '); + } + System.out.println("Timing for " + theClass.getName() + " is " + (System.currentTimeMillis() - start)); + } + } + + private long traceBegin(Class theClass) { + long start = 0; + if (trace) { + for (int i = 0; i < indent.get(); i++) { + System.out.print(' '); + } + System.out.println("Starting part " + theClass.getName()); + start = System.currentTimeMillis(); + } + return start; + } + private abstract class AlgorithmPart { + public void start() { if (CHECK) { preCheck(); } - long start = 0; - if (TRACE) { - System.out.println("##################################################"); - System.out.println("Starting part " + this.getClass().getName()); - start = System.currentTimeMillis(); + try { + indent.set(indent.get() + 2); + long start = traceBegin(getClass()); + run(); + traceEnd(start, getClass()); + } finally { + indent.set(indent.get() - 2); } - run(); - if (TRACE) { - System.out.println("Timing for " + this.getClass().getName() + " is " + (System.currentTimeMillis() - start)); + if (trace) { printStatistics(); } @@ -358,20 +493,8 @@ protected void preCheck() { } } - public HierarchicalLayoutManager() { - this(Combine.NONE, LayoutSettings.getBean()); - } + public HierarchicalLayoutManager(Combine b, LayoutSettingBean setting) { - public HierarchicalLayoutManager(Combine b) { - this(b, LayoutSettings.getBean()); - } - - public HierarchicalLayoutManager(LayoutSettingBean layoutSetting) { - this(Combine.NONE, layoutSetting); - } - - public HierarchicalLayoutManager(Combine b, LayoutSettingBean layoutSetting) { - setting = layoutSetting; isDefaultLayout = setting.get(Boolean.class, DEFAULT_LAYOUT); isDummyCrossing = setting.get(Boolean.class, DUMMY_CROSSING); isCrossingByConnDiff = setting.get(Boolean.class, CROSS_BY_CONN_DIFF); @@ -386,7 +509,10 @@ public HierarchicalLayoutManager(Combine b, LayoutSettingBean layoutSetting) { } else { this.dummyWidth = setting.get(Integer.class, LayoutSettings.DUMMY_WIDTH); } - this.maxLayerLength = MAX_LAYER_LENGTH; + + this.settings = setting; + + maxLayerLength = MAX_LAYER_LENGTH; offset = X_OFFSET + dummyWidth; vertexToLayoutNode = new HashMap<>(); @@ -394,11 +520,50 @@ public HierarchicalLayoutManager(Combine b, LayoutSettingBean layoutSetting) { reversedLinkStartPoints = new HashMap<>(); reversedLinkEndPoints = new HashMap<>(); nodes = new ArrayList<>(); - standAlones = new ArrayList<>(); + standAlones = new HashSet<>(); longEdges = new HashSet<>(); cancelled = new AtomicBoolean(false); } + private void initSettings() { + // Boolean settings (cached once per instance) + bothSort = settings.get(Boolean.class, BOTH_SORT); + reverseSort = settings.get(Boolean.class, REVERSE_SORT); + dummyFirstSort = settings.get(Boolean.class, DUMMY_FIRST_SORT); + lastDownSweep = settings.get(Boolean.class, LAST_DOWN_SWEEP); + optimalUpVip = settings.get(Boolean.class, OPTIMAL_UP_VIP); + squashPosition = settings.get(Boolean.class, SQUASH_POSITION); + centerSimpleNodes = settings.get(Boolean.class, CENTER_SIMPLE_NODES); + meanNotMedian = settings.get(Boolean.class, MEAN_NOT_MEDIAN); + crossingSort = settings.get(Boolean.class, CROSSING_SORT); + noCrossingLayerReassign = settings.get(Boolean.class, NO_CROSSING_LAYER_REASSIGN); + lastUpCrossingSweep = settings.get(Boolean.class, LAST_UP_CROSSING_SWEEP); + irrelevantLayoutCode = settings.get(Boolean.class, IRRELEVANT_LAYOUT_CODE); + centerCrossingX = settings.get(Boolean.class, CENTER_CROSSING_X); + crossResetXFromNode = settings.get(Boolean.class, CROSS_RESET_X_FROM_NODE); + crossResetXFromMiddle = settings.get(Boolean.class, CROSS_RESET_X_FROM_MIDDLE); + spanByAngle = settings.get(Boolean.class, SPAN_BY_ANGLE); + noDummyLongEdges = settings.get(Boolean.class, NO_DUMMY_LONG_EDGES); + standalones = settings.get(Boolean.class, STANDALONES); + edgeBending = settings.get(Boolean.class, EDGE_BENDING); + decreaseLayerWidthDeviation = settings.get(Boolean.class, DECREASE_LAYER_WIDTH_DEVIATION); + decreaseLayerWidthDeviationQuick = settings.get(Boolean.class, DECREASE_LAYER_WIDTH_DEVIATION_QUICK); + decreaseLayerWidthDeviationUp = settings.get(Boolean.class, DECREASE_LAYER_WIDTH_DEVIATION_UP); + unreverseVips = settings.get(Boolean.class, UNREVERSE_VIPS); + noVip = settings.get(Boolean.class, NO_VIP); + crossReduceRouting = settings.get(Boolean.class, CROSS_REDUCE_ROUTING); + crossPositionDuring = settings.get(Boolean.class, CROSS_POSITION_DURING); + properCrossingClosestNode = settings.get(Boolean.class, PROPER_CROSSING_CLOSEST_NODE); + unknownCrossingNumber = settings.get(Boolean.class, UNKNOWN_CROSSING_NUMBER); + + // int and float settings + + xAssignSweepCount = settings.get(Integer.class, X_ASSIGN_SWEEP_COUNT); + crossingSweepCount = settings.get(Integer.class, CROSSING_SWEEP_COUNT); + minEdgeAngle = settings.get(Integer.class, MIN_EDGE_ANGLE); + crossFactor = settings.get(Float.class, CROSS_FACTOR); + } + public int getMaxLayerLength() { return maxLayerLength; } @@ -414,6 +579,8 @@ private void cleanup() { reversedLinkEndPoints.clear(); nodes.clear(); longEdges.clear(); + + initSettings(); } @Override @@ -422,6 +589,8 @@ public void doLayout(LayoutGraph graph) { cleanup(); + long start = traceBegin(getClass()); + // ############################################################# // Step 1: Build up data structure new BuildDatastructure().start(); @@ -474,26 +643,26 @@ public void doLayout(LayoutGraph graph) { // ############################################################# // STEP 8: Write back to interface new WriteResult().start(); + + traceEnd(start, getClass()); } private class WriteResult extends AlgorithmPart { - private HashMap> splitStartPoints; - private HashMap> splitEndPoints; private HashMap pointsIdentity; private int pointCount; private final int addition = LAYER_OFFSET / 2; @Override protected void run() { - splitStartPoints = new HashMap<>(); - splitEndPoints = new HashMap<>(); + HashMap> splitStartPoints = new HashMap<>(); + HashMap> splitEndPoints = new HashMap<>(); pointsIdentity = new HashMap<>(); HashMap vertexPositions = new HashMap<>(); HashMap> linkPositions = new HashMap<>(); for (Vertex v : graph.getVertices()) { LayoutNode n = vertexToLayoutNode.get(v); - if (!(setting.get(Boolean.class, STANDALONES) && standAlones.contains(n))) { + if (!(standalones && standAlones.contains(n))) { assert !vertexPositions.containsKey(v); vertexPositions.put(v, new Point(n.getLeftSide(), n.y)); } @@ -673,7 +842,7 @@ protected void run() { } } Dimension dim; - if (setting.get(Boolean.class, STANDALONES)) { + if (standalones) { dim = setStandAlones(new Rectangle(minX, minY, maxX - minX, maxY - minY), vertexPositions); minY -= dim.height - maxY + minY; } else { @@ -692,7 +861,7 @@ protected void run() { linkPositions.put(e.link, makeLongEnding(e)); } - if (!setting.get(Boolean.class, EDGE_BENDING)) { + if (!edgeBending) { for (List points : linkPositions.values()) { points.remove(points.size() - 2); points.remove(1); @@ -767,7 +936,7 @@ private boolean allLinksSet() { return graph.getLinks().stream().filter(l -> l.getFrom().getVertex() != l.getTo().getVertex() && l.getTo().getVertex().isVisible() && l.getFrom().getVertex().isVisible()).map(l -> l.getControlPoints().size()).allMatch(s -> s > 1); } - private class ReductionEntry { + private static class ReductionEntry { final Point lastPoint; final int nextPointIndex; @@ -846,10 +1015,11 @@ private List makeReductionEntries(List 1; - return nexts.entrySet().stream().map(e - -> new ReductionEntry(lastPoint, nextIndex, e.getKey(), e.getValue(), - branching ? new ArrayList<>(reducedPoints) : reducedPoints)) - .collect(Collectors.toList()); + List list = new ArrayList<>(); + for (Map.Entry>>> e : nexts.entrySet()) { + list.add(new ReductionEntry(lastPoint, nextIndex, e.getKey(), e.getValue(), branching ? new ArrayList<>(reducedPoints) : reducedPoints)); + } + return list; } /** @@ -990,18 +1160,15 @@ private class AssignXCoordinates extends AlgorithmPart { private LayoutNode[][] upProcessingOrder; private LayoutNode[][] bothProcessingOrder; - private Map dangling = new HashMap<>(); - private void initialPositions() { for (LayoutNode n : nodes) { n.x = space[n.layer][n.pos]; } } - @SuppressWarnings({"rawtypes", "unchecked"}) private void createArrays() { space = new int[layers.length][]; - if (isDefaultLayout || !setting.get(Boolean.class, BOTH_SORT)) { + if (isDefaultLayout || !bothSort) { downProcessingOrder = new LayoutNode[layers.length][]; upProcessingOrder = new LayoutNode[layers.length][]; } else { @@ -1013,8 +1180,8 @@ private void createArrays() { bothComparer = NODE_PROCESSING_DUMMY_BOTH_COMPARATOR; if (isDefaultLayout) { //leave default sort - } else if (setting.get(Boolean.class, REVERSE_SORT)) { - if (setting.get(Boolean.class, DUMMY_FIRST_SORT)) { + } else if (reverseSort) { + if (dummyFirstSort) { upComparer = NODE_PROCESSING_DUMMY_UP_REVERSE_COMPARATOR; downComparer = NODE_PROCESSING_DUMMY_DOWN_REVERSE_COMPARATOR; bothComparer = NODE_PROCESSING_DUMMY_BOTH_REVERSE_COMPARATOR; @@ -1023,7 +1190,7 @@ private void createArrays() { downComparer = NODE_PROCESSING_DOWN_REVERSE_COMPARATOR; bothComparer = NODE_PROCESSING_BOTH_REVERSE_COMPARATOR; } - } else if (!setting.get(Boolean.class, DUMMY_FIRST_SORT)) { + } else if (!dummyFirstSort) { upComparer = NODE_PROCESSING_UP_COMPARATOR; downComparer = NODE_PROCESSING_DOWN_COMPARATOR; bothComparer = NODE_PROCESSING_BOTH_COMPARATOR; @@ -1045,29 +1212,56 @@ private void createArrays() { } } - if (isDefaultLayout || !setting.get(Boolean.class, BOTH_SORT)) { - downProcessingOrder[i] = layer.toArray(new LayoutNode[layer.size()]); - upProcessingOrder[i] = layer.toArray(new LayoutNode[layer.size()]); + if (isDefaultLayout || !bothSort) { + downProcessingOrder[i] = layer.toArray(new LayoutNode[0]); Arrays.sort(downProcessingOrder[i], downComparer); - Arrays.sort(upProcessingOrder[i], upComparer); + upProcessingOrder[i] = reverseSort(downProcessingOrder[i], upComparer); } else { - bothProcessingOrder[i] = layer.toArray(new LayoutNode[layer.size()]); + bothProcessingOrder[i] = layer.toArray(new LayoutNode[0]); Arrays.sort(bothProcessingOrder[i], bothComparer); } } } + /** + * Instead of sorting again in reverse order, just reverse the array. Assert that this produces the same order as sorting . + */ + private static LayoutNode[] reverseSort(LayoutNode[] source, Comparator upComparer) { + LayoutNode[] array = source.clone(); + int left = 0, right = array.length - 1; + while (left < right) { + LayoutNode temp = array[left]; + array[left] = array[right]; + array[right] = temp; + left++; + right--; + } + assert verifySort(array, upComparer); + return array; + } + + /** + * Ensure that the reversed array is sorted in the same order. + */ + private static boolean verifySort(LayoutNode[] array, Comparator upComparer) { + LayoutNode[] copy = array.clone(); + Arrays.sort(copy, upComparer); + assert Arrays.equals(array, copy); + return true; + } + + @Override protected void run() { createArrays(); initialPositions(); - for (int i = 0; i < (isDefaultLayout ? SWEEP_ITERATIONS : setting.get(Integer.class, X_ASSIGN_SWEEP_COUNT)); i++) { + for (int i = 0; i < (isDefaultLayout ? SWEEP_ITERATIONS : xAssignSweepCount); i++) { sweepDown(); sweepUp(); } sweepDown(); - if (isDefaultLayout || !setting.get(Boolean.class, LAST_DOWN_SWEEP)) { + if (isDefaultLayout || !lastDownSweep) { sweepUp(); } } @@ -1089,43 +1283,42 @@ private int getVisibleLayer(int start, boolean up) { return -1; } - public List getOptimalPositions(LayoutEdge edge, int layer, List vals, int correction, boolean up) { + public void getOptimalPositions(LayoutEdge edge, int layer, List vals, int correction, boolean up, boolean hasNoDangling) { if (up) { if (edge.from.layer <= layer) { - if (!dangling.containsKey(edge.from)) { + if (!edge.from.isDangling()) { vals.add(edge.getStartPoint() - correction); } } else if (edge.from.isDummy()) { - edge.from.preds.forEach(x -> getOptimalPositions(x, layer, vals, correction, up)); + edge.from.preds.forEach(x -> getOptimalPositions(x, layer, vals, correction, up, hasNoDangling)); } } else { if (edge.to.layer >= layer) { - if (!dangling.containsKey(edge.to)) { + if (!edge.to.isDangling()) { vals.add(edge.getEndPoint() - correction); } } else if (edge.to.isDummy()) { - edge.to.succs.forEach(x -> getOptimalPositions(x, layer, vals, correction, up)); + edge.to.succs.forEach(x -> getOptimalPositions(x, layer, vals, correction, up, hasNoDangling)); } } - return vals; } - private int calculateOptimalDown(LayoutNode n, LayoutNode last) { - List values = new ArrayList<>(); + private int calculateOptimalDown(LayoutNode n, LayoutNode last, boolean hasNoDangling) { + ArrayList values = new ArrayList<>(n.preds.size()); int layer = getVisibleLayer(n.layer, true); - if (n.preds.stream().anyMatch(x -> x.vip)) { + if (n.getPredsVips() != 0) { for (LayoutEdge e : n.preds) { if (e.vip) { - values = getOptimalPositions(e, layer, values, e.relativeTo, true); + getOptimalPositions(e, layer, values, e.relativeTo, true, hasNoDangling); } } } else { for (LayoutEdge e : n.preds) { - values = getOptimalPositions(e, layer, values, e.relativeTo, true); + getOptimalPositions(e, layer, values, e.relativeTo, true, hasNoDangling); } } - return median(values, n, last, true); + return median(values, n, last, true, hasNoDangling); } private int calcLayerCenter(LayoutLayer layer) { @@ -1133,9 +1326,9 @@ private int calcLayerCenter(LayoutLayer layer) { return (layer.get(0).x + last.getRightSide()) / 2; } - private int calcClosestPosition(LayoutNode n, LayoutNode last, boolean up) { + private int calcClosestPosition(LayoutNode n, LayoutNode last, boolean up, boolean hasNoDangling) { assert n != null; - return last == null ? calcLastLayerCenter(n, up) : last.x; + return last == null ? calcLastLayerCenter(n, up, hasNoDangling) : last.x; } public int getExpectedRelativePosition(LayoutLayer layer, LayoutNode n) { @@ -1147,16 +1340,16 @@ public int getExpectedRelativePosition(LayoutLayer layer, LayoutNode n) { return x; } - private int calcLastLayerCenter(LayoutNode n, boolean up) { + private int calcLastLayerCenter(LayoutNode n, boolean up, boolean hasNoDangling) { if (up) { for (int i = n.layer - 1; i >= 0; --i) { - if (layers[i].isVisible() && (dangling.isEmpty() || layers[i].stream().allMatch(node -> !dangling.containsKey(node)))) { + if (layers[i].isVisible() && (hasNoDangling || layers[i].stream().noneMatch(LayoutNode::isDangling))) { return (calcLayerCenter(layers[i]) - layers[n.layer].getMinimalWidth() / 2) + getExpectedRelativePosition(layers[n.layer], n); } } } else { for (int i = n.layer + 1; i < layers.length; ++i) { - if (layers[i].isVisible() && (dangling.isEmpty() || layers[i].stream().allMatch(node -> !dangling.containsKey(node)))) { + if (layers[i].isVisible() && (hasNoDangling || layers[i].stream().noneMatch(LayoutNode::isDangling))) { return (calcLayerCenter(layers[i]) - layers[n.layer].getMinimalWidth() / 2) + getExpectedRelativePosition(layers[n.layer], n); } } @@ -1164,43 +1357,43 @@ private int calcLastLayerCenter(LayoutNode n, boolean up) { return n.x; } - private int calculateOptimalUp(LayoutNode n, LayoutNode last) { - List values = new ArrayList<>(); + private int calculateOptimalUp(LayoutNode n, LayoutNode last, boolean hasNoDangling) { + ArrayList values = new ArrayList<>(n.succs.size()); int layer = getVisibleLayer(n.layer, false); - if (isDefaultLayout || !setting.get(Boolean.class, OPTIMAL_UP_VIP)) { + if (isDefaultLayout || !optimalUpVip) { for (LayoutEdge e : n.succs) { if (e.vip) { - values = getOptimalPositions(e, layer, new ArrayList<>(), e.relativeFrom, false); + getOptimalPositions(e, layer, new ArrayList<>(), e.relativeFrom, false, hasNoDangling); break; } - values = getOptimalPositions(e, layer, values, e.relativeFrom, false); + getOptimalPositions(e, layer, values, e.relativeFrom, false, hasNoDangling); } } else { - if (n.succs.stream().anyMatch(x -> x.vip)) { + if (n.getSuccsVips() != 0) { for (LayoutEdge e : n.succs) { if (e.vip) { - values = getOptimalPositions(e, layer, values, e.relativeFrom, false); + getOptimalPositions(e, layer, values, e.relativeFrom, false, hasNoDangling); } } } else { for (LayoutEdge e : n.succs) { - values = getOptimalPositions(e, layer, values, e.relativeFrom, false); + getOptimalPositions(e, layer, values, e.relativeFrom, false, hasNoDangling); } } } - return median(values, n, last, false); + return median(values, n, last, false, hasNoDangling); } - private int median(List values, LayoutNode n, LayoutNode last, boolean up) { + private int median(ArrayList values, LayoutNode n, LayoutNode last, boolean up, boolean hasNoDangling) { if (values.isEmpty()) { - if (isDefaultLayout || !setting.get(Boolean.class, SQUASH_POSITION)) { + if (isDefaultLayout || !squashPosition) { return n.x; } else { - return calcClosestPosition(n, last, up); + return calcClosestPosition(n, last, up, hasNoDangling); } } - if (setting.get(Boolean.class, CENTER_SIMPLE_NODES) && !n.isDummy() && values.size() == 1 && (up ? n.preds.size() == 1 : n.succs.size() == 1) && !(n.vertex instanceof ClusterSlotNode)) { + if (centerSimpleNodes && !n.isDummy() && values.size() == 1 && (up ? n.preds.size() == 1 : n.succs.size() == 1) && !(n.vertex instanceof ClusterSlotNode)) { LayoutNode node; if (up) { node = n.preds.get(0).from; @@ -1211,8 +1404,12 @@ private int median(List values, LayoutNode n, LayoutNode last, boolean return node.x + ((node.getWholeWidth() - n.getWholeWidth()) / 2); } } - if (!isDefaultLayout && setting.get(Boolean.class, MEAN_NOT_MEDIAN)) { - return values.stream().reduce(0, Integer::sum) / values.size(); + if (!isDefaultLayout && meanNotMedian) { + int sum = 0; + for (int v : values) { + sum += v; + } + return sum / values.size(); } values.sort(Integer::compare); if (values.size() % 2 == 0) { @@ -1224,85 +1421,137 @@ private int median(List values, LayoutNode n, LayoutNode last, boolean private void sweepUp() { LayoutNode[][] chosenOrder; - if (isDefaultLayout || !setting.get(Boolean.class, BOTH_SORT)) { + if (isDefaultLayout || !bothSort) { chosenOrder = upProcessingOrder; } else { chosenOrder = bothProcessingOrder; } + + List dangling = new ArrayList<>(); for (int i = layers.length - 2; i >= 0; i--) { - NodeRow r = new NodeRow(space[i]); + NodeRow r = new NodeRow(space[i], chosenOrder[i].length); LayoutNode last = null; for (LayoutNode n : chosenOrder[i]) { - if (!isDefaultLayout && isDelayDanglingNodes && ((n.succs.isEmpty() && !n.preds.isEmpty()) || (!n.succs.isEmpty() && n.succs.stream().allMatch(e -> dangling.containsKey(e.to))))) { - dangling.put(n, r); + if (!isDefaultLayout && isDelayDanglingNodes && ((n.succs.isEmpty() && !n.preds.isEmpty()) || (!n.succs.isEmpty() && isAllDanglingSuccs(n)))) { + n.setDangling(r); + dangling.add(n); } else { - int optimal = calculateOptimalUp(n, last); + int optimal = calculateOptimalUp(n, last, dangling.isEmpty()); r.insert(n, optimal); last = n; } } } - resolveDanglingNodes(true); + resolveDanglingNodes(true, dangling); + } + + private boolean isAllDanglingPreds(LayoutNode n) { + for (LayoutEdge e : n.preds) { + if (!e.from.isDangling()) { + return false; + } + } + return true; + } + + private boolean isAllDanglingSuccs(LayoutNode n) { + if (n.succs.isEmpty()) return false; + for (LayoutEdge e : n.succs) { + if (!e.to.isDangling()) { + return false; + } + } + return true; } private void sweepDown() { LayoutNode[][] chosenOrder; - if (isDefaultLayout || !setting.get(Boolean.class, BOTH_SORT)) { + if (isDefaultLayout || !bothSort) { chosenOrder = downProcessingOrder; } else { chosenOrder = bothProcessingOrder; } + boolean noDefaultAndDelay = !isDefaultLayout && isDelayDanglingNodes; + ArrayList dangling = new ArrayList<>(); for (int i = 1; i < layers.length; i++) { - NodeRow r = new NodeRow(space[i]); + NodeRow r = new NodeRow(space[i], chosenOrder[i].length); LayoutNode last = null; for (LayoutNode n : chosenOrder[i]) { - if (!isDefaultLayout && isDelayDanglingNodes && ((n.preds.isEmpty() && !n.succs.isEmpty()) || (!n.preds.isEmpty() && n.preds.stream().allMatch(e -> dangling.containsKey(e.from))))) { - dangling.put(n, r); + boolean predsIsEmpty = n.preds.isEmpty(); + if (noDefaultAndDelay && (predsIsEmpty ? !n.succs.isEmpty() : !dangling.isEmpty() && isAllDanglingPreds(n))) { + n.setDangling(r); + dangling.add(n); } else { - int optimal = calculateOptimalDown(n, last); + int optimal = calculateOptimalDown(n, last, dangling.isEmpty()); r.insert(n, optimal); last = n; } } } - resolveDanglingNodes(false); + resolveDanglingNodes(false, dangling); + } + + public static void reverse(Object[] array, int size) { + for (int i = 0, mid = size >> 1, j = size - 1; i < mid; i++, j--) { + Object tmp = array[i]; + array[i] = array[j]; + array[j] = tmp; + } } - private void resolveDanglingNodes(boolean up) { + private void resolveDanglingNodes(boolean up, List dangling) { boolean dir = up; boolean force = false; - while (!dangling.isEmpty()) { + LayoutNode[] nodes = dangling.toArray(new LayoutNode[0]); + Arrays.parallelSort(nodes, up ? DANGLING_UP_NODE_COMPARATOR : DANGLING_DOWN_NODE_COMPARATOR); + int length = nodes.length; + /* + * The nodes array can be quite large so it's best to use an array to support efficient sorting but + * removing elements from the array requires shifting elements down repeatedly. Instead elements are + * shifted down into the empty slot as nodes visited. + */ + while (length > 0) { int newResolved = 0; - List nodes = new ArrayList<>(dangling.keySet()); - if (up) { - nodes.sort(DANGLING_UP_NODE_COMPARATOR); - for (LayoutNode n : nodes) { - NodeRow r = dangling.get(n); - if (r != null && (!(n.preds.isEmpty() || n.preds.stream().allMatch(e -> dangling.containsKey(e.from))) || (force && n.preds.isEmpty()))) { - int optimal = calculateOptimalDown(n, null); - r.insert(n, optimal); - dangling.remove(n); - newResolved++; + int emptySlot = -1; + for (int i = 0; i < length; i++) { + var n = nodes[i]; + NodeRow r = n.getDanglingRow(); + int optimal = -1; + boolean hasOptimal = false; + if (up) { + if (!n.preds.isEmpty() && !isAllDanglingPreds(n) || force && n.preds.isEmpty()) { + optimal = calculateOptimalDown(n, null, false); + hasOptimal = true; + } + } else { + if (!(n.succs.isEmpty() || isAllDanglingSuccs(n)) || force && n.succs.isEmpty()) { + optimal = calculateOptimalUp(n, null, false); + hasOptimal = true; } } - } else { - nodes.sort(DANGLING_DOWN_NODE_COMPARATOR); - for (LayoutNode n : nodes) { - NodeRow r = dangling.get(n); - if (r != null && (!(n.succs.isEmpty() || n.succs.stream().allMatch(e -> dangling.containsKey(e.to))) || (force && n.succs.isEmpty()))) { - int optimal = calculateOptimalUp(n, null); - r.insert(n, optimal); - dangling.remove(n); - newResolved++; + if (hasOptimal) { + r.insert(n, optimal); + nodes[i] = null; + if (emptySlot == -1) { + emptySlot = i; } + n.removeDangling(); + newResolved++; + } else if (emptySlot != -1) { + nodes[emptySlot++] = nodes[i]; + nodes[i] = null; } } - if (newResolved == 0 && !dangling.isEmpty()) { + length -= newResolved; + if (emptySlot != -1 && length != emptySlot) { + throw new InternalError("Incorrect packing"); + } + if (newResolved == 0 && length != 0) { + reverse(nodes, length); up = !up; force = up == dir; } } - assert dangling.isEmpty() : "dangling size: " + dangling.size(); } } @@ -1322,7 +1571,7 @@ public boolean isVisible() { @Override public boolean addAll(Collection c) { - c.forEach((n) -> add0(n)); + c.forEach(this::add0); return super.addAll(c); } @@ -1377,11 +1626,12 @@ public void refresh() { private class NodeRow { - private final TreeSet treeSet; + private final ArrayList positions; private final int[] space; + static final Comparator COMPARATOR = Comparator.comparingInt(LayoutNode::getPos); - public NodeRow(int[] space) { - treeSet = new TreeSet<>(NODE_POSITION_COMPARATOR); + public NodeRow(int[] space, int length) { + this.positions = new ArrayList<>(); this.space = space; } @@ -1392,33 +1642,31 @@ public int offset(LayoutNode n1, LayoutNode n2) { } public void insert(LayoutNode n, int pos) { - SortedSet headSet = treeSet.headSet(n); LayoutNode leftNeighbor; int minX = Integer.MIN_VALUE; - if (!headSet.isEmpty()) { - leftNeighbor = headSet.last(); + int idx = Collections.binarySearch(positions, n, COMPARATOR); + if (idx > 0) { + throw new IllegalArgumentException("already in list"); + } + int insertPos = -idx - 1; // where you would insert + leftNeighbor = insertPos > 0 ? positions.get(insertPos - 1) : null; + LayoutNode rightNeighbor = insertPos < positions.size() ? positions.get(insertPos) : null; + if (leftNeighbor != null) { minX = leftNeighbor.getRightSide() + offset(leftNeighbor, n); } if (pos < minX) { n.x = minX; } else { - LayoutNode rightNeighbor; - SortedSet tailSet = treeSet.tailSet(n); int maxX = Integer.MAX_VALUE; - if (!tailSet.isEmpty()) { - rightNeighbor = tailSet.first(); + if (rightNeighbor != null) { maxX = rightNeighbor.x - offset(n, rightNeighbor) - n.getWholeWidth(); } - if (pos > maxX) { - n.x = maxX; - } else { - n.x = pos; - } + n.x = Math.min(pos, maxX); assert minX <= maxX : minX + " vs " + maxX; } - treeSet.add(n); + positions.add(insertPos, n); } } @@ -1445,9 +1693,9 @@ public void preCheck() { } } - @SuppressWarnings({"unchecked", "rawtypes"}) + @SuppressWarnings({"unchecked"}) private void createLayers() { - if (!isDefaultLayout && setting.get(Boolean.class, CROSSING_SORT) && !isCrossingByConnDiff) { + if (!isDefaultLayout && crossingSort && !isCrossingByConnDiff) { upCrossing = new ArrayList[layerCount]; downCrossing = new ArrayList[layerCount]; } @@ -1456,7 +1704,7 @@ private void createLayers() { for (int i = 0; i < layerCount; i++) { layers[i] = new LayoutLayer(); } - if (!isDefaultLayout && setting.get(Boolean.class, NO_CROSSING_LAYER_REASSIGN)) { + if (!isDefaultLayout && noCrossingLayerReassign) { for (LayoutNode n : nodes) { layers[n.layer].add(n); } @@ -1486,8 +1734,8 @@ private void createLayers() { } } } - toMove = !routingCrossing ? layers : Arrays.stream(layers).map(l -> l.stream().filter(n -> n.isDummy()).collect(LayoutLayer::new, LayoutLayer::add, LayoutLayer::addAll)).toArray(LayoutLayer[]::new); - if (!isDefaultLayout && setting.get(Boolean.class, CROSSING_SORT) && !isCrossingByConnDiff) { + toMove = !routingCrossing ? layers : Arrays.stream(layers).map(l -> l.stream().filter(LayoutNode::isDummy).collect(LayoutLayer::new, LayoutLayer::add, LayoutLayer::addAll)).toArray(LayoutLayer[]::new); + if (!isDefaultLayout && crossingSort && !isCrossingByConnDiff) { for (int i = 0; i < layerCount; ++i) { upCrossing[i] = new ArrayList<>(layers[i]); downCrossing[i] = new ArrayList<>(layers[i]); @@ -1500,28 +1748,28 @@ private void createLayers() { @Override protected void run() { createLayers(); - if (setting.get(Boolean.class, IRRELEVANT_LAYOUT_CODE)) { + if (irrelevantLayoutCode) { updatePositions(); } //will be reassigned initX(); // Optimize - int sweepCount = isDefaultLayout ? CROSSING_ITERATIONS : setting.get(Integer.class, CROSSING_SWEEP_COUNT); + int sweepCount = isDefaultLayout ? CROSSING_ITERATIONS : crossingSweepCount; for (int i = 0; i < sweepCount; i++) { downSweep(); upSweep(); } - if (isDefaultLayout || !setting.get(Boolean.class, LAST_UP_CROSSING_SWEEP)) { + if (isDefaultLayout || !lastUpCrossingSweep) { downSweep(); } - if (setting.get(Boolean.class, IRRELEVANT_LAYOUT_CODE)) { + if (irrelevantLayoutCode) { initX(); } updatePositions(); } private void initX() { - if (!isDefaultLayout && setting.get(Boolean.class, CENTER_CROSSING_X)) { + if (!isDefaultLayout && centerCrossingX) { createCenterDiffs(); } for (int i = 0; i < layers.length; i++) { @@ -1549,14 +1797,14 @@ private void createCenterDiffs() { } private void updateXOfLayer(int index) { - if (!isDefaultLayout && setting.get(Boolean.class, CROSS_RESET_X_FROM_NODE)) { - if (setting.get(Boolean.class, CROSS_RESET_X_FROM_MIDDLE)) { + if (!isDefaultLayout && crossResetXFromNode) { + if (crossResetXFromMiddle) { updateFromMiddle(index); } else { updateFromLeft(index); } } else { - int x = (isDefaultLayout || !setting.get(Boolean.class, CENTER_CROSSING_X)) ? 0 : centerDiffs[index]; + int x = (isDefaultLayout || !centerCrossingX) ? 0 : centerDiffs[index]; for (LayoutNode n : layers[index]) { n.x = x; x += n.getWholeWidth() + offset; @@ -1568,7 +1816,7 @@ private void updateFromMiddle(int index) { LayoutLayer layer = layers[index]; int middleIndex = layer.size() / 2; LayoutNode n = layer.get(middleIndex); - int add = (isDefaultLayout || !setting.get(Boolean.class, CENTER_CROSSING_X)) ? 0 : (centerDiffs[index] / (layer.size() + 1)); + int add = (isDefaultLayout || !centerCrossingX) ? 0 : (centerDiffs[index] / (layer.size() + 1)); int x = n.x; for (int i = middleIndex - 1; i >= 0; --i) { n = layer.get(i); @@ -1586,7 +1834,7 @@ private void updateFromMiddle(int index) { private void updateFromLeft(int index) { LayoutLayer layer = layers[index]; LayoutNode n = layer.get(0); - int add = (isDefaultLayout || !setting.get(Boolean.class, CENTER_CROSSING_X)) ? 0 : (centerDiffs[index] / (layer.size() + 1)); + int add = (isDefaultLayout || !centerCrossingX) ? 0 : (centerDiffs[index] / (layer.size() + 1)); int x = n.getRightSide() + offset; for (int i = 0; i < layer.size(); ++i) { n = layer.get(i); @@ -1610,14 +1858,14 @@ private void updateLayerPositions(int index) { } private void changeXOfLayer(int index) { - float factor = !isDefaultLayout && setting.get(Boolean.class, CROSS_RESET_X_FROM_MIDDLE) ? setting.get(Float.class, CROSS_FACTOR) : 1; + float factor = !isDefaultLayout && crossResetXFromMiddle ? crossFactor : 1; for (LayoutNode n : toMove[index]) { n.x += n.crossingNumber * factor; } } private void downSweep() { - if (!isDefaultLayout && setting.get(Boolean.class, CROSS_POSITION_DURING)) { + if (!isDefaultLayout && crossPositionDuring) { updatePositions(); new AssignXCoordinates().start(); } @@ -1628,21 +1876,21 @@ private void downSweep() { } if (!isDefaultLayout && isCrossingByConnDiff) { changeXOfLayer(i); - layers[i].sort((n1, n2) -> Integer.compare(n1.getCenterX(), n2.getCenterX())); + layers[i].sort(Comparator.comparingInt(LayoutNode::getCenterX)); updateXOfLayer(i); } else { updateCrossingNumbers(i, true); layers[i].sort(CROSSING_NODE_COMPARATOR); updateXOfLayer(i); } - if (setting.get(Boolean.class, IRRELEVANT_LAYOUT_CODE)) { + if (irrelevantLayoutCode) { updateLayerPositions(i); } } } private void upSweep() { - if (!isDefaultLayout && setting.get(Boolean.class, CROSS_POSITION_DURING)) { + if (!isDefaultLayout && crossPositionDuring) { updatePositions(); new AssignXCoordinates().start(); } @@ -1654,14 +1902,14 @@ private void upSweep() { if (!isDefaultLayout && isCrossingByConnDiff) { changeXOfLayer(i); - layers[i].sort((n1, n2) -> Integer.compare(n1.getCenterX(), n2.getCenterX())); + layers[i].sort(Comparator.comparingInt(LayoutNode::getCenterX)); updateXOfLayer(i); } else { updateCrossingNumbers(i, false); layers[i].sort(CROSSING_NODE_COMPARATOR); updateXOfLayer(i); } - if (setting.get(Boolean.class, IRRELEVANT_LAYOUT_CODE)) { + if (irrelevantLayoutCode) { updateLayerPositions(i); } } @@ -1669,13 +1917,13 @@ private void upSweep() { private void updateCrossingNumbers(int index, boolean down) { List layer; - if (!isDefaultLayout && setting.get(Boolean.class, CROSSING_SORT)) { + if (!isDefaultLayout && crossingSort) { layer = down ? downCrossing[index] : upCrossing[index]; } else { layer = layers[index]; } - boolean properCrossing = !isDefaultLayout && setting.get(Boolean.class, PROPER_CROSSING_CLOSEST_NODE); - int diff = (!isDefaultLayout && setting.get(Boolean.class, UNKNOWN_CROSSING_NUMBER)) ? 1 : 0; + boolean properCrossing = !isDefaultLayout && properCrossingClosestNode; + int diff = (!isDefaultLayout && unknownCrossingNumber) ? 1 : 0; LayoutNode prev; LayoutNode next; for (int i = 0; i < layer.size(); i++) { @@ -1752,10 +2000,10 @@ private class AssignYCoordinates extends AlgorithmPart { @Override protected void run() { final IntUnaryOperator layerDiff; - if (isDefaultLayout || !setting.get(Boolean.class, SPAN_BY_ANGLE)) { + if (isDefaultLayout || !spanByAngle) { layerDiff = (in) -> (int) (Math.sqrt(in) * 2); } else { - final double coef = Math.tan(Math.toRadians(setting.get(Integer.class, MIN_EDGE_ANGLE))); + final double coef = Math.tan(Math.toRadians(minEdgeAngle)); layerDiff = (in) -> (int) (in * coef); } int curY = 0; @@ -1812,7 +2060,6 @@ protected void run() { oldNodeCount = nodes.size(); if (combine == Combine.SAME_OUTPUTS) { - final boolean noDummyLongEdges = setting.get(Boolean.class, NO_DUMMY_LONG_EDGES); HashMap> portHash = new HashMap<>(); ArrayList currentNodes = new ArrayList<>(nodes); for (LayoutNode n : currentNodes) { @@ -1824,7 +2071,7 @@ protected void run() { for (LayoutEdge e : succs) { assert e.from.layer < e.to.layer; if (e.from.layer != e.to.layer - 1) { - if (maxLayerLength != -1 && e.to.layer - e.from.layer > maxLayerLength && !setting.get(Boolean.class, DRAW_LONG_EDGES) /* && e.to.preds.size() > 1 && e.from.succs.size() > 1 */) { + if (maxLayerLength != -1 && e.to.layer - e.from.layer > maxLayerLength && !isDrawLongEdges /* && e.to.preds.size() > 1 && e.from.succs.size() > 1 */) { assert maxLayerLength > 2; e.to.preds.remove(e); e.from.succs.remove(e); @@ -1876,7 +2123,7 @@ protected void run() { if (portHash.containsKey(i)) { List list = portHash.get(i); - Collections.sort(list, LAYER_COMPARATOR); + list.sort(LAYER_COMPARATOR); if (list.size() == 1) { processSingleEdge(list.get(0)); @@ -1912,7 +2159,7 @@ protected void run() { curEdge.relativeFrom = anchor.width / 2; n.succs.remove(curEdge); } - if (!isDefaultLayout && setting.get(Boolean.class, NO_CROSSING_LAYER_REASSIGN)) { + if (!isDefaultLayout && noCrossingLayerReassign) { HierarchicalLayoutManager.this.nodes.addAll(Arrays.asList(nodes)); } } @@ -1969,7 +2216,7 @@ public void postCheck() { private class AssignLayers extends AlgorithmPart { - Set checked = new HashSet<>(); + HashSet checked = new HashSet<>(); @Override public void preCheck() { @@ -1983,7 +2230,7 @@ protected void run() { assignLayerDownwards(); assignLayerUpwards(); reassignInOutBlockNodes(); - if (!isDefaultLayout && setting.get(Boolean.class, DECREASE_LAYER_WIDTH_DEVIATION)) { + if (!isDefaultLayout && decreaseLayerWidthDeviation) { reassignLayers(); } } @@ -2114,6 +2361,7 @@ private void assignLayerUpwards() { private final SortedSet lay = new TreeSet<>(LAYER_WIDTH_COMPARATOR); private void reassignLayers() { + int iterations = 0; layers = new LayoutLayer[layerCount]; if (layerCount == 0) { return; @@ -2127,8 +2375,7 @@ private void reassignLayers() { lay.addAll(Arrays.asList(HierarchicalLayoutManager.this.layers)); double avg = lay.stream().mapToInt(l -> l.getMinimalWidth()).sum() / layerCount; boolean up, down; - LayoutNode node = null; - final boolean isQuick = setting.get(Boolean.class, DECREASE_LAYER_WIDTH_DEVIATION_QUICK); + final boolean isQuick = decreaseLayerWidthDeviationQuick; while (!lay.isEmpty()) { if (cancelled.get()) { return; @@ -2139,8 +2386,15 @@ private void reassignLayers() { if (layer.getMinimalWidth() < avg) { break; } + if (++iterations > 20000) { + // This algorithm is not guaranteed to converge so limit the number of iterations. Thie value + // was picked based on processing some very large graphs. The max number of iterations seen + // for a successful layout was around 1500 so 20000 was chosen as a high bound. + LOG.log(Level.INFO, "Too many iterations in assignLayers so giving up"); + break; + } layer.sort(NODE_WIDTH_COMPARATOR); - if (!setting.get(Boolean.class, DECREASE_LAYER_WIDTH_DEVIATION_UP)) { + if (!decreaseLayerWidthDeviationUp) { for (LayoutNode n : layer) { if ((down = canMoveDown(n)) || (up = canMoveUp(n))) { break; @@ -2200,9 +2454,7 @@ private void moveUp() { al.add(node); --node.layer; } - for (LayoutLayer l : reassign) { - lay.add(l); - } + lay.addAll(reassign); } private void moveDown() { @@ -2218,9 +2470,7 @@ private void moveDown() { al.add(node); ++node.layer; } - for (LayoutLayer l : reassign) { - lay.add(l); - } + lay.addAll(reassign); } private boolean canMoveUp(LayoutNode node) { @@ -2329,7 +2579,7 @@ private boolean canMoveDown(LayoutNode node) { private void getSizesDown(List sizes) { assert !checked.isEmpty(); List nodes = new ArrayList<>(checked); - nodes.sort((n1, n2) -> Integer.compare(n1.layer, n2.layer)); + nodes.sort(Comparator.comparingInt(n -> n.layer)); int first = nodes.get(0).layer; for (LayoutNode node : nodes) { int index = node.layer - first; @@ -2400,7 +2650,7 @@ protected void run() { } } } - if (setting.get(Boolean.class, STANDALONES)) { + if (standalones) { for (Iterator it = nodes.iterator(); it.hasNext(); ) { LayoutNode n = it.next(); if (n.succs.isEmpty() && n.preds.isEmpty()) { @@ -2427,7 +2677,7 @@ protected void run() { } // Start DFS and reverse back edges - visited = new HashSet<>(); + visited = new HashSet<>(nodes.size()); active = new HashSet<>(); DFS(); resolveInsOuts(); @@ -2438,7 +2688,7 @@ private void DFS(LayoutNode startNode, boolean vip) { return; } - final boolean sortSuccs = vip && !isDefaultLayout && setting.get(Boolean.class, UNREVERSE_VIPS); + final boolean sortSuccs = vip && !isDefaultLayout && unreverseVips; Stack stack = new Stack<>(); stack.push(startNode); @@ -2460,7 +2710,7 @@ private void DFS(LayoutNode startNode, boolean vip) { ArrayList succs = new ArrayList<>(node.succs); if (sortSuccs) { //VIPs are first to follow - Collections.sort(succs, (e1, e2) -> Integer.compare(e1.vip ? e2.to.preds.size() : 0, e2.vip ? e1.to.preds.size() : 0)); + succs.sort((e1, e2) -> Integer.compare(e1.vip ? e2.to.preds.size() : 0, e2.vip ? e1.to.preds.size() : 0)); } for (LayoutEdge e : succs) { if (active.contains(e.to)) { @@ -2475,17 +2725,29 @@ private void DFS(LayoutNode startNode, boolean vip) { } private void DFS() { - if (isDefaultLayout || !setting.get(Boolean.class, UNREVERSE_VIPS)) { - nodes.forEach(n -> DFS(n, false)); + if (isDefaultLayout || !unreverseVips) { + for (LayoutNode n : nodes) { + DFS(n, false); + } } else { //first search from vip starting nodes - nodes.stream().filter((n) -> n.preds.stream().allMatch((e) -> !e.vip) && n.succs.stream().anyMatch((e) -> e.vip)) - .forEach(n -> DFS(n, true)); + for (LayoutNode node : nodes) { + if (node.preds.getVips() == 0 && node.succs.getVips() != 0) { + DFS(node, true); + } + } if (visited.size() < nodes.size()) { //second look from leftover nodes - nodes.stream().filter((n) -> !visited.contains(n)) - .sorted((n1, n2) -> Integer.compare(n1.preds.size(), n2.preds.size())) - .forEach(n -> DFS(n, false)); + List toSort = new ArrayList<>(); + for (LayoutNode n : nodes) { + if (!visited.contains(n)) { + toSort.add(n); + } + } + toSort.sort(Comparator.comparingInt(n -> n.preds.size())); + for (LayoutNode n : toSort) { + DFS(n, false); + } } } } @@ -2661,12 +2923,12 @@ protected void run() { } // Set up edges - List links = new ArrayList<>(graph.getLinks()); - final boolean VIP = !setting.get(Boolean.class, NO_VIP); + Link[] links = graph.getLinks().toArray(new Link[0]); + final boolean VIP = !noVip; if (VIP) { - Collections.sort(links, LINK_COMPARATOR); + Arrays.parallelSort(links, HierarchicalLayoutManager::compareLink); } else { - Collections.sort(links, LINK_NOVIP_COMPARATOR); + Arrays.parallelSort(links, HierarchicalLayoutManager::compareLinkNoVip); } for (Link l : links) { @@ -2705,130 +2967,251 @@ public void postCheck() { } } - private static interface PartialComparator { - - Integer partiallyCompare(T o1, T o2); + /** + * Compare by sum of VIP predecessors and successors, highest first. + * Returns 0 if equal. + */ + private static int nodeBothVipCompare(LayoutNode n1, LayoutNode n2) { + int n1VIP = n1.getPredsVips() + n1.getSuccsVips(); + int n2VIP = n2.getPredsVips() + n2.getSuccsVips(); + return Integer.compare(n2VIP, n1VIP); } - private static class NestedComparator implements Comparator { + /** + * Compare by number of VIP predecessor edges, highest first. + * Returns 0 if equal. + */ + private static int compareNodeDownVIP(LayoutNode n1, LayoutNode n2) { + int n1VIP = n1.getPredsVips(); + int n2VIP = n2.getPredsVips(); + return Integer.compare(n2VIP, n1VIP); + } - private final PartialComparator[] partials; - private final Comparator nested; + /** + * Compare by number of VIP successor edges, highest first. + * Returns 0 if equal. + */ + private static int compareNodeUpVIP(LayoutNode n1, LayoutNode n2) { + int n1VIP = n1.getSuccsVips(); + int n2VIP = n2.getSuccsVips(); + return Integer.compare(n2VIP, n1VIP); + } - public NestedComparator(Comparator comparator, PartialComparator... partials) { - this.partials = partials; - nested = comparator; + /** + * Compare dummy status: dummy nodes sort after real nodes. + * Returns 1 if n1 is dummy and n2 isn't, -1 if n2 is dummy and n1 isn't, 0 if equal. + */ + private static int compareNodeDummy(LayoutNode n1, LayoutNode n2) { + if (n1.isDummy()) { + return n2.isDummy() ? 0 : 1; } + return n2.isDummy() ? -1 : 0; + } - @Override - public int compare(T o1, T o2) { - Integer part; - for (PartialComparator partial : partials) { - part = partial.partiallyCompare(o1, o2); - if (part != null) { - return part; - } - } - return nested.compare(o1, o2); + /** + * Reversed dummy comparison: dummy nodes sort before real nodes. + * Returns -1 if n1 is dummy and n2 isn't, 1 if n2 is dummy and n1 isn't, 0 if equal. + */ + private static int compareNodeRDummy(LayoutNode n1, LayoutNode n2) { + if (n1.isDummy()) { + return n2.isDummy() ? 0 : -1; } + return n2.isDummy() ? 1 : 0; + } + + private static int compareNodeBoth(LayoutNode n1, LayoutNode n2) { + return Integer.compare(n1.preds.size() + n1.succs.size(), n2.preds.size() + n2.succs.size()); + } + + private static int compareNodeBothReverse(LayoutNode n1, LayoutNode n2) { + return Integer.compare(n2.preds.size() + n2.succs.size(), n1.preds.size() + n1.succs.size()); + } + + private static int compareNodeDown(LayoutNode n1, LayoutNode n2) { + return Integer.compare(n1.preds.size(), n2.preds.size()); + } + + private static int compareNodeDownReverse(LayoutNode n1, LayoutNode n2) { + return Integer.compare(n2.preds.size(), n1.preds.size()); + } + + private static int compareNodeUp(LayoutNode n1, LayoutNode n2) { + return Integer.compare(n1.succs.size(), n2.succs.size()); } - private static final Comparator NODE_POSITION_COMPARATOR = (n1, n2) -> n1.pos - n2.pos; + private static int compareNodeUpReverse(LayoutNode n1, LayoutNode n2) { + return Integer.compare(n2.succs.size(), n1.succs.size()); + } - private static final PartialComparator NODE_BOTHVIP = (n1, n2) -> { - long n1VIP = n1.preds.stream().filter(x -> x.vip).count() + n1.succs.stream().filter(x -> x.vip).count(); - long n2VIP = n2.preds.stream().filter(x -> x.vip).count() + n2.succs.stream().filter(x -> x.vip).count(); - if (n1VIP != n2VIP) { - return (int) (n2VIP - n1VIP); + private static final Comparator NODE_PROCESSING_BOTH_COMPARATOR = (n1, n2) -> { + int part = nodeBothVipCompare(n1, n2); + if (part != 0) { + return part; } - return null; - }; - private static final PartialComparator NODE_DOWNVIP = (n1, n2) -> { - long n1VIP = n1.preds.stream().filter(x -> x.vip).count(); - long n2VIP = n2.preds.stream().filter(x -> x.vip).count(); - if (n1VIP != n2VIP) { - return (int) (n2VIP - n1VIP); + part = compareNodeDummy(n1, n2); + if (part != 0) { + return part; } - return null; + return compareNodeBoth(n1, n2); }; - private static final PartialComparator NODE_UPVIP = (n1, n2) -> { - long n1VIP = n1.succs.stream().filter(x -> x.vip).count(); - long n2VIP = n2.succs.stream().filter(x -> x.vip).count(); - if (n1VIP != n2VIP) { - return (int) (n2VIP - n1VIP); + + private static final Comparator NODE_PROCESSING_BOTH_REVERSE_COMPARATOR = (n1, n2) -> { + int part = nodeBothVipCompare(n1, n2); + if (part != 0) { + return part; } - return null; - }; - private static final PartialComparator NODE_DUMMY = (n1, n2) -> { - if (n1.isDummy()) { - return n2.isDummy() ? 0 : 1; + part = compareNodeDummy(n1, n2); + if (part != 0) { + return part; } - return n2.isDummy() ? -1 : null; + return compareNodeBothReverse(n1, n2); }; - private static final PartialComparator NODE_RDUMMY = (n1, n2) -> { - if (n1.isDummy()) { - return n2.isDummy() ? 0 : -1; + + private static final Comparator NODE_PROCESSING_DOWN_COMPARATOR = (n1, n2) -> { + int part = compareNodeDownVIP(n1, n2); + if (part != 0) { + return part; } - return n2.isDummy() ? 1 : null; + part = compareNodeDummy(n1, n2); + if (part != 0) { + return part; + } + return compareNodeDown(n1, n2); }; - private static final Comparator NODE_BOTH = (n1, n2) -> (n1.preds.size() + n1.succs.size()) - (n2.preds.size() + n2.succs.size()); - private static final Comparator NODE_BOTH_REVERSE = (n1, n2) -> (n2.preds.size() + n2.succs.size()) - (n1.preds.size() + n1.succs.size()); - private static final Comparator NODE_DOWN = (n1, n2) -> n1.preds.size() - n2.preds.size(); - private static final Comparator NODE_DOWN_REVERSE = (n1, n2) -> n2.preds.size() - n1.preds.size(); - private static final Comparator NODE_UP = (n1, n2) -> n1.succs.size() - n2.succs.size(); - private static final Comparator NODE_UP_REVERSE = (n1, n2) -> n2.succs.size() - n1.succs.size(); - - private static final Comparator NODE_PROCESSING_BOTH_COMPARATOR = new NestedComparator( - NODE_BOTH, NODE_BOTHVIP, NODE_DUMMY); - - private static final Comparator NODE_PROCESSING_BOTH_REVERSE_COMPARATOR = new NestedComparator( - NODE_BOTH_REVERSE, NODE_BOTHVIP, NODE_DUMMY); - - private static final Comparator NODE_PROCESSING_DOWN_COMPARATOR = new NestedComparator( - NODE_DOWN, NODE_DOWNVIP, NODE_DUMMY); - - private static final Comparator NODE_PROCESSING_DOWN_REVERSE_COMPARATOR = new NestedComparator( - NODE_DOWN_REVERSE, NODE_DOWNVIP, NODE_DUMMY); + private static final Comparator NODE_PROCESSING_DOWN_REVERSE_COMPARATOR = (n1, n2) -> { + int part = compareNodeDownVIP(n1, n2); + if (part != 0) { + return part; + } + part = compareNodeDummy(n1, n2); + if (part != 0) { + return part; + } + return compareNodeDownReverse(n1, n2); + }; - private static final Comparator NODE_PROCESSING_UP_COMPARATOR = new NestedComparator( - NODE_UP, NODE_UPVIP, NODE_DUMMY); + private static final Comparator NODE_PROCESSING_UP_COMPARATOR = (n1, n2) -> { + int part = compareNodeUpVIP(n1, n2); + if (part != 0) { + return part; + } + part = compareNodeDummy(n1, n2); + if (part != 0) { + return part; + } + return compareNodeUp(n1, n2); + }; - private static final Comparator NODE_PROCESSING_UP_REVERSE_COMPARATOR = new NestedComparator( - NODE_UP_REVERSE, NODE_UPVIP, NODE_DUMMY); + private static final Comparator NODE_PROCESSING_UP_REVERSE_COMPARATOR = (n1, n2) -> { + int part = compareNodeUpVIP(n1, n2); + if (part != 0) { + return part; + } + part = compareNodeDummy(n1, n2); + if (part != 0) { + return part; + } + return compareNodeUpReverse(n1, n2); + }; - private static final Comparator NODE_PROCESSING_DUMMY_BOTH_COMPARATOR = new NestedComparator( - NODE_BOTH, NODE_BOTHVIP, NODE_RDUMMY); + private static final Comparator NODE_PROCESSING_DUMMY_BOTH_COMPARATOR = (n1, n2) -> { + int part = nodeBothVipCompare(n1, n2); + if (part != 0) { + return part; + } + part = compareNodeRDummy(n1, n2); + if (part != 0) { + return part; + } + return compareNodeBoth(n1, n2); + }; - private static final Comparator NODE_PROCESSING_DUMMY_BOTH_REVERSE_COMPARATOR = new NestedComparator( - NODE_BOTH_REVERSE, NODE_BOTHVIP, NODE_RDUMMY); + private static final Comparator NODE_PROCESSING_DUMMY_BOTH_REVERSE_COMPARATOR = (n1, n2) -> { + int part = nodeBothVipCompare(n1, n2); + if (part != 0) { + return part; + } + part = compareNodeRDummy(n1, n2); + if (part != 0) { + return part; + } + return compareNodeBothReverse(n1, n2); + }; - private static final Comparator NODE_PROCESSING_DUMMY_DOWN_COMPARATOR = new NestedComparator( - NODE_DOWN, NODE_DOWNVIP, NODE_RDUMMY); + private static final Comparator NODE_PROCESSING_DUMMY_DOWN_COMPARATOR = (n1, n2) -> { + int part = compareNodeDownVIP(n1, n2); + if (part != 0) { + return part; + } + part = compareNodeRDummy(n1, n2); + if (part != 0) { + return part; + } + return compareNodeDown(n1, n2); + }; - private static final Comparator NODE_PROCESSING_DUMMY_DOWN_REVERSE_COMPARATOR = new NestedComparator( - NODE_DOWN_REVERSE, NODE_DOWNVIP, NODE_RDUMMY); + private static final Comparator NODE_PROCESSING_DUMMY_DOWN_REVERSE_COMPARATOR = (n1, n2) -> { + int part = compareNodeDownVIP(n1, n2); + if (part != 0) { + return part; + } + part = compareNodeRDummy(n1, n2); + if (part != 0) { + return part; + } + return compareNodeDownReverse(n1, n2); + }; - private static final Comparator NODE_PROCESSING_DUMMY_UP_COMPARATOR = new NestedComparator( - NODE_UP, NODE_UPVIP, NODE_RDUMMY); + private static final Comparator NODE_PROCESSING_DUMMY_UP_COMPARATOR = (n1, n2) -> { + int part = compareNodeUpVIP(n1, n2); + if (part != 0) { + return part; + } + part = compareNodeRDummy(n1, n2); + if (part != 0) { + return part; + } + return compareNodeUp(n1, n2); + }; - private static final Comparator NODE_PROCESSING_DUMMY_UP_REVERSE_COMPARATOR = new NestedComparator( - NODE_UP_REVERSE, NODE_UPVIP, NODE_RDUMMY); + private static final Comparator NODE_PROCESSING_DUMMY_UP_REVERSE_COMPARATOR = (n1, n2) -> { + int part = compareNodeUpVIP(n1, n2); + if (part != 0) { + return part; + } + part = compareNodeRDummy(n1, n2); + if (part != 0) { + return part; + } + return compareNodeUpReverse(n1, n2); + }; private static final Comparator CROSSING_NODE_COMPARATOR = (n1, n2) -> Float.compare(n1.crossingNumber, n2.crossingNumber); - private static final Comparator DOWN_CROSSING_COMPARATOR = (n1, n2) -> n1.succs.size() - n2.succs.size(); - private static final Comparator UP_CROSSING_COMPARATOR = (n1, n2) -> n1.preds.size() - n2.preds.size(); + private static final Comparator DOWN_CROSSING_COMPARATOR = Comparator.comparingInt(n -> n.succs.size()); + private static final Comparator UP_CROSSING_COMPARATOR = Comparator.comparingInt(n -> n.preds.size()); private static final Comparator DANGLING_UP_NODE_COMPARATOR = (n1, n2) -> { int ret = Integer.compare(n1.layer, n2.layer); if (ret != 0) { return ret; } - ret = NODE_PROCESSING_UP_COMPARATOR.compare(n1, n2); - if (ret != 0) { - return ret; + // Inline NODE_PROCESSING_UP_COMPARATOR logic + int part = compareNodeUpVIP(n1, n2); + if (part != 0) { + return part; + } else { + part = compareNodeDummy(n1, n2); + if (part != 0) { + return part; + } else { + ret = Integer.compare(n1.succs.size(), n2.succs.size()); + if (ret != 0) { + return ret; + } + } } - return NODE_POSITION_COMPARATOR.compare(n1, n2); + return n1.pos - n2.pos; }; private static final Comparator DANGLING_DOWN_NODE_COMPARATOR = (n1, n2) -> { @@ -2840,28 +3223,33 @@ public int compare(T o1, T o2) { if (ret != 0) { return ret; } - return NODE_POSITION_COMPARATOR.compare(n1, n2); + return n1.pos - n2.pos; }; - private static final Comparator LAYER_COMPARATOR = (e1, e2) -> e1.to.layer - e2.to.layer; + private static final Comparator LAYER_COMPARATOR = Comparator.comparingInt(e -> e.to.layer); - private static final Comparator LINK_NOVIP_COMPARATOR = (l1, l2) -> { - int result = l1.getFrom().getVertex().compareTo(l2.getFrom().getVertex()); + private static int compareLinkNoVip(Link l1, Link l2) { + Port l1From = l1.getFrom(); + Port l2From = l2.getFrom(); + int result = l1From.getVertex().compareTo(l2From.getVertex()); if (result != 0) { return result; } - result = l1.getTo().getVertex().compareTo(l2.getTo().getVertex()); + Port l1To = l1.getTo(); + Port l2To = l2.getTo(); + result = l1To.getVertex().compareTo(l2To.getVertex()); if (result != 0) { return result; } - result = l1.getFrom().getRelativePosition().x - l2.getFrom().getRelativePosition().x; + result = l1From.getRelativePosition().x - l2From.getRelativePosition().x; if (result != 0) { return result; } - result = l1.getTo().getRelativePosition().x - l2.getTo().getRelativePosition().x; + result = l1To.getRelativePosition().x - l2To.getRelativePosition().x; return result; - }; - private static final Comparator LINK_COMPARATOR = (l1, l2) -> { + } + + private static int compareLink(Link l1, Link l2) { if (l1.isVIP() && !l2.isVIP()) { return -1; } @@ -2869,8 +3257,8 @@ public int compare(T o1, T o2) { if (!l1.isVIP() && l2.isVIP()) { return 1; } - return LINK_NOVIP_COMPARATOR.compare(l1, l2); - }; + return compareLinkNoVip(l1, l2); + } @Override public void doRouting(LayoutGraph graph) { @@ -2888,7 +3276,7 @@ public void doRouting(LayoutGraph graph) { return; } - if (setting.get(Boolean.class, CROSS_REDUCE_ROUTING)) { + if (crossReduceRouting) { new CrossingReduction(true).start(); } if (cancelled.get()) { @@ -3006,11 +3394,11 @@ private void resolveDummyNodes(LayoutNode n, List succs) { int invisibleLayers = getInvisible(n.layer, e.to.layer); assert e.from.layer <= e.to.layer : e.from + "=" + e.from.layer + "; " + e.to + "=" + e.to.layer; if (e.from.layer != e.to.layer - 1 - invisibleLayers) { - if ((maxLayerLength != -1 && e.to.layer - invisibleLayers - e.from.layer > maxLayerLength && !setting.get(Boolean.class, DRAW_LONG_EDGES)) || e.from.layer == e.to.layer) { + if ((maxLayerLength != -1 && e.to.layer - invisibleLayers - e.from.layer > maxLayerLength && !isDrawLongEdges) || e.from.layer == e.to.layer) { assert maxLayerLength > 2; e.to.preds.remove(e); e.from.succs.remove(e); - if ((isDefaultLayout || !setting.get(Boolean.class, NO_DUMMY_LONG_EDGES)) && e.from.layer != e.to.layer) { + if ((isDefaultLayout || !noDummyLongEdges) && e.from.layer != e.to.layer) { LayoutEdge topEdge; LayoutNode topNode = topNodeHash.get(e.relativeFrom); @@ -3061,7 +3449,7 @@ private void resolveDummyNodes(LayoutNode n, List succs) { if (list.size() == 1) { resolveDummyNodes(list.get(0)); } else { - Collections.sort(list, LAYER_COMPARATOR); + list.sort(LAYER_COMPARATOR); int maxLayer = list.get(list.size() - 1).to.layer; int cnt = maxLayer - n.layer - 1; LayoutEdge[] edges = new LayoutEdge[cnt]; @@ -3123,7 +3511,7 @@ private void reassignInOutBlockNodes() { lay.add(l); } } - layers = lay.toArray(new LayoutLayer[lay.size()]); + layers = lay.toArray(new LayoutLayer[0]); layerCount = layers.length; } @@ -3198,7 +3586,13 @@ private List resolveEdgePoints(List edges) { } private List resolveUnconnectedEdges(List positions, List edges) { - edges = edges.stream().filter(e -> e.link.getControlPoints().contains(null) || e.link.getControlPoints().isEmpty()).collect(Collectors.toList()); + List list = new ArrayList<>(); + for (LayoutEdge edge : edges) { + if (edge.link.getControlPoints().contains(null) || edge.link.getControlPoints().isEmpty()) { + list.add(edge); + } + } + edges = list; if (edges.isEmpty()) { return positions; } @@ -3279,7 +3673,7 @@ private void recreateLayers() { int nodeHeight = n.getWholeHeight(); if (n.vertex instanceof ClusterNode) { //we need to calculate with old height of clusternode, because they could grow a lot - nodeHeight = ((ClusterNode) n.vertex).getCluster().getBounds().height; + nodeHeight = n.vertex.getCluster().getBounds().height; } boolean assigned = false; int avgPos = n.y + (nodeHeight / 2); @@ -3304,8 +3698,8 @@ private void recreateLayers() { for (LayoutLayer l : layers) { l.refresh(); } - layers.sort((l1, l2) -> Integer.compare(l1.y, l2.y)); - HierarchicalLayoutManager.this.layers = layers.toArray(new LayoutLayer[layers.size()]); + layers.sort(Comparator.comparingInt(l -> l.y)); + HierarchicalLayoutManager.this.layers = layers.toArray(new LayoutLayer[0]); layerCount = layers.size(); } diff --git a/visualizer/IdealGraphVisualizer/JSONExporter/pom.xml b/visualizer/IdealGraphVisualizer/JSONExporter/pom.xml index 5835f2f0f75c..49e3e2777bdc 100644 --- a/visualizer/IdealGraphVisualizer/JSONExporter/pom.xml +++ b/visualizer/IdealGraphVisualizer/JSONExporter/pom.xml @@ -3,7 +3,7 @@ org.graalvm.visualizer IdealGraphVisualizer-parent - 1.23-SNAPSHOT + 1.24-SNAPSHOT JSONExporter nbm diff --git a/visualizer/IdealGraphVisualizer/JavaSources/pom.xml b/visualizer/IdealGraphVisualizer/JavaSources/pom.xml index 06618751af2b..d7be69a43093 100644 --- a/visualizer/IdealGraphVisualizer/JavaSources/pom.xml +++ b/visualizer/IdealGraphVisualizer/JavaSources/pom.xml @@ -3,7 +3,7 @@ org.graalvm.visualizer IdealGraphVisualizer-parent - 1.23-SNAPSHOT + 1.24-SNAPSHOT JavaSources nbm diff --git a/visualizer/IdealGraphVisualizer/Layout/pom.xml b/visualizer/IdealGraphVisualizer/Layout/pom.xml index 3db273db8aea..3b07685cdde5 100644 --- a/visualizer/IdealGraphVisualizer/Layout/pom.xml +++ b/visualizer/IdealGraphVisualizer/Layout/pom.xml @@ -3,7 +3,7 @@ org.graalvm.visualizer IdealGraphVisualizer-parent - 1.23-SNAPSHOT + 1.24-SNAPSHOT Layout nbm diff --git a/visualizer/IdealGraphVisualizer/NetworkConnection/pom.xml b/visualizer/IdealGraphVisualizer/NetworkConnection/pom.xml index 1e6a06a5c2a1..afc8733ae5c1 100644 --- a/visualizer/IdealGraphVisualizer/NetworkConnection/pom.xml +++ b/visualizer/IdealGraphVisualizer/NetworkConnection/pom.xml @@ -3,7 +3,7 @@ org.graalvm.visualizer IdealGraphVisualizer-parent - 1.23-SNAPSHOT + 1.24-SNAPSHOT NetworkConnection nbm diff --git a/visualizer/IdealGraphVisualizer/PolyglotRunner/pom.xml b/visualizer/IdealGraphVisualizer/PolyglotRunner/pom.xml index 6bb4fe5555de..974e83d4b803 100644 --- a/visualizer/IdealGraphVisualizer/PolyglotRunner/pom.xml +++ b/visualizer/IdealGraphVisualizer/PolyglotRunner/pom.xml @@ -3,7 +3,7 @@ org.graalvm.visualizer IdealGraphVisualizer-parent - 1.23-SNAPSHOT + 1.24-SNAPSHOT PolyglotRunner nbm diff --git a/visualizer/IdealGraphVisualizer/SelectionCoordinator/pom.xml b/visualizer/IdealGraphVisualizer/SelectionCoordinator/pom.xml index d962429d0e8a..7f6bb161e9d7 100644 --- a/visualizer/IdealGraphVisualizer/SelectionCoordinator/pom.xml +++ b/visualizer/IdealGraphVisualizer/SelectionCoordinator/pom.xml @@ -3,7 +3,7 @@ org.graalvm.visualizer IdealGraphVisualizer-parent - 1.23-SNAPSHOT + 1.24-SNAPSHOT SelectionCoordinator nbm diff --git a/visualizer/IdealGraphVisualizer/Settings/pom.xml b/visualizer/IdealGraphVisualizer/Settings/pom.xml index 1d1409d129b9..13403a5f037c 100644 --- a/visualizer/IdealGraphVisualizer/Settings/pom.xml +++ b/visualizer/IdealGraphVisualizer/Settings/pom.xml @@ -3,7 +3,7 @@ org.graalvm.visualizer IdealGraphVisualizer-parent - 1.23-SNAPSHOT + 1.24-SNAPSHOT Settings nbm diff --git a/visualizer/IdealGraphVisualizer/SettingsUI/pom.xml b/visualizer/IdealGraphVisualizer/SettingsUI/pom.xml index 6fd402b57869..af646801e461 100644 --- a/visualizer/IdealGraphVisualizer/SettingsUI/pom.xml +++ b/visualizer/IdealGraphVisualizer/SettingsUI/pom.xml @@ -3,7 +3,7 @@ org.graalvm.visualizer IdealGraphVisualizer-parent - 1.23-SNAPSHOT + 1.24-SNAPSHOT SettingsUI nbm diff --git a/visualizer/IdealGraphVisualizer/Shell/pom.xml b/visualizer/IdealGraphVisualizer/Shell/pom.xml index 93610b76f742..c2f9b22ddefc 100644 --- a/visualizer/IdealGraphVisualizer/Shell/pom.xml +++ b/visualizer/IdealGraphVisualizer/Shell/pom.xml @@ -3,7 +3,7 @@ org.graalvm.visualizer IdealGraphVisualizer-parent - 1.23-SNAPSHOT + 1.24-SNAPSHOT Shell nbm diff --git a/visualizer/IdealGraphVisualizer/SourceRepository/pom.xml b/visualizer/IdealGraphVisualizer/SourceRepository/pom.xml index 4f2e2f616441..216db14f3a9e 100644 --- a/visualizer/IdealGraphVisualizer/SourceRepository/pom.xml +++ b/visualizer/IdealGraphVisualizer/SourceRepository/pom.xml @@ -3,7 +3,7 @@ org.graalvm.visualizer IdealGraphVisualizer-parent - 1.23-SNAPSHOT + 1.24-SNAPSHOT SourceRepository nbm diff --git a/visualizer/IdealGraphVisualizer/Upgrade/pom.xml b/visualizer/IdealGraphVisualizer/Upgrade/pom.xml index 433fc9ac11b3..8de9d8e0cbdb 100644 --- a/visualizer/IdealGraphVisualizer/Upgrade/pom.xml +++ b/visualizer/IdealGraphVisualizer/Upgrade/pom.xml @@ -3,7 +3,7 @@ org.graalvm.visualizer IdealGraphVisualizer-parent - 1.23-SNAPSHOT + 1.24-SNAPSHOT Upgrade nbm diff --git a/visualizer/IdealGraphVisualizer/Util/pom.xml b/visualizer/IdealGraphVisualizer/Util/pom.xml index 549e982c457a..ec6ea755bdbd 100644 --- a/visualizer/IdealGraphVisualizer/Util/pom.xml +++ b/visualizer/IdealGraphVisualizer/Util/pom.xml @@ -3,7 +3,7 @@ org.graalvm.visualizer IdealGraphVisualizer-parent - 1.23-SNAPSHOT + 1.24-SNAPSHOT Util nbm diff --git a/visualizer/IdealGraphVisualizer/View/pom.xml b/visualizer/IdealGraphVisualizer/View/pom.xml index f750d0bd604f..b92597db7c6b 100644 --- a/visualizer/IdealGraphVisualizer/View/pom.xml +++ b/visualizer/IdealGraphVisualizer/View/pom.xml @@ -3,7 +3,7 @@ org.graalvm.visualizer IdealGraphVisualizer-parent - 1.23-SNAPSHOT + 1.24-SNAPSHOT View nbm diff --git a/visualizer/IdealGraphVisualizer/View/src/main/java/org/graalvm/visualizer/view/impl/DiagramCacheTask.java b/visualizer/IdealGraphVisualizer/View/src/main/java/org/graalvm/visualizer/view/impl/DiagramCacheTask.java index 22678efe6bc5..0583bf60a311 100644 --- a/visualizer/IdealGraphVisualizer/View/src/main/java/org/graalvm/visualizer/view/impl/DiagramCacheTask.java +++ b/visualizer/IdealGraphVisualizer/View/src/main/java/org/graalvm/visualizer/view/impl/DiagramCacheTask.java @@ -314,7 +314,7 @@ protected void execute() { figures.add(f); } } - for (Connection c : outputDiagram.getConnections()) { + for (Connection c : outputDiagram.iterateConnections()) { Figure f1 = c.getOutputSlot().getFigure(); Figure f2 = c.getInputSlot().getFigure(); if (f1.isVisible() && f2.isVisible()) { diff --git a/visualizer/IdealGraphVisualizer/View/src/test/java/org/graalvm/visualizer/view/HierarchicalLayoutManagerBenchmarkTest.java b/visualizer/IdealGraphVisualizer/View/src/test/java/org/graalvm/visualizer/view/HierarchicalLayoutManagerBenchmarkTest.java new file mode 100644 index 000000000000..9eb7c5a5bd02 --- /dev/null +++ b/visualizer/IdealGraphVisualizer/View/src/test/java/org/graalvm/visualizer/view/HierarchicalLayoutManagerBenchmarkTest.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.graalvm.visualizer.view; + +import jdk.graal.compiler.graphio.parsing.BinaryReader; +import jdk.graal.compiler.graphio.parsing.BinarySource; +import jdk.graal.compiler.graphio.parsing.GraphParser; +import jdk.graal.compiler.graphio.parsing.ModelBuilder; +import jdk.graal.compiler.graphio.parsing.model.FolderElement; +import jdk.graal.compiler.graphio.parsing.model.GraphDocument; +import jdk.graal.compiler.graphio.parsing.model.Group; +import jdk.graal.compiler.graphio.parsing.model.InputGraph; +import org.graalvm.visualizer.data.serialization.lazy.FileContent; +import org.graalvm.visualizer.graph.Connection; +import org.graalvm.visualizer.graph.Diagram; +import org.graalvm.visualizer.graph.Figure; +import org.graalvm.visualizer.hierarchicallayout.HierarchicalLayoutManager; +import org.graalvm.visualizer.layout.LayoutGraph; +import org.graalvm.visualizer.settings.layout.LayoutSettings; +import org.junit.Assume; +import org.junit.BeforeClass; +import org.junit.Test; +import org.openide.util.Exceptions; + +import java.awt.Dimension; +import java.io.File; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.file.StandardOpenOption; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.ExecutionException; + +import static org.graalvm.visualizer.settings.layout.LayoutSettings.MAX_LAYER_LENGTH; + +/** + * This is a somewhat ad-hoc benchmark of layout performance. It's ad hoc because of the complexity of integrating + * JMH with maven since that seems to require having the benchmark in a separate pom.xml. The test can be launched with + * the following command line. + *
+ *     mvn test -Dtest=org.graalvm.visualizer.view.HierarchicalLayoutManagerBenchmarkTest -DfailIfNoTests=false -DenableAssertions=false -DHierarchicalLayoutManagerBenchmarkTest.file=<filename>
+ * 
+ */ +public class HierarchicalLayoutManagerBenchmarkTest { + + static final String BENCHMARK_TEST_FILE = "HierarchicalLayoutManagerBenchmarkTest.file"; + static final String BENCHMARK_TEST_ITERATIONS = "HierarchicalLayoutManagerBenchmarkTest.iterations"; + + private static Diagram diagram; + + protected static GraphDocument loadData(File f) { + try { + final FileChannel channel = FileChannel.open(f.toPath(), StandardOpenOption.READ); + FileContent content = new FileContent(f.toPath(), channel); + BinarySource src = new BinarySource(null, content); + + GraphDocument targetDocument = new GraphDocument(); + ModelBuilder bld = new ModelBuilder(targetDocument, null); + bld.setDocumentId(""); + GraphParser parser = new BinaryReader(src, bld); + return parser.parse(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static final LayoutSettings.LayoutSettingBean layoutSetting = LayoutSettings.getBean(); + + @BeforeClass + public static void beforeClass() { + // Create BinarySource and parse document + String pathname = System.getProperty(BENCHMARK_TEST_FILE); + Assume.assumeTrue("Must set " + BENCHMARK_TEST_FILE + " property to run benchmark", pathname != null); + GraphDocument doc = loadData(new File(pathname)); + + // Find first Group and first InputGraph in it + List elements = doc.getElements(); + Group group = (Group) elements.get(0); + InputGraph inputGraph = null; + for (FolderElement fe : group.getElements()) { + if (fe instanceof InputGraph) { + inputGraph = (InputGraph) fe; + break; + } + } + if (inputGraph == null) { + throw new IllegalStateException("No InputGraph found in BGV file"); + } + InputGraph g = inputGraph; + if (g instanceof Group.LazyContent) { + Group.LazyContent lg = (Group.LazyContent) g; + if (!lg.isComplete()) { + try { + lg.completeContents(null).get(); + } catch (InterruptedException | ExecutionException ex) { + Exceptions.printStackTrace(ex); + } + } + } + diagram = Diagram.createDiagram(inputGraph, ""); + } + + + @Test + public void benchmarkHierarchicalLayout() { + int iterations = Integer.parseInt(System.getProperty(BENCHMARK_TEST_ITERATIONS, "3")); + + for (int i = 0; i < iterations; i++) { + // Prepare layout input + HashSet
figures = new HashSet<>(diagram.getFigures()); + figures.forEach(x -> x.setVisible(true)); + HashSet edges = new HashSet<>(diagram.getConnections()); + LayoutGraph layoutGraph = new LayoutGraph(edges, figures); + + HierarchicalLayoutManager mgr = new HierarchicalLayoutManager(HierarchicalLayoutManager.Combine.SAME_OUTPUTS, layoutSetting); + mgr.setTrace(true); + + mgr.setMaxLayerLength(layoutSetting.get(Integer.class, MAX_LAYER_LENGTH)); + mgr.doLayout(layoutGraph); + + Dimension size = layoutGraph.getSize(); + if (size == null) { + throw new RuntimeException("Layout produced no size."); + } + } + } +} diff --git a/visualizer/IdealGraphVisualizer/View/src/test/java/org/graalvm/visualizer/view/HierarchicalLayoutManagerStressTest.java b/visualizer/IdealGraphVisualizer/View/src/test/java/org/graalvm/visualizer/view/HierarchicalLayoutManagerStressTest.java new file mode 100644 index 000000000000..a6b7f013c04c --- /dev/null +++ b/visualizer/IdealGraphVisualizer/View/src/test/java/org/graalvm/visualizer/view/HierarchicalLayoutManagerStressTest.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.graalvm.visualizer.view; + +import jdk.graal.compiler.graphio.parsing.BinaryReader; +import jdk.graal.compiler.graphio.parsing.BinarySource; +import jdk.graal.compiler.graphio.parsing.GraphParser; +import jdk.graal.compiler.graphio.parsing.ModelBuilder; +import jdk.graal.compiler.graphio.parsing.model.GraphDocument; +import jdk.graal.compiler.graphio.parsing.model.GraphDocumentVisitor; +import jdk.graal.compiler.graphio.parsing.model.Group; +import jdk.graal.compiler.graphio.parsing.model.InputGraph; +import org.graalvm.visualizer.data.serialization.lazy.FileContent; +import org.graalvm.visualizer.data.serialization.lazy.LazyModelBuilder; +import org.graalvm.visualizer.data.serialization.lazy.ReaderErrors; +import org.graalvm.visualizer.graph.Block; +import org.graalvm.visualizer.graph.Connection; +import org.graalvm.visualizer.graph.Diagram; +import org.graalvm.visualizer.graph.Figure; +import org.graalvm.visualizer.hierarchicallayout.HierarchicalClusterLayoutManager; +import org.graalvm.visualizer.hierarchicallayout.HierarchicalLayoutManager; +import org.graalvm.visualizer.layout.LayoutGraph; +import org.graalvm.visualizer.settings.layout.LayoutSettings; +import org.junit.Assume; +import org.junit.Test; +import org.openide.util.Exceptions; + +import java.awt.Dimension; +import java.io.File; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.HashSet; +import java.util.concurrent.ExecutionException; +import java.util.stream.Stream; + +import static org.graalvm.visualizer.settings.layout.LayoutSettings.MAX_BLOCK_LAYER_LENGTH; +import static org.graalvm.visualizer.settings.layout.LayoutSettings.MAX_LAYER_LENGTH; + +public class HierarchicalLayoutManagerStressTest { + static final String STRESS_TEST_PATH = "HierarchicalLayoutManagerStressTest.path"; + static final String STRESS_TEST_VERBOSE = "HierarchicalLayoutManagerStressTest.verbose"; + + protected static GraphDocument loadData(File f) { + try { + final FileChannel channel = FileChannel.open(f.toPath(), StandardOpenOption.READ); + FileContent content = new FileContent(f.toPath(), channel); + BinarySource src = new BinarySource(null, content); + + GraphDocument targetDocument = new GraphDocument(); + ModelBuilder bld = new LazyModelBuilder(targetDocument, null); + bld.setDocumentId(""); + GraphParser parser = new BinaryReader(src, bld); + return parser.parse(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + LayoutSettings.LayoutSettingBean layoutSettings = LayoutSettings.getBean(); + boolean verbose = Boolean.getBoolean(STRESS_TEST_VERBOSE); + + @Test + public void stressHierarchicalLayout() throws IOException { + // Create BinarySource and parse document + String pathname = System.getProperty(STRESS_TEST_PATH); + Assume.assumeTrue("Must set " + STRESS_TEST_PATH + " property to run benchmark", pathname != null); + + Path path = new File(pathname).toPath(); + if (Files.isDirectory(path)) { + try (Stream walk = Files.walk(path, 1)) { + walk.forEach(this::layoutFile); + } + } else { + layoutFile(path); + } + } + + private void layoutFile(Path path) { + if (!path.toString().endsWith(".bgv")) { + return; + } + System.err.println("Processing " + path); + GraphDocument doc = loadData(path.toFile()); + GraphDocumentVisitor.visitAll(doc, this::doLayout); + } + + /** + * Resolve any lazy context. + */ + private static InputGraph completeGraph(InputGraph g) { + if (g instanceof Group.LazyContent) { + Group.LazyContent lg = (Group.LazyContent) g; + if (!lg.isComplete()) { + try { + lg.completeContents(null).get(); + } catch (InterruptedException | ExecutionException ex) { + Exceptions.printStackTrace(ex); + } + } + } + return g; + } + + private void doLayout(InputGraph inputGraph) { + if (verbose) { + System.err.println("Laying out " + inputGraph.getName()); + } + + boolean hasError = ReaderErrors.containsError(inputGraph, false); + if (hasError) { + System.err.println("Skipping " + inputGraph.getName() + " because of loading errors"); + return; + } + InputGraph graph = completeGraph(inputGraph); + Diagram d = Diagram.createDiagram(graph, ""); + + LayoutGraph layoutGraph = getLayoutGraph(d); + + HierarchicalLayoutManager mgr = new HierarchicalLayoutManager(HierarchicalLayoutManager.Combine.SAME_OUTPUTS, layoutSettings); + mgr.setMaxLayerLength(layoutSettings.get(Integer.class, MAX_LAYER_LENGTH)); + mgr.doLayout(layoutGraph); + + Dimension size = layoutGraph.getSize(); + if (size == null) { + throw new RuntimeException("Layout produced no size."); + } + + layoutGraph = getLayoutGraph(d); + + HierarchicalClusterLayoutManager clusterManager = new HierarchicalClusterLayoutManager(HierarchicalLayoutManager.Combine.SAME_OUTPUTS, layoutSettings); + clusterManager.getManager().setMaxLayerLength(layoutSettings.get(Integer.class, MAX_BLOCK_LAYER_LENGTH)); + clusterManager.getSubManager().setMaxLayerLength(layoutSettings.get(Integer.class, MAX_LAYER_LENGTH)); + clusterManager.doLayout(layoutGraph); + + size = layoutGraph.getSize(); + if (size == null) { + throw new RuntimeException("Layout produced no size."); + } + } + + private static LayoutGraph getLayoutGraph(Diagram outputDiagram) { + // Prepare layout input + for (Figure f : outputDiagram.getFigures()) { + f.setVisible(true); + f.setBoundary(false); + } + for (Block b : outputDiagram.getBlocks()) { + b.setVisible(true); + } + HashSet
figures = new HashSet<>(outputDiagram.getFigures()); + HashSet edges = new HashSet<>(outputDiagram.getConnections()); + LayoutGraph layoutGraph = new LayoutGraph(edges, figures); + return layoutGraph; + } +} diff --git a/visualizer/IdealGraphVisualizer/ViewerApi/pom.xml b/visualizer/IdealGraphVisualizer/ViewerApi/pom.xml index 984688f658b1..2af548f08787 100644 --- a/visualizer/IdealGraphVisualizer/ViewerApi/pom.xml +++ b/visualizer/IdealGraphVisualizer/ViewerApi/pom.xml @@ -3,7 +3,7 @@ org.graalvm.visualizer IdealGraphVisualizer-parent - 1.23-SNAPSHOT + 1.24-SNAPSHOT ViewerApi nbm diff --git a/visualizer/IdealGraphVisualizer/VisualizerUI/pom.xml b/visualizer/IdealGraphVisualizer/VisualizerUI/pom.xml index 851123bf2e9f..c58da33995ba 100644 --- a/visualizer/IdealGraphVisualizer/VisualizerUI/pom.xml +++ b/visualizer/IdealGraphVisualizer/VisualizerUI/pom.xml @@ -3,7 +3,7 @@ org.graalvm.visualizer IdealGraphVisualizer-parent - 1.23-SNAPSHOT + 1.24-SNAPSHOT VisualizerUI nbm diff --git a/visualizer/IdealGraphVisualizer/application/pom.xml b/visualizer/IdealGraphVisualizer/application/pom.xml index 73daec8af81d..054b3e57524f 100644 --- a/visualizer/IdealGraphVisualizer/application/pom.xml +++ b/visualizer/IdealGraphVisualizer/application/pom.xml @@ -4,7 +4,7 @@ org.graalvm.visualizer IdealGraphVisualizer-parent - 1.23-SNAPSHOT + 1.24-SNAPSHOT IdealGraphVisualizer-app nbm-application diff --git a/visualizer/IdealGraphVisualizer/application/src/main/launchers/bin/idealgraphvisualizer b/visualizer/IdealGraphVisualizer/application/src/main/launchers/bin/idealgraphvisualizer index e9622683e452..8bd327ef04b5 100755 --- a/visualizer/IdealGraphVisualizer/application/src/main/launchers/bin/idealgraphvisualizer +++ b/visualizer/IdealGraphVisualizer/application/src/main/launchers/bin/idealgraphvisualizer @@ -75,7 +75,12 @@ while [ $# -gt 0 ] ; do case "$1" in --userdir) shift; if [ $# -gt 0 ] ; then userdir="$1"; fi ;; - *) args="$args \"$1\"" + *) + if [[ "$1" == *\$* ]]; then + echo "Error: Argument '$1' contains a \$ character which cannot be safely handled the launcher script" >&2 + exit 1 + fi + args="$args \"$1\"" ;; esac shift diff --git a/visualizer/IdealGraphVisualizer/application/src/main/resources/idealgraphvisualizer.conf b/visualizer/IdealGraphVisualizer/application/src/main/resources/idealgraphvisualizer.conf index f31418972ebd..96f50a440749 100644 --- a/visualizer/IdealGraphVisualizer/application/src/main/resources/idealgraphvisualizer.conf +++ b/visualizer/IdealGraphVisualizer/application/src/main/resources/idealgraphvisualizer.conf @@ -26,7 +26,7 @@ default_cachedir="${DEFAULT_CACHEDIR_ROOT}/0.31" # options used by the launcher by default, can be overridden by explicit # command line switches -default_options="--branding idealgraphvisualizer -J-XX:+UseStringDeduplication -J-DRepositoryUpdate.increasedLogLevel=800 -J-client -J-Xss2m -J-Xms32m -J-Dnetbeans.logger.console=false -J-Djdk.gtk.version=2.2 -J-Dapple.laf.useScreenMenuBar=true -J-Dapple.awt.graphics.UseQuartz=true -J-Dsun.java2d.noddraw=true -J-Dsun.java2d.dpiaware=true -J-Dsun.zip.disableMemoryMapping=true -J-Dplugin.manager.check.updates=false -J--add-opens=java.base/java.net=ALL-UNNAMED -J--add-opens=java.base/java.lang.ref=ALL-UNNAMED -J--add-opens=java.base/java.lang=ALL-UNNAMED -J--add-opens=java.base/java.security=ALL-UNNAMED -J--add-opens=java.base/java.util=ALL-UNNAMED -J--add-opens=java.desktop/javax.swing.plaf.basic=ALL-UNNAMED -J--add-opens=java.desktop/javax.swing.text=ALL-UNNAMED -J--add-opens=java.desktop/javax.swing=ALL-UNNAMED -J--add-opens=java.desktop/java.awt=ALL-UNNAMED -J--add-opens=java.desktop/java.awt.event=ALL-UNNAMED -J--add-opens=java.prefs/java.util.prefs=ALL-UNNAMED -J--add-opens=jdk.jshell/jdk.jshell=ALL-UNNAMED -J--add-modules=jdk.jshell -J--add-exports=java.desktop/sun.awt=ALL-UNNAMED -J--add-exports=java.desktop/java.awt.peer=ALL-UNNAMED -J--add-exports=java.desktop/com.sun.beans.editors=ALL-UNNAMED -J--add-exports=java.desktop/sun.swing=ALL-UNNAMED -J--add-exports=java.desktop/sun.awt.im=ALL-UNNAMED -J--add-exports=jdk.internal.jvmstat/sun.jvmstat.monitor=ALL-UNNAMED -J--add-exports=java.management/sun.management=ALL-UNNAMED -J--add-exports=java.base/sun.reflect.annotation=ALL-UNNAMED -J--add-exports=jdk.javadoc/com.sun.tools.javadoc.main=ALL-UNNAMED -J-XX:+IgnoreUnrecognizedVMOptions" +default_options="--branding idealgraphvisualizer -J-XX:+UseStringDeduplication -J-DRepositoryUpdate.increasedLogLevel=800 -J-client -J-Xss2m -J-Xms32m -J-Dnetbeans.logger.console=false -J-Djdk.gtk.version=2.2 -J-Dapple.laf.useScreenMenuBar=true -J-Dapple.awt.graphics.UseQuartz=true -J-Dsun.java2d.noddraw=true -J-Dsun.java2d.dpiaware=true -J-Dsun.zip.disableMemoryMapping=true -J-Dplugin.manager.check.updates=false -J--add-opens=java.base/java.net=ALL-UNNAMED -J--add-opens=java.base/java.lang.ref=ALL-UNNAMED -J--add-opens=java.base/java.lang=ALL-UNNAMED -J--add-opens=java.base/java.security=ALL-UNNAMED -J--add-opens=java.base/java.util=ALL-UNNAMED -J--add-opens=java.desktop/javax.swing.plaf.basic=ALL-UNNAMED -J--add-opens=java.desktop/javax.swing.text=ALL-UNNAMED -J--add-opens=java.desktop/javax.swing=ALL-UNNAMED -J--add-opens=java.desktop/java.awt=ALL-UNNAMED -J--add-opens=java.desktop/java.awt.event=ALL-UNNAMED -J--add-opens=java.prefs/java.util.prefs=ALL-UNNAMED -J--add-opens=jdk.jshell/jdk.jshell=ALL-UNNAMED -J--add-modules=jdk.jshell -J--add-exports=java.desktop/sun.awt=ALL-UNNAMED -J--add-exports=java.desktop/java.awt.peer=ALL-UNNAMED -J--add-exports=java.desktop/com.sun.beans.editors=ALL-UNNAMED -J--add-exports=java.desktop/sun.swing=ALL-UNNAMED -J--add-exports=java.desktop/sun.awt.im=ALL-UNNAMED -J--add-exports=jdk.internal.jvmstat/sun.jvmstat.monitor=ALL-UNNAMED -J--add-exports=java.management/sun.management=ALL-UNNAMED -J--add-exports=java.base/sun.reflect.annotation=ALL-UNNAMED -J--enable-native-access=ALL-UNNAMED -J-XX:+IgnoreUnrecognizedVMOptions" # for development purposes you may wish to append: -J-Dnetbeans.logger.console=true -J-ea diff --git a/visualizer/IdealGraphVisualizer/branding/pom.xml b/visualizer/IdealGraphVisualizer/branding/pom.xml index 4467beaa0292..ca8e24e7e885 100644 --- a/visualizer/IdealGraphVisualizer/branding/pom.xml +++ b/visualizer/IdealGraphVisualizer/branding/pom.xml @@ -4,7 +4,7 @@ org.graalvm.visualizer IdealGraphVisualizer-parent - 1.23-SNAPSHOT + 1.24-SNAPSHOT IdealGraphVisualizer-branding nbm diff --git a/visualizer/IdealGraphVisualizer/pom.xml b/visualizer/IdealGraphVisualizer/pom.xml index eae0c12566ac..ad7ced889976 100644 --- a/visualizer/IdealGraphVisualizer/pom.xml +++ b/visualizer/IdealGraphVisualizer/pom.xml @@ -3,7 +3,7 @@ IdealGraphVisualizer-parent org.graalvm.visualizer IdealGraphVisualizer-parent - 1.23-SNAPSHOT + 1.24-SNAPSHOT pom @@ -31,6 +31,9 @@ 11 17 + + -g + @@ -47,6 +50,7 @@ true + --enable-native-access=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-exports=java.desktop/sun.awt=ALL-UNNAMED -Dpolyglot.engine.WarnInterpreterOnly=false diff --git a/visualizer/ci/ci.jsonnet b/visualizer/ci/ci.jsonnet index 8258e32153f2..a441e78a7493 100644 --- a/visualizer/ci/ci.jsonnet +++ b/visualizer/ci/ci.jsonnet @@ -27,7 +27,7 @@ run: [ ["cd", "./compiler"], ["mx", "build" ], - ["mx", "benchmark", "dacapo:fop", "--", "-Djdk.graal.Dump=:1", "-Djdk.graal.PrintGraph=File", "-Djdk.graal.DumpPath=../IGV_Dumps"], + ["mx", "benchmark", "dacapo:fop", "--", "-Djdk.graal.Dump=:1", "-Djdk.graal.PrintGraph=File", "-Djdk.graal.DumpPath=../IGV_Dumps", "-Djdk.graal.ShowDumpFiles=false"], ["cd", "../visualizer"], ["mx", "--java-home=$TOOLS_JAVA_HOME", "build" ], ["mx", "--java-home=$TOOLS_JAVA_HOME", "igv", "-J-Digv.openfile.onstartup.and.close=../compiler/IGV_Dumps", "--nosplash"],