Bug 128705 - [Compiler][1.5] Jsr inlining limitation in the compiler
Summary: [Compiler][1.5] Jsr inlining limitation in the compiler
Status: VERIFIED FIXED
Alias: None
Product: JDT
Classification: Eclipse Project
Component: Core (show other bugs)
Version: 3.2   Edit
Hardware: All All
: P3 normal (vote)
Target Milestone: 3.2 M6   Edit
Assignee: Philipe Mulet CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2006-02-20 14:02 EST by Mark Bottomley CLA
Modified: 2006-03-28 04:27 EST (History)
0 users

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Mark Bottomley CLA 2006-02-20 14:02:12 EST
The attached .java file and .class output file (from Sun's javac for version 5) is a file that will not compile on the Eclipse compiler with the -1.5 flag set or the -inlineJSR flag set.  The _jspService method inlines to around 130K bytes of code.  This file - the Java source is programmatically generated - is from a PMR from Oracle (PMR successfully dealt with).

1) Sun in their 1.5 compiler will generate jsr bytecodes (complete with the 1.5 version tag) if they fail to inline the jsr's.

2) This file will not copmile under the Sun Java6 javac early access compiler.

The problem is that the finally clause of just under 500 bytes is inlined about 270 times.  The format of the file is that there are multiple return points scattered throughout the source code and each of these becomes an inline point.

Is it possible for the compiler to generate code that goto's a common jsr followed by return point reducing the number of inline locations.  I suspect this pattern may show up elsewhere with other customers and it appears that Sun currently can't handle it for Java6, so it would be an advantage for us to be able to compile the class without jsr's.

I will attach enough files (inlinetest.zip) to compile the class with the following command line:

D:\test2>javac -cp ascontrol.jar;oc4j-internal.jar;ojsp.jar;servlet.jar;uix2.jar _topology.java
Comment 1 Mark Bottomley CLA 2006-02-20 16:57:33 EST
There were errors when trying to attach the sample code - A .zip of the source file and supporting libraries for compilation have been forwarded to Olivier Thomann.
Comment 2 Philipe Mulet CLA 2006-02-22 15:19:18 EST
Actually, using regular branch to subroutine (and back, using some tableswitch to map back to proper location) is not doable due to some classfile limitations.

e.g. for code:
public class X {
	public static void main(String[] args) {
		new X().foo(args);
	}
	int foo(String[] args){
		try {
			if (args == null) return 0;
			if (args.length == 0) return args.length;
		} finally {
			System.out.print("finally");
		}
		System.out.println();
		return 2;
	}
}

I could generate:
  // Method descriptor #20 ([Ljava/lang/String;)I
  // Stack: 3, Locals: 5
  int foo(String[] args);
     0  aload_1 [args]
     1  ifnonnull 11
     4  iconst_0
     5  istore_2
     6  goto 39
     9  iconst_0
    10  ireturn
    11  aload_1 [args]
    12  arraylength
    13  ifne 80
    16  aload_1 [args]
    17  arraylength
    18  istore 4
    20  iconst_1
    21  istore_2
    22  goto 39
    25  iload 4
    27  ireturn
    28  goto 80
    31  astore_3
    32  iconst_2
    33  istore_2
    34  goto 39
    37  aload_3
    38  athrow
    39  getstatic System.out : PrintStream [23]
    42  ldc <String "finally"> [29]
    44  invokevirtual PrintStream.print(String) : void [31]
    47  iload_2
    48  tableswitch default: 85
          case 0: 9
          case 1: 25
          case 2: 37
          case 3: 85
    80  iconst_4
    81  istore_2
    82  goto 39
    85  getstatic System.out : PrintStream [23]
    88  invokevirtual PrintStream.println() : void [37]
    91  iconst_2
    92  ireturn
      Exception Table:
        [pc: 0, pc: 9] -> 31 when : any
        [pc: 11, pc: 25] -> 31 when : any
        [pc: 28, pc: 31] -> 31 when : any
        [pc: 80, pc: 85] -> 31 when : any
      Line numbers:
        [pc: 0, line: 7]
        [pc: 11, line: 8]
        [pc: 31, line: 9]
        [pc: 37, line: 11]
        [pc: 39, line: 10]
        [pc: 47, line: 11]
        [pc: 85, line: 12]
        [pc: 91, line: 13]
      Local variable table:
        [pc: 0, pc: 93] local: this index: 0 type: X
        [pc: 0, pc: 93] local: args index: 1 type: String[]


But this is illegal from verifier standpoint:
java.lang.VerifyError: (class: X, method: foo signature: ([Ljava/lang/String;)I) Accessing value from uninitialized register 4
	at java.lang.Class.forName0(Native Method)
	at java.lang.Class.forName(Class.java:164)
	at Compile.main(Compile.java:108)
Comment 3 Philipe Mulet CLA 2006-02-22 18:57:22 EST
We can hack to issue JSR bytecodes as a fallback plan, but in Java7 these are forbidden. So this would only be a temporary workaround.
Comment 4 Mark Bottomley CLA 2006-02-22 21:37:37 EST
Philippe - I understand that some inlining situations would lead to inline bloat always.  The sample I sent has multiple "return" places leading to excessive inlining.  My suggestion is, in the presence of multiple returns, to replace the returns with goto's to a single return point and inline there.  The line number table for the return source code lines would be the goto bytecodes, not the return bytecode.  This is a solution to a limited subset of the inlining problems.
Comment 5 Philipe Mulet CLA 2006-02-23 05:57:15 EST
Also it wouldn't work in situation where some non-constant value is returned.
So it would only improve situation for return instruction in void methods or return constant.

In other circumstances, it would break bytecode verification, as the returned value must be cached in a variable which is treated as unitialized from bytecode verifier standpoint.
Comment 6 Philipe Mulet CLA 2006-02-23 13:13:31 EST
Different approach, which could benefit this usecase. Simply branch into similar subroutine sequences (either inlining finally or not).

e.g.
public class X {
	public static void main(String[] args) {
		System.out.println(new X().foo(args));
	}
	String foo(String[] args) {
		try {
			if (args == null) return "KO";
			switch(args.length) {
			case 0:
				return "OK";
			case 1:
				return "KO";
			case 3:
				return "OK";
			default:
				return "KO";
			}
		} finally {
			System.out.print("FINALLY:");
		}
	}
}

  // Method descriptor #26 ([Ljava/lang/String;)Ljava/lang/String;
  // Stack: 2, Locals: 3
  String foo(String[] args);
     0  aload_1 [args]
     1  ifnonnull 15
     4  getstatic System.out : PrintStream [16]
     7  ldc <String "FINALLY:"> [35]
     9  invokevirtual PrintStream.print(String) : void [37]
    12  ldc <String "KO"> [40]
    14  areturn
    15  aload_1 [args]
    16  arraylength
    17  tableswitch default: 65
          case 0: 48
          case 1: 59
          case 2: 65
          case 3: 62
    48  getstatic System.out : PrintStream [16]
    51  ldc <String "FINALLY:"> [35]
    53  invokevirtual PrintStream.print(String) : void [37]
    56  ldc <String "OK"> [42]
    58  areturn
    59  goto 4
    62  goto 48
    65  goto 4
    68  astore_2
    69  getstatic System.out : PrintStream [16]
    72  ldc <String "FINALLY:"> [35]
    74  invokevirtual PrintStream.print(String) : void [37]
    77  aload_2
    78  athrow
      Exception Table:
        [pc: 0, pc: 4] -> 68 when : any
        [pc: 15, pc: 48] -> 68 when : any
        [pc: 59, pc: 68] -> 68 when : any


Reuse occurs as soon as the sequence is the strictly the same:
- same break/continue target label
- return from void method
- same constant being returned

Other situations are not optimizable, since the returned value needs to be cached before finally runs, and thus each sequence may denote a different cached value.
Comment 7 Philipe Mulet CLA 2006-02-23 13:16:19 EST
WITHOUT optimization, the last test case would produce:

  // Method descriptor #26 ([Ljava/lang/String;)Ljava/lang/String;
  // Stack: 2, Locals: 3
  String foo(String[] args);
      0  aload_1 [args]
      1  ifnonnull 15
      4  getstatic System.out : PrintStream [16]
      7  ldc <String "FINALLY:"> [35]
      9  invokevirtual PrintStream.print(String) : void [37]
     12  ldc <String "KO"> [40]
     14  areturn
     15  aload_1 [args]
     16  arraylength
     17  tableswitch default: 81
          case 0: 48
          case 1: 59
          case 2: 81
          case 3: 70
     48  getstatic System.out : PrintStream [16]
     51  ldc <String "FINALLY:"> [35]
     53  invokevirtual PrintStream.print(String) : void [37]
     56  ldc <String "OK"> [42]
     58  areturn
     59  getstatic System.out : PrintStream [16]
     62  ldc <String "FINALLY:"> [35]
     64  invokevirtual PrintStream.print(String) : void [37]
     67  ldc <String "KO"> [40]
     69  areturn
     70  getstatic System.out : PrintStream [16]
     73  ldc <String "FINALLY:"> [35]
     75  invokevirtual PrintStream.print(String) : void [37]
     78  ldc <String "OK"> [42]
     80  areturn
     81  getstatic System.out : PrintStream [16]
     84  ldc <String "FINALLY:"> [35]
     86  invokevirtual PrintStream.print(String) : void [37]
     89  ldc <String "KO"> [40]
     91  areturn
     92  astore_2
     93  getstatic System.out : PrintStream [16]
     96  ldc <String "FINALLY:"> [35]
     98  invokevirtual PrintStream.print(String) : void [37]
    101  aload_2
    102  athrow
      Exception Table:
        [pc: 0, pc: 4] -> 92 when : any
        [pc: 15, pc: 48] -> 92 when : any
Comment 8 Philipe Mulet CLA 2006-02-23 15:28:16 EST
In presence of multiple scopes with different local variables, the optimization is causing grief.
e.g. 
public class X {
	public void save() {
		int a = 3;
		try {
			Object warnings = null;
			try {
				Object contexts = null;
				try {
					System.out.println(warnings);
					return;
				} catch (NullPointerException npe) {
					System.out.println(contexts);
					return;
				}
			} catch (Exception e) {
				return;
			}
		} catch (Exception e) {
			int dummy1 = 11;
			System.out.println(dummy1);
			int dummy2 = 12;
			System.out.println(dummy2);
			return;
		} finally {
			int b = 4;
			System.out.println("#save -> " + b + a);
		}
	}

	public static void main(String[] args) {
		new X().save();
	}
}

produces

  // Method descriptor #6 ()V
  // Stack: 4, Locals: 7
  public void save();
      0  iconst_3
      1  istore_1 [a]
      2  aconst_null
      3  astore_2 [warnings]
      4  aconst_null
      5  astore_3 [contexts]
      6  getstatic System.out : PrintStream [15]
      9  aload_2 [warnings]
     10  invokevirtual PrintStream.println(Object) : void [21]

SUBROUTINE
     13  iconst_4
     14  istore 6 [b]
     16  getstatic System.out : PrintStream [15]
     19  new StringBuilder [27]
     22  dup
     23  ldc <String "#save -> "> [29]
     25  invokespecial StringBuilder(String) [31]
     28  iload 6 [b]
     30  invokevirtual StringBuilder.append(int) : StringBuilder [34]
     33  iload_1 [a]
     34  invokevirtual StringBuilder.append(int) : StringBuilder [34]
     37  invokevirtual StringBuilder.toString() : String [38]
     40  invokevirtual PrintStream.println(String) : void [42]
     43  return

CATCH NULL POINTER EXCEPTION
     44  astore 4 [npe]
     46  getstatic System.out : PrintStream [15]
     49  aload_3 [contexts]
     50  invokevirtual PrintStream.println(Object) : void [21]
     53  goto 13

CATCH EXCEPTION
     56  astore_3 [e]
     57  goto 13

CATCH EXCEPTION
     60  astore_2 [e]
     61  bipush 11
     63  istore_3 [dummy1]
     64  getstatic System.out : PrintStream [15]
     67  iload_3 [dummy1]
     68  invokevirtual PrintStream.println(int) : void [44]
     71  bipush 12
     73  istore 4 [dummy2]
     75  getstatic System.out : PrintStream [15]
     78  iload 4 [dummy2]
     80  invokevirtual PrintStream.println(int) : void [44]
     83  goto 13

CATCH ANY
     86  astore 5
     88  iconst_4
     89  istore 6 [b]
     91  getstatic System.out : PrintStream [15]
     94  new StringBuilder [27]
     97  dup
     98  ldc <String "#save -> "> [29]
    100  invokespecial StringBuilder(String) [31]
    103  iload 6 [b]
    105  invokevirtual StringBuilder.append(int) : StringBuilder [34]
    108  iload_1 [a]
    109  invokevirtual StringBuilder.append(int) : StringBuilder [34]
    112  invokevirtual StringBuilder.toString() : String [38]
    115  invokevirtual PrintStream.println(String) : void [42]
    118  aload 5
    120  athrow
      Exception Table:
        [pc: 6, pc: 44] -> 44 when : java.lang.NullPointerException
        [pc: 4, pc: 56] -> 56 when : java.lang.Exception
        [pc: 2, pc: 13] -> 60 when : java.lang.Exception
        [pc: 43, pc: 60] -> 60 when : java.lang.Exception
        [pc: 2, pc: 13] -> 86 when : any
        [pc: 44, pc: 86] -> 86 when : any
      Line numbers:
        [pc: 0, line: 3]
        [pc: 2, line: 5]
        [pc: 4, line: 7]
        [pc: 6, line: 9]
        [pc: 13, line: 25]
        [pc: 16, line: 26]
        [pc: 43, line: 10]
        [pc: 44, line: 11]
        [pc: 46, line: 12]
        [pc: 53, line: 13]
        [pc: 56, line: 15]
        [pc: 57, line: 16]
        [pc: 60, line: 18]
        [pc: 61, line: 19]
        [pc: 64, line: 20]
        [pc: 71, line: 21]
        [pc: 75, line: 22]
        [pc: 83, line: 23]
        [pc: 86, line: 24]
        [pc: 88, line: 25]
        [pc: 91, line: 26]
        [pc: 118, line: 27]

and verifyError:
java.lang.VerifyError: (class: X, method: save signature: ()V) Register 3 contains wrong type
	at java.lang.Class.forName0(Native Method)
	at java.lang.Class.forName(Class.java:164)
	at Compile.main(Compile.java:108)
Comment 9 Philipe Mulet CLA 2006-02-23 15:29:39 EST
The problem is that when entering subroutine (pc:13), register 3 may contain various local variable contents, which are not tolerated by bytecode verifier (even though the register is not used within the shared block).
Comment 10 Philipe Mulet CLA 2006-02-25 17:20:04 EST
Problem solved. Issues where related to miscomputed exception handler ranges.
Optimization is also performed for "return null" (not a constant).

And optimization is performing independantly from inlineJSR mode, so it can slightly reduce code size in presence of JSR patterns:

10 jsr <finally>
   areturn
...
   jsr <finally>
   areturn

becomes:

10 jsr <finally>
   areturn
...
   goto 10

Added GenericTypeTest#test040-043.
Also tuned existing tests which got affected by reuse and exception handler changes.
Comment 11 David Audel CLA 2006-03-28 04:27:11 EST
Verified for 3.2 M6 using build I20060328-0010