Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8244975: [lworld] Investigate if VM can implement Object::hashCode by calling ValueBootstrapMethods for inline types #136

Closed
wants to merge 8 commits into from
@@ -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*/
@@ -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() {
@@ -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; }
@@ -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


@@ -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
@@ -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<MethodHandle> HASHCODE_METHOD_HANDLES = new ClassValue<>() {
@Override protected MethodHandle computeValue(Class<?> type) {
return MethodHandleBuilder.inlineTypeHashCode(type);
}
};

/**
* Returns {@code true} if the arguments are <em>substitutable</em> to each
* other and {@code false} otherwise.
@@ -1163,7 +1163,7 @@ private void addEnumMembers(JCClassDecl tree, Env<AttrContext> env) {
*/
private void addValueMembers(JCClassDecl tree, Env<AttrContext> 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<AttrContext> env) {
&& !methodDecl.sym.type.isErroneous()
&& (methodDecl.sym.flags() & STATIC) == 0) {
final List<Type> 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) {
This conversation was marked as resolved by MrSimms

This comment has been minimized.

@sadayapalam

sadayapalam Aug 10, 2020
Collaborator

The two statements preceding if (requireToString) viz

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))));

should be folded into the if (requireToString) itself for better readability and encapsulation

This comment has been minimized.

@MrSimms

MrSimms Aug 10, 2020
Author Member

Applied comments, thanks !

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),
@@ -1080,29 +1080,14 @@ private int initCode(JCMethodDecl tree, Env<GenContext> env, boolean fatcode) {
}

private void synthesizeValueMethod(JCMethodDecl methodDecl) {

Name name; List<Type> 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<Type> 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) {