[java] JVM의 컴파일러가 “wide”goto를 사용합니까?

나는 당신 goto이 Java 언어로 예약 된 키워드이지만 실제로 사용되지 않는다는 것을 대부분 알고 있습니다. 또한 gotoJVM (Java Virtual Machine) opcode 임을 알고있을 것입니다 . 나는 JVM 수준에서의 조합을 사용하여 구현하는 모든에게 자바, 스칼라와 코 틀린의 정교한 제어 흐름 구조를 이해 할수 gotoifeq, ifle, iflt, 등

JVM을 사양을 보면 https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.goto_w 나는 거기에 또한 참조 goto_w연산 코드가. goto2 바이트 분기 오프셋을 사용하는 반면 goto_w4 바이트 분기 오프셋을 사용합니다. 사양은

있지만 goto_w 명령 오프셋 4 바이트 지점을 얻어, 다른 요인은 65,535 바이트 (§4.11)에있어서의 크기를 제한한다. 이 한계는 다음 릴리스의 Java Virtual Machine에서 발생할 수 있습니다.

goto_w다른 *_wopcode 와 마찬가지로 미래를 보장하는 것처럼 들립니다 . 그러나 필요에 따라 조정 goto_w하여 2 개의 더 중요한 바이트가 0으로 설정되고 2 개의 덜 중요한 바이트가와 동일하게 사용될 수 있다는 것도 나에게 발생 goto합니다.

예를 들어, 다음 Java 스위치 케이스 (또는 스칼라 매치 케이스)가 주어진 경우 :

     12: lookupswitch  {
                112785: 48 // case "red"
               3027034: 76 // case "green"
              98619139: 62 // case "blue"
               default: 87
          }
      48: aload_2
      49: ldc           #17                 // String red
      51: invokevirtual #18
            // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      54: ifeq          87
      57: iconst_0
      58: istore_3
      59: goto          87
      62: aload_2
      63: ldc           #19                 // String green
      65: invokevirtual #18
            // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      68: ifeq          87
      71: iconst_1
      72: istore_3
      73: goto          87
      76: aload_2
      77: ldc           #20                 // String blue
      79: invokevirtual #18 
      // etc.

우리는 그것을 다음과 같이 다시 쓸 수 있습니다

     12: lookupswitch  { 
                112785: 48
               3027034: 78
              98619139: 64
               default: 91
          }
      48: aload_2
      49: ldc           #17                 // String red
      51: invokevirtual #18
            // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      54: ifeq          91 // 00 5B
      57: iconst_0
      58: istore_3
      59: goto_w        91 // 00 00 00 5B
      64: aload_2
      65: ldc           #19                 // String green
      67: invokevirtual #18
            // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      70: ifeq          91
      73: iconst_1
      74: istore_3
      75: goto_w          91
      79: aload_2
      81: ldc           #20                 // String blue
      83: invokevirtual #18 
      // etc.

goto_ws 를 수용하기 위해 “줄 번호”를 변경하는 실수를했기 때문에 실제로 이것을 시도하지 않았습니다 . 그러나 그것은 사양에 있기 때문에 가능할 것입니다.

내 질문은 컴파일러 또는 다른 바이트 코드 생성기가 goto_w현재 65535 제한으로 사용할 수있는 이유가 있는지 여부입니다 .



답변

메소드 코드의 크기는 64K만큼 클 수 있습니다.

short의 분기 오프셋은 goto-32768에서 32767 사이의 부호있는 16 비트 정수입니다.

따라서 짧은 오프셋은 65K 방법의 시작에서 끝으로 점프하기에 충분하지 않습니다.

심지어 javac때로는 방출한다 goto_w. 예를 들면 다음과 같습니다.

public class WideGoto {

    public static void main(String[] args) {
        for (int i = 0; i < 1_000_000_000; ) {
            i += 123456;
            // ... repeat 10K times ...
        }
    }
}

로 디 컴파일 javap -c:

  public static void main(java.lang.String[]);
    Code:
       0: iconst_0
       1: istore_1
       2: iload_1
       3: ldc           #2
       5: if_icmplt     13
       8: goto_w        50018     // <<< Here it is! A jump to the end of the loop
          ...


답변

goto_w분기가에 맞을 때 사용할 이유가 없습니다 goto. 그러나 분기가 뒤로 갈 수 있기 때문에 부호있는 오프셋을 사용하여 분기가 상대적 인 것을 놓친 것 같습니다 .

javap인쇄하기 전에 결과 절대 목표 주소를 계산 하므로과 같은 도구의 출력을 볼 때이를 알 수 없습니다 .

그래서 goto의의 범위는 -327678 … +32767‬항상 각 가능한 대상 위치를 처리하는 것만으로는 충분하지 않습니다 0 … +65535범위.

예를 들어 다음 방법은 goto_w시작 부분에 지침 이 있습니다 .

public static void methodWithLargeJump(int i) {
    for(; i == 0;) {
        try {x();} finally { switch(i){ case 1: try {x();} finally { switch(i){ case 1: 
        try {x();} finally { switch(i){ case 1: try {x();} finally { switch(i){ case 1: 
        try {x();} finally { switch(i){ case 1: try {x();} finally { switch(i){ case 1: 
        try {x();} finally { switch(i){ case 1: try {x();} finally { switch(i){ case 1: 
        try {x();} finally { switch(i){ case 1: try {x();} finally { switch(i){ case 1: 
        } } } } } } } } } } } } } } } } } } } } 
    }
}
static void x() {}

이데온 데모

Compiled from "Main.java"
class LargeJump {
  public static void methodWithLargeJump(int);
    Code:
       0: iload_0
       1: ifeq          9
       4: goto_w        57567


답변

일부 컴파일러 (1.6.0 및 11.0.7에서 시도)에서 메소드가 goto_w를 필요로 할 정도로 충분히 크면 goto_w 만을 사용 하는 것으로 보입니다. 로컬 점프가 매우 많더라도 여전히 goto_w를 사용합니다.


답변