[pypy-commit] stmgc gc-small-uniform: hg merge default
arigo
noreply at buildbot.pypy.org
Tue May 13 17:53:15 CEST 2014
Author: Armin Rigo <arigo at tunes.org>
Branch: gc-small-uniform
Changeset: r1208:7cc0f05c1049
Date: 2014-05-13 16:31 +0200
http://bitbucket.org/pypy/stmgc/changeset/7cc0f05c1049/
Log: hg merge default
diff too long, truncating to 2000 out of 2212 lines
diff --git a/c7/TODO b/c7/TODO
--- a/c7/TODO
+++ b/c7/TODO
@@ -11,3 +11,8 @@
- 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)
diff --git a/c7/demo/demo2.c b/c7/demo/demo2.c
--- a/c7/demo/demo2.c
+++ b/c7/demo/demo2.c
@@ -44,6 +44,16 @@
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;
@@ -88,6 +98,18 @@
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);
@@ -194,15 +216,24 @@
{
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);
@@ -245,6 +276,7 @@
stm_setup();
stm_register_thread_local(&stm_thread_local);
+ stmcb_expand_marker = expand_marker;
setup_list();
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
@@ -23,6 +23,8 @@
abort();
}
+void stmcb_commit_soon() {}
+
/************************************************************/
#define ARENA_SIZE (1024*1024*1024)
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
@@ -79,6 +79,8 @@
assert(n->next == *last_next);
}
+void stmcb_commit_soon() {}
+
int get_rand(int max)
{
if (max == 0)
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
@@ -39,6 +39,8 @@
visit((object_t **)&n->next);
}
+void stmcb_commit_soon() {}
+
static sem_t done;
@@ -50,6 +52,7 @@
{
int status;
stm_register_thread_local(&stm_thread_local);
+ char *org = (char *)stm_thread_local.shadowstack;
tl_counter = 0;
object_t *tmp;
@@ -65,7 +68,7 @@
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);
diff --git a/c7/doc/marker.txt b/c7/doc/marker.txt
new file mode 100644
--- /dev/null
+++ b/c7/doc/marker.txt
@@ -0,0 +1,42 @@
+
+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.
diff --git a/c7/stm/contention.c b/c7/stm/contention.c
--- a/c7/stm/contention.c
+++ b/c7/stm/contention.c
@@ -99,7 +99,8 @@
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);
@@ -161,10 +162,12 @@
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);
@@ -176,12 +179,22 @@
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();
}
@@ -189,6 +202,7 @@
/* 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) {
@@ -256,10 +270,18 @@
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();
@@ -270,7 +292,7 @@
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. */
@@ -279,12 +301,13 @@
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);
}
diff --git a/c7/stm/contention.h b/c7/stm/contention.h
--- a/c7/stm/contention.h
+++ b/c7/stm/contention.h
@@ -1,10 +1,13 @@
-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) {
diff --git a/c7/stm/core.c b/c7/stm/core.c
--- a/c7/stm/core.c
+++ b/c7/stm/core.c
@@ -14,13 +14,10 @@
#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
@@ -76,9 +73,15 @@
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));
@@ -86,6 +89,15 @@
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. */
@@ -127,7 +139,7 @@
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;
}
@@ -195,7 +207,13 @@
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;
@@ -223,12 +241,17 @@
}
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();
}
@@ -263,7 +286,7 @@
({
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
@@ -359,12 +382,15 @@
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);
@@ -444,13 +470,16 @@
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*/,
@@ -470,9 +499,11 @@
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)
@@ -614,6 +645,7 @@
}));
list_clear(pseg->modified_old_objects);
+ list_clear(pseg->modified_old_objects_markers);
}
static void abort_data_structures_from_segment_num(int segment_num)
@@ -638,6 +670,10 @@
(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);
@@ -648,6 +684,7 @@
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;
@@ -719,6 +756,7 @@
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;
diff --git a/c7/stm/core.h b/c7/stm/core.h
--- a/c7/stm/core.h
+++ b/c7/stm/core.h
@@ -75,9 +75,17 @@
/* 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
@@ -145,10 +153,30 @@
/* 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
@@ -163,6 +191,11 @@
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 */ {
@@ -185,6 +218,7 @@
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];
@@ -236,3 +270,31 @@
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);
+}
diff --git a/c7/stm/forksupport.c b/c7/stm/forksupport.c
--- a/c7/stm/forksupport.c
+++ b/c7/stm/forksupport.c
@@ -8,14 +8,10 @@
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;
@@ -74,7 +70,8 @@
/* 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
@@ -139,6 +136,7 @@
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;
@@ -163,6 +161,7 @@
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();
@@ -214,6 +213,8 @@
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
diff --git a/c7/stm/gcpage.c b/c7/stm/gcpage.c
--- a/c7/stm/gcpage.c
+++ b/c7/stm/gcpage.c
@@ -49,17 +49,20 @@
/* 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;
@@ -336,8 +339,8 @@
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);
@@ -375,6 +378,23 @@
}
}
+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;
@@ -477,6 +497,7 @@
/* 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);
diff --git a/c7/stm/largemalloc.c b/c7/stm/largemalloc.c
--- a/c7/stm/largemalloc.c
+++ b/c7/stm/largemalloc.c
@@ -353,6 +353,9 @@
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();
diff --git a/c7/stm/list.h b/c7/stm/list.h
--- a/c7/stm/list.h
+++ b/c7/stm/list.h
@@ -33,6 +33,18 @@
#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)
{
@@ -66,6 +78,11 @@
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); \
diff --git a/c7/stm/marker.c b/c7/stm/marker.c
new file mode 100644
--- /dev/null
+++ b/c7/stm/marker.c
@@ -0,0 +1,198 @@
+#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];
+}
diff --git a/c7/stm/marker.h b/c7/stm/marker.h
new file mode 100644
--- /dev/null
+++ b/c7/stm/marker.h
@@ -0,0 +1,12 @@
+
+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);
diff --git a/c7/stm/nursery.c b/c7/stm/nursery.c
--- a/c7/stm/nursery.c
+++ b/c7/stm/nursery.c
@@ -152,9 +152,29 @@
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(¤t->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(¤t->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);
}
@@ -184,6 +204,7 @@
_collect_now(obj);
+ XXX acquire_privatization_lock(); release_privatization_lock(); ?
synchronize_object_enqueue(obj);
/* the list could have moved while appending */
@@ -199,6 +220,24 @@
_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 */
@@ -207,6 +246,11 @@
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);
@@ -248,8 +292,16 @@
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;
@@ -263,6 +315,7 @@
/* 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();
@@ -272,11 +325,15 @@
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();
@@ -288,6 +345,8 @@
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)
diff --git a/c7/stm/nursery.h b/c7/stm/nursery.h
--- a/c7/stm/nursery.h
+++ b/c7/stm/nursery.h
@@ -1,6 +1,7 @@
/* '_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;
diff --git a/c7/stm/pages.c b/c7/stm/pages.c
--- a/c7/stm/pages.c
+++ b/c7/stm/pages.c
@@ -81,9 +81,18 @@
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)
@@ -108,18 +117,20 @@
{
/* 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
@@ -133,9 +144,9 @@
/* 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)
@@ -167,6 +178,7 @@
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.
@@ -185,4 +197,5 @@
/* errors here ignored */
}
}
+#endif
}
diff --git a/c7/stm/pages.h b/c7/stm/pages.h
--- a/c7/stm/pages.h
+++ b/c7/stm/pages.h
@@ -19,6 +19,8 @@
#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;
@@ -34,20 +36,6 @@
};
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);
@@ -86,7 +74,3 @@
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
diff --git a/c7/stm/setup.c b/c7/stm/setup.c
--- a/c7/stm/setup.c
+++ b/c7/stm/setup.c
@@ -3,7 +3,8 @@
#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,
@@ -13,6 +14,45 @@
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)
{
@@ -56,7 +96,8 @@
(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;
@@ -78,6 +119,7 @@
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();
@@ -85,15 +127,16 @@
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();
@@ -115,6 +158,7 @@
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);
@@ -124,6 +168,7 @@
munmap(stm_object_pages, TOTAL_MEMORY);
stm_object_pages = NULL;
+ close_fd_mmap(stm_object_pages_fd);
teardown_core();
teardown_sync();
@@ -154,11 +199,13 @@
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);
diff --git a/c7/stm/setup.h b/c7/stm/setup.h
new file mode 100644
--- /dev/null
+++ b/c7/stm/setup.h
@@ -0,0 +1,5 @@
+
+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 *);
diff --git a/c7/stm/sync.c b/c7/stm/sync.c
--- a/c7/stm/sync.c
+++ b/c7/stm/sync.c
@@ -2,6 +2,10 @@
#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':
@@ -260,6 +264,18 @@
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);
@@ -323,7 +339,21 @@
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. */
diff --git a/c7/stm/timing.c b/c7/stm/timing.c
--- a/c7/stm/timing.c
+++ b/c7/stm/timing.c
@@ -25,18 +25,26 @@
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[] = {
@@ -51,6 +59,7 @@
"wait write read",
"wait inevitable",
"wait other",
+ "sync commit soon",
"bookkeeping",
"minor gc",
"major gc",
@@ -70,9 +79,13 @@
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();
}
}
diff --git a/c7/stm/timing.h b/c7/stm/timing.h
--- a/c7/stm/timing.h
+++ b/c7/stm/timing.h
@@ -8,7 +8,7 @@
}
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);
diff --git a/c7/stmgc.c b/c7/stmgc.c
--- a/c7/stmgc.c
+++ b/c7/stmgc.c
@@ -8,6 +8,7 @@
#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"
@@ -15,6 +16,7 @@
#include "stm/fprintcolor.h"
#include "stm/weakref.h"
#include "stm/timing.h"
+#include "stm/marker.h"
#include "stm/misc.c"
#include "stm/list.c"
@@ -35,3 +37,4 @@
#include "stm/fprintcolor.c"
#include "stm/weakref.c"
#include "stm/timing.c"
+#include "stm/marker.c"
diff --git a/c7/stmgc.h b/c7/stmgc.h
--- a/c7/stmgc.h
+++ b/c7/stmgc.h
@@ -66,6 +66,7 @@
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,
@@ -73,6 +74,8 @@
_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;
@@ -90,6 +93,11 @@
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;
@@ -213,9 +221,13 @@
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
@@ -268,6 +280,8 @@
#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
@@ -370,6 +384,43 @@
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
diff --git a/c7/test/support.py b/c7/test/support.py
--- a/c7/test/support.py
+++ b/c7/test/support.py
@@ -12,6 +12,8 @@
#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;
@@ -26,6 +28,10 @@
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;
@@ -121,6 +127,17 @@
#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);
""")
@@ -275,6 +292,24 @@
}
}
+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'),
@@ -446,6 +481,8 @@
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!
@@ -517,7 +554,8 @@
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
diff --git a/c7/test/test_gcpage.py b/c7/test/test_gcpage.py
--- a/c7/test/test_gcpage.py
+++ b/c7/test/test_gcpage.py
@@ -228,3 +228,22 @@
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'
diff --git a/c7/test/test_marker.py b/c7/test/test_marker.py
new file mode 100644
--- /dev/null
+++ b/c7/test/test_marker.py
@@ -0,0 +1,340 @@
+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
More information about the pypy-commit
mailing list