[pypy-commit] stmgc default: hg merge rewind_setjmp

arigo noreply at buildbot.pypy.org
Sun Aug 10 18:00:35 CEST 2014


Author: Armin Rigo <arigo at tunes.org>
Branch: 
Changeset: r1295:bdc151305c79
Date: 2014-08-10 18:00 +0200
http://bitbucket.org/pypy/stmgc/changeset/bdc151305c79/

Log:	hg merge rewind_setjmp

	Add a custom layer around setjmp(), removing the restriction that we
	can only longjmp() if the function calling setjmp() didn't return
	yet.

diff --git a/c7/TODO b/c7/TODO
--- a/c7/TODO
+++ b/c7/TODO
@@ -24,3 +24,6 @@
 
 - increase the memory limit, currently 2.5GB; this requires, apparently,
   more fighting against LLVM bugs
+
+- avoid __builtin_frame_address(0) in precisely the performance-critical
+  functions like the interpreter main loop
diff --git a/c7/demo/demo2.c b/c7/demo/demo2.c
--- a/c7/demo/demo2.c
+++ b/c7/demo/demo2.c
@@ -71,9 +71,8 @@
 {
     nodeptr_t r_n;
     long prev, sum;
-    stm_jmpbuf_t here;
 
-    STM_START_TRANSACTION(&stm_thread_local, here);
+    stm_start_transaction(&stm_thread_local);
 
     stm_read((objptr_t)global_chained_list);
     r_n = global_chained_list;
@@ -101,11 +100,9 @@
 
 nodeptr_t swap_nodes(nodeptr_t initial)
 {
-    stm_jmpbuf_t here;
-
     assert(initial != NULL);
 
-    STM_START_TRANSACTION(&stm_thread_local, here);
+    stm_start_transaction(&stm_thread_local);
 
     if (stm_thread_local.longest_marker_state != 0) {
         fprintf(stderr, "[%p] marker %d for %.6f seconds:\n",
@@ -202,7 +199,7 @@
 
     stm_commit_transaction();
 
-    stm_start_inevitable_transaction(&stm_thread_local);
+    stm_start_transaction(&stm_thread_local);
     STM_POP_ROOT(stm_thread_local, global_chained_list);   /* update value */
     assert(global_chained_list->value == -1);
     STM_PUSH_ROOT(stm_thread_local, global_chained_list);  /* remains forever in the shadow stack */
@@ -224,7 +221,9 @@
 void *demo2(void *arg)
 {
     int status;
+    rewind_jmp_buf rjbuf;
     stm_register_thread_local(&stm_thread_local);
+    stm_rewind_jmp_enterframe(&stm_thread_local, &rjbuf);
     char *org = (char *)stm_thread_local.shadowstack;
 
     STM_PUSH_ROOT(stm_thread_local, global_chained_list);  /* remains forever in the shadow stack */
@@ -244,6 +243,7 @@
     STM_POP_ROOT(stm_thread_local, global_chained_list);
     OPT_ASSERT(org == (char *)stm_thread_local.shadowstack);
 
+    stm_rewind_jmp_leaveframe(&stm_thread_local, &rjbuf);
     unregister_thread_local();
     status = sem_post(&done); assert(status == 0);
     return NULL;
@@ -280,11 +280,13 @@
 int main(void)
 {
     int status, i;
+    rewind_jmp_buf rjbuf;
 
     status = sem_init(&done, 0, 0); assert(status == 0);
 
     stm_setup();
     stm_register_thread_local(&stm_thread_local);
+    stm_rewind_jmp_enterframe(&stm_thread_local, &rjbuf);
     stmcb_expand_marker = expand_marker;
 
 
@@ -302,6 +304,7 @@
     final_check();
 
 
+    stm_rewind_jmp_leaveframe(&stm_thread_local, &rjbuf);
     unregister_thread_local();
     //stm_teardown();
 
diff --git a/c7/demo/demo_largemalloc.c b/c7/demo/demo_largemalloc.c
--- a/c7/demo/demo_largemalloc.c
+++ b/c7/demo/demo_largemalloc.c
@@ -25,6 +25,15 @@
 
 void stmcb_commit_soon() {}
 
+void stmcb_trace_cards(struct object_s *obj, void cb(object_t **),
+                       uintptr_t start, uintptr_t stop) {
+    abort();
+}
+void stmcb_get_card_base_itemsize(struct object_s *obj,
+                                  uintptr_t offset_itemsize[2]) {
+    abort();
+}
+
 /************************************************************/
 
 #define ARENA_SIZE  (1024*1024*1024)
@@ -67,7 +76,7 @@
     int i;
     arena_data = malloc(ARENA_SIZE);
     assert(arena_data != NULL);
-    _stm_mutex_pages_lock();
+    //_stm_mutex_pages_lock();
     for (i = 0; i < 25; i++)
         timing(i);
     return 0;
diff --git a/c7/demo/demo_random.c b/c7/demo/demo_random.c
--- a/c7/demo/demo_random.c
+++ b/c7/demo/demo_random.c
@@ -332,15 +332,15 @@
 void *demo_random(void *arg)
 {
     int status;
+    rewind_jmp_buf rjbuf;
     stm_register_thread_local(&stm_thread_local);
+    stm_rewind_jmp_enterframe(&stm_thread_local, &rjbuf);
 
     setup_thread();
 
     objptr_t p;
-    stm_jmpbuf_t here;
-    volatile int call_fork = (arg != NULL);
 
-    STM_START_TRANSACTION(&stm_thread_local, here);
+    stm_start_transaction(&stm_thread_local);
     assert(td.num_roots >= td.num_roots_at_transaction_start);
     td.num_roots = td.num_roots_at_transaction_start;
     p = NULL;
@@ -358,11 +358,12 @@
         if (p == (objptr_t)-1) {
             push_roots();
 
+            long call_fork = (arg != NULL && *(long *)arg);
             if (call_fork == 0) {   /* common case */
                 stm_commit_transaction();
                 td.num_roots_at_transaction_start = td.num_roots;
                 if (get_rand(100) < 98) {
-                    STM_START_TRANSACTION(&stm_thread_local, here);
+                    stm_start_transaction(&stm_thread_local);
                 } else {
                     stm_start_inevitable_transaction(&stm_thread_local);
                 }
@@ -374,7 +375,7 @@
             else {
                 /* run a fork() inside the transaction */
                 printf("==========   FORK  =========\n");
-                call_fork = 0;
+                *(long*)arg = 0;
                 pid_t child = fork();
                 printf("=== in process %d thread %lx, fork() returned %d\n",
                        (int)getpid(), (long)pthread_self(), (int)child);
@@ -394,6 +395,7 @@
     }
     stm_commit_transaction();
 
+    stm_rewind_jmp_leaveframe(&stm_thread_local, &rjbuf);
     stm_unregister_thread_local(&stm_thread_local);
 
     status = sem_post(&done); assert(status == 0);
@@ -442,6 +444,7 @@
 int main(void)
 {
     int i, status;
+    rewind_jmp_buf rjbuf;
 
     /* pick a random seed from the time in seconds.
        A bit pointless for now... because the interleaving of the
@@ -455,6 +458,7 @@
 
     stm_setup();
     stm_register_thread_local(&stm_thread_local);
+    stm_rewind_jmp_enterframe(&stm_thread_local, &rjbuf);
 
     setup_globals();
 
@@ -472,7 +476,7 @@
             long forkbase = NUMTHREADS * THREAD_STARTS / (FORKS + 1);
             long _fork = (thread_starts % forkbase) == 0;
             thread_starts--;
-            newthread(demo_random, (void *)_fork);
+            newthread(demo_random, &_fork);
         }
     }
 
@@ -492,6 +496,7 @@
 
     printf("Test OK!\n");
 
+    stm_rewind_jmp_leaveframe(&stm_thread_local, &rjbuf);
     stm_unregister_thread_local(&stm_thread_local);
     stm_teardown();
 
diff --git a/c7/demo/demo_simple.c b/c7/demo/demo_simple.c
--- a/c7/demo/demo_simple.c
+++ b/c7/demo/demo_simple.c
@@ -41,12 +41,20 @@
 
 void stmcb_commit_soon() {}
 
+void stmcb_trace_cards(struct object_s *obj, void cb(object_t **),
+                       uintptr_t start, uintptr_t stop) {
+    abort();
+}
+void stmcb_get_card_base_itemsize(struct object_s *obj,
+                                  uintptr_t offset_itemsize[2]) {
+    abort();
+}
 
 
 static sem_t done;
 
 static __thread int tl_counter = 0;
-static int gl_counter = 0;
+//static int gl_counter = 0;
 
 void *demo2(void *arg)
 {
diff --git a/c7/stm/core.c b/c7/stm/core.c
--- a/c7/stm/core.c
+++ b/c7/stm/core.c
@@ -324,14 +324,14 @@
     STM_SEGMENT->transaction_read_version = 1;
 }
 
-void _stm_start_transaction(stm_thread_local_t *tl, stm_jmpbuf_t *jmpbuf)
+static void _stm_start_transaction(stm_thread_local_t *tl, bool inevitable)
 {
     assert(!_stm_in_transaction(tl));
 
     s_mutex_lock();
 
   retry:
-    if (jmpbuf == NULL) {
+    if (inevitable) {
         wait_for_end_of_inevitable_transaction(tl);
     }
 
@@ -346,11 +346,9 @@
     STM_PSEGMENT->signalled_to_commit_soon = false;
     STM_PSEGMENT->safe_point = SP_RUNNING;
     STM_PSEGMENT->marker_inev[1] = 0;
-    if (jmpbuf == NULL)
+    if (inevitable)
         marker_fetch_inev();
-    STM_PSEGMENT->transaction_state = (jmpbuf != NULL ? TS_REGULAR
-                                                      : TS_INEVITABLE);
-    STM_SEGMENT->jmpbuf_ptr = jmpbuf;
+    STM_PSEGMENT->transaction_state = (inevitable ? TS_INEVITABLE : TS_REGULAR);
 #ifndef NDEBUG
     STM_PSEGMENT->running_pthread = pthread_self();
 #endif
@@ -390,6 +388,22 @@
     check_nursery_at_transaction_start();
 }
 
+long stm_start_transaction(stm_thread_local_t *tl)
+{
+#ifdef STM_NO_AUTOMATIC_SETJMP
+    long repeat_count = 0;    /* test/support.py */
+#else
+    long repeat_count = rewind_jmp_setjmp(&tl->rjthread);
+#endif
+    _stm_start_transaction(tl, false);
+    return repeat_count;
+}
+
+void stm_start_inevitable_transaction(stm_thread_local_t *tl)
+{
+    _stm_start_transaction(tl, true);
+}
+
 
 /************************************************************/
 
@@ -814,7 +828,7 @@
     dprintf(("commit_transaction\n"));
 
     assert(STM_SEGMENT->nursery_end == NURSERY_END);
-    STM_SEGMENT->jmpbuf_ptr = NULL;
+    rewind_jmp_forget(&STM_SEGMENT->running_thread->rjthread);
 
     /* if a major collection is required, do it here */
     if (is_major_collection_requested()) {
@@ -987,6 +1001,18 @@
 #pragma pop_macro("STM_PSEGMENT")
 }
 
+#ifdef STM_NO_AUTOMATIC_SETJMP
+void _test_run_abort(stm_thread_local_t *tl) __attribute__((noreturn));
+int stm_is_inevitable(void)
+{
+    switch (STM_PSEGMENT->transaction_state) {
+    case TS_REGULAR: return 0;
+    case TS_INEVITABLE: return 1;
+    default: abort();
+    }
+}
+#endif
+
 static void abort_with_mutex(void)
 {
     assert(_has_mutex());
@@ -996,10 +1022,9 @@
 
     abort_data_structures_from_segment_num(STM_SEGMENT->segment_num);
 
-    stm_jmpbuf_t *jmpbuf_ptr = STM_SEGMENT->jmpbuf_ptr;
+    stm_thread_local_t *tl = STM_SEGMENT->running_thread;
 
     /* clear memory registered on the thread-local */
-    stm_thread_local_t *tl = STM_SEGMENT->running_thread;
     if (tl->mem_clear_on_abort)
         memset(tl->mem_clear_on_abort, 0, tl->mem_bytes_to_clear_on_abort);
 
@@ -1035,9 +1060,11 @@
     */
     usleep(1);
 
-    assert(jmpbuf_ptr != NULL);
-    assert(jmpbuf_ptr != (stm_jmpbuf_t *)-1);    /* for tests only */
-    __builtin_longjmp(*jmpbuf_ptr, 1);
+#ifdef STM_NO_AUTOMATIC_SETJMP
+    _test_run_abort(tl);
+#else
+    rewind_jmp_longjmp(&tl->rjthread);
+#endif
 }
 
 void _stm_become_inevitable(const char *msg)
@@ -1051,12 +1078,11 @@
         marker_fetch_inev();
         wait_for_end_of_inevitable_transaction(NULL);
         STM_PSEGMENT->transaction_state = TS_INEVITABLE;
-        STM_SEGMENT->jmpbuf_ptr = NULL;
+        rewind_jmp_forget(&STM_SEGMENT->running_thread->rjthread);
         clear_callbacks_on_abort();
     }
     else {
         assert(STM_PSEGMENT->transaction_state == TS_INEVITABLE);
-        assert(STM_SEGMENT->jmpbuf_ptr == NULL);
     }
 
     s_mutex_unlock();
diff --git a/c7/stm/forksupport.c b/c7/stm/forksupport.c
--- a/c7/stm/forksupport.c
+++ b/c7/stm/forksupport.c
@@ -176,14 +176,15 @@
 static void fork_abort_thread(long i)
 {
     struct stm_priv_segment_info_s *pr = get_priv_segment(i);
+    stm_thread_local_t *tl = pr->pub.running_thread;
     dprintf(("forksupport_child: abort in seg%ld\n", i));
-    assert(pr->pub.running_thread->associated_segment_num == i);
+    assert(tl->associated_segment_num == i);
     assert(pr->transaction_state == TS_REGULAR);
     set_gs_register(get_segment_base(i));
 
-    stm_jmpbuf_t jmpbuf;
-    if (__builtin_setjmp(jmpbuf) == 0) {
-        pr->pub.jmpbuf_ptr = &jmpbuf;
+    rewind_jmp_buf rjbuf;
+    stm_rewind_jmp_enterframe(tl, &rjbuf);
+    if (rewind_jmp_setjmp(&tl->rjthread) == 0) {
 #ifndef NDEBUG
         pr->running_pthread = pthread_self();
 #endif
@@ -192,6 +193,8 @@
         strcpy(pr->marker_self, "fork");
         stm_abort_transaction();
     }
+    rewind_jmp_forget(&tl->rjthread);
+    stm_rewind_jmp_leaveframe(tl, &rjbuf);
 }
 
 static void forksupport_child(void)
diff --git a/c7/stm/rewind_setjmp.c b/c7/stm/rewind_setjmp.c
new file mode 100644
--- /dev/null
+++ b/c7/stm/rewind_setjmp.c
@@ -0,0 +1,112 @@
+#include "rewind_setjmp.h"
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <alloca.h>
+
+
+struct _rewind_jmp_moved_s {
+    struct _rewind_jmp_moved_s *next;
+    size_t size;
+};
+#define RJM_HEADER  sizeof(struct _rewind_jmp_moved_s)
+
+#ifndef RJBUF_CUSTOM_MALLOC
+#define rj_malloc malloc
+#define rj_free free
+#else
+void *rj_malloc(size_t);
+void rj_free(void *);
+#endif
+
+
+static void copy_stack(rewind_jmp_thread *rjthread, char *base)
+{
+    assert(rjthread->head != NULL);
+    char *stop = rjthread->head->frame_base;
+    assert(stop > base);
+    struct _rewind_jmp_moved_s *next = (struct _rewind_jmp_moved_s *)
+        rj_malloc(RJM_HEADER + (stop - base));
+    assert(next != NULL);    /* XXX out of memory */
+    next->next = rjthread->moved_off;
+    next->size = stop - base;
+    memcpy(((char *)next) + RJM_HEADER, base, stop - base);
+
+    rjthread->moved_off_base = stop;
+    rjthread->moved_off = next;
+}
+
+__attribute__((noinline))
+long rewind_jmp_setjmp(rewind_jmp_thread *rjthread)
+{
+    if (rjthread->moved_off) {
+        _rewind_jmp_free_stack_slices(rjthread);
+    }
+    rewind_jmp_thread *volatile rjthread1 = rjthread;
+    int result;
+    if (__builtin_setjmp(rjthread->jmpbuf) == 0) {
+        rjthread = rjthread1;
+        rjthread->initial_head = rjthread->head;
+        result = 0;
+    }
+    else {
+        rjthread = rjthread1;
+        rjthread->head = rjthread->initial_head;
+        result = rjthread->repeat_count + 1;
+    }
+    rjthread->repeat_count = result;
+    copy_stack(rjthread, (char *)&rjthread1);
+    return result;
+}
+
+__attribute__((noinline, noreturn))
+static void do_longjmp(rewind_jmp_thread *rjthread, char *stack_free)
+{
+    assert(rjthread->moved_off_base != NULL);
+
+    while (rjthread->moved_off) {
+        struct _rewind_jmp_moved_s *p = rjthread->moved_off;
+        char *target = rjthread->moved_off_base;
+        target -= p->size;
+        if (target < stack_free) {
+            /* need more stack space! */
+            do_longjmp(rjthread, alloca(stack_free - target));
+        }
+        memcpy(target, ((char *)p) + RJM_HEADER, p->size);
+        rjthread->moved_off_base = target;
+        rjthread->moved_off = p->next;
+        rj_free(p);
+    }
+    __builtin_longjmp(rjthread->jmpbuf, 1);
+}
+
+__attribute__((noreturn))
+void rewind_jmp_longjmp(rewind_jmp_thread *rjthread)
+{
+    char _rewind_jmp_marker;
+    do_longjmp(rjthread, &_rewind_jmp_marker);
+}
+
+__attribute__((noinline))
+void _rewind_jmp_copy_stack_slice(rewind_jmp_thread *rjthread)
+{
+    if (rjthread->head == NULL) {
+        _rewind_jmp_free_stack_slices(rjthread);
+        return;
+    }
+    assert(rjthread->moved_off_base < (char *)rjthread->head);
+    copy_stack(rjthread, rjthread->moved_off_base);
+}
+
+void _rewind_jmp_free_stack_slices(rewind_jmp_thread *rjthread)
+{
+    struct _rewind_jmp_moved_s *p = rjthread->moved_off;
+    struct _rewind_jmp_moved_s *pnext;
+    while (p) {
+        pnext = p->next;
+        rj_free(p);
+        p = pnext;
+    }
+    rjthread->moved_off = NULL;
+    rjthread->moved_off_base = NULL;
+}
diff --git a/c7/stm/rewind_setjmp.h b/c7/stm/rewind_setjmp.h
new file mode 100644
--- /dev/null
+++ b/c7/stm/rewind_setjmp.h
@@ -0,0 +1,82 @@
+#ifndef _REWIND_SETJMP_H_
+#define _REWIND_SETJMP_H_
+
+/************************************************************
+
+           :                   :       ^^^^^
+           |-------------------|    older frames in the stack
+           |   prev=0          |
+     ,---> | rewind_jmp_buf    |
+     |     |-------------------|
+     |     |                   |
+     |     :                   :
+     |     :                   :
+     |     |                   |
+     |     |-------------------|
+     `---------prev            |
+    ,----> | rewind_jmp_buf    |
+    |      +-------------------|
+    |      |                   |
+    |      :                   :
+    |      |                   |
+    |      |-------------------|
+    `----------prev            |
+     ,---> | rewind_jmp_buf    | <--------------- MOVED_OFF_BASE
+     |     |----------------  +-------------+
+     |     |                  | STACK COPY  |
+     |     |                  :             :
+     |     :                  |  size       |
+     |     |                  |  next       | <---- MOVED_OFF
+     |     |                  +---|------  +-------------+
+     |     |                   |  |        | STACK COPY  |
+     |     |-------------------|  |        : (SEQUEL)    :
+     `---------prev            |  |        :             :
+HEAD-----> | rewind_jmp_buf    |  |        |             |
+           |-------------------|  |        |  size       |
+                                  `------> |  next=0     |
+                                           +-------------+
+
+
+************************************************************/
+
+typedef struct _rewind_jmp_buf {
+    char *frame_base;
+    struct _rewind_jmp_buf *prev;
+} rewind_jmp_buf;
+
+typedef struct {
+    rewind_jmp_buf *head;
+    rewind_jmp_buf *initial_head;
+    char *moved_off_base;
+    struct _rewind_jmp_moved_s *moved_off;
+    void *jmpbuf[5];
+    long repeat_count;
+} rewind_jmp_thread;
+
+
+#define rewind_jmp_enterframe(rjthread, rjbuf)   do {   \
+    (rjbuf)->frame_base = __builtin_frame_address(0);   \
+    (rjbuf)->prev = (rjthread)->head;                   \
+    (rjthread)->head = (rjbuf);                         \
+} while (0)
+
+#define rewind_jmp_leaveframe(rjthread, rjbuf)   do {   \
+    (rjthread)->head = (rjbuf)->prev;                   \
+    if ((rjbuf)->frame_base == (rjthread)->moved_off_base) \
+        _rewind_jmp_copy_stack_slice(rjthread);         \
+} while (0)
+
+long rewind_jmp_setjmp(rewind_jmp_thread *rjthread);
+void rewind_jmp_longjmp(rewind_jmp_thread *rjthread) __attribute__((noreturn));
+
+#define rewind_jmp_forget(rjthread)  do {                               \
+    if ((rjthread)->moved_off) _rewind_jmp_free_stack_slices(rjthread); \
+    (rjthread)->moved_off_base = 0;                                     \
+} while (0)
+
+void _rewind_jmp_copy_stack_slice(rewind_jmp_thread *);
+void _rewind_jmp_free_stack_slices(rewind_jmp_thread *);
+
+#define rewind_jmp_armed(rjthread)   ((rjthread)->moved_off_base != 0)
+
+#endif
diff --git a/c7/stmgc.c b/c7/stmgc.c
--- a/c7/stmgc.c
+++ b/c7/stmgc.c
@@ -36,3 +36,4 @@
 #include "stm/weakref.c"
 #include "stm/timing.c"
 #include "stm/marker.c"
+#include "stm/rewind_setjmp.c"
diff --git a/c7/stmgc.h b/c7/stmgc.h
--- a/c7/stmgc.h
+++ b/c7/stmgc.h
@@ -13,6 +13,8 @@
 #include <limits.h>
 #include <unistd.h>
 
+#include "stm/rewind_setjmp.h"
+
 #if LONG_MAX == 2147483647
 # error "Requires a 64-bit environment"
 #endif
@@ -25,7 +27,6 @@
 typedef TLPREFIX struct stm_read_marker_s stm_read_marker_t;
 typedef TLPREFIX struct stm_creation_marker_s stm_creation_marker_t;
 typedef TLPREFIX char stm_char;
-typedef void* stm_jmpbuf_t[5];  /* for use with __builtin_setjmp() */
 
 struct stm_read_marker_s {
     /* In every segment, every object has a corresponding read marker.
@@ -44,7 +45,6 @@
     stm_char *nursery_current;
     uintptr_t nursery_end;
     struct stm_thread_local_s *running_thread;
-    stm_jmpbuf_t *jmpbuf_ptr;
 };
 #define STM_SEGMENT           ((stm_segment_info_t *)4352)
 
@@ -79,6 +79,8 @@
 typedef struct stm_thread_local_s {
     /* every thread should handle the shadow stack itself */
     struct stm_shadowentry_s *shadowstack, *shadowstack_base;
+    /* rewind_setjmp's interface */
+    rewind_jmp_thread rjthread;
     /* a generic optional thread-local object */
     object_t *thread_local_obj;
     /* in case this thread runs a transaction that aborts,
@@ -114,7 +116,6 @@
 object_t *_stm_allocate_slowpath(ssize_t);
 object_t *_stm_allocate_external(ssize_t);
 void _stm_become_inevitable(const char*);
-void _stm_start_transaction(stm_thread_local_t *, stm_jmpbuf_t *);
 void _stm_collectable_safe_point(void);
 
 /* for tests, but also used in duhton: */
@@ -326,40 +327,42 @@
 void stm_register_thread_local(stm_thread_local_t *tl);
 void stm_unregister_thread_local(stm_thread_local_t *tl);
 
+/* At some key places, like the entry point of the thread and in the
+   function with the interpreter's dispatch loop, you need to declare
+   a local variable of type 'rewind_jmp_buf' and call these macros. */
+#define stm_rewind_jmp_enterframe(tl, rjbuf)       \
+    rewind_jmp_enterframe(&(tl)->rjthread, rjbuf)
+#define stm_rewind_jmp_leaveframe(tl, rjbuf)       \
+    rewind_jmp_leaveframe(&(tl)->rjthread, rjbuf)
+
 /* Starting and ending transactions.  stm_read(), stm_write() and
    stm_allocate() should only be called from within a transaction.
-   Use the macro STM_START_TRANSACTION() to start a transaction that
-   can be restarted using the 'jmpbuf' (a local variable of type
-   stm_jmpbuf_t). */
-#define STM_START_TRANSACTION(tl, jmpbuf)  ({                   \
-    while (__builtin_setjmp(jmpbuf) == 1) { /*redo setjmp*/ }   \
-    _stm_start_transaction(tl, &jmpbuf);                        \
-})
-
-/* Start an inevitable transaction, if it's going to return from the
-   current function immediately. */
-static inline void stm_start_inevitable_transaction(stm_thread_local_t *tl) {
-    _stm_start_transaction(tl, NULL);
-}
-
-/* Commit a transaction. */
+   The stm_start_transaction() call returns the number of times it
+   returned, starting at 0.  If it is > 0, then the transaction was
+   aborted and restarted this number of times. */
+long stm_start_transaction(stm_thread_local_t *tl);
+void stm_start_inevitable_transaction(stm_thread_local_t *tl);
 void stm_commit_transaction(void);
 
-/* Abort the currently running transaction. */
+/* Abort the currently running transaction.  This function never
+   returns: it jumps back to the stm_start_transaction(). */
 void stm_abort_transaction(void) __attribute__((noreturn));
 
-/* Turn the current transaction inevitable.  The 'jmpbuf' passed to
-   STM_START_TRANSACTION() is not going to be used any more after
-   this call (but the stm_become_inevitable() itself may still abort). */
+/* Turn the current transaction inevitable.
+   The stm_become_inevitable() itself may still abort. */
+#ifdef STM_NO_AUTOMATIC_SETJMP
+int stm_is_inevitable(void);
+#else
+static inline int stm_is_inevitable(void) {
+    return !rewind_jmp_armed(&STM_SEGMENT->running_thread->rjthread); 
+}
+#endif
 static inline void stm_become_inevitable(stm_thread_local_t *tl,
                                          const char* msg) {
     assert(STM_SEGMENT->running_thread == tl);
-    if (STM_SEGMENT->jmpbuf_ptr != NULL)
+    if (!stm_is_inevitable())
         _stm_become_inevitable(msg);
 }
-static inline int stm_is_inevitable(void) {
-    return (STM_SEGMENT->jmpbuf_ptr == NULL);
-}
 
 /* Forces a safe-point if needed.  Normally not needed: this is
    automatic if you call stm_allocate(). */
diff --git a/c7/test/support.py b/c7/test/support.py
--- a/c7/test/support.py
+++ b/c7/test/support.py
@@ -7,7 +7,6 @@
 ffi = cffi.FFI()
 ffi.cdef("""
 typedef ... object_t;
-typedef ... stm_jmpbuf_t;
 #define SIZEOF_MYOBJ ...
 #define STM_NB_SEGMENTS ...
 #define _STM_FAST_ALLOC ...
@@ -64,7 +63,8 @@
 uintptr_t _stm_get_private_page(uintptr_t pagenum);
 int _stm_get_flags(object_t *obj);
 
-void _stm_start_transaction(stm_thread_local_t *tl, stm_jmpbuf_t *jmpbuf);
+void clear_jmpbuf(stm_thread_local_t *tl);
+long stm_start_transaction(stm_thread_local_t *tl);
 bool _check_commit_transaction(void);
 bool _check_abort_transaction(void);
 bool _check_become_inevitable(stm_thread_local_t *tl);
@@ -148,7 +148,7 @@
 GC_N_SMALL_REQUESTS = 36      # from gcpage.c
 LARGE_MALLOC_OVERHEAD = 16    # from largemalloc.h
 
-lib = ffi.verify('''
+lib = ffi.verify(r'''
 #include <stdlib.h>
 #include <string.h>
 #include <assert.h>
@@ -167,23 +167,26 @@
     return obj->stm_flags;
 }
 
+void clear_jmpbuf(stm_thread_local_t *tl) {
+    memset(&tl->rjthread, 0, sizeof(rewind_jmp_thread));
+}
+
+__attribute__((noreturn))
+void _test_run_abort(stm_thread_local_t *tl) {
+    void **jmpbuf = tl->rjthread.jmpbuf;
+    fprintf(stderr, "~~~~~ ABORT ~~~~~\n");
+    __builtin_longjmp(jmpbuf, 1);
+}
+
 #define CHECKED(CALL)                                           \
-    stm_jmpbuf_t here;                                          \
-    stm_segment_info_t *segment = STM_SEGMENT;                  \
-    if (__builtin_setjmp(here) == 0) { /* returned directly */  \
-        if (segment->jmpbuf_ptr != NULL) {                      \
-            assert(segment->jmpbuf_ptr == (stm_jmpbuf_t *)-1);  \
-            segment->jmpbuf_ptr = &here;                        \
-        }                                                       \
+    stm_thread_local_t *_tl = STM_SEGMENT->running_thread;      \
+    void **jmpbuf = _tl->rjthread.jmpbuf;                       \
+    if (__builtin_setjmp(jmpbuf) == 0) { /* returned directly */\
         CALL;                                                   \
-        if (segment->jmpbuf_ptr != NULL) {                      \
-            segment->jmpbuf_ptr = (stm_jmpbuf_t *)-1;           \
-        }                                                       \
+        clear_jmpbuf(_tl);                                      \
         return 0;                                               \
     }                                                           \
-    if (segment->jmpbuf_ptr != NULL) {                          \
-        segment->jmpbuf_ptr = (stm_jmpbuf_t *)-1;               \
-    }                                                           \
+    clear_jmpbuf(_tl);                                          \
     return 1
 
 bool _checked_stm_write(object_t *object) {
@@ -350,6 +353,7 @@
 }
 ''', sources=source_files,
      define_macros=[('STM_TESTS', '1'),
+                    ('STM_NO_AUTOMATIC_SETJMP', '1'),
                     ('STM_LARGEMALLOC_TEST', '1'),
                     ('STM_NO_COND_WAIT', '1'),
                     ('STM_DEBUGPRINT', '1'),
@@ -559,7 +563,9 @@
     def start_transaction(self):
         tl = self.tls[self.current_thread]
         assert not lib._stm_in_transaction(tl)
-        lib._stm_start_transaction(tl, ffi.cast("stm_jmpbuf_t *", -1))
+        res = lib.stm_start_transaction(tl)
+        assert res == 0
+        lib.clear_jmpbuf(tl)
         assert lib._stm_in_transaction(tl)
         #
         seen = set()
diff --git a/c7/test/test_basic.py b/c7/test/test_basic.py
--- a/c7/test/test_basic.py
+++ b/c7/test/test_basic.py
@@ -369,6 +369,7 @@
 
     def test_inevitable_transaction_has_priority(self):
         self.start_transaction()
+        assert lib.stm_is_inevitable() == 0
         lp1 = stm_allocate(16)
         stm_set_char(lp1, 'a')
         self.push_root(lp1)
diff --git a/c7/test/test_rewind.c b/c7/test/test_rewind.c
new file mode 100644
--- /dev/null
+++ b/c7/test/test_rewind.c
@@ -0,0 +1,255 @@
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "rewind_setjmp.h"
+
+
+rewind_jmp_thread gthread;
+int gevents[1000];
+int num_gevents = 0;
+
+void gevent(int num)
+{
+    assert(num_gevents <= sizeof(gevents) / sizeof(int));
+    gevents[num_gevents++] = num;
+}
+
+void check_gevents(int expected[], int expected_size)
+{
+    int i;
+    int expected_count = expected_size / sizeof(int);
+    for (i = 0; i < expected_count && i < num_gevents; i++) {
+        assert(gevents[i] == expected[i]);
+    }
+    assert(num_gevents == expected_count);
+}
+
+#define CHECK(expected)  check_gevents(expected, sizeof(expected))
+
+/************************************************************/
+
+__attribute__((noinline))
+void f1(int x)
+{
+    gevent(1);
+    if (x < 10) {
+        rewind_jmp_longjmp(&gthread);
+    }
+}
+
+static int test1_x;
+
+void test1(void)
+{
+    rewind_jmp_buf buf;
+    rewind_jmp_enterframe(&gthread, &buf);
+
+    test1_x = 0;
+    rewind_jmp_setjmp(&gthread);
+
+    test1_x++;
+    f1(test1_x);
+
+    assert(test1_x == 10);
+    int expected[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
+    CHECK(expected);
+
+    assert(rewind_jmp_armed(&gthread));
+    rewind_jmp_forget(&gthread);
+    assert(!rewind_jmp_armed(&gthread));
+
+    rewind_jmp_leaveframe(&gthread, &buf);
+}
+
+/************************************************************/
+
+static int test2_x;
+
+__attribute__((noinline))
+int f2(void)
+{
+    rewind_jmp_buf buf;
+    rewind_jmp_enterframe(&gthread, &buf);
+    test2_x = 0;
+    rewind_jmp_setjmp(&gthread);
+    rewind_jmp_leaveframe(&gthread, &buf);
+    return ++test2_x;
+}
+
+void test2(void)
+{
+    rewind_jmp_buf buf;
+    rewind_jmp_enterframe(&gthread, &buf);
+    int x = f2();
+    gevent(x);
+    if (x < 10)
+        rewind_jmp_longjmp(&gthread);
+    rewind_jmp_leaveframe(&gthread, &buf);
+    int expected[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+    CHECK(expected);
+}
+
+/************************************************************/
+
+__attribute__((noinline))
+int f3(int rec)
+{
+    if (rec > 0)
+        return f3(rec - 1);
+    else
+        return f2();
+}
+
+void test3(void)
+{
+    rewind_jmp_buf buf;
+    rewind_jmp_enterframe(&gthread, &buf);
+    int x = f3(50);
+    gevent(x);
+    if (x < 10)
+        rewind_jmp_longjmp(&gthread);
+    rewind_jmp_leaveframe(&gthread, &buf);
+    int expected[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+    CHECK(expected);
+}
+
+/************************************************************/
+
+__attribute__((noinline))
+int f4(int rec)
+{
+    rewind_jmp_buf buf;
+    rewind_jmp_enterframe(&gthread, &buf);
+    int res;
+    if (rec > 0)
+        res = f4(rec - 1);
+    else
+        res = f2();
+    rewind_jmp_leaveframe(&gthread, &buf);
+    return res;
+}
+
+void test4(void)
+{
+    rewind_jmp_buf buf;
+    rewind_jmp_enterframe(&gthread, &buf);
+    int x = f4(5);
+    gevent(x);
+    if (x < 10)
+        rewind_jmp_longjmp(&gthread);
+    rewind_jmp_leaveframe(&gthread, &buf);
+    int expected[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+    CHECK(expected);
+}
+
+/************************************************************/
+
+void test5(void)
+{
+    struct { int a; rewind_jmp_buf buf; int b; } sbuf;
+    rewind_jmp_enterframe(&gthread, &sbuf.buf);
+    sbuf.a = 42;
+    sbuf.b = -42;
+    test2_x = 0;
+    rewind_jmp_setjmp(&gthread);
+    sbuf.a++;
+    sbuf.b--;
+    gevent(sbuf.a);
+    gevent(sbuf.b);
+    if (test2_x == 0) {
+        test2_x++;
+        rewind_jmp_longjmp(&gthread);
+    }
+    int expected[] = {43, -43, 43, -43};
+    CHECK(expected);
+    rewind_jmp_leaveframe(&gthread, &sbuf.buf);
+}
+
+/************************************************************/
+
+static int test6_x;
+
+__attribute__((noinline))
+void foo(int *x) { ++*x; }
+
+__attribute__((noinline))
+void f6(int a1, int a2, int a3, int a4, int a5, int a6, int a7,
+        int a8, int a9, int a10, int a11, int a12, int a13)
+{
+    rewind_jmp_buf buf;
+    rewind_jmp_enterframe(&gthread, &buf);
+
+    rewind_jmp_setjmp(&gthread);
+    gevent(a1); gevent(a2); gevent(a3); gevent(a4);
+    gevent(a5); gevent(a6); gevent(a7); gevent(a8);
+    gevent(a9); gevent(a10); gevent(a11); gevent(a12);
+    gevent(a13);
+    if (++test6_x < 4) {
+        foo(&a1);
+        foo(&a2);
+        foo(&a3);
+        foo(&a4);
+        foo(&a5);
+        foo(&a6);
+        foo(&a7);
+        foo(&a8);
+        foo(&a9);
+        foo(&a10);
+        foo(&a11);
+        foo(&a12);
+        foo(&a13);
+        rewind_jmp_longjmp(&gthread);
+    }
+    rewind_jmp_leaveframe(&gthread, &buf);
+}
+
+void test6(void)
+{
+    rewind_jmp_buf buf;
+    rewind_jmp_enterframe(&gthread, &buf);
+    test6_x = 0;
+    f6(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13);
+    rewind_jmp_leaveframe(&gthread, &buf);
+    int expected[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
+                      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
+                      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
+                      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
+    CHECK(expected);
+}
+
+/************************************************************/
+
+int rj_malloc_count = 0;
+
+void *rj_malloc(size_t size)
+{
+    rj_malloc_count++;
+    void *ptr = malloc(size);
+    fprintf(stderr, "malloc(%ld) -> %p\n", (long)size, ptr);
+    return ptr;
+}
+
+void rj_free(void *ptr)
+{
+    if (ptr)
+        rj_malloc_count--;
+    fprintf(stderr, "free(%p)\n", ptr);
+    free(ptr);
+}
+
+
+int main(int argc, char *argv[])
+{
+    assert(argc > 1);
+    if (!strcmp(argv[1], "1"))       test1();
+    else if (!strcmp(argv[1], "2"))  test2();
+    else if (!strcmp(argv[1], "3"))  test3();
+    else if (!strcmp(argv[1], "4"))  test4();
+    else if (!strcmp(argv[1], "5"))  test5();
+    else if (!strcmp(argv[1], "6"))  test6();
+    else
+        assert(!"bad argv[1]");
+    assert(rj_malloc_count == 0);
+    return 0;
+}
diff --git a/c7/test/test_rewind.py b/c7/test/test_rewind.py
new file mode 100644
--- /dev/null
+++ b/c7/test/test_rewind.py
@@ -0,0 +1,19 @@
+import os
+
+def run_test(opt):
+    err = os.system("clang -g -O%d -Werror -DRJBUF_CUSTOM_MALLOC -I../stm"
+                    " -o test_rewind_O%d test_rewind.c ../stm/rewind_setjmp.c"
+                    % (opt, opt))
+    if err != 0:
+        raise OSError("clang failed on test_rewind.c")
+    for testnum in [1, 2, 3, 4, 5, 6]:
+        print '=== O%d: RUNNING TEST %d ===' % (opt, testnum)
+        err = os.system("./test_rewind_O%d %d" % (opt, testnum))
+        if err != 0:
+            raise OSError("'test_rewind_O%d %d' failed" % (opt, testnum))
+    os.unlink("./test_rewind_O%d" % (opt,))
+
+def test_O0(): run_test(0)
+def test_O1(): run_test(1)
+def test_O2(): run_test(2)
+def test_O3(): run_test(3)
diff --git a/duhton/transaction.c b/duhton/transaction.c
--- a/duhton/transaction.c
+++ b/duhton/transaction.c
@@ -162,8 +162,9 @@
 
 void *run_thread(void *thread_id)
 {
-    stm_jmpbuf_t here;
+    rewind_jmp_buf rjbuf;
     stm_register_thread_local(&stm_thread_local);
+    stm_rewind_jmp_enterframe(&stm_thread_local, &rjbuf);
 
     TLOBJ = NULL;
 
@@ -176,7 +177,7 @@
 
         TLOBJ = cell;
         stm_commit_transaction(); /* inevitable */
-        STM_START_TRANSACTION(&stm_thread_local, here);
+        stm_start_transaction(&stm_thread_local);
         cell = TLOBJ;
         TLOBJ = NULL;
 
@@ -187,6 +188,7 @@
     }
 
     stm_flush_timing(&stm_thread_local, 1);
+    stm_rewind_jmp_leaveframe(&stm_thread_local, &rjbuf);
     stm_unregister_thread_local(&stm_thread_local);
 
     return NULL;


More information about the pypy-commit mailing list