Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Would it be legal for a Java compiler to omit getfield opcodes after the first access?

I was experimenting with a Java port of some C# code and I was surprised to see that javac 1.8.0_60 was emitting a getfield opcode each time that an object field was accessed.

Here is the Java code:

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

As reported by javap, javac 1.8.0_60 produces the following 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

Note that a getfield opcode was emitted by the compiler each time that the sign and bits fields were accessed.

Reading §17.4.5, Happens-before Order, of JLS8, I am not seeing why it would be required to emit a getfield opcode each time the sign and bits fields are accessed (other than the first time).

Would it be legal for a Java compiler to emit only two getfield opcodes and save the then-visible values of the fields in frame local variables?

like image 485
Daniel Trebbien Avatar asked Oct 07 '15 15:10

Daniel Trebbien


1 Answers

Not only is it legal, but it is likely to happen once the code gets compiled by the JIT compiler (expression hoisting is one of the available optimisations).

For example the code below:

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

}

terminates promptly (on my machine at least). This is not guaranteed but because the field is fetched each time, there is a point at which the change gets propagated and caught.

If I uncomment Thread.sleep(1000) however, the program never ends because the JIT has enough time to optimise the code and replace stop by a hard coded value, i.e. false.

like image 166
assylias Avatar answered Nov 15 '22 00:11

assylias