changeset 18335:636d3aa761e4

perform both capturing and replay when testing replay compilation and use deep object graph comparison to test compilation results
author Doug Simon <doug.simon@oracle.com>
date Tue, 11 Nov 2014 11:43:27 +0100
parents 0950fa8782c7
children fbd92038a434
files graal/com.oracle.graal.compiler.common/src/com/oracle/graal/compiler/common/remote/Context.java graal/com.oracle.graal.compiler.common/src/com/oracle/graal/compiler/common/remote/DeepFieldsEquals.java graal/com.oracle.graal.compiler.common/src/com/oracle/graal/compiler/common/remote/Handler.java graal/com.oracle.graal.compiler.test/src/com/oracle/graal/compiler/test/GraalCompilerTest.java
diffstat 4 files changed, 255 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- a/graal/com.oracle.graal.compiler.common/src/com/oracle/graal/compiler/common/remote/Context.java	Tue Nov 11 10:48:27 2014 +0100
+++ b/graal/com.oracle.graal.compiler.common/src/com/oracle/graal/compiler/common/remote/Context.java	Tue Nov 11 11:43:27 2014 +0100
@@ -40,6 +40,21 @@
 
     private final Map<Class<?>, Fields> fieldsMap = new HashMap<>();
 
+    public enum Mode {
+        Capturing,
+        Replaying
+    }
+
+    private Mode mode = Mode.Capturing;
+
+    public Mode getMode() {
+        return mode;
+    }
+
+    public void setMode(Mode mode) {
+        this.mode = mode;
+    }
+
     /**
      * Gets a descriptor for the fields in a class that can be used for serialization.
      */
@@ -76,6 +91,29 @@
     ));
     // @formatter:on
 
+    private static void registerStaticField(Class<?> declaringClass, String staticFieldName) {
+        try {
+            Field f = declaringClass.getDeclaredField(staticFieldName);
+            assert Modifier.isStatic(f.getModifiers()) : f;
+            assert Modifier.isFinal(f.getModifiers()) : f;
+            assert !f.getType().isPrimitive() : f;
+            f.setAccessible(true);
+            Object obj = f.get(null);
+            Field existing = SpecialStaticFields.put(obj, f);
+            assert existing == null;
+        } catch (Exception e) {
+            throw new GraalInternalError(e);
+        }
+    }
+
+    /**
+     * Objects that should not be copied but retrieved from final static fields.
+     */
+    private static final Map<Object, Field> SpecialStaticFields = new IdentityHashMap<>();
+    static {
+        registerStaticField(ArrayList.class, "DEFAULTCAPACITY_EMPTY_ELEMENTDATA");
+    }
+
     /**
      * Determines if a given class is a subclass of any class in a given collection of classes.
      */
@@ -187,7 +225,7 @@
     private Object copyFieldOrElement(Deque<Object> worklist, Map<Object, Object> copies, Object srcValue) {
         Object dstValue = srcValue;
         if (srcValue != null && !Proxy.isProxyClass(srcValue.getClass())) {
-            if (isAssignableTo(srcValue.getClass(), DontCopyClasses)) {
+            if (isAssignableTo(srcValue.getClass(), DontCopyClasses) || SpecialStaticFields.containsKey(srcValue)) {
                 pool.put(srcValue, srcValue);
                 return srcValue;
             }
@@ -223,6 +261,9 @@
         if (isAssignableTo(root.getClass(), DontCopyClasses)) {
             return root;
         }
+        if (SpecialStaticFields.containsKey(root)) {
+            return root;
+        }
         // System.out.printf("----- %s ------%n", s(obj));
         assert pool.get(root) == null;
         Deque<Object> worklist = new IdentityLinkedList<>();
@@ -272,7 +313,11 @@
         } else {
             Object value = pool.get(obj);
             if (value == null) {
-                value = copy(obj);
+                if (mode == Mode.Capturing) {
+                    value = copy(obj);
+                } else {
+                    throw new GraalInternalError("No captured state for %s", obj);
+                }
             }
             return (T) value;
         }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.graal.compiler.common/src/com/oracle/graal/compiler/common/remote/DeepFieldsEquals.java	Tue Nov 11 11:43:27 2014 +0100
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2014, 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 com.oracle.graal.compiler.common.remote;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+import com.oracle.graal.compiler.common.*;
+
+/**
+ * Utility for structurally comparing two object graphs for equality. The comparison handles cycles
+ * in the graphs. It also {@linkplain Handler#unproxifyObject(Object) unproxifies} any proxy objects
+ * encountered.
+ */
+public class DeepFieldsEquals {
+
+    /**
+     * Compares the object graphs rooted at {@code a} and {@code b} for equality. This comparison
+     * handles cycles in the graphs.
+     *
+     * @param a
+     * @param b
+     * @return a path to the first differing nodes in each graph or {@code null} if the graphs are
+     *         equal
+     */
+    public static String equals(Object a, Object b) {
+        return equals(a, b, new HashMap<>());
+    }
+
+    /**
+     * Compares the object graphs rooted at {@code a} and {@code b} for equality. This comparison
+     * handles cycles in the graphs.
+     *
+     * @param a
+     * @param b
+     * @param fieldsMap
+     * @return a path to the first differing nodes in each graph or {@code null} if the graphs are
+     *         equal
+     */
+    public static String equals(Object a, Object b, Map<Class<?>, Fields> fieldsMap) {
+
+        Set<Item> visited = new HashSet<>();
+        LinkedList<Item> worklist = new LinkedList<>();
+        worklist.addFirst(new Item("", a, b));
+
+        while (!worklist.isEmpty()) {
+            Item item = worklist.removeFirst();
+            visited.add(item);
+
+            Object f1 = item.f1;
+            Object f2 = item.f2;
+
+            if (f1 == f2) {
+                continue;
+            }
+            if (f1 == null || f2 == null) {
+                return String.format("%s: %s != %s", item.path, f1, f2);
+            }
+            Class<?> f1Class = f1.getClass();
+            Class<?> f2Class = f2.getClass();
+            if (f1Class != f2Class) {
+                return String.format("%s: %s != %s", item.path, f1Class, f2Class);
+            }
+
+            Class<?> componentType = f1Class.getComponentType();
+            if (componentType != null) {
+                int f1Len = Array.getLength(f1);
+                int f2Len = Array.getLength(f2);
+                if (f1Len != f2Len) {
+                    return String.format("%s.length: %s != %s", item.path, f1Len, f2Len);
+                }
+                if (componentType.isPrimitive()) {
+                    for (int i = 0; i < f1Len; i++) {
+                        Object e1 = Array.get(f1, i);
+                        Object e2 = Array.get(f2, i);
+                        if (!e1.equals(e2)) {
+                            return String.format("%s[%d]: %s != %s", item.path, i, f1, f2);
+                        }
+                    }
+                } else {
+                    for (int i = 0; i < f1Len; i++) {
+                        String nextPath = item.path.length() == 0 ? "[" + i + "]" : item.path + "[" + i + "]";
+                        Item next = new Item(nextPath, Array.get(f1, i), Array.get(f2, i));
+                        if (!visited.contains(next)) {
+                            worklist.addFirst(next);
+                        }
+                    }
+                }
+            } else {
+                Fields fields = fieldsMap.get(f1Class);
+                if (fields == null) {
+                    fields = Fields.forClass(f1Class, Object.class, true, null);
+                    fieldsMap.put(f1Class, fields);
+                }
+
+                for (int i = 0; i < fields.getCount(); ++i) {
+                    Class<?> type = fields.getType(i);
+                    Object e1 = fields.get(f1, i);
+                    Object e2 = fields.get(f2, i);
+                    if (type.isPrimitive()) {
+                        if (!e1.equals(e2)) {
+                            return String.format("%s.%s: %s != %s", item.path, fields.getName(i), e1, e2);
+                        }
+                    } else {
+                        String nextPath = item.path.length() == 0 ? fields.getName(i) : item.path + "." + fields.getName(i);
+                        Item next = new Item(nextPath, e1, e2);
+                        if (!visited.contains(next)) {
+                            worklist.addFirst(next);
+                        }
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    static class Item {
+        final String path;
+        final Object f1;
+        final Object f2;
+
+        public Item(String path, Object f1, Object f2) {
+            this.path = path;
+            this.f1 = Handler.unproxifyObject(f1);
+            this.f2 = Handler.unproxifyObject(f2);
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + ((f1 == null) ? 0 : System.identityHashCode(f1));
+            result = prime * result + ((f2 == null) ? 0 : System.identityHashCode(f2));
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof Item) {
+                Item that = (Item) obj;
+                return this.f1 == that.f1 && this.f2 == that.f2;
+            }
+            return false;
+        }
+
+        @Override
+        public String toString() {
+            return path;
+        }
+    }
+
+}
--- a/graal/com.oracle.graal.compiler.common/src/com/oracle/graal/compiler/common/remote/Handler.java	Tue Nov 11 10:48:27 2014 +0100
+++ b/graal/com.oracle.graal.compiler.common/src/com/oracle/graal/compiler/common/remote/Handler.java	Tue Nov 11 11:43:27 2014 +0100
@@ -41,6 +41,14 @@
         return delegate;
     }
 
+    static Object unproxifyObject(Object obj) {
+        if (obj != null && Proxy.isProxyClass(obj.getClass())) {
+            Handler<?> h = (Handler<?>) Proxy.getInvocationHandler(obj);
+            return h.delegate;
+        }
+        return obj;
+    }
+
     static Object[] unproxify(Object[] args) {
         if (args == null) {
             return args;
--- a/graal/com.oracle.graal.compiler.test/src/com/oracle/graal/compiler/test/GraalCompilerTest.java	Tue Nov 11 10:48:27 2014 +0100
+++ b/graal/com.oracle.graal.compiler.test/src/com/oracle/graal/compiler/test/GraalCompilerTest.java	Tue Nov 11 11:43:27 2014 +0100
@@ -44,6 +44,7 @@
 import com.oracle.graal.compiler.GraalCompiler.Request;
 import com.oracle.graal.compiler.common.*;
 import com.oracle.graal.compiler.common.remote.*;
+import com.oracle.graal.compiler.common.remote.Context.Mode;
 import com.oracle.graal.compiler.target.*;
 import com.oracle.graal.debug.*;
 import com.oracle.graal.debug.Debug.Scope;
@@ -682,7 +683,7 @@
      */
     private static final boolean TEST_REPLAY = Boolean.getBoolean("graal.testReplay");
 
-    protected CompilationResult replayCompile(ResolvedJavaMethod installedCodeOwner, StructuredGraph graph) {
+    protected CompilationResult replayCompile(CompilationResult originalResult, ResolvedJavaMethod installedCodeOwner, StructuredGraph graph) {
 
         StructuredGraph graphToCompile = graph == null ? parseForCompile(installedCodeOwner) : graph;
         lastCompiledGraph = graphToCompile;
@@ -690,12 +691,32 @@
         CallingConvention cc = getCallingConvention(getCodeCache(), Type.JavaCallee, graphToCompile.method(), false);
         try (Context c = new Context(); Debug.Scope s = Debug.scope("ReplayCompiling", new DebugDumpScope("REPLAY", true))) {
             try {
-                Request<CompilationResult> request = new GraalCompiler.Request<>(graphToCompile, null, cc, installedCodeOwner, getProviders(), getBackend(), getCodeCache().getTarget(), null,
-                                getDefaultGraphBuilderSuite(), OptimisticOptimizations.ALL, getProfilingInfo(graphToCompile), getSpeculationLog(), getSuites(), new CompilationResult(),
-                                CompilationResultBuilderFactory.Default);
+                // Capturing compilation
+                Request<CompilationResult> capturingRequest = c.get(new GraalCompiler.Request<>(graphToCompile, null, cc, installedCodeOwner, getProviders(), getBackend(), getCodeCache().getTarget(),
+                                null, getDefaultGraphBuilderSuite(), OptimisticOptimizations.ALL, getProfilingInfo(graphToCompile), getSpeculationLog(), getSuites(), new CompilationResult(),
+                                CompilationResultBuilderFactory.Default));
+                CompilationResult capturingResult = GraalCompiler.compile(capturingRequest);
+                String path = DeepFieldsEquals.equals(originalResult, capturingResult);
+                if (path != null) {
+                    DeepFieldsEquals.equals(originalResult, capturingResult);
+                    Assert.fail("Capturing replay compilation result differs from original compilation result at " + path);
+                }
+
+                c.setMode(Mode.Replaying);
 
-                request = c.get(request);
-                return GraalCompiler.compile(request);
+                // Replay compilation
+                Request<CompilationResult> replyRequest = new GraalCompiler.Request<>(graphToCompile, null, cc, capturingRequest.installedCodeOwner, capturingRequest.providers,
+                                capturingRequest.backend, capturingRequest.target, null, capturingRequest.graphBuilderSuite, capturingRequest.optimisticOpts, capturingRequest.profilingInfo,
+                                capturingRequest.speculationLog, capturingRequest.suites, new CompilationResult(), capturingRequest.factory);
+
+                CompilationResult replayResult = GraalCompiler.compile(replyRequest);
+                path = DeepFieldsEquals.equals(originalResult, replayResult);
+                if (path != null) {
+                    DeepFieldsEquals.equals(originalResult, replayResult);
+                    Assert.fail("Capturing replay compilation result differs from original compilation result at " + path);
+                }
+
+                return capturingResult;
             } catch (Throwable e) {
                 e.printStackTrace();
                 throw e;
@@ -721,7 +742,7 @@
                         OptimisticOptimizations.ALL, getProfilingInfo(graphToCompile), getSpeculationLog(), getSuites(), new CompilationResult(), CompilationResultBuilderFactory.Default);
 
         if (TEST_REPLAY && graph == null) {
-            replayCompile(installedCodeOwner, null);
+            replayCompile(res, installedCodeOwner, null);
         }
         return res;
     }