/******************************************************************************* * Copyright 2014, * Luis Pina <luis@luispina.me>, * Michael Hicks <mwh@cs.umd.edu> * * This file is part of Rubah. * * Rubah is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Rubah 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 for more details. * * You should have received a copy of the GNU General Public License * along with Rubah. If not, see <http://www.gnu.org/licenses/>. *******************************************************************************/ package rubah.runtime.state.strategy; import java.io.FileDescriptor; import java.lang.ref.Reference; import java.lang.ref.SoftReference; import java.lang.ref.WeakReference; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; import org.cliffc.high_scale_lib.Counter; import rubah.Rubah; import rubah.bytecode.RubahProxy; import rubah.bytecode.transformers.AddForwardField; import rubah.bytecode.transformers.AddTraverseMethod; import rubah.bytecode.transformers.ProxyGenerator; import rubah.framework.Clazz; import rubah.framework.Type; import rubah.runtime.Version; import rubah.runtime.VersionManager; import rubah.runtime.state.MigratingProgramState; import rubah.runtime.state.migrator.MigratorSubFactory; import rubah.runtime.state.migrator.MigratorSubFactory.Migrator; import rubah.runtime.state.migrator.StaticFieldsMigratorFactory; import rubah.runtime.state.migrator.UnsafeUtils; @SuppressWarnings("restriction") public class FullyLazyMonolithic implements MigrationStrategy { private static final long INFO_OFFSET; public static final int SMALL_ARRAY_SIZE = 1025; private transient Counter counter; public static final HashSet<String> BLACK_LIST = new HashSet<String>(Arrays.asList(new String[]{ FileDescriptor.class.getName() })); private transient Version v1; private transient rubah.runtime.state.ConcurrentHashMap<Object, Object> map; private Set<String> transformedClasses, outdatedClasses; static { long val; try { val = UnsafeUtils.getUnsafe().objectFieldOffset(Class.class.getDeclaredField(AddForwardField.CLASS_INFO_FIELD_NAME)); } catch (NoSuchFieldException | SecurityException e) { val = 0; // Ignore, this class is loaded in a different context probably } INFO_OFFSET = val; } @Override public void waitForFinish() { return; } @Override public String getDescription() { return "Fully lazy ugly"; } @Override public long countMigrated() { return this.counter.estimate_get(); } @Override public MappingStrategy getMapping() { throw new Error("Should not be invoked"); } @Override public MigrationStrategy setState(MigratingProgramState state) { this.counter = new Counter(); this.transformedClasses = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>()); this.outdatedClasses = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>()); Version v1 = VersionManager.getInstance().getLatestVersion(); Version v0 = v1.getPrevious(); if (v0 != null) { for (Clazz c0 : v0.getNamespace().getDefinedClasses()) { this.outdatedClasses.add(v0.getUpdatableName(c0.getFqn())); if (v1.getUpdate().isConverted(c0)) { this.transformedClasses.add(v0.getUpdatableName(c0.getFqn())); } } } this.v1 = v1; this.map = new rubah.runtime.state.ConcurrentHashMap<>(); return this; } @Override public void migrateStaticFields(Collection<Class<?>> classes) { Version v1 = VersionManager.getInstance().getLatestVersion(); MigratorSubFactory staticMigratorFactory = new StaticFieldsMigratorFactory(this, v1); for (Class<?> c : classes) { if (!staticMigratorFactory.canMigrate(c)) continue; Migrator migrator = staticMigratorFactory.buildMigrator(); Object newC = migrator.migrate(c); migrator.followReferences(newC); } } @Override public void migrate(Object fromBase, long fromOffset, Object toBase, long toOffset) { // Only invoked from the static migration, to traverse the static fields this.traverse(fromBase, fromOffset, toBase, toOffset); } private void traverse(Object fromBase, long fromOffset, Object toBase, long toOffset) { Object fromObj = unsafe.getObject(fromBase, fromOffset); Object toObj = unsafe.getObject(toBase, toOffset); if (fromObj == null) return; Class<?> c = fromObj.getClass(); ClassConversionInfo info = this.getConversionInfo(c); // Has this object been converted already? Object ret = this.getMappedObject(fromObj, info); if (ret != null) { unsafe.compareAndSwapObject(toBase, toOffset, toObj, ret); return; } // Updated classes were already visited, no need to visit them again or install proxies switch (info.traverseAction) { case INSTALL_FRONTIER: UnsafeUtils.getInstance().changeClass(fromObj, info.proxyClassToken); break; case MIGRATE: // Migrate directly ret = this.migrate(fromObj); unsafe.compareAndSwapObject(toBase, toOffset, toObj, ret); break; case INSTALL_PROXY: { Object proxy; try { proxy = unsafe.allocateInstance(info.postClass); // ClassConversionInfo objectInfo = this.getConversionInfo(pre.getClass()); UnsafeUtils.getInstance().setHashCode(fromObj, proxy); info.conversionMethodHolder.convert(fromObj, proxy); this.counter.increment(); // for (long offset : this.getConversionInfo(info.proxyClass).fieldOffsets) // this.traverse(ret, offset, ret, offset); UnsafeUtils.getInstance().changeClass(proxy, info.proxyClassToken); } catch (ReflectiveOperationException | IllegalArgumentException e) { throw new Error(e); } // ClassConversionInfo proxyInfo = this.getConversionInfo(info.proxyClass); // this.mapObject(proxy, fromObj, proxyInfo); // Map outdated to proxy // This is where the algorithm synchronizes // If we lose the race, there will be another proxy for this object // Therefore the assignment, so that we continue with the correct proxy proxy = this.mapObject(fromObj, proxy, info); // unsafe.putObject(proxy, proxyInfo.forwardFieldOffset, fromObj); // ret = this.mapObject(proxy, fromObj, proxyInfo); // unsafe.putObject(proxy, proxyInfo.proxyBaseOffset, toBase); // unsafe.putObject(proxy, proxyInfo.proxyProxiedOffset, fromObj); // unsafe.putInt(proxy, proxyInfo.proxyOffsetOffset, (int) toOffset); // unsafe.putInt(proxy, proxyInfo.proxyScaleOffset, -1); // Install proxy with CAS because program might write a new value to this field unsafe.compareAndSwapObject(toBase, toOffset, toObj, proxy); } break; case NONE: break; default: throw new Error("Code should not reach here"); } } private Object getMappedObject(Object obj, ClassConversionInfo info) { Object ret; switch (info.mappingAction) { case NONE: return null; case SAME: ret = obj; break; case FORWARD: ret = unsafe.getObject(obj, info.forwardFieldOffset); break; case MAP: ret = this.map.get(obj); break; case ARRAY: if (Array.getLength(obj) > SMALL_ARRAY_SIZE) { ret = this.map.get(obj); break; } return null; default: throw new Error("Should not reach here"); } if (ret != null && info.migrateAction == MigrateAction.REMOVE_PROXY) { UnsafeUtils.getInstance().changeClass(obj, info.proxyClassToken); // } else if (ret != null && info.migrateAction == MigrateAction.REMOVE_PROXY) { // if (ret == obj) // UnsafeUtils.getInstance().changeClass(obj, info.proxyClassToken); // else // throw new Error("Should never happen"); } return ret; } private Object mapObject(Object pre, Object post, ClassConversionInfo info) { Object ret = post; switch (info.mappingAction) { case NONE: case SAME: return ret; case FORWARD: boolean success = unsafe.compareAndSwapObject(pre, info.forwardFieldOffset, null, post); if (!success) { // Someone did the same concurrently // Discard the object created and return the other one ret = unsafe.getObject(pre, info.forwardFieldOffset); } return ret; case MAP: ret = this.map.putIfAbsent(pre, post); return (ret == null) ? post : ret; case ARRAY: if (Array.getLength(post) > SMALL_ARRAY_SIZE) { ret = this.map.putIfAbsent(pre, post); if (ret == null) { // Map large arrays so that they don't get re-migrated this.map.put(post, post); ret = post; } } return ret; default: throw new Error("Should not reach here"); } } @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public Object migrate(Object obj) { if (obj == null) { return null; } Class<?> c = obj.getClass(); ClassConversionInfo info = this.getConversionInfo(c); // Has this object been converted already? Object ret = this.getMappedObject(obj, info); if (ret != null) { return ret; } switch (info.migrateAction) { case MIGRATE_OUTDATED_ARRAY: UnsafeUtils.getInstance().changeClass(obj, info.postClassToken); case MIGRATE_ARRAY: case MIGRATE_NEW_ARRAY: { int length = Array.getLength(obj); // boolean isLarge = (length > SMALL_ARRAY_SIZE); // boolean isLarge = false; // if (!isLarge) { if (info.migrateAction == MigrateAction.MIGRATE_ARRAY || info.migrateAction == MigrateAction.MIGRATE_OUTDATED_ARRAY) { // Install frontier objects in-place ret = obj; } else { // Create new array and install proxies ret = Array.newInstance(info.postClass.getComponentType(), length); } long index = info.baseArrayIndex; for (int i = 0; i < length; i++, index += info.scaleArray) this.traverse(obj, index, ret, index); // } else { // ClassConversionInfo postInfo; // ClassConversionInfo elementInfo; // if (info.migrateAction == MigrateAction.MIGRATE_NEW_ARRAY) { // elementInfo = this.getConversionInfo(info.postClass.getComponentType()); // ret = Array.newInstance(info.postClass.getComponentType(), length); // postInfo = this.getConversionInfo(info.postClass); // } else { // elementInfo = this.getConversionInfo(c.getComponentType()); // ret = Array.newInstance(c.getComponentType(), length); // postInfo = info; // } // // ClassConversionInfo proxyInfo = this.getConversionInfo(elementInfo.proxyClass); // RubahProxy theProxy; // try { // theProxy = (RubahProxy) unsafe.allocateInstance(elementInfo.proxyClass); // // unsafe.putObject(theProxy, proxyInfo.proxyBaseOffset, ret); // unsafe.putObject(theProxy, proxyInfo.proxyProxiedOffset, obj); // unsafe.putInt(theProxy, proxyInfo.proxyOffsetOffset, (int) postInfo.baseArrayIndex); // unsafe.putInt(theProxy, proxyInfo.proxyScaleOffset, (int) postInfo.scaleArray); // } catch (InstantiationException e) { // throw new Error(e); // } // // long index = postInfo.baseArrayIndex; // for (int i = 0; i < length; i++, index += postInfo.scaleArray) // unsafe.putObject(ret, index, theProxy); // } } break; case MIGRATE_CLASS: { Class<?> cc = (Class<?>) obj; ret = cc; String realName = v1.getOriginalName(cc.getName()); if (realName != null) { try { ret = Class.forName( v1.getUpdatableName(realName), false, Rubah.getLoader()); } catch (ClassNotFoundException e) { throw new Error(e); } catch (SecurityException e) { throw new Error(e); } catch (IllegalArgumentException e) { throw new Error(e); } } } break; case MIGRATE_OBJECT: { Object post; try { post = sun.misc.Unsafe.getUnsafe().allocateInstance(info.postClass); ret = post; UnsafeUtils.getInstance().setHashCode(obj, ret); info.conversionMethodHolder.convert(obj, post); this.counter.increment(); for (long offset : this.getConversionInfo(ret.getClass()).fieldOffsets) this.traverse(ret, offset, ret, offset); } catch (IllegalArgumentException | InstantiationException e) { throw new Error(e); } break; } case MIGRATE_REFERENCE: { ret = obj; if (obj.getClass().equals(SoftReference.class)) { SoftReference<?> ref = (SoftReference<?>) obj; ret = new SoftReference(this.migrate(ref.get())); } else if (obj.getClass().equals(WeakReference.class)) { WeakReference<?> ref = (WeakReference<?>) obj; ret = new WeakReference(this.migrate(ref.get())); } else if (obj.getClass().equals(AtomicReference.class)) { AtomicReference ref = (AtomicReference) obj; ret = new AtomicReference(this.migrate(ref.get())); } else { // TODO find a more general way to migrated references // Taking a chance here... // Follow reference here this.migrate(((Reference<?>) obj).get()); } // Register in the mapping here // This is where the algorithm synchronizes // If we lose the race, there will be another reference for this object // Therefore the assignment, so that we continue with the correct reference ret = this.mapObject(obj, ret, info); // Resulting reference should not be further migrated this.mapObject(ret, ret, info); } break; case REMOVE_PROXY: { try { ret = obj; // Object pre = unsafe.getObject(obj, info.forwardFieldOffset); // if (pre == null) { // throw new Error("Should never happen"); // } else if (pre == obj) { // // Already converted // } else { // ClassConversionInfo objectInfo = this.getConversionInfo(pre.getClass()); // objectInfo.conversionMethod.invoke(null, pre, obj); // this.counter.increment(); ClassConversionInfo proxiedInfo = this.getConversionInfo(info.proxyClass); for (long offset : proxiedInfo.fieldOffsets) this.traverse(ret, offset, ret, offset); UnsafeUtils.getInstance().changeClass(ret, info.proxyClassToken); // } } catch (IllegalArgumentException e) { throw new Error(e); } break; // Object proxied = unsafe.getObject(obj, info.proxyProxiedOffset); // Object base = unsafe.getObject(obj, info.proxyBaseOffset); // long offset = unsafe.getInt(obj, info.proxyOffsetOffset); // // ret = this.migrate(proxied); // // unsafe.compareAndSwapObject(base, offset, obj, ret); } // break; case SKIP: break; case MIGRATE_OUTDATED: UnsafeUtils.getInstance().changeClass(obj, info.postClassToken); case NONE: ret = obj; for (long offset : info.fieldOffsets) this.traverse(ret, offset, ret, offset); break; default: throw new Error("Should not reach here"); } ret = this.mapObject(obj, ret, info); return ret; } private ClassConversionInfo getConversionInfo(Class<?> c) { ClassConversionInfo ret = (ClassConversionInfo) UnsafeUtils.getUnsafe().getObject(c, INFO_OFFSET); if (ret == null) { ret = this.buildConversionInfo(c); UnsafeUtils.getUnsafe().putObject(c, INFO_OFFSET, ret); } return ret; } private ClassConversionInfo buildConversionInfo(Class<?> c) { ClassConversionInfo ret; ret = new ClassConversionInfo(); Class<?> comp = c; while (comp.isArray()) comp = comp.getComponentType(); try { if (RubahProxy.class.isAssignableFrom(c)) { ret.migrateAction = MigrateAction.REMOVE_PROXY; ret.traverseAction = TraverseAction.NONE; // ret.forwardFieldOffset = -1; ret.mappingAction = MappingAction.MAP; try { Field forwardField = c.getField(AddForwardField.FIELD_NAME); ret.forwardFieldOffset = unsafe.objectFieldOffset(forwardField); ret.mappingAction = MappingAction.FORWARD; } catch (NoSuchFieldException e) { // No $forward field } ret.proxyClass = Class.forName(ProxyGenerator.getOriginalName(c.getName()), false, Rubah.getLoader()); ret.proxyClassToken = UnsafeUtils.getInstance().getClassToken(ret.proxyClass); } else if (c.isPrimitive()) { ret.migrateAction = MigrateAction.SKIP; ret.traverseAction = TraverseAction.NONE; ret.forwardFieldOffset = -1; ret.mappingAction = MappingAction.NONE; } else if (c.isArray() && comp.isPrimitive()) { ret.migrateAction = MigrateAction.SKIP; ret.traverseAction = TraverseAction.NONE; ret.forwardFieldOffset = -2; ret.mappingAction = MappingAction.SAME; } else if (!AddTraverseMethod.isAllowed(c.getName())) { ret.migrateAction = MigrateAction.SKIP; ret.traverseAction = TraverseAction.NONE; ret.forwardFieldOffset = -1; ret.mappingAction = MappingAction.NONE; } else if (c.equals(Object.class)) { ret.migrateAction = MigrateAction.NONE; ret.mappingAction = MappingAction.SAME; ret.traverseAction = TraverseAction.NONE; } else if (Class.class.isAssignableFrom(c)) { ret.migrateAction = MigrateAction.MIGRATE_CLASS; ret.mappingAction = MappingAction.MAP; ret.traverseAction = TraverseAction.MIGRATE; } else if (Reference.class.isAssignableFrom(c) || AtomicReference.class.isAssignableFrom(c)) { ret.migrateAction = MigrateAction.MIGRATE_REFERENCE; ret.traverseAction = TraverseAction.MIGRATE; ret.proxyClass = Class.forName(ProxyGenerator.generateProxyName(c.getName()), false, Rubah.getLoader()); ret.forwardFieldOffset = -1; ret.mappingAction = MappingAction.MAP; } else if (this.transformedClasses.contains((c.isArray() ? c.getComponentType().getName() : c.getName()))) { if (c.isArray()) { ret.migrateAction = MigrateAction.MIGRATE_NEW_ARRAY; ret.mappingAction = MappingAction.MAP; ret.traverseAction = TraverseAction.MIGRATE; ret.baseArrayIndex = unsafe.arrayBaseOffset(c); ret.scaleArray = unsafe.arrayIndexScale(c); Version v0 = v1.getPrevious(); String originalName = v0.getOriginalName(c.getComponentType().getName()); Clazz c0 = v0.getNamespace().getClass(Type.getObjectType(originalName.replace('.', '/'))); Clazz c1 = v1.getUpdate().getV1(c0); ret.postClass = Class.forName(v1.getUpdatableName(c1.getFqn()), true, Rubah.getLoader()); ret.postClass = Array.newInstance(ret.postClass, 0).getClass(); originalName = v1.getOriginalName(c.getComponentType().getName()); originalName = v1.getUpdatableName(originalName); if (!AddTraverseMethod.isAllowed(originalName) || BLACK_LIST.contains(originalName) || ProxyGenerator.isProxyName(originalName)) { throw new Error("This should never happen"); // ret.migrateAction = MigrateAction.NONE; // ret.mappingAction = MappingAction.SAME; } } else { LinkedList<Long> offsets = UnsafeUtils.getInstance().getOffsets(c).getOffsets(); ret.fieldOffsets = new long[offsets.size()]; int i = 0; for (Long offset : offsets) { ret.fieldOffsets[i++] = offset; } ret.mappingAction = MappingAction.MAP; try { Field forwardField = c.getField(AddForwardField.FIELD_NAME); ret.forwardFieldOffset = unsafe.objectFieldOffset(forwardField); ret.mappingAction = MappingAction.FORWARD; ret.migrateAction = MigrateAction.MIGRATE_OBJECT; ret.traverseAction = TraverseAction.INSTALL_PROXY; } catch (NoSuchFieldException e) { // No $forward field ret.migrateAction = MigrateAction.MIGRATE_OBJECT; ret.traverseAction = TraverseAction.MIGRATE; } Version v0 = v1.getPrevious(); String originalName = v0.getOriginalName(c.getName()); Clazz c0 = v0.getNamespace().getClass(Type.getObjectType(originalName.replace('.', '/'))); Clazz c1 = v1.getUpdate().getV1(c0); ret.postClass = Class.forName(v1.getUpdatableName(c1.getFqn()), true, Rubah.getLoader()); ret.proxyClass = Class.forName(ProxyGenerator.generateProxyName(ret.postClass.getName()), false, Rubah.getLoader()); ret.proxyClassToken = UnsafeUtils.getInstance().getClassToken(ret.proxyClass); if (v1.getUpdate().isUpdated(c0)) { Class<?> conversionClass = Class.forName( ProxyGenerator.generateProxyName(ret.postClass.getName()), true, Rubah.getLoader()); try { ret.conversionMethodHolder = (RubahProxy) UnsafeUtils.getUnsafe().allocateInstance(conversionClass); } catch (InstantiationException e) { throw new Error(e); } } else { throw new Error("Pure conversion not supported"); } } } else { if (c.isArray()) { ret.mappingAction = MappingAction.ARRAY; ret.traverseAction = TraverseAction.MIGRATE; ret.baseArrayIndex = unsafe.arrayBaseOffset(c); ret.scaleArray = unsafe.arrayIndexScale(c); String originalName = c.getComponentType().getName(); if (!AddTraverseMethod.isAllowed(originalName) || BLACK_LIST.contains(originalName) || ProxyGenerator.isProxyName(originalName)) { ret.migrateAction = MigrateAction.NONE; ret.mappingAction = MappingAction.SAME; } else if (outdatedClasses.contains(originalName)) { originalName = v1.getOriginalName(c.getComponentType().getName()); String newUpdatableName = v1.getUpdatableName(originalName); Class<?> newClass = Class.forName(newUpdatableName, true, Rubah.getLoader()); ret.migrateAction = MigrateAction.MIGRATE_OUTDATED_ARRAY; ret.postClassToken = UnsafeUtils.getInstance().getClassToken(Array.newInstance(newClass, 0).getClass()); } else { ret.migrateAction = MigrateAction.MIGRATE_ARRAY; } } else { ret.proxyClass = Class.forName(ProxyGenerator.generateProxyName(c.getName()), false, Rubah.getLoader()); if (outdatedClasses.contains(c.getName())) { String originalName = v1.getOriginalName(c.getName()); String newUpdatableName = v1.getUpdatableName(originalName); Class<?> newClass = Class.forName(newUpdatableName, true, Rubah.getLoader()); ret.postClassToken = UnsafeUtils.getInstance().getClassToken(newClass); ret.migrateAction = MigrateAction.MIGRATE_OUTDATED; ret.proxyClassToken = UnsafeUtils.getInstance().getClassToken(Class.forName(ProxyGenerator.generateProxyName(newClass.getName()), false, Rubah.getLoader())); } else { ret.migrateAction = MigrateAction.NONE; ret.proxyClassToken = UnsafeUtils.getInstance().getClassToken(Class.forName(ProxyGenerator.generateProxyName(c.getName()), false, Rubah.getLoader())); } ret.traverseAction = TraverseAction.INSTALL_FRONTIER; LinkedList<Long> offsets = UnsafeUtils.getInstance().getOffsets(c).getOffsets(); ret.fieldOffsets = new long[offsets.size()]; int i = 0; for (Long offset : offsets) { ret.fieldOffsets[i++] = offset; } ret.mappingAction = MappingAction.MAP; try { Field forwardField = c.getField(AddForwardField.FIELD_NAME); ret.forwardFieldOffset = unsafe.objectFieldOffset(forwardField); ret.mappingAction = MappingAction.FORWARD; } catch (NoSuchFieldException e) { // No $forward field } } } } catch (SecurityException | ReflectiveOperationException e) { throw new Error(e); } return ret; } private enum MigrateAction { REMOVE_PROXY, MIGRATE_OUTDATED, MIGRATE_OBJECT, MIGRATE_REFERENCE, MIGRATE_CLASS, MIGRATE_ARRAY, MIGRATE_OUTDATED_ARRAY, MIGRATE_NEW_ARRAY, SKIP, // Skip the object, not even traversing it NONE // Traverse the object but do not migrate it in any way } private enum TraverseAction { INSTALL_PROXY, INSTALL_FRONTIER, MIGRATE, NONE, } private enum MappingAction { FORWARD, MAP, ARRAY, SAME, NONE, } private static class ClassConversionInfo { long[] fieldOffsets = new long[0]; long forwardFieldOffset; MigrateAction migrateAction; MappingAction mappingAction; Class<?> postClass; Object postClassToken; Class<?> proxyClass; Object proxyClassToken; RubahProxy conversionMethodHolder; long baseArrayIndex, scaleArray; TraverseAction traverseAction; } }