Author: Remi Meier <[email protected]>
Branch: use-gcc
Changeset: r1960:10e4734e83c7
Date: 2015-09-16 10:49 +0200
http://bitbucket.org/pypy/stmgc/changeset/10e4734e83c7/
Log: implement noconflict objects
diff --git a/c8/stm/core.c b/c8/stm/core.c
--- a/c8/stm/core.c
+++ b/c8/stm/core.c
@@ -12,7 +12,7 @@
{
assert(undo->type != TYPE_POSITION_MARKER);
free(undo->backup);
- assert(undo->backup = (char*)-88);
+ assert(undo->backup = (char*)0xbb);
increment_total_allocated(-SLICE_SIZE(undo->slice));
}
@@ -308,7 +308,7 @@
}
}
-static void reset_modified_from_backup_copies(int segment_num); /* forward */
+static void reset_modified_from_backup_copies(int segment_num, object_t
*only_obj); /* forward */
static bool _stm_validate(void)
{
@@ -409,6 +409,25 @@
if (LIKELY(!_stm_was_read(obj)))
continue;
+ /* check for NO_CONFLICT flag in seg0. While its data
may
+ not be current there, the flag will be there and is
+ immutable. (we cannot check in my_segnum bc. we may
+ only have executed stm_read(o) but not touched its
pages
+ yet -> they may be NO_ACCESS */
+ struct object_s *obj0 = (struct object_s
*)REAL_ADDRESS(get_segment_base(0), obj);
+ if (obj0->stm_flags & GCFLAG_NO_CONFLICT) {
+ /* obj is noconflict and therefore shouldn't cause
+ an abort. However, from now on, we also assume
+ that an abort would not roll-back to what is in
+ the backup copy, as we don't trace the bkcpy
+ during major GCs.
+ We choose the approach to reset all our changes
+ to this obj here, so that we can throw away the
+ backup copy completely: */
+ reset_modified_from_backup_copies(my_segnum, obj);
+ continue;
+ }
+
/* conflict! */
dprintf(("_stm_validate() failed for obj %p\n", obj));
@@ -418,7 +437,7 @@
from the old (but unmodified) version to the newer
version.
*/
- reset_modified_from_backup_copies(my_segnum);
+ reset_modified_from_backup_copies(my_segnum, NULL);
timing_write_read_contention(cl->written, undo);
needs_abort = true;
break;
@@ -1399,7 +1418,7 @@
invoke_general_finalizers(tl);
}
-static void reset_modified_from_backup_copies(int segment_num)
+static void reset_modified_from_backup_copies(int segment_num, object_t
*only_obj)
{
#pragma push_macro("STM_PSEGMENT")
#pragma push_macro("STM_SEGMENT")
@@ -1415,7 +1434,11 @@
for (; undo < end; undo++) {
if (undo->type == TYPE_POSITION_MARKER)
continue;
+
object_t *obj = undo->object;
+ if (only_obj != NULL && obj != only_obj)
+ continue;
+
char *dst = REAL_ADDRESS(pseg->pub.segment_base, obj);
memcpy(dst + SLICE_OFFSET(undo->slice),
@@ -1427,12 +1450,29 @@
SLICE_SIZE(undo->slice), undo->backup));
free_bk(undo);
+
+ if (only_obj != NULL) {
+ assert(IMPLY(only_obj != NULL,
+ (((struct object_s *)dst)->stm_flags
+ & (GCFLAG_NO_CONFLICT
+ | GCFLAG_WRITE_BARRIER
+ | GCFLAG_WB_EXECUTED))
+ == (GCFLAG_NO_CONFLICT | GCFLAG_WRITE_BARRIER)));
+ /* copy last element over this one */
+ end--;
+ list->count -= 3;
+ if (undo < end)
+ *undo = *end;
+ undo--; /* next itr */
+ }
}
- /* check that all objects have the GCFLAG_WRITE_BARRIER afterwards */
- check_all_write_barrier_flags(pseg->pub.segment_base, list);
+ if (only_obj == NULL) {
+ /* check that all objects have the GCFLAG_WRITE_BARRIER afterwards */
+ check_all_write_barrier_flags(pseg->pub.segment_base, list);
- list_clear(list);
+ list_clear(list);
+ }
#pragma pop_macro("STM_SEGMENT")
#pragma pop_macro("STM_PSEGMENT")
}
@@ -1468,7 +1508,7 @@
});
acquire_modification_lock_wr(segment_num);
- reset_modified_from_backup_copies(segment_num);
+ reset_modified_from_backup_copies(segment_num, NULL);
release_modification_lock_wr(segment_num);
_verify_cards_cleared_in_all_lists(pseg);
diff --git a/c8/stm/core.h b/c8/stm/core.h
--- a/c8/stm/core.h
+++ b/c8/stm/core.h
@@ -43,6 +43,7 @@
GCFLAG_CARDS_SET = _STM_GCFLAG_CARDS_SET,
GCFLAG_VISITED = 0x10,
GCFLAG_FINALIZATION_ORDERING = 0x20,
+ GCFLAG_NO_CONFLICT = _STM_GCFLAG_NO_CONFLICT,
/* All remaining bits of the 32-bit 'stm_flags' field are taken by
the "overflow number". This is a number that identifies the
"overflow objects" from the current transaction among all old
@@ -50,7 +51,7 @@
current transaction that have been flushed out of the nursery,
which occurs if the same transaction allocates too many objects.
*/
- GCFLAG_OVERFLOW_NUMBER_bit0 = 0x40 /* must be last */
+ GCFLAG_OVERFLOW_NUMBER_bit0 = 0x80 /* must be last */
};
#define SYNC_QUEUE_SIZE 31
diff --git a/c8/stmgc.h b/c8/stmgc.h
--- a/c8/stmgc.h
+++ b/c8/stmgc.h
@@ -162,6 +162,7 @@
#endif
#define _STM_GCFLAG_WRITE_BARRIER 0x01
+#define _STM_GCFLAG_NO_CONFLICT 0x40
#define _STM_FAST_ALLOC (66*1024)
#define _STM_NSE_SIGNAL_ABORT 1
#define _STM_NSE_SIGNAL_MAX 2
@@ -780,6 +781,24 @@
void stm_queue_tracefn(stm_queue_t *queue, void trace(object_t **));
+
+/* stm_allocate_noconflict() allocates a special kind of object. Validation
+ will never detect conflicts on such an object. However, writes to it can
+ get lost. More precisely: every possible point for validation during a
+ transaction may import a committed version of such objs, thereby resetting
+ it or even contain not-yet-seen values from other (committed) transactions.
+ Hence, changes to such an obj that a transaction commits may or may not
+ propagate to other transactions. */
+__attribute__((always_inline))
+static inline object_t *stm_allocate_noconflict(ssize_t size_rounded_up)
+{
+ object_t *o = stm_allocate(size_rounded_up);
+ o->stm_flags |= _STM_GCFLAG_NO_CONFLICT;
+ return o;
+}
+
+
+
/* ==================== END ==================== */
extern 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
@@ -45,6 +45,7 @@
object_t *stm_allocate(ssize_t size_rounded_up);
object_t *stm_allocate_weakref(ssize_t size_rounded_up);
object_t *stm_allocate_with_finalizer(ssize_t size_rounded_up);
+object_t *stm_allocate_noconflict(ssize_t size_rounded_up);
/*void stm_write_card(); use _checked_stm_write_card() instead */
@@ -664,6 +665,18 @@
lib._set_type_id(o, tid)
return o
+def stm_allocate_noconflict(size):
+ o = lib.stm_allocate_noconflict(size)
+ tid = 42 + size
+ lib._set_type_id(o, tid)
+ return o
+
+def stm_allocate_noconflict_refs(n):
+ o = lib.stm_allocate_noconflict(HDR + n * WORD)
+ tid = 421420 + n
+ lib._set_type_id(o, tid)
+ return o
+
def stm_allocate_with_finalizer(size):
o = lib.stm_allocate_with_finalizer(size)
tid = 42 + size
diff --git a/c8/test/test_noconfl.py b/c8/test/test_noconfl.py
new file mode 100644
--- /dev/null
+++ b/c8/test/test_noconfl.py
@@ -0,0 +1,155 @@
+from support import *
+import py
+
+
+
+class TestNoConflict(BaseTest):
+
+ def test_basic(self):
+ self.start_transaction()
+ o = stm_allocate_noconflict(16)
+ stm_set_char(o, 'x')
+ self.push_root(o)
+ self.commit_transaction()
+ o = self.pop_root()
+ self.push_root(o)
+
+ self.switch(1)
+
+ self.start_transaction()
+ assert stm_get_char(o) == 'x'
+
+ def test_propagate_on_validation(self):
+ self.start_transaction()
+ o = stm_allocate_noconflict(16)
+ self.push_root(o)
+ self.commit_transaction()
+
+ self.start_transaction()
+ o = self.pop_root()
+ self.push_root(o)
+
+ self.switch(1)
+
+ self.start_transaction()
+ assert stm_get_char(o) == '\0'
+
+ self.switch(0)
+ stm_set_char(o, 'a')
+ self.commit_transaction()
+
+ self.switch(1, False)
+ assert stm_get_char(o) == '\0'
+ stm_validate()
+ assert stm_get_char(o) == 'a'
+
+ def test_propagate_on_validation2(self):
+ self.start_transaction()
+ o = stm_allocate_noconflict(16)
+ self.push_root(o)
+ self.commit_transaction()
+
+ self.start_transaction()
+ o = self.pop_root()
+ self.push_root(o)
+
+ self.switch(1)
+
+ self.start_transaction()
+ assert stm_get_char(o) == '\0'
+ stm_set_char(o, 'b') # later lost
+
+ self.switch(0)
+ stm_set_char(o, 'a')
+ self.commit_transaction()
+
+ self.switch(1, False)
+ assert stm_get_char(o) == 'b'
+ stm_validate()
+ assert stm_get_char(o) == 'a'
+
+ def test_abort_doesnt_revert(self):
+ self.start_transaction()
+ o = stm_allocate_noconflict(16)
+ self.push_root(o)
+ self.commit_transaction()
+
+ self.start_transaction()
+ o = self.pop_root()
+ self.push_root(o)
+
+ self.switch(1)
+
+ self.start_transaction()
+ assert stm_get_char(o) == '\0'
+ stm_set_char(o, 'b') # later lost
+
+ self.switch(0)
+ stm_set_char(o, 'a')
+ self.commit_transaction()
+
+ self.switch(1, False)
+ assert stm_get_char(o) == 'b'
+ stm_validate()
+ assert stm_get_char(o) == 'a'
+ # now make sure we never revert back to '\0'
+ # since then we would need to trace backup copies
+ # in the GC to keep refs alive there
+ self.abort_transaction()
+ self.start_transaction()
+ assert stm_get_char(o) == 'a'
+
+
+ def test_huge_obj(self):
+ self.start_transaction()
+ o = stm_allocate_noconflict(1000+20*CARD_SIZE)
+ self.push_root(o)
+ self.commit_transaction()
+ self.start_transaction()
+ o = self.pop_root()
+ self.push_root(o)
+
+ stm_set_char(o, 'x', HDR, True)
+ stm_set_char(o, 'y', 1000, True)
+
+ self.switch(1)
+ self.start_transaction()
+ assert stm_get_char(o, HDR) == '\0'
+ stm_set_char(o, 'b', HDR, False) # later lost
+
+ self.switch(0)
+ self.commit_transaction()
+
+ self.switch(1, False)
+ assert stm_get_char(o, HDR) == 'b'
+ assert stm_get_char(o, 1000) == '\0'
+ stm_validate()
+ assert stm_get_char(o, HDR) == 'x'
+ assert stm_get_char(o, 1000) == 'y'
+ self.abort_transaction()
+ self.start_transaction()
+ assert stm_get_char(o, HDR) == 'x'
+ assert stm_get_char(o, 1000) == 'y'
+
+ def test_only_read(self):
+ self.start_transaction()
+ o = stm_allocate_noconflict(16)
+ self.push_root(o)
+ self.commit_transaction()
+
+ self.start_transaction()
+ o = self.pop_root()
+ self.push_root(o)
+
+ self.switch(1)
+
+ self.start_transaction()
+ stm_read(o) # don't touch the memory
+
+ self.switch(0)
+ stm_set_char(o, 'a')
+ self.commit_transaction()
+
+ self.switch(1, False)
+ stm_validate()
+ assert stm_get_char(o) == 'a'
diff --git a/c8/test/test_random.py b/c8/test/test_random.py
--- a/c8/test/test_random.py
+++ b/c8/test/test_random.py
@@ -243,6 +243,7 @@
self.root_numbering = 0
self.ref_type_map = {}
self.root_sizes = {}
+ self.noconfl_objs = set()
def get_new_root_name(self, is_ref_type, size):
self.root_numbering += 1
@@ -261,6 +262,12 @@
self.global_time += 1
return self.global_time
+ def add_noconfl_obj(self, o):
+ self.noconfl_objs.add(o)
+
+ def is_noconfl(self, o):
+ return o in self.noconfl_objs
+
def push_state_to_other_threads(self, trs):
assert not trs.check_must_abort()
for ts in self.thread_states:
@@ -382,6 +389,27 @@
thread_state.reload_roots(ex)
thread_state.register_root(r)
+def op_allocate_no_confl(ex, global_state, thread_state):
+ # copy&paste of op_allocate...
+ size = global_state.rnd.choice([
+ "16", "48", "288",
+ str(4096+16),
+ str(80*1024+16),
+ #"SOME_MEDIUM_SIZE+16",
+ #"SOME_LARGE_SIZE+16",
+ ])
+ r = global_state.get_new_root_name(False, size)
+ thread_state.push_roots(ex)
+
+ ex.do('%s = stm_allocate_noconflict(%s)' % (r, size))
+ ex.do('# 0x%x' % (int(ffi.cast("uintptr_t", ex.content[r]))))
+ thread_state.transaction_state.add_root(r, 0, True)
+
+ thread_state.pop_roots(ex)
+ thread_state.reload_roots(ex)
+ thread_state.register_root(r)
+ global_state.add_noconfl_obj(r)
+
def op_allocate_ref(ex, global_state, thread_state):
num = str(global_state.rnd.randrange(1, 1000))
r = global_state.get_new_root_name(True, num)
@@ -429,6 +457,10 @@
trs = thread_state.transaction_state
is_ref = global_state.has_ref_type(r)
try_cards = global_state.rnd.randrange(1, 100) > 5 # and False
+ # noconfl:
+ if global_state.is_noconfl(r):
+ ex.do('stm_write(%s) # noconfl' % r)
+ return
#
# decide on a value to write
if is_ref:
@@ -449,6 +481,10 @@
r = thread_state.get_random_root()
trs = thread_state.transaction_state
v = trs.read_root(r)
+ # noconfl:
+ if global_state.is_noconfl(r):
+ ex.do('stm_read(%s) # noconfl' % r)
+ return
#
offset = global_state.get_root_size(r) + " - 1"
if global_state.has_ref_type(r):
@@ -576,6 +612,7 @@
[op_write,]*70,
[op_allocate,]*10,
[op_allocate_ref]*10,
+ [op_allocate_no_confl]*2,
[op_commit_transaction,]*6,
[op_abort_transaction,],
[op_forget_root]*10,
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit