--- old/src/hotspot/share/classfile/classFileParser.cpp 2019-09-04 12:23:25.000000000 -0400 +++ new/src/hotspot/share/classfile/classFileParser.cpp 2019-09-04 12:23:25.000000000 -0400 @@ -1546,7 +1546,7 @@ const InjectedField* const injected = JavaClasses::get_injected(_class_name, &num_injected); - const int total_fields = length + num_injected + (is_value_type ? 1 : 0); + const int total_fields = length + num_injected + (is_value_type ? 2 : 0); // The field array starts with tuples of shorts // [access, name index, sig index, initial value index, byte offset]. @@ -1576,6 +1576,7 @@ // The generic signature slots start after all other fields' data. int generic_signature_slot = total_fields * FieldInfo::field_slots; int num_generic_signature = 0; + int instance_fields_count = 0; for (int n = 0; n < length; n++) { // access_flags, name_index, descriptor_index, attributes_count cfs->guarantee_more(8, CHECK); @@ -1616,6 +1617,7 @@ } _has_flattenable_fields = true; } + if (!access_flags.is_static()) instance_fields_count++; u2 constantvalue_index = 0; bool is_synthetic = false; @@ -1723,7 +1725,6 @@ } if (is_value_type) { - index = length + num_injected; FieldInfo* const field = FieldInfo::from_field_array(fa, index); field->initialize(JVM_ACC_FIELD_INTERNAL | JVM_ACC_STATIC, vmSymbols::default_value_name_enum, @@ -1735,6 +1736,19 @@ index++; } + if (is_value_type && instance_fields_count == 0) { + _is_empty_value = true; + FieldInfo* const field = FieldInfo::from_field_array(fa, index); + field->initialize(JVM_ACC_FIELD_INTERNAL, + vmSymbols::empty_marker_name_enum, + vmSymbols::byte_signature_enum, + 0); + const BasicType type = FieldType::basic_type(vmSymbols::byte_signature()); + const FieldAllocationType atype = fac->update(false, type, false); + field->set_allocation_type(atype); + index++; + } + assert(NULL == _fields, "invariant"); _fields = @@ -5793,6 +5807,9 @@ // Not yet: supers are done below to support the new subtype-checking fields ik->set_nonstatic_field_size(_field_info->nonstatic_field_size); ik->set_has_nonstatic_fields(_field_info->has_nonstatic_fields); + if (_is_empty_value) { + ik->set_is_empty_value(); + } assert(_fac != NULL, "invariant"); ik->set_static_oop_field_count(_fac->count[STATIC_OOP] + _fac->count[STATIC_FLATTENABLE]); @@ -5961,9 +5978,14 @@ } if (is_value_type()) { - ValueKlass::cast(ik)->set_alignment(_alignment); - ValueKlass::cast(ik)->set_first_field_offset(_first_field_offset); - ValueKlass::cast(ik)->set_exact_size_in_bytes(_exact_size_in_bytes); + ValueKlass* vk = ValueKlass::cast(ik); + if (UseNewLayout) { + vk->set_alignment(_alignment); + vk->set_first_field_offset(_first_field_offset); + vk->set_exact_size_in_bytes(_exact_size_in_bytes); + } else { + vk->set_first_field_offset(vk->first_field_offset_old()); + } ValueKlass::cast(ik)->initialize_calling_convention(CHECK); } @@ -6157,6 +6179,7 @@ _declares_nonstatic_concrete_methods(false), _has_final_method(false), _has_flattenable_fields(false), + _is_empty_value(false), _has_finalizer(false), _has_empty_finalizer(false), _has_vanilla_constructor(false), --- old/src/hotspot/share/classfile/classFileParser.hpp 2019-09-04 12:23:26.000000000 -0400 +++ new/src/hotspot/share/classfile/classFileParser.hpp 2019-09-04 12:23:26.000000000 -0400 @@ -253,6 +253,7 @@ bool _declares_nonstatic_concrete_methods; bool _has_final_method; bool _has_flattenable_fields; + bool _is_empty_value; // precomputed flags bool _has_finalizer; --- old/src/hotspot/share/classfile/vmSymbols.hpp 2019-09-04 12:23:27.000000000 -0400 +++ new/src/hotspot/share/classfile/vmSymbols.hpp 2019-09-04 12:23:27.000000000 -0400 @@ -453,6 +453,7 @@ template(resolved_references_name, "") \ template(init_lock_name, "") \ template(default_value_name, ".default") \ + template(empty_marker_name, ".empty") \ template(address_size_name, "ADDRESS_SIZE0") \ template(page_size_name, "PAGE_SIZE") \ template(big_endian_name, "BIG_ENDIAN") \ --- old/src/hotspot/share/interpreter/interpreterRuntime.cpp 2019-09-04 12:23:28.000000000 -0400 +++ new/src/hotspot/share/interpreter/interpreterRuntime.cpp 2019-09-04 12:23:28.000000000 -0400 @@ -417,7 +417,9 @@ assert(value->is_value(), "Sanity check"); ValueKlass* vklass = ValueKlass::cast(value->klass()); - vklass->value_store(vklass->data_for_oop(value), ((char*)(oopDesc*)rcv) + offset, true, true); + if (!vklass->is_empty_value()) { + vklass->value_store(vklass->data_for_oop(value), ((char*)(oopDesc*)rcv) + offset, true, true); + } JRT_END JRT_ENTRY(void, InterpreterRuntime::read_flattened_field(JavaThread* thread, oopDesc* obj, int index, Klass* field_holder)) @@ -433,11 +435,17 @@ ValueKlass* field_vklass = ValueKlass::cast(klass->get_value_field_klass(index)); assert(field_vklass->is_initialized(), "Must be initialized at this point"); - // allocate instance - instanceOop res = field_vklass->allocate_instance(CHECK); - // copy value - field_vklass->value_store(((char*)(oopDesc*)obj_h()) + klass->field_offset(index), - field_vklass->data_for_oop(res), true, true); + instanceOop res = NULL; + if (field_vklass->is_empty_value()) { + res = (instanceOop)field_vklass->default_value(); + } else { + // allocate instance + res = field_vklass->allocate_instance(CHECK); + // copy value + field_vklass->value_store(((char*)(oopDesc*)obj_h()) + klass->field_offset(index), + field_vklass->data_for_oop(res), true, true); + } + assert(res != NULL, "Must be set in one of two paths above"); thread->set_vm_result(res); JRT_END @@ -467,10 +475,16 @@ ValueArrayKlass* vaklass = ValueArrayKlass::cast(klass); ValueKlass* vklass = vaklass->element_klass(); arrayHandle ah(THREAD, array); - instanceOop value_holder = vklass->allocate_instance(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, false); + instanceOop value_holder = NULL; + if (vklass->is_empty_value()) { + value_holder = (instanceOop)vklass->default_value(); + } else { + value_holder = vklass->allocate_instance(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, false); + } + assert(value_holder != NULL, "Must be set in one of two paths above"); thread->set_vm_result(value_holder); JRT_END @@ -483,9 +497,11 @@ valueArrayOop varray = (valueArrayOop)array; ValueArrayKlass* vaklass = ValueArrayKlass::cast(klass); ValueKlass* vklass = vaklass->element_klass(); - const int lh = vaklass->layout_helper(); - vklass->value_store(vklass->data_for_oop((oop)val), varray->value_at_addr(index, lh), - vaklass->element_byte_size(), true, false); + if (!vklass->is_empty_value()) { + const int lh = vaklass->layout_helper(); + vklass->value_store(vklass->data_for_oop((oop)val), varray->value_at_addr(index, lh), + vaklass->element_byte_size(), true, false); + } JRT_END JRT_ENTRY(void, InterpreterRuntime::multianewarray(JavaThread* thread, jint* first_size_address)) --- old/src/hotspot/share/oops/instanceKlass.hpp 2019-09-04 12:23:29.000000000 -0400 +++ new/src/hotspot/share/oops/instanceKlass.hpp 2019-09-04 12:23:29.000000000 -0400 @@ -257,7 +257,8 @@ _extra_is_being_redefined = 1 << 0, // used for locking redefinition _extra_has_resolved_methods = 1 << 1, // resolved methods table entries added for this class _extra_has_value_fields = 1 << 2, // has value fields and related embedded section is not empty - _extra_is_bufferable = 1 << 3 // value can be buffered out side of the Java heap + _extra_is_bufferable = 1 << 3, // value can be buffered out side of the Java heap + _extra_is_empty_value = 1 << 4 // empty value type }; protected: @@ -424,6 +425,13 @@ _extra_flags |= _extra_has_value_fields; } + bool is_empty_value() const { + return (_extra_flags & _extra_is_empty_value) != 0; + } + void set_is_empty_value() { + _extra_flags |= _extra_is_empty_value; + } + // field sizes int nonstatic_field_size() const { return _nonstatic_field_size; } void set_nonstatic_field_size(int size) { _nonstatic_field_size = size; } --- old/src/hotspot/share/oops/valueKlass.cpp 2019-09-04 12:23:30.000000000 -0400 +++ new/src/hotspot/share/oops/valueKlass.cpp 2019-09-04 12:23:30.000000000 -0400 @@ -46,10 +46,10 @@ #include "runtime/thread.inline.hpp" #include "utilities/copy.hpp" -int ValueKlass::first_field_offset_old() const { +int ValueKlass::first_field_offset_old() { #ifdef ASSERT int first_offset = INT_MAX; - for (JavaFieldStream fs(this); !fs.done(); fs.next()) { + for (AllFieldStream fs(this); !fs.done(); fs.next()) { if (fs.offset() < first_offset) first_offset= fs.offset(); } #endif @@ -60,7 +60,7 @@ return base_offset; } -int ValueKlass::raw_value_byte_size() const { +int ValueKlass::raw_value_byte_size() { int heapOopAlignedSize = nonstatic_field_size() << LogBytesPerHeapOop; // If bigger than 64 bits or needs oop alignment, then use jlong aligned // which for values should be jlong aligned, asserts in raw_field_copy otherwise @@ -75,11 +75,11 @@ int first_offset = first_field_offset(); int last_offset = 0; // find the last offset, add basic type size int last_tsz = 0; - for (JavaFieldStream fs(this); !fs.done(); fs.next()) { + for (AllFieldStream fs(this); !fs.done(); fs.next()) { if (fs.access_flags().is_static()) { continue; } else if (fs.offset() > last_offset) { - BasicType type = fs.field_descriptor().field_type(); + BasicType type = char2type(fs.signature()->char_at(0)); if (is_java_primitive(type)) { last_tsz = type2aelembytes(type); } else if (type == T_VALUETYPE) { @@ -329,10 +329,10 @@ // T_VALUETYPE, drop everything until and including the closing // T_VOID) or the compiler point of view (each field of the value // types is an argument: drop all T_VALUETYPE/T_VOID from the list). -int ValueKlass::collect_fields(GrowableArray* sig, int base_off) const { +int ValueKlass::collect_fields(GrowableArray* sig, int base_off) { int count = 0; SigEntry::add_entry(sig, T_VALUETYPE, base_off); - for (JavaFieldStream fs(this); !fs.done(); fs.next()) { + for (AllFieldStream fs(this); !fs.done(); fs.next()) { if (fs.access_flags().is_static()) continue; int offset = base_off + fs.offset() - (base_off > 0 ? first_field_offset() : 0); if (fs.is_flattened()) { --- old/src/hotspot/share/oops/valueKlass.hpp 2019-09-04 12:23:31.000000000 -0400 +++ new/src/hotspot/share/oops/valueKlass.hpp 2019-09-04 12:23:31.000000000 -0400 @@ -143,7 +143,7 @@ *(int*)adr_alignment() = alignment; } - int get_first_field_offset() const { + int first_field_offset() const { int offset = *(int*)adr_first_field_offset(); assert(offset != 0, "Must be initialized before use"); return *(int*)adr_first_field_offset(); @@ -161,12 +161,13 @@ *(int*)adr_exact_size_in_bytes() = exact_size; } + int first_field_offset_old(); + private: - int collect_fields(GrowableArray* sig, int base_off = 0) const; + int collect_fields(GrowableArray* sig, int base_off = 0); void cleanup_blobs(); - int first_field_offset_old() const; protected: // Returns the array class for the n'th dimension @@ -206,16 +207,7 @@ instanceOop allocate_instance(TRAPS); // minimum number of bytes occupied by nonstatic fields, HeapWord aligned or pow2 - int raw_value_byte_size() const; - - int first_field_offset() const { - // To be refactored/simplified once old layout code and UseNewLayout are removed - if (UseNewLayout) { - return get_first_field_offset(); - } else { - return first_field_offset_old(); - } - }; + int raw_value_byte_size(); address data_for_oop(oop o) const { return ((address) (void*) o) + first_field_offset(); --- /dev/null 2019-09-04 12:23:32.000000000 -0400 +++ new/test/hotspot/jtreg/runtime/valhalla/valuetypes/EmptyValueTest.java 2019-09-04 12:23:32.000000000 -0400 @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2019, 2019 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. + */ +package runtime.valhalla.valuetypes; + +import jdk.test.lib.Asserts; + +import java.lang.reflect.Field; + +/* + * @test + * @summary Test support for empty value types (no instance fields) + * @library /test/lib + * @compile -XDallowEmptyValues EmptyValueTest.java + * @run main/othervm -XX:+EnableValhalla runtime.valhalla.valuetypes.EmptyValueTest + + */ + +public class EmptyValueTest { + + static inline class EmptyValue { + public boolean isEmpty() { + return true; + } + } + + static inline class EmptyField { + EmptyValue empty; + + EmptyField() { + this.empty = new EmptyValue(); + } + } + + static class WithInt { + int i; + } + + static class WithEmptyField extends WithInt { + // With current layout strategy for reference classs, the empty + // value field would be placed between the int and the Object + // fields, along with some padding. + Object o; + EmptyValue empty; + } + + public static void main(String[] args) { + // Create an empty value + EmptyValue empty = new EmptyValue(); + Asserts.assertTrue(empty.isEmpty()); + + // Create a value with a empty value field + EmptyField emptyField = new EmptyField(); + Asserts.assertTrue(emptyField.empty.isEmpty()); + System.out.println(emptyField.empty.isEmpty()); + + // Regular instance with an empty field inside + WithEmptyField w = new WithEmptyField(); + Asserts.assertTrue(w.empty.isEmpty()); + w.empty = new EmptyValue(); + Asserts.assertTrue(w.empty.isEmpty()); + + // Create an array of empty values + EmptyValue[] emptyArray = new EmptyValue[100]; + for(EmptyValue element : emptyArray) { + Asserts.assertTrue(element.isEmpty()); + } + + // Testing arrayCopy + EmptyValue[] array2 = new EmptyValue[100]; + // with two arrays + System.arraycopy(emptyArray, 10, array2, 20, 50); + for(EmptyValue element : array2) { + Asserts.assertTrue(element.isEmpty()); + } + // single array, no overlap + System.arraycopy(emptyArray, 10, emptyArray, 50, 20); + for(EmptyValue element : emptyArray) { + Asserts.assertTrue(element.isEmpty()); + } + // single array with overlap + System.arraycopy(emptyArray, 10, emptyArray, 20, 50); + for(EmptyValue element : emptyArray) { + Asserts.assertTrue(element.isEmpty()); + } + + // Passing an empty value in argument + assert isEmpty(empty); + + // Returning an empty value + assert getEmpty().isEmpty(); + + // Checking fields with reflection + Class c = empty.getClass(); + try { + Field[] fields = c.getDeclaredFields(); + Asserts.assertTrue(fields.length == 0); + } catch (Throwable t) { + t.printStackTrace(); + } + + // Testing JIT compiler + // for(int i=0; i < 100000; i++) { + // test(); + // } + } + + static boolean isEmpty(EmptyValue empty) { + return empty.isEmpty(); + } + + static EmptyValue getEmpty() { + return new EmptyValue(); + } + + static void test() { + for(int i=0; i < 10000; i++) { + Asserts.assertTrue(getEmpty().isEmpty()); + } + } +} --- old/test/hotspot/jtreg/runtime/valhalla/valuetypes/Empty.java 2019-09-04 12:23:33.000000000 -0400 +++ /dev/null 2019-09-04 12:23:33.000000000 -0400 @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2017, 2019, 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. - */ - -package runtime.valhalla.valuetypes; - -inline final class EmptyValue { - - private EmptyValue() { - } - - public static EmptyValue createEmptyValue() { - EmptyValue e = EmptyValue.default; - return e; - } -} - -class EmptyTest { - public void run() { - EmptyValue.createEmptyValue(); - throw new RuntimeException("Expected class file parse error"); - } -} - -/** - * @test Empty - * @summary Test empty inline type - * @compile -XDemitQtypes -XDenableValueTypes -XDallowEmptyValues Empty.java - * @run main/othervm -Xint runtime.valhalla.valuetypes.Empty - * @run main/othervm -Xcomp runtime.valhalla.valuetypes.Empty - */ -public class Empty { - public static void main(String[] args) { - try { - EmptyTest test = new EmptyTest(); - test.run(); - } catch (ClassFormatError cfe) {} - } -}