Author: Remi Meier <remi.me...@inf.ethz.ch> Branch: Changeset: r1753:a92ba2549987 Date: 2015-04-09 15:18 +0200 http://bitbucket.org/pypy/stmgc/changeset/a92ba2549987/
Log: Merge with c8-hashtable diff --git a/c8/stm/core.c b/c8/stm/core.c --- a/c8/stm/core.c +++ b/c8/stm/core.c @@ -280,8 +280,14 @@ struct stm_undo_s *end = undo + cl->written_count; for (; undo < end; undo++) { if (undo->type == TYPE_POSITION_MARKER) { - fprintf(stderr, " marker %p %lu\n", - undo->marker_object, undo->marker_odd_number); + if (undo->type2 == TYPE_MODIFIED_HASHTABLE) { + fprintf(stderr, " hashtable %p\n", + undo->modif_hashtable); + } + else { + fprintf(stderr, " marker %p %lu\n", + undo->marker_object, undo->marker_odd_number); + } continue; } fprintf(stderr, " obj %p, size %d, ofs %lu: ", undo->object, @@ -383,21 +389,40 @@ struct stm_undo_s *undo = cl->written; struct stm_undo_s *end = cl->written + cl->written_count; for (; undo < end; undo++) { - if (undo->type == TYPE_POSITION_MARKER) + object_t *obj; + + if (undo->type != TYPE_POSITION_MARKER) { + /* common case: 'undo->object' was written to + in this past commit, so we must check that + it was not read by us. */ + obj = undo->object; + } + else if (undo->type2 != TYPE_MODIFIED_HASHTABLE) continue; - if (_stm_was_read(undo->object)) { - /* first reset all modified objects from the backup - copies as soon as the first conflict is detected; - then we will proceed below to update our segment from - the old (but unmodified) version to the newer version. - */ - reset_modified_from_backup_copies(my_segnum); - timing_write_read_contention(cl->written, undo); - needs_abort = true; + else { + /* the previous stm_undo_s is about a written + 'entry' object, which belongs to the hashtable + given now. Check that we haven't read the + hashtable (via stm_hashtable_list()). */ + obj = undo->modif_hashtable; + } - dprintf(("_stm_validate() failed for obj %p\n", undo->object)); - break; - } + if (LIKELY(!_stm_was_read(obj))) + continue; + + /* conflict! */ + dprintf(("_stm_validate() failed for obj %p\n", obj)); + + /* first reset all modified objects from the backup + copies as soon as the first conflict is detected; + then we will proceed below to update our segment + from the old (but unmodified) version to the newer + version. + */ + reset_modified_from_backup_copies(my_segnum); + timing_write_read_contention(cl->written, undo); + needs_abort = true; + break; } } diff --git a/c8/stm/core.h b/c8/stm/core.h --- a/c8/stm/core.h +++ b/c8/stm/core.h @@ -190,9 +190,15 @@ uintptr_t marker_odd_number; /* the odd number part of the marker */ object_t *marker_object; /* the object part of the marker */ }; + struct { + intptr_t type1; /* TYPE_POSITION_MARKER (again) */ + intptr_t type2; /* TYPE_MODIFIED_HASHTABLE */ + object_t *modif_hashtable; /* modified entry is previous stm_undo_s */ + }; }; }; #define TYPE_POSITION_MARKER (-1) +#define TYPE_MODIFIED_HASHTABLE (-2) #define SLICE_OFFSET(slice) ((slice) >> 16) #define SLICE_SIZE(slice) ((int)((slice) & 0xFFFF)) #define NEW_SLICE(offset, size) (((uint64_t)(offset)) << 16 | (size)) @@ -251,6 +257,14 @@ return stm_object_pages + segment_num * (NB_PAGES * 4096UL); } +static inline long get_num_segment_containing_address(char *addr) +{ + uintptr_t delta = addr - stm_object_pages; + uintptr_t result = delta / (NB_PAGES * 4096UL); + assert(result < NB_SEGMENTS); + return result; +} + static inline struct stm_segment_info_s *get_segment(long segment_num) { return (struct stm_segment_info_s *)REAL_ADDRESS( @@ -285,6 +299,17 @@ static void _signal_handler(int sig, siginfo_t *siginfo, void *context); static bool _stm_validate(); +static inline bool was_read_remote(char *base, object_t *obj) +{ + uint8_t other_transaction_read_version = + ((struct stm_segment_info_s *)REAL_ADDRESS(base, STM_PSEGMENT)) + ->transaction_read_version; + uint8_t rm = ((struct stm_read_marker_s *) + (base + (((uintptr_t)obj) >> 4)))->rm; + assert(rm <= other_transaction_read_version); + return rm == other_transaction_read_version; +} + static inline void _duck(void) { /* put a call to _duck() between two instructions that set 0 into a %gs-prefixed address and that may otherwise be replaced with diff --git a/c8/stm/gcpage.c b/c8/stm/gcpage.c --- a/c8/stm/gcpage.c +++ b/c8/stm/gcpage.c @@ -130,6 +130,58 @@ return o; } +static void _fill_preexisting_slice(long segnum, char *dest, + const char *src, uintptr_t size) +{ + uintptr_t np = dest - get_segment_base(segnum); + if (get_page_status_in(segnum, np / 4096) != PAGE_NO_ACCESS) + memcpy(dest, src, size); +} + +object_t *stm_allocate_preexisting(ssize_t size_rounded_up, + const char *initial_data) +{ + stm_char *np = allocate_outside_nursery_large(size_rounded_up); + uintptr_t nobj = (uintptr_t)np; + dprintf(("allocate_preexisting: %p\n", (object_t *)nobj)); + + char *nobj_seg0 = stm_object_pages + nobj; + memcpy(nobj_seg0, initial_data, size_rounded_up); + ((struct object_s *)nobj_seg0)->stm_flags = GCFLAG_WRITE_BARRIER; + + acquire_privatization_lock(STM_SEGMENT->segment_num); + DEBUG_EXPECT_SEGFAULT(false); + + long j; + for (j = 1; j < NB_SEGMENTS; j++) { + const char *src = nobj_seg0; + char *dest = get_segment_base(j) + nobj; + char *end = dest + size_rounded_up; + + while (((uintptr_t)dest) / 4096 != ((uintptr_t)end - 1) / 4096) { + uintptr_t count = 4096 - (((uintptr_t)dest) & 4095); + _fill_preexisting_slice(j, dest, src, count); + src += count; + dest += count; + } + _fill_preexisting_slice(j, dest, src, end - dest); + +#ifdef STM_TESTS + /* can't really enable this check outside tests, because there is + a change that the transaction_state changes in parallel */ + if (get_priv_segment(j)->transaction_state != TS_NONE) { + assert(!was_read_remote(get_segment_base(j), (object_t *)nobj)); + } +#endif + } + + DEBUG_EXPECT_SEGFAULT(true); + release_privatization_lock(STM_SEGMENT->segment_num); + + write_fence(); /* make sure 'nobj' is fully initialized from + all threads here */ + return (object_t *)nobj; +} /************************************************************/ @@ -249,6 +301,8 @@ } +#define TRACE_FOR_MAJOR_COLLECTION (&mark_record_trace) + static void mark_and_trace( object_t *obj, char *segment_base, /* to trace obj in */ @@ -408,7 +462,8 @@ struct stm_undo_s *modified = (struct stm_undo_s *)lst->items; struct stm_undo_s *end = (struct stm_undo_s *)(lst->items + lst->count); for (; modified < end; modified++) { - if (modified->type == TYPE_POSITION_MARKER) + if (modified->type == TYPE_POSITION_MARKER && + modified->type2 != TYPE_MODIFIED_HASHTABLE) mark_visit_possibly_new_object(modified->marker_object, pseg); } } @@ -541,6 +596,31 @@ list_set_item(lst, n, list_pop_item(lst)); } } + + /* Remove from 'modified_old_objects' all old hashtables that die */ + { + lst = pseg->modified_old_objects; + uintptr_t j, k = 0, limit = list_count(lst); + for (j = 0; j < limit; j += 3) { + uintptr_t e0 = list_item(lst, j + 0); + uintptr_t e1 = list_item(lst, j + 1); + uintptr_t e2 = list_item(lst, j + 2); + if (e0 == TYPE_POSITION_MARKER && + e1 == TYPE_MODIFIED_HASHTABLE && + !mark_visited_test((object_t *)e2)) { + /* hashtable object dies */ + } + else { + if (j != k) { + list_set_item(lst, k + 0, e0); + list_set_item(lst, k + 1, e1); + list_set_item(lst, k + 2, e2); + } + k += 3; + } + } + lst->count = k; + } } #pragma pop_macro("STM_SEGMENT") #pragma pop_macro("STM_PSEGMENT") diff --git a/c8/stm/hashtable.c b/c8/stm/hashtable.c new file mode 100644 --- /dev/null +++ b/c8/stm/hashtable.c @@ -0,0 +1,532 @@ +/* +Design of stmgc's "hashtable" objects +===================================== + +A "hashtable" is theoretically a lazily-filled array of objects of +length 2**64. Initially it is full of NULLs. It's obviously +implemented as a dictionary in which NULL objects are not needed. + +A real dictionary can be implemented on top of it, by using the index +`hash(key)` in the hashtable, and storing a list of `(key, value)` +pairs at that index (usually only one, unless there is a hash +collision). + +The main operations on a hashtable are reading or writing an object at a +given index. It also supports fetching the list of non-NULL entries. + +There are two markers for every index (a read and a write marker). +This is unlike regular arrays, which have only two markers in total. + +Additionally, we use the read marker for the hashtable object itself +to mean "we have read the complete list of keys". This plays the role +of a "global" read marker: when any thread adds a new key/value object +to the hashtable, this new object's read marker is initialized with a +copy of the "global" read marker --- in all segments. + + +Implementation +-------------- + +First idea: have the hashtable in raw memory, pointing to "entry" +objects (which are regular, GC- and STM-managed objects). The entry +objects themselves point to the user-specified objects. The entry +objects hold the read/write markers. Every entry object, once +created, stays around. It is only removed by the next major GC if it +points to NULL and its read/write markers are not set in any +currently-running transaction. + +References +---------- + +Inspired by: http://ppl.stanford.edu/papers/podc011-bronson.pdf +*/ + + +uint32_t stm_hashtable_entry_userdata; + + +#define INITIAL_HASHTABLE_SIZE 8 +#define PERTURB_SHIFT 5 +#define RESIZING_LOCK 0 + +typedef struct { + uintptr_t mask; + + /* 'resize_counter' start at an odd value, and is decremented (by + 6) for every new item put in 'items'. When it crosses 0, we + instead allocate a bigger table and change 'resize_counter' to + be a regular pointer to it (which is then even). The whole + structure is immutable then. + + The field 'resize_counter' also works as a write lock: changes + go via the intermediate value RESIZING_LOCK (0). + */ + uintptr_t resize_counter; + + stm_hashtable_entry_t *items[INITIAL_HASHTABLE_SIZE]; +} stm_hashtable_table_t; + +#define IS_EVEN(p) (((p) & 1) == 0) + +struct stm_hashtable_s { + stm_hashtable_table_t *table; + stm_hashtable_table_t initial_table; + uint64_t additions; +}; + + +static inline void init_table(stm_hashtable_table_t *table, uintptr_t itemcount) +{ + table->mask = itemcount - 1; + table->resize_counter = itemcount * 4 + 1; + memset(table->items, 0, itemcount * sizeof(stm_hashtable_entry_t *)); +} + +stm_hashtable_t *stm_hashtable_create(void) +{ + stm_hashtable_t *hashtable = malloc(sizeof(stm_hashtable_t)); + assert(hashtable); + hashtable->table = &hashtable->initial_table; + hashtable->additions = 0; + init_table(&hashtable->initial_table, INITIAL_HASHTABLE_SIZE); + return hashtable; +} + +void stm_hashtable_free(stm_hashtable_t *hashtable) +{ + uintptr_t rc = hashtable->initial_table.resize_counter; + free(hashtable); + while (IS_EVEN(rc)) { + assert(rc != RESIZING_LOCK); + + stm_hashtable_table_t *table = (stm_hashtable_table_t *)rc; + rc = table->resize_counter; + free(table); + } +} + +static bool _stm_was_read_by_anybody(object_t *obj) +{ + /* can only be safely called during major GC, when all other threads + are suspended */ + long i; + for (i = 1; i < NB_SEGMENTS; i++) { + if (get_priv_segment(i)->transaction_state == TS_NONE) + continue; + if (was_read_remote(get_segment_base(i), obj)) + return true; + } + return false; +} + +#define VOLATILE_HASHTABLE(p) ((volatile stm_hashtable_t *)(p)) +#define VOLATILE_TABLE(p) ((volatile stm_hashtable_table_t *)(p)) + +static void _insert_clean(stm_hashtable_table_t *table, + stm_hashtable_entry_t *entry, + uintptr_t index) +{ + uintptr_t mask = table->mask; + uintptr_t i = index & mask; + if (table->items[i] == NULL) { + table->items[i] = entry; + return; + } + + uintptr_t perturb = index; + while (1) { + i = (i << 2) + i + perturb + 1; + i &= mask; + if (table->items[i] == NULL) { + table->items[i] = entry; + return; + } + + perturb >>= PERTURB_SHIFT; + } +} + +static void _stm_rehash_hashtable(stm_hashtable_t *hashtable, + uintptr_t biggercount, + char *segment_base) +{ + dprintf(("rehash %p to size %ld, segment_base=%p\n", + hashtable, biggercount, segment_base)); + + size_t size = (offsetof(stm_hashtable_table_t, items) + + biggercount * sizeof(stm_hashtable_entry_t *)); + stm_hashtable_table_t *biggertable = malloc(size); + assert(biggertable); // XXX + + stm_hashtable_table_t *table = hashtable->table; + table->resize_counter = (uintptr_t)biggertable; + /* ^^^ this unlocks the table by writing a non-zero value to + table->resize_counter, but the new value is a pointer to the + new bigger table, so IS_EVEN() is still true */ + assert(IS_EVEN(table->resize_counter)); + + init_table(biggertable, biggercount); + + uintptr_t j, mask = table->mask; + uintptr_t rc = biggertable->resize_counter; + for (j = 0; j <= mask; j++) { + stm_hashtable_entry_t *entry = table->items[j]; + if (entry == NULL) + continue; + if (segment_base != NULL) { + if (((struct stm_hashtable_entry_s *) + REAL_ADDRESS(segment_base, entry))->object == NULL && + !_stm_was_read_by_anybody((object_t *)entry)) { + dprintf((" removing dead %p\n", entry)); + continue; + } + } + + uintptr_t eindex; + if (segment_base == NULL) + eindex = entry->index; /* read from STM_SEGMENT */ + else + eindex = ((struct stm_hashtable_entry_s *) + REAL_ADDRESS(segment_base, entry))->index; + + dprintf((" insert_clean %p at index=%ld\n", + entry, eindex)); + _insert_clean(biggertable, entry, eindex); + assert(rc > 6); + rc -= 6; + } + biggertable->resize_counter = rc; + + write_fence(); /* make sure that 'biggertable' is valid here, + and make sure 'table->resize_counter' is updated + ('table' must be immutable from now on). */ + VOLATILE_HASHTABLE(hashtable)->table = biggertable; +} + +stm_hashtable_entry_t *stm_hashtable_lookup(object_t *hashtableobj, + stm_hashtable_t *hashtable, + uintptr_t index) +{ + stm_hashtable_table_t *table; + uintptr_t mask; + uintptr_t i; + stm_hashtable_entry_t *entry; + + restart: + /* classical dict lookup logic */ + table = VOLATILE_HASHTABLE(hashtable)->table; + mask = table->mask; /* read-only field */ + i = index & mask; + entry = VOLATILE_TABLE(table)->items[i]; + if (entry != NULL) { + if (entry->index == index) + return entry; /* found at the first try */ + + uintptr_t perturb = index; + while (1) { + i = (i << 2) + i + perturb + 1; + i &= mask; + entry = VOLATILE_TABLE(table)->items[i]; + if (entry != NULL) { + if (entry->index == index) + return entry; /* found */ + } + else + break; + perturb >>= PERTURB_SHIFT; + } + } + /* here, we didn't find the 'entry' with the correct index. Note + that even if the same 'table' is modified or resized by other + threads concurrently, any new item found from a race condition + would anyway contain NULL in the present segment (ensured by + the first write_fence() below). If the 'table' grows an entry + just after we checked above, then we go ahead and lock the + table; but after we get the lock, we will notice the new entry + (ensured by the second write_fence() below) and restart the + whole process. + */ + + uintptr_t rc = VOLATILE_TABLE(table)->resize_counter; + + /* if rc is RESIZING_LOCK (which is 0, so even), a concurrent thread + is writing to the hashtable. Or, if rc is another even number, it is + actually a pointer to the next version of the table, installed + just now. In both cases, this thread must simply spin loop. + */ + if (IS_EVEN(rc)) { + spin_loop(); + goto restart; + } + /* in the other cases, we need to grab the RESIZING_LOCK. + */ + if (!__sync_bool_compare_and_swap(&table->resize_counter, + rc, RESIZING_LOCK)) { + goto restart; + } + /* we now have the lock. The only table with a non-even value of + 'resize_counter' should be the last one in the chain, so if we + succeeded in locking it, check this. */ + assert(table == hashtable->table); + + /* Check that 'table->items[i]' is still NULL, + i.e. hasn't been populated under our feet. + */ + if (table->items[i] != NULL) { + table->resize_counter = rc; /* unlock */ + goto restart; + } + /* if rc is greater than 6, there is enough room for a new + item in the current table. + */ + if (rc > 6) { + /* we can only enter here once! If we allocate stuff, we may + run the GC, and so 'hashtableobj' might move afterwards. */ + if (_is_in_nursery(hashtableobj)) { + entry = (stm_hashtable_entry_t *) + stm_allocate(sizeof(stm_hashtable_entry_t)); + entry->userdata = stm_hashtable_entry_userdata; + entry->index = index; + entry->object = NULL; + } + else { + /* for a non-nursery 'hashtableobj', we pretend that the + 'entry' object we're about to return was already + existing all along, with NULL in all segments. If the + caller of this function is going to modify the 'object' + field, it will call stm_write(entry) first, which will + correctly schedule 'entry' for write propagation. We + do that even if 'hashtableobj' was created by the + running transaction: the new 'entry' object is created + as if it was older than the transaction. + + Note the following difference: if 'hashtableobj' is + still in the nursery (case above), the 'entry' object + is also allocated from the nursery, and after a minor + collection it ages as an old-but-created-by-the- + current-transaction object. We could try to emulate + this here, or to create young 'entry' objects, but + doing either of these would require careful + synchronization with other pieces of the code that may + change. + */ + struct stm_hashtable_entry_s initial = { + .userdata = stm_hashtable_entry_userdata, + .index = index, + .object = NULL + }; + entry = (stm_hashtable_entry_t *) + stm_allocate_preexisting(sizeof(stm_hashtable_entry_t), + (char *)&initial.header); + hashtable->additions++; + } + table->items[i] = entry; + write_fence(); /* make sure 'table->items' is written here */ + VOLATILE_TABLE(table)->resize_counter = rc - 6; /* unlock */ + return entry; + } + else { + /* if rc is smaller than 6, we must allocate a new bigger table. + */ + uintptr_t biggercount = table->mask + 1; + if (biggercount < 50000) + biggercount *= 4; + else + biggercount *= 2; + _stm_rehash_hashtable(hashtable, biggercount, /*segment_base=*/NULL); + goto restart; + } +} + +object_t *stm_hashtable_read(object_t *hobj, stm_hashtable_t *hashtable, + uintptr_t key) +{ + stm_hashtable_entry_t *e = stm_hashtable_lookup(hobj, hashtable, key); + stm_read((object_t *)e); + return e->object; +} + +void stm_hashtable_write_entry(object_t *hobj, stm_hashtable_entry_t *entry, + object_t *nvalue) +{ + if (_STM_WRITE_CHECK_SLOWPATH((object_t *)entry)) { + + stm_write((object_t *)entry); + + uintptr_t i = list_count(STM_PSEGMENT->modified_old_objects); + if (i > 0 && list_item(STM_PSEGMENT->modified_old_objects, i - 3) + == (uintptr_t)entry) { + /* The stm_write() above recorded a write to 'entry'. Here, + we add another stm_undo_s to modified_old_objects with + TYPE_MODIFIED_HASHTABLE. It is ignored everywhere except + in _stm_validate(). + + The goal is that this TYPE_MODIFIED_HASHTABLE ends up in + the commit log's 'cl_written' array. Later, another + transaction validating that log will check two things: + + - the regular stm_undo_s entry put by stm_write() above + will make the other transaction check that it didn't + read the same 'entry' object; + + - the TYPE_MODIFIED_HASHTABLE entry we're adding now + will make the other transaction check that it didn't + do any stm_hashtable_list() on the complete hashtable. + */ + STM_PSEGMENT->modified_old_objects = list_append3( + STM_PSEGMENT->modified_old_objects, + TYPE_POSITION_MARKER, /* type1 */ + TYPE_MODIFIED_HASHTABLE, /* type2 */ + (uintptr_t)hobj); /* modif_hashtable */ + } + } + entry->object = nvalue; +} + +void stm_hashtable_write(object_t *hobj, stm_hashtable_t *hashtable, + uintptr_t key, object_t *nvalue, + stm_thread_local_t *tl) +{ + STM_PUSH_ROOT(*tl, nvalue); + STM_PUSH_ROOT(*tl, hobj); + stm_hashtable_entry_t *e = stm_hashtable_lookup(hobj, hashtable, key); + STM_POP_ROOT(*tl, hobj); + STM_POP_ROOT(*tl, nvalue); + stm_hashtable_write_entry(hobj, e, nvalue); +} + +long stm_hashtable_length_upper_bound(stm_hashtable_t *hashtable) +{ + stm_hashtable_table_t *table; + uintptr_t rc; + + restart: + table = VOLATILE_HASHTABLE(hashtable)->table; + rc = VOLATILE_TABLE(table)->resize_counter; + if (IS_EVEN(rc)) { + spin_loop(); + goto restart; + } + + uintptr_t initial_rc = (table->mask + 1) * 4 + 1; + uintptr_t num_entries_times_6 = initial_rc - rc; + return num_entries_times_6 / 6; +} + +long stm_hashtable_list(object_t *hobj, stm_hashtable_t *hashtable, + stm_hashtable_entry_t **results) +{ + /* Set the read marker. It will be left as long as we're running + the same transaction. + */ + stm_read(hobj); + + /* Get the table. No synchronization is needed: we may miss some + entries that are being added, but they would contain NULL in + this segment anyway. */ + stm_hashtable_table_t *table = VOLATILE_HASHTABLE(hashtable)->table; + + /* Read all entries, check which ones are not NULL, count them, + and optionally list them in 'results'. + */ + uintptr_t i, mask = table->mask; + stm_hashtable_entry_t *entry; + long nresult = 0; + + if (results != NULL) { + /* collect the results in the provided list */ + for (i = 0; i <= mask; i++) { + entry = VOLATILE_TABLE(table)->items[i]; + if (entry != NULL) { + stm_read((object_t *)entry); + if (entry->object != NULL) + results[nresult++] = entry; + } + } + } + else { + /* don't collect, just get the exact number of results */ + for (i = 0; i <= mask; i++) { + entry = VOLATILE_TABLE(table)->items[i]; + if (entry != NULL) { + stm_read((object_t *)entry); + if (entry->object != NULL) + nresult++; + } + } + } + return nresult; +} + +static void _stm_compact_hashtable(struct object_s *hobj, + stm_hashtable_t *hashtable) +{ + stm_hashtable_table_t *table = hashtable->table; + uintptr_t rc = table->resize_counter; + assert(!IS_EVEN(rc)); + + if (hashtable->additions * 4 > table->mask) { + hashtable->additions = 0; + + /* If 'hobj' was created in some current transaction, i.e. if it is + now an overflow object, then we have the risk that some of its + entry objects were not created with stm_allocate_preexisting(). + In that situation, a valid workaround is to read all entry + objects in the segment of the running transaction. Otherwise, + the base case is to read them all from segment zero. + */ + long segnum = get_num_segment_containing_address((char *)hobj); + if (!IS_OVERFLOW_OBJ(get_priv_segment(segnum), hobj)) + segnum = 0; + + uintptr_t initial_rc = (table->mask + 1) * 4 + 1; + uintptr_t num_entries_times_6 = initial_rc - rc; + uintptr_t count = INITIAL_HASHTABLE_SIZE; + while (count * 4 < num_entries_times_6) + count *= 2; + /* sanity-check: 'num_entries_times_6 < initial_rc', and so 'count' + can never grow larger than the current table size. */ + assert(count <= table->mask + 1); + + dprintf(("compact with %ld items:\n", num_entries_times_6 / 6)); + _stm_rehash_hashtable(hashtable, count, get_segment_base(segnum)); + } + + table = hashtable->table; + assert(!IS_EVEN(table->resize_counter)); + + if (table != &hashtable->initial_table) { + uintptr_t rc = hashtable->initial_table.resize_counter; + while (1) { + assert(IS_EVEN(rc)); + assert(rc != RESIZING_LOCK); + + stm_hashtable_table_t *old_table = (stm_hashtable_table_t *)rc; + if (old_table == table) + break; + rc = old_table->resize_counter; + free(old_table); + } + hashtable->initial_table.resize_counter = (uintptr_t)table; + assert(IS_EVEN(hashtable->initial_table.resize_counter)); + } +} + +void stm_hashtable_tracefn(struct object_s *hobj, stm_hashtable_t *hashtable, + void trace(object_t **)) +{ + if (trace == TRACE_FOR_MAJOR_COLLECTION) + _stm_compact_hashtable(hobj, hashtable); + + stm_hashtable_table_t *table; + table = VOLATILE_HASHTABLE(hashtable)->table; + + uintptr_t j, mask = table->mask; + for (j = 0; j <= mask; j++) { + stm_hashtable_entry_t *volatile *pentry; + pentry = &VOLATILE_TABLE(table)->items[j]; + if (*pentry != NULL) { + trace((object_t **)pentry); + } + } +} diff --git a/c8/stm/marker.c b/c8/stm/marker.c --- a/c8/stm/marker.c +++ b/c8/stm/marker.c @@ -42,7 +42,8 @@ */ while (contention != start) { --contention; - if (contention->type == TYPE_POSITION_MARKER) { + if (contention->type == TYPE_POSITION_MARKER && + contention->type2 != TYPE_MODIFIED_HASHTABLE) { out_marker->odd_number = contention->marker_odd_number; out_marker->object = contention->marker_object; return; @@ -69,6 +70,9 @@ return; /* already up-to-date */ } + /* -2 is not odd */ + assert(marker.odd_number != (uintptr_t)TYPE_MODIFIED_HASHTABLE); + STM_PSEGMENT->position_markers_last = list_count(list); STM_PSEGMENT->modified_old_objects = list_append3( list, diff --git a/c8/stm/misc.c b/c8/stm/misc.c --- a/c8/stm/misc.c +++ b/c8/stm/misc.c @@ -31,10 +31,7 @@ bool _stm_was_read(object_t *obj) { - uint8_t rm = ((struct stm_read_marker_s *) - (STM_SEGMENT->segment_base + (((uintptr_t)obj) >> 4)))->rm; - assert(rm <= STM_SEGMENT->transaction_read_version); - return rm == STM_SEGMENT->transaction_read_version; + return was_read_remote(STM_SEGMENT->segment_base, obj); } bool _stm_was_written(object_t *obj) diff --git a/c8/stm/nursery.c b/c8/stm/nursery.c --- a/c8/stm/nursery.c +++ b/c8/stm/nursery.c @@ -424,6 +424,7 @@ struct stm_undo_s *end = (struct stm_undo_s *)(list->items + list->count); for (; undo < end; undo++) { + /* this logic also works if type2 == TYPE_MODIFIED_HASHTABLE */ if (undo->type == TYPE_POSITION_MARKER) minor_trace_if_young(&undo->marker_object); } diff --git a/c8/stm/pages.h b/c8/stm/pages.h --- a/c8/stm/pages.h +++ b/c8/stm/pages.h @@ -62,7 +62,11 @@ static inline bool get_page_status_in(long segnum, uintptr_t pagenum) { - /* reading page status requires "read"-lock: */ + /* reading page status requires "read"-lock, which is defined as + "any segment has the privatization_lock". This is enough to + prevent the "write"-lock from being acquired by somebody else + (defined as "_all_ segments have the privatization_lock"). + */ assert(STM_PSEGMENT->privatization_lock); OPT_ASSERT(segnum < 8 * sizeof(struct page_shared_s)); diff --git a/c8/stm/setup.c b/c8/stm/setup.c --- a/c8/stm/setup.c +++ b/c8/stm/setup.c @@ -250,8 +250,6 @@ set_gs_register(get_segment_base(num + 1)); s_mutex_unlock(); - DEBUG_EXPECT_SEGFAULT(true); - if (num == 0) { dprintf(("STM_GC_NURSERY: %d\n", STM_GC_NURSERY)); dprintf(("NB_PAGES: %d\n", NB_PAGES)); diff --git a/c8/stm/setup.h b/c8/stm/setup.h --- a/c8/stm/setup.h +++ b/c8/stm/setup.h @@ -3,8 +3,8 @@ static pthread_t *_get_cpth(stm_thread_local_t *); #ifndef NDEBUG -static __thread long _stm_segfault_expected = 0; -#define DEBUG_EXPECT_SEGFAULT(v) do {if (v) _stm_segfault_expected++; else _stm_segfault_expected--;} while (0) +static __thread long _stm_segfault_expected = 1; +#define DEBUG_EXPECT_SEGFAULT(v) do {if (v) _stm_segfault_expected++; else _stm_segfault_expected--; assert(_stm_segfault_expected <= 1);} while (0) #else #define DEBUG_EXPECT_SEGFAULT(v) {} #endif diff --git a/c8/stmgc.c b/c8/stmgc.c --- a/c8/stmgc.c +++ b/c8/stmgc.c @@ -39,3 +39,4 @@ #include "stm/prof.c" #include "stm/rewind_setjmp.c" #include "stm/finalizer.c" +#include "stm/hashtable.c" diff --git a/c8/stmgc.h b/c8/stmgc.h --- a/c8/stmgc.h +++ b/c8/stmgc.h @@ -196,10 +196,13 @@ STM_SEGMENT->transaction_read_version; } +#define _STM_WRITE_CHECK_SLOWPATH(obj) \ + UNLIKELY(((obj)->stm_flags & _STM_GCFLAG_WRITE_BARRIER) != 0) + __attribute__((always_inline)) static inline void stm_write(object_t *obj) { - if (UNLIKELY((obj->stm_flags & _STM_GCFLAG_WRITE_BARRIER) != 0)) + if (_STM_WRITE_CHECK_SLOWPATH(obj)) _stm_write_slowpath(obj); } @@ -208,7 +211,7 @@ static inline void stm_write_card(object_t *obj, uintptr_t index) { /* if GCFLAG_WRITE_BARRIER is set, then don't do anything more. */ - if (UNLIKELY((obj->stm_flags & _STM_GCFLAG_WRITE_BARRIER) != 0)) { + if (_STM_WRITE_CHECK_SLOWPATH(obj)) { /* GCFLAG_WRITE_BARRIER is not set. This might be because it's the first time we see a given small array; or it might @@ -479,6 +482,38 @@ /* dummies for now: */ static inline void stm_flush_timing(stm_thread_local_t *tl, int verbose) {} + +/* Hashtables. Keys are 64-bit unsigned integers, values are + 'object_t *'. Note that the type 'stm_hashtable_t' is not an + object type at all; you need to allocate and free it explicitly. + If you want to embed the hashtable inside an 'object_t' you + probably need a light finalizer to do the freeing. */ +typedef struct stm_hashtable_s stm_hashtable_t; +typedef TLPREFIX struct stm_hashtable_entry_s stm_hashtable_entry_t; + +stm_hashtable_t *stm_hashtable_create(void); +void stm_hashtable_free(stm_hashtable_t *); +stm_hashtable_entry_t *stm_hashtable_lookup(object_t *, stm_hashtable_t *, + uintptr_t key); +object_t *stm_hashtable_read(object_t *, stm_hashtable_t *, uintptr_t key); +void stm_hashtable_write(object_t *, stm_hashtable_t *, uintptr_t key, + object_t *nvalue, stm_thread_local_t *); +void stm_hashtable_write_entry(object_t *hobj, stm_hashtable_entry_t *entry, + object_t *nvalue); +long stm_hashtable_length_upper_bound(stm_hashtable_t *); +long stm_hashtable_list(object_t *, stm_hashtable_t *, + stm_hashtable_entry_t **results); +extern uint32_t stm_hashtable_entry_userdata; +void stm_hashtable_tracefn(struct object_s *, stm_hashtable_t *, + void (object_t **)); + +struct stm_hashtable_entry_s { + struct object_s header; + uint32_t userdata; + uintptr_t index; + object_t *object; +}; + /* ==================== END ==================== */ static void (*stmcb_expand_marker)(char *segment_base, uintptr_t odd_number, diff --git a/c8/test/support.py b/c8/test/support.py --- a/c8/test/support.py +++ b/c8/test/support.py @@ -197,6 +197,26 @@ void stm_enable_light_finalizer(object_t *); void (*stmcb_finalizer)(object_t *); + +typedef struct stm_hashtable_s stm_hashtable_t; +typedef ... stm_hashtable_entry_t; +stm_hashtable_t *stm_hashtable_create(void); +void stm_hashtable_free(stm_hashtable_t *); +bool _check_hashtable_read(object_t *, stm_hashtable_t *, uintptr_t key); +object_t *hashtable_read_result; +bool _check_hashtable_write(object_t *, stm_hashtable_t *, uintptr_t key, + object_t *nvalue, stm_thread_local_t *tl); +long stm_hashtable_length_upper_bound(stm_hashtable_t *); +long stm_hashtable_list(object_t *, stm_hashtable_t *, + stm_hashtable_entry_t **results); +uint32_t stm_hashtable_entry_userdata; +void stm_hashtable_tracefn(struct object_s *, stm_hashtable_t *, + void trace(object_t **)); + +void _set_hashtable(object_t *obj, stm_hashtable_t *h); +stm_hashtable_t *_get_hashtable(object_t *obj); +uintptr_t _get_entry_index(stm_hashtable_entry_t *entry); +object_t *_get_entry_object(stm_hashtable_entry_t *entry); """) @@ -299,6 +319,19 @@ CHECKED(stm_validate()); } +object_t *hashtable_read_result; + +bool _check_hashtable_read(object_t *hobj, stm_hashtable_t *h, uintptr_t key) +{ + CHECKED(hashtable_read_result = stm_hashtable_read(hobj, h, key)); +} + +bool _check_hashtable_write(object_t *hobj, stm_hashtable_t *h, uintptr_t key, + object_t *nvalue, stm_thread_local_t *tl) +{ + CHECKED(stm_hashtable_write(hobj, h, key, nvalue, tl)); +} + #undef CHECKED @@ -326,6 +359,32 @@ return *WEAKREF_PTR(obj, size); } +void _set_hashtable(object_t *obj, stm_hashtable_t *h) +{ + stm_char *field_addr = ((stm_char*)obj); + field_addr += SIZEOF_MYOBJ; /* header */ + *(stm_hashtable_t *TLPREFIX *)field_addr = h; +} + +stm_hashtable_t *_get_hashtable(object_t *obj) +{ + stm_char *field_addr = ((stm_char*)obj); + field_addr += SIZEOF_MYOBJ; /* header */ + return *(stm_hashtable_t *TLPREFIX *)field_addr; +} + +uintptr_t _get_entry_index(stm_hashtable_entry_t *entry) +{ + stm_read((object_t *)entry); + return entry->index; +} + +object_t *_get_entry_object(stm_hashtable_entry_t *entry) +{ + stm_read((object_t *)entry); + return entry->object; +} + void _set_ptr(object_t *obj, int n, object_t *v) { long nrefs = (long)((myobj_t*)obj)->type_id - 421420; @@ -351,11 +410,17 @@ } - ssize_t stmcb_size_rounded_up(struct object_s *obj) { struct myobj_s *myobj = (struct myobj_s*)obj; + assert(myobj->type_id != 0); if (myobj->type_id < 421420) { + if (myobj->type_id == 421419) { /* hashtable */ + return sizeof(struct myobj_s) + 1 * sizeof(void*); + } + if (myobj->type_id == 421418) { /* hashtable entry */ + return sizeof(struct stm_hashtable_entry_s); + } /* basic case: tid equals 42 plus the size of the object */ assert(myobj->type_id >= 42 + sizeof(struct myobj_s)); assert((myobj->type_id - 42) >= 16); @@ -371,11 +436,21 @@ } } - void stmcb_trace(struct object_s *obj, void visit(object_t **)) { int i; struct myobj_s *myobj = (struct myobj_s*)obj; + if (myobj->type_id == 421419) { + /* hashtable */ + stm_hashtable_t *h = *((stm_hashtable_t **)(myobj + 1)); + stm_hashtable_tracefn(obj, h, visit); + return; + } + if (myobj->type_id == 421418) { + /* hashtable entry */ + object_t **ref = &((struct stm_hashtable_entry_s *)myobj)->object; + visit(ref); + } if (myobj->type_id < 421420) { /* basic case: no references */ return; @@ -396,6 +471,7 @@ { int i; struct myobj_s *myobj = (struct myobj_s*)obj; + assert(myobj->type_id != 0); assert(myobj->type_id != 421419); assert(myobj->type_id != 421418); if (myobj->type_id < 421420) { @@ -413,6 +489,9 @@ uintptr_t offset_itemsize[2]) { struct myobj_s *myobj = (struct myobj_s*)obj; + assert(myobj->type_id != 0); + assert(myobj->type_id != 421419); + assert(myobj->type_id != 421418); if (myobj->type_id < 421420) { offset_itemsize[0] = SIZEOF_MYOBJ; offset_itemsize[1] = 1; @@ -468,6 +547,7 @@ CARD_CLEAR = 0 CARD_MARKED = lib._STM_CARD_MARKED CARD_MARKED_OLD = lib._stm_get_transaction_read_version +lib.stm_hashtable_entry_userdata = 421418 class Conflict(Exception): @@ -530,6 +610,18 @@ lib._set_type_id(o, tid) return o +def stm_allocate_hashtable(): + o = lib.stm_allocate(16) + tid = 421419 + lib._set_type_id(o, tid) + h = lib.stm_hashtable_create() + lib._set_hashtable(o, h) + return o + +def get_hashtable(o): + assert lib._get_type_id(o) == 421419 + return lib._get_hashtable(o) + def stm_get_weakref(o): return lib._get_weakref(o) diff --git a/c8/test/test_hashtable.py b/c8/test/test_hashtable.py new file mode 100644 --- /dev/null +++ b/c8/test/test_hashtable.py @@ -0,0 +1,561 @@ +from support import * +import random +import py, sys + + +def htget(o, key): + h = get_hashtable(o) + res = lib._check_hashtable_read(o, h, key) + if res: + raise Conflict + return lib.hashtable_read_result + +def htset(o, key, nvalue, tl): + h = get_hashtable(o) + res = lib._check_hashtable_write(o, h, key, nvalue, tl) + if res: + raise Conflict + +def ht_length_upper_bound(o): + h = get_hashtable(o) + return lib.stm_hashtable_length_upper_bound(h) + +def htitems(o): + h = get_hashtable(o) + upper_bound = lib.stm_hashtable_length_upper_bound(h) + entries = ffi.new("stm_hashtable_entry_t *[]", upper_bound) + count = lib.stm_hashtable_list(o, h, entries) + assert count <= upper_bound + return [(lib._get_entry_index(entries[i]), + lib._get_entry_object(entries[i])) for i in range(count)] + +def htlen(o): + h = get_hashtable(o) + count = lib.stm_hashtable_list(o, h, ffi.NULL) + return count + + +class BaseTestHashtable(BaseTest): + + def setup_method(self, meth): + BaseTest.setup_method(self, meth) + # + @ffi.callback("void(object_t *)") + def light_finalizer(obj): + print 'light_finalizer:', obj + try: + assert lib._get_type_id(obj) == 421419 + self.seen_hashtables -= 1 + except: + self.errors.append(sys.exc_info()[2]) + raise + + lib.stmcb_light_finalizer = light_finalizer + self._light_finalizer_keepalive = light_finalizer + self.seen_hashtables = 0 + self.errors = [] + + def teardown_method(self, meth): + BaseTest.teardown_method(self, meth) + lib.stmcb_light_finalizer = ffi.NULL + assert self.errors == [] + assert self.seen_hashtables == 0 + + def allocate_hashtable(self): + h = stm_allocate_hashtable() + lib.stm_enable_light_finalizer(h) + self.seen_hashtables += 1 + return h + + +class TestHashtable(BaseTestHashtable): + + def test_empty(self): + self.start_transaction() + h = self.allocate_hashtable() + for i in range(100): + index = random.randrange(0, 1<<64) + got = htget(h, index) + assert got == ffi.NULL + + def test_set_value(self): + self.start_transaction() + tl0 = self.tls[self.current_thread] + h = self.allocate_hashtable() + lp1 = stm_allocate(16) + htset(h, 12345678901, lp1, tl0) + assert htget(h, 12345678901) == lp1 + for i in range(64): + index = 12345678901 ^ (1 << i) + assert htget(h, index) == ffi.NULL + assert htget(h, 12345678901) == lp1 + + def test_no_conflict(self): + lp1 = stm_allocate_old(16) + lp2 = stm_allocate_old(16) + # + self.start_transaction() + tl0 = self.tls[self.current_thread] + h = self.allocate_hashtable() + self.push_root(h) + stm_set_char(lp1, 'A') + htset(h, 1234, lp1, tl0) + self.commit_transaction() + # + self.start_transaction() + h = self.pop_root() + stm_set_char(lp2, 'B') + self.switch(1) + self.start_transaction() + self.switch(0) + htset(h, 9991234, lp2, tl0) + # + self.switch(1) + lp1b = htget(h, 1234) + assert lp1b != ffi.NULL + assert stm_get_char(lp1b) == 'A' + assert lp1b == lp1 + self.commit_transaction() + # + self.switch(0) + assert htget(h, 9991234) == lp2 + assert stm_get_char(lp2) == 'B' + assert htget(h, 1234) == lp1 + htset(h, 1234, ffi.NULL, tl0) + self.commit_transaction() + # + self.start_transaction() + stm_major_collect() # to get rid of the hashtable object + + def test_conflict(self): + lp1 = stm_allocate_old(16) + lp2 = stm_allocate_old(16) + # + self.start_transaction() + h = self.allocate_hashtable() + self.push_root(h) + self.commit_transaction() + # + self.start_transaction() + h = self.pop_root() + self.push_root(h) + tl0 = self.tls[self.current_thread] + htset(h, 1234, lp1, tl0) + # + self.switch(1) + self.start_transaction() + tl1 = self.tls[self.current_thread] + htset(h, 1234, lp2, tl1) + # + self.switch(0) + self.commit_transaction() + # + py.test.raises(Conflict, self.switch, 1) + # + self.switch(0) + self.start_transaction() + self.pop_root() + stm_major_collect() # to get rid of the hashtable object + self.commit_transaction() + + def test_keepalive_minor(self): + self.start_transaction() + h = self.allocate_hashtable() + self.push_root(h) + lp1 = stm_allocate(16) + stm_set_char(lp1, 'N') + tl0 = self.tls[self.current_thread] + htset(h, 1234, lp1, tl0) + stm_minor_collect() + h = self.pop_root() + lp1b = htget(h, 1234) + assert lp1b != ffi.NULL + assert stm_get_char(lp1b) == 'N' + assert lp1b != lp1 + + def test_keepalive_major(self): + lp1 = stm_allocate_old(16) + # + self.start_transaction() + h = self.allocate_hashtable() + self.push_root(h) + stm_set_char(lp1, 'N') + tl0 = self.tls[self.current_thread] + htset(h, 1234, lp1, tl0) + self.commit_transaction() + # + self.start_transaction() + stm_major_collect() + h = self.pop_root() + lp1b = htget(h, 1234) + assert lp1b == lp1 + assert stm_get_char(lp1b) == 'N' + # + stm_major_collect() # to get rid of the hashtable object + self.commit_transaction() + + def test_minor_collect_bug1(self): + self.start_transaction() + lp1 = stm_allocate(32) + self.push_root(lp1) + h = self.allocate_hashtable() + self.push_root(h) + stm_minor_collect() + h = self.pop_root() + lp1 = self.pop_root() + print 'h', h # 0xa040010 + print 'lp1', lp1 # 0xa040040 + tl0 = self.tls[self.current_thread] + htset(h, 1, lp1, tl0) + self.commit_transaction() + # + self.start_transaction() + assert htget(h, 1) == lp1 + stm_major_collect() # to get rid of the hashtable object + + def test_minor_collect_bug1_different_thread(self): + self.start_transaction() + lp1 = stm_allocate(32) + self.push_root(lp1) + h = self.allocate_hashtable() + self.push_root(h) + stm_minor_collect() + h = self.pop_root() + lp1 = self.pop_root() + print 'h', h # 0xa040010 + print 'lp1', lp1 # 0xa040040 + tl0 = self.tls[self.current_thread] + htset(h, 1, lp1, tl0) + self.commit_transaction() + # + self.switch(1) # in a different thread + self.start_transaction() + assert htget(h, 1) == lp1 + stm_major_collect() # to get rid of the hashtable object + + def test_major_collect_bug2(self): + self.start_transaction() + lp1 = stm_allocate(24) + self.push_root(lp1) + self.commit_transaction() + lp1 = self.pop_root() + # + self.switch(1) + self.start_transaction() + stm_write(lp1) # force this page to be shared + # + self.switch(0) + self.start_transaction() + h = self.allocate_hashtable() + tl0 = self.tls[self.current_thread] + htset(h, 10, stm_allocate(32), tl0) + htset(h, 11, stm_allocate(32), tl0) + htset(h, 12, stm_allocate(32), tl0) + self.push_root(h) + # + self.switch(1) # in a different thread + stm_major_collect() # force a _stm_rehash_hashtable() + # + self.switch(0) # back to the original thread + h = self.pop_root() + assert htget(h, 10) != ffi.NULL + assert htget(h, 11) != ffi.NULL + assert htget(h, 12) != ffi.NULL + + def test_list_1(self): + self.start_transaction() + h = self.allocate_hashtable() + tl0 = self.tls[self.current_thread] + for i in range(32): + assert ht_length_upper_bound(h) == i + htset(h, 19 ^ i, stm_allocate(32), tl0) + assert ht_length_upper_bound(h) == 32 + + def test_list_2(self): + self.start_transaction() + h = self.allocate_hashtable() + tl0 = self.tls[self.current_thread] + expected = [] + for i in range(29): + lp = stm_allocate(32) + htset(h, 19 ^ i, lp, tl0) + expected.append((19 ^ i, lp)) + lst = htitems(h) + assert len(lst) == 29 + assert sorted(lst) == sorted(expected) + + def test_list_3(self): + self.start_transaction() + h = self.allocate_hashtable() + tl0 = self.tls[self.current_thread] + for i in range(29): + htset(h, 19 ^ i, stm_allocate(32), tl0) + assert htlen(h) == 29 + + def test_len_conflicts_with_additions(self): + self.start_transaction() + h = self.allocate_hashtable() + self.push_root(h) + self.commit_transaction() + # + self.start_transaction() + h = self.pop_root() + self.push_root(h) + tl0 = self.tls[self.current_thread] + htset(h, 10, stm_allocate(32), tl0) + # + self.switch(1) + self.start_transaction() + assert htlen(h) == 0 + # + self.switch(0) + self.commit_transaction() + # + py.test.raises(Conflict, self.switch, 1) + # + self.switch(0) + self.start_transaction() + self.pop_root() + stm_major_collect() # to get rid of the hashtable object + self.commit_transaction() + + def test_grow_without_conflict(self): + self.start_transaction() + h = self.allocate_hashtable() + self.push_root(h) + self.commit_transaction() + h = self.pop_root() + self.push_root(h) + # + STEPS = 50 + for i in range(STEPS): + self.switch(1) + self.start_transaction() + tl0 = self.tls[self.current_thread] + htset(h, i + STEPS, stm_allocate(32), tl0) + # + self.switch(0) + self.start_transaction() + tl0 = self.tls[self.current_thread] + htset(h, i, stm_allocate(24), tl0) + # + self.switch(1) + self.commit_transaction() + # + self.switch(0) + self.commit_transaction() + # + self.pop_root() + self.start_transaction() + stm_major_collect() # to get rid of the hashtable object + + +class TestRandomHashtable(BaseTestHashtable): + + def setup_method(self, meth): + BaseTestHashtable.setup_method(self, meth) + self.values = [] + self.mirror = None + self.roots = [] + self.other_thread = ([], []) + + def push_roots(self): + assert self.roots is None + self.roots = [] + for k, hitems in self.mirror.items(): + assert lib._get_type_id(k) == 421419 + for key, value in hitems.items(): + assert lib._get_type_id(value) < 1000 + self.push_root(value) + self.roots.append(key) + self.push_root(k) + self.roots.append(None) + for v in self.values: + self.push_root(v) + self.mirror = None + + def pop_roots(self): + assert self.mirror is None + for i in reversed(range(len(self.values))): + self.values[i] = self.pop_root() + assert stm_get_char(self.values[i]) == chr((i + 1) & 255) + self.mirror = {} + for r in reversed(self.roots): + obj = self.pop_root() + if r is None: + assert lib._get_type_id(obj) == 421419 + self.mirror[obj] = curhitems = {} + else: + assert lib._get_type_id(obj) < 1000 + curhitems[r] = obj + self.roots = None + + def exchange_threads(self): + old_thread = (self.values, self.roots) + self.switch(1 - self.current_thread) + (self.values, self.roots) = self.other_thread + self.mirror = None + self.other_thread = old_thread + print "----- switch to %s -----" % (self.current_thread,) + + def test_random_single_thread(self): + import random + # + for i in range(100): + print "start_transaction" + self.start_transaction() + self.pop_roots() + for j in range(10): + r = random.random() + if r < 0.05: + h = self.allocate_hashtable() + print "allocate_hashtable ->", h + self.mirror[h] = {} + elif r < 0.10: + print "stm_minor_collect" + self.push_roots() + stm_minor_collect() + self.pop_roots() + elif r < 0.11: + print "stm_major_collect" + self.push_roots() + stm_major_collect() + self.pop_roots() + elif r < 0.5: + if not self.mirror: continue + h = random.choice(self.mirror.keys()) + if not self.mirror[h]: continue + key = random.choice(self.mirror[h].keys()) + value = self.mirror[h][key] + print "htget(%r, %r) == %r" % (h, key, value) + self.push_roots() + self.push_root(value) + result = htget(h, key) + value = self.pop_root() + assert result == value + self.pop_roots() + elif r < 0.6: + if not self.mirror: continue + h = random.choice(self.mirror.keys()) + key = random.randrange(0, 40) + if key in self.mirror[h]: continue + print "htget(%r, %r) == NULL" % (h, key) + self.push_roots() + assert htget(h, key) == ffi.NULL + self.pop_roots() + elif r < 0.63: + if not self.mirror: continue + h, _ = self.mirror.popitem() + print "popped", h + elif r < 0.75: + obj = stm_allocate(32) + self.values.append(obj) + stm_set_char(obj, chr(len(self.values) & 255)) + else: + if not self.mirror or not self.values: continue + h = random.choice(self.mirror.keys()) + key = random.randrange(0, 32) + value = random.choice(self.values) + print "htset(%r, %r, %r)" % (h, key, value) + self.push_roots() + tl = self.tls[self.current_thread] + htset(h, key, value, tl) + self.pop_roots() + self.mirror[h][key] = value + self.push_roots() + print "commit_transaction" + self.commit_transaction() + # + self.start_transaction() + self.become_inevitable() + self.pop_roots() + stm_major_collect() # to get rid of the hashtable objects + + def test_random_multiple_threads(self): + from random import randrange, Random + seed = randrange(0, 10000) + print "----------------------------------------- seed:", seed + random = Random(seed) + # + self.start_transaction() + self.exchange_threads() + self.start_transaction() + self.pop_roots() + # + for j in range(1000): + r = random.random() + if r > 0.9: + if r > 0.95: + self.push_roots() + self.commit_transaction() + self.start_transaction() + self.pop_roots() + else: + self.push_roots() + self.exchange_threads() + self.pop_roots() + continue + + if r < 0.05: + h = self.allocate_hashtable() + print "allocate_hashtable -> %r/%r" % (h, get_hashtable(h)) + self.mirror[h] = {} + elif r < 0.10: + print "stm_minor_collect" + self.push_roots() + stm_minor_collect() + self.pop_roots() + elif r < 0.11: + print "stm_major_collect" + self.push_roots() + stm_major_collect() + self.pop_roots() + elif r < 0.5: + if not self.mirror: continue + h = random.choice(self.mirror.keys()) + if not self.mirror[h]: continue + key = random.choice(self.mirror[h].keys()) + value = self.mirror[h][key] + print "htget(%r/%r, %r) == %r" % (h, get_hashtable(h), key, value) + self.push_roots() + self.push_root(value) + result = htget(h, key) + value = self.pop_root() + assert result == value + self.pop_roots() + elif r < 0.6: + if not self.mirror: continue + h = random.choice(self.mirror.keys()) + key = random.randrange(0, 40) + if key in self.mirror[h]: continue + print "htget(%r/%r, %r) == NULL" % (h, get_hashtable(h), key) + self.push_roots() + assert htget(h, key) == ffi.NULL + self.pop_roots() + elif r < 0.63: + if not self.mirror: continue + h, _ = self.mirror.popitem() + print "popped", h + elif r < 0.75: + obj = stm_allocate(32) + self.values.append(obj) + stm_set_char(obj, chr(len(self.values) & 255)) + else: + if not self.mirror or not self.values: continue + h = random.choice(self.mirror.keys()) + key = random.randrange(0, 32) + value = random.choice(self.values) + print "htset(%r/%r, %r, %r)" % (h, get_hashtable(h), key, value) + self.push_roots() + tl = self.tls[self.current_thread] + htset(h, key, value, tl) + self.pop_roots() + self.mirror[h][key] = value + # + print 'closing down...' + self.become_inevitable() + self.commit_transaction() + self.exchange_threads() + self.pop_roots() + self.become_inevitable() + stm_major_collect() # to get rid of the hashtable objects _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit