Skip to content

Commit

Permalink
Merge pull request #33 from NotStirred/1.20.4_dasm2
Browse files Browse the repository at this point in the history
Pre-compute dasm TargetClass and redirects before applying any mixins

Switch to dasm 2, update all annotations
  • Loading branch information
NotStirred authored Feb 24, 2024
2 parents 392d8f9 + 895c8f0 commit 076b3de
Show file tree
Hide file tree
Showing 15 changed files with 561 additions and 244 deletions.
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,8 @@ forge*changelog.txt

# Fabric
.fabric
.mixin.out
.mixin.out

# Dasm
.dasm.out

2 changes: 1 addition & 1 deletion .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ runs {
sourceSets.main.resources { srcDir 'src/generated/resources' }

repositories {
mavenLocal()
maven { setUrl("https://oss.sonatype.org/content/repositories/snapshots/") }
maven { setUrl("https://jitpack.io") }
maven { setUrl("https://maven.fabricmc.net/") }
Expand All @@ -235,7 +236,7 @@ dependencies {
targetConfiguration = "testArchivesOutput"
}

libraries("com.github.OpenCubicChunks:dasm:c08e10a63a") {
libraries("io.github.notstirred:dasm:2.0.1") {
transitive = false
}
libraries("io.github.opencubicchunks:regionlib:0.63.0-SNAPSHOT")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,67 +1,70 @@
package io.github.opencubicchunks.cubicchunks.mixin;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.annotation.Nullable;

import io.github.notstirred.dasm.annotation.AnnotationParser;
import io.github.notstirred.dasm.api.annotations.transform.ApplicationStage;
import io.github.notstirred.dasm.api.provider.MappingsProvider;
import io.github.notstirred.dasm.exception.DasmException;
import io.github.notstirred.dasm.exception.wrapped.DasmWrappedExceptions;
import io.github.notstirred.dasm.transformer.Transformer;
import io.github.notstirred.dasm.transformer.data.ClassTransform;
import io.github.notstirred.dasm.transformer.data.MethodTransform;
import io.github.notstirred.dasm.util.CachingClassProvider;
import io.github.notstirred.dasm.util.ClassNodeProvider;
import io.github.notstirred.dasm.util.Either;
import io.github.opencubicchunks.cubicchunks.CubicChunks;
import io.github.opencubicchunks.dasm.AnnotationParser;
import io.github.opencubicchunks.dasm.Transformer;
import io.github.opencubicchunks.dasm.api.provider.CachingClassProvider;
import io.github.opencubicchunks.dasm.api.provider.ClassProvider;
import io.github.opencubicchunks.dasm.api.provider.MappingsProvider;
import io.github.opencubicchunks.dasm.api.transform.TransformFrom;
import io.github.opencubicchunks.dasm.transformer.redirect.RedirectSet;
import io.github.opencubicchunks.dasm.transformer.target.TargetClass;
import net.neoforged.fml.loading.FMLEnvironment;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
import org.spongepowered.asm.mixin.transformer.ClassInfo;
import org.spongepowered.asm.service.MixinService;

public class ASMConfigPlugin implements IMixinConfigPlugin {
private final Map<String, Boolean> dasmTransformedInPreApply = new ConcurrentHashMap<>();
private final Transformer transformer;
private final AnnotationParser annotationParser;

private final Map<String, Either<ClassTransform, Collection<MethodTransform>>> preApplyTargets = new HashMap<>();
private final Map<String, Either<ClassTransform, Collection<MethodTransform>>> postApplyTargets = new HashMap<>();

public ASMConfigPlugin() {
boolean developmentEnvironment = false;
try {
developmentEnvironment = !FMLEnvironment.production;
} catch (Throwable ignored) {
}
MappingsProvider mappings = new MappingsProvider() {

@Override public String mapFieldName(String owner, String fieldName, String descriptor) {
return fieldName;
}

@Override public String mapMethodName(String owner, String methodName, String descriptor) {
return methodName;
}

@Override public String mapClassName(String className) {
return className;
}
};
MappingsProvider mappings = MappingsProvider.IDENTITY;

// TODO: breaks on fabric (remapped at runtime)
ClassProvider classProvider = new CachingClassProvider(s -> {
ClassNodeProvider classProvider = new CachingClassProvider(s -> {
try (var classStream = ASMConfigPlugin.class.getClassLoader().getResourceAsStream(s.replace(".", "/") + ".class")) {
return classStream.readAllBytes();
return Optional.ofNullable(classStream.readAllBytes());
} catch (IOException e) {
throw new RuntimeException(e);
}
});
this.transformer = new Transformer(mappings, classProvider, developmentEnvironment);
this.annotationParser = new AnnotationParser(classProvider, GeneralSet.class);
this.transformer = new Transformer(classProvider, mappings);
this.annotationParser = new AnnotationParser(classProvider);
}

@Override public void onLoad(String mixinPackage) {
Expand All @@ -72,6 +75,47 @@ public ASMConfigPlugin() {
}

@Override public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
try {
ClassNode targetClass = MixinService.getService().getBytecodeProvider().getClassNode(targetClassName);
ClassNode mixinClass = MixinService.getService().getBytecodeProvider().getClassNode(mixinClassName);
// rename the mixin class to get dasm to generate owners correctly
mixinClass.name = targetClass.name;

// PRE_APPLY
this.annotationParser.findRedirectSets(mixinClass);
var methodTransformsMixin = this.annotationParser.buildMethodTargets(mixinClass, "cc_dasm$");
this.annotationParser.findRedirectSets(targetClass);
var classTransform = this.annotationParser.buildClassTarget(targetClass);
var methodTransformsTarget = this.annotationParser.buildMethodTargets(targetClass, "cc_dasm$");

var methodTransforms = Stream.of(methodTransformsTarget, methodTransformsMixin)
.filter(Optional::isPresent).map(Optional::get)
.flatMap(Collection::stream).toList();

String key = mixinClassName + "|" + targetClassName;
if (classTransform.isPresent()) {
// TODO: nice error
assert methodTransformsMixin.isEmpty() && methodTransforms.isEmpty() : "Whole class transform WITH method transforms?";
ClassTransform transform = classTransform.get();
if (transform.stage() == ApplicationStage.PRE_APPLY) {
this.preApplyTargets.put(key, Either.left(transform));
} else {
this.postApplyTargets.put(key, Either.left(transform));
}
} else {
Collection<MethodTransform> preTransforms = this.preApplyTargets.computeIfAbsent(key, k -> Either.right(new ArrayList<>())).right().get();
Collection<MethodTransform> postTransforms = this.postApplyTargets.computeIfAbsent(key, k -> Either.right(new ArrayList<>())).right().get();
methodTransforms.forEach(transform -> {
if (transform.stage() == ApplicationStage.PRE_APPLY) {
preTransforms.add(transform);
} else {
postTransforms.add(transform);
}
});
}
} catch (ClassNotFoundException | IOException | DasmWrappedExceptions e) {
throw new RuntimeException(e);
}
return true;
}

Expand All @@ -83,14 +127,46 @@ public ASMConfigPlugin() {
}

@Override public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
var wasTransformed = transformClass(targetClassName, targetClass, mixinClassName, TransformFrom.ApplicationStage.PRE_APPLY);
boolean wasTransformed;
try {
wasTransformed = transformClass(targetClassName, targetClass, mixinClassName, ApplicationStage.PRE_APPLY);
} catch (DasmException e) {
throw new RuntimeException(e);
}
dasmTransformedInPreApply.put(mixinClassName + "|" + targetClassName, wasTransformed);

try {
// ugly hack to add class metadata to mixin
// based on https://github.com/Chocohead/OptiFabric/blob/54fc2ef7533e43d1982e14bc3302bcf156f590d8/src/main/java/me/modmuss50/optifabric/compat/fabricrendererapi
// /RendererMixinPlugin.java#L25:L44
Method addMethod = ClassInfo.class.getDeclaredMethod("addMethod", MethodNode.class, boolean.class);
addMethod.setAccessible(true);

ClassInfo ci = ClassInfo.forName(targetClassName);
Set<String> existingMethods = ci.getMethods().stream().map(x -> x.getName() + x.getDesc()).collect(Collectors.toSet());
for (MethodNode method : targetClass.methods) {
if (!existingMethods.contains(method.name + method.desc)) {
addMethod.invoke(ci, method, false);
}
}
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}

@Override public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
// Apply POST_APPLY dasm transforms
boolean wasTransformed = transformClass(targetClassName, targetClass, mixinClassName, TransformFrom.ApplicationStage.POST_APPLY);

boolean wasTransformed;
try {
wasTransformed = transformClass(targetClassName, targetClass, mixinClassName, ApplicationStage.POST_APPLY);
} catch (DasmException e) {
throw new RuntimeException(e);
}
for (MethodNode methodNode : targetClass.methods) {
if (methodNode.name.contains("cc_dasm$")) {
int asd = 0;
}
}
// If no DASM transformation happened to this class, we can skip removing the prefixed methods
if (!(wasTransformed | dasmTransformedInPreApply.get(mixinClassName + "|" + targetClassName)))
return;
Expand All @@ -117,41 +193,54 @@ record PrefixMethodPair(MethodNode dasmAddedMethod, MethodNode mixinAddedMethod)
if (prefixMethodPair.mixinAddedMethod != null) {
targetClass.methods.remove(prefixMethodPair.mixinAddedMethod);
}
prefixMethodPair.dasmAddedMethod.name = prefixMethodPair.dasmAddedMethod.name.substring(
prefixMethodPair.dasmAddedMethod.name.indexOf("$") + 1
);

prefixMethodPair.dasmAddedMethod.name = prefixMethodPair.dasmAddedMethod.name
.replace("__init__", "<init>")
.replace("__clinit__", "<clinit>");

// remove the prefix
prefixMethodPair.dasmAddedMethod.name = prefixMethodPair.dasmAddedMethod.name.substring("cc_dasm$".length());
});

ClassWriter classWriter = new ClassWriter(0);
targetClass.accept(classWriter);
try {
Path path = Path.of(".dasm.out/" + "POSTIER_APPLY" + "/" + targetClassName.replace('.', '/') + ".class").toAbsolutePath();
Files.createDirectories(path.getParent());
Files.write(path, classWriter.toByteArray());
} catch (IOException e) {
throw new RuntimeException(e);
}
}

/**
* @return Whether any transformation was done to the targetClass
*/
private boolean transformClass(String targetClassName, ClassNode targetClass, String mixinClassName, TransformFrom.ApplicationStage stage) {
ClassNode mixinClass;
try {
mixinClass = MixinService.getService().getBytecodeProvider().getClassNode(mixinClassName);
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
private boolean transformClass(String targetClassName, ClassNode targetClass, String mixinClassName, ApplicationStage stage) throws DasmException {
Either<ClassTransform, Collection<MethodTransform>> target = null;
switch (stage) {
case PRE_APPLY -> target = preApplyTargets.get(mixinClassName + "|" + targetClassName);
case POST_APPLY -> target = postApplyTargets.get(mixinClassName + "|" + targetClassName);
}

var target = new TargetClass(targetClassName);
Set<RedirectSet> redirectSets = new HashSet<>();

this.annotationParser.findRedirectSets(targetClassName, mixinClass, redirectSets);
this.annotationParser.buildClassTarget(mixinClass, target, stage, "cc_dasm$");
this.annotationParser.findRedirectSets(targetClassName, targetClass, redirectSets);
this.annotationParser.buildClassTarget(targetClass, target, stage, "cc_dasm$");
redirectSets.forEach(target::addRedirectSet);

if (target.targetMethods().isEmpty() && target.wholeClass() == null) {
if (target == null) {
return false;
}

if (target.wholeClass() != null) {
this.transformer.transformClass(targetClass, target);
if (target.isLeft()) {
this.transformer.transform(targetClass, target.left().get());
} else {
this.transformer.transformClass(targetClass, target);
this.transformer.transform(targetClass, target.right().get());
}
ClassWriter classWriter = new ClassWriter(0);
targetClass.accept(classWriter);
try {
Path path = Path.of(".dasm.out/" + stage + "/" + targetClassName.replace('.', '/') + ".class").toAbsolutePath();
Files.createDirectories(path.getParent());
Files.write(path, classWriter.toByteArray());
} catch (IOException e) {
throw new RuntimeException(e);
}

return true;
}
}
Original file line number Diff line number Diff line change
@@ -1,53 +1,56 @@
package io.github.opencubicchunks.cubicchunks.mixin;

import io.github.notstirred.dasm.api.annotations.redirect.redirects.FieldRedirect;
import io.github.notstirred.dasm.api.annotations.redirect.redirects.MethodRedirect;
import io.github.notstirred.dasm.api.annotations.redirect.redirects.TypeRedirect;
import io.github.notstirred.dasm.api.annotations.redirect.sets.RedirectSet;
import io.github.notstirred.dasm.api.annotations.selector.FieldSig;
import io.github.notstirred.dasm.api.annotations.selector.MethodSig;
import io.github.notstirred.dasm.api.annotations.selector.Ref;
import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloPos;
import io.github.opencubicchunks.cubicchunks.world.level.cube.CubeAccess;
import io.github.opencubicchunks.cubicchunks.world.level.cube.ImposterProtoCube;
import io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube;
import io.github.opencubicchunks.cubicchunks.world.level.cube.ProtoCube;
import io.github.opencubicchunks.dasm.api.Ref;
import io.github.opencubicchunks.dasm.api.redirect.DasmRedirectSet;
import io.github.opencubicchunks.dasm.api.redirect.FieldRedirect;
import io.github.opencubicchunks.dasm.api.redirect.MethodRedirect;
import io.github.opencubicchunks.dasm.api.redirect.TypeRedirect;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ImposterProtoChunk;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.ProtoChunk;

@DasmRedirectSet
@RedirectSet
public interface CubeAccessAndDescendantsSet extends GeneralSet {
@TypeRedirect(from = @Ref(ChunkAccess.class), to = @Ref(CubeAccess.class))
abstract class ChunkAccessToCubeAccessRedirects {
@FieldRedirect("cloPos") protected ChunkPos chunkPos;
abstract class ChunkAccess_to_CubeAccess_redirects {
@FieldRedirect(@FieldSig(type = @Ref(ChunkPos.class), name = "chunkPos")) protected CloPos cloPos;

@MethodRedirect("cc_getCloPos") public native ChunkPos getPos();
@MethodRedirect(@MethodSig("getPos()Lnet/minecraft/world/level/ChunkPos;")) public native CloPos cc_getCloPos();
}

@TypeRedirect(from = @Ref(LevelChunk.class), to = @Ref(LevelCube.class))
abstract class LevelChunkToLevelCubeRedirects { }
abstract class LevelChunk_to_LevelCube_redirects { }

@TypeRedirect(
from = @Ref(string = "net.minecraft.world.level.chunk.LevelChunk$BoundTickingBlockEntity"),
to = @Ref(string = "io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube$BoundTickingBlockEntity")
)
abstract class LevelChunk$BoundTickingBlockEntityToLevelCube$BoundTickingBlockEntityRedirects { }
abstract class LevelChunk$BoundTickingBlockEntity_to_LevelCube$BoundTickingBlockEntity_redirects { }

@TypeRedirect(
from = @Ref(string = "net.minecraft.world.level.chunk.LevelChunk$PostLoadProcessor"),
to = @Ref(string = "io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube$PostLoadProcessor")
)
abstract class LevelChunk$PostLoadProcessorToLevelCube$PostLoadProcessorRedirects { }
abstract class LevelChunk$PostLoadProcessor_to_LevelCube$PostLoadProcessor_redirects { }

@TypeRedirect(
from = @Ref(string = "net.minecraft.world.level.chunk.LevelChunk$RebindableTickingBlockEntityWrapper"),
to = @Ref(string = "io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube$RebindableTickingBlockEntityWrapper")
)
abstract class LevelChunk$RebindableTickingBlockEntityWrapperToLevelCube$RebindableTickingBlockEntityWrapperRedirects { }
abstract class LevelChunk$RebindableTickingBlockEntityWrapper_to_LevelCube$RebindableTickingBlockEntityWrapper_redirects { }

@TypeRedirect(from = @Ref(ProtoChunk.class), to = @Ref(ProtoCube.class))
abstract class ProtoChunkToProtoCubeRedirects { }
abstract class ProtoChunk_to_ProtoCube_redirects { }

@TypeRedirect(from = @Ref(ImposterProtoChunk.class), to = @Ref(ImposterProtoCube.class))
abstract class ImposterProtoChunkToImposterProtoCubeRedirects { }
abstract class ImposterProtoChunk_to_ImposterProtoCube_redirects { }
}
Loading

0 comments on commit 076b3de

Please sign in to comment.