java - Remapper variables during bytecode method inlining by ASM -
i doing online bytecode method inlining optimization using asm. changes based on example 3.2.6 inline method
(http://asm.ow2.org/current/asm-transformations.pdf). test example (inline callee's calculate(int,int) @ caller::test) is:
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; } }
based on asm 5.0 version, code is:
//maininliner.java public class maininliner extends classloader{ public byte[] generator(string caller, string callee) throws classnotfoundexception{ string resource = callee.replace('.', '/') + ".class"; inputstream = getresourceasstream(resource); byte[] buffer; // adapts class on fly try { resource = caller.replace('.', '/')+".class"; = 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 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 fly adaptation"); return super.loadclass(name, resolve); } else { system.err.println("adapt: loading class '" + name + "' on fly adaptation"); } string caller = "code.sxu.asm.example.caller"; string callee = "code.sxu.asm.example.callee"; byte[] b = generator(caller, callee); // returns adapted class return defineclass(caller, b, 0, b.length); } public static void main(final string args[]) throws exception { // loads application class (in args[0]) 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 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 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); // (int = args.length-1; >= 0; i--) { // super.visitvarinsn(args[i].getopcode( // opcodes.istore), + 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); } }
in code, both inliningadapter
, methodcallinliner
extends localvariablessorter
, renumbers local variables. , inline references coping body of callee::calculate() @ caller::test::invokevirtual(callee::calculate) call site.
the bytecodes caller::test(), callee::calculate, , generated::test are:
//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
the javap result on data.class shows body of callee::calculate has been inserted right place (caller::test line::15). however, there 2 main problems:
the top 3 stack objects before invokevirtual
callee::calculate(line 15) 9: aload_0
10: getfield #14 // field _callee:lcode/sxu/asm/example/callee; 13: iload_1
14: iload_2should not on stack after inline.
the variable number 0 in copied body (callee::calculate()) should mapped right number
the variable number not correct. first, variable numbers of copied body of callee::calculate in data.class (from line 15 line 42) shoud begin 5 (instead of 0). second, variable numbers after callee::calculate() should renumbered rule: a) not change if between (0,4]; b) renumber if conflict number in copied body of callee::calculate()
i went check base class localvariablessorter
's implementation. problem seems @ construction:
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; (int = 0; < args.length; i++) { nextlocal += args[i].getsize(); } firstlocal = nextlocal; } private int[] mapping = new int[40];
to me, seems firstlocal
starts @ 1+ args.length() (in case 3). class provide private int remap(final int var, final type type)
, creates new local variables , keeps mapping (from existing variable number new index) in mapping array.
my problem how use localvariablessorter
, inline bytecode method (callee::calculate) correctly. suggestion efficient inline welcome.
for parameters on stack before inline (before line 15). idea store them new created local variables referred copied body of callee::calculate. example, add: astore 5
after 10: getfield #14 // field _callee:lcode/sxu/asm/example/callee;
and add mapping[0]=5+1
in localvariablessorter
but main problem users not allowed update localvariablessorter::mapping
(from old variable number new variable in mapping array) because mapping
array private , place update in method:
private int remap(final int var, final type type) { if (var + type.getsize() <= firstlocal) { //variable index never modified if less firstlocal. 0 < 3. nothing can 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: data.class after uncomment constructor of inliningadapter:
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 istore 5 16: istore_1 //should istore 6 17: astore_0 //should 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
the new stored 3 variables (15,16,17) should numbered 5,6,7, rather 2,1,0, , mapping in *store/*load
in inlined code should like
0 => 7 1 => 6 2 => 5 3 => 8 4 => 9 ...
these mapping should in array:localvariablessorter::mapping
updated method localvariablessorter::remap()
. however, seems not possible insert them mapping
array.
there 2 kinds of remappering should done:
- remapping inside of inlined code (from line 18 t0 45) , variable index starts @ 5. max index k
- remapping after inlined code (from line 46 end), , variable index should remapped (new index starts @ k+1) if original index greater 5
as @holger suggested, start uncommenting lines in inliningadapter
.
to tackle main problems listed:
localvariablessorter
(extendedinliningadapter
) thinks arguments stored in local variables @ fixed locations - indeed normal situation when entering method. not map @ (see first line inlocalvariablessorter.remap()
-firstlocal
calculated in constructor). in case arguments on stack instead , need allocate local variables manually. solution telllocalvariablessorter
there no parameters stored in local variables (makefirstlocal = 0
). treat reference them new variables , allocate new local variables them. can achieve foolinglocalvariablessorter
think there no arguments , method static (even if isn't). change first line ininliningadapter
fromsuper(acc, desc, mv, remapper);
to
super(acc | opcodes.acc_static, "()v", mv, remapper);
now variables 0,1,2,... remapped 5,6,7,... or similar (doesn't matter are,
localvariablessorter
ofcaller
(i.e.methodcallinliner
instance) takes care of allocating them).there problem map
callee
classcaller
havinginliningadapter
extendremappingmethodadaptor
- guess want keep_a
,_b
variables stored incallee
instance.- if guess correct should not remap references
callee
caller
. can makeinliningadapter
extendlocalvariablesorter
instead , rid of remapper. - if guess incorrect guess need embed variables of
callee
caller
well, in case should keepremappingmethodadaptor
have.
- if guess correct should not remap references
when debugging inlined code linenumbers
callee
not make sense since code inlinedcaller
class. linenumberscallee
should replaced line number of line incaller
inlined call occured. unfortunately in java cannot specify different source code files on line-by-line basis (like can in c example). overridevisitlinenumber()
ininliningadapter
using (inlinedline
passed constructor ofinliningadapter
):@override public void visitlinenumber(int line, label start) { super.visitlinenumber(inlinedline, start); }
.. or perhaps skip super call altogether, i'm not 100% sure this.
Comments
Post a Comment