diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index ecdc76eb487..3d35dba2cc9 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -675,6 +675,7 @@ \ template(java_lang_invoke_ValueBootstrapMethods, "java/lang/invoke/ValueBootstrapMethods") \ template(isSubstitutable_name, "isSubstitutable0") \ + template(inlineObjectHashCode_name, "inlineObjectHashCode") \ \ template(jdk_internal_vm_jni_SubElementSelector, "jdk/internal/vm/jni/SubElementSelector") \ /*end*/ diff --git a/src/hotspot/share/memory/universe.cpp b/src/hotspot/share/memory/universe.cpp index 2460bce3f48..e0de35baa23 100644 --- a/src/hotspot/share/memory/universe.cpp +++ b/src/hotspot/share/memory/universe.cpp @@ -118,6 +118,7 @@ LatestMethodCache* Universe::_throw_illegal_access_error_cache = NULL; LatestMethodCache* Universe::_throw_no_such_method_error_cache = NULL; LatestMethodCache* Universe::_do_stack_walk_cache = NULL; LatestMethodCache* Universe::_is_substitutable_cache = NULL; +LatestMethodCache* Universe::_inline_type_hash_code_cache = NULL; oop Universe::_out_of_memory_error_java_heap = NULL; oop Universe::_out_of_memory_error_metaspace = NULL; oop Universe::_out_of_memory_error_class_metaspace = NULL; @@ -235,6 +236,7 @@ void Universe::metaspace_pointers_do(MetaspaceClosure* it) { _throw_no_such_method_error_cache->metaspace_pointers_do(it); _do_stack_walk_cache->metaspace_pointers_do(it); _is_substitutable_cache->metaspace_pointers_do(it); + _inline_type_hash_code_cache->metaspace_pointers_do(it); } #define ASSERT_MIRROR_NULL(m) \ @@ -273,6 +275,7 @@ void Universe::serialize(SerializeClosure* f) { _throw_no_such_method_error_cache->serialize(f); _do_stack_walk_cache->serialize(f); _is_substitutable_cache->serialize(f); + _inline_type_hash_code_cache->serialize(f); } void Universe::check_alignment(uintx size, uintx alignment, const char* name) { @@ -707,6 +710,7 @@ jint universe_init() { Universe::_throw_no_such_method_error_cache = new LatestMethodCache(); Universe::_do_stack_walk_cache = new LatestMethodCache(); Universe::_is_substitutable_cache = new LatestMethodCache(); + Universe::_inline_type_hash_code_cache = new LatestMethodCache(); #if INCLUDE_CDS if (UseSharedSpaces) { @@ -865,6 +869,10 @@ void Universe::initialize_known_methods(TRAPS) { SystemDictionary::ValueBootstrapMethods_klass(), vmSymbols::isSubstitutable_name()->as_C_string(), vmSymbols::object_object_boolean_signature(), true, CHECK); + initialize_known_method(_inline_type_hash_code_cache, + SystemDictionary::ValueBootstrapMethods_klass(), + vmSymbols::inlineObjectHashCode_name()->as_C_string(), + vmSymbols::object_int_signature(), true, CHECK); } void universe2_init() { diff --git a/src/hotspot/share/memory/universe.hpp b/src/hotspot/share/memory/universe.hpp index 1880db33401..8b4663271bd 100644 --- a/src/hotspot/share/memory/universe.hpp +++ b/src/hotspot/share/memory/universe.hpp @@ -119,6 +119,7 @@ class Universe: AllStatic { static LatestMethodCache* _throw_no_such_method_error_cache; // Unsafe.throwNoSuchMethodError() method static LatestMethodCache* _do_stack_walk_cache; // method for stack walker callback static LatestMethodCache* _is_substitutable_cache; // ValueBootstrapMethods.isSubstitutable() method + static LatestMethodCache* _inline_type_hash_code_cache; // ValueBootstrapMethods.inlineObjectHashCode() method // preallocated error objects (no backtrace) static oop _out_of_memory_error_java_heap; @@ -285,6 +286,7 @@ class Universe: AllStatic { static Method* do_stack_walk_method() { return _do_stack_walk_cache->get_method(); } static Method* is_substitutable_method() { return _is_substitutable_cache->get_method(); } + static Method* inline_type_hash_code_method() { return _inline_type_hash_code_cache->get_method(); } static oop the_null_sentinel() { return _the_null_sentinel; } static address the_null_sentinel_addr() { return (address) &_the_null_sentinel; } diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index 7ea018bbce5..2dc4a95e179 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -658,7 +658,28 @@ JVM_END JVM_ENTRY(jint, JVM_IHashCode(JNIEnv* env, jobject handle)) JVMWrapper("JVM_IHashCode"); // as implemented in the classic virtual machine; return 0 if object is NULL - return handle == NULL ? 0 : ObjectSynchronizer::FastHashCode (THREAD, JNIHandles::resolve_non_null(handle)) ; + if (handle == NULL) { + return 0; + } + oop obj = JNIHandles::resolve_non_null(handle); + if (EnableValhalla && obj->klass()->is_inline_klass()) { + JavaValue result(T_INT); + JavaCallArguments args; + Handle ho(THREAD, obj); + args.push_oop(ho); + methodHandle method(THREAD, Universe::inline_type_hash_code_method()); + JavaCalls::call(&result, method, &args, THREAD); + if (HAS_PENDING_EXCEPTION) { + if (!PENDING_EXCEPTION->is_a(SystemDictionary::Error_klass())) { + Handle e(THREAD, PENDING_EXCEPTION); + CLEAR_PENDING_EXCEPTION; + THROW_MSG_CAUSE_(vmSymbols::java_lang_InternalError(), "Internal error in hashCode", e, false); + } + } + return result.get_jint(); + } else { + return ObjectSynchronizer::FastHashCode(THREAD, obj); + } JVM_END diff --git a/src/hotspot/share/runtime/synchronizer.cpp b/src/hotspot/share/runtime/synchronizer.cpp index 0dbc34b7f3c..eed5aa0e9d0 100644 --- a/src/hotspot/share/runtime/synchronizer.cpp +++ b/src/hotspot/share/runtime/synchronizer.cpp @@ -1013,12 +1013,8 @@ static inline intptr_t get_next_hash(Thread* self, oop obj) { intptr_t ObjectSynchronizer::FastHashCode(Thread* self, oop obj) { if (EnableValhalla && obj->klass()->is_inline_klass()) { - // Expected tooling to override hashCode for inline type, just don't crash - if (log_is_enabled(Debug, monitorinflation)) { - ResourceMark rm; - log_debug(monitorinflation)("FastHashCode for value type: %s", obj->klass()->external_name()); - } - return obj->klass()->java_mirror()->identity_hash(); + // VM should be calling bootstrap method + ShouldNotReachHere(); } if (UseBiasedLocking) { // NOTE: many places throughout the JVM do not expect a safepoint diff --git a/src/java.base/share/classes/java/lang/invoke/ValueBootstrapMethods.java b/src/java.base/share/classes/java/lang/invoke/ValueBootstrapMethods.java index a16c8200cd5..40c6cae8637 100644 --- a/src/java.base/share/classes/java/lang/invoke/ValueBootstrapMethods.java +++ b/src/java.base/share/classes/java/lang/invoke/ValueBootstrapMethods.java @@ -193,6 +193,36 @@ static MethodHandle inlineTypeEquals(Class type) { instanceFalse)); } + static MethodHandle inlineTypeHashCode(Class type) { + assert type.isInlineClass(); + MethodHandle target = dropArguments(constant(int.class, SALT), 0, type); + MethodHandle cls = dropArguments(constant(Class.class, type),0, type); + MethodHandle classHashCode = filterReturnValue(cls, hashCodeForType(Class.class)); + MethodHandle combiner = filterArguments(HASH_COMBINER, 0, target, classHashCode); + // int v = SALT * 31 + type.hashCode(); + MethodHandle init = permuteArguments(combiner, target.type(), 0, 0); + MethodHandles.Lookup lookup = new MethodHandles.Lookup(type); + MethodHandle[] getters = MethodHandleBuilder.getters(lookup); + MethodHandle iterations = dropArguments(constant(int.class, getters.length), 0, type); + MethodHandle[] hashers = new MethodHandle[getters.length]; + for (int i=0; i < getters.length; i++) { + MethodHandle getter = getters[i]; + // For inline type or reference type, this calls Objects::hashCode. + // If the instance is of inline type and the hashCode method is not + // overridden, VM will call inlineObjectHashCode to compute the + // hash code. + MethodHandle hasher = hashCodeForType(getter.type().returnType()); + hashers[i] = filterReturnValue(getter, hasher); + } + + // for (int i=0; i < getters.length; i++) { + // v = computeHash(v, i, a); + // } + MethodHandle body = COMPUTE_HASH.bindTo(hashers) + .asType(methodType(int.class, int.class, int.class, type)); + return countedLoop(iterations, init, body); + } + // ------ utility methods ------ private static boolean eq(byte a, byte b) { return a == b; } private static boolean eq(short a, short b) { return a == b; } @@ -365,28 +395,7 @@ private static MethodHandle findStatic(Class cls, String name, MethodType met * Produces a method handle that computes the hashcode */ private static MethodHandle hashCodeInvoker(Lookup lookup, String name, MethodType mt) { - Class type = lookup.lookupClass(); - MethodHandle target = dropArguments(constant(int.class, SALT), 0, type); - MethodHandle cls = dropArguments(constant(Class.class, type),0, type); - MethodHandle classHashCode = filterReturnValue(cls, hashCodeForType(Class.class)); - MethodHandle combiner = filterArguments(HASH_COMBINER, 0, target, classHashCode); - // int v = SALT * 31 + type.hashCode(); - MethodHandle init = permuteArguments(combiner, target.type(), 0, 0); - MethodHandle[] getters = MethodHandleBuilder.getters(lookup); - MethodHandle iterations = dropArguments(constant(int.class, getters.length), 0, type); - MethodHandle[] hashers = new MethodHandle[getters.length]; - for (int i=0; i < getters.length; i++) { - MethodHandle getter = getters[i]; - MethodHandle hasher = hashCodeForType(getter.type().returnType()); - hashers[i] = filterReturnValue(getter, hasher); - } - - // for (int i=0; i < getters.length; i++) { - // v = computeHash(v, i, a); - // } - MethodHandle body = COMPUTE_HASH.bindTo(hashers) - .asType(methodType(int.class, int.class, int.class, type)); - return countedLoop(iterations, init, body); + return inlineTypeHashCode(lookup.lookupClass()); } /* @@ -458,6 +467,33 @@ private static LinkageError newLinkageError(Throwable e) { return (LinkageError) new LinkageError().initCause(e); } + /** + * Invoke the bootstrap methods hashCode for the given instance. + * @param o the instance to hash. + * @return the hash code of the given instance {code o}. + */ + private static int inlineObjectHashCode(Object o) { + try { + Class type = o.getClass(); + // Note: javac disallows user to call super.hashCode if user implementated + // risk for recursion for experts crafting byte-code + if (!type.isInlineClass()) + throw new InternalError("must be inline type: " + type.getName()); + return (int) HASHCODE_METHOD_HANDLES.get(type).invoke(o); + } catch (Error|RuntimeException e) { + throw e; + } catch (Throwable e) { + if (VERBOSE) e.printStackTrace(); + throw new InternalError(e); + } + } + + private static ClassValue HASHCODE_METHOD_HANDLES = new ClassValue<>() { + @Override protected MethodHandle computeValue(Class type) { + return MethodHandleBuilder.inlineTypeHashCode(type); + } + }; + /** * Returns {@code true} if the arguments are substitutable to each * other and {@code false} otherwise. diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TypeEnter.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TypeEnter.java index 479ee4ca776..611b48956c7 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TypeEnter.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TypeEnter.java @@ -1163,7 +1163,7 @@ private void addEnumMembers(JCClassDecl tree, Env env) { */ private void addValueMembers(JCClassDecl tree, Env env) { - boolean requireHashCode = true, requireEquals = true, requireToString = true; + boolean requireToString = true; for (JCTree def : tree.defs) { if (def.getTag() == METHODDEF) { @@ -1173,64 +1173,25 @@ private void addValueMembers(JCClassDecl tree, Env env) { && !methodDecl.sym.type.isErroneous() && (methodDecl.sym.flags() & STATIC) == 0) { final List parameterTypes = methodDecl.sym.type.getParameterTypes(); - switch (parameterTypes.size()) { - case 0: - String name = methodDecl.name.toString(); - if (name.equals("hashCode")) - requireHashCode = false; - else if (name.equals("toString")) - requireToString = false; - break; - case 1: - name = methodDecl.name.toString(); - if (name.equals("equals") && parameterTypes.head.tsym == syms.objectType.tsym) - requireEquals = false; - break; + if (parameterTypes.size() == 0) { + String name = methodDecl.name.toString(); + if (name.equals("toString")) { + requireToString = false; + } } } } } - make.at(tree.pos); - // Make a body comprising { throw new RuntimeException(""Internal error: This method must have been replaced by javac"); } - JCBlock body = make.Block(Flags.SYNTHETIC, List.of(make.Throw( - make.NewClass(null, - null, - make.Ident(names.fromString("RuntimeException")), - List.of(make.Literal(CLASS, "Internal error: This method must have been replaced by javac")), - null)))); - - if (requireHashCode) { - // public int hashCode() { throw new RuntimeException(message); } - JCMethodDecl hashCode = make. - MethodDef(make.Modifiers(Flags.PUBLIC | Flags.FINAL), - names.hashCode, - make.TypeIdent(TypeTag.INT), - List.nil(), - List.nil(), - List.nil(), // thrown - body, - null); - memberEnter.memberEnter(hashCode, env); - tree.defs = tree.defs.append(hashCode); - } - - if (requireEquals) { - // public boolean equals(Object o) { throw new RuntimeException(message); } - JCMethodDecl equals = make. - MethodDef(make.Modifiers(Flags.PUBLIC | Flags.FINAL), - names.equals, - make.TypeIdent(TypeTag.BOOLEAN), - List.nil(), - List.of(make.VarDef(make.Modifiers(PARAMETER), names.fromString("o"), make.Ident(names.fromString("Object")), null )), - List.nil(), // thrown - body, - null); - memberEnter.memberEnter(equals, env); - tree.defs = tree.defs.append(equals); - } - if (requireToString) { + make.at(tree.pos); + // Make a body comprising { throw new RuntimeException(""Internal error: This method must have been replaced by javac"); } + JCBlock body = make.Block(Flags.SYNTHETIC, List.of(make.Throw( + make.NewClass(null, + null, + make.Ident(names.fromString("RuntimeException")), + List.of(make.Literal(CLASS, "Internal error: This method must have been replaced by javac")), + null)))); // public String toString() { throw new RuntimeException(message); } JCMethodDecl toString = make. MethodDef(make.Modifiers(Flags.PUBLIC | Flags.FINAL), diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java index b34b9cc1ce2..02692ea2a3a 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java @@ -1080,29 +1080,14 @@ private int initCode(JCMethodDecl tree, Env env, boolean fatcode) { } private void synthesizeValueMethod(JCMethodDecl methodDecl) { - - Name name; List argTypes; Type resType; - - switch (methodDecl.name.toString()) { - case "hashCode": - name = names.hashCode; - argTypes = List.of(methodDecl.sym.owner.type); - resType = methodDecl.restype.type; - break; - case "equals": - name = names.equals; - argTypes = List.of(methodDecl.sym.owner.type, syms.objectType); - resType = methodDecl.restype.type; - break; - case "toString": - name = names.toString; - argTypes = List.of(methodDecl.sym.owner.type); - resType = methodDecl.restype.type; - break; - default: - throw new AssertionError("Unexpected synthetic method body"); + if (!methodDecl.name.toString().equals("toString")) { + throw new AssertionError("Unexpected synthetic method body"); } + Name name = names.toString; + List argTypes = List.of(methodDecl.sym.owner.type); + Type resType = methodDecl.restype.type; + Type.MethodType indyType = new Type.MethodType(argTypes, resType, List.nil(), @@ -1125,25 +1110,9 @@ private void synthesizeValueMethod(JCMethodDecl methodDecl) { indyType, List.nil().toArray(new LoadableConstant[0])); - - switch (methodDecl.name.toString()) { - case "hashCode": - code.emitop0(aload_0); - items.makeDynamicItem(dynSym).invoke(); - code.emitop0(ireturn); - return; - case "equals": - code.emitop0(aload_0); - code.emitop0(aload_1); - items.makeDynamicItem(dynSym).invoke(); - code.emitop0(ireturn); - return; - case "toString": - code.emitop0(aload_0); - items.makeDynamicItem(dynSym).invoke(); - code.emitop0(areturn); - return; - } + code.emitop0(aload_0); + items.makeDynamicItem(dynSym).invoke(); + code.emitop0(areturn); } public void visitVarDef(JCVariableDecl tree) { diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/ObjectMethods.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/ObjectMethods.java index 087d47144e1..9bd1a63f17e 100644 --- a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/ObjectMethods.java +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/ObjectMethods.java @@ -33,11 +33,11 @@ * java.base/jdk.experimental.value * @library /test/lib * @compile -XDallowWithFieldOperator ObjectMethods.java - * @run main/othervm -Xint -XX:+UseBiasedLocking -XX:+UseCompressedClassPointers runtime.valhalla.inlinetypes.ObjectMethods - * @run main/othervm -Xint -XX:-UseBiasedLocking -XX:-UseCompressedClassPointers runtime.valhalla.inlinetypes.ObjectMethods + * @run main/othervm -Xint -XX:+UseCompressedClassPointers runtime.valhalla.inlinetypes.ObjectMethods + * @run main/othervm -Xint -XX:-UseCompressedClassPointers runtime.valhalla.inlinetypes.ObjectMethods * @run main/othervm -Xint -noverify runtime.valhalla.inlinetypes.ObjectMethods noverify - * @run main/othervm -Xcomp -XX:+UseBiasedLocking -XX:+UseCompressedClassPointers runtime.valhalla.inlinetypes.ObjectMethods - * @run main/othervm -Xcomp -XX:-UseBiasedLocking -XX:-UseCompressedClassPointers runtime.valhalla.inlinetypes.ObjectMethods + * @run main/othervm -Xcomp -XX:+UseCompressedClassPointers runtime.valhalla.inlinetypes.ObjectMethods + * @run main/othervm -Xcomp -XX:-UseCompressedClassPointers runtime.valhalla.inlinetypes.ObjectMethods * @run main/othervm -Xcomp -noverify runtime.valhalla.inlinetypes.ObjectMethods noverify */ @@ -89,8 +89,14 @@ static void checkGetClass(Object val, Class expectedClass) { // Just check we don't crash the VM static void checkHashCodes(Object val, int expectedHashCode) { - if (val.hashCode() != expectedHashCode) { - throw new RuntimeException("Hash code mismatch value: " + val.hashCode() + + int hash = val.hashCode(); + if (hash != expectedHashCode) { + throw new RuntimeException("Hash code mismatch value: " + hash + + " expected: " + expectedHashCode); + } + hash = System.identityHashCode(val); + if (hash != expectedHashCode) { + throw new RuntimeException("Identity hash code mismatch value: " + hash + " expected: " + expectedHashCode); } } diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/UnsafeTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/UnsafeTest.java index d0f656ce6ad..a55695059a1 100644 --- a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/UnsafeTest.java +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/UnsafeTest.java @@ -45,16 +45,9 @@ public class UnsafeTest { static inline class Value1 { Point point; Point[] array; - Value1() { - this.point = Point.createPoint(1, 1); - this.array = new Point[0]; - } - - static Value1 create(Point p, Point... points) { - Value1 o = Value1.default; - o = __WithField(o.point, p); - o = __WithField(o.array, points); - return o; + Value1(Point p, Point... points) { + this.point = p; + this.array = points; } } @@ -62,16 +55,9 @@ static inline class Value2 { int i; Value1 v; - Value2() { - this.i = 0; - this.v = Value1.create(Point.createPoint(0,0), new Point[0]); - } - - static Value2 create(Value1 v, int i) { - Value2 o = Value2.default; - o = __WithField(o.v, v); - o = __WithField(o.i, i); - return o; + Value2(Value1 v, int i) { + this.v = v; + this.i = i; } } @@ -79,26 +65,20 @@ static inline class Value3 { Object o; Value2 v; - Value3() { - this.v = Value2.create(Value1.create(Point.createPoint(0,0), new Point[0]), 0); - this.o = new Object(); + Value3(Value2 v, Object ref) { + this.v = v; + this.o = ref; } - static Value3 create(Value2 v, Object ref) { - Value3 o = Value3.default; - o = __WithField(o.v, v); - o = __WithField(o.o, ref); - return o; - } } public static void main(String[] args) throws Throwable { printValueClass(Value3.class, 0); - Value1 v1 = Value1.create(Point.createPoint(10,10), Point.createPoint(20,20), Point.createPoint(30,30)); - Value2 v2 = Value2.create(v1, 20); - Value3 v3 = Value3.create(v2, List.of("Value3")); + Value1 v1 = new Value1(Point.createPoint(10,10), Point.createPoint(20,20), Point.createPoint(30,30)); + Value2 v2 = new Value2(v1, 20); + Value3 v3 = new Value3(v2, List.of("Value3")); long off_o = U.objectFieldOffset(Value3.class, "o"); long off_v = U.objectFieldOffset(Value3.class, "v"); long off_i = U.objectFieldOffset(Value2.class, "i"); @@ -118,11 +98,12 @@ public static void main(String[] args) throws Throwable { * ^-------------------^ * Value2 */ + List list = List.of("Value1", "Value2", "Value3"); Value3 v = v3; try { v = U.makePrivateBuffer(v); // patch v3.o - U.putObject(v, off_o, List.of("Value1", "Value2", "Value3")); + U.putObject(v, off_o, list); // patch v3.v.i; U.putInt(v, off_v + off_i - U.valueHeaderSize(Value2.class), 999); // patch v3.v.v.point @@ -134,12 +115,12 @@ public static void main(String[] args) throws Throwable { assertEquals(v.v.v.point, Point.createPoint(100, 100)); assertEquals(v.v.i, 999); - assertEquals(v.o, List.of("Value1", "Value2", "Value3")); + assertEquals(v.o, list); assertEquals(v.v.v.array, v1.array); - Value1 nv1 = Value1.create(Point.createPoint(70,70), Point.createPoint(80,80), Point.createPoint(90,90)); - Value2 nv2 = Value2.create(nv1, 100); - Value3 nv3 = Value3.create(nv2, List.of("Value1", "Value2", "Value3")); + Value1 nv1 = new Value1(Point.createPoint(70,70), Point.createPoint(80,80), Point.createPoint(90,90)); + Value2 nv2 = new Value2(nv1, 100); + Value3 nv3 = new Value3(nv2, list); try { v = U.makePrivateBuffer(v); diff --git a/test/jdk/valhalla/valuetypes/ValueBootstrapMethods.java b/test/jdk/valhalla/valuetypes/ValueBootstrapMethods.java index b5d7d501ead..57135bc4dd8 100644 --- a/test/jdk/valhalla/valuetypes/ValueBootstrapMethods.java +++ b/test/jdk/valhalla/valuetypes/ValueBootstrapMethods.java @@ -52,15 +52,17 @@ public class ValueBootstrapMethods { public static void main(String... args) throws Throwable { Class test = valueTestClass(); - Value value = Value.make(10, 5.03, "foo", "bar", "goo"); + Value value = new Value(10, 5.03, "foo", "bar", "goo"); Class valueClass = Value.class; Method hashCode = test.getMethod("hashCode", valueClass); int hash = (int)hashCode.invoke(null, value); + assertEquals(hash, value.localHashCode()); assertEquals(hash, value.hashCode()); Method toString = test.getMethod("toString", valueClass); String s = (String)toString.invoke(null, value); + assertEquals(s, value.localToString()); assertEquals(s, value.toString()); Method equals = test.getMethod("equals", valueClass, Object.class); @@ -70,35 +72,27 @@ public static void main(String... args) throws Throwable { } } - public static final inline class Value { + public static inline class Value { private final int i; private final double d; private final String s; private final List l; - Value() { - this.i = 0; - this.d = 0; - this.s = "default"; - this.l = List.of(); - } - public static Value make(int i, double d, String s, String... items) { - Value v = Value.default; - v = __WithField(v.i, i); - v = __WithField(v.d, d); - v = __WithField(v.s, s); - v = __WithField(v.l, List.of(items)); - return v; + Value(int i, double d, String s, String... items) { + this.i = i; + this.d = d; + this.s = s; + this.l = List.of(items); } List values() { return List.of(Value.class, i, d, s, l); } - public int hashCode() { + public int localHashCode() { return values().hashCode(); } - public String toString() { + public String localToString() { System.out.println(l); return String.format("[%s i=%s d=%s s=%s l=%s]", Value.class.getName(), i, String.valueOf(d), s, l.toString()); diff --git a/test/langtools/tools/javac/valhalla/lworld-values/ValueBootstrapMethodsTest.java b/test/langtools/tools/javac/valhalla/lworld-values/ValueBootstrapMethodsTest.java index af8daf00ee3..77d53fb1f99 100644 --- a/test/langtools/tools/javac/valhalla/lworld-values/ValueBootstrapMethodsTest.java +++ b/test/langtools/tools/javac/valhalla/lworld-values/ValueBootstrapMethodsTest.java @@ -58,6 +58,17 @@ public String localToString() { return String.format("[%s i=%s d=%s s=%s l=%s]", Value.class.getName(), i, String.valueOf(d), s, l.toString()); } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Value) { + Value v = (Value)obj; + return this.i == v.i && this.d == v.d && + Objects.equals(this.s, v.s) && + Objects.equals(this.l, this.l); + } + return false; + } } private static void assertEquals(Object o1, Object expected) { @@ -73,18 +84,24 @@ public static void main(String... args) throws Throwable { assertEquals(value.localHashCode(), value.hashCode()); assertEquals(value.localToString(), value.toString()); - if (!value.equals(value)) { - throw new RuntimeException("expected equals"); + // verify ifacmp and the overridden equals method + + // same instance + if (value != value || !value.equals(value)) { + throw new RuntimeException("expected == and equals"); } + // value and v2 are of different values Value v2 = new Value(20, 5.03, "foo", "bar", "goo"); - if (value.equals(v2)) { - throw new RuntimeException("expected unequals"); + if (value == v2 || value.equals(v2)) { + throw new RuntimeException("expected != and unequals"); } + // v2 and v3 are of different values but Value::equals + // returns true because v2::l and v3::l field contain the same elements Value v3 = new Value(20, 5.03, "foo", "bar", "goo"); - if (!v2.equals(v3)) { - throw new RuntimeException("expected equals"); + if (v2 == v3 || !v2.equals(v3)) { + throw new RuntimeException("expected != and equals"); } } } diff --git a/test/langtools/tools/javac/valhalla/lworld-values/ValueModifierTest.out b/test/langtools/tools/javac/valhalla/lworld-values/ValueModifierTest.out index 45f2aa3d58d..9a169e0d3f1 100644 --- a/test/langtools/tools/javac/valhalla/lworld-values/ValueModifierTest.out +++ b/test/langtools/tools/javac/valhalla/lworld-values/ValueModifierTest.out @@ -3,5 +3,5 @@ ValueModifierTest.java:14:16: compiler.err.empty.value.not.yet ValueModifierTest.java:15:13: compiler.err.cant.inherit.from.final: value ValueModifierTest.java:15:21: compiler.err.empty.value.not.yet ValueModifierTest.java:16:20: compiler.err.cant.inherit.from.final: value -ValueModifierTest.java:16:28: compiler.err.override.meth: (compiler.misc.cant.override: hashCode(), compiler.misc.anonymous.class: ValueModifierTest$3, hashCode(), value), final +ValueModifierTest.java:16:28: compiler.err.override.meth: (compiler.misc.cant.override: toString(), compiler.misc.anonymous.class: ValueModifierTest$3, toString(), value), final 6 errors