--- old/src/cpu/x86/vm/frame_x86.cpp 2017-06-20 16:27:57.230074718 -0400 +++ new/src/cpu/x86/vm/frame_x86.cpp 2017-06-20 16:27:56.634071763 -0400 @@ -642,6 +642,7 @@ DESCRIBE_FP_OFFSET(interpreter_frame_mirror); DESCRIBE_FP_OFFSET(interpreter_frame_mdp); DESCRIBE_FP_OFFSET(interpreter_frame_cache); + DESCRIBE_FP_OFFSET(interpreter_frame_vt_alloc_ptr); DESCRIBE_FP_OFFSET(interpreter_frame_locals); DESCRIBE_FP_OFFSET(interpreter_frame_bcp); DESCRIBE_FP_OFFSET(interpreter_frame_initial_sp); --- old/src/cpu/x86/vm/frame_x86.hpp 2017-06-20 16:27:59.114084061 -0400 +++ new/src/cpu/x86/vm/frame_x86.hpp 2017-06-20 16:27:58.442080728 -0400 @@ -73,7 +73,8 @@ interpreter_frame_mirror_offset = interpreter_frame_method_offset - 1, interpreter_frame_mdp_offset = interpreter_frame_mirror_offset - 1, interpreter_frame_cache_offset = interpreter_frame_mdp_offset - 1, - interpreter_frame_locals_offset = interpreter_frame_cache_offset - 1, + interpreter_frame_vt_alloc_ptr_offset = interpreter_frame_cache_offset - 1, + interpreter_frame_locals_offset = interpreter_frame_vt_alloc_ptr_offset - 1, interpreter_frame_bcp_offset = interpreter_frame_locals_offset - 1, interpreter_frame_initial_sp_offset = interpreter_frame_bcp_offset - 1, --- old/src/cpu/x86/vm/frame_x86.inline.hpp 2017-06-20 16:28:01.210094454 -0400 +++ new/src/cpu/x86/vm/frame_x86.inline.hpp 2017-06-20 16:28:00.570091281 -0400 @@ -192,6 +192,10 @@ return (oop*)addr_at(interpreter_frame_mirror_offset); } +inline intptr_t** frame::interpreter_frame_vt_alloc_ptr_addr() const { + return (intptr_t**)addr_at(interpreter_frame_vt_alloc_ptr_offset); +} + // top of expression stack inline intptr_t* frame::interpreter_frame_tos_address() const { intptr_t* last_sp = interpreter_frame_last_sp(); --- old/src/cpu/x86/vm/interp_masm_x86.cpp 2017-06-20 16:28:03.306104848 -0400 +++ new/src/cpu/x86/vm/interp_masm_x86.cpp 2017-06-20 16:28:02.706101873 -0400 @@ -27,6 +27,7 @@ #include "interpreter/interpreter.hpp" #include "interpreter/interpreterRuntime.hpp" #include "logging/log.hpp" +#include "memory/vtBuffer.hpp" #include "oops/arrayOop.hpp" #include "oops/markOop.hpp" #include "oops/methodData.hpp" @@ -1077,6 +1078,28 @@ notify_method_exit(state, SkipNotifyJVMTI); // preserve TOSCA } + Label vtbuffer_slow, vtbuffer_done; + const Register thread1 = NOT_LP64(rcx) LP64_ONLY(r15_thread); + const uintptr_t chunk_mask = VTBufferChunk::chunk_mask(); + movptr(rbx, Address(rbp, frame::interpreter_frame_vt_alloc_ptr_offset * wordSize)); + NOT_LP64(get_thread(thread1)); + movptr(rcx, Address(thread1, JavaThread::vt_alloc_ptr_offset())); + cmpptr(rbx, rcx); + jcc(Assembler::equal, vtbuffer_done); + andptr(rbx, chunk_mask); + andptr(rcx, chunk_mask); + cmpptr(rbx, rcx); + jcc(Assembler::notEqual, vtbuffer_slow); + movptr(rbx, Address(rbp, frame::interpreter_frame_vt_alloc_ptr_offset * wordSize)); + movptr(Address(thread1, JavaThread::vt_alloc_ptr_offset()), rbx); + jmp(vtbuffer_done); + bind(vtbuffer_slow); + push(state); + call_VM(noreg, CAST_FROM_FN_PTR(address, + InterpreterRuntime::recycle_vtbuffer)); + pop(state); + bind(vtbuffer_done); + // remove activation // get sender sp movptr(rbx, --- old/src/cpu/x86/vm/stubGenerator_x86_64.cpp 2017-06-20 16:28:05.274114607 -0400 +++ new/src/cpu/x86/vm/stubGenerator_x86_64.cpp 2017-06-20 16:28:04.654111532 -0400 @@ -995,7 +995,7 @@ StubCodeMark mark(this, "StubRoutines", "verify_oop"); address start = __ pc(); - Label exit, error; + Label exit, error, in_Java_heap; __ pushf(); __ incrementl(ExternalAddress((address) StubRoutines::verify_oop_count_addr())); @@ -1029,7 +1029,14 @@ __ andptr(c_rarg2, c_rarg3); __ movptr(c_rarg3, (intptr_t) Universe::verify_oop_bits()); __ cmpptr(c_rarg2, c_rarg3); - __ jcc(Assembler::notZero, error); + __ jcc(Assembler::zero, in_Java_heap); + // Not in Java heap, but could be valid if it's a bufferable value type + __ load_klass(c_rarg2, rax); + __ movbool(c_rarg2, Address(c_rarg2, InstanceKlass::extra_flags_offset())); + __ andptr(c_rarg2, InstanceKlass::_extra_is_bufferable); + __ testbool(c_rarg2); + __ jcc(Assembler::zero, error); + __ bind(in_Java_heap); // set r12 to heapbase for load_klass() __ reinit_heapbase(); --- old/src/cpu/x86/vm/templateInterpreterGenerator_x86.cpp 2017-06-20 16:28:07.330124802 -0400 +++ new/src/cpu/x86/vm/templateInterpreterGenerator_x86.cpp 2017-06-20 16:28:06.662121489 -0400 @@ -686,6 +686,10 @@ __ movptr(rdx, Address(rdx, ConstMethod::constants_offset())); __ movptr(rdx, Address(rdx, ConstantPool::cache_offset_in_bytes())); __ push(rdx); // set constant pool cache + const Register thread1 = NOT_LP64(rdx) LP64_ONLY(r15_thread); + NOT_LP64(__ get_thread(thread1)); + __ movptr(rdx, Address(thread1, JavaThread::vt_alloc_ptr_offset())); + __ push(rdx); // value type allocation pointer when activation is created __ push(rlocals); // set locals pointer if (native_call) { __ push(0); // no bcp --- old/src/cpu/x86/vm/templateTable_x86.cpp 2017-06-20 16:28:09.106133609 -0400 +++ new/src/cpu/x86/vm/templateTable_x86.cpp 2017-06-20 16:28:08.482130514 -0400 @@ -2104,6 +2104,36 @@ } void TemplateTable::branch(bool is_jsr, bool is_wide) { + if (ValueTypesThreadLocalRecycling) { + Label no_vt_recycling, no_fixing_required; + const Register thread1 = NOT_LP64(rbx) LP64_ONLY(r15_thread); + NOT_LP64(__ get_thread(thread1)); + __ movptr(rbx, Address(thread1, in_bytes(JavaThread::vt_alloc_ptr_offset()))); + __ testptr(rbx, rbx); + __ jcc(Assembler::zero, no_vt_recycling); + __ movptr(rcx, Address(rbp, frame::interpreter_frame_vt_alloc_ptr_offset * wordSize)); + __ testptr(rcx, rcx); + __ jcc(Assembler::notZero, no_fixing_required); + // vt_alloc_ptr in JavaThread is non-null but frame vt_alloc_ptr is null + // which means frame vt_alloc_ptr needs to be initialized + __ call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::fix_frame_vt_alloc_ptr)); + __ movptr(rcx, Address(rbp, frame::interpreter_frame_vt_alloc_ptr_offset * wordSize)); + __ bind(no_fixing_required); + __ testptr(rcx, rbx); + __ jcc(Assembler::equal, no_vt_recycling); + __ andptr(rcx, VTBufferChunk::chunk_mask()); + __ movl(rcx, Address(rcx, VTBufferChunk::index_offset())); + __ andptr(rbx, VTBufferChunk::chunk_mask()); + __ movl(rbx, Address(rbx, VTBufferChunk::index_offset())); + __ subl(rbx, rcx); + __ get_method(rcx); + __ movl(rcx, Address(rcx, Method::max_vt_buffer_offset())); + __ cmpl(rbx, rcx); + __ jcc(Assembler::lessEqual, no_vt_recycling); + __ call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::recycle_buffered_values)); + __ bind(no_vt_recycling); + } + __ get_method(rcx); // rcx holds method __ profile_taken_branch(rax, rbx); // rax holds updated MDP, rbx // holds bumped taken count @@ -2620,12 +2650,30 @@ __ bind(skip_register_finalizer); } + if (state == qtos) { + const Register thread1 = NOT_LP64(rcx) LP64_ONLY(r15_thread); + __ call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::return_value), rax); + NOT_LP64(__ get_thread(thread1)); + __ get_vm_result(rax, thread1); + } // Narrow result if state is itos but result type is smaller. // Need to narrow in the return bytecode rather than in generate_return_entry // since compiled code callers expect the result to already be narrowed. if (state == itos) { __ narrow(rax); } + +#ifdef ASSERT + if (EnableMVT || EnableValhalla) { + if (state == atos) { + const Register thread1 = NOT_LP64(rcx) LP64_ONLY(r15_thread); + __ call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::check_areturn), rax); + NOT_LP64(__ get_thread(thread1)); + __ get_vm_result(rax, thread1); + } + } +#endif // ASSERT + __ remove_activation(state, rbcp, true, true, true, state == qtos && ValueTypeReturnedAsFields); __ jmp(rbcp); @@ -2845,20 +2893,21 @@ __ jcc(Assembler::notEqual, notValueType); // qtos if (is_static) { + Label initialized; + // Issue below if the static field has not been initialized yet __ load_heap_oop(rax, field); + __ testptr(rax, rax); + __ jcc(Assembler::notZero, initialized); + __ call_VM(rax, CAST_FROM_FN_PTR(address, InterpreterRuntime::initialize_static_value_field), + obj, off); + __ verify_oop(rax); + __ bind(initialized); __ push(qtos); // if (!is_static && !is_vgetfield) { // patch_bytecode(Bytecodes::_fast_qgetfield, bc, rbx); // } } else { - - // cp cache entry pointer -// __ addptr(cache, in_bytes(ConstantPoolCache::base_offset())); -// __ shll(index, LogBytesPerWord); -// __ addptr(cache, index); - pop_and_check_object(obj); - call_VM(rax, CAST_FROM_FN_PTR(address, InterpreterRuntime::qgetfield), obj, off); __ verify_oop(rax); @@ -3206,7 +3255,9 @@ } else { // Store into the static field // Value types in static fields are currently handled with indirection - do_oop_store(_masm, field, rax, _bs->kind(), false); + // but a copy to the Java heap might be required if the value is currently + // stored in a thread local buffer + call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::qputstatic), rax); } __ jmp(Done); } --- old/src/share/vm/classfile/classFileParser.cpp 2017-06-20 16:28:11.042143209 -0400 +++ new/src/share/vm/classfile/classFileParser.cpp 2017-06-20 16:28:10.442140233 -0400 @@ -5635,6 +5635,11 @@ CHECK); } + if (is_value_type()) { + ValueKlass* vk = ValueKlass::cast(ik); + vk->set_if_bufferable(); + } + // Valhalla shady value type conversion if (_parsed_annotations->is_derive_value_type()) { ik->create_derive_value_type(Handle(THREAD, _loader_data->class_loader()), --- old/src/share/vm/interpreter/interpreterRuntime.cpp 2017-06-20 16:28:12.858152214 -0400 +++ new/src/share/vm/interpreter/interpreterRuntime.cpp 2017-06-20 16:28:12.318149536 -0400 @@ -38,6 +38,7 @@ #include "memory/oopFactory.hpp" #include "memory/resourceArea.hpp" #include "memory/universe.inline.hpp" +#include "memory/vtBuffer.hpp" #include "oops/constantPool.hpp" #include "oops/instanceKlass.hpp" #include "oops/methodData.hpp" @@ -193,7 +194,7 @@ instance()->int_field_put(offset, (jint)*((int*)addr)); break; case T_LONG: - instance()->long_field_put(offset, (jlong)*((long*)addr)); // Is it correct on 32 and 64 bits? + instance()->long_field_put(offset, (jlong)*((long long*)addr)); break; case T_OBJECT: case T_ARRAY: @@ -214,14 +215,10 @@ vklass->initialize(THREAD); // Creating value - instanceOop value = vklass->allocate_instance(CHECK); + bool in_heap; + instanceOop value = vklass->allocate_buffered_or_heap_instance(&in_heap, CHECK); Handle value_h = Handle(THREAD, value); - // Zeroing, already performed by allocate_instance() when allocating in the Java Heap - // Might need to be performed manually for off-heap allocations - // memset(((char*)(oopDesc*)value) + vklass_h->first_field_offset(), 0, - // vklass_h->size_helper() * wordSize - vklass_h->first_field_offset()); - thread->set_vm_result(value_h()); IRT_END @@ -252,11 +249,13 @@ Handle old_value_h(THREAD, old_value); // Creating new value by copying the one passed in argument - instanceOop new_value = vklass->allocate_instance(CHECK_0); + bool in_heap; + instanceOop new_value = vklass->allocate_buffered_or_heap_instance(&in_heap, + CHECK_((type2size[field_type]) * AbstractInterpreter::stackElementSize)); Handle new_value_h = Handle(THREAD, new_value); int first_offset = vklass->first_field_offset(); - vklass->value_store(((char*)(oopDesc*)old_value_h()) + first_offset, - ((char*)(oopDesc*)new_value_h()) + first_offset, true, false); + vklass->value_store(vklass->data_for_oop(old_value_h()), + vklass->data_for_oop(new_value_h()), in_heap, false); // Updating the field specified in arguments if (field_type == T_OBJECT || field_type == T_ARRAY) { @@ -269,8 +268,8 @@ oop vt_oop = *(oop*)f.interpreter_frame_expression_stack_at(tos_idx); assert(vt_oop != NULL && vt_oop->is_oop() && vt_oop->is_value(),"argument must be a value type"); assert(field_vk == vt_oop->klass(), "Must match"); - field_vk->value_store(((char*)(oopDesc*)vt_oop + field_vk->first_field_offset()), - ((char*)(oopDesc*)new_value_h()) + fd.offset(), true, false); + field_vk->value_store(field_vk->data_for_oop(vt_oop), + ((char*)(oopDesc*)new_value_h()) + fd.offset(), true, false); } else { intptr_t* addr = f.interpreter_frame_expression_stack_at(tos_idx); copy_primitive_argument(addr, new_value_h, fd.offset(), field_type); @@ -300,11 +299,10 @@ if (vtklass->get_vcc_klass() != target_klass) { THROW_MSG(vmSymbols::java_lang_ClassCastException(), "vbox target is not derive value type box"); } - - oop boxed = vtklass->derive_value_type_copy(Handle(THREAD, value), - InstanceKlass::cast(target_klass), - CHECK); - thread->set_vm_result(boxed); + oop box = vtklass->box(Handle(THREAD, value), + InstanceKlass::cast(target_klass), + CHECK); + thread->set_vm_result(box); IRT_END IRT_ENTRY(void, InterpreterRuntime::vunbox(JavaThread* thread, ConstantPool* pool, int index, oopDesc* obj)) @@ -323,9 +321,9 @@ if (klass != InstanceKlass::cast(target_klass)->get_vcc_klass()) { THROW_MSG(vmSymbols::java_lang_ClassCastException(), "vunbox target is not derive value type"); } - oop value = ValueKlass::cast(target_klass)->derive_value_type_copy(Handle(THREAD, obj), - InstanceKlass::cast(target_klass), - CHECK); + oop value = ValueKlass::cast(target_klass)->unbox(Handle(THREAD, obj), + InstanceKlass::cast(target_klass), + CHECK); thread->set_vm_result(value); IRT_END @@ -337,28 +335,90 @@ klass->find_field_from_offset(offset, false, &fd); Klass* field_k = klass->get_value_field_klass(fd.index()); ValueKlass* field_vklass = ValueKlass::cast(field_k); + field_vklass->initialize(THREAD); + // allocate instance - instanceOop res = field_vklass->allocate_instance(CHECK); + bool in_heap; + instanceOop res = field_vklass->allocate_buffered_or_heap_instance(&in_heap, CHECK); + instanceHandle res_h(THREAD, res); // copy value - int size = field_vklass->layout_helper_size_in_bytes(field_vklass->layout_helper()); field_vklass->value_store(((char*)(oopDesc*)value_h()) + offset, - ((char*)(oopDesc*)res) + field_vklass->first_field_offset(),true, false); - thread->set_vm_result(res); + field_vklass->data_for_oop(res), in_heap, false); + thread->set_vm_result(res_h()); +IRT_END + +IRT_ENTRY(void, InterpreterRuntime::initialize_static_value_field(JavaThread* thread, oopDesc* mirror, int offset)) + instanceHandle mirror_h(THREAD, (instanceOop)mirror); + InstanceKlass* klass = InstanceKlass::cast(java_lang_Class::as_Klass(mirror)); + assert(mirror->obj_field(offset) == NULL,"Field must not be initialized twice"); + + fieldDescriptor fd; + klass->find_field_from_offset(offset, true, &fd); + Klass* field_k = klass->get_value_field_klass(fd.index()); + ValueKlass* field_vklass = ValueKlass::cast(field_k); + // allocate instance, because it is going to be assigned to a static field + // it must not be a buffered value + instanceOop res = field_vklass->allocate_instance(CHECK); + instanceHandle res_h(THREAD, res); + mirror_h()->obj_field_put(offset, res_h()); + thread->set_vm_result(res_h()); IRT_END IRT_ENTRY(void, InterpreterRuntime::qputfield(JavaThread* thread, oopDesc* obj, oopDesc* value, int offset)) Handle value_h(THREAD, value); Handle obj_h(THREAD, obj); + assert(!obj_h()->klass()->is_value(), "obj must be an object"); + assert(value_h()->klass()->is_value(), "value must be an value type"); - InstanceKlass* klass_h = InstanceKlass::cast(obj->klass()); + InstanceKlass* klass = InstanceKlass::cast(obj->klass()); + fieldDescriptor fd; + klass->find_field_from_offset(offset, false, &fd); + Klass* field_k = klass->get_value_field_klass(fd.index()); ValueKlass* field_vklass = ValueKlass::cast(value->klass()); - + assert(field_k == field_vklass, "Field descriptor and argument must match"); // copy value - int size = field_vklass->layout_helper_size_in_bytes(field_vklass->layout_helper()); - field_vklass->value_store(((char*)(oopDesc*)value_h()) + field_vklass->first_field_offset(), + field_vklass->value_store(field_vklass->data_for_oop(value_h()), ((char*)(oopDesc*)obj_h()) + offset, true, false); IRT_END +IRT_ENTRY(void, InterpreterRuntime::qputstatic(JavaThread* thread, oopDesc* value)) + instanceHandle value_h(THREAD, (instanceOop)value); + assert(value_h()->is_value(), "qputstatic only deals with value arguments"); + Method* m = last_frame(thread).interpreter_frame_method(); + jint bci = last_frame(thread).interpreter_frame_bci(); + assert(m->code_at(bci) == Bytecodes::_putstatic, "qputstatic is a particular case of putstatic"); + ConstantPoolCache* cp_cache = last_frame(thread).interpreter_frame_method()->constants()->cache(); + int index = ConstantPool::decode_cpcache_index(get_index_u2_cpcache(thread, Bytecodes::_putstatic)); + ConstantPoolCacheEntry* cp_entry = cp_cache->entry_at(index); + assert(cp_entry->is_field_entry(), "Sanity check"); + + InstanceKlass* klass = InstanceKlass::cast(cp_entry->f1_as_klass()); + int offset = cp_entry->f2_as_index(); + oop mirror = klass->java_mirror(); + + if (Universe::heap()->is_in_reserved(value_h())) { + mirror->obj_field_put(offset, value_h()); + } else { + // The argument is a buffered value, a copy must be created in the Java heap + // because a static field cannot point to a thread-local buffered value + fieldDescriptor fd; + klass->find_field_from_offset(offset, false, &fd); + Klass* field_k = klass->get_value_field_klass(fd.index()); + ValueKlass* field_vklass = ValueKlass::cast(field_k); + assert(field_vklass == value->klass(), "Field descriptor and argument must match"); + // allocate heap instance + instanceOop res = field_vklass->allocate_instance(CHECK); + assert(Universe::heap()->is_in_reserved(res), "Must be in the Java heap"); + instanceHandle res_h(THREAD, res); + // copy value + field_vklass->value_store(field_vklass->data_for_oop(value_h()), + field_vklass->data_for_oop(res), true, false); + // writing static field + mirror->obj_field_put(offset, res_h()); + assert(mirror->obj_field(offset) != NULL,"Sanity check"); + } +IRT_END + IRT_ENTRY(void, InterpreterRuntime::newarray(JavaThread* thread, BasicType type, jint size)) oop obj = oopFactory::new_typeArray(type, size, CHECK); thread->set_vm_result(obj); @@ -382,14 +442,14 @@ thread->set_vm_result(((objArrayOop) array)->obj_at(index)); } else { - // Early prototype: we don't have valorind support...just allocate aref and copy ValueArrayKlass* vaklass = ValueArrayKlass::cast(klass); ValueKlass* vklass = vaklass->element_klass(); arrayHandle ah(THREAD, array); - instanceOop value_holder = vklass->allocate_instance(CHECK); + bool in_heap; + instanceOop value_holder = vklass->allocate_buffered_or_heap_instance(&in_heap, CHECK); void* src = ((valueArrayOop)ah())->value_at_addr(index, vaklass->layout_helper()); vklass->value_store(src, vklass->data_for_oop(value_holder), - vaklass->element_byte_size(), true, true); + vaklass->element_byte_size(), in_heap, false); thread->set_vm_result(value_holder); } IRT_END @@ -402,9 +462,25 @@ THROW(vmSymbols::java_lang_ArrayStoreException()); } if (klass->is_objArray_klass()) { + if(!Universe::heap()->is_in_reserved(val)) { + // A Java heap allocated copy must be made because an array cannot + // reference a thread-local buffered value + Handle val_h(THREAD, (oop)val); + ObjArrayKlass* aklass = ObjArrayKlass::cast(klass); + Klass* eklass = aklass->element_klass(); + assert(eklass->is_value(), "Sanity check"); + assert(eklass == ((oop)val)->klass(), "Sanity check"); + ValueKlass* vklass = ValueKlass::cast(eklass); + // allocate heap instance + instanceOop res = vklass->allocate_instance(CHECK); + Handle res_h(THREAD, res); + // copy value + vklass->value_store(((char*)(oopDesc*)val_h()) + vklass->first_field_offset(), + ((char*)(oopDesc*)res_h()) + vklass->first_field_offset(),true, false); + val = res_h(); + } ((objArrayOop) array)->obj_at_put(index, (oop)val); - } - else { + } else { valueArrayOop varray = (valueArrayOop)array; ValueArrayKlass* vaklass = ValueArrayKlass::cast(klass); ValueKlass* vklass = vaklass->element_klass(); @@ -444,6 +520,58 @@ thread->set_vm_result(obj); IRT_END +IRT_ENTRY(void, InterpreterRuntime::recycle_vtbuffer(JavaThread* thread)) + VTBuffer::recycle_vtbuffer(thread, last_frame(thread)); +IRT_END + +IRT_ENTRY(void, InterpreterRuntime::recycle_buffered_values(JavaThread* thread)) + frame f = thread->last_frame(); + assert(f.is_interpreted_frame(), "recycling can only be triggered from interpreted frames"); + VTBuffer::recycle_vt_in_frame(thread, &f); +IRT_END + +IRT_ENTRY(void, InterpreterRuntime::fix_frame_vt_alloc_ptr(JavaThread* thread)) + frame f = thread->last_frame(); + VTBuffer::fix_frame_vt_alloc_ptr(f, VTBufferChunk::chunk(thread->vt_alloc_ptr())); +IRT_END + +IRT_ENTRY(void, InterpreterRuntime::return_value(JavaThread* thread, oopDesc* obj)) + if (Universe::heap()->is_in_reserved(obj)) { + thread->set_vm_result(obj); + return; + } + assert(obj->klass()->is_value(), "Sanity check"); + ValueKlass* vk = ValueKlass::cast(obj->klass()); + RegisterMap reg_map(thread, false); + frame current_frame = last_frame(thread); + frame caller_frame = current_frame.sender(®_map); + if (!caller_frame.is_interpreted_frame()) { + // caller is not an interpreted frame, creating a new value in Java heap + Handle obj_h(THREAD, obj); + instanceOop res = vk->allocate_instance(CHECK); + Handle res_h(THREAD, res); + // copy value + vk->value_store(vk->data_for_oop(obj_h()), + vk->data_for_oop(res_h()), true, false); + thread->set_vm_result(res_h()); + return; + } else { + oop dest = VTBuffer::relocate_return_value(thread, current_frame, obj); + thread->set_vm_result(dest); + } +IRT_END + +IRT_ENTRY(void, InterpreterRuntime::check_areturn(JavaThread* thread, oopDesc* obj)) + if (obj != NULL) { + Klass* k = obj->klass(); + if (k->is_value()) { + ResourceMark rm(thread); + tty->print_cr("areturn used on a value from %s", k->name()->as_C_string()); + } + assert(!k->is_value(), "areturn should never be used on values"); + } + thread->set_vm_result(obj); +IRT_END IRT_ENTRY(void, InterpreterRuntime::register_finalizer(JavaThread* thread, oopDesc* obj)) assert(obj->is_oop(), "must be a valid oop"); @@ -974,7 +1102,8 @@ Symbol* signature = call.signature(); receiver = Handle(thread, thread->last_frame().interpreter_callee_receiver(signature)); - assert(Universe::heap()->is_in_reserved_or_null(receiver()), + assert(Universe::heap()->is_in_reserved_or_null(receiver()) + || VTBuffer::is_in_vt_buffer(receiver()), "sanity check"); assert(receiver.is_null() || !Universe::heap()->is_in_reserved(receiver->klass()), --- old/src/share/vm/interpreter/interpreterRuntime.hpp 2017-06-20 16:28:15.086163262 -0400 +++ new/src/share/vm/interpreter/interpreterRuntime.hpp 2017-06-20 16:28:14.150158621 -0400 @@ -91,6 +91,15 @@ static int vwithfield (JavaThread* thread, ConstantPoolCache* cp_cache); static void qgetfield (JavaThread* thread, oopDesc* value, int offset); static void qputfield (JavaThread* thread, oopDesc* obj, oopDesc* value, int offset); + static void qputstatic (JavaThread* thread, oopDesc* value); + static void initialize_static_value_field(JavaThread*, oopDesc* mirror, int offset); + + // Value Buffers support + static void recycle_vtbuffer(JavaThread* thread); + static void recycle_buffered_values(JavaThread* thread); + static void return_value(JavaThread* thread, oopDesc* obj); + static void check_areturn(JavaThread* thread, oopDesc* obj); + static void fix_frame_vt_alloc_ptr(JavaThread* thread); // vaload/vastore static void value_array_load(JavaThread* thread, arrayOopDesc* array, int index); --- old/src/share/vm/interpreter/oopMapCache.cpp 2017-06-20 16:28:17.254174013 -0400 +++ new/src/share/vm/interpreter/oopMapCache.cpp 2017-06-20 16:28:16.330169431 -0400 @@ -439,7 +439,7 @@ OopMapCache::OopMapCache() : - _mut(Mutex::leaf, "An OopMapCache lock", true) + _mut(Mutex::leaf, "An OopMapCache lock", true, Monitor::_safepoint_check_never) { _array = NEW_C_HEAP_ARRAY(OopMapCacheEntry, _size, mtClass); // Cannot call flush for initialization, since flush @@ -484,7 +484,7 @@ void OopMapCache::lookup(const methodHandle& method, int bci, InterpreterOopMap* entry_for) const { - MutexLocker x(&_mut); + MutexLockerEx x(&_mut, Mutex::_no_safepoint_check_flag); OopMapCacheEntry* entry = NULL; int probe = hash_value_for(method, bci); --- old/src/share/vm/logging/logTag.hpp 2017-06-20 16:28:19.326184287 -0400 +++ new/src/share/vm/logging/logTag.hpp 2017-06-20 16:28:18.810181728 -0400 @@ -141,6 +141,7 @@ LOG_TAG(vmoperation) \ LOG_TAG(vtables) \ LOG_TAG(workgang) \ + LOG_TAG(valuetypes) \ LOG_TAG_LIST_EXT #define PREFIX_LOG_TAG(T) (LogTag::_##T) --- old/src/share/vm/memory/allocation.hpp 2017-06-20 16:28:21.242193788 -0400 +++ new/src/share/vm/memory/allocation.hpp 2017-06-20 16:28:20.554190376 -0400 @@ -145,8 +145,9 @@ mtLogging = 0x0F, // memory for logging mtArguments = 0x10, // memory for argument processing mtModule = 0x11, // memory for module processing - mtNone = 0x12, // undefined - mt_number_of_types = 0x13 // number of memory types (mtDontTrack + mtValueTypes = 0x12, // memory for buffered value types + mtNone = 0x13, // undefined + mt_number_of_types = 0x14 // number of memory types (mtDontTrack // is not included as validate type) }; @@ -753,4 +754,10 @@ static void free(E* addr, size_t length); }; +template class CMmapObj ALLOCATION_SUPER_CLASS_SPEC { +public: + void* operator new(size_t size); + void operator delete(void* address, size_t length); +}; + #endif // SHARE_VM_MEMORY_ALLOCATION_HPP --- old/src/share/vm/memory/allocation.inline.hpp 2017-06-20 16:28:23.410204539 -0400 +++ new/src/share/vm/memory/allocation.inline.hpp 2017-06-20 16:28:22.718201107 -0400 @@ -268,4 +268,29 @@ } } +template void* CMmapObj::operator new(size_t size) { + int alignment = os::vm_allocation_granularity(); + + int pages = (int)(size / os::vm_page_size()); + if (pages == 0 || size % (pages * os::vm_page_size()) != 0) pages++; + size_t mmap_size = pages * os::vm_page_size(); + + char* addr = os::reserve_memory(mmap_size, NULL, alignment, F); + if (addr == NULL) { + vm_exit_out_of_memory(size, OOM_MMAP_ERROR, "VTBuffer (reserve)"); + } + + os::commit_memory_or_exit(addr, mmap_size, !ExecMem, "VTBuffer (commit)"); + + return addr; +} + +template void CMmapObj::operator delete(void* addr, size_t length) { + int pages = (int)(length / os::vm_page_size()); + if (pages == 0 || length % (pages * os::vm_page_size()) != 0) pages++; + size_t mmap_size = pages * os::vm_page_size(); + bool result = os::release_memory((char*)addr, mmap_size); + assert(result, "Failed to release memory"); +} + #endif // SHARE_VM_MEMORY_ALLOCATION_INLINE_HPP --- old/src/share/vm/memory/iterator.hpp 2017-06-20 16:28:25.270213762 -0400 +++ new/src/share/vm/memory/iterator.hpp 2017-06-20 16:28:24.630210588 -0400 @@ -128,6 +128,11 @@ _wrapped_closure->do_oop(p);} }; +class BufferedValueClosure : public Closure { +public: + virtual void do_buffered_value(oop* p) = 0; +}; + class KlassClosure : public Closure { public: virtual void do_klass(Klass* k) = 0; --- old/src/share/vm/oops/instanceKlass.cpp 2017-06-20 16:28:27.038222529 -0400 +++ new/src/share/vm/oops/instanceKlass.cpp 2017-06-20 16:28:26.474219732 -0400 @@ -1212,7 +1212,7 @@ OopMapCache* oop_map_cache = static_cast(OrderAccess::load_ptr_acquire(&_oop_map_cache)); if (oop_map_cache == NULL) { - MutexLocker x(OopMapCacheAlloc_lock); + MutexLockerEx x(OopMapCacheAlloc_lock, Mutex::_no_safepoint_check_flag); // Check if _oop_map_cache was allocated while we were waiting for this lock if ((oop_map_cache = _oop_map_cache) == NULL) { oop_map_cache = new OopMapCache(); --- old/src/share/vm/oops/instanceKlass.hpp 2017-06-20 16:28:29.042232466 -0400 +++ new/src/share/vm/oops/instanceKlass.hpp 2017-06-20 16:28:28.442229491 -0400 @@ -207,12 +207,15 @@ // _misc_flags. bool _is_marked_dependent; // used for marking during flushing and deoptimization + public: enum { _extra_is_being_redefined = 1 << 0, // used for locking redefinition _extra_has_value_fields = 1 << 1, // has value fields and related embedded section is not empty - _extra_has_vcc_klass = 1 << 2 // has a pointer to its Value Capable Class (MVT) + _extra_is_bufferable = 1 << 2, // value can be buffered out side of the Java heap + _extra_has_vcc_klass = 1 << 3 // has a pointer to its Value Capable Class (MVT) }; + protected: u1 _extra_flags; // The low three bits of _misc_flags contains the kind field. @@ -543,6 +546,8 @@ bool is_marked_dependent() const { return _is_marked_dependent; } void set_is_marked_dependent(bool value) { _is_marked_dependent = value; } + static ByteSize extra_flags_offset() { return in_ByteSize(offset_of(InstanceKlass, _extra_flags)); } + // initialization (virtuals from Klass) bool should_be_initialized() const; // means that initialize should be called void initialize(TRAPS); --- old/src/share/vm/oops/method.cpp 2017-06-20 16:28:30.998242166 -0400 +++ new/src/share/vm/oops/method.cpp 2017-06-20 16:28:30.342238913 -0400 @@ -40,6 +40,7 @@ #include "memory/metaspaceShared.hpp" #include "memory/oopFactory.hpp" #include "memory/resourceArea.hpp" +#include "memory/vtBuffer.hpp" #include "oops/constMethod.hpp" #include "oops/method.hpp" #include "oops/methodData.hpp" @@ -104,6 +105,8 @@ set_signature_handler(NULL); } + initialize_max_vt_buffer(); + NOT_PRODUCT(set_compiled_invocation_count(0);) } @@ -1865,6 +1868,14 @@ } } +void Method::initialize_max_vt_buffer() { + long long max_entries = constMethod()->max_locals() + constMethod()->max_stack(); + max_entries *= 2; // Add margin for loops + long long max_size = max_entries * (BigValueTypeThreshold + 8); // 8 -> header size + int max_chunks = (int)(max_size / VTBufferChunk::max_alloc_size()) + 1; + set_max_vt_buffer(MAX2(MinimumVTBufferChunkPerFrame, max_chunks)); +} + int Method::highest_comp_level() const { const MethodCounters* mcs = method_counters(); if (mcs != NULL) { --- old/src/share/vm/oops/method.hpp 2017-06-20 16:28:33.322253690 -0400 +++ new/src/share/vm/oops/method.hpp 2017-06-20 16:28:32.370248969 -0400 @@ -103,6 +103,7 @@ // NULL only at safepoints (because of a de-opt). CompiledMethod* volatile _code; // Points to the corresponding piece of native code volatile address _from_interpreted_entry; // Cache of _code ? _adapter->i2c_entry() : _i2i_entry + int _max_vt_buffer; // max number of VT buffer chunk to use before recycling #if INCLUDE_AOT && defined(TIERED) CompiledMethod* _aot_code; @@ -263,6 +264,12 @@ int max_locals() const { return constMethod()->max_locals(); } void set_max_locals(int size) { constMethod()->set_max_locals(size); } + // value type buffering + void initialize_max_vt_buffer(); + int max_vt_buffer() const { return _max_vt_buffer; } + void set_max_vt_buffer(int size) { _max_vt_buffer = size; } + + int highest_comp_level() const; void set_highest_comp_level(int level); int highest_osr_comp_level() const; @@ -705,6 +712,8 @@ static int intrinsic_id_offset_in_bytes() { return offset_of(Method, _intrinsic_id); } static int intrinsic_id_size_in_bytes() { return sizeof(u2); } + static ByteSize max_vt_buffer_offset() { return byte_offset_of(Method, _max_vt_buffer); } + // Static methods that are used to implement member methods where an exposed this pointer // is needed due to possible GCs static objArrayHandle resolved_checked_exceptions_impl(Method* method, TRAPS); --- old/src/share/vm/oops/oop.inline.hpp 2017-06-20 16:28:35.210263052 -0400 +++ new/src/share/vm/oops/oop.inline.hpp 2017-06-20 16:28:34.610260077 -0400 @@ -31,6 +31,7 @@ #include "gc/shared/collectedHeap.inline.hpp" #include "gc/shared/genCollectedHeap.hpp" #include "gc/shared/generation.hpp" +#include "memory/vtBuffer.hpp" #include "oops/arrayKlass.hpp" #include "oops/arrayOop.hpp" #include "oops/klass.inline.hpp" @@ -547,7 +548,12 @@ bool oopDesc::is_oop(bool ignore_mark_word) const { oop obj = (oop) this; if (!check_obj_alignment(obj)) return false; - if (!Universe::heap()->is_in_reserved(obj)) return false; + if (!Universe::heap()->is_in_reserved(obj)) { + assert(obj->klass()->is_value(), "Only value type can be outside of the Java heap"); + VTBufferChunk* chunk = VTBufferChunk::chunk(obj); + assert(chunk->is_valid(), "if not in the heap, should a buffered VT"); + if (!VTBuffer::is_in_vt_buffer(obj)) return false; + } // obj is aligned and accessible in heap if (Universe::heap()->is_in_reserved(obj->klass_or_null())) return false; @@ -561,7 +567,8 @@ if (mark() != NULL) { return true; } - return !SafepointSynchronize::is_at_safepoint(); + return !SafepointSynchronize::is_at_safepoint() + || (obj->klass()->is_value() && !Universe::heap()->is_in_reserved(obj)) ; } --- old/src/share/vm/oops/valueKlass.cpp 2017-06-20 16:28:37.018272017 -0400 +++ new/src/share/vm/oops/valueKlass.cpp 2017-06-20 16:28:36.418269042 -0400 @@ -25,6 +25,7 @@ #include "precompiled.hpp" #include "gc/shared/gcLocker.inline.hpp" #include "interpreter/interpreter.hpp" +#include "logging/log.hpp" #include "oops/oop.inline.hpp" #include "oops/fieldStreams.hpp" #include "oops/method.hpp" @@ -86,6 +87,28 @@ return 1 << upper_log2(last_offset - first_offset); } +instanceOop ValueKlass::allocate_instance(TRAPS) { + int size = size_helper(); // Query before forming handle. + + return (instanceOop)CollectedHeap::obj_allocate(this, size, CHECK_NULL); +} + +instanceOop ValueKlass::allocate_buffered_or_heap_instance(bool* in_heap, TRAPS) { + assert(THREAD->is_Java_thread(), "Only Java threads can call this method"); + + instanceOop value = NULL; + if (is_bufferable()) { + value = (instanceOop)VTBuffer::allocate_value(this, CHECK_NULL); + *in_heap = false; + } + if (value == NULL) { + log_info(valuetypes)("Value buffering failed, allocating in the Java heap"); + value = allocate_instance(CHECK_NULL); + *in_heap = true; + } + return value; +} + bool ValueKlass::is_atomic() { return (nonstatic_field_size() * heapOopSize) <= longSize; } @@ -235,24 +258,29 @@ } } -oop ValueKlass::derive_value_type_copy(Handle src, InstanceKlass* target_klass, TRAPS) { - assert(EnableMVT, "Only supported with the MVT programming model"); - // assert(InstanceKlass::cast(src->klass())->derive_value_type_klass() == target_klass, "Not this DVT"); -#ifdef ASSERT - if (InstanceKlass::cast(src->klass())->has_vcc_klass()) { - assert(InstanceKlass::cast(src->klass())->get_vcc_klass() == target_klass, - "VCC/DVT mismatch"); - } else { - assert(target_klass->has_vcc_klass(), "Sanity check"); - assert(target_klass->get_vcc_klass() == InstanceKlass::cast(src->klass()), - "VCC/DVT mismatch"); - } -#endif // ASSERT +oop ValueKlass::box(Handle src, InstanceKlass* target_klass, TRAPS) { + assert(src()->klass()->is_value(), "src must be a value type"); + assert(!target_klass->is_value(), "target_klass must not be a value type"); - // Allocate new for safety, simply reinstalling the klass pointer is a little too risky target_klass->initialize(CHECK_0); - instanceOop value = target_klass->allocate_instance(CHECK_0); - value_store(data_for_oop(src()), data_for_oop(value), true, true); + instanceOop box = target_klass->allocate_instance(CHECK_0); + value_store(data_for_oop(src()), data_for_oop(box), true, false); + + assert(!box->klass()->is_value(), "Sanity check"); + return box; +} + +oop ValueKlass::unbox(Handle src, InstanceKlass* target_klass, TRAPS) { + assert(!src()->klass()->is_value(), "src must not be a value type"); + assert(target_klass->is_value(), "target_klass must be a value type"); + ValueKlass* vtklass = ValueKlass::cast(target_klass); + + vtklass->initialize(CHECK_0); + bool in_heap; + instanceOop value = vtklass->allocate_buffered_or_heap_instance(&in_heap, CHECK_0); + value_store(data_for_oop(src()), data_for_oop(value), in_heap, false); + + assert(value->klass()->is_value(), "Sanity check"); return value; } @@ -511,8 +539,13 @@ address loc = map.location(pair.first()); intptr_t ptr = *(intptr_t*)loc; - if (Universe::heap()->is_in_reserved((void*)ptr)) { - return NULL; + if (Metaspace::contains((void*)ptr)) { + return (ValueKlass*)ptr; } - return (ValueKlass*)ptr; + return NULL; +// if (Universe::heap()->is_in_reserved((void*)ptr)) { +// return NULL; +// } +// return (ValueKlass*)ptr; } + --- old/src/share/vm/oops/valueKlass.hpp 2017-06-20 16:28:38.922281459 -0400 +++ new/src/share/vm/oops/valueKlass.hpp 2017-06-20 16:28:38.322278484 -0400 @@ -69,6 +69,13 @@ return layout_helper_to_size_helper(layout_helper()); } + // allocate_instance() allocates a stand alone value in the Java heap + instanceOop allocate_instance(TRAPS); + // allocate_buffered_or_heap_instance() tries to allocate a value in the + // thread local value buffer, if allocation fails, it allocates it in the + // Java heap + instanceOop allocate_buffered_or_heap_instance(bool* in_heap, TRAPS); + // minimum number of bytes occupied by nonstatic fields, HeapWord aligned or pow2 int raw_value_byte_size() const; @@ -84,6 +91,27 @@ return o; } + void set_if_bufferable() { + bool bufferable; + if (contains_oops()) { + bufferable = false; + } else { + int size_in_heap_words = size_helper(); + int base_offset = instanceOopDesc::base_offset_in_bytes(); + size_t size_in_bytes = size_in_heap_words * HeapWordSize - base_offset; + bufferable = size_in_bytes <= BigValueTypeThreshold; + } + if (bufferable) { + _extra_flags |= _extra_is_bufferable; + } else { + _extra_flags &= ~_extra_is_bufferable; + } + } + + bool is_bufferable() const { + return (_extra_flags & _extra_is_bufferable) != 0; + } + // Query if h/w provides atomic load/store bool is_atomic(); @@ -104,8 +132,8 @@ // store the value of this klass contained with src into dst, raw data ptr void value_store(void* src, void* dst, size_t raw_byte_size, bool dst_is_heap, bool dst_uninitialized); - - oop derive_value_type_copy(Handle src, InstanceKlass* target_klass, TRAPS); + oop unbox(Handle src, InstanceKlass* target_klass, TRAPS); + oop box(Handle src, InstanceKlass* target_klass, TRAPS); // GC support... --- old/src/share/vm/runtime/frame.cpp 2017-06-20 16:28:40.718290365 -0400 +++ new/src/share/vm/runtime/frame.cpp 2017-06-20 16:28:40.186287727 -0400 @@ -443,6 +443,16 @@ *interpreter_frame_mdp_addr() = (intptr_t)mdp; } +intptr_t* frame::interpreter_frame_vt_alloc_ptr() const { + assert(is_interpreted_frame(), "interpreted frame expected"); + return (intptr_t*)*interpreter_frame_vt_alloc_ptr_addr(); +} + +void frame::interpreter_frame_set_vt_alloc_ptr(intptr_t* ptr) { + assert(is_interpreted_frame(), "interpreted frame expected"); + *interpreter_frame_vt_alloc_ptr_addr() = ptr; +} + BasicObjectLock* frame::next_monitor_in_interpreter_frame(BasicObjectLock* current) const { assert(is_interpreted_frame(), "Not an interpreted frame"); #ifdef ASSERT @@ -747,16 +757,18 @@ private: frame* _fr; OopClosure* _f; + BufferedValueClosure* _bvt_f; int _max_locals; int _max_stack; public: InterpreterFrameClosure(frame* fr, int max_locals, int max_stack, - OopClosure* f) { + OopClosure* f, BufferedValueClosure* bvt_f) { _fr = fr; _max_locals = max_locals; _max_stack = max_stack; _f = f; + _bvt_f = bvt_f; } void offset_do(int offset) { @@ -764,7 +776,19 @@ if (offset < _max_locals) { addr = (oop*) _fr->interpreter_frame_local_at(offset); assert((intptr_t*)addr >= _fr->sp(), "must be inside the frame"); - _f->do_oop(addr); + if (Universe::heap()->is_in_reserved_or_null(*addr)) { + if (_f != NULL) { + _f->do_oop(addr); + } + } else { // Buffered value types case + if (_f != NULL) { + oop* addr_mirror = (oop*)(*addr)->mark_addr(); + _f->do_oop(addr_mirror); + } + if (_bvt_f != NULL) { + _bvt_f->do_buffered_value(addr); + } + } } else { addr = (oop*) _fr->interpreter_frame_expression_stack_at((offset - _max_locals)); // In case of exceptions, the expression stack is invalid and the esp will be reset to express @@ -776,7 +800,19 @@ in_stack = (intptr_t*)addr >= _fr->interpreter_frame_tos_address(); } if (in_stack) { - _f->do_oop(addr); + if (Universe::heap()->is_in_reserved_or_null(*addr)) { + if (_f != NULL) { + _f->do_oop(addr); + } + } else { // Buffered value types case + if (_f != NULL) { + oop* addr_mirror = (oop*)(*addr)->mark_addr(); + _f->do_oop(addr_mirror); + } + if (_bvt_f != NULL) { + _bvt_f->do_buffered_value(addr); + } + } } } } @@ -949,7 +985,7 @@ } } - InterpreterFrameClosure blk(this, max_locals, m->max_stack(), f); + InterpreterFrameClosure blk(this, max_locals, m->max_stack(), f, NULL); // process locals & expression stack InterpreterOopMap mask; @@ -961,6 +997,23 @@ mask.iterate_oop(&blk); } +void frame::buffered_values_interpreted_do(BufferedValueClosure* f) { + assert(is_interpreted_frame(), "Not an interpreted frame"); + Thread *thread = Thread::current(); + methodHandle m (thread, interpreter_frame_method()); + jint bci = interpreter_frame_bci(); + + assert(m->is_method(), "checking frame value"); + assert(!m->is_native() && bci >= 0 && bci < m->code_size(), + "invalid bci value"); + + InterpreterFrameClosure blk(this, m->max_locals(), m->max_stack(), NULL, f); + + // process locals & expression stack + InterpreterOopMap mask; + m->mask_for(bci, &mask); + mask.iterate_oop(&blk); +} void frame::oops_interpreted_arguments_do(Symbol* signature, bool has_receiver, OopClosure* f) { InterpretedArgumentOopFinder finder(signature, has_receiver, this, f); --- old/src/share/vm/runtime/frame.hpp 2017-06-20 16:28:42.546299429 -0400 +++ new/src/share/vm/runtime/frame.hpp 2017-06-20 16:28:41.874296097 -0400 @@ -325,6 +325,10 @@ ConstantPoolCache** interpreter_frame_cache_addr() const; oop* interpreter_frame_mirror_addr() const; + intptr_t* interpreter_frame_vt_alloc_ptr() const; + intptr_t** interpreter_frame_vt_alloc_ptr_addr() const; + void interpreter_frame_set_vt_alloc_ptr(intptr_t* ptr); + void interpreter_frame_set_mirror(oop mirror); public: @@ -393,6 +397,7 @@ // Oops-do's void oops_compiled_arguments_do(Symbol* signature, bool has_receiver, bool has_appendix, const RegisterMap* reg_map, OopClosure* f); void oops_interpreted_do(OopClosure* f, const RegisterMap* map, bool query_oop_map_cache = true); + void buffered_values_interpreted_do(BufferedValueClosure* f); private: void oops_interpreted_arguments_do(Symbol* signature, bool has_receiver, OopClosure* f); --- old/src/share/vm/runtime/globals.hpp 2017-06-20 16:28:44.438308811 -0400 +++ new/src/share/vm/runtime/globals.hpp 2017-06-20 16:28:43.798305638 -0400 @@ -4093,6 +4093,22 @@ \ develop(bool, FullGCALotWithValueTypes, false, \ "Force full GCs to stress test handling of value types") \ + \ + product(size_t, BigValueTypeThreshold, 4 * sizeof(long long), \ + "Max value type size for buffering") \ + \ + product(intx, ValueTypesBufferMaxMemory, 128, \ + "Max memory used for value types buffers (in pages)") \ + \ + product(bool, ValueTypesThreadLocalRecycling, true, \ + "Enable Thread local recycling of buffered values") \ + \ + product(bool, ReportVTBufferRecyclingTimes, false, \ + "Print duration of each VBuffer recycling") \ + \ + product(int, MinimumVTBufferChunkPerFrame, 2, \ + "Minimum number of VT buffer chunk allowed per frame") \ + /* --- old/src/share/vm/runtime/handles.cpp 2017-06-20 16:28:46.362318352 -0400 +++ new/src/share/vm/runtime/handles.cpp 2017-06-20 16:28:45.762315377 -0400 @@ -100,8 +100,10 @@ // This test can be moved up but for now check every oop. assert((*bottom)->is_oop(), "handle should point to oop"); - - f->do_oop(bottom++); + if (Universe::heap()->is_in_reserved_or_null(*bottom)) { + f->do_oop(bottom); + } + bottom++; } return handles_visited; } --- old/src/share/vm/runtime/mutexLocker.cpp 2017-06-20 16:28:48.426328587 -0400 +++ new/src/share/vm/runtime/mutexLocker.cpp 2017-06-20 16:28:47.754325255 -0400 @@ -209,7 +209,7 @@ #endif def(CodeCache_lock , PaddedMutex , special, true, Monitor::_safepoint_check_never); def(RawMonitor_lock , PaddedMutex , special, true, Monitor::_safepoint_check_never); - def(OopMapCacheAlloc_lock , PaddedMutex , leaf, true, Monitor::_safepoint_check_always); // used for oop_map_cache allocation. + def(OopMapCacheAlloc_lock , PaddedMutex , leaf, true, Monitor::_safepoint_check_never); // used for oop_map_cache allocation. def(Patching_lock , PaddedMutex , special, true, Monitor::_safepoint_check_never); // used for safepointing and code patching. def(Service_lock , PaddedMonitor, special, true, Monitor::_safepoint_check_never); // used for service thread operations --- old/src/share/vm/runtime/sharedRuntime.cpp 2017-06-20 16:28:50.482338782 -0400 +++ new/src/share/vm/runtime/sharedRuntime.cpp 2017-06-20 16:28:49.890335847 -0400 @@ -3465,7 +3465,8 @@ } #endif - if (Universe::heap()->is_in_reserved((void*)res)) { +// if (Universe::heap()->is_in_reserved((void*)res)) { + if (!Metaspace::contains((void*)res)) { // We're not returning with value type fields in registers (the // calling convention didn't allow it for this value klass) thread->set_vm_result((oopDesc*)res); --- old/src/share/vm/runtime/thread.cpp 2017-06-20 16:28:52.498348779 -0400 +++ new/src/share/vm/runtime/thread.cpp 2017-06-20 16:28:51.894345784 -0400 @@ -840,6 +840,16 @@ } } +void JavaThread::print_vt_buffer_stats_on(outputStream* st) const { + st->print_cr("%s:", this->name()); + st->print_cr("\tChunks in use : %d", vtchunk_in_use()); + st->print_cr("\tCached chunk : %d", local_free_chunk() == NULL ? 0 : 1); + st->print_cr("\tMax chunks : %d", vtchunk_max()); + st->print_cr("\tReturned chunks: %d", vtchunk_total_returned()); + st->print_cr("\tMemory buffered: %ld", vtchunk_total_memory_buffered()); + st->print_cr(""); +} + #ifdef ASSERT void Thread::print_owned_locks_on(outputStream* st) const { Monitor *cur = _owned_locks; @@ -1511,6 +1521,16 @@ _popframe_preserved_args_size = 0; _frames_to_pop_failed_realloc = 0; + // Buffered value types support + _vt_alloc_ptr = NULL; + _vt_alloc_limit = NULL; + _local_free_chunk = NULL; + // Buffered value types instrumentation support + _vtchunk_in_use = 0; + _vtchunk_max = 0; + _vtchunk_total_returned = 0; + _vtchunk_total_memory_buffered = 0; + pd_initialize(); } @@ -2821,7 +2841,10 @@ // Traverse instance variables at the end since the GC may be moving things // around using this function f->do_oop((oop*) &_threadObj); - f->do_oop((oop*) &_vm_result); + // if (Universe::heap()->is_in_reserved_or_null((void*)_vm_result)) { + if (!VTBufferChunk::check_buffered(&_vm_result)) { + f->do_oop((oop*) &_vm_result); + } f->do_oop((oop*) &_exception_oop); f->do_oop((oop*) &_pending_async_exception); @@ -4877,3 +4900,9 @@ VMThread* thread = VMThread::vm_thread(); if (thread != NULL) thread->verify(); } + +void Threads::print_vt_buffer_stats_on(outputStream* st) { + ALL_JAVA_THREADS(p) { + p->print_vt_buffer_stats_on(st); + } +} --- old/src/share/vm/runtime/thread.hpp 2017-06-20 16:28:54.302357725 -0400 +++ new/src/share/vm/runtime/thread.hpp 2017-06-20 16:28:53.698354729 -0400 @@ -27,6 +27,7 @@ #include "gc/shared/threadLocalAllocBuffer.hpp" #include "memory/allocation.hpp" +#include "memory/vtBuffer.hpp" #include "oops/oop.hpp" #include "prims/jni.h" #include "prims/jvmtiExport.hpp" @@ -994,6 +995,17 @@ // failed reallocations. int _frames_to_pop_failed_realloc; + // Buffered value types support + void* _vt_alloc_ptr; + void* _vt_alloc_limit; + VTBufferChunk* _local_free_chunk; + // Next 4 fields are used to monitor VT buffer memory consumption + // We may want to not support them in PRODUCT builds + jint _vtchunk_in_use; + jint _vtchunk_max; + jint _vtchunk_total_returned; + jlong _vtchunk_total_memory_buffered; + #ifndef PRODUCT int _jmp_ring_index; struct { @@ -1673,6 +1685,7 @@ void print_thread_state() const PRODUCT_RETURN; void print_on_error(outputStream* st, char* buf, int buflen) const; void print_name_on_error(outputStream* st, char* buf, int buflen) const; + void print_vt_buffer_stats_on(outputStream* st) const; void verify(); const char* get_thread_name() const; private: @@ -1874,6 +1887,35 @@ _stack_size_at_create = value; } + void* vt_alloc_ptr() const { return _vt_alloc_ptr; } + void set_vt_alloc_ptr(void* ptr) { _vt_alloc_ptr = ptr; } + void* vt_alloc_limit() const { return _vt_alloc_limit; } + void set_vt_alloc_limit(void* ptr) { _vt_alloc_limit = ptr; } + VTBufferChunk* local_free_chunk() const { return _local_free_chunk; } + void set_local_free_chunk(VTBufferChunk* chunk) { _local_free_chunk = chunk; } + VTBufferChunk* current_chunk() { + if (_vt_alloc_ptr == NULL) return NULL; + VTBufferChunk* chunk = VTBufferChunk::chunk(_vt_alloc_ptr); + assert(chunk->owner() == this, "Sanity check"); + return chunk; + // return _vt_alloc_ptr == NULL ? NULL : VTBufferChunk::chunk(_vt_alloc_ptr); + } + + void increment_vtchunk_in_use() { + _vtchunk_in_use++; + if (_vtchunk_in_use > _vtchunk_max) _vtchunk_max = _vtchunk_in_use; + } + void decrement_vtchunk_in_use() { _vtchunk_in_use--; } + jint vtchunk_in_use() const { return _vtchunk_in_use; } + jint vtchunk_max() const { return _vtchunk_max; } + void increment_vtchunk_returned() { _vtchunk_total_returned++; } + jint vtchunk_total_returned() const { return _vtchunk_total_returned; } + void increment_vtchunk_total_memory_buffered(jlong size) { _vtchunk_total_memory_buffered += size; } + jlong vtchunk_total_memory_buffered() const { return _vtchunk_total_memory_buffered; } + + static ByteSize vt_alloc_ptr_offset() { return byte_offset_of(JavaThread, _vt_alloc_ptr); } + + #if INCLUDE_ALL_GCS // SATB marking queue support SATBMarkQueue& satb_mark_queue() { return _satb_mark_queue; } @@ -2138,6 +2180,7 @@ static void print_on_error(Thread* this_thread, outputStream* st, Thread* current, char* buf, int buflen, bool* found_current); static void print_threads_compiling(outputStream* st, char* buf, int buflen); + static void print_vt_buffer_stats_on(outputStream* st); // Get Java threads that are waiting to enter a monitor. If doLock // is true, then Threads_lock is grabbed as needed. Otherwise, the --- old/src/share/vm/runtime/vframeArray.cpp 2017-06-20 16:28:56.462368435 -0400 +++ new/src/share/vm/runtime/vframeArray.cpp 2017-06-20 16:28:55.598364151 -0400 @@ -295,6 +295,8 @@ is_top_frame, is_bottom_frame); + iframe()->interpreter_frame_set_vt_alloc_ptr((intptr_t*)thread->vt_alloc_ptr()); + // Update the pc in the frame object and overwrite the temporary pc // we placed in the skeletal frame now that we finally know the // exact interpreter address we should use. --- old/src/share/vm/runtime/vm_operations.cpp 2017-06-20 16:28:58.322377659 -0400 +++ new/src/share/vm/runtime/vm_operations.cpp 2017-06-20 16:28:57.722374684 -0400 @@ -501,6 +501,10 @@ CompileBroker::print_compile_queues(_out); } +void VM_VTBufferStats::doit() { + Threads::print_vt_buffer_stats_on(_out); +} + #if INCLUDE_SERVICES void VM_PrintClassHierarchy::doit() { KlassHierarchy::print_class_hierarchy(_out, _print_interfaces, _print_subclasses, _classname); --- old/src/share/vm/runtime/vm_operations.hpp 2017-06-20 16:29:00.190386922 -0400 +++ new/src/share/vm/runtime/vm_operations.hpp 2017-06-20 16:28:59.590383946 -0400 @@ -106,6 +106,7 @@ template(MarkActiveNMethods) \ template(PrintCompileQueue) \ template(PrintClassHierarchy) \ + template(VTBufferStats) \ class VM_Operation: public CHeapObj { public: @@ -434,6 +435,16 @@ void doit(); }; +class VM_VTBufferStats: public VM_Operation { +private: + outputStream* _out; +public: + VM_VTBufferStats() { _out = tty; } + VM_VTBufferStats(outputStream* out) { _out = out; } + VMOp_Type type() const { return VMOp_VTBufferStats; } + void doit(); +}; + #if INCLUDE_SERVICES class VM_PrintClassHierarchy: public VM_Operation { private: --- old/src/share/vm/services/diagnosticCommand.cpp 2017-06-20 16:29:02.334397553 -0400 +++ new/src/share/vm/services/diagnosticCommand.cpp 2017-06-20 16:29:01.474393289 -0400 @@ -111,7 +111,7 @@ DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(jmx_agent_export_flags, true,false)); DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(jmx_agent_export_flags, true,false)); DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(jmx_agent_export_flags, true,false)); - + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); } #ifndef HAVE_EXTRA_DCMD @@ -1013,3 +1013,29 @@ int TouchedMethodsDCmd::num_arguments() { return 0; } + +VTBufferStatsDCmd::VTBufferStatsDCmd(outputStream* output, bool heap) : + DCmd(output, heap) { } + +void VTBufferStatsDCmd::execute(DCmdSource source, TRAPS) { + + VM_VTBufferStats op1(output()); + VMThread::execute(&op1); + + int in_pool; + int max_in_pool; + int total_allocated; + int total_deallocated; + { + MutexLockerEx ml(VTBuffer::lock(), Mutex::_no_safepoint_check_flag); + in_pool = VTBuffer::in_pool(); + max_in_pool = VTBuffer::max_in_pool(); + total_allocated = VTBuffer::total_allocated(); + total_deallocated = VTBuffer::total_deallocated(); + } + output()->print_cr("Global VTBuffer Pool statistics:"); + output()->print_cr("\tChunks in pool : %d", in_pool); + output()->print_cr("\tMax in pool : %d", max_in_pool); + output()->print_cr("\tTotal allocated : %d", total_allocated); + output()->print_cr("\tTotal deallocated: %d", total_deallocated); +} --- old/src/share/vm/services/diagnosticCommand.hpp 2017-06-20 16:29:04.642408998 -0400 +++ new/src/share/vm/services/diagnosticCommand.hpp 2017-06-20 16:29:03.590403782 -0400 @@ -724,4 +724,23 @@ virtual void execute(DCmdSource source, TRAPS); }; +class VTBufferStatsDCmd : public DCmd { +public: + VTBufferStatsDCmd(outputStream* output, bool heap); + static const char* name() { return "VTBuffer.stats"; } + static const char* description() { + return "[EXPERIMENTAL] Print statistics about Value Types buffering."; + } + static const char* impact() { + return "Medium: Depends on the number of threads."; + } + static const JavaPermission permission() { + JavaPermission p = {"java.lang.management.ManagementPermission", + "monitor", NULL}; + return p; + } + static int num_arguments() { return 0; } + virtual void execute(DCmdSource source, TRAPS); +}; + #endif // SHARE_VM_SERVICES_DIAGNOSTICCOMMAND_HPP --- old/src/share/vm/services/nmtCommon.cpp 2017-06-20 16:29:06.482418122 -0400 +++ new/src/share/vm/services/nmtCommon.cpp 2017-06-20 16:29:05.878415127 -0400 @@ -43,6 +43,7 @@ "Logging", "Arguments", "Module", + "Value Types", "Unknown" }; --- old/test/runtime/valhalla/valuetypes/ValueTypeGetField.java 2017-06-20 16:29:08.410427683 -0400 +++ new/test/runtime/valhalla/valuetypes/ValueTypeGetField.java 2017-06-20 16:29:07.714424231 -0400 @@ -12,29 +12,60 @@ */ public class ValueTypeGetField { - static Point staticPoint; - Point myPoint; + static Point staticPoint0; + static Point staticPoint1; + Point instancePoint0; + Point instancePoint1; + static { + staticPoint0 = Point.createPoint(358, 406); + staticPoint1 = Point.createPoint(101, 2653); + } + + ValueTypeGetField() { + instancePoint0 = Point.createPoint(1890, 1918); + instancePoint1 = Point.createPoint(91, 102); + } + public static void main(String[] args) { ValueTypeGetField valueTypeGetField = new ValueTypeGetField(); + System.gc(); // check that VTs survive GC valueTypeGetField.run(); } public void run() { - Point p = Point.createPoint(1, 2); - fieldTest(p); - System.gc(); // check that VTs survive GC - fieldTest(p); - System.gc(); // check that VTs survive GC + // testing initial configuration + checkPoint(staticPoint0, 358, 406); + checkPoint(staticPoint1, 101, 2653); + checkPoint(instancePoint0, 1890, 1918); + checkPoint(instancePoint1, 91, 102); + // swapping static fields + Point p = staticPoint1; + staticPoint1 = staticPoint0; + staticPoint0 = p; + System.gc(); + checkPoint(staticPoint0, 101, 2653); + checkPoint(staticPoint1, 358, 406); + //swapping instance fields + p = instancePoint1; + instancePoint1 = instancePoint0; + instancePoint0 = p; + System.gc(); + checkPoint(instancePoint0, 91, 102); + checkPoint(instancePoint1, 1890, 1918); + // instance to static + staticPoint0 = instancePoint0; + System.gc(); + checkPoint(staticPoint0, 91, 102); + // static to instance + instancePoint1 = staticPoint1; + System.gc(); + checkPoint(instancePoint1, 358, 406); } - static void fieldTest(Point p) { - staticPoint = p; - Asserts.assertEquals(staticPoint.x, 1, "invalid staticPoint x value"); - Asserts.assertEquals(staticPoint.y, 2, "invalid staticPoint y value"); - ValueTypeGetField valueTypeGetField = new ValueTypeGetField(); - valueTypeGetField.myPoint = Point.createPoint(5, 6); - Asserts.assertEquals(valueTypeGetField.myPoint.x, 5, "invalid myPoint x value"); - Asserts.assertEquals(valueTypeGetField.myPoint.y, 6, "invalid myPoint y value"); + static void checkPoint(Point p , int x, int y) { + Asserts.assertEquals(p.x, x, "invalid x value"); + Asserts.assertEquals(p.y, y, "invalid y value"); } + } --- /dev/null 2017-03-31 09:06:27.183526083 -0400 +++ new/src/share/vm/memory/vtBuffer.cpp 2017-06-20 16:29:09.666433911 -0400 @@ -0,0 +1,470 @@ +/* + * Copyright (c) 2016, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "gc/shared/gcLocker.hpp" +#include "memory/vtBuffer.hpp" +#include "oops/oop.inline.hpp" +#include "oops/valueKlass.hpp" +#include "runtime/frame.hpp" +#include "runtime/thread.hpp" +#include "utilities/globalDefinitions.hpp" +#include "utilities/ticks.hpp" +#include "utilities/ticks.inline.hpp" + +VTBufferChunk* VTBuffer::_free_list = NULL; +Mutex* VTBuffer::_pool_lock = new Mutex(Mutex::leaf, "VTBuffer::_pool_lock", true, Monitor::_safepoint_check_never); +int VTBuffer::_pool_counter = 0; +int VTBuffer::_max_pool_counter = 0; +int VTBuffer::_total_allocated = 0; +int VTBuffer::_total_deallocated = 0; + +oop VTBuffer::allocate_value(ValueKlass* k, TRAPS) { + assert(THREAD->is_Java_thread(), "Only JavaThreads have a buffer for value types"); + JavaThread* thread = (JavaThread*)THREAD; + if (thread->vt_alloc_ptr() == NULL) { + if (!allocate_vt_chunk(thread)) { + return NULL; // will trigger fall back strategy: allocation in Java heap + } + } + assert(thread->vt_alloc_ptr() != NULL, "should not be null if chunk allocation was successful"); + int size_in_bytes = k->size_helper() * wordSize; + if ((char*)thread->vt_alloc_ptr() + size_in_bytes >= thread->vt_alloc_limit()) { + if (size_in_bytes > (int)VTBufferChunk::max_alloc_size()) { + // Too big to be allocated in a buffer + return NULL; + } + if (!allocate_vt_chunk(thread)) { + return NULL; // will trigger fall back strategy: allocation in Java heap + } + } + assert((char*)thread->vt_alloc_ptr() + size_in_bytes < thread->vt_alloc_limit(),"otherwise the logic above is wrong"); + oop new_vt = (oop)thread->vt_alloc_ptr(); + int size_in_words = k->size_helper(); + thread->increment_vtchunk_total_memory_buffered(size_in_words * HeapWordSize); + int increment = align_object_size(size_in_words); + void* new_ptr = (char*)thread->vt_alloc_ptr() + increment * HeapWordSize; + new_ptr = MIN2(new_ptr, thread->vt_alloc_limit()); + assert(VTBufferChunk::chunk(new_ptr) == VTBufferChunk::chunk(thread->vt_alloc_ptr()), + "old and new alloc ptr must be in the same chunk"); + thread->set_vt_alloc_ptr(new_ptr); + // the value and its header must be initialized before being returned!!! + memset(((char*)(oopDesc*)new_vt), 0, size_in_bytes); + new_vt->set_klass(k); + new_vt->set_mark(markOop(k->java_mirror())); + return new_vt; +} + +bool VTBuffer::allocate_vt_chunk(JavaThread* thread) { + VTBufferChunk* new_chunk = NULL; + // Trying local cache; + if (thread->local_free_chunk() != NULL) { + new_chunk = thread->local_free_chunk(); + thread->set_local_free_chunk(NULL); + } else { + // Trying global pool + MutexLockerEx ml(_pool_lock, Mutex::_no_safepoint_check_flag); + if (_free_list != NULL) { + new_chunk = _free_list; + _free_list = new_chunk->next(); + if (_free_list != NULL) { + _free_list->set_prev(NULL); + } + new_chunk->set_next(NULL); + _pool_counter--; + } else { + // A new chunk has to be allocated + // Done with _pool_lock to maintain counters + if ((_total_allocated + 1) > ValueTypesBufferMaxMemory) { + // Maximum memory for value types buffer has been reached + // fallback to Java heap allocations + return false; + } + new_chunk = new VTBufferChunk(thread); + _total_allocated++; + } + } + if (new_chunk == NULL) return false; // allocation failed + VTBufferChunk* current = thread->current_chunk(); + assert(new_chunk->owner() == thread || new_chunk->owner()== NULL, "Sanity check"); + assert(new_chunk->index() == -1, "Sanity check"); + new_chunk->set_owner(thread); + if(current != NULL) { + new_chunk->set_prev(current); + new_chunk->set_index(current->index() + 1); + current->set_next(new_chunk); + } else { + new_chunk->set_index(0); + } + thread->increment_vtchunk_in_use(); + thread->set_vt_alloc_ptr(new_chunk->first_alloc()); + thread->set_vt_alloc_limit(new_chunk->alloc_limit()); + return true; // allocation was successful +} + +void VTBuffer::recycle_chunk(JavaThread* thread, VTBufferChunk* chunk) { + if (thread->local_free_chunk() == NULL) { + chunk->set_prev(NULL); + chunk->set_next(NULL); + chunk->set_index(-1); + thread->set_local_free_chunk(chunk); + } else { + return_vt_chunk(thread, chunk); + } + thread->decrement_vtchunk_in_use(); +} + +// This is the main way to recycle VTBuffer memory, it is called from +// remove_activation() when an interpreter frame is about to be removed +// from the stack. All memory used in the context of this frame is freed, +// and the vt_alloc_ptr is restored to the value it had when the frame +// was created (modulo a possible adjustment if a value is being returned) +void VTBuffer::recycle_vtbuffer(JavaThread* thread, frame current_frame) { + address current_ptr = (address)thread->vt_alloc_ptr(); + assert(current_ptr != NULL, "Should not reach here if NULL"); + VTBufferChunk* current_chunk = VTBufferChunk::chunk(current_ptr); + assert(current_chunk->owner() == thread, "Sanity check"); + address previous_ptr = (address)current_frame.interpreter_frame_vt_alloc_ptr(); + if (previous_ptr == NULL) { + // vt_alloc_ptr has not been initialized in this frame + // let's initialize it to the first_alloc() value of the first chunk + VTBufferChunk* first_chunk = current_chunk; + while (first_chunk->prev() != NULL) { + first_chunk = first_chunk->prev(); + } + previous_ptr = (address)first_chunk->first_alloc(); + } + assert(previous_ptr != NULL, "Should not reach here if NULL"); + VTBufferChunk* previous_chunk = VTBufferChunk::chunk(previous_ptr); + assert(previous_chunk->owner() == thread, "Sanity check"); + if (current_ptr == previous_ptr) return; + assert(current_chunk != previous_chunk || current_ptr >= previous_ptr, "Sanity check"); + VTBufferChunk* del = previous_chunk->next(); + previous_chunk->set_next(NULL); + thread->set_vt_alloc_ptr(previous_ptr); + thread->set_vt_alloc_limit(previous_chunk->alloc_limit()); + while (del != NULL) { + VTBufferChunk* temp = del->next(); + VTBuffer::recycle_chunk(thread, del); + del = temp; + } +} + +void VTBuffer::return_vt_chunk(JavaThread* thread, VTBufferChunk* chunk) { + chunk->set_prev(NULL); + chunk->set_owner(NULL); + chunk->set_index(-1); + MutexLockerEx ml(_pool_lock, Mutex::_no_safepoint_check_flag); + if (_pool_counter < _max_free_list) { + if (_free_list != NULL) { + chunk->set_next(_free_list); + _free_list->set_prev(chunk); + _free_list = chunk; + } else { + chunk->set_next(NULL); + _free_list = chunk; + } + _pool_counter++; + if (_pool_counter > _max_pool_counter) { + _max_pool_counter = _pool_counter; + } + } else { + delete chunk; + _total_deallocated++; + } + thread->increment_vtchunk_returned(); +} + +bool VTBuffer::value_belongs_to_frame(oop p, frame* f) { + // the code below assumes that frame f is the last interpreted frame + // on the execution stack + int p_chunk_idx = VTBufferChunk::chunk(p)->index(); + int frame_first_chunk_idx; + if (f->interpreter_frame_vt_alloc_ptr() != NULL) { + frame_first_chunk_idx = VTBufferChunk::chunk(f->interpreter_frame_vt_alloc_ptr())->index(); + } else { + frame_first_chunk_idx = 0; + } + if (p_chunk_idx == frame_first_chunk_idx) { + return (intptr_t*)p >= f->interpreter_frame_vt_alloc_ptr(); + } else { + return p_chunk_idx > frame_first_chunk_idx; + } + +} + +void VTBuffer::fix_frame_vt_alloc_ptr(frame f, VTBufferChunk* chunk) { + assert(f.is_interpreted_frame(), "recycling can only be triggered from interpreted frames"); + assert(chunk != NULL, "Should not be called if null"); + while (chunk->prev() != NULL) { + chunk = chunk->prev(); + } + f.interpreter_frame_set_vt_alloc_ptr((intptr_t*)chunk->first_alloc()); +} + +extern "C" { + static int compare_reloc_entries(const void* void_a, const void* void_b) { + struct VT_relocation_entry* entry_a = (struct VT_relocation_entry*)void_a; + struct VT_relocation_entry* entry_b = (struct VT_relocation_entry*)void_b; + if (entry_a->chunk_index == entry_b->chunk_index) { + if (entry_a->old_ptr < entry_b->old_ptr) { + return -1; + } else { + return 1; + } + } else { + if (entry_a->chunk_index < entry_b->chunk_index) { + return -1; + } else { + return 1; + } + } + } +} + +void dump_reloc_table(struct VT_relocation_entry* table, int nelem, bool print_new_ptr) { + ResourceMark rm; + for (int i = 0; i < nelem; i++) { + InstanceKlass* ik = InstanceKlass::cast(((oop)table[i].old_ptr)->klass()); + tty->print("%d:\t%p\t%d\t%s\t%x", i, table[i].old_ptr, table[i].chunk_index, + ik->name()->as_C_string(), ik->size_helper() * HeapWordSize); + if (print_new_ptr) { + tty->print_cr("\t%p\t%d\n", table[i].new_ptr, VTBufferChunk::chunk(table[i].new_ptr)->index()); + } else { + tty->print_cr(""); + } + } +} + +// Relocate value 'old' after value 'previous' +address VTBuffer::relocate_value(address old, address previous, int previous_size_in_words) { + InstanceKlass* ik_old = InstanceKlass::cast(((oop)old)->klass()); + assert(ik_old->is_value(), "Sanity check"); + VTBufferChunk* chunk = VTBufferChunk::chunk(previous); + address next_alloc = previous + align_object_size(ik_old->size_helper()); + if(next_alloc + ik_old->size_helper() * HeapWordSize < chunk->alloc_limit()) { + // relocation can be performed in the same chunk + return previous + align_object_size(previous_size_in_words) * HeapWordSize; + } else { + // relocation must be performed in the next chunk + VTBufferChunk* next_chunk = chunk->next(); + assert(next_chunk != NULL, "Because we are compacting, there should be enough in use chunks"); + return (address)next_chunk->first_alloc(); + } +} + +oop VTBuffer::relocate_return_value(JavaThread* thread, frame current_frame, oop obj) { + assert(!Universe::heap()->is_in_reserved(obj), "This method should never be called on Java heap allocated values"); + assert(obj->klass()->is_value(), "Sanity check"); + ValueKlass* vk = ValueKlass::cast(obj->klass()); + address current_ptr = (address)thread->vt_alloc_ptr(); + VTBufferChunk* current_chunk = VTBufferChunk::chunk(current_ptr); + address previous_ptr = (address)current_frame.interpreter_frame_vt_alloc_ptr(); + if (previous_ptr == NULL) { + fix_frame_vt_alloc_ptr(current_frame, current_chunk); + previous_ptr = (address)current_frame.interpreter_frame_vt_alloc_ptr(); + } + VTBufferChunk* previous_chunk = VTBufferChunk::chunk(previous_ptr); + address dest; + if ((address)obj != previous_ptr) { + if (previous_chunk == current_chunk + || (previous_ptr + vk->size_helper() * wordSize) < previous_chunk->alloc_limit()) { + dest = previous_ptr; + } else { + assert(previous_chunk->next() != NULL, "Should not happen"); + dest = (address)previous_chunk->next()->first_alloc(); + } + // Copying header + memcpy(dest, obj, vk->first_field_offset()); + // Copying value content + vk->value_store(((char*)(address)obj) + vk->first_field_offset(), + dest + vk->first_field_offset(), false, true); + } else { + dest = (address)obj; + } + address new_alloc_ptr = dest + vk->size_helper() * wordSize; + current_frame.interpreter_frame_set_vt_alloc_ptr((intptr_t*)new_alloc_ptr); + VTBufferChunk* last = VTBufferChunk::chunk(dest); + VTBufferChunk* del = last->next(); + thread->set_vt_alloc_ptr(new_alloc_ptr); + thread->set_vt_alloc_limit(last->alloc_limit()); + last->set_next(NULL); + while (del != NULL) { + VTBufferChunk* tmp = del->next(); + VTBuffer::recycle_chunk(thread, del); + del = tmp; + } + return (oop)dest; +} + +// This method is called to recycle VTBuffer memory when the VM has detected +// that too much memory is being consumed in the current frame context. This +// can only happen when the method contains at least one loop in which new +// values are created. +void VTBuffer::recycle_vt_in_frame(JavaThread* thread, frame* f) { + Ticks begin, end; + Ticks step1, step2, step3, step4, step5, step6, step7; + int returned_chunks = 0; + + if (ReportVTBufferRecyclingTimes) { + begin = Ticks::now(); + } + assert(f->is_interpreted_frame(), "only interpreted frames are using VT buffering so far"); + ResourceMark rm(thread); + + // 1 - allocate relocation table + Method* m = f->interpreter_frame_method(); + int max_entries = m->max_locals() + m->max_stack(); + VT_relocation_entry* reloc_table = NEW_RESOURCE_ARRAY_IN_THREAD(thread, struct VT_relocation_entry, max_entries); + int n_entries = 0; + if (ReportVTBufferRecyclingTimes) { + step1 = Ticks::now(); + } + + { + // No GC should occur during the phases 2->5 + // either because the mark word (usually containing the pointer + // to the Java mirror) is used for marking, or because the values are being relocated + NoSafepointVerifier nsv; + + // 2 - marking phase + populate relocation table + BufferedValuesMarking marking_closure = BufferedValuesMarking(f, reloc_table, max_entries, &n_entries); + f->buffered_values_interpreted_do(&marking_closure); + if (ReportVTBufferRecyclingTimes) { + step2 = Ticks::now(); + } + + if (n_entries > 0) { + // 3 - sort relocation table entries and compute compaction + qsort(reloc_table, n_entries, sizeof(struct VT_relocation_entry), compare_reloc_entries); + if (f->interpreter_frame_vt_alloc_ptr() == NULL) { + VTBufferChunk* chunk = VTBufferChunk::chunk(reloc_table[0].old_ptr); + while (chunk->prev() != NULL) chunk = chunk->prev(); + //f->interpreter_frame_set_vt_alloc_ptr((intptr_t*)chunk->first_alloc()); + reloc_table[0].new_ptr = (address)chunk->first_alloc(); + } else { + reloc_table[0].new_ptr = (address)f->interpreter_frame_vt_alloc_ptr(); + } + ((oop)reloc_table[0].old_ptr)->set_mark((markOop)reloc_table[0].new_ptr); + for (int i = 1; i < n_entries; i++) { + reloc_table[i].new_ptr = relocate_value(reloc_table[i].old_ptr, reloc_table[i-1].new_ptr, + InstanceKlass::cast(((oop)reloc_table[i-1].old_ptr)->klass())->size_helper()); + ((oop)reloc_table[i].old_ptr)->set_mark((markOop)reloc_table[i].new_ptr); + } + if (ReportVTBufferRecyclingTimes) { + step3 = Ticks::now(); + } + + // 4 - update pointers + BufferedValuesPointersUpdate update_closure = BufferedValuesPointersUpdate(f); + f->buffered_values_interpreted_do(&update_closure); + if (ReportVTBufferRecyclingTimes) { + step4 = Ticks::now(); + } + + // 5 - relocate values + for (int i = 0; i < n_entries; i++) { + if (reloc_table[i].old_ptr != reloc_table[i].new_ptr) { + InstanceKlass* ik_old = InstanceKlass::cast(((oop)reloc_table[i].old_ptr)->klass()); + // instead of memcpy, a value_store() might be required here + memcpy(reloc_table[i].new_ptr, reloc_table[i].old_ptr, ik_old->size_helper() * HeapWordSize); + } + // Resetting the mark word + ((oop)reloc_table[i].new_ptr)->set_mark(markOop(((oop)reloc_table[i].new_ptr)->klass()->java_mirror())); + } + if (ReportVTBufferRecyclingTimes) { + step5 = Ticks::now(); + } + + // 6 - update thread allocation pointer + oop last_oop = (oop)reloc_table[n_entries - 1].new_ptr; + InstanceKlass* ik = InstanceKlass::cast(last_oop->klass()); + thread->set_vt_alloc_ptr((address)last_oop + ik->size_helper() * HeapWordSize); + thread->set_vt_alloc_limit(VTBufferChunk::chunk(thread->vt_alloc_ptr())->alloc_limit()); + if (ReportVTBufferRecyclingTimes) { + step6 = Ticks::now(); + } + + // 7 - free/return unused chunks + VTBufferChunk* chunk = VTBufferChunk::chunk(reloc_table[n_entries - 1].new_ptr); + VTBufferChunk* temp = chunk; + chunk = chunk->next(); + temp->set_next(NULL); + while (chunk != NULL) { + returned_chunks++; + temp = chunk->next(); + VTBuffer::recycle_chunk(thread, chunk); + chunk = temp; + } + if (ReportVTBufferRecyclingTimes) { + step7 = Ticks::now(); + } + } else { + f->interpreter_frame_set_vt_alloc_ptr((intptr_t*)thread->vt_alloc_ptr()); + } + } + + // 8 - free relocation table + FREE_RESOURCE_ARRAY(struct VT_relocation_entry, reloc_table, max_entries); + if (ReportVTBufferRecyclingTimes) { + end = Ticks::now(); + ResourceMark rm(thread); + tty->print_cr("VTBufferRecyling: %s : %s.%s %s : %ldus", + thread->name(), + f->interpreter_frame_method()->klass_name()->as_C_string(), + f->interpreter_frame_method()->name()->as_C_string(), + f->interpreter_frame_method()->signature()->as_C_string(), + (end.value() - begin.value()) / 1000); + tty->print("Step1 : %6ldns ", step1.value() - begin.value()); + tty->print("Step2 : %6ldns ", step2.value() - step1.value()); + tty->print("Step3 : %6ldns ", step3.value() - step2.value()); + tty->print("Step4 : %6ldns ", step4.value() - step3.value()); + tty->print("Step5 : %6ldns ", step5.value() - step4.value()); + tty->print("Step6 : %6ldns ", step6.value() - step5.value()); + tty->print("Step7 : %6ldns ", step7.value() - step6.value()); + tty->print("Step8 : %6ldns ", end.value() - step7.value()); + tty->print_cr("Returned chunks: %d", returned_chunks); + } +} + +void BufferedValuesMarking::do_buffered_value(oop* p) { + assert(!Universe::heap()->is_in_reserved_or_null(*p), "Sanity check"); + if (VTBuffer::value_belongs_to_frame(*p, _frame)) { + if (!(*p)->mark()->is_marked()) { + assert(*_index < _size, "index outside of relocation table range"); + _reloc_table[*_index].old_ptr = (address)*p; + _reloc_table[*_index].chunk_index = VTBufferChunk::chunk(*p)->index(); + *_index = (*_index) + 1; + (*p)->set_mark((*p)->mark()->set_marked()); + } + } +} + +void BufferedValuesPointersUpdate::do_buffered_value(oop* p) { + assert(!Universe::heap()->is_in_reserved_or_null(*p), "Sanity check"); + // might be coded more efficiently just by checking mark word is not NULL + if (VTBuffer::value_belongs_to_frame(*p, _frame)) { + *p = (oop)(*p)->mark(); + } +} --- /dev/null 2017-03-31 09:06:27.183526083 -0400 +++ new/src/share/vm/memory/vtBuffer.hpp 2017-06-20 16:29:11.506443035 -0400 @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2016, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_MEMORY_VTBUFFER_HPP +#define SHARE_VM_MEMORY_VTBUFFER_HPP + +#include "memory/allocation.hpp" +#include "runtime/globals.hpp" +#include "runtime/os.hpp" +#include "utilities/globalDefinitions.hpp" + +class VTBufferChunk : public CMmapObj { + friend class VMStructs; + + static const int MAGIC_NUMBER = 3141592; + +private: + int _magic; + int _index; + VTBufferChunk* _prev; + VTBufferChunk* _next; + JavaThread* _owner; + public: + + VTBufferChunk(JavaThread* thread) { + _magic = MAGIC_NUMBER; + _index = -1; + _prev = NULL; + _next = NULL; + _owner = thread; + } + + int index() { return _index; } + void set_index(int index) { _index = index; } + VTBufferChunk* prev() { return _prev; } + void set_prev(VTBufferChunk* prev) { _prev = prev; } + VTBufferChunk* next() { return _next; } + void set_next(VTBufferChunk* next) { _next = next; } + JavaThread* owner() { return _owner; } + void set_owner(JavaThread* thread) { + assert(thread == NULL || _owner == NULL || _owner == thread, "Sanity check"); + _owner = thread; + } + + bool is_valid() { + return _magic == MAGIC_NUMBER && _owner != NULL && _index != -1; + } + + void* first_alloc() { return (void*)((char*)this + align_object_size(sizeof (VTBufferChunk))); } + void* alloc_limit() { return (void*)((char*)this + chunk_size() - 1); } + + static int chunk_size() { + return os::vm_page_size(); + } + + static uintptr_t chunk_mask() { + return ~(chunk_size() - 1); + } + + static ByteSize index_offset() { return byte_offset_of(VTBufferChunk, _index); } + + static size_t max_alloc_size() { + return chunk_size() - align_object_size(sizeof (VTBufferChunk)); + } + + static VTBufferChunk* chunk(void* address) { + VTBufferChunk* c = (VTBufferChunk*)((intptr_t)address & chunk_mask()); + assert(c->is_valid(), "Sanity check"); + return c; + } + + static bool check_buffered(void* address) { + assert(address != NULL, "Sanity check"); + VTBufferChunk* c = (VTBufferChunk*)((intptr_t)address & chunk_mask()); + return c->is_valid(); + } + + bool contains(void* address) { + return address > (char*)chunk(address) && address < ((char*)chunk(address) + chunk_size()); + } +}; + +class VTBuffer : AllStatic { + friend class VMStructs; +private: + static VTBufferChunk* _free_list; + static Mutex* _pool_lock; + static int _pool_counter; + static int _max_pool_counter; + static int _total_allocated; + static int _total_deallocated; + static const int _max_free_list = 64; // Should be tunable + +public: + static Mutex* lock() { return _pool_lock; } + static oop allocate_value(ValueKlass* k, TRAPS); + static bool allocate_vt_chunk(JavaThread* thread); + static void recycle_chunk(JavaThread* thread, VTBufferChunk* chunk); + static void return_vt_chunk(JavaThread* thread, VTBufferChunk* chunk); + + static int in_pool() { return _pool_counter; } + static int max_in_pool() { return _max_pool_counter; } + static int total_allocated() { return _total_allocated; } + static int total_deallocated() { return _total_deallocated; } + + static bool is_in_vt_buffer(const void* p) { + intptr_t chunk_mask = (~(VTBufferChunk::chunk_size() - 1)); + VTBufferChunk* c = (VTBufferChunk*)((intptr_t)p & chunk_mask); + return c->is_valid(); + } + + static bool value_belongs_to_frame(oop p, frame *f); + static void recycle_vt_in_frame(JavaThread* thread, frame* f); + static void recycle_vtbuffer(JavaThread *thread, frame f); + static address relocate_value(address old, address previous, int previous_size_in_words); + static oop relocate_return_value(JavaThread* thread, frame fr, oop old); + + static void fix_frame_vt_alloc_ptr(frame fr, VTBufferChunk* chunk); + +}; + +struct VT_relocation_entry { + int chunk_index; + address old_ptr; + address new_ptr; +}; + + +class BufferedValuesMarking : public BufferedValueClosure { + frame* _frame; + struct VT_relocation_entry* _reloc_table; + int _size; + int* _index; +public: + BufferedValuesMarking(frame* frame, struct VT_relocation_entry* reloc_table, int size, int* index) { + _frame = frame; + _reloc_table = reloc_table; + _size = size; + _index = index; + } + virtual void do_buffered_value(oop* p); +}; + +class BufferedValuesPointersUpdate : public BufferedValueClosure { + frame* _frame; +public: + BufferedValuesPointersUpdate(frame* frame) { + _frame = frame; + } + virtual void do_buffered_value(oop* p); +}; + +#endif /* SHARE_VM_MEMORY_VTBUFFER_HPP */ --- /dev/null 2017-03-31 09:06:27.183526083 -0400 +++ new/test/runtime/valhalla/valuetypes/UninitializedValueFieldsTest.java 2017-06-20 16:29:13.534453091 -0400 @@ -0,0 +1,37 @@ +package runtime.valhalla.valuetypes; + +import jdk.test.lib.Asserts; + +/* + * @test + * @summary Uninitialized value fields test + * @library /test/lib + * @compile -XDenableValueTypes Point.java UninitializedValueFieldsTest.java + * @run main/othervm -noverify -Xint runtime.valhalla.valuetypes.UninitializedValueFieldsTest + */ + +/* + * disabled run main/othervm -noverify -Xcomp runtime.valhalla.valuetypes.UninitializedValueFieldsTest + */ +public class UninitializedValueFieldsTest { + + static Point staticPoint; + Point instancePoint; + + UninitializedValueFieldsTest() { } + + public static void main(String[] args) { + checkUninitializedPoint(UninitializedValueFieldsTest.staticPoint, 0, 0); + UninitializedValueFieldsTest.staticPoint = Point.createPoint(456, 678); + checkUninitializedPoint(UninitializedValueFieldsTest.staticPoint, 456, 678); + UninitializedValueFieldsTest test = new UninitializedValueFieldsTest(); + checkUninitializedPoint(test.instancePoint, 0, 0); + test.instancePoint = Point.createPoint(123, 345); + checkUninitializedPoint(test.instancePoint, 123, 345); + } + + static void checkUninitializedPoint(Point p, int x, int y) { + Asserts.assertEquals(p.x, x, "invalid x value"); + Asserts.assertEquals(p.y, y, "invalid y value"); + } +} --- /dev/null 2017-03-31 09:06:27.183526083 -0400 +++ new/test/runtime/valhalla/valuetypes/VTBufferTest.java 2017-06-20 16:29:15.438462533 -0400 @@ -0,0 +1,340 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test VTBufferTest + * @summary Value Type interpreter value buffering test + * @library /test/lib + * @build ValueTypeGenerator + * @run main/othervm -noverify -Xint -XX:+ReportVTBufferRecyclingTimes VTBufferTest generate-and-run + * @run main/othervm -noverify -Xint -XX:ValueTypesBufferMaxMemory=0 VTBufferTest generate-and-run + * @run main/othervm -noverify -Xint -XX:BigValueTypeThreshold=196 VTBufferTest generate-and-run + */ + +/* This test generates its source code. + * To reproduce a run (for instance to investigate a failure), look at + * the test output and search for a line starting with "Seed=". The value + * at the end of the line is the seed used to generate the test. + * It possible to re-generate the same test with the following commande + * line: + * $ java VTBufferTest generate-and-run -seed + * where is the seed value from the test output. + * The test source code is generated in the current directory with + * names Value[0-9][0-9].java and Loop.java. + * Once generated, the test can be run again without going through + * the generation phase with the following commande line: + * $ java VTBufferTest run + */ + +import jvm.internal.value.*; + +import javax.management.*; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.ToolProvider; +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.management.*; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; + +public class VTBufferTest implements Runnable { + static Random random; + static boolean generate; + static boolean execute; + static long seed = 0; + long startTime; + boolean verbose = false; + String[] valueNames; + File[] valueSources; + File[] loopSource; + + + static void usage() { + System.out.println("Usage:\n"); + System.out.println("\tVTBufferTest [options]...\n"); + System.out.println("\nWhere is one of the following: generate | generate-and-run | run\n"); + System.out.println("Where [options] can be: -seed \n"); + } + + public static void main(String[] args) { + if (args.length < 1) { + usage(); + System.exit(-1); + } + + if (args[0].compareTo("generate") == 0) { + generate = true; + execute = false; + } else if (args[0].compareTo("generate-and-run") == 0) { + generate = true; + execute = true; + } else if (args[0].compareTo("run") == 0) { + generate = false; + execute = true; + } else { + System.out.println("Unknown command\n"); + usage(); + System.exit(-1); + } + + if (args.length > 1) { + int cursor = 1; + if (args[cursor].compareTo("-seed") == 0) { + if (args.length < 3) { + usage(); + System.exit(-1); + } + seed = Long.valueOf(args[cursor+1]); + cursor++; + } else { + System.out.println("Unknown option\n"); + usage(); + System.exit(-1); + } + } + + if (generate) { + if (seed == 0) { + seed = System.nanoTime(); + } + random = new Random(seed); + System.out.println("Seed= " + seed); + } + + VTBufferTest test = new VTBufferTest(true); + test.run(); + } + + public VTBufferTest(boolean verbose) { + this.verbose = verbose; + } + + static private String[] generateValueNames() { + int nvalues = random.nextInt(16) + 4; + String[] names = new String[nvalues]; + for (int i = 0; i < nvalues; i++) { + names[i] = new String("Value"+i); + } + return names; + } + + static private File writeSource(String filename, String source) { + try{ + PrintWriter writer = new PrintWriter(filename, "UTF-8"); + writer.println(source); + writer.close(); + } catch (IOException e) { + throw new RuntimeException("Writing source file failed"); + } + return new File(filename); + } + + static private File[] generateValueSources(String[] valueNames) { + File[] sources = new File[valueNames.length]; + for (int i = 0; i < valueNames.length; i++) { + int nfields = random.nextInt(6) + 1; + String s = ValueTypeGenerator.generateValueTypeNoObjectRef(random, valueNames[i], nfields); + String filename = valueNames[i]+".java"; + sources[i] = writeSource(filename, s); + } + return sources; + } + + static private File[] generateLoopSource(String[] names) { + StringBuilder sb = new StringBuilder(); + sb.append("// Seed = ").append(seed).append("\n"); + // class declaration + sb.append("public final class Loop {\n"); + sb.append("\n"); + + sb.append("\tstatic {\n"); + int i = 0; + for (String name : names) { + sb.append("\t\t").append(names[i]).append(" lv").append(i).append(" = "); + sb.append(names[i]).append(".make").append(names[i]).append("();\n"); + sb.append("\t\tlv").append(i).append(".printLayout(System.out);\n"); + i++; + } + sb.append("\t}\n\n"); + + // loop method + sb.append("\tstatic public void loop(int iterations) { \n"); + i = 0; + for (String name : names) { + sb.append("\t\t").append(names[i]).append(" lv").append(i).append(" = "); + sb.append(names[i]).append(".make").append(names[i]).append("();\n"); + i++; + } + sb.append("\t\tfor (int i = 0; i < iterations; i++) {\n"); + i = 0; + for (String name : names) { + sb.append("\t\t\tif (!").append(names[i]).append(".verify(lv").append(i).append("))\n"); + sb.append("\t\t\t\tthrow new RuntimeException(\"Error in ").append(names[i]).append("\");\n"); + i++; + } + i = 0; + for (String name : names) { + if (i != 0) { + sb.append("\t\t\tif (i % ").append(i).append(" != 0) {\n"); + sb.append("\t\t\t\tlv").append(i).append(" = "); + sb.append(names[i]).append(".make").append(names[i]).append("();\n"); + sb.append("\t\t\t}\n"); + } + i++; + } + sb.append("\t\t}\n"); + sb.append("\t}\n"); + sb.append("}\n"); + + String source = sb.toString(); + + File[] files = new File[1]; + files[0] = writeSource("Loop.java", source); + return files; + } + + public void run() { + if (generate) { + valueNames = generateValueNames(); + valueSources = generateValueSources(valueNames); + loopSource = generateLoopSource(valueNames); + + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); + List optionList = new ArrayList(); + optionList.addAll(Arrays.asList("-classpath",".","-XDenableValueTypes")); + + Iterable compilationUnits1 = + fileManager.getJavaFileObjectsFromFiles(Arrays.asList(valueSources)); + compiler.getTask(null, fileManager, null, optionList, null, compilationUnits1).call(); + + Iterable compilationUnits2 = + fileManager.getJavaFileObjectsFromFiles(Arrays.asList(loopSource)); + compiler.getTask(null, fileManager, null, optionList, null, compilationUnits2).call(); + } + + if (execute) { + startTime = ManagementFactory.getRuntimeMXBean().getUptime(); + + ClassLoader cl = createClassLoader(); + try { + iterate(100, 5000, cl); + } catch(InvocationTargetException e) { + e.getCause().printStackTrace(); + System.exit(-1); + } + + if (verbose) { + printVTBufferStats(); + + System.out.println("\nGC Statistics:"); + List gcs = ManagementFactory.getGarbageCollectorMXBeans(); + for (GarbageCollectorMXBean gc : gcs) { + System.out.println("Name=" + gc.getName()); + System.out.println("GC counts=" + gc.getCollectionCount()); + System.out.println("GC time=" + gc.getCollectionTime() + "ms"); + } + + System.out.println("\nHeap Statistics"); + List memPools = ManagementFactory.getMemoryPoolMXBeans(); + for (MemoryPoolMXBean memPool : memPools) { + if (memPool.getType() == MemoryType.HEAP) { + System.out.println("\nName: " + memPool.getName()); + System.out.println("Usage: " + memPool.getUsage()); + System.out.println("Collection Usage: " + memPool.getCollectionUsage()); + System.out.println("Peak Usage: " + memPool.getPeakUsage()); + } + } + } + } + } + + + ClassLoader createClassLoader() { + try{ + File file = new File("."); + URL url = file.toURI().toURL(); + URL[] urls = new URL[]{url}; + ClassLoader cl = new URLClassLoader(urls); + return cl; + } catch(Exception ex){ + ex.printStackTrace(); + } + return null; + } + + public void iterate(int n, int m, ClassLoader cl) throws InvocationTargetException { + for (int i = 0; i < n; i++) { + Class loop = null; + try { + loop = Class.forName("Loop", true, cl); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + Method method = null; + try { + method = loop.getMethod("loop", int.class); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + return; + } + try { + method.invoke(null, m); + } catch (IllegalAccessException e) { + e.printStackTrace(); + return; + } catch (InvocationTargetException e) { + throw e; + } + } + } + + public void printVTBufferStats() { + MBeanServerConnection mbs = ManagementFactory.getPlatformMBeanServer(); + String MBeanName = "com.sun.management:type=DiagnosticCommand"; + ObjectName beanName; + try { + beanName = new ObjectName(MBeanName); + } catch (MalformedObjectNameException e) { + String message = "MBean not found: " + MBeanName; + throw new RuntimeException(message, e); + } + String result = null; + try { + result = (String)mbs.invoke(beanName,"vtbufferStats",new Object[0],new String[0]); + } catch(Exception e) { + e.printStackTrace(); + } + System.out.println(result); + } + +} --- /dev/null 2017-03-31 09:06:27.183526083 -0400 +++ new/test/runtime/valhalla/valuetypes/ValueTypeGenerator.java 2017-06-20 16:29:17.122470883 -0400 @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.lang.reflect.Type; +import java.util.Random; + +public class ValueTypeGenerator { + + static class FieldDescriptor { + + final public String name; + final public Type type; + + public FieldDescriptor(String name, Type type) { + this.name = name; + this.type = type; + } + } + + static Type[] typeArray; + static String[] defaultArray; + static int NB_TYPES = 9; + + static { + typeArray = new Type[NB_TYPES]; + typeArray[0] = byte.class; + typeArray[1] = short.class; + typeArray[2] = int.class; + typeArray[3] = long.class; + typeArray[4] = char.class; + typeArray[5] = float.class; + typeArray[6] = double.class; + typeArray[7] = boolean.class; + typeArray[8] = Object.class; + + } + + static String defaultValue(Type t) { + switch(t.getTypeName()) { + case "byte": return "(byte)123"; + case "short": return "(short)32056"; + case "int": return "483647"; + case "long": return "922337203685477L"; + case "char": return "(char)65456"; + case "float": return "2.71828f"; + case "double": return "3.14159d"; + case "boolean": return "true"; + case "java.lang.Object": return "null"; + default: + throw new RuntimeException(); + } + } + static private String generateValueTypeInternal(Random random, String name, int nfields, int typeLimit) { + // generate the fields + FieldDescriptor[] fieldDescArray = new FieldDescriptor[nfields]; + for (int i = 0; i < nfields; i++) { + int idx = random.nextInt(typeLimit); + String fieldName = typeArray[idx].getTypeName()+"Field"+i; + fieldDescArray[i] = new FieldDescriptor(fieldName, typeArray[idx]); + } + + String source = generateSource(name, fieldDescArray); + return source; + } + + static public String generateValueType(Random random, String name, int nfields) { + return generateValueTypeInternal(random, name, nfields, NB_TYPES); + } + + static public String generateValueTypeNoObjectRef(Random random, String name, int nfields) { + return generateValueTypeInternal(random, name, nfields, NB_TYPES - 1); + } + + static String fieldsAsArgs(FieldDescriptor[] fields) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < fields.length; i++) { + sb.append(fields[i].type).append(" ").append(fields[i].name); + if (i != fields.length - 1) { + sb.append(", "); + } + } + return sb.toString(); + } + + static String generateSource(String name, FieldDescriptor[] fields) { + StringBuilder sb = new StringBuilder(); + + // imports + sb.append("import java.io.PrintStream;\n\n"); + + // class declaration + sb.append("public __ByValue final class ").append(name).append(" {\n"); + + // field declarations + for (FieldDescriptor f : fields) { + sb.append("\tfinal public ").append(f.type).append(" "); + sb.append(f.name).append(";\n"); + } + sb.append("\n"); + + // private constructor + sb.append("\tprivate ").append(name).append("() {\n"); + for (int i = 0 ; i < fields.length; i++) { + sb.append("\t\tthis.").append(fields[i].name).append(" = ").append(defaultValue(fields[i].type)).append(";\n"); + } + sb.append("\t}\n"); + sb.append("\n"); + + // factory + sb.append("\t__ValueFactory static public ").append(name).append(" ").append("make").append(name).append("("); + sb.append(fieldsAsArgs(fields)); + sb.append(") {\n"); + sb.append("\t\t").append(name).append(" v = ").append("__MakeDefault ").append(name).append("();\n"); + for (int i = 0 ; i < fields.length; i++) { + sb.append("\t\tv.").append(fields[i].name).append(" = ").append(fields[i].name).append(";\n"); + } + sb.append("\t\treturn v;\n"); + sb.append("\t};\n"); + sb.append("\n"); + + // default factory + sb.append("\t__ValueFactory static public ").append(name).append(" ").append("make").append(name).append("() {\n"); + sb.append("\t\t").append(name).append(" v = ").append("__MakeDefault ").append(name).append("();\n"); + for (int i = 0 ; i < fields.length; i++) { + sb.append("\t\tv.").append(fields[i].name).append(" = ").append(defaultValue(fields[i].type)).append(";\n"); + } + sb.append("\t\treturn v;\n"); + sb.append("\t}\n"); + sb.append("\n"); + + // verify method + sb.append("\tstatic public boolean verify(").append(name).append(" value) {\n"); + for (FieldDescriptor f : fields) { + sb.append("\t\tif (value.").append(f.name).append(" != ").append(defaultValue(f.type)).append(") return false;\n"); + } + sb.append("\t\treturn true;\n"); + sb.append("\t}\n"); + + // printLayout method + sb.append("\tstatic public void printLayout(PrintStream out) {\n"); + sb.append("\t\tout.println(\"").append(name).append(" fields: "); + for (int i = 0; i < fields.length; i++) { + sb.append(fields[i].type); + if (i != fields.length - 1) { + sb.append(", "); + } + } + sb.append("\");\n"); + sb.append("\t}\n"); + + sb.append("}\n"); + + return sb.toString(); + } +}