Summary: | [Compiler][1.5] Jsr inlining limitation in the compiler | ||
---|---|---|---|
Product: | [Eclipse Project] JDT | Reporter: | Mark Bottomley <Mark_Bottomley> |
Component: | Core | Assignee: | Philipe Mulet <philippe_mulet> |
Status: | VERIFIED FIXED | QA Contact: | |
Severity: | normal | ||
Priority: | P3 | ||
Version: | 3.2 | ||
Target Milestone: | 3.2 M6 | ||
Hardware: | All | ||
OS: | All | ||
Whiteboard: |
Description
Mark Bottomley
2006-02-20 14:02:12 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. 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) 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. 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. 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. 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. 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 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) 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). 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. Verified for 3.2 M6 using build I20060328-0010 |