diff --git a/make/hotspot/symbols/symbols-unix b/make/hotspot/symbols/symbols-unix index 5c455f023cb..2d5fd38dc25 100644 --- a/make/hotspot/symbols/symbols-unix +++ b/make/hotspot/symbols/symbols-unix @@ -170,6 +170,8 @@ JVM_RegisterContinuationMethods JVM_RegisterSignal JVM_ReleaseUTF JVM_ResumeThread +JVM_ScopedCache +JVM_SetScopedCache JVM_SetArrayElement JVM_SetClassSigners JVM_SetNativeThreadName diff --git a/src/hotspot/share/c1/c1_Compiler.cpp b/src/hotspot/share/c1/c1_Compiler.cpp index 4001db7d9d5..17c83f12f4a 100644 --- a/src/hotspot/share/c1/c1_Compiler.cpp +++ b/src/hotspot/share/c1/c1_Compiler.cpp @@ -152,6 +152,7 @@ bool Compiler::is_intrinsic_supported(const methodHandle& method) { case vmIntrinsics::_isInstance: case vmIntrinsics::_isPrimitive: case vmIntrinsics::_currentThread: + case vmIntrinsics::_scopedCache: case vmIntrinsics::_dabs: case vmIntrinsics::_dsqrt: case vmIntrinsics::_dsin: diff --git a/src/hotspot/share/c1/c1_LIRGenerator.cpp b/src/hotspot/share/c1/c1_LIRGenerator.cpp index c70a4254205..89bf39fd9ae 100644 --- a/src/hotspot/share/c1/c1_LIRGenerator.cpp +++ b/src/hotspot/share/c1/c1_LIRGenerator.cpp @@ -1314,6 +1314,13 @@ void LIRGenerator::do_currentThread(Intrinsic* x) { } +void LIRGenerator::do_scopedCache(Intrinsic* x) { + assert(x->number_of_arguments() == 0, "wrong type"); + LIR_Opr reg = rlock_result(x); + __ move_wide(new LIR_Address(getThreadPointer(), in_bytes(JavaThread::scopedCache_offset()), T_OBJECT), reg); +} + + void LIRGenerator::do_RegisterFinalizer(Intrinsic* x) { assert(x->number_of_arguments() == 1, "wrong type"); LIRItem receiver(x->argument_at(0), this); @@ -3038,6 +3045,7 @@ void LIRGenerator::do_Intrinsic(Intrinsic* x) { case vmIntrinsics::_isPrimitive: do_isPrimitive(x); break; case vmIntrinsics::_getClass: do_getClass(x); break; case vmIntrinsics::_currentThread: do_currentThread(x); break; + case vmIntrinsics::_scopedCache: do_scopedCache(x); break; case vmIntrinsics::_dlog: // fall through case vmIntrinsics::_dlog10: // fall through diff --git a/src/hotspot/share/c1/c1_LIRGenerator.hpp b/src/hotspot/share/c1/c1_LIRGenerator.hpp index 8baad1fd8ea..8569d5554ce 100644 --- a/src/hotspot/share/c1/c1_LIRGenerator.hpp +++ b/src/hotspot/share/c1/c1_LIRGenerator.hpp @@ -253,6 +253,7 @@ class LIRGenerator: public InstructionVisitor, public BlockClosure { void do_isPrimitive(Intrinsic* x); void do_getClass(Intrinsic* x); void do_currentThread(Intrinsic* x); + void do_scopedCache(Intrinsic* x); void do_FmaIntrinsic(Intrinsic* x); void do_MathIntrinsic(Intrinsic* x); void do_LibmIntrinsic(Intrinsic* x); diff --git a/src/hotspot/share/classfile/javaClasses.cpp b/src/hotspot/share/classfile/javaClasses.cpp index a9fa97d45e1..37d31af32e9 100644 --- a/src/hotspot/share/classfile/javaClasses.cpp +++ b/src/hotspot/share/classfile/javaClasses.cpp @@ -4511,6 +4511,8 @@ class UnsafeConstantsFixup : public FieldClosure { mirror->bool_field_put(fd->offset(), _use_unaligned_access); } else if (fd->name() == vmSymbols::data_cache_line_flush_size_name()) { mirror->int_field_put(fd->offset(), _data_cache_line_flush_size); + } else if (fd->name() == vmSymbols::scoped_cache_shift_name()) { + mirror->int_field_put(fd->offset(), ScopedCacheSize ? exact_log2(ScopedCacheSize) : -1); } else { assert(false, "unexpected UnsafeConstants field"); } diff --git a/src/hotspot/share/classfile/vmSymbols.cpp b/src/hotspot/share/classfile/vmSymbols.cpp index e7ac1a40ec5..5915512301a 100644 --- a/src/hotspot/share/classfile/vmSymbols.cpp +++ b/src/hotspot/share/classfile/vmSymbols.cpp @@ -363,6 +363,8 @@ bool vmIntrinsics::preserves_state(vmIntrinsics::ID id) { case vmIntrinsics::_getClass: case vmIntrinsics::_isInstance: case vmIntrinsics::_currentThread: + case vmIntrinsics::_scopedCache: + case vmIntrinsics::_setScopedCache: case vmIntrinsics::_dabs: case vmIntrinsics::_fabs: case vmIntrinsics::_iabs: @@ -411,6 +413,8 @@ bool vmIntrinsics::can_trap(vmIntrinsics::ID id) { case vmIntrinsics::_doubleToRawLongBits: case vmIntrinsics::_longBitsToDouble: case vmIntrinsics::_currentThread: + case vmIntrinsics::_scopedCache: + case vmIntrinsics::_setScopedCache: case vmIntrinsics::_dabs: case vmIntrinsics::_fabs: case vmIntrinsics::_iabs: @@ -578,6 +582,8 @@ bool vmIntrinsics::is_disabled_by_flags(vmIntrinsics::ID id) { case vmIntrinsics::_currentThread: if (!InlineThreadNatives) return true; break; + case vmIntrinsics::_scopedCache: + case vmIntrinsics::_setScopedCache: case vmIntrinsics::_floatToRawIntBits: case vmIntrinsics::_intBitsToFloat: case vmIntrinsics::_doubleToRawLongBits: diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index 8409e641fb7..651f323217b 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -500,6 +500,7 @@ template(big_endian_name, "BIG_ENDIAN") \ template(use_unaligned_access_name, "UNALIGNED_ACCESS") \ template(data_cache_line_flush_size_name, "DATA_CACHE_LINE_FLUSH_SIZE") \ + template(scoped_cache_shift_name, "SCOPED_CACHE_SHIFT") \ \ /* name symbols needed by intrinsics */ \ VM_INTRINSICS_DO(VM_INTRINSIC_IGNORE, VM_SYMBOL_IGNORE, template, VM_SYMBOL_IGNORE, VM_ALIAS_IGNORE) \ @@ -915,8 +916,14 @@ do_name( arraycopy_name, "arraycopy") \ do_signature(arraycopy_signature, "(Ljava/lang/Object;ILjava/lang/Object;II)V") \ do_intrinsic(_currentThread, java_lang_Thread, currentThread_name, currentThread_signature, F_S) \ + do_intrinsic(_scopedCache, java_lang_Thread, scopedCache_name, scopedCache_signature, F_S) \ + do_intrinsic(_setScopedCache, java_lang_Thread, setScopedCache_name, setScopedCache_signature, F_S) \ do_name( currentThread_name, "currentThread0") \ + do_name( scopedCache_name, "scopedCache") \ + do_name( setScopedCache_name, "setScopedCache") \ do_signature(currentThread_signature, "()Ljava/lang/Thread;") \ + do_signature(scopedCache_signature, "()[Ljava/lang/Object;") \ + do_signature(setScopedCache_signature, "([Ljava/lang/Object;)V") \ \ /* reflective intrinsics, for java/lang/Class, etc. */ \ do_intrinsic(_isAssignableFrom, java_lang_Class, isAssignableFrom_name, class_boolean_signature, F_RN) \ diff --git a/src/hotspot/share/include/jvm.h b/src/hotspot/share/include/jvm.h index 419612a51d7..b1e671f318b 100644 --- a/src/hotspot/share/include/jvm.h +++ b/src/hotspot/share/include/jvm.h @@ -213,6 +213,12 @@ JVM_CallStackWalk(JNIEnv *env, jobject stackStream, jlong mode, jint skip_frames, jobject contScope, jobject cont, jint frame_count, jint start_index, jobjectArray frames); +JNIEXPORT jobject JNICALL +JVM_ScopedCache(JNIEnv *env, jclass threadClass); + +JNIEXPORT void JNICALL +JVM_SetScopedCache(JNIEnv *env, jclass threadClass, jobject theCache); + JNIEXPORT jint JNICALL JVM_MoreStackWalk(JNIEnv *env, jobject stackStream, jlong mode, jlong anchor, jint frame_count, jint start_index, diff --git a/src/hotspot/share/opto/c2compiler.cpp b/src/hotspot/share/opto/c2compiler.cpp index 89d1153fce0..19cc5d8fcbd 100644 --- a/src/hotspot/share/opto/c2compiler.cpp +++ b/src/hotspot/share/opto/c2compiler.cpp @@ -582,6 +582,8 @@ bool C2Compiler::is_intrinsic_supported(const methodHandle& method, bool is_virt case vmIntrinsics::_storeFence: case vmIntrinsics::_fullFence: case vmIntrinsics::_currentThread: + case vmIntrinsics::_scopedCache: + case vmIntrinsics::_setScopedCache: #ifdef JFR_HAVE_INTRINSICS case vmIntrinsics::_counterTime: case vmIntrinsics::_getClassId: diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index 15b1622988b..f0851576306 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -258,6 +258,8 @@ class LibraryCallKit : public GraphKit { bool inline_unsafe_writebackSync0(bool is_pre); bool inline_unsafe_copyMemory(); bool inline_native_currentThread(); + bool inline_native_scopedCache(); + bool inline_native_setScopedCache(); bool inline_native_time_funcs(address method, const char* funcName); #ifdef JFR_HAVE_INTRINSICS @@ -758,6 +760,9 @@ bool LibraryCallKit::try_to_inline(int predicate) { case vmIntrinsics::_currentThread: return inline_native_currentThread(); + case vmIntrinsics::_scopedCache: return inline_native_scopedCache(); + case vmIntrinsics::_setScopedCache: return inline_native_setScopedCache(); + #ifdef JFR_HAVE_INTRINSICS case vmIntrinsics::_counterTime: return inline_native_time_funcs(CAST_FROM_FN_PTR(address, JFR_TIME_FUNCTION), "counterTime"); case vmIntrinsics::_getClassId: return inline_native_classID(); @@ -3045,6 +3050,40 @@ bool LibraryCallKit::inline_native_currentThread() { return true; } +//------------------------inline_native_scopedCache------------------ +bool LibraryCallKit::inline_native_scopedCache() { + ciKlass *objects_klass = ciObjArrayKlass::make(env()->Object_klass()); + const TypeOopPtr *etype = TypeOopPtr::make_from_klass(env()->Object_klass()); + + // It might be nice to eliminate the bounds check on the cache array + // by replacing TypeInt::POS here with + // TypeInt::make(ScopedCacheSize*2), but this causes a performance + // regression in some test cases. + const TypeAry* arr0 = TypeAry::make(etype, TypeInt::POS); + bool xk = etype->klass_is_exact(); + + // Because we create the scoped cache lazily we have to make the + // type of the result BotPTR. + const Type* objects_type = TypeAryPtr::make(TypePtr::BotPTR, arr0, objects_klass, xk, 0); + Node* thread = _gvn.transform(new ThreadLocalNode()); + Node* p = basic_plus_adr(top()/*!oop*/, thread, in_bytes(JavaThread::scopedCache_offset())); + Node* threadObj = make_load(NULL, p, objects_type, T_OBJECT, MemNode::unordered); + set_result(threadObj); + + return true; +} + +//------------------------inline_native_setScopedCache------------------ +bool LibraryCallKit::inline_native_setScopedCache() { + Node* arr = argument(0); + Node* thread = _gvn.transform(new ThreadLocalNode()); + Node* p = basic_plus_adr(top()/*!oop*/, thread, in_bytes(JavaThread::scopedCache_offset())); + const TypePtr *adr_type = _gvn.type(p)->isa_ptr(); + store_to_memory(control(), p, arr, T_OBJECT, adr_type, MemNode::unordered); + + return true; +} + //---------------------------load_mirror_from_klass---------------------------- // Given a klass oop, load its java mirror (a java.lang.Class oop). Node* LibraryCallKit::load_mirror_from_klass(Node* klass) { diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index 14516013604..73822fb6980 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -3124,6 +3124,25 @@ JVM_ENTRY(void, JVM_Interrupt(JNIEnv* env, jobject jthread)) } JVM_END +JVM_ENTRY(jobject, JVM_ScopedCache(JNIEnv* env, jclass threadClass)) + JVMWrapper("JVM_ScopedCache"); + oop theCache = thread->_scopedCache; + if (theCache) { + arrayOop objs = arrayOop(theCache); + assert(objs->length() == ScopedCacheSize * 2, "wrong length"); + } + return JNIHandles::make_local(env, theCache); +JVM_END + +JVM_ENTRY(void, JVM_SetScopedCache(JNIEnv* env, jclass threadClass, + jobject theCache)) + JVMWrapper("JVM_SetScopedCache"); + arrayOop objs = arrayOop(JNIHandles::resolve(theCache)); + if (objs != NULL) { + assert(objs->length() == ScopedCacheSize * 2, "wrong length"); + } + thread->_scopedCache = objs; +JVM_END // Return true iff the current thread has locked the object passed in diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp index 5ec111090ad..c15df342d4c 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -2483,6 +2483,10 @@ const size_t minimumSymbolTableSize = 1024; product(bool, UseContinuationStreamingCopy, false, \ "Use streaming memory when copying continuation stack chunks") \ \ + product(intx, ScopedCacheSize, 16, \ + "Size of the cache for scoped values") \ + range(0, max_intx) \ + \ experimental(ccstr, AllocateOldGenAt, NULL, \ "Path to the directoy where a temporary file will be " \ "created to use as the backing store for old generation." \ diff --git a/src/hotspot/share/runtime/thread.cpp b/src/hotspot/share/runtime/thread.cpp index 7099000ecb1..f5de3c7d97f 100644 --- a/src/hotspot/share/runtime/thread.cpp +++ b/src/hotspot/share/runtime/thread.cpp @@ -1733,6 +1733,8 @@ void JavaThread::initialize() { _class_to_be_initialized = NULL; + _scopedCache = NULL; + pd_initialize(); } @@ -3032,6 +3034,8 @@ void JavaThread::oops_do(OopClosure* f, CodeBlobClosure* cf) { if (jvmti_thread_state() != NULL) { jvmti_thread_state()->oops_do(f, cf); } + + f->do_oop(&_scopedCache); } #ifdef ASSERT @@ -5120,3 +5124,9 @@ void Threads::verify() { VMThread* thread = VMThread::vm_thread(); if (thread != NULL) thread->verify(); } + +void JavaThread::allocate_scoped_hash_table(int count) { + if (count > 0) { + _scopedCache = oopFactory::new_objectArray(count, this); + } +} diff --git a/src/hotspot/share/runtime/thread.hpp b/src/hotspot/share/runtime/thread.hpp index d8ba09380f3..ac774b787df 100644 --- a/src/hotspot/share/runtime/thread.hpp +++ b/src/hotspot/share/runtime/thread.hpp @@ -1229,6 +1229,13 @@ class JavaThread: public Thread { friend class ThreadWaitTransition; friend class VM_Exit; +public: + + oop _scopedCache; + jlong _scoped_hash_table_shift; + + void allocate_scoped_hash_table(int count); + void initialize(); // Initialized the instance variables public: @@ -1785,6 +1792,8 @@ class JavaThread: public Thread { void clr_do_not_unlock(void) { _do_not_unlock_if_synchronized = false; } bool do_not_unlock(void) { return _do_not_unlock_if_synchronized; } +static ByteSize scopedCache_offset() { return byte_offset_of(JavaThread, _scopedCache); } + // For assembly stub generation static ByteSize threadObj_offset() { return byte_offset_of(JavaThread, _threadObj); } static ByteSize jni_environment_offset() { return byte_offset_of(JavaThread, _jni_environment); } diff --git a/src/java.base/share/classes/java/lang/Continuation.java b/src/java.base/share/classes/java/lang/Continuation.java index 446f46e923d..eb6b7ec16bc 100644 --- a/src/java.base/share/classes/java/lang/Continuation.java +++ b/src/java.base/share/classes/java/lang/Continuation.java @@ -160,6 +160,8 @@ private static Thread currentCarrierThread() { private int stackWatermark; private int refStackWatermark; + private Object[] scopedCache; + // private long[] nmethods = null; // grows up // private int numNmethods = 0; @@ -292,9 +294,12 @@ private Continuation innermost() { private void mount() { if (!compareAndSetMounted(false, true)) throw new IllegalStateException("Mounted!!!!"); + Thread.setScopedCache(scopedCache); } private void unmount() { + scopedCache = Thread.scopedCache(); + Thread.setScopedCache(null); setMounted(false); } diff --git a/src/java.base/share/classes/java/lang/Scoped.java b/src/java.base/share/classes/java/lang/Scoped.java new file mode 100644 index 00000000000..e2082f195c8 --- /dev/null +++ b/src/java.base/share/classes/java/lang/Scoped.java @@ -0,0 +1,405 @@ +/* + * Copyright (c) 2019, Red Hat, Inc. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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 java.lang; + +import jdk.internal.access.SharedSecrets; +import jdk.internal.misc.Unsafe; +import jdk.internal.org.objectweb.asm.ClassWriter; +import jdk.internal.org.objectweb.asm.FieldVisitor; +import jdk.internal.org.objectweb.asm.MethodVisitor; +import jdk.internal.org.objectweb.asm.Type; +import jdk.internal.reflect.CallerSensitive; +import jdk.internal.reflect.FieldAccessor; +import jdk.internal.reflect.Reflection; +import jdk.internal.vm.annotation.ForceInline; +import static jdk.internal.misc.UnsafeConstants.SCOPED_CACHE_SHIFT; +import static jdk.internal.org.objectweb.asm.Opcodes.*; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.security.ProtectionDomain; +import java.util.concurrent.Callable; +import java.util.function.Supplier; + +/** + * TBD + */ +public abstract class Scoped { + + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + + private static final boolean USE_CACHE = Cache.INDEX_BITS > 0; + + private static final boolean DEBUG + = System.getProperty("java.lang.Scoped.DEBUG") != null + && System.getProperty("java.lang.Scoped.DEBUG").equals("true"); + + private static int nextKey = 0xf0f0_f0f0; + + @ForceInline + @SuppressWarnings("unchecked") // one map has entries for all types + static final Object getObject(int hash, Scoped key) { + Object[] objects; + if (USE_CACHE && (objects = Thread.scopedCache()) != null) { + // This code should perhaps be in class Cache. We do it + // here because the generated code is small and fast and + // we really want it to be inlined in the caller. + int n = (hash & Cache.TABLE_MASK) * 2; + if (objects[n] == key) { + return objects[n + 1]; + } + n = ((hash >>> Cache.INDEX_BITS) & Cache.TABLE_MASK) * 2; + if (objects[n] == key) { + return objects[n + 1]; + } + } + return key.slowGet(Thread.currentThread()); + } + + /** + * TBD + * + * @param t TBD + * @param chain TBD + * @return TBD + */ + @SuppressWarnings(value = {"unchecked", "rawtypes"}) + // one map has entries for all types + public ScopedBinding bind(T t) { + if (t != null && ! getType().isInstance(t)) + throw new ClassCastException(ScopedBinding.cannotBindMsg(t, getType())); + var map = Thread.currentThread().scopedMap(); + Object previousMapping = map.put(hashCode(), this, t); + var b = new ScopedBinding(this, t, previousMapping); + Cache.update(this, t); + + return b; + } + + /** + * TBD + * + * @param TBD + * @param klass TBD + * @return TBD + */ + @SuppressWarnings("unchecked") // one map has entries for all types + @CallerSensitive + public static Scoped forType(Class klass) { + Class caller = Reflection.getCallerClass(); + return (Scoped) writeClass(klass, generateKey(), caller, Scoped.class); + } + + /** + * TBD + * + * @param TBD + * @param klass TBD + * @return TBD + */ + @SuppressWarnings("unchecked") // one map has entries for all types + @CallerSensitive + public static Scoped finalForType(Class klass) { + Class caller = Reflection.getCallerClass(); + return (Scoped) writeClass(klass, generateKey(), caller, ScopedFinal.class); + } + + /** + * TBD + * + * @return TBD + */ + public abstract T get(); + + abstract Class getType(); + + final void release(Object prev) { + var map = Thread.currentThread().scopedMap(); + if (prev != ScopedMap.NULL_PLACEHOLDER) { + map.put(hashCode(), this, prev); + } else { + map.remove(hashCode(), this); + } + Cache.remove(this); + } + + /** + * TBD + * + * @return TBD + */ + public boolean isBound() { + var hash = hashCode(); + Object[] objects; + if (USE_CACHE && (objects = Thread.scopedCache()) != null) { + int n = (hash & Cache.TABLE_MASK) * 2; + if (objects[n] == this) { + return true; + } + n = ((hash >>> Cache.INDEX_BITS) & Cache.TABLE_MASK) * 2; + if (objects[n] == this) { + return true; + } + } + + var value = Thread.currentThread().scopedMap().get(hashCode(), this); + + if (value == ScopedMap.NULL_PLACEHOLDER) + return false; + + return true; + } + + @SuppressWarnings("unchecked") // one map has entries for all types + private T slowGet(Thread thread) { + var value = Thread.currentThread().scopedMap().get(hashCode(), this); + + if (value == ScopedMap.NULL_PLACEHOLDER) + throw new UnboundScopedException("Scoped<" + getType().getName() + "> is not bound"); + + if (USE_CACHE) { + Cache.put(thread, this, value); + } + + return (T) value; + } + + // A Marsaglia xor-shift generator used to generate hashes. + private static synchronized int generateKey() { + int x = nextKey; + do { + x ^= x >>> 12; + x ^= x << 9; + x ^= x >>> 23; + } while (USE_CACHE && ((x & Cache.TABLE_MASK) == ((x >>> Cache.INDEX_BITS) + & Cache.TABLE_MASK))); + return (nextKey = x); + } + + private static long sequenceNumber = 0; + + @SuppressWarnings("unchecked") // one map has entries for all types + private static Scoped writeClass(Class klass, int hashKey, Class caller, + Class superClass) { + long seq; + synchronized (Scoped.class) { + seq = sequenceNumber++; + } + String superClassName = superClass.getName().replace(".", "/"); + String className = superClassName + "$" + seq; + + ClassWriter cw = new ClassWriter(0); + cw.visit(V11, ACC_PUBLIC | ACC_SUPER | ACC_FINAL, className, null, superClassName, null); + + { + FieldVisitor fv = cw.visitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL, "boundClass", + "Ljava/lang/Class;", null, null); + } + { + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); + + mv.visitCode(); + mv.visitIntInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, superClassName, "", "()V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + } + { + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "hashCode", "()I", null, null); + + mv.visitCode(); + mv.visitLdcInsn(hashKey); + mv.visitInsn(IRETURN); + mv.visitMaxs(2, 1); + mv.visitEnd(); + + } + { + MethodVisitor mv = cw.visitMethod(ACC_FINAL, "getType", "()Ljava/lang/Class;", + null, null); + + mv.visitCode(); + mv.visitFieldInsn(GETSTATIC, className, "boundClass", "Ljava/lang/Class;"); + mv.visitInsn(ARETURN); + mv.visitMaxs(2, 1); + mv.visitEnd(); + + } + { + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_FINAL, "get", + // "()" + klass.descriptorString(), + "()Ljava/lang/Object;", + null, null); + + mv.visitCode(); + mv.visitLdcInsn(hashKey); + mv.visitIntInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Scoped", "getObject", "(ILjava/lang/Scoped;)Ljava/lang/Object;", false); + // mv.visitTypeInsn(CHECKCAST, klass.getName().replace('.', '/')); + mv.visitInsn(ARETURN); + mv.visitMaxs(3, 1); + mv.visitEnd(); + } + cw.visitEnd(); + + byte[] bytes = cw.toByteArray(); + + if (DEBUG) { + try { + FileOutputStream out = new FileOutputStream("/tmp/sample/" + + className.replace('.', '/') + ".class"); + out.write(bytes); + out.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + ClassLoader cl = caller.getClass().getClassLoader(); + ProtectionDomain pd = (cl != null) ? Scoped.class.getProtectionDomain() : null; + Class c = SharedSecrets.getJavaLangAccess().defineClass(cl, className, bytes, + pd, "Scoped_forType"); + + try { + Field f = c.getDeclaredField("boundClass"); + Object base = UNSAFE.staticFieldBase(f); + long offset = UNSAFE.staticFieldOffset(f); + UNSAFE.putReference(base, offset, klass); + + Scoped singleton = (Scoped) UNSAFE.allocateInstance(c); + if (singleton.getType() != klass) { + throw new Error("wrong class in Scoped"); + } + return singleton; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static class Cache { + static final int INDEX_BITS = SCOPED_CACHE_SHIFT; + + static final int TABLE_SIZE = 1 << INDEX_BITS; + + static final int TABLE_MASK = TABLE_SIZE - 1; + + static void put(Thread t, Scoped key, Object value) { + if (Thread.scopedCache() == null) { + Thread.setScopedCache(new Object[TABLE_SIZE * 2]); + } + setKeyAndObjectAt(chooseVictim(t, key.hashCode()), key, value); + } + + private static final void update(Object key, Object value) { + Object[] objects; + if (USE_CACHE && (objects = Thread.scopedCache()) != null) { + + int k1 = key.hashCode() & TABLE_MASK; + if (getKey(objects, k1) == key) { + setKeyAndObjectAt(k1, key, value); + } + int k2 = (key.hashCode() >> INDEX_BITS) & TABLE_MASK; + if (getKey(objects, k2) == key) { + setKeyAndObjectAt(k2, key, value); + } + } + } + + private static final void remove(Object key) { + Object[] objects; + if (USE_CACHE && (objects = Thread.scopedCache()) != null) { + + int k1 = key.hashCode() & TABLE_MASK; + if (getKey(objects, k1) == key) { + setKeyAndObjectAt(k1, null, null); + } + int k2 = (key.hashCode() >> INDEX_BITS) & TABLE_MASK; + if (getKey(objects, k2) == key) { + setKeyAndObjectAt(k2, null, null); + } + } + } + + private static void setKeyAndObjectAt(int n, Object key, Object value) { + Thread.scopedCache()[n * 2] = key; + Thread.scopedCache()[n * 2 + 1] = value; + } + + private static Object getKey(Object[] objs, long hash) { + int n = (int) (hash & TABLE_MASK); + return objs[n * 2]; + } + + private static void setKey(Object[] objs, long hash, Object key) { + int n = (int) (hash & TABLE_MASK); + objs[n * 2] = key; + } + + @SuppressWarnings("unchecked") // one map has entries for all types + final Object getKey(int n) { + return Thread.scopedCache()[n * 2]; + } + + @SuppressWarnings("unchecked") // one map has entries for all types + private static Object getObject(int n) { + return Thread.scopedCache()[n * 2 + 1]; + } + + private static int chooseVictim(Thread thread, int hash) { + // Update the cache to replace one entry with the value we just looked up. + // Each value can be in one of two possible places in the cache. + // Pick a victim at (pseudo-)random. + int k1 = hash & TABLE_MASK; + int k2 = (hash >> INDEX_BITS) & TABLE_MASK; + int tmp = thread.victims; + thread.victims = (tmp << 31) | (tmp >>> 1); + return (tmp & 1) == 0 ? k1 : k2; + } + } + +} + +abstract class ScopedFinal extends Scoped { + /** + * TBD + * + * @param t TBD + * @param chain TBD + * @return TBD + */ + @SuppressWarnings(value = {"unchecked", "rawtypes"}) + // one map has entries for all types + public ScopedBinding bind(T t) { + if (isBound()) { + throw new ScopedAlreadyBoundException("Scoped<" + getType().getName() + + "> is already bound"); + } + return super.bind(t); + } +} diff --git a/src/java.base/share/classes/java/lang/ScopedAlreadyBoundException.java b/src/java.base/share/classes/java/lang/ScopedAlreadyBoundException.java new file mode 100644 index 00000000000..93a27110b20 --- /dev/null +++ b/src/java.base/share/classes/java/lang/ScopedAlreadyBoundException.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019, Red Hat, Inc. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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 java.lang; + +/** + * TBD + */ +public class ScopedAlreadyBoundException extends RuntimeException { + @java.io.Serial + static final long serialVersionUID = -9106475100367583963L; + + /** + * Constructs a new exception with the specified detail message. + * @param message the detail message. The detail message is saved for + * later retrieval by the {@link #getMessage()} method. + */ + public ScopedAlreadyBoundException(String message) { + super(message); + } +} diff --git a/src/java.base/share/classes/java/lang/ScopedBinding.java b/src/java.base/share/classes/java/lang/ScopedBinding.java new file mode 100644 index 00000000000..158a00af7bc --- /dev/null +++ b/src/java.base/share/classes/java/lang/ScopedBinding.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2018, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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 java.lang; + +import java.io.*; +import jdk.internal.misc.UnsafeConstants; + +/** + * TBD + */ +public final class ScopedBinding + implements AutoCloseable { + + final Scoped referent; + + final Object prev; + + static String cannotBindMsg(Object obj, Class klass) { + return "Cannot bind " + obj.getClass().getName() + " to " + klass.getName(); + } + + /** + * TBD + * @param v TBD + * @param t TBD + * @param prev TBD + */ + ScopedBinding(Scoped v, Object t, Object prev) { + if (t != null && !v.getType().isInstance(t)) + throw new ClassCastException(cannotBindMsg(t, v.getType())); + referent = v; + this.prev = prev; + } + + /** + * TBD + */ + public final void close() { + referent.release(prev); + } +} diff --git a/src/java.base/share/classes/java/lang/ScopedMap.java b/src/java.base/share/classes/java/lang/ScopedMap.java new file mode 100644 index 00000000000..47e0c625118 --- /dev/null +++ b/src/java.base/share/classes/java/lang/ScopedMap.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 1994, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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 java.lang; + +class ScopedMap { + + Object[] tab = new Object[16]; + + int size; + + static final Object NULL_PLACEHOLDER = new Object(); + + private static final int MAXIMUM_CAPACITY = 1 << 29; + + /** + * Circularly traverses table of size len. + */ + private static int nextKeyIndex(int i, int len) { + return (i + 2 < len ? i + 2 : 0); + } + + private static int hash(Scoped key, int len) { + return hash(key.hashCode(), len); + } + + private static int hash(long k, int len) { + k <<= 1; + return (int)k & (len - 1); + } + + @SuppressWarnings("unchecked") + public Object get(long k, Scoped key) { + int len = tab.length; + int i = hash(k, len); + while (true) { + Object item = tab[i]; + if (item == key) + return tab[i + 1]; + if (item == null) + return NULL_PLACEHOLDER;; + i = nextKeyIndex(i, len); + } + } + + @SuppressWarnings(value = {"unchecked", "rawtypes"}) // one map has entries for all types + public Object put(long k, Scoped key, Object value) { + + retryAfterResize: for (;;) { + final int len = tab.length; + int i = hash(k, len); + + for (Object item; (item = tab[i]) != null; + i = nextKeyIndex(i, len)) { + if (item == key) { + @SuppressWarnings("unchecked") + Object oldValue = tab[i + 1]; + tab[i + 1] = value; + return oldValue; + } + } + + final int s = size + 1; + // Use optimized form of 3 * s. + // Next capacity is len, 2 * current capacity. + if (s + (s << 1) > len && resize(len)) + continue retryAfterResize; + + tab[i] = key; + tab[i + 1] = value; + size = s; + return NULL_PLACEHOLDER; + } + } + + Object remove(long k, Object key) { + int len = tab.length; + int i = hash(k, len); + + while (true) { + Object item = tab[i]; + if (item == key) { + size--; + Object oldValue = tab[i + 1]; + tab[i] = null; + tab[i + 1] = null; + closeDeletion(i); + return oldValue; + } + if (item == null) + throw new RuntimeException("not bound"); + i = nextKeyIndex(i, len); + } + } + + private void closeDeletion(int d) { + // Adapted from Knuth Section 6.4 Algorithm R + int len = tab.length; + + // Look for items to swap into newly vacated slot + // starting at index immediately following deletion, + // and continuing until a null slot is seen, indicating + // the end of a run of possibly-colliding keys. + Object item; + for (int i = nextKeyIndex(d, len); (item = tab[i]) != null; + i = nextKeyIndex(i, len) ) { + // The following test triggers if the item at slot i (which + // hashes to be at slot r) should take the spot vacated by d. + // If so, we swap it in, and then continue with d now at the + // newly vacated i. This process will terminate when we hit + // the null slot at the end of this run. + // The test is messy because we are using a circular table. + int r = hash((Scoped)tab[i], len); + if ((i < r && (r <= d || d <= i)) || (r <= d && d <= i)) { + tab[d] = item; + tab[d + 1] = tab[i + 1]; + tab[i] = null; + tab[i + 1] = null; + d = i; + } + } + } + + + private boolean resize(int newCapacity) { + assert (newCapacity & -newCapacity) == newCapacity; // power of 2 + int newLength = newCapacity * 2; + + final Object[] oldTable = tab; + int oldLength = oldTable.length; + if (oldLength == 2 * MAXIMUM_CAPACITY) { // can't expand any further + if (size == MAXIMUM_CAPACITY - 1) + throw new IllegalStateException("Capacity exhausted."); + return false; + } + if (oldLength >= newLength) + return false; + + Object[] newTable = new Object[newLength]; + + for (int j = 0; j < oldLength; j += 2) { + Object key = oldTable[j]; + if (key != null) { + Object value = oldTable[j+1]; + oldTable[j] = null; + oldTable[j + 1] = null; + int i = hash((Scoped)key, newLength); + while (newTable[i] != null) + i = nextKeyIndex(i, newLength); + newTable[i] = key; + newTable[i + 1] = value; + } + } + tab = newTable; + return true; + } +} \ No newline at end of file diff --git a/src/java.base/share/classes/java/lang/Thread.java b/src/java.base/share/classes/java/lang/Thread.java index ade5ecee497..fc37ed8cf11 100644 --- a/src/java.base/share/classes/java/lang/Thread.java +++ b/src/java.base/share/classes/java/lang/Thread.java @@ -270,8 +270,41 @@ static Thread currentCarrierThread() { return currentThread0(); } + // Scoped support: + + /** + * TBD + * @return TBD + */ + @HotSpotIntrinsicCandidate + static native Object[] scopedCache(); + @HotSpotIntrinsicCandidate - private static native Thread currentThread0(); + static native void setScopedCache(Object[] cache); + + // A simple (not very) random string of bits to use when evicting + // cache entries. + int victims + = 0b1100_1001_0000_1111_1101_1010_1010_0010; + + private ScopedMap scopedMap; + + final ScopedMap scopedMap() { + var map = scopedMap; + if (map == null) { + map = scopedMap = new ScopedMap(); + } + return map; + } + + // end Scoped support + + /** + * TBD + * @return TBD + */ + @HotSpotIntrinsicCandidate + static native Thread currentThread0(); /** * A hint to the scheduler that the current thread is willing to yield @@ -2986,6 +3019,11 @@ private static AccessControlContext accessControlContext() { /** Secondary seed isolated from public ThreadLocalRandom sequence */ int threadLocalRandomSecondarySeed; + /** + * TBD + */ + public Object userObject; + /* Some private helper methods */ private native void setPriority0(int newPriority); private native void stop0(Object o); diff --git a/src/java.base/share/classes/java/lang/UnboundScopedException.java b/src/java.base/share/classes/java/lang/UnboundScopedException.java new file mode 100644 index 00000000000..0a1ce1eda6b --- /dev/null +++ b/src/java.base/share/classes/java/lang/UnboundScopedException.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019, Red Hat, Inc. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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 java.lang; + +/** + * TBD + */ +public class UnboundScopedException extends RuntimeException { + @java.io.Serial + static final long serialVersionUID = 5971360953913194977L; + + /** + * Constructs a new exception with the specified detail message. + * @param message the detail message. The detail message is saved for + * later retrieval by the {@link #getMessage()} method. + */ + public UnboundScopedException(String message) { + super(message); + } +} diff --git a/src/java.base/share/classes/jdk/internal/misc/UnsafeConstants.java b/src/java.base/share/classes/jdk/internal/misc/UnsafeConstants.java index 65ee7b3a616..ec4e4dd5083 100644 --- a/src/java.base/share/classes/jdk/internal/misc/UnsafeConstants.java +++ b/src/java.base/share/classes/jdk/internal/misc/UnsafeConstants.java @@ -45,7 +45,7 @@ * any class that uses them. */ -final class UnsafeConstants { +public final class UnsafeConstants { /** * This constructor is private because the class is not meant to @@ -112,11 +112,21 @@ private UnsafeConstants() {} static final int DATA_CACHE_LINE_FLUSH_SIZE; + /** + * The exact log (base 2) of the number of lines in the Scoped cache. + * + * @implNote + * The actual value for this field is injected by the JVM. + */ + + public static final int SCOPED_CACHE_SHIFT; + static { ADDRESS_SIZE0 = 0; PAGE_SIZE = 0; BIG_ENDIAN = false; UNALIGNED_ACCESS = false; DATA_CACHE_LINE_FLUSH_SIZE = 0; + SCOPED_CACHE_SHIFT = -1; } } diff --git a/src/java.base/share/native/libjava/Thread.c b/src/java.base/share/native/libjava/Thread.c index 015de4e3ca3..97cf349648e 100644 --- a/src/java.base/share/native/libjava/Thread.c +++ b/src/java.base/share/native/libjava/Thread.c @@ -55,6 +55,8 @@ static JNINativeMethod methods[] = { {"getThreads", "()[" THD, (void *)&JVM_GetAllThreads}, {"dumpThreads", "([" THD ")[[" STE, (void *)&JVM_DumpThreads}, {"setNativeName", "(" STR ")V", (void *)&JVM_SetNativeThreadName}, + {"scopedCache", "()[" OBJ, (void *)&JVM_ScopedCache}, + {"setScopedCache", "([" OBJ ")V", (void *)&JVM_SetScopedCache}, }; #undef THD