Будет ли законным для компилятора Java пропускать коды операций getfield после первого доступа?

Я экспериментировал с портом Java некоторого кода C#, и я был удивлен, увидев, что javac 1.8.0_60 испускает getfield код операции каждый раз, когда к объектному полю обращались.

Вот код 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;
    }
}

Как сообщает javap, javac 1.8.0_60 выдает следующий байт-код:

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

Обратите внимание, что getfield код операции выдавался компилятором каждый раз, когда sign а также bits поля были доступны.

Читая §17.4.5, "Порядок до и после", JLS8, я не понимаю, почему было бы необходимо испускать getfield код операции каждый раз, когда sign а также bits Доступ к полям (кроме первого раза).

Будет ли законным для компилятора Java испускать только два getfield коды операций и сохранить видимые тогда значения полей в кадре локальных переменных?

1 ответ

Решение

Это не только законно, но и, вероятно, произойдет после того, как код скомпилирован компилятором JIT (поднятие выражения является одной из доступных оптимизаций).

Например код ниже:

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

}

быстро завершается (по крайней мере, на моей машине). Это не гарантируется, но поскольку поле выбирается каждый раз, существует момент, когда изменение распространяется и перехватывается.

Если я раскомментирую Thread.sleep(1000) тем не менее, программа никогда не заканчивается, потому что JIT имеет достаточно времени для оптимизации кода и замены stop жестко закодированным значением, т.е. false,

Другие вопросы по тегам