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_2

    should 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.

  1. to tackle main problems listed: localvariablessorter (extended inliningadapter) thinks arguments stored in local variables @ fixed locations - indeed normal situation when entering method. not map @ (see first line in localvariablessorter.remap() - firstlocal calculated in constructor). in case arguments on stack instead , need allocate local variables manually. solution tell localvariablessorter there no parameters stored in local variables (make firstlocal = 0). treat reference them new variables , allocate new local variables them. can achieve fooling localvariablessorter think there no arguments , method static (even if isn't). change first line in inliningadapter from

        super(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 of caller (i.e. methodcallinliner instance) takes care of allocating them).

  2. there problem map callee class caller having inliningadapter extend remappingmethodadaptor - guess want keep _a , _b variables stored in callee instance.

    • if guess correct should not remap references callee caller. can make inliningadapter extend localvariablesorter instead , rid of remapper.
    • if guess incorrect guess need embed variables of callee caller well, in case should keep remappingmethodadaptor have.
  3. when debugging inlined code linenumbers callee not make sense since code inlined caller class. linenumbers callee should replaced line number of line in caller inlined call occured. unfortunately in java cannot specify different source code files on line-by-line basis (like can in c example). override visitlinenumber() in inliningadapter using (inlinedline passed constructor of inliningadapter):

    @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

Popular posts from this blog

java - Custom OutputStreamAppender not run: LOGBACK: No context given for <MYAPPENDER> -

java - UML - How would you draw a try catch in a sequence diagram? -

c++ - No viable overloaded operator for references a map -