changeset 53279:93c4a7310ed2 cont

Add continuation critical sections
author rpressler
date Tue, 08 Jan 2019 14:53:21 +0000
parents 44dbe0605a43
children d106b8016296 70c7313e066e
files src/hotspot/share/classfile/javaClasses.cpp src/hotspot/share/classfile/javaClasses.hpp src/hotspot/share/classfile/javaClasses.inline.hpp src/hotspot/share/classfile/vmSymbols.hpp src/hotspot/share/runtime/continuation.cpp src/java.base/share/classes/java/lang/Continuation.java test/jdk/java/lang/Continuation/Basic.java
diffstat 7 files changed, 220 insertions(+), 43 deletions(-) [+]
line wrap: on
line diff
--- a/src/hotspot/share/classfile/javaClasses.cpp	Mon Jan 07 14:25:44 2019 +0000
+++ b/src/hotspot/share/classfile/javaClasses.cpp	Tue Jan 08 14:53:21 2019 +0000
@@ -2402,8 +2402,10 @@
       if (cont != NULL && Continuation::is_continuation_entry_frame(fr, &map)) {
         oop scope = java_lang_Continuation::scope(cont);
         if (contScope.not_null() && oopDesc::equals(scope, contScope())) {
-          assert (Continuation::is_frame_in_continuation(fr, cont), "must be"); // assert (Continuation::is_scope_bottom(contScope(), fr, &map), "must be");
+          assert (Continuation::is_scope_bottom(contScope(), fr, &map), "must be");
           is_last = true;
+        } else {
+          assert (Continuation::is_frame_in_continuation(fr, cont), "must be");
         }
         cont = java_lang_Continuation::parent(cont);
       } else {
@@ -4102,6 +4104,7 @@
 int java_lang_Continuation::_sp_offset;
 int java_lang_Continuation::_pc_offset;
 int java_lang_Continuation::_refSP_offset;
+int java_lang_Continuation::_cs_offset;
 int java_lang_Continuation::_flags_offset;
 int java_lang_Continuation::_reset_offset;
 int java_lang_ClassLoader::parent_offset;
@@ -4297,6 +4300,7 @@
   macro(_pc_offset,        k, vmSymbols::pc_name(),        long_signature,              false); \
   macro(_refSP_offset,     k, vmSymbols::refSP_name(),     int_signature,               false); \
   macro(_flags_offset,     k, vmSymbols::flags_name(),     byte_signature,              false); \
+  macro(_cs_offset,        k, vmSymbols::cs_name(),        short_signature,             false); \
   macro(_reset_offset,     k, vmSymbols::reset_name(),     bool_signature,              false); \
   macro(_numFrames_offset, k, vmSymbols::numFrames_name(), short_signature,             false); \
   macro(_numInterpretedFrames_offset, k, vmSymbols::numInterpretedFrames_name(), short_signature, false);
--- a/src/hotspot/share/classfile/javaClasses.hpp	Mon Jan 07 14:25:44 2019 +0000
+++ b/src/hotspot/share/classfile/javaClasses.hpp	Tue Jan 08 14:53:21 2019 +0000
@@ -985,6 +985,7 @@
   static int _sp_offset;
   static int _pc_offset;
   static int _refSP_offset;
+  static int _cs_offset;
   static int _flags_offset;
   static int _reset_offset;
 
@@ -1026,6 +1027,7 @@
   static inline int stack_size(oop ref);
   static inline void* stack_base(oop ref);
   static inline HeapWord* refStack_base(oop ref);
+  static inline jshort cs(oop ref);
   static bool on_local_stack(oop ref, address adr);
   static bool is_reset(oop ref);
 };
--- a/src/hotspot/share/classfile/javaClasses.inline.hpp	Mon Jan 07 14:25:44 2019 +0000
+++ b/src/hotspot/share/classfile/javaClasses.inline.hpp	Tue Jan 08 14:53:21 2019 +0000
@@ -222,6 +222,9 @@
 inline void java_lang_Continuation::set_flags(oop ref, unsigned char flags) {
   ref->byte_field_put(_flags_offset, (jbyte)flags);
 }
+inline jshort java_lang_Continuation::cs(oop ref) {
+  return ref->short_field(_cs_offset);
+}
 inline bool java_lang_Continuation::is_reset(oop ref) {
   return ref->bool_field(_reset_offset);
 }
--- a/src/hotspot/share/classfile/vmSymbols.hpp	Mon Jan 07 14:25:44 2019 +0000
+++ b/src/hotspot/share/classfile/vmSymbols.hpp	Tue Jan 08 14:53:21 2019 +0000
@@ -395,6 +395,7 @@
   template(fp_name,                                   "fp")                                       \
   template(sp_name,                                   "sp")                                       \
   template(pc_name,                                   "pc")                                       \
+  template(cs_name,                                   "cs")                                       \
   template(refStack_name,                             "refStack")                                 \
   template(refSP_name,                                "refSP")                                    \
   template(get_name,                                  "get")                                      \
--- a/src/hotspot/share/runtime/continuation.cpp	Mon Jan 07 14:25:44 2019 +0000
+++ b/src/hotspot/share/runtime/continuation.cpp	Tue Jan 08 14:53:21 2019 +0000
@@ -356,9 +356,10 @@
 // freeze result
 enum res_freeze {
   freeze_ok = 0,
-  freeze_pinned_native = 1,
-  freeze_pinned_monitor = 2,
-  freeze_exception = 3
+  freeze_pinned_cs = 1,
+  freeze_pinned_native = 2,
+  freeze_pinned_monitor = 3,
+  freeze_exception = 4
 };
 
 // struct oopLoc {
@@ -1435,11 +1436,11 @@
   return f.is_interpreted_frame() ? interpreted_frame_bottom(f) : compiled_frame_bottom(f);
 }
 
-static bool is_interpreted_frame_owning_locks(frame& f) {
+static bool is_interpreted_frame_owning_locks(const frame& f) {
   return f.interpreter_frame_monitor_end() < f.interpreter_frame_monitor_begin();
 }
 
-static bool is_compiled_frame_owning_locks(JavaThread* thread, RegisterMap* map, frame& f) {
+static bool is_compiled_frame_owning_locks(JavaThread* thread, const RegisterMap* map, const frame& f) {
   if (!DetectLocksInCompiledFrames)
     return false;
   nmethod* nm = f.cb()->as_nmethod();
@@ -2170,6 +2171,11 @@
   log_trace(jvmcont)("Freeze 1111 sp: %d fp: 0x%lx pc: " INTPTR_FORMAT, cont.sp(), cont.fp(), p2i(cont.pc()));
 #endif
 
+  if (java_lang_Continuation::cs(oopCont) > 0) {
+    log_debug(jvmcont)("PINNED due to critical section");
+    return freeze_pinned_cs;
+  }
+
   intptr_t* bottom = cont.entrySP(); // (bottom is highest address; stacks grow down)
   intptr_t* top = f.sp();
 
@@ -2193,6 +2199,7 @@
 
   cont.set_flag(FLAG_SAFEPOINT_YIELD, safepoint_yield);
 
+  cont.set_entryPC(0);
   cont.write();
 
   // notify JVMTI
@@ -2338,10 +2345,66 @@
   return freeze0(thread, fi, false);
 JRT_END
 
+static res_freeze is_pinned(const frame& f, const RegisterMap* map) {
+  if (f.is_interpreted_frame()) {
+    if (is_interpreted_frame_owning_locks(f)) 
+      return freeze_pinned_monitor;
+  } else if (f.is_compiled_frame()) {
+    if (is_compiled_frame_owning_locks(map->thread(), map, f)) 
+      return freeze_pinned_monitor;
+  } else {
+    return freeze_pinned_native;
+  }
+  return freeze_ok;
+}
+
+static res_freeze is_pinned0(JavaThread* thread, oop cont_scope, bool safepoint_yield) {
+  oop cont = get_continuation(thread);
+  if (cont == (oop)NULL) {
+    return freeze_ok;
+  }
+  if (java_lang_Continuation::cs(cont) > 0)
+    return freeze_pinned_cs;
+
+  RegisterMap map(thread, false, false, false); // should first argument be true?
+  map.set_include_argument_oops(false);
+  frame f = thread->last_frame();
+
+  if (!safepoint_yield) {
+    f = f.frame_sender<ContinuationCodeBlobLookup>(&map); // LOOKUP // this is the yield frame
+  } else { // safepoint yield
+    f.set_fp(f.real_fp()); // Instead of this, maybe in ContMirror::set_last_frame always use the real_fp?
+    if (!Interpreter::contains(f.pc())) {
+      assert (f.cb()->is_safepoint_stub(), "must be");
+      assert (f.oop_map() != NULL, "must be");
+      f.oop_map()->update_register_map(&f, &map); // we have callee-save registers in this case
+    }
+  }
+
+  while(true) {
+    res_freeze res = is_pinned(f, &map);
+    if (res != freeze_ok)
+      return res;
+    
+    f = f.frame_sender<ContinuationCodeBlobLookup>(&map);
+    if (!Continuation::is_frame_in_continuation(f, cont)) {
+      oop scope = java_lang_Continuation::scope(cont);
+      if (oopDesc::equals(scope, cont_scope))
+        break; 
+      cont = java_lang_Continuation::parent(cont);
+      if (cont == (oop)NULL)
+        break;
+      if (java_lang_Continuation::cs(cont) > 0)
+        return freeze_pinned_cs;
+    }
+  }
+  return freeze_ok;
+}
+
 typedef int (*DoYieldStub)(int scopes);
 
 // called in a safepoint
-int Continuation::try_force_yield(JavaThread* thread, oop cont) {
+int Continuation::try_force_yield(JavaThread* thread, const oop cont) {
   // this is the only place where we traverse the continuatuion hierarchy in native code, as it needs to be done in a safepoint
   oop scope = NULL;
   oop innermost = get_continuation(thread);
@@ -2359,10 +2422,9 @@
   }
   if (!oopDesc::equals(innermost, cont)) { // we have nested continuations
     // make sure none of the continuations in the hierarchy are pinned
-    // for (oop c = innermost; c != NULL && !oopDesc::equals(c, cont); c = java_lang_Continuation::parent(c)) {
-    //   if ()
-    //     return PINNED;
-    // }
+    res_freeze res_pinned = is_pinned0(thread, java_lang_Continuation::scope(cont), true);
+    if (res_pinned != freeze_ok)
+      return res_pinned;
 
     java_lang_Continuation::set_yieldInfo(cont, scope);
   }
@@ -3044,7 +3106,7 @@
 // }
 
 bool Continuation::is_frame_in_continuation(const frame& f, oop cont) {
-  return java_lang_Continuation::entrySP(cont) < f.sp();
+  return java_lang_Continuation::entrySP(cont) >= f.sp();
 }
 
 bool Continuation::is_frame_in_continuation(JavaThread* thread, const frame& f) {
@@ -3065,7 +3127,6 @@
 
   oop sc = continuation_scope(cont);
   assert(sc != NULL, "");
-
   return oopDesc::equals(sc, cont_scope);
 }
 
@@ -3108,7 +3169,7 @@
 }
 
 static frame continuation_parent_frame(ContMirror& cont, RegisterMap* map) {
-  assert (map->thread() != NULL || cont.entryPC() == NULL, "");
+  assert (map->thread() != NULL || cont.entryPC() == NULL, "map->thread() == NULL: %d cont.entryPC() == NULL: %d", map->thread() == NULL, cont.entryPC() == NULL);
 
   // if (map->thread() == NULL) { // When a continuation is mounted, its entry frame is always on the v-stack
   //   oop parentOop = java_lang_Continuation::parent(cont.mirror());
@@ -3479,11 +3540,18 @@
 }
 JVM_END
 
+JVM_ENTRY(jint, CONT_isPinned0(JNIEnv* env, jobject cont_scope)) {
+  JavaThread* thread = JavaThread::thread_from_jni_environment(env);
+  return is_pinned0(thread, JNIHandles::resolve(cont_scope), false);
+}
+JVM_END
+
 JVM_ENTRY(jint, CONT_TryForceYield0(JNIEnv* env, jobject jcont, jobject jthread)) {
   JavaThread* thread = JavaThread::thread_from_jni_environment(env);
 
-  guarantee(ThreadLocalHandshakes, "ThreadLocalHandshakes disabled");
-  guarantee(SafepointMechanism::uses_thread_local_poll(), "ThreadLocalHandshakes disabled");
+  if (!ThreadLocalHandshakes || !SafepointMechanism::uses_thread_local_poll()) {
+    return -5;
+  }
 
   class ForceYieldClosure : public ThreadClosure {
     jobject _jcont;
@@ -3524,8 +3592,9 @@
 #define FN_PTR(f) CAST_FROM_FN_PTR(void*, &f)
 
 static JNINativeMethod CONT_methods[] = {
-    {CC"clean0",           CC"()V",                   FN_PTR(CONT_Clean)},
-    {CC"tryForceYield0",   CC"(Ljava/lang/Thread;)I", FN_PTR(CONT_TryForceYield0)},
+    {CC"clean0",           CC"()V",                              FN_PTR(CONT_Clean)},
+    {CC"tryForceYield0",   CC"(Ljava/lang/Thread;)I",            FN_PTR(CONT_TryForceYield0)},
+    {CC"isPinned0",        CC"(Ljava/lang/ContinuationScope;)I", FN_PTR(CONT_isPinned0)},
 };
 
 void CONT_RegisterNativeMethods(JNIEnv *env, jclass cls) {
--- a/src/java.base/share/classes/java/lang/Continuation.java	Mon Jan 07 14:25:44 2019 +0000
+++ b/src/java.base/share/classes/java/lang/Continuation.java	Tue Jan 08 14:53:21 2019 +0000
@@ -59,12 +59,15 @@
     /** Reason for pinning */
     public enum Pinned { 
         /** Native frame on stack */ NATIVE,
-        /** Monitor held */          MONITOR }
+        /** Monitor held */          MONITOR,
+        /** In critical section */   CRITICAL_SECTION }
     /** Preemption attempt result */
     public enum PreemptStatus { 
         /** Success */                                                      SUCCESS(null), 
+        /** Permanent failure */                                            PERM_FAIL_UNSUPPORTED(null), 
         /** Permanent failure: continuation alreay yielding */              PERM_FAIL_YIELDING(null), 
         /** Permanent failure: continuation not mounted on the thread */    PERM_FAIL_NOT_MOUNTED(null), 
+        /** Transient failure: continuation pinned due to a held CS */      TRANSIENT_FAIL_PINNED_CRITICAL_SECTION(Pinned.CRITICAL_SECTION),
         /** Transient failure: continuation pinned due to native frame */   TRANSIENT_FAIL_PINNED_NATIVE(Pinned.NATIVE), 
         /** Transient failure: continuation pinned due to a held monitor */ TRANSIENT_FAIL_PINNED_MONITOR(Pinned.MONITOR);
 
@@ -77,6 +80,29 @@
         public Pinned pinned() { return pinned; }
     }
 
+    private static PreemptStatus preemptStatus(int status) {
+        switch (status) {
+            case -5: return PreemptStatus.PERM_FAIL_UNSUPPORTED;
+            case  0: return PreemptStatus.SUCCESS;
+            case -1: return PreemptStatus.PERM_FAIL_NOT_MOUNTED;
+            case -2: return PreemptStatus.PERM_FAIL_YIELDING;
+            case  1: return PreemptStatus.TRANSIENT_FAIL_PINNED_CRITICAL_SECTION;
+            case  2: return PreemptStatus.TRANSIENT_FAIL_PINNED_NATIVE;
+            case  3: return PreemptStatus.TRANSIENT_FAIL_PINNED_MONITOR;
+            default: throw new AssertionError("Unknown status: " + status);
+        }
+    }
+
+    private static Pinned pinnedReason(int reason) {
+        switch (reason) {
+            case 1: return Pinned.CRITICAL_SECTION;
+            case 2: return Pinned.NATIVE;
+            case 3: return Pinned.MONITOR;
+            default:
+                throw new AssertionError("Unknown pinned reason: " + reason);
+        }
+    }
+
     private static Thread currentCarrierThread() {
         return Thread.currentThread();
     }
@@ -116,6 +142,8 @@
     private volatile boolean mounted = false;
     private Object yieldInfo;
 
+    private short cs; // critical section semaphore
+
     private boolean reset = false; // perftest only
 
     // transient state
@@ -460,15 +488,6 @@
         onPinned(pinnedReason(reason));
     }
 
-    private static Pinned pinnedReason(int reason) {
-        switch (reason) {
-            case 1: return Pinned.NATIVE;
-            case 2: return Pinned.MONITOR;
-            default:
-                throw new AssertionError("Unknown pinned reason: " + reason);
-        }
-    }
-
     /**
      * TBD
      * @param reason TBD
@@ -499,6 +518,49 @@
         return (flags & flag) != 0;
     }
 
+    /**
+     * Pins the current continuation (enters a critical section).
+     * This increments an internal semaphore that, when greater than 0, pins the continuation.
+     */
+    public static void pin() {
+        Continuation cont = currentCarrierThread().getContinuation();
+        if (cont != null) {
+            assert cont.cs >= 0;
+            if (cont.cs == Short.MAX_VALUE)
+                throw new IllegalStateException("Too many pins");
+            cont.cs++;
+        }
+    }
+
+    /**
+     * Unpins the current continuation (exits a critical section).
+     * This decrements an internal semaphore that, when equal 0, unpins the current continuation
+     * if pinne with {@link #pin()}.
+     */
+    public static void unpin() {
+        Continuation cont = currentCarrierThread().getContinuation();
+        if (cont != null) {
+            assert cont.cs >= 0;
+            if (cont.cs == 0)
+                throw new IllegalStateException("Not pinned");
+            cont.cs--;
+        }
+    }
+
+    /**
+     * Tests whether the given scope is pinned. 
+     * This method is slow.
+     * 
+     * @param scope the continuation scope
+     * @return {@code} true if we're in the give scope and are pinned; {@code false otherwise}
+     */
+    public static boolean isPinned(ContinuationScope scope) {
+        int res = isPinned0(scope);
+        return res != 0;
+    }
+
+    static private native int isPinned0(ContinuationScope scope);
+
     private void clean() {
         if (!isStackEmpty())
             clean0();
@@ -822,22 +884,15 @@
      * @throws UnsupportedOperationException if this continuation does not support preemption
      */
     public PreemptStatus tryPreempt(Thread thread) {
-        return preemptStatus(tryForceYield0(thread));
+        PreemptStatus res = preemptStatus(tryForceYield0(thread));
+        if (res == PreemptStatus.PERM_FAIL_UNSUPPORTED) {
+            throw new UnsupportedOperationException("Thread-local handshakes disabled");
+        }
+        return res;
     }
 
     private native int tryForceYield0(Thread thread);
 
-    private static PreemptStatus preemptStatus(int status) {
-        switch (status) {
-            case  0: return PreemptStatus.SUCCESS;
-            case -1: return PreemptStatus.PERM_FAIL_NOT_MOUNTED;
-            case -2: return PreemptStatus.PERM_FAIL_YIELDING;
-            case  1: return PreemptStatus.TRANSIENT_FAIL_PINNED_NATIVE;
-            case  2: return PreemptStatus.TRANSIENT_FAIL_PINNED_MONITOR;
-            default: throw new AssertionError("Unknown status: " + status);
-        }
-    }
-
     // native methods
     private static native void registerNatives();
 
--- a/test/jdk/java/lang/Continuation/Basic.java	Mon Jan 07 14:25:44 2019 +0000
+++ b/test/jdk/java/lang/Continuation/Basic.java	Tue Jan 08 14:53:21 2019 +0000
@@ -73,7 +73,7 @@
             System.gc();
 
             List<String> frames = cont.stackWalker().walk(fs -> fs.map(StackWalker.StackFrame::getMethodName).collect(Collectors.toList()));
-            assertEquals(frames, Arrays.asList("yield0", "yield", "bar", "foo", "lambda$test1$0", "enter0"));
+            assertEquals(frames, cont.isDone() ? List.of() : Arrays.asList("yield0", "yield", "bar", "foo", "lambda$test1$0", "enter0", "enter"));
         }
         assertEquals(res.get(), 247);
     }
@@ -165,7 +165,7 @@
             // System.out.println(Arrays.toString(stes));
             assertEquals(stes[0].getMethodName(), "barThrow");
             assertEquals(stes[stes.length - 1].getClassName(), "java.lang.Continuation");
-            assertEquals(stes[stes.length - 1].getMethodName(), "enter0");
+            assertEquals(stes[stes.length - 1].getMethodName(), "enter");
         }
     }
 
@@ -232,6 +232,7 @@
         }) {
             @Override
             protected void onPinned(Continuation.Pinned reason) {
+                assert Continuation.isPinned(FOO);
                 res.set(reason);
             }
         };
@@ -271,10 +272,52 @@
         try {
             long x = 8;
             String s = "yyy";
-            String r = (String)Basic.class.getDeclaredMethod("bar2", long.class).invoke(null, 1L);
+            String r = (String)Basic.class.getDeclaredMethod("nativeBar", long.class).invoke(null, 1L);
             return Integer.parseInt(r)+1;
         } catch (Exception e) {
             throw new AssertionError(e);
         }
     }
+
+    static String nativeBar(long b) {
+        double x = 9.99;
+        String s = "zzz";
+        assert Continuation.isPinned(FOO);
+        boolean res = Continuation.yield(FOO);
+        assert res == false;
+
+        long r = b+1;
+        return "" + r;
+    }
+
+    public void testPinnedCriticalSection() {
+        System.out.println("testPinnedCriticalSection");
+        final AtomicReference<Continuation.Pinned> res = new AtomicReference<>();
+        
+        Continuation cont = new Continuation(FOO, ()-> {
+            csFoo(1);
+        }) {
+            @Override
+            protected void onPinned(Continuation.Pinned reason) {
+                res.set(reason);
+            }
+        };
+        
+        cont.run();
+        assertEquals(res.get(), Continuation.Pinned.CRITICAL_SECTION);
+    }
+
+    static double csFoo(int a) {
+        long x = 8;
+        String s = "yyy";
+        String r;
+        Continuation.pin();
+        try {
+            assert Continuation.isPinned(FOO);
+            r = bar2(a + 1);
+        } finally {
+            Continuation.unpin();
+        }
+        return Integer.parseInt(r)+1;
+    }
 }