United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
JDK-7196857 : Int array initializes with nonzero elements.

Details
Type:
Bug
Submit Date:
2012-09-07
Status:
In Progress
Updated Date:
2014-07-31
Project Name:
JDK
Resolved Date:
Component:
hotspot
OS:
generic
Sub-Component:
compiler
CPU:
generic
Priority:
P2
Resolution:
Unresolved
Affected Versions:
7
Targeted Versions:
9

Related Reports

Sub Tasks

Description
FULL PRODUCT VERSION :
java version: all versions from 1.7.0_04 up to 1.7.0_10
Java HotSpot(TM) 64-Bit Server VM


FULL OS VERSION :
All amd64  Linux, Windows, MacOS distributions.  Not tested on x86 OSes.

A DESCRIPTION OF THE PROBLEM :
Primitive integer array has nonzero elements just after allocation. This situation is in the contradiction with JLS.  Such a behavior occurs after jvm performs the compilation of the corresponding place of the code and only if corresponding code contains Arrays.fill(..) statement somewhere after array initialization (but not just after initialization). For more details see simple code example below.

THE PROBLEM WAS REPRODUCIBLE WITH -Xint FLAG: No

THE PROBLEM WAS REPRODUCIBLE WITH -server FLAG: Yes

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Just run the code listed below.

EXPECTED VERSUS ACTUAL BEHAVIOR :
Expected: Build success
Actual: RuntimeException
REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
public class JvmBug {
public static void main(String[] args) {
        int[] a;
        int n = 0;
        for (int i = 0; i < 100000000; ++i) {
            a = new int[10];
            for (int f : a)
                if (f != 0)
                    throw new RuntimeException("Array just after allocation: "+  Arrays.toString(a));
            Arrays.fill(a, 0);
            for (int j = 0; j < a.length; ++j)
                a[j] = (n - j)*i;
            for (int f : a)
                n += f;
        }
        System.out.println(n);
    }
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Add Arrays.fill(a,0) just after array initialization.

                                    

Comments
@Peter:  java.util.Arrays.parallelSetAll gives everything except the allocation part.
                                     
2014-07-31
@John: Ah, I didn't know about that one.  Thanks.  But the important point is to keep the allocation and the initialization together so that the runtime compiler doesn't have to work too hard to figure out that it doesn't need to zero the array before pointing parallelSetAll at the individual elements.  That is, I want a method in Arrays that combines the allocation and initialization so the compiler can know the semantics.
                                     
2014-07-31
It would be nice if I could supply code to initialize arrays.  The problem, obviously, is not to allow the reference to the array to escape before the array is completely initialized, or to read the elements of the array before they are initialized.  Based on some discussions with David Leibs, the following might be workable.

Define a class, somewhere in the JRE, that is the default initializer.  E.g., for initializing an array of doubles

    class Initializer {
        // The default initializer returns 0.0 for every element.
        double apply(int i) {
            return 0.0;
        }
    }

(and I'd need one of these for all the primitive types).  Then add a library method, e.g., to class Arrays

    public static double[] initializerDouble(int len, Initializer init) {
        final double[] result = new double[len];
        for (int i = 0; i < len; i += 1) {
            result[i] = init.apply(i);
        }
        return result;
    }

whose jobs is to allocate the array, but not expose it or its elements, until all the elements have been initialized.  Then I can use that as in

    double[] iArray = initializerDouble(7,
                                        new Initializer() {
                                            double apply(int i) {
                                                return (double) i;
                                            }
                                        });

to get an array whose elements are initialized with their offset in the array.  The point is that Initializer.apply(int) does *not* get a reference to the array, so it can't peek into it, or leak it, and if the initialization code exits abruptly, the reference to the array is dropped on the floor and thus not exposed. This does not work for Object[], because the garbage collector needs to see nulls or valid references in all the elements.

Since the Initializer instance is a closure, it could do arbitrary computations, using (final) variables from its scope, etc.  If I have to reify a big array to be the result of a computation, I can construct an Initializer that does the computation, rather than initialize all the elements to 0.0 and then reassign them all with the value of the computation.  One trip through the cache, etc.

Then I would want the runtime compiler to identify calls to InitializerDouble() and generate good code for it.  In the default case generate the code we have now to set all the elements to 0.0, or in the easy cases inlining the call to Initializer.apply() and unrolling the loop, inserting non-temporal prefetches, wide registers, etc.  Identifying the call seems easier than the pattern-matching in PhaseIdealLoop::match_fill_loop().  In my dreams it would parallelize the initialization of the array, since otherwise initializing a big array is a serial piece of what might otherwise be an embarrassingly parallel bunch of math on the individual elements, and Amdahl will come and bite my ankle.
                                     
2013-07-01
Peter B. Kessler wrote:

In src/share/vm/opto/loopTransform.cpp, the comment

     2404   // Process the loop looking for stores.  If there are multiple
     2405   // stores or extra control flow give at this point.

"give at this point" should say "give up at this point".  I would move that sentence down to where the code gives up, e.g.,

     2410     if (n->is_Store()) {
     2411       if (store != NULL) {
     +            //If there are multiple  stores give up at this point.
     2412         msg = "multiple stores";
     2413         break;
     2414       }
     ....
     2428     } else if (n->is_If() && n != head->loopexit()) {
     +          // If there is extra control flow give up at this point.
     2429       msg = "extra control flow";
     2430       msg_node = n;
     2431     }

There are several other reasons that code gives up (oop assignments, negative strides, etc.) that deserve equal documentation, which might mean not calling out these two specially.


                                     
2013-06-28
(Lots of run/debug options here for future reference, and in case anyone wants to check my work)

Something has changed in the processing upstream of the bug, and (in the debugger) it no longer comes near the code that might commit this error in the bug itself.  Compiling the JDK library for a normal run (i.e., -Xcomp) the path is taken, but not for the bug itself (here, the test was reorganized slightly into main and test, within a package, because failure to trigger the bug caused doubts about corner cases in CompileOnly behavior):

-Xcomp -XX:+UnlockDiagnosticVMOptions -XX:CompileOnly=pack/JvmBug.main,pack.JvmBug.test pack.JvmBug)

Breakpoints that should have triggered but did not:
LibraryCallKit::tightly_coupled_allocation(Node*, RegionNode*) at library_call.cpp:4951
LibraryCallKit::generate_arraycopy(TypePtr const*, BasicType, Node*, Node*, Node*, Node*, Node*, bool, bool, RegionNode*)
(gdb) break LibraryCallKit::inline_arraycopy
Breakpoint 4 at 0x102192e29: file library_call.cpp, line 4410.

To verify compilation at all, I set this breakpoint, which was triggered:
C2Compiler::compile_method(ciEnv*, ciMethod*, int) at c2compiler.cpp:122

Stack traces compiling the library confirmed that the breakpoints listed above should all be triggered before a call to tightly_coupled_allocation.
                                     
2013-06-19
Oddly enough, did not reproduce for me.  x86_64, Mac,

Java(TM) SE Runtime Environment (build 1.7.0_21-b12)
Java HotSpot(TM) 64-Bit Server VM (build 23.21-b01, mixed mode)

or with

java version "1.8.0-internal-fastdebug"
Java(TM) SE Runtime Environment (build 1.8.0-internal-fastdebug-dr2chase_2013_06_10_15_00-b00)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b37-fastdebug, mixed mode)

was this fixed accidentally?
I will continue to investigate.

                                     
2013-06-11
The problem is caused by: when optimizing Arrays.fill() calls, C2 checks if that call is tightly coupled with an array allocation and omits the implicit zeroing if it is; but it's missing necessary checks of the use of the implicit zero'd values, so it may mistakenly omit the zeroing even when there are uses of the array's contents in between the allocation site and the Arrays.fill() call.

The fix would have to enforce the check so that it respects the uses of implicit zero'd values.
                                     
2012-10-18
EVALUATION

The bug reproduces with the fill or with a zeroing for loop, but not without.

So compiler correctly matches a complete array fill, but the check for uses between the allocation and the fill is faulty.
                                     
2012-09-07



Hardware and Software, Engineered to Work Together