Author: Armin Rigo <[email protected]>
Branch: stmgc-c8
Changeset: r75738:053bac719b8c
Date: 2015-02-06 17:29 +0100
http://bitbucket.org/pypy/pypy/changeset/053bac719b8c/

Log:    hg merge stmgc-c7, and import stmgc/c8

diff too long, truncating to 2000 out of 4037 lines

diff --git a/lib_pypy/atomic.py b/lib_pypy/atomic.py
deleted file mode 100644
--- a/lib_pypy/atomic.py
+++ /dev/null
@@ -1,24 +0,0 @@
-"""
-API for accessing the multithreading extensions of PyPy
-"""
-import thread
-
-try:
-    from __pypy__ import thread as _thread
-    from __pypy__.thread import (atomic, getsegmentlimit,
-                                 hint_commit_soon, is_atomic)
-except ImportError:
-    # Not a STM-enabled PyPy.  We can still provide a version of 'atomic'
-    # that is good enough for our purposes.  With this limited version,
-    # an atomic block in thread X will not prevent running thread Y, if
-    # thread Y is not within an atomic block at all.
-    atomic = thread.allocate_lock()
-
-    def getsegmentlimit():
-        return 1
-
-    def hint_commit_soon():
-        pass
-
-    def is_atomic():
-        return atomic.locked()
diff --git a/lib_pypy/pypy_test/test_transaction.py 
b/lib_pypy/pypy_test/test_transaction.py
--- a/lib_pypy/pypy_test/test_transaction.py
+++ b/lib_pypy/pypy_test/test_transaction.py
@@ -1,3 +1,4 @@
+import py
 from lib_pypy import transaction
 
 N = 1000
@@ -7,9 +8,9 @@
 def test_simple_random_order():
     for x in range(N):
         lst = []
-        for i in range(10):
-            transaction.add(lst.append, i)
-        transaction.run()
+        with transaction.TransactionQueue():
+            for i in range(10):
+                transaction.add(lst.append, i)
         if VERBOSE:
             print lst
         assert sorted(lst) == range(10), lst
@@ -22,8 +23,8 @@
             i += 1
             if i < 10:
                 transaction.add(do_stuff, i)
-        transaction.add(do_stuff, 0)
-        transaction.run()
+        with transaction.TransactionQueue():
+            transaction.add(do_stuff, 0)
         if VERBOSE:
             print lst
         assert lst == range(10), lst
@@ -36,9 +37,9 @@
             j += 1
             if j < 10:
                 transaction.add(do_stuff, i, j)
-        for i in range(5):
-            transaction.add(do_stuff, i, 0)
-        transaction.run()
+        with transaction.TransactionQueue():
+            for i in range(5):
+                transaction.add(do_stuff, i, 0)
         if VERBOSE:
             print lsts
         assert lsts == (range(10),) * 5, lsts
@@ -56,10 +57,10 @@
             else:
                 lsts[i].append('foo')
                 raise FooError
-        for i in range(10):
-            transaction.add(do_stuff, i, 0)
         try:
-            transaction.run()
+            with transaction.TransactionQueue():
+                for i in range(10):
+                    transaction.add(do_stuff, i, 0)
         except FooError:
             pass
         else:
@@ -77,8 +78,9 @@
 
 
 def test_number_of_transactions_reported():
-    transaction.add(lambda: None)
-    transaction.run()
+    py.test.skip("not reimplemented")
+    with transaction.TransactionQueue():
+        transaction.add(lambda: None)
     assert transaction.number_of_transactions_in_last_run() == 1
 
     def add_transactions(l):
@@ -86,11 +88,52 @@
             for x in range(l[0]):
                 transaction.add(add_transactions, l[1:])
 
-    transaction.add(add_transactions, [10, 10, 10])
-    transaction.run()
+    with transaction.TransactionQueue():
+        transaction.add(add_transactions, [10, 10, 10])
     assert transaction.number_of_transactions_in_last_run() == 1111
 
 
+def test_stmidset():
+    s = transaction.stmidset()
+    key1 = []
+    key2 = []
+    s.add(key1)
+    assert key1 in s
+    assert key2 not in s
+    s.add(key2)
+    assert key1 in s
+    assert key2 in s
+    s.remove(key1)
+    assert key1 not in s
+    assert key2 in s
+    py.test.raises(KeyError, s.remove, key1)
+    s.discard(key1)
+    assert key1 not in s
+    assert key2 in s
+    s.discard(key2)
+    assert key2 not in s
+
+def test_stmiddict():
+    d = transaction.stmiddict()
+    key1 = []
+    key2 = []
+    py.test.raises(KeyError, "d[key1]")
+    d[key1] = 5
+    assert d[key1] == 5
+    assert key1 in d
+    assert d.get(key1) == 5
+    assert d.get(key1, 42) == 5
+    del d[key1]
+    py.test.raises(KeyError, "d[key1]")
+    assert key1 not in d
+    assert d.get(key1) is None
+    assert d.get(key1, 42) == 42
+    assert d.setdefault(key1, 42) == 42
+    assert d.setdefault(key1, 43) == 42
+    assert d.setdefault(key2) is None
+    assert d[key2] is None
+
+
 def run_tests():
     for name in sorted(globals().keys()):
         if name.startswith('test_'):
diff --git a/lib_pypy/transaction.py b/lib_pypy/transaction.py
--- a/lib_pypy/transaction.py
+++ b/lib_pypy/transaction.py
@@ -6,16 +6,16 @@
 give a simple-to-use API.
 
 Note that some rough edges still need to be sorted out; for now you
-have to explicitly set the number of threads to use by calling
-set_num_threads(), or you get a default of 4.
-
+have to explicitly set the number of threads to use by passing the
+'nb_segments' argument to TransactionQueue(), or you get a default of 4
+(or whatever the compiled-in maximum is).
 """
 
 from __future__ import with_statement
 import sys, thread, collections, cStringIO, linecache
 
 try:
-    from __pypy__.thread import atomic, is_atomic
+    from pypystm import atomic, is_atomic
 except ImportError:
     # Not a STM-enabled PyPy.  We can use a regular lock for 'atomic',
     # which is good enough for our purposes.  With this limited version,
@@ -37,63 +37,117 @@
     signals_enabled = _SignalsEnabled()
 
 try:
-    from __pypy__.thread import last_abort_info
+    from pypystm import hint_commit_soon
 except ImportError:
     # Not a STM-enabled PyPy.
-    def last_abort_info():
+    def hint_commit_soon():
         return None
 
+try:
+    from pypystm import getsegmentlimit
+except ImportError:
+    # Not a STM-enabled PyPy.
+    def getsegmentlimit():
+        return 1
 
-def set_num_threads(num):
-    """Set the number of threads to use."""
-    if num < 1:
-        raise ValueError("'num' must be at least 1, got %r" % (num,))
-    if _thread_pool.in_transaction:
-        raise TransactionError("cannot change the number of threads "
-                               "while running transactions")
-    _thread_pool.num_threads = num
+try:
+    from pypystm import hashtable
+except ImportError:
+    # Not a STM-enabled PyPy.
+    hashtable = dict
+
+class stmidset(object):
+    def __init__(self):
+        self._hashtable = hashtable()
+
+    def add(self, key):
+        self._hashtable[id(key)] = key
+
+    def __contains__(self, key):
+        return id(key) in self._hashtable
+
+    def remove(self, key):
+        del self._hashtable[id(key)]
+
+    def discard(self, key):
+        try:
+            del self._hashtable[id(key)]
+        except KeyError:
+            pass
+
+class stmiddict(object):
+    def __init__(self):
+        self._hashtable = hashtable()
+
+    def __getitem__(self, key):
+        return self._hashtable[id(key)][1]
+
+    def __setitem__(self, key, value):
+        self._hashtable[id(key)] = (key, value)
+
+    def __delitem__(self, key):
+        del self._hashtable[id(key)]
+
+    def __contains__(self, key):
+        return id(key) in self._hashtable
+
+    def get(self, key, default=None):
+        try:
+            return self._hashtable[id(key)][1]
+        except KeyError:
+            return default
+
+    def setdefault(self, key, default=None):
+        return self._hashtable.setdefault(id(key), (key, default))[1]
+
+
+# ------------------------------------------------------------
 
 
 class TransactionError(Exception):
     pass
 
 
-# XXX right now uses the same API as the old pypy-stm.  This will
-# be redesigned later.
-
 def add(f, *args, **kwds):
-    """Register the call 'f(*args, **kwds)' as running a new
-    transaction.  If we are currently running in a transaction too, the
-    new transaction will only start after the end of the current
-    transaction.  Note that if the current transaction or another running
-    in the meantime raises an exception, all pending transactions are
-    cancelled.
+    """Register a new transaction that will be done by 'f(*args, **kwds)'.
+    Must be called within the transaction in the "with TransactionQueue()"
+    block, or within a transaction started by this one, directly or
+    indirectly.
     """
     _thread_local.pending.append((f, args, kwds))
 
 
-def run():
-    """Run the pending transactions, as well as all transactions started
-    by them, and so on.  The order is random and undeterministic.  Must
-    be called from the main program, i.e. not from within another
-    transaction.  If at some point all transactions are done, returns.
-    If a transaction raises an exception, it propagates here; in this
-    case all pending transactions are cancelled.
+class TransactionQueue(object):
+    """Use in 'with TransactionQueue():'.  Creates a queue of
+    transactions.  The first transaction in the queue is the content of
+    the 'with:' block, which is immediately started.
+
+    Any transaction can register new transactions that will be run
+    after the current one is finished, using the global function add().
     """
-    tpool = _thread_pool
-    if tpool.in_transaction:
-        raise TransactionError("recursive invocation of transaction.run()")
-    if not _thread_local.pending:
-        return     # nothing to do
-    try:
-        tpool.setup()
-        tpool.run()
-    finally:
-        tpool.teardown()
-    tpool.reraise()
 
-def number_of_transactions_in_last_run():
-    return _thread_pool.transactions_run
+    def __init__(self, nb_segments=0):
+        if nb_segments <= 0:
+            nb_segments = getsegmentlimit()
+        _thread_pool.ensure_threads(nb_segments)
+
+    def __enter__(self):
+        if hasattr(_thread_local, "pending"):
+            raise TransactionError(
+                "recursive invocation of TransactionQueue()")
+        if is_atomic():
+            raise TransactionError(
+                "invocation of TransactionQueue() from an atomic context")
+        _thread_local.pending = []
+        atomic.__enter__()
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        atomic.__exit__(exc_type, exc_value, traceback)
+        pending = _thread_local.pending
+        del _thread_local.pending
+        if exc_type is None and len(pending) > 0:
+            _thread_pool.run(pending)
+
 
 # ____________________________________________________________
 
@@ -101,159 +155,118 @@
 class _ThreadPool(object):
 
     def __init__(self):
-        try:
-            from __pypy__.thread import getsegmentlimit
-            self.num_threads = getsegmentlimit()
-        except ImportError:
-            self.num_threads = 4
-        self.in_transaction = False
-        self.transactions_run = None
+        self.lock_running = thread.allocate_lock()
+        self.lock_done_running = thread.allocate_lock()
+        self.lock_done_running.acquire()
+        self.nb_threads = 0
+        self.deque = collections.deque()
+        self.locks = []
+        self.lock_deque = thread.allocate_lock()
+        self.exception = []
 
-    def setup(self):
-        # a mutex to protect parts of _grab_next_thing_to_do()
-        self.lock_mutex = thread.allocate_lock()
-        # this lock is released if and only if there are things to do in
-        # 'self.pending'; both are modified together, with the lock_mutex.
-        self.lock_pending = thread.allocate_lock()
-        # this lock is released when we are finished at the end
-        self.lock_if_released_then_finished = thread.allocate_lock()
-        self.lock_if_released_then_finished.acquire()
+    def ensure_threads(self, n):
+        if n > self.nb_threads:
+            with self.lock_running:
+                for i in range(self.nb_threads, n):
+                    assert len(self.locks) == self.nb_threads
+                    self.nb_threads += 1
+                    thread.start_new_thread(self.thread_runner, ())
+                    # The newly started thread should run immediately into
+                    # the case 'if len(self.locks) == self.nb_threads:'
+                    # and release this lock.  Wait until it does.
+                    self.lock_done_running.acquire()
+
+    def run(self, pending):
+        # For now, can't run multiple threads with each an independent
+        # TransactionQueue(): they are serialized.
+        with self.lock_running:
+            assert self.exception == []
+            assert len(self.deque) == 0
+            deque = self.deque
+            with self.lock_deque:
+                deque.extend(pending)
+                try:
+                    for i in range(len(pending)):
+                        self.locks.pop().release()
+                except IndexError:     # pop from empty list
+                    pass
+            #
+            self.lock_done_running.acquire()
+            #
+            if self.exception:
+                exc_type, exc_value, exc_traceback = self.exception
+                del self.exception[:]
+                raise exc_type, exc_value, exc_traceback
+
+    def thread_runner(self):
+        deque = self.deque
+        lock = thread.allocate_lock()
+        lock.acquire()
+        pending = []
+        _thread_local.pending = pending
+        lock_deque = self.lock_deque
+        exception = self.exception
         #
-        self.pending = _thread_local.pending
-        # there must be pending items at the beginning, which means that
-        # 'lock_pending' can indeed be released
-        assert self.pending
-        _thread_local.pending = None
-        #
-        self.num_waiting_threads = 0
-        self.transactions_run = 0
-        self.finished = False
-        self.got_exception = []
-        self.in_transaction = True
+        while True:
+            #
+            # Look at the deque and try to fetch the next item on the left.
+            # If empty, we add our lock to the 'locks' list.
+            lock_deque.acquire()
+            if deque:
+                next_transaction = deque.popleft()
+                lock_deque.release()
+            else:
+                self.locks.append(lock)
+                if len(self.locks) == self.nb_threads:
+                    self.lock_done_running.release()
+                lock_deque.release()
+                #
+                # Now wait until our lock is released.
+                lock.acquire()
+                continue
+            #
+            # Now we have a next_transaction.  Run it.
+            assert len(pending) == 0
+            while True:
+                f, args, kwds = next_transaction
+                with atomic:
+                    if len(exception) == 0:
+                        try:
+                            f(*args, **kwds)
+                        except:
+                            exception.extend(sys.exc_info())
+                del next_transaction
+                #
+                # If no new 'pending' transactions have been added, exit
+                # this loop and go back to fetch more from the deque.
+                if len(pending) == 0:
+                    break
+                #
+                # If we have some new 'pending' transactions, add them
+                # to the right of the deque and pop the next one from
+                # the left.  As we do this atomically with the
+                # 'lock_deque', we are sure that the deque cannot be
+                # empty before the popleft().  (We do that even when
+                # 'len(pending) == 1' instead of simply assigning the
+                # single item to 'next_transaction', because it looks
+                # like a good idea to preserve some first-in-first-out
+                # approximation.)
+                with self.lock_deque:
+                    deque.extend(pending)
+                    next_transaction = deque.popleft()
+                    try:
+                        for i in range(1, len(pending)):
+                            self.locks.pop().release()
+                    except IndexError:     # pop from empty list
+                        pass
+                del pending[:]
 
-    def run(self):
-        # start the N threads
-        task_counters = [[0] for i in range(self.num_threads)]
-        for counter in task_counters:
-            thread.start_new_thread(self._run_thread, (counter,))
-        # now wait.  When we manage to acquire the following lock, then
-        # we are finished.
-        self.lock_if_released_then_finished.acquire()
-        self.transactions_run = sum(x[0] for x in task_counters)
-
-    def teardown(self):
-        self.in_transaction = False
-        self.pending = None
-        self.lock_if_released_then_finished = None
-        self.lock_pending = None
-        self.lock_mutex = None
-        _thread_local.pending = collections.deque()
-
-    def reraise(self):
-        exc = self.got_exception
-        self.got_exception = None
-        if exc:
-            raise exc[0], exc[1], exc[2]    # exception, value, traceback
-
-    def _run_thread(self, counter):
-        tloc_pending = _thread_local.pending
-        got_exception = self.got_exception
-        try:
-            while True:
-                self._do_it(self._grab_next_thing_to_do(tloc_pending),
-                            got_exception)
-                counter[0] += 1
-        except _Done:
-            pass
-
-    def _grab_next_thing_to_do(self, tloc_pending):
-        if tloc_pending:
-            # grab the next thing to do from the thread-local deque
-            next = tloc_pending.popleft()
-            # add the rest, if any, to the global 'pending'
-            if tloc_pending:
-                #
-                self.lock_mutex.acquire()
-                if not self.pending:
-                    # self.pending is empty so far, but we are adding stuff.
-                    # we have to release the following lock.
-                    self.lock_pending.release()
-                self.pending.extend(tloc_pending)
-                self.lock_mutex.release()
-                #
-                tloc_pending.clear()
-            return next
-        #
-        self.lock_mutex.acquire()
-        while True:
-            try:
-                next = self.pending.popleft()
-            except IndexError:
-                # self.pending is empty: wait until it no longer is.
-                pass
-            else:
-                # self.pending was not empty.  If now it is empty, then
-                # fix the status of 'lock_pending'.
-                if not self.pending:
-                    self.lock_pending.acquire()
-                self.lock_mutex.release()
-                return next
-            #
-            # first check if all N threads are waiting here.
-            assert not self.finished
-            self.num_waiting_threads += 1
-            if self.num_waiting_threads == self.num_threads:
-                # yes, so finished!  unlock this to wake up the other
-                # threads, which are all waiting on the following acquire().
-                self.finished = True
-                self.lock_pending.release()
-            #
-            self.lock_mutex.release()
-            self.lock_pending.acquire()
-            self.lock_pending.release()
-            self.lock_mutex.acquire()
-            #
-            self.num_waiting_threads -= 1
-            if self.finished:
-                last_one_to_leave = self.num_waiting_threads == 0
-                self.lock_mutex.release()
-                if last_one_to_leave:
-                    self.lock_if_released_then_finished.release()
-                raise _Done
-
-    @staticmethod
-    def _do_it((f, args, kwds), got_exception):
-        # this is a staticmethod in order to make sure that we don't
-        # accidentally use 'self' in the atomic block.
-        try:
-            while True:
-                with signals_enabled:
-                    with atomic:
-                        info = last_abort_info()
-                        if info is None:
-                            if not got_exception:
-                                f(*args, **kwds)
-                            # else return early if already an exc to reraise
-                            return
-                report_abort_info(info)
-        except:
-            got_exception[:] = sys.exc_info()
 
 _thread_pool = _ThreadPool()
+_thread_local = thread._local()
 
 
-class _Done(Exception):
-    pass
-
-
-class _ThreadLocal(thread._local):
-    def __init__(self):
-        self.pending = collections.deque()
-
-_thread_local = _ThreadLocal()
-
-
-def report_abort_info(info):
+def XXXreport_abort_info(info):
     header = info[0]
     f = cStringIO.StringIO()
     if len(info) > 1:
@@ -279,3 +292,27 @@
         header[1], 'atom '*header[3], 'inev '*(header[4]>1),
         header[5], header[6])
     sys.stderr.write(f.getvalue())
+
+
+class threadlocalproperty(object):
+    def __init__(self, *default):
+        self.tl_default = default
+        self.tl_name = intern(str(id(self)))
+
+    def tl_get(self, obj):
+        try:
+            return obj._threadlocalproperties
+        except AttributeError:
+            return obj.__dict__.setdefault('_threadlocalproperties',
+                                           thread._local())
+
+    def __get__(self, obj, cls=None):
+        if obj is None:
+            return self
+        return getattr(self.tl_get(obj), self.tl_name, *self.tl_default)
+
+    def __set__(self, obj, value):
+        setattr(self.tl_get(obj), self.tl_name, value)
+
+    def __delete__(self, obj):
+        delattr(self.tl_get(obj), self.tl_name)
diff --git a/pypy/goal/targetpypystandalone.py 
b/pypy/goal/targetpypystandalone.py
--- a/pypy/goal/targetpypystandalone.py
+++ b/pypy/goal/targetpypystandalone.py
@@ -226,9 +226,10 @@
         # expose the following variables to ease debugging
         global space, entry_point
 
-        if config.translation.stm:
+        if config.translation.stm or config.objspace.usemodules.pypystm:
+            config.translation.stm = True
             config.translation.thread = True
-            config.objspace.usemodules._stm = True
+            config.objspace.usemodules.pypystm = True
 
         if config.objspace.allworkingmodules:
             from pypy.config.pypyoption import enable_allworkingmodules
diff --git a/pypy/interpreter/baseobjspace.py b/pypy/interpreter/baseobjspace.py
--- a/pypy/interpreter/baseobjspace.py
+++ b/pypy/interpreter/baseobjspace.py
@@ -1022,6 +1022,9 @@
     def newlist_unicode(self, list_u):
         return self.newlist([self.wrap(u) for u in list_u])
 
+    def newlist_int(self, list_i):
+        return self.newlist([self.wrap(i) for i in list_i])
+
     def newlist_hint(self, sizehint):
         from pypy.objspace.std.listobject import make_empty_list_with_size
         return make_empty_list_with_size(self, sizehint)
diff --git a/pypy/interpreter/executioncontext.py 
b/pypy/interpreter/executioncontext.py
--- a/pypy/interpreter/executioncontext.py
+++ b/pypy/interpreter/executioncontext.py
@@ -34,7 +34,7 @@
         self.w_profilefuncarg = None
         #
         if self.space.config.translation.stm:
-            from pypy.module._stm.ec import initialize_execution_context
+            from pypy.module.pypystm.ec import initialize_execution_context
             initialize_execution_context(self)
         self.thread_disappeared = False   # might be set to True after 
os.fork()
 
diff --git a/pypy/module/__pypy__/__init__.py b/pypy/module/__pypy__/__init__.py
--- a/pypy/module/__pypy__/__init__.py
+++ b/pypy/module/__pypy__/__init__.py
@@ -29,22 +29,11 @@
 class ThreadModule(MixedModule):
     appleveldefs = {
         'signals_enabled': 'app_signal.signals_enabled',
-        'atomic':                  'app_atomic.atomic',
-        'exclusive_atomic':        'app_atomic.exclusive_atomic',
     }
     interpleveldefs = {
         '_signals_enter':  'interp_signal.signals_enter',
         '_signals_exit':   'interp_signal.signals_exit',
-        '_atomic_enter':           'interp_atomic.atomic_enter',
-        '_exclusive_atomic_enter': 'interp_atomic.exclusive_atomic_enter',
-        '_atomic_exit':            'interp_atomic.atomic_exit',
-        'getsegmentlimit':         'interp_atomic.getsegmentlimit',
-        'hint_commit_soon':        'interp_atomic.hint_commit_soon',
-        'is_atomic':               'interp_atomic.is_atomic',
-        'error': 'space.fromcache(pypy.module.thread.error.Cache).w_error',
     }
-    def activate(self, space):
-        return self.space.config.objspace.usemodules.thread
 
 
 class IntOpModule(MixedModule):
diff --git a/pypy/module/__pypy__/test/test_signal.py 
b/pypy/module/__pypy__/test/test_signal.py
--- a/pypy/module/__pypy__/test/test_signal.py
+++ b/pypy/module/__pypy__/test/test_signal.py
@@ -4,7 +4,7 @@
 
 
 class AppTestMinimal:
-    spaceconfig = dict(usemodules=['__pypy__'])
+    spaceconfig = dict(usemodules=['__pypy__', 'thread'])
 
     def test_signal(self):
         from __pypy__ import thread
diff --git a/pypy/module/_stm/__init__.py b/pypy/module/pypystm/__init__.py
rename from pypy/module/_stm/__init__.py
rename to pypy/module/pypystm/__init__.py
--- a/pypy/module/_stm/__init__.py
+++ b/pypy/module/pypystm/__init__.py
@@ -4,12 +4,24 @@
 
 class Module(MixedModule):
     appleveldefs = {
+        'atomic':                  'app_atomic.atomic',
+        'exclusive_atomic':        'app_atomic.exclusive_atomic',
     }
 
     interpleveldefs = {
+        '_atomic_enter':           'interp_atomic.atomic_enter',
+        '_exclusive_atomic_enter': 'interp_atomic.exclusive_atomic_enter',
+        '_atomic_exit':            'interp_atomic.atomic_exit',
+        'getsegmentlimit':         'interp_atomic.getsegmentlimit',
+        'hint_commit_soon':        'interp_atomic.hint_commit_soon',
+        'is_atomic':               'interp_atomic.is_atomic',
+        'error': 'space.fromcache(pypy.module.thread.error.Cache).w_error',
+
         'local': 'local.STMLocal',
         'count': 'count.count',
         'hashtable': 'hashtable.W_Hashtable',
         'time': 'time.time',
         'clock': 'time.clock',
+        'stmset': 'stmset.W_STMSet',
+        'stmdict': 'stmdict.W_STMDict',
     }
diff --git a/pypy/module/__pypy__/app_atomic.py 
b/pypy/module/pypystm/app_atomic.py
rename from pypy/module/__pypy__/app_atomic.py
rename to pypy/module/pypystm/app_atomic.py
--- a/pypy/module/__pypy__/app_atomic.py
+++ b/pypy/module/pypystm/app_atomic.py
@@ -1,12 +1,12 @@
-from __pypy__ import thread
+import pypystm
 
 class Atomic(object):
-    __enter__ = thread._atomic_enter
-    __exit__  = thread._atomic_exit
+    __enter__ = pypystm._atomic_enter
+    __exit__  = pypystm._atomic_exit
 
 class ExclusiveAtomic(object):
-    __enter__ = thread._exclusive_atomic_enter
-    __exit__ = thread._atomic_exit
+    __enter__ = pypystm._exclusive_atomic_enter
+    __exit__ = pypystm._atomic_exit
 
 atomic = Atomic()
 exclusive_atomic = ExclusiveAtomic()
diff --git a/pypy/module/_stm/count.py b/pypy/module/pypystm/count.py
rename from pypy/module/_stm/count.py
rename to pypy/module/pypystm/count.py
--- a/pypy/module/_stm/count.py
+++ b/pypy/module/pypystm/count.py
@@ -1,5 +1,5 @@
 """
-_stm.count()
+pypystm.count()
 """
 
 from rpython.rlib import rstm
diff --git a/pypy/module/_stm/ec.py b/pypy/module/pypystm/ec.py
rename from pypy/module/_stm/ec.py
rename to pypy/module/pypystm/ec.py
--- a/pypy/module/_stm/ec.py
+++ b/pypy/module/pypystm/ec.py
@@ -20,7 +20,7 @@
     """Called from ExecutionContext.__init__()."""
     if ec.space.config.translation.rweakref:
         from rpython.rlib import rweakref
-        from pypy.module._stm.local import STMLocal
+        from pypy.module.pypystm.local import STMLocal
         ec._thread_local_dicts = rweakref.RWeakKeyDictionary(STMLocal, W_Root)
     else:
         ec._thread_local_dicts = FakeWeakKeyDictionary()
diff --git a/pypy/module/_stm/hashtable.py b/pypy/module/pypystm/hashtable.py
rename from pypy/module/_stm/hashtable.py
rename to pypy/module/pypystm/hashtable.py
--- a/pypy/module/_stm/hashtable.py
+++ b/pypy/module/pypystm/hashtable.py
@@ -1,12 +1,13 @@
 """
-The class _stm.hashtable, mapping integers to objects.
+The class pypystm.hashtable, mapping integers to objects.
 """
 
 from pypy.interpreter.baseobjspace import W_Root
 from pypy.interpreter.typedef import TypeDef
-from pypy.interpreter.gateway import interp2app, unwrap_spec
+from pypy.interpreter.gateway import interp2app, unwrap_spec, WrappedDefault
 
 from rpython.rlib import rstm
+from rpython.rlib.rarithmetic import intmask
 from rpython.rtyper.annlowlevel import cast_gcref_to_instance
 from rpython.rtyper.annlowlevel import cast_instance_to_gcref
 
@@ -25,21 +26,68 @@
 
     @unwrap_spec(key=int)
     def setitem_w(self, key, w_value):
-        gcref = cast_instance_to_gcref(w_value)
-        self.h.set(key, gcref)
+        entry = self.h.lookup(key)
+        entry.object = cast_instance_to_gcref(w_value)
 
     @unwrap_spec(key=int)
     def delitem_w(self, space, key):
-        gcref = self.h.get(key)
-        if not gcref:
+        entry = self.h.lookup(key)
+        if not entry.object:
             space.raise_key_error(space.wrap(key))
-        self.h.set(key, rstm.NULL_GCREF)
+        entry.object = rstm.NULL_GCREF
 
     @unwrap_spec(key=int)
     def contains_w(self, space, key):
         gcref = self.h.get(key)
         return space.newbool(not not gcref)
 
+    @unwrap_spec(key=int, w_default=WrappedDefault(None))
+    def get_w(self, space, key, w_default):
+        gcref = self.h.get(key)
+        if not gcref:
+            return w_default
+        return cast_gcref_to_instance(W_Root, gcref)
+
+    @unwrap_spec(key=int, w_default=WrappedDefault(None))
+    def setdefault_w(self, space, key, w_default):
+        entry = self.h.lookup(key)
+        gcref = entry.object
+        if not gcref:
+            entry.object = cast_instance_to_gcref(w_default)
+            return w_default
+        return cast_gcref_to_instance(W_Root, gcref)
+
+    def len_w(self, space):
+        return space.wrap(self.h.len())
+
+    def keys_w(self, space):
+        array, count = self.h.list()
+        try:
+            lst = [intmask(array[i].index) for i in range(count)]
+        finally:
+            self.h.freelist(array)
+        return space.newlist_int(lst)
+
+    def values_w(self, space):
+        array, count = self.h.list()
+        try:
+            lst_w = [cast_gcref_to_instance(W_Root, array[i].object)
+                     for i in range(count)]
+        finally:
+            self.h.freelist(array)
+        return space.newlist(lst_w)
+
+    def items_w(self, space):
+        array, count = self.h.list()
+        try:
+            lst_w = [space.newtuple([
+                         space.wrap(intmask(array[i].index)),
+                         cast_gcref_to_instance(W_Root, array[i].object)])
+                     for i in range(count)]
+        finally:
+            self.h.freelist(array)
+        return space.newlist(lst_w)
+
 
 def W_Hashtable___new__(space, w_subtype):
     r = space.allocate_instance(W_Hashtable, w_subtype)
@@ -47,10 +95,17 @@
     return space.wrap(r)
 
 W_Hashtable.typedef = TypeDef(
-    '_stm.hashtable',
+    'pypystm.hashtable',
     __new__ = interp2app(W_Hashtable___new__),
     __getitem__ = interp2app(W_Hashtable.getitem_w),
     __setitem__ = interp2app(W_Hashtable.setitem_w),
     __delitem__ = interp2app(W_Hashtable.delitem_w),
     __contains__ = interp2app(W_Hashtable.contains_w),
-    )
+    get = interp2app(W_Hashtable.get_w),
+    setdefault = interp2app(W_Hashtable.setdefault_w),
+
+    __len__ = interp2app(W_Hashtable.len_w),
+    keys    = interp2app(W_Hashtable.keys_w),
+    values  = interp2app(W_Hashtable.values_w),
+    items   = interp2app(W_Hashtable.items_w),
+)
diff --git a/pypy/module/__pypy__/interp_atomic.py 
b/pypy/module/pypystm/interp_atomic.py
rename from pypy/module/__pypy__/interp_atomic.py
rename to pypy/module/pypystm/interp_atomic.py
diff --git a/pypy/module/_stm/local.py b/pypy/module/pypystm/local.py
rename from pypy/module/_stm/local.py
rename to pypy/module/pypystm/local.py
--- a/pypy/module/_stm/local.py
+++ b/pypy/module/pypystm/local.py
@@ -1,5 +1,5 @@
 """
-The '_stm.local' class, used for 'thread._local' with STM.
+The 'pypystm.local' class, used for 'thread._local' with STM.
 """
 
 from pypy.interpreter.gateway import W_Root, interp2app
@@ -10,7 +10,7 @@
 
 def _fill_untranslated(ec):
     if not we_are_translated() and not hasattr(ec, '_thread_local_dicts'):
-        from pypy.module._stm.ec import initialize_execution_context
+        from pypy.module.pypystm.ec import initialize_execution_context
         initialize_execution_context(ec)
 
 
@@ -67,7 +67,7 @@
         # No arguments allowed
         pass
 
-STMLocal.typedef = TypeDef("_stm.local",
+STMLocal.typedef = TypeDef("pypystm.local",
                      __doc__ = "Thread-local data",
                      __new__ = interp2app(STMLocal.descr_local__new__.im_func),
                      __init__ = interp2app(STMLocal.descr_local__init__),
diff --git a/pypy/module/pypystm/stmdict.py b/pypy/module/pypystm/stmdict.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/pypystm/stmdict.py
@@ -0,0 +1,243 @@
+"""
+The class pypystm.stmdict, giving a part of the regular 'dict' interface
+"""
+
+from pypy.interpreter.baseobjspace import W_Root
+from pypy.interpreter.typedef import TypeDef
+from pypy.interpreter.gateway import interp2app, unwrap_spec, WrappedDefault
+
+from rpython.rlib import rstm, jit, rgc
+from rpython.rlib.objectmodel import specialize, we_are_translated
+from rpython.rtyper.annlowlevel import cast_gcref_to_instance
+from rpython.rtyper.annlowlevel import cast_instance_to_gcref
+from rpython.rtyper.lltypesystem import lltype, llmemory
+
+ARRAY = lltype.GcArray(llmemory.GCREF)
+PARRAY = lltype.Ptr(ARRAY)
+
+
+def find_equal_item(space, array, w_key):
+    w_item = cast_gcref_to_instance(W_Root, array[0])
+    if space.eq_w(w_key, w_item):
+        return 0
+    if len(array) > 2:
+        return _run_next_iterations(space, array, w_key)
+    return -1
+
[email protected]_look_inside
+def _run_next_iterations(space, array, w_key):
+    i = 2
+    limit = len(array)
+    while True:
+        w_item = cast_gcref_to_instance(W_Root, array[i])
+        if space.eq_w(w_key, w_item):
+            return i
+        i += 2
+        if i >= limit:
+            return -1
+
+def ll_arraycopy(source, dest, source_start, dest_start, length):
+    if we_are_translated():
+        rgc.ll_arraycopy(source, dest, source_start, dest_start, length)
+    else:
+        for i in range(length):
+            dest[dest_start + i] = source[source_start + i]
+
+def pop_from_entry(entry, space, w_key):
+    array = lltype.cast_opaque_ptr(PARRAY, entry.object)
+    if not array:
+        return None
+    i = find_equal_item(space, array, w_key)
+    if i < 0:
+        return None
+    # found
+    w_value = cast_gcref_to_instance(W_Root, array[i + 1])
+    L = len(array) - 2
+    if L == 0:
+        narray = lltype.nullptr(ARRAY)
+    else:
+        narray = lltype.malloc(ARRAY, L)
+        ll_arraycopy(array, narray, 0, 0, i)
+        ll_arraycopy(array, narray, i + 2, i, L - i)
+    entry.object = lltype.cast_opaque_ptr(llmemory.GCREF, narray)
+    return w_value
+
+
+class W_STMDict(W_Root):
+
+    def __init__(self):
+        self.h = rstm.create_hashtable()
+
+    def getitem_w(self, space, w_key):
+        hkey = space.hash_w(w_key)
+        gcref = self.h.get(hkey)
+        array = lltype.cast_opaque_ptr(PARRAY, gcref)
+        if array:
+            i = find_equal_item(space, array, w_key)
+            if i >= 0:
+                return cast_gcref_to_instance(W_Root, array[i + 1])
+        space.raise_key_error(w_key)
+
+    def setitem_w(self, space, w_key, w_value):
+        hkey = space.hash_w(w_key)
+        entry = self.h.lookup(hkey)
+        array = lltype.cast_opaque_ptr(PARRAY, entry.object)
+        if array:
+            i = find_equal_item(space, array, w_key)
+            if i >= 0:
+                # already there, update the value
+                array[i + 1] = cast_instance_to_gcref(w_value)
+                return
+            L = len(array)
+            narray = lltype.malloc(ARRAY, L + 2)
+            ll_arraycopy(array, narray, 0, 0, L)
+        else:
+            narray = lltype.malloc(ARRAY, 2)
+            L = 0
+        narray[L] = cast_instance_to_gcref(w_key)
+        narray[L + 1] = cast_instance_to_gcref(w_value)
+        entry.object = lltype.cast_opaque_ptr(llmemory.GCREF, narray)
+
+    def delitem_w(self, space, w_key):
+        hkey = space.hash_w(w_key)
+        entry = self.h.lookup(hkey)
+        if pop_from_entry(entry, space, w_key) is None:
+            space.raise_key_error(w_key)
+
+    def contains_w(self, space, w_key):
+        hkey = space.hash_w(w_key)
+        gcref = self.h.get(hkey)
+        array = lltype.cast_opaque_ptr(PARRAY, gcref)
+        if array and find_equal_item(space, array, w_key) >= 0:
+            return space.w_True
+        return space.w_False
+
+    @unwrap_spec(w_default=WrappedDefault(None))
+    def get_w(self, space, w_key, w_default):
+        hkey = space.hash_w(w_key)
+        gcref = self.h.get(hkey)
+        array = lltype.cast_opaque_ptr(PARRAY, gcref)
+        if array:
+            i = find_equal_item(space, array, w_key)
+            if i >= 0:
+                return cast_gcref_to_instance(W_Root, array[i + 1])
+        return w_default
+
+    def pop_w(self, space, w_key, w_default=None):
+        hkey = space.hash_w(w_key)
+        entry = self.h.lookup(hkey)
+        w_value = pop_from_entry(entry, space, w_key)
+        if w_value is not None:
+            return w_value
+        elif w_default is not None:
+            return w_default
+        else:
+            space.raise_key_error(w_key)
+
+    @unwrap_spec(w_default=WrappedDefault(None))
+    def setdefault_w(self, space, w_key, w_default):
+        hkey = space.hash_w(w_key)
+        entry = self.h.lookup(hkey)
+        array = lltype.cast_opaque_ptr(PARRAY, entry.object)
+        if array:
+            i = find_equal_item(space, array, w_key)
+            if i >= 0:
+                # already there, return the existing value
+                return cast_gcref_to_instance(W_Root, array[i + 1])
+            L = len(array)
+            narray = lltype.malloc(ARRAY, L + 2)
+            ll_arraycopy(array, narray, 0, 0, L)
+        else:
+            narray = lltype.malloc(ARRAY, 2)
+            L = 0
+        narray[L] = cast_instance_to_gcref(w_key)
+        narray[L + 1] = cast_instance_to_gcref(w_default)
+        entry.object = lltype.cast_opaque_ptr(llmemory.GCREF, narray)
+        return w_default
+
+    def get_length(self):
+        array, count = self.h.list()
+        try:
+            total_length_times_two = 0
+            for i in range(count):
+                subarray = lltype.cast_opaque_ptr(PARRAY, array[i].object)
+                assert subarray
+                total_length_times_two += len(subarray)
+        finally:
+            self.h.freelist(array)
+        return total_length_times_two >> 1
+
+    def get_keys_values_w(self, offset):
+        array, count = self.h.list()
+        try:
+            result_list_w = []
+            for i in range(count):
+                subarray = lltype.cast_opaque_ptr(PARRAY, array[i].object)
+                assert subarray
+                j = offset
+                limit = len(subarray)
+                while j < limit:
+                    w_item = cast_gcref_to_instance(W_Root, subarray[j])
+                    result_list_w.append(w_item)
+                    j += 2
+        finally:
+            self.h.freelist(array)
+        return result_list_w
+
+    def get_items_w(self, space):
+        array, count = self.h.list()
+        try:
+            result_list_w = []
+            for i in range(count):
+                subarray = lltype.cast_opaque_ptr(PARRAY, array[i].object)
+                assert subarray
+                j = 0
+                limit = len(subarray)
+                while j < limit:
+                    w_key = cast_gcref_to_instance(W_Root, subarray[j])
+                    w_value = cast_gcref_to_instance(W_Root, subarray[j + 1])
+                    result_list_w.append(space.newtuple([w_key, w_value]))
+                    j += 2
+        finally:
+            self.h.freelist(array)
+        return result_list_w
+
+    def len_w(self, space):
+        return space.wrap(self.get_length())
+
+    def iter_w(self, space):
+        # not a real lazy iterator!
+        return space.iter(self.keys_w(space))
+
+    def keys_w(self, space):
+        return space.newlist(self.get_keys_values_w(offset=0))
+
+    def values_w(self, space):
+        return space.newlist(self.get_keys_values_w(offset=1))
+
+    def items_w(self, space):
+        return space.newlist(self.get_items_w(space))
+
+
+def W_STMDict___new__(space, w_subtype):
+    r = space.allocate_instance(W_STMDict, w_subtype)
+    r.__init__()
+    return space.wrap(r)
+
+W_STMDict.typedef = TypeDef(
+    'pypystm.stmdict',
+    __new__ = interp2app(W_STMDict___new__),
+    __getitem__ = interp2app(W_STMDict.getitem_w),
+    __setitem__ = interp2app(W_STMDict.setitem_w),
+    __delitem__ = interp2app(W_STMDict.delitem_w),
+    __contains__ = interp2app(W_STMDict.contains_w),
+    get = interp2app(W_STMDict.get_w),
+    pop = interp2app(W_STMDict.pop_w),
+    setdefault = interp2app(W_STMDict.setdefault_w),
+
+    __len__  = interp2app(W_STMDict.len_w),
+    __iter__ = interp2app(W_STMDict.iter_w),
+    keys     = interp2app(W_STMDict.keys_w),
+    values   = interp2app(W_STMDict.values_w),
+    items    = interp2app(W_STMDict.items_w),
+    )
diff --git a/pypy/module/pypystm/stmset.py b/pypy/module/pypystm/stmset.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/pypystm/stmset.py
@@ -0,0 +1,146 @@
+"""
+The class pypystm.stmset, giving a part of the regular 'set' interface
+"""
+
+from pypy.interpreter.baseobjspace import W_Root
+from pypy.interpreter.typedef import TypeDef
+from pypy.interpreter.gateway import interp2app
+
+from rpython.rlib import rstm, jit
+from rpython.rlib.rgc import ll_arraycopy
+from rpython.rlib.objectmodel import specialize
+from rpython.rtyper.annlowlevel import cast_gcref_to_instance
+from rpython.rtyper.annlowlevel import cast_instance_to_gcref
+from rpython.rtyper.lltypesystem import lltype, llmemory
+
+ARRAY = lltype.GcArray(llmemory.GCREF)
+PARRAY = lltype.Ptr(ARRAY)
+
+
+def find_equal_item(space, array, w_key):
+    w_item = cast_gcref_to_instance(W_Root, array[0])
+    if space.eq_w(w_key, w_item):
+        return 0
+    if len(array) > 1:
+        return _run_next_iterations(space, array, w_key)
+    return -1
+
[email protected]_look_inside
+def _run_next_iterations(space, array, w_key):
+    i = 1
+    limit = len(array)
+    while True:
+        w_item = cast_gcref_to_instance(W_Root, array[i])
+        if space.eq_w(w_key, w_item):
+            return i
+        i += 1
+        if i >= limit:
+            return -1
+
+
+class W_STMSet(W_Root):
+
+    def __init__(self):
+        self.h = rstm.create_hashtable()
+
+    def contains_w(self, space, w_key):
+        hkey = space.hash_w(w_key)
+        gcref = self.h.get(hkey)
+        array = lltype.cast_opaque_ptr(PARRAY, gcref)
+        if array and find_equal_item(space, array, w_key) >= 0:
+            return space.w_True
+        return space.w_False
+
+    def add_w(self, space, w_key):
+        hkey = space.hash_w(w_key)
+        entry = self.h.lookup(hkey)
+        array = lltype.cast_opaque_ptr(PARRAY, entry.object)
+        if array:
+            if find_equal_item(space, array, w_key) >= 0:
+                return      # already there
+            L = len(array)
+            narray = lltype.malloc(ARRAY, L + 1)
+            ll_arraycopy(array, narray, 0, 0, L)
+        else:
+            narray = lltype.malloc(ARRAY, 1)
+            L = 0
+        narray[L] = cast_instance_to_gcref(w_key)
+        entry.object = lltype.cast_opaque_ptr(llmemory.GCREF, narray)
+
+    def try_remove(self, space, w_key):
+        hkey = space.hash_w(w_key)
+        entry = self.h.lookup(hkey)
+        array = lltype.cast_opaque_ptr(PARRAY, entry.object)
+        if not array:
+            return False
+        i = find_equal_item(space, array, w_key)
+        if i < 0:
+            return False
+        # found
+        L = len(array) - 1
+        if L == 0:
+            narray = lltype.nullptr(ARRAY)
+        else:
+            narray = lltype.malloc(ARRAY, L)
+            ll_arraycopy(array, narray, 0, 0, i)
+            ll_arraycopy(array, narray, i + 1, i, L - i)
+        entry.object = lltype.cast_opaque_ptr(llmemory.GCREF, narray)
+        return True
+
+    def remove_w(self, space, w_key):
+        if not self.try_remove(space, w_key):
+            space.raise_key_error(w_key)
+
+    def discard_w(self, space, w_key):
+        self.try_remove(space, w_key)
+
+    def get_length(self):
+        array, count = self.h.list()
+        try:
+            total_length = 0
+            for i in range(count):
+                subarray = lltype.cast_opaque_ptr(PARRAY, array[i].object)
+                assert subarray
+                total_length += len(subarray)
+        finally:
+            self.h.freelist(array)
+        return total_length
+
+    def get_items_w(self):
+        array, count = self.h.list()
+        try:
+            result_list_w = []
+            for i in range(count):
+                subarray = lltype.cast_opaque_ptr(PARRAY, array[i].object)
+                assert subarray
+                for j in range(len(subarray)):
+                    w_item = cast_gcref_to_instance(W_Root, subarray[j])
+                    result_list_w.append(w_item)
+        finally:
+            self.h.freelist(array)
+        return result_list_w
+
+    def len_w(self, space):
+        return space.wrap(self.get_length())
+
+    def iter_w(self, space):
+        # not a real lazy iterator!
+        return space.iter(space.newlist(self.get_items_w()))
+
+
+def W_STMSet___new__(space, w_subtype):
+    r = space.allocate_instance(W_STMSet, w_subtype)
+    r.__init__()
+    return space.wrap(r)
+
+W_STMSet.typedef = TypeDef(
+    'pypystm.stmset',
+    __new__ = interp2app(W_STMSet___new__),
+    __contains__ = interp2app(W_STMSet.contains_w),
+    add = interp2app(W_STMSet.add_w),
+    remove = interp2app(W_STMSet.remove_w),
+    discard = interp2app(W_STMSet.discard_w),
+
+    __len__ = interp2app(W_STMSet.len_w),
+    __iter__ = interp2app(W_STMSet.iter_w),
+    )
diff --git a/pypy/module/_stm/test/__init__.py 
b/pypy/module/pypystm/test/__init__.py
rename from pypy/module/_stm/test/__init__.py
rename to pypy/module/pypystm/test/__init__.py
diff --git a/pypy/module/__pypy__/test/test_atomic.py 
b/pypy/module/pypystm/test/test_atomic.py
rename from pypy/module/__pypy__/test/test_atomic.py
rename to pypy/module/pypystm/test/test_atomic.py
--- a/pypy/module/__pypy__/test/test_atomic.py
+++ b/pypy/module/pypystm/test/test_atomic.py
@@ -1,32 +1,13 @@
-from __future__ import with_statement
-from pypy.module.thread.test.support import GenericTestThread
-from rpython.rtyper.lltypesystem import rffi
 
 
-def test_bdecode(space):
-    from pypy.module.__pypy__.interp_atomic import bdecode
-    def bdec(s, expected):
-        p = rffi.str2charp(s)
-        w_obj, q = bdecode(space, p)
-        assert q == rffi.ptradd(p, len(s))
-        rffi.free_charp(p)
-        w_expected = space.wrap(expected)
-        assert space.eq_w(w_obj, w_expected)
-
-    bdec("i123e", 123)
-    bdec("i-123e", -123)
-    bdec('12:+"*-%&/()=?\x00', '+"*-%&/()=?\x00')
-    bdec("li123eli456eee", [123, [456]])
-    bdec("l5:abcdei2ee", ["abcde", 2])
-
-
-class AppTestAtomic(GenericTestThread):
+class AppTestAtomic:
+    spaceconfig = dict(usemodules=['pypystm', 'thread'])
 
     def test_simple(self):
-        from __pypy__ import thread
-        for atomic in thread.atomic, thread.exclusive_atomic:
+        import pypystm
+        for atomic in pypystm.atomic, pypystm.exclusive_atomic:
             with atomic:
-                assert thread.is_atomic()
+                assert pypystm.is_atomic()
             try:
                 with atomic:
                     raise ValueError
@@ -34,40 +15,40 @@
                 pass
 
     def test_nest_composable_atomic(self):
-        from __pypy__ import thread
-        with thread.atomic:
-            with thread.atomic:
-                assert thread.is_atomic()
-            assert thread.is_atomic()
-        assert not thread.is_atomic()
+        import pypystm
+        with pypystm.atomic:
+            with pypystm.atomic:
+                assert pypystm.is_atomic()
+            assert pypystm.is_atomic()
+        assert not pypystm.is_atomic()
 
     def test_nest_composable_below_exclusive(self):
-        from __pypy__ import thread
-        with thread.exclusive_atomic:
-            with thread.atomic:
-                with thread.atomic:
-                    assert thread.is_atomic()
-                assert thread.is_atomic()
-            assert thread.is_atomic()
-        assert not thread.is_atomic()
+        import pypystm
+        with pypystm.exclusive_atomic:
+            with pypystm.atomic:
+                with pypystm.atomic:
+                    assert pypystm.is_atomic()
+                assert pypystm.is_atomic()
+            assert pypystm.is_atomic()
+        assert not pypystm.is_atomic()
 
     def test_nest_exclusive_fails(self):
-        from __pypy__ import thread
+        import pypystm
         try:
-            with thread.exclusive_atomic:
-                with thread.exclusive_atomic:
-                    assert thread.is_atomic()
-        except thread.error, e:
-            assert not thread.is_atomic()
+            with pypystm.exclusive_atomic:
+                with pypystm.exclusive_atomic:
+                    assert pypystm.is_atomic()
+        except pypystm.error, e:
+            assert not pypystm.is_atomic()
             assert e.message == "exclusive_atomic block can't be entered 
inside another atomic block"
 
     def test_nest_exclusive_fails2(self):
-        from __pypy__ import thread
+        import pypystm
         try:
-            with thread.atomic:
-                with thread.exclusive_atomic:
-                    assert thread.is_atomic()
-                assert thread.is_atomic()
-        except thread.error, e:
-            assert not thread.is_atomic()
+            with pypystm.atomic:
+                with pypystm.exclusive_atomic:
+                    assert pypystm.is_atomic()
+                assert pypystm.is_atomic()
+        except pypystm.error, e:
+            assert not pypystm.is_atomic()
             assert e.message == "exclusive_atomic block can't be entered 
inside another atomic block"
diff --git a/pypy/module/_stm/test/test_count.py 
b/pypy/module/pypystm/test/test_count.py
rename from pypy/module/_stm/test/test_count.py
rename to pypy/module/pypystm/test/test_count.py
--- a/pypy/module/_stm/test/test_count.py
+++ b/pypy/module/pypystm/test/test_count.py
@@ -1,10 +1,10 @@
 
 
 class AppTestCount:
-    spaceconfig = dict(usemodules=['_stm'])
+    spaceconfig = dict(usemodules=['pypystm'])
 
     def test_count(self):
-        import _stm
-        x = _stm.count()
-        y = _stm.count()
+        import pypystm
+        x = pypystm.count()
+        y = pypystm.count()
         assert y == x + 1
diff --git a/pypy/module/_stm/test/test_hashtable.py 
b/pypy/module/pypystm/test/test_hashtable.py
rename from pypy/module/_stm/test/test_hashtable.py
rename to pypy/module/pypystm/test/test_hashtable.py
--- a/pypy/module/_stm/test/test_hashtable.py
+++ b/pypy/module/pypystm/test/test_hashtable.py
@@ -1,11 +1,11 @@
 
 
 class AppTestHashtable:
-    spaceconfig = dict(usemodules=['_stm'])
+    spaceconfig = dict(usemodules=['pypystm'])
 
     def test_simple(self):
-        import _stm
-        h = _stm.hashtable()
+        import pypystm
+        h = pypystm.hashtable()
         h[42+65536] = "bar"
         raises(KeyError, "h[42]")
         h[42] = "foo"
@@ -16,3 +16,42 @@
         raises(KeyError, "h[42]")
         assert h[42+65536] == "bar"
         raises(KeyError, "del h[42]")
+
+    def test_get_setdefault(self):
+        import pypystm
+        h = pypystm.hashtable()
+        assert h.get(42) is None
+        assert h.get(-43, None) is None
+        assert h.get(44, 81) == 81
+        raises(KeyError, "h[42]")
+        raises(KeyError, "h[-43]")
+        raises(KeyError, "h[44]")
+        assert h.setdefault(42) is None
+        assert h[42] is None
+        assert h.setdefault(42, "81") is None
+        assert h[42] is None
+        assert h.setdefault(44, "-81") == "-81"
+        assert h[44] == "-81"
+        assert h[42] is None
+
+    def test_len(self):
+        import pypystm
+        h = pypystm.hashtable()
+        assert len(h) == 0
+        h[42] = "foo"
+        assert len(h) == 1
+        h[43] = "bar"
+        assert len(h) == 2
+        h[42] = "baz"
+        assert len(h) == 2
+        del h[42]
+        assert len(h) == 1
+
+    def test_keys_values_items(self):
+        import pypystm
+        h = pypystm.hashtable()
+        h[42] = "foo"
+        h[43] = "bar"
+        assert sorted(h.keys()) == [42, 43]
+        assert sorted(h.values()) == ["bar", "foo"]
+        assert sorted(h.items()) == [(42, "foo"), (43, "bar")]
diff --git a/pypy/module/_stm/test/test_local.py 
b/pypy/module/pypystm/test/test_local.py
rename from pypy/module/_stm/test/test_local.py
rename to pypy/module/pypystm/test/test_local.py
--- a/pypy/module/_stm/test/test_local.py
+++ b/pypy/module/pypystm/test/test_local.py
@@ -3,11 +3,11 @@
 
 class AppTestSTMLocal(test_local.AppTestLocal):
     spaceconfig = test_local.AppTestLocal.spaceconfig.copy()
-    spaceconfig['usemodules'] += ('_stm',)
+    spaceconfig['usemodules'] += ('pypystm',)
 
     def setup_class(cls):
         test_local.AppTestLocal.setup_class.im_func(cls)
         cls.w__local = cls.space.appexec([], """():
-            import _stm
-            return _stm.local
+            import pypystm
+            return pypystm.local
         """)
diff --git a/pypy/module/pypystm/test/test_stmdict.py 
b/pypy/module/pypystm/test/test_stmdict.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/pypystm/test/test_stmdict.py
@@ -0,0 +1,118 @@
+
+
+class AppTestDict:
+    spaceconfig = dict(usemodules=['pypystm'])
+
+    def test_simple(self):
+        import pypystm
+        d = pypystm.stmdict()
+        raises(KeyError, "d[42.0]")
+        d[42.0] = "5"
+        assert d[42.0] == "5"
+        assert 42.0 in d
+        assert d.get(42.0) == "5"
+        assert d.get(42.0, 42) == "5"
+        del d[42.0]
+        raises(KeyError, "d[42.0]")
+        assert 42.0 not in d
+        assert d.get(42.0) is None
+        assert d.get(42.0, "41") == "41"
+        assert d.setdefault(42.0, "42") == "42"
+        assert d.setdefault(42.0, "43") == "42"
+        assert d.setdefault(42.5) is None
+        assert d[42.5] is None
+        assert d[42.0] == "42"
+        d[42.0] = "foo"
+        assert d[42.0] == "foo"
+
+    def test_hash_collision(self):
+        import pypystm
+        key1 = 5L
+        key2 = 5L + 2**64 - 1
+        key3 = 5L + 2**65 - 2
+        assert hash(key1) == hash(key2) == hash(key3)
+        d = pypystm.stmdict()
+        d[key1] = 1.0
+        d[key2] = 2.0
+        assert d[key1] == 1.0
+        assert key2 in d
+        assert key3 not in d
+        raises(KeyError, "d[key3]")
+        assert d.get(key3) is None
+        del d[key1]
+        assert key1 not in d
+        assert d[key2] == 2.0
+        assert key3 not in d
+        raises(KeyError, "del d[key1]")
+        del d[key2]
+        assert key1 not in d
+        assert key2 not in d
+        assert key3 not in d
+        raises(KeyError, "del d[key3]")
+        assert d.setdefault(key1, 5.0) == 5.0
+        assert d.setdefault(key2, 7.5) == 7.5
+        assert d.setdefault(key1, 2.3) == 5.0
+
+    def test_must_be_hashable(self):
+        import pypystm
+        d = pypystm.stmdict()
+        raises(TypeError, "d[[]]")
+        raises(TypeError, "d[[]] = 5")
+        raises(TypeError, "del d[[]]")
+        raises(TypeError, "[] in d")
+        raises(TypeError, "d.get([])")
+        raises(TypeError, "d.setdefault([], 0)")
+
+    def test_equal_elements(self):
+        import pypystm
+        d = pypystm.stmdict()
+        d[42.0] = "hello"
+        assert d[42] == "hello"
+        assert d.get(42L) == "hello"
+        assert d.get(42.001) is None
+
+    def test_list_from_dict(self):
+        import pypystm
+        d = pypystm.stmdict()
+        assert len(d) == 0
+        assert tuple(d) == ()
+        d[42.5] = "foo"
+        d[42.0] = ["bar"]
+        assert sorted(d) == [42.0, 42.5]
+        assert len(d) == 2
+        del d[42]
+        assert len(d) == 1
+        assert list(d) == [42.5]
+        #
+        class Key(object):
+            def __hash__(self):
+                return hash(42.5)
+        key3 = Key()
+        d[key3] = "other"
+        assert len(d) == 2
+        items = list(d)
+        assert items == [42.5, key3] or items == [key3, 42.5]
+
+    def test_keys_values_items(self):
+        import pypystm
+        d = pypystm.stmdict()
+        d[42.5] = "bar"
+        d[42.0] = "foo"
+        assert sorted(d.keys()) == [42.0, 42.5]
+        assert sorted(d.values()) == ["bar", "foo"]
+        assert sorted(d.items()) == [(42.0, "foo"), (42.5, "bar")]
+
+    def test_pop(self):
+        import pypystm
+        d = pypystm.stmdict()
+        raises(KeyError, d.pop, 42.0)
+        assert d.pop(42.0, "foo") == "foo"
+        raises(KeyError, "d[42.0]")
+        d[42.0] = "bar"
+        res = d.pop(42.0)
+        assert res == "bar"
+        raises(KeyError, "d[42.0]")
+        d[42.0] = "bar"
+        res = d.pop(42.0, "foo")
+        assert res == "bar"
+        raises(KeyError, "d[42.0]")
diff --git a/pypy/module/pypystm/test/test_stmset.py 
b/pypy/module/pypystm/test/test_stmset.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/pypystm/test/test_stmset.py
@@ -0,0 +1,85 @@
+
+
+class AppTestSet:
+    spaceconfig = dict(usemodules=['pypystm'])
+
+    def test_simple(self):
+        import pypystm
+        s = pypystm.stmset()
+        s.add(42.0)
+        assert 42.0 in s
+        assert 42.5 not in s
+        s.add(42.5)
+        assert 42.0 in s
+        assert 42.5 in s
+        s.add(42.5)
+        assert 42.0 in s
+        assert 42.5 in s
+        s.remove(42.0)
+        assert 42.0 not in s
+        assert 42.5 in s
+        raises(KeyError, s.remove, 42.0)
+        s.discard(42.0)
+        assert 42.0 not in s
+        assert 42.5 in s
+        s.discard(42.5)
+        assert 42.5 not in s
+
+    def test_hash_collision(self):
+        import pypystm
+        class Key(object):
+            def __hash__(self):
+                return 42
+        key1 = Key()
+        key2 = Key()
+        key3 = Key()
+        s = pypystm.stmset()
+        s.add(key1)
+        s.add(key2)
+        assert key1 in s
+        assert key2 in s
+        assert key3 not in s
+        s.remove(key1)
+        assert key1 not in s
+        assert key2 in s
+        assert key3 not in s
+        s.remove(key2)
+        assert key1 not in s
+        assert key2 not in s
+        assert key3 not in s
+
+    def test_must_be_hashable(self):
+        import pypystm
+        s = pypystm.stmset()
+        raises(TypeError, s.add, [])
+        raises(TypeError, s.remove, [])
+        raises(TypeError, s.discard, [])
+
+    def test_equal_elements(self):
+        import pypystm
+        s = pypystm.stmset()
+        s.add(42.0)
+        assert 42 in s
+        assert 42L in s
+        assert 42.001 not in s
+
+    def test_list_from_set(self):
+        import pypystm
+        s = pypystm.stmset()
+        assert len(s) == 0
+        assert tuple(s) == ()
+        s.add(42.5)
+        s.add(42.0)
+        assert sorted(s) == [42.0, 42.5]
+        assert len(s) == 2
+        s.remove(42.0)
+        assert list(s) == [42.5]
+        #
+        class Key(object):
+            def __hash__(self):
+                return hash(42.5)
+        key3 = Key()
+        s.add(key3)
+        assert len(s) == 2
+        items = list(s)
+        assert items == [42.5, key3] or items == [key3, 42.5]
diff --git a/pypy/module/_stm/test/test_time.py 
b/pypy/module/pypystm/test/test_time.py
rename from pypy/module/_stm/test/test_time.py
rename to pypy/module/pypystm/test/test_time.py
--- a/pypy/module/_stm/test/test_time.py
+++ b/pypy/module/pypystm/test/test_time.py
@@ -1,13 +1,13 @@
 
 
 class AppTestHashtable:
-    spaceconfig = dict(usemodules=['_stm'])
+    spaceconfig = dict(usemodules=['pypystm'])
 
     def test_simple(self):
-        import _stm
-        t1 = _stm.time()
-        t2 = _stm.time()
+        import pypystm
+        t1 = pypystm.time()
+        t2 = pypystm.time()
         assert t1 < t2 < t1 + 1
-        t1 = _stm.clock()
-        t2 = _stm.clock()
+        t1 = pypystm.clock()
+        t2 = pypystm.clock()
         assert t1 < t2 < t1 + 1
diff --git a/pypy/module/pypystm/test_pypy_c/__init__.py 
b/pypy/module/pypystm/test_pypy_c/__init__.py
new file mode 100644
diff --git a/pypy/module/pypystm/test_pypy_c/support.py 
b/pypy/module/pypystm/test_pypy_c/support.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/pypystm/test_pypy_c/support.py
@@ -0,0 +1,146 @@
+import py
+import sys, os, subprocess, types
+from rpython.tool.udir import udir
+
+
+class BaseTestSTM(object):
+
+    HEADER = """
+import thread, pypystm
+
+NUM_THREADS = 3
+
+_b_to_go = NUM_THREADS
+_b_done = False
+_b_lock = thread.allocate_lock()
+_b_locks = [thread.allocate_lock() for _i in range(NUM_THREADS)]
+for _bl in _b_locks:
+    _bl.acquire()
+
+class BarrierThreadsDone(Exception):
+    pass
+
+def barrier(tnum, done=False):
+    '''Waits until NUM_THREADS call this function, and then returns
+    in all these threads at once.'''
+    global _b_to_go, _b_done
+    _b_lock.acquire()
+    if done:
+        _b_done = True
+    _b_to_go -= 1
+    if _b_to_go > 0:
+        _b_lock.release()
+        _b_locks[tnum].acquire()
+    else:
+        _b_to_go = NUM_THREADS
+        for i in range(NUM_THREADS):
+            if i != tnum:
+                _b_locks[i].release()
+        _b_lock.release()
+    if _b_done:
+        raise BarrierThreadsDone
+
+def _run(tnum, lock, result, function, args):
+    start = pypystm.time()
+    try:
+        try:
+            while True:
+                function(*args)
+                if pypystm.time() - start >= 3.0:
+                    break
+        except BarrierThreadsDone:
+            pass
+        result.append(1)
+    finally:
+        lock.release()
+    while len(result) != NUM_THREADS:
+        barrier(tnum, done=True)
+
+def run_in_threads(function, arg_thread_num=False, arg_class=None):
+    locks = []
+    result = []
+    for i in range(NUM_THREADS):
+        lock = thread.allocate_lock()
+        lock.acquire()
+        args = ()
+        if arg_thread_num:
+            args += (i,)
+        if arg_class:
+            args += (arg_class(),)
+        thread.start_new_thread(_run, (i, lock, result, function, args))
+        locks.append(lock)
+    for lock in locks:
+        lock._py3k_acquire(timeout=30)
+    if len(result) < len(locks):
+        raise Exception("not all threads completed successfully")
+"""
+
+    def setup_class(cls):
+        if '__pypy__' not in sys.builtin_module_names:
+            py.test.skip("must run this test with pypy")
+        try:
+            import pypystm
+        except ImportError:
+            py.test.skip("must give a pypy-c with stm enabled")
+        cls.tmpdir = udir.join('test-pypy-stm')
+        cls.tmpdir.ensure(dir=True)
+
+    def setup_method(self, meth):
+        self.filepath = self.tmpdir.join(meth.im_func.func_name + '.py')
+        self.logfile = self.filepath.new(ext='.log')
+
+    def _write_source(self, func_or_src, args=[]):
+        src = py.code.Source(func_or_src)
+        if isinstance(func_or_src, types.FunctionType):
+            funcname = func_or_src.func_name
+        else:
+            funcname = 'main'
+        arglist = ', '.join(map(repr, args))
+        with self.filepath.open("w") as f:
+            f.write(self.HEADER)
+            f.write(str(src) + '\n')
+            f.write("print %s(%s)\n" % (funcname, arglist))
+
+    def _execute(self, import_site=False):
+        cmdline = [sys.executable]
+        if not import_site:
+            cmdline.append('-S')
+        cmdline.append(str(self.filepath))
+        env = os.environ.copy()
+        env['PYPYSTM'] = str(self.logfile)
+        #
+        pipe = subprocess.Popen(cmdline,
+                                env=env,
+                                stdout=subprocess.PIPE,
+                                stderr=subprocess.PIPE)
+        stdout, stderr = pipe.communicate()
+        if pipe.returncode > 0:
+            raise IOError("subprocess error %d:\n%s" % (pipe.returncode,
+                                                        stderr))
+        if pipe.returncode < 0:
+            raise IOError("subprocess was killed by signal %d" % (
+                pipe.returncode,))
+
+    def _parse_log(self):
+        from pypy.stm.print_stm_log import StmLog
+        return StmLog(str(self.logfile))
+
+    def _check_count_conflicts(self, func_or_src, args=[]):
+        self._write_source(func_or_src, args)
+        self._execute()
+        stmlog = self._parse_log()
+        count = stmlog.get_total_aborts_and_pauses()
+        print 'stmlog.get_total_aborts_and_pauses():', count
+        return count
+
+    def check_almost_no_conflict(self, *args):
+        count = self._check_count_conflicts(*args)
+        assert count < 500
+
+    def check_MANY_conflicts(self, *args):
+        count = self._check_count_conflicts(*args)
+        assert count > 20000
+
+    def check_SOME_conflicts(self, *args):
+        count = self._check_count_conflicts(*args)
+        assert count > 1000
diff --git a/pypy/module/pypystm/test_pypy_c/test_conflict.py 
b/pypy/module/pypystm/test_pypy_c/test_conflict.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/pypystm/test_pypy_c/test_conflict.py
@@ -0,0 +1,46 @@
+from pypy.module.pypystm.test_pypy_c.support import BaseTestSTM
+
+
+class TestConflict(BaseTestSTM):
+
+    def test_obvious(self):
+        def f():
+            class X(object):
+                pass
+            x = X()     # shared
+            x.a = 0
+            def g():
+                x.a += 1
+            run_in_threads(g)
+        #
+        self.check_MANY_conflicts(f)
+
+    def test_plain_dict_access(self):
+        def f():
+            d = {}     # shared
+            def g(n):
+                d[n] = d.get(n, 0) + 1
+            run_in_threads(g, arg_thread_num=True)
+        #
+        self.check_MANY_conflicts(f)
+
+    def test_write_to_many_objects_in_order(self):
+        def f():
+            import weakref
+
+            class X(object):
+                pass
+
+            lst = []     # shared
+
+            def g(tnum):
+                if tnum == 0:
+                    lst[:] = [X() for i in range(1000)]
+                barrier(tnum)
+                for x in lst:
+                    x.a = 5
+                barrier(tnum)
+
+            run_in_threads(g, arg_thread_num=True)
+        #
+        self.check_SOME_conflicts(f)
diff --git a/pypy/module/pypystm/test_pypy_c/test_no_conflict.py 
b/pypy/module/pypystm/test_pypy_c/test_no_conflict.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/pypystm/test_pypy_c/test_no_conflict.py
@@ -0,0 +1,52 @@
+import py
+from pypy.module.pypystm.test_pypy_c.support import BaseTestSTM
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to