|
|
|
package org.openrs2.deob.bytecode.transform
|
Replace DummyArgTransformer with new ConstantArgTransformer
The new transformer uses a different approach to the old one. It starts
exploring the call graph from the entry points, recursively analysing
method calls. Methods are only re-analysed if their possible argument
values change, with the Unknown value being used if we can't identify a
single integer constant at a call site. This prevents us from recursing
infinitely if the client code does.
While this first pass does simplify branches in order to ignore dummy
method calls that are never evaluated at runtime, it operates on a copy
of the method (as we initially ignore more calls while the argument
value sets are smaller, ignoring fewer calls as they build up).
A separate second pass simplifies branches on the original method and
inlines singleton constants, paving the way for the UnusedArgTransformer
to actually remove the newly unused arguments.
This new approach has several benefits:
- It is much faster than the old approach, as we only re-analyse methods
as required by argument value changes, rather than re-analysing every
method during every pass.
- It doesn't require special cases for dealing with mutually recursive
dummy calls. The old approach hard-coded special cases for mutually
recursive calls involving groups of 1 and 2 methods. The code for this
wasn't clean. Furthermore, while it was just about good enough for the
HD client, the SD client contains a mutually recursive group of 3
methods. The new approach is capable of dealing with mutually
recursive groups of any size.
Finally, the new transformer has a much cleaner implementation.
Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
|
|
|
|
|
|
|
import com.github.michaelbull.logging.InlineLogger
|
|
|
|
import org.objectweb.asm.Opcodes.GOTO
|
|
|
|
import org.objectweb.asm.Opcodes.IFEQ
|
|
|
|
import org.objectweb.asm.Opcodes.IFGE
|
|
|
|
import org.objectweb.asm.Opcodes.IFGT
|
|
|
|
import org.objectweb.asm.Opcodes.IFLE
|
|
|
|
import org.objectweb.asm.Opcodes.IFLT
|
|
|
|
import org.objectweb.asm.Opcodes.IFNE
|
|
|
|
import org.objectweb.asm.Opcodes.IF_ICMPEQ
|
|
|
|
import org.objectweb.asm.Opcodes.IF_ICMPGE
|
|
|
|
import org.objectweb.asm.Opcodes.IF_ICMPGT
|
|
|
|
import org.objectweb.asm.Opcodes.IF_ICMPLE
|
|
|
|
import org.objectweb.asm.Opcodes.IF_ICMPLT
|
|
|
|
import org.objectweb.asm.Opcodes.IF_ICMPNE
|
|
|
|
import org.objectweb.asm.Type
|
|
|
|
import org.objectweb.asm.tree.AbstractInsnNode
|
|
|
|
import org.objectweb.asm.tree.ClassNode
|
|
|
|
import org.objectweb.asm.tree.JumpInsnNode
|
|
|
|
import org.objectweb.asm.tree.MethodInsnNode
|
|
|
|
import org.objectweb.asm.tree.MethodNode
|
|
|
|
import org.objectweb.asm.tree.analysis.Analyzer
|
|
|
|
import org.openrs2.asm.MemberRef
|
|
|
|
import org.openrs2.asm.classpath.ClassPath
|
|
|
|
import org.openrs2.asm.classpath.Library
|
|
|
|
import org.openrs2.asm.copy
|
|
|
|
import org.openrs2.asm.deleteExpression
|
|
|
|
import org.openrs2.asm.filter.MemberFilter
|
|
|
|
import org.openrs2.asm.filter.UnionMemberFilter
|
|
|
|
import org.openrs2.asm.hasCode
|
|
|
|
import org.openrs2.asm.intConstant
|
|
|
|
import org.openrs2.asm.isPure
|
|
|
|
import org.openrs2.asm.nextReal
|
|
|
|
import org.openrs2.asm.replaceExpression
|
|
|
|
import org.openrs2.asm.stackMetadata
|
|
|
|
import org.openrs2.asm.toAbstractInsnNode
|
|
|
|
import org.openrs2.asm.transform.Transformer
|
|
|
|
import org.openrs2.deob.bytecode.ArgPartition
|
|
|
|
import org.openrs2.deob.bytecode.Profile
|
|
|
|
import org.openrs2.deob.bytecode.analysis.IntBranch
|
|
|
|
import org.openrs2.deob.bytecode.analysis.IntBranchResult.ALWAYS_TAKEN
|
|
|
|
import org.openrs2.deob.bytecode.analysis.IntBranchResult.NEVER_TAKEN
|
|
|
|
import org.openrs2.deob.bytecode.analysis.IntInterpreter
|
|
|
|
import org.openrs2.deob.bytecode.analysis.IntValueSet
|
|
|
|
import org.openrs2.deob.bytecode.filter.ReflectedConstructorFilter
|
|
|
|
import org.openrs2.deob.bytecode.remap.MethodMappingGenerator
|
|
|
|
import org.openrs2.util.collect.DisjointSet
|
|
|
|
import org.openrs2.util.collect.removeFirstOrNull
|
|
|
|
import javax.inject.Inject
|
|
|
|
import javax.inject.Singleton
|
Replace DummyArgTransformer with new ConstantArgTransformer
The new transformer uses a different approach to the old one. It starts
exploring the call graph from the entry points, recursively analysing
method calls. Methods are only re-analysed if their possible argument
values change, with the Unknown value being used if we can't identify a
single integer constant at a call site. This prevents us from recursing
infinitely if the client code does.
While this first pass does simplify branches in order to ignore dummy
method calls that are never evaluated at runtime, it operates on a copy
of the method (as we initially ignore more calls while the argument
value sets are smaller, ignoring fewer calls as they build up).
A separate second pass simplifies branches on the original method and
inlines singleton constants, paving the way for the UnusedArgTransformer
to actually remove the newly unused arguments.
This new approach has several benefits:
- It is much faster than the old approach, as we only re-analyse methods
as required by argument value changes, rather than re-analysing every
method during every pass.
- It doesn't require special cases for dealing with mutually recursive
dummy calls. The old approach hard-coded special cases for mutually
recursive calls involving groups of 1 and 2 methods. The code for this
wasn't clean. Furthermore, while it was just about good enough for the
HD client, the SD client contains a mutually recursive group of 3
methods. The new approach is capable of dealing with mutually
recursive groups of any size.
Finally, the new transformer has a much cleaner implementation.
Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
|
|
|
|
|
|
|
@Singleton
|
|
|
|
public class ConstantArgTransformer @Inject constructor(private val profile: Profile) : Transformer() {
|
Replace DummyArgTransformer with new ConstantArgTransformer
The new transformer uses a different approach to the old one. It starts
exploring the call graph from the entry points, recursively analysing
method calls. Methods are only re-analysed if their possible argument
values change, with the Unknown value being used if we can't identify a
single integer constant at a call site. This prevents us from recursing
infinitely if the client code does.
While this first pass does simplify branches in order to ignore dummy
method calls that are never evaluated at runtime, it operates on a copy
of the method (as we initially ignore more calls while the argument
value sets are smaller, ignoring fewer calls as they build up).
A separate second pass simplifies branches on the original method and
inlines singleton constants, paving the way for the UnusedArgTransformer
to actually remove the newly unused arguments.
This new approach has several benefits:
- It is much faster than the old approach, as we only re-analyse methods
as required by argument value changes, rather than re-analysing every
method during every pass.
- It doesn't require special cases for dealing with mutually recursive
dummy calls. The old approach hard-coded special cases for mutually
recursive calls involving groups of 1 and 2 methods. The code for this
wasn't clean. Furthermore, while it was just about good enough for the
HD client, the SD client contains a mutually recursive group of 3
methods. The new approach is capable of dealing with mutually
recursive groups of any size.
Finally, the new transformer has a much cleaner implementation.
Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
|
|
|
private val pendingMethods = LinkedHashSet<MemberRef>()
|
|
|
|
private val arglessMethods = mutableSetOf<DisjointSet.Partition<MemberRef>>()
|
|
|
|
private val argValues = mutableMapOf<ArgPartition, IntValueSet>()
|
Replace DummyArgTransformer with new ConstantArgTransformer
The new transformer uses a different approach to the old one. It starts
exploring the call graph from the entry points, recursively analysing
method calls. Methods are only re-analysed if their possible argument
values change, with the Unknown value being used if we can't identify a
single integer constant at a call site. This prevents us from recursing
infinitely if the client code does.
While this first pass does simplify branches in order to ignore dummy
method calls that are never evaluated at runtime, it operates on a copy
of the method (as we initially ignore more calls while the argument
value sets are smaller, ignoring fewer calls as they build up).
A separate second pass simplifies branches on the original method and
inlines singleton constants, paving the way for the UnusedArgTransformer
to actually remove the newly unused arguments.
This new approach has several benefits:
- It is much faster than the old approach, as we only re-analyse methods
as required by argument value changes, rather than re-analysing every
method during every pass.
- It doesn't require special cases for dealing with mutually recursive
dummy calls. The old approach hard-coded special cases for mutually
recursive calls involving groups of 1 and 2 methods. The code for this
wasn't clean. Furthermore, while it was just about good enough for the
HD client, the SD client contains a mutually recursive group of 3
methods. The new approach is capable of dealing with mutually
recursive groups of any size.
Finally, the new transformer has a much cleaner implementation.
Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
|
|
|
private lateinit var inheritedMethodSets: DisjointSet<MemberRef>
|
|
|
|
private lateinit var entryPoints: MemberFilter
|
Replace DummyArgTransformer with new ConstantArgTransformer
The new transformer uses a different approach to the old one. It starts
exploring the call graph from the entry points, recursively analysing
method calls. Methods are only re-analysed if their possible argument
values change, with the Unknown value being used if we can't identify a
single integer constant at a call site. This prevents us from recursing
infinitely if the client code does.
While this first pass does simplify branches in order to ignore dummy
method calls that are never evaluated at runtime, it operates on a copy
of the method (as we initially ignore more calls while the argument
value sets are smaller, ignoring fewer calls as they build up).
A separate second pass simplifies branches on the original method and
inlines singleton constants, paving the way for the UnusedArgTransformer
to actually remove the newly unused arguments.
This new approach has several benefits:
- It is much faster than the old approach, as we only re-analyse methods
as required by argument value changes, rather than re-analysing every
method during every pass.
- It doesn't require special cases for dealing with mutually recursive
dummy calls. The old approach hard-coded special cases for mutually
recursive calls involving groups of 1 and 2 methods. The code for this
wasn't clean. Furthermore, while it was just about good enough for the
HD client, the SD client contains a mutually recursive group of 3
methods. The new approach is capable of dealing with mutually
recursive groups of any size.
Finally, the new transformer has a much cleaner implementation.
Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
|
|
|
private var branchesSimplified = 0
|
|
|
|
private var constantsPropagated = 0
|
Replace DummyArgTransformer with new ConstantArgTransformer
The new transformer uses a different approach to the old one. It starts
exploring the call graph from the entry points, recursively analysing
method calls. Methods are only re-analysed if their possible argument
values change, with the Unknown value being used if we can't identify a
single integer constant at a call site. This prevents us from recursing
infinitely if the client code does.
While this first pass does simplify branches in order to ignore dummy
method calls that are never evaluated at runtime, it operates on a copy
of the method (as we initially ignore more calls while the argument
value sets are smaller, ignoring fewer calls as they build up).
A separate second pass simplifies branches on the original method and
inlines singleton constants, paving the way for the UnusedArgTransformer
to actually remove the newly unused arguments.
This new approach has several benefits:
- It is much faster than the old approach, as we only re-analyse methods
as required by argument value changes, rather than re-analysing every
method during every pass.
- It doesn't require special cases for dealing with mutually recursive
dummy calls. The old approach hard-coded special cases for mutually
recursive calls involving groups of 1 and 2 methods. The code for this
wasn't clean. Furthermore, while it was just about good enough for the
HD client, the SD client contains a mutually recursive group of 3
methods. The new approach is capable of dealing with mutually
recursive groups of any size.
Finally, the new transformer has a much cleaner implementation.
Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
|
|
|
|
|
|
|
override fun preTransform(classPath: ClassPath) {
|
|
|
|
pendingMethods.clear()
|
|
|
|
arglessMethods.clear()
|
|
|
|
argValues.clear()
|
|
|
|
inheritedMethodSets = classPath.createInheritedMethodSets()
|
|
|
|
entryPoints = UnionMemberFilter(profile.entryPoints, ReflectedConstructorFilter.create(classPath))
|
Replace DummyArgTransformer with new ConstantArgTransformer
The new transformer uses a different approach to the old one. It starts
exploring the call graph from the entry points, recursively analysing
method calls. Methods are only re-analysed if their possible argument
values change, with the Unknown value being used if we can't identify a
single integer constant at a call site. This prevents us from recursing
infinitely if the client code does.
While this first pass does simplify branches in order to ignore dummy
method calls that are never evaluated at runtime, it operates on a copy
of the method (as we initially ignore more calls while the argument
value sets are smaller, ignoring fewer calls as they build up).
A separate second pass simplifies branches on the original method and
inlines singleton constants, paving the way for the UnusedArgTransformer
to actually remove the newly unused arguments.
This new approach has several benefits:
- It is much faster than the old approach, as we only re-analyse methods
as required by argument value changes, rather than re-analysing every
method during every pass.
- It doesn't require special cases for dealing with mutually recursive
dummy calls. The old approach hard-coded special cases for mutually
recursive calls involving groups of 1 and 2 methods. The code for this
wasn't clean. Furthermore, while it was just about good enough for the
HD client, the SD client contains a mutually recursive group of 3
methods. The new approach is capable of dealing with mutually
recursive groups of any size.
Finally, the new transformer has a much cleaner implementation.
Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
|
|
|
branchesSimplified = 0
|
|
|
|
constantsPropagated = 0
|
Replace DummyArgTransformer with new ConstantArgTransformer
The new transformer uses a different approach to the old one. It starts
exploring the call graph from the entry points, recursively analysing
method calls. Methods are only re-analysed if their possible argument
values change, with the Unknown value being used if we can't identify a
single integer constant at a call site. This prevents us from recursing
infinitely if the client code does.
While this first pass does simplify branches in order to ignore dummy
method calls that are never evaluated at runtime, it operates on a copy
of the method (as we initially ignore more calls while the argument
value sets are smaller, ignoring fewer calls as they build up).
A separate second pass simplifies branches on the original method and
inlines singleton constants, paving the way for the UnusedArgTransformer
to actually remove the newly unused arguments.
This new approach has several benefits:
- It is much faster than the old approach, as we only re-analyse methods
as required by argument value changes, rather than re-analysing every
method during every pass.
- It doesn't require special cases for dealing with mutually recursive
dummy calls. The old approach hard-coded special cases for mutually
recursive calls involving groups of 1 and 2 methods. The code for this
wasn't clean. Furthermore, while it was just about good enough for the
HD client, the SD client contains a mutually recursive group of 3
methods. The new approach is capable of dealing with mutually
recursive groups of any size.
Finally, the new transformer has a much cleaner implementation.
Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
|
|
|
|
|
|
|
queueEntryPoints(classPath)
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
val method = pendingMethods.removeFirstOrNull() ?: break
|
|
|
|
analyzeMethod(classPath, method)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun queueEntryPoints(classPath: ClassPath) {
|
|
|
|
for (partition in inheritedMethodSets) {
|
|
|
|
if (isEntryPoint(classPath, partition)) {
|
Replace DummyArgTransformer with new ConstantArgTransformer
The new transformer uses a different approach to the old one. It starts
exploring the call graph from the entry points, recursively analysing
method calls. Methods are only re-analysed if their possible argument
values change, with the Unknown value being used if we can't identify a
single integer constant at a call site. This prevents us from recursing
infinitely if the client code does.
While this first pass does simplify branches in order to ignore dummy
method calls that are never evaluated at runtime, it operates on a copy
of the method (as we initially ignore more calls while the argument
value sets are smaller, ignoring fewer calls as they build up).
A separate second pass simplifies branches on the original method and
inlines singleton constants, paving the way for the UnusedArgTransformer
to actually remove the newly unused arguments.
This new approach has several benefits:
- It is much faster than the old approach, as we only re-analyse methods
as required by argument value changes, rather than re-analysing every
method during every pass.
- It doesn't require special cases for dealing with mutually recursive
dummy calls. The old approach hard-coded special cases for mutually
recursive calls involving groups of 1 and 2 methods. The code for this
wasn't clean. Furthermore, while it was just about good enough for the
HD client, the SD client contains a mutually recursive group of 3
methods. The new approach is capable of dealing with mutually
recursive groups of any size.
Finally, the new transformer has a much cleaner implementation.
Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
|
|
|
pendingMethods.addAll(partition)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun isEntryPoint(classPath: ClassPath, partition: DisjointSet.Partition<MemberRef>): Boolean {
|
|
|
|
for (method in partition) {
|
|
|
|
val clazz = classPath[method.owner]!!
|
|
|
|
|
|
|
|
if (entryPoints.matches(method) || clazz.dependency) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
Replace DummyArgTransformer with new ConstantArgTransformer
The new transformer uses a different approach to the old one. It starts
exploring the call graph from the entry points, recursively analysing
method calls. Methods are only re-analysed if their possible argument
values change, with the Unknown value being used if we can't identify a
single integer constant at a call site. This prevents us from recursing
infinitely if the client code does.
While this first pass does simplify branches in order to ignore dummy
method calls that are never evaluated at runtime, it operates on a copy
of the method (as we initially ignore more calls while the argument
value sets are smaller, ignoring fewer calls as they build up).
A separate second pass simplifies branches on the original method and
inlines singleton constants, paving the way for the UnusedArgTransformer
to actually remove the newly unused arguments.
This new approach has several benefits:
- It is much faster than the old approach, as we only re-analyse methods
as required by argument value changes, rather than re-analysing every
method during every pass.
- It doesn't require special cases for dealing with mutually recursive
dummy calls. The old approach hard-coded special cases for mutually
recursive calls involving groups of 1 and 2 methods. The code for this
wasn't clean. Furthermore, while it was just about good enough for the
HD client, the SD client contains a mutually recursive group of 3
methods. The new approach is capable of dealing with mutually
recursive groups of any size.
Finally, the new transformer has a much cleaner implementation.
Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
|
|
|
private fun analyzeMethod(classPath: ClassPath, ref: MemberRef) {
|
|
|
|
// find ClassNode/MethodNode
|
|
|
|
val owner = classPath.getClassNode(ref.owner) ?: return
|
Replace DummyArgTransformer with new ConstantArgTransformer
The new transformer uses a different approach to the old one. It starts
exploring the call graph from the entry points, recursively analysing
method calls. Methods are only re-analysed if their possible argument
values change, with the Unknown value being used if we can't identify a
single integer constant at a call site. This prevents us from recursing
infinitely if the client code does.
While this first pass does simplify branches in order to ignore dummy
method calls that are never evaluated at runtime, it operates on a copy
of the method (as we initially ignore more calls while the argument
value sets are smaller, ignoring fewer calls as they build up).
A separate second pass simplifies branches on the original method and
inlines singleton constants, paving the way for the UnusedArgTransformer
to actually remove the newly unused arguments.
This new approach has several benefits:
- It is much faster than the old approach, as we only re-analyse methods
as required by argument value changes, rather than re-analysing every
method during every pass.
- It doesn't require special cases for dealing with mutually recursive
dummy calls. The old approach hard-coded special cases for mutually
recursive calls involving groups of 1 and 2 methods. The code for this
wasn't clean. Furthermore, while it was just about good enough for the
HD client, the SD client contains a mutually recursive group of 3
methods. The new approach is capable of dealing with mutually
recursive groups of any size.
Finally, the new transformer has a much cleaner implementation.
Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
|
|
|
val originalMethod = owner.methods.singleOrNull { it.name == ref.name && it.desc == ref.desc } ?: return
|
|
|
|
if (!originalMethod.hasCode) {
|
Replace DummyArgTransformer with new ConstantArgTransformer
The new transformer uses a different approach to the old one. It starts
exploring the call graph from the entry points, recursively analysing
method calls. Methods are only re-analysed if their possible argument
values change, with the Unknown value being used if we can't identify a
single integer constant at a call site. This prevents us from recursing
infinitely if the client code does.
While this first pass does simplify branches in order to ignore dummy
method calls that are never evaluated at runtime, it operates on a copy
of the method (as we initially ignore more calls while the argument
value sets are smaller, ignoring fewer calls as they build up).
A separate second pass simplifies branches on the original method and
inlines singleton constants, paving the way for the UnusedArgTransformer
to actually remove the newly unused arguments.
This new approach has several benefits:
- It is much faster than the old approach, as we only re-analyse methods
as required by argument value changes, rather than re-analysing every
method during every pass.
- It doesn't require special cases for dealing with mutually recursive
dummy calls. The old approach hard-coded special cases for mutually
recursive calls involving groups of 1 and 2 methods. The code for this
wasn't clean. Furthermore, while it was just about good enough for the
HD client, the SD client contains a mutually recursive group of 3
methods. The new approach is capable of dealing with mutually
recursive groups of any size.
Finally, the new transformer has a much cleaner implementation.
Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Clone the method - we don't want to mutate it permanently until the
|
|
|
|
* final pass, as we might discover more routes through the call graph
|
|
|
|
* later which reduce the number of branches we can simplify.
|
|
|
|
*/
|
|
|
|
val method = originalMethod.copy()
|
Replace DummyArgTransformer with new ConstantArgTransformer
The new transformer uses a different approach to the old one. It starts
exploring the call graph from the entry points, recursively analysing
method calls. Methods are only re-analysed if their possible argument
values change, with the Unknown value being used if we can't identify a
single integer constant at a call site. This prevents us from recursing
infinitely if the client code does.
While this first pass does simplify branches in order to ignore dummy
method calls that are never evaluated at runtime, it operates on a copy
of the method (as we initially ignore more calls while the argument
value sets are smaller, ignoring fewer calls as they build up).
A separate second pass simplifies branches on the original method and
inlines singleton constants, paving the way for the UnusedArgTransformer
to actually remove the newly unused arguments.
This new approach has several benefits:
- It is much faster than the old approach, as we only re-analyse methods
as required by argument value changes, rather than re-analysing every
method during every pass.
- It doesn't require special cases for dealing with mutually recursive
dummy calls. The old approach hard-coded special cases for mutually
recursive calls involving groups of 1 and 2 methods. The code for this
wasn't clean. Furthermore, while it was just about good enough for the
HD client, the SD client contains a mutually recursive group of 3
methods. The new approach is capable of dealing with mutually
recursive groups of any size.
Finally, the new transformer has a much cleaner implementation.
Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
|
|
|
|
|
|
|
// find existing constant arguments
|
|
|
|
val args = getArgs(ref)
|
|
|
|
|
|
|
|
// simplify branches
|
|
|
|
simplifyBranches(owner, method, args)
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Record new constant arguments in method calls. This re-runs the
|
|
|
|
* analyzer rather than re-using the frames from simplifyBranches. This
|
|
|
|
* ensures we ignore branches that always evaluate to false, preventing
|
|
|
|
* us from recording constant arguments found in dummy calls (which
|
|
|
|
* would prevent us from removing further dummy calls/branches).
|
|
|
|
*/
|
|
|
|
addArgValues(owner, method, args)
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun getArgs(ref: MemberRef): Array<IntValueSet> {
|
|
|
|
val partition = inheritedMethodSets[ref]!!
|
|
|
|
val size = Type.getArgumentTypes(ref.desc).sumOf { it.size }
|
|
|
|
return Array(size) { i -> argValues[ArgPartition(partition, i)] ?: IntValueSet.Unknown }
|
Replace DummyArgTransformer with new ConstantArgTransformer
The new transformer uses a different approach to the old one. It starts
exploring the call graph from the entry points, recursively analysing
method calls. Methods are only re-analysed if their possible argument
values change, with the Unknown value being used if we can't identify a
single integer constant at a call site. This prevents us from recursing
infinitely if the client code does.
While this first pass does simplify branches in order to ignore dummy
method calls that are never evaluated at runtime, it operates on a copy
of the method (as we initially ignore more calls while the argument
value sets are smaller, ignoring fewer calls as they build up).
A separate second pass simplifies branches on the original method and
inlines singleton constants, paving the way for the UnusedArgTransformer
to actually remove the newly unused arguments.
This new approach has several benefits:
- It is much faster than the old approach, as we only re-analyse methods
as required by argument value changes, rather than re-analysing every
method during every pass.
- It doesn't require special cases for dealing with mutually recursive
dummy calls. The old approach hard-coded special cases for mutually
recursive calls involving groups of 1 and 2 methods. The code for this
wasn't clean. Furthermore, while it was just about good enough for the
HD client, the SD client contains a mutually recursive group of 3
methods. The new approach is capable of dealing with mutually
recursive groups of any size.
Finally, the new transformer has a much cleaner implementation.
Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
|
|
|
}
|
|
|
|
|
|
|
|
private fun addArgValues(owner: ClassNode, method: MethodNode, args: Array<IntValueSet>) {
|
|
|
|
val analyzer = Analyzer(IntInterpreter(args))
|
|
|
|
val frames = analyzer.analyze(owner.name, method)
|
|
|
|
for ((i, frame) in frames.withIndex()) {
|
|
|
|
if (frame == null) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
val insn = method.instructions[i]
|
|
|
|
if (insn !is MethodInsnNode) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
val invokedMethod = inheritedMethodSets[MemberRef(insn)] ?: continue
|
|
|
|
val size = Type.getArgumentTypes(insn.desc).size
|
|
|
|
|
|
|
|
var index = 0
|
|
|
|
for (j in 0 until size) {
|
|
|
|
val value = frame.getStack(frame.stackSize - size + j)
|
|
|
|
if (addArgValues(ArgPartition(invokedMethod, index), value.set)) {
|
Replace DummyArgTransformer with new ConstantArgTransformer
The new transformer uses a different approach to the old one. It starts
exploring the call graph from the entry points, recursively analysing
method calls. Methods are only re-analysed if their possible argument
values change, with the Unknown value being used if we can't identify a
single integer constant at a call site. This prevents us from recursing
infinitely if the client code does.
While this first pass does simplify branches in order to ignore dummy
method calls that are never evaluated at runtime, it operates on a copy
of the method (as we initially ignore more calls while the argument
value sets are smaller, ignoring fewer calls as they build up).
A separate second pass simplifies branches on the original method and
inlines singleton constants, paving the way for the UnusedArgTransformer
to actually remove the newly unused arguments.
This new approach has several benefits:
- It is much faster than the old approach, as we only re-analyse methods
as required by argument value changes, rather than re-analysing every
method during every pass.
- It doesn't require special cases for dealing with mutually recursive
dummy calls. The old approach hard-coded special cases for mutually
recursive calls involving groups of 1 and 2 methods. The code for this
wasn't clean. Furthermore, while it was just about good enough for the
HD client, the SD client contains a mutually recursive group of 3
methods. The new approach is capable of dealing with mutually
recursive groups of any size.
Finally, the new transformer has a much cleaner implementation.
Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
|
|
|
pendingMethods.addAll(invokedMethod)
|
|
|
|
}
|
|
|
|
index += value.size
|
|
|
|
}
|
|
|
|
|
|
|
|
if (size == 0 && arglessMethods.add(invokedMethod)) {
|
|
|
|
pendingMethods.addAll(invokedMethod)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun addArgValues(partition: ArgPartition, value: IntValueSet): Boolean {
|
|
|
|
val old = argValues[partition]
|
Replace DummyArgTransformer with new ConstantArgTransformer
The new transformer uses a different approach to the old one. It starts
exploring the call graph from the entry points, recursively analysing
method calls. Methods are only re-analysed if their possible argument
values change, with the Unknown value being used if we can't identify a
single integer constant at a call site. This prevents us from recursing
infinitely if the client code does.
While this first pass does simplify branches in order to ignore dummy
method calls that are never evaluated at runtime, it operates on a copy
of the method (as we initially ignore more calls while the argument
value sets are smaller, ignoring fewer calls as they build up).
A separate second pass simplifies branches on the original method and
inlines singleton constants, paving the way for the UnusedArgTransformer
to actually remove the newly unused arguments.
This new approach has several benefits:
- It is much faster than the old approach, as we only re-analyse methods
as required by argument value changes, rather than re-analysing every
method during every pass.
- It doesn't require special cases for dealing with mutually recursive
dummy calls. The old approach hard-coded special cases for mutually
recursive calls involving groups of 1 and 2 methods. The code for this
wasn't clean. Furthermore, while it was just about good enough for the
HD client, the SD client contains a mutually recursive group of 3
methods. The new approach is capable of dealing with mutually
recursive groups of any size.
Finally, the new transformer has a much cleaner implementation.
Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
|
|
|
|
|
|
|
val new = if (value.singleton != null) {
|
|
|
|
if (old != null) {
|
|
|
|
old union value
|
|
|
|
} else {
|
|
|
|
value
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
IntValueSet.Unknown
|
|
|
|
}
|
|
|
|
argValues[partition] = new
|
Replace DummyArgTransformer with new ConstantArgTransformer
The new transformer uses a different approach to the old one. It starts
exploring the call graph from the entry points, recursively analysing
method calls. Methods are only re-analysed if their possible argument
values change, with the Unknown value being used if we can't identify a
single integer constant at a call site. This prevents us from recursing
infinitely if the client code does.
While this first pass does simplify branches in order to ignore dummy
method calls that are never evaluated at runtime, it operates on a copy
of the method (as we initially ignore more calls while the argument
value sets are smaller, ignoring fewer calls as they build up).
A separate second pass simplifies branches on the original method and
inlines singleton constants, paving the way for the UnusedArgTransformer
to actually remove the newly unused arguments.
This new approach has several benefits:
- It is much faster than the old approach, as we only re-analyse methods
as required by argument value changes, rather than re-analysing every
method during every pass.
- It doesn't require special cases for dealing with mutually recursive
dummy calls. The old approach hard-coded special cases for mutually
recursive calls involving groups of 1 and 2 methods. The code for this
wasn't clean. Furthermore, while it was just about good enough for the
HD client, the SD client contains a mutually recursive group of 3
methods. The new approach is capable of dealing with mutually
recursive groups of any size.
Finally, the new transformer has a much cleaner implementation.
Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
|
|
|
|
|
|
|
return old != new
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun simplifyBranches(owner: ClassNode, method: MethodNode, args: Array<IntValueSet>): Int {
|
|
|
|
val analyzer = Analyzer(IntInterpreter(args))
|
|
|
|
val frames = analyzer.analyze(owner.name, method)
|
|
|
|
|
|
|
|
val alwaysTakenBranches = mutableListOf<JumpInsnNode>()
|
|
|
|
val neverTakenBranches = mutableListOf<JumpInsnNode>()
|
|
|
|
|
|
|
|
frame@ for ((i, frame) in frames.withIndex()) {
|
|
|
|
if (frame == null) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
val insn = method.instructions[i]
|
|
|
|
if (insn !is JumpInsnNode) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
when (insn.opcode) {
|
|
|
|
IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE -> {
|
|
|
|
val value = frame.getStack(frame.stackSize - 1)
|
|
|
|
if (value.set !is IntValueSet.Constant) {
|
|
|
|
continue@frame
|
|
|
|
}
|
|
|
|
|
|
|
|
@Suppress("NON_EXHAUSTIVE_WHEN")
|
|
|
|
when (IntBranch.evaluateUnary(insn.opcode, value.set.values)) {
|
|
|
|
ALWAYS_TAKEN -> alwaysTakenBranches += insn
|
|
|
|
NEVER_TAKEN -> neverTakenBranches += insn
|
|
|
|
}
|
|
|
|
}
|
|
|
|
IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE -> {
|
|
|
|
val value1 = frame.getStack(frame.stackSize - 2)
|
|
|
|
val value2 = frame.getStack(frame.stackSize - 1)
|
|
|
|
if (value1.set !is IntValueSet.Constant || value2.set !is IntValueSet.Constant) {
|
|
|
|
continue@frame
|
|
|
|
}
|
|
|
|
|
|
|
|
@Suppress("NON_EXHAUSTIVE_WHEN")
|
|
|
|
when (IntBranch.evaluateBinary(insn.opcode, value1.set.values, value2.set.values)) {
|
|
|
|
ALWAYS_TAKEN -> alwaysTakenBranches += insn
|
|
|
|
NEVER_TAKEN -> neverTakenBranches += insn
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var simplified = 0
|
|
|
|
|
|
|
|
for (insn in alwaysTakenBranches) {
|
|
|
|
val replacement = JumpInsnNode(GOTO, insn.label)
|
|
|
|
if (method.instructions.replaceExpression(insn, replacement, AbstractInsnNode::isPure)) {
|
Replace DummyArgTransformer with new ConstantArgTransformer
The new transformer uses a different approach to the old one. It starts
exploring the call graph from the entry points, recursively analysing
method calls. Methods are only re-analysed if their possible argument
values change, with the Unknown value being used if we can't identify a
single integer constant at a call site. This prevents us from recursing
infinitely if the client code does.
While this first pass does simplify branches in order to ignore dummy
method calls that are never evaluated at runtime, it operates on a copy
of the method (as we initially ignore more calls while the argument
value sets are smaller, ignoring fewer calls as they build up).
A separate second pass simplifies branches on the original method and
inlines singleton constants, paving the way for the UnusedArgTransformer
to actually remove the newly unused arguments.
This new approach has several benefits:
- It is much faster than the old approach, as we only re-analyse methods
as required by argument value changes, rather than re-analysing every
method during every pass.
- It doesn't require special cases for dealing with mutually recursive
dummy calls. The old approach hard-coded special cases for mutually
recursive calls involving groups of 1 and 2 methods. The code for this
wasn't clean. Furthermore, while it was just about good enough for the
HD client, the SD client contains a mutually recursive group of 3
methods. The new approach is capable of dealing with mutually
recursive groups of any size.
Finally, the new transformer has a much cleaner implementation.
Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
|
|
|
simplified++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (insn in neverTakenBranches) {
|
|
|
|
if (method.instructions.deleteExpression(insn, AbstractInsnNode::isPure)) {
|
Replace DummyArgTransformer with new ConstantArgTransformer
The new transformer uses a different approach to the old one. It starts
exploring the call graph from the entry points, recursively analysing
method calls. Methods are only re-analysed if their possible argument
values change, with the Unknown value being used if we can't identify a
single integer constant at a call site. This prevents us from recursing
infinitely if the client code does.
While this first pass does simplify branches in order to ignore dummy
method calls that are never evaluated at runtime, it operates on a copy
of the method (as we initially ignore more calls while the argument
value sets are smaller, ignoring fewer calls as they build up).
A separate second pass simplifies branches on the original method and
inlines singleton constants, paving the way for the UnusedArgTransformer
to actually remove the newly unused arguments.
This new approach has several benefits:
- It is much faster than the old approach, as we only re-analyse methods
as required by argument value changes, rather than re-analysing every
method during every pass.
- It doesn't require special cases for dealing with mutually recursive
dummy calls. The old approach hard-coded special cases for mutually
recursive calls involving groups of 1 and 2 methods. The code for this
wasn't clean. Furthermore, while it was just about good enough for the
HD client, the SD client contains a mutually recursive group of 3
methods. The new approach is capable of dealing with mutually
recursive groups of any size.
Finally, the new transformer has a much cleaner implementation.
Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
|
|
|
simplified++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return simplified
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun propagateConstantArgs(
|
|
|
|
classPath: ClassPath,
|
|
|
|
clazz: ClassNode,
|
|
|
|
method: MethodNode,
|
|
|
|
args: Array<IntValueSet>
|
|
|
|
): Int {
|
|
|
|
val partition = inheritedMethodSets[MemberRef(clazz, method)]!!
|
|
|
|
if (!MethodMappingGenerator.isRenamable(classPath, profile.excludedMethods, partition)) {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
Replace DummyArgTransformer with new ConstantArgTransformer
The new transformer uses a different approach to the old one. It starts
exploring the call graph from the entry points, recursively analysing
method calls. Methods are only re-analysed if their possible argument
values change, with the Unknown value being used if we can't identify a
single integer constant at a call site. This prevents us from recursing
infinitely if the client code does.
While this first pass does simplify branches in order to ignore dummy
method calls that are never evaluated at runtime, it operates on a copy
of the method (as we initially ignore more calls while the argument
value sets are smaller, ignoring fewer calls as they build up).
A separate second pass simplifies branches on the original method and
inlines singleton constants, paving the way for the UnusedArgTransformer
to actually remove the newly unused arguments.
This new approach has several benefits:
- It is much faster than the old approach, as we only re-analyse methods
as required by argument value changes, rather than re-analysing every
method during every pass.
- It doesn't require special cases for dealing with mutually recursive
dummy calls. The old approach hard-coded special cases for mutually
recursive calls involving groups of 1 and 2 methods. The code for this
wasn't clean. Furthermore, while it was just about good enough for the
HD client, the SD client contains a mutually recursive group of 3
methods. The new approach is capable of dealing with mutually
recursive groups of any size.
Finally, the new transformer has a much cleaner implementation.
Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
|
|
|
val analyzer = Analyzer(IntInterpreter(args))
|
|
|
|
val frames = analyzer.analyze(clazz.name, method)
|
|
|
|
|
|
|
|
val constInsns = mutableMapOf<AbstractInsnNode, Int>()
|
|
|
|
|
|
|
|
for ((i, frame) in frames.withIndex()) {
|
|
|
|
if (frame == null) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
val insn = method.instructions[i]
|
|
|
|
if (insn.intConstant != null) {
|
|
|
|
// already constant
|
|
|
|
continue
|
|
|
|
} else if (!insn.isPure) {
|
Replace DummyArgTransformer with new ConstantArgTransformer
The new transformer uses a different approach to the old one. It starts
exploring the call graph from the entry points, recursively analysing
method calls. Methods are only re-analysed if their possible argument
values change, with the Unknown value being used if we can't identify a
single integer constant at a call site. This prevents us from recursing
infinitely if the client code does.
While this first pass does simplify branches in order to ignore dummy
method calls that are never evaluated at runtime, it operates on a copy
of the method (as we initially ignore more calls while the argument
value sets are smaller, ignoring fewer calls as they build up).
A separate second pass simplifies branches on the original method and
inlines singleton constants, paving the way for the UnusedArgTransformer
to actually remove the newly unused arguments.
This new approach has several benefits:
- It is much faster than the old approach, as we only re-analyse methods
as required by argument value changes, rather than re-analysing every
method during every pass.
- It doesn't require special cases for dealing with mutually recursive
dummy calls. The old approach hard-coded special cases for mutually
recursive calls involving groups of 1 and 2 methods. The code for this
wasn't clean. Furthermore, while it was just about good enough for the
HD client, the SD client contains a mutually recursive group of 3
methods. The new approach is capable of dealing with mutually
recursive groups of any size.
Finally, the new transformer has a much cleaner implementation.
Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
|
|
|
// can't replace instructions with a side effect
|
|
|
|
continue
|
|
|
|
} else if (insn.stackMetadata.pushes != 1) {
|
Replace DummyArgTransformer with new ConstantArgTransformer
The new transformer uses a different approach to the old one. It starts
exploring the call graph from the entry points, recursively analysing
method calls. Methods are only re-analysed if their possible argument
values change, with the Unknown value being used if we can't identify a
single integer constant at a call site. This prevents us from recursing
infinitely if the client code does.
While this first pass does simplify branches in order to ignore dummy
method calls that are never evaluated at runtime, it operates on a copy
of the method (as we initially ignore more calls while the argument
value sets are smaller, ignoring fewer calls as they build up).
A separate second pass simplifies branches on the original method and
inlines singleton constants, paving the way for the UnusedArgTransformer
to actually remove the newly unused arguments.
This new approach has several benefits:
- It is much faster than the old approach, as we only re-analyse methods
as required by argument value changes, rather than re-analysing every
method during every pass.
- It doesn't require special cases for dealing with mutually recursive
dummy calls. The old approach hard-coded special cases for mutually
recursive calls involving groups of 1 and 2 methods. The code for this
wasn't clean. Furthermore, while it was just about good enough for the
HD client, the SD client contains a mutually recursive group of 3
methods. The new approach is capable of dealing with mutually
recursive groups of any size.
Finally, the new transformer has a much cleaner implementation.
Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
|
|
|
// can't replace instructions pushing more than one result
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// the value pushed by this instruction is held in the following frame
|
|
|
|
val nextInsn = insn.nextReal ?: continue
|
|
|
|
val nextInsnIndex = method.instructions.indexOf(nextInsn)
|
|
|
|
val nextFrame = frames[nextInsnIndex]
|
|
|
|
|
|
|
|
val value = nextFrame.getStack(nextFrame.stackSize - 1)
|
|
|
|
val singleton = value.set.singleton
|
|
|
|
if (singleton != null) {
|
|
|
|
constInsns[insn] = singleton
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var inlined = 0
|
|
|
|
|
|
|
|
for ((insn, value) in constInsns) {
|
|
|
|
if (insn !in method.instructions) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
val replacement = value.toAbstractInsnNode()
|
|
|
|
if (method.instructions.replaceExpression(insn, replacement, AbstractInsnNode::isPure)) {
|
Replace DummyArgTransformer with new ConstantArgTransformer
The new transformer uses a different approach to the old one. It starts
exploring the call graph from the entry points, recursively analysing
method calls. Methods are only re-analysed if their possible argument
values change, with the Unknown value being used if we can't identify a
single integer constant at a call site. This prevents us from recursing
infinitely if the client code does.
While this first pass does simplify branches in order to ignore dummy
method calls that are never evaluated at runtime, it operates on a copy
of the method (as we initially ignore more calls while the argument
value sets are smaller, ignoring fewer calls as they build up).
A separate second pass simplifies branches on the original method and
inlines singleton constants, paving the way for the UnusedArgTransformer
to actually remove the newly unused arguments.
This new approach has several benefits:
- It is much faster than the old approach, as we only re-analyse methods
as required by argument value changes, rather than re-analysing every
method during every pass.
- It doesn't require special cases for dealing with mutually recursive
dummy calls. The old approach hard-coded special cases for mutually
recursive calls involving groups of 1 and 2 methods. The code for this
wasn't clean. Furthermore, while it was just about good enough for the
HD client, the SD client contains a mutually recursive group of 3
methods. The new approach is capable of dealing with mutually
recursive groups of any size.
Finally, the new transformer has a much cleaner implementation.
Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
|
|
|
inlined++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return inlined
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun transformCode(classPath: ClassPath, library: Library, clazz: ClassNode, method: MethodNode): Boolean {
|
|
|
|
val args = getArgs(MemberRef(clazz, method))
|
|
|
|
branchesSimplified += simplifyBranches(clazz, method, args)
|
|
|
|
constantsPropagated += propagateConstantArgs(classPath, clazz, method, args)
|
Replace DummyArgTransformer with new ConstantArgTransformer
The new transformer uses a different approach to the old one. It starts
exploring the call graph from the entry points, recursively analysing
method calls. Methods are only re-analysed if their possible argument
values change, with the Unknown value being used if we can't identify a
single integer constant at a call site. This prevents us from recursing
infinitely if the client code does.
While this first pass does simplify branches in order to ignore dummy
method calls that are never evaluated at runtime, it operates on a copy
of the method (as we initially ignore more calls while the argument
value sets are smaller, ignoring fewer calls as they build up).
A separate second pass simplifies branches on the original method and
inlines singleton constants, paving the way for the UnusedArgTransformer
to actually remove the newly unused arguments.
This new approach has several benefits:
- It is much faster than the old approach, as we only re-analyse methods
as required by argument value changes, rather than re-analysing every
method during every pass.
- It doesn't require special cases for dealing with mutually recursive
dummy calls. The old approach hard-coded special cases for mutually
recursive calls involving groups of 1 and 2 methods. The code for this
wasn't clean. Furthermore, while it was just about good enough for the
HD client, the SD client contains a mutually recursive group of 3
methods. The new approach is capable of dealing with mutually
recursive groups of any size.
Finally, the new transformer has a much cleaner implementation.
Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun postTransform(classPath: ClassPath) {
|
|
|
|
logger.info { "Simplified $branchesSimplified branches and propagated $constantsPropagated constants" }
|
Replace DummyArgTransformer with new ConstantArgTransformer
The new transformer uses a different approach to the old one. It starts
exploring the call graph from the entry points, recursively analysing
method calls. Methods are only re-analysed if their possible argument
values change, with the Unknown value being used if we can't identify a
single integer constant at a call site. This prevents us from recursing
infinitely if the client code does.
While this first pass does simplify branches in order to ignore dummy
method calls that are never evaluated at runtime, it operates on a copy
of the method (as we initially ignore more calls while the argument
value sets are smaller, ignoring fewer calls as they build up).
A separate second pass simplifies branches on the original method and
inlines singleton constants, paving the way for the UnusedArgTransformer
to actually remove the newly unused arguments.
This new approach has several benefits:
- It is much faster than the old approach, as we only re-analyse methods
as required by argument value changes, rather than re-analysing every
method during every pass.
- It doesn't require special cases for dealing with mutually recursive
dummy calls. The old approach hard-coded special cases for mutually
recursive calls involving groups of 1 and 2 methods. The code for this
wasn't clean. Furthermore, while it was just about good enough for the
HD client, the SD client contains a mutually recursive group of 3
methods. The new approach is capable of dealing with mutually
recursive groups of any size.
Finally, the new transformer has a much cleaner implementation.
Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
|
|
|
}
|
|
|
|
|
|
|
|
private companion object {
|
Replace DummyArgTransformer with new ConstantArgTransformer
The new transformer uses a different approach to the old one. It starts
exploring the call graph from the entry points, recursively analysing
method calls. Methods are only re-analysed if their possible argument
values change, with the Unknown value being used if we can't identify a
single integer constant at a call site. This prevents us from recursing
infinitely if the client code does.
While this first pass does simplify branches in order to ignore dummy
method calls that are never evaluated at runtime, it operates on a copy
of the method (as we initially ignore more calls while the argument
value sets are smaller, ignoring fewer calls as they build up).
A separate second pass simplifies branches on the original method and
inlines singleton constants, paving the way for the UnusedArgTransformer
to actually remove the newly unused arguments.
This new approach has several benefits:
- It is much faster than the old approach, as we only re-analyse methods
as required by argument value changes, rather than re-analysing every
method during every pass.
- It doesn't require special cases for dealing with mutually recursive
dummy calls. The old approach hard-coded special cases for mutually
recursive calls involving groups of 1 and 2 methods. The code for this
wasn't clean. Furthermore, while it was just about good enough for the
HD client, the SD client contains a mutually recursive group of 3
methods. The new approach is capable of dealing with mutually
recursive groups of any size.
Finally, the new transformer has a much cleaner implementation.
Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
|
|
|
private val logger = InlineLogger()
|
|
|
|
}
|
|
|
|
}
|