Commits

Armin Rigo  committed 7cc0f05 Merge

hg merge default

  • Participants
  • Parent commits 69b0eac, 2504945
  • Branches gc-small-uniform

Comments (0)

Files changed (34)

 - fork() is done by copying the whole mmap non-lazily; improve.
 
 - contention.c: when pausing: should also tell other_pseg "please commit soon"
+
+- resharing: remap_file_pages on multiple pages at once; and madvise()
+  the unused pages away --- or maybe use consecutive addresses from the
+  lowest ones from segment N, instead of the page corresponding to the page
+  number in segment 0 (possibly a bit messy)

File c7/demo/demo2.c

     visit((object_t **)&n->next);
 }
 
+void stmcb_commit_soon() {}
+
+static void expand_marker(char *base, uintptr_t odd_number,
+                          object_t *following_object,
+                          char *outputbuf, size_t outputbufsize)
+{
+    assert(following_object == NULL);
+    snprintf(outputbuf, outputbufsize, "<%p %lu>", base, odd_number);
+}
+
 
 nodeptr_t global_chained_list;
 
 
     STM_START_TRANSACTION(&stm_thread_local, here);
 
+    if (stm_thread_local.longest_marker_state != 0) {
+        fprintf(stderr, "[%p] marker %d for %.6f seconds:\n",
+                &stm_thread_local,
+                stm_thread_local.longest_marker_state,
+                stm_thread_local.longest_marker_time);
+        fprintf(stderr, "\tself:\t\"%s\"\n\tother:\t\"%s\"\n",
+                stm_thread_local.longest_marker_self,
+                stm_thread_local.longest_marker_other);
+        stm_thread_local.longest_marker_state = 0;
+        stm_thread_local.longest_marker_time = 0.0;
+    }
+
     nodeptr_t prev = initial;
     stm_read((objptr_t)prev);
 
 {
     int status;
     stm_register_thread_local(&stm_thread_local);
+    char *org = (char *)stm_thread_local.shadowstack;
 
     STM_PUSH_ROOT(stm_thread_local, global_chained_list);  /* remains forever in the shadow stack */
 
+    int loops = 0;
+
     while (check_sorted() == -1) {
+
+        STM_PUSH_MARKER(stm_thread_local, 2 * loops + 1, NULL);
+
         bubble_run();
+
+        STM_POP_MARKER(stm_thread_local);
+        loops++;
     }
 
     STM_POP_ROOT(stm_thread_local, global_chained_list);
-    assert(stm_thread_local.shadowstack == stm_thread_local.shadowstack_base);
+    OPT_ASSERT(org == (char *)stm_thread_local.shadowstack);
 
     unregister_thread_local();
     status = sem_post(&done); assert(status == 0);
 
     stm_setup();
     stm_register_thread_local(&stm_thread_local);
+    stmcb_expand_marker = expand_marker;
 
 
     setup_list();

File c7/demo/demo_largemalloc.c

     abort();
 }
 
+void stmcb_commit_soon() {}
+
 /************************************************************/
 
 #define ARENA_SIZE  (1024*1024*1024)

File c7/demo/demo_random.c

     assert(n->next == *last_next);
 }
 
+void stmcb_commit_soon() {}
+
 int get_rand(int max)
 {
     if (max == 0)

File c7/demo/demo_simple.c

     visit((object_t **)&n->next);
 }
 
+void stmcb_commit_soon() {}
+
 
 
 static sem_t done;
 {
     int status;
     stm_register_thread_local(&stm_thread_local);
+    char *org = (char *)stm_thread_local.shadowstack;
     tl_counter = 0;
 
     object_t *tmp;
         i++;
     }
 
-    assert(stm_thread_local.shadowstack == stm_thread_local.shadowstack_base);
+    assert(org == (char *)stm_thread_local.shadowstack);
 
     stm_unregister_thread_local(&stm_thread_local);
     status = sem_post(&done); assert(status == 0);

File c7/doc/marker.txt

+
+Reports
+=======
+
+- self-abort:
+    WRITE_WRITE_CONTENTION, INEVITABLE_CONTENTION:
+       marker in both threads, time lost by this thread
+    WRITE_READ_CONTENTION:
+       marker pointing back to the write, time lost by this thread
+
+- aborted by a different thread:
+    WRITE_WRITE_CONTENTION:
+       marker in both threads, time lost by this thread
+    WRITE_READ_CONTENTION:
+       remote marker pointing back to the write, time lost by this thread
+       (no local marker available to know where we've read the object from)
+    INEVITABLE_CONTENTION:
+       n/a
+
+- self-pausing:
+    same as self-abort, but reporting the time lost by pausing
+
+- waiting for a free segment:
+    - if we're waiting because of inevitability, report with a
+      marker and the time lost
+    - if we're just waiting because of no free segment, don't report it,
+      or maybe with only the total time lost and no marker
+
+- more internal reasons for cond_wait(), like synchronizing the threads,
+  should all be resolved quickly and are unlikely worth a report
+
+
+Internal Measurements
+=====================
+
+- use clock_gettime(CLOCK_MONOTONIC), it seems to be the fastest way
+  (less than 5 times slower than a RDTSC instruction, which is itself
+  not safe in the presence of threads migrating among CPUs)
+
+- record only the highest-time entry.  The user of the library is
+  responsible for getting and clearing it often enough if it wants
+  more details.

File c7/stm/contention.c

 
 
 static void contention_management(uint8_t other_segment_num,
-                                  enum contention_kind_e kind)
+                                  enum contention_kind_e kind,
+                                  object_t *obj)
 {
     assert(_has_mutex());
     assert(other_segment_num != STM_SEGMENT->segment_num);
              itself already paused here.
         */
         contmgr.other_pseg->signal_when_done = true;
+        marker_contention(kind, false, other_segment_num, obj);
 
         change_timing_state(wait_category);
 
-        /* XXX should also tell other_pseg "please commit soon" */
+        /* tell the other to commit ASAP */
+        signal_other_to_commit_soon(contmgr.other_pseg);
 
         dprintf(("pausing...\n"));
         cond_signal(C_AT_SAFE_POINT);
         if (must_abort())
             abort_with_mutex();
 
-        change_timing_state(STM_TIME_RUN_CURRENT);
+        struct stm_priv_segment_info_s *pseg =
+            get_priv_segment(STM_SEGMENT->segment_num);
+        double elapsed =
+            change_timing_state_tl(pseg->pub.running_thread,
+                                   STM_TIME_RUN_CURRENT);
+        marker_copy(pseg->pub.running_thread, pseg,
+                    wait_category, elapsed);
     }
 
     else if (!contmgr.abort_other) {
+        /* tell the other to commit ASAP, since it causes aborts */
+        signal_other_to_commit_soon(contmgr.other_pseg);
+
         dprintf(("abort in contention\n"));
         STM_SEGMENT->nursery_end = abort_category;
+        marker_contention(kind, false, other_segment_num, obj);
         abort_with_mutex();
     }
 
         /* We have to signal the other thread to abort, and wait until
            it does. */
         contmgr.other_pseg->pub.nursery_end = abort_category;
+        marker_contention(kind, true, other_segment_num, obj);
 
         int sp = contmgr.other_pseg->safe_point;
         switch (sp) {
             abort_data_structures_from_segment_num(other_segment_num);
         }
         dprintf(("killed other thread\n"));
+
+        /* we should commit soon, we caused an abort */
+        //signal_other_to_commit_soon(get_priv_segment(STM_SEGMENT->segment_num));
+        if (!STM_PSEGMENT->signalled_to_commit_soon) {
+            STM_PSEGMENT->signalled_to_commit_soon = true;
+            stmcb_commit_soon();
+        }
     }
 }
 
-static void write_write_contention_management(uintptr_t lock_idx)
+static void write_write_contention_management(uintptr_t lock_idx,
+                                              object_t *obj)
 {
     s_mutex_lock();
 
         assert(get_priv_segment(other_segment_num)->write_lock_num ==
                prev_owner);
 
-        contention_management(other_segment_num, WRITE_WRITE_CONTENTION);
+        contention_management(other_segment_num, WRITE_WRITE_CONTENTION, obj);
 
         /* now we return into _stm_write_slowpath() and will try again
            to acquire the write lock on our object. */
     s_mutex_unlock();
 }
 
-static void write_read_contention_management(uint8_t other_segment_num)
+static void write_read_contention_management(uint8_t other_segment_num,
+                                             object_t *obj)
 {
-    contention_management(other_segment_num, WRITE_READ_CONTENTION);
+    contention_management(other_segment_num, WRITE_READ_CONTENTION, obj);
 }
 
 static void inevitable_contention_management(uint8_t other_segment_num)
 {
-    contention_management(other_segment_num, INEVITABLE_CONTENTION);
+    contention_management(other_segment_num, INEVITABLE_CONTENTION, NULL);
 }

File c7/stm/contention.h

 
-static void write_write_contention_management(uintptr_t lock_idx);
-static void write_read_contention_management(uint8_t other_segment_num);
+static void write_write_contention_management(uintptr_t lock_idx,
+                                              object_t *obj);
+static void write_read_contention_management(uint8_t other_segment_num,
+                                             object_t *obj);
 static void inevitable_contention_management(uint8_t other_segment_num);
 
 static inline bool is_abort(uintptr_t nursery_end) {
-    return (nursery_end <= _STM_NSE_SIGNAL_MAX && nursery_end != NSE_SIGPAUSE);
+    return (nursery_end <= _STM_NSE_SIGNAL_MAX && nursery_end != NSE_SIGPAUSE
+            && nursery_end != NSE_SIGCOMMITSOON);
 }
 
 static inline bool is_aborting_now(uint8_t other_segment_num) {

File c7/stm/core.c

 #define EVENTUALLY(condition)                                   \
     {                                                           \
         if (!(condition)) {                                     \
-            int _i;                                             \
-            for (_i = 1; _i <= NB_SEGMENTS; _i++)               \
-                spinlock_acquire(lock_pages_privatizing[_i]);   \
+            acquire_privatization_lock();                       \
             if (!(condition))                                   \
                 stm_fatalerror("fails: " #condition);           \
-            for (_i = 1; _i <= NB_SEGMENTS; _i++)               \
-                spinlock_release(lock_pages_privatizing[_i]);   \
+            release_privatization_lock();                       \
         }                                                       \
     }
 #endif
     assert(lock_idx < sizeof(write_locks));
  retry:
     if (write_locks[lock_idx] == 0) {
+        /* A lock to prevent reading garbage from
+           lookup_other_thread_recorded_marker() */
+        acquire_marker_lock(STM_SEGMENT->segment_base);
+
         if (UNLIKELY(!__sync_bool_compare_and_swap(&write_locks[lock_idx],
-                                                   0, lock_num)))
+                                                   0, lock_num))) {
+            release_marker_lock(STM_SEGMENT->segment_base);
             goto retry;
+        }
 
         dprintf_test(("write_slowpath %p -> mod_old\n", obj));
 
            Add it to the list 'modified_old_objects'. */
         LIST_APPEND(STM_PSEGMENT->modified_old_objects, obj);
 
+        /* Add the current marker, recording where we wrote to this object */
+        uintptr_t marker[2];
+        marker_fetch(STM_SEGMENT->running_thread, marker);
+        STM_PSEGMENT->modified_old_objects_markers =
+            list_append2(STM_PSEGMENT->modified_old_objects_markers,
+                         marker[0], marker[1]);
+
+        release_marker_lock(STM_SEGMENT->segment_base);
+
         /* We need to privatize the pages containing the object, if they
            are still SHARED_PAGE.  The common case is that there is only
            one page in total. */
     else {
         /* call the contention manager, and then retry (unless we were
            aborted). */
-        write_write_contention_management(lock_idx);
+        write_write_contention_management(lock_idx, obj);
         goto retry;
     }
 
     assert(STM_PSEGMENT->transaction_state == TS_NONE);
     change_timing_state(STM_TIME_RUN_CURRENT);
     STM_PSEGMENT->start_time = tl->_timing_cur_start;
+    STM_PSEGMENT->signalled_to_commit_soon = false;
     STM_PSEGMENT->safe_point = SP_RUNNING;
+#ifndef NDEBUG
+    STM_PSEGMENT->marker_inev[1] = 99999999999999999L;
+#endif
+    if (jmpbuf == NULL)
+        marker_fetch_inev();
     STM_PSEGMENT->transaction_state = (jmpbuf != NULL ? TS_REGULAR
                                                       : TS_INEVITABLE);
     STM_SEGMENT->jmpbuf_ptr = jmpbuf;
     }
 
     assert(list_is_empty(STM_PSEGMENT->modified_old_objects));
+    assert(list_is_empty(STM_PSEGMENT->modified_old_objects_markers));
     assert(list_is_empty(STM_PSEGMENT->young_weakrefs));
     assert(tree_is_cleared(STM_PSEGMENT->young_outside_nursery));
     assert(tree_is_cleared(STM_PSEGMENT->nursery_objects_shadows));
     assert(tree_is_cleared(STM_PSEGMENT->callbacks_on_abort));
     assert(STM_PSEGMENT->objects_pointing_to_nursery == NULL);
     assert(STM_PSEGMENT->large_overflow_objects == NULL);
+#ifndef NDEBUG
+    /* this should not be used when objects_pointing_to_nursery == NULL */
+    STM_PSEGMENT->modified_old_objects_markers_num_old = 99999999999999999L;
+#endif
 
     check_nursery_at_transaction_start();
 }
             ({
                 if (was_read_remote(remote_base, item, remote_version)) {
                     /* A write-read conflict! */
-                    write_read_contention_management(i);
+                    write_read_contention_management(i, item);
 
                     /* If we reach this point, we didn't abort, but maybe we
                        had to wait for the other thread to commit.  If we
        It is first copied into the shared pages, and then into other
        segments' own private pages.  (The second part might be done
        later; call synchronize_objects_flush() to flush this queue.)
+
+       Must be called with the privatization lock acquired.
     */
     assert(!_is_young(obj));
     assert(obj->stm_flags & GCFLAG_WRITE_BARRIER);
     ssize_t obj_size = stmcb_size_rounded_up(
         (struct object_s *)REAL_ADDRESS(STM_SEGMENT->segment_base, obj));
     OPT_ASSERT(obj_size >= 16);
+    assert(STM_PSEGMENT->privatization_lock == 1);
 
     if (LIKELY(is_small_uniform(obj))) {
         _synchronize_fragment((stm_char *)obj, obj_size);
     if (STM_PSEGMENT->large_overflow_objects == NULL)
         return;
 
+    acquire_privatization_lock();
     LIST_FOREACH_R(STM_PSEGMENT->large_overflow_objects, object_t *,
                    synchronize_object_enqueue(item));
     synchronize_objects_flush();
+    release_privatization_lock();
 }
 
 static void push_modified_to_other_segments(void)
 {
+    acquire_privatization_lock();
     LIST_FOREACH_R(
         STM_PSEGMENT->modified_old_objects,
         object_t * /*item*/,
                private pages as needed */
             synchronize_object_enqueue(item);
         }));
+    release_privatization_lock();
 
     synchronize_objects_flush();
     list_clear(STM_PSEGMENT->modified_old_objects);
+    list_clear(STM_PSEGMENT->modified_old_objects_markers);
 }
 
 static void _finish_transaction(int attribute_to)
         }));
 
     list_clear(pseg->modified_old_objects);
+    list_clear(pseg->modified_old_objects_markers);
 }
 
 static void abort_data_structures_from_segment_num(int segment_num)
                        (int)pseg->transaction_state);
     }
 
+    /* if we don't have marker information already, look up and preserve
+       the marker information from the shadowstack as a string */
+    marker_default_for_abort(pseg);
+
     /* throw away the content of the nursery */
     long bytes_in_nursery = throw_away_nursery(pseg);
 
        value before the transaction start */
     stm_thread_local_t *tl = pseg->pub.running_thread;
     assert(tl->shadowstack >= pseg->shadowstack_at_start_of_transaction);
+    pseg->shadowstack_at_abort = tl->shadowstack;
     tl->shadowstack = pseg->shadowstack_at_start_of_transaction;
     tl->thread_local_obj = pseg->threadlocal_at_start_of_transaction;
     tl->last_abort__bytes_in_nursery = bytes_in_nursery;
     if (STM_PSEGMENT->transaction_state == TS_REGULAR) {
         dprintf(("become_inevitable: %s\n", msg));
 
+        marker_fetch_inev();
         wait_for_end_of_inevitable_transaction(NULL);
         STM_PSEGMENT->transaction_state = TS_INEVITABLE;
         STM_SEGMENT->jmpbuf_ptr = NULL;

File c7/stm/core.h

     /* List of old objects (older than the current transaction) that the
        current transaction attempts to modify.  This is used to track
        the STM status: they are old objects that where written to and
-       that need to be copied to other segments upon commit. */
+       that need to be copied to other segments upon commit.  Note that
+       every object takes three list items: the object, and two words for
+       the location marker. */
     struct list_s *modified_old_objects;
 
+    /* For each entry in 'modified_old_objects', we have two entries
+       in the following list, which give the marker at the time we added
+       the entry to modified_old_objects. */
+    struct list_s *modified_old_objects_markers;
+    uintptr_t modified_old_objects_markers_num_old;
+
     /* List of out-of-nursery objects that may contain pointers to
        nursery objects.  This is used to track the GC status: they are
        all objects outside the nursery on which an stm_write() occurred
     /* For sleeping contention management */
     bool signal_when_done;
 
+    /* This lock is acquired when that segment calls synchronize_object_now.
+       On the rare event of a page_privatize(), the latter will acquire
+       all the locks in all segments.  Otherwise, for the common case,
+       it's cheap.  (The set of all 'privatization_lock' in all segments
+       works like one single read-write lock, with page_privatize() acquiring
+       the write lock; but this variant is more efficient for the case of
+       many reads / rare writes.) */
+    uint8_t privatization_lock;
+
+    /* This lock is acquired when we mutate 'modified_old_objects' but
+       we don't have the global mutex.  It is also acquired during minor
+       collection.  It protects against a different thread that tries to
+       get this segment's marker corresponding to some object, or to
+       expand the marker into a full description. */
+    uint8_t marker_lock;
+
     /* In case of abort, we restore the 'shadowstack' field and the
        'thread_local_obj' field. */
     struct stm_shadowentry_s *shadowstack_at_start_of_transaction;
     object_t *threadlocal_at_start_of_transaction;
+    struct stm_shadowentry_s *shadowstack_at_abort;
+
+    /* Already signalled to commit soon: */
+    bool signalled_to_commit_soon;
 
     /* For debugging */
 #ifndef NDEBUG
     stm_char *sq_fragments[SYNC_QUEUE_SIZE];
     int sq_fragsizes[SYNC_QUEUE_SIZE];
     int sq_len;
+
+    /* Temporarily stores the marker information */
+    char marker_self[_STM_MARKER_LEN];
+    char marker_other[_STM_MARKER_LEN];
+    uintptr_t marker_inev[2];  /* marker where this thread became inevitable */
 };
 
 enum /* safe_point */ {
 static
 #endif
        char *stm_object_pages;
+static int stm_object_pages_fd;
 static stm_thread_local_t *stm_all_thread_locals = NULL;
 
 static uint8_t write_locks[WRITELOCK_END - WRITELOCK_START];
 static void copy_object_to_shared(object_t *obj, int source_segment_num);
 static void synchronize_object_enqueue(object_t *obj);
 static void synchronize_objects_flush(void);
+
+static inline void acquire_privatization_lock(void)
+{
+    uint8_t *lock = (uint8_t *)REAL_ADDRESS(STM_SEGMENT->segment_base,
+                                            &STM_PSEGMENT->privatization_lock);
+    spinlock_acquire(*lock);
+}
+
+static inline void release_privatization_lock(void)
+{
+    uint8_t *lock = (uint8_t *)REAL_ADDRESS(STM_SEGMENT->segment_base,
+                                            &STM_PSEGMENT->privatization_lock);
+    spinlock_release(*lock);
+}
+
+static inline void acquire_marker_lock(char *segment_base)
+{
+    uint8_t *lock = (uint8_t *)REAL_ADDRESS(segment_base,
+                                            &STM_PSEGMENT->marker_lock);
+    spinlock_acquire(*lock);
+}
+
+static inline void release_marker_lock(char *segment_base)
+{
+    uint8_t *lock = (uint8_t *)REAL_ADDRESS(segment_base,
+                                            &STM_PSEGMENT->marker_lock);
+    spinlock_release(*lock);
+}

File c7/stm/forksupport.c

 
 
 static char *fork_big_copy = NULL;
+static int fork_big_copy_fd;
 static stm_thread_local_t *fork_this_tl;
 static bool fork_was_in_transaction;
 
-static char *setup_mmap(char *reason);            /* forward, in setup.c */
-static void setup_protection_settings(void);      /* forward, in setup.c */
-static pthread_t *_get_cpth(stm_thread_local_t *);/* forward, in setup.c */
-
-
 static bool page_is_null(char *p)
 {
     long *q = (long *)p;
     /* Make a new mmap at some other address, but of the same size as
        the standard mmap at stm_object_pages
     */
-    char *big_copy = setup_mmap("stmgc's fork support");
+    int big_copy_fd;
+    char *big_copy = setup_mmap("stmgc's fork support", &big_copy_fd);
 
     /* Copy each of the segment infos into the new mmap, nurseries,
        and associated read markers
 
     assert(fork_big_copy == NULL);
     fork_big_copy = big_copy;
+    fork_big_copy_fd = big_copy_fd;
     fork_this_tl = this_tl;
     fork_was_in_transaction = was_in_transaction;
 
     assert(fork_big_copy != NULL);
     munmap(fork_big_copy, TOTAL_MEMORY);
     fork_big_copy = NULL;
+    close_fd_mmap(fork_big_copy_fd);
     bool was_in_transaction = fork_was_in_transaction;
 
     s_mutex_unlock();
     if (res != stm_object_pages)
         stm_fatalerror("after fork: mremap failed: %m");
     fork_big_copy = NULL;
+    close_fd_mmap(stm_object_pages_fd);
+    stm_object_pages_fd = fork_big_copy_fd;
 
     /* Unregister all other stm_thread_local_t, mostly as a way to free
        the memory used by the shadowstacks

File c7/stm/gcpage.c

     /* uncommon case: need to initialize some more pages */
     spinlock_acquire(lock_growth_large);
 
-    if (addr + size > uninitialized_page_start) {
+    char *start = uninitialized_page_start;
+    if (addr + size > start) {
         uintptr_t npages;
-        npages = (addr + size - uninitialized_page_start) / 4096UL;
+        npages = (addr + size - start) / 4096UL;
         npages += GCPAGE_NUM_PAGES;
-        if (uninitialized_page_stop - uninitialized_page_start <
-                npages * 4096UL) {
+        if (uninitialized_page_stop - start < npages * 4096UL) {
             stm_fatalerror("out of memory!");   /* XXX */
         }
-        setup_N_pages(uninitialized_page_start, npages);
-        __sync_synchronize();
-        uninitialized_page_start += npages * 4096UL;
+        setup_N_pages(start, npages);
+        if (!__sync_bool_compare_and_swap(&uninitialized_page_start,
+                                          start,
+                                          start + npages * 4096UL)) {
+            stm_fatalerror("uninitialized_page_start changed?");
+        }
     }
     spinlock_release(lock_growth_large);
     return addr;
         struct stm_shadowentry_s *current = tl->shadowstack;
         struct stm_shadowentry_s *base = tl->shadowstack_base;
         while (current-- != base) {
-            assert(current->ss != (object_t *)-1);
-            mark_visit_object(current->ss, segment_base);
+            if ((((uintptr_t)current->ss) & 3) == 0)
+                mark_visit_object(current->ss, segment_base);
         }
         mark_visit_object(tl->thread_local_obj, segment_base);
 
     }
 }
 
+static void mark_visit_from_markers(void)
+{
+    long j;
+    for (j = 1; j <= NB_SEGMENTS; j++) {
+        char *base = get_segment_base(j);
+        struct list_s *lst = get_priv_segment(j)->modified_old_objects_markers;
+        uintptr_t i;
+        for (i = list_count(lst); i > 0; i -= 2) {
+            mark_visit_object((object_t *)list_item(lst, i - 1), base);
+        }
+        if (get_priv_segment(j)->transaction_state == TS_INEVITABLE) {
+            uintptr_t marker_inev_obj = get_priv_segment(j)->marker_inev[1];
+            mark_visit_object((object_t *)marker_inev_obj, base);
+        }
+    }
+}
+
 static void clean_up_segment_lists(void)
 {
     long i;
     /* marking */
     LIST_CREATE(mark_objects_to_trace);
     mark_visit_from_modified_objects();
+    mark_visit_from_markers();
     mark_visit_from_roots();
     LIST_FREE(mark_objects_to_trace);
 

File c7/stm/largemalloc.c

     mscan->size = request_size;
     mscan->prev_size = BOTH_CHUNKS_USED;
     increment_total_allocated(request_size + LARGE_MALLOC_OVERHEAD);
+#ifndef NDEBUG
+    memset((char *)&mscan->d, 0xda, request_size);
+#endif
 
     lm_unlock();
 

File c7/stm/list.h

 
 #define LIST_APPEND(lst, e)   ((lst) = list_append((lst), (uintptr_t)(e)))
 
+static inline struct list_s *list_append2(struct list_s *lst,
+                                          uintptr_t item0, uintptr_t item1)
+{
+    uintptr_t index = lst->count;
+    lst->count += 2;
+    if (UNLIKELY(index >= lst->last_allocated))
+        lst = _list_grow(lst, index + 1);
+    lst->items[index + 0] = item0;
+    lst->items[index + 1] = item1;
+    return lst;
+}
+
 
 static inline void list_clear(struct list_s *lst)
 {
     lst->items[index] = newitem;
 }
 
+static inline uintptr_t *list_ptr_to_item(struct list_s *lst, uintptr_t index)
+{
+    return &lst->items[index];
+}
+
 #define LIST_FOREACH_R(lst, TYPE, CODE)         \
     do {                                        \
         struct list_s *_lst = (lst);            \

File c7/stm/marker.c

+#ifndef _STM_CORE_H_
+# error "must be compiled via stmgc.c"
+#endif
+
+
+void (*stmcb_expand_marker)(char *segment_base, uintptr_t odd_number,
+                            object_t *following_object,
+                            char *outputbuf, size_t outputbufsize);
+
+void (*stmcb_debug_print)(const char *cause, double time,
+                          const char *marker);
+
+
+static void marker_fetch(stm_thread_local_t *tl, uintptr_t marker[2])
+{
+    /* fetch the current marker from the tl's shadow stack,
+       and return it in 'marker[2]'. */
+    struct stm_shadowentry_s *current = tl->shadowstack - 1;
+    struct stm_shadowentry_s *base = tl->shadowstack_base;
+
+    /* The shadowstack_base contains STM_STACK_MARKER_OLD, which is
+       a convenient stopper for the loop below but which shouldn't
+       be returned. */
+    assert(base->ss == (object_t *)STM_STACK_MARKER_OLD);
+
+    while (!(((uintptr_t)current->ss) & 1)) {
+        current--;
+        assert(current >= base);
+    }
+    if (current != base) {
+        /* found the odd marker */
+        marker[0] = (uintptr_t)current[0].ss;
+        marker[1] = (uintptr_t)current[1].ss;
+    }
+    else {
+        /* no marker found */
+        marker[0] = 0;
+        marker[1] = 0;
+    }
+}
+
+static void marker_expand(uintptr_t marker[2], char *segment_base,
+                          char *outmarker)
+{
+    /* Expand the marker given by 'marker[2]' into a full string.  This
+       works assuming that the marker was produced inside the segment
+       given by 'segment_base'.  If that's from a different thread, you
+       must first acquire the corresponding 'marker_lock'. */
+    assert(_has_mutex());
+    outmarker[0] = 0;
+    if (marker[0] == 0)
+        return;   /* no marker entry found */
+    if (stmcb_expand_marker != NULL) {
+        stmcb_expand_marker(segment_base, marker[0], (object_t *)marker[1],
+                            outmarker, _STM_MARKER_LEN);
+    }
+}
+
+static void marker_default_for_abort(struct stm_priv_segment_info_s *pseg)
+{
+    if (pseg->marker_self[0] != 0)
+        return;   /* already collected an entry */
+
+    uintptr_t marker[2];
+    marker_fetch(pseg->pub.running_thread, marker);
+    marker_expand(marker, pseg->pub.segment_base, pseg->marker_self);
+    pseg->marker_other[0] = 0;
+}
+
+char *_stm_expand_marker(void)
+{
+    /* for tests only! */
+    static char _result[_STM_MARKER_LEN];
+    uintptr_t marker[2];
+    _result[0] = 0;
+    s_mutex_lock();
+    marker_fetch(STM_SEGMENT->running_thread, marker);
+    marker_expand(marker, STM_SEGMENT->segment_base, _result);
+    s_mutex_unlock();
+    return _result;
+}
+
+static void marker_copy(stm_thread_local_t *tl,
+                        struct stm_priv_segment_info_s *pseg,
+                        enum stm_time_e attribute_to, double time)
+{
+    /* Copies the marker information from pseg to tl.  This is called
+       indirectly from abort_with_mutex(), but only if the lost time is
+       greater than that of the previous recorded marker.  By contrast,
+       pseg->marker_self has been filled already in all cases.  The
+       reason for the two steps is that we must fill pseg->marker_self
+       earlier than now (some objects may be GCed), but we only know
+       here the total time it gets attributed.
+    */
+    if (stmcb_debug_print) {
+        stmcb_debug_print(timer_names[attribute_to], time, pseg->marker_self);
+    }
+    if (time * 0.99 > tl->longest_marker_time) {
+        tl->longest_marker_state = attribute_to;
+        tl->longest_marker_time = time;
+        memcpy(tl->longest_marker_self, pseg->marker_self, _STM_MARKER_LEN);
+        memcpy(tl->longest_marker_other, pseg->marker_other, _STM_MARKER_LEN);
+    }
+    pseg->marker_self[0] = 0;
+    pseg->marker_other[0] = 0;
+}
+
+static void marker_fetch_obj_write(uint8_t in_segment_num, object_t *obj,
+                                   uintptr_t marker[2])
+{
+    assert(_has_mutex());
+
+    /* here, we acquired the other thread's marker_lock, which means that:
+
+       (1) it has finished filling 'modified_old_objects' after it sets
+           up the write_locks[] value that we're conflicting with
+
+       (2) it is not mutating 'modified_old_objects' right now (we have
+           the global mutex_lock at this point too).
+    */
+    long i;
+    struct stm_priv_segment_info_s *pseg = get_priv_segment(in_segment_num);
+    struct list_s *mlst = pseg->modified_old_objects;
+    struct list_s *mlstm = pseg->modified_old_objects_markers;
+    for (i = list_count(mlst); --i >= 0; ) {
+        if (list_item(mlst, i) == (uintptr_t)obj) {
+            assert(list_count(mlstm) == 2 * list_count(mlst));
+            marker[0] = list_item(mlstm, i * 2 + 0);
+            marker[1] = list_item(mlstm, i * 2 + 1);
+            return;
+        }
+    }
+    marker[0] = 0;
+    marker[1] = 0;
+}
+
+static void marker_contention(int kind, bool abort_other,
+                              uint8_t other_segment_num, object_t *obj)
+{
+    uintptr_t self_marker[2];
+    uintptr_t other_marker[2];
+    struct stm_priv_segment_info_s *my_pseg, *other_pseg;
+
+    my_pseg = get_priv_segment(STM_SEGMENT->segment_num);
+    other_pseg = get_priv_segment(other_segment_num);
+
+    char *my_segment_base = STM_SEGMENT->segment_base;
+    char *other_segment_base = get_segment_base(other_segment_num);
+
+    acquire_marker_lock(other_segment_base);
+
+    /* Collect the location for myself.  It's usually the current
+       location, except in a write-read abort, in which case it's the
+       older location of the write. */
+    if (kind == WRITE_READ_CONTENTION)
+        marker_fetch_obj_write(my_pseg->pub.segment_num, obj, self_marker);
+    else
+        marker_fetch(my_pseg->pub.running_thread, self_marker);
+
+    /* Expand this location into either my_pseg->marker_self or
+       other_pseg->marker_other, depending on who aborts. */
+    marker_expand(self_marker, my_segment_base,
+                  abort_other ? other_pseg->marker_other
+                              : my_pseg->marker_self);
+
+    /* For some categories, we can also collect the relevant information
+       for the other segment. */
+    char *outmarker = abort_other ? other_pseg->marker_self
+                                  : my_pseg->marker_other;
+    switch (kind) {
+    case WRITE_WRITE_CONTENTION:
+        marker_fetch_obj_write(other_segment_num, obj, other_marker);
+        marker_expand(other_marker, other_segment_base, outmarker);
+        break;
+    case INEVITABLE_CONTENTION:
+        assert(abort_other == false);
+        other_marker[0] = other_pseg->marker_inev[0];
+        other_marker[1] = other_pseg->marker_inev[1];
+        marker_expand(other_marker, other_segment_base, outmarker);
+        break;
+    case WRITE_READ_CONTENTION:
+        strcpy(outmarker, "<read at unknown location>");
+        break;
+    default:
+        outmarker[0] = 0;
+        break;
+    }
+
+    release_marker_lock(other_segment_base);
+}
+
+static void marker_fetch_inev(void)
+{
+    uintptr_t marker[2];
+    marker_fetch(STM_SEGMENT->running_thread, marker);
+    STM_PSEGMENT->marker_inev[0] = marker[0];
+    STM_PSEGMENT->marker_inev[1] = marker[1];
+}

File c7/stm/marker.h

+
+static void marker_fetch(stm_thread_local_t *tl, uintptr_t marker[2]);
+static void marker_fetch_inev(void);
+static void marker_expand(uintptr_t marker[2], char *segment_base,
+                          char *outmarker);
+static void marker_default_for_abort(struct stm_priv_segment_info_s *pseg);
+static void marker_copy(stm_thread_local_t *tl,
+                        struct stm_priv_segment_info_s *pseg,
+                        enum stm_time_e attribute_to, double time);
+
+static void marker_contention(int kind, bool abort_other,
+                              uint8_t other_segment_num, object_t *obj);

File c7/stm/nursery.c

     stm_thread_local_t *tl = STM_SEGMENT->running_thread;
     struct stm_shadowentry_s *current = tl->shadowstack;
     struct stm_shadowentry_s *base = tl->shadowstack_base;
-    while (current-- != base) {
-        assert(current->ss != (object_t *)-1);
-        minor_trace_if_young(&current->ss);
+    while (1) {
+        --current;
+        OPT_ASSERT(current >= base);
+
+        uintptr_t x = (uintptr_t)current->ss;
+
+        if ((x & 3) == 0) {
+            /* the stack entry is a regular pointer (possibly NULL) */
+            minor_trace_if_young(&current->ss);
+        }
+        else if (x == STM_STACK_MARKER_NEW) {
+            /* the marker was not already seen: mark it as seen,
+               but continue looking more deeply in the shadowstack */
+            current->ss = (object_t *)STM_STACK_MARKER_OLD;
+        }
+        else if (x == STM_STACK_MARKER_OLD) {
+            /* the marker was already seen: we can stop the
+               root stack tracing at this point */
+            break;
+        }
+        else {
+            /* it is an odd-valued marker, ignore */
+        }
     }
     minor_trace_if_young(&tl->thread_local_obj);
 }
 
         _collect_now(obj);
 
+        XXX acquire_privatization_lock(); release_privatization_lock(); ?
         synchronize_object_enqueue(obj);
 
         /* the list could have moved while appending */
                    _collect_now(item));
 }
 
+static void collect_roots_from_markers(uintptr_t num_old)
+{
+    /* visit the marker objects */
+    struct list_s *mlst = STM_PSEGMENT->modified_old_objects_markers;
+    STM_PSEGMENT->modified_old_objects_markers_num_old = list_count(mlst);
+    uintptr_t i, total = list_count(mlst);
+    assert((total & 1) == 0);
+    for (i = num_old + 1; i < total; i += 2) {
+        minor_trace_if_young((object_t **)list_ptr_to_item(mlst, i));
+    }
+    if (STM_PSEGMENT->transaction_state == TS_INEVITABLE) {
+        uintptr_t *pmarker_inev_obj = (uintptr_t *)
+            REAL_ADDRESS(STM_SEGMENT->segment_base,
+                         &STM_PSEGMENT->marker_inev[1]);
+        minor_trace_if_young((object_t **)pmarker_inev_obj);
+    }
+}
+
 static size_t throw_away_nursery(struct stm_priv_segment_info_s *pseg)
 {
     /* reset the nursery by zeroing it */
 
     realnursery = REAL_ADDRESS(pseg->pub.segment_base, _stm_nursery_start);
     nursery_used = pseg->pub.nursery_current - (stm_char *)_stm_nursery_start;
+    if (nursery_used > NB_NURSERY_PAGES * 4096) {
+        /* possible in rare cases when the program artificially advances
+           its own nursery_current */
+        nursery_used = NB_NURSERY_PAGES * 4096;
+    }
     OPT_ASSERT((nursery_used & 7) == 0);
     memset(realnursery, 0, nursery_used);
 
 
     dprintf(("minor_collection commit=%d\n", (int)commit));
 
+    acquire_marker_lock(STM_SEGMENT->segment_base);
+
     STM_PSEGMENT->minor_collect_will_commit_now = commit;
     if (!commit) {
+        /* We should commit soon, probably. This is kind of a
+           workaround for the broken stm_should_break_transaction of
+           pypy that doesn't want to commit any more after a minor
+           collection. It may, however, always be a good idea... */
+        stmcb_commit_soon();
+
         /* 'STM_PSEGMENT->overflow_number' is used now by this collection,
            in the sense that it's copied to the overflow objects */
         STM_PSEGMENT->overflow_number_has_been_used = true;
     /* All the objects we move out of the nursery become "overflow"
        objects.  We use the list 'objects_pointing_to_nursery'
        to hold the ones we didn't trace so far. */
+    uintptr_t num_old;
     if (STM_PSEGMENT->objects_pointing_to_nursery == NULL) {
         STM_PSEGMENT->objects_pointing_to_nursery = list_create();
 
            into objects_pointing_to_nursery, but instead we use the
            following shortcut */
         collect_modified_old_objects();
+        num_old = 0;
     }
     else {
+        num_old = STM_PSEGMENT->modified_old_objects_markers_num_old;
         abort();  // handle specially the objects_pointing_to_nursery already there
     }
 
+    collect_roots_from_markers(num_old);
+
     collect_roots_in_nursery();
 
     collect_oldrefs_to_nursery();
 
     assert(MINOR_NOTHING_TO_DO(STM_PSEGMENT));
     assert(list_is_empty(STM_PSEGMENT->objects_pointing_to_nursery));
+
+    release_marker_lock(STM_SEGMENT->segment_base);
 }
 
 static void minor_collection(bool commit)

File c7/stm/nursery.h

 
 /* '_stm_nursery_section_end' is either NURSERY_END or NSE_SIGxxx */
 #define NSE_SIGPAUSE   STM_TIME_WAIT_OTHER
+#define NSE_SIGCOMMITSOON   STM_TIME_SYNC_COMMIT_SOON
 
 
 static uint32_t highest_overflow_number;

File c7/stm/pages.c

        can only be remapped to page N in another segment */
     assert(((addr - stm_object_pages) / 4096UL - pgoff) % NB_PAGES == 0);
 
+#ifdef USE_REMAP_FILE_PAGES
     int res = remap_file_pages(addr, size, 0, pgoff, 0);
     if (UNLIKELY(res < 0))
         stm_fatalerror("remap_file_pages: %m");
+#else
+    char *res = mmap(addr, size,
+                     PROT_READ | PROT_WRITE,
+                     (MAP_PAGES_FLAGS & ~MAP_ANONYMOUS) | MAP_FIXED,
+                     stm_object_pages_fd, pgoff * 4096UL);
+    if (UNLIKELY(res != addr))
+        stm_fatalerror("mmap (remapping page): %m");
+#endif
 }
 
 static void pages_initialize_shared(uintptr_t pagenum, uintptr_t count)
 {
     /* check this thread's 'pages_privatized' bit */
     uint64_t bitmask = 1UL << (STM_SEGMENT->segment_num - 1);
-    struct page_shared_s *ps = &pages_privatized[pagenum - PAGE_FLAG_START];
+    volatile struct page_shared_s *ps = (volatile struct page_shared_s *)
+        &pages_privatized[pagenum - PAGE_FLAG_START];
     if (ps->by_segment & bitmask) {
         /* the page is already privatized; nothing to do */
         return;
     }
 
-#ifndef NDEBUG
-    spinlock_acquire(lock_pages_privatizing[STM_SEGMENT->segment_num]);
-#endif
+    long i;
+    for (i = 1; i <= NB_SEGMENTS; i++) {
+        spinlock_acquire(get_priv_segment(i)->privatization_lock);
+    }
 
     /* add this thread's 'pages_privatized' bit */
-    __sync_fetch_and_add(&ps->by_segment, bitmask);
+    ps->by_segment |= bitmask;
 
     /* "unmaps" the page to make the address space location correspond
        again to its underlying file offset (XXX later we should again
     /* copy the content from the shared (segment 0) source */
     pagecopy(new_page, stm_object_pages + pagenum * 4096UL);
 
-#ifndef NDEBUG
-    spinlock_release(lock_pages_privatizing[STM_SEGMENT->segment_num]);
-#endif
+    for (i = NB_SEGMENTS; i >= 1; i--) {
+        spinlock_release(get_priv_segment(i)->privatization_lock);
+    }
 }
 
 static void _page_do_reshare(long segnum, uintptr_t pagenum)
 
 static void pages_setup_readmarkers_for_nursery(void)
 {
+#ifdef USE_REMAP_FILE_PAGES
     /* The nursery page's read markers are never read, but must still
        be writeable.  We'd like to map the pages to a general "trash
        page"; missing one, we remap all the pages over to the same one.
             /* errors here ignored */
         }
     }
+#endif
 }

File c7/stm/pages.h

 #define PAGE_FLAG_START   END_NURSERY_PAGE
 #define PAGE_FLAG_END     NB_PAGES
 
+#define USE_REMAP_FILE_PAGES
+
 struct page_shared_s {
 #if NB_SEGMENTS <= 8
     uint8_t by_segment;
 };
 
 static struct page_shared_s pages_privatized[PAGE_FLAG_END - PAGE_FLAG_START];
-/* Rules for concurrent access to this array, possibly with is_private_page():
-
-   - we clear bits only during major collection, when all threads are
-     synchronized anyway
-
-   - we set only the bit corresponding to our segment number, using
-     an atomic addition; and we do it _before_ we actually make the
-     page private.
-
-   - concurrently, other threads checking the bits might (rarely)
-     get the answer 'true' to is_private_page() even though it is not
-     actually private yet.  This inconsistency is in the direction
-     that we want for synchronize_object_now().
-*/
 
 static void pages_initialize_shared(uintptr_t pagenum, uintptr_t count);
 static void page_privatize(uintptr_t pagenum);
     if (pages_privatized[pagenum - PAGE_FLAG_START].by_segment != 0)
         page_reshare(pagenum);
 }
-
-#ifndef NDEBUG
-static char lock_pages_privatizing[NB_SEGMENTS + 1] = { 0 };
-#endif

File c7/stm/setup.c

 #endif
 
 
-static char *setup_mmap(char *reason)
+#ifdef USE_REMAP_FILE_PAGES
+static char *setup_mmap(char *reason, int *ignored)
 {
     char *result = mmap(NULL, TOTAL_MEMORY,
                         PROT_READ | PROT_WRITE,
 
     return result;
 }
+static void close_fd_mmap(int ignored)
+{
+}
+#else
+#include <fcntl.h>           /* For O_* constants */
+static char *setup_mmap(char *reason, int *map_fd)
+{
+    char name[128];
+    sprintf(name, "/stmgc-c7-bigmem-%ld-%.18e",
+            (long)getpid(), get_stm_time());
+
+    /* Create the big shared memory object, and immediately unlink it.
+       There is a small window where if this process is killed the
+       object is left around.  It doesn't seem possible to do anything
+       about it...
+    */
+    int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
+    shm_unlink(name);
+
+    if (fd == -1) {
+        stm_fatalerror("%s failed (stm_open): %m", reason);
+    }
+    if (ftruncate(fd, TOTAL_MEMORY) != 0) {
+        stm_fatalerror("%s failed (ftruncate): %m", reason);
+    }
+    char *result = mmap(NULL, TOTAL_MEMORY,
+                        PROT_READ | PROT_WRITE,
+                        MAP_PAGES_FLAGS & ~MAP_ANONYMOUS, fd, 0);
+    if (result == MAP_FAILED) {
+        stm_fatalerror("%s failed (mmap): %m", reason);
+    }
+    *map_fd = fd;
+    return result;
+}
+static void close_fd_mmap(int map_fd)
+{
+    close(map_fd);
+}
+#endif
 
 static void setup_protection_settings(void)
 {
            (FIRST_READMARKER_PAGE * 4096UL));
     assert(_STM_FAST_ALLOC <= NB_NURSERY_PAGES * 4096);
 
-    stm_object_pages = setup_mmap("initial stm_object_pages mmap()");
+    stm_object_pages = setup_mmap("initial stm_object_pages mmap()",
+                                  &stm_object_pages_fd);
     setup_protection_settings();
 
     long i;
         pr->objects_pointing_to_nursery = NULL;
         pr->large_overflow_objects = NULL;
         pr->modified_old_objects = list_create();
+        pr->modified_old_objects_markers = list_create();
         pr->young_weakrefs = list_create();
         pr->old_weakrefs = list_create();
         pr->young_outside_nursery = tree_create();
         pr->callbacks_on_abort = tree_create();
         pr->overflow_number = GCFLAG_OVERFLOW_NUMBER_bit0 * i;
         highest_overflow_number = pr->overflow_number;
+        pr->pub.transaction_read_version = 0xff;
     }
 
     /* The pages are shared lazily, as remap_file_pages() takes a relatively
        long time for each page.
 
-       The read markers are initially zero, which is correct:
-       STM_SEGMENT->transaction_read_version never contains zero,
-       so a null read marker means "not read" whatever the
-       current transaction_read_version is.
+       The read markers are initially zero, but we set anyway
+       transaction_read_version to 0xff in order to force the first
+       transaction to "clear" the read markers by mapping a different,
+       private range of addresses.
     */
 
     setup_sync();
         assert(pr->objects_pointing_to_nursery == NULL);
         assert(pr->large_overflow_objects == NULL);
         list_free(pr->modified_old_objects);
+        list_free(pr->modified_old_objects_markers);
         list_free(pr->young_weakrefs);
         list_free(pr->old_weakrefs);
         tree_free(pr->young_outside_nursery);
 
     munmap(stm_object_pages, TOTAL_MEMORY);
     stm_object_pages = NULL;
+    close_fd_mmap(stm_object_pages_fd);
 
     teardown_core();
     teardown_sync();
     struct stm_shadowentry_s *s = (struct stm_shadowentry_s *)start;
     tl->shadowstack = s;
     tl->shadowstack_base = s;
+    STM_PUSH_ROOT(*tl, STM_STACK_MARKER_OLD);
 }
 
 static void _done_shadow_stack(stm_thread_local_t *tl)
 {
-    assert(tl->shadowstack >= tl->shadowstack_base);
+    assert(tl->shadowstack > tl->shadowstack_base);
+    assert(tl->shadowstack_base->ss == (object_t *)STM_STACK_MARKER_OLD);
 
     char *start = (char *)tl->shadowstack_base;
     _shadowstack_trap_page(start, PROT_READ | PROT_WRITE);

File c7/stm/setup.h

+
+static char *setup_mmap(char *reason, int *map_fd);
+static void close_fd_mmap(int map_fd);
+static void setup_protection_settings(void);
+static pthread_t *_get_cpth(stm_thread_local_t *);

File c7/stm/sync.c

 #include <sys/prctl.h>
 #include <asm/prctl.h>
 
+#ifndef _STM_CORE_H_
+# error "must be compiled via stmgc.c"
+#endif
+
 
 /* Each segment can be in one of three possible states, described by
    the segment variable 'safe_point':
 static bool _safe_points_requested = false;
 #endif
 
+static void signal_other_to_commit_soon(struct stm_priv_segment_info_s *other_pseg)
+{
+    assert(_has_mutex());
+    /* never overwrite abort signals or safepoint requests
+       (too messy to deal with) */
+    if (!other_pseg->signalled_to_commit_soon
+        && !is_abort(other_pseg->pub.nursery_end)
+        && !pause_signalled) {
+        other_pseg->pub.nursery_end = NSE_SIGCOMMITSOON;
+    }
+}
+
 static void signal_everybody_to_pause_running(void)
 {
     assert(_safe_points_requested == false);
         if (STM_SEGMENT->nursery_end == NURSERY_END)
             break;    /* no safe point requested */
 
+        if (STM_SEGMENT->nursery_end == NSE_SIGCOMMITSOON) {
+            if (previous_state == -1) {
+                previous_state = change_timing_state(STM_TIME_SYNC_COMMIT_SOON);
+            }
+
+            STM_PSEGMENT->signalled_to_commit_soon = true;
+            stmcb_commit_soon();
+            if (!pause_signalled) {
+                STM_SEGMENT->nursery_end = NURSERY_END;
+                break;
+            }
+            STM_SEGMENT->nursery_end = NSE_SIGPAUSE;
+        }
         assert(STM_SEGMENT->nursery_end == NSE_SIGPAUSE);
+        assert(pause_signalled);
 
         /* If we are requested to enter a safe-point, we cannot proceed now.
            Wait until the safe-point request is removed for us. */

File c7/stm/timing.c

     return oldstate;
 }
 
-static void change_timing_state_tl(stm_thread_local_t *tl,
-                                   enum stm_time_e newstate)
+static double change_timing_state_tl(stm_thread_local_t *tl,
+                                     enum stm_time_e newstate)
 {
     TIMING_CHANGE(tl, newstate);
+    return elasped;
 }
 
 static void timing_end_transaction(enum stm_time_e attribute_to)
 {
     stm_thread_local_t *tl = STM_SEGMENT->running_thread;
     TIMING_CHANGE(tl, STM_TIME_OUTSIDE_TRANSACTION);
-    add_timing(tl, attribute_to, tl->timing[STM_TIME_RUN_CURRENT]);
+    double time_this_transaction = tl->timing[STM_TIME_RUN_CURRENT];
+    add_timing(tl, attribute_to, time_this_transaction);
     tl->timing[STM_TIME_RUN_CURRENT] = 0.0f;
+
+    if (attribute_to != STM_TIME_RUN_COMMITTED) {
+        struct stm_priv_segment_info_s *pseg =
+            get_priv_segment(STM_SEGMENT->segment_num);
+        marker_copy(tl, pseg, attribute_to, time_this_transaction);
+    }
 }
 
 static const char *timer_names[] = {
     "wait write read",
     "wait inevitable",
     "wait other",
+    "sync commit soon",
     "bookkeeping",
     "minor gc",
     "major gc",
         s_mutex_lock();
         fprintf(stderr, "thread %p:\n", tl);
         for (i = 0; i < _STM_TIME_N; i++) {
-            fprintf(stderr, "    %-24s %9u  %.3f s\n",
+            fprintf(stderr, "    %-24s %9u %8.3f s\n",
                     timer_names[i], tl->events[i], (double)tl->timing[i]);
         }
+        fprintf(stderr, "    %-24s %6s %11.6f s\n",
+                "longest recorded marker", "", tl->longest_marker_time);
+        fprintf(stderr, "    \"%.*s\"\n",
+                (int)_STM_MARKER_LEN, tl->longest_marker_self);
         s_mutex_unlock();
     }
 }

File c7/stm/timing.h

 }
 
 static enum stm_time_e change_timing_state(enum stm_time_e newstate);
-static void change_timing_state_tl(stm_thread_local_t *tl,
-                                   enum stm_time_e newstate);
+static double change_timing_state_tl(stm_thread_local_t *tl,
+                                     enum stm_time_e newstate);
 
 static void timing_end_transaction(enum stm_time_e attribute_to);
 #include "stm/pages.h"
 #include "stm/gcpage.h"
 #include "stm/sync.h"
+#include "stm/setup.h"
 #include "stm/largemalloc.h"
 #include "stm/nursery.h"
 #include "stm/contention.h"
 #include "stm/fprintcolor.h"
 #include "stm/weakref.h"
 #include "stm/timing.h"
+#include "stm/marker.h"
 
 #include "stm/misc.c"
 #include "stm/list.c"
 #include "stm/fprintcolor.c"
 #include "stm/weakref.c"
 #include "stm/timing.c"
+#include "stm/marker.c"
     STM_TIME_WAIT_WRITE_READ,
     STM_TIME_WAIT_INEVITABLE,
     STM_TIME_WAIT_OTHER,
+    STM_TIME_SYNC_COMMIT_SOON,
     STM_TIME_BOOKKEEPING,
     STM_TIME_MINOR_GC,
     STM_TIME_MAJOR_GC,
     _STM_TIME_N
 };
 
+#define _STM_MARKER_LEN  80
+
 typedef struct stm_thread_local_s {
     /* every thread should handle the shadow stack itself */
     struct stm_shadowentry_s *shadowstack, *shadowstack_base;
     float timing[_STM_TIME_N];
     double _timing_cur_start;
     enum stm_time_e _timing_cur_state;
+    /* the marker with the longest associated time so far */
+    enum stm_time_e longest_marker_state;
+    double longest_marker_time;
+    char longest_marker_self[_STM_MARKER_LEN];
+    char longest_marker_other[_STM_MARKER_LEN];
     /* the next fields are handled internally by the library */
     int associated_segment_num;
     struct stm_thread_local_s *prev, *next;
    The "size rounded up" must be a multiple of 8 and at least 16.
    "Tracing" an object means enumerating all GC references in it,
    by invoking the callback passed as argument.
+   stmcb_commit_soon() is called when it is advised to commit
+   the transaction as soon as possible in order to avoid conflicts
+   or improve performance in general.
 */
 extern ssize_t stmcb_size_rounded_up(struct object_s *);
 extern void stmcb_trace(struct object_s *, void (object_t **));
+extern void stmcb_commit_soon(void);
 
 
 /* Allocate an object of the given size, which must be a multiple
 #define STM_PUSH_ROOT(tl, p)   ((tl).shadowstack++->ss = (object_t *)(p))
 #define STM_POP_ROOT(tl, p)    ((p) = (typeof(p))((--(tl).shadowstack)->ss))
 #define STM_POP_ROOT_RET(tl)   ((--(tl).shadowstack)->ss)
+#define STM_STACK_MARKER_NEW  (-41)
+#define STM_STACK_MARKER_OLD  (-43)
 
 
 /* Every thread needs to have a corresponding stm_thread_local_t
 void stm_flush_timing(stm_thread_local_t *tl, int verbose);
 
 
+/* The markers pushed in the shadowstack are an odd number followed by a
+   regular pointer.  When needed, this library invokes this callback to
+   turn this pair into a human-readable explanation. */
+extern void (*stmcb_expand_marker)(char *segment_base, uintptr_t odd_number,
+                                   object_t *following_object,
+                                   char *outputbuf, size_t outputbufsize);
+extern void (*stmcb_debug_print)(const char *cause, double time,
+                                 const char *marker);
+
+/* Conventience macros to push the markers into the shadowstack */
+#define STM_PUSH_MARKER(tl, odd_num, p)   do {  \
+    uintptr_t _odd_num = (odd_num);             \
+    assert(_odd_num & 1);                       \
+    STM_PUSH_ROOT(tl, _odd_num);                \
+    STM_PUSH_ROOT(tl, p);                       \
+} while (0)
+
+#define STM_POP_MARKER(tl)   ({                 \
+    object_t *_popped = STM_POP_ROOT_RET(tl);   \
+    STM_POP_ROOT_RET(tl);                       \
+    _popped;                                    \
+})
+
+#define STM_UPDATE_MARKER_NUM(tl, odd_num)  do {                \
+    uintptr_t _odd_num = (odd_num);                             \
+    assert(_odd_num & 1);                                       \
+    struct stm_shadowentry_s *_ss = (tl).shadowstack - 2;       \
+    while (!(((uintptr_t)(_ss->ss)) & 1)) {                     \
+        _ss--;                                                  \
+        assert(_ss >= (tl).shadowstack_base);                   \
+    }                                                           \
+    _ss->ss = (object_t *)_odd_num;                             \
+} while (0)
+
+char *_stm_expand_marker(void);
+
+
 /* ==================== END ==================== */
 
 #endif

File c7/test/support.py

 #define STM_NB_SEGMENTS ...
 #define _STM_FAST_ALLOC ...
 #define _STM_GCFLAG_WRITE_BARRIER ...
+#define STM_STACK_MARKER_NEW ...
+#define STM_STACK_MARKER_OLD ...
 
 struct stm_shadowentry_s {
     object_t *ss;
     int associated_segment_num;
     uint32_t events[];
     float timing[];
+    int longest_marker_state;
+    double longest_marker_time;
+    char longest_marker_self[];
+    char longest_marker_other[];
     ...;
 } stm_thread_local_t;
 
 #define STM_TIME_SYNC_PAUSE ...
 
 void stm_flush_timing(stm_thread_local_t *, int);
+
+void (*stmcb_expand_marker)(char *segment_base, uintptr_t odd_number,
+                            object_t *following_object,
+                            char *outputbuf, size_t outputbufsize);
+void (*stmcb_debug_print)(const char *cause, double time,
+                          const char *marker);
+
+void stm_push_marker(stm_thread_local_t *, uintptr_t, object_t *);
+void stm_update_marker_num(stm_thread_local_t *, uintptr_t);
+void stm_pop_marker(stm_thread_local_t *);
+char *_stm_expand_marker(void);
 """)
 
 
     }
 }
 
+void stm_push_marker(stm_thread_local_t *tl, uintptr_t onum, object_t *ob)
+{
+    STM_PUSH_MARKER(*tl, onum, ob);
+}
+
+void stm_update_marker_num(stm_thread_local_t *tl, uintptr_t onum)
+{
+    STM_UPDATE_MARKER_NUM(*tl, onum);
+}
+
+void stm_pop_marker(stm_thread_local_t *tl)
+{
+    STM_POP_MARKER(*tl);
+}
+
+void stmcb_commit_soon()
+{
+}
 ''', sources=source_files,
      define_macros=[('STM_TESTS', '1'),
                     ('STM_LARGEMALLOC_TEST', '1'),
         self.current_thread = 0
 
     def teardown_method(self, meth):
+        lib.stmcb_expand_marker = ffi.NULL
+        lib.stmcb_debug_print = ffi.NULL
         tl = self.tls[self.current_thread]
         if lib._stm_in_transaction(tl) and lib.stm_is_inevitable():
             self.commit_transaction()      # must succeed!
     def pop_root(self):
         tl = self.tls[self.current_thread]
         curlength = tl.shadowstack - tl.shadowstack_base
-        if curlength == 0:
+        assert curlength >= 1
+        if curlength == 1:
             raise EmptyStack
         assert 0 < curlength <= SHADOWSTACK_LENGTH
         tl.shadowstack -= 1

File c7/test/test_gcpage.py

 
         self.start_transaction()
         assert stm_get_char(self.get_thread_local_obj()) == 'L'
+
+    def test_marker_1(self):
+        self.start_transaction()
+        p1 = stm_allocate(600)
+        stm_set_char(p1, 'o')
+        self.push_root(p1)
+        self.push_root(ffi.cast("object_t *", lib.STM_STACK_MARKER_NEW))
+        p2 = stm_allocate(600)
+        stm_set_char(p2, 't')
+        self.push_root(p2)
+        stm_major_collect()
+        assert lib._stm_total_allocated() == 2 * 616
+        #
+        p2 = self.pop_root()
+        m = self.pop_root()
+        assert m == ffi.cast("object_t *", lib.STM_STACK_MARKER_OLD)
+        p1 = self.pop_root()
+        assert stm_get_char(p1) == 'o'
+        assert stm_get_char(p2) == 't'

File c7/test/test_marker.py

+from support import *
+import py, time
+
+class TestMarker(BaseTest):
+
+    def test_marker_odd_simple(self):
+        self.start_transaction()
+        self.push_root(ffi.cast("object_t *", 29))
+        stm_minor_collect()
+        stm_major_collect()
+        # assert did not crash
+        x = self.pop_root()
+        assert int(ffi.cast("uintptr_t", x)) == 29
+
+    def test_abort_marker_no_shadowstack(self):
+        tl = self.get_stm_thread_local()
+        assert tl.longest_marker_state == lib.STM_TIME_OUTSIDE_TRANSACTION
+        assert tl.longest_marker_time == 0.0
+        #
+        self.start_transaction()
+        start = time.time()
+        while abs(time.time() - start) <= 0.1:
+            pass
+        self.abort_transaction()
+        #
+        tl = self.get_stm_thread_local()
+        assert tl.longest_marker_state == lib.STM_TIME_RUN_ABORTED_OTHER
+        assert 0.099 <= tl.longest_marker_time <= 0.9
+        assert tl.longest_marker_self[0] == '\x00'
+        assert tl.longest_marker_other[0] == '\x00'
+
+    def test_abort_marker_shadowstack(self):
+        self.start_transaction()
+        p = stm_allocate(16)
+        self.push_root(ffi.cast("object_t *", 29))
+        self.push_root(p)
+        start = time.time()
+        while abs(time.time() - start) <= 0.1:
+            pass
+        self.abort_transaction()
+        #
+        tl = self.get_stm_thread_local()
+        assert tl.longest_marker_state == lib.STM_TIME_RUN_ABORTED_OTHER
+        assert 0.099 <= tl.longest_marker_time <= 0.9
+        assert tl.longest_marker_self[0] == '\x00'
+        assert tl.longest_marker_other[0] == '\x00'
+
+    def test_abort_marker_no_shadowstack_cb(self):
+        @ffi.callback("void(char *, uintptr_t, object_t *, char *, size_t)")
+        def expand_marker(base, number, ptr, outbuf, outbufsize):
+            seen.append(1)
+        lib.stmcb_expand_marker = expand_marker
+        seen = []
+        #
+        self.start_transaction()
+        self.abort_transaction()
+        #
+        tl = self.get_stm_thread_local()
+        assert tl.longest_marker_self[0] == '\x00'
+        assert not seen
+
+    def test_abort_marker_shadowstack_cb(self):
+        @ffi.callback("void(char *, uintptr_t, object_t *, char *, size_t)")
+        def expand_marker(base, number, ptr, outbuf, outbufsize):
+            s = '%d %r\x00' % (number, ptr)
+            assert len(s) <= outbufsize
+            outbuf[0:len(s)] = s
+        lib.stmcb_expand_marker = expand_marker
+        #
+        self.start_transaction()
+        p = stm_allocate(16)
+        self.push_root(ffi.cast("object_t *", 29))
+        self.push_root(p)
+        start = time.time()
+        while abs(time.time() - start) <= 0.1:
+            pass
+        self.abort_transaction()
+        #
+        tl = self.get_stm_thread_local()
+        assert tl.longest_marker_state == lib.STM_TIME_RUN_ABORTED_OTHER
+        assert 0.099 <= tl.longest_marker_time <= 0.9
+        assert ffi.string(tl.longest_marker_self) == '29 %r' % (p,)
+        assert ffi.string(tl.longest_marker_other) == ''
+
+    def test_macros(self):
+        self.start_transaction()
+        p = stm_allocate(16)
+        tl = self.get_stm_thread_local()
+        lib.stm_push_marker(tl, 29, p)
+        p1 = self.pop_root()
+        assert p1 == p
+        p1 = self.pop_root()
+        assert p1 == ffi.cast("object_t *", 29)
+        py.test.raises(EmptyStack, self.pop_root)
+        #
+        lib.stm_push_marker(tl, 29, p)
+        lib.stm_update_marker_num(tl, 27)
+        p1 = self.pop_root()
+        assert p1 == p
+        p1 = self.pop_root()
+        assert p1 == ffi.cast("object_t *", 27)
+        py.test.raises(EmptyStack, self.pop_root)
+        #
+        lib.stm_push_marker(tl, 29, p)
+        self.push_root(p)
+        lib.stm_update_marker_num(tl, 27)
+        p1 = self.pop_root()
+        assert p1 == p
+        p1 = self.pop_root()
+        assert p1 == p
+        p1 = self.pop_root()
+        assert p1 == ffi.cast("object_t *", 27)
+        py.test.raises(EmptyStack, self.pop_root)
+        #
+        lib.stm_push_marker(tl, 29, p)
+        lib.stm_pop_marker(tl)
+        py.test.raises(EmptyStack, self.pop_root)
+
+    def test_stm_expand_marker(self):
+        @ffi.callback("void(char *, uintptr_t, object_t *, char *, size_t)")
+        def expand_marker(base, number, ptr, outbuf, outbufsize):
+            s = '%d %r\x00' % (number, ptr)
+            assert len(s) <= outbufsize
+            outbuf[0:len(s)] = s
+        lib.stmcb_expand_marker = expand_marker
+        self.start_transaction()
+        p = stm_allocate(16)
+        self.push_root(ffi.cast("object_t *", 29))
+        self.push_root(p)
+        self.push_root(stm_allocate(32))
+        self.push_root(stm_allocate(16))
+        raw = lib._stm_expand_marker()
+        assert ffi.string(raw) == '29 %r' % (p,)
+
+    def test_stmcb_debug_print(self):
+        @ffi.callback("void(char *, uintptr_t, object_t *, char *, size_t)")
+        def expand_marker(base, number, ptr, outbuf, outbufsize):
+            s = '<<<%d>>>\x00' % (number,)
+            assert len(s) <= outbufsize
+            outbuf[0:len(s)] = s
+        @ffi.callback("void(char *, double, char *)")
+        def debug_print(cause, time, marker):
+            if 0.0 < time < 1.0:
+                time = "time_ok"
+            seen.append((ffi.string(cause), time, ffi.string(marker)))
+        seen = []
+        lib.stmcb_expand_marker = expand_marker
+        lib.stmcb_debug_print = debug_print
+        #
+        self.start_transaction()
+        p = stm_allocate(16)
+        self.push_root(ffi.cast("object_t *", 29))
+        self.push_root(p)
+        self.abort_transaction()
+        #
+        assert seen == [("run aborted other", "time_ok", "<<<29>>>")]
+
+    def test_multiple_markers(self):
+        @ffi.callback("void(char *, uintptr_t, object_t *, char *, size_t)")
+        def expand_marker(base, number, ptr, outbuf, outbufsize):
+            seen.append(number)
+            s = '%d %r\x00' % (number, ptr == ffi.NULL)
+            assert len(s) <= outbufsize
+            outbuf[0:len(s)] = s
+        seen = []
+        lib.stmcb_expand_marker = expand_marker
+        #
+        self.start_transaction()
+        p = stm_allocate(16)
+        self.push_root(ffi.cast("object_t *", 27))
+        self.push_root(p)
+        self.push_root(ffi.cast("object_t *", 29))
+        self.push_root(ffi.cast("object_t *", ffi.NULL))
+        raw = lib._stm_expand_marker()
+        assert ffi.string(raw) == '29 True'
+        assert seen == [29]
+
+    def test_double_abort_markers_cb_write_write(self):
+        @ffi.callback("void(char *, uintptr_t, object_t *, char *, size_t)")
+        def expand_marker(base, number, ptr, outbuf, outbufsize):
+            s = '%d\x00' % (number,)
+            assert len(s) <= outbufsize
+            outbuf[0:len(s)] = s
+        lib.stmcb_expand_marker = expand_marker
+        p = stm_allocate_old(16)
+        #
+        self.start_transaction()
+        self.push_root(ffi.cast("object_t *", 19))
+        self.push_root(ffi.cast("object_t *", ffi.NULL))
+        stm_set_char(p, 'A')
+        self.pop_root()
+        self.pop_root()
+        self.push_root(ffi.cast("object_t *", 17))
+        self.push_root(ffi.cast("object_t *", ffi.NULL))
+        stm_minor_collect()
+        #
+        self.switch(1)
+        self.start_transaction()
+        self.push_root(ffi.cast("object_t *", 21))
+        self.push_root(ffi.cast("object_t *", ffi.NULL))
+        py.test.raises(Conflict, stm_set_char, p, 'B')
+        #
+        tl = self.get_stm_thread_local()
+        assert tl.longest_marker_state == lib.STM_TIME_RUN_ABORTED_WRITE_WRITE
+        assert ffi.string(tl.longest_marker_self) == '21'
+        assert ffi.string(tl.longest_marker_other) == '19'
+
+    def test_double_abort_markers_cb_inevitable(self):
+        @ffi.callback("void(char *, uintptr_t, object_t *, char *, size_t)")
+        def expand_marker(base, number, ptr, outbuf, outbufsize):
+            c = (base + int(ffi.cast("uintptr_t", ptr)))[8]
+            s = '%d %r\x00' % (number, c)
+            assert len(s) <= outbufsize
+            outbuf[0:len(s)] = s
+        lib.stmcb_expand_marker = expand_marker
+        #
+        self.start_transaction()
+        p = stm_allocate(16)
+        stm_set_char(p, 'A')
+        self.push_root(ffi.cast("object_t *", 19))
+        self.push_root(ffi.cast("object_t *", p))
+        self.become_inevitable()
+        self.pop_root()
+        self.pop_root()
+        self.push_root(ffi.cast("object_t *", 17))
+        self.push_root(ffi.cast("object_t *", ffi.NULL))
+        stm_minor_collect()
+        #
+        self.switch(1)
+        self.start_transaction()
+        p = stm_allocate(16)
+        stm_set_char(p, 'B')
+        self.push_root(ffi.cast("object_t *", 21))
+        self.push_root(ffi.cast("object_t *", p))
+        py.test.raises(Conflict, self.become_inevitable)
+        #
+        tl = self.get_stm_thread_local()
+        assert tl.longest_marker_state == lib.STM_TIME_RUN_ABORTED_INEVITABLE
+        assert ffi.string(tl.longest_marker_self) == "21 'B'"
+        assert ffi.string(tl.longest_marker_other) == "19 'A'"
+
+    def test_read_write_contention(self):
+        @ffi.callback("void(char *, uintptr_t, object_t *, char *, size_t)")
+        def expand_marker(base, number, ptr, outbuf, outbufsize):
+            s = '%d\x00' % (number,)
+            assert len(s) <= outbufsize
+            outbuf[0:len(s)] = s
+        lib.stmcb_expand_marker = expand_marker
+        p = stm_allocate_old(16)
+        #
+        self.start_transaction()
+        assert stm_get_char(p) == '\x00'
+        #
+        self.switch(1)
+        self.start_transaction()
+        self.push_root(ffi.cast("object_t *", 19))
+        self.push_root(ffi.cast("object_t *", ffi.NULL))
+        stm_set_char(p, 'A')
+        self.pop_root()
+        self.pop_root()
+        self.push_root(ffi.cast("object_t *", 17))
+        self.push_root(ffi.cast("object_t *", ffi.NULL))
+        py.test.raises(Conflict, self.commit_transaction)
+        #
+        tl = self.get_stm_thread_local()
+        assert tl.longest_marker_state == lib.STM_TIME_RUN_ABORTED_WRITE_READ
+        assert ffi.string(tl.longest_marker_self) == '19'
+        assert ffi.string(tl.longest_marker_other) == (
+            '<read at unknown location>')
+
+    def test_double_remote_markers_cb_write_write(self):
+        @ffi.callback("void(char *, uintptr_t, object_t *, char *, size_t)")
+        def expand_marker(base, number, ptr, outbuf, outbufsize):
+            s = '%d\x00' % (number,)
+            assert len(s) <= outbufsize
+            outbuf[0:len(s)] = s
+        lib.stmcb_expand_marker = expand_marker
+        p = stm_allocate_old(16)
+        #
+        self.start_transaction()
+        self.push_root(ffi.cast("object_t *", 19))
+        self.push_root(ffi.cast("object_t *", ffi.NULL))
+        stm_set_char(p, 'A')
+        self.pop_root()
+        self.pop_root()
+        self.push_root(ffi.cast("object_t *", 17))
+        self.push_root(ffi.cast("object_t *", ffi.NULL))
+        tl0 = self.get_stm_thread_local()
+        #
+        self.switch(1)
+        self.start_transaction()
+        self.become_inevitable()
+        self.push_root(ffi.cast("object_t *", 21))
+        self.push_root(ffi.cast("object_t *", ffi.NULL))
+        stm_set_char(p, 'B')    # aborts in #0
+        self.pop_root()
+        self.pop_root()
+        self.push_root(ffi.cast("object_t *", 23))
+        self.push_root(ffi.cast("object_t *", ffi.NULL))
+        #
+        py.test.raises(Conflict, self.switch, 0)
+        #
+        tl = self.get_stm_thread_local()
+        assert tl is tl0
+        assert tl.longest_marker_state == lib.STM_TIME_RUN_ABORTED_WRITE_WRITE
+        assert ffi.string(tl.longest_marker_self) == '19'
+        assert ffi.string(tl.longest_marker_other) == '21'
+
+    def test_double_remote_markers_cb_write_read(self):
+        @ffi.callback("void(char *, uintptr_t, object_t *, char *, size_t)")
+        def expand_marker(base, number, ptr, outbuf, outbufsize):
+            s = '%d\x00' % (number,)
+            assert len(s) <= outbufsize
+            outbuf[0:len(s)] = s
+        lib.stmcb_expand_marker = expand_marker
+        p = stm_allocate_old(16)
+        #
+        self.start_transaction()
+        assert stm_get_char(p) == '\x00'    # read
+        tl0 = self.get_stm_thread_local()
+        #
+        self.switch(1)
+        self.start_transaction()
+        self.become_inevitable()
+        self.push_root(ffi.cast("object_t *", 21))
+        self.push_root(ffi.cast("object_t *", ffi.NULL))
+        stm_set_char(p, 'B')                # write, will abort #0
+        self.pop_root()
+        self.pop_root()
+        self.push_root(ffi.cast("object_t *", 23))
+        self.push_root(ffi.cast("object_t *", ffi.NULL))
+        self.commit_transaction()
+        #
+        py.test.raises(Conflict, self.switch, 0)
+        #
+        tl = self.get_stm_thread_local()
+        assert tl is tl0
+        assert tl.longest_marker_state == lib.STM_TIME_RUN_ABORTED_WRITE_READ
+        assert ffi.string(tl.longest_marker_self)=='<read at unknown location>'
+        assert ffi.string(tl.longest_marker_other) == '21'

File c7/test/test_nursery.py

 from support import *
 import py
 
-class TestBasic(BaseTest):
+class TestNursery(BaseTest):
 
     def test_nursery_full(self):
         lib._stm_set_nursery_free_count(2048)
         self.switch(1)
         self.start_transaction()
         assert stm_get_char(new2) == 'a'
+
+    def test_marker_1(self):
+        self.start_transaction()
+        p1 = stm_allocate(600)
+        stm_set_char(p1, 'o')
+        self.push_root(p1)
+        self.push_root(ffi.cast("object_t *", lib.STM_STACK_MARKER_NEW))
+        p2 = stm_allocate(600)
+        stm_set_char(p2, 't')
+        self.push_root(p2)
+        stm_minor_collect()
+        assert lib._stm_total_allocated() == 2 * 616
+        #
+        p2 = self.pop_root()
+        m = self.pop_root()
+        assert m == ffi.cast("object_t *", lib.STM_STACK_MARKER_OLD)
+        p1 = self.pop_root()
+        assert stm_get_char(p1) == 'o'
+        assert stm_get_char(p2) == 't'
+
+    def test_marker_2(self):
+        self.start_transaction()
+        p1 = stm_allocate(600