Skip to content

Commit

Permalink
Inline, swap, substitution as param transformers
Browse files Browse the repository at this point in the history
  • Loading branch information
Matyrobbrt committed Jan 25, 2024
1 parent afc16b7 commit f2dc31c
Show file tree
Hide file tree
Showing 11 changed files with 273 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ private int calculateLVTIndex(List<Type> parameters, boolean nonStatic, int inde
return lvt;
}

private Consumer<Void> swapLVT(MethodNode methodNode, int from, int to) {
public static Consumer<Void> swapLVT(MethodNode methodNode, int from, int to) {
Consumer<Void> r = v -> {};
for (LocalVariableNode lvn : methodNode.localVariables) {
if (lvn.index == from) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package dev.su5ed.sinytra.adapter.patch.transformer.param;

import com.mojang.logging.LogUtils;
import dev.su5ed.sinytra.adapter.patch.api.MethodContext;
import dev.su5ed.sinytra.adapter.patch.api.Patch;
import dev.su5ed.sinytra.adapter.patch.api.PatchContext;
import dev.su5ed.sinytra.adapter.patch.util.AdapterUtil;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.InstructionAdapter;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.LocalVariableNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.VarInsnNode;
import org.slf4j.Logger;

import java.util.Comparator;
import java.util.List;
import java.util.function.Consumer;

import static dev.su5ed.sinytra.adapter.patch.PatchInstance.MIXINPATCH;

public record InlineParameterTransformer(int target, Consumer<InstructionAdapter> adapter) implements ParameterTransformer {
private static final Logger LOGGER = LogUtils.getLogger();

@Override
public Patch.Result apply(ClassNode classNode, MethodNode methodNode, MethodContext methodContext, PatchContext context, List<Type> parameters, int offset) {
final int index = this.target + offset;
LOGGER.info(MIXINPATCH, "Inlining parameter {} of method {}.{}", index, classNode.name, methodNode.name);
final int replaceIndex = -999 + index;

withLVTSnapshot(methodNode, () -> {
if (index < methodNode.parameters.size()) {
methodNode.parameters.remove(index);
}

methodNode.localVariables.sort(Comparator.comparingInt(lvn -> lvn.index));
LocalVariableNode lvn = methodNode.localVariables.remove(index + (methodContext.isStatic(methodNode) ? 0 : 1));
AdapterUtil.replaceLVT(methodNode, idx -> idx == lvn.index ? replaceIndex : idx);
});

parameters.remove(index);

for (AbstractInsnNode insn : methodNode.instructions) {
if (insn instanceof VarInsnNode varInsn && varInsn.var == replaceIndex) {
InsnList replacementInsns = AdapterUtil.insnsWithAdapter(adapter);
methodNode.instructions.insert(varInsn, replacementInsns);
methodNode.instructions.remove(varInsn);
}
}

return Patch.Result.COMPUTE_FRAMES;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package dev.su5ed.sinytra.adapter.patch.transformer.param;

import com.mojang.logging.LogUtils;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import dev.su5ed.sinytra.adapter.patch.api.MethodContext;
import dev.su5ed.sinytra.adapter.patch.api.Patch;
import dev.su5ed.sinytra.adapter.patch.api.PatchContext;
import dev.su5ed.sinytra.adapter.patch.util.AdapterUtil;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
import org.slf4j.Logger;

import java.util.List;

public record SubstituteParameterTransformer(int target, int substitute) implements ParameterTransformer {
static final Codec<SubstituteParameterTransformer> CODEC = RecordCodecBuilder.create(in -> in.group(
Codec.intRange(0, 255).fieldOf("target").forGetter(SubstituteParameterTransformer::target),
Codec.intRange(0, 255).fieldOf("substitute").forGetter(SubstituteParameterTransformer::substitute)
).apply(in, SubstituteParameterTransformer::new));

private static final Logger LOGGER = LogUtils.getLogger();

@Override
public Patch.Result apply(ClassNode classNode, MethodNode methodNode, MethodContext methodContext, PatchContext context, List<Type> parameters, int offset) {
final int paramIndex = this.target + offset;
final int substituteParamIndex = this.substitute + offset;
final boolean isNonStatic = !methodContext.isStatic(methodNode);
final int localIndex = ParameterTransformer.calculateLVTIndex(parameters, isNonStatic, paramIndex);

if (methodNode.parameters.size() <= paramIndex) {
return Patch.Result.PASS;
}

withLVTSnapshot(methodNode, () -> {
LOGGER.info("Substituting parameter {} for {} in {}.{}", paramIndex, substituteParamIndex, classNode.name, methodNode.name);
parameters.remove(paramIndex);
methodNode.parameters.remove(paramIndex);
methodNode.localVariables.removeIf(lvn -> lvn.index == localIndex);

final int substituteIndex = ParameterTransformer.calculateLVTIndex(parameters, isNonStatic, substituteParamIndex);
AdapterUtil.replaceLVT(methodNode, idx -> idx == localIndex ? substituteIndex : idx);
});

return Patch.Result.COMPUTE_FRAMES;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package dev.su5ed.sinytra.adapter.patch.transformer.param;

import com.mojang.logging.LogUtils;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import dev.su5ed.sinytra.adapter.patch.api.MethodContext;
import dev.su5ed.sinytra.adapter.patch.api.Patch;
import dev.su5ed.sinytra.adapter.patch.api.PatchContext;
import dev.su5ed.sinytra.adapter.patch.transformer.ModifyMethodParams;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.ParameterNode;
import org.slf4j.Logger;

import java.util.List;

import static dev.su5ed.sinytra.adapter.patch.PatchInstance.MIXINPATCH;

public record SwapParametersTransformer(int from, int to) implements ParameterTransformer {
private static final Logger LOGGER = LogUtils.getLogger();

static final Codec<SwapParametersTransformer> CODEC = RecordCodecBuilder.create(in -> in.group(
Codec.intRange(0, 255).fieldOf("from").forGetter(SwapParametersTransformer::from),
Codec.intRange(0, 255).fieldOf("to").forGetter(SwapParametersTransformer::to)
).apply(in, SwapParametersTransformer::new));

@Override
public Patch.Result apply(ClassNode classNode, MethodNode methodNode, MethodContext methodContext, PatchContext context, List<Type> parameters, int offset) {
int from = offset + this.from;
int to = offset + this.to;
boolean nonStatic = !methodContext.isStatic(methodNode);
ParameterNode fromNode = methodNode.parameters.get(from);
ParameterNode toNode = methodNode.parameters.get(to);

int fromOldLVT = ParameterTransformer.calculateLVTIndex(parameters, nonStatic, from);
int toOldLVT = ParameterTransformer.calculateLVTIndex(parameters, nonStatic, to);

Type fromType = parameters.get(from);
Type toType = parameters.get(to);
parameters.set(from, toType);
parameters.set(to, fromType);

methodNode.parameters.set(from, toNode);
methodNode.parameters.set(to, fromNode);

LOGGER.info(MIXINPATCH, "Swapped parameters at positions {}({}) and {}({}) in {}.{}", from, fromNode.name, to, toNode.name, classNode.name, methodNode.name);

int fromNewLVT = ParameterTransformer.calculateLVTIndex(parameters, nonStatic, from);
int toNewLVT = ParameterTransformer.calculateLVTIndex(parameters, nonStatic, to);

// Account for "big" LVT variables (like longs and doubles)
// Uses of the old parameter need to be the new parameter and vice versa
ModifyMethodParams.swapLVT(methodNode, fromOldLVT, toNewLVT)
.andThen(ModifyMethodParams.swapLVT(methodNode, toOldLVT, fromNewLVT))
.accept(null);

return Patch.Result.COMPUTE_FRAMES;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,21 @@
import dev.su5ed.sinytra.adapter.patch.api.Patch;
import dev.su5ed.sinytra.adapter.patch.api.PatchContext;
import dev.su5ed.sinytra.adapter.patch.selector.AnnotationHandle;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.InstructionAdapter;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;

public record TransformParameters(List<ParameterTransformer> transformers, boolean withOffset) implements MethodTransform {
private static final BiMap<String, Codec<? extends ParameterTransformer>> TRANSFORMER_CODECS = ImmutableBiMap.<String, Codec<? extends ParameterTransformer>>builder()
.put("inject_parameter", InjectParameterTransform.CODEC)
.put("swap_parameters", SwapParametersTransformer.CODEC)
.put("substitute_parameters", SubstituteParameterTransformer.CODEC)
.build();

public static final Codec<TransformParameters> CODEC = RecordCodecBuilder.create(in -> in.group(
Expand Down Expand Up @@ -72,6 +75,18 @@ public Builder inject(int parameterIndex, Type type) {
return this.transform(new InjectParameterTransform(parameterIndex, type));
}

public Builder swap(int from, int to) {
return this.transform(new SwapParametersTransformer(from, to));
}

public Builder substitute(int target, int substitute) {
return this.transform(new SubstituteParameterTransformer(target, substitute));
}

public Builder inline(int target, Consumer<InstructionAdapter> adapter) {
return this.transform(new InlineParameterTransformer(target, adapter));
}

public Builder withOffset() {
this.offset = true;
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import dev.su5ed.sinytra.adapter.patch.api.PatchEnvironment;
import dev.su5ed.sinytra.adapter.patch.selector.AnnotationHandle;
import dev.su5ed.sinytra.adapter.patch.selector.AnnotationValueHandle;
import it.unimi.dsi.fastutil.ints.Int2IntFunction;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.VisibleForTesting;
import org.objectweb.asm.Opcodes;
Expand Down Expand Up @@ -109,6 +110,19 @@ public static SingleValueHandle<Integer> handleLocalVarInsnValue(AbstractInsnNod
return null;
}

public static void replaceLVT(MethodNode methodNode, Int2IntFunction operator) {
for (AbstractInsnNode insn : methodNode.instructions) {
SingleValueHandle<Integer> handle = AdapterUtil.handleLocalVarInsnValue(insn);
if (handle == null) continue;

final int oldValue = handle.get();
final int newValue = operator.applyAsInt(oldValue);
if (newValue != oldValue) {
handle.set(newValue);
}
}
}

public static boolean canHandleLocalVarInsnValue(AbstractInsnNode insn) {
return insn instanceof VarInsnNode || insn instanceof IincInsnNode;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package dev.su5ed.sinytra.adapter.patch.test.mixin;

import dev.su5ed.sinytra.adapter.patch.api.MixinConstants;
import dev.su5ed.sinytra.adapter.patch.api.Patch;
import org.junit.jupiter.api.Test;

public class ParameterInlineTest extends MixinPatchTest {
@Test
void testSimpleInline() throws Exception {
assertSameCode(
"org/sinytra/adapter/test/mixins/ParameterInlineMixin",
"testSimpleInline",
Patch.builder()
.targetInjectionPoint("")
.targetMethod("simple")
.targetMixinType(MixinConstants.INJECT)
.transformParams(params -> params.inline(3, adapter -> adapter.visitLdcInsn("inlined")))
);
}

@Test
void testBigInline() throws Exception {
assertSameCode(
"org/sinytra/adapter/test/mixins/ParameterInlineMixin",
"testBigInline",
Patch.builder()
.targetInjectionPoint("")
.targetMethod("big")
.targetMixinType(MixinConstants.INJECT)
.transformParams(params -> params.inline(2, adapter -> adapter.visitLdcInsn(43.0d)))
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ void testSimpleSubstitution() throws Exception {
.targetInjectionPoint("")
.targetMethod("injectTarget")
.targetMixinType(MixinConstants.INJECT)
.modifyParams(params -> params.substitute(1, 0))
.transformParams(params -> params.substitute(1, 0))
);
}

Expand All @@ -27,7 +27,7 @@ void testBigSubstitution() throws Exception {
.targetInjectionPoint("")
.targetMethod("injectTarget2")
.targetMixinType(MixinConstants.INJECT)
.modifyParams(params -> params.substitute(2, 0))
.transformParams(params -> params.substitute(2, 0))
);
}

Expand All @@ -40,7 +40,7 @@ void testComplexSubstitution() throws Exception {
.targetInjectionPoint("")
.targetMethod("injectTarget3")
.targetMixinType(MixinConstants.INJECT)
.modifyParams(params -> params.substitute(1, 2))
.transformParams(params -> params.substitute(1, 2))
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ void testSimpleSwap() throws Exception {
.targetInjectionPoint("")
.targetMethod("injectTarget")
.targetMixinType(MixinConstants.INJECT)
.modifyParams(params -> params.swap(0, 1))
.transformParams(params -> params.swap(0, 1))
);
}

Expand All @@ -30,7 +30,7 @@ void testComplexSwap() throws Exception {
.targetInjectionPoint("")
.targetMethod("injectTarget2")
.targetMixinType(MixinConstants.INJECT)
.modifyParams(params -> params.swap(2, 1)
.transformParams(params -> params.swap(2, 1)
.swap(1, 0))
);
}
Expand All @@ -45,7 +45,7 @@ void testBigSwap() throws Exception {
.targetInjectionPoint("")
.targetMethod("injectTarget3")
.targetMixinType(MixinConstants.INJECT)
.modifyParams(params -> params.swap(0, 1))
.transformParams(params -> params.swap(0, 1))
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.sinytra.adapter.test.classes;

public class ParameterInline {
public static void simple(int p1, String p2, int p3) {

}

public static void big(int p1, double p2, int p3) {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.sinytra.adapter.test.mixins;

import org.sinytra.adapter.test.classes.ParameterInline;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;

@Mixin(ParameterInline.class)
public class ParameterInlineMixin {
@Inject(method = "simple(ILjava/lang/String;ILjava/lang/String;)V", at = @At("HEAD"))
private void testSimpleInline(int p1, String p2, int p3, String p4) {
System.out.println(p4);
}

private void testSimpleInlineExpected(int p1, String p2, int p3) {
System.out.println("inlined");
}

@Inject(method = "big(IDI)V", at = @At("HEAD"))
private void testBigInline(int p1, double p2, double p3, int p4) {
System.out.println(p3 * p2);
System.out.println("p4: " + p4);
}

private void testBigInlineExpected(int p1, double p2, int p4) {
System.out.println(43d * p2);
System.out.println("p4: " + p4);
}
}

0 comments on commit f2dc31c

Please sign in to comment.