2015-10-07 11 views
5

Stavo sperimentando una porta Java di some C# code e sono stato sorpreso di vedere che javac 1.8.0_60 emetteva un opcode getfield ogni volta che si accede a un campo oggetto.Sarebbe legale per un compilatore Java omettere gli opcode di getfield dopo il primo accesso?

Ecco il codice Java:

public class BigInteger 
{ 
    private int[] bits; 
    private int sign; 

    //... 

    public byte[] ToByteArray() 
    { 
     if (sign == 0) 
     { 
      return new byte[] { 0 }; 
     } 

     byte highByte; 
     int nonZeroDwordIndex = 0; 
     int highDword; 
     if (bits == null) 
     { 
      highByte = (byte)((sign < 0) ? 0xff : 0x00); 
      highDword = sign; 
     } 
     else if (sign == -1) 
     { 
      highByte = (byte)0xff; 
      assert bits.length > 0; 
      assert bits[bits.length - 1] != 0; 
      while (bits[nonZeroDwordIndex] == 0) 
      { 
       nonZeroDwordIndex++; 
      } 

      highDword = ~bits[bits.length - 1]; 
      if (bits.length - 1 == nonZeroDwordIndex) 
      { 
       highDword += 1; 
      } 
     } 
     else 
     { 
      assert sign == 1; 
      highByte = 0x00; 
      highDword = bits[bits.length - 1]; 
     } 

     byte msb; 
     int msbIndex; 
     if ((msb = (byte)(highDword >>> 24)) != highByte) 
     { 
      msbIndex = 3; 
     } 
     else if ((msb = (byte)(highDword >>> 16)) != highByte) 
     { 
      msbIndex = 2; 
     } 
     else if ((msb = (byte)(highDword >>> 8)) != highByte) 
     { 
      msbIndex = 1; 
     } 
     else 
     { 
      msb = (byte)highDword; 
      msbIndex = 0; 
     } 

     boolean needExtraByte = (msb & 0x80) != (highByte & 0x80); 
     byte[] bytes; 
     int curByte = 0; 
     if (bits == null) 
     { 
      bytes = new byte[msbIndex + 1 + (needExtraByte ? 1 : 0)]; 
      assert bytes.length <= 4; 
     } 
     else 
     { 
      bytes = new byte[4 * (bits.length - 1) + msbIndex + 1 + (needExtraByte ? 1 : 0)]; 

      for (int i = 0; i < bits.length - 1; i++) 
      { 
       int dword = bits[i]; 
       if (sign == -1) 
       { 
        dword = ~dword; 
        if (i <= nonZeroDwordIndex) 
        { 
         dword = dword + 1; 
        } 
       } 
       for (int j = 0; j < 4; j++) 
       { 
        bytes[curByte++] = (byte)dword; 
        dword >>>= 8; 
       } 
      } 
     } 
     for (int j = 0; j <= msbIndex; j++) 
     { 
      bytes[curByte++] = (byte)highDword; 
      highDword >>>= 8; 
     } 
     if (needExtraByte) 
     { 
      bytes[bytes.length - 1] = highByte; 
     } 
     return bytes; 
    } 
} 

Secondo quanto riportato da javap, javac 1.8.0_60 produce i seguenti bytecode:

 
    public byte[] ToByteArray(); 
    Code: 
     0: aload_0 
     1: getfield  #3     // Field sign:I 
     4: ifne   15 
     7: iconst_1 
     8: newarray  byte 
     10: dup 
     11: iconst_0 
     12: iconst_0 
     13: bastore 
     14: areturn 
     15: iconst_0 
     16: istore_2 
     17: aload_0 
     18: getfield  #2     // Field bits:[I 
     21: ifnonnull  48 
     24: aload_0 
     25: getfield  #3     // Field sign:I 
     28: ifge   37 
     31: sipush  255 
     34: goto   38 
     37: iconst_0 
     38: i2b 
     39: istore_1 
     40: aload_0 
     41: getfield  #3     // Field sign:I 
     44: istore_3 
     45: goto   193 
     48: aload_0 
     49: getfield  #3     // Field sign:I 
     52: iconst_m1 
     53: if_icmpne  156 
     56: iconst_m1 
     57: istore_1 
     58: getstatic  #11     // Field $assertionsDisabled:Z 
     61: ifne   80 
     64: aload_0 
     65: getfield  #2     // Field bits:[I 
     68: arraylength 
     69: ifgt   80 
     72: new   #12     // class java/lang/AssertionError 
     75: dup 
     76: invokespecial #13     // Method java/lang/AssertionError."":()V 
     79: athrow 
     80: getstatic  #11     // Field $assertionsDisabled:Z 
     83: ifne   109 
     86: aload_0 
     87: getfield  #2     // Field bits:[I 
     90: aload_0 
     91: getfield  #2     // Field bits:[I 
     94: arraylength 
     95: iconst_1 
     96: isub 
     97: iaload 
     98: ifne   109 
    101: new   #12     // class java/lang/AssertionError 
    104: dup 
    105: invokespecial #13     // Method java/lang/AssertionError."":()V 
    108: athrow 
    109: aload_0 
    110: getfield  #2     // Field bits:[I 
    113: iload_2 
    114: iaload 
    115: ifne   124 
    118: iinc   2, 1 
    121: goto   109 
    124: aload_0 
    125: getfield  #2     // Field bits:[I 
    128: aload_0 
    129: getfield  #2     // Field bits:[I 
    132: arraylength 
    133: iconst_1 
    134: isub 
    135: iaload 
    136: iconst_m1 
    137: ixor 
    138: istore_3 
    139: aload_0 
    140: getfield  #2     // Field bits:[I 
    143: arraylength 
    144: iconst_1 
    145: isub 
    146: iload_2 
    147: if_icmpne  193 
    150: iinc   3, 1 
    153: goto   193 
    156: getstatic  #11     // Field $assertionsDisabled:Z 
    159: ifne   178 
    162: aload_0 
    163: getfield  #3     // Field sign:I 
    166: iconst_1 
    167: if_icmpeq  178 
    170: new   #12     // class java/lang/AssertionError 
    173: dup 
    174: invokespecial #13     // Method java/lang/AssertionError."":()V 
    177: athrow 
    178: iconst_0 
    179: istore_1 
    180: aload_0 
    181: getfield  #2     // Field bits:[I 
    184: aload_0 
    185: getfield  #2     // Field bits:[I 
    188: arraylength 
    189: iconst_1 
    190: isub 
    191: iaload 
    192: istore_3 
    193: iload_3 
    194: bipush  24 
    196: iushr 
    197: i2b 
    198: dup 
    199: istore  4 
    201: iload_1 
    202: if_icmpeq  211 
    205: iconst_3 
    206: istore  5 
    208: goto   254 
    211: iload_3 
    212: bipush  16 
    214: iushr 
    215: i2b 
    216: dup 
    217: istore  4 
    219: iload_1 
    220: if_icmpeq  229 
    223: iconst_2 
    224: istore  5 
    226: goto   254 
    229: iload_3 
    230: bipush  8 
    232: iushr 
    233: i2b 
    234: dup 
    235: istore  4 
    237: iload_1 
    238: if_icmpeq  247 
    241: iconst_1 
    242: istore  5 
    244: goto   254 
    247: iload_3 
    248: i2b 
    249: istore  4 
    251: iconst_0 
    252: istore  5 
    254: iload   4 
    256: sipush  128 
    259: iand 
    260: iload_1 
    261: sipush  128 
    264: iand 
    265: if_icmpeq  272 
    268: iconst_1 
    269: goto   273 
    272: iconst_0 
    273: istore  6 
    275: iconst_0 
    276: istore  8 
    278: aload_0 
    279: getfield  #2     // Field bits:[I 
    282: ifnonnull  325 
    285: iload   5 
    287: iconst_1 
    288: iadd 
    289: iload   6 
    291: ifeq   298 
    294: iconst_1 
    295: goto   299 
    298: iconst_0 
    299: iadd 
    300: newarray  byte 
    302: astore  7 
    304: getstatic  #11     // Field $assertionsDisabled:Z 
    307: ifne   443 
    310: aload   7 
    312: arraylength 
    313: iconst_4 
    314: if_icmple  443 
    317: new   #12     // class java/lang/AssertionError 
    320: dup 
    321: invokespecial #13     // Method java/lang/AssertionError."":()V 
    324: athrow 
    325: iconst_4 
    326: aload_0 
    327: getfield  #2     // Field bits:[I 
    330: arraylength 
    331: iconst_1 
    332: isub 
    333: imul 
    334: iload   5 
    336: iadd 
    337: iconst_1 
    338: iadd 
    339: iload   6 
    341: ifeq   348 
    344: iconst_1 
    345: goto   349 
    348: iconst_0 
    349: iadd 
    350: newarray  byte 
    352: astore  7 
    354: iconst_0 
    355: istore  9 
    357: iload   9 
    359: aload_0 
    360: getfield  #2     // Field bits:[I 
    363: arraylength 
    364: iconst_1 
    365: isub 
    366: if_icmpge  443 
    369: aload_0 
    370: getfield  #2     // Field bits:[I 
    373: iload   9 
    375: iaload 
    376: istore  10 
    378: aload_0 
    379: getfield  #3     // Field sign:I 
    382: iconst_m1 
    383: if_icmpne  404 
    386: iload   10 
    388: iconst_m1 
    389: ixor 
    390: istore  10 
    392: iload   9 
    394: iload_2 
    395: if_icmpgt  404 
    398: iload   10 
    400: iconst_1 
    401: iadd 
    402: istore  10 
    404: iconst_0 
    405: istore  11 
    407: iload   11 
    409: iconst_4 
    410: if_icmpge  437 
    413: aload   7 
    415: iload   8 
    417: iinc   8, 1 
    420: iload   10 
    422: i2b 
    423: bastore 
    424: iload   10 
    426: bipush  8 
    428: iushr 
    429: istore  10 
    431: iinc   11, 1 
    434: goto   407 
    437: iinc   9, 1 
    440: goto   357 
    443: iconst_0 
    444: istore  9 
    446: iload   9 
    448: iload   5 
    450: if_icmpgt  474 
    453: aload   7 
    455: iload   8 
    457: iinc   8, 1 
    460: iload_3 
    461: i2b 
    462: bastore 
    463: iload_3 
    464: bipush  8 
    466: iushr 
    467: istore_3 
    468: iinc   9, 1 
    471: goto   446 
    474: iload   6 
    476: ifeq   488 
    479: aload   7 
    481: aload   7 
    483: arraylength 
    484: iconst_1 
    485: isub 
    486: iload_1 
    487: bastore 
    488: aload   7 
    490: areturn 

Nota che un codice operativo getfield è stata emessa dal compilatore di volta in volta accesso ai campi sign e bits.

§17.4.5 lettura, Happens-prima di ordine, di JLS8, non sto vedendo il motivo per cui sarebbe necessario per emettere un codice operativo getfield ogni volta che i campi sign e bits si accede (diverso la prima volta).

Sarebbe legale per un compilatore Java emettere solo due codici opzionali getfield e salvare i valori allora visibili dei campi nelle variabili locali del frame?

risposta

4

Non solo è legale, ma è probabile che accada una volta che il codice viene compilato dal compilatore JIT (il sollevamento di espressioni è una delle ottimizzazioni disponibili).

Per esempio il codice qui sotto:

public class Test { 
    private boolean stop; 

    public static void main(String[] args) throws InterruptedException { 
    Test t = new Test(); 
    new Thread(t::m).start(); 
// Thread.sleep(1000); 
    System.out.println("stop is now true"); 
    t.stop = true; 
    } 

    private void m() { 
    while (!stop); 
    System.out.println("Finished"); 
    } 

} 

termina immediatamente (sulla mia macchina, almeno). Questo non è garantito, ma poiché il campo viene recuperato ogni volta, c'è un punto in cui la modifica viene propagata e catturata.

Se si rimuove il commento Thread.sleep(1000), tuttavia, il programma non termina mai perché la JIT ha tempo sufficiente per ottimizzare il codice e sostituire stop con un valore codificato, ad esempio false.

+1

Grazie per la risposta. Ho provato [jitwatch] (https://github.com/AdoptOpenJDK/jitwatch) su un programma che chiama ToByteArray() su vari input, ripetutamente, e dormendo tra le esecuzioni. Il JIT ricompila il metodo 4 volte durante una corsa del mio test. Nella compilazione finale, l'assembly x86-64 ottiene i valori dei campi 'sign' e' bits' solo una volta. Ho anche provato una versione in cui ho usato esplicitamente le variabili locali. L'assembly x86-64 di questa versione "locals" ottiene anche i valori dei campi 'sign' e' bits' solo una volta. È interessante notare che la versione "membri" è 34 byte asm più breve. –

+0

@JohnM "* L'ottimizzatore non assumerà che una variabile non cambierà mai solo perché non ha un secondo *" => non è quello che sto dicendo. 'stop' non viene mai modificato in' m' ed è una variabile locale, non volatile. Quindi l'ottimizzatore può riscrivere legalmente 'm' come:' booleano localStop = stop; while (! localStop); '(e lo fa). Se 'stop' fosse stato 'volatile', non sarebbe stato un cambiamento legale. Il punto del sonno è di dare abbastanza tempo per la JIT per dare il via e compilare il codice. – assylias

Problemi correlati