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?
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With