--- old/src/share/vm/ci/ciEnv.cpp 2016-11-16 09:06:09.002498493 +0100 +++ new/src/share/vm/ci/ciEnv.cpp 2016-11-16 09:06:08.930498491 +0100 @@ -388,7 +388,7 @@ // Now we need to check the SystemDictionary Symbol* sym = name->get_symbol(); - if (sym->byte_at(0) == 'L' && + if ((sym->byte_at(0) == 'L' || sym->byte_at(0) == 'Q') && sym->byte_at(sym->utf8_length()-1) == ';') { // This is a name from a signature. Strip off the trimmings. // Call recursive to keep scope of strippedsym. --- old/src/share/vm/ci/ciField.cpp 2016-11-16 09:06:09.270498500 +0100 +++ new/src/share/vm/ci/ciField.cpp 2016-11-16 09:06:09.198498498 +0100 @@ -198,6 +198,26 @@ "bootstrap classes must not create & cache unshared fields"); } +// Special copy constructor used to flatten value type fields by +// copying the fields of the value type to a new holder klass. +ciField::ciField(ciField* field, ciInstanceKlass* holder, int offset, bool is_final) { + assert(field->holder()->is_valuetype(), "should only be used for value type field flattening"); + // Set the is_final flag + jint final = is_final ? JVM_ACC_FINAL : ~JVM_ACC_FINAL; + AccessFlags flags(field->flags().as_int() & final); + _flags = ciFlags(flags); + _holder = holder; + _offset = offset; + // Copy remaining fields + _name = field->_name; + _signature = field->_signature; + _type = field->_type; + _is_constant = field->_is_constant; + _known_to_link_with_put = field->_known_to_link_with_put; + _known_to_link_with_get = field->_known_to_link_with_get; + _constant_value = field->_constant_value; +} + static bool trust_final_non_static_fields(ciInstanceKlass* holder) { if (holder == NULL) return false; --- old/src/share/vm/ci/ciField.hpp 2016-11-16 09:06:09.526498507 +0100 +++ new/src/share/vm/ci/ciField.hpp 2016-11-16 09:06:09.454498505 +0100 @@ -57,6 +57,7 @@ ciField(ciInstanceKlass* klass, int index); ciField(fieldDescriptor* fd); + ciField(ciField* field, ciInstanceKlass* holder, int offset, bool is_final); // shared constructor code void initialize_from(fieldDescriptor* fd); --- old/src/share/vm/ci/ciInstanceKlass.cpp 2016-11-16 09:06:09.786498515 +0100 +++ new/src/share/vm/ci/ciInstanceKlass.cpp 2016-11-16 09:06:09.714498513 +0100 @@ -397,6 +397,29 @@ } // ------------------------------------------------------------------ +// ciInstanceKlass::get_field_type_by_offset +ciType* ciInstanceKlass::get_field_type_by_offset(int field_offset) { + ASSERT_IN_VM; + fieldDescriptor fd; + InstanceKlass* klass = get_instanceKlass(); + // Important: We cannot get the field type via get_field_by_offset() because if the field + // is another value type, the offset would refer to the first field of that value type due + // to flattening. Instead, do a SystemDictionary lookup for the type of the declared field. + bool found = klass->find_field_from_offset(field_offset, false, &fd); + assert(found, "field not found"); + BasicType field_type = fd.field_type(); + if (is_java_primitive(field_type)) { + // Primitive type + return ciType::make(field_type); + } else { + // Do a SystemDictionary lookup for the type + ciEnv* env = CURRENT_ENV; + ciSymbol* signature = env->get_symbol(fd.signature()); + return env->get_klass_by_name_impl(this, constantPoolHandle(), signature, false); + } +} + +// ------------------------------------------------------------------ // ciInstanceKlass::get_field_by_name ciField* ciInstanceKlass::get_field_by_name(ciSymbol* name, ciSymbol* signature, bool is_static) { VM_ENTRY_MARK; @@ -500,8 +523,25 @@ for (JavaFieldStream fs(k); !fs.done(); fs.next()) { if (fs.access_flags().is_static()) continue; fieldDescriptor& fd = fs.field_descriptor(); - ciField* field = new (arena) ciField(&fd); - fields->append(field); + if (fd.field_type() == T_VALUETYPE) { + // Value type fields are embedded + int field_offset = fd.offset(); + // Get ValueKlass and adjust number of fields + ciValueKlass* vk = get_field_type_by_offset(field_offset)->as_value_klass(); + flen += vk->get_field_count() - 1; + // Iterate over fields of flattened value type and copy them to 'this' + for (int i = 0; i < vk->nof_nonstatic_fields(); ++i) { + ciField* flattened_field = vk->nonstatic_field_at(i); + // Adjust offset to account for missing oop header + int offset = field_offset + (flattened_field->offset() - vk->get_first_field_offset()); + bool is_final = is_valuetype(); // flattened fields are final if holder is a value type + ciField* field = new (arena) ciField(flattened_field, this, offset, is_final); + fields->append(field); + } + } else { + ciField* field = new (arena) ciField(&fd); + fields->append(field); + } } assert(fields->length() == flen, "sanity"); return fields; --- old/src/share/vm/ci/ciInstanceKlass.hpp 2016-11-16 09:06:10.054498522 +0100 +++ new/src/share/vm/ci/ciInstanceKlass.hpp 2016-11-16 09:06:09.982498520 +0100 @@ -185,6 +185,7 @@ ciInstanceKlass* get_canonical_holder(int offset); ciField* get_field_by_offset(int field_offset, bool is_static); + ciType* get_field_type_by_offset(int field_offset); ciField* get_field_by_name(ciSymbol* name, ciSymbol* signature, bool is_static); // total number of nonstatic fields (including inherited): @@ -212,7 +213,7 @@ bool has_finalizable_subclass(); bool contains_field_offset(int offset) { - return instanceOopDesc::contains_field_offset(offset, nonstatic_field_size()); + return instanceOopDesc::contains_field_offset(offset, nonstatic_field_size(), is_valuetype()); } // Get the instance of java.lang.Class corresponding to --- old/src/share/vm/ci/ciValueKlass.cpp 2016-11-16 09:06:10.326498529 +0100 +++ new/src/share/vm/ci/ciValueKlass.cpp 2016-11-16 09:06:10.258498528 +0100 @@ -26,20 +26,49 @@ #include "ci/ciValueKlass.hpp" #include "oops/valueKlass.hpp" -int ciValueKlass::get_field_index_by_offset(int offset) { - // TODO do we need handles here? +// Number of value type factory parameters +int ciValueKlass::param_count() const { valueKlassHandle vklass_h(ValueKlass::cast(get_Klass())); methodHandle factory_h(vklass_h->factory_method()); - // Search field with field_offset and return factory parameter index - for (int index = 0; index < field_count(); ++index) { - if (get_field_offset_by_index(index) == offset) { - return index; + return factory_h->constMethod()->valuefactory_parameter_mapping_length(); +} + +// Size of value type factory parameters in words +int ciValueKlass::param_size() { + int size = 0; + for (int i = 0; i < param_count(); ++i) { + size += get_field_type_by_index(i)->size(); + } + return size; +} + +// Returns the value factory parameter index of the field with the given offset. +// If the field at 'offset' belongs to a flattened value type field, return the +// index of the ValueType parameter corresponding to this flattened value type. +int ciValueKlass::get_field_index_by_offset(int offset) { + assert(contains_field_offset(offset), "invalid field offset"); + int best_offset = 0; + int best_index = -1; + // Search the field with the given offset + for (int i = 0; i < param_count(); ++i) { + int field_offset = get_field_offset_by_index(i); + if (field_offset == offset) { + // Exact match + return i; + } else if (field_offset < offset && field_offset > best_offset) { + // No exact match. Save the index of the field with the closest offset that + // is smaller than the given field offset. This index corresponds to the + // flattened value type field that holds the field we are looking for. + best_offset = field_offset; + best_index = i; } } - ShouldNotReachHere(); - return -1; + assert(best_index >= 0, "field not found"); + assert(best_offset == offset || get_field_type_by_index(best_index)->is_valuetype(), "offset should match for non-VTs"); + return best_index; } +// Returns the field offset of the value factory parameter with the given index int ciValueKlass::get_field_offset_by_index(int index) const { // Compute the field index from the factory parameter index valueKlassHandle vklass_h(ValueKlass::cast(get_Klass())); @@ -49,12 +78,15 @@ return vklass_h->field_offset(field_index); } -BasicType ciValueKlass::get_field_type_by_index(int index) const { - // Compute the field index from the factory parameter index +// Returns the field type of the value factory parameter with the given index +ciType* ciValueKlass::get_field_type_by_index(int index) { + int offset = get_field_offset_by_index(index); + VM_ENTRY_MARK; + return get_field_type_by_offset(offset); +} + +// Offset of the first field in the value type +int ciValueKlass::get_first_field_offset() const { valueKlassHandle vklass_h(ValueKlass::cast(get_Klass())); - methodHandle factory_h(vklass_h->factory_method()); - int field_index = factory_h->constMethod()->valuefactory_parameter_mapping_start()[index].data.field_index; - // Get the basic type of the field - Symbol* signature = vklass_h->field_signature(field_index); - return vmSymbols::signature_type(signature); + return vklass_h()->first_field_offset(); } --- old/src/share/vm/ci/ciValueKlass.hpp 2016-11-16 09:06:10.590498537 +0100 +++ new/src/share/vm/ci/ciValueKlass.hpp 2016-11-16 09:06:10.518498535 +0100 @@ -32,7 +32,7 @@ // ciValueKlass // -// TODO +// Specialized ciInstanceKlass for value types. class ciValueKlass : public ciInstanceKlass { CI_PACKAGE_ACCESS @@ -42,12 +42,18 @@ }; public: - bool is_valuetype() const { return true; } - int field_size() { return nonstatic_field_size(); } - int field_count() { return nof_nonstatic_fields(); } + bool is_valuetype() const { return true; } + + // Value type factory parameters + int param_count() const; + int param_size(); + + // Value type fields ('index' refers to the value factory parameter index) + int get_field_count() { return nof_nonstatic_fields(); } int get_field_index_by_offset(int offset); int get_field_offset_by_index(int index) const; - BasicType get_field_type_by_index(int index) const; + ciType* get_field_type_by_index(int index); + int get_first_field_offset() const; }; #endif // SHARE_VM_CI_CIVALUEKLASS_HPP --- old/src/share/vm/jvmci/jvmciCompilerToVM.cpp 2016-11-16 09:06:10.846498544 +0100 +++ new/src/share/vm/jvmci/jvmciCompilerToVM.cpp 2016-11-16 09:06:10.770498542 +0100 @@ -1120,7 +1120,7 @@ // native wrapper do not have a scope if (scope != NULL && scope->objects() != NULL) { bool realloc_failures = Deoptimization::realloc_objects(thread, fst.current(), scope->objects(), THREAD); - Deoptimization::reassign_fields(fst.current(), fst.register_map(), scope->objects(), realloc_failures, false); + Deoptimization::reassign_fields(fst.current(), fst.register_map(), scope->objects(), realloc_failures, false, THREAD); GrowableArray* local_values = scope->locals(); typeArrayHandle array = oopFactory::new_boolArray(local_values->length(), thread); @@ -1286,7 +1286,7 @@ } bool realloc_failures = Deoptimization::realloc_objects(thread, fstAfterDeopt.current(), objects, THREAD); - Deoptimization::reassign_fields(fstAfterDeopt.current(), fstAfterDeopt.register_map(), objects, realloc_failures, false); + Deoptimization::reassign_fields(fstAfterDeopt.current(), fstAfterDeopt.register_map(), objects, realloc_failures, false, THREAD); for (int frame_index = 0; frame_index < virtualFrames->length(); frame_index++) { compiledVFrame* cvf = virtualFrames->at(frame_index); --- old/src/share/vm/oops/instanceKlass.hpp 2016-11-16 09:06:11.138498552 +0100 +++ new/src/share/vm/oops/instanceKlass.hpp 2016-11-16 09:06:11.062498550 +0100 @@ -487,7 +487,7 @@ // find a non-static or static field given its offset within the class. bool contains_field_offset(int offset) { - return instanceOopDesc::contains_field_offset(offset, nonstatic_field_size()); + return instanceOopDesc::contains_field_offset(offset, nonstatic_field_size(), is_value()); } bool find_local_field_from_offset(int offset, bool is_static, fieldDescriptor* fd) const; --- old/src/share/vm/oops/instanceOop.hpp 2016-11-16 09:06:11.394498559 +0100 +++ new/src/share/vm/oops/instanceOop.hpp 2016-11-16 09:06:11.330498557 +0100 @@ -44,8 +44,12 @@ sizeof(instanceOopDesc); } - static bool contains_field_offset(int offset, int nonstatic_field_size) { + static bool contains_field_offset(int offset, int nonstatic_field_size, bool is_value) { int base_in_bytes = base_offset_in_bytes(); + if (is_value) { + // The first field of value types is aligned on a long boundary + base_in_bytes = align_size_up(base_in_bytes, BytesPerLong); + } return (offset >= base_in_bytes && (offset-base_in_bytes) < nonstatic_field_size * heapOopSize); } --- old/src/share/vm/oops/valueKlass.cpp 2016-11-16 09:06:11.638498566 +0100 +++ new/src/share/vm/oops/valueKlass.cpp 2016-11-16 09:06:11.574498564 +0100 @@ -42,13 +42,18 @@ } int ValueKlass::first_field_offset() { - // Really inefficient, should be cached somewhere +#ifdef ASSERT instanceKlassHandle k(this); int first_offset = INT_MAX; for (JavaFieldStream fs(k()); !fs.done(); fs.next()) { if (fs.offset() < first_offset) first_offset= fs.offset(); } - return first_offset; +#endif + int base_offset = instanceOopDesc::base_offset_in_bytes(); + // The first field of value types is aligned on a long boundary + base_offset = align_size_up(base_offset, BytesPerLong); + assert(base_offset = first_offset, "inconsistent offsets"); + return base_offset; } bool ValueKlass::is_atomic() { --- old/src/share/vm/opto/compile.cpp 2016-11-16 09:06:11.926498574 +0100 +++ new/src/share/vm/opto/compile.cpp 2016-11-16 09:06:11.854498572 +0100 @@ -1757,8 +1757,9 @@ // Check for final fields. const TypeInstPtr* tinst = flat->isa_instptr(); + const TypeValueTypePtr* vtptr = flat->isa_valuetypeptr(); + ciField* field = NULL; if (tinst && tinst->offset() >= instanceOopDesc::base_offset_in_bytes()) { - ciField* field; if (tinst->const_oop() != NULL && tinst->klass() == ciEnv::current()->Class_klass() && tinst->offset() >= (tinst->klass()->as_instance_klass()->size_helper() * wordSize)) { @@ -1769,14 +1770,18 @@ ciInstanceKlass *k = tinst->klass()->as_instance_klass(); field = k->get_field_by_offset(tinst->offset(), false); } - assert(field == NULL || - original_field == NULL || - (field->holder() == original_field->holder() && - field->offset() == original_field->offset() && - field->is_static() == original_field->is_static()), "wrong field?"); - // Set field() and is_rewritable() attributes. - if (field != NULL) alias_type(idx)->set_field(field); + } else if (vtptr) { + // Value type field + ciValueKlass* vk = vtptr->klass()->as_value_klass(); + field = vk->get_field_by_offset(vtptr->offset(), false); } + assert(field == NULL || + original_field == NULL || + (field->holder() == original_field->holder() && + field->offset() == original_field->offset() && + field->is_static() == original_field->is_static()), "wrong field?"); + // Set field() and is_rewritable() attributes. + if (field != NULL) alias_type(idx)->set_field(field); } // Fill the cache for next time. --- old/src/share/vm/opto/graphKit.cpp 2016-11-16 09:06:12.242498582 +0100 +++ new/src/share/vm/opto/graphKit.cpp 2016-11-16 09:06:12.170498580 +0100 @@ -1080,7 +1080,7 @@ case Bytecodes::_vnew: { // vnew pops the values from the stack ciValueKlass* vk = method()->holder()->as_value_klass(); - inputs = vk->field_size(); + inputs = vk->param_size(); depth = rsize - inputs; break; } --- old/src/share/vm/opto/macro.cpp 2016-11-16 09:06:12.562498591 +0100 +++ new/src/share/vm/opto/macro.cpp 2016-11-16 09:06:12.494498589 +0100 @@ -851,6 +851,7 @@ offset = field->offset(); elem_type = field->type(); basic_elem_type = field->layout_type(); + assert(basic_elem_type != T_VALUETYPE, "value type fields are flattened"); } else { offset = array_base + j * (intptr_t)element_size; } --- old/src/share/vm/opto/parse1.cpp 2016-11-16 09:06:12.870498600 +0100 +++ new/src/share/vm/opto/parse1.cpp 2016-11-16 09:06:12.806498598 +0100 @@ -1745,32 +1745,17 @@ ValueTypeNode* vtm = m->as_ValueType(); // Current value type ValueTypeNode* vtn = n->as_ValueType(); // Incoming value type if (TraceOptoParse) { - tty->print_cr("Merging value types"); #ifdef ASSERT + tty->print_cr("\nMerging value types"); tty->print_cr("Current:"); - vtm->dump(1); + vtm->dump(2); tty->print_cr("Incoming:"); - vtn->dump(1); + vtn->dump(2); + tty->cr(); #endif } - // Merge oop inputs - phi = vtm->get_oop()->as_Phi(); - phi->set_req(pnum, vtn->get_oop()); - if (pnum == PhiNode::Input) { - // Last merge - vtm->set_oop(_gvn.transform_no_reclaim(phi)); - record_for_igvn(phi); - } - // Merge field values - for (uint index = 0; index < vtm->field_count(); ++index) { - phi = vtm->get_field_value(index)->as_Phi(); - phi->set_req(pnum, vtn->get_field_value(index)); - if (pnum == PhiNode::Input) { - // Last merge - vtm->set_field_value(index, _gvn.transform_no_reclaim(phi)); - record_for_igvn(phi); - } - } + // Do the merge + map()->set_req(j, vtm->merge_with(this, vtn, pnum)); } else if (phi != NULL) { assert(n != top() || r->in(pnum) == top(), "live value must not be garbage"); assert(phi->region() == r, ""); @@ -1968,8 +1953,16 @@ } ValueTypeNode* vt = o->isa_ValueType(); - if (vt != NULL && vt->get_oop()->is_Phi() && vt->get_oop()->as_Phi()->region() == region) { - // ValueTypeNode already has Phi inputs + if (vt != NULL) { + // Value types are merged by merging their field values + if (vt->get_oop()->is_Phi() && vt->get_oop()->as_Phi()->region() == region) { + // ValueTypeNode already has Phi inputs + return NULL; + } + // Create a cloned ValueTypeNode with phi inputs that + // represents the merged value type and update the map. + vt = vt->clone_with_phis(gvn(), region); + map->set_req(idx, vt); return NULL; } @@ -2005,31 +1998,6 @@ return NULL; } - // Value types are merged by merging their field values - if (vt != NULL) { - // Create new ValueTypeNode that represents the merged value type - vt = vt->clone()->as_ValueType(); - - // Create a PhiNode for merging the oop - const TypeValueTypePtr* vtptr = TypeValueTypePtr::make(t->is_valuetype()); - PhiNode* oop = PhiNode::make(region, vt->get_oop(), vtptr); - gvn().set_type(oop, vtptr); - vt->set_oop(oop); - - // Create a PhiNode for merging each field value - for (uint i = 0; i < vt->field_count(); ++i) { - const Type* field_type = Type::get_const_basic_type(vt->get_field_type(i)); - PhiNode* phi = PhiNode::make(region, vt->get_field_value(i), field_type); - gvn().set_type(phi, field_type); - vt->set_field_value(i, phi); - } - - // Update map to use cloned value type - gvn().set_type(vt, t); - map->set_req(idx, vt); - return NULL; - } - PhiNode* phi = PhiNode::make(region, o, t); gvn().set_type(phi, t); if (C->do_escape_analysis()) record_for_igvn(phi); --- old/src/share/vm/opto/parse3.cpp 2016-11-16 09:06:13.170498608 +0100 +++ new/src/share/vm/opto/parse3.cpp 2016-11-16 09:06:13.098498606 +0100 @@ -180,7 +180,7 @@ bool must_assert_null = false; - if( bt == T_OBJECT ) { + if (bt == T_OBJECT || bt == T_VALUETYPE) { if (!field->type()->is_loaded()) { type = TypeInstPtr::BOTTOM; must_assert_null = true; @@ -200,6 +200,14 @@ if (support_IRIW_for_not_multiple_copy_atomic_cpu && field->is_volatile()) { insert_mem_bar(Op_MemBarVolatile); // StoreLoad barrier } + + if (type->isa_valuetypeptr()) { + // Load value type from flattened field + Node* vt = ValueTypeNode::make(_gvn, field_klass->as_value_klass(), map()->memory(), field->holder(), obj, offset); + push_node(bt, vt); + return; + } + // Build the load. // MemNode::MemOrd mo = is_vol ? MemNode::acquire : MemNode::unordered; @@ -282,6 +290,9 @@ field_type = TypeOopPtr::make_from_klass(field->type()->as_klass()); } store = store_oop_to_object(control(), obj, adr, adr_type, val, field_type, bt, mo); + } else if (bt == T_VALUETYPE) { + // Store value type to flattened field + val->as_ValueType()->store_to_field(this, field->holder(), obj, offset); } else { bool needs_atomic_access = is_vol || AlwaysAtomicAccesses; store = store_to_memory(control(), adr, val, bt, adr_type, mo, needs_atomic_access); --- old/src/share/vm/opto/parseHelper.cpp 2016-11-16 09:06:13.430498615 +0100 +++ new/src/share/vm/opto/parseHelper.cpp 2016-11-16 09:06:13.358498613 +0100 @@ -329,9 +329,9 @@ // Pop values from stack (last argument is first) and // connect them to the ValueTypeNode in reverse order. - for (int i = vk->field_count() - 1; i >= 0 ; --i) { - BasicType bt = vk->get_field_type_by_index(i); - Node* value = type2size[bt] == 1 ? pop() : pop_pair(); + for (int i = vk->param_count() - 1; i >= 0 ; --i) { + ciType* field_type = vt->get_field_type(i); + Node* value = field_type->size() == 1 ? pop() : pop_pair(); vt->set_field_value(i, value); } push(_gvn.transform(vt)); --- old/src/share/vm/opto/type.cpp 2016-11-16 09:06:13.678498622 +0100 +++ new/src/share/vm/opto/type.cpp 2016-11-16 09:06:13.610498620 +0100 @@ -2210,10 +2210,10 @@ //------------------------------dump2------------------------------------------ #ifndef PRODUCT void TypeValueType::dump2(Dict &d, uint depth, outputStream* st) const { - st->print("valuetype[%d]:{", _vk->field_count()); - st->print("%s", type2name(_vk->get_field_type_by_index(0))); - for (int i = 1; i < _vk->field_count(); ++i) { - st->print(", %s", type2name(_vk->get_field_type_by_index(i))); + st->print("valuetype[%d]:{", _vk->param_count()); + st->print("%s", _vk->get_field_type_by_index(0)->name()); + for (int i = 1; i < _vk->param_count(); ++i) { + st->print(", %s", _vk->get_field_type_by_index(i)->name()); } st->print("}"); } --- old/src/share/vm/opto/valuetypenode.cpp 2016-11-16 09:06:13.974498630 +0100 +++ new/src/share/vm/opto/valuetypenode.cpp 2016-11-16 09:06:13.910498628 +0100 @@ -37,20 +37,68 @@ Node* ValueTypeNode::make(PhaseGVN& gvn, Node* mem, Node* oop) { // Create and initialize a ValueTypeNode by loading all field - // values from memory and also save the oop to the heap-allocated version. + // values from a heap-allocated version and also save the oop. const TypeValueTypePtr* vtptr = gvn.type(oop)->is_valuetypeptr(); ValueTypeNode* vt = new ValueTypeNode(vtptr->value_type(), oop); - for (uint index = 0; index < vt->field_count(); ++index) { - int offset = vt->get_field_offset(index); - const TypePtr* adr_type = vtptr->add_offset(offset); - const Type* field_type = Type::get_const_basic_type(vt->get_field_type(index)); - Node* adr = gvn.transform(new AddPNode(oop, oop, gvn.longcon(offset))); - Node* ld = LoadNode::make(gvn, NULL, mem, adr, adr_type, field_type, field_type->basic_type(), MemNode::unordered); - vt->set_field_value(index, gvn.transform(ld)); - } + vt->load_values(gvn, mem, vt->get_value_klass(), oop); + return gvn.transform(vt); +} + +Node* ValueTypeNode::make(PhaseGVN& gvn, ciValueKlass* klass, Node* mem, ciInstanceKlass* holder, Node* obj, int field_offset) { + // Create and initialize a ValueTypeNode by loading all field + // values from a flattened value type field at 'field_offset' in 'obj'. + ValueTypeNode* vt = make(gvn, klass)->as_ValueType(); + // The value type is flattened into the object without an oop header. Subtract the + // offset of the first field to account for the missing header when loading the values. + int base_offset = field_offset - klass->get_first_field_offset(); + vt->load_values(gvn, mem, holder, obj, base_offset); return gvn.transform(vt); } +void ValueTypeNode::load_values(PhaseGVN& gvn, Node* mem, ciInstanceKlass* holder, Node* base, int base_offset) { + // Initialize the value type by loading its field values from + // memory and adding the values as input edges to the node. + for (uint i = 0; i < field_count(); ++i) { + int offset = base_offset + get_field_offset(i); + Node* adr = gvn.transform(new AddPNode(base, base, gvn.longcon(offset))); + ciField* field = holder->get_field_by_offset(offset, false); + const TypePtr* adr_type = gvn.C->alias_type(field)->adr_type(); + Node* value = NULL; + ciType* field_type = get_field_type(i); + if (field_type->is_valuetype()) { + // Recursively load the flattened value type field + value = ValueTypeNode::make(gvn, field_type->as_value_klass(), mem, holder, base, offset); + } else { + value = LoadNode::make(gvn, NULL, mem, adr, adr_type, Type::get_const_type(field_type), field_type->basic_type(), MemNode::unordered); + } + set_field_value(i, gvn.transform(value)); + } +} + +void ValueTypeNode::store_to_field(GraphKit* kit, ciInstanceKlass* holder, Node* obj, int field_offset) const { + // The value type is embedded into the object without an oop header. Subtract the + // offset of the first field to account for the missing header when storing the values. + int base_offset = field_offset - get_value_klass()->get_first_field_offset(); + store_values(kit, holder, obj, base_offset); +} + +void ValueTypeNode::store_values(GraphKit* kit, ciInstanceKlass* holder, Node* base, int base_offset) const { + // Write field values to memory + for (uint i = 0; i < field_count(); ++i) { + int offset = base_offset + get_field_offset(i); + Node* adr = kit->basic_plus_adr(base, base, offset); + ciField* field = holder->get_field_by_offset(offset, false); + const TypePtr* adr_type = kit->C->alias_type(field)->adr_type(); + Node* value = get_field_value(i); + if (value->is_ValueType()) { + // Recursively store the flattened value type field + value->isa_ValueType()->store_to_field(kit, holder, base, offset); + } else { + kit->store_to_memory(kit->control(), adr, value, get_field_type(i)->basic_type(), adr_type, MemNode::unordered); + } + } +} + Node* ValueTypeNode::store_to_memory(GraphKit* kit) { Node* in_oop = get_oop(); Node* null_ctl = kit->top(); @@ -86,12 +134,7 @@ Node* klass_node = kit->makecon(TypeKlassPtr::make(get_value_klass())); Node* alloc_oop = kit->new_instance(klass_node); // Write field values to memory - for (uint index = 0; index < field_count(); ++index) { - int offset = get_field_offset(index); - const TypePtr* adr_type = vtptr_type->add_offset(offset); - Node* adr = kit->basic_plus_adr(alloc_oop, alloc_oop, offset); - kit->store_to_memory(kit->control(), adr, get_field_value(index), get_field_type(index), adr_type, MemNode::unordered); - } + store_values(kit, get_value_klass(), alloc_oop); region->init_req(2, kit->control()); oop ->init_req(2, alloc_oop); io ->init_req(2, kit->i_o()); @@ -106,6 +149,7 @@ kit->record_for_igvn(io); kit->record_for_igvn(mem); + // Use cloned ValueTypeNode to propagate oop from now on Node* res_oop = kit->gvn().transform(oop); ValueTypeNode* vt = clone()->as_ValueType(); vt->set_oop(res_oop); @@ -113,14 +157,91 @@ return res_oop; } +// Clones the values type to handle control flow merges involving multiple value types. +// The input edges are replaced by PhiNodes to represent the merged values. +ValueTypeNode* ValueTypeNode::clone_with_phis(PhaseGVN& gvn, Node* region) { + ValueTypeNode* vt = clone()->as_ValueType(); + + // Create a PhiNode for merging the oop values + const TypeValueTypePtr* vtptr = TypeValueTypePtr::make(vt->bottom_type()->isa_valuetype()); + PhiNode* oop = PhiNode::make(region, vt->get_oop(), vtptr); + gvn.set_type(oop, vtptr); + vt->set_oop(oop); + + // Create a PhiNode each for merging the field values + for (uint i = 0; i < vt->field_count(); ++i) { + ciType* type = vt->get_field_type(i); + Node* value = vt->get_field_value(i); + if (type->is_valuetype()) { + // Handle flattened value type fields recursively + value = value->as_ValueType()->clone_with_phis(gvn, region); + } else { + const Type* phi_type = Type::get_const_type(type); + value = PhiNode::make(region, value, phi_type); + gvn.set_type(value, phi_type); + } + vt->set_field_value(i, value); + } + gvn.set_type(vt, vt->bottom_type()); + return vt; +} + +// Merges 'this' with 'other' by updating the input PhiNodes added by 'clone_with_phis' +Node* ValueTypeNode::merge_with(GraphKit* kit, const ValueTypeNode* other, int pnum) { + // Merge oop inputs + PhiNode* phi = get_oop()->as_Phi(); + phi->set_req(pnum, other->get_oop()); + if (pnum == PhiNode::Input) { + // Last merge + set_oop(kit->gvn().transform_no_reclaim(phi)); + kit->record_for_igvn(phi); + } + // Merge field values + for (uint i = 0; i < field_count(); ++i) { + Node* val1 = get_field_value(i); + Node* val2 = other->get_field_value(i); + if (val1->isa_ValueType()) { + val1->as_ValueType()->merge_with(kit, val2->as_ValueType(), pnum); + } else { + assert(!val2->is_ValueType(), "inconsistent merge values"); + val1->set_req(pnum, val2); + } + if (pnum == PhiNode::Input) { + // Last merge + set_field_value(i, kit->gvn().transform_no_reclaim(val1)); + kit->record_for_igvn(val1); + } + } + if (pnum == PhiNode::Input) { + // Last merge for this value type. + kit->record_for_igvn(this); + return kit->gvn().transform_no_reclaim(this); + } + return this; +} + Node* ValueTypeNode::get_field_value(uint index) const { assert(index < field_count(), "index out of bounds"); return in(Values + index); } -Node* ValueTypeNode::get_field_value_by_offset(int field_offset) const { - int index = get_value_klass()->get_field_index_by_offset(field_offset); - return get_field_value(index); +// Get the value of the field at the given offset. +// If 'recursive' is true, flattened value type fields will be resolved recursively. +Node* ValueTypeNode::get_field_value_by_offset(int offset, bool recursive) const { + // If the field at 'offset' belongs to a flattened value type field, 'index' refers to the + // corresponding ValueTypeNode input and 'sub_offset' is the offset in flattened value type. + int index = get_value_klass()->get_field_index_by_offset(offset); + int sub_offset = offset - get_field_offset(index); + Node* value = get_field_value(index); + if (recursive && value->is_ValueType()) { + // Flattened value type field + ValueTypeNode* vt = value->as_ValueType(); + sub_offset += vt->get_value_klass()->get_first_field_offset(); // Add header size + return vt->get_field_value_by_offset(sub_offset); + } + assert(!(recursive && value->is_ValueType()), "should not be a value type"); + assert(sub_offset == 0, "offset mismatch"); + return value; } void ValueTypeNode::set_field_value(uint index, Node* value) { @@ -133,14 +254,15 @@ return get_value_klass()->get_field_offset_by_index(index); } -BasicType ValueTypeNode::get_field_type(uint index) const { +ciType* ValueTypeNode::get_field_type(uint index) const { assert(index < field_count(), "index out of bounds"); return get_value_klass()->get_field_type_by_index(index); } void ValueTypeNode::make_scalar_in_safepoints(Compile* C) { const TypeValueTypePtr* res_type = TypeValueTypePtr::make(bottom_type()->isa_valuetype(), TypePtr::NotNull); - uint nfields = field_count(); + ciValueKlass* vk = get_value_klass(); + uint nfields = vk->get_field_count(); for (DUIterator_Fast imax, i = fast_outs(imax); i < imax; i++) { Node* u = fast_out(i); if (u->is_SafePoint() && (!u->is_Call() || u->as_Call()->has_debug_use(this))) { @@ -151,9 +273,11 @@ int start = jvms->debug_start(); int end = jvms->debug_end(); if (oop_type->meet(TypePtr::NULL_PTR) != oop_type) { + // Replace safepoint edge by oop int nb = sfpt->replace_edges_in_range(this, in_oop, start, end); --i; imax -= nb; } else { + // Replace safepoint edge by SafePointScalarObjectNode and add field values assert(jvms != NULL, "missing JVMS"); uint first_ind = (sfpt->req() - jvms->scloff()); SafePointScalarObjectNode* sobj = new SafePointScalarObjectNode(res_type, @@ -162,20 +286,12 @@ #endif first_ind, nfields); sobj->init_req(0, C->root()); - // fields must be added to the safepoint in order of increasing offset - int min = 0; - for (uint j = 0; j < nfields; j++) { - int off = INT_MAX; - uint next = 0; - for (uint k = 0; k < nfields; k++) { - int offset = get_field_offset(k); - if (offset > min && offset < off) { - off = offset; - next = k; - } - } - min = get_field_offset(next); - sfpt->add_req(in(Values + next)); + // Iterate over the value type fields in order of increasing + // offset and add the field values to the safepoint. + for (uint j = 0; j < nfields; ++j) { + int offset = vk->nonstatic_field_at(j)->offset(); + Node* value = get_field_value_by_offset(offset, true /* include flattened value type fields */); + sfpt->add_req(value); } jvms->set_endoff(sfpt->req()); int nb = sfpt->replace_edges_in_range(this, sobj, start, end); @@ -194,9 +310,6 @@ void ValueTypeNode::dump_spec(outputStream* st) const { TypeNode::dump_spec(st); - if (!get_oop()->is_top()) { - st->print(" #oop"); - } } #endif --- old/src/share/vm/opto/valuetypenode.hpp 2016-11-16 09:06:14.278498639 +0100 +++ new/src/share/vm/opto/valuetypenode.hpp 2016-11-16 09:06:14.210498637 +0100 @@ -30,15 +30,22 @@ class GraphKit; -// TODO add comment +//------------------------------ValueTypeNode------------------------------------- +// Node representing a value type in C2 IR class ValueTypeNode : public TypeNode { private: ValueTypeNode(const TypeValueType* t, Node* oop) - : TypeNode(t, Values + t->value_klass()->field_count()) { + : TypeNode(t, Values + t->value_klass()->param_count()) { init_class_id(Class_ValueType); init_req(Oop, oop); } + + // Get the klass defining the field layout of the value type ciValueKlass* get_value_klass() const { return type()->is_valuetype()->value_klass(); } + // Initialize the value type by loading its field values from memory + void load_values(PhaseGVN& gvn, Node* mem, ciInstanceKlass* holder, Node* base, int base_offset = 0); + // Store the field values to memory + void store_values(GraphKit* kit, ciInstanceKlass* holder, Node* base, int base_offset = 0) const; enum { Control, // Control input Oop, // Oop of TypeValueTypePtr @@ -48,23 +55,31 @@ public: // Create a new ValueTypeNode with uninitialized values static Node* make(PhaseGVN& gvn, ciValueKlass* klass); - // Create a new ValueTypeNode and load its values from memory + // Create a new ValueTypeNode and load its values from an oop static Node* make(PhaseGVN& gvn, Node* mem, Node* oop); + // Create a new ValueTypeNode and load its values from a flattened value type field + static Node* make(PhaseGVN& gvn, ciValueKlass* klass, Node* mem, ciInstanceKlass* holder, Node* obj, int field_offset); + + // Support for control flow merges + ValueTypeNode* clone_with_phis(PhaseGVN& gvn, Node* region); + Node* merge_with(GraphKit* kit, const ValueTypeNode* other, int pnum); - // Stores the value type to memory if not yet allocated and returns the oop + // Store the value type to memory if not yet allocated and returns the oop Node* store_to_memory(GraphKit* kit); + // Store the value type in a field of an object + void store_to_field(GraphKit* kit, ciInstanceKlass* holder, Node* obj, int field_offset) const; // Get oop for heap allocated value type (may be TypePtr::NULL_PTR) Node* get_oop() const { return in(Oop); } void set_oop(Node* oop) { set_req(Oop, oop); } // Value type fields - uint field_count() const { return req() - Values; } + uint field_count() const { return req() - Values; } Node* get_field_value(uint index) const; - Node* get_field_value_by_offset(int field_offset) const; + Node* get_field_value_by_offset(int offset, bool recursive = false) const; void set_field_value(uint index, Node* value); int get_field_offset(uint index) const; - BasicType get_field_type(uint index) const; + ciType* get_field_type(uint index) const; // Replace ValueTypeNodes in debug info at safepoints with SafePointScalarObjectNodes void make_scalar_in_safepoints(Compile* C); --- old/src/share/vm/runtime/deoptimization.cpp 2016-11-16 09:06:14.558498646 +0100 +++ new/src/share/vm/runtime/deoptimization.cpp 2016-11-16 09:06:14.486498644 +0100 @@ -39,6 +39,7 @@ #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" #include "oops/fieldStreams.hpp" +#include "oops/valueKlass.hpp" #include "oops/verifyOopClosure.hpp" #include "prims/jvmtiThreadState.hpp" #include "runtime/biasedLocking.hpp" @@ -239,8 +240,8 @@ if (objects != NULL) { JRT_BLOCK realloc_failures = realloc_objects(thread, &deoptee, objects, THREAD); + reassign_fields(&deoptee, &map, objects, realloc_failures, skip_internal, THREAD); JRT_END - reassign_fields(&deoptee, &map, objects, realloc_failures, skip_internal); #ifndef PRODUCT if (TraceDeoptimization) { ttyLocker ttyl; @@ -931,10 +932,12 @@ public: int _offset; BasicType _type; + InstanceKlass* _klass; public: ReassignedField() { _offset = 0; _type = T_ILLEGAL; + _klass = NULL; } }; @@ -944,9 +947,9 @@ // Restore fields of an eliminated instance object using the same field order // returned by HotSpotResolvedObjectTypeImpl.getInstanceFields(true) -static int reassign_fields_by_klass(InstanceKlass* klass, frame* fr, RegisterMap* reg_map, ObjectValue* sv, int svIndex, oop obj, bool skip_internal) { +static int reassign_fields_by_klass(InstanceKlass* klass, frame* fr, RegisterMap* reg_map, ObjectValue* sv, int svIndex, oop obj, bool skip_internal, int base_offset, TRAPS) { if (klass->superklass() != NULL) { - svIndex = reassign_fields_by_klass(klass->superklass(), fr, reg_map, sv, svIndex, obj, skip_internal); + svIndex = reassign_fields_by_klass(klass->superklass(), fr, reg_map, sv, svIndex, obj, skip_internal, 0, CHECK_0); } GrowableArray* fields = new GrowableArray(); @@ -955,6 +958,13 @@ ReassignedField field; field._offset = fs.offset(); field._type = FieldType::basic_type(fs.signature()); + if (field._type == T_VALUETYPE) { + // Resolve klass of flattened value type field + SignatureStream ss(fs.signature(), false); + Klass* vk = ss.as_klass(Handle(klass->class_loader()), Handle(klass->protection_domain()), SignatureStream::NCDFError, CHECK_0); + assert(vk->is_value(), "must be a ValueKlass"); + field._klass = InstanceKlass::cast(vk); + } fields->append(field); } } @@ -963,7 +973,7 @@ intptr_t val; ScopeValue* scope_field = sv->field_at(svIndex); StackValue* value = StackValue::create_stack_value(fr, reg_map, scope_field); - int offset = fields->at(i)._offset; + int offset = base_offset + fields->at(i)._offset; BasicType type = fields->at(i)._type; switch (type) { case T_OBJECT: case T_ARRAY: @@ -971,6 +981,15 @@ obj->obj_field_put(offset, value->get_obj()()); break; + case T_VALUETYPE: { + // Recursively re-assign flattened value type fields + InstanceKlass* vk = fields->at(i)._klass; + assert(vk != NULL, "must be resolved"); + offset -= ValueKlass::cast(vk)->first_field_offset(); // Adjust offset to omit oop header + svIndex = reassign_fields_by_klass(vk, fr, reg_map, sv, svIndex, obj, skip_internal, offset, CHECK_0); + continue; // Continue because we don't need to increment svIndex + } + // Have to cast to INT (32 bits) pointer to avoid little/big-endian problem. case T_INT: case T_FLOAT: { // 4 bytes. assert(value->type() == T_INT, "Agreement."); @@ -1040,7 +1059,7 @@ } // restore fields of all eliminated objects and arrays -void Deoptimization::reassign_fields(frame* fr, RegisterMap* reg_map, GrowableArray* objects, bool realloc_failures, bool skip_internal) { +void Deoptimization::reassign_fields(frame* fr, RegisterMap* reg_map, GrowableArray* objects, bool realloc_failures, bool skip_internal, TRAPS) { for (int i = 0; i < objects->length(); i++) { ObjectValue* sv = (ObjectValue*) objects->at(i); KlassHandle k(java_lang_Class::as_Klass(sv->klass()->as_ConstantOopReadValue()->value()())); @@ -1055,7 +1074,7 @@ if (k->is_instance_klass()) { InstanceKlass* ik = InstanceKlass::cast(k()); - reassign_fields_by_klass(ik, fr, reg_map, sv, 0, obj(), skip_internal); + reassign_fields_by_klass(ik, fr, reg_map, sv, 0, obj(), skip_internal, 0, CHECK); } else if (k->is_typeArray_klass()) { TypeArrayKlass* ak = TypeArrayKlass::cast(k()); reassign_type_array_elements(fr, reg_map, sv, (typeArrayOop) obj(), ak->element_type()); --- old/src/share/vm/runtime/deoptimization.hpp 2016-11-16 09:06:14.834498654 +0100 +++ new/src/share/vm/runtime/deoptimization.hpp 2016-11-16 09:06:14.770498652 +0100 @@ -153,7 +153,7 @@ static bool realloc_objects(JavaThread* thread, frame* fr, GrowableArray* objects, TRAPS); static void reassign_type_array_elements(frame* fr, RegisterMap* reg_map, ObjectValue* sv, typeArrayOop obj, BasicType type); static void reassign_object_array_elements(frame* fr, RegisterMap* reg_map, ObjectValue* sv, objArrayOop obj); - static void reassign_fields(frame* fr, RegisterMap* reg_map, GrowableArray* objects, bool realloc_failures, bool skip_internal); + static void reassign_fields(frame* fr, RegisterMap* reg_map, GrowableArray* objects, bool realloc_failures, bool skip_internal, TRAPS); static void relock_objects(GrowableArray* monitors, JavaThread* thread, bool realloc_failures); static void pop_frames_failed_reallocs(JavaThread* thread, vframeArray* array); NOT_PRODUCT(static void print_objects(GrowableArray* objects, bool realloc_failures);) --- old/test/compiler/valhalla/valuetypes/ValueTypeTestBench.java 2016-11-16 09:06:15.086498661 +0100 +++ new/test/compiler/valhalla/valuetypes/ValueTypeTestBench.java 2016-11-16 09:06:15.018498659 +0100 @@ -52,36 +52,62 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -// Test value type -__ByValue final class MyValue { +// Test value types +__ByValue final class MyValue1 { final int x; final long y; - final double z; + final MyValue2 v1; + final MyValue2 v2; + final int c; - private MyValue(int x, long y, double z) { + private MyValue1(int x, long y, MyValue2 v1, MyValue2 v2, int c) { this.x = x; this.y = y; - this.z = z; + this.v1 = v1; + this.v2 = v2; + this.c = c; } @DontInline - public static MyValue createDontInline(int x, long y, double z) { - return __Make MyValue(x, y, z); + public static MyValue1 createDontInline(int x, long y) { + return __Make MyValue1(x, y, MyValue2.createInline(x, x < y), MyValue2.createInline(x, x > y), ValueTypeTestBench.rI); } @ForceInline - public static MyValue createInline(int x, long y, double z) { - return __Make MyValue(x, y, z); + public static MyValue1 createInline(int x, long y) { + return __Make MyValue1(x, y, MyValue2.createInline(x, x < y), MyValue2.createInline(x, x > y), ValueTypeTestBench.rI); } - @DontInline - public String toStringDontInline() { - return "MyValue: x=" + x + " y=" + y + " z=" + z; + @ForceInline + public long hash() { + return x + y + c + v1.hash() + v2.hash(); + } + + @DontCompile + public long hashInterpreted() { + return x + y + c + v1.hash() + v2.hash(); + } +} + +__ByValue final class MyValue2 { + final int x; + final boolean b; + final long c; + + private MyValue2(int x, boolean b, long c) { + this.x = x; + this.b = b; + this.c = c; } @ForceInline - public String toStringInline() { - return "MyValue: x=" + x + " y=" + y + " z=" + z; + public static MyValue2 createInline(int x, boolean b) { + return __Make MyValue2(x, b, ValueTypeTestBench.rL); + } + + @ForceInline + public long hash() { + return x + (b ? 0 : 1) + c; } } @@ -89,146 +115,154 @@ // Print ideal graph after execution of each test private static final boolean PRINT_GRAPH = true; + // ========== Helper methods ========== + + public long hash() { + return hash(rI, rL); + } + + public long hash(int x, long y) { + return MyValue1.createInline(x, y).hash(); + } + // ========== Test definitions ========== // Receive value type through call to interpreter - @Test(failOn = ALLOC + STORE) - public double test1() { - MyValue v = MyValue.createDontInline(rI, rL, rD); - return v.x + v.y + v.z; + @Test(failOn = ALLOC + STORE + TRAP) + public long test1() { + MyValue1 v = MyValue1.createDontInline(rI, rL); + return v.hash(); } @DontCompile public void test1_verifier(boolean warmup) { - double result = test1(); - Asserts.assertEQ(result, rI + rL + rD); + long result = test1(); + Asserts.assertEQ(result, hash()); } // Receive value type from interpreter via parameter - @Test(failOn = ALLOC + STORE) - public double test2(MyValue v) { - return v.x + v.y + v.z; + @Test(failOn = ALLOC + STORE + TRAP) + public long test2(MyValue1 v) { + return v.hash(); } @DontCompile public void test2_verifier(boolean warmup) { - MyValue v = MyValue.createDontInline(rI, rL, rD); - double result = test2(v); - Asserts.assertEQ(result, rI + rL + rD); + MyValue1 v = MyValue1.createDontInline(rI, rL); + long result = test2(v); + Asserts.assertEQ(result, hash()); } // Return incoming value type without accessing fields - @Test(failOn = ALLOC + LOAD + STORE) - public MyValue test3(MyValue v) { + @Test(failOn = ALLOC + LOAD + STORE + TRAP) + public MyValue1 test3(MyValue1 v) { return v; } @DontCompile public void test3_verifier(boolean warmup) { - MyValue v1 = MyValue.createDontInline(rI, rL, rD); - MyValue v2 = test3(v1); + MyValue1 v1 = MyValue1.createDontInline(rI, rL); + MyValue1 v2 = test3(v1); Asserts.assertEQ(v1.x, v2.x); Asserts.assertEQ(v1.y, v2.y); - Asserts.assertEQ(v1.z, v2.z); } // Create a value type in compiled code and only use fields. // Allocation should go away because value type does not escape. - @Test(failOn = ALLOC + LOAD + STORE) - public double test4() { - MyValue v = MyValue.createInline(rI, rL, rD); - return v.x + v.y + v.z; + @Test(failOn = ALLOC + LOAD + STORE + TRAP) + public long test4() { + MyValue1 v = MyValue1.createInline(rI, rL); + return v.hash(); } @DontCompile public void test4_verifier(boolean warmup) { - double result = test4(); - Asserts.assertEQ(result, rI + rL + rD); + long result = test4(); + Asserts.assertEQ(result, hash()); } // Create a value type in compiled code and pass it to // an inlined compiled method via a call. - @Test(failOn = ALLOC + LOAD + STORE) - public double test5() { - MyValue v = MyValue.createInline(rI, rL, rD); + @Test(failOn = ALLOC + LOAD + STORE + TRAP) + public long test5() { + MyValue1 v = MyValue1.createInline(rI, rL); return test5Inline(v); } @ForceInline - public double test5Inline(MyValue v) { - return v.x + v.y + v.z; + public long test5Inline(MyValue1 v) { + return v.hash(); } @DontCompile public void test5_verifier(boolean warmup) { - double result = test5(); - Asserts.assertEQ(result, rI + rL + rD); + long result = test5(); + Asserts.assertEQ(result, hash()); } // Create a value type in compiled code and pass it to // the interpreter via a call. - @Test(match = {ALLOC}, matchCount = {1}, failOn = LOAD) - public double test6() { - MyValue v = MyValue.createInline(rI, rL, rD); + @Test(match = {ALLOC}, matchCount = {1}, failOn = LOAD + TRAP) + public long test6() { + MyValue1 v = MyValue1.createInline(rI, rL); // Pass to interpreter - return sumValue(v); + return v.hashInterpreted(); } @DontCompile public void test6_verifier(boolean warmup) { - double result = test6(); - Asserts.assertEQ(result, rI + rL + rD); + long result = test6(); + Asserts.assertEQ(result, hash()); } // Create a value type in compiled code and pass it to // the interpreter by returning. - @Test(match = {ALLOC}, matchCount = {1}, failOn = LOAD) - public MyValue test7(int x, long y, double z) { - return MyValue.createInline(x, y, z); + @Test(match = {ALLOC}, matchCount = {1}, failOn = LOAD + TRAP) + public MyValue1 test7(int x, long y) { + return MyValue1.createInline(x, y); } @DontCompile public void test7_verifier(boolean warmup) { - MyValue v = test7(rI, rL, rD); - double result = v.x + v.y + v.z; - Asserts.assertEQ(result, rI + rL + rD); + MyValue1 v = test7(rI, rL); + Asserts.assertEQ(v.hash(), hash()); } // Merge value types created from two branches - @Test(failOn = ALLOC + STORE) - public double test8(boolean b) { - MyValue v; + @Test(failOn = ALLOC + STORE + TRAP) + public long test8(boolean b) { + MyValue1 v; if (b) { - v = MyValue.createInline(rI, rL, rD); + v = MyValue1.createInline(rI, rL); } else { - v = MyValue.createDontInline(rI + 1, rL + 1, rD + 1); + v = MyValue1.createDontInline(rI + 1, rL + 1); } - return v.x + v.y + v.z; + return v.hash(); } @DontCompile public void test8_verifier(boolean warmup) { - Asserts.assertEQ(test8(true), rI + rL + rD); - Asserts.assertEQ(test8(false), rI + 1 + rL + 1 + ((double)rD + 1)); + Asserts.assertEQ(test8(true), hash()); + Asserts.assertEQ(test8(false), hash(rI + 1, rL + 1)); } // Merge value types created from two branches - @Test(match = {ALLOC, STORE}, matchCount = {1, 3}, failOn = LOAD) - public MyValue test9(boolean b) { - MyValue v; + @Test(match = {ALLOC, STORE}, matchCount = {1, 9}, failOn = LOAD + TRAP) + public MyValue1 test9(boolean b) { + MyValue1 v; if (b) { // Value type is not allocated - v = MyValue.createInline(rI, rL, rD); + v = MyValue1.createInline(rI, rL); } else { // Value type is allocated by the callee - v = MyValue.createDontInline(rI + 1, rL + 1, rD + 1); + v = MyValue1.createDontInline(rI + 1, rL + 1); } // Need to allocate value type if 'b' is true - double sum = sumValue(v); + long sum = v.hashInterpreted(); if (b) { - v = MyValue.createDontInline(rI, rL, sum); + v = MyValue1.createDontInline(rI, sum); } else { - v = MyValue.createDontInline(rI, rL, sum + 1); + v = MyValue1.createDontInline(rI, sum + 1); } // Don't need to allocate value type because both branches allocate return v; @@ -236,61 +270,58 @@ @DontCompile public void test9_verifier(boolean warmup) { - MyValue v = test9(true); + MyValue1 v = test9(true); Asserts.assertEQ(v.x, rI); - Asserts.assertEQ(v.y, rL); - Asserts.assertEQ(v.z, rI + rL + rD); - + Asserts.assertEQ(v.y, hash()); v = test9(false); Asserts.assertEQ(v.x, rI); - Asserts.assertEQ(v.y, rL); - Asserts.assertEQ(v.z, rI + rL + ((double)rD + 1)); + Asserts.assertEQ(v.y, hash(rI + 1, rL + 1) + 1); } // Merge value types created in a loop (not inlined) - @Test(failOn = ALLOC + STORE) - public double test10(int x, long y, double z) { - MyValue v = MyValue.createDontInline(x, y, z); + @Test(failOn = ALLOC + STORE + TRAP) + public long test10(int x, long y) { + MyValue1 v = MyValue1.createDontInline(x, y); for (int i = 0; i < 10; ++i) { - v = MyValue.createDontInline(v.x + 1, v.y + 1, v.z + 1); + v = MyValue1.createDontInline(v.x + 1, v.y + 1); } - return v.x + v.y + v.z; + return v.hash(); } @DontCompile public void test10_verifier(boolean warmup) { - double result = test10(rI, rL, rD); - Asserts.assertEQ(result, rI + rL + 20 + ((double)rD + 10)); + long result = test10(rI, rL); + Asserts.assertEQ(result, hash(rI + 10, rL + 10)); } // Merge value types created in a loop (inlined) - @Test(failOn = ALLOC + LOAD + STORE + LOOP) - public double test11(int x, long y, double z) { - MyValue v = MyValue.createInline(x, y, z); + @Test(failOn = ALLOC + LOAD + STORE + LOOP + TRAP) + public long test11(int x, long y) { + MyValue1 v = MyValue1.createInline(x, y); for (int i = 0; i < 10; ++i) { - v = MyValue.createInline(v.x + 1, v.y + 1, v.z + 1); + v = MyValue1.createInline(v.x + 1, v.y + 1); } - return v.x + v.y + v.z; + return v.hash(); } @DontCompile public void test11_verifier(boolean warmup) { - double result = test11(rI, rL, rD); - Asserts.assertEQ(result, rI + rL + 20 + ((double)rD + 10)); + long result = test11(rI, rL); + Asserts.assertEQ(result, hash(rI + 10, rL + 10)); } // Test loop with uncommon trap referencing a value type @Test(match = {TRAP, SCOBJ}, matchCount = {1, 1}, failOn = ALLOC + LOAD + STORE) - public double test12(boolean b) { - MyValue v = MyValue.createInline(rI, rL, rD); - double result = 42; + public long test12(boolean b) { + MyValue1 v = MyValue1.createInline(rI, rL); + long result = 42; for (int i = 0; i < 1000; ++i) { if (b) { result += v.x; } else { // Uncommon trap referencing v. We delegate allocation to the // interpreter by adding a SafePointScalarObjectNode. - result = sumValue(v); + result = v.hashInterpreted(); } } return result; @@ -298,22 +329,22 @@ @DontCompile public void test12_verifier(boolean warmup) { - double result = test12(warmup); - Asserts.assertEQ(result, warmup ? 42 + (1000*(double)rI) : (rI + rL + rD)); + long result = test12(warmup); + Asserts.assertEQ(result, warmup ? 42 + (1000*rI) : hash()); } // Test loop with uncommon trap referencing a value type @Test(match = {TRAP, LOAD}, matchCount = {1, 1}, failOn = ALLOC + STORE + SCOBJ) - public double test13(boolean b) { - MyValue v = MyValue.createDontInline(rI, rL, rD); - double result = 42; + public long test13(boolean b) { + MyValue1 v = MyValue1.createDontInline(rI, rL); + long result = 42; for (int i = 0; i < 1000; ++i) { if (b) { result += v.x; } else { // Uncommon trap referencing v. Should not allocate // but just pass the existing oop to the uncommon trap. - result = sumValue(v); + result = v.hashInterpreted(); } } return result; @@ -321,101 +352,100 @@ @DontCompile public void test13_verifier(boolean warmup) { - double result = test13(warmup); - Asserts.assertEQ(result, warmup ? 42 + (1000*(double)rI) : (rI + rL + rD)); + long result = test13(warmup); + Asserts.assertEQ(result, warmup ? 42 + (1000*rI) : hash()); } // Create a value type in a non-inlined method and then call a // non-inlined method on that value type. - @Test(failOn = (ALLOC + STORE)) - public String test14() { - MyValue v = MyValue.createDontInline(32, 64L, 128.0); - String s = v.toStringDontInline(); - return s; + @Test(failOn = (ALLOC + LOAD + STORE + TRAP)) + public long test14() { + MyValue1 v = MyValue1.createDontInline(rI, rL); + return v.hashInterpreted(); } @DontCompile public void test14_verifier(boolean b) { - String s = test14(); - System.out.println("Result is: " + s); + long result = test14(); + Asserts.assertEQ(result, hash()); } // Create a value type in an inlined method and then call a // non-inlined method on that value type. - @Test(match = {ALLOC}, matchCount = {1}) - public String test15() { - MyValue v = MyValue.createInline(65, 129L, 257.0); - String s = v.toStringDontInline(); - return s; + @Test(failOn = (LOAD + TRAP), match = {ALLOC}, matchCount = {1}) + public long test15() { + MyValue1 v = MyValue1.createInline(rI, rL); + return v.hashInterpreted(); } @DontCompile public void test15_verifier(boolean b) { - String s = test15(); - System.out.println("Result is: " + s); + long result = test15(); + Asserts.assertEQ(result, hash()); } // Create a value type in a non-inlined method and then call an - // inlined method on that value type. Allocations are due to building - // String objects and not due to allocating value types. - @Test(match = {ALLOC}, matchCount = {2}) - public String test16() { - MyValue v = MyValue.createDontInline(130, 258L, 514.0); - String s = v.toStringInline(); - return s; + // inlined method on that value type. + @Test(failOn = (ALLOC + STORE + TRAP)) + public long test16() { + MyValue1 v = MyValue1.createDontInline(rI, rL); + return v.hash(); } @DontCompile public void test16_verifier(boolean b) { - String s = test16(); - System.out.println("Result is: " + s); + long result = test16(); + Asserts.assertEQ(result, hash()); } // Create a value type in an inlined method and then call an - // inlined method on that value type. Allocations are due to - // building String objects and not due to allocating value types. - @Test(match = {ALLOC}, matchCount = {2}) - public String test17() { - MyValue v = MyValue.createInline(259, 515L, 1027.0); - String s = v.toStringInline(); - return s; + // inlined method on that value type. + @Test(failOn = (ALLOC + LOAD + STORE + TRAP)) + public long test17() { + MyValue1 v = MyValue1.createInline(rI, rL); + return v.hash(); } @DontCompile public void test17_verifier(boolean b) { - String s = test17(); - System.out.println("Result is: " + s); + long result = test17(); + Asserts.assertEQ(result, hash()); } // Create a value type in compiled code and pass it to the // interpreter via a call. The value is live at the first call so // debug info should include a reference to all its fields. - @Test(match = {ALLOC}, matchCount = {1}, failOn = LOAD) - public double test18() { - MyValue v = MyValue.createInline(rI, rL, rD); - sumValue(v); - return sumValue(v); + @Test(match = {ALLOC}, matchCount = {1}, failOn = LOAD + TRAP) + public long test18() { + MyValue1 v = MyValue1.createInline(rI, rL); + v.hashInterpreted(); + return v.hashInterpreted(); } @DontCompile public void test18_verifier(boolean warmup) { - double result = test18(); - Asserts.assertEQ(result, rI + rL + rD); + long result = test18(); + Asserts.assertEQ(result, hash()); } // Create a value type in compiled code and pass it to the // interpreter via a call. The value type is passed twice but // should only be allocated once. - @Test(match = {ALLOC}, matchCount = {1}, failOn = LOAD) - public double test19() { - MyValue v = MyValue.createInline(rI, rL, rD); + @Test(match = {ALLOC}, matchCount = {1}, failOn = LOAD + TRAP) + public long test19() { + MyValue1 v = MyValue1.createInline(rI, rL); return sumValue(v, v); } @DontCompile + public long sumValue(MyValue1 v, MyValue1 dummy) { + return v.hash(); + } + + @DontCompile public void test19_verifier(boolean warmup) { - double result = test19(); - Asserts.assertEQ(result, rI + rL + rD); + long result = test19(); + Asserts.assertEQ(result, hash()); } // Create a value type in compiled code and pass it to the @@ -423,37 +453,54 @@ // trap: verify that deoptimization causes the value type to be // correctly allocated. @Test(match = {ALLOC}, matchCount = {1}, failOn = LOAD) - public double test20(boolean flag) { - MyValue v = MyValue.createInline(rI, rL, rD); - if (flag) { + public long test20(boolean flag) { + MyValue1 v = MyValue1.createInline(rI, rL); + if (flag) { // uncommon trap WHITE_BOX.deoptimizeMethod(tests.get("ValueTypeTestBench::test16")); } - return sumValue(v); + return v.hashInterpreted(); } @DontCompile public void test20_verifier(boolean warmup) { - double result = test20(false); - Asserts.assertEQ(result, rI + rL + rD); + long result = test20(false); + Asserts.assertEQ(result, hash()); if (!warmup) { result = test20(true); - Asserts.assertEQ(result, rI + rL + rD); + Asserts.assertEQ(result, hash()); } } - // ========== Helper methods ========== - - @DontCompile - public double sumValue(MyValue v) { - return v.x + v.y + v.z; - } + // Value type fields in regular object + MyValue1 val1; + MyValue2 val2; + + // Test value type fields in objects + @Test(failOn = (ALLOC + TRAP)) + public long test21(int x, long y) { + // Compute hash of value type fields + long result = val1.hash() + val2.hash(); + // Update fields + val1 = MyValue1.createInline(x, y); + val2 = MyValue2.createInline(x, true); + return result; + } @DontCompile - public double sumValue(MyValue v, MyValue dummy) { - return v.x + v.y + v.z; + public void test21_verifier(boolean warmup) { + // Check if hash computed by test18 is correct + val1 = MyValue1.createInline(rI, rL); + val2 = val1.v2; + long hash = val1.hash() + val2.hash(); + long result = test21(rI + 1, rL + 1); + Asserts.assertEQ(result, hash); + // Check if value type fields were updated + Asserts.assertEQ(val1.hash(), hash(rI + 1, rL + 1)); + Asserts.assertEQ(val2.hash(), MyValue2.createInline(rI + 1, true).hash()); } + // ========== Test infrastructure ========== private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); @@ -461,6 +508,8 @@ private static final int COMP_LEVEL_FULL_OPTIMIZATION = 4; private static final Hashtable tests = new Hashtable(); private static final int WARMUP = 10; + private static boolean USE_COMPILER = WHITE_BOX.getBooleanVMFlag("UseCompiler"); + private static boolean PRINT_IDEAL = WHITE_BOX.getBooleanVMFlag("PrintIdeal"); // Regular expressions used to match nodes in the PrintIdeal output private static final String START = "(\\d+\\t(.*"; @@ -474,11 +523,9 @@ // TODO: match field values of scalar replaced object private static final String SCOBJ = "(.*# ScObj.*" + END; - // Random test values - private static final int rI = Utils.getRandomInstance().nextInt(); - private static final long rL = Utils.getRandomInstance().nextLong(); - private static final double rD = Utils.getRandomInstance().nextDouble(); + public static final int rI = Utils.getRandomInstance().nextInt() % 1000; + public static final long rL = Utils.getRandomInstance().nextLong() % 1000; static { // Gather all test methods and put them in Hashtable @@ -493,16 +540,25 @@ if (args.length == 0) { // Run tests in own process and verify output OutputAnalyzer oa = ProcessTools.executeTestJvm("-noverify", - "-XX:+UnlockDiagnosticVMOptions", "-Xbootclasspath/a:.", "-XX:+WhiteBoxAPI", - "-XX:-TieredCompilation", "-XX:-BackgroundCompilation", "-XX:-UseOnStackReplacement", - "-XX:CompileCommand=quiet", "-XX:+PrintCompilation", "-XX:+PrintIdeal", "-XX:+PrintOptoAssembly", - "-XX:CompileCommand=compileonly,compiler.valhalla.valuetypes.ValueTypeTestBench::*", - "-XX:CompileCommand=compileonly,compiler.valhalla.valuetypes.MyValue::*", - ValueTypeTestBench.class.getName(), "run"); + "-XX:+UnlockDiagnosticVMOptions", "-Xbootclasspath/a:.", "-XX:+WhiteBoxAPI", + "-XX:-TieredCompilation", "-XX:-BackgroundCompilation", "-XX:-UseOnStackReplacement", + "-XX:+IgnoreUnrecognizedVMOptions", "-XX:+PrintCompilation", "-XX:+PrintIdeal", "-XX:+PrintOptoAssembly", + "-XX:CompileCommand=quiet", "-XX:CompileCommand=compileonly,compiler.valhalla.valuetypes.ValueTypeTestBench::*", + "-XX:CompileCommand=compileonly,compiler.valhalla.valuetypes.MyValue1::*", + "-XX:CompileCommand=compileonly,compiler.valhalla.valuetypes.MyValue2::*", + ValueTypeTestBench.class.getName(), "run"); + // If ideal graph printing is enabled/supported, verify output String output = oa.getOutput(); oa.shouldHaveExitValue(0); - parseOutput(output); + if (output.contains("PrintIdeal enabled")) { + parseOutput(output); + } else { + System.out.println("WARNING: test methods not compiled or PrintIdeal disabled! Running with -Xint or release build?"); + } } else { + if (USE_COMPILER && PRINT_IDEAL) { + System.out.println("PrintIdeal enabled"); + } // Execute tests ValueTypeTestBench bench = new ValueTypeTestBench(); bench.run(); @@ -588,9 +644,10 @@ } public void run() throws Exception { - System.out.format("rI = %d, rL = %d, rD = %f\n", rI, rL, rD); + System.out.format("rI = %d, rL = %d\n", rI, rL); setup(this.getClass().getDeclaredMethods()); - setup(MyValue.class.getDeclaredMethods()); + setup(MyValue1.class.getDeclaredMethods()); + setup(MyValue2.class.getDeclaredMethods()); // Execute tests for (Method test : tests.values()) { @@ -601,7 +658,7 @@ } // Trigger compilation WHITE_BOX.enqueueMethodForCompilation(test, COMP_LEVEL_FULL_OPTIMIZATION); - Asserts.assertTrue(WHITE_BOX.isMethodCompiled(test, false)); + Asserts.assertTrue(!USE_COMPILER || WHITE_BOX.isMethodCompiled(test, false), test + " not compiled"); // Check result verifier.invoke(this, false); }