--- /dev/null Thu Feb 23 20:40:44 2012 +++ new/test/compiler/7145024/Generate.java Thu Feb 23 20:40:44 2012 @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +import java.io.*; + +// Exercise JavaCritical native method signatures + +public class Generate { + public static void main(String[] args) throws Exception { + PrintStream j = new PrintStream(new FileOutputStream("JNITest.java")); + generateJava(j); + j.close(); + j = new PrintStream(new FileOutputStream("JNITest.c")); + generateJNI(j); + generateJavaCritical(j); + j.close(); + } + + static void generateJava(PrintStream out) { + out.println("public class JNITest {"); + for (int i = 0; i < 255; i++) { + out.printf(" static native void nativeTest%d(", i); + for (int b = 0; b < 8; b++) { + out.printf("%s a%d", (i & (1 << b)) == 0 ? "int" : "byte[]", b); + if (b < 7) { + out.print(", "); + } else { + out.println(");"); + } + } + } + out.println(" static byte[] array0 = new byte[1024];"); + out.println(" static {"); + out.println(" System.loadLibrary(\"jnitest\");"); + out.println(" }"); + + out.println(" public static void main(String[] args) {"); + out.println(" for (int i = 0; i < 20000; i++) {"); + for (int i = 0; i < 255; i++) { + out.printf(" nativeTest%d(", i); + for (int b = 0; b < 8; b++) { + if ((i & (1 << b)) == 0) { + out.printf("(0xfeafd00f + %d)", b); + } else { + out.printf("array0"); + } + if (b < 7) { + out.print(", "); + } else { + out.println(");"); + } + } + } + out.println(" }"); + out.println(" }"); + out.println("}"); + } + + static void generateJNI(PrintStream out) { + out.println("#include \"JNITest.h\""); + out.println(); + for (int i = 0; i < 255; i++) { + out.printf("JNIEXPORT void JNICALL Java_JNITest_nativeTest%d(JNIEnv* env, jclass cl, ", i); + for (int b = 0; b < 8; b++) { + out.printf("%s a%d", (i & (1 << b)) == 0 ? "jint" : "jbyteArray", b); + if (b < 7) { + out.print(", "); + } else { + out.println(") {\n}\n\n"); + } + } + } + } + + static void generateJavaCritical(PrintStream out) { + for (int i = 0; i < 255; i++) { + out.printf("JNIEXPORT void JNICALL JavaCritical_JNITest_nativeTest%d(", i); + for (int b = 0; b < 8; b++) { + if ((i & (1 << b)) == 0) { + out.printf("%s a%d", "jint", b); + } else { + out.printf("int a%dlen, %s a%d", b, "jbyte*", b); + } + if (b < 7) { + out.print(", "); + } else { + out.println(") {\n"); + } + } + for (int b = 0; b < 8; b++) { + if ((i & (1 << b)) == 0) { + out.printf(" if (a%d != 0x%x) fprintf(stderr, \"a%d %%d != %d in nativeTest%d\\n\", a%d);\n", + b, b + 0xfeafd00f, b, b + 0xfeafd00f, i, b); + } else { + out.printf(" a%d[0] = 1;\n", b); + } + } + out.println("}\n\n"); + + } + } +} --- /dev/null Thu Feb 23 20:40:44 2012 +++ new/test/compiler/7145024/Test7145024.sh Thu Feb 23 20:40:44 2012 @@ -0,0 +1,133 @@ +#!/bin/sh +# +# Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# + +# For now this is constructed as a manual test since there's no guarantee that a +# C compiler is available in the test environment + +## +## @test Test7145024.sh +## @bug 7145024 +## @summary Crashes in ucrypto related to C2 +## @run shell/manual Test7145024.sh +## + +if [ "${TESTSRC}" = "" ] +then TESTSRC=. +fi + +if [ "${TESTJAVA}" = "" ] +then + PARENT=`dirname \`which java\`` + TESTJAVA=`dirname ${PARENT}` + echo "TESTJAVA not set, selecting " ${TESTJAVA} + echo "If this is incorrect, try setting the variable manually." +fi + +# set platform-dependent variables +OS=`uname -s` +case "$OS" in + *BSD) + FS="/" + CC=gcc + CFLAGS="-fPIC -shared" + ;; + Linux) + FS="/" + CC=gcc + CFLAGS="-fPIC -shared" + OS=linux + ;; + SunOS) + FS="/" + CC=cc + CFLAGS="-G -KPIC" + OS=solaris + COMPILE64=-m64 + ;; + Windows_*) + FS="\\" + echo "Test passed; not currently valid on Windows" + exit 0; + ;; + * ) + echo "Unrecognized system!" + exit 1; + ;; +esac + + + +LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH +export LD_LIBRARY_PATH + +THIS_DIR=`pwd` + +cp ${TESTSRC}${FS}Generate.java ${THIS_DIR} + +set -e + +${TESTJAVA}${FS}bin${FS}java -fullversion + +${TESTJAVA}${FS}bin${FS}javac Generate.java +${TESTJAVA}${FS}bin${FS}java Generate +${TESTJAVA}${FS}bin${FS}javac JNITest.java +${TESTJAVA}${FS}bin${FS}javah JNITest + +if [ "${COMPILE64}" != "" ]; then + $CC ${COMPILE64} -g ${CFLAGS} -o libjnitest.so -I${TESTJAVA}/include -I${TESTJAVA}/include/$OS JNITest.c + ${TESTJAVA}${FS}bin${FS}java -d64 JNITest + exitcode=$? + if [ $exitcode -ne 0 ]; then + exit $exitcode + fi +fi + +$CC -g ${CFLAGS} -o libjnitest.so -I${TESTJAVA}/include -I${TESTJAVA}/include/$OS JNITest.c + +EXTRA_OPTS="-XX:+IgnoreUnrecognizedVMOptions" + +${TESTJAVA}${FS}bin${FS}java -server ${EXTRA_OPTS} JNITest +exitcode=$? +if [ $exitcode -ne 0 ]; then + exit $exitcode +fi + +${TESTJAVA}${FS}bin${FS}java -client ${EXTRA_OPTS} -XX:UseSSE=1 JNITest +exitcode=$? +if [ $exitcode -ne 0 ]; then + exit $exitcode +fi +${TESTJAVA}${FS}bin${FS}java -client ${EXTRA_OPTS} -XX:UseSSE=0 JNITest +exitcode=$? +if [ $exitcode -ne 0 ]; then + exit $exitcode +fi + +${TESTJAVA}${FS}bin${FS}java -client ${EXTRA_OPTS} JNITest +exitcode=$? +if [ $exitcode -ne 0 ]; then + exit $exitcode +fi + +exit $? --- old/src/cpu/x86/vm/sharedRuntime_x86_64.cpp Thu Feb 23 20:40:44 2012 +++ new/src/cpu/x86/vm/sharedRuntime_x86_64.cpp Thu Feb 23 20:40:44 2012 @@ -1181,14 +1181,13 @@ BasicType* in_sig_bt) { // if map is non-NULL then the code should store the values, // otherwise it should load them. - int handle_index = 0; + int slot = arg_save_area; // Save down double word first for ( int i = 0; i < total_in_args; i++) { if (in_regs[i].first()->is_XMMRegister() && in_sig_bt[i] == T_DOUBLE) { - int slot = handle_index * VMRegImpl::slots_per_word + arg_save_area; int offset = slot * VMRegImpl::stack_slot_size; - handle_index += 2; - assert(handle_index <= stack_slots, "overflow"); + slot += VMRegImpl::slots_per_word; + assert(slot <= stack_slots, "overflow"); if (map != NULL) { __ movdbl(Address(rsp, offset), in_regs[i].first()->as_XMMRegister()); } else { @@ -1197,10 +1196,8 @@ } if (in_regs[i].first()->is_Register() && (in_sig_bt[i] == T_LONG || in_sig_bt[i] == T_ARRAY)) { - int slot = handle_index * VMRegImpl::slots_per_word + arg_save_area; int offset = slot * VMRegImpl::stack_slot_size; - handle_index += 2; - assert(handle_index <= stack_slots, "overflow"); + slot += VMRegImpl::slots_per_word; if (map != NULL) { __ movq(Address(rsp, offset), in_regs[i].first()->as_Register()); if (in_sig_bt[i] == T_ARRAY) { @@ -1214,9 +1211,9 @@ // Save or restore single word registers for ( int i = 0; i < total_in_args; i++) { if (in_regs[i].first()->is_Register()) { - int slot = handle_index++ * VMRegImpl::slots_per_word + arg_save_area; int offset = slot * VMRegImpl::stack_slot_size; - assert(handle_index <= stack_slots, "overflow"); + slot++; + assert(slot <= stack_slots, "overflow"); // Value is in an input register pass we must flush it to the stack const Register reg = in_regs[i].first()->as_Register(); @@ -1241,9 +1238,9 @@ } } else if (in_regs[i].first()->is_XMMRegister()) { if (in_sig_bt[i] == T_FLOAT) { - int slot = handle_index++ * VMRegImpl::slots_per_word + arg_save_area; int offset = slot * VMRegImpl::stack_slot_size; - assert(handle_index <= stack_slots, "overflow"); + slot++; + assert(slot <= stack_slots, "overflow"); if (map != NULL) { __ movflt(Address(rsp, offset), in_regs[i].first()->as_XMMRegister()); } else { @@ -1368,6 +1365,175 @@ __ bind(done); } + +class ComputeMoveOrder: public StackObj { + class MoveOperation: public ResourceObj { + friend class ComputeMoveOrder; + private: + VMRegPair _src; + VMRegPair _dst; + int _src_index; + int _dst_index; + bool _processed; + MoveOperation* _next; + MoveOperation* _prev; + + static int get_id(VMRegPair r) { + return r.first()->value(); + } + + public: + MoveOperation(int src_index, VMRegPair src, int dst_index, VMRegPair dst): + _src(src) + , _src_index(src_index) + , _dst(dst) + , _dst_index(dst_index) + , _next(NULL) + , _prev(NULL) + , _processed(false) { + } + + VMRegPair src() const { return _src; } + int src_id() const { return get_id(src()); } + int src_index() const { return _src_index; } + VMRegPair dst() const { return _dst; } + void set_dst(int i, VMRegPair dst) { _dst_index = i, _dst = dst; } + int dst_index() const { return _dst_index; } + int dst_id() const { return get_id(dst()); } + MoveOperation* next() const { return _next; } + MoveOperation* prev() const { return _prev; } + void set_processed() { _processed = true; } + bool is_processed() const { return _processed; } + + // insert + void break_cycle(VMRegPair temp_register) { + // create a new store following the last store + // to move from the temp_register to the original + MoveOperation* new_store = new MoveOperation(-1, temp_register, dst_index(), dst()); + + // break the cycle of links and insert new_store at the end + // break the reverse link. + MoveOperation* p = prev(); + if (p->_next != NULL) { + p->_next->_prev = NULL; + } + p->_next = new_store; + new_store->_prev = p; + + // change the original store to save it's value in the temp. + set_dst(-1, temp_register); + } + + void link(GrowableArray& killer) { + // link this store in front the store that it depends on + MoveOperation* n = killer.at_grow(src_id(), NULL); + if (n != NULL) { + assert(_next == NULL && n->_prev == NULL, "shouldn't have been set yet"); + _next = n; + n->_prev = this; + } + } + }; + + private: + GrowableArray edges; + + public: + ComputeMoveOrder(int total_in_args, VMRegPair* in_regs, int total_c_args, VMRegPair* out_regs, + BasicType* in_sig_bt, GrowableArray& arg_order, VMRegPair tmp_vmreg) { + // Move operations where the dest is the stack can all be + // scheduled first since they can't interfere with the other moves. + for (int i = total_in_args - 1, c_arg = total_c_args - 1; i >= 0; i--, c_arg--) { + if (in_sig_bt[i] == T_ARRAY) { + c_arg--; + if (out_regs[c_arg].first()->is_stack() && + out_regs[c_arg + 1].first()->is_stack()) { + arg_order.push(i); + arg_order.push(c_arg); + } else { + if (out_regs[c_arg].first()->is_stack() || + in_regs[i].first() == out_regs[c_arg].first()) { + add_edge(i, in_regs[i].first(), c_arg, out_regs[c_arg + 1]); + } else { + add_edge(i, in_regs[i].first(), c_arg, out_regs[c_arg]); + } + } + } else if (in_sig_bt[i] == T_VOID) { + arg_order.push(i); + arg_order.push(c_arg); + } else { + if (out_regs[c_arg].first()->is_stack() || + in_regs[i].first() == out_regs[c_arg].first()) { + arg_order.push(i); + arg_order.push(c_arg); + } else { + add_edge(i, in_regs[i].first(), c_arg, out_regs[c_arg]); + } + } + } + // Break any cycles in the register moves and emit the in the + // proper order. + GrowableArray* stores = get_store_order(tmp_vmreg); + for (int i = 0; i < stores->length(); i++) { + arg_order.push(stores->at(i)->src_index()); + arg_order.push(stores->at(i)->dst_index()); + } + } + + // Collected all the move operations + void add_edge(int src_index, VMRegPair src, int dst_index, VMRegPair dst) { + if (src.first() == dst.first()) return; + edges.append(new MoveOperation(src_index, src, dst_index, dst)); + } + + // Walk the edges breaking cycles between moves. The result list + // can be walked in order to produce the proper set of loads + GrowableArray* get_store_order(VMRegPair temp_register) { + // Record which moves kill which values + GrowableArray killer; + for (int i = 0; i < edges.length(); i++) { + MoveOperation* s = edges.at(i); + assert(killer.at_grow(s->dst_id(), NULL) == NULL, "only one killer"); + killer.at_put_grow(s->dst_id(), s, NULL); + } + assert(killer.at_grow(MoveOperation::get_id(temp_register), NULL) == NULL, + "make sure temp isn't in the registers that are killed"); + + // create links between loads and stores + for (int i = 0; i < edges.length(); i++) { + edges.at(i)->link(killer); + } + + // at this point, all the move operations are chained together + // in a doubly linked list. Processing it backwards finds + // the beginning of the chain, forwards finds the end. If there's + // a cycle it can be broken at any point, so pick an edge and walk + // backward until the list ends or we end where we started. + GrowableArray* stores = new GrowableArray(); + for (int e = 0; e < edges.length(); e++) { + MoveOperation* s = edges.at(e); + if (!s->is_processed()) { + MoveOperation* start = s; + // search for the beginning of the chain or cycle + while (start->prev() != NULL && start->prev() != s) { + start = start->prev(); + } + if (start->prev() == s) { + start->break_cycle(temp_register); + } + // walk the chain forward inserting to store list + while (start != NULL) { + stores->append(start); + start->set_processed(); + start = start->next(); + } + } + } + return stores; + } +}; + + // --------------------------------------------------------------------------- // Generate a native wrapper for a given method. The method takes arguments // in the Java compiled code convention, marshals them to the native @@ -1488,12 +1654,12 @@ if (in_regs[i].first()->is_Register()) { const Register reg = in_regs[i].first()->as_Register(); switch (in_sig_bt[i]) { - case T_ARRAY: case T_BOOLEAN: case T_BYTE: case T_SHORT: case T_CHAR: case T_INT: single_slots++; break; + case T_ARRAY: case T_LONG: double_slots++; break; default: ShouldNotReachHere(); } @@ -1690,36 +1856,43 @@ #endif /* ASSERT */ - if (is_critical_native) { - // The mapping of Java and C arguments passed in registers are - // rotated by one, which helps when passing arguments to regular - // Java method but for critical natives that creates a cycle which - // can cause arguments to be killed before they are used. Break - // the cycle by moving the first argument into a temporary - // register. - for (int i = 0; i < total_c_args; i++) { - if (in_regs[i].first()->is_Register() && - in_regs[i].first()->as_Register() == rdi) { - __ mov(rbx, rdi); - in_regs[i].set1(rbx->as_VMReg()); - } - } - } - // This may iterate in two different directions depending on the // kind of native it is. The reason is that for regular JNI natives // the incoming and outgoing registers are offset upwards and for // critical natives they are offset down. - int c_arg = total_c_args - 1; - int stride = -1; - int init = total_in_args - 1; - if (is_critical_native) { - // stride forwards - c_arg = 0; - stride = 1; - init = 0; + GrowableArray arg_order(2 * total_in_args); + VMRegPair tmp_vmreg; + tmp_vmreg.set1(rbx->as_VMReg()); + + if (!is_critical_native) { + for (int i = total_in_args - 1, c_arg = total_c_args - 1; i >= 0; i--, c_arg--) { + arg_order.push(i); + arg_order.push(c_arg); + } + } else { + // Compute a valid move order, using tmp_vmreg to break any cycles + ComputeMoveOrder cmo(total_in_args, in_regs, total_c_args, out_regs, in_sig_bt, arg_order, tmp_vmreg); } - for (int i = init, count = 0; count < total_in_args; i += stride, c_arg += stride, count++ ) { + + int temploc = -1; + for (int ai = 0; ai < arg_order.length(); ai += 2) { + int i = arg_order.at(ai); + int c_arg = arg_order.at(ai + 1); + __ block_comment(err_msg("move %d -> %d", i, c_arg)); + if (c_arg == -1) { + assert(is_critical_native, "should only be required for critical natives"); + // This arg needs to be moved to a temporary + __ mov(tmp_vmreg.first()->as_Register(), in_regs[i].first()->as_Register()); + in_regs[i] = tmp_vmreg; + temploc = i; + continue; + } else if (i == -1) { + assert(is_critical_native, "should only be required for critical natives"); + // Read from the temporary location + assert(temploc != -1, "must be valid"); + i = temploc; + temploc = -1; + } #ifdef ASSERT if (in_regs[i].first()->is_Register()) { assert(!reg_destroyed[in_regs[i].first()->as_Register()->encoding()], "destroyed reg!"); @@ -1779,7 +1952,7 @@ // point c_arg at the first arg that is already loaded in case we // need to spill before we call out - c_arg++; + int c_arg = total_c_args - total_in_args; // Pre-load a static method's oop into r14. Used both by locking code and // the normal JNI call code.