--- old/src/hotspot/cpu/x86/frame_x86.cpp 2018-12-18 11:42:15.445484135 +0100 +++ new/src/hotspot/cpu/x86/frame_x86.cpp 2018-12-18 11:42:15.077668141 +0100 @@ -144,13 +144,16 @@ if ((address)sender_sp >= thread->stack_base()) { return false; } - sender_unextended_sp = sender_sp; // On Intel the return_address is always the word on the stack sender_pc = (address) *(sender_sp-1); // Note: frame::sender_sp_offset is only valid for compiled frame - saved_fp = (intptr_t*) *(sender_sp - frame::sender_sp_offset); - } + intptr_t** saved_fp_addr = (intptr_t**) (sender_sp - frame::sender_sp_offset); + saved_fp = *saved_fp_addr; + // Repair the sender sp if this is a method with scalarized value type args + sender_sp = repair_sender_sp(sender_sp, saved_fp_addr); + sender_unextended_sp = sender_sp; + } // If the potential sender is the interpreter then we can do some more checking if (Interpreter::contains(sender_pc)) { @@ -454,7 +457,6 @@ // frame owned by optimizing compiler assert(_cb->frame_size() >= 0, "must have non-zero frame size"); intptr_t* sender_sp = unextended_sp() + _cb->frame_size(); - intptr_t* unextended_sp = sender_sp; // On Intel the return_address is always the word on the stack address sender_pc = (address) *(sender_sp-1); @@ -463,6 +465,9 @@ // It is only an FP if the sender is an interpreter frame (or C1?). intptr_t** saved_fp_addr = (intptr_t**) (sender_sp - frame::sender_sp_offset); + // Repair the sender sp if this is a method with scalarized value type args + sender_sp = repair_sender_sp(sender_sp, saved_fp_addr); + if (map->update_map()) { // Tell GC to use argument oopmaps for some runtime stubs that need it. // For C1, the runtime stub might not have oop maps, so set this flag @@ -479,7 +484,7 @@ } assert(sender_sp != sp(), "must have changed"); - return frame(sender_sp, unextended_sp, *saved_fp_addr, sender_pc); + return frame(sender_sp, sender_sp, *saved_fp_addr, sender_pc); } @@ -687,6 +692,22 @@ void frame::pd_ps() {} #endif +// Check for a method with scalarized value type arguments that needs +// a stack repair and return the repaired sender stack pointer. +intptr_t* frame::repair_sender_sp(intptr_t* sender_sp, intptr_t** saved_fp_addr) const { + CompiledMethod* cm = _cb->as_compiled_method_or_null(); + if (cm != NULL && cm->method()->needs_stack_repair()) { + // The stack increment resides just below the saved rbp on the stack + // and does not account for the return address. + intptr_t* sp_inc_addr = (intptr_t*) (saved_fp_addr - 1); + int sp_inc = (*sp_inc_addr) / wordSize; + int real_frame_size = sp_inc + 1; // Add size of return address + assert(real_frame_size >= _cb->frame_size(), "invalid frame size"); + sender_sp = unextended_sp() + real_frame_size; + } + return sender_sp; +} + void JavaFrameAnchor::make_walkable(JavaThread* thread) { // last frame set? if (last_Java_sp() == NULL) return; --- old/src/hotspot/cpu/x86/frame_x86.hpp 2018-12-18 11:42:16.329042123 +0100 +++ new/src/hotspot/cpu/x86/frame_x86.hpp 2018-12-18 11:42:15.985214128 +0100 @@ -123,6 +123,9 @@ return (intptr_t*) addr_at(offset); } + // Support for scalarized value type calling convention + intptr_t* repair_sender_sp(intptr_t* sender_sp, intptr_t** saved_fp_addr) const; + #ifdef ASSERT // Used in frame::sender_for_{interpreter,compiled}_frame static void verify_deopt_original_pc(CompiledMethod* nm, intptr_t* unextended_sp); --- old/src/hotspot/cpu/x86/globals_x86.hpp 2018-12-18 11:42:17.184614111 +0100 +++ new/src/hotspot/cpu/x86/globals_x86.hpp 2018-12-18 11:42:16.836788116 +0100 @@ -102,8 +102,8 @@ define_pd_global(bool, ThreadLocalHandshakes, false); #endif -define_pd_global(bool, ValueTypePassFieldsAsArgs, LP64_ONLY(false) NOT_LP64(false)); -define_pd_global(bool, ValueTypeReturnedAsFields, LP64_ONLY(false) NOT_LP64(false)); +define_pd_global(bool, ValueTypePassFieldsAsArgs, LP64_ONLY(true) NOT_LP64(false)); +define_pd_global(bool, ValueTypeReturnedAsFields, LP64_ONLY(true) NOT_LP64(false)); #define ARCH_FLAGS(develop, \ product, \ --- old/src/hotspot/cpu/x86/interp_masm_x86.cpp 2018-12-18 11:42:18.256078096 +0100 +++ new/src/hotspot/cpu/x86/interp_masm_x86.cpp 2018-12-18 11:42:17.936238100 +0100 @@ -956,8 +956,7 @@ Register ret_addr, bool throw_monitor_exception, bool install_monitor_exception, - bool notify_jvmdi, - bool load_values) { + bool notify_jvmdi) { // Note: Registers rdx xmm0 may be in use for the // result check if synchronized method Label unlocked, unlock, no_unlock; @@ -975,7 +974,7 @@ movbool(rbx, do_not_unlock_if_synchronized); movbool(do_not_unlock_if_synchronized, false); // reset the flag - // get method access flags + // get method access flags movptr(rcx, Address(rbp, frame::interpreter_frame_method_offset * wordSize)); movl(rcx, Address(rcx, Method::access_flags_offset())); testl(rcx, JVM_ACC_SYNCHRONIZED); @@ -1128,26 +1127,32 @@ movptr(rbx, Address(rbp, frame::interpreter_frame_sender_sp_offset * wordSize)); - if (load_values) { + if (state == atos && ValueTypeReturnedAsFields) { + Label skip; + // Test if the return type is a value type + movptr(rdi, Address(rbp, frame::interpreter_frame_method_offset * wordSize)); + movptr(rdi, Address(rdi, Method::const_offset())); + load_unsigned_byte(rdi, Address(rdi, ConstMethod::result_type_offset())); + cmpl(rdi, T_VALUETYPE); + jcc(Assembler::notEqual, skip); + // We are returning a value type, load its fields into registers #ifndef _LP64 super_call_VM_leaf(StubRoutines::load_value_type_fields_in_regs()); #else + // Load fields from a buffered value with a value class specific handler load_klass(rdi, rax); + movptr(rdi, Address(rdi, InstanceKlass::adr_valueklass_fixed_block_offset())); movptr(rdi, Address(rdi, ValueKlass::unpack_handler_offset())); - Label skip; testptr(rdi, rdi); jcc(Assembler::equal, skip); - // Load fields from a buffered value with a value class specific - // handler call(rdi); - - bind(skip); #endif // call above kills the value in rbx. Reload it. movptr(rbx, Address(rbp, frame::interpreter_frame_sender_sp_offset * wordSize)); + bind(skip); } leave(); // remove frame anchor pop(ret_addr); // get return address --- old/src/hotspot/cpu/x86/interp_masm_x86.hpp 2018-12-18 11:42:19.463474079 +0100 +++ new/src/hotspot/cpu/x86/interp_masm_x86.hpp 2018-12-18 11:42:19.091660085 +0100 @@ -215,8 +215,7 @@ void remove_activation(TosState state, Register ret_addr, bool throw_monitor_exception = true, bool install_monitor_exception = true, - bool notify_jvmdi = true, - bool load_values = false); + bool notify_jvmdi = true); void get_method_counters(Register method, Register mcs, Label& skip); // Object locking --- old/src/hotspot/cpu/x86/macroAssembler_x86.cpp 2018-12-18 11:42:20.407002066 +0100 +++ new/src/hotspot/cpu/x86/macroAssembler_x86.cpp 2018-12-18 11:42:20.027192072 +0100 @@ -47,6 +47,7 @@ #include "runtime/stubRoutines.hpp" #include "runtime/thread.hpp" #include "utilities/macros.hpp" +#include "vmreg_x86.inline.hpp" #include "crc32c.h" #ifdef COMPILER2 #include "opto/intrinsicnode.hpp" @@ -5494,7 +5495,12 @@ #endif // _LP64 // C2 compiled method's prolog code. -void MacroAssembler::verified_entry(int framesize, int stack_bang_size, bool fp_mode_24b, bool is_stub) { +void MacroAssembler::verified_entry(Compile* C, int sp_inc) { + int framesize = C->frame_size_in_bytes(); + int bangsize = C->bang_size_in_bytes(); + bool fp_mode_24b = C->in_24_bit_fp_mode(); + int stack_bang_size = C->need_stack_bang(bangsize) ? bangsize : 0; + bool is_stub = C->stub_function() != NULL; // WARNING: Initial instruction MUST be 5 bytes or longer so that // NativeJump::patch_verified_entry will be able to patch out the entry @@ -5547,6 +5553,12 @@ } } + if (C->needs_stack_repair()) { + // Save stack increment (also account for fixed framesize and rbp) + assert((sp_inc & (StackAlignmentInBytes-1)) == 0, "stack increment not aligned"); + movptr(Address(rsp, C->sp_inc_offset()), sp_inc + framesize + wordSize); + } + if (VerifyStackAtCalls) { // Majik cookie to verify stack depth framesize -= wordSize; movptr(Address(rsp, framesize), (int32_t)0xbadb100d); @@ -5636,6 +5648,271 @@ BIND(L_end); } +// Move a value between registers/stack slots and update the reg_state +bool MacroAssembler::move_helper(VMReg from, VMReg to, BasicType bt, RegState reg_state[]) { + if (reg_state[to->value()] == reg_written) { + return true; // Already written + } + if (from != to && bt != T_VOID) { + if (reg_state[to->value()] == reg_readonly) { + return false; // Not yet writable + } + if (from->is_reg()) { + if (to->is_reg()) { + if (from->is_XMMRegister()) { + if (bt == T_DOUBLE) { + movdbl(to->as_XMMRegister(), from->as_XMMRegister()); + } else { + assert(bt == T_FLOAT, "must be float"); + movflt(to->as_XMMRegister(), from->as_XMMRegister()); + } + } else { + movq(to->as_Register(), from->as_Register()); + } + } else { + Address to_addr = Address(rsp, to->reg2stack() * VMRegImpl::stack_slot_size + wordSize); + if (from->is_XMMRegister()) { + if (bt == T_DOUBLE) { + movdbl(to_addr, from->as_XMMRegister()); + } else { + assert(bt == T_FLOAT, "must be float"); + movflt(to_addr, from->as_XMMRegister()); + } + } else { + movq(to_addr, from->as_Register()); + } + } + } else { + Address from_addr = Address(rsp, from->reg2stack() * VMRegImpl::stack_slot_size + wordSize); + if (to->is_reg()) { + if (to->is_XMMRegister()) { + if (bt == T_DOUBLE) { + movdbl(to->as_XMMRegister(), from_addr); + } else { + assert(bt == T_FLOAT, "must be float"); + movflt(to->as_XMMRegister(), from_addr); + } + } else { + movq(to->as_Register(), from_addr); + } + } else { + movq(r13, from_addr); + movq(Address(rsp, to->reg2stack() * VMRegImpl::stack_slot_size + wordSize), r13); + } + } + } + // Update register states + reg_state[from->value()] = reg_writable; + reg_state[to->value()] = reg_written; + return true; +} + +// Read all fields from a value type oop and store the values in registers/stack slots +bool MacroAssembler::unpack_value_helper(const GrowableArray* sig, int& sig_index, VMReg from, VMRegPair* regs_to, int& to_index, RegState reg_state[]) { + Register fromReg = from->is_reg() ? from->as_Register() : noreg; + + int vt = 1; + bool done = true; + bool mark_done = true; + do { + sig_index--; + BasicType bt = sig->at(sig_index)._bt; + if (bt == T_VALUETYPE) { + vt--; + } else if (bt == T_VOID && + sig->at(sig_index-1)._bt != T_LONG && + sig->at(sig_index-1)._bt != T_DOUBLE) { + vt++; + } else if (SigEntry::is_reserved_entry(sig, sig_index)) { + to_index--; // Ignore this + } else { + assert(to_index >= 0, "invalid to_index"); + VMRegPair pair_to = regs_to[to_index--]; + VMReg r_1 = pair_to.first(); + + if (bt == T_VOID) continue; + + int idx = (int)r_1->value(); + assert(idx >= 0 && idx <= 1000, "out of bounds"); + if (reg_state[idx] == reg_readonly) { + if (idx != from->value()) { + mark_done = false; + } + done = false; + continue; + } else if (reg_state[idx] == reg_written) { + continue; + } else { + assert(reg_state[idx] == reg_writable, "must be writable"); + reg_state[idx] = reg_written; + } + + if (fromReg == noreg) { + int st_off = from->reg2stack() * VMRegImpl::stack_slot_size + wordSize; + movq(r10, Address(rsp, st_off)); + fromReg = r10; + } + + int off = sig->at(sig_index)._offset; + assert(off > 0, "offset in object should be positive"); + bool is_oop = (bt == T_OBJECT || bt == T_ARRAY); + + Address fromAddr = Address(fromReg, off); + bool is_signed = (bt != T_CHAR) && (bt != T_BOOLEAN); + if (!r_1->is_XMMRegister()) { + Register dst = r_1->is_stack() ? r13 : r_1->as_Register(); + if (is_oop) { + load_heap_oop(dst, fromAddr); + } else { + load_sized_value(dst, fromAddr, type2aelembytes(bt), is_signed); + } + if (r_1->is_stack()) { + int st_off = r_1->reg2stack() * VMRegImpl::stack_slot_size + wordSize; + movq(Address(rsp, st_off), dst); + } + } else { + if (bt == T_DOUBLE) { + movdbl(r_1->as_XMMRegister(), fromAddr); + } else { + assert(bt == T_FLOAT, "must be float"); + movflt(r_1->as_XMMRegister(), fromAddr); + } + } + } + } while (vt != 0); + if (mark_done && reg_state[from->value()] != reg_written) { + // This is okay because no one else will write to that slot + reg_state[from->value()] = reg_writable; + } + return done; +} + +// Unpack all value type arguments passed as oops +void MacroAssembler::unpack_value_args(Compile* C) { + assert(C->has_scalarized_args(), "value type argument scalarization is disabled"); + Method* method = C->method()->get_Method(); + const GrowableArray* sig_cc = method->adapter()->get_sig_cc(); + assert(sig_cc != NULL, "must have scalarized signature"); + + // Get unscalarized calling convention + BasicType* sig_bt = NEW_RESOURCE_ARRAY(BasicType, sig_cc->length()); + int args_passed = 0; + if (!method->is_static()) { + sig_bt[args_passed++] = T_OBJECT; + } + for (SignatureStream ss(method->signature()); !ss.at_return_type(); ss.next()) { + BasicType bt = ss.type(); + if (bt == T_VALUETYPE) { + bt = T_OBJECT; + } + sig_bt[args_passed++] = bt; + if (type2size[bt] == 2) { + sig_bt[args_passed++] = T_VOID; + } + } + VMRegPair* regs = NEW_RESOURCE_ARRAY(VMRegPair, args_passed); + int args_on_stack = SharedRuntime::java_calling_convention(sig_bt, regs, args_passed, false); + + // Get scalarized calling convention + int args_passed_cc = SigEntry::fill_sig_bt(sig_cc, sig_bt); + VMRegPair* regs_cc = NEW_RESOURCE_ARRAY(VMRegPair, sig_cc->length()); + int args_on_stack_cc = SharedRuntime::java_calling_convention(sig_bt, regs_cc, args_passed_cc, false); + + // Check if we need to extend the stack for unpacking + int sp_inc = (args_on_stack_cc - args_on_stack) * VMRegImpl::stack_slot_size; + if (sp_inc > 0) { + // Save the return address, adjust the stack (make sure it is properly + // 16-byte aligned) and copy the return address to the new top of the stack. + pop(r13); + sp_inc = align_up(sp_inc, StackAlignmentInBytes); + subptr(rsp, sp_inc); + push(r13); + } else { + // The scalarized calling convention needs less stack space than the unscalarized one. + // No need to extend the stack, the caller will take care of these adjustments. + sp_inc = 0; + } + + // Initialize register/stack slot states (make all writable) + int max_stack = MAX2(args_on_stack + sp_inc/VMRegImpl::stack_slot_size, args_on_stack_cc); + int max_reg = VMRegImpl::stack2reg(max_stack)->value(); + RegState* reg_state = NEW_RESOURCE_ARRAY(RegState, max_reg); + for (int i = 0; i < max_reg; ++i) { + reg_state[i] = reg_writable; + } + // Set all source registers/stack slots to readonly to prevent accidental overwriting + for (int i = 0; i < args_passed; ++i) { + VMReg reg = regs[i].first(); + if (!reg->is_valid()) continue; + if (reg->is_stack()) { + // Update source stack location by adding stack increment + reg = VMRegImpl::stack2reg(reg->reg2stack() + sp_inc/VMRegImpl::stack_slot_size); + regs[i] = reg; + } + assert(reg->value() >= 0 && reg->value() < max_reg, "reg value out of bounds"); + reg_state[reg->value()] = reg_readonly; + } + + // Emit code for unpacking value type arguments + // We try multiple times and eventually start spilling to resolve (circular) dependencies + bool done = false; + for (int i = 0; i < 2*args_passed_cc && !done; ++i) { + done = true; + bool spill = (i > args_passed_cc); // Start spilling? + // Iterate over all arguments (in reverse) + for (int from_index = args_passed-1, to_index = args_passed_cc-1, sig_index = sig_cc->length()-1; sig_index >= 0; sig_index--) { + if (SigEntry::is_reserved_entry(sig_cc, sig_index)) { + to_index--; // Skipp reserved entry + } else { + assert(from_index >= 0, "index out of bounds"); + VMReg reg = regs[from_index--].first(); + if (spill && reg->is_valid() && reg_state[reg->value()] == reg_readonly) { + // Spill argument to be able to write the source and resolve circular dependencies + VMReg spill_reg = reg->is_XMMRegister() ? xmm8->as_VMReg() : r14->as_VMReg(); + bool res = move_helper(reg, spill_reg, T_DOUBLE, reg_state); + assert(res, "Spilling should not fail"); + // Set spill_reg as new source and update state + reg = spill_reg; + regs[from_index+1].set1(reg); + reg_state[reg->value()] = reg_readonly; + spill = false; // Do not spill again in this round + } + if (SigEntry::skip_value_delimiters(sig_cc, sig_index)) { + BasicType bt = sig_cc->at(sig_index)._bt; + assert(to_index >= 0, "index out of bounds"); + done &= move_helper(reg, regs_cc[to_index].first(), bt, reg_state); + to_index--; + } else { + done &= unpack_value_helper(sig_cc, sig_index, reg, regs_cc, to_index, reg_state); + } + } + } + } + guarantee(done, "Could not resolve circular dependency when unpacking value type arguments"); + + // Emit code for verified entry and save increment for stack repair on return + verified_entry(C, sp_inc); +} + +// Restores the stack on return +void MacroAssembler::restore_stack(Compile* C) { + int framesize = C->frame_size_in_bytes(); + assert((framesize & (StackAlignmentInBytes-1)) == 0, "frame size not aligned"); + // Remove word for return addr already pushed and RBP + framesize -= 2*wordSize; + + if (C->needs_stack_repair()) { + // Restore rbp and repair rsp by adding the stack increment + movq(rbp, Address(rsp, framesize)); + addq(rsp, Address(rsp, C->sp_inc_offset())); + } else { + if (framesize > 0) { + addq(rsp, framesize); + } + pop(rbp); + } +} + void MacroAssembler::clear_mem(Register base, Register cnt, Register val, XMMRegister xtmp, bool is_large, bool word_copy_only) { // cnt - number of qwords (8-byte words). // base - start address, qword aligned. --- old/src/hotspot/cpu/x86/macroAssembler_x86.hpp 2018-12-18 11:42:21.886262047 +0100 +++ new/src/hotspot/cpu/x86/macroAssembler_x86.hpp 2018-12-18 11:42:21.470470053 +0100 @@ -28,6 +28,7 @@ #include "asm/assembler.hpp" #include "utilities/macros.hpp" #include "runtime/rtmLocking.hpp" +#include "runtime/signature.hpp" // MacroAssembler extends Assembler by frequently used macros. // @@ -1596,7 +1597,19 @@ void movl2ptr(Register dst, Register src) { LP64_ONLY(movslq(dst, src)) NOT_LP64(if (dst != src) movl(dst, src)); } // C2 compiled method's prolog code. - void verified_entry(int framesize, int stack_bang_size, bool fp_mode_24b, bool is_stub); + void verified_entry(Compile* C, int sp_inc = 0); + + enum RegState { + reg_readonly, + reg_writable, + reg_written + }; + + // Unpack all value type arguments passed as oops + void unpack_value_args(Compile* C); + bool move_helper(VMReg from, VMReg to, BasicType bt, RegState reg_state[]); + bool unpack_value_helper(const GrowableArray* sig, int& sig_index, VMReg from, VMRegPair* regs_to, int& to_index, RegState reg_state[]); + void restore_stack(Compile* C); // clear memory of size 'cnt' qwords, starting at 'base'; // if 'is_large' is set, do not try to produce short loop --- old/src/hotspot/cpu/x86/methodHandles_x86.cpp 2018-12-18 11:42:22.949730031 +0100 +++ new/src/hotspot/cpu/x86/methodHandles_x86.cpp 2018-12-18 11:42:22.553928037 +0100 @@ -147,7 +147,11 @@ __ BIND(run_compiled_code); } - const ByteSize entry_offset = for_compiler_entry ? Method::from_compiled_offset() : + // The following jump might pass a value type argument that was erased to Object as oop to a + // callee that expects value type arguments to be passed as fields. We need to call the compiled + // value entry (_code->value_entry_point() or _adapter->c2i_value_entry()) which will take care + // of translating between the calling conventions. + const ByteSize entry_offset = for_compiler_entry ? Method::from_compiled_value_offset() : Method::from_interpreted_offset(); __ jmp(Address(method, entry_offset)); --- old/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp 2018-12-18 11:42:23.981214017 +0100 +++ new/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp 2018-12-18 11:42:23.605402022 +0100 @@ -492,8 +492,6 @@ case T_OBJECT: case T_ARRAY: case T_ADDRESS: - case T_VALUETYPE: - case T_VALUETYPEPTR: if (int_args < Argument::n_int_register_parameters_j) { regs[i].set2(INT_ArgReg[int_args++]->as_VMReg()); } else { @@ -576,7 +574,6 @@ case T_ARRAY: case T_ADDRESS: case T_METADATA: - case T_VALUETYPEPTR: if (int_args < Argument::n_int_register_parameters_j+1) { regs[i].set2(INT_ArgReg[int_args]->as_VMReg()); int_args++; @@ -656,12 +653,14 @@ // the value type. This utility function computes the number of // arguments for the call if value types are passed by reference (the // calling convention the interpreter expects). -static int compute_total_args_passed_int(const GrowableArray& sig_extended) { +static int compute_total_args_passed_int(const GrowableArray* sig_extended) { int total_args_passed = 0; if (ValueTypePassFieldsAsArgs) { - for (int i = 0; i < sig_extended.length(); i++) { - BasicType bt = sig_extended.at(i)._bt; - if (bt == T_VALUETYPE) { + for (int i = 0; i < sig_extended->length(); i++) { + BasicType bt = sig_extended->at(i)._bt; + if (SigEntry::is_reserved_entry(sig_extended, i)) { + // Ignore reserved entry + } else if (bt == T_VALUETYPE) { // In sig_extended, a value type argument starts with: // T_VALUETYPE, followed by the types of the fields of the // value type and T_VOID to mark the end of the value @@ -675,8 +674,8 @@ int vt = 1; do { i++; - BasicType bt = sig_extended.at(i)._bt; - BasicType prev_bt = sig_extended.at(i-1)._bt; + BasicType bt = sig_extended->at(i)._bt; + BasicType prev_bt = sig_extended->at(i-1)._bt; if (bt == T_VALUETYPE) { vt++; } else if (bt == T_VOID && @@ -690,7 +689,7 @@ } } } else { - total_args_passed = sig_extended.length(); + total_args_passed = sig_extended->length(); } return total_args_passed; } @@ -742,7 +741,14 @@ val = r_1->as_Register(); } if (is_oop) { - __ store_heap_oop(to, val); + // We don't need barriers because the destination is a newly allocated object. + // Also, we cannot use store_heap_oop(to, val) because it uses r8 as tmp. + if (UseCompressedOops) { + __ encode_heap_oop(val); + __ movl(to, val); + } else { + __ movptr(to, val); + } } else { __ store_sized_value(to, val, size_in_bytes); } @@ -756,7 +762,7 @@ } static void gen_c2i_adapter(MacroAssembler *masm, - const GrowableArray& sig_extended, + const GrowableArray* sig_extended, const VMRegPair *regs, Label& skip_fixup, address start, @@ -775,8 +781,8 @@ bool has_value_argument = false; if (ValueTypePassFieldsAsArgs) { // Is there a value type argument? - for (int i = 0; i < sig_extended.length() && !has_value_argument; i++) { - has_value_argument = (sig_extended.at(i)._bt == T_VALUETYPE); + for (int i = 0; i < sig_extended->length() && !has_value_argument; i++) { + has_value_argument = (sig_extended->at(i)._bt == T_VALUETYPE); } if (has_value_argument) { // There is at least a value type argument: we're coming from @@ -853,17 +859,20 @@ // interpreter point of view (value types are passed by reference). bool has_oop_field = false; for (int next_arg_comp = 0, ignored = 0, next_vt_arg = 0, next_arg_int = 0; - next_arg_comp < sig_extended.length(); next_arg_comp++) { - assert(ignored <= next_arg_comp, "shouldn't skip over more slot than there are arguments"); - assert(next_arg_int < total_args_passed, "more arguments for the interpreter than expected?"); - BasicType bt = sig_extended.at(next_arg_comp)._bt; + next_arg_comp < sig_extended->length(); next_arg_comp++) { + assert(ignored <= next_arg_comp, "shouldn't skip over more slots than there are arguments"); + assert(next_arg_int <= total_args_passed, "more arguments for the interpreter than expected?"); + BasicType bt = sig_extended->at(next_arg_comp)._bt; int st_off = (total_args_passed - next_arg_int) * Interpreter::stackElementSize; if (!ValueTypePassFieldsAsArgs || bt != T_VALUETYPE) { + if (SigEntry::is_reserved_entry(sig_extended, next_arg_comp)) { + continue; // Ignore reserved entry + } int next_off = st_off - Interpreter::stackElementSize; const int offset = (bt == T_LONG || bt == T_DOUBLE) ? next_off : st_off; const VMRegPair reg_pair = regs[next_arg_comp-ignored]; size_t size_in_bytes = reg_pair.second()->is_valid() ? 8 : 4; - gen_c2i_adapter_helper(masm, bt, next_arg_comp > 0 ? sig_extended.at(next_arg_comp-1)._bt : T_ILLEGAL, + gen_c2i_adapter_helper(masm, bt, next_arg_comp > 0 ? sig_extended->at(next_arg_comp-1)._bt : T_ILLEGAL, size_in_bytes, reg_pair, Address(rsp, offset), extraspace, false); next_arg_int++; #ifdef ASSERT @@ -888,8 +897,8 @@ // sig_extended contains a field offset in the buffer. do { next_arg_comp++; - BasicType bt = sig_extended.at(next_arg_comp)._bt; - BasicType prev_bt = sig_extended.at(next_arg_comp-1)._bt; + BasicType bt = sig_extended->at(next_arg_comp)._bt; + BasicType prev_bt = sig_extended->at(next_arg_comp-1)._bt; if (bt == T_VALUETYPE) { vt++; ignored++; @@ -898,13 +907,15 @@ prev_bt != T_DOUBLE) { vt--; ignored++; + } else if (SigEntry::is_reserved_entry(sig_extended, next_arg_comp)) { + // Ignore reserved entry } else { - int off = sig_extended.at(next_arg_comp)._offset; + int off = sig_extended->at(next_arg_comp)._offset; assert(off > 0, "offset in object should be positive"); size_t size_in_bytes = is_java_primitive(bt) ? type2aelembytes(bt) : wordSize; - bool is_oop = (bt == T_OBJECT || bt == T_VALUETYPEPTR || bt == T_ARRAY); + bool is_oop = (bt == T_OBJECT || bt == T_ARRAY); has_oop_field = has_oop_field || is_oop; - gen_c2i_adapter_helper(masm, bt, next_arg_comp > 0 ? sig_extended.at(next_arg_comp-1)._bt : T_ILLEGAL, + gen_c2i_adapter_helper(masm, bt, next_arg_comp > 0 ? sig_extended->at(next_arg_comp-1)._bt : T_ILLEGAL, size_in_bytes, regs[next_arg_comp-ignored], Address(r11, off), extraspace, is_oop); } } while (vt != 0); @@ -1001,7 +1012,7 @@ void SharedRuntime::gen_i2c_adapter(MacroAssembler *masm, int comp_args_on_stack, - const GrowableArray& sig_extended, + const GrowableArray* sig, const VMRegPair *regs) { // Note: r13 contains the senderSP on entry. We must preserve it since @@ -1079,7 +1090,6 @@ __ subptr(rsp, comp_words_on_stack * wordSize); } - // Ensure compiled code always sees stack at proper alignment __ andptr(rsp, -16); @@ -1093,7 +1103,13 @@ // Will jump to the compiled code just as if compiled code was doing it. // Pre-load the register-jump target early, to schedule it better. - __ movptr(r11, Address(rbx, in_bytes(Method::from_compiled_offset()))); + if (StressValueTypePassFieldsAsArgs) { + // For stress testing, don't unpack value types in the i2c adapter but + // call the value type entry point and let it take care of unpacking. + __ movptr(r11, Address(rbx, in_bytes(Method::from_compiled_value_offset()))); + } else { + __ movptr(r11, Address(rbx, in_bytes(Method::from_compiled_offset()))); + } #if INCLUDE_JVMCI if (EnableJVMCI || UseAOT) { @@ -1107,7 +1123,7 @@ } #endif // INCLUDE_JVMCI - int total_args_passed = compute_total_args_passed_int(sig_extended); + int total_args_passed = compute_total_args_passed_int(sig); // Now generate the shuffle code. Pick up all register args and move the // rest through the floating point stack top. @@ -1118,19 +1134,22 @@ // to mark the end of the value type. ignored counts the number of // T_VALUETYPE/T_VOID. next_arg_int is the next argument from the // interpreter point of view (value types are passed by reference). - for (int next_arg_comp = 0, ignored = 0, next_arg_int = 0; next_arg_comp < sig_extended.length(); next_arg_comp++) { - assert(ignored <= next_arg_comp, "shouldn't skip over more slot than there are arguments"); - assert(next_arg_int < total_args_passed, "more arguments from the interpreter than expected?"); - BasicType bt = sig_extended.at(next_arg_comp)._bt; + for (int next_arg_comp = 0, ignored = 0, next_arg_int = 0; next_arg_comp < sig->length(); next_arg_comp++) { + assert(ignored <= next_arg_comp, "shouldn't skip over more slots than there are arguments"); + assert(next_arg_int <= total_args_passed, "more arguments from the interpreter than expected?"); + BasicType bt = sig->at(next_arg_comp)._bt; int ld_off = (total_args_passed - next_arg_int)*Interpreter::stackElementSize; if (!ValueTypePassFieldsAsArgs || bt != T_VALUETYPE) { // Load in argument order going down. // Point to interpreter value (vs. tag) + if (SigEntry::is_reserved_entry(sig, next_arg_comp)) { + continue; // Ignore reserved entry + } int next_off = ld_off - Interpreter::stackElementSize; int offset = (bt == T_LONG || bt == T_DOUBLE) ? next_off : ld_off; const VMRegPair reg_pair = regs[next_arg_comp-ignored]; size_t size_in_bytes = reg_pair.second()->is_valid() ? 8 : 4; - gen_i2c_adapter_helper(masm, bt, next_arg_comp > 0 ? sig_extended.at(next_arg_comp-1)._bt : T_ILLEGAL, + gen_i2c_adapter_helper(masm, bt, next_arg_comp > 0 ? sig->at(next_arg_comp-1)._bt : T_ILLEGAL, size_in_bytes, reg_pair, Address(saved_sp, offset), false); next_arg_int++; } else { @@ -1147,8 +1166,8 @@ // field offset in the buffer. do { next_arg_comp++; - BasicType bt = sig_extended.at(next_arg_comp)._bt; - BasicType prev_bt = sig_extended.at(next_arg_comp-1)._bt; + BasicType bt = sig->at(next_arg_comp)._bt; + BasicType prev_bt = sig->at(next_arg_comp-1)._bt; if (bt == T_VALUETYPE) { vt++; ignored++; @@ -1157,11 +1176,13 @@ prev_bt != T_DOUBLE) { vt--; ignored++; + } else if (SigEntry::is_reserved_entry(sig, next_arg_comp)) { + // Ignore reserved entry } else { - int off = sig_extended.at(next_arg_comp)._offset; + int off = sig->at(next_arg_comp)._offset; assert(off > 0, "offset in object should be positive"); size_t size_in_bytes = is_java_primitive(bt) ? type2aelembytes(bt) : wordSize; - bool is_oop = (bt == T_OBJECT || bt == T_VALUETYPEPTR || bt == T_ARRAY); + bool is_oop = (bt == T_OBJECT || bt == T_ARRAY); gen_i2c_adapter_helper(masm, bt, prev_bt, size_in_bytes, regs[next_arg_comp - ignored], Address(r10, off), is_oop); } } while (vt != 0); @@ -1190,13 +1211,22 @@ // --------------------------------------------------------------- AdapterHandlerEntry* SharedRuntime::generate_i2c2i_adapters(MacroAssembler *masm, int comp_args_on_stack, - const GrowableArray& sig_extended, - const VMRegPair *regs, + int comp_args_on_stack_cc, + const GrowableArray* sig, + const VMRegPair* regs, + const GrowableArray* sig_cc, + const VMRegPair* regs_cc, AdapterFingerPrint* fingerprint, AdapterBlob*& new_adapter) { address i2c_entry = __ pc(); - gen_i2c_adapter(masm, comp_args_on_stack, sig_extended, regs); + if (StressValueTypePassFieldsAsArgs) { + // For stress testing, don't unpack value types in the i2c adapter but + // call the value type entry point and let it take care of unpacking. + gen_i2c_adapter(masm, comp_args_on_stack, sig, regs); + } else { + gen_i2c_adapter(masm, comp_args_on_stack_cc, sig_cc, regs_cc); + } // ------------------------------------------------------------------------- // Generate a C2I adapter. On entry we know rbx holds the Method* during calls @@ -1232,54 +1262,28 @@ } address c2i_entry = __ pc(); + address c2i_value_entry = c2i_entry; OopMapSet* oop_maps = NULL; int frame_complete = CodeOffsets::frame_never_safe; int frame_size_in_words = 0; - gen_c2i_adapter(masm, sig_extended, regs, skip_fixup, i2c_entry, oop_maps, frame_complete, frame_size_in_words); + gen_c2i_adapter(masm, sig_cc, regs_cc, skip_fixup, i2c_entry, oop_maps, frame_complete, frame_size_in_words); + + if (regs != regs_cc) { + // Non-scalarized c2i adapter + c2i_value_entry = __ pc(); + Label unused; + gen_c2i_adapter(masm, sig, regs, unused, i2c_entry, oop_maps, frame_complete, frame_size_in_words); + } __ flush(); - new_adapter = AdapterBlob::create(masm->code(), frame_complete, frame_size_in_words, oop_maps); - // If the method has value types arguments, save the extended signature as symbol in - // the AdapterHandlerEntry to be used for scalarization of value type arguments. - Symbol* extended_signature = NULL; - bool has_value_argument = false; - Thread* THREAD = Thread::current(); - ResourceMark rm(THREAD); - int length = sig_extended.length(); - char* sig_str = NEW_RESOURCE_ARRAY(char, 2*length + 3); - int idx = 0; - sig_str[idx++] = '('; - for (int index = 0; index < length; index++) { - BasicType bt = sig_extended.at(index)._bt; - if (bt == T_VALUETYPE) { - has_value_argument = true; - } else if (bt == T_VALUETYPEPTR) { - has_value_argument = true; - // non-flattened value type field - sig_str[idx++] = type2char(T_VALUETYPE); - sig_str[idx++] = ';'; - } else if (bt == T_VOID) { - // Ignore - } else { - if (bt == T_ARRAY) { - bt = T_OBJECT; // We don't know the element type, treat as Object - } - sig_str[idx++] = type2char(bt); - if (bt == T_OBJECT) { - sig_str[idx++] = ';'; - } - } - } - sig_str[idx++] = ')'; - sig_str[idx++] = '\0'; - if (has_value_argument) { - // Extended signature is only required if a value type argument is passed - extended_signature = SymbolTable::new_permanent_symbol(sig_str, THREAD); - } + // The c2i adapter might safepoint and trigger a GC. The caller must make sure that + // the GC knows about the location of oop argument locations passed to the c2i adapter. + bool caller_must_gc_arguments = (regs != regs_cc); + new_adapter = AdapterBlob::create(masm->code(), frame_complete, frame_size_in_words, oop_maps, caller_must_gc_arguments); - return AdapterHandlerLibrary::new_entry(fingerprint, i2c_entry, c2i_entry, c2i_unverified_entry, extended_signature); + return AdapterHandlerLibrary::new_entry(fingerprint, i2c_entry, c2i_entry, c2i_value_entry, c2i_unverified_entry); } int SharedRuntime::c_calling_convention(const BasicType *sig_bt, @@ -4348,8 +4352,7 @@ buffer.insts()->initialize_shared_locs((relocInfo*)buffer_locs, sizeof(buffer_locs)/sizeof(relocInfo)); - MacroAssembler _masm(&buffer); - MacroAssembler* masm = &_masm; + MacroAssembler* masm = new MacroAssembler(&buffer); const Array* sig_vk = vk->extended_sig(); const Array* regs = vk->return_regs(); @@ -4370,6 +4373,7 @@ continue; } int off = sig_vk->at(i)._offset; + assert(off > 0, "offset in object should be positive"); VMRegPair pair = regs->at(j); VMReg r_1 = pair.first(); VMReg r_2 = pair.second(); @@ -4378,10 +4382,21 @@ __ movflt(to, r_1->as_XMMRegister()); } else if (bt == T_DOUBLE) { __ movdbl(to, r_1->as_XMMRegister()); - } else if (bt == T_OBJECT || bt == T_VALUETYPEPTR || bt == T_ARRAY) { - __ store_heap_oop(to, r_1->as_Register()); + } else if (bt == T_OBJECT || bt == T_ARRAY) { + Register val = r_1->as_Register(); + assert_different_registers(rax, val); + // We don't need barriers because the destination is a newly allocated object. + // Also, we cannot use store_heap_oop(to, val) because it uses r8 as tmp. + if (UseCompressedOops) { + __ encode_heap_oop(val); + __ movl(to, val); + } else { + __ movptr(to, val); + } + } else { assert(is_java_primitive(bt), "unexpected basic type"); + assert_different_registers(rax, r_1->as_Register()); size_t size_in_bytes = type2aelembytes(bt); __ store_sized_value(to, r_1->as_Register(), size_in_bytes); } @@ -4407,6 +4422,7 @@ continue; } int off = sig_vk->at(i)._offset; + assert(off > 0, "offset in object should be positive"); VMRegPair pair = regs->at(j); VMReg r_1 = pair.first(); VMReg r_2 = pair.second(); @@ -4415,10 +4431,12 @@ __ movflt(r_1->as_XMMRegister(), from); } else if (bt == T_DOUBLE) { __ movdbl(r_1->as_XMMRegister(), from); - } else if (bt == T_OBJECT || bt == T_VALUETYPEPTR || bt == T_ARRAY) { + } else if (bt == T_OBJECT || bt == T_ARRAY) { + assert_different_registers(rax, r_1->as_Register()); __ load_heap_oop(r_1->as_Register(), from); } else { assert(is_java_primitive(bt), "unexpected basic type"); + assert_different_registers(rax, r_1->as_Register()); size_t size_in_bytes = type2aelembytes(bt); __ load_sized_value(r_1->as_Register(), from, size_in_bytes, bt != T_CHAR && bt != T_BOOLEAN); } --- old/src/hotspot/cpu/x86/templateInterpreterGenerator_x86.cpp 2018-12-18 11:42:25.340533998 +0100 +++ new/src/hotspot/cpu/x86/templateInterpreterGenerator_x86.cpp 2018-12-18 11:42:24.952728004 +0100 @@ -206,11 +206,11 @@ // and NULL it as marker that esp is now tos until next java call __ movptr(Address(rbp, frame::interpreter_frame_last_sp_offset * wordSize), (int32_t)NULL_WORD); - if (/*state == qtos*/ false && ValueTypeReturnedAsFields) { + if (state == atos && ValueTypeReturnedAsFields) { #ifndef _LP64 __ super_call_VM_leaf(StubRoutines::store_value_type_fields_to_buf()); #else - // A value type is being returned. If fields are in registers we + // A value type might be returned. If fields are in registers we // need to allocate a value type instance and initialize it with // the value of the fields. Label skip, slow_case; @@ -230,14 +230,8 @@ __ cmpptr(r14, Address(r15_thread, in_bytes(JavaThread::tlab_end_offset()))); __ jcc(Assembler::above, slow_case); __ movptr(Address(r15_thread, in_bytes(JavaThread::tlab_top_offset())), r14); + __ movptr(Address(r13, oopDesc::mark_offset_in_bytes()), (intptr_t)markOopDesc::always_locked_prototype()); - if (UseBiasedLocking) { - __ movptr(rax, Address(rbx, Klass::prototype_header_offset())); - __ movptr(Address(r13, oopDesc::mark_offset_in_bytes ()), rax); - } else { - __ movptr(Address(r13, oopDesc::mark_offset_in_bytes ()), - (intptr_t)markOopDesc::prototype()); - } __ xorl(rax, rax); // use zero reg to clear memory (shorter code) __ store_klass_gap(r13, rax); // zero klass gap for compressed oops __ mov(rax, rbx); @@ -245,7 +239,8 @@ // We have our new buffered value, initialize its fields with a // value class specific handler - __ movptr(rbx, Address(rax, ValueKlass::pack_handler_offset())); + __ movptr(rbx, Address(rax, InstanceKlass::adr_valueklass_fixed_block_offset())); + __ movptr(rbx, Address(rbx, ValueKlass::pack_handler_offset())); __ mov(rax, r13); __ call(rbx); __ jmp(skip); --- old/src/hotspot/cpu/x86/templateTable_x86.cpp 2018-12-18 11:42:26.751827979 +0100 +++ new/src/hotspot/cpu/x86/templateTable_x86.cpp 2018-12-18 11:42:26.348029984 +0100 @@ -2774,7 +2774,7 @@ __ narrow(rax); } - __ remove_activation(state, rbcp, true, true, true, /*state == qtos*/ false && ValueTypeReturnedAsFields); + __ remove_activation(state, rbcp, true, true, true); __ jmp(rbcp); } --- old/src/hotspot/cpu/x86/x86_32.ad 2018-12-18 11:42:28.115145960 +0100 +++ new/src/hotspot/cpu/x86/x86_32.ad 2018-12-18 11:42:27.703351965 +0100 @@ -616,10 +616,7 @@ Compile* C = ra_->C; MacroAssembler _masm(&cbuf); - int framesize = C->frame_size_in_bytes(); - int bangsize = C->bang_size_in_bytes(); - - __ verified_entry(framesize, C->need_stack_bang(bangsize)?bangsize:0, C->in_24_bit_fp_mode(), C->stub_function() != NULL); + __ verified_entry(C); C->set_frame_complete(cbuf.insts_size()); --- old/src/hotspot/cpu/x86/x86_64.ad 2018-12-18 11:42:29.590407939 +0100 +++ new/src/hotspot/cpu/x86/x86_64.ad 2018-12-18 11:42:29.202601945 +0100 @@ -907,10 +907,8 @@ Compile* C = ra_->C; MacroAssembler _masm(&cbuf); - int framesize = C->frame_size_in_bytes(); - int bangsize = C->bang_size_in_bytes(); - - __ verified_entry(framesize, C->need_stack_bang(bangsize)?bangsize:0, false, C->stub_function() != NULL); + __ verified_entry(C); + __ bind(*_verified_entry); C->set_frame_complete(cbuf.insts_size()); @@ -984,29 +982,8 @@ __ vzeroupper(); } - int framesize = C->frame_size_in_bytes(); - assert((framesize & (StackAlignmentInBytes-1)) == 0, "frame size not aligned"); - // Remove word for return adr already pushed - // and RBP - framesize -= 2*wordSize; - - // Note that VerifyStackAtCalls' Majik cookie does not change the frame size popped here - - if (framesize) { - emit_opcode(cbuf, Assembler::REX_W); - if (framesize < 0x80) { - emit_opcode(cbuf, 0x83); // addq rsp, #framesize - emit_rm(cbuf, 0x3, 0x00, RSP_enc); - emit_d8(cbuf, framesize); - } else { - emit_opcode(cbuf, 0x81); // addq rsp, #framesize - emit_rm(cbuf, 0x3, 0x00, RSP_enc); - emit_d32(cbuf, framesize); - } - } + __ restore_stack(C); - // popq rbp - emit_opcode(cbuf, 0x58 | RBP_enc); if (StackReservedPages > 0 && C->has_reserved_stack_access()) { __ reserved_stack_check(); @@ -1578,6 +1555,28 @@ } //============================================================================= +#ifndef PRODUCT +void MachVVEPNode::format(PhaseRegAlloc* ra_, outputStream* st) const +{ + st->print_cr("MachVVEPNode"); +} +#endif + +void MachVVEPNode::emit(CodeBuffer& cbuf, PhaseRegAlloc* ra_) const +{ + // Unpack all value type args passed as oop and then jump to + // the verified entry point (skipping the unverified entry). + MacroAssembler masm(&cbuf); + masm.unpack_value_args(ra_->C); + masm.jmp(*_verified_entry); +} + +uint MachVVEPNode::size(PhaseRegAlloc* ra_) const +{ + return MachNode::size(ra_); // too many variables; just compute it the hard way +} + +//============================================================================= #ifndef PRODUCT void MachUEPNode::format(PhaseRegAlloc* ra_, outputStream* st) const { --- old/src/hotspot/share/aot/aotCompiledMethod.hpp 2018-12-18 11:42:31.157623918 +0100 +++ new/src/hotspot/share/aot/aotCompiledMethod.hpp 2018-12-18 11:42:30.785809923 +0100 @@ -194,6 +194,7 @@ virtual int comp_level() const { return CompLevel_aot; } virtual address verified_entry_point() const { return _code + _meta->verified_entry_offset(); } + virtual address verified_value_entry_point() const { return NULL; } virtual void log_identity(xmlStream* stream) const; virtual void log_state_change() const; virtual bool make_entrant() NOT_TIERED({ ShouldNotReachHere(); return false; }); --- old/src/hotspot/share/asm/codeBuffer.hpp 2018-12-18 11:42:31.961221906 +0100 +++ new/src/hotspot/share/asm/codeBuffer.hpp 2018-12-18 11:42:31.645379911 +0100 @@ -42,6 +42,7 @@ public: enum Entries { Entry, Verified_Entry, + Verified_Value_Entry, Frame_Complete, // Offset in the code where the frame setup is (for forte stackwalks) is complete OSR_Entry, Exceptions, // Offset where exception handler lives @@ -62,6 +63,7 @@ CodeOffsets() { _values[Entry ] = 0; _values[Verified_Entry] = 0; + _values[Verified_Value_Entry] = -1; _values[Frame_Complete] = frame_never_safe; _values[OSR_Entry ] = 0; _values[Exceptions ] = -1; --- old/src/hotspot/share/c1/c1_LIRAssembler.cpp 2018-12-18 11:42:32.756823896 +0100 +++ new/src/hotspot/share/c1/c1_LIRAssembler.cpp 2018-12-18 11:42:32.448977900 +0100 @@ -621,6 +621,7 @@ check_icache(); } offsets()->set_value(CodeOffsets::Verified_Entry, _masm->offset()); + offsets()->set_value(CodeOffsets::Verified_Value_Entry, _masm->offset()); _masm->verified_entry(); build_frame(); offsets()->set_value(CodeOffsets::Frame_Complete, _masm->offset()); --- old/src/hotspot/share/ci/ciEnv.cpp 2018-12-18 11:42:33.844279880 +0100 +++ new/src/hotspot/share/ci/ciEnv.cpp 2018-12-18 11:42:33.552425885 +0100 @@ -554,7 +554,7 @@ klass_name = cpool->symbol_at(index); } else { // Check if it's resolved if it's not a symbol constant pool entry. - klass = ConstantPool::klass_at_if_loaded(cpool, index); + klass = ConstantPool::klass_at_if_loaded(cpool, index); // Try to look it up by name. if (klass == NULL) { klass_name = cpool->klass_name_at(index); --- old/src/hotspot/share/classfile/classFileParser.cpp 2018-12-18 11:42:34.855773867 +0100 +++ new/src/hotspot/share/classfile/classFileParser.cpp 2018-12-18 11:42:34.547927871 +0100 @@ -1500,8 +1500,7 @@ BAD_ALLOCATION_TYPE, // T_NARROWOOP = 17, BAD_ALLOCATION_TYPE, // T_METADATA = 18, BAD_ALLOCATION_TYPE, // T_NARROWKLASS = 19, - BAD_ALLOCATION_TYPE, // T_VALUETYPEPTR= 20, - BAD_ALLOCATION_TYPE, // T_CONFLICT = 21, + BAD_ALLOCATION_TYPE, // T_CONFLICT = 20, BAD_ALLOCATION_TYPE, // 0 BAD_ALLOCATION_TYPE, // 1 BAD_ALLOCATION_TYPE, // 2 @@ -1522,8 +1521,7 @@ BAD_ALLOCATION_TYPE, // T_NARROWOOP = 17, BAD_ALLOCATION_TYPE, // T_METADATA = 18, BAD_ALLOCATION_TYPE, // T_NARROWKLASS = 19, - BAD_ALLOCATION_TYPE, // T_VALUETYPEPTR= 20, - BAD_ALLOCATION_TYPE, // T_CONFLICT = 21, + BAD_ALLOCATION_TYPE, // T_CONFLICT = 20 }; static FieldAllocationType basic_type_to_atype(bool is_static, BasicType type, bool is_flattenable) { @@ -6021,11 +6019,6 @@ CHECK); } - if (is_value_type()) { - ValueKlass* vk = ValueKlass::cast(ik); - vk->initialize_calling_convention(); - } - // Add read edges to the unnamed modules of the bootstrap and app class loaders. if (changed_by_loadhook && !module_handle.is_null() && module_entry->is_named() && !module_entry->has_default_read_edges()) { @@ -6037,7 +6030,7 @@ int nfields = ik->java_fields_count(); if (ik->is_value()) nfields++; - for(int i = 0; i < nfields; i++) { + for (int i = 0; i < nfields; i++) { if (ik->field_access_flags(i) & JVM_ACC_FLATTENABLE) { Symbol* klass_name = ik->field_signature(i)->fundamental_name(CHECK); // Value classes must have been pre-loaded @@ -6054,6 +6047,10 @@ } } + if (is_value_type()) { + ValueKlass::cast(ik)->initialize_calling_convention(CHECK); + } + // Update the loader_data graph. record_defined_class_dependencies(ik, CHECK); --- old/src/hotspot/share/code/codeBlob.cpp 2018-12-18 11:42:35.899251852 +0100 +++ new/src/hotspot/share/code/codeBlob.cpp 2018-12-18 11:42:35.591405856 +0100 @@ -272,27 +272,27 @@ MemoryService::track_code_cache_memory_usage(); } -BufferBlob::BufferBlob(const char* name, int size, CodeBuffer* cb, int frame_complete, int frame_size, OopMapSet* oop_maps) - : RuntimeBlob(name, cb, sizeof(BufferBlob), size, frame_complete, frame_size, oop_maps) +BufferBlob::BufferBlob(const char* name, int size, CodeBuffer* cb, int frame_complete, int frame_size, OopMapSet* oop_maps, bool caller_must_gc_arguments) + : RuntimeBlob(name, cb, sizeof(BufferBlob), size, frame_complete, frame_size, oop_maps, caller_must_gc_arguments) {} //---------------------------------------------------------------------------------------------------- // Implementation of AdapterBlob -AdapterBlob::AdapterBlob(int size, CodeBuffer* cb, int frame_complete, int frame_size, OopMapSet* oop_maps) : - BufferBlob("I2C/C2I adapters", size, cb, frame_complete, frame_size, oop_maps) { +AdapterBlob::AdapterBlob(int size, CodeBuffer* cb, int frame_complete, int frame_size, OopMapSet* oop_maps, bool caller_must_gc_arguments) : + BufferBlob("I2C/C2I adapters", size, cb, frame_complete, frame_size, oop_maps, caller_must_gc_arguments) { CodeCache::commit(this); } -AdapterBlob* AdapterBlob::create(CodeBuffer* cb, int frame_complete, int frame_size, OopMapSet* oop_maps) { +AdapterBlob* AdapterBlob::create(CodeBuffer* cb, int frame_complete, int frame_size, OopMapSet* oop_maps, bool caller_must_gc_arguments) { ThreadInVMfromUnknown __tiv; // get to VM state in case we block on CodeCache_lock AdapterBlob* blob = NULL; unsigned int size = CodeBlob::allocation_size(cb, sizeof(AdapterBlob)); { MutexLockerEx mu(CodeCache_lock, Mutex::_no_safepoint_check_flag); - blob = new (size) AdapterBlob(size, cb, frame_complete, frame_size, oop_maps); + blob = new (size) AdapterBlob(size, cb, frame_complete, frame_size, oop_maps, caller_must_gc_arguments); } // Track memory usage statistic after releasing CodeCache_lock MemoryService::track_code_cache_memory_usage(); --- old/src/hotspot/share/code/codeBlob.hpp 2018-12-18 11:42:36.934733838 +0100 +++ new/src/hotspot/share/code/codeBlob.hpp 2018-12-18 11:42:36.634883843 +0100 @@ -394,7 +394,7 @@ // Creation support BufferBlob(const char* name, int size); BufferBlob(const char* name, int size, CodeBuffer* cb); - BufferBlob(const char* name, int size, CodeBuffer* cb, int frame_complete, int frame_size, OopMapSet* oop_maps); + BufferBlob(const char* name, int size, CodeBuffer* cb, int frame_complete, int frame_size, OopMapSet* oop_maps, bool caller_must_gc_arguments = false); // This ordinary operator delete is needed even though not used, so the // below two-argument operator delete will be treated as a placement @@ -427,14 +427,15 @@ class AdapterBlob: public BufferBlob { private: - AdapterBlob(int size, CodeBuffer* cb, int frame_complete, int frame_size, OopMapSet* oop_maps); + AdapterBlob(int size, CodeBuffer* cb, int frame_complete, int frame_size, OopMapSet* oop_maps, bool caller_must_gc_arguments = false); public: // Creation static AdapterBlob* create(CodeBuffer* cb, int frame_complete, int frame_size, - OopMapSet* oop_maps); + OopMapSet* oop_maps, + bool caller_must_gc_arguments = false); // Typing virtual bool is_adapter_blob() const { return true; } --- old/src/hotspot/share/code/compiledMethod.cpp 2018-12-18 11:42:37.922239824 +0100 +++ new/src/hotspot/share/code/compiledMethod.cpp 2018-12-18 11:42:37.622389828 +0100 @@ -357,27 +357,12 @@ has_receiver = !(callee->access_flags().is_static()); has_appendix = false; signature = callee->signature(); - } - // If value types are passed as fields, use the extended signature - // which contains the types of all (oop) fields of the value type. - if (ValueTypePassFieldsAsArgs && callee != NULL) { - // Get the extended signature from the callee's adapter through the attached method - Symbol* sig_ext = callee->adapter()->get_sig_extended(); -#ifdef ASSERT - // Check if receiver or one of the arguments is a value type - bool has_value_receiver = has_receiver && callee->method_holder()->is_value(); - bool has_value_argument = has_value_receiver; - for (SignatureStream ss(signature); !has_value_argument && !ss.at_return_type(); ss.next()) { - if (ss.type() == T_VALUETYPE) { - has_value_argument = true; - break; - } - } - assert(has_value_argument == (sig_ext != NULL), "Signature is inconsistent"); -#endif - if (sig_ext != NULL) { - signature = sig_ext; + // If value types are passed as fields, use the extended signature + // which contains the types of all (oop) fields of the value type. + if (callee->has_scalarized_args()) { + const GrowableArray* sig = callee->adapter()->get_sig_cc(); + signature = SigEntry::create_symbol(sig); has_receiver = false; // The extended signature contains the receiver type } } --- old/src/hotspot/share/code/compiledMethod.hpp 2018-12-18 11:42:38.921739810 +0100 +++ new/src/hotspot/share/code/compiledMethod.hpp 2018-12-18 11:42:38.609895815 +0100 @@ -212,6 +212,7 @@ virtual int compile_id() const = 0; virtual address verified_entry_point() const = 0; + virtual address verified_value_entry_point() const = 0; virtual void log_identity(xmlStream* log) const = 0; virtual void log_state_change() const = 0; virtual bool make_not_used() = 0; --- old/src/hotspot/share/code/nmethod.cpp 2018-12-18 11:42:39.901249797 +0100 +++ new/src/hotspot/share/code/nmethod.cpp 2018-12-18 11:42:39.605397801 +0100 @@ -598,6 +598,7 @@ _comp_level = CompLevel_none; _entry_point = code_begin() + offsets->value(CodeOffsets::Entry); _verified_entry_point = code_begin() + offsets->value(CodeOffsets::Verified_Entry); + _verified_value_entry_point = _verified_entry_point; _osr_entry_point = NULL; _exception_cache = NULL; _pc_desc_container.reset_to(NULL); @@ -758,6 +759,7 @@ _nmethod_end_offset = _nul_chk_table_offset + align_up(nul_chk_table->size_in_bytes(), oopSize); _entry_point = code_begin() + offsets->value(CodeOffsets::Entry); _verified_entry_point = code_begin() + offsets->value(CodeOffsets::Verified_Entry); + _verified_value_entry_point = code_begin() + offsets->value(CodeOffsets::Verified_Value_Entry); _osr_entry_point = code_begin() + offsets->value(CodeOffsets::OSR_Entry); _exception_cache = NULL; @@ -2515,7 +2517,7 @@ } void nmethod::print_nmethod_labels(outputStream* stream, address block_begin) const { - address low = entry_point(); + address low = verified_value_entry_point() != NULL ? verified_value_entry_point() : entry_point(); if (block_begin == low) { // Print method arguments before the method entry methodHandle m = method(); @@ -2531,18 +2533,15 @@ VMRegPair* regs = NEW_RESOURCE_ARRAY(VMRegPair, 256); Symbol* sig = m->signature(); bool has_value_arg = false; - if (ValueTypePassFieldsAsArgs && m->adapter()->get_sig_extended() != NULL) { + if (m->has_scalarized_args()) { // Use extended signature if value type arguments are passed as fields - sig = m->adapter()->get_sig_extended(); + sig = SigEntry::create_symbol(m->adapter()->get_sig_cc()); has_value_arg = true; } else if (!m->is_static()) { sig_bt[sizeargs++] = T_OBJECT; // 'this' } for (SignatureStream ss(sig); !ss.at_return_type(); ss.next()) { BasicType t = ss.type(); - if (!ValueTypePassFieldsAsArgs && t == T_VALUETYPE) { - t = T_VALUETYPEPTR; // Pass value types by reference - } sig_bt[sizeargs++] = t; if (type2size[t] == 2) { sig_bt[sizeargs++] = T_VOID; @@ -2557,13 +2556,11 @@ int sig_index = 0; int arg_index = ((m->is_static() || has_value_arg) ? 0 : -1); bool did_old_sp = false; + SigEntry res_entry = m->get_res_entry(); for (SignatureStream ss(sig); !ss.at_return_type(); ) { bool at_this = (arg_index == -1); bool at_old_sp = false; BasicType t = (at_this ? T_OBJECT : ss.type()); - if (!ValueTypePassFieldsAsArgs && t == T_VALUETYPE) { - t = T_VALUETYPEPTR; // Pass value types by reference - } assert(t == sig_bt[sig_index], "sigs in sync"); if (at_this) { stream->print(" # this: "); @@ -2602,6 +2599,9 @@ if (!did_name) stream->print("%s", type2name(t)); } + if (sig_index == res_entry._offset) { + stream->print(" [RESERVED] "); + } if (at_old_sp) { stream->print(" (%s of caller)", spname); did_old_sp = true; @@ -2623,6 +2623,7 @@ if (block_begin == entry_point()) stream->print_cr("[Entry Point]"); if (block_begin == verified_entry_point()) stream->print_cr("[Verified Entry Point]"); + if (block_begin == verified_value_entry_point()) stream->print_cr("[Verified Value Entry Point]"); if (JVMCI_ONLY(_exception_offset >= 0 &&) block_begin == exception_begin()) stream->print_cr("[Exception Handler]"); if (block_begin == stub_begin()) stream->print_cr("[Stub Code]"); if (JVMCI_ONLY(_deopt_handler_begin != NULL &&) block_begin == deopt_handler_begin()) stream->print_cr("[Deopt Handler Code]"); --- old/src/hotspot/share/code/nmethod.hpp 2018-12-18 11:42:40.944727782 +0100 +++ new/src/hotspot/share/code/nmethod.hpp 2018-12-18 11:42:40.616891787 +0100 @@ -90,6 +90,7 @@ // offsets for entry points address _entry_point; // entry point with class check address _verified_entry_point; // entry point without class check + address _verified_value_entry_point; // value type entry point without class check address _osr_entry_point; // entry point for on stack replacement // Offsets for different nmethod parts @@ -317,6 +318,7 @@ // entry points address entry_point() const { return _entry_point; } // normal entry point address verified_entry_point() const { return _verified_entry_point; } // if klass is correct + address verified_value_entry_point() const { return _verified_value_entry_point; } // pass value type args as oops // flag accessing and manipulation bool is_not_installed() const { return _state == not_installed; } --- old/src/hotspot/share/compiler/compileBroker.cpp 2018-12-18 11:42:41.948225769 +0100 +++ new/src/hotspot/share/compiler/compileBroker.cpp 2018-12-18 11:42:41.644377773 +0100 @@ -1188,18 +1188,6 @@ assert(WhiteBoxAPI || TieredCompilation || comp_level == CompLevel_highest_tier, "only CompLevel_highest_tier must be used in non-tiered"); // return quickly if possible - // Lambda forms with an Object in their signature can be passed a - // value type. If compiled as root of a compilation, C2 has no way - // to know a value type is passed. - if (ValueTypePassFieldsAsArgs && method->is_compiled_lambda_form()) { - ResourceMark rm; - for (SignatureStream ss(method->signature()); !ss.at_return_type(); ss.next()) { - if (ss.type() == T_VALUETYPE) { - return NULL; - } - } - } - // lock, make sure that the compilation // isn't prohibited in a straightforward way. AbstractCompiler* comp = CompileBroker::compiler(comp_level); --- old/src/hotspot/share/jvmci/jvmciCodeInstaller.cpp 2018-12-18 11:42:43.015691754 +0100 +++ new/src/hotspot/share/jvmci/jvmciCodeInstaller.cpp 2018-12-18 11:42:42.691853758 +0100 @@ -1320,6 +1320,7 @@ break; case VERIFIED_ENTRY: _offsets.set_value(CodeOffsets::Verified_Entry, pc_offset); + _offsets.set_value(CodeOffsets::Verified_Value_Entry, pc_offset); break; case OSR_ENTRY: _offsets.set_value(CodeOffsets::OSR_Entry, pc_offset); --- old/src/hotspot/share/oops/method.cpp 2018-12-18 11:42:44.099149739 +0100 +++ new/src/hotspot/share/oops/method.cpp 2018-12-18 11:42:43.759319743 +0100 @@ -471,7 +471,6 @@ // safepoint as it is called with references live on the stack at // locations the GC is unaware of. ValueKlass* Method::returned_value_type(Thread* thread) const { - assert(is_returning_vt(), "method return type should be value type"); SignatureStream ss(signature()); while (!ss.at_return_type()) { ss.next(); @@ -488,8 +487,16 @@ } #endif -bool Method::has_value_args() const { - return adapter()->get_sig_extended() != NULL; +bool Method::has_scalarized_args() const { + return adapter() != NULL ? (adapter()->get_sig_cc() != NULL) : false; +} + +bool Method::needs_stack_repair() const { + return adapter() != NULL ? (adapter()->get_res_entry()._offset != -1) : false; +} + +SigEntry Method::get_res_entry() const { + return adapter() != NULL ? adapter()->get_res_entry() : SigEntry(); } bool Method::is_empty_method() const { @@ -941,7 +948,7 @@ _from_compiled_value_entry = NULL; } else { _from_compiled_entry = adapter()->get_c2i_entry(); - _from_compiled_value_entry = adapter()->get_c2i_entry(); + _from_compiled_value_entry = adapter()->get_c2i_value_entry(); } OrderAccess::storestore(); _from_interpreted_entry = _i2i_entry; @@ -1104,7 +1111,7 @@ // Adapters for compiled code are made eagerly here. They are fairly // small (generally < 100 bytes) and quick to make (and cached and shared) // so making them eagerly shouldn't be too expensive. - AdapterHandlerEntry* adapter = AdapterHandlerLibrary::get_adapter(mh, CHECK_0); + AdapterHandlerEntry* adapter = AdapterHandlerLibrary::get_adapter(mh); if (adapter == NULL ) { if (!is_init_completed()) { // Don't throw exceptions during VM initialization because java.lang.* classes @@ -1123,7 +1130,7 @@ } else { mh->set_adapter_entry(adapter); mh->_from_compiled_entry = adapter->get_c2i_entry(); - mh->_from_compiled_value_entry = adapter->get_c2i_entry(); + mh->_from_compiled_value_entry = adapter->get_c2i_value_entry(); } return adapter->get_c2i_entry(); } @@ -1192,7 +1199,7 @@ OrderAccess::storestore(); mh->_from_compiled_entry = code->verified_entry_point(); - mh->_from_compiled_value_entry = code->verified_entry_point(); + mh->_from_compiled_value_entry = code->verified_value_entry_point(); OrderAccess::storestore(); // Instantly compiled code can execute. if (!mh->is_method_handle_intrinsic()) --- old/src/hotspot/share/oops/method.hpp 2018-12-18 11:42:44.910743728 +0100 +++ new/src/hotspot/share/oops/method.hpp 2018-12-18 11:42:44.586905732 +0100 @@ -104,7 +104,7 @@ // Entry point for calling from compiled code, to compiled code if it exists // or else the interpreter. volatile address _from_compiled_entry; // Cache of: _code ? _code->entry_point() : _adapter->c2i_entry() - volatile address _from_compiled_value_entry; // Cache of: _code ? _code->value_entry_point() : _adapter->c2i_entry() + volatile address _from_compiled_value_entry; // Cache of: _code ? _code->value_entry_point() : _adapter->c2i_value_entry() // The entry point for calling both from and to compiled code is // "_code->entry_point()". Because of tiered compilation and de-opt, this // field can come and go. It can transition from NULL to not-null at any @@ -576,11 +576,14 @@ Symbol* klass_name() const; // returns the name of the method holder BasicType result_type() const; // type of the method result bool may_return_oop() const { BasicType r = result_type(); return (r == T_OBJECT || r == T_ARRAY || r == T_VALUETYPE); } - bool is_returning_vt() const { BasicType r = result_type(); return r == T_VALUETYPE; } #ifdef ASSERT ValueKlass* returned_value_type(Thread* thread) const; #endif - bool has_value_args() const; + + // Support for scalarized value type calling convention + bool has_scalarized_args() const; + bool needs_stack_repair() const; + SigEntry get_res_entry() const; // Checked exceptions thrown by this method (resolved to mirrors) objArrayHandle resolved_checked_exceptions(TRAPS) { return resolved_checked_exceptions_impl(this, THREAD); } --- old/src/hotspot/share/oops/valueKlass.cpp 2018-12-18 11:42:45.774311715 +0100 +++ new/src/hotspot/share/oops/valueKlass.cpp 2018-12-18 11:42:45.442477720 +0100 @@ -267,10 +267,10 @@ // the offset of each field in the value type: i2c and c2i adapters // need that to load or store fields. Finally, the list of fields is // sorted in order of increasing offsets: the adapters and the -// compiled code need and agreed upon order of fields. +// compiled code need to agree upon the order of fields. // // The list of basic types that is returned starts with a T_VALUETYPE -// and ends with an extra T_VOID. T_VALUETYPE/T_VOID are used as +// and ends with an extra T_VOID. T_VALUETYPE/T_VOID pairs are used as // delimiters. Every entry between the two is a field of the value // type. If there's an embedded value type in the list, it also starts // with a T_VALUETYPE and ends with a T_VOID. This is so we can @@ -280,74 +280,58 @@ // T_VALUETYPE, drop everything until and including the closing // T_VOID) or the compiler point of view (each field of the value // types is an argument: drop all T_VALUETYPE/T_VOID from the list). -GrowableArray ValueKlass::collect_fields(int base_off) const { - GrowableArray sig_extended; - sig_extended.push(SigEntry(T_VALUETYPE, base_off)); +int ValueKlass::collect_fields(GrowableArray* sig, int base_off) const { + int count = 0; + SigEntry::add_entry(sig, T_VALUETYPE, base_off); for (JavaFieldStream fs(this); !fs.done(); fs.next()) { if (fs.access_flags().is_static()) continue; - fieldDescriptor& fd = fs.field_descriptor(); - BasicType bt = fd.field_type(); - int offset = base_off + fd.offset() - (base_off > 0 ? first_field_offset() : 0); - if (bt == T_VALUETYPE) { - if (fd.is_flattened()) { - Symbol* signature = fd.signature(); - JavaThread* THREAD = JavaThread::current(); - oop loader = class_loader(); - oop domain = protection_domain(); - ResetNoHandleMark rnhm; - HandleMark hm; - NoSafepointVerifier nsv; - Klass* klass = SystemDictionary::resolve_or_null(signature, - Handle(THREAD, loader), Handle(THREAD, domain), - THREAD); - assert(klass != NULL && !HAS_PENDING_EXCEPTION, "lookup shouldn't fail"); - const GrowableArray& embedded = ValueKlass::cast(klass)->collect_fields(offset); - sig_extended.appendAll(&embedded); - } else { - sig_extended.push(SigEntry(T_VALUETYPEPTR, offset)); - } + int offset = base_off + fs.offset() - (base_off > 0 ? first_field_offset() : 0); + if (fs.is_flattened()) { + // Resolve klass of flattened value type field and recursively collect fields + Klass* vk = get_value_field_klass(fs.index()); + count += ValueKlass::cast(vk)->collect_fields(sig, offset); } else { - sig_extended.push(SigEntry(bt, offset)); - if (bt == T_LONG || bt == T_DOUBLE) { - sig_extended.push(SigEntry(T_VOID, offset)); + BasicType bt = FieldType::basic_type(fs.signature()); + if (bt == T_VALUETYPE) { + bt = T_OBJECT; } + SigEntry::add_entry(sig, bt, offset); + count += type2size[bt]; } } int offset = base_off + size_helper()*HeapWordSize - (base_off > 0 ? first_field_offset() : 0); - sig_extended.push(SigEntry(T_VOID, offset)); // hack: use T_VOID to mark end of value type fields + SigEntry::add_entry(sig, T_VOID, offset); if (base_off == 0) { - sig_extended.sort(SigEntry::compare); + sig->sort(SigEntry::compare); } - assert(sig_extended.at(0)._bt == T_VALUETYPE && sig_extended.at(sig_extended.length()-1)._bt == T_VOID, "broken structure"); - return sig_extended; + assert(sig->at(0)._bt == T_VALUETYPE && sig->at(sig->length()-1)._bt == T_VOID, "broken structure"); + return count; } -void ValueKlass::initialize_calling_convention() { +void ValueKlass::initialize_calling_convention(TRAPS) { // Because the pack and unpack handler addresses need to be loadable from generated code, // they are stored at a fixed offset in the klass metadata. Since value type klasses do // not have a vtable, the vtable offset is used to store these addresses. - //guarantee(vtable_length() == 0, "vtables are not supported in value klasses"); if (ValueTypeReturnedAsFields || ValueTypePassFieldsAsArgs) { - Thread* THREAD = Thread::current(); - assert(!HAS_PENDING_EXCEPTION, "should have no exception"); ResourceMark rm; - const GrowableArray& sig_vk = collect_fields(); - int nb_fields = SigEntry::count_fields(sig_vk)+1; - Array* extended_sig = MetadataFactory::new_array(class_loader_data(), sig_vk.length(), CHECK_AND_CLEAR); + GrowableArray sig_vk; + int nb_fields = collect_fields(&sig_vk); + Array* extended_sig = MetadataFactory::new_array(class_loader_data(), sig_vk.length(), CHECK); *((Array**)adr_extended_sig()) = extended_sig; for (int i = 0; i < sig_vk.length(); i++) { extended_sig->at_put(i, sig_vk.at(i)); } if (ValueTypeReturnedAsFields) { + nb_fields++; BasicType* sig_bt = NEW_RESOURCE_ARRAY(BasicType, nb_fields); sig_bt[0] = T_METADATA; - SigEntry::fill_sig_bt(sig_vk, sig_bt+1, nb_fields-1, true); + SigEntry::fill_sig_bt(&sig_vk, sig_bt+1); VMRegPair* regs = NEW_RESOURCE_ARRAY(VMRegPair, nb_fields); int total = SharedRuntime::java_return_convention(sig_bt, regs, nb_fields); if (total > 0) { - Array* return_regs = MetadataFactory::new_array(class_loader_data(), nb_fields, CHECK_AND_CLEAR); + Array* return_regs = MetadataFactory::new_array(class_loader_data(), nb_fields, CHECK); *((Array**)adr_return_regs()) = return_regs; for (int i = 0; i < nb_fields; i++) { return_regs->at_put(i, regs[i]); @@ -401,8 +385,7 @@ for (int i = 0; i < sig_vk->length(); i++) { BasicType bt = sig_vk->at(i)._bt; - if (bt == T_OBJECT || bt == T_VALUETYPEPTR || bt == T_ARRAY) { - int off = sig_vk->at(i)._offset; + if (bt == T_OBJECT || bt == T_ARRAY) { VMRegPair pair = regs->at(j); address loc = reg_map.location(pair.first()); oop v = *(oop*)loc; @@ -434,7 +417,6 @@ for (int i = 0, k = 0; i < sig_vk->length(); i++) { BasicType bt = sig_vk->at(i)._bt; if (bt == T_OBJECT || bt == T_ARRAY) { - int off = sig_vk->at(i)._offset; VMRegPair pair = regs->at(j); address loc = reg_map.location(pair.first()); *(oop*)loc = handles.at(k++)(); @@ -455,7 +437,6 @@ // Fields are in registers. Create an instance of the value type and // initialize it with the values of the fields. oop ValueKlass::realloc_result(const RegisterMap& reg_map, const GrowableArray& handles, TRAPS) { - oop new_vt = allocate_instance(CHECK_NULL); const Array* sig_vk = extended_sig(); const Array* regs = return_regs(); @@ -475,58 +456,50 @@ continue; } int off = sig_vk->at(i)._offset; + assert(off > 0, "offset in object should be positive"); VMRegPair pair = regs->at(j); address loc = reg_map.location(pair.first()); switch(bt) { case T_BOOLEAN: { - jboolean v = *(intptr_t*)loc; - *(jboolean*)((address)new_vt + off) = v; + new_vt->bool_field_put(off, *(jboolean*)loc); break; } case T_CHAR: { - jchar v = *(intptr_t*)loc; - *(jchar*)((address)new_vt + off) = v; + new_vt->char_field_put(off, *(jchar*)loc); break; } case T_BYTE: { - jbyte v = *(intptr_t*)loc; - *(jbyte*)((address)new_vt + off) = v; + new_vt->byte_field_put(off, *(jbyte*)loc); break; } case T_SHORT: { - jshort v = *(intptr_t*)loc; - *(jshort*)((address)new_vt + off) = v; + new_vt->short_field_put(off, *(jshort*)loc); break; } case T_INT: { - jint v = *(intptr_t*)loc; - *(jint*)((address)new_vt + off) = v; + new_vt->int_field_put(off, *(jint*)loc); break; } case T_LONG: { #ifdef _LP64 - jlong v = *(intptr_t*)loc; - *(jlong*)((address)new_vt + off) = v; + new_vt->double_field_put(off, *(jdouble*)loc); #else Unimplemented(); #endif break; } case T_OBJECT: - case T_VALUETYPEPTR: case T_ARRAY: { Handle handle = handles.at(k++); - HeapAccess<>::oop_store_at(new_vt, off, handle()); + new_vt->obj_field_put(off, handle()); break; } case T_FLOAT: { - jfloat v = *(jfloat*)loc; - *(jfloat*)((address)new_vt + off) = v; + new_vt->float_field_put(off, *(jfloat*)loc); break; } case T_DOUBLE: { - jdouble v = *(jdouble*)loc; - *(jdouble*)((address)new_vt + off) = v; + new_vt->double_field_put(off, *(jdouble*)loc); break; } default: --- old/src/hotspot/share/oops/valueKlass.hpp 2018-12-18 11:42:46.833781701 +0100 +++ new/src/hotspot/share/oops/valueKlass.hpp 2018-12-18 11:42:46.525935705 +0100 @@ -111,9 +111,7 @@ return ((address)_adr_valueklass_fixed_block) + in_bytes(default_value_offset_offset()); } - // static Klass* array_klass_impl(InstanceKlass* this_k, bool or_null, int n, TRAPS); - - GrowableArray collect_fields(int base_off = 0) const; + int collect_fields(GrowableArray* sig, int base_off = 0) const; void cleanup_blobs(); @@ -195,7 +193,7 @@ inline void oop_iterate_specialized_bounded(const address oop_addr, OopClosureType* closure, void* lo, void* hi); // calling convention support - void initialize_calling_convention(); + void initialize_calling_convention(TRAPS); Array* extended_sig() const { return *((Array**)adr_extended_sig()); } @@ -211,13 +209,11 @@ // pack and unpack handlers. Need to be loadable from generated code // so at a fixed offset from the base of the klass pointer. static ByteSize pack_handler_offset() { - fatal("Should be re-implemented using the ValueKlassStaticBlock indirection"); - return in_ByteSize(InstanceKlass::header_size() * wordSize); + return byte_offset_of(ValueKlassFixedBlock, _pack_handler); } static ByteSize unpack_handler_offset() { - fatal("Should be re-implemented using the ValueKlassStaticBlock indirection"); - return in_ByteSize((InstanceKlass::header_size()+1) * wordSize); + return byte_offset_of(ValueKlassFixedBlock, _unpack_handler); } static ByteSize default_value_offset_offset() { --- old/src/hotspot/share/opto/callGenerator.cpp 2018-12-18 11:42:47.569413690 +0100 +++ new/src/hotspot/share/opto/callGenerator.cpp 2018-12-18 11:42:47.281557695 +0100 @@ -123,14 +123,14 @@ public: DirectCallGenerator(ciMethod* method, bool separate_io_proj) : CallGenerator(method), + _call_node(NULL), _separate_io_proj(separate_io_proj) { - // TODO fix this with the calling convention changes - if (false /*method->signature()->return_type()->is__Value()*/) { - // If that call has not been optimized by the time optimizations - // are over, we'll need to add a call to create a value type - // instance from the klass returned by the call. Separating - // memory and I/O projections for exceptions is required to + if (ValueTypeReturnedAsFields && method->is_method_handle_intrinsic()) { + // If that call has not been optimized by the time optimizations are over, + // we'll need to add a call to create a value type instance from the klass + // returned by the call (see PhaseMacroExpand::expand_mh_intrinsic_return). + // Separating memory and I/O projections for exceptions is required to // perform that graph transformation. _separate_io_proj = true; } @@ -436,24 +436,27 @@ uint j = TypeFunc::Parms; for (uint i1 = 0; i1 < nargs; i1++) { const Type* t = domain_sig->field_at(TypeFunc::Parms + i1); - if (!ValueTypePassFieldsAsArgs) { - Node* arg = call->in(TypeFunc::Parms + i1); - map->set_argument(jvms, i1, arg); - } else { - assert(false, "FIXME"); - // TODO move this into Parse::Parse because we might need to deopt - /* + if (method()->get_Method()->has_scalarized_args()) { GraphKit arg_kit(jvms, &gvn); - if (t->is_valuetypeptr()) { - ciValueKlass* vk = t->value_klass(); - ValueTypeNode* vt = ValueTypeNode::make_from_multi(&arg_kit, call, vk, j, true); - arg_kit.set_argument(i1, vt); - j += vk->value_arg_slots(); + // TODO for now, don't scalarize value type receivers because of interface calls + if (t->is_valuetypeptr() && (method()->is_static() || i1 != 0)) { + arg_kit.set_control(map->control()); + ValueTypeNode* vt = ValueTypeNode::make_from_multi(&arg_kit, call, t->value_klass(), j, true); + map->set_control(arg_kit.control()); + map->set_argument(jvms, i1, vt); } else { - arg_kit.set_argument(i1, call->in(j)); + int index = j; + SigEntry res_entry = method()->get_Method()->get_res_entry(); + if (res_entry._offset != -1 && (index - TypeFunc::Parms) >= res_entry._offset) { + // Skip reserved entry + index += type2size[res_entry._bt]; + } + map->set_argument(jvms, i1, call->in(index)); j++; } - */ + } else { + Node* arg = call->in(TypeFunc::Parms + i1); + map->set_argument(jvms, i1, arg); } } @@ -502,20 +505,18 @@ bool returned_as_fields = call->tf()->returns_value_type_as_fields(); if (result->is_ValueType()) { ValueTypeNode* vt = result->as_ValueType(); - if (!returned_as_fields) { - result = ValueTypePtrNode::make_from_value_type(&kit, vt); - } else { - assert(false, "FIXME"); + if (returned_as_fields) { // Return of multiple values (the fields of a value type) vt->replace_call_results(&kit, call, C); - if (gvn.type(vt->get_oop()) == TypePtr::NULL_PTR) { - result = vt->tagged_klass(gvn); - } else { + if (vt->is_allocated(&gvn) && !StressValueTypeReturnedAsFields) { result = vt->get_oop(); + } else { + result = vt->tagged_klass(gvn); } + } else { + result = ValueTypePtrNode::make_from_value_type(&kit, vt); } } else if (gvn.type(result)->is_valuetypeptr() && returned_as_fields) { - assert(false, "FIXME"); const Type* vt_t = call->_tf->range_sig()->field_at(TypeFunc::Parms); Node* cast = new CheckCastPPNode(NULL, result, vt_t); gvn.record_for_igvn(cast); --- old/src/hotspot/share/opto/callnode.hpp 2018-12-18 11:42:48.097149683 +0100 +++ new/src/hotspot/share/opto/callnode.hpp 2018-12-18 11:42:47.785305688 +0100 @@ -727,9 +727,9 @@ method != NULL && method->is_method_handle_intrinsic() && r->cnt() > TypeFunc::Parms && - r->field_at(TypeFunc::Parms)->is_valuetypeptr() && - // TODO fix this with the calling convention changes - false /*r->field_at(TypeFunc::Parms)->is_valuetypeptr()->is__Value()*/) { + r->field_at(TypeFunc::Parms)->isa_oopptr() && + r->field_at(TypeFunc::Parms)->is_oopptr()->can_be_value_type()) { + // Make sure this call is processed by PhaseMacroExpand::expand_mh_intrinsic_return init_flags(Flag_is_macro); C->add_macro_node(this); } --- old/src/hotspot/share/opto/chaitin.cpp 2018-12-18 11:42:48.600897676 +0100 +++ new/src/hotspot/share/opto/chaitin.cpp 2018-12-18 11:42:48.309043681 +0100 @@ -2229,6 +2229,9 @@ _matcher._parm_regs[j].second() == reg ) { tty->print("parm %d: ",j); domain->field_at(j + TypeFunc::Parms)->dump(); + if (j == C->get_res_entry()._offset) { + tty->print(" [RESERVED] "); + } tty->cr(); break; } --- old/src/hotspot/share/opto/compile.cpp 2018-12-18 11:42:49.360517666 +0100 +++ new/src/hotspot/share/opto/compile.cpp 2018-12-18 11:42:49.064665670 +0100 @@ -547,6 +547,12 @@ ResourceMark rm; _scratch_const_size = const_size; int size = C2Compiler::initial_code_buffer_size(const_size); +#ifdef ASSERT + if (C->has_scalarized_args()) { + // Oop verification for loading object fields from scalarized value types in the new entry point requires lots of space + size += 5120; + } +#endif blob = BufferBlob::create("Compile::scratch_buffer", size); // Record the buffer blob for next time. set_scratch_buffer_blob(blob); @@ -611,6 +617,9 @@ masm.bind(fakeL); n->as_MachBranch()->save_label(&saveL, &save_bnum); n->as_MachBranch()->label_set(&fakeL, 0); + } else if (n->is_MachProlog()) { + saveL = ((MachPrologNode*)n)->_verified_entry; + ((MachPrologNode*)n)->_verified_entry = &fakeL; } n->emit(buf, this->regalloc()); @@ -620,6 +629,8 @@ // Restore label. if (is_branch) { n->as_MachBranch()->label_set(saveL, save_bnum); + } else if (n->is_MachProlog()) { + ((MachPrologNode*)n)->_verified_entry = saveL; } // End scratch_emit_size section. @@ -653,6 +664,8 @@ _max_node_limit(MaxNodeLimit), _orig_pc_slot(0), _orig_pc_slot_offset_in_bytes(0), + _sp_inc_slot(0), + _sp_inc_slot_offset_in_bytes(0), _inlining_progress(false), _inlining_incrementally(false), _has_reserved_stack_access(target->has_reserved_stack_access()), @@ -912,8 +925,15 @@ // Now that we know the size of all the monitors we can add a fixed slot // for the original deopt pc. - _orig_pc_slot = fixed_slots(); + _orig_pc_slot = fixed_slots(); int next_slot = _orig_pc_slot + (sizeof(address) / VMRegImpl::stack_slot_size); + + if (method()->get_Method()->needs_stack_repair()) { + // One extra slot for the special stack increment value + _sp_inc_slot = next_slot; + next_slot += 2; + } + set_fixed_slots(next_slot); // Compute when to use implicit null checks. Used by matching trap based @@ -939,6 +959,13 @@ _code_offsets.set_value(CodeOffsets::OSR_Entry, _first_block_size); } else { _code_offsets.set_value(CodeOffsets::Verified_Entry, _first_block_size); + if (_code_offsets.value(CodeOffsets::Verified_Value_Entry) == -1) { + _code_offsets.set_value(CodeOffsets::Verified_Value_Entry, _first_block_size); + } + if (_code_offsets.value(CodeOffsets::Entry) == -1) { + // We emitted a value type entry point, adjust normal entry + _code_offsets.set_value(CodeOffsets::Entry, _first_block_size); + } _code_offsets.set_value(CodeOffsets::OSR_Entry, 0); } @@ -984,6 +1011,8 @@ _max_node_limit(MaxNodeLimit), _orig_pc_slot(0), _orig_pc_slot_offset_in_bytes(0), + _sp_inc_slot(0), + _sp_inc_slot_offset_in_bytes(0), _inlining_progress(false), _inlining_incrementally(false), _has_reserved_stack_access(false), --- old/src/hotspot/share/opto/compile.hpp 2018-12-18 11:42:50.407993651 +0100 +++ new/src/hotspot/share/opto/compile.hpp 2018-12-18 11:42:50.104145656 +0100 @@ -381,6 +381,10 @@ int _orig_pc_slot; int _orig_pc_slot_offset_in_bytes; + // For value type calling convention + int _sp_inc_slot; + int _sp_inc_slot_offset_in_bytes; + int _major_progress; // Count of something big happening bool _inlining_progress; // progress doing incremental inlining? bool _inlining_incrementally;// Are we doing incremental inlining (post parse) @@ -715,6 +719,12 @@ uint max_node_limit() const { return (uint)_max_node_limit; } void set_max_node_limit(uint n) { _max_node_limit = n; } + // Support for scalarized value type calling convention + bool has_scalarized_args() const { return _method != NULL && _method->get_Method()->has_scalarized_args(); } + bool needs_stack_repair() const { return _method != NULL && _method->get_Method()->needs_stack_repair(); } + SigEntry get_res_entry() const { return _method->get_Method()->get_res_entry(); } + int sp_inc_offset() const { return _sp_inc_slot_offset_in_bytes; } + // check the CompilerOracle for special behaviours for this compile bool method_has_option(const char * option) { return method() != NULL && method()->has_option(option); --- old/src/hotspot/share/opto/connode.cpp 2018-12-18 11:42:51.159617641 +0100 +++ new/src/hotspot/share/opto/connode.cpp 2018-12-18 11:42:50.863765645 +0100 @@ -50,7 +50,6 @@ case T_FLOAT: return new ConFNode( t->is_float_constant() ); case T_DOUBLE: return new ConDNode( t->is_double_constant() ); case T_VOID: return new ConNode ( Type::TOP ); - case T_VALUETYPEPTR: case T_OBJECT: return new ConPNode( t->is_ptr() ); case T_ARRAY: return new ConPNode( t->is_aryptr() ); case T_ADDRESS: return new ConPNode( t->is_ptr() ); --- old/src/hotspot/share/opto/escape.cpp 2018-12-18 11:42:51.907243631 +0100 +++ new/src/hotspot/share/opto/escape.cpp 2018-12-18 11:42:51.611391634 +0100 @@ -2125,9 +2125,7 @@ } } } - // TODO enable when using T_VALUETYPEPTR - //assert(bt != T_VALUETYPE, "should not have valuetype here"); - return (bt == T_OBJECT || bt == T_VALUETYPE || bt == T_VALUETYPEPTR || bt == T_NARROWOOP || bt == T_ARRAY); + return (bt == T_OBJECT || bt == T_VALUETYPE || bt == T_NARROWOOP || bt == T_ARRAY); } // Returns unique pointed java object or NULL. --- old/src/hotspot/share/opto/graphKit.cpp 2018-12-18 11:42:52.922735617 +0100 +++ new/src/hotspot/share/opto/graphKit.cpp 2018-12-18 11:42:52.610891621 +0100 @@ -1544,7 +1544,7 @@ } ld = _gvn.transform(ld); - if (((bt == T_OBJECT || bt == T_VALUETYPE || bt == T_VALUETYPEPTR) && C->do_escape_analysis()) || C->eliminate_boxing()) { + if (((bt == T_OBJECT || bt == T_VALUETYPE) && C->do_escape_analysis()) || C->eliminate_boxing()) { // Improve graph before escape analysis and boxing elimination. record_for_igvn(ld); } @@ -1791,7 +1791,8 @@ if (arg->is_ValueType()) { assert(t->is_oopptr()->can_be_value_type(), "wrong argument type"); ValueTypeNode* vt = arg->as_ValueType(); - if (ValueTypePassFieldsAsArgs) { + // TODO for now, don't scalarize value type receivers because of interface calls + if (call->method()->get_Method()->has_scalarized_args() && t->is_valuetypeptr() && (call->method()->is_static() || i != TypeFunc::Parms)) { // We don't pass value type arguments by reference but instead // pass each field of the value type idx += vt->pass_fields(call, idx, *this); @@ -1805,8 +1806,16 @@ arg = vt->allocate(this)->get_oop(); } } - call->init_req(idx, arg); - idx++; + call->init_req(idx++, arg); + + SigEntry res_entry = call->method()->get_Method()->get_res_entry(); + if ((int)(idx - TypeFunc::Parms) == res_entry._offset) { + // Skip reserved entry + call->init_req(idx++, top()); + if (res_entry._bt == T_DOUBLE || res_entry._bt == T_LONG) { + call->init_req(idx++, top()); + } + } } } @@ -1862,18 +1871,15 @@ if (call->method() == NULL || call->method()->return_type()->basic_type() == T_VOID) { ret = top(); + } else if (call->tf()->returns_value_type_as_fields()) { + // Return of multiple values (value type fields): we create a + // ValueType node, each field is a projection from the call. + const TypeTuple* range_sig = call->tf()->range_sig(); + const Type* t = range_sig->field_at(TypeFunc::Parms); + uint base_input = TypeFunc::Parms + 1; + ret = ValueTypeNode::make_from_multi(this, call, t->value_klass(), base_input, false); } else { - if (!call->tf()->returns_value_type_as_fields()) { - ret = _gvn.transform(new ProjNode(call, TypeFunc::Parms)); - } else { - // Return of multiple values (value type fields): we create a - // ValueType node, each field is a projection from the call. - const TypeTuple* range_sig = call->tf()->range_sig(); - const Type* t = range_sig->field_at(TypeFunc::Parms); - assert(t->is_valuetypeptr(), "only value types for multiple return values"); - ciValueKlass* vk = t->value_klass(); - ret = ValueTypeNode::make_from_multi(this, call, vk, TypeFunc::Parms+1, false); - } + ret = _gvn.transform(new ProjNode(call, TypeFunc::Parms)); } return ret; --- old/src/hotspot/share/opto/machnode.hpp 2018-12-18 11:42:53.682355607 +0100 +++ new/src/hotspot/share/opto/machnode.hpp 2018-12-18 11:42:53.390501610 +0100 @@ -463,6 +463,22 @@ int constant_offset_unchecked() const; }; +//------------------------------MachVVEPNode----------------------------------- +// Machine Verified Value Type Entry Point Node +class MachVVEPNode : public MachIdealNode { +public: + MachVVEPNode(Label* verified_entry) : _verified_entry(verified_entry) {} + virtual void emit(CodeBuffer& cbuf, PhaseRegAlloc* ra_) const; + virtual uint size(PhaseRegAlloc* ra_) const; + +#ifndef PRODUCT + virtual const char* Name() const { return "Verified ValueType Entry-Point"; } + virtual void format(PhaseRegAlloc*, outputStream* st) const; +#endif +private: + Label* _verified_entry; +}; + //------------------------------MachUEPNode----------------------------------- // Machine Unvalidated Entry Point Node class MachUEPNode : public MachIdealNode { @@ -481,11 +497,14 @@ // Machine function Prolog Node class MachPrologNode : public MachIdealNode { public: - MachPrologNode( ) {} + MachPrologNode(Label* verified_entry) : _verified_entry(verified_entry) { + init_class_id(Class_MachProlog); + } virtual void emit(CodeBuffer &cbuf, PhaseRegAlloc *ra_) const; virtual uint size(PhaseRegAlloc *ra_) const; virtual int reloc() const; + Label* _verified_entry; #ifndef PRODUCT virtual const char *Name() const { return "Prolog"; } virtual void format( PhaseRegAlloc *, outputStream *st ) const; --- old/src/hotspot/share/opto/macro.cpp 2018-12-18 11:42:54.497947595 +0100 +++ new/src/hotspot/share/opto/macro.cpp 2018-12-18 11:42:54.174109599 +0100 @@ -2497,7 +2497,7 @@ _igvn.replace_node(_memproj_fallthrough, mem_phi); } -// A value type is returned from the call but we don't know its +// A value type might be returned from the call but we don't know its // type. Either we get a buffered value (and nothing needs to be done) // or one of the values being returned is the klass of the value type // and we need to allocate a value type instance of that type and @@ -2505,18 +2505,16 @@ // first try a fast path allocation and initialize the value with the // value klass's pack handler or we fall back to a runtime call. void PhaseMacroExpand::expand_mh_intrinsic_return(CallStaticJavaNode* call) { - Node* ret = call->proj_out(TypeFunc::Parms); + assert(call->method()->is_method_handle_intrinsic(), "must be a method handle intrinsic call"); + Node* ret = call->proj_out_or_null(TypeFunc::Parms); if (ret == NULL) { return; } - // TODO fix this with the calling convention changes - //assert(ret->bottom_type()->is_valuetypeptr()->is__Value(), "unexpected return type from MH intrinsic"); const TypeFunc* tf = call->_tf; const TypeTuple* domain = OptoRuntime::store_value_type_fields_Type()->domain_cc(); const TypeFunc* new_tf = TypeFunc::make(tf->domain_sig(), tf->domain_cc(), tf->range_sig(), domain); call->_tf = new_tf; - // Make sure the change of type is applied before projections are - // processed by igvn + // Make sure the change of type is applied before projections are processed by igvn _igvn.set_type(call, call->Value(&_igvn)); _igvn.set_type(ret, ret->Value(&_igvn)); @@ -2541,7 +2539,7 @@ Node* allocation_ctl = transform_later(new IfTrueNode(allocation_iff)); Node* no_allocation_ctl = transform_later(new IfFalseNode(allocation_iff)); - Node* no_allocation_res = transform_later(new CheckCastPPNode(no_allocation_ctl, res, TypeInstPtr::NOTNULL)); + Node* no_allocation_res = transform_later(new CheckCastPPNode(no_allocation_ctl, res, TypeInstPtr::BOTTOM)); Node* mask2 = MakeConX(-2); Node* masked2 = transform_later(new AndXNode(cast, mask2)); @@ -2622,7 +2620,8 @@ if (UseCompressedClassPointers) { rawmem = make_store(slowpath_false, rawmem, old_top, oopDesc::klass_gap_offset_in_bytes(), intcon(0), T_INT); } - Node* pack_handler = make_load(slowpath_false, rawmem, klass_node, in_bytes(ValueKlass::pack_handler_offset()), TypeRawPtr::BOTTOM, T_ADDRESS); + Node* fixed_block = make_load(slowpath_false, rawmem, klass_node, in_bytes(InstanceKlass::adr_valueklass_fixed_block_offset()), TypeRawPtr::BOTTOM, T_ADDRESS); + Node* pack_handler = make_load(slowpath_false, rawmem, fixed_block, in_bytes(ValueKlass::pack_handler_offset()), TypeRawPtr::BOTTOM, T_ADDRESS); CallLeafNoFPNode* handler_call = new CallLeafNoFPNode(OptoRuntime::pack_value_type_Type(), NULL, @@ -2665,7 +2664,7 @@ Node* r = new RegionNode(4); Node* mem_phi = new PhiNode(r, Type::MEMORY, TypePtr::BOTTOM); Node* io_phi = new PhiNode(r, Type::ABIO); - Node* res_phi = new PhiNode(r, ret->bottom_type()); + Node* res_phi = new PhiNode(r, TypeInstPtr::BOTTOM); r->init_req(1, no_allocation_ctl); mem_phi->init_req(1, mem); --- old/src/hotspot/share/opto/matcher.cpp 2018-12-18 11:42:55.489451581 +0100 +++ new/src/hotspot/share/opto/matcher.cpp 2018-12-18 11:42:55.193599585 +0100 @@ -492,9 +492,20 @@ // At first, start with the empty mask C->FIRST_STACK_mask().Clear(); + // Check if method has a reserved entry in the argument stack area that + // should not be used for spilling because it holds the return address. + OptoRegPair res_entry; + if (C->needs_stack_repair()) { + int res_idx = C->get_res_entry()._offset; + res_entry = _parm_regs[res_idx]; + } + // Add in the incoming argument area OptoReg::Name init_in = OptoReg::add(_old_SP, C->out_preserve_stack_slots()); for (i = init_in; i < _in_arg_limit; i = OptoReg::add(i,1)) { + if (i == res_entry.first() || i == res_entry.second()) { + continue; // Skip reserved slot to avoid spilling + } C->FIRST_STACK_mask().Insert(i); } // Add in all bits past the outgoing argument area --- old/src/hotspot/share/opto/memnode.cpp 2018-12-18 11:42:56.536927566 +0100 +++ new/src/hotspot/share/opto/memnode.cpp 2018-12-18 11:42:56.233079571 +0100 @@ -817,7 +817,6 @@ case T_DOUBLE: load = new LoadDNode (ctl, mem, adr, adr_type, rt, mo, control_dependency); break; case T_ADDRESS: load = new LoadPNode (ctl, mem, adr, adr_type, rt->is_ptr(), mo, control_dependency); break; case T_VALUETYPE: - case T_VALUETYPEPTR: case T_OBJECT: #ifdef _LP64 if (adr->bottom_type()->is_ptr_to_narrowoop()) { --- old/src/hotspot/share/opto/node.hpp 2018-12-18 11:42:57.592399552 +0100 +++ new/src/hotspot/share/opto/node.hpp 2018-12-18 11:42:57.284553556 +0100 @@ -102,6 +102,7 @@ class MachNode; class MachNullCheckNode; class MachProjNode; +class MachPrologNode; class MachReturnNode; class MachSafePointNode; class MachSpillCopyNode; @@ -664,6 +665,7 @@ DEFINE_CLASS_ID(MachJump, MachConstant, 0) DEFINE_CLASS_ID(MachMerge, Mach, 6) DEFINE_CLASS_ID(MachMemBar, Mach, 7) + DEFINE_CLASS_ID(MachProlog, Mach, 8) DEFINE_CLASS_ID(Type, Node, 2) DEFINE_CLASS_ID(Phi, Type, 0) @@ -856,6 +858,7 @@ DEFINE_CLASS_QUERY(MachJump) DEFINE_CLASS_QUERY(MachNullCheck) DEFINE_CLASS_QUERY(MachProj) + DEFINE_CLASS_QUERY(MachProlog) DEFINE_CLASS_QUERY(MachReturn) DEFINE_CLASS_QUERY(MachSafePoint) DEFINE_CLASS_QUERY(MachSpillCopy) --- old/src/hotspot/share/opto/output.cpp 2018-12-18 11:42:58.340025541 +0100 +++ new/src/hotspot/share/opto/output.cpp 2018-12-18 11:42:58.048171546 +0100 @@ -71,12 +71,14 @@ const StartNode *start = entry->head()->as_Start(); // Replace StartNode with prolog - MachPrologNode *prolog = new MachPrologNode(); + Label verified_entry; + MachPrologNode* prolog = new MachPrologNode(&verified_entry); entry->map_node(prolog, 0); _cfg->map_node_to_block(prolog, entry); _cfg->unmap_node_from_block(start); // start is no longer in any block // Virtual methods need an unverified entry point + bool has_value_entry = false; if (is_osr_compilation()) { if (PoisonOSREntry) { // TODO: Should use a ShouldNotReachHereNode... @@ -87,6 +89,11 @@ // Insert unvalidated entry point _cfg->insert(broot, 0, new MachUEPNode()); } + if (_method && _method->get_Method()->has_scalarized_args()) { + // Insert value type entry point + _cfg->insert(broot, 0, new MachVVEPNode(&verified_entry)); + has_value_entry = true; + } } // Break before main entry point @@ -122,6 +129,18 @@ return; } + if (has_value_entry) { + // We added an entry point for unscalarized value types + // Compute offset of "normal" entry point + _code_offsets.set_value(CodeOffsets::Verified_Value_Entry, 0); + uint entry_offset = -1; // will be patched later + if (!_method->flags().is_static()) { + MachVVEPNode* vvep = (MachVVEPNode*)broot->get_node(0); + entry_offset = vvep->size(_regalloc); + } + _code_offsets.set_value(CodeOffsets::Entry, entry_offset); + } + ScheduleAndBundle(); #ifndef PRODUCT @@ -973,6 +992,10 @@ if (fixed_slots() != 0) { _orig_pc_slot_offset_in_bytes = _regalloc->reg2offset(OptoReg::stack2reg(_orig_pc_slot)); } + if (C->needs_stack_repair()) { + // Compute the byte offset of the stack increment value + _sp_inc_slot_offset_in_bytes = _regalloc->reg2offset(OptoReg::stack2reg(_sp_inc_slot)); + } // Compute prolog code size _method_size = 0; --- old/src/hotspot/share/opto/parse1.cpp 2018-12-18 11:42:58.863763534 +0100 +++ new/src/hotspot/share/opto/parse1.cpp 2018-12-18 11:42:58.563913538 +0100 @@ -609,15 +609,10 @@ for (uint i = 0; i < (uint)arg_size_sig; i++) { Node* parm = map()->in(i); const Type* t = _gvn.type(parm); - if (!ValueTypePassFieldsAsArgs) { - if (t->is_valuetypeptr() && t->value_klass()->is_scalarizable() && !t->maybe_null()) { - // Create ValueTypeNode from the oop and replace the parameter - Node* vt = ValueTypeNode::make_from_oop(this, parm, t->value_klass()); - map()->replace_edge(parm, vt); - } - } else { - assert(false, "FIXME"); - // TODO move the code from build_start_state and do_late_inline here + if (t->is_valuetypeptr() && t->value_klass()->is_scalarizable() && !t->maybe_null()) { + // Create ValueTypeNode from the oop and replace the parameter + Node* vt = ValueTypeNode::make_from_oop(this, parm, t->value_klass()); + map()->replace_edge(parm, vt); } } @@ -842,12 +837,14 @@ // Construct a state which contains only the incoming arguments from an // unknown caller. The method & bci will be NULL & InvocationEntryBci. JVMState* Compile::build_start_state(StartNode* start, const TypeFunc* tf) { - int arg_size_sig = tf->domain_sig()->cnt(); - int max_size = MAX2(arg_size_sig, (int)tf->range_cc()->cnt()); + int arg_size = tf->domain_sig()->cnt(); + int max_size = MAX2(arg_size, (int)tf->range_cc()->cnt()); JVMState* jvms = new (this) JVMState(max_size - TypeFunc::Parms); SafePointNode* map = new SafePointNode(max_size, NULL); + map->set_jvms(jvms); + jvms->set_map(map); record_for_igvn(map); - assert(arg_size_sig == TypeFunc::Parms + (is_osr_compilation() ? 1 : method()->arg_size()), "correct arg_size"); + assert(arg_size == TypeFunc::Parms + (is_osr_compilation() ? 1 : method()->arg_size()), "correct arg_size"); Node_Notes* old_nn = default_node_notes(); if (old_nn != NULL && has_method()) { Node_Notes* entry_nn = old_nn->clone(this); @@ -859,56 +856,41 @@ } PhaseGVN& gvn = *initial_gvn(); uint j = 0; - for (uint i = 0; i < (uint)arg_size_sig; i++) { - assert(j >= i, "less actual arguments than in the signature?"); - if (ValueTypePassFieldsAsArgs) { - assert(false, "FIXME"); - // TODO move this into Parse::Parse because we might need to deopt - /* - if (i < TypeFunc::Parms) { - assert(i == j, "no change before the actual arguments"); - Node* parm = gvn.transform(new ParmNode(start, i)); - map->init_req(i, parm); - // Record all these guys for later GVN. - record_for_igvn(parm); - j++; - } else { - // Value type arguments are not passed by reference: we get an - // argument per field of the value type. Build ValueTypeNodes - // from the value type arguments. - const Type* t = tf->domain_sig()->field_at(i); - if (t->is_valuetypeptr()) { - ciValueKlass* vk = t->value_klass(); - GraphKit kit(jvms, &gvn); - kit.set_control(map->control()); - ValueTypeNode* vt = ValueTypeNode::make_from_multi(&kit, start, vk, j, true); - map->set_control(kit.control()); - map->init_req(i, vt); - j += vk->value_arg_slots(); - } else { - Node* parm = gvn.transform(new ParmNode(start, j)); - map->init_req(i, parm); - // Record all these guys for later GVN. - record_for_igvn(parm); - j++; - } - } - */ + for (uint i = 0; i < (uint)arg_size; i++) { + const Type* t = tf->domain_sig()->field_at(i); + Node* parm = NULL; + // TODO for now, don't scalarize value type receivers because of interface calls + if (has_scalarized_args() && t->is_valuetypeptr() && (method()->is_static() || i != TypeFunc::Parms)) { + // Value type arguments are not passed by reference: we get an argument per + // field of the value type. Build ValueTypeNodes from the value type arguments. + GraphKit kit(jvms, &gvn); + kit.set_control(map->control()); + Node* old_mem = map->memory(); + // Use immutable memory for value type loads and restore it below + // TODO make sure value types are always loaded from immutable memory + kit.set_all_memory(C->immutable_memory()); + parm = ValueTypeNode::make_from_multi(&kit, start, t->value_klass(), j, true); + map->set_control(kit.control()); + map->set_memory(old_mem); } else { - Node* parm = gvn.transform(new ParmNode(start, i)); - map->init_req(i, parm); - // Record all these guys for later GVN. - record_for_igvn(parm); + int index = j; + SigEntry res_slot = get_res_entry(); + if (res_slot._offset != -1 && (index - TypeFunc::Parms) >= res_slot._offset) { + // Skip reserved entry + index += type2size[res_slot._bt]; + } + parm = gvn.transform(new ParmNode(start, index)); j++; } + map->init_req(i, parm); + // Record all these guys for later GVN. + record_for_igvn(parm); } for (; j < map->req(); j++) { map->init_req(j, top()); } assert(jvms->argoff() == TypeFunc::Parms, "parser gets arguments here"); set_default_node_notes(old_nn); - map->set_jvms(jvms); - jvms->set_map(map); return jvms; } @@ -943,10 +925,14 @@ if (tf()->returns_value_type_as_fields()) { // Multiple return values (value type fields): add as many edges // to the Return node as returned values. - assert(res->is_ValueType(), "what else supports multi value return"); + assert(res->is_ValueType(), "what else supports multi value return?"); ValueTypeNode* vt = res->as_ValueType(); ret->add_req_batch(NULL, tf()->range_cc()->cnt() - TypeFunc::Parms); - vt->pass_klass(ret, TypeFunc::Parms, kit); + if (vt->is_allocated(&kit.gvn()) && !StressValueTypeReturnedAsFields) { + ret->init_req(TypeFunc::Parms, vt->get_oop()); + } else { + ret->init_req(TypeFunc::Parms, vt->tagged_klass(kit.gvn())); + } vt->pass_fields(ret, TypeFunc::Parms+1, kit, /* assert_allocated */ true); } else { ret->add_req(res); @@ -2324,12 +2310,6 @@ //------------------------------return_current--------------------------------- // Append current _map to _exit_return void Parse::return_current(Node* value) { - if (tf()->returns_value_type_as_fields()) { - assert(false, "Fix this with the calling convention changes"); - // Value type is returned as fields, make sure non-flattened value type fields are allocated - // value = value->as_ValueType()->allocate_fields(this); - } - if (RegisterFinalizersAtInit && method()->intrinsic_id() == vmIntrinsics::_Object_init) { call_register_finalizer(); @@ -2350,9 +2330,19 @@ if (value != NULL) { Node* phi = _exits.argument(0); const TypeOopPtr* tr = phi->bottom_type()->isa_oopptr(); + if (tf()->returns_value_type_as_fields() && !_caller->has_method() && !value->is_ValueType()) { + // TODO there should be a checkcast in between, right? + value = ValueTypeNode::make_from_oop(this, value, phi->bottom_type()->is_valuetype()->value_klass()); + } if (value->is_ValueType() && !_caller->has_method()) { - // Value type is returned as oop from root method, make sure it's allocated - value = value->as_ValueType()->allocate(this)->get_oop(); + // Value type is returned as oop from root method + if (tf()->returns_value_type_as_fields()) { + // Make sure non-flattened value type fields are allocated + value = value->as_ValueType()->allocate_fields(this); + } else { + // Make sure value type is allocated + value = value->as_ValueType()->allocate(this)->get_oop(); + } } else if (tr && tr->isa_instptr() && tr->klass()->is_loaded() && tr->klass()->is_interface()) { // If returning oops to an interface-return, there is a silent free // cast from oop to interface allowed by the Verifier. Make it explicit here. --- old/src/hotspot/share/opto/type.cpp 2018-12-18 11:42:59.623383524 +0100 +++ new/src/hotspot/share/opto/type.cpp 2018-12-18 11:42:59.327531529 +0100 @@ -138,7 +138,6 @@ { Bad, T_ADDRESS, "rawptr:", false, Op_RegP, relocInfo::none }, // RawPtr { Bad, T_OBJECT, "oop:", true, Op_RegP, relocInfo::oop_type }, // OopPtr { Bad, T_OBJECT, "inst:", true, Op_RegP, relocInfo::oop_type }, // InstPtr - { Bad, T_VALUETYPEPTR,"valueptr:", true, Op_RegP, relocInfo::oop_type }, // ValueTypePtr { Bad, T_OBJECT, "ary:", true, Op_RegP, relocInfo::oop_type }, // AryPtr { Bad, T_METADATA, "metadata:", false, Op_RegP, relocInfo::metadata_type }, // MetadataPtr { Bad, T_METADATA, "klass:", false, Op_RegP, relocInfo::metadata_type }, // KlassPtr @@ -642,7 +641,6 @@ TypeAryPtr::_array_body_type[T_OBJECT] = TypeAryPtr::OOPS; TypeAryPtr::_array_body_type[T_VALUETYPE] = TypeAryPtr::OOPS; TypeAryPtr::_array_body_type[T_ARRAY] = TypeAryPtr::OOPS; // arrays are stored in oop arrays - TypeAryPtr::_array_body_type[T_VALUETYPEPTR] = NULL; TypeAryPtr::_array_body_type[T_BYTE] = TypeAryPtr::BYTES; TypeAryPtr::_array_body_type[T_BOOLEAN] = TypeAryPtr::BYTES; // boolean[] is a byte array TypeAryPtr::_array_body_type[T_SHORT] = TypeAryPtr::SHORTS; @@ -691,7 +689,6 @@ _const_basic_type[T_FLOAT] = Type::FLOAT; _const_basic_type[T_DOUBLE] = Type::DOUBLE; _const_basic_type[T_OBJECT] = TypeInstPtr::BOTTOM; - _const_basic_type[T_VALUETYPEPTR]= TypeInstPtr::BOTTOM; _const_basic_type[T_ARRAY] = TypeInstPtr::BOTTOM; // there is no separate bottom for arrays _const_basic_type[T_VALUETYPE] = TypeInstPtr::BOTTOM; _const_basic_type[T_VOID] = TypePtr::NULL_PTR; // reflection represents void this way @@ -709,7 +706,6 @@ _zero_type[T_FLOAT] = TypeF::ZERO; _zero_type[T_DOUBLE] = TypeD::ZERO; _zero_type[T_OBJECT] = TypePtr::NULL_PTR; - _zero_type[T_VALUETYPEPTR]= TypePtr::NULL_PTR; _zero_type[T_ARRAY] = TypePtr::NULL_PTR; // null array is null oop _zero_type[T_VALUETYPE] = TypePtr::NULL_PTR; _zero_type[T_ADDRESS] = TypePtr::NULL_PTR; // raw pointers use the same null @@ -1040,7 +1036,6 @@ Bad, // RawPtr - handled in v-call Bad, // OopPtr - handled in v-call Bad, // InstPtr - handled in v-call - Bad, // ValueTypePtr - handled in v-call Bad, // AryPtr - handled in v-call Bad, // MetadataPtr - handled in v-call @@ -1927,8 +1922,15 @@ const TypeTuple *TypeTuple::INT_CC_PAIR; const TypeTuple *TypeTuple::LONG_CC_PAIR; -static void collect_value_fields(ciValueKlass* vk, const Type** field_array, uint& pos) { +static void collect_value_fields(ciValueKlass* vk, const Type** field_array, uint& pos, SigEntry* res_entry = NULL) { for (int j = 0; j < vk->nof_nonstatic_fields(); j++) { + if (res_entry != NULL && (int)pos == (res_entry->_offset + TypeFunc::Parms)) { + // Add reserved entry + field_array[pos++] = Type::get_const_basic_type(res_entry->_bt); + if (res_entry->_bt == T_LONG || res_entry->_bt == T_DOUBLE) { + field_array[pos++] = Type::HALF; + } + } ciField* field = vk->nonstatic_field_at(j); BasicType bt = field->type()->basic_type(); const Type* ft = Type::get_const_type(field->type()); @@ -1998,36 +2000,40 @@ } // Make a TypeTuple from the domain of a method signature -const TypeTuple *TypeTuple::make_domain(ciInstanceKlass* recv, ciSignature* sig, bool vt_fields_as_args) { +const TypeTuple *TypeTuple::make_domain(ciMethod* method, bool vt_fields_as_args) { + ciInstanceKlass* recv = method->is_static() ? NULL : method->holder(); + ciSignature* sig = method->signature(); uint arg_cnt = sig->size(); int vt_extra = 0; + SigEntry res_entry = method->get_Method()->get_res_entry(); if (vt_fields_as_args) { for (int i = 0; i < sig->count(); i++) { ciType* type = sig->type_at(i); - if (type->basic_type() == T_VALUETYPE) { - assert(type->is_valuetype(), "inconsistent type"); - ciValueKlass* vk = (ciValueKlass*)type; - vt_extra += vk->value_arg_slots()-1; + if (type->is_valuetype()) { + vt_extra += type->as_value_klass()->value_arg_slots()-1; } } - assert(((int)arg_cnt) + vt_extra >= 0, "negative number of actual arguments?"); + if (res_entry._offset != -1) { + // Account for the reserved stack slot + vt_extra += type2size[res_entry._bt]; + } } uint pos = TypeFunc::Parms; const Type **field_array; if (recv != NULL) { arg_cnt++; - bool vt_fields_for_recv = vt_fields_as_args && recv->is_valuetype(); + // TODO for now, don't scalarize value type receivers because of interface calls + //bool vt_fields_for_recv = vt_fields_as_args && recv->is_valuetype(); + bool vt_fields_for_recv = false; if (vt_fields_for_recv) { - ciValueKlass* vk = (ciValueKlass*)recv; - vt_extra += vk->value_arg_slots()-1; + vt_extra += recv->as_value_klass()->value_arg_slots()-1; } field_array = fields(arg_cnt + vt_extra); // Use get_const_type here because it respects UseUniqueSubclasses: if (vt_fields_for_recv) { - ciValueKlass* vk = (ciValueKlass*)recv; - collect_value_fields(vk, field_array, pos); + collect_value_fields(recv->as_value_klass(), field_array, pos, &res_entry); } else { field_array[pos++] = get_const_type(recv)->join_speculative(TypePtr::NOTNULL); } @@ -2061,10 +2067,8 @@ field_array[pos++] = TypeInt::INT; break; case T_VALUETYPE: { - assert(type->is_valuetype(), "inconsistent type"); if (vt_fields_as_args) { - ciValueKlass* vk = (ciValueKlass*)type; - collect_value_fields(vk, field_array, pos); + collect_value_fields(type->as_value_klass(), field_array, pos, &res_entry); } else { field_array[pos++] = get_const_type(type)->join_speculative(sig->is_never_null_at(i) ? TypePtr::NOTNULL : TypePtr::BOTTOM); } @@ -2074,6 +2078,14 @@ ShouldNotReachHere(); } i++; + + if (vt_fields_as_args && (int)pos == (res_entry._offset + TypeFunc::Parms)) { + // Add reserved entry + field_array[pos++] = Type::get_const_basic_type(res_entry._bt); + if (res_entry._bt == T_LONG || res_entry._bt == T_DOUBLE) { + field_array[pos++] = Type::HALF; + } + } } assert(pos == TypeFunc::Parms + arg_cnt + vt_extra, "wrong number of arguments"); @@ -3210,7 +3222,6 @@ ciField* field = vk->get_field_by_offset(foffset, false); assert(field != NULL, "missing field"); BasicType bt = field->layout_type(); - assert(bt != T_VALUETYPEPTR, "unexpected type"); _is_ptr_to_narrowoop = (bt == T_OBJECT || bt == T_ARRAY || T_VALUETYPE); } } else if (klass()->is_instance_klass()) { @@ -3253,7 +3264,6 @@ _is_ptr_to_narrowoop = UseCompressedOops && (basic_elem_type == T_OBJECT || basic_elem_type == T_VALUETYPE || basic_elem_type == T_ARRAY); - assert(basic_elem_type != T_VALUETYPEPTR, "unexpected type"); } else if (klass()->equals(ciEnv::current()->Object_klass())) { // Compile::find_alias_type() cast exactness on all types to verify // that it does not affect alias type. @@ -5605,11 +5615,11 @@ // calling convention (with a value type argument as a list of its // fields). if (method->is_static()) { - domain_sig = TypeTuple::make_domain(NULL, method->signature(), false); - domain_cc = TypeTuple::make_domain(NULL, method->signature(), ValueTypePassFieldsAsArgs); + domain_sig = TypeTuple::make_domain(method, false); + domain_cc = TypeTuple::make_domain(method, method->get_Method()->has_scalarized_args()); } else { - domain_sig = TypeTuple::make_domain(method->holder(), method->signature(), false); - domain_cc = TypeTuple::make_domain(method->holder(), method->signature(), ValueTypePassFieldsAsArgs); + domain_sig = TypeTuple::make_domain(method, false); + domain_cc = TypeTuple::make_domain(method, method->get_Method()->has_scalarized_args()); } const TypeTuple *range_sig = TypeTuple::make_range(method->signature(), false); const TypeTuple *range_cc = TypeTuple::make_range(method->signature(), ValueTypeReturnedAsFields); --- old/src/hotspot/share/opto/type.hpp 2018-12-18 11:43:00.650869510 +0100 +++ new/src/hotspot/share/opto/type.hpp 2018-12-18 11:43:00.355017515 +0100 @@ -101,7 +101,6 @@ RawPtr, // Raw (non-oop) pointers OopPtr, // Any and all Java heap entities InstPtr, // Instance pointers (non-array objects) - ValueTypePtr, // Oop to heap allocated value type AryPtr, // Array pointers // (Ptr order matters: See is_ptr, isa_ptr, is_oopptr, isa_oopptr.) @@ -692,7 +691,7 @@ static const TypeTuple *make( uint cnt, const Type **fields ); static const TypeTuple *make_range(ciSignature* sig, bool ret_vt_fields = false); static const TypeTuple *make_range(ciType* return_type, bool never_null = false, bool ret_vt_fields = false); - static const TypeTuple *make_domain(ciInstanceKlass* recv, ciSignature *sig, bool vt_fields_as_args = false); + static const TypeTuple *make_domain(ciMethod* method, bool vt_fields_as_args = false); // Subroutine call type with space allocated for argument types // Memory for Control, I_O, Memory, FramePtr, and ReturnAdr is allocated implicitly --- old/src/hotspot/share/opto/valuetypenode.cpp 2018-12-18 11:43:01.670359496 +0100 +++ new/src/hotspot/share/opto/valuetypenode.cpp 2018-12-18 11:43:01.366511500 +0100 @@ -254,8 +254,9 @@ } } -void ValueTypeBaseNode::initialize(GraphKit* kit, MultiNode* multi, ciValueKlass* vk, int base_offset, int base_input, bool in) { +void ValueTypeBaseNode::initialize(GraphKit* kit, MultiNode* multi, ciValueKlass* vk, int base_offset, uint& base_input, bool in) { assert(base_offset >= 0, "offset in value type must be positive"); + assert(base_input >= TypeFunc::Parms, "invalid base input"); PhaseGVN& gvn = kit->gvn(); for (uint i = 0; i < field_count(); i++) { ciType* ft = field_type(i); @@ -263,7 +264,8 @@ if (field_is_flattened(i)) { // Flattened value type field ValueTypeNode* vt = ValueTypeNode::make_uninitialized(gvn, ft->as_value_klass()); - vt->initialize(kit, multi, vk, offset - value_klass()->first_field_offset(), base_input, in); + uint base = base_input; + vt->initialize(kit, multi, vk, offset - value_klass()->first_field_offset(), base, in); set_field_value(i, gvn.transform(vt)); } else { int j = 0; int extra = 0; @@ -280,26 +282,40 @@ } assert(j != vk->nof_nonstatic_fields(), "must find"); Node* parm = NULL; + int index = base_input + j + extra; + + ciMethod* method = multi->is_Start()? kit->C->method() : multi->as_CallStaticJava()->method(); + SigEntry res_entry = method->get_Method()->get_res_entry(); + if (res_entry._offset != -1 && (index - TypeFunc::Parms) >= res_entry._offset) { + // Skip reserved entry + index += type2size[res_entry._bt]; + } if (multi->is_Start()) { assert(in, "return from start?"); - parm = gvn.transform(new ParmNode(multi->as_Start(), base_input + j + extra)); + parm = gvn.transform(new ParmNode(multi->as_Start(), index)); } else { if (in) { - parm = multi->as_Call()->in(base_input + j + extra); + parm = multi->as_Call()->in(index); } else { - parm = gvn.transform(new ProjNode(multi->as_Call(), base_input + j + extra)); + parm = gvn.transform(new ProjNode(multi->as_Call(), index)); } } - if (ft->is_valuetype()) { - // Non-flattened value type field - assert(!gvn.type(parm)->maybe_null(), "should never be null"); - parm = ValueTypeNode::make_from_oop(kit, parm, ft->as_value_klass()); + + if (field_is_flattenable(i)) { + // Non-flattened but flattenable value type + if (ft->as_value_klass()->is_scalarizable()) { + parm = ValueTypeNode::make_from_oop(kit, parm, ft->as_value_klass()); + } else { + parm = kit->null2default(parm, ft->as_value_klass()); + } } + set_field_value(i, parm); // Record all these guys for later GVN. gvn.record_for_igvn(parm); } } + base_input += vk->value_arg_slots(); } const TypePtr* ValueTypeBaseNode::field_adr_type(Node* base, int offset, ciInstanceKlass* holder, PhaseGVN& gvn) const { @@ -602,7 +618,7 @@ return kit->gvn().transform(vt)->as_ValueType(); } -ValueTypeNode* ValueTypeNode::make_from_multi(GraphKit* kit, MultiNode* multi, ciValueKlass* vk, int base_input, bool in) { +ValueTypeNode* ValueTypeNode::make_from_multi(GraphKit* kit, MultiNode* multi, ciValueKlass* vk, uint& base_input, bool in) { ValueTypeNode* vt = ValueTypeNode::make_uninitialized(kit->gvn(), vk); vt->initialize(kit, multi, vk, 0, base_input, in); return kit->gvn().transform(vt)->as_ValueType(); @@ -686,11 +702,8 @@ return gvn.makecon(TypeRawPtr::make((address)bits)); } -void ValueTypeNode::pass_klass(Node* n, uint pos, const GraphKit& kit) { - n->init_req(pos, tagged_klass(kit.gvn())); -} - uint ValueTypeNode::pass_fields(Node* n, int base_input, GraphKit& kit, bool assert_allocated, ciValueKlass* base_vk, int base_offset) { + assert(base_input >= TypeFunc::Parms, "invalid base input"); ciValueKlass* vk = value_klass(); if (base_vk == NULL) { base_vk = vk; @@ -721,13 +734,29 @@ assert(!assert_allocated || vt->is_allocated(&kit.gvn()), "value type field should be allocated"); arg = vt->allocate(&kit)->get_oop(); } - n->init_req(base_input + j + extra, arg); + + int index = base_input + j + extra; + n->init_req(index++, arg); edges++; BasicType bt = field_type(i)->basic_type(); if (bt == T_LONG || bt == T_DOUBLE) { - n->init_req(base_input + j + extra + 1, kit.top()); + n->init_req(index++, kit.top()); edges++; } + if (n->isa_CallJava()) { + Method* m = n->as_CallJava()->method()->get_Method(); + SigEntry res_entry = m->get_res_entry(); + if ((index - TypeFunc::Parms) == res_entry._offset) { + // Skip reserved entry + int size = type2size[res_entry._bt]; + n->init_req(index++, kit.top()); + if (size == 2) { + n->init_req(index++, kit.top()); + } + base_input += size; + edges += size; + } + } } } return edges; --- old/src/hotspot/share/opto/valuetypenode.hpp 2018-12-18 11:43:02.457965486 +0100 +++ new/src/hotspot/share/opto/valuetypenode.hpp 2018-12-18 11:43:02.158115489 +0100 @@ -51,7 +51,7 @@ int make_scalar_in_safepoint(Unique_Node_List& worklist, SafePointNode* sfpt, Node* root, PhaseGVN* gvn); // Initialize the value type fields with the inputs or outputs of a MultiNode - void initialize(GraphKit* kit, MultiNode* multi, ciValueKlass* vk, int base_offset = 0, int base_input = TypeFunc::Parms+1, bool in = false); + void initialize(GraphKit* kit, MultiNode* multi, ciValueKlass* vk, int base_offset, uint& base_input, bool in); const TypePtr* field_adr_type(Node* base, int offset, ciInstanceKlass* holder, PhaseGVN& gvn) const; @@ -125,7 +125,7 @@ // Create and initialize by loading the field values from a flattened field or array static ValueTypeNode* make_from_flattened(GraphKit* kit, ciValueKlass* vk, Node* obj, Node* ptr, ciInstanceKlass* holder = NULL, int holder_offset = 0); // Create and initialize with the inputs or outputs of a MultiNode (method entry or call) - static ValueTypeNode* make_from_multi(GraphKit* kit, MultiNode* multi, ciValueKlass* vk, int base_input, bool in); + static ValueTypeNode* make_from_multi(GraphKit* kit, MultiNode* multi, ciValueKlass* vk, uint& base_input, bool in); // Returns the constant oop of the default value type allocation static Node* default_oop(PhaseGVN& gvn, ciValueKlass* vk); @@ -134,7 +134,6 @@ Node* allocate_fields(GraphKit* kit); Node* tagged_klass(PhaseGVN& gvn); - void pass_klass(Node* n, uint pos, const GraphKit& kit); uint pass_fields(Node* call, int base_input, GraphKit& kit, bool assert_allocated = false, ciValueKlass* base_vk = NULL, int base_offset = 0); // Allocation optimizations --- old/src/hotspot/share/runtime/arguments.cpp 2018-12-18 11:43:03.201593475 +0100 +++ new/src/hotspot/share/runtime/arguments.cpp 2018-12-18 11:43:02.909739478 +0100 @@ -2063,16 +2063,12 @@ } } - // FIXME - //if (LP64_ONLY(false &&) !FLAG_IS_DEFAULT(ValueTypePassFieldsAsArgs)) { - if (!FLAG_IS_DEFAULT(ValueTypePassFieldsAsArgs)) { + if (LP64_ONLY(false &&) !FLAG_IS_DEFAULT(ValueTypePassFieldsAsArgs)) { FLAG_SET_CMDLINE(bool, ValueTypePassFieldsAsArgs, false); warning("ValueTypePassFieldsAsArgs is not supported on this platform"); } - // FIXME - //if (LP64_ONLY(false &&) !FLAG_IS_DEFAULT(ValueTypeReturnedAsFields)) { - if (!FLAG_IS_DEFAULT(ValueTypeReturnedAsFields)) { + if (LP64_ONLY(false &&) !FLAG_IS_DEFAULT(ValueTypeReturnedAsFields)) { FLAG_SET_CMDLINE(bool, ValueTypeReturnedAsFields, false); warning("ValueTypeReturnedAsFields is not supported on this platform"); } @@ -2089,6 +2085,14 @@ warning("C1 doesn't work with C2 yet. Forcing TieredStopAtLevel=1"); FLAG_SET_CMDLINE(intx, TieredStopAtLevel, 1); } + if (ValueTypePassFieldsAsArgs) { + warning("C1 doesn't work with ValueTypePassFieldsAsArgs yet. Forcing ValueTypePassFieldsAsArgs=false"); + FLAG_SET_CMDLINE(bool, ValueTypePassFieldsAsArgs, false); + } + if (ValueTypeReturnedAsFields) { + warning("C1 doesn't work with ValueTypeReturnedAsFields yet. Forcing ValueTypeReturnedAsFields=false"); + FLAG_SET_CMDLINE(bool, ValueTypeReturnedAsFields, false); + } } } else { FLAG_SET_CMDLINE(bool, ValueArrayFlatten, false); --- old/src/hotspot/share/runtime/deoptimization.cpp 2018-12-18 11:43:04.213087460 +0100 +++ new/src/hotspot/share/runtime/deoptimization.cpp 2018-12-18 11:43:03.917235465 +0100 @@ -503,7 +503,7 @@ caller_adjustment = last_frame_adjust(callee_parameters, callee_locals); } - // If the sender is deoptimized the we must retrieve the address of the handler + // If the sender is deoptimized we must retrieve the address of the handler // since the frame will "magically" show the original pc before the deopt // and we'd undo the deopt. @@ -1008,17 +1008,15 @@ if (!fs.access_flags().is_static() && (!skip_internal || !fs.access_flags().is_internal())) { ReassignedField field; field._offset = fs.offset(); - field._type = fs.is_flattened() ? T_VALUETYPE : FieldType::basic_type(fs.signature()); + field._type = FieldType::basic_type(fs.signature()); if (field._type == T_VALUETYPE) { - if (fs.is_flattened()) { - // Resolve klass of flattened value type field - Klass* vk = klass->get_value_field_klass(fs.index()); - assert(vk->is_value(), "must be a ValueKlass"); - field._klass = InstanceKlass::cast(vk); - } else { - // Non-flattened value type field - field._type = T_VALUETYPEPTR; - } + field._type = T_OBJECT; + } + if (fs.is_flattened()) { + // Resolve klass of flattened value type field + Klass* vk = klass->get_value_field_klass(fs.index()); + field._klass = ValueKlass::cast(vk); + field._type = T_VALUETYPE; } fields->append(field); } @@ -1032,7 +1030,6 @@ BasicType type = fields->at(i)._type; switch (type) { case T_OBJECT: - case T_VALUETYPEPTR: case T_ARRAY: assert(value->type() == T_OBJECT, "Agreement."); obj->obj_field_put(offset, value->get_obj()()); --- old/src/hotspot/share/runtime/globals.hpp 2018-12-18 11:43:05.196595447 +0100 +++ new/src/hotspot/share/runtime/globals.hpp 2018-12-18 11:43:04.908739451 +0100 @@ -2631,13 +2631,16 @@ "Enable C1 compiler for Valhalla") \ \ product_pd(bool, ValueTypePassFieldsAsArgs, \ - "Pass each value type field as an argument at calls") \ + "Pass each value type field as an argument at calls") \ \ product_pd(bool, ValueTypeReturnedAsFields, \ - "return fields instead of a value type reference") \ + "Return fields instead of a value type reference") \ + \ + develop(bool, StressValueTypePassFieldsAsArgs, false, \ + "Stress passing each value type field as an argument at calls") \ \ develop(bool, StressValueTypeReturnedAsFields, false, \ - "stress return of fields instead of a value type reference") \ + "Stress return of fields instead of a value type reference") \ \ develop(bool, ScalarizeValueTypes, true, \ "Scalarize value types in compiled code") \ --- old/src/hotspot/share/runtime/safepoint.cpp 2018-12-18 11:43:06.248069433 +0100 +++ new/src/hotspot/share/runtime/safepoint.cpp 2018-12-18 11:43:05.936225437 +0100 @@ -1169,16 +1169,23 @@ GrowableArray return_values; ValueKlass* vk = NULL; - if (method->is_returning_vt() && ValueTypeReturnedAsFields) { - // Check if value type is returned as fields - vk = ValueKlass::returned_value_klass(map); - if (vk != NULL) { - // We're at a safepoint at the return of a method that returns - // multiple values. We must make sure we preserve the oop values - // across the safepoint. - assert(vk == method->returned_value_type(thread()), "bad value klass"); - vk->save_oop_fields(map, return_values); - return_oop = false; + + if (return_oop && ValueTypeReturnedAsFields) { + SignatureStream ss(method->signature()); + while (!ss.at_return_type()) { + ss.next(); + } + if (ss.type() == T_VALUETYPE) { + // Check if value type is returned as fields + vk = ValueKlass::returned_value_klass(map); + if (vk != NULL) { + // We're at a safepoint at the return of a method that returns + // multiple values. We must make sure we preserve the oop values + // across the safepoint. + assert(vk == method->returned_value_type(thread()), "bad value klass"); + vk->save_oop_fields(map, return_values); + return_oop = false; + } } } --- old/src/hotspot/share/runtime/sharedRuntime.cpp 2018-12-18 11:43:07.331527417 +0100 +++ new/src/hotspot/share/runtime/sharedRuntime.cpp 2018-12-18 11:43:07.015685422 +0100 @@ -1151,7 +1151,8 @@ THROW_(vmSymbols::java_lang_NoSuchMethodException(), nullHandle); } } - if (ValueTypePassFieldsAsArgs && callee->method_holder()->is_value()) { + // TODO for now, don't scalarize value type receivers because of interface calls + if (ValueTypePassFieldsAsArgs && callee->method_holder()->is_value() && false) { // If the receiver is a value type that is passed as fields, no oop is available. // Resolve the call without receiver null checking. assert(bc == Bytecodes::_invokevirtual, "only allowed with invokevirtual"); @@ -2296,9 +2297,6 @@ return ValueTypePassFieldsAsArgs ? in : adapter_encoding(T_OBJECT, false); } - case T_VALUETYPEPTR: - return T_VALUETYPE; // TODO hack because we don't have enough bits to represent T_VALUETYPEPTR. - case T_OBJECT: case T_ARRAY: // In other words, we assume that any register good enough for @@ -2323,7 +2321,7 @@ } public: - AdapterFingerPrint(int total_args_passed, BasicType* sig_bt) { + AdapterFingerPrint(int total_args_passed, const GrowableArray* sig) { // The fingerprint is based on the BasicType signature encoded // into an array of ints with eight entries per int. int* ptr; @@ -2350,7 +2348,7 @@ for (int byte = 0; byte < _basic_types_per_int; byte++) { int bt = 0; if (sig_index < total_args_passed) { - BasicType sbt = sig_bt[sig_index++]; + BasicType sbt = sig->at(sig_index++)._bt; if (ValueTypePassFieldsAsArgs && sbt == T_VALUETYPE) { // Found start of value type in signature vt_count++; @@ -2454,9 +2452,9 @@ : BasicHashtable(293, (DumpSharedSpaces ? sizeof(CDSAdapterHandlerEntry) : sizeof(AdapterHandlerEntry))) { } // Create a new entry suitable for insertion in the table - AdapterHandlerEntry* new_entry(AdapterFingerPrint* fingerprint, address i2c_entry, address c2i_entry, address c2i_unverified_entry, Symbol* sig_extended) { + AdapterHandlerEntry* new_entry(AdapterFingerPrint* fingerprint, address i2c_entry, address c2i_entry, address c2i_value_entry, address c2i_unverified_entry) { AdapterHandlerEntry* entry = (AdapterHandlerEntry*)BasicHashtable::new_entry(fingerprint->compute_hash()); - entry->init(fingerprint, i2c_entry, c2i_entry, c2i_unverified_entry, sig_extended); + entry->init(fingerprint, i2c_entry, c2i_entry, c2i_value_entry, c2i_unverified_entry); if (DumpSharedSpaces) { ((CDSAdapterHandlerEntry*)entry)->init(); } @@ -2475,9 +2473,9 @@ } // Find a entry with the same fingerprint if it exists - AdapterHandlerEntry* lookup(int total_args_passed, BasicType* sig_bt) { + AdapterHandlerEntry* lookup(int total_args_passed, const GrowableArray* sig) { NOT_PRODUCT(_lookups++); - AdapterFingerPrint fp(total_args_passed, sig_bt); + AdapterFingerPrint fp(total_args_passed, sig); unsigned int hash = fp.compute_hash(); int index = hash_to_index(hash); for (AdapterHandlerEntry* e = bucket(index); e != NULL; e = e->next()) { @@ -2599,19 +2597,19 @@ address wrong_method_abstract = SharedRuntime::get_handle_wrong_method_abstract_stub(); _abstract_method_handler = AdapterHandlerLibrary::new_entry(new AdapterFingerPrint(0, NULL), StubRoutines::throw_AbstractMethodError_entry(), - wrong_method_abstract, wrong_method_abstract); + wrong_method_abstract, wrong_method_abstract, wrong_method_abstract); } AdapterHandlerEntry* AdapterHandlerLibrary::new_entry(AdapterFingerPrint* fingerprint, address i2c_entry, address c2i_entry, - address c2i_unverified_entry, - Symbol* sig_extended) { - return _adapters->new_entry(fingerprint, i2c_entry, c2i_entry, c2i_unverified_entry, sig_extended); + address c2i_value_entry, + address c2i_unverified_entry) { + return _adapters->new_entry(fingerprint, i2c_entry, c2i_entry, c2i_value_entry, c2i_unverified_entry); } -AdapterHandlerEntry* AdapterHandlerLibrary::get_adapter(const methodHandle& method, TRAPS) { - AdapterHandlerEntry* entry = get_adapter0(method, CHECK_NULL); +AdapterHandlerEntry* AdapterHandlerLibrary::get_adapter(const methodHandle& method) { + AdapterHandlerEntry* entry = get_adapter0(method); if (method->is_shared()) { // See comments around Method::link_method() MutexLocker mu(AdapterHandlerLibrary_lock); @@ -2634,7 +2632,7 @@ return entry; } -AdapterHandlerEntry* AdapterHandlerLibrary::get_adapter0(const methodHandle& method, TRAPS) { +AdapterHandlerEntry* AdapterHandlerLibrary::get_adapter0(const methodHandle& method) { // Use customized signature handler. Need to lock around updates to // the AdapterHandlerTable (it is not safe for concurrent readers // and a single writer: this could be fixed if it becomes a @@ -2655,68 +2653,101 @@ return _abstract_method_handler; } - // Fill in the signature array, for the calling-convention call. - GrowableArray sig_extended; - { + bool has_value_arg = false; + GrowableArray sig(method->size_of_parameters()); + if (!method->is_static()) { + // TODO for now, don't scalarize value type receivers because of interface calls + //has_value_arg |= method->method_holder()->is_value(); + SigEntry::add_entry(&sig, T_OBJECT); + } + for (SignatureStream ss(method->signature()); !ss.at_return_type(); ss.next()) { + BasicType bt = ss.type(); + if (bt == T_VALUETYPE) { + has_value_arg = true; + bt = T_OBJECT; + } + SigEntry::add_entry(&sig, bt); + } + + // Get a description of the compiled java calling convention and the largest used (VMReg) stack slot usage + VMRegPair* regs = NEW_RESOURCE_ARRAY(VMRegPair, sig.length()); + int args_on_stack = SharedRuntime::java_calling_convention(&sig, regs); + + // Now compute the scalarized calling convention if there are value types in the signature + GrowableArray sig_cc = sig; + VMRegPair* regs_cc = regs; + SigEntry reserved_entry; + int args_on_stack_cc = args_on_stack; + + if (ValueTypePassFieldsAsArgs && has_value_arg) { MutexUnlocker mul(AdapterHandlerLibrary_lock); - Thread* THREAD = Thread::current(); - Klass* holder = method->method_holder(); - GrowableArray sig_bt_tmp; + InstanceKlass* holder = method->method_holder(); - int i = 0; - if (!method->is_static()) { // Pass in receiver first - if (holder->is_value()) { - ValueKlass* vk = ValueKlass::cast(holder); - if (!ValueTypePassFieldsAsArgs) { - // If we don't pass value types as arguments or if the holder of - // the method is __Value, we must pass a reference. - sig_extended.push(SigEntry(T_VALUETYPEPTR)); - } else { - const Array* sig_vk = vk->extended_sig(); - sig_extended.appendAll(sig_vk); - } + sig_cc = GrowableArray(method->size_of_parameters()); + if (!method->is_static()) { + // TODO for now, don't scalarize value type receivers because of interface calls + if (false && holder->is_value()) { + sig_cc.appendAll(ValueKlass::cast(holder)->extended_sig()); } else { - sig_extended.push(SigEntry(T_OBJECT)); + SigEntry::add_entry(&sig_cc, T_OBJECT); } } + Thread* THREAD = Thread::current(); for (SignatureStream ss(method->signature()); !ss.at_return_type(); ss.next()) { - Symbol* sym = ss.as_symbol_or_null(); - if (sym != NULL && sym->is_Q_signature()) { - if (!ValueTypePassFieldsAsArgs) { - sig_extended.push(SigEntry(T_VALUETYPEPTR)); - } else { - // Method handle intrinsics with a __Value argument may be created during - // compilation. Only do a full system dictionary lookup if the argument name - // is not __Value, to avoid lookups from the compiler thread. - Klass* k = ss.as_klass(Handle(THREAD, holder->class_loader()), - Handle(THREAD, holder->protection_domain()), - SignatureStream::ReturnNull, CHECK_NULL); - const Array* sig_vk = ValueKlass::cast(k)->extended_sig(); - sig_extended.appendAll(sig_vk); - } + if (ss.type() == T_VALUETYPE) { + Klass* k = ss.as_klass(Handle(THREAD, holder->class_loader()), + Handle(THREAD, holder->protection_domain()), + SignatureStream::ReturnNull, THREAD); + assert(k != NULL && !HAS_PENDING_EXCEPTION, "value klass should have been pre-loaded"); + sig_cc.appendAll(ValueKlass::cast(k)->extended_sig()); } else { - sig_extended.push(SigEntry(ss.type())); - if (ss.type() == T_LONG || ss.type() == T_DOUBLE) { - sig_extended.push(SigEntry(T_VOID)); - } + SigEntry::add_entry(&sig_cc, ss.type()); } } - } + regs_cc = NEW_RESOURCE_ARRAY(VMRegPair, sig_cc.length() + 2); + args_on_stack_cc = SharedRuntime::java_calling_convention(&sig_cc, regs_cc); - int total_args_passed_cc = ValueTypePassFieldsAsArgs ? SigEntry::count_fields(sig_extended) : sig_extended.length(); - BasicType* sig_bt_cc = NEW_RESOURCE_ARRAY(BasicType, total_args_passed_cc); - SigEntry::fill_sig_bt(sig_extended, sig_bt_cc, total_args_passed_cc, ValueTypePassFieldsAsArgs); - - int total_args_passed_fp = sig_extended.length(); - BasicType* sig_bt_fp = NEW_RESOURCE_ARRAY(BasicType, total_args_passed_fp); - for (int i = 0; i < sig_extended.length(); i++) { - sig_bt_fp[i] = sig_extended.at(i)._bt; + // This stack slot is occupied by the return address with the unscalarized calling + // convention. Don't use it for argument with the scalarized calling convention. + int ret_addr_slot = args_on_stack_cc - args_on_stack; + if (ret_addr_slot > 0) { + // Make sure stack of the scalarized calling convention with + // the reserved entry (2 slots) is 16-byte (4 slots) aligned. + int alignment = StackAlignmentInBytes/VMRegImpl::stack_slot_size; + ret_addr_slot = align_up(ret_addr_slot + 2, alignment) - 2; + // Find index in signature that belongs to return address slot + reserved_entry._offset = 0; + int sig_idx = 0; + for (; sig_idx < sig_cc.length(); ++sig_idx) { + if (SigEntry::skip_value_delimiters(&sig_cc, sig_idx)) { + VMReg first = regs_cc[reserved_entry._offset].first(); + if (first->is_stack()) { + // Select a type for the reserved entry that will end up on the stack + reserved_entry._bt = sig_cc.at(sig_idx)._bt; + if ((int)first->reg2stack() == ret_addr_slot) { + break; + } + } + reserved_entry._offset++; + } + } + // Insert reserved entry and re-compute calling convention + SigEntry::insert_reserved_entry(&sig_cc, sig_idx, reserved_entry._bt); + args_on_stack_cc = SharedRuntime::java_calling_convention(&sig_cc, regs_cc); + } + // Upper bound on stack arguments to avoid hitting the argument limit and + // bailing out of compilation ("unsupported incoming calling sequence"). + // TODO we need a reasonable limit (flag?) here + if (args_on_stack_cc > 50) { + // Don't scalarize value type arguments + sig_cc = sig; + regs_cc = regs; + args_on_stack_cc = args_on_stack; + } } - VMRegPair* regs = NEW_RESOURCE_ARRAY(VMRegPair, total_args_passed_cc); - // Lookup method signature's fingerprint - entry = _adapters->lookup(total_args_passed_fp, sig_bt_fp); + entry = _adapters->lookup(sig_cc.length(), &sig_cc); #ifdef ASSERT AdapterHandlerEntry* shared_entry = NULL; @@ -2731,11 +2762,8 @@ return entry; } - // Get a description of the compiled java calling convention and the largest used (VMReg) stack slot usage - int comp_args_on_stack = SharedRuntime::java_calling_convention(sig_bt_cc, regs, total_args_passed_cc, false); - // Make a C heap allocated version of the fingerprint to store in the adapter - fingerprint = new AdapterFingerPrint(total_args_passed_fp, sig_bt_fp); + fingerprint = new AdapterFingerPrint(sig_cc.length(), &sig_cc); // StubRoutines::code2() is initialized after this function can be called. As a result, // VerifyAdapterCalls and VerifyAdapterSharing can fail if we re-use code that generated @@ -2753,11 +2781,23 @@ MacroAssembler _masm(&buffer); entry = SharedRuntime::generate_i2c2i_adapters(&_masm, - comp_args_on_stack, - sig_extended, + args_on_stack, + args_on_stack_cc, + &sig, regs, + &sig_cc, + regs_cc, fingerprint, new_adapter); + + if (regs != regs_cc) { + // Save a C heap allocated version of the scalarized signature and store it in the adapter + GrowableArray* heap_sig = new (ResourceObj::C_HEAP, mtInternal)GrowableArray(method->size_of_parameters(), true); + heap_sig->appendAll(&sig_cc); + entry->set_sig_cc(heap_sig); + entry->set_res_entry(reserved_entry); + } + #ifdef ASSERT if (VerifyAdapterSharing) { if (shared_entry != NULL) { @@ -2826,6 +2866,7 @@ address base = _i2c_entry; if (base == NULL) base = _c2i_entry; assert(base <= _c2i_entry || _c2i_entry == NULL, ""); + assert(base <= _c2i_value_entry || _c2i_value_entry == NULL, ""); assert(base <= _c2i_unverified_entry || _c2i_unverified_entry == NULL, ""); return base; } @@ -2838,6 +2879,8 @@ _i2c_entry += delta; if (_c2i_entry != NULL) _c2i_entry += delta; + if (_c2i_value_entry != NULL) + _c2i_value_entry += delta; if (_c2i_unverified_entry != NULL) _c2i_unverified_entry += delta; assert(base_address() == new_base, ""); @@ -2846,6 +2889,9 @@ void AdapterHandlerEntry::deallocate() { delete _fingerprint; + if (_sig_cc != NULL) { + delete _sig_cc; + } #ifdef ASSERT if (_saved_code) FREE_C_HEAP_ARRAY(unsigned char, _saved_code); #endif @@ -2918,19 +2964,6 @@ SignatureStream ss(method->signature()); for (; !ss.at_return_type(); ss.next()) { BasicType bt = ss.type(); - if (bt == T_VALUETYPE) { -#ifdef ASSERT - Thread* THREAD = Thread::current(); - // Avoid class loading from compiler thread - if (THREAD->can_call_java()) { - Handle class_loader(THREAD, method->method_holder()->class_loader()); - Handle protection_domain(THREAD, method->method_holder()->protection_domain()); - Klass* k = ss.as_klass(class_loader, protection_domain, SignatureStream::ReturnNull, THREAD); - assert(k != NULL && !HAS_PENDING_EXCEPTION, "can't resolve klass"); - } -#endif - bt = T_VALUETYPEPTR; - } sig_bt[i++] = bt; // Collect remaining bits of signature if (ss.type() == T_LONG || ss.type() == T_DOUBLE) sig_bt[i++] = T_VOID; // Longs & doubles take 2 Java slots @@ -3196,9 +3229,9 @@ } void AdapterHandlerEntry::print_adapter_on(outputStream* st) const { - st->print_cr("AHE@" INTPTR_FORMAT ": %s i2c: " INTPTR_FORMAT " c2i: " INTPTR_FORMAT " c2iUV: " INTPTR_FORMAT, + st->print_cr("AHE@" INTPTR_FORMAT ": %s i2c: " INTPTR_FORMAT " c2i: " INTPTR_FORMAT " c2iMH: " INTPTR_FORMAT " c2iUV: " INTPTR_FORMAT, p2i(this), fingerprint()->as_string(), - p2i(get_i2c_entry()), p2i(get_c2i_entry()), p2i(get_c2i_unverified_entry())); + p2i(get_i2c_entry()), p2i(get_c2i_entry()), p2i(get_c2i_value_entry()), p2i(get_c2i_unverified_entry())); } @@ -3305,12 +3338,15 @@ methodHandle callee(callee_method); int nb_slots = 0; - bool has_value_receiver = !callee->is_static() && callee->method_holder()->is_value(); + InstanceKlass* holder = callee->method_holder(); + // TODO for now, don't scalarize value type receivers because of interface calls + //bool has_value_receiver = !callee->is_static() && holder->is_value(); + bool has_value_receiver = false; if (has_value_receiver) { nb_slots++; } - Handle class_loader(THREAD, callee->method_holder()->class_loader()); - Handle protection_domain(THREAD, callee->method_holder()->protection_domain()); + Handle class_loader(THREAD, holder->class_loader()); + Handle protection_domain(THREAD, holder->protection_domain()); for (SignatureStream ss(callee->signature()); !ss.at_return_type(); ss.next()) { if (ss.type() == T_VALUETYPE) { nb_slots++; @@ -3320,7 +3356,7 @@ objArrayHandle array(THREAD, array_oop); int i = 0; if (has_value_receiver) { - ValueKlass* vk = ValueKlass::cast(callee->method_holder()); + ValueKlass* vk = ValueKlass::cast(holder); oop res = vk->allocate_instance(CHECK); array->obj_at_put(i, res); i++; @@ -3377,7 +3413,7 @@ ValueKlass* vk = ValueKlass::cast(res->klass()); - const Array* sig_vk = vk->extended_sig() ; + const Array* sig_vk = vk->extended_sig(); const Array* regs = vk->return_regs(); if (regs == NULL) { @@ -3399,44 +3435,43 @@ continue; } int off = sig_vk->at(i)._offset; + assert(off > 0, "offset in object should be positive"); VMRegPair pair = regs->at(j); address loc = reg_map.location(pair.first()); switch(bt) { case T_BOOLEAN: - *(intptr_t*)loc = *(jboolean*)((address)res + off); + *(jboolean*)loc = res->bool_field(off); break; case T_CHAR: - *(intptr_t*)loc = *(jchar*)((address)res + off); + *(jchar*)loc = res->char_field(off); break; case T_BYTE: - *(intptr_t*)loc = *(jbyte*)((address)res + off); + *(jbyte*)loc = res->byte_field(off); break; case T_SHORT: - *(intptr_t*)loc = *(jshort*)((address)res + off); + *(jshort*)loc = res->short_field(off); break; case T_INT: { - jint v = *(jint*)((address)res + off); - *(intptr_t*)loc = v; + *(jint*)loc = res->int_field(off); break; } case T_LONG: #ifdef _LP64 - *(intptr_t*)loc = *(jlong*)((address)res + off); + *(intptr_t*)loc = res->long_field(off); #else Unimplemented(); #endif break; case T_OBJECT: case T_ARRAY: { - oop v = HeapAccess<>::oop_load_at(res, off); - *(oop*)loc = v; + *(oop*)loc = res->obj_field(off); break; } case T_FLOAT: - *(jfloat*)loc = *(jfloat*)((address)res + off); + *(jfloat*)loc = res->float_field(off); break; case T_DOUBLE: - *(jdouble*)loc = *(jdouble*)((address)res + off); + *(jdouble*)loc = res->double_field(off); break; default: ShouldNotReachHere(); @@ -3483,35 +3518,19 @@ assert(verif_vk == vk, "broken calling convention"); assert(Metaspace::contains((void*)res), "should be klass"); - // Allocate handles for every oop fields so they are safe in case of + // Allocate handles for every oop field so they are safe in case of // a safepoint when allocating GrowableArray handles; vk->save_oop_fields(reg_map, handles); // It's unsafe to safepoint until we are here - - Handle new_vt; JRT_BLOCK; { Thread* THREAD = thread; oop vt = vk->realloc_result(reg_map, handles, CHECK); - new_vt = Handle(thread, vt); - -#ifdef ASSERT - javaVFrame* vf = javaVFrame::cast(vframe::new_vframe(&callerFrame, ®_map, thread)); - Method* m = vf->method(); - int bci = vf->bci(); - Bytecode_invoke inv(m, bci); - - methodHandle callee = inv.static_target(thread); - assert(!thread->has_pending_exception(), "call resolution should work"); - ValueKlass* verif_vk2 = callee->returned_value_type(thread); - assert(verif_vk == verif_vk2, "Bad value klass"); -#endif + thread->set_vm_result(vt); } JRT_BLOCK_END; - - thread->set_vm_result(new_vt()); } JRT_END --- old/src/hotspot/share/runtime/sharedRuntime.hpp 2018-12-18 11:43:08.159113407 +0100 +++ new/src/hotspot/share/runtime/sharedRuntime.hpp 2018-12-18 11:43:07.847269410 +0100 @@ -372,6 +372,11 @@ // will be just above it. ( // return value is the maximum number of VMReg stack slots the convention will use. static int java_calling_convention(const BasicType* sig_bt, VMRegPair* regs, int total_args_passed, int is_outgoing); + static int java_calling_convention(const GrowableArray* sig, VMRegPair* regs) { + BasicType* sig_bt = NEW_RESOURCE_ARRAY(BasicType, sig->length()); + int total_args_passed = SigEntry::fill_sig_bt(sig, sig_bt); + return java_calling_convention(sig_bt, regs, total_args_passed, false); + } static int java_return_convention(const BasicType* sig_bt, VMRegPair* regs, int total_args_passed); static const uint java_return_convention_max_int; static const uint java_return_convention_max_float; @@ -424,14 +429,17 @@ static AdapterHandlerEntry* generate_i2c2i_adapters(MacroAssembler *masm, int comp_args_on_stack, - const GrowableArray& sig_extended, - const VMRegPair *regs, + int comp_args_on_stack_cc, + const GrowableArray* sig, + const VMRegPair* regs, + const GrowableArray* sig_cc, + const VMRegPair* regs_cc, AdapterFingerPrint* fingerprint, AdapterBlob*& new_adapter); static void gen_i2c_adapter(MacroAssembler *_masm, int comp_args_on_stack, - const GrowableArray& sig_extended, + const GrowableArray* sig, const VMRegPair *regs); // OSR support @@ -640,8 +648,12 @@ AdapterFingerPrint* _fingerprint; address _i2c_entry; address _c2i_entry; + address _c2i_value_entry; address _c2i_unverified_entry; - Symbol* _sig_extended; + + // Support for scalarized value type calling convention + const GrowableArray* _sig_cc; + SigEntry _res_sig_entry; #ifdef ASSERT // Captures code and signature used to generate this adapter when @@ -650,12 +662,14 @@ int _saved_code_length; #endif - void init(AdapterFingerPrint* fingerprint, address i2c_entry, address c2i_entry, address c2i_unverified_entry, Symbol* sig_extended) { + void init(AdapterFingerPrint* fingerprint, address i2c_entry, address c2i_entry, address c2i_value_entry, address c2i_unverified_entry) { _fingerprint = fingerprint; _i2c_entry = i2c_entry; _c2i_entry = c2i_entry; + _c2i_value_entry = c2i_value_entry; _c2i_unverified_entry = c2i_unverified_entry; - _sig_extended = sig_extended; + _sig_cc = NULL; + _res_sig_entry = SigEntry(); #ifdef ASSERT _saved_code = NULL; _saved_code_length = 0; @@ -670,11 +684,17 @@ public: address get_i2c_entry() const { return _i2c_entry; } address get_c2i_entry() const { return _c2i_entry; } + address get_c2i_value_entry() const { return _c2i_value_entry; } address get_c2i_unverified_entry() const { return _c2i_unverified_entry; } - Symbol* get_sig_extended() const { return _sig_extended; } address base_address(); void relocate(address new_base); + // Support for scalarized value type calling convention + void set_sig_cc(const GrowableArray* sig) { _sig_cc = sig; } + const GrowableArray* get_sig_cc() const { return _sig_cc; } + void set_res_entry(SigEntry res_sig_entry) { _res_sig_entry = res_sig_entry; } + SigEntry get_res_entry() const { return _res_sig_entry; } + AdapterFingerPrint* fingerprint() const { return _fingerprint; } AdapterHandlerEntry* next() { @@ -712,15 +732,14 @@ static AdapterHandlerEntry* _abstract_method_handler; static BufferBlob* buffer_blob(); static void initialize(); - static AdapterHandlerEntry* get_adapter0(const methodHandle& method, TRAPS); + static AdapterHandlerEntry* get_adapter0(const methodHandle& method); public: static AdapterHandlerEntry* new_entry(AdapterFingerPrint* fingerprint, - address i2c_entry, address c2i_entry, address c2i_unverified_entry, - Symbol* sig_extended = NULL); + address i2c_entry, address c2i_entry, address c2i_value_entry, address c2i_unverified_entry); static void create_native_wrapper(const methodHandle& method); - static AdapterHandlerEntry* get_adapter(const methodHandle& method, TRAPS); + static AdapterHandlerEntry* get_adapter(const methodHandle& method); static void print_handler(const CodeBlob* b) { print_handler_on(tty, b); } static void print_handler_on(outputStream* st, const CodeBlob* b); --- old/src/hotspot/share/runtime/signature.cpp 2018-12-18 11:43:08.934725396 +0100 +++ new/src/hotspot/share/runtime/signature.cpp 2018-12-18 11:43:08.626879400 +0100 @@ -523,29 +523,70 @@ } } -int SigEntry::count_fields(const GrowableArray& sig_extended) { - int values = 0; - for (int i = 0; i < sig_extended.length(); i++) { - if (sig_extended.at(i)._bt == T_VALUETYPE) { - values++; +// Adds an argument to the signature +void SigEntry::add_entry(GrowableArray* sig, BasicType bt, int offset) { + sig->append(SigEntry(bt, offset)); + if (bt == T_LONG || bt == T_DOUBLE) { + sig->append(SigEntry(T_VOID, offset)); // Longs and doubles take two stack slots + } +} + +// Inserts a reserved argument at position 'i' +void SigEntry::insert_reserved_entry(GrowableArray* sig, int i, BasicType bt) { + if (bt == T_OBJECT || bt == T_ARRAY || bt == T_VALUETYPE) { + // Treat this as INT to not confuse the GC + bt = T_INT; + } else if (bt == T_LONG || bt == T_DOUBLE) { + // Longs and doubles take two stack slots + sig->insert_before(i, SigEntry(T_VOID, SigEntry::ReservedOffset)); + } + sig->insert_before(i, SigEntry(bt, SigEntry::ReservedOffset)); +} + +// Returns true if the argument at index 'i' is a reserved argument +bool SigEntry::is_reserved_entry(const GrowableArray* sig, int i) { + return sig->at(i)._offset == SigEntry::ReservedOffset; +} + +// Returns true if the argument at index 'i' is not a value type delimiter +bool SigEntry::skip_value_delimiters(const GrowableArray* sig, int i) { + return (sig->at(i)._bt != T_VALUETYPE && + (sig->at(i)._bt != T_VOID || sig->at(i-1)._bt == T_LONG || sig->at(i-1)._bt == T_DOUBLE)); +} + +// Fill basic type array from signature array +int SigEntry::fill_sig_bt(const GrowableArray* sig, BasicType* sig_bt) { + int count = 0; + for (int i = 0; i < sig->length(); i++) { + if (skip_value_delimiters(sig, i)) { + sig_bt[count++] = sig->at(i)._bt; } } - return sig_extended.length() - 2 * values; + return count; } -void SigEntry::fill_sig_bt(const GrowableArray& sig_extended, BasicType* sig_bt_cc, int total_args_passed_cc, bool skip_vt) { - int j = 0; - for (int i = 0; i < sig_extended.length(); i++) { - if (!skip_vt) { - BasicType bt = sig_extended.at(i)._bt; - // assert(bt != T_VALUETYPE, "value types should be passed as fields or reference"); - sig_bt_cc[j++] = bt; - } else if (sig_extended.at(i)._bt != T_VALUETYPE && - (sig_extended.at(i)._bt != T_VOID || - sig_extended.at(i-1)._bt == T_LONG || - sig_extended.at(i-1)._bt == T_DOUBLE)) { - sig_bt_cc[j++] = sig_extended.at(i)._bt; +// Create a temporary symbol from the signature array +TempNewSymbol SigEntry::create_symbol(const GrowableArray* sig) { + ResourceMark rm; + int length = sig->length(); + char* sig_str = NEW_RESOURCE_ARRAY(char, 2*length + 3); + int idx = 0; + sig_str[idx++] = '('; + for (int i = 0; i < length; i++) { + BasicType bt = sig->at(i)._bt; + if (bt == T_VALUETYPE || bt == T_VOID) { + // Ignore + } else { + if (bt == T_ARRAY) { + bt = T_OBJECT; // We don't know the element type, treat as Object + } + sig_str[idx++] = type2char(bt); + if (bt == T_OBJECT) { + sig_str[idx++] = ';'; + } } } - assert(j == total_args_passed_cc, "bad number of arguments"); + sig_str[idx++] = ')'; + sig_str[idx++] = '\0'; + return SymbolTable::new_symbol(sig_str, Thread::current()); } --- old/src/hotspot/share/runtime/signature.hpp 2018-12-18 11:43:10.034175380 +0100 +++ new/src/hotspot/share/runtime/signature.hpp 2018-12-18 11:43:09.686349384 +0100 @@ -25,6 +25,7 @@ #ifndef SHARE_VM_RUNTIME_SIGNATURE_HPP #define SHARE_VM_RUNTIME_SIGNATURE_HPP +#include "classfile/symbolTable.hpp" #include "memory/allocation.hpp" #include "oops/method.hpp" @@ -441,7 +442,9 @@ public: BasicType _bt; int _offset; - + + enum { ReservedOffset = -2 }; // Special offset to mark the reserved entry + SigEntry() : _bt(T_ILLEGAL), _offset(-1) { } @@ -450,7 +453,7 @@ SigEntry(BasicType bt) : _bt(bt), _offset(-1) {} - + static int compare(SigEntry* e1, SigEntry* e2) { if (e1->_offset != e2->_offset) { return e1->_offset - e2->_offset; @@ -473,8 +476,12 @@ ShouldNotReachHere(); return 0; } - static int count_fields(const GrowableArray& sig_extended); - static void fill_sig_bt(const GrowableArray& sig_extended, BasicType* sig_bt_cc, int total_args_passed_cc, bool skip_vt); + static void add_entry(GrowableArray* sig, BasicType bt, int offset = -1); + static void insert_reserved_entry(GrowableArray* sig, int i, BasicType bt); + static bool is_reserved_entry(const GrowableArray* sig, int i); + static bool skip_value_delimiters(const GrowableArray* sig, int i); + static int fill_sig_bt(const GrowableArray* sig, BasicType* sig_bt); + static TempNewSymbol create_symbol(const GrowableArray* sig); }; #endif // SHARE_VM_RUNTIME_SIGNATURE_HPP --- old/src/hotspot/share/utilities/globalDefinitions.cpp 2018-12-18 11:43:10.797793369 +0100 +++ new/src/hotspot/share/utilities/globalDefinitions.cpp 2018-12-18 11:43:10.489947373 +0100 @@ -115,7 +115,6 @@ case T_NARROWKLASS: // compressed klass pointer case T_CONFLICT: // might as well support a bottom type case T_VOID: // padding or other unaddressed word - case T_VALUETYPEPTR: // layout type must map to itself assert(vt == ft, ""); break; @@ -180,7 +179,7 @@ // Map BasicType to signature character -char type2char_tab[T_CONFLICT+1]={ 0, 0, 0, 0, 'Z', 'C', 'F', 'D', 'B', 'S', 'I', 'J', 'L', '[', 'Q', 'V', 0, 0, 0, 0, 0, 0}; +char type2char_tab[T_CONFLICT+1]={ 0, 0, 0, 0, 'Z', 'C', 'F', 'D', 'B', 'S', 'I', 'J', 'L', '[', 'Q', 'V', 0, 0, 0, 0, 0}; // Map BasicType to Java type name const char* type2name_tab[T_CONFLICT+1] = { @@ -201,7 +200,6 @@ "*narrowoop*", "*metadata*", "*narrowklass*", - "valuetypeptr", "*conflict*" }; @@ -216,7 +214,7 @@ } // Map BasicType to size in words -int type2size[T_CONFLICT+1]={ -1, 0, 0, 0, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 0, 1, 1, 1, 1, 1, -1}; +int type2size[T_CONFLICT+1]={ -1, 0, 0, 0, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 0, 1, 1, 1, 1, -1}; BasicType type2field[T_CONFLICT+1] = { (BasicType)0, // 0, @@ -239,8 +237,7 @@ T_NARROWOOP, // T_NARROWOOP= 17, T_METADATA, // T_METADATA = 18, T_NARROWKLASS, // T_NARROWKLASS = 19, - T_VALUETYPEPTR, // T_VALUETYPEPTR = 20, - T_CONFLICT // T_CONFLICT = 21, + T_CONFLICT // T_CONFLICT = 20 }; @@ -265,8 +262,7 @@ T_NARROWOOP, // T_NARROWOOP = 17, T_METADATA, // T_METADATA = 18, T_NARROWKLASS, // T_NARROWKLASS = 19, - T_VALUETYPEPTR,// T_VALUETYPEPTR = 20, - T_CONFLICT // T_CONFLICT = 21, + T_CONFLICT // T_CONFLICT = 20 }; @@ -291,8 +287,7 @@ T_NARROWOOP_aelem_bytes, // T_NARROWOOP= 17, T_OBJECT_aelem_bytes, // T_METADATA = 18, T_NARROWKLASS_aelem_bytes, // T_NARROWKLASS= 19, - T_VALUETYPEPTR_aelem_bytes,// T_VALUETYPEPTR = 20, - 0 // T_CONFLICT = 21, + 0 // T_CONFLICT = 20 }; #ifdef ASSERT --- old/src/hotspot/share/utilities/globalDefinitions.hpp 2018-12-18 11:43:11.657363357 +0100 +++ new/src/hotspot/share/utilities/globalDefinitions.hpp 2018-12-18 11:43:11.305539362 +0100 @@ -585,8 +585,7 @@ T_NARROWOOP = 17, T_METADATA = 18, T_NARROWKLASS = 19, - T_VALUETYPEPTR= 20, // the compiler needs a way to identify buffered values - T_CONFLICT = 21, // for stack value type with conflicting contents + T_CONFLICT = 20, // for stack value type with conflicting contents T_ILLEGAL = 99 }; @@ -684,12 +683,7 @@ #endif T_NARROWOOP_aelem_bytes = 4, T_NARROWKLASS_aelem_bytes = 4, - T_VOID_aelem_bytes = 0, -#ifdef _LP64 - T_VALUETYPEPTR_aelem_bytes= 8 -#else - T_VALUETYPEPTR_aelem_bytes= 4 -#endif + T_VOID_aelem_bytes = 0 }; extern int _type2aelembytes[T_CONFLICT+1]; // maps a BasicType to nof bytes used by its array element --- old/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestBasicFunctionality.java 2018-12-18 11:43:12.768807342 +0100 +++ new/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestBasicFunctionality.java 2018-12-18 11:43:12.448967347 +0100 @@ -89,7 +89,7 @@ } // Return incoming value type without accessing fields - @Test(valid = ValueTypePassFieldsAsArgsOn, match = {ALLOC, STORE}, matchCount = {1, 11}, failOn = LOAD + TRAP) + @Test(valid = ValueTypePassFieldsAsArgsOn, match = {ALLOC, STORE}, matchCount = {1, 14}, failOn = LOAD + TRAP) @Test(valid = ValueTypePassFieldsAsArgsOff, failOn = ALLOC + LOAD + STORE + TRAP) public MyValue1 test3(MyValue1 v) { return v; @@ -138,7 +138,9 @@ // Create a value type in compiled code and pass it to // the interpreter via a call. - @Test(valid = ValueTypePassFieldsAsArgsOn, failOn = LOAD + TRAP + ALLOC) +// TODO enable once receiver is scalarized +// @Test(valid = ValueTypePassFieldsAsArgsOn, failOn = LOAD + TRAP + ALLOC) + @Test(valid = ValueTypePassFieldsAsArgsOn) @Test(valid = ValueTypePassFieldsAsArgsOff, match = {ALLOC}, matchCount = {1}, failOn = LOAD + TRAP) public long test6() { MyValue1 v = MyValue1.createWithFieldsInline(rI, rL); @@ -185,7 +187,9 @@ } // Merge value types created from two branches - @Test(valid = ValueTypePassFieldsAsArgsOn, match = {LOAD}, matchCount = {10}, failOn = TRAP + ALLOC + STORE) +// TODO enable once receiver is scalarized +// @Test(valid = ValueTypePassFieldsAsArgsOn, match = {LOAD}, matchCount = {10}, failOn = TRAP + ALLOC + STORE) + @Test(valid = ValueTypePassFieldsAsArgsOn) @Test(valid = ValueTypePassFieldsAsArgsOff, match = {ALLOC, STORE}, matchCount = {1, 5}, failOn = LOAD + TRAP) public MyValue1 test9(boolean b) { MyValue1 v; @@ -311,7 +315,9 @@ // Create a value type in a non-inlined method and then call a // non-inlined method on that value type. - @Test(valid = ValueTypePassFieldsAsArgsOn, failOn = (ALLOC + STORE + TRAP), match = {LOAD}, matchCount = {10}) +// TODO enable once receiver is scalarized +// @Test(valid = ValueTypePassFieldsAsArgsOn, failOn = (ALLOC + STORE + TRAP), match = {LOAD}, matchCount = {10}) + @Test(valid = ValueTypePassFieldsAsArgsOn) @Test(valid = ValueTypePassFieldsAsArgsOff, failOn = (ALLOC + LOAD + STORE + TRAP)) public long test14() { MyValue1 v = MyValue1.createWithFieldsDontInline(rI, rL); @@ -326,7 +332,9 @@ // Create a value type in an inlined method and then call a // non-inlined method on that value type. - @Test(valid = ValueTypePassFieldsAsArgsOn, failOn = (LOAD + TRAP + ALLOC)) +// TODO enable once receiver is scalarized +// @Test(valid = ValueTypePassFieldsAsArgsOn, failOn = (LOAD + TRAP + ALLOC)) + @Test(valid = ValueTypePassFieldsAsArgsOn) @Test(valid = ValueTypePassFieldsAsArgsOff, failOn = (LOAD + TRAP), match = {ALLOC}, matchCount = {1}) public long test15() { MyValue1 v = MyValue1.createWithFieldsInline(rI, rL); @@ -370,7 +378,9 @@ // Create a value type in compiled code and pass it to the // interpreter via a call. The value is live at the first call so // debug info should include a reference to all its fields. - @Test(valid = ValueTypePassFieldsAsArgsOn, failOn = ALLOC + LOAD + TRAP) +// TODO enable once receiver is scalarized +// @Test(valid = ValueTypePassFieldsAsArgsOn, failOn = ALLOC + LOAD + TRAP) + @Test(valid = ValueTypePassFieldsAsArgsOn) @Test(valid = ValueTypePassFieldsAsArgsOff, match = {ALLOC}, matchCount = {1}, failOn = LOAD + TRAP) public long test18() { MyValue1 v = MyValue1.createWithFieldsInline(rI, rL); @@ -409,7 +419,9 @@ // interpreter via a call. The value type is live at the uncommon // trap: verify that deoptimization causes the value type to be // correctly allocated. - @Test(valid = ValueTypePassFieldsAsArgsOn, failOn = LOAD + ALLOC + STORE) +// TODO enable once receiver is scalarized +// @Test(valid = ValueTypePassFieldsAsArgsOn, failOn = LOAD + ALLOC + STORE) + @Test(valid = ValueTypePassFieldsAsArgsOn) @Test(valid = ValueTypePassFieldsAsArgsOff, match = {ALLOC}, matchCount = {1}, failOn = LOAD) public long test20(boolean deopt) { MyValue1 v = MyValue1.createWithFieldsInline(rI, rL); --- old/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestCallingConvention.java 2018-12-18 11:43:13.788297328 +0100 +++ new/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestCallingConvention.java 2018-12-18 11:43:13.476453332 +0100 @@ -49,371 +49,422 @@ return null; } - public static void main(String[] args) throws Throwable { - TestCallingConvention test = new TestCallingConvention(); - test.run(args, MyValue1.class, MyValue2.class, MyValue2Inline.class, MyValue4.class); - } - - // Test interpreter to compiled code with various signatures - @Test(failOn = ALLOC + STORE + TRAP) - public long test1(MyValue2 v) { - return v.hash(); - } - - @DontCompile - public void test1_verifier(boolean warmup) { - MyValue2 v = MyValue2.createWithFieldsInline(rI, true); - long result = test1(v); - Asserts.assertEQ(result, v.hashInterpreted()); - } - - @Test(failOn = ALLOC + STORE + TRAP) - public long test2(int i1, MyValue2 v, int i2) { - return v.hash() + i1 - i2; - } - - @DontCompile - public void test2_verifier(boolean warmup) { - MyValue2 v = MyValue2.createWithFieldsInline(rI, true); - long result = test2(rI, v, 2*rI); - Asserts.assertEQ(result, v.hashInterpreted() - rI); - } - - @Test(failOn = ALLOC + STORE + TRAP) - public long test3(long l1, MyValue2 v, long l2) { - return v.hash() + l1 - l2; - } - - @DontCompile - public void test3_verifier(boolean warmup) { - MyValue2 v = MyValue2.createWithFieldsInline(rI, true); - long result = test3(rL, v, 2*rL); - Asserts.assertEQ(result, v.hashInterpreted() - rL); - } - - @Test(failOn = ALLOC + STORE + TRAP) - public long test4(int i, MyValue2 v, long l) { - return v.hash() + i + l; - } - - @DontCompile - public void test4_verifier(boolean warmup) { - MyValue2 v = MyValue2.createWithFieldsInline(rI, true); - long result = test4(rI, v, rL); - Asserts.assertEQ(result, v.hashInterpreted() + rL + rI); - } - - @Test(failOn = ALLOC + STORE + TRAP) - public long test5(long l, MyValue2 v, int i) { - return v.hash() + i + l; - } - - @DontCompile - public void test5_verifier(boolean warmup) { - MyValue2 v = MyValue2.createWithFieldsInline(rI, true); - long result = test5(rL, v, rI); - Asserts.assertEQ(result, v.hashInterpreted() + rL + rI); - } - - @Test(failOn = ALLOC + STORE + TRAP) - public long test6(long l, MyValue1 v1, int i, MyValue2 v2) { - return v1.hash() + i + l + v2.hash(); - } - - @DontCompile - public void test6_verifier(boolean warmup) { - MyValue1 v1 = MyValue1.createWithFieldsDontInline(rI, rL); - MyValue2 v2 = MyValue2.createWithFieldsInline(rI, true); - long result = test6(rL, v1, rI, v2); - Asserts.assertEQ(result, v1.hashInterpreted() + rL + rI + v2.hashInterpreted()); - } - - // Test compiled code to interpreter with various signatures - @DontCompile - public long test7_interp(MyValue2 v) { - return v.hash(); - } - - @Test(failOn = ALLOC + STORE + TRAP) - public long test7(MyValue2 v) { - return test7_interp(v); - } - - @DontCompile - public void test7_verifier(boolean warmup) { - MyValue2 v = MyValue2.createWithFieldsInline(rI, true); - long result = test7(v); - Asserts.assertEQ(result, v.hashInterpreted()); - } - - @DontCompile - public long test8_interp(int i1, MyValue2 v, int i2) { - return v.hash() + i1 - i2; - } - - @Test(failOn = ALLOC + STORE + TRAP) - public long test8(int i1, MyValue2 v, int i2) { - return test8_interp(i1, v, i2); - } - - @DontCompile - public void test8_verifier(boolean warmup) { - MyValue2 v = MyValue2.createWithFieldsInline(rI, true); - long result = test8(rI, v, 2*rI); - Asserts.assertEQ(result, v.hashInterpreted() - rI); - } - - @DontCompile - public long test9_interp(long l1, MyValue2 v, long l2) { - return v.hash() + l1 - l2; - } - - @Test(failOn = ALLOC + STORE + TRAP) - public long test9(long l1, MyValue2 v, long l2) { - return test9_interp(l1, v, l2); - } - - @DontCompile - public void test9_verifier(boolean warmup) { - MyValue2 v = MyValue2.createWithFieldsInline(rI, true); - long result = test9(rL, v, 2*rL); - Asserts.assertEQ(result, v.hashInterpreted() - rL); - } - - @DontCompile - public long test10_interp(int i, MyValue2 v, long l) { - return v.hash() + i + l; - } - - @Test(failOn = ALLOC + STORE + TRAP) - public long test10(int i, MyValue2 v, long l) { - return test10_interp(i, v, l); - } - - @DontCompile - public void test10_verifier(boolean warmup) { - MyValue2 v = MyValue2.createWithFieldsInline(rI, true); - long result = test10(rI, v, rL); - Asserts.assertEQ(result, v.hashInterpreted() + rL + rI); - } - - @DontCompile - public long test11_interp(long l, MyValue2 v, int i) { - return v.hash() + i + l; - } - - @Test(failOn = ALLOC + STORE + TRAP) - public long test11(long l, MyValue2 v, int i) { - return test11_interp(l, v, i); - } - - @DontCompile - public void test11_verifier(boolean warmup) { - MyValue2 v = MyValue2.createWithFieldsInline(rI, true); - long result = test11(rL, v, rI); - Asserts.assertEQ(result, v.hashInterpreted() + rL + rI); - } - - @DontCompile - public long test12_interp(long l, MyValue1 v1, int i, MyValue2 v2) { - return v1.hash() + i + l + v2.hash(); - } - - @Test(failOn = ALLOC + STORE + TRAP) - public long test12(long l, MyValue1 v1, int i, MyValue2 v2) { - return test12_interp(l, v1, i, v2); - } - - @DontCompile - public void test12_verifier(boolean warmup) { - MyValue1 v1 = MyValue1.createWithFieldsDontInline(rI, rL); - MyValue2 v2 = MyValue2.createWithFieldsInline(rI, true); - long result = test12(rL, v1, rI, v2); - Asserts.assertEQ(result, v1.hashInterpreted() + rL + rI + v2.hashInterpreted()); - } - - // Test that debug info at a call is correct - @DontCompile - public long test13_interp(MyValue2 v, MyValue1[] va, boolean deopt) { - if (deopt) { - // uncommon trap - WHITE_BOX.deoptimizeMethod(tests.get(getClass().getSimpleName() + "::test13")); - } - return v.hash() + va[0].hash() + va[1].hash(); - } - - @Test(failOn = ALLOC + STORE + TRAP) - public long test13(MyValue2 v, MyValue1[] va, boolean flag, long l) { - return test13_interp(v, va, flag) + l; - } - - @DontCompile - public void test13_verifier(boolean warmup) { - MyValue2 v = MyValue2.createWithFieldsInline(rI, true); - MyValue1[] va = new MyValue1[2]; - va[0] = MyValue1.createWithFieldsDontInline(rI, rL); - va[1] = MyValue1.createWithFieldsDontInline(rI, rL); - long result = test13(v, va, !warmup, rL); - Asserts.assertEQ(result, v.hashInterpreted() + va[0].hash() + va[1].hash() + rL); - } - - // Test deoptimization at call return with return value in registers - @DontCompile - public MyValue2 test14_interp(boolean deopt) { - if (deopt) { - // uncommon trap - WHITE_BOX.deoptimizeMethod(tests.get(getClass().getSimpleName() + "::test14")); - } - return MyValue2.createWithFieldsInline(rI, true); - } - - @Test() - public MyValue2 test14(boolean flag) { - return test14_interp(flag); - } - - @DontCompile - public void test14_verifier(boolean warmup) { - MyValue2 result = test14(!warmup); - MyValue2 v = MyValue2.createWithFieldsInline(rI, true); - Asserts.assertEQ(result.hash(), v.hash()); - } - - // Return value types in registers from interpreter -> compiled - final MyValue3 test15_vt = MyValue3.create(); - @DontCompile - public MyValue3 test15_interp() { - return test15_vt; - } - - MyValue3 test15_vt2; - @Test(valid = ValueTypeReturnedAsFieldsOn, failOn = ALLOC + LOAD + TRAP) - @Test(valid = ValueTypeReturnedAsFieldsOff) - public void test15() { - test15_vt2 = test15_interp(); - } - - @DontCompile - public void test15_verifier(boolean warmup) { - test15(); - test15_vt.verify(test15_vt2); - } - - // Return value types in registers from compiled -> interpreter - final MyValue3 test16_vt = MyValue3.create(); - @Test(valid = ValueTypeReturnedAsFieldsOn, failOn = ALLOC + STORE + TRAP) - @Test(valid = ValueTypeReturnedAsFieldsOff) - public MyValue3 test16() { - return test16_vt; - } - - @DontCompile - public void test16_verifier(boolean warmup) { - MyValue3 vt = test16(); - test16_vt.verify(vt); - } - - // Return value types in registers from compiled -> compiled - final MyValue3 test17_vt = MyValue3.create(); - @DontInline - public MyValue3 test17_comp() { - return test17_vt; - } - - MyValue3 test17_vt2; - @Test(valid = ValueTypeReturnedAsFieldsOn, failOn = ALLOC + LOAD + TRAP) - @Test(valid = ValueTypeReturnedAsFieldsOff) - public void test17() { - test17_vt2 = test17_comp(); - } - - @DontCompile - public void test17_verifier(boolean warmup) throws Exception { - Method helper_m = getClass().getDeclaredMethod("test17_comp"); - if (!warmup && USE_COMPILER && !WHITE_BOX.isMethodCompiled(helper_m, false)) { - WHITE_BOX.enqueueMethodForCompilation(helper_m, COMP_LEVEL_FULL_OPTIMIZATION); - Asserts.assertTrue(WHITE_BOX.isMethodCompiled(helper_m, false), "test17_comp not compiled"); - } - test17(); - test17_vt.verify(test17_vt2); - } - - // Same tests as above but with a value type that cannot be returned in registers - - // Return value types in registers from interpreter -> compiled - final MyValue4 test18_vt = MyValue4.create(); - @DontCompile - public MyValue4 test18_interp() { - return test18_vt; - } - - MyValue4 test18_vt2; - @Test - public void test18() { - test18_vt2 = test18_interp(); - } - - @DontCompile - public void test18_verifier(boolean warmup) { - test18(); - test18_vt.verify(test18_vt2); - } - - // Return value types in registers from compiled -> interpreter - final MyValue4 test19_vt = MyValue4.create(); - @Test - public MyValue4 test19() { - return test19_vt; - } - - @DontCompile - public void test19_verifier(boolean warmup) { - MyValue4 vt = test19(); - test19_vt.verify(vt); - } - - // Return value types in registers from compiled -> compiled - final MyValue4 test20_vt = MyValue4.create(); - @DontInline - public MyValue4 test20_comp() { - return test20_vt; - } - - MyValue4 test20_vt2; - @Test - public void test20() { - test20_vt2 = test20_comp(); - } - - @DontCompile - public void test20_verifier(boolean warmup) throws Exception { - Method helper_m = getClass().getDeclaredMethod("test20_comp"); - if (!warmup && USE_COMPILER && !WHITE_BOX.isMethodCompiled(helper_m, false)) { - WHITE_BOX.enqueueMethodForCompilation(helper_m, COMP_LEVEL_FULL_OPTIMIZATION); - Asserts.assertTrue(WHITE_BOX.isMethodCompiled(helper_m, false), "test20_comp not compiled"); - } - test20(); - test20_vt.verify(test20_vt2); - } - - // Test no result from inlined method for incremental inlining - final MyValue3 test21_vt = MyValue3.create(); - public MyValue3 test21_inlined() { - throw new RuntimeException(); - } + public static void main(String[] args) throws Throwable { + TestCallingConvention test = new TestCallingConvention(); + test.run(args, MyValue1.class, MyValue2.class, MyValue2Inline.class, MyValue4.class); + } + + // Test interpreter to compiled code with various signatures + @Test(failOn = ALLOC + STORE + TRAP) + public long test1(MyValue2 v) { + return v.hash(); + } + + @DontCompile + public void test1_verifier(boolean warmup) { + MyValue2 v = MyValue2.createWithFieldsInline(rI, true); + long result = test1(v); + Asserts.assertEQ(result, v.hashInterpreted()); + } + + @Test(failOn = ALLOC + STORE + TRAP) + public long test2(int i1, MyValue2 v, int i2) { + return v.hash() + i1 - i2; + } + + @DontCompile + public void test2_verifier(boolean warmup) { + MyValue2 v = MyValue2.createWithFieldsInline(rI, true); + long result = test2(rI, v, 2*rI); + Asserts.assertEQ(result, v.hashInterpreted() - rI); + } + + @Test(failOn = ALLOC + STORE + TRAP) + public long test3(long l1, MyValue2 v, long l2) { + return v.hash() + l1 - l2; + } + + @DontCompile + public void test3_verifier(boolean warmup) { + MyValue2 v = MyValue2.createWithFieldsInline(rI, true); + long result = test3(rL, v, 2*rL); + Asserts.assertEQ(result, v.hashInterpreted() - rL); + } + + @Test(failOn = ALLOC + STORE + TRAP) + public long test4(int i, MyValue2 v, long l) { + return v.hash() + i + l; + } + + @DontCompile + public void test4_verifier(boolean warmup) { + MyValue2 v = MyValue2.createWithFieldsInline(rI, true); + long result = test4(rI, v, rL); + Asserts.assertEQ(result, v.hashInterpreted() + rL + rI); + } + + @Test(failOn = ALLOC + STORE + TRAP) + public long test5(long l, MyValue2 v, int i) { + return v.hash() + i + l; + } + + @DontCompile + public void test5_verifier(boolean warmup) { + MyValue2 v = MyValue2.createWithFieldsInline(rI, true); + long result = test5(rL, v, rI); + Asserts.assertEQ(result, v.hashInterpreted() + rL + rI); + } + + @Test(failOn = ALLOC + STORE + TRAP) + public long test6(long l, MyValue1 v1, int i, MyValue2 v2) { + return v1.hash() + i + l + v2.hash(); + } + + @DontCompile + public void test6_verifier(boolean warmup) { + MyValue1 v1 = MyValue1.createWithFieldsDontInline(rI, rL); + MyValue2 v2 = MyValue2.createWithFieldsInline(rI, true); + long result = test6(rL, v1, rI, v2); + Asserts.assertEQ(result, v1.hashInterpreted() + rL + rI + v2.hashInterpreted()); + } + + // Test compiled code to interpreter with various signatures + @DontCompile + public long test7_interp(MyValue2 v) { + return v.hash(); + } + + @Test(failOn = ALLOC + STORE + TRAP) + public long test7(MyValue2 v) { + return test7_interp(v); + } + + @DontCompile + public void test7_verifier(boolean warmup) { + MyValue2 v = MyValue2.createWithFieldsInline(rI, true); + long result = test7(v); + Asserts.assertEQ(result, v.hashInterpreted()); + } + + @DontCompile + public long test8_interp(int i1, MyValue2 v, int i2) { + return v.hash() + i1 - i2; + } + + @Test(failOn = ALLOC + STORE + TRAP) + public long test8(int i1, MyValue2 v, int i2) { + return test8_interp(i1, v, i2); + } + + @DontCompile + public void test8_verifier(boolean warmup) { + MyValue2 v = MyValue2.createWithFieldsInline(rI, true); + long result = test8(rI, v, 2*rI); + Asserts.assertEQ(result, v.hashInterpreted() - rI); + } + + @DontCompile + public long test9_interp(long l1, MyValue2 v, long l2) { + return v.hash() + l1 - l2; + } + + @Test(failOn = ALLOC + STORE + TRAP) + public long test9(long l1, MyValue2 v, long l2) { + return test9_interp(l1, v, l2); + } + + @DontCompile + public void test9_verifier(boolean warmup) { + MyValue2 v = MyValue2.createWithFieldsInline(rI, true); + long result = test9(rL, v, 2*rL); + Asserts.assertEQ(result, v.hashInterpreted() - rL); + } + + @DontCompile + public long test10_interp(int i, MyValue2 v, long l) { + return v.hash() + i + l; + } + + @Test(failOn = ALLOC + STORE + TRAP) + public long test10(int i, MyValue2 v, long l) { + return test10_interp(i, v, l); + } + + @DontCompile + public void test10_verifier(boolean warmup) { + MyValue2 v = MyValue2.createWithFieldsInline(rI, true); + long result = test10(rI, v, rL); + Asserts.assertEQ(result, v.hashInterpreted() + rL + rI); + } + + @DontCompile + public long test11_interp(long l, MyValue2 v, int i) { + return v.hash() + i + l; + } + + @Test(failOn = ALLOC + STORE + TRAP) + public long test11(long l, MyValue2 v, int i) { + return test11_interp(l, v, i); + } + + @DontCompile + public void test11_verifier(boolean warmup) { + MyValue2 v = MyValue2.createWithFieldsInline(rI, true); + long result = test11(rL, v, rI); + Asserts.assertEQ(result, v.hashInterpreted() + rL + rI); + } + + @DontCompile + public long test12_interp(long l, MyValue1 v1, int i, MyValue2 v2) { + return v1.hash() + i + l + v2.hash(); + } + + @Test(failOn = ALLOC + STORE + TRAP) + public long test12(long l, MyValue1 v1, int i, MyValue2 v2) { + return test12_interp(l, v1, i, v2); + } + + @DontCompile + public void test12_verifier(boolean warmup) { + MyValue1 v1 = MyValue1.createWithFieldsDontInline(rI, rL); + MyValue2 v2 = MyValue2.createWithFieldsInline(rI, true); + long result = test12(rL, v1, rI, v2); + Asserts.assertEQ(result, v1.hashInterpreted() + rL + rI + v2.hashInterpreted()); + } + + // Test that debug info at a call is correct + @DontCompile + public long test13_interp(MyValue2 v, MyValue1[] va, boolean deopt) { + if (deopt) { + // uncommon trap + WHITE_BOX.deoptimizeMethod(tests.get(getClass().getSimpleName() + "::test13")); + } + return v.hash() + va[0].hash() + va[1].hash(); + } + + @Test(failOn = ALLOC + STORE + TRAP) + public long test13(MyValue2 v, MyValue1[] va, boolean flag, long l) { + return test13_interp(v, va, flag) + l; + } + + @DontCompile + public void test13_verifier(boolean warmup) { + MyValue2 v = MyValue2.createWithFieldsInline(rI, true); + MyValue1[] va = new MyValue1[2]; + va[0] = MyValue1.createWithFieldsDontInline(rI, rL); + va[1] = MyValue1.createWithFieldsDontInline(rI, rL); + long result = test13(v, va, !warmup, rL); + Asserts.assertEQ(result, v.hashInterpreted() + va[0].hash() + va[1].hash() + rL); + } + + // Test deoptimization at call return with return value in registers + @DontCompile + public MyValue2 test14_interp(boolean deopt) { + if (deopt) { + // uncommon trap + WHITE_BOX.deoptimizeMethod(tests.get(getClass().getSimpleName() + "::test14")); + } + return MyValue2.createWithFieldsInline(rI, true); + } + + @Test() + public MyValue2 test14(boolean flag) { + return test14_interp(flag); + } + + @DontCompile + public void test14_verifier(boolean warmup) { + MyValue2 result = test14(!warmup); + MyValue2 v = MyValue2.createWithFieldsInline(rI, true); + Asserts.assertEQ(result.hash(), v.hash()); + } + + // Return value types in registers from interpreter -> compiled + final MyValue3 test15_vt = MyValue3.create(); + @DontCompile + public MyValue3 test15_interp() { + return test15_vt; + } + + MyValue3 test15_vt2; + @Test(valid = ValueTypeReturnedAsFieldsOn, failOn = ALLOC + LOAD + TRAP) + @Test(valid = ValueTypeReturnedAsFieldsOff) + public void test15() { + test15_vt2 = test15_interp(); + } + + @DontCompile + public void test15_verifier(boolean warmup) { + test15(); + test15_vt.verify(test15_vt2); + } + + // Return value types in registers from compiled -> interpreter + final MyValue3 test16_vt = MyValue3.create(); + @Test(valid = ValueTypeReturnedAsFieldsOn, failOn = ALLOC + STORE + TRAP) + @Test(valid = ValueTypeReturnedAsFieldsOff) + public MyValue3 test16() { + return test16_vt; + } + + @DontCompile + public void test16_verifier(boolean warmup) { + MyValue3 vt = test16(); + test16_vt.verify(vt); + } + + // Return value types in registers from compiled -> compiled + final MyValue3 test17_vt = MyValue3.create(); + @DontInline + public MyValue3 test17_comp() { + return test17_vt; + } + + MyValue3 test17_vt2; + @Test(valid = ValueTypeReturnedAsFieldsOn, failOn = ALLOC + LOAD + TRAP) + @Test(valid = ValueTypeReturnedAsFieldsOff) + public void test17() { + test17_vt2 = test17_comp(); + } + + @DontCompile + public void test17_verifier(boolean warmup) throws Exception { + Method helper_m = getClass().getDeclaredMethod("test17_comp"); + if (!warmup && USE_COMPILER && !WHITE_BOX.isMethodCompiled(helper_m, false)) { + WHITE_BOX.enqueueMethodForCompilation(helper_m, COMP_LEVEL_FULL_OPTIMIZATION); + Asserts.assertTrue(WHITE_BOX.isMethodCompiled(helper_m, false), "test17_comp not compiled"); + } + test17(); + test17_vt.verify(test17_vt2); + } + + // Same tests as above but with a value type that cannot be returned in registers + + // Return value types in registers from interpreter -> compiled + final MyValue4 test18_vt = MyValue4.create(); + @DontCompile + public MyValue4 test18_interp() { + return test18_vt; + } + + MyValue4 test18_vt2; + @Test + public void test18() { + test18_vt2 = test18_interp(); + } + + @DontCompile + public void test18_verifier(boolean warmup) { + test18(); + test18_vt.verify(test18_vt2); + } + + // Return value types in registers from compiled -> interpreter + final MyValue4 test19_vt = MyValue4.create(); + @Test + public MyValue4 test19() { + return test19_vt; + } + + @DontCompile + public void test19_verifier(boolean warmup) { + MyValue4 vt = test19(); + test19_vt.verify(vt); + } + + // Return value types in registers from compiled -> compiled + final MyValue4 test20_vt = MyValue4.create(); + @DontInline + public MyValue4 test20_comp() { + return test20_vt; + } + + MyValue4 test20_vt2; + @Test + public void test20() { + test20_vt2 = test20_comp(); + } + + @DontCompile + public void test20_verifier(boolean warmup) throws Exception { + Method helper_m = getClass().getDeclaredMethod("test20_comp"); + if (!warmup && USE_COMPILER && !WHITE_BOX.isMethodCompiled(helper_m, false)) { + WHITE_BOX.enqueueMethodForCompilation(helper_m, COMP_LEVEL_FULL_OPTIMIZATION); + Asserts.assertTrue(WHITE_BOX.isMethodCompiled(helper_m, false), "test20_comp not compiled"); + } + test20(); + test20_vt.verify(test20_vt2); + } + + // Test no result from inlined method for incremental inlining + final MyValue3 test21_vt = MyValue3.create(); + public MyValue3 test21_inlined() { + throw new RuntimeException(); + } + + @Test + public MyValue3 test21() { + try { + return test21_inlined(); + } catch (RuntimeException ex) { + return test21_vt; + } + } + + @DontCompile + public void test21_verifier(boolean warmup) { + MyValue3 vt = test21(); + test21_vt.verify(vt); + } + + // Test returning a non-flattened value type as fields + MyValue3.box test22_vt = MyValue3.create(); + + @Test + public MyValue3 test22() { + return test22_vt; + } + + @DontCompile + public void test22_verifier(boolean warmup) { + MyValue3 vt = test22(); + test22_vt.verify(vt); + } + + // Test calling a method that has circular register/stack dependencies when unpacking value type arguments + value class TestValue23 { + final double f1; + TestValue23(double val) { + f1 = val; + } + } - @Test - public MyValue3 test21() { - try { - return test21_inlined(); - } catch (RuntimeException ex) { - return test21_vt; - } - } + static double test23Callee(int i1, int i2, int i3, int i4, int i5, int i6, + TestValue23 v1, TestValue23 v2, TestValue23 v3, TestValue23 v4, TestValue23 v5, TestValue23 v6, TestValue23 v7, TestValue23 v8, + double d1, double d2, double d3, double d4, double d5, double d6, double d7, double d8) { + return i1 + i2 + i3 + i4 + i5 + i6 + v1.f1 + v2.f1 + v3.f1 + v4.f1 + v5.f1 + v6.f1 + v7.f1 + v8.f1 + d1 + d2 + d3 + d4 + d5 + d6 + d7 + d8; + } - @DontCompile - public void test21_verifier(boolean warmup) { - MyValue3 vt = test21(); - test21_vt.verify(vt); - } + @Test + public double test23(int i1, int i2, int i3, int i4, int i5, int i6, + TestValue23 v1, TestValue23 v2, TestValue23 v3, TestValue23 v4, TestValue23 v5, TestValue23 v6, TestValue23 v7, TestValue23 v8, + double d1, double d2, double d3, double d4, double d5, double d6, double d7, double d8) { + return test23Callee(i1, i2, i3, i4, i5, i6, + v1, v2, v3, v4, v5, v6, v7, v8, + d1, d2, d3, d4, d5, d6, d7, d8); + } + + @DontCompile + public void test23_verifier(boolean warmup) { + TestValue23 vt = new TestValue23(rI); + double res1 = test23(rI, rI, rI, rI, rI, rI, + vt, vt, vt, vt, vt, vt, vt, vt, + rI, rI, rI, rI, rI, rI, rI, rI); + double res2 = test23Callee(rI, rI, rI, rI, rI, rI, + vt, vt, vt, vt, vt, vt, vt, vt, + rI, rI, rI, rI, rI, rI, rI, rI); + double res3 = 6*rI + 8*rI + 8*rI; + Asserts.assertEQ(res1, res2); + Asserts.assertEQ(res2, res3); + } } --- old/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestMethodHandles.java 2018-12-18 11:43:14.815783314 +0100 +++ new/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestMethodHandles.java 2018-12-18 11:43:14.511935318 +0100 @@ -43,6 +43,7 @@ * @run driver ClassFileInstaller sun.hotspot.WhiteBox jdk.test.lib.Platform * @run main/othervm/timeout=120 -Xbootclasspath/a:. -ea -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions * -XX:+UnlockExperimentalVMOptions -XX:+WhiteBoxAPI -XX:+EnableValhalla + * -DVerifyIR=false * compiler.valhalla.valuetypes.ValueTypeTest * compiler.valhalla.valuetypes.TestMethodHandles */ @@ -51,7 +52,10 @@ @Override public String[] getExtraVMParameters(int scenario) { switch (scenario) { + // Prevent inlining through MethodHandle linkTo adapters to stress the calling convention + case 2: return new String[] {"-XX:CompileCommand=dontinline,java.lang.invoke.DirectMethodHandle::internalMemberName"}; case 3: return new String[] {"-XX:-ValueArrayFlatten"}; + case 4: return new String[] {"-XX:CompileCommand=dontinline,java.lang.invoke.DirectMethodHandle::internalMemberName"}; } return null; } --- old/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestUnresolvedValueClass.java 2018-12-18 11:43:15.983199298 +0100 +++ new/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestUnresolvedValueClass.java 2018-12-18 11:43:15.631375303 +0100 @@ -25,7 +25,6 @@ * @test * @bug 8187679 * @summary The VM should exit gracefully when unable to resolve a value type argument - * @requires vm.compMode != "Xint" * @library /test/lib * @compile -XDemitQtypes -XDenableValueTypes -XDallowFlattenabilityModifiers TestUnresolvedValueClass.java * @run main/othervm -XX:+EnableValhalla TestUnresolvedValueClass @@ -58,10 +57,7 @@ // Adapter creation for TestUnresolvedValueClass::test1 should fail with a // ClassNotFoundException because the class for argument 'vt' was not found. String output = oa.getOutput(); - if (!output.contains("ValueTypePassFieldsAsArgs is not supported on this platform")) { - oa.shouldContain("java.lang.ClassNotFoundException: SimpleValueType"); - oa.shouldHaveExitValue(1); - } + oa.shouldContain("java.lang.NoClassDefFoundError: SimpleValueType"); } } } --- old/test/hotspot/jtreg/compiler/valhalla/valuetypes/ValueTypeTest.java 2018-12-18 11:43:17.074653282 +0100 +++ new/test/hotspot/jtreg/compiler/valhalla/valuetypes/ValueTypeTest.java 2018-12-18 11:43:16.750815287 +0100 @@ -115,7 +115,7 @@ "-XX:+PrintCompilation", "-XX:+PrintIdeal", "-XX:+PrintOptoAssembly"); private static final List verifyFlags = Arrays.asList( "-XX:+VerifyOops", "-XX:+VerifyStack", "-XX:+VerifyLastFrame", "-XX:+VerifyBeforeGC", "-XX:+VerifyAfterGC", - "-XX:+VerifyDuringGC", "-XX:+VerifyAdapterSharing", "-XX:+StressValueTypeReturnedAsFields"); + "-XX:+VerifyDuringGC", "-XX:+VerifyAdapterSharing"); protected static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); protected static final int ValueTypePassFieldsAsArgsOn = 0x1; @@ -145,7 +145,7 @@ protected static final String ALLOCA = "(.*precise klass \\[Lcompiler/valhalla/valuetypes/MyValue.*\\R(.*(nop|spill).*\\R)*.*_new_array_Java" + END; protected static final String LOAD = START + "Load(B|S|I|L|F|D|P|N)" + MID + "@compiler/valhalla/valuetypes/MyValue.*" + END; protected static final String LOADK = START + "LoadK" + MID + END; - protected static final String STORE = START + "Store(B|S|I|L|F|D|P|N)" + MID + "@compiler/valhalla/valuetypes/MyValue.*" + END; + protected static final String STORE = START + "Store(B|C|S|I|L|F|D|P|N)" + MID + "@compiler/valhalla/valuetypes/MyValue.*" + END; protected static final String LOOP = START + "Loop" + MID + "" + END; protected static final String TRAP = START + "CallStaticJava" + MID + "uncommon_trap.*(unstable_if|predicate)" + END; protected static final String RETURN = START + "Return" + MID + "returns" + END; @@ -219,7 +219,9 @@ "-XX:-ValueArrayFlatten", "-XX:ValueFieldMaxFlatSize=0", "-XX:+ValueTypePassFieldsAsArgs", - "-XX:+ValueTypeReturnedAsFields"}; + "-XX:+ValueTypeReturnedAsFields", + "-XX:+StressValueTypePassFieldsAsArgs", + "-XX:+StressValueTypeReturnedAsFields"}; case 3: return new String[] { "-DVerifyIR=false", "-XX:+AlwaysIncrementalInline", @@ -235,7 +237,8 @@ "-XX:+ValueArrayFlatten", "-XX:ValueFieldMaxFlatSize=0", "-XX:+ValueTypePassFieldsAsArgs", - "-XX:-ValueTypeReturnedAsFields"}; + "-XX:-ValueTypeReturnedAsFields", + "-XX:+StressValueTypePassFieldsAsArgs"}; } return null;