Mercurial > hg > truffle
view graal/com.oracle.graal.compiler.common/src/com/oracle/graal/compiler/common/remote/Context.java @ 18383:1518c3296cc8
use deterministic iteration order Set and Map data structures when in the scope of a replay compilation context
author | Doug Simon <doug.simon@oracle.com> |
---|---|
date | Sun, 16 Nov 2014 09:44:04 +0100 |
parents | a8bc8724657e |
children | 84bef219afc7 |
line wrap: on
line source
/* * 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 static java.lang.reflect.Modifier.*; import java.lang.reflect.*; import java.nio.*; import java.util.*; import sun.awt.util.*; import com.oracle.graal.api.code.Register.RegisterCategory; import com.oracle.graal.api.meta.*; import com.oracle.graal.compiler.common.*; import com.oracle.graal.compiler.common.type.*; /** * Manages a context for replay or remote compilation. */ public class Context implements AutoCloseable { private static final ThreadLocal<Context> currentContext = new ThreadLocal<>(); 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; } public static <K, V> HashMap<K, V> newMap() { return Context.getCurrent() == null ? new HashMap<>() : new LinkedHashMap<>(); } public static <K, V> HashMap<K, V> newMap(Map<K, V> m) { return Context.getCurrent() == null ? new HashMap<>(m) : new LinkedHashMap<>(m); } public static <K, V> HashMap<K, V> newMap(int initialCapacity) { return Context.getCurrent() == null ? new HashMap<>(initialCapacity) : new LinkedHashMap<>(initialCapacity); } public static <K, V> Map<K, V> newIdentityMap() { return Context.getCurrent() == null ? new IdentityHashMap<>() : new LinkedIdentityHashMap<>(); } public static <K, V> Map<K, V> newIdentityMap(int expectedMaxSize) { return Context.getCurrent() == null ? new IdentityHashMap<>(expectedMaxSize) : new LinkedIdentityHashMap<>(); } public static <K, V> Map<K, V> newIdentityMap(Map<K, V> m) { return Context.getCurrent() == null ? new IdentityHashMap<>(m) : new LinkedIdentityHashMap<>(m); } /** * Gets a descriptor for the fields in a class that can be used for serialization. */ private Fields fieldsFor(Class<?> c) { Fields fields = fieldsMap.get(c); if (fields == null) { fields = Fields.forClass(c, Object.class, true, null); fieldsMap.put(c, fields); } return fields; } /** * Classes whose values are subject to special serialization handling. */ // @formatter:off private static final Set<Class<?>> DontCopyClasses = new HashSet<>(Arrays.asList( Enum.class, Integer.class, Boolean.class, Short.class, Byte.class, Character.class, Float.class, Long.class, Double.class, String.class, Method.class, Class.class, Field.class, Constructor.class, RegisterCategory.class, NamedLocationIdentity.class )); // @formatter:on private static void registerSharedGlobal(Class<?> declaringClass, String staticFieldName) { try { SharedGlobal global = new SharedGlobal(declaringClass.getDeclaredField(staticFieldName)); SharedGlobals.put(global.get(), global); } catch (NoSuchFieldException e) { // ignore non-existing fields } catch (Exception e) { throw new GraalInternalError(e); } } private static void registerSharedGlobals(Class<?> declaringClass, Class<?> staticFieldType) { assert !staticFieldType.isPrimitive(); try { for (Field f : declaringClass.getDeclaredFields()) { if (isStatic(f.getModifiers()) && isFinal(f.getModifiers()) && !f.getType().isPrimitive()) { SharedGlobal global = new SharedGlobal(f); if (staticFieldType.isAssignableFrom(f.getType())) { SharedGlobals.put(global.get(), global); } else { Class<?> componentType = f.getType().getComponentType(); if (componentType != null && staticFieldType.isAssignableFrom(componentType)) { Object[] vals = global.get(); for (int i = 0; i < vals.length; i++) { SharedGlobal g = new SharedGlobal(f, i); Object obj = g.get(); SharedGlobals.put(obj, g); } } } } } } catch (Exception e) { throw new GraalInternalError(e); } } /** * A shared global is a non-primitive value in a static final variable whose identity is * important to the compiler. That is, equality tests against these values are performed with * {@code ==} or these values are keys in identity hash maps. */ static class SharedGlobal { final Field staticField; final Integer index; public SharedGlobal(Field staticField) { this(staticField, null); } public SharedGlobal(Field staticField, Integer index) { int mods = staticField.getModifiers(); assert isStatic(mods) && isFinal(mods) && !staticField.getType().isPrimitive() : staticField; staticField.setAccessible(true); this.staticField = staticField; this.index = index; } @SuppressWarnings("unchecked") public <T> T get() { try { Object value = staticField.get(null); if (index != null) { value = ((Object[]) value)[index.intValue()]; } return (T) value; } catch (Exception e) { throw new GraalInternalError(e); } } @Override public String toString() { String res = staticField.getDeclaringClass().getName() + "." + staticField.getName(); if (index != null) { res += "[" + index + "]"; } return res; } } /** * Objects that should not be copied but retrieved from final static fields. */ private static final Map<Object, SharedGlobal> SharedGlobals = new IdentityHashMap<>(); static { registerSharedGlobal(ByteOrder.class, "BIG_ENDIAN"); registerSharedGlobal(ByteOrder.class, "LITTLE_ENDIAN"); registerSharedGlobal(ArrayList.class, "EMPTY_ELEMENTDATA"); registerSharedGlobal(ArrayList.class, "DEFAULTCAPACITY_EMPTY_ELEMENTDATA"); registerSharedGlobals(StampFactory.class, Stamp.class); } /** * Determines if a given class is a subclass of any class in a given collection of classes. */ private static boolean isAssignableTo(Class<?> from, Collection<Class<?>> to) { return to.stream().anyMatch(c -> c.isAssignableFrom(from)); } /** * Gets a string representing the identity of an object. */ private static String id(Object o) { if (o == null) { return "null"; } return String.format("%s@%x", MetaUtil.getSimpleName(o.getClass(), true), System.identityHashCode(o)); } /** * Process an object graph copy operation iteratively using a worklist. */ private void copy0(Deque<Object> worklist, Map<Object, Object> copies) { // 1. Traverse object graph, making uninitialized copies of // objects that need to be copied (other object values are transferred 'as is') while (!worklist.isEmpty()) { Object obj = worklist.pollFirst(); // System.out.printf("worklist-: %s%n", s(obj)); assert copies.get(obj) == copies : id(obj) + " -> " + id(copies.get(obj)); assert pool.get(obj) == null; Class<? extends Object> clazz = obj.getClass(); Class<?> componentType = clazz.getComponentType(); if (componentType != null) { if (componentType.isPrimitive()) { if (obj instanceof int[]) { copies.put(obj, ((int[]) obj).clone()); } else if (obj instanceof byte[]) { copies.put(obj, ((byte[]) obj).clone()); } else if (obj instanceof char[]) { copies.put(obj, ((char[]) obj).clone()); } else if (obj instanceof short[]) { copies.put(obj, ((short[]) obj).clone()); } else if (obj instanceof float[]) { copies.put(obj, ((float[]) obj).clone()); } else if (obj instanceof long[]) { copies.put(obj, ((long[]) obj).clone()); } else if (obj instanceof double[]) { copies.put(obj, ((double[]) obj).clone()); } else { copies.put(obj, ((boolean[]) obj).clone()); } } else { Object[] o = (Object[]) obj; Object[] c = o.clone(); copies.put(obj, c); // System.out.printf("m+: %s%n", s(obj)); for (int i = 0; i < c.length; i++) { c[i] = copyFieldOrElement(worklist, copies, o[i]); } } } else { assert !isAssignableTo(clazz, DontCopyClasses); Object c; try { c = UnsafeAccess.unsafe.allocateInstance(clazz); copies.put(obj, c); // System.out.printf("m+: %s%n", s(obj)); Fields fields = fieldsFor(clazz); fields.copy(obj, c, (i, o) -> copyFieldOrElement(worklist, copies, o)); } catch (InstantiationException e) { throw new GraalInternalError(e); } } } // 2. Initialize fields of copied objects for (Map.Entry<Object, Object> e : copies.entrySet()) { Object src = e.getKey(); Object dst = e.getValue(); assert dst != copies : id(src); pool.put(src, dst); if (src instanceof Object[]) { Object[] srcArr = (Object[]) src; Object[] dstArr = (Object[]) dst; for (int i = 0; i < srcArr.length; i++) { Object dstElement = copies.get(srcArr[i]); if (dstElement != null) { dstArr[i] = dstElement; } } } else { Fields fields = fieldsFor(src.getClass()); assert !Proxy.isProxyClass(dst.getClass()); for (int index = 0; index < fields.getCount(); index++) { if (!fields.getType(index).isPrimitive()) { Object value = copies.get(fields.getObject(src, index)); if (value != null) { fields.set(dst, index, value); } } } } } } /** * Given the value of an object field or object array element, returns the value that should be * written into the copy of the object or array. */ 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) || SharedGlobals.containsKey(srcValue)) { pool.put(srcValue, srcValue); return srcValue; } dstValue = pool.get(srcValue); if (dstValue == null) { if (srcValue instanceof Remote) { dstValue = get(srcValue); } else { dstValue = copies.get(srcValue); if (dstValue == null) { assert !worklist.contains(srcValue) : id(srcValue); // System.out.printf("worklist+: %s%n", s(srcValue)); worklist.add(srcValue); copies.put(srcValue, copies); } else if (dstValue == copies) { dstValue = null; } } } } return dstValue; } /** * Copies an object graph. This operation does not copy: * <ul> * <li>objects whose type is assignable to one of {@link #DontCopyClasses}</li> * <li>proxy objects</li> * </ul> * In addition, copies in {@link #pool} are re-used. */ private Object copy(Object root) { assert !(isAssignableTo(root.getClass(), DontCopyClasses) || SharedGlobals.containsKey(root)); // System.out.printf("----- %s ------%n", s(obj)); assert pool.get(root) == null; Deque<Object> worklist = new IdentityLinkedList<>(); worklist.add(root); IdentityHashMap<Object, Object> copies = new IdentityHashMap<>(); copies.put(root, copies); copy0(worklist, copies); return pool.get(root); } /** * Creates an opens a context for a remote compilation request or a replay compilation * capturing. This should be used in conjunction with the try-finally statement: * * <pre> * try (Context c = new Context()) { * ... * } * </pre> * * Open one context can be active at any time for a thread. */ public Context() { assert currentContext.get() == null : currentContext.get(); currentContext.set(this); } private final Map<Object, Object> proxies = new IdentityHashMap<>(); private final Map<Object, Object> pool = new IdentityHashMap<>(); /** * Gets the value of a given object within this context. */ @SuppressWarnings("unchecked") public <T> T get(Object obj) { if (obj == null || Proxy.isProxyClass(obj.getClass())) { return (T) obj; } if (obj instanceof Remote) { Object proxy = proxies.get(obj); if (proxy == null) { Class<?>[] interfaces = ProxyUtil.getAllInterfaces(obj.getClass()); proxy = Proxy.newProxyInstance(obj.getClass().getClassLoader(), interfaces, new Handler<>(obj, this)); proxies.put(obj, proxy); } return (T) proxy; } else { Object value; if (isAssignableTo(obj.getClass(), DontCopyClasses) || SharedGlobals.containsKey(obj)) { value = obj; } else { value = pool.get(obj); if (value == null) { if (mode == Mode.Capturing) { value = copy(obj); } else { throw new GraalInternalError("No captured state for %s [class=%s]", obj, obj.getClass()); } } } return (T) value; } } public void close() { assert currentContext.get() == this : currentContext.get(); currentContext.set(null); } /** * Checks that a given value is valid within the {@linkplain #getCurrent() current context} (if * any). */ public static boolean check(Object o) { if (o != null) { Context c = currentContext.get(); if (c != null) { if (o instanceof Remote) { if (!Proxy.isProxyClass(o.getClass())) { throw new GraalInternalError("Expecting proxy, found instance of %s", o.getClass()); } } else { if (!Proxy.isProxyClass(o.getClass())) { throw new GraalInternalError("Expecting instance of %s, found proxy", o.getClass()); } } } } return true; } /** * Gets the currently active context for the calling thread. * * @return {@code null} if there is no context active on the calling thread */ public static Context getCurrent() { return currentContext.get(); } }