8

Wykonuję metodę kodu bajtowego online, optymalizując metodę za pomocą ASM. Moje zmiany są oparte na przykładzie 3.2.6 Inline Method (http://asm.ow2.org/current/asm-transformations.pdf). Przykład testowy (oblicz Inline wywoływany jest (int, int) na Caller :: testu) wynosi:Zmienne remapper podczas metody kodu bajtowego Inlining przez ASM

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; 
    } 
} 

podstawie ASM wersji 5.0, mój kod jest:

//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); 
    } 
} 

W kodzie zarówno InliningAdapter i MethodCallInliner rozszerza LocalVariablesSorter, która zmienia numery lokalne. I wstawia odniesienia do radzenia sobie z ciałem Callee :: calcul() na stronie wywołania Caller :: test :: invokevirtual (Callee :: calcul).

W bytecodes dla rozmówcy :: test(), Wywoływany :: obliczyć, i generowane :: testy są:

//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   

Wynik javap na data.class pokazuje, że ciało wywoływany :: oblicz został wstawiony we właściwe miejsce (Caller :: test line :: 15). Jednak istnieją dwa główne problemy:

  • Górne trzy obiekty stos przed invokevirtual

    wywoływany :: oblicz (linia 15) 9: aload_0
    10: getfield # 14 // _callee Pole: Lcode/sxu/asm/example/Callee; 13: iload_1
    14: iload_2

    nie powinien być na stosie po wstawieniu.

  • liczba zmienna 0 w kopiowane ciała (wywoływany :: oblicz()) powinny odwzorowany na odpowiednią liczbę

  • The zmiennej liczbie nie są poprawne. Po pierwsze, zmienne liczby kopiowanego ciała Callee :: calcul w klasie data.class (od linii 15 do linii 42) zaczynają się od 5 (zamiast 0). Po drugie, zmienne liczby po Callee :: calculate() powinny być ponumerowane zgodnie z zasadą: a) nie zmieniać, jeśli jest pomiędzy (0,4), b) przenumerować, jeśli jest sprzeczne z liczbą w skopiowanym ciele Callee: : calculate()

Poszedłem sprawdzić implementację klasy podstawowej LocalVariablesSorter. Problem wydaje się być w jego budowie:

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]; 

Dla mnie wydaje się, że firstLocal są zawsze zaczyna się od 1+ args.length() (W tym przypadku jest to 3). Ta klasa zapewnia również private int remap(final int var, final Type type), która tworzy nowe zmienne lokalne i zachowuje odwzorowanie (z istniejącej liczby zmiennej na nowy indeks) w tablicy odwzorowania.


Moim problemem jest to, jak wykorzystać LocalVariablesSorter i inline metody kodu bajtowego (Wywoływany :: oblicz) prawidłowo. Wszelkie sugestie dotyczące wydajnego wstawiania są mile widziane.

Dla parametrów na stosie przed wstawieniem (przed wierszem 15). Moim zamysłem jest zapisanie ich jako nowych utworzonych zmiennych lokalnych, które będą odesłane przez skopiowane ciało Callee :: calcul.Na przykład dodaj: astore 5 po 10: getfield # 14 // Pole _callee: Lcode/sxu/asm/example/Callee;

i dodać mapping[0]=5+1 w LocalVariablesSorter

Ale głównym problemem jest to, że użytkownicy nie mogą aktualizować LocalVariablesSorter::mapping (ze starego numeru zmiennej do nowej zmiennej w tablicy odwzorowania), ponieważ mapping tablica jest prywatna i jedynym miejscem, jego aktualizacji jest w metodzie:

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: The data.class po odkomentuj Constructo R w 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 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 

nowe przechowywane trzy zmienne (15,16,17), powinny być ponumerowane 5,6,7 zamiast 2,1,0 i odwzorowanie na *store/*load w wplatany kod powinien być jak

 0 => 7 
     1 => 6 
     2 => 5 
     3 => 8 
     4 => 9 ... 

to mapowanie powinny być w tablicy: LocalVariablesSorter::mapping aktualizowanej metodą LocalVariablesSorter::remap(). Jednak nie wydaje się możliwe wstawienie ich do tablicy mapping.

Istnieją dwa rodzaje remappering należy zrobić:

  • Remapping wewnątrz kodu inlined (Z linii 18 t0 45) i zmienna indeks rozpoczyna się 5. Indeks max k
  • Remapping po kod inline (od linii 46 do końca) i każdy indeks zmiennej powinien zostać odwzorowany (nowy indeks zaczyna się od k + 1), jeśli oryginalny indeks jest większy niż 5
+0

W Twoim kodzie brakuje dokładnie części, którą skomentowałeś. Jest to część, która wyrzuca argumenty ze stosu i umieszcza je w zmiennych lokalnych, które pasują do parametrów i odbiorcy kodu inlined. Jeśli tego nie zrobisz, będziesz mieć zwisające wartości na stosie i niedopasowanie zmiennych lokalnych dla "this" i parametrów metody inlined, tak jak opisujesz. – Holger

+0

Dzięki @Holger, masz rację, kod ten został skomentowany z powodu błędnych indeksów zmiennych na początku. Zobacz update1 na końcu postu, aby wyjaśnić problem: nie można zachować mapowania w tablicy '' LocalVariablesSorter :: rmapping'' –

Odpowiedz

1

Jak już zasugerował @Holger, zacznij od odkomentowanie linii w InliningAdapter.

  1. Aby rozwiązać główne problemy, wymieniony: LocalVariablesSorter (przedłużony o InliningAdapter) uważa, że ​​argumenty są już przechowywane w zmiennych lokalnych w stałych miejscach - to jest rzeczywiście normalna sytuacja podczas wprowadzania metody. Więc nie mapuje ich wcale (pierwsza linia w LocalVariablesSorter.remap() - firstLocal jest obliczana w konstruktorze). Jednak w tym przypadku otrzymujemy zamiast tego argumenty na stosie i musimy ręcznie przydzielić zmienne lokalne. Rozwiązaniem jest powiedzenie LocalVariablesSorter, że nie ma żadnych parametrów już zapisanych w zmiennych lokalnych (make firstLocal = 0). Następnie traktuje wszelkie odniesienia do nich jako nowe zmienne i przydziela im nowe zmienne lokalne. Możemy to osiągnąć przez oszukiwanie LocalVariablesSorter, aby myśleć, że nie ma żadnych argumentów i że metoda jest statyczna (nawet jeśli tak naprawdę nie jest). Więc zmienić pierwszą linię w InliningAdapter z

    super(acc, desc, mv, remapper); 
    

    do

    super(acc | Opcodes.ACC_STATIC, "()V", mv, remapper); 
    

    teraz zmiennych 0,1,2, ... się odwzorować na 5,6,7, ... lub podobny (nie ma znaczenia, jacy są, LocalVariablesSorter z (tj. instancja MethodCallInliner) zajmuje się przydzielaniem ich).

  2. Jest też inny problem, że mapa klasę Callee do Caller poprzez InliningAdapter przedłużyć RemappingMethodAdaptor - jednak myślę, że chcesz zachować _a i _b zmiennych przechowywanych w instancji Callee.

    • Jeśli moje przypuszczenie jest prawidłowe to należy właściwie nie przemapować odwołań od Callee do Caller. Zamiast tego możesz zamiast tego zrobić InliningAdapter i pozbyć się remappera.
    • Jeśli moje przypuszczenie jest niepoprawne, prawdopodobnie prawdopodobnie będziesz musiał również osadzić zmienne Callee w Caller, w takim przypadku powinieneś zachować numer RemappingMethodAdaptor.
  3. Gdy debugowanie kodu inlined numery linii od Callee nie ma sensu, ponieważ kod został inlined do klasy Caller. Dlatego wszystkie lniane numery od Callee powinny prawdopodobnie zostać zastąpione numerem linii w numerze Caller, w której nastąpiło wywołanie inicjowane. Niestety w Javie nie można określać różnych plików kodu źródłowego na zasadzie line-by-line (jak na przykład w C). Więc można zastąpić visitLineNumber() w InliningAdapter pomocą czegoś takiego (inlinedLine byłyby przekazywane do konstruktora InliningAdapter):

    @Override 
    public void visitLineNumber(int line, Label start) { 
        super.visitLineNumber(inlinedLine, start); 
    } 
    

    .. a może pominąć super połączenie zupełnie nie jestem w 100% pewien na ten temat.

+0

Właściwie, chcę osadzić wszystkich członków Field of Callee na Caller. –

+0

Czy zawsze masz dokładnie jedno wystąpienie Callee w Caller? –

+0

istnieje więcej niż jedno wystąpienie Callee w dzwoniącym. Mapa jest tworzona w celu zindeksowania każdego członka pola Callee przez odpowiadającą mu nazwę pola w Rozmówcy. Później wszystkie pola cmentarza są dodawane do dzwoniącego zgodnie z mapą. –

Powiązane problemy