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