ASM 字节码方法内联期间的重新映射器变量
Remapper variables during bytecode method inlining by ASM
我正在使用 ASM 进行在线字节码方法内联优化。我的更改基于示例 3.2.6 Inline Method
(http://asm.ow2.org/current/asm-transformations.pdf)。测试示例(内联被调用者的 calculate(int,int) 在 Caller::test)是:
public class Caller {
final Callee _callee;
public Caller(Callee callee){
_callee = callee;
}
public static void main(String[] args) {
new Caller(new Callee("xu", "shijie")).test(5, 100);
}
public void test(int a, int b){
int t = a;
int p = b;
int r = t+p-_callee.calculate(a, b);
int m = t-p;
System.out.println(t);
}
}
public class Callee {
final String _a;
final String _b;
public Callee(String a, String b){
_a = a;
_b = b;
}
public int calculate(int t, int p){
int tmp = _a.length()+_b.length();
tmp+=t+p;
return tmp;
}
}
基于ASM 5.0版本,我的代码是:
//MainInliner.java
public class MainInliner extends ClassLoader{
public byte[] generator(String caller, String callee) throws ClassNotFoundException{
String resource = callee.replace('.', '/') + ".class";
InputStream is = getResourceAsStream(resource);
byte[] buffer;
// adapts the class on the fly
try {
resource = caller.replace('.', '/')+".class";
is = getResourceAsStream(resource);
ClassReader cr = new ClassReader(is);
ClassWriter cw = new ClassWriter(0);
ClassVisitor visitor = new BCMerge(Opcodes.ASM5, cw, callee);
cr.accept(visitor, 0);
buffer= cw.toByteArray();
} catch (Exception e) {
throw new ClassNotFoundException(caller, e);
}
// optional: stores the adapted class on disk
try {
FileOutputStream fos = new FileOutputStream("/tmp/data.class");
fos.write(buffer);
fos.close();
} catch (IOException e) {}
return buffer;
}
@Override
protected synchronized Class<?> loadClass(final String name,
final boolean resolve) throws ClassNotFoundException {
if (name.startsWith("java.")) {
System.err.println("Adapt: loading class '" + name
+ "' without on the fly adaptation");
return super.loadClass(name, resolve);
} else {
System.err.println("Adapt: loading class '" + name
+ "' with on the fly adaptation");
}
String caller = "code.sxu.asm.example.Caller";
String callee = "code.sxu.asm.example.Callee";
byte[] b = generator(caller, callee);
// returns the adapted class
return defineClass(caller, b, 0, b.length);
}
public static void main(final String args[]) throws Exception {
// loads the application class (in args[0]) with an Adapt class loader
ClassLoader loader = new MainInliner();
Class<?> c = loader.loadClass(args[0]);
Method m = c.getMethod("main", new Class<?>[] { String[].class });
}
}
class BCMerge extends ClassVisitor{
String _callee;
String _caller;
public BCMerge(int api, ClassVisitor cv, String callee) {
super(api, cv);
// TODO Auto-generated constructor stub
_callee = callee.replace('.', '/');
}
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
this._caller = name;
}
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
if(!name.equals("test")){
return super.visitMethod(access, name, desc, signature, exceptions);
}
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
ClassReader cr = null;
try {
cr = new ClassReader(this.getClass().getClassLoader().getResourceAsStream(_callee.replace('.', '/')+".class"));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
ClassNode classNode = new ClassNode();
cr.accept(classNode, 0);
MethodNode inlinedMethod = null;
for(MethodNode node: classNode.methods){
if(node.name.equals("calculate")){
inlinedMethod = node;
break;
}
}
return new MethodCallInliner(access, desc, mv, inlinedMethod, _callee, _caller );
}
}
//MethodCallInliner.java
public class MethodCallInliner extends LocalVariablesSorter {
private final String oldClass;
private final String newClass;
private final MethodNode mn; //Method Visitor wrappers the mv.
private List blocks = new ArrayList();
private boolean inlining;
public MethodCallInliner(int access, String desc, MethodVisitor mv, MethodNode mn,
String oldClass, String newClass){
super(Opcodes.ASM5, access, desc, mv);
this.oldClass = oldClass;
this.newClass = newClass;
this.mn = mn;
inlining = false;
}
public void visitMethodInsn(int opcode, String owner, String name,
String desc, boolean itf) {
System.out.println("opcode:" + opcode + " owner:" + owner + " name:"
+ name + " desc:" + desc);
if (!canBeInlined(owner, name, desc)) {
mv.visitMethodInsn(opcode, owner, name, desc, itf);
return;
}
//if it is INVOKEVIRTUAL ../Callee::calculate(II), then..
Remapper remapper = new SimpleRemapper(oldClass, newClass);
Label end = new Label();
inlining = true;
mn.instructions.resetLabels();
mn.accept(new InliningAdapter(this,opcode == Opcodes.INVOKESTATIC ? Opcodes.ACC_STATIC : 0, desc,remapper, end));
inlining = false;
super.visitLabel(end);
}
private boolean canBeInlined(String owner, String name, String decs){
if(name.equals("calculate") && owner.equals("code/sxu/asm/example/Callee")){
return true;
}
return false;
}
}
//InliningAdapter.java
public class InliningAdapter extends RemappingMethodAdapter {
private final LocalVariablesSorter lvs;
private final Label end;
public InliningAdapter(LocalVariablesSorter mv,
int acc, String desc,Remapper remapper, Label end) {
super(acc, desc, mv, remapper);
this.lvs = mv;
this.end = end;
// int offset = (acc & Opcodes.ACC_STATIC)!=0 ?0 : 1;
// Type[] args = Type.getArgumentTypes(desc);
// for (int i = args.length-1; i >= 0; i--) {
// super.visitVarInsn(args[i].getOpcode(
// Opcodes.ISTORE), i + offset);
// }
// if(offset>0) {
// super.visitVarInsn(Opcodes.ASTORE, 0);
// }
}
public void visitInsn(int opcode) {
if(opcode==Opcodes.RETURN || opcode == Opcodes.IRETURN) {
super.visitJumpInsn(Opcodes.GOTO, end);
} else {
super.visitInsn(opcode);
}
}
public void visitMaxs(int stack, int locals) {
System.out.println("visit maxs: "+stack+" "+locals);
}
protected int newLocalMapping(Type type) {
return lvs.newLocal(type);
}
}
在代码中,InliningAdapter
和MethodCallInliner
都扩展了LocalVariablesSorter
,它重新编号了局部变量。并且在 Caller::test::invokevirtual(Callee::calculate) 调用站点内联引用了 Callee::calculate() 的处理主体。
Caller::test()、Callee::calculate 和 generated::test 的字节码是:
//Caller::test()
public void test(int, int);
flags: ACC_PUBLIC
Code:
stack=4, locals=7, args_size=3
0: iload_1
1: istore_3
2: iload_2
3: istore 4
5: iload_3
6: iload 4
8: iadd
9: aload_0
10: getfield #13 // Field _callee:Lcode/sxu/asm/example/Callee;
13: iload_1
14: iload_2
15: invokevirtual #39 // Method code/sxu/asm/example/Callee.calculate:(II)I //Copy calculate's body here
18: isub
19: istore 5
21: iload_3
22: iload 4
24: isub
25: istore 6
27: getstatic #43 // Field java/lang/System.out:Ljava/io/PrintStream;
30: iload_3
31: invokevirtual #49 // Method java/io/PrintStream.println:(I)V
34: getstatic #43 // Field java/lang/System.out:Ljava/io/PrintStream;
37: ldc #55 // String 1..........
39: invokevirtual #57 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
42: return
//Callee::calculate()
public int calculate(int, int);
flags: ACC_PUBLIC
Code:
stack=3, locals=4, args_size=3
0: aload_0
1: getfield #14 // Field _a:Ljava/lang/String;
4: invokevirtual #26 // Method java/lang/String.length:()I
7: aload_0
8: getfield #16 // Field _b:Ljava/lang/String;
11: invokevirtual #26 // Method java/lang/String.length:()I
14: iadd
15: istore_3
16: iload_3
17: iload_1
18: iload_2
19: iadd
20: iadd
21: istore_3
22: iload_3
23: ireturn
//data.class
public void test(int, int);
flags: ACC_PUBLIC
Code:
stack=4, locals=8, args_size=3
0: iload_1
1: istore_3
2: iload_2
3: istore 4
5: iload_3
6: iload 4
8: iadd
9: aload_0
10: getfield #14 // Field _callee:Lcode/sxu/asm/example/Callee;
13: iload_1
14: iload_2
15: aload_0
16: getfield #40 // Field _a:Ljava/lang/String;
19: invokevirtual #46 // Method java/lang/String.length:()I
22: aload_0
23: getfield #49 // Field _b:Ljava/lang/String;
26: invokevirtual #46 // Method java/lang/String.length:()I
29: iadd
30: istore 6
32: iload 6
34: iload_1
35: iload_2
36: iadd
37: iadd
38: istore 6
40: iload 6
42: goto 45
45: isub
46: istore 6
48: iload_3
49: iload 4
51: isub
52: istore 7
54: getstatic #59 // Field java/lang/System.out:Ljava/io/PrintStream;
57: iload_3
58: invokevirtual #65 // Method java/io/PrintStream.println:(I)V
61: getstatic #59 // Field java/lang/System.out:Ljava/io/PrintStream;
64: ldc #67 // String 1..........
66: invokevirtual #70 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
69: return
data.class 上的 javap 结果显示 Callee::calculate 的主体已插入到正确的位置 (Caller::test line::15)。但是,有两个主要问题:
invokevirtual前的栈顶三个对象
Callee::calculate(第 15 行)
9: aload_0
10: getfield #14 // 字段_callee:Lcode/sxu/asm/example/Callee;
13: iload_1
14: iload_2
内联后不应在堆栈上。
复制体(Callee::calculate())中的变量号0应该映射到正确的号
变量号不正确。首先,data.class中Callee::calculate的复制体的变量号(从第15行到第42行)应该从5开始(而不是0)。其次,Callee::calculate()之后的变量编号要按规则重新编号:a)如果在(0,4]之间则不更改;b)如果与[的复制体中的编号冲突则重新编号=100=]()
我去检查了基础 class LocalVariablesSorter
的实现。问题似乎出在它的构造上:
protected LocalVariablesSorter(final int api, final int access,
final String desc, final MethodVisitor mv) {
super(api, mv);
Type[] args = Type.getArgumentTypes(desc);
nextLocal = (Opcodes.ACC_STATIC & access) == 0 ? 1 : 0;
for (int i = 0; i < args.length; i++) {
nextLocal += args[i].getSize();
}
firstLocal = nextLocal;
}
private int[] mapping = new int[40];
对我来说,似乎 firstLocal
总是从 1+ args.length() 开始(在本例中是 3)。这个class还提供了private int remap(final int var, final Type type)
,它创建新的局部变量并在映射数组中保留映射(从现有变量号到新索引)。
我的问题是如何正确使用LocalVariablesSorter
和内联字节码方法(Callee::calculate)。欢迎任何有关高效内联的建议。
对于内联之前(第15行之前)堆栈上的参数。我的想法是将它们存储为新创建的局部变量,这些变量将由 Callee::calculate 的复制主体引用。例如,添加:
astore 5
后
10: getfield #14 // 字段_callee:Lcode/sxu/asm/example/Callee;
并在 LocalVariablesSorter
中添加 mapping[0]=5+1
但主要问题是不允许用户更新LocalVariablesSorter::mapping
(从映射数组中的旧变量号到新变量),因为mapping
数组是私有的,是唯一的地方它的更新在方法中:
private int remap(final int var, final Type type) {
if (var + type.getSize() <= firstLocal) {
//Variable index will never be modified if it is less than firstLocal. 0 < 3. Nothing i can do for ALOAD 0.
return var;
}
int key = 2 * var + type.getSize() - 1;
int size = mapping.length;
if (key >= size) {
.....
}
int value = mapping[key];
if (value == 0) {
value = newLocalMapping(type);
setLocalType(value, type);
mapping[key] = value + 1;
} else {
value--;
}
if (value != var) {
changed = true;
}
return value;
}
Update1: InliningAdapter:
取消注释构造函数后的 data.class
0: iload_1
1: istore_3
2: iload_2
3: istore 4
5: iload_3
6: iload 4
8: iadd
9: aload_0
10: getfield #14 // Field _callee:Lcode/sxu/asm/example/Callee;
13: iload_1
14: iload_2
15: istore_2 //should be istore 5
16: istore_1 //should be istore 6
17: astore_0 //should be astore 7
18: aload_0
19: getfield #40 // Field _a:Ljava/lang/String;
22: invokevirtual #46 // Method java/lang/String.length:()I
25: aload_0
26: getfield #49 // Field _b:Ljava/lang/String;
29: invokevirtual #46 // Method java/lang/String.length:()I
32: iadd
33: istore 6
35: iload 6
37: iload_1
38: iload_2
39: iadd
40: iadd
41: istore 6
43: iload 6
45: goto 48
48: isub
49: istore 6
51: iload_3
52: iload 4
54: isub
55: istore 7
57: getstatic #59
新存储的三个变量(15,16,17)应该编号为5,6,7,而不是2,1,0,内联代码中*store/*load
中的映射应该是喜欢
0 => 7
1 => 6
2 => 5
3 => 8
4 => 9 ...
这些映射应该在数组中:LocalVariablesSorter::mapping
由方法 LocalVariablesSorter::remap()
更新。但是,我似乎无法将它们插入 mapping
数组。
应该进行两种重新映射:
- 在内联代码内部重新映射(从第 18 行到 45 行)和变量
索引从 5 开始。最大索引为 k
- 在内联代码后重新映射(从第46行到最后),如果原始索引大于5,则应重新映射任何变量索引(新索引从k+1开始)
正如@Holger 已经建议的那样,首先取消注释 InliningAdapter
.
中的行
解决您列出的主要问题:LocalVariablesSorter
(由InliningAdapter
扩展)认为参数已经存储在固定位置的局部变量中 - 这是确实是进入方法时的正常情况。所以它根本不映射那些(参见 LocalVariablesSorter.remap()
中的第一行 - firstLocal
是在构造函数中计算的)。然而,在这种情况下,我们取而代之的是在堆栈上获取参数,并且需要手动分配局部变量。解决方法是告诉LocalVariablesSorter
局部变量中没有已经存储的参数(使firstLocal = 0
)。然后它将对它们的任何引用都视为新变量并为它们分配新的局部变量。我们可以通过欺骗 LocalVariablesSorter
认为没有参数并且该方法是静态的(即使它真的不是)来实现这一点。所以我们将 InliningAdapter
中的第一行从
super(acc, desc, mv, remapper);
到
super(acc | Opcodes.ACC_STATIC, "()V", mv, remapper);
现在变量 0,1,2,... 重新映射到 5,6,7,... 或类似变量(它们是什么并不重要,[= 的 LocalVariablesSorter
23=](即 MethodCallInliner
实例)负责分配它们)。
还有另一个问题,您通过 InliningAdapter
扩展 RemappingMethodAdaptor
将 Callee
class 映射到 Caller
- 但是我猜您想将 _a
和 _b
变量保存在 Callee
实例中。
- 如果我的猜测是正确的,那么您实际上不应该将引用从
Callee
重新映射到 Caller
。您可以只让 InliningAdapter
扩展 LocalVariableSorter
并摆脱重新映射器。
- 如果我的猜测不正确,那么我猜你可能需要将
Callee
的变量也嵌入到 Caller
中,在这种情况下你应该保留你拥有的 RemappingMethodAdaptor
.
调试内联代码时,Callee
中的行号将没有意义,因为代码已内联到 Caller
class 中。因此,Callee
中的所有行号可能应该替换为 Caller
中发生内联调用的行的行号。不幸的是,在 Java 中,您不能逐行指定不同的源代码文件(例如,您可以在 C 中指定)。因此,您将使用类似这样的方法覆盖 InliningAdapter
中的 visitLineNumber()
(inlinedLine
将传递给 InliningAdapter
的构造函数):
@Override
public void visitLineNumber(int line, Label start) {
super.visitLineNumber(inlinedLine, start);
}
.. 或者完全跳过 super 调用,我对此不是 100% 确定。
我正在使用 ASM 进行在线字节码方法内联优化。我的更改基于示例 3.2.6 Inline Method
(http://asm.ow2.org/current/asm-transformations.pdf)。测试示例(内联被调用者的 calculate(int,int) 在 Caller::test)是:
public class Caller {
final Callee _callee;
public Caller(Callee callee){
_callee = callee;
}
public static void main(String[] args) {
new Caller(new Callee("xu", "shijie")).test(5, 100);
}
public void test(int a, int b){
int t = a;
int p = b;
int r = t+p-_callee.calculate(a, b);
int m = t-p;
System.out.println(t);
}
}
public class Callee {
final String _a;
final String _b;
public Callee(String a, String b){
_a = a;
_b = b;
}
public int calculate(int t, int p){
int tmp = _a.length()+_b.length();
tmp+=t+p;
return tmp;
}
}
基于ASM 5.0版本,我的代码是:
//MainInliner.java
public class MainInliner extends ClassLoader{
public byte[] generator(String caller, String callee) throws ClassNotFoundException{
String resource = callee.replace('.', '/') + ".class";
InputStream is = getResourceAsStream(resource);
byte[] buffer;
// adapts the class on the fly
try {
resource = caller.replace('.', '/')+".class";
is = getResourceAsStream(resource);
ClassReader cr = new ClassReader(is);
ClassWriter cw = new ClassWriter(0);
ClassVisitor visitor = new BCMerge(Opcodes.ASM5, cw, callee);
cr.accept(visitor, 0);
buffer= cw.toByteArray();
} catch (Exception e) {
throw new ClassNotFoundException(caller, e);
}
// optional: stores the adapted class on disk
try {
FileOutputStream fos = new FileOutputStream("/tmp/data.class");
fos.write(buffer);
fos.close();
} catch (IOException e) {}
return buffer;
}
@Override
protected synchronized Class<?> loadClass(final String name,
final boolean resolve) throws ClassNotFoundException {
if (name.startsWith("java.")) {
System.err.println("Adapt: loading class '" + name
+ "' without on the fly adaptation");
return super.loadClass(name, resolve);
} else {
System.err.println("Adapt: loading class '" + name
+ "' with on the fly adaptation");
}
String caller = "code.sxu.asm.example.Caller";
String callee = "code.sxu.asm.example.Callee";
byte[] b = generator(caller, callee);
// returns the adapted class
return defineClass(caller, b, 0, b.length);
}
public static void main(final String args[]) throws Exception {
// loads the application class (in args[0]) with an Adapt class loader
ClassLoader loader = new MainInliner();
Class<?> c = loader.loadClass(args[0]);
Method m = c.getMethod("main", new Class<?>[] { String[].class });
}
}
class BCMerge extends ClassVisitor{
String _callee;
String _caller;
public BCMerge(int api, ClassVisitor cv, String callee) {
super(api, cv);
// TODO Auto-generated constructor stub
_callee = callee.replace('.', '/');
}
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
this._caller = name;
}
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
if(!name.equals("test")){
return super.visitMethod(access, name, desc, signature, exceptions);
}
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
ClassReader cr = null;
try {
cr = new ClassReader(this.getClass().getClassLoader().getResourceAsStream(_callee.replace('.', '/')+".class"));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
ClassNode classNode = new ClassNode();
cr.accept(classNode, 0);
MethodNode inlinedMethod = null;
for(MethodNode node: classNode.methods){
if(node.name.equals("calculate")){
inlinedMethod = node;
break;
}
}
return new MethodCallInliner(access, desc, mv, inlinedMethod, _callee, _caller );
}
}
//MethodCallInliner.java
public class MethodCallInliner extends LocalVariablesSorter {
private final String oldClass;
private final String newClass;
private final MethodNode mn; //Method Visitor wrappers the mv.
private List blocks = new ArrayList();
private boolean inlining;
public MethodCallInliner(int access, String desc, MethodVisitor mv, MethodNode mn,
String oldClass, String newClass){
super(Opcodes.ASM5, access, desc, mv);
this.oldClass = oldClass;
this.newClass = newClass;
this.mn = mn;
inlining = false;
}
public void visitMethodInsn(int opcode, String owner, String name,
String desc, boolean itf) {
System.out.println("opcode:" + opcode + " owner:" + owner + " name:"
+ name + " desc:" + desc);
if (!canBeInlined(owner, name, desc)) {
mv.visitMethodInsn(opcode, owner, name, desc, itf);
return;
}
//if it is INVOKEVIRTUAL ../Callee::calculate(II), then..
Remapper remapper = new SimpleRemapper(oldClass, newClass);
Label end = new Label();
inlining = true;
mn.instructions.resetLabels();
mn.accept(new InliningAdapter(this,opcode == Opcodes.INVOKESTATIC ? Opcodes.ACC_STATIC : 0, desc,remapper, end));
inlining = false;
super.visitLabel(end);
}
private boolean canBeInlined(String owner, String name, String decs){
if(name.equals("calculate") && owner.equals("code/sxu/asm/example/Callee")){
return true;
}
return false;
}
}
//InliningAdapter.java
public class InliningAdapter extends RemappingMethodAdapter {
private final LocalVariablesSorter lvs;
private final Label end;
public InliningAdapter(LocalVariablesSorter mv,
int acc, String desc,Remapper remapper, Label end) {
super(acc, desc, mv, remapper);
this.lvs = mv;
this.end = end;
// int offset = (acc & Opcodes.ACC_STATIC)!=0 ?0 : 1;
// Type[] args = Type.getArgumentTypes(desc);
// for (int i = args.length-1; i >= 0; i--) {
// super.visitVarInsn(args[i].getOpcode(
// Opcodes.ISTORE), i + offset);
// }
// if(offset>0) {
// super.visitVarInsn(Opcodes.ASTORE, 0);
// }
}
public void visitInsn(int opcode) {
if(opcode==Opcodes.RETURN || opcode == Opcodes.IRETURN) {
super.visitJumpInsn(Opcodes.GOTO, end);
} else {
super.visitInsn(opcode);
}
}
public void visitMaxs(int stack, int locals) {
System.out.println("visit maxs: "+stack+" "+locals);
}
protected int newLocalMapping(Type type) {
return lvs.newLocal(type);
}
}
在代码中,InliningAdapter
和MethodCallInliner
都扩展了LocalVariablesSorter
,它重新编号了局部变量。并且在 Caller::test::invokevirtual(Callee::calculate) 调用站点内联引用了 Callee::calculate() 的处理主体。
Caller::test()、Callee::calculate 和 generated::test 的字节码是:
//Caller::test()
public void test(int, int);
flags: ACC_PUBLIC
Code:
stack=4, locals=7, args_size=3
0: iload_1
1: istore_3
2: iload_2
3: istore 4
5: iload_3
6: iload 4
8: iadd
9: aload_0
10: getfield #13 // Field _callee:Lcode/sxu/asm/example/Callee;
13: iload_1
14: iload_2
15: invokevirtual #39 // Method code/sxu/asm/example/Callee.calculate:(II)I //Copy calculate's body here
18: isub
19: istore 5
21: iload_3
22: iload 4
24: isub
25: istore 6
27: getstatic #43 // Field java/lang/System.out:Ljava/io/PrintStream;
30: iload_3
31: invokevirtual #49 // Method java/io/PrintStream.println:(I)V
34: getstatic #43 // Field java/lang/System.out:Ljava/io/PrintStream;
37: ldc #55 // String 1..........
39: invokevirtual #57 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
42: return
//Callee::calculate()
public int calculate(int, int);
flags: ACC_PUBLIC
Code:
stack=3, locals=4, args_size=3
0: aload_0
1: getfield #14 // Field _a:Ljava/lang/String;
4: invokevirtual #26 // Method java/lang/String.length:()I
7: aload_0
8: getfield #16 // Field _b:Ljava/lang/String;
11: invokevirtual #26 // Method java/lang/String.length:()I
14: iadd
15: istore_3
16: iload_3
17: iload_1
18: iload_2
19: iadd
20: iadd
21: istore_3
22: iload_3
23: ireturn
//data.class
public void test(int, int);
flags: ACC_PUBLIC
Code:
stack=4, locals=8, args_size=3
0: iload_1
1: istore_3
2: iload_2
3: istore 4
5: iload_3
6: iload 4
8: iadd
9: aload_0
10: getfield #14 // Field _callee:Lcode/sxu/asm/example/Callee;
13: iload_1
14: iload_2
15: aload_0
16: getfield #40 // Field _a:Ljava/lang/String;
19: invokevirtual #46 // Method java/lang/String.length:()I
22: aload_0
23: getfield #49 // Field _b:Ljava/lang/String;
26: invokevirtual #46 // Method java/lang/String.length:()I
29: iadd
30: istore 6
32: iload 6
34: iload_1
35: iload_2
36: iadd
37: iadd
38: istore 6
40: iload 6
42: goto 45
45: isub
46: istore 6
48: iload_3
49: iload 4
51: isub
52: istore 7
54: getstatic #59 // Field java/lang/System.out:Ljava/io/PrintStream;
57: iload_3
58: invokevirtual #65 // Method java/io/PrintStream.println:(I)V
61: getstatic #59 // Field java/lang/System.out:Ljava/io/PrintStream;
64: ldc #67 // String 1..........
66: invokevirtual #70 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
69: return
data.class 上的 javap 结果显示 Callee::calculate 的主体已插入到正确的位置 (Caller::test line::15)。但是,有两个主要问题:
invokevirtual前的栈顶三个对象
Callee::calculate(第 15 行) 9: aload_0
10: getfield #14 // 字段_callee:Lcode/sxu/asm/example/Callee; 13: iload_1
14: iload_2内联后不应在堆栈上。
复制体(Callee::calculate())中的变量号0应该映射到正确的号
变量号不正确。首先,data.class中Callee::calculate的复制体的变量号(从第15行到第42行)应该从5开始(而不是0)。其次,Callee::calculate()之后的变量编号要按规则重新编号:a)如果在(0,4]之间则不更改;b)如果与[的复制体中的编号冲突则重新编号=100=]()
我去检查了基础 class LocalVariablesSorter
的实现。问题似乎出在它的构造上:
protected LocalVariablesSorter(final int api, final int access,
final String desc, final MethodVisitor mv) {
super(api, mv);
Type[] args = Type.getArgumentTypes(desc);
nextLocal = (Opcodes.ACC_STATIC & access) == 0 ? 1 : 0;
for (int i = 0; i < args.length; i++) {
nextLocal += args[i].getSize();
}
firstLocal = nextLocal;
}
private int[] mapping = new int[40];
对我来说,似乎 firstLocal
总是从 1+ args.length() 开始(在本例中是 3)。这个class还提供了private int remap(final int var, final Type type)
,它创建新的局部变量并在映射数组中保留映射(从现有变量号到新索引)。
我的问题是如何正确使用LocalVariablesSorter
和内联字节码方法(Callee::calculate)。欢迎任何有关高效内联的建议。
对于内联之前(第15行之前)堆栈上的参数。我的想法是将它们存储为新创建的局部变量,这些变量将由 Callee::calculate 的复制主体引用。例如,添加:
astore 5
后
10: getfield #14 // 字段_callee:Lcode/sxu/asm/example/Callee;
并在 LocalVariablesSorter
mapping[0]=5+1
但主要问题是不允许用户更新LocalVariablesSorter::mapping
(从映射数组中的旧变量号到新变量),因为mapping
数组是私有的,是唯一的地方它的更新在方法中:
private int remap(final int var, final Type type) {
if (var + type.getSize() <= firstLocal) {
//Variable index will never be modified if it is less than firstLocal. 0 < 3. Nothing i can do for ALOAD 0.
return var;
}
int key = 2 * var + type.getSize() - 1;
int size = mapping.length;
if (key >= size) {
.....
}
int value = mapping[key];
if (value == 0) {
value = newLocalMapping(type);
setLocalType(value, type);
mapping[key] = value + 1;
} else {
value--;
}
if (value != var) {
changed = true;
}
return value;
}
Update1: InliningAdapter:
取消注释构造函数后的 data.class 0: iload_1
1: istore_3
2: iload_2
3: istore 4
5: iload_3
6: iload 4
8: iadd
9: aload_0
10: getfield #14 // Field _callee:Lcode/sxu/asm/example/Callee;
13: iload_1
14: iload_2
15: istore_2 //should be istore 5
16: istore_1 //should be istore 6
17: astore_0 //should be astore 7
18: aload_0
19: getfield #40 // Field _a:Ljava/lang/String;
22: invokevirtual #46 // Method java/lang/String.length:()I
25: aload_0
26: getfield #49 // Field _b:Ljava/lang/String;
29: invokevirtual #46 // Method java/lang/String.length:()I
32: iadd
33: istore 6
35: iload 6
37: iload_1
38: iload_2
39: iadd
40: iadd
41: istore 6
43: iload 6
45: goto 48
48: isub
49: istore 6
51: iload_3
52: iload 4
54: isub
55: istore 7
57: getstatic #59
新存储的三个变量(15,16,17)应该编号为5,6,7,而不是2,1,0,内联代码中*store/*load
中的映射应该是喜欢
0 => 7
1 => 6
2 => 5
3 => 8
4 => 9 ...
这些映射应该在数组中:LocalVariablesSorter::mapping
由方法 LocalVariablesSorter::remap()
更新。但是,我似乎无法将它们插入 mapping
数组。
应该进行两种重新映射:
- 在内联代码内部重新映射(从第 18 行到 45 行)和变量 索引从 5 开始。最大索引为 k
- 在内联代码后重新映射(从第46行到最后),如果原始索引大于5,则应重新映射任何变量索引(新索引从k+1开始)
正如@Holger 已经建议的那样,首先取消注释 InliningAdapter
.
解决您列出的主要问题:
LocalVariablesSorter
(由InliningAdapter
扩展)认为参数已经存储在固定位置的局部变量中 - 这是确实是进入方法时的正常情况。所以它根本不映射那些(参见LocalVariablesSorter.remap()
中的第一行 -firstLocal
是在构造函数中计算的)。然而,在这种情况下,我们取而代之的是在堆栈上获取参数,并且需要手动分配局部变量。解决方法是告诉LocalVariablesSorter
局部变量中没有已经存储的参数(使firstLocal = 0
)。然后它将对它们的任何引用都视为新变量并为它们分配新的局部变量。我们可以通过欺骗LocalVariablesSorter
认为没有参数并且该方法是静态的(即使它真的不是)来实现这一点。所以我们将InliningAdapter
中的第一行从super(acc, desc, mv, remapper);
到
super(acc | Opcodes.ACC_STATIC, "()V", mv, remapper);
现在变量 0,1,2,... 重新映射到 5,6,7,... 或类似变量(它们是什么并不重要,[= 的
LocalVariablesSorter
23=](即MethodCallInliner
实例)负责分配它们)。还有另一个问题,您通过
InliningAdapter
扩展RemappingMethodAdaptor
将Callee
class 映射到Caller
- 但是我猜您想将_a
和_b
变量保存在Callee
实例中。- 如果我的猜测是正确的,那么您实际上不应该将引用从
Callee
重新映射到Caller
。您可以只让InliningAdapter
扩展LocalVariableSorter
并摆脱重新映射器。 - 如果我的猜测不正确,那么我猜你可能需要将
Callee
的变量也嵌入到Caller
中,在这种情况下你应该保留你拥有的RemappingMethodAdaptor
.
- 如果我的猜测是正确的,那么您实际上不应该将引用从
调试内联代码时,
Callee
中的行号将没有意义,因为代码已内联到Caller
class 中。因此,Callee
中的所有行号可能应该替换为Caller
中发生内联调用的行的行号。不幸的是,在 Java 中,您不能逐行指定不同的源代码文件(例如,您可以在 C 中指定)。因此,您将使用类似这样的方法覆盖InliningAdapter
中的visitLineNumber()
(inlinedLine
将传递给InliningAdapter
的构造函数):@Override public void visitLineNumber(int line, Label start) { super.visitLineNumber(inlinedLine, start); }
.. 或者完全跳过 super 调用,我对此不是 100% 确定。