From fa47e923f0f5bbbcaba0ecb5b4e4036e9da451ae Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sat, 12 Aug 2023 21:19:43 -0400 Subject: [PATCH] Better compatibility with mods that inject into initCache --- build.gradle | 1 + .../BlockStateBaseMixin.java | 27 +---- .../modernfix/core/ModernFixMixinPlugin.java | 109 ++++++++++++++++++ 3 files changed, 113 insertions(+), 24 deletions(-) diff --git a/build.gradle b/build.gradle index 0df67a03..5d1c67c9 100644 --- a/build.gradle +++ b/build.gradle @@ -206,6 +206,7 @@ configure(subprojects.findAll {it.name == "forge" || it.name == "fabric"}) { client { vmArgs "-Xmx1G" vmArgs "-Xms1G" + property("mixin.debug.export", "true") } } } diff --git a/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/reduce_blockstate_cache_rebuilds/BlockStateBaseMixin.java b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/reduce_blockstate_cache_rebuilds/BlockStateBaseMixin.java index db024b63..3974cdef 100644 --- a/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/reduce_blockstate_cache_rebuilds/BlockStateBaseMixin.java +++ b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/reduce_blockstate_cache_rebuilds/BlockStateBaseMixin.java @@ -3,13 +3,10 @@ package org.embeddedt.modernfix.common.mixin.perf.reduce_blockstate_cache_rebuil import net.minecraft.world.level.block.state.BlockBehaviour; import org.embeddedt.modernfix.duck.IBlockState; import org.objectweb.asm.Opcodes; -import org.spongepowered.asm.mixin.Dynamic; 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.Redirect; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(BlockBehaviour.BlockStateBase.class) @@ -30,7 +27,7 @@ public abstract class BlockStateBaseMixin implements IBlockState { return cacheInvalid; } - private BlockBehaviour.BlockStateBase.Cache generateCache(BlockBehaviour.BlockStateBase base) { + private void mfix$generateCache() { if(cacheInvalid) { // Ensure that only one block's cache is built at a time synchronized (BlockBehaviour.BlockStateBase.class) { @@ -49,7 +46,6 @@ public abstract class BlockStateBaseMixin implements IBlockState { } } - return this.cache; } @Redirect(method = "*", at = @At( @@ -59,24 +55,7 @@ public abstract class BlockStateBaseMixin implements IBlockState { ordinal = 0 )) private BlockBehaviour.BlockStateBase.Cache dynamicCacheGen(BlockBehaviour.BlockStateBase base) { - return generateCache(base); - } - - @Dynamic - @Inject(method = "getPathNodeType", at = @At("HEAD"), require = 0, remap = false) - private void generateCacheLithium(CallbackInfoReturnable cir) { - generateCache((BlockBehaviour.BlockStateBase)(Object)this); - } - - @Dynamic - @Inject(method = "getNeighborPathNodeType", at = @At("HEAD"), require = 0, remap = false) - private void generateCacheLithium2(CallbackInfoReturnable cir) { - generateCache((BlockBehaviour.BlockStateBase)(Object)this); - } - - @Dynamic - @Inject(method = "getAllFlags", at = @At("HEAD"), require = 0, remap = false) - private void generateCacheLithium3(CallbackInfoReturnable cir) { - generateCache((BlockBehaviour.BlockStateBase)(Object)this); + mfix$generateCache(); + return this.cache; } } diff --git a/common/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java b/common/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java index 6c902036..c61492a3 100644 --- a/common/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java +++ b/common/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java @@ -1,14 +1,18 @@ package org.embeddedt.modernfix.core; +import com.google.common.collect.ImmutableSet; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.embeddedt.modernfix.core.config.ModernFixEarlyConfig; import org.embeddedt.modernfix.core.config.Option; import org.embeddedt.modernfix.platform.ModernFixPlatformHooks; import org.embeddedt.modernfix.world.ThreadDumper; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; import org.spongepowered.asm.mixin.extensibility.IMixinInfo; +import org.spongepowered.asm.mixin.transformer.meta.MixinMerged; import java.io.File; import java.util.*; @@ -146,6 +150,111 @@ public class ModernFixMixinPlugin implements IMixinConfigPlugin { @Override public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { + if(mixinClassName.equals("org.embeddedt.modernfix.common.mixin.perf.reduce_blockstate_cache_rebuilds.BlockStateBaseMixin")) { + try { + applyBlockStateCacheScan(targetClass); + } catch(RuntimeException e) { + ModernFixMixinPlugin.instance.logger.error("Applying blockstate cache ASM patch failed", e); + } + } ModernFixPlatformHooks.INSTANCE.applyASMTransformers(mixinClassName, targetClass); } + + private void applyBlockStateCacheScan(ClassNode targetClass) { + Set initCacheMethodNames = ImmutableSet.of("m_60611_", "func_215692_c", "method_26200", "initCache"); + Set whitelistedInjections = ImmutableSet.of( + "getFluidState", "method_26227", "m_60819_", "func_204520_s" + ); + Map injectorMethodNames = new HashMap<>(); + Map injectorMixinSource = new HashMap<>(); + String descriptor = Type.getDescriptor(MixinMerged.class); + for(MethodNode m : targetClass.methods) { + if((m.access & Opcodes.ACC_STATIC) != 0) + continue; + Set seenNodes = new HashSet<>(); + if(m.invisibleAnnotations != null) { + for(AnnotationNode ann : m.invisibleAnnotations) { + if(ann.desc.equals(descriptor)) { + seenNodes.add(ann); + } + } + } + if(m.visibleAnnotations != null) { + for(AnnotationNode ann : m.visibleAnnotations) { + if(ann.desc.equals(descriptor)) { + seenNodes.add(ann); + } + } + } + if(seenNodes.size() > 0) { + injectorMethodNames.put(m.name, m); + for(AnnotationNode node : seenNodes) { + for(int i = 0; i < node.values.size(); i += 2) { + if(Objects.equals(node.values.get(i), "mixin")) { + injectorMixinSource.put(m.name, (String)node.values.get(i + 1)); + break; + } + } + } + } + } + Set cacheCalledInjectors = new HashSet<>(); + // Search for initCache in the class + for(MethodNode m : targetClass.methods) { + if((m.access & Opcodes.ACC_STATIC) != 0) + continue; + if(initCacheMethodNames.contains(m.name)) { + // This is it. Check for any injectors it calls + for(AbstractInsnNode n : m.instructions) { + if(n instanceof MethodInsnNode) { + MethodInsnNode invoke = (MethodInsnNode)n; + if(((MethodInsnNode)n).owner.equals(targetClass.name) && injectorMethodNames.containsKey(((MethodInsnNode)n).name)) { + cacheCalledInjectors.add(invoke.name); + } + } + } + break; + } + } + Set accessedFieldNames = new HashSet<>(); + // We now know all methods that have been injected into initCache. See what fields they write to + injectorMethodNames.forEach((name, method) -> { + if(cacheCalledInjectors.contains(name)) { + for(AbstractInsnNode n : method.instructions) { + if(n instanceof FieldInsnNode) { + FieldInsnNode fieldAcc = (FieldInsnNode)n; + if(fieldAcc.getOpcode() == Opcodes.PUTFIELD && fieldAcc.owner.equals(targetClass.name)) { + accessedFieldNames.add(fieldAcc.name); + } + } + } + } + }); + // Lastly, scan all injected methods and see if they retrieve from the field. If so, inject a generateCache + // call at the start. + injectorMethodNames.forEach((name, method) -> { + // skip whitelisted injectors, and injectors called by initCache itself (to prevent recursion) + if(whitelistedInjections.contains(name) || cacheCalledInjectors.contains(name)) + return; + boolean needInjection = false; + for(AbstractInsnNode n : method.instructions) { + if(n instanceof FieldInsnNode) { + FieldInsnNode fieldAcc = (FieldInsnNode)n; + if(fieldAcc.getOpcode() == Opcodes.GETFIELD && accessedFieldNames.contains(fieldAcc.name)) { + needInjection = true; + break; + } + } + } + if(needInjection) { + ModernFixMixinPlugin.instance.logger.info("Injecting BlockStateBase cache population hook into {} from {}", + name, injectorMixinSource.getOrDefault(name, "[unknown mixin]")); + // inject this.mfix$generateCache() at method head + InsnList injection = new InsnList(); + injection.add(new VarInsnNode(Opcodes.ALOAD, 0)); + injection.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, targetClass.name, "mfix$generateCache", "()V")); + method.instructions.insert(injection); + } + }); + } } \ No newline at end of file