Bug 232814 - [compiler] Testcase with "value1 - (-value2)" not working when value2 is final
Summary: [compiler] Testcase with "value1 - (-value2)" not working when value2 is final
Status: VERIFIED FIXED
Alias: None
Product: JDT
Classification: Eclipse Project
Component: Core (show other bugs)
Version: 3.3.1   Edit
Hardware: PC Windows XP
: P3 major (vote)
Target Milestone: 3.4 RC3   Edit
Assignee: Philipe Mulet CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2008-05-19 11:00 EDT by Dominik Stadler CLA
Modified: 2008-05-30 11:07 EDT (History)
3 users (show)

See Also:
Olivier_Thomann: review+
maxime_daniel: review+
jerome_lanneluc: review+


Attachments
Proposed fix (3.52 KB, patch)
2008-05-19 22:16 EDT, Olivier Thomann CLA
no flags Details | Diff
Proposed patch (5.46 KB, patch)
2008-05-23 07:36 EDT, Philipe Mulet CLA
no flags Details | Diff
Proposed patch (8.08 KB, patch)
2008-05-23 07:43 EDT, Philipe Mulet CLA
no flags Details | Diff
Patch for 3.3.x (7.14 KB, patch)
2008-05-23 09:58 EDT, Philipe Mulet CLA
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Dominik Stadler CLA 2008-05-19 11:00:49 EDT
Build ID: M20071023-1652

Steps To Reproduce:
Hi, 

We found a problem with the way the Java compiler inside Eclipse works. The test case below shows a problem with doing subtractions with negative values if one of the variables is final.

If I run the same test outside of Eclipse it works fine! It fails inside Eclipse regardless of which JDK I use for running the test (JDK 5,6, JRockit 5, IBM JDK 5).

The only difference is a "final". Looking at the bytecode it seems both versions result in different bytecode, where one is missing a "times -1" and thus results in the incorrect result.

Thanks... Dominik.

public class TestSimpleThings extends TestCase
{
// case that works
public void testShort1()
{
short min = Short.MIN_VALUE;
int num = -32638;
num = num - min;

// -32638 - (-32768) should be 130, which it is here
assertEquals(130, num);
}

// case that should work, but doesn't!!
public void testShort2()
{
final short min = Short.MIN_VALUE;
int num = -32638;
num = num - min;

// -32638 - (-32768) should be equal to 130, even if "min" is final!
assertEquals("Here the problem manifests, the compiler incorrectly optimizes! " +
"-32638 - (-32768) should still be 130, but is reported as -65406 here!",
130, num);
}
}

More information:
The following is a detailed analysis from one of our developers:
-----------------------------------------------------------


In fact, not short, or the conversion is the problem – the problem is the combination of the final keyword with an integer or short value <= -32768!


Having a look at the bytecode,


short min = Short.MIN_VALUE;

int num = -32638;

num = num - min;

System.out.println(num);
L0 (0)

LINENUMBER 3 L0

SIPUSH -32768

ISTORE 1: min

L1 (3)

LINENUMBER 4 L1

SIPUSH -32638

ISTORE 2: num

L2 (6)

LINENUMBER 5 L2

ILOAD 2: num

ILOAD 1: min

ISUB

ISTORE 2: num

L3 (11)

LINENUMBER 6 L3

GETSTATIC System.out : PrintStream

ILOAD 2: num

INVOKEVIRTUAL PrintStream.println(int) : void

L4 (15)

LINENUMBER 7 L4

RETURN

L5 (17)

LOCALVARIABLE args String[] L0 L5 0

LOCALVARIABLE min short L1 L5 1

LOCALVARIABLE num int L2 L5 2

MAXSTACK = 2

MAXLOCALS = 3

final short min = Short.MIN_VALUE;

int num = -32638;

num = num - min;

System.out.println(num);
L0 (0)

LINENUMBER 3 L0

SIPUSH -32768

ISTORE 1: min

L1 (3)

LINENUMBER 4 L1

SIPUSH -32638

ISTORE 2: num

L2 (6)

LINENUMBER 5 L2

IINC 2: num -32768

L3 (8)

LINENUMBER 6 L3

GETSTATIC System.out : PrintStream

ILOAD 2: num

INVOKEVIRTUAL PrintStream.println(int) : void

L4 (12)

LINENUMBER 7 L4

RETURN

L5 (14)

LOCALVARIABLE args String[] L0 L5 0

LOCALVARIABLE min short L1 L5 1

LOCALVARIABLE num int L2 L5 2

MAXSTACK = 2

MAXLOCALS = 3

As you see, without final, Java is using ISUB, with final it‘s using IINC – a valid optimization – however, it must use IINC with the number *-1, which for some reason doesn’t work for numbers <= -32768


To illustrate it further:

final short min = Short.MAX_VALUE;

int num = -32638;

num = num - min;

System.out.println(num);
L0 (0)

LINENUMBER 3 L0

SIPUSH 32767

ISTORE 1: min

L1 (3)

LINENUMBER 4 L1

SIPUSH -32638

ISTORE 2: num

L2 (6)

LINENUMBER 5 L2

IINC 2: num -32767

L3 (8)

LINENUMBER 6 L3

GETSTATIC System.out : PrintStream

ILOAD 2: num

INVOKEVIRTUAL PrintStream.println(int) : void

L4 (12)

LINENUMBER 7 L4

RETURN

L5 (14)

LOCALVARIABLE args String[] L0 L5 0

LOCALVARIABLE min short L1 L5 1

LOCALVARIABLE num int L2 L5 2

MAXSTACK = 2

MAXLOCALS = 3

Here the compiler optimizes correctly and uses IINC with Short.MAX_VALUE * -1.


final int min = Short.MIN_VALUE;

final int num = -32638;

System.out.println(num - min);
L0 (0)

LINENUMBER 3 L0

SIPUSH -32768

ISTORE 1: min

L1 (3)

LINENUMBER 4 L1

SIPUSH -32638

ISTORE 2: num

L2 (6)

LINENUMBER 5 L2

GETSTATIC System.out : PrintStream

SIPUSH 130

INVOKEVIRTUAL PrintStream.println(int) : void

L3 (10)

LINENUMBER 6 L3

RETURN

L4 (12)

LOCALVARIABLE args String[] L0 L4 0

LOCALVARIABLE min int L1 L4 1

LOCALVARIABLE num int L2 L4 2

MAXSTACK = 2

MAXLOCALS = 3

Another time, the compiler optimizes correctly and inserts the result (130) in the bytecode.


final short min = Short.MIN_VALUE;

int num = -32638;

System.out.println(num - min);
L0 (0)

LINENUMBER 3 L0

SIPUSH -32768

ISTORE 1: min

L1 (3)

LINENUMBER 4 L1

SIPUSH -32638

ISTORE 2: num

L2 (6)

LINENUMBER 6 L2

GETSTATIC System.out : PrintStream

ILOAD 2: num

SIPUSH -32768

ISUB

INVOKEVIRTUAL PrintStream.println(int) : void

L3 (12)

LINENUMBER 7 L3

RETURN

L4 (14)

LOCALVARIABLE args String[] L0 L4 0

LOCALVARIABLE min short L1 L4 1

LOCALVARIABLE num int L2 L4 2

MAXSTACK = 3

MAXLOCALS = 3

final int min = -32768;

int num = -32638;

int num2 = num - min;

System.out.println(num2);
L0 (0)

LINENUMBER 3 L0

SIPUSH -32768

ISTORE 1: min

L1 (3)

LINENUMBER 4 L1

SIPUSH -32638

ISTORE 2: num

L2 (6)

LINENUMBER 5 L2

ILOAD 2: num

SIPUSH -32768

ISUB

ISTORE 3: num2

L3 (11)

LINENUMBER 6 L3

GETSTATIC System.out : PrintStream

ILOAD 3: num2

INVOKEVIRTUAL PrintStream.println(int) : void

L4 (15)

LINENUMBER 7 L4

RETURN

L5 (17)

LOCALVARIABLE args String[] L0 L5 0

LOCALVARIABLE min int L1 L5 1

LOCALVARIABLE num int L2 L5 2

LOCALVARIABLE num2 int L3 L5 3

MAXSTACK = 2

MAXLOCALS = 4

final int min = -32767;

int num = -32638;

num = num - min;

System.out.println(num);
L0 (0)

LINENUMBER 3 L0

SIPUSH -32767

ISTORE 1: min

L1 (3)

LINENUMBER 4 L1

SIPUSH -32638

ISTORE 2: num

L2 (6)

LINENUMBER 5 L2

IINC 2: num 32767

L3 (8)

LINENUMBER 6 L3

GETSTATIC System.out : PrintStream

ILOAD 2: num

INVOKEVIRTUAL PrintStream.println(int) : void

L4 (12)

LINENUMBER 7 L4

RETURN

L5 (14)

LOCALVARIABLE args String[] L0 L5 0

LOCALVARIABLE min int L1 L5 1

LOCALVARIABLE num int L2 L5 2

MAXSTACK = 2

MAXLOCALS = 3

You can also replace the short-keyword by int, the result stays the same. However, it works with long, byte and char as they use other operations - the only affected data types are short and int.


Conclusion: Java optimizes additions incorrectly if someone subtracts a negative final value <= -32768 from a non-final value.


The following conditions must hold:

1. The subtrahend must be final

2. The subtrahend must be negative

3. The subtrahend must be <=-32768

4. The minuend must be non-final

5. The result must be assigned to the minuend (either num = num – min or num -= min)
Comment 1 Olivier Thomann CLA 2008-05-19 22:15:51 EDT
Reproduced with 3.4RC1.
The problem is that we take iinc of -increment where increment is Short.MIN_VALUE.
Comment 2 Olivier Thomann CLA 2008-05-19 22:16:28 EDT
Created attachment 100988 [details]
Proposed fix

This seems to fix the problem.
Comment 3 Philipe Mulet CLA 2008-05-23 07:36:05 EDT
Created attachment 101716 [details]
Proposed patch

I would rather suggest this version of the fix. It doesn't single out Short.MIN_VALUE, and simply treats it as a 16bit representation problem. The problem was that for minus operator, we didn't consider -increment early enough for 16bit representation check.
Comment 4 Philipe Mulet CLA 2008-05-23 07:43:26 EDT
Created attachment 101718 [details]
Proposed patch

Same patch augmented with a testcase
Comment 5 Philipe Mulet CLA 2008-05-23 07:44:24 EDT
Added NumericTest#test005
Comment 6 Philipe Mulet CLA 2008-05-23 07:46:09 EDT
Fix is quite trivial, and consequences are quite nasty from compiler standpoint (wrong arithmetics !?).
Suggest we try to address it for 3.4RC3, and backport to 3.3.x.
Comment 7 Maxime Daniel CLA 2008-05-23 08:25:33 EDT
Patch looks good. Pointed a small glitch it test case to Philippe (the testInt series of methods should declare an int), other than that +1.
Comment 8 Philipe Mulet CLA 2008-05-23 09:58:21 EDT
Created attachment 101733 [details]
Patch for 3.3.x

3.3.x patch, including Maxime's test correction
Comment 9 Jerome Lanneluc CLA 2008-05-23 10:56:29 EDT
Patch looks good. Note that the following test looks unnecessary:
(assignConstant.typeID() != TypeIds.T_float) // only for integral types
&& (assignConstant.typeID() != TypeIds.T_double)

as this would have been caught before (one cannot assign a float or a double to an int local). But it is harmless and it could be removed post 3.4.
Comment 10 Philipe Mulet CLA 2008-05-23 11:25:03 EDT
Yes, this extra test has always been in, and I agree it feels useless.
Comment 11 Olivier Thomann CLA 2008-05-23 18:08:59 EDT
+1
Comment 12 Philipe Mulet CLA 2008-05-26 06:48:28 EDT
Released for 3.4RC3.
Fixed
Comment 13 Philipe Mulet CLA 2008-05-26 06:57:54 EDT
Released in 3.3.x maintenance branch.
Comment 14 David Audel CLA 2008-05-30 11:07:00 EDT
Verified for 3.4RC4 using build I20080530-0100