Improve class transformer cache

This commit is contained in:
embeddedt 2023-01-22 14:23:06 -05:00
parent 95d7410722
commit 23b4652864
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
10 changed files with 105 additions and 148 deletions

View File

@ -4,8 +4,12 @@ import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import cpw.mods.modlauncher.api.ITransformer;
@ -44,6 +48,14 @@ public class ModernFixCachingClassTransformer extends ClassTransformer {
public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
public static ThreadLocal<MessageDigest> systemHasher = ThreadLocal.withInitial(() -> {
try {
return MessageDigest.getInstance("SHA-256");
} catch(NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
});
private static File childFile(File file) {
file.getParentFile().mkdirs();
return file;
@ -82,12 +94,12 @@ public class ModernFixCachingClassTransformer extends ClassTransformer {
}
}
private ArrayList<byte[]> computeHash(String className, boolean isEmpty, String reason) {
private ArrayList<byte[]> computeHash(String className, byte[] inputClass, String reason) {
final String internalName = className.replace('.', '/');
final Type classDesc = Type.getObjectType(internalName);
ArrayList<ILaunchPluginService> pluginList = new ArrayList<>();
for(ILaunchPluginService plugin : plugins.values()) {
if(!plugin.handlesClass(classDesc, isEmpty, reason).isEmpty()) {
if(!plugin.handlesClass(classDesc, inputClass.length == 0, reason).isEmpty()) {
pluginList.add(plugin);
}
}
@ -117,6 +129,10 @@ public class ModernFixCachingClassTransformer extends ClassTransformer {
}
}
}
/* Hash the class itself last, so that we bail out early if plugins can't hash */
MessageDigest hasher = systemHasher.get();
hasher.reset();
hashList.add(hasher.digest(inputClass));
return hashList;
}
@ -132,42 +148,52 @@ public class ModernFixCachingClassTransformer extends ClassTransformer {
public byte[] transform(byte[] inputClass, String className, String reason) {
/* We only want to cache actual transformations */
if(ITransformerActivity.CLASSLOADING_REASON.equals(reason)) {
ArrayList<byte[]> hashList = computeHash(className, inputClass.length == 0, reason);
if(hashList != null) {
/* Check if the cache contains a transformed class matching these hashes */
/* TODO maybe sanitize the class name? */
File cacheLocation = new File(CLASS_CACHE_FOLDER, className.replace('.', '/'));
boolean hashesMatch = true;
try(ObjectInputStream stream = new ObjectInputStream(new FileInputStream(cacheLocation))) {
ArrayList<byte[]> savedHash = (ArrayList<byte[]>)stream.readObject();
final byte[] classToHash = inputClass;
CompletableFuture<ArrayList<byte[]>> futureHashList = CompletableFuture.supplyAsync(() -> computeHash(className, classToHash, reason), ForkJoinPool.commonPool());
ArrayList<byte[]> hashList = null;
/* Check if the cache contains a transformed class matching these hashes */
/* TODO maybe sanitize the class name? */
File cacheLocation = new File(CLASS_CACHE_FOLDER, className.replace('.', '/'));
boolean hashesMatch = true;
try(ObjectInputStream stream = new ObjectInputStream(new FileInputStream(cacheLocation))) {
ArrayList<byte[]> savedHash = (ArrayList<byte[]>)stream.readObject();
byte[] savedInputClass = (byte[])stream.readObject();
hashList = futureHashList.get();
if(hashList != null) {
for(int i = 0; i < savedHash.size(); i++) {
if(!Arrays.equals(savedHash.get(i), hashList.get(i))) {
hashesMatch = false;
break;
}
}
if(hashesMatch)
inputClass = (byte[])stream.readObject();
} catch(IOException | ClassNotFoundException | ClassCastException e) {
if(!(e instanceof FileNotFoundException))
e.printStackTrace();
} else
hashesMatch = false;
}
if(!hashesMatch) {
inputClass = super.transform(inputClass, className, reason);
if(hashesMatch)
inputClass = savedInputClass;
} catch(IOException | ClassNotFoundException | ClassCastException | InterruptedException |
ExecutionException e) {
if(!(e instanceof FileNotFoundException))
e.printStackTrace();
hashesMatch = false;
}
if(!hashesMatch) {
inputClass = super.transform(inputClass, className, reason);
if(hashList != null) {
final byte[] classToSave = inputClass;
final ArrayList<byte[]> hashListToSave = hashList;
classSaverPool.submit(() -> {
cacheLocation.getParentFile().mkdirs();
try(ObjectOutputStream stream = new ObjectOutputStream(new FileOutputStream(cacheLocation))) {
stream.writeObject(hashList);
stream.writeObject(hashListToSave);
stream.writeObject(classToSave);
} catch(IOException e) {
e.printStackTrace();
}
});
}
return inputClass;
}
return inputClass;
}
return super.transform(inputClass, className, reason);
}

View File

@ -1,5 +1,6 @@
package org.embeddedt.modernfix.classloading.hashers;
import cpw.mods.modlauncher.ModernFixCachingClassTransformer;
import net.minecraftforge.coremod.CoreMod;
import net.minecraftforge.coremod.transformer.CoreModBaseTransformer;
@ -13,13 +14,6 @@ import java.util.concurrent.ConcurrentHashMap;
public class CoreModTransformerHasher {
private static final ConcurrentHashMap<CoreMod, byte[]> hashForCoremod;
private static Field coremodField;
private static ThreadLocal<MessageDigest> coremodHasher = ThreadLocal.withInitial(() -> {
try {
return MessageDigest.getInstance("SHA-256");
} catch(NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
});
static {
hashForCoremod = new ConcurrentHashMap<>();
@ -38,10 +32,9 @@ public class CoreModTransformerHasher {
} catch(IOException e) {
throw new RuntimeException(e);
}
MessageDigest hasher = coremodHasher.get();
byte[] hash = hasher.digest(coreModContents);
MessageDigest hasher = ModernFixCachingClassTransformer.systemHasher.get();
hasher.reset();
return hash;
return hasher.digest(coreModContents);
}
public static byte[] obtainHash(CoreModBaseTransformer<?> transformer) {

View File

@ -1,12 +1,11 @@
package org.embeddedt.modernfix.classloading.hashers;
import com.google.common.collect.HashMultimap;
import com.google.common.io.Resources;
import cpw.mods.modlauncher.ModernFixCachingClassTransformer;
import org.spongepowered.asm.launch.IClassProcessor;
import org.spongepowered.asm.launch.MixinLaunchPluginLegacy;
import org.spongepowered.asm.mixin.MixinEnvironment;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
import org.spongepowered.asm.service.MixinService;
import java.io.IOException;
import java.lang.reflect.Field;
@ -19,6 +18,10 @@ public class MixinTransformerHasher {
private static HashMap<String, byte[]> hashesByClass = null;
private final static MessageDigest hasher;
private static Field processorsListField, transformerField, processorField, environmentField;
private static final byte[] NO_MIXINS = new byte[] {(byte)0xde, (byte)0xad, (byte)0xbe, (byte)0xef};
static {
try {
hasher = MessageDigest.getInstance("SHA-256");
@ -31,12 +34,12 @@ public class MixinTransformerHasher {
/* FIXME runs too early right now, and therefore doesn't pick up the list of mixins correctly */
synchronized (MixinTransformerHasher.class) {
if(hashesByClass == null) {
hashesByClass = new HashMap<>();
HashMap<String, ArrayList<IMixinInfo>> mixinsByClass = new HashMap<>();
try {
Field processorsField = MixinLaunchPluginLegacy.class.getDeclaredField("processors");
processorsField.setAccessible(true);
List<IClassProcessor> processors = (List<IClassProcessor>)processorsField.get(plugin);
if(processorsListField == null) {
processorsListField = MixinLaunchPluginLegacy.class.getDeclaredField("processors");
processorsListField.setAccessible(true);
}
List<IClassProcessor> processors = (List<IClassProcessor>) processorsListField.get(plugin);
Object transformHandler = null;
for(IClassProcessor processor : processors) {
if(processor.getClass().getName().equals("org.spongepowered.asm.service.modlauncher.MixinTransformationHandler")) {
@ -46,12 +49,24 @@ public class MixinTransformerHasher {
}
if(transformHandler == null)
throw new IllegalStateException("Mixin transform handler not found");
Field transformerField = transformHandler.getClass().getDeclaredField("transformer");
transformerField.setAccessible(true);
if(transformerField == null) {
transformerField = transformHandler.getClass().getDeclaredField("transformer");
transformerField.setAccessible(true);
}
Object transformer = transformerField.get(transformHandler);
Field processorField = transformer.getClass().getDeclaredField("processor");
processorField.setAccessible(true);
if(processorField == null) {
processorField = transformer.getClass().getDeclaredField("processor");
processorField.setAccessible(true);
}
Object processor = processorField.get(transformer);
if(environmentField == null) {
environmentField = processor.getClass().getDeclaredField("currentEnvironment");
environmentField.setAccessible(true);
}
MixinEnvironment currentEnv = (MixinEnvironment)environmentField.get(processor);
if(currentEnv == null || currentEnv.getPhase() != MixinEnvironment.Phase.DEFAULT) {
return null; /* no hash obtained until mixin is ready */
}
Field configsField = processor.getClass().getDeclaredField("configs");
configsField.setAccessible(true);
List<?> configs = (List<?>)configsField.get(processor);
@ -60,6 +75,7 @@ public class MixinTransformerHasher {
/* getTargetClasses can't be used because it's package-private */
Field classNamesField = Class.forName("org.spongepowered.asm.mixin.transformer.MixinInfo").getDeclaredField("targetClassNames");
classNamesField.setAccessible(true);
HashMap<String, ArrayList<IMixinInfo>> mixinsByClass = new HashMap<>();
for(Object config : configs) {
List<? extends IMixinInfo> mixins = (List<? extends IMixinInfo>)mixinsField.get(config);
for(IMixinInfo mixin : mixins) {
@ -73,6 +89,7 @@ public class MixinTransformerHasher {
infos.sort((info1, info2) -> Comparator.<String>naturalOrder().compare(info1.getClassName(), info2.getClassName()));
}
/* Now go through each class name and hash it */
HashMap<String, byte[]> hashesByClassInit = new HashMap<>();
for(Map.Entry<String, ArrayList<IMixinInfo>> mixinsForClass : mixinsByClass.entrySet()) {
hasher.reset();
for(IMixinInfo mixin : mixinsForClass.getValue()) {
@ -87,13 +104,14 @@ public class MixinTransformerHasher {
}
hasher.update(bytecode);
}
hashesByClass.put(mixinsForClass.getKey().replace('/', '.'), hasher.digest());
hashesByClassInit.put(mixinsForClass.getKey().replace('/', '.'), hasher.digest());
}
hashesByClass = hashesByClassInit;
} catch(ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}
return hashesByClass.getOrDefault(className, ModernFixCachingClassTransformer.EMPTY_BYTE_ARRAY);
return hashesByClass.getOrDefault(className, NO_MIXINS);
}
}

View File

@ -59,23 +59,6 @@ public class ModernFixMixinPlugin implements IMixinConfigPlugin {
throw new IllegalStateException("Expected a TransformingClassLoader");
}
try {
if(isOptionEnabled("launch.transformer_cache.ModernFixClassTransformer")) {
Field classTransformerField = TransformingClassLoader.class.getDeclaredField("classTransformer");
classTransformerField.setAccessible(true);
ClassTransformer t = (ClassTransformer)classTransformerField.get(loader);
TransformStore store = ObfuscationReflectionHelper.getPrivateValue(ClassTransformer.class, t, "transformers");
LaunchPluginHandler pluginHandler = ObfuscationReflectionHelper.getPrivateValue(ClassTransformer.class, t, "pluginHandler");
TransformerAuditTrail trail = ObfuscationReflectionHelper.getPrivateValue(ClassTransformer.class, t, "auditTrail");
injectClassIntoSystemLoader("org.embeddedt.modernfix.classloading.api.IHashableTransformer");
injectClassIntoSystemLoader("org.embeddedt.modernfix.classloading.hashers.CoreModTransformerHasher");
injectClassIntoSystemLoader("org.embeddedt.modernfix.classloading.hashers.MixinTransformerHasher");
Class<?> newTransformerClass = injectClassIntoSystemLoader("cpw.mods.modlauncher.ModernFixCachingClassTransformer");
Constructor<?> constructor = newTransformerClass.getConstructor(TransformStore.class, LaunchPluginHandler.class, TransformingClassLoader.class, TransformerAuditTrail.class);
ClassTransformer newTransformer = (ClassTransformer)constructor.newInstance(store, pluginHandler, loader, trail);
classTransformerField.set(loader, newTransformer);
logger.info("Successfully injected caching transformer");
}
if(isOptionEnabled("launch.class_search_cache.ModernFixResourceFinder")) {
Field resourceFinderField = TransformingClassLoader.class.getDeclaredField("resourceFinder");
/* Construct a new list of resource finders, using similar logic to ML */
@ -88,7 +71,7 @@ public class ModernFixMixinPlugin implements IMixinConfigPlugin {
resourceFinder = EnumerationHelper.mergeFunctors(resourceFinder, LamdbaExceptionUtils.rethrowFunction(dcl::findResources));
resourceFinderField.set(loader, resourceFinder);
}
} catch(RuntimeException | ReflectiveOperationException | IOException e) {
} catch(RuntimeException | ReflectiveOperationException e) {
logger.error("Failed to make classloading changes", e);
}
}
@ -127,7 +110,28 @@ public class ModernFixMixinPlugin implements IMixinConfigPlugin {
@Override
public void onLoad(String mixinPackage) {
try {
if(isOptionEnabled("launch.transformer_cache.ModernFixClassTransformer")) {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Field classTransformerField = TransformingClassLoader.class.getDeclaredField("classTransformer");
classTransformerField.setAccessible(true);
ClassTransformer t = (ClassTransformer)classTransformerField.get(loader);
TransformStore store = ObfuscationReflectionHelper.getPrivateValue(ClassTransformer.class, t, "transformers");
LaunchPluginHandler pluginHandler = ObfuscationReflectionHelper.getPrivateValue(ClassTransformer.class, t, "pluginHandler");
TransformerAuditTrail trail = ObfuscationReflectionHelper.getPrivateValue(ClassTransformer.class, t, "auditTrail");
injectClassIntoSystemLoader("org.embeddedt.modernfix.classloading.api.IHashableTransformer");
injectClassIntoSystemLoader("org.embeddedt.modernfix.classloading.hashers.CoreModTransformerHasher");
injectClassIntoSystemLoader("org.embeddedt.modernfix.classloading.hashers.MixinTransformerHasher");
Class<?> newTransformerClass = injectClassIntoSystemLoader("cpw.mods.modlauncher.ModernFixCachingClassTransformer");
Constructor<?> constructor = newTransformerClass.getConstructor(TransformStore.class, LaunchPluginHandler.class, TransformingClassLoader.class, TransformerAuditTrail.class);
ClassTransformer newTransformer = (ClassTransformer)constructor.newInstance(store, pluginHandler, loader, trail);
classTransformerField.set(loader, newTransformer);
logger.info("Successfully injected caching transformer");
}
} catch(RuntimeException | ReflectiveOperationException | IOException e) {
logger.error("Failed to make classloading changes", e);
}
}
@Override

View File

@ -30,7 +30,7 @@ public class ModernFixEarlyConfig {
this.addMixinRule("perf.async_jei", true);
this.addMixinRule("perf.thread_priorities", true);
this.addMixinRule("perf.preload_block_classes", false);
this.addMixinRule("perf.parallel_potentially_unsafe", false);
this.addMixinRule("perf.defer_voxelshape_optimize", false);
this.addMixinRule("perf.parallel_blockstate_cache_rebuild", true);
this.addMixinRule("perf.deduplicate_location", true);
this.addMixinRule("safety", true);

View File

@ -1,27 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.parallel_potentially_unsafe.parallel_deferred_suppliers;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.IForgeRegistry;
import org.embeddedt.modernfix.registry.DeferredRegisterBaker;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import java.util.function.Supplier;
@Mixin(DeferredRegister.class)
public class DeferredRegisterMixin {
@Shadow(remap = false) private IForgeRegistry type;
@Shadow(remap = false) @Final private String modid;
@ModifyArg(method = "register(Ljava/lang/String;Ljava/util/function/Supplier;)Lnet/minecraftforge/fml/RegistryObject;", at = @At(value = "INVOKE", target = "Ljava/util/Map;putIfAbsent(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"), index = 1, remap = false)
private Object swapForCachedSupplier(Object original) {
if(this.type != null)
return DeferredRegisterBaker.cacheForComputationLater(this.type.getRegistryName(), this.modid, (Supplier<?>)original);
else
return original;
}
}

View File

@ -1,24 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.parallel_potentially_unsafe.parallel_deferred_suppliers;
import net.minecraft.block.DispenserBlock;
import net.minecraft.dispenser.IDispenseItemBehavior;
import net.minecraft.item.Item;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Mixin(DispenserBlock.class)
public class DispenserBlockMixin {
@Shadow private static Map<Item, IDispenseItemBehavior> DISPENSER_REGISTRY;
@Inject(method = "<clinit>", at = @At("TAIL"))
private static void makeMapConcurrent(CallbackInfo ci) {
DISPENSER_REGISTRY = new ConcurrentHashMap<>();
}
}

View File

@ -1,31 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.parallel_potentially_unsafe.parallel_deferred_suppliers;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.IForgeRegistry;
import org.embeddedt.modernfix.registry.DeferredRegisterBaker;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.HashMap;
@Mixin(DeferredRegister.EventDispatcher.class)
public class EventDispatcherMixin {
private static HashMap<ResourceLocation, Boolean> hasRegistryBaked = new HashMap<>();
@Inject(method = "handleEvent", at = @At("HEAD"), remap = false)
private void bakeIfNeeded(RegistryEvent.Register<?> event, CallbackInfo ci) {
IForgeRegistry<?> registry = event.getRegistry();
if(registry == null)
return;
ResourceLocation location = registry.getRegistryName();
if(location == null || !(location.getNamespace().equals("minecraft") && location.getPath().equals("block")))
return;
if(!hasRegistryBaked.getOrDefault(location, false)) {
DeferredRegisterBaker.bakeSuppliers(location);
hasRegistryBaked.put(location, true);
}
}
}

View File

@ -1,4 +1,5 @@
public net.minecraft.client.Minecraft$WorldSelectionType
public net.minecraft.client.renderer.RenderType$Type
public net.minecraft.client.renderer.RenderType$Type <init>(Ljava/lang/String;Lnet/minecraft/client/renderer/vertex/VertexFormat;IIZZLnet/minecraft/client/renderer/RenderType$State;)V
public net.minecraft.block.AbstractBlock$AbstractBlockState$Cache
public net.minecraft.block.AbstractBlock$AbstractBlockState$Cache
public net.minecraft.util.math.shapes.VoxelShape <init>(Lnet/minecraft/util/math/shapes/VoxelShapePart;)V # <init>

View File

@ -19,12 +19,9 @@
"perf.boost_worker_count.UtilMixin",
"perf.thread_priorities.UtilMixin",
"perf.preload_block_classes.GameDataMixin",
"perf.parallel_potentially_unsafe.parallel_deferred_suppliers.DeferredRegisterMixin",
"perf.parallel_potentially_unsafe.parallel_deferred_suppliers.EventDispatcherMixin",
"perf.parallel_blockstate_cache_rebuild.BlocksMixin",
"perf.parallel_blockstate_cache_rebuild.BlockCallbacksMixin",
"perf.parallel_blockstate_cache_rebuild.ShapeCacheMixin",
"perf.parallel_potentially_unsafe.parallel_deferred_suppliers.DispenserBlockMixin",
"perf.deduplicate_location.MixinResourceLocation"
],
"client": [