[pypy-commit] pypy numpy-dtype-alt: Merged default. Includes removing the sort from numarrary, which was incompatible with dtypes (plus there were efficiency concerns, bugs, and style issues). Will be added back post-merge.
alex_gaynor
noreply at buildbot.pypy.org
Sun Aug 28 06:58:33 CEST 2011
Author: Alex Gaynor <alex.gaynor at gmail.com>
Branch: numpy-dtype-alt
Changeset: r46845:699a64e0ee86
Date: 2011-08-28 00:58 -0400
http://bitbucket.org/pypy/pypy/changeset/699a64e0ee86/
Log: Merged default. Includes removing the sort from numarrary, which
was incompatible with dtypes (plus there were efficiency concerns,
bugs, and style issues). Will be added back post-merge.
diff too long, truncating to 10000 out of 13786 lines
diff --git a/lib_pypy/greenlet.py b/lib_pypy/greenlet.py
--- a/lib_pypy/greenlet.py
+++ b/lib_pypy/greenlet.py
@@ -1,1 +1,138 @@
-from _stackless import greenlet
+import _continuation, sys
+
+
+# ____________________________________________________________
+# Exceptions
+
+class GreenletExit(Exception):
+ """This special exception does not propagate to the parent greenlet; it
+can be used to kill a single greenlet."""
+
+error = _continuation.error
+
+# ____________________________________________________________
+# Helper function
+
+def getcurrent():
+ "Returns the current greenlet (i.e. the one which called this function)."
+ try:
+ return _tls.current
+ except AttributeError:
+ # first call in this thread: current == main
+ _green_create_main()
+ return _tls.current
+
+# ____________________________________________________________
+# The 'greenlet' class
+
+_continulet = _continuation.continulet
+
+class greenlet(_continulet):
+ getcurrent = staticmethod(getcurrent)
+ error = error
+ GreenletExit = GreenletExit
+ __main = False
+ __started = False
+
+ def __new__(cls, *args, **kwds):
+ self = _continulet.__new__(cls)
+ self.parent = getcurrent()
+ return self
+
+ def __init__(self, run=None, parent=None):
+ if run is not None:
+ self.run = run
+ if parent is not None:
+ self.parent = parent
+
+ def switch(self, *args):
+ "Switch execution to this greenlet, optionally passing the values "
+ "given as argument(s). Returns the value passed when switching back."
+ return self.__switch(_continulet.switch, args)
+
+ def throw(self, typ=GreenletExit, val=None, tb=None):
+ "raise exception in greenlet, return value passed when switching back"
+ return self.__switch(_continulet.throw, typ, val, tb)
+
+ def __switch(target, unbound_method, *args):
+ current = getcurrent()
+ #
+ while not target:
+ if not target.__started:
+ _continulet.__init__(target, _greenlet_start, *args)
+ args = ()
+ target.__started = True
+ break
+ # already done, go to the parent instead
+ # (NB. infinite loop possible, but unlikely, unless you mess
+ # up the 'parent' explicitly. Good enough, because a Ctrl-C
+ # will show that the program is caught in this loop here.)
+ target = target.parent
+ #
+ try:
+ if current.__main:
+ if target.__main:
+ # switch from main to main
+ if unbound_method == _continulet.throw:
+ raise args[0], args[1], args[2]
+ (args,) = args
+ else:
+ # enter from main to target
+ args = unbound_method(target, *args)
+ else:
+ if target.__main:
+ # leave to go to target=main
+ args = unbound_method(current, *args)
+ else:
+ # switch from non-main to non-main
+ args = unbound_method(current, *args, to=target)
+ except GreenletExit, e:
+ args = (e,)
+ finally:
+ _tls.current = current
+ #
+ if len(args) == 1:
+ return args[0]
+ else:
+ return args
+
+ def __nonzero__(self):
+ return self.__main or _continulet.is_pending(self)
+
+ @property
+ def dead(self):
+ return self.__started and not self
+
+ @property
+ def gr_frame(self):
+ raise NotImplementedError("attribute 'gr_frame' of greenlet objects")
+
+# ____________________________________________________________
+# Internal stuff
+
+try:
+ from thread import _local
+except ImportError:
+ class _local(object): # assume no threads
+ pass
+
+_tls = _local()
+
+def _green_create_main():
+ # create the main greenlet for this thread
+ _tls.current = None
+ gmain = greenlet.__new__(greenlet)
+ gmain._greenlet__main = True
+ gmain._greenlet__started = True
+ assert gmain.parent is None
+ _tls.main = gmain
+ _tls.current = gmain
+
+def _greenlet_start(greenlet, args):
+ _tls.current = greenlet
+ try:
+ res = greenlet.run(*args)
+ finally:
+ if greenlet.parent is not _tls.main:
+ _continuation.permute(greenlet, greenlet.parent)
+ return (res,)
diff --git a/pypy/config/makerestdoc.py b/pypy/config/makerestdoc.py
--- a/pypy/config/makerestdoc.py
+++ b/pypy/config/makerestdoc.py
@@ -134,7 +134,7 @@
for child in self._children:
subpath = fullpath + "." + child._name
toctree.append(subpath)
- content.add(Directive("toctree", *toctree, maxdepth=4))
+ content.add(Directive("toctree", *toctree, **{'maxdepth': 4}))
content.join(
ListItem(Strong("name:"), self._name),
ListItem(Strong("description:"), self.doc))
diff --git a/pypy/config/pypyoption.py b/pypy/config/pypyoption.py
--- a/pypy/config/pypyoption.py
+++ b/pypy/config/pypyoption.py
@@ -33,7 +33,8 @@
"struct", "_hashlib", "_md5", "_sha", "_minimal_curses", "cStringIO",
"thread", "itertools", "pyexpat", "_ssl", "cpyext", "array",
"_bisect", "binascii", "_multiprocessing", '_warnings',
- "_collections", "_multibytecodec", "micronumpy", "_ffi"]
+ "_collections", "_multibytecodec", "micronumpy", "_ffi",
+ "_continuation"]
))
translation_modules = default_modules.copy()
@@ -99,6 +100,7 @@
"_ssl" : ["pypy.module._ssl.interp_ssl"],
"_hashlib" : ["pypy.module._ssl.interp_ssl"],
"_minimal_curses": ["pypy.module._minimal_curses.fficurses"],
+ "_continuation": ["pypy.rlib.rstacklet"],
}
def get_module_validator(modname):
diff --git a/pypy/config/test/test_config.py b/pypy/config/test/test_config.py
--- a/pypy/config/test/test_config.py
+++ b/pypy/config/test/test_config.py
@@ -1,5 +1,5 @@
from pypy.config.config import *
-import py
+import py, sys
def make_description():
gcoption = ChoiceOption('name', 'GC name', ['ref', 'framework'], 'ref')
@@ -69,13 +69,15 @@
attrs = dir(config)
assert '__repr__' in attrs # from the type
assert '_cfgimpl_values' in attrs # from self
- assert 'gc' in attrs # custom attribute
- assert 'objspace' in attrs # custom attribute
+ if sys.version_info >= (2, 6):
+ assert 'gc' in attrs # custom attribute
+ assert 'objspace' in attrs # custom attribute
#
attrs = dir(config.gc)
- assert 'name' in attrs
- assert 'dummy' in attrs
- assert 'float' in attrs
+ if sys.version_info >= (2, 6):
+ assert 'name' in attrs
+ assert 'dummy' in attrs
+ assert 'float' in attrs
def test_arbitrary_option():
descr = OptionDescription("top", "", [
diff --git a/pypy/config/translationoption.py b/pypy/config/translationoption.py
--- a/pypy/config/translationoption.py
+++ b/pypy/config/translationoption.py
@@ -28,10 +28,9 @@
translation_optiondescription = OptionDescription(
"translation", "Translation Options", [
- BoolOption("stackless", "enable stackless features during compilation",
- default=False, cmdline="--stackless",
- requires=[("translation.type_system", "lltype"),
- ("translation.gcremovetypeptr", False)]), # XXX?
+ BoolOption("continuation", "enable single-shot continuations",
+ default=False, cmdline="--continuation",
+ requires=[("translation.type_system", "lltype")]),
ChoiceOption("type_system", "Type system to use when RTyping",
["lltype", "ootype"], cmdline=None, default="lltype",
requires={
@@ -70,7 +69,8 @@
"statistics": [("translation.gctransformer", "framework")],
"generation": [("translation.gctransformer", "framework")],
"hybrid": [("translation.gctransformer", "framework")],
- "boehm": [("translation.gctransformer", "boehm")],
+ "boehm": [("translation.gctransformer", "boehm"),
+ ("translation.continuation", False)], # breaks
"markcompact": [("translation.gctransformer", "framework")],
"minimark": [("translation.gctransformer", "framework")],
},
@@ -389,8 +389,6 @@
config.translation.suggest(withsmallfuncsets=5)
elif word == 'jit':
config.translation.suggest(jit=True)
- if config.translation.stackless:
- raise NotImplementedError("JIT conflicts with stackless for now")
elif word == 'removetypeptr':
config.translation.suggest(gcremovetypeptr=True)
else:
diff --git a/pypy/doc/config/objspace.usemodules._stackless.txt b/pypy/doc/config/objspace.usemodules._continuation.txt
copy from pypy/doc/config/objspace.usemodules._stackless.txt
copy to pypy/doc/config/objspace.usemodules._continuation.txt
--- a/pypy/doc/config/objspace.usemodules._stackless.txt
+++ b/pypy/doc/config/objspace.usemodules._continuation.txt
@@ -1,6 +1,4 @@
-Use the '_stackless' module.
+Use the '_continuation' module.
-Exposes the `stackless` primitives, and also implies a stackless build.
-See also :config:`translation.stackless`.
-
-.. _`stackless`: ../stackless.html
+Exposes the `continulet` app-level primitives.
+See also :config:`translation.continuation`.
diff --git a/pypy/doc/config/objspace.usemodules._stackless.txt b/pypy/doc/config/objspace.usemodules._stackless.txt
--- a/pypy/doc/config/objspace.usemodules._stackless.txt
+++ b/pypy/doc/config/objspace.usemodules._stackless.txt
@@ -1,6 +1,1 @@
-Use the '_stackless' module.
-
-Exposes the `stackless` primitives, and also implies a stackless build.
-See also :config:`translation.stackless`.
-
-.. _`stackless`: ../stackless.html
+Deprecated.
diff --git a/pypy/doc/config/translation.stackless.txt b/pypy/doc/config/translation.continuation.txt
rename from pypy/doc/config/translation.stackless.txt
rename to pypy/doc/config/translation.continuation.txt
--- a/pypy/doc/config/translation.stackless.txt
+++ b/pypy/doc/config/translation.continuation.txt
@@ -1,5 +1,2 @@
-Run the `stackless transform`_ on each generated graph, which enables the use
-of coroutines at RPython level and the "stackless" module when translating
-PyPy.
-
-.. _`stackless transform`: ../stackless.html
+Enable the use of a stackless-like primitive called "stacklet".
+In PyPy, this is exposed at app-level by the "_continuation" module.
diff --git a/pypy/interpreter/test/test_gateway.py b/pypy/interpreter/test/test_gateway.py
--- a/pypy/interpreter/test/test_gateway.py
+++ b/pypy/interpreter/test/test_gateway.py
@@ -704,7 +704,7 @@
class TestPassThroughArguments_CALL_METHOD(TestPassThroughArguments):
def setup_class(cls):
- space = gettestobjspace(usemodules=('_stackless',), **{
+ space = gettestobjspace(usemodules=('itertools',), **{
"objspace.opcodes.CALL_METHOD": True
})
cls.space = space
diff --git a/pypy/jit/backend/llgraph/runner.py b/pypy/jit/backend/llgraph/runner.py
--- a/pypy/jit/backend/llgraph/runner.py
+++ b/pypy/jit/backend/llgraph/runner.py
@@ -312,7 +312,7 @@
token = history.getkind(getattr(S, fieldname))
return self.getdescr(ofs, token[0], name=fieldname)
- def calldescrof(self, FUNC, ARGS, RESULT, extrainfo=None):
+ def calldescrof(self, FUNC, ARGS, RESULT, extrainfo):
arg_types = []
for ARG in ARGS:
token = history.getkind(ARG)
@@ -326,7 +326,7 @@
return self.getdescr(0, token[0], extrainfo=extrainfo,
arg_types=''.join(arg_types))
- def calldescrof_dynamic(self, ffi_args, ffi_result, extrainfo=None):
+ def calldescrof_dynamic(self, ffi_args, ffi_result, extrainfo):
from pypy.jit.backend.llsupport.ffisupport import get_ffi_type_kind
from pypy.jit.backend.llsupport.ffisupport import UnsupportedKind
arg_types = []
@@ -522,7 +522,7 @@
return FieldDescr.new(T1, fieldname)
@staticmethod
- def calldescrof(FUNC, ARGS, RESULT, extrainfo=None):
+ def calldescrof(FUNC, ARGS, RESULT, extrainfo):
return StaticMethDescr.new(FUNC, ARGS, RESULT, extrainfo)
@staticmethod
diff --git a/pypy/jit/backend/llsupport/gc.py b/pypy/jit/backend/llsupport/gc.py
--- a/pypy/jit/backend/llsupport/gc.py
+++ b/pypy/jit/backend/llsupport/gc.py
@@ -366,36 +366,92 @@
def add_jit2gc_hooks(self, jit2gc):
#
- def collect_jit_stack_root(callback, gc, addr):
- if addr.signed[0] != GcRootMap_shadowstack.MARKER:
- # common case
- if gc.points_to_valid_gc_object(addr):
- callback(gc, addr)
- return WORD
- else:
- # case of a MARKER followed by an assembler stack frame
- follow_stack_frame_of_assembler(callback, gc, addr)
- return 2 * WORD
+ # ---------------
+ # This is used to enumerate the shadowstack in the presence
+ # of the JIT. It is also used by the stacklet support in
+ # rlib/_stacklet_shadowstack. That's why it is written as
+ # an iterator that can also be used with a custom_trace.
#
- def follow_stack_frame_of_assembler(callback, gc, addr):
- frame_addr = addr.signed[1]
- addr = llmemory.cast_int_to_adr(frame_addr + self.force_index_ofs)
- force_index = addr.signed[0]
- if force_index < 0:
- force_index = ~force_index
- callshape = self._callshapes[force_index]
- n = 0
- while True:
- offset = rffi.cast(lltype.Signed, callshape[n])
- if offset == 0:
- break
- addr = llmemory.cast_int_to_adr(frame_addr + offset)
- if gc.points_to_valid_gc_object(addr):
- callback(gc, addr)
- n += 1
+ class RootIterator:
+ _alloc_flavor_ = "raw"
+
+ def next(iself, gc, next, range_highest):
+ # Return the "next" valid GC object' address. This usually
+ # means just returning "next", until we reach "range_highest",
+ # except that we are skipping NULLs. If "next" contains a
+ # MARKER instead, then we go into JIT-frame-lookup mode.
+ #
+ while True:
+ #
+ # If we are not iterating right now in a JIT frame
+ if iself.frame_addr == 0:
+ #
+ # Look for the next shadowstack address that
+ # contains a valid pointer
+ while next != range_highest:
+ if next.signed[0] == self.MARKER:
+ break
+ if gc.points_to_valid_gc_object(next):
+ return next
+ next += llmemory.sizeof(llmemory.Address)
+ else:
+ return llmemory.NULL # done
+ #
+ # It's a JIT frame. Save away 'next' for later, and
+ # go into JIT-frame-exploring mode.
+ next += llmemory.sizeof(llmemory.Address)
+ frame_addr = next.signed[0]
+ iself.saved_next = next
+ iself.frame_addr = frame_addr
+ addr = llmemory.cast_int_to_adr(frame_addr +
+ self.force_index_ofs)
+ addr = iself.translateptr(iself.context, addr)
+ force_index = addr.signed[0]
+ if force_index < 0:
+ force_index = ~force_index
+ # NB: the next line reads a still-alive _callshapes,
+ # because we ensure that just before we called this
+ # piece of assembler, we put on the (same) stack a
+ # pointer to a loop_token that keeps the force_index
+ # alive.
+ callshape = self._callshapes[force_index]
+ else:
+ # Continuing to explore this JIT frame
+ callshape = iself.callshape
+ #
+ # 'callshape' points to the next INT of the callshape.
+ # If it's zero we are done with the JIT frame.
+ while rffi.cast(lltype.Signed, callshape[0]) != 0:
+ #
+ # Non-zero: it's an offset inside the JIT frame.
+ # Read it and increment 'callshape'.
+ offset = rffi.cast(lltype.Signed, callshape[0])
+ callshape = lltype.direct_ptradd(callshape, 1)
+ addr = llmemory.cast_int_to_adr(iself.frame_addr +
+ offset)
+ addr = iself.translateptr(iself.context, addr)
+ if gc.points_to_valid_gc_object(addr):
+ #
+ # The JIT frame contains a valid GC pointer at
+ # this address (as opposed to NULL). Save
+ # 'callshape' for the next call, and return the
+ # address.
+ iself.callshape = callshape
+ return addr
+ #
+ # Restore 'prev' and loop back to the start.
+ iself.frame_addr = 0
+ next = iself.saved_next
+ next += llmemory.sizeof(llmemory.Address)
+
+ # ---------------
#
+ root_iterator = RootIterator()
+ root_iterator.frame_addr = 0
+ root_iterator.context = llmemory.NULL
+ root_iterator.translateptr = lambda context, addr: addr
jit2gc.update({
- 'rootstackhook': collect_jit_stack_root,
+ 'root_iterator': root_iterator,
})
def initialize(self):
@@ -550,7 +606,7 @@
has_finalizer = bool(tid & (1<<llgroup.HALFSHIFT))
check_typeid(type_id)
res = llop1.do_malloc_fixedsize_clear(llmemory.GCREF,
- type_id, size, True,
+ type_id, size,
has_finalizer, False)
# In case the operation above failed, we are returning NULL
# from this function to assembler. There is also an RPython
@@ -575,7 +631,7 @@
return llop1.do_malloc_varsize_clear(
llmemory.GCREF,
type_id, num_elem, self.array_basesize, itemsize,
- self.array_length_ofs, True)
+ self.array_length_ofs)
self.malloc_array = malloc_array
self.GC_MALLOC_ARRAY = lltype.Ptr(lltype.FuncType(
[lltype.Signed] * 3, llmemory.GCREF))
@@ -591,12 +647,12 @@
return llop1.do_malloc_varsize_clear(
llmemory.GCREF,
str_type_id, length, str_basesize, str_itemsize,
- str_ofs_length, True)
+ str_ofs_length)
def malloc_unicode(length):
return llop1.do_malloc_varsize_clear(
llmemory.GCREF,
unicode_type_id, length, unicode_basesize,unicode_itemsize,
- unicode_ofs_length, True)
+ unicode_ofs_length)
self.malloc_str = malloc_str
self.malloc_unicode = malloc_unicode
self.GC_MALLOC_STR_UNICODE = lltype.Ptr(lltype.FuncType(
@@ -622,7 +678,7 @@
# also use it to allocate varsized objects. The tid
# and possibly the length are both set afterward.
gcref = llop1.do_malloc_fixedsize_clear(llmemory.GCREF,
- 0, size, True, False, False)
+ 0, size, False, False)
return rffi.cast(lltype.Signed, gcref)
self.malloc_slowpath = malloc_slowpath
self.MALLOC_SLOWPATH = lltype.FuncType([lltype.Signed], lltype.Signed)
diff --git a/pypy/jit/backend/llsupport/llmodel.py b/pypy/jit/backend/llsupport/llmodel.py
--- a/pypy/jit/backend/llsupport/llmodel.py
+++ b/pypy/jit/backend/llsupport/llmodel.py
@@ -254,10 +254,10 @@
return ofs, size, sign
unpack_arraydescr_size._always_inline_ = True
- def calldescrof(self, FUNC, ARGS, RESULT, extrainfo=None):
+ def calldescrof(self, FUNC, ARGS, RESULT, extrainfo):
return get_call_descr(self.gc_ll_descr, ARGS, RESULT, extrainfo)
- def calldescrof_dynamic(self, ffi_args, ffi_result, extrainfo=None):
+ def calldescrof_dynamic(self, ffi_args, ffi_result, extrainfo):
from pypy.jit.backend.llsupport import ffisupport
return ffisupport.get_call_descr_dynamic(self, ffi_args, ffi_result,
extrainfo)
diff --git a/pypy/jit/backend/llsupport/test/test_gc.py b/pypy/jit/backend/llsupport/test/test_gc.py
--- a/pypy/jit/backend/llsupport/test/test_gc.py
+++ b/pypy/jit/backend/llsupport/test/test_gc.py
@@ -246,9 +246,8 @@
def __init__(self):
self.record = []
- def do_malloc_fixedsize_clear(self, RESTYPE, type_id, size, can_collect,
+ def do_malloc_fixedsize_clear(self, RESTYPE, type_id, size,
has_finalizer, contains_weakptr):
- assert can_collect
assert not contains_weakptr
p = llmemory.raw_malloc(size)
p = llmemory.cast_adr_to_ptr(p, RESTYPE)
@@ -258,8 +257,7 @@
return p
def do_malloc_varsize_clear(self, RESTYPE, type_id, length, size,
- itemsize, offset_to_length, can_collect):
- assert can_collect
+ itemsize, offset_to_length):
p = llmemory.raw_malloc(size + itemsize * length)
(p + offset_to_length).signed[0] = length
p = llmemory.cast_adr_to_ptr(p, RESTYPE)
diff --git a/pypy/jit/backend/test/calling_convention_test.py b/pypy/jit/backend/test/calling_convention_test.py
--- a/pypy/jit/backend/test/calling_convention_test.py
+++ b/pypy/jit/backend/test/calling_convention_test.py
@@ -8,6 +8,7 @@
ConstObj, BoxFloat, ConstFloat)
from pypy.jit.metainterp.resoperation import ResOperation, rop
from pypy.jit.metainterp.typesystem import deref
+from pypy.jit.codewriter.effectinfo import EffectInfo
from pypy.jit.tool.oparser import parse
from pypy.rpython.lltypesystem import lltype, llmemory, rstr, rffi, rclass
from pypy.rpython.ootypesystem import ootype
@@ -96,7 +97,8 @@
FUNC = self.FuncType(funcargs, F)
FPTR = self.Ptr(FUNC)
func_ptr = llhelper(FPTR, func)
- calldescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT)
+ calldescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
+ EffectInfo.MOST_GENERAL)
funcbox = self.get_funcbox(cpu, func_ptr)
ops = '[%s]\n' % arguments
@@ -148,7 +150,8 @@
FUNC = self.FuncType(args, F)
FPTR = self.Ptr(FUNC)
func_ptr = llhelper(FPTR, func)
- calldescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT)
+ calldescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
+ EffectInfo.MOST_GENERAL)
funcbox = self.get_funcbox(cpu, func_ptr)
res = self.execute_operation(rop.CALL,
@@ -190,7 +193,8 @@
FUNC = self.FuncType(args, F)
FPTR = self.Ptr(FUNC)
func_ptr = llhelper(FPTR, func)
- calldescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT)
+ calldescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
+ EffectInfo.MOST_GENERAL)
funcbox = self.get_funcbox(cpu, func_ptr)
res = self.execute_operation(rop.CALL,
@@ -268,7 +272,8 @@
else:
ARGS.append(lltype.Signed)
FakeJitDriverSD.portal_calldescr = self.cpu.calldescrof(
- lltype.Ptr(lltype.FuncType(ARGS, RES)), ARGS, RES)
+ lltype.Ptr(lltype.FuncType(ARGS, RES)), ARGS, RES,
+ EffectInfo.MOST_GENERAL)
ops = '''
[%s]
f99 = call_assembler(%s, descr=called_looptoken)
@@ -337,7 +342,8 @@
FUNC = self.FuncType(args, F)
FPTR = self.Ptr(FUNC)
func_ptr = llhelper(FPTR, func)
- calldescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT)
+ calldescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
+ EffectInfo.MOST_GENERAL)
funcbox = self.get_funcbox(cpu, func_ptr)
res = self.execute_operation(rop.CALL,
diff --git a/pypy/jit/backend/test/runner_test.py b/pypy/jit/backend/test/runner_test.py
--- a/pypy/jit/backend/test/runner_test.py
+++ b/pypy/jit/backend/test/runner_test.py
@@ -9,6 +9,7 @@
ConstObj, BoxFloat, ConstFloat)
from pypy.jit.metainterp.resoperation import ResOperation, rop
from pypy.jit.metainterp.typesystem import deref
+from pypy.jit.codewriter.effectinfo import EffectInfo
from pypy.jit.tool.oparser import parse
from pypy.rpython.lltypesystem import lltype, llmemory, rstr, rffi, rclass
from pypy.rpython.ootypesystem import ootype
@@ -445,7 +446,8 @@
return chr(ord(c) + 1)
FPTR = self.Ptr(self.FuncType([lltype.Char], lltype.Char))
func_ptr = llhelper(FPTR, func)
- calldescr = cpu.calldescrof(deref(FPTR), (lltype.Char,), lltype.Char)
+ calldescr = cpu.calldescrof(deref(FPTR), (lltype.Char,), lltype.Char,
+ EffectInfo.MOST_GENERAL)
x = cpu.bh_call_i(self.get_funcbox(cpu, func_ptr).value,
calldescr, [ord('A')], None, None)
assert x == ord('B')
@@ -458,7 +460,8 @@
lltype.Float))
func_ptr = llhelper(FPTR, func)
FTP = deref(FPTR)
- calldescr = cpu.calldescrof(FTP, FTP.ARGS, FTP.RESULT)
+ calldescr = cpu.calldescrof(FTP, FTP.ARGS, FTP.RESULT,
+ EffectInfo.MOST_GENERAL)
x = cpu.bh_call_f(self.get_funcbox(cpu, func_ptr).value,
calldescr,
[42], None, [longlong.getfloatstorage(3.5)])
@@ -486,13 +489,15 @@
FUNC = deref(FPTR)
funcbox = self.get_funcbox(cpu, func_ptr)
# first, try it with the "normal" calldescr
- calldescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT)
+ calldescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
+ EffectInfo.MOST_GENERAL)
res = self.execute_operation(rop.CALL,
[funcbox, BoxInt(num), BoxInt(num)],
'int', descr=calldescr)
assert res.value == 2 * num
# then, try it with the dynamic calldescr
- dyn_calldescr = cpu.calldescrof_dynamic([ffi_type, ffi_type], ffi_type)
+ dyn_calldescr = cpu.calldescrof_dynamic([ffi_type, ffi_type], ffi_type,
+ EffectInfo.MOST_GENERAL)
res = self.execute_operation(rop.CALL,
[funcbox, BoxInt(num), BoxInt(num)],
'int', descr=dyn_calldescr)
@@ -507,7 +512,8 @@
FUNC = self.FuncType([F] * 7 + [I] * 2 + [F] * 3, F)
FPTR = self.Ptr(FUNC)
func_ptr = llhelper(FPTR, func)
- calldescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT)
+ calldescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
+ EffectInfo.MOST_GENERAL)
funcbox = self.get_funcbox(cpu, func_ptr)
args = ([boxfloat(.1) for i in range(7)] +
[BoxInt(1), BoxInt(2), boxfloat(.2), boxfloat(.3),
@@ -529,7 +535,8 @@
FUNC = self.FuncType([lltype.Signed]*16, lltype.Signed)
FPTR = self.Ptr(FUNC)
- calldescr = self.cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT)
+ calldescr = self.cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
+ EffectInfo.MOST_GENERAL)
func_ptr = llhelper(FPTR, func)
args = range(16)
funcbox = self.get_funcbox(self.cpu, func_ptr)
@@ -552,7 +559,8 @@
FPTR = self.Ptr(self.FuncType([TP] * nb_args, TP))
func_ptr = llhelper(FPTR, func_ints)
FUNC = deref(FPTR)
- calldescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT)
+ calldescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
+ EffectInfo.MOST_GENERAL)
funcbox = self.get_funcbox(cpu, func_ptr)
args = [280-24*i for i in range(nb_args)]
res = self.execute_operation(rop.CALL,
@@ -566,7 +574,8 @@
FUNC = self.FuncType([lltype.Float, lltype.Float], lltype.Float)
FPTR = self.Ptr(FUNC)
- calldescr = self.cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT)
+ calldescr = self.cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
+ EffectInfo.MOST_GENERAL)
func_ptr = llhelper(FPTR, func)
funcbox = self.get_funcbox(self.cpu, func_ptr)
res = self.execute_operation(rop.CALL, [funcbox, constfloat(1.5),
@@ -1589,7 +1598,8 @@
'''
FPTR = lltype.Ptr(lltype.FuncType([lltype.Signed], lltype.Void))
fptr = llhelper(FPTR, func)
- calldescr = self.cpu.calldescrof(FPTR.TO, FPTR.TO.ARGS, FPTR.TO.RESULT)
+ calldescr = self.cpu.calldescrof(FPTR.TO, FPTR.TO.ARGS, FPTR.TO.RESULT,
+ EffectInfo.MOST_GENERAL)
xtp = lltype.malloc(rclass.OBJECT_VTABLE, immortal=True)
xtp.subclassrange_min = 1
@@ -1807,7 +1817,8 @@
FUNC = self.FuncType([lltype.Signed, lltype.Signed], lltype.Void)
func_ptr = llhelper(lltype.Ptr(FUNC), maybe_force)
funcbox = self.get_funcbox(self.cpu, func_ptr).constbox()
- calldescr = self.cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT)
+ calldescr = self.cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
+ EffectInfo.MOST_GENERAL)
cpu = self.cpu
i0 = BoxInt()
i1 = BoxInt()
@@ -1850,7 +1861,8 @@
FUNC = self.FuncType([lltype.Signed, lltype.Signed], lltype.Signed)
func_ptr = llhelper(lltype.Ptr(FUNC), maybe_force)
funcbox = self.get_funcbox(self.cpu, func_ptr).constbox()
- calldescr = self.cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT)
+ calldescr = self.cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
+ EffectInfo.MOST_GENERAL)
cpu = self.cpu
i0 = BoxInt()
i1 = BoxInt()
@@ -1895,7 +1907,8 @@
FUNC = self.FuncType([lltype.Signed, lltype.Signed], lltype.Float)
func_ptr = llhelper(lltype.Ptr(FUNC), maybe_force)
funcbox = self.get_funcbox(self.cpu, func_ptr).constbox()
- calldescr = self.cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT)
+ calldescr = self.cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
+ EffectInfo.MOST_GENERAL)
cpu = self.cpu
i0 = BoxInt()
i1 = BoxInt()
@@ -1941,7 +1954,8 @@
cpu = self.cpu
func_adr = llmemory.cast_ptr_to_adr(c_tolower.funcsym)
funcbox = ConstInt(heaptracker.adr2int(func_adr))
- calldescr = cpu.calldescrof_dynamic([types.uchar], types.sint)
+ calldescr = cpu.calldescrof_dynamic([types.uchar], types.sint,
+ EffectInfo.MOST_GENERAL)
i1 = BoxInt()
i2 = BoxInt()
tok = BoxInt()
@@ -1997,7 +2011,8 @@
funcbox = ConstInt(heaptracker.adr2int(func_adr))
calldescr = cpu.calldescrof_dynamic([types.pointer, types_size_t,
types_size_t, types.pointer],
- types.void)
+ types.void,
+ EffectInfo.MOST_GENERAL)
i0 = BoxInt()
i1 = BoxInt()
i2 = BoxInt()
@@ -2292,7 +2307,8 @@
ARGS = [lltype.Signed] * 10
RES = lltype.Signed
FakeJitDriverSD.portal_calldescr = self.cpu.calldescrof(
- lltype.Ptr(lltype.FuncType(ARGS, RES)), ARGS, RES)
+ lltype.Ptr(lltype.FuncType(ARGS, RES)), ARGS, RES,
+ EffectInfo.MOST_GENERAL)
for i in range(10):
self.cpu.set_future_value_int(i, i+1)
res = self.cpu.execute_token(looptoken)
@@ -2332,7 +2348,8 @@
ARGS = [lltype.Float, lltype.Float]
RES = lltype.Float
FakeJitDriverSD.portal_calldescr = self.cpu.calldescrof(
- lltype.Ptr(lltype.FuncType(ARGS, RES)), ARGS, RES)
+ lltype.Ptr(lltype.FuncType(ARGS, RES)), ARGS, RES,
+ EffectInfo.MOST_GENERAL)
ops = '''
[f0, f1]
@@ -2422,7 +2439,8 @@
ARGS = [lltype.Float, lltype.Float]
RES = lltype.Float
FakeJitDriverSD.portal_calldescr = self.cpu.calldescrof(
- lltype.Ptr(lltype.FuncType(ARGS, RES)), ARGS, RES)
+ lltype.Ptr(lltype.FuncType(ARGS, RES)), ARGS, RES,
+ EffectInfo.MOST_GENERAL)
ops = '''
[f0, f1]
@@ -2634,7 +2652,8 @@
#
FUNC = self.FuncType([lltype.Signed], RESTYPE)
FPTR = self.Ptr(FUNC)
- calldescr = self.cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT)
+ calldescr = self.cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
+ EffectInfo.MOST_GENERAL)
x = self.cpu.bh_call_i(self.get_funcbox(self.cpu, f).value,
calldescr, [value], None, None)
assert x == expected, (
@@ -2667,7 +2686,8 @@
#
FUNC = self.FuncType([lltype.Signed], RESTYPE)
FPTR = self.Ptr(FUNC)
- calldescr = self.cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT)
+ calldescr = self.cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
+ EffectInfo.MOST_GENERAL)
funcbox = self.get_funcbox(self.cpu, f)
res = self.execute_operation(rop.CALL, [funcbox, BoxInt(value)],
'int', descr=calldescr)
@@ -2701,7 +2721,8 @@
#
FUNC = self.FuncType([lltype.SignedLongLong], lltype.SignedLongLong)
FPTR = self.Ptr(FUNC)
- calldescr = self.cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT)
+ calldescr = self.cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
+ EffectInfo.MOST_GENERAL)
x = self.cpu.bh_call_f(self.get_funcbox(self.cpu, f).value,
calldescr, None, None, [value])
assert x == expected
@@ -2728,7 +2749,8 @@
#
FUNC = self.FuncType([lltype.SignedLongLong], lltype.SignedLongLong)
FPTR = self.Ptr(FUNC)
- calldescr = self.cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT)
+ calldescr = self.cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
+ EffectInfo.MOST_GENERAL)
funcbox = self.get_funcbox(self.cpu, f)
res = self.execute_operation(rop.CALL, [funcbox, BoxFloat(value)],
'float', descr=calldescr)
@@ -2756,7 +2778,8 @@
#
FUNC = self.FuncType([lltype.SingleFloat], lltype.SingleFloat)
FPTR = self.Ptr(FUNC)
- calldescr = self.cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT)
+ calldescr = self.cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
+ EffectInfo.MOST_GENERAL)
ivalue = longlong.singlefloat2int(value)
iexpected = longlong.singlefloat2int(expected)
x = self.cpu.bh_call_i(self.get_funcbox(self.cpu, f).value,
@@ -2785,7 +2808,8 @@
#
FUNC = self.FuncType([lltype.SingleFloat], lltype.SingleFloat)
FPTR = self.Ptr(FUNC)
- calldescr = self.cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT)
+ calldescr = self.cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
+ EffectInfo.MOST_GENERAL)
funcbox = self.get_funcbox(self.cpu, f)
ivalue = longlong.singlefloat2int(value)
iexpected = longlong.singlefloat2int(expected)
diff --git a/pypy/jit/backend/test/test_ll_random.py b/pypy/jit/backend/test/test_ll_random.py
--- a/pypy/jit/backend/test/test_ll_random.py
+++ b/pypy/jit/backend/test/test_ll_random.py
@@ -6,6 +6,7 @@
from pypy.jit.metainterp.history import BoxPtr, BoxInt
from pypy.jit.metainterp.history import BasicFailDescr
from pypy.jit.codewriter import heaptracker
+from pypy.jit.codewriter.effectinfo import EffectInfo
from pypy.rpython.annlowlevel import llhelper
from pypy.rlib.rarithmetic import intmask
from pypy.rpython.llinterp import LLException
@@ -468,6 +469,10 @@
exec code in d
return subset, d['f'], vtableptr
+ def getcalldescr(self, builder, TP):
+ ef = EffectInfo.MOST_GENERAL
+ return builder.cpu.calldescrof(TP, TP.ARGS, TP.RESULT, ef)
+
# 1. non raising call and guard_no_exception
class CallOperation(BaseCallOperation):
def produce_into(self, builder, r):
@@ -481,7 +486,7 @@
ptr = llhelper(lltype.Ptr(TP), f)
c_addr = ConstAddr(llmemory.cast_ptr_to_adr(ptr), builder.cpu)
args = [c_addr] + subset
- descr = builder.cpu.calldescrof(TP, TP.ARGS, TP.RESULT)
+ descr = self.getcalldescr(builder, TP)
self.put(builder, args, descr)
op = ResOperation(rop.GUARD_NO_EXCEPTION, [], None,
descr=BasicFailDescr())
@@ -501,7 +506,7 @@
ptr = llhelper(lltype.Ptr(TP), f)
c_addr = ConstAddr(llmemory.cast_ptr_to_adr(ptr), builder.cpu)
args = [c_addr] + subset
- descr = builder.cpu.calldescrof(TP, TP.ARGS, TP.RESULT)
+ descr = self.getcalldescr(builder, TP)
self.put(builder, args, descr)
_, vtableptr = builder.get_random_structure_type_and_vtable(r)
exc_box = ConstAddr(llmemory.cast_ptr_to_adr(vtableptr), builder.cpu)
@@ -523,7 +528,7 @@
ptr = llhelper(lltype.Ptr(TP), f)
c_addr = ConstAddr(llmemory.cast_ptr_to_adr(ptr), builder.cpu)
args = [c_addr] + subset
- descr = builder.cpu.calldescrof(TP, TP.ARGS, TP.RESULT)
+ descr = self.getcalldescr(builder, TP)
self.put(builder, args, descr)
exc_box = ConstAddr(llmemory.cast_ptr_to_adr(exc), builder.cpu)
op = ResOperation(rop.GUARD_EXCEPTION, [exc_box], BoxPtr(),
@@ -540,7 +545,7 @@
ptr = llhelper(lltype.Ptr(TP), f)
c_addr = ConstAddr(llmemory.cast_ptr_to_adr(ptr), builder.cpu)
args = [c_addr] + subset
- descr = builder.cpu.calldescrof(TP, TP.ARGS, TP.RESULT)
+ descr = self.getcalldescr(builder, TP)
self.put(builder, args, descr)
op = ResOperation(rop.GUARD_NO_EXCEPTION, [], BoxPtr(),
descr=BasicFailDescr())
@@ -559,7 +564,7 @@
ptr = llhelper(lltype.Ptr(TP), f)
c_addr = ConstAddr(llmemory.cast_ptr_to_adr(ptr), builder.cpu)
args = [c_addr] + subset
- descr = builder.cpu.calldescrof(TP, TP.ARGS, TP.RESULT)
+ descr = self.getcalldescr(builder, TP)
self.put(builder, args, descr)
while True:
_, vtableptr = builder.get_random_structure_type_and_vtable(r)
diff --git a/pypy/jit/backend/x86/regalloc.py b/pypy/jit/backend/x86/regalloc.py
--- a/pypy/jit/backend/x86/regalloc.py
+++ b/pypy/jit/backend/x86/regalloc.py
@@ -843,8 +843,8 @@
def consider_call(self, op):
effectinfo = op.getdescr().get_extra_info()
- if effectinfo is not None:
- oopspecindex = effectinfo.oopspecindex
+ oopspecindex = effectinfo.oopspecindex
+ if oopspecindex != EffectInfo.OS_NONE:
if IS_X86_32:
# support for some of the llong operations,
# which only exist on x86-32
diff --git a/pypy/jit/backend/x86/test/test_gc_integration.py b/pypy/jit/backend/x86/test/test_gc_integration.py
--- a/pypy/jit/backend/x86/test/test_gc_integration.py
+++ b/pypy/jit/backend/x86/test/test_gc_integration.py
@@ -7,6 +7,7 @@
BoxPtr, ConstPtr, TreeLoop
from pypy.jit.metainterp.resoperation import rop, ResOperation
from pypy.jit.codewriter import heaptracker
+from pypy.jit.codewriter.effectinfo import EffectInfo
from pypy.jit.backend.llsupport.descr import GcCache
from pypy.jit.backend.llsupport.gc import GcLLDescription
from pypy.jit.backend.detect_cpu import getcpuclass
@@ -76,7 +77,8 @@
for box in boxes:
regalloc.rm.try_allocate_reg(box)
TP = lltype.FuncType([], lltype.Signed)
- calldescr = cpu.calldescrof(TP, TP.ARGS, TP.RESULT)
+ calldescr = cpu.calldescrof(TP, TP.ARGS, TP.RESULT,
+ EffectInfo.MOST_GENERAL)
regalloc.rm._check_invariants()
box = boxes[0]
regalloc.position = 0
diff --git a/pypy/jit/backend/x86/test/test_regalloc.py b/pypy/jit/backend/x86/test/test_regalloc.py
--- a/pypy/jit/backend/x86/test/test_regalloc.py
+++ b/pypy/jit/backend/x86/test/test_regalloc.py
@@ -16,6 +16,7 @@
from pypy.rpython.annlowlevel import llhelper
from pypy.rpython.lltypesystem import rclass, rstr
from pypy.jit.codewriter import longlong
+from pypy.jit.codewriter.effectinfo import EffectInfo
from pypy.jit.backend.x86.rx86 import *
def test_is_comparison_or_ovf_op():
@@ -92,7 +93,8 @@
zd_addr = cpu.cast_int_to_adr(zero_division_tp)
zero_division_error = llmemory.cast_adr_to_ptr(zd_addr,
lltype.Ptr(rclass.OBJECT_VTABLE))
- raising_calldescr = cpu.calldescrof(FPTR.TO, FPTR.TO.ARGS, FPTR.TO.RESULT)
+ raising_calldescr = cpu.calldescrof(FPTR.TO, FPTR.TO.ARGS, FPTR.TO.RESULT,
+ EffectInfo.MOST_GENERAL)
fdescr1 = BasicFailDescr(1)
fdescr2 = BasicFailDescr(2)
@@ -115,9 +117,12 @@
f2ptr = llhelper(F2PTR, f2)
f10ptr = llhelper(F10PTR, f10)
- f1_calldescr = cpu.calldescrof(F1PTR.TO, F1PTR.TO.ARGS, F1PTR.TO.RESULT)
- f2_calldescr = cpu.calldescrof(F2PTR.TO, F2PTR.TO.ARGS, F2PTR.TO.RESULT)
- f10_calldescr = cpu.calldescrof(F10PTR.TO, F10PTR.TO.ARGS, F10PTR.TO.RESULT)
+ f1_calldescr = cpu.calldescrof(F1PTR.TO, F1PTR.TO.ARGS, F1PTR.TO.RESULT,
+ EffectInfo.MOST_GENERAL)
+ f2_calldescr = cpu.calldescrof(F2PTR.TO, F2PTR.TO.ARGS, F2PTR.TO.RESULT,
+ EffectInfo.MOST_GENERAL)
+ f10_calldescr= cpu.calldescrof(F10PTR.TO, F10PTR.TO.ARGS, F10PTR.TO.RESULT,
+ EffectInfo.MOST_GENERAL)
namespace = locals().copy()
type_system = 'lltype'
diff --git a/pypy/jit/codewriter/call.py b/pypy/jit/codewriter/call.py
--- a/pypy/jit/codewriter/call.py
+++ b/pypy/jit/codewriter/call.py
@@ -6,7 +6,7 @@
from pypy.jit.codewriter import support
from pypy.jit.codewriter.jitcode import JitCode
from pypy.jit.codewriter.effectinfo import (VirtualizableAnalyzer,
- QuasiImmutAnalyzer, CanReleaseGILAnalyzer, effectinfo_from_writeanalyze,
+ QuasiImmutAnalyzer, RandomEffectsAnalyzer, effectinfo_from_writeanalyze,
EffectInfo, CallInfoCollection)
from pypy.translator.simplify import get_funcobj, get_functype
from pypy.rpython.lltypesystem import lltype, llmemory
@@ -31,7 +31,7 @@
self.readwrite_analyzer = ReadWriteAnalyzer(translator)
self.virtualizable_analyzer = VirtualizableAnalyzer(translator)
self.quasiimmut_analyzer = QuasiImmutAnalyzer(translator)
- self.canreleasegil_analyzer = CanReleaseGILAnalyzer(translator)
+ self.randomeffects_analyzer = RandomEffectsAnalyzer(translator)
#
for index, jd in enumerate(jitdrivers_sd):
jd.index = index
@@ -187,7 +187,7 @@
fnaddr = llmemory.cast_ptr_to_adr(fnptr)
NON_VOID_ARGS = [ARG for ARG in FUNC.ARGS if ARG is not lltype.Void]
calldescr = self.cpu.calldescrof(FUNC, tuple(NON_VOID_ARGS),
- FUNC.RESULT)
+ FUNC.RESULT, EffectInfo.MOST_GENERAL)
return (fnaddr, calldescr)
def getcalldescr(self, op, oopspecindex=EffectInfo.OS_NONE,
@@ -219,9 +219,11 @@
assert not NON_VOID_ARGS, ("arguments not supported for "
"loop-invariant function!")
# build the extraeffect
- can_release_gil = self.canreleasegil_analyzer.analyze(op)
- # can_release_gil implies can_invalidate
- can_invalidate = can_release_gil or self.quasiimmut_analyzer.analyze(op)
+ random_effects = self.randomeffects_analyzer.analyze(op)
+ if random_effects:
+ extraeffect = EffectInfo.EF_RANDOM_EFFECTS
+ # random_effects implies can_invalidate
+ can_invalidate = random_effects or self.quasiimmut_analyzer.analyze(op)
if extraeffect is None:
if self.virtualizable_analyzer.analyze(op):
extraeffect = EffectInfo.EF_FORCES_VIRTUAL_OR_VIRTUALIZABLE
@@ -239,12 +241,10 @@
#
effectinfo = effectinfo_from_writeanalyze(
self.readwrite_analyzer.analyze(op), self.cpu, extraeffect,
- oopspecindex, can_invalidate, can_release_gil)
+ oopspecindex, can_invalidate)
#
- if oopspecindex != EffectInfo.OS_NONE:
- assert effectinfo is not None
+ assert effectinfo is not None
if elidable or loopinvariant:
- assert effectinfo is not None
assert extraeffect != EffectInfo.EF_FORCES_VIRTUAL_OR_VIRTUALIZABLE
# XXX this should also say assert not can_invalidate, but
# it can't because our analyzer is not good enough for now
@@ -264,8 +264,7 @@
def calldescr_canraise(self, calldescr):
effectinfo = calldescr.get_extra_info()
- return (effectinfo is None or
- effectinfo.extraeffect > EffectInfo.EF_CANNOT_RAISE)
+ return effectinfo.check_can_raise()
def jitdriver_sd_from_portal_graph(self, graph):
for jd in self.jitdrivers_sd:
diff --git a/pypy/jit/codewriter/effectinfo.py b/pypy/jit/codewriter/effectinfo.py
--- a/pypy/jit/codewriter/effectinfo.py
+++ b/pypy/jit/codewriter/effectinfo.py
@@ -15,6 +15,7 @@
EF_ELIDABLE_CAN_RAISE = 3 #elidable function (but can raise)
EF_CAN_RAISE = 4 #normal function (can raise)
EF_FORCES_VIRTUAL_OR_VIRTUALIZABLE = 5 #can raise and force virtualizables
+ EF_RANDOM_EFFECTS = 6 #can do whatever
# the 'oopspecindex' field is one of the following values:
OS_NONE = 0 # normal case, no oopspec
@@ -80,17 +81,26 @@
write_descrs_fields, write_descrs_arrays,
extraeffect=EF_CAN_RAISE,
oopspecindex=OS_NONE,
- can_invalidate=False, can_release_gil=False):
- key = (frozenset(readonly_descrs_fields),
- frozenset(readonly_descrs_arrays),
- frozenset(write_descrs_fields),
- frozenset(write_descrs_arrays),
+ can_invalidate=False):
+ key = (frozenset_or_none(readonly_descrs_fields),
+ frozenset_or_none(readonly_descrs_arrays),
+ frozenset_or_none(write_descrs_fields),
+ frozenset_or_none(write_descrs_arrays),
extraeffect,
oopspecindex,
- can_invalidate,
- can_release_gil)
+ can_invalidate)
if key in cls._cache:
return cls._cache[key]
+ if extraeffect == EffectInfo.EF_RANDOM_EFFECTS:
+ assert readonly_descrs_fields is None
+ assert readonly_descrs_arrays is None
+ assert write_descrs_fields is None
+ assert write_descrs_arrays is None
+ else:
+ assert readonly_descrs_fields is not None
+ assert readonly_descrs_arrays is not None
+ assert write_descrs_fields is not None
+ assert write_descrs_arrays is not None
result = object.__new__(cls)
result.readonly_descrs_fields = readonly_descrs_fields
result.readonly_descrs_arrays = readonly_descrs_arrays
@@ -104,11 +114,13 @@
result.write_descrs_arrays = write_descrs_arrays
result.extraeffect = extraeffect
result.can_invalidate = can_invalidate
- result.can_release_gil = can_release_gil
result.oopspecindex = oopspecindex
cls._cache[key] = result
return result
+ def check_can_raise(self):
+ return self.extraeffect > self.EF_CANNOT_RAISE
+
def check_can_invalidate(self):
return self.can_invalidate
@@ -116,56 +128,71 @@
return self.extraeffect >= self.EF_FORCES_VIRTUAL_OR_VIRTUALIZABLE
def has_random_effects(self):
- return self.oopspecindex == self.OS_LIBFFI_CALL or self.can_release_gil
+ return self.extraeffect >= self.EF_RANDOM_EFFECTS
+
+
+def frozenset_or_none(x):
+ if x is None:
+ return None
+ return frozenset(x)
+
+EffectInfo.MOST_GENERAL = EffectInfo(None, None, None, None,
+ EffectInfo.EF_RANDOM_EFFECTS,
+ can_invalidate=True)
+
def effectinfo_from_writeanalyze(effects, cpu,
extraeffect=EffectInfo.EF_CAN_RAISE,
oopspecindex=EffectInfo.OS_NONE,
- can_invalidate=False,
- can_release_gil=False):
+ can_invalidate=False):
from pypy.translator.backendopt.writeanalyze import top_set
- if effects is top_set:
- return None
- readonly_descrs_fields = []
- readonly_descrs_arrays = []
- write_descrs_fields = []
- write_descrs_arrays = []
+ if effects is top_set or extraeffect == EffectInfo.EF_RANDOM_EFFECTS:
+ readonly_descrs_fields = None
+ readonly_descrs_arrays = None
+ write_descrs_fields = None
+ write_descrs_arrays = None
+ extraeffect = EffectInfo.EF_RANDOM_EFFECTS
+ else:
+ readonly_descrs_fields = []
+ readonly_descrs_arrays = []
+ write_descrs_fields = []
+ write_descrs_arrays = []
- def add_struct(descrs_fields, (_, T, fieldname)):
- T = deref(T)
- if consider_struct(T, fieldname):
- descr = cpu.fielddescrof(T, fieldname)
- descrs_fields.append(descr)
+ def add_struct(descrs_fields, (_, T, fieldname)):
+ T = deref(T)
+ if consider_struct(T, fieldname):
+ descr = cpu.fielddescrof(T, fieldname)
+ descrs_fields.append(descr)
- def add_array(descrs_arrays, (_, T)):
- ARRAY = deref(T)
- if consider_array(ARRAY):
- descr = cpu.arraydescrof(ARRAY)
- descrs_arrays.append(descr)
+ def add_array(descrs_arrays, (_, T)):
+ ARRAY = deref(T)
+ if consider_array(ARRAY):
+ descr = cpu.arraydescrof(ARRAY)
+ descrs_arrays.append(descr)
- for tup in effects:
- if tup[0] == "struct":
- add_struct(write_descrs_fields, tup)
- elif tup[0] == "readstruct":
- tupw = ("struct",) + tup[1:]
- if tupw not in effects:
- add_struct(readonly_descrs_fields, tup)
- elif tup[0] == "array":
- add_array(write_descrs_arrays, tup)
- elif tup[0] == "readarray":
- tupw = ("array",) + tup[1:]
- if tupw not in effects:
- add_array(readonly_descrs_arrays, tup)
- else:
- assert 0
+ for tup in effects:
+ if tup[0] == "struct":
+ add_struct(write_descrs_fields, tup)
+ elif tup[0] == "readstruct":
+ tupw = ("struct",) + tup[1:]
+ if tupw not in effects:
+ add_struct(readonly_descrs_fields, tup)
+ elif tup[0] == "array":
+ add_array(write_descrs_arrays, tup)
+ elif tup[0] == "readarray":
+ tupw = ("array",) + tup[1:]
+ if tupw not in effects:
+ add_array(readonly_descrs_arrays, tup)
+ else:
+ assert 0
+ #
return EffectInfo(readonly_descrs_fields,
readonly_descrs_arrays,
write_descrs_fields,
write_descrs_arrays,
extraeffect,
oopspecindex,
- can_invalidate,
- can_release_gil)
+ can_invalidate)
def consider_struct(TYPE, fieldname):
if fieldType(TYPE, fieldname) is lltype.Void:
@@ -201,12 +228,13 @@
def analyze_simple_operation(self, op, graphinfo):
return op.opname == 'jit_force_quasi_immutable'
-class CanReleaseGILAnalyzer(BoolGraphAnalyzer):
+class RandomEffectsAnalyzer(BoolGraphAnalyzer):
def analyze_direct_call(self, graph, seen=None):
- releases_gil = False
if hasattr(graph, "func") and hasattr(graph.func, "_ptr"):
- releases_gil = graph.func._ptr._obj.releases_gil
- return releases_gil or super(CanReleaseGILAnalyzer, self).analyze_direct_call(graph, seen)
+ if graph.func._ptr._obj.random_effects_on_gcobjs:
+ return True
+ return super(RandomEffectsAnalyzer, self).analyze_direct_call(graph,
+ seen)
def analyze_simple_operation(self, op, graphinfo):
return False
diff --git a/pypy/jit/codewriter/jtransform.py b/pypy/jit/codewriter/jtransform.py
--- a/pypy/jit/codewriter/jtransform.py
+++ b/pypy/jit/codewriter/jtransform.py
@@ -1,4 +1,5 @@
import py
+
from pypy.jit.codewriter import support, heaptracker, longlong
from pypy.jit.codewriter.effectinfo import EffectInfo
from pypy.jit.codewriter.flatten import ListOfKind, IndirectCallTargets
@@ -22,6 +23,11 @@
t = Transformer(cpu, callcontrol, portal_jd)
t.transform(graph)
+def integer_bounds(size, unsigned):
+ if unsigned:
+ return 0, 1 << (8 * size)
+ else:
+ return -(1 << (8 * size - 1)), 1 << (8 * size - 1)
class Transformer(object):
vable_array_vars = None
@@ -780,81 +786,127 @@
raise NotImplementedError("cast_ptr_to_int")
def rewrite_op_force_cast(self, op):
- assert not self._is_gc(op.args[0])
- fromll = longlong.is_longlong(op.args[0].concretetype)
- toll = longlong.is_longlong(op.result.concretetype)
- if fromll and toll:
+ v_arg = op.args[0]
+ v_result = op.result
+ assert not self._is_gc(v_arg)
+
+ if v_arg.concretetype == v_result.concretetype:
return
- if fromll:
- args = op.args
- opname = 'truncate_longlong_to_int'
- RESULT = lltype.Signed
- v = varoftype(RESULT)
- op1 = SpaceOperation(opname, args, v)
- op2 = self.rewrite_operation(op1)
- oplist = self.force_cast_without_longlong(op2.result, op.result)
+
+ float_arg = v_arg.concretetype in [lltype.Float, lltype.SingleFloat]
+ float_res = v_result.concretetype in [lltype.Float, lltype.SingleFloat]
+ if not float_arg and not float_res:
+ # some int -> some int cast
+ return self._int_to_int_cast(v_arg, v_result)
+ elif float_arg and float_res:
+ # some float -> some float cast
+ return self._float_to_float_cast(v_arg, v_result)
+ elif not float_arg and float_res:
+ # some int -> some float
+ ops = []
+ v1 = varoftype(lltype.Signed)
+ oplist = self.rewrite_operation(
+ SpaceOperation('force_cast', [v_arg], v1)
+ )
if oplist:
- return [op2] + oplist
- #
- # force a renaming to put the correct result in place, even though
- # it might be slightly mistyped (e.g. Signed versus Unsigned)
- assert op2.result is v
- op2.result = op.result
- return op2
- elif toll:
- size, unsigned = rffi.size_and_sign(op.args[0].concretetype)
- if unsigned:
+ ops.extend(oplist)
+ else:
+ v1 = v_arg
+ v2 = varoftype(lltype.Float)
+ op = self.rewrite_operation(
+ SpaceOperation('cast_int_to_float', [v1], v2)
+ )
+ ops.append(op)
+ op2 = self.rewrite_operation(
+ SpaceOperation('force_cast', [v2], v_result)
+ )
+ if op2:
+ ops.append(op2)
+ else:
+ op.result = v_result
+ return ops
+ elif float_arg and not float_res:
+ # some float -> some int
+ ops = []
+ v1 = varoftype(lltype.Float)
+ op1 = self.rewrite_operation(
+ SpaceOperation('force_cast', [v_arg], v1)
+ )
+ if op1:
+ ops.append(op1)
+ else:
+ v1 = v_arg
+ v2 = varoftype(lltype.Signed)
+ op = self.rewrite_operation(
+ SpaceOperation('cast_float_to_int', [v1], v2)
+ )
+ ops.append(op)
+ oplist = self.rewrite_operation(
+ SpaceOperation('force_cast', [v2], v_result)
+ )
+ if oplist:
+ ops.extend(oplist)
+ else:
+ op.result = v_result
+ return ops
+ else:
+ assert False
+
+ def _int_to_int_cast(self, v_arg, v_result):
+ longlong_arg = longlong.is_longlong(v_arg.concretetype)
+ longlong_res = longlong.is_longlong(v_result.concretetype)
+ size1, unsigned1 = rffi.size_and_sign(v_arg.concretetype)
+ size2, unsigned2 = rffi.size_and_sign(v_result.concretetype)
+
+ if longlong_arg and longlong_res:
+ return
+ elif longlong_arg:
+ v = varoftype(lltype.Signed)
+ op1 = self.rewrite_operation(
+ SpaceOperation('truncate_longlong_to_int', [v_arg], v)
+ )
+ op2 = SpaceOperation('force_cast', [v], v_result)
+ oplist = self.rewrite_operation(op2)
+ if not oplist:
+ op1.result = v_result
+ oplist = []
+ return [op1] + oplist
+ elif longlong_res:
+ if unsigned1:
INTERMEDIATE = lltype.Unsigned
else:
INTERMEDIATE = lltype.Signed
v = varoftype(INTERMEDIATE)
- oplist = self.force_cast_without_longlong(op.args[0], v)
+ op1 = SpaceOperation('force_cast', [v_arg], v)
+ oplist = self.rewrite_operation(op1)
if not oplist:
- v = op.args[0]
+ v = v_arg
oplist = []
- if unsigned:
+ if unsigned1:
opname = 'cast_uint_to_longlong'
else:
opname = 'cast_int_to_longlong'
- op1 = SpaceOperation(opname, [v], op.result)
- op2 = self.rewrite_operation(op1)
+ op2 = self.rewrite_operation(
+ SpaceOperation(opname, [v], v_result)
+ )
return oplist + [op2]
- else:
- return self.force_cast_without_longlong(op.args[0], op.result)
- def force_cast_without_longlong(self, v_arg, v_result):
- if v_result.concretetype == v_arg.concretetype:
+ # We've now, ostensibly, dealt with the longlongs, everything should be
+ # a Signed or smaller
+ assert size1 <= rffi.sizeof(lltype.Signed)
+ assert size2 <= rffi.sizeof(lltype.Signed)
+
+ # the target type is LONG or ULONG
+ if size2 == rffi.sizeof(lltype.Signed):
return
- if v_arg.concretetype == rffi.FLOAT:
- assert v_result.concretetype == lltype.Float, "cast %s -> %s" % (
- v_arg.concretetype, v_result.concretetype)
- return SpaceOperation('cast_singlefloat_to_float', [v_arg],
- v_result)
- if v_result.concretetype == rffi.FLOAT:
- assert v_arg.concretetype == lltype.Float, "cast %s -> %s" % (
- v_arg.concretetype, v_result.concretetype)
- return SpaceOperation('cast_float_to_singlefloat', [v_arg],
- v_result)
- return self.force_cast_without_singlefloat(v_arg, v_result)
- def force_cast_without_singlefloat(self, v_arg, v_result):
- size2, unsigned2 = rffi.size_and_sign(v_result.concretetype)
- assert size2 <= rffi.sizeof(lltype.Signed)
- if size2 == rffi.sizeof(lltype.Signed):
- return # the target type is LONG or ULONG
- size1, unsigned1 = rffi.size_and_sign(v_arg.concretetype)
- assert size1 <= rffi.sizeof(lltype.Signed)
- #
- def bounds(size, unsigned):
- if unsigned:
- return 0, 1<<(8*size)
- else:
- return -(1<<(8*size-1)), 1<<(8*size-1)
- min1, max1 = bounds(size1, unsigned1)
- min2, max2 = bounds(size2, unsigned2)
+ min1, max1 = integer_bounds(size1, unsigned1)
+ min2, max2 = integer_bounds(size2, unsigned2)
+
+ # the target type includes the source range
if min2 <= min1 <= max1 <= max2:
- return # the target type includes the source range
- #
+ return
+
result = []
if min2:
c_min2 = Constant(min2, lltype.Signed)
@@ -862,15 +914,28 @@
result.append(SpaceOperation('int_sub', [v_arg, c_min2], v2))
else:
v2 = v_arg
- c_mask = Constant(int((1<<(8*size2))-1), lltype.Signed)
- v3 = varoftype(lltype.Signed)
+ c_mask = Constant(int((1 << (8 * size2)) - 1), lltype.Signed)
+ if min2:
+ v3 = varoftype(lltype.Signed)
+ else:
+ v3 = v_result
result.append(SpaceOperation('int_and', [v2, c_mask], v3))
if min2:
result.append(SpaceOperation('int_add', [v3, c_min2], v_result))
- else:
- result[-1].result = v_result
return result
+ def _float_to_float_cast(self, v_arg, v_result):
+ if v_arg.concretetype == lltype.SingleFloat:
+ assert v_result.concretetype == lltype.Float, "cast %s -> %s" % (
+ v_arg.concretetype, v_result.concretetype)
+ return SpaceOperation('cast_singlefloat_to_float', [v_arg],
+ v_result)
+ if v_result.concretetype == lltype.SingleFloat:
+ assert v_arg.concretetype == lltype.Float, "cast %s -> %s" % (
+ v_arg.concretetype, v_result.concretetype)
+ return SpaceOperation('cast_float_to_singlefloat', [v_arg],
+ v_result)
+
def rewrite_op_direct_ptradd(self, op):
# xxx otherwise, not implemented:
assert op.args[0].concretetype == rffi.CCHARP
@@ -1417,7 +1482,7 @@
extraeffect = EffectInfo.EF_CANNOT_RAISE
elif oopspec_name.startswith('libffi_call_'):
oopspecindex = EffectInfo.OS_LIBFFI_CALL
- extraeffect = EffectInfo.EF_FORCES_VIRTUAL_OR_VIRTUALIZABLE
+ extraeffect = EffectInfo.EF_RANDOM_EFFECTS
else:
assert False, 'unsupported oopspec: %s' % oopspec_name
return self._handle_oopspec_call(op, args, oopspecindex, extraeffect)
diff --git a/pypy/jit/codewriter/test/test_call.py b/pypy/jit/codewriter/test/test_call.py
--- a/pypy/jit/codewriter/test/test_call.py
+++ b/pypy/jit/codewriter/test/test_call.py
@@ -191,4 +191,4 @@
[block, _] = list(f_graph.iterblocks())
[op] = block.operations
call_descr = cc.getcalldescr(op)
- assert call_descr.extrainfo.can_release_gil
\ No newline at end of file
+ assert call_descr.extrainfo.has_random_effects()
diff --git a/pypy/jit/codewriter/test/test_codewriter.py b/pypy/jit/codewriter/test/test_codewriter.py
--- a/pypy/jit/codewriter/test/test_codewriter.py
+++ b/pypy/jit/codewriter/test/test_codewriter.py
@@ -5,7 +5,7 @@
from pypy.rpython.lltypesystem import lltype, llmemory, rffi
class FakeCallDescr(AbstractDescr):
- def __init__(self, FUNC, ARGS, RESULT, effectinfo=None):
+ def __init__(self, FUNC, ARGS, RESULT, effectinfo):
self.FUNC = FUNC
self.ARGS = ARGS
self.RESULT = RESULT
diff --git a/pypy/jit/codewriter/test/test_flatten.py b/pypy/jit/codewriter/test/test_flatten.py
--- a/pypy/jit/codewriter/test/test_flatten.py
+++ b/pypy/jit/codewriter/test/test_flatten.py
@@ -50,7 +50,7 @@
def __init__(self, rtyper):
rtyper._builtin_func_for_spec_cache = FakeDict()
self.rtyper = rtyper
- def calldescrof(self, FUNC, ARGS, RESULT):
+ def calldescrof(self, FUNC, ARGS, RESULT, effectinfo):
return FakeDescr()
def fielddescrof(self, STRUCT, name):
return FakeDescr()
@@ -324,7 +324,7 @@
def test_exc_exitswitch(self):
def g(i):
pass
-
+
def f(i):
try:
g(i)
@@ -854,13 +854,51 @@
int_return %i0
""", transform=True)
- def test_force_cast_float(self):
+ def test_force_cast_floats(self):
from pypy.rpython.lltypesystem import rffi
+ # Caststs to lltype.Float
def f(n):
return rffi.cast(lltype.Float, n)
self.encoding_test(f, [12.456], """
float_return %f0
""", transform=True)
+ self.encoding_test(f, [rffi.cast(rffi.SIGNEDCHAR, 42)], """
+ cast_int_to_float %i0 -> %f0
+ float_return %f0
+ """, transform=True)
+
+ # Casts to lltype.SingleFloat
+ def g(n):
+ return rffi.cast(lltype.SingleFloat, n)
+ self.encoding_test(g, [12.456], """
+ cast_float_to_singlefloat %f0 -> %i0
+ int_return %i0
+ """, transform=True)
+ self.encoding_test(g, [rffi.cast(rffi.SIGNEDCHAR, 42)], """
+ cast_int_to_float %i0 -> %f0
+ cast_float_to_singlefloat %f0 -> %i1
+ int_return %i1
+ """, transform=True)
+
+ # Casts from floats
+ def f(n):
+ return rffi.cast(rffi.SIGNEDCHAR, n)
+ self.encoding_test(f, [12.456], """
+ cast_float_to_int %f0 -> %i0
+ int_sub %i0, $-128 -> %i1
+ int_and %i1, $255 -> %i2
+ int_add %i2, $-128 -> %i3
+ int_return %i3
+ """, transform=True)
+ self.encoding_test(f, [rffi.cast(lltype.SingleFloat, 12.456)], """
+ cast_singlefloat_to_float %i0 -> %f0
+ cast_float_to_int %f0 -> %i1
+ int_sub %i1, $-128 -> %i2
+ int_and %i2, $255 -> %i3
+ int_add %i3, $-128 -> %i4
+ int_return %i4
+ """, transform=True)
+
def test_direct_ptradd(self):
from pypy.rpython.lltypesystem import rffi
diff --git a/pypy/jit/metainterp/optimizeopt/fficall.py b/pypy/jit/metainterp/optimizeopt/fficall.py
--- a/pypy/jit/metainterp/optimizeopt/fficall.py
+++ b/pypy/jit/metainterp/optimizeopt/fficall.py
@@ -19,7 +19,8 @@
self.funcval = funcval
self.opargs = []
argtypes, restype = self._get_signature(funcval)
- self.descr = cpu.calldescrof_dynamic(argtypes, restype)
+ self.descr = cpu.calldescrof_dynamic(argtypes, restype,
+ EffectInfo.MOST_GENERAL)
# ^^^ may be None if unsupported
self.prepare_op = prepare_op
self.delayed_ops = []
@@ -195,9 +196,7 @@
def _get_oopspec(self, op):
effectinfo = op.getdescr().get_extra_info()
- if effectinfo is not None:
- return effectinfo.oopspecindex
- return EffectInfo.OS_NONE
+ return effectinfo.oopspecindex
def _get_funcval(self, op):
return self.getvalue(op.getarg(1))
diff --git a/pypy/jit/metainterp/optimizeopt/heap.py b/pypy/jit/metainterp/optimizeopt/heap.py
--- a/pypy/jit/metainterp/optimizeopt/heap.py
+++ b/pypy/jit/metainterp/optimizeopt/heap.py
@@ -235,31 +235,33 @@
opnum == rop.CALL_RELEASE_GIL or
opnum == rop.CALL_ASSEMBLER):
if opnum == rop.CALL_ASSEMBLER:
- effectinfo = None
+ self._seen_guard_not_invalidated = False
else:
effectinfo = op.getdescr().get_extra_info()
- if effectinfo is None or effectinfo.check_can_invalidate():
- self._seen_guard_not_invalidated = False
- if effectinfo is not None and not effectinfo.has_random_effects():
- # XXX we can get the wrong complexity here, if the lists
- # XXX stored on effectinfo are large
- for fielddescr in effectinfo.readonly_descrs_fields:
- self.force_lazy_setfield(fielddescr)
- for arraydescr in effectinfo.readonly_descrs_arrays:
- self.force_lazy_setarrayitem(arraydescr)
- for fielddescr in effectinfo.write_descrs_fields:
- self.force_lazy_setfield(fielddescr, can_cache=False)
- for arraydescr in effectinfo.write_descrs_arrays:
- self.force_lazy_setarrayitem(arraydescr, can_cache=False)
- if effectinfo.check_forces_virtual_or_virtualizable():
- vrefinfo = self.optimizer.metainterp_sd.virtualref_info
- self.force_lazy_setfield(vrefinfo.descr_forced)
- # ^^^ we only need to force this field; the other fields
- # of virtualref_info and virtualizable_info are not gcptrs.
- return
+ if effectinfo.check_can_invalidate():
+ self._seen_guard_not_invalidated = False
+ if not effectinfo.has_random_effects():
+ self.force_from_effectinfo(effectinfo)
+ return
self.force_all_lazy_setfields_and_arrayitems()
self.clean_caches()
+ def force_from_effectinfo(self, effectinfo):
+ # XXX we can get the wrong complexity here, if the lists
+ # XXX stored on effectinfo are large
+ for fielddescr in effectinfo.readonly_descrs_fields:
+ self.force_lazy_setfield(fielddescr)
+ for arraydescr in effectinfo.readonly_descrs_arrays:
+ self.force_lazy_setarrayitem(arraydescr)
+ for fielddescr in effectinfo.write_descrs_fields:
+ self.force_lazy_setfield(fielddescr, can_cache=False)
+ for arraydescr in effectinfo.write_descrs_arrays:
+ self.force_lazy_setarrayitem(arraydescr, can_cache=False)
+ if effectinfo.check_forces_virtual_or_virtualizable():
+ vrefinfo = self.optimizer.metainterp_sd.virtualref_info
+ self.force_lazy_setfield(vrefinfo.descr_forced)
+ # ^^^ we only need to force this field; the other fields
+ # of virtualref_info and virtualizable_info are not gcptrs.
def turned_constant(self, value):
assert value.is_constant()
diff --git a/pypy/jit/metainterp/optimizeopt/rewrite.py b/pypy/jit/metainterp/optimizeopt/rewrite.py
--- a/pypy/jit/metainterp/optimizeopt/rewrite.py
+++ b/pypy/jit/metainterp/optimizeopt/rewrite.py
@@ -433,11 +433,10 @@
# specifically the given oopspec call. For non-oopspec calls,
# oopspecindex is just zero.
effectinfo = op.getdescr().get_extra_info()
- if effectinfo is not None:
- oopspecindex = effectinfo.oopspecindex
- if oopspecindex == EffectInfo.OS_ARRAYCOPY:
- if self._optimize_CALL_ARRAYCOPY(op):
- return
+ oopspecindex = effectinfo.oopspecindex
+ if oopspecindex == EffectInfo.OS_ARRAYCOPY:
+ if self._optimize_CALL_ARRAYCOPY(op):
+ return
self.emit_operation(op)
def _optimize_CALL_ARRAYCOPY(self, op):
diff --git a/pypy/jit/metainterp/optimizeopt/test/test_optimizefficall.py b/pypy/jit/metainterp/optimizeopt/test/test_optimizefficall.py
--- a/pypy/jit/metainterp/optimizeopt/test/test_optimizefficall.py
+++ b/pypy/jit/metainterp/optimizeopt/test/test_optimizefficall.py
@@ -51,14 +51,18 @@
restype=types.sint)
#
def calldescr(cpu, FUNC, oopspecindex, extraeffect=None):
- einfo = EffectInfo([], [], [], [], oopspecindex=oopspecindex,
+ if extraeffect == EffectInfo.EF_RANDOM_EFFECTS:
+ f = None # means "can force all" really
+ else:
+ f = []
+ einfo = EffectInfo(f, f, f, f, oopspecindex=oopspecindex,
extraeffect=extraeffect)
return cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT, einfo)
#
libffi_prepare = calldescr(cpu, FUNC, EffectInfo.OS_LIBFFI_PREPARE)
libffi_push_arg = calldescr(cpu, FUNC, EffectInfo.OS_LIBFFI_PUSH_ARG)
libffi_call = calldescr(cpu, FUNC, EffectInfo.OS_LIBFFI_CALL,
- EffectInfo.EF_FORCES_VIRTUAL_OR_VIRTUALIZABLE)
+ EffectInfo.EF_RANDOM_EFFECTS)
namespace = namespace.__dict__
diff --git a/pypy/jit/metainterp/optimizeopt/test/test_util.py b/pypy/jit/metainterp/optimizeopt/test/test_util.py
--- a/pypy/jit/metainterp/optimizeopt/test/test_util.py
+++ b/pypy/jit/metainterp/optimizeopt/test/test_util.py
@@ -167,7 +167,8 @@
onedescr = cpu.fielddescrof(U, 'one')
FUNC = lltype.FuncType([lltype.Signed], lltype.Signed)
- plaincalldescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT)
+ plaincalldescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
+ EffectInfo.MOST_GENERAL)
nonwritedescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
EffectInfo([], [], [], []))
writeadescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
diff --git a/pypy/jit/metainterp/optimizeopt/vstring.py b/pypy/jit/metainterp/optimizeopt/vstring.py
--- a/pypy/jit/metainterp/optimizeopt/vstring.py
+++ b/pypy/jit/metainterp/optimizeopt/vstring.py
@@ -455,8 +455,8 @@
# specifically the given oopspec call. For non-oopspec calls,
# oopspecindex is just zero.
effectinfo = op.getdescr().get_extra_info()
- if effectinfo is not None:
- oopspecindex = effectinfo.oopspecindex
+ oopspecindex = effectinfo.oopspecindex
+ if oopspecindex != EffectInfo.OS_NONE:
for value, meth in opt_call_oopspec_ops:
if oopspecindex == value: # a match with the OS_STR_xxx
if meth(self, op, mode_string):
diff --git a/pypy/jit/metainterp/pyjitpl.py b/pypy/jit/metainterp/pyjitpl.py
--- a/pypy/jit/metainterp/pyjitpl.py
+++ b/pypy/jit/metainterp/pyjitpl.py
@@ -1257,10 +1257,8 @@
assert i == len(allboxes)
#
effectinfo = descr.get_extra_info()
- if (effectinfo is None or
- effectinfo.extraeffect ==
- effectinfo.EF_FORCES_VIRTUAL_OR_VIRTUALIZABLE or
- assembler_call):
+ if (assembler_call or
+ effectinfo.check_forces_virtual_or_virtualizable()):
# residual calls require attention to keep virtualizables in-sync
self.metainterp.clear_exception()
self.metainterp.vable_and_vrefs_before_residual_call()
@@ -1693,12 +1691,11 @@
return
if opnum == rop.CALL:
effectinfo = descr.get_extra_info()
- if effectinfo is not None:
- ef = effectinfo.extraeffect
- if ef == effectinfo.EF_LOOPINVARIANT or \
- ef == effectinfo.EF_ELIDABLE_CANNOT_RAISE or \
- ef == effectinfo.EF_ELIDABLE_CAN_RAISE:
- return
+ ef = effectinfo.extraeffect
+ if ef == effectinfo.EF_LOOPINVARIANT or \
+ ef == effectinfo.EF_ELIDABLE_CANNOT_RAISE or \
+ ef == effectinfo.EF_ELIDABLE_CAN_RAISE:
+ return
if self.heap_cache:
self.heap_cache.clear()
if self.heap_array_cache:
diff --git a/pypy/jit/metainterp/test/test_compile.py b/pypy/jit/metainterp/test/test_compile.py
--- a/pypy/jit/metainterp/test/test_compile.py
+++ b/pypy/jit/metainterp/test/test_compile.py
@@ -190,7 +190,7 @@
class FakeJitDriverSD:
portal_runner_ptr = llhelper(lltype.Ptr(FUNC), ll_portal_runner)
portal_runner_adr = llmemory.cast_ptr_to_adr(portal_runner_ptr)
- portal_calldescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT)
+ portal_calldescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT, None)
portal_finishtoken = compile.DoneWithThisFrameDescrInt()
num_red_args = 2
result_type = INT
diff --git a/pypy/jit/metainterp/test/test_string.py b/pypy/jit/metainterp/test/test_string.py
--- a/pypy/jit/metainterp/test/test_string.py
+++ b/pypy/jit/metainterp/test/test_string.py
@@ -1,5 +1,6 @@
import py
from pypy.rlib.jit import JitDriver, dont_look_inside, we_are_jitted
+from pypy.rlib.debug import debug_print
from pypy.jit.codewriter.policy import StopAtXPolicy
from pypy.rpython.ootypesystem import ootype
from pypy.jit.metainterp.test.support import LLJitMixin, OOJitMixin
@@ -521,7 +522,8 @@
jitdriver = JitDriver(greens = ['g'], reds = ['m'])
@dont_look_inside
def escape(x):
- print str(x)
+ # a plain "print" would call os.write() and release the gil
+ debug_print(str(x))
def f(g, m):
g = str(g)
while m >= 0:
diff --git a/pypy/jit/metainterp/test/test_virtualstate.py b/pypy/jit/metainterp/test/test_virtualstate.py
--- a/pypy/jit/metainterp/test/test_virtualstate.py
+++ b/pypy/jit/metainterp/test/test_virtualstate.py
@@ -1,3 +1,4 @@
+from __future__ import with_statement
import py
from pypy.jit.metainterp.optimize import InvalidLoop
from pypy.jit.metainterp.optimizeopt.virtualstate import VirtualStateInfo, VStructStateInfo, \
diff --git a/pypy/jit/metainterp/warmspot.py b/pypy/jit/metainterp/warmspot.py
--- a/pypy/jit/metainterp/warmspot.py
+++ b/pypy/jit/metainterp/warmspot.py
@@ -21,6 +21,7 @@
from pypy.jit.metainterp.jitdriver import JitDriverStaticData
from pypy.jit.codewriter import support, codewriter, longlong
from pypy.jit.codewriter.policy import JitPolicy
+from pypy.jit.codewriter.effectinfo import EffectInfo
from pypy.jit.metainterp.optimizeopt import ALL_OPTS_NAMES
# ____________________________________________________________
@@ -746,7 +747,8 @@
jd.portal_calldescr = self.cpu.calldescrof(
jd._PTR_PORTAL_FUNCTYPE.TO,
jd._PTR_PORTAL_FUNCTYPE.TO.ARGS,
- jd._PTR_PORTAL_FUNCTYPE.TO.RESULT)
+ jd._PTR_PORTAL_FUNCTYPE.TO.RESULT,
+ EffectInfo.MOST_GENERAL)
vinfo = jd.virtualizable_info
diff --git a/pypy/module/__builtin__/test/test_classobj.py b/pypy/module/__builtin__/test/test_classobj.py
--- a/pypy/module/__builtin__/test/test_classobj.py
+++ b/pypy/module/__builtin__/test/test_classobj.py
@@ -981,6 +981,86 @@
assert a.x == 2
raises(TypeError, descr.__delete__, a)
+ def test_partial_ordering(self):
+ class A:
+ def __lt__(self, other):
+ return self
+ a1 = A()
+ a2 = A()
+ assert (a1 < a2) is a1
+ assert (a1 > a2) is a2
+
+ def test_eq_order(self):
+ # this gives the ordering of equality-related functions on top of
+ # CPython **for old-style classes**.
+ class A:
+ def __eq__(self, other): return self.__class__.__name__+':A.eq'
+ def __ne__(self, other): return self.__class__.__name__+':A.ne'
+ def __lt__(self, other): return self.__class__.__name__+':A.lt'
+ def __le__(self, other): return self.__class__.__name__+':A.le'
+ def __gt__(self, other): return self.__class__.__name__+':A.gt'
+ def __ge__(self, other): return self.__class__.__name__+':A.ge'
+ class B:
+ def __eq__(self, other): return self.__class__.__name__+':B.eq'
+ def __ne__(self, other): return self.__class__.__name__+':B.ne'
+ def __lt__(self, other): return self.__class__.__name__+':B.lt'
+ def __le__(self, other): return self.__class__.__name__+':B.le'
+ def __gt__(self, other): return self.__class__.__name__+':B.gt'
+ def __ge__(self, other): return self.__class__.__name__+':B.ge'
+ #
+ assert (A() == B()) == 'A:A.eq'
+ assert (A() != B()) == 'A:A.ne'
+ assert (A() < B()) == 'A:A.lt'
+ assert (A() <= B()) == 'A:A.le'
+ assert (A() > B()) == 'A:A.gt'
+ assert (A() >= B()) == 'A:A.ge'
+ #
+ assert (B() == A()) == 'B:B.eq'
+ assert (B() != A()) == 'B:B.ne'
+ assert (B() < A()) == 'B:B.lt'
+ assert (B() <= A()) == 'B:B.le'
+ assert (B() > A()) == 'B:B.gt'
+ assert (B() >= A()) == 'B:B.ge'
+ #
+ class C(A):
+ def __eq__(self, other): return self.__class__.__name__+':C.eq'
+ def __ne__(self, other): return self.__class__.__name__+':C.ne'
+ def __lt__(self, other): return self.__class__.__name__+':C.lt'
+ def __le__(self, other): return self.__class__.__name__+':C.le'
+ def __gt__(self, other): return self.__class__.__name__+':C.gt'
+ def __ge__(self, other): return self.__class__.__name__+':C.ge'
+ #
+ assert (A() == C()) == 'A:A.eq'
+ assert (A() != C()) == 'A:A.ne'
+ assert (A() < C()) == 'A:A.lt'
+ assert (A() <= C()) == 'A:A.le'
+ assert (A() > C()) == 'A:A.gt'
+ assert (A() >= C()) == 'A:A.ge'
+ #
+ assert (C() == A()) == 'C:C.eq'
+ assert (C() != A()) == 'C:C.ne'
+ assert (C() < A()) == 'C:C.lt'
+ assert (C() <= A()) == 'C:C.le'
+ assert (C() > A()) == 'C:C.gt'
+ assert (C() >= A()) == 'C:C.ge'
+ #
+ class D(A):
+ pass
+ #
+ assert (A() == D()) == 'A:A.eq'
+ assert (A() != D()) == 'A:A.ne'
+ assert (A() < D()) == 'A:A.lt'
+ assert (A() <= D()) == 'A:A.le'
+ assert (A() > D()) == 'A:A.gt'
+ assert (A() >= D()) == 'A:A.ge'
+ #
+ assert (D() == A()) == 'D:A.eq'
+ assert (D() != A()) == 'D:A.ne'
+ assert (D() < A()) == 'D:A.lt'
+ assert (D() <= A()) == 'D:A.le'
+ assert (D() > A()) == 'D:A.gt'
+ assert (D() >= A()) == 'D:A.ge'
+
class AppTestOldStyleClassStrDict(object):
def setup_class(cls):
diff --git a/pypy/module/__pypy__/interp_builders.py b/pypy/module/__pypy__/interp_builders.py
--- a/pypy/module/__pypy__/interp_builders.py
+++ b/pypy/module/__pypy__/interp_builders.py
@@ -7,7 +7,7 @@
class W_UnicodeBuilder(Wrappable):
def __init__(self, space, size):
- if size == -1:
+ if size < 0:
self.builder = UnicodeBuilder()
else:
self.builder = UnicodeBuilder(size)
@@ -47,4 +47,4 @@
append_slice = interp2app(W_UnicodeBuilder.descr_append_slice),
build = interp2app(W_UnicodeBuilder.descr_build),
)
-W_UnicodeBuilder.typedef.acceptable_as_base_class = False
\ No newline at end of file
+W_UnicodeBuilder.typedef.acceptable_as_base_class = False
diff --git a/pypy/module/_continuation/__init__.py b/pypy/module/_continuation/__init__.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/_continuation/__init__.py
@@ -0,0 +1,40 @@
+from pypy.interpreter.mixedmodule import MixedModule
+
+
+class Module(MixedModule):
+ """This module exposes 'one-shot continuation containers'.
+
+A 'continulet' object from this module is a container that stores a
+one-shot continuation. It is similar in purpose to the 'f_back'
+attribute of frames, which points to where execution should continue
+after this frame finishes. The difference is that it will be changed
+(often repeatedly) before the frame actually returns.
+
+To make a continulet object, call 'continulet' with a callable and
+optional extra arguments. Later, the first time you switch() to the
+continulet, the callable is invoked wih the same continulet object as
+the extra first argument.
+
+At this point, the one-shot continuation stored in the continulet points
+to the caller of switch(). When switch() is called again, this one-shot
+continuation is exchanged with the current one; it means that the caller
+of switch() is suspended, its continuation stored in the container, and
+the old continuation from the continulet object is resumed.
+
+Continulets are internally implemented using stacklets. Stacklets
+are a bit more primitive (they are really one-shot continuations), but
+that idea only works in C, not in Python, notably because of exceptions.
+
+The most primitive API is actually 'permute()', which just permutes the
+one-shot continuation stored in two (or more) continulets.
+"""
+
+ appleveldefs = {
+ 'error': 'app_continuation.error',
+ 'generator': 'app_continuation.generator',
+ }
+
+ interpleveldefs = {
+ 'continulet': 'interp_continuation.W_Continulet',
+ 'permute': 'interp_continuation.permute',
+ }
diff --git a/pypy/module/_continuation/app_continuation.py b/pypy/module/_continuation/app_continuation.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/_continuation/app_continuation.py
@@ -0,0 +1,35 @@
+
+class error(Exception):
+ "Usage error of the _continuation module."
+
+
+import _continuation
+
+
+class generator(object):
+
+ def __init__(self, callable):
+ self.__func__ = callable
+
+ def __get__(self, obj, type=None):
+ return generator(self.__func__.__get__(obj, type))
+
+ def __call__(self, *args, **kwds):
+ return genlet(self.__func__, *args, **kwds)
+
+
+class genlet(_continuation.continulet):
+
+ def __iter__(self):
+ return self
+
+ def next(self, value=None):
+ res = self.switch(value)
+ if self.is_pending():
+ return res
+ else:
+ if res is not None:
+ raise TypeError("_continuation.generator must return None")
+ raise StopIteration
+
+ send = next
diff --git a/pypy/module/_continuation/interp_continuation.py b/pypy/module/_continuation/interp_continuation.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/_continuation/interp_continuation.py
@@ -0,0 +1,245 @@
+from pypy.rlib.rstacklet import StackletThread
+from pypy.rlib import jit
+from pypy.interpreter.error import OperationError
+from pypy.interpreter.executioncontext import ExecutionContext
+from pypy.interpreter.baseobjspace import Wrappable
+from pypy.interpreter.typedef import TypeDef
+from pypy.interpreter.gateway import interp2app
+
+
+class W_Continulet(Wrappable):
+ sthread = None
+
+ def __init__(self, space):
+ self.space = space
+ # states:
+ # - not init'ed: self.sthread == None
+ # - normal: self.sthread != None, not is_empty_handle(self.h)
+ # - finished: self.sthread != None, is_empty_handle(self.h)
+
+ def check_sthread(self):
+ ec = self.space.getexecutioncontext()
+ if ec.stacklet_thread is not self.sthread:
+ start_state.clear()
+ raise geterror(self.space, "inter-thread support is missing")
+ return ec
+
+ def descr_init(self, w_callable, __args__):
+ if self.sthread is not None:
+ raise geterror(self.space, "continulet already __init__ialized")
+ start_state.origin = self
+ start_state.w_callable = w_callable
+ start_state.args = __args__
+ self.sthread = build_sthread(self.space)
+ try:
+ self.h = self.sthread.new(new_stacklet_callback)
+ if self.sthread.is_empty_handle(self.h): # early return
+ raise MemoryError
+ except MemoryError:
+ self.sthread = None
+ start_state.clear()
+ raise getmemoryerror(self.space)
+
+ def switch(self, w_to):
+ to = self.space.interp_w(W_Continulet, w_to, can_be_None=True)
+ if to is not None:
+ if self is to: # double-switch to myself: no-op
+ return get_result()
+ if to.sthread is None:
+ start_state.clear()
+ raise geterror(self.space, "continulet not initialized yet")
+ if self.sthread is None:
+ start_state.clear()
+ raise geterror(self.space, "continulet not initialized yet")
+ ec = self.check_sthread()
+ saved_topframeref = ec.topframeref
+ #
+ start_state.origin = self
+ if to is None:
+ # simple switch: going to self.h
+ start_state.destination = self
+ else:
+ # double switch: the final destination is to.h
+ start_state.destination = to
+ #
+ h = start_state.destination.h
+ sthread = self.sthread
+ if sthread.is_empty_handle(h):
+ start_state.clear()
+ raise geterror(self.space, "continulet already finished")
+ #
+ try:
+ do_switch(sthread, h)
+ except MemoryError:
+ start_state.clear()
+ raise getmemoryerror(self.space)
+ #
+ ec = sthread.ec
+ ec.topframeref = saved_topframeref
+ return get_result()
+
+ def descr_switch(self, w_value=None, w_to=None):
+ start_state.w_value = w_value
+ return self.switch(w_to)
+
+ def descr_throw(self, w_type, w_val=None, w_tb=None, w_to=None):
+ from pypy.interpreter.pytraceback import check_traceback
+ space = self.space
+ #
+ msg = "throw() third argument must be a traceback object"
+ if space.is_w(w_tb, space.w_None):
+ tb = None
+ else:
+ tb = check_traceback(space, w_tb, msg)
+ #
+ operr = OperationError(w_type, w_val, tb)
+ operr.normalize_exception(space)
+ start_state.w_value = None
+ start_state.propagate_exception = operr
+ return self.switch(w_to)
+
+ def descr_is_pending(self):
+ valid = (self.sthread is not None
+ and not self.sthread.is_empty_handle(self.h))
+ return self.space.newbool(valid)
+
+
+def W_Continulet___new__(space, w_subtype, __args__):
+ r = space.allocate_instance(W_Continulet, w_subtype)
+ r.__init__(space)
+ return space.wrap(r)
+
+
+W_Continulet.typedef = TypeDef(
+ 'continulet',
+ __module__ = '_continuation',
+ __new__ = interp2app(W_Continulet___new__),
+ __init__ = interp2app(W_Continulet.descr_init),
+ switch = interp2app(W_Continulet.descr_switch),
+ throw = interp2app(W_Continulet.descr_throw),
+ is_pending = interp2app(W_Continulet.descr_is_pending),
+ )
+
+
+# ____________________________________________________________
+
+
+class State:
+ def __init__(self, space):
+ self.space = space
+ w_module = space.getbuiltinmodule('_continuation')
+ self.w_error = space.getattr(w_module, space.wrap('error'))
+ self.w_memoryerror = OperationError(space.w_MemoryError, space.w_None)
+
+def geterror(space, message):
+ cs = space.fromcache(State)
+ return OperationError(cs.w_error, space.wrap(message))
+
+def getmemoryerror(space):
+ cs = space.fromcache(State)
+ return cs.w_memoryerror
+
+# ____________________________________________________________
+
+
+class SThread(StackletThread):
+
+ def __init__(self, space, ec):
+ StackletThread.__init__(self, space.config)
+ self.space = space
+ self.ec = ec
+
+ExecutionContext.stacklet_thread = None
+
+# ____________________________________________________________
+
+
+class StartState: # xxx a single global to pass around the function to start
+ def clear(self):
+ self.origin = None
+ self.destination = None
+ self.w_callable = None
+ self.args = None
+ self.w_value = None
+ self.propagate_exception = None
+start_state = StartState()
+start_state.clear()
+
+
+def new_stacklet_callback(h, arg):
+ self = start_state.origin
+ w_callable = start_state.w_callable
+ args = start_state.args
+ start_state.clear()
+ try:
+ do_switch(self.sthread, h)
+ except MemoryError:
+ return h # oups! do an early return in this case
+ #
+ space = self.space
+ try:
+ ec = self.sthread.ec
+ ec.topframeref = jit.vref_None
+
+ if start_state.propagate_exception is not None:
+ raise start_state.propagate_exception # just propagate it further
+ if start_state.w_value is not space.w_None:
+ raise OperationError(space.w_TypeError, space.wrap(
+ "can't send non-None value to a just-started continulet"))
+
+ args = args.prepend(self.space.wrap(self))
+ w_result = space.call_args(w_callable, args)
+ except Exception, e:
+ start_state.propagate_exception = e
+ else:
+ start_state.w_value = w_result
+ start_state.origin = self
+ start_state.destination = self
+ return self.h
+
+
+def do_switch(sthread, h):
+ h = sthread.switch(h)
+ origin = start_state.origin
+ self = start_state.destination
+ start_state.origin = None
+ start_state.destination = None
+ self.h, origin.h = origin.h, h
+
+def get_result():
+ if start_state.propagate_exception:
+ e = start_state.propagate_exception
+ start_state.propagate_exception = None
+ raise e
+ w_value = start_state.w_value
+ start_state.w_value = None
+ return w_value
+
+def build_sthread(space):
+ ec = space.getexecutioncontext()
+ sthread = ec.stacklet_thread
+ if not sthread:
+ sthread = ec.stacklet_thread = SThread(space, ec)
+ return sthread
+
+# ____________________________________________________________
+
+def permute(space, args_w):
+ sthread = build_sthread(space)
+ #
+ contlist = []
+ for w_cont in args_w:
+ cont = space.interp_w(W_Continulet, w_cont)
+ if cont.sthread is not sthread:
+ if cont.sthread is None:
+ raise geterror(space, "got a non-initialized continulet")
+ else:
+ raise geterror(space, "inter-thread support is missing")
+ elif sthread.is_empty_handle(cont.h):
+ raise geterror(space, "got an already-finished continulet")
+ contlist.append(cont)
+ #
+ if len(contlist) > 1:
+ other = contlist[-1].h
+ for cont in contlist:
+ other, cont.h = cont.h, other
diff --git a/pypy/module/_continuation/test/__init__.py b/pypy/module/_continuation/test/__init__.py
new file mode 100644
diff --git a/pypy/module/_continuation/test/support.py b/pypy/module/_continuation/test/support.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/_continuation/test/support.py
@@ -0,0 +1,12 @@
+import py
+from pypy.conftest import gettestobjspace
+from pypy.rpython.tool.rffi_platform import CompilationError
+
+
+class BaseAppTest:
+ def setup_class(cls):
+ try:
+ import pypy.rlib.rstacklet
+ except CompilationError, e:
+ py.test.skip("cannot import rstacklet: %s" % e)
+ cls.space = gettestobjspace(usemodules=['_continuation'])
diff --git a/pypy/module/_continuation/test/test_generator.py b/pypy/module/_continuation/test/test_generator.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/_continuation/test/test_generator.py
@@ -0,0 +1,70 @@
+from pypy.module._continuation.test.support import BaseAppTest
+
+
+class AppTestGenerator(BaseAppTest):
+
+ def test_simple(self):
+ from _continuation import generator
+ #
+ @generator
+ def f(gen, n):
+ gen.switch(n+1)
+ f2(gen, n+2)
+ gen.switch(n+3)
+ #
+ def f2(gen, m):
+ gen.switch(m*2)
+ #
+ g = f(10)
+ res = g.next()
+ assert res == 11
+ res = g.next()
+ assert res == 24
+ res = g.next()
+ assert res == 13
+ raises(StopIteration, g.next)
+
+ def test_iterator(self):
+ from _continuation import generator
+ #
+ @generator
+ def f(gen, n):
+ gen.switch(n+1)
+ f2(gen, n+2)
+ gen.switch(n+3)
+ #
+ def f2(gen, m):
+ gen.switch(m*2)
+ #
+ res = list(f(10))
+ assert res == [11, 24, 13]
+ g = f(20)
+ assert iter(g) is g
+
+ def test_bound_method(self):
+ from _continuation import generator
+ #
+ class A(object):
+ def __init__(self, m):
+ self.m = m
+ #
+ @generator
+ def f(self, gen, n):
+ gen.switch(n - self.m)
+ #
+ a = A(10)
+ res = list(a.f(25))
+ assert res == [15]
+
+ def test_must_return_None(self):
+ from _continuation import generator
+ #
+ @generator
+ def f(gen, n):
+ gen.switch(n+1)
+ return "foo"
+ #
+ g = f(10)
+ res = g.next()
+ assert res == 11
+ raises(TypeError, g.next)
diff --git a/pypy/module/_continuation/test/test_stacklet.py b/pypy/module/_continuation/test/test_stacklet.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/_continuation/test/test_stacklet.py
@@ -0,0 +1,635 @@
+import os
+from pypy.module._continuation.test.support import BaseAppTest
+
+
+class AppTestStacklet(BaseAppTest):
+ def setup_class(cls):
+ BaseAppTest.setup_class.im_func(cls)
+ cls.w_translated = cls.space.wrap(
+ os.path.join(os.path.dirname(__file__),
+ 'test_translated.py'))
+
+ def test_new_empty(self):
+ from _continuation import continulet
+ #
+ def empty_callback(c):
+ pass
+ #
+ c = continulet(empty_callback)
+ assert type(c) is continulet
+
+ def test_call_empty(self):
+ from _continuation import continulet
+ #
+ def empty_callback(c1):
+ assert c1 is c
+ seen.append(1)
+ return 42
+ #
+ seen = []
+ c = continulet(empty_callback)
+ res = c.switch()
+ assert res == 42
+ assert seen == [1]
+
+ def test_no_double_init(self):
+ from _continuation import continulet, error
+ #
+ def empty_callback(c1):
+ pass
+ #
+ c = continulet(empty_callback)
+ raises(error, c.__init__, empty_callback)
+
+ def test_no_init_after_started(self):
+ from _continuation import continulet, error
+ #
+ def empty_callback(c1):
+ raises(error, c1.__init__, empty_callback)
+ return 42
+ #
+ c = continulet(empty_callback)
+ res = c.switch()
+ assert res == 42
+
+ def test_no_init_after_finished(self):
+ from _continuation import continulet, error
+ #
+ def empty_callback(c1):
+ return 42
+ #
+ c = continulet(empty_callback)
+ res = c.switch()
+ assert res == 42
+ raises(error, c.__init__, empty_callback)
+
+ def test_propagate_exception(self):
+ from _continuation import continulet
+ #
+ def empty_callback(c1):
+ assert c1 is c
+ seen.append(42)
+ raise ValueError
+ #
+ seen = []
+ c = continulet(empty_callback)
+ raises(ValueError, c.switch)
+ assert seen == [42]
+
+ def test_callback_with_arguments(self):
+ from _continuation import continulet
+ #
+ def empty_callback(c1, *args, **kwds):
+ seen.append(c1)
+ seen.append(args)
+ seen.append(kwds)
+ return 42
+ #
+ seen = []
+ c = continulet(empty_callback, 42, 43, foo=44, bar=45)
+ res = c.switch()
+ assert res == 42
+ assert seen == [c, (42, 43), {'foo': 44, 'bar': 45}]
+
+ def test_switch(self):
+ from _continuation import continulet
+ #
+ def switchbackonce_callback(c):
+ seen.append(1)
+ res = c.switch('a')
+ assert res == 'b'
+ seen.append(3)
+ return 'c'
+ #
+ seen = []
+ c = continulet(switchbackonce_callback)
+ seen.append(0)
+ res = c.switch()
+ assert res == 'a'
+ seen.append(2)
+ res = c.switch('b')
+ assert res == 'c'
+ assert seen == [0, 1, 2, 3]
+
+ def test_initial_switch_must_give_None(self):
+ from _continuation import continulet
+ #
+ def empty_callback(c):
+ return 'ok'
+ #
+ c = continulet(empty_callback)
+ res = c.switch(None)
+ assert res == 'ok'
+ #
+ c = continulet(empty_callback)
+ raises(TypeError, c.switch, 'foo') # "can't send non-None value"
+
+ def test_continuation_error(self):
+ from _continuation import continulet, error
+ #
+ def empty_callback(c):
+ return 42
+ #
+ c = continulet(empty_callback)
+ c.switch()
+ e = raises(error, c.switch)
+ assert str(e.value) == "continulet already finished"
+
+ def test_not_initialized_yet(self):
+ from _continuation import continulet, error
+ c = continulet.__new__(continulet)
+ e = raises(error, c.switch)
+ assert str(e.value) == "continulet not initialized yet"
+
+ def test_go_depth2(self):
+ from _continuation import continulet
+ #
+ def depth2(c):
+ seen.append(3)
+ return 4
+ #
+ def depth1(c):
+ seen.append(1)
+ c2 = continulet(depth2)
+ seen.append(2)
+ res = c2.switch()
+ seen.append(res)
+ return 5
+ #
+ seen = []
+ c = continulet(depth1)
+ seen.append(0)
+ res = c.switch()
+ seen.append(res)
+ assert seen == [0, 1, 2, 3, 4, 5]
+
+ def test_exception_depth2(self):
+ from _continuation import continulet
+ #
+ def depth2(c):
+ seen.append(2)
+ raise ValueError
+ #
+ def depth1(c):
+ seen.append(1)
+ try:
+ continulet(depth2).switch()
+ except ValueError:
+ seen.append(3)
+ return 4
+ #
+ seen = []
+ c = continulet(depth1)
+ res = c.switch()
+ seen.append(res)
+ assert seen == [1, 2, 3, 4]
+
+ def test_exception_with_switch(self):
+ from _continuation import continulet
+ #
+ def depth1(c):
+ seen.append(1)
+ c.switch()
+ seen.append(3)
+ raise ValueError
+ #
+ seen = []
+ c = continulet(depth1)
+ seen.append(0)
+ c.switch()
+ seen.append(2)
+ raises(ValueError, c.switch)
+ assert seen == [0, 1, 2, 3]
+
+ def test_is_pending(self):
+ from _continuation import continulet
+ #
+ def switchbackonce_callback(c):
+ assert c.is_pending()
+ res = c.switch('a')
+ assert res == 'b'
+ assert c.is_pending()
+ return 'c'
+ #
+ c = continulet.__new__(continulet)
+ assert not c.is_pending()
+ c.__init__(switchbackonce_callback)
+ assert c.is_pending()
+ res = c.switch()
+ assert res == 'a'
+ assert c.is_pending()
+ res = c.switch('b')
+ assert res == 'c'
+ assert not c.is_pending()
+
+ def test_switch_alternate(self):
+ from _continuation import continulet
+ #
+ def func_lower(c):
+ res = c.switch('a')
+ assert res == 'b'
+ res = c.switch('c')
+ assert res == 'd'
+ return 'e'
+ #
+ def func_upper(c):
+ res = c.switch('A')
+ assert res == 'B'
+ res = c.switch('C')
+ assert res == 'D'
+ return 'E'
+ #
+ c_lower = continulet(func_lower)
+ c_upper = continulet(func_upper)
+ res = c_lower.switch()
+ assert res == 'a'
+ res = c_upper.switch()
+ assert res == 'A'
+ res = c_lower.switch('b')
+ assert res == 'c'
+ res = c_upper.switch('B')
+ assert res == 'C'
+ res = c_lower.switch('d')
+ assert res == 'e'
+ res = c_upper.switch('D')
+ assert res == 'E'
+
+ def test_exception_with_switch_depth2(self):
+ from _continuation import continulet
+ #
+ def depth2(c):
+ seen.append(4)
+ c.switch()
+ seen.append(6)
+ raise ValueError
+ #
+ def depth1(c):
+ seen.append(1)
+ c.switch()
+ seen.append(3)
+ c2 = continulet(depth2)
+ c2.switch()
+ seen.append(5)
+ raises(ValueError, c2.switch)
+ assert not c2.is_pending()
+ seen.append(7)
+ assert c.is_pending()
+ raise KeyError
+ #
+ seen = []
+ c = continulet(depth1)
+ c.switch()
+ seen.append(2)
+ raises(KeyError, c.switch)
+ assert not c.is_pending()
+ assert seen == [1, 2, 3, 4, 5, 6, 7]
+
+ def test_random_switching(self):
+ from _continuation import continulet
+ #
+ def t1(c1):
+ return c1.switch()
+ def s1(c1, n):
+ assert n == 123
+ c2 = t1(c1)
+ return c1.switch('a') + 1
+ #
+ def s2(c2, c1):
+ res = c1.switch(c2)
+ assert res == 'a'
+ return c2.switch('b') + 2
+ #
+ def f():
+ c1 = continulet(s1, 123)
+ c2 = continulet(s2, c1)
+ c1.switch()
+ res = c2.switch()
+ assert res == 'b'
+ res = c1.switch(1000)
+ assert res == 1001
+ return c2.switch(2000)
+ #
+ res = f()
+ assert res == 2002
+
+ def test_f_back_is_None_for_now(self):
+ import sys
+ from _continuation import continulet
+ #
+ def g(c):
+ c.switch(sys._getframe(0))
+ c.switch(sys._getframe(0).f_back)
+ c.switch(sys._getframe(1))
+ c.switch(sys._getframe(1).f_back)
+ c.switch(sys._getframe(2))
+ def f(c):
+ g(c)
+ #
+ c = continulet(f)
+ f1 = c.switch()
+ assert f1.f_code.co_name == 'g'
+ f2 = c.switch()
+ assert f2.f_code.co_name == 'f'
+ f3 = c.switch()
+ assert f3.f_code.co_name == 'f'
+ f4 = c.switch()
+ assert f4 is None
+ raises(ValueError, c.switch) # "call stack is not deep enough"
+
+ def test_traceback_is_complete(self):
+ import sys
+ from _continuation import continulet
+ #
+ def g():
+ raise KeyError
+ def f(c):
+ g()
+ #
+ def do(c):
+ c.switch()
+ #
+ c = continulet(f)
+ try:
+ do(c)
+ except KeyError:
+ tb = sys.exc_info()[2]
+ else:
+ raise AssertionError("should have raised!")
+ #
+ assert tb.tb_next.tb_frame.f_code.co_name == 'do'
+ assert tb.tb_next.tb_next.tb_frame.f_code.co_name == 'f'
+ assert tb.tb_next.tb_next.tb_next.tb_frame.f_code.co_name == 'g'
+ assert tb.tb_next.tb_next.tb_next.tb_next is None
+
+ def test_switch2_simple(self):
+ from _continuation import continulet
+ #
+ def f1(c1):
+ res = c1.switch('started 1')
+ assert res == 'a'
+ res = c1.switch('b', to=c2)
+ assert res == 'c'
+ return 42
+ def f2(c2):
+ res = c2.switch('started 2')
+ assert res == 'b'
+ res = c2.switch('c', to=c1)
+ not_reachable
+ #
+ c1 = continulet(f1)
+ c2 = continulet(f2)
+ res = c1.switch()
+ assert res == 'started 1'
+ res = c2.switch()
+ assert res == 'started 2'
+ res = c1.switch('a')
+ assert res == 42
+
+ def test_switch2_pingpong(self):
+ from _continuation import continulet
+ #
+ def f1(c1):
+ res = c1.switch('started 1')
+ assert res == 'go'
+ for i in range(10):
+ res = c1.switch(i, to=c2)
+ assert res == 100 + i
+ return 42
+ def f2(c2):
+ res = c2.switch('started 2')
+ for i in range(10):
+ assert res == i
+ res = c2.switch(100 + i, to=c1)
+ not_reachable
+ #
+ c1 = continulet(f1)
+ c2 = continulet(f2)
+ res = c1.switch()
+ assert res == 'started 1'
+ res = c2.switch()
+ assert res == 'started 2'
+ res = c1.switch('go')
+ assert res == 42
+
+ def test_switch2_more_complex(self):
+ from _continuation import continulet
+ #
+ def f1(c1):
+ res = c1.switch(to=c2)
+ assert res == 'a'
+ res = c1.switch('b', to=c2)
+ assert res == 'c'
+ return 41
+ def f2(c2):
+ res = c2.switch('a', to=c1)
+ assert res == 'b'
+ return 42
+ #
+ c1 = continulet(f1)
+ c2 = continulet(f2)
+ res = c1.switch()
+ assert res == 42
+ assert not c2.is_pending() # finished by returning 42
+ res = c1.switch('c')
+ assert res == 41
+
+ def test_switch2_no_op(self):
+ from _continuation import continulet
+ #
+ def f1(c1):
+ res = c1.switch('a', to=c1)
+ assert res == 'a'
+ return 42
+ #
+ c1 = continulet(f1)
+ res = c1.switch()
+ assert res == 42
+
+ def test_switch2_immediately_away(self):
+ from _continuation import continulet
+ #
+ def f1(c1):
+ print 'in f1'
+ return 'm'
+ #
+ def f2(c2):
+ res = c2.switch('z')
+ print 'got there!'
+ assert res == 'a'
+ return None
+ #
+ c1 = continulet(f1)
+ c2 = continulet(f2)
+ res = c2.switch()
+ assert res == 'z'
+ assert c1.is_pending()
+ assert c2.is_pending()
+ print 'calling!'
+ res = c1.switch('a', to=c2)
+ print 'back'
+ assert res == 'm'
+
+ def test_switch2_immediately_away_corner_case(self):
+ from _continuation import continulet
+ #
+ def f1(c1):
+ this_is_never_seen
+ #
+ def f2(c2):
+ res = c2.switch('z')
+ assert res is None
+ return 'b' # this goes back into the caller, which is f1,
+ # but f1 didn't start yet, so a None-value value
+ # has nowhere to go to...
+ c1 = continulet(f1)
+ c2 = continulet(f2)
+ res = c2.switch()
+ assert res == 'z'
+ raises(TypeError, c1.switch, to=c2) # "can't send non-None value"
+
+ def test_switch2_not_initialized_yet(self):
+ from _continuation import continulet, error
+ #
+ def f1(c1):
+ not_reachable
+ #
+ c1 = continulet(f1)
+ c2 = continulet.__new__(continulet)
+ e = raises(error, c1.switch, to=c2)
+ assert str(e.value) == "continulet not initialized yet"
+
+ def test_switch2_already_finished(self):
+ from _continuation import continulet, error
+ #
+ def f1(c1):
+ not_reachable
+ def empty_callback(c):
+ return 42
+ #
+ c1 = continulet(f1)
+ c2 = continulet(empty_callback)
+ c2.switch()
+ e = raises(error, c1.switch, to=c2)
+ assert str(e.value) == "continulet already finished"
+
+ def test_throw(self):
+ import sys
+ from _continuation import continulet
+ #
+ def f1(c1):
+ try:
+ c1.switch()
+ except KeyError:
+ res = "got keyerror"
+ try:
+ c1.switch(res)
+ except IndexError, e:
+ pass
+ try:
+ c1.switch(e)
+ except IndexError, e2:
+ pass
+ try:
+ c1.switch(e2)
+ except IndexError:
+ c1.throw(*sys.exc_info())
+ should_never_reach_here
+ #
+ c1 = continulet(f1)
+ c1.switch()
+ res = c1.throw(KeyError)
+ assert res == "got keyerror"
+ class FooError(IndexError):
+ pass
+ foo = FooError()
+ res = c1.throw(foo)
+ assert res is foo
+ res = c1.throw(IndexError, foo)
+ assert res is foo
+ #
+ def main():
+ def do_raise():
+ raise foo
+ try:
+ do_raise()
+ except IndexError:
+ tb = sys.exc_info()[2]
+ try:
+ c1.throw(IndexError, foo, tb)
+ except IndexError:
+ tb = sys.exc_info()[2]
+ return tb
+ #
+ tb = main()
+ assert tb.tb_frame.f_code.co_name == 'main'
+ assert tb.tb_next.tb_frame.f_code.co_name == 'f1'
+ assert tb.tb_next.tb_next.tb_frame.f_code.co_name == 'main'
+ assert tb.tb_next.tb_next.tb_next.tb_frame.f_code.co_name == 'do_raise'
+ assert tb.tb_next.tb_next.tb_next.tb_next is None
+
+ def test_throw_to_starting(self):
+ from _continuation import continulet
+ #
+ def f1(c1):
+ not_reached
+ #
+ c1 = continulet(f1)
+ raises(IndexError, c1.throw, IndexError)
+
+ def test_throw2_simple(self):
+ from _continuation import continulet
+ #
+ def f1(c1):
+ not_reached
+ def f2(c2):
+ try:
+ c2.switch("ready")
+ except IndexError:
+ raise ValueError
+ #
+ c1 = continulet(f1)
+ c2 = continulet(f2)
+ res = c2.switch()
+ assert res == "ready"
+ assert c1.is_pending()
+ assert c2.is_pending()
+ raises(ValueError, c1.throw, IndexError, to=c2)
+ assert not c1.is_pending()
+ assert not c2.is_pending()
+
+ def test_throw2_no_op(self):
+ from _continuation import continulet
+ #
+ def f1(c1):
+ raises(ValueError, c1.throw, ValueError, to=c1)
+ return "ok"
+ #
+ c1 = continulet(f1)
+ res = c1.switch()
+ assert res == "ok"
+
+ def test_permute(self):
+ from _continuation import continulet, permute
+ #
+ def f1(c1):
+ res = c1.switch()
+ assert res == "ok"
+ return "done"
+ #
+ def f2(c2):
+ permute(c1, c2)
+ return "ok"
+ #
+ c1 = continulet(f1)
+ c2 = continulet(f2)
+ c1.switch()
+ res = c2.switch()
+ assert res == "done"
+
+ def test_various_depths(self):
+ skip("may fail on top of CPython")
+ # run it from test_translated, but not while being actually translated
+ d = {}
+ execfile(self.translated, d)
+ d['set_fast_mode']()
+ d['test_various_depths']()
diff --git a/pypy/module/_continuation/test/test_translated.py b/pypy/module/_continuation/test/test_translated.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/_continuation/test/test_translated.py
@@ -0,0 +1,132 @@
+import py
+try:
+ import _continuation
+except ImportError:
+ py.test.skip("to run on top of a translated pypy-c")
+
+import sys, random
+
+# ____________________________________________________________
+
+STATUS_MAX = 50000
+CONTINULETS = 50
+
+def set_fast_mode():
+ global STATUS_MAX, CONTINULETS
+ STATUS_MAX = 100
+ CONTINULETS = 5
+
+# ____________________________________________________________
+
+class Done(Exception):
+ pass
+
+
+class Runner(object):
+
+ def __init__(self):
+ self.foobar = 12345
+ self.conts = {} # {continulet: parent-or-None}
+ self.contlist = []
+
+ def run_test(self):
+ self.start_continulets()
+ self.n = 0
+ try:
+ while True:
+ self.do_switch(src=None)
+ assert self.target is None
+ except Done:
+ self.check_traceback(sys.exc_info()[2])
+
+ def do_switch(self, src):
+ assert src not in self.conts.values()
+ c = random.choice(self.contlist)
+ self.target = self.conts[c]
+ self.conts[c] = src
+ c.switch()
+ assert self.target is src
+
+ def run_continulet(self, c, i):
+ while True:
+ assert self.target is c
+ assert self.contlist[i] is c
+ self.do_switch(c)
+ assert self.foobar == 12345
+ self.n += 1
+ if self.n >= STATUS_MAX:
+ raise Done
+
+ def start_continulets(self, i=0):
+ c = _continuation.continulet(self.run_continulet, i)
+ self.contlist.append(c)
+ if i < CONTINULETS:
+ self.start_continulets(i + 1)
+ # ^^^ start each continulet with a different base stack
+ self.conts[c] = c # initially (i.e. not started) there are all loops
+
+ def check_traceback(self, tb):
+ found = []
+ tb = tb.tb_next
+ while tb:
+ if tb.tb_frame.f_code.co_name != 'do_switch':
+ assert tb.tb_frame.f_code.co_name == 'run_continulet', (
+ "got %r" % (tb.tb_frame.f_code.co_name,))
+ found.append(tb.tb_frame.f_locals['c'])
+ tb = tb.tb_next
+ found.reverse()
+ #
+ expected = []
+ c = self.target
+ while c is not None:
+ expected.append(c)
+ c = self.conts[c]
+ #
+ assert found == expected, "%r == %r" % (found, expected)
+
+# ____________________________________________________________
+
+class AppTestWrapper:
+ def setup_class(cls):
+ "Run test_various_depths() when we are run with 'pypy py.test -A'."
+ from pypy.conftest import option
+ if not option.runappdirect:
+ py.test.skip("meant only for -A run")
+
+ def test_single_threaded(self):
+ for i in range(20):
+ yield Runner().run_test,
+
+ def test_multi_threaded(self):
+ for i in range(5):
+ yield multithreaded_test,
+
+class ThreadTest(object):
+ def __init__(self, lock):
+ self.lock = lock
+ self.ok = False
+ lock.acquire()
+ def run(self):
+ try:
+ Runner().run_test()
+ self.ok = True
+ finally:
+ self.lock.release()
+
+def multithreaded_test():
+ try:
+ import thread
+ except ImportError:
+ py.test.skip("no threads")
+ ts = [ThreadTest(thread.allocate_lock()) for i in range(5)]
+ for t in ts:
+ thread.start_new_thread(t.run, ())
+ for t in ts:
+ t.lock.acquire()
+ for t in ts:
+ assert t.ok
+
+# ____________________________________________________________
+
+if __name__ == '__main__':
+ Runner().run_test()
diff --git a/pypy/module/posix/__init__.py b/pypy/module/posix/__init__.py
--- a/pypy/module/posix/__init__.py
+++ b/pypy/module/posix/__init__.py
@@ -161,6 +161,8 @@
interpleveldefs['mknod'] = 'interp_posix.mknod'
if hasattr(os, 'nice'):
interpleveldefs['nice'] = 'interp_posix.nice'
+ if hasattr(os, 'getlogin'):
+ interpleveldefs['getlogin'] = 'interp_posix.getlogin'
for name in ['setsid', 'getuid', 'geteuid', 'getgid', 'getegid', 'setuid',
'seteuid', 'setgid', 'setegid', 'getgroups', 'getpgrp',
diff --git a/pypy/module/posix/interp_posix.py b/pypy/module/posix/interp_posix.py
--- a/pypy/module/posix/interp_posix.py
+++ b/pypy/module/posix/interp_posix.py
@@ -464,6 +464,15 @@
space.wrap("strerror() argument out of range"))
return space.wrap(text)
+def getlogin(space):
+ """Return the currently logged in user."""
+ try:
+ cur = os.getlogin()
+ except OSError, e:
+ raise wrap_oserror(space, e)
+ else:
+ return space.wrap(cur)
+
# ____________________________________________________________
def getstatfields(space):
diff --git a/pypy/module/posix/test/test_posix2.py b/pypy/module/posix/test/test_posix2.py
--- a/pypy/module/posix/test/test_posix2.py
+++ b/pypy/module/posix/test/test_posix2.py
@@ -805,6 +805,16 @@
data = f.read()
assert data == "who cares?"
+ try:
+ os.getlogin()
+ except (AttributeError, OSError):
+ pass
+ else:
+ def test_getlogin(self):
+ assert isinstance(self.posix.getlogin(), str)
+ # How else could we test that getlogin is properly
+ # working?
+
def test_tmpfile(self):
os = self.posix
f = os.tmpfile()
diff --git a/pypy/module/pyexpat/interp_pyexpat.py b/pypy/module/pyexpat/interp_pyexpat.py
--- a/pypy/module/pyexpat/interp_pyexpat.py
+++ b/pypy/module/pyexpat/interp_pyexpat.py
@@ -9,6 +9,7 @@
from pypy.rpython.tool import rffi_platform
from pypy.translator.tool.cbuild import ExternalCompilationInfo
+from pypy.translator.platform import platform
import sys
import py
@@ -19,7 +20,9 @@
libname = 'expat'
eci = ExternalCompilationInfo(
libraries=[libname],
+ library_dirs=platform.preprocess_library_dirs([]),
includes=['expat.h'],
+ include_dirs=platform.preprocess_include_dirs([]),
)
eci = rffi_platform.configure_external_library(
diff --git a/pypy/module/pypyjit/interp_jit.py b/pypy/module/pypyjit/interp_jit.py
--- a/pypy/module/pypyjit/interp_jit.py
+++ b/pypy/module/pypyjit/interp_jit.py
@@ -24,6 +24,7 @@
'last_exception',
'lastblock',
'is_being_profiled',
+ 'w_globals',
]
JUMP_ABSOLUTE = opmap['JUMP_ABSOLUTE']
diff --git a/pypy/module/pypyjit/test_pypy_c/test_call.py b/pypy/module/pypyjit/test_pypy_c/test_call.py
--- a/pypy/module/pypyjit/test_pypy_c/test_call.py
+++ b/pypy/module/pypyjit/test_pypy_c/test_call.py
@@ -67,24 +67,14 @@
assert log.opnames(ops) == ["guard_value",
"getfield_gc", "guard_value",
"getfield_gc", "guard_value",
- "getfield_gc", "guard_nonnull_class"]
- # LOAD_GLOBAL of OFFSET but in different function partially folded
- # away
- # XXX could be improved
+ "guard_not_invalidated"]
ops = entry_bridge.ops_by_id('add', opcode='LOAD_GLOBAL')
- assert log.opnames(ops) == ["guard_value", "getfield_gc", "guard_value"]
+ assert log.opnames(ops) == ["guard_not_invalidated"]
#
- # two LOAD_GLOBAL of f, the second is folded away
ops = entry_bridge.ops_by_id('call', opcode='LOAD_GLOBAL')
- assert log.opnames(ops) == ["getfield_gc", "guard_nonnull_class"]
+ assert log.opnames(ops) == []
#
assert entry_bridge.match_by_id('call', """
- p29 = getfield_gc(ConstPtr(ptr28), descr=<GcPtrFieldDescr pypy.objspace.std.celldict.ModuleCell.inst_w_value .*>)
- guard_nonnull_class(p29, ConstClass(Function), descr=...)
- p33 = getfield_gc(p29, descr=<GcPtrFieldDescr pypy.interpreter.function.Function.inst_code .*>)
- guard_value(p33, ConstPtr(ptr34), descr=...)
- p35 = getfield_gc(p29, descr=<GcPtrFieldDescr pypy.interpreter.function.Function.inst_w_func_globals .*>)
- p36 = getfield_gc(p29, descr=<GcPtrFieldDescr pypy.interpreter.function.Function.inst_closure .*>)
p38 = call(ConstClass(getexecutioncontext), descr=<GcPtrCallDescr>)
p39 = getfield_gc(p38, descr=<GcPtrFieldDescr pypy.interpreter.executioncontext.ExecutionContext.inst_topframeref .*>)
i40 = force_token()
@@ -100,19 +90,16 @@
# -----------------------------
loop, = log.loops_by_id('call')
assert loop.match("""
- i12 = int_lt(i5, i6)
- guard_true(i12, descr=...)
+ guard_not_invalidated(descr=...)
+ i9 = int_lt(i5, i6)
+ guard_true(i9, descr=...)
+ i10 = force_token()
+ i12 = int_add(i5, 1)
i13 = force_token()
- i15 = int_add(i5, 1)
- i16 = int_add_ovf(i15, i7)
- guard_no_overflow(descr=...)
- i18 = force_token()
- i20 = int_add_ovf(i16, 1)
- guard_no_overflow(descr=...)
- i21 = int_add_ovf(i20, i7)
+ i15 = int_add_ovf(i12, 1)
guard_no_overflow(descr=...)
--TICK--
- jump(p0, p1, p2, p3, p4, i21, i6, i7, p8, p9, p10, p11, descr=<Loop0>)
+ jump(p0, p1, p2, p3, p4, i15, i6, p7, p8, descr=<Loop0>)
""")
def test_method_call(self):
diff --git a/pypy/module/pypyjit/test_pypy_c/test_globals.py b/pypy/module/pypyjit/test_pypy_c/test_globals.py
--- a/pypy/module/pypyjit/test_pypy_c/test_globals.py
+++ b/pypy/module/pypyjit/test_pypy_c/test_globals.py
@@ -20,11 +20,9 @@
guard_value(p10, ConstPtr(ptr11), descr=...)
p12 = getfield_gc(p10, descr=<GcPtrFieldDescr .*W_DictMultiObject.inst_strategy .*>)
guard_value(p12, ConstPtr(ptr13), descr=...)
- p15 = getfield_gc(ConstPtr(ptr14), descr=<GcPtrFieldDescr .*ModuleCell.inst_w_value .*>)
- guard_isnull(p15, descr=...)
guard_not_invalidated(descr=...)
p19 = getfield_gc(ConstPtr(p17), descr=<GcPtrFieldDescr .*W_DictMultiObject.inst_strategy .*>)
guard_value(p19, ConstPtr(ptr20), descr=...)
p22 = getfield_gc(ConstPtr(ptr21), descr=<GcPtrFieldDescr .*ModuleCell.inst_w_value .*>)
guard_nonnull(p22, descr=...)
- """)
\ No newline at end of file
+ """)
diff --git a/pypy/module/pypyjit/test_pypy_c/test_instance.py b/pypy/module/pypyjit/test_pypy_c/test_instance.py
--- a/pypy/module/pypyjit/test_pypy_c/test_instance.py
+++ b/pypy/module/pypyjit/test_pypy_c/test_instance.py
@@ -52,7 +52,7 @@
i10 = int_add_ovf(i5, i7)
guard_no_overflow(descr=...)
--TICK--
- jump(p0, p1, p2, p3, p4, i10, i6, p7, i7, p8, descr=<Loop0>)
+ jump(p0, p1, p2, p3, p4, i10, i6, i7, p8, descr=<Loop0>)
""")
def test_getattr_with_dynamic_attribute(self):
@@ -151,6 +151,7 @@
assert loop.match_by_id('loadattr',
'''
guard_not_invalidated(descr=...)
+ i16 = arraylen_gc(p10, descr=<GcPtrArrayDescr>)
i19 = call(ConstClass(ll_dict_lookup), _, _, _, descr=...)
guard_no_exception(descr=...)
i21 = int_and(i19, _)
diff --git a/pypy/module/pypyjit/test_pypy_c/test_math.py b/pypy/module/pypyjit/test_pypy_c/test_math.py
--- a/pypy/module/pypyjit/test_pypy_c/test_math.py
+++ b/pypy/module/pypyjit/test_pypy_c/test_math.py
@@ -47,6 +47,7 @@
assert loop.match("""
i2 = int_lt(i0, i1)
guard_true(i2, descr=...)
+ guard_not_invalidated(descr=...)
f1 = cast_int_to_float(i0)
i3 = float_eq(f1, inf)
i4 = float_eq(f1, -inf)
@@ -60,4 +61,32 @@
i7 = int_add(i0, f1)
--TICK--
jump(..., descr=)
+ """)
+
+ def test_fmod(self):
+ def main(n):
+ import math
+
+ s = 0
+ while n > 0:
+ s += math.fmod(n, 2.0)
+ n -= 1
+ return s
+ log = self.run(main, [500])
+ assert log.result == main(500)
+ loop, = log.loops_by_filename(self.filepath)
+ assert loop.match("""
+ i1 = int_gt(i0, 0)
+ guard_true(i1, descr=...)
+ f1 = cast_int_to_float(i0)
+ i2 = float_eq(f1, inf)
+ i3 = float_eq(f1, -inf)
+ i4 = int_or(i2, i3)
+ i5 = int_is_true(i4)
+ guard_false(i5, descr=...)
+ f2 = call(ConstClass(fmod), f1, 2.0, descr=<FloatCallDescr>)
+ f3 = float_add(f0, f2)
+ i6 = int_sub(i0, 1)
+ --TICK--
+ jump(..., descr=)
""")
\ No newline at end of file
diff --git a/pypy/module/pypyjit/test_pypy_c/test_misc.py b/pypy/module/pypyjit/test_pypy_c/test_misc.py
--- a/pypy/module/pypyjit/test_pypy_c/test_misc.py
+++ b/pypy/module/pypyjit/test_pypy_c/test_misc.py
@@ -234,3 +234,18 @@
return total
#
self.run_and_check(main, [])
+
+
+ def test_global(self):
+ log = self.run("""
+ i = 0
+ globalinc = 1
+ def main(n):
+ global i
+ while i < n:
+ l = globalinc # ID: globalread
+ i += l
+ """, [1000])
+
+ loop, = log.loops_by_id("globalread", is_entry_bridge=True)
+ assert len(loop.ops_by_id("globalread")) == 0
diff --git a/pypy/module/pypyjit/test_pypy_c/test_string.py b/pypy/module/pypyjit/test_pypy_c/test_string.py
--- a/pypy/module/pypyjit/test_pypy_c/test_string.py
+++ b/pypy/module/pypyjit/test_pypy_c/test_string.py
@@ -34,9 +34,9 @@
i25 = unicodegetitem(p13, i19)
p27 = newstr(1)
strsetitem(p27, 0, i23)
- p30 = call(ConstClass(ll_str2unicode__rpy_stringPtr), p27, descr=<GcPtrCallDescr>)
+ p30 = call(ConstClass(ll_str2unicode__rpy_stringPtr), p27, descr=...)
guard_no_exception(descr=...)
- i32 = call(ConstClass(_ll_2_str_eq_checknull_char__rpy_unicodePtr_UniChar), p30, i25, descr=<SignedCallDescr>)
+ i32 = call(ConstClass(_ll_2_str_eq_checknull_char__rpy_unicodePtr_UniChar), p30, i25, descr=...)
guard_true(i32, descr=...)
i34 = int_add(i6, 1)
--TICK--
@@ -105,5 +105,5 @@
i58 = int_add_ovf(i6, i57)
guard_no_overflow(descr=...)
--TICK--
- jump(p0, p1, p2, p3, p4, p5, i58, i7, i8, p9, p10, descr=<Loop4>)
+ jump(p0, p1, p2, p3, p4, p5, i58, i7, descr=<Loop4>)
""")
diff --git a/pypy/module/struct/formatiterator.py b/pypy/module/struct/formatiterator.py
--- a/pypy/module/struct/formatiterator.py
+++ b/pypy/module/struct/formatiterator.py
@@ -154,6 +154,13 @@
self.inputpos = end
return s
+ def get_pos_and_advance(self, count):
+ pos = self.inputpos
+ self.inputpos += count
+ if self.inputpos > len(self.input):
+ raise StructError("unpack str size too short for format")
+ return pos
+
def appendobj(self, value):
self.result_w.append(self.space.wrap(value))
appendobj._annspecialcase_ = 'specialize:argtype(1)'
diff --git a/pypy/module/test_lib_pypy/test_greenlet.py b/pypy/module/test_lib_pypy/test_greenlet.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/test_lib_pypy/test_greenlet.py
@@ -0,0 +1,233 @@
+from pypy.conftest import gettestobjspace
+
+
+class AppTestGreenlet:
+ def setup_class(cls):
+ cls.space = gettestobjspace(usemodules=['_continuation'])
+
+ def test_simple(self):
+ from greenlet import greenlet
+ lst = []
+ def f():
+ lst.append(1)
+ greenlet.getcurrent().parent.switch()
+ lst.append(3)
+ g = greenlet(f)
+ lst.append(0)
+ g.switch()
+ lst.append(2)
+ g.switch()
+ lst.append(4)
+ assert lst == range(5)
+
+ def test_parent(self):
+ from greenlet import greenlet
+ gmain = greenlet.getcurrent()
+ assert gmain.parent is None
+ g = greenlet(lambda: None)
+ assert g.parent is gmain
+
+ def test_pass_around(self):
+ from greenlet import greenlet
+ seen = []
+ def f(x, y):
+ seen.append((x, y))
+ seen.append(greenlet.getcurrent().parent.switch())
+ seen.append(greenlet.getcurrent().parent.switch(42))
+ return 44, 'z'
+ g = greenlet(f)
+ seen.append(g.switch(40, 'x'))
+ seen.append(g.switch(41, 'y'))
+ seen.append(g.switch(43))
+ #
+ def f2():
+ return 45
+ g = greenlet(f2)
+ seen.append(g.switch())
+ #
+ def f3():
+ pass
+ g = greenlet(f3)
+ seen.append(g.switch())
+ #
+ assert seen == [(40, 'x'), (), (41, 'y'), 42, 43, (44, 'z'), 45, None]
+
+ def test_exception_simple(self):
+ from greenlet import greenlet
+ #
+ def fmain():
+ raise ValueError
+ #
+ g1 = greenlet(fmain)
+ raises(ValueError, g1.switch)
+
+ def test_dead(self):
+ from greenlet import greenlet
+ #
+ def fmain():
+ assert g1 and not g1.dead
+ #
+ g1 = greenlet(fmain)
+ assert not g1 and not g1.dead
+ g1.switch()
+ assert not g1 and g1.dead
+ #
+ gmain = greenlet.getcurrent()
+ assert gmain and not gmain.dead
+
+ def test_GreenletExit(self):
+ from greenlet import greenlet, GreenletExit
+ #
+ def fmain(*args):
+ raise GreenletExit(*args)
+ #
+ g1 = greenlet(fmain)
+ res = g1.switch('foo', 'bar')
+ assert isinstance(res, GreenletExit) and res.args == ('foo', 'bar')
+
+ def test_throw_1(self):
+ from greenlet import greenlet
+ gmain = greenlet.getcurrent()
+ #
+ def f():
+ try:
+ gmain.switch()
+ except ValueError:
+ return "ok"
+ #
+ g = greenlet(f)
+ g.switch()
+ res = g.throw(ValueError)
+ assert res == "ok"
+
+ def test_throw_2(self):
+ from greenlet import greenlet
+ gmain = greenlet.getcurrent()
+ #
+ def f():
+ gmain.throw(ValueError)
+ #
+ g = greenlet(f)
+ raises(ValueError, g.switch)
+
+ def test_throw_3(self):
+ from greenlet import greenlet
+ gmain = greenlet.getcurrent()
+ raises(ValueError, gmain.throw, ValueError)
+
+ def test_throw_4(self):
+ from greenlet import greenlet
+ gmain = greenlet.getcurrent()
+ #
+ def f1():
+ g2.throw(ValueError)
+ #
+ def f2():
+ try:
+ gmain.switch()
+ except ValueError:
+ return "ok"
+ #
+ g1 = greenlet(f1)
+ g2 = greenlet(f2)
+ g2.switch()
+ res = g1.switch()
+ assert res == "ok"
+
+ def test_nondefault_parent(self):
+ from greenlet import greenlet
+ #
+ def f1():
+ g2 = greenlet(f2)
+ res = g2.switch()
+ assert res == "from 2"
+ return "from 1"
+ #
+ def f2():
+ return "from 2"
+ #
+ g1 = greenlet(f1)
+ res = g1.switch()
+ assert res == "from 1"
+
+ def test_change_parent(self):
+ from greenlet import greenlet
+ #
+ def f1():
+ res = g2.switch()
+ assert res == "from 2"
+ return "from 1"
+ #
+ def f2():
+ return "from 2"
+ #
+ g1 = greenlet(f1)
+ g2 = greenlet(f2)
+ g2.parent = g1
+ res = g1.switch()
+ assert res == "from 1"
+
+ def test_raises_through_parent_chain(self):
+ from greenlet import greenlet
+ #
+ def f1():
+ raises(IndexError, g2.switch)
+ raise ValueError
+ #
+ def f2():
+ raise IndexError
+ #
+ g1 = greenlet(f1)
+ g2 = greenlet(f2)
+ g2.parent = g1
+ raises(ValueError, g1.switch)
+
+ def test_switch_to_dead_1(self):
+ from greenlet import greenlet
+ #
+ def f1():
+ return "ok"
+ #
+ g1 = greenlet(f1)
+ res = g1.switch()
+ assert res == "ok"
+ res = g1.switch("goes to gmain instead")
+ assert res == "goes to gmain instead"
+
+ def test_switch_to_dead_2(self):
+ from greenlet import greenlet
+ #
+ def f1():
+ g2 = greenlet(f2)
+ return g2.switch()
+ #
+ def f2():
+ return "ok"
+ #
+ g1 = greenlet(f1)
+ res = g1.switch()
+ assert res == "ok"
+ res = g1.switch("goes to gmain instead")
+ assert res == "goes to gmain instead"
+
+ def test_switch_to_dead_3(self):
+ from greenlet import greenlet
+ gmain = greenlet.getcurrent()
+ #
+ def f1():
+ res = g2.switch()
+ assert res == "ok"
+ res = gmain.switch("next step")
+ assert res == "goes to f1 instead"
+ return "all ok"
+ #
+ def f2():
+ return "ok"
+ #
+ g1 = greenlet(f1)
+ g2 = greenlet(f2)
+ g2.parent = g1
+ res = g1.switch()
+ assert res == "next step"
+ res = g2.switch("goes to f1 instead")
+ assert res == "all ok"
diff --git a/pypy/module/thread/os_thread.py b/pypy/module/thread/os_thread.py
--- a/pypy/module/thread/os_thread.py
+++ b/pypy/module/thread/os_thread.py
@@ -15,11 +15,6 @@
# * The start-up data (the app-level callable and arguments) is
# stored in the global bootstrapper object.
#
-# * The GC is notified that a new thread is about to start; in the
-# framework GC with shadow stacks, this allocates a fresh new shadow
-# stack (but doesn't use it yet). See gc_thread_prepare(). This
-# has no effect in asmgcc.
-#
# * The new thread is launched at RPython level using an rffi call
# to the C function RPyThreadStart() defined in
# translator/c/src/thread*.h. This RPython thread will invoke the
@@ -33,8 +28,8 @@
# operation is called (this is all done by gil.after_external_call(),
# called from the rffi-generated wrapper). The gc_thread_run()
# operation will automatically notice that the current thread id was
-# not seen before, and start using the freshly prepared shadow stack.
-# Again, this has no effect in asmgcc.
+# not seen before, and (in shadowstack) it will allocate and use a
+# fresh new stack. Again, this has no effect in asmgcc.
#
# * Only then does bootstrap() really run. The first thing it does
# is grab the start-up information (app-level callable and args)
@@ -180,7 +175,7 @@
bootstrapper.acquire(space, w_callable, args)
try:
try:
- thread.gc_thread_prepare()
+ thread.gc_thread_prepare() # (this has no effect any more)
ident = thread.start_new_thread(bootstrapper.bootstrap, ())
except Exception, e:
bootstrapper.release() # normally called by the new thread
diff --git a/pypy/objspace/descroperation.py b/pypy/objspace/descroperation.py
--- a/pypy/objspace/descroperation.py
+++ b/pypy/objspace/descroperation.py
@@ -724,13 +724,22 @@
w_left_src, w_left_impl = space.lookup_in_type_where(w_typ1, left)
w_first = w_obj1
w_second = w_obj2
-
- if _same_class_w(space, w_obj1, w_obj2, w_typ1, w_typ2):
+ #
+ if left == right and _same_class_w(space, w_obj1, w_obj2,
+ w_typ1, w_typ2):
+ # for __eq__ and __ne__, if the objects have the same
+ # (old-style or new-style) class, then don't try the
+ # opposite method, which is the same one.
w_right_impl = None
else:
- w_right_src, w_right_impl = space.lookup_in_type_where(w_typ2, right)
- # XXX see binop_impl
- if space.is_true(space.issubtype(w_typ2, w_typ1)):
+ # in all other cases, try the opposite method.
+ w_right_src, w_right_impl = space.lookup_in_type_where(w_typ2,right)
+ if space.is_w(w_typ1, w_typ2):
+ # if the type is the same, *or* if both are old-style classes,
+ # then don't reverse: try left first, right next.
+ pass
+ elif space.is_true(space.issubtype(w_typ2, w_typ1)):
+ # for new-style classes, if typ2 is a subclass of typ1.
w_obj1, w_obj2 = w_obj2, w_obj1
w_left_impl, w_right_impl = w_right_impl, w_left_impl
diff --git a/pypy/objspace/std/celldict.py b/pypy/objspace/std/celldict.py
--- a/pypy/objspace/std/celldict.py
+++ b/pypy/objspace/std/celldict.py
@@ -1,50 +1,57 @@
-""" A very simple cell dict implementation. The dictionary maps keys to cell.
-This ensures that the function (dict, key) -> cell is pure. By itself, this
-optimization is not helping at all, but in conjunction with the JIT it can
-speed up global lookups a lot."""
+""" A very simple cell dict implementation using a version tag. The dictionary
+maps keys to objects. If a specific key is changed a lot, a level of
+indirection is introduced to make the version tag change less often.
+"""
+from pypy.interpreter.baseobjspace import W_Root
from pypy.objspace.std.dictmultiobject import IteratorImplementation
from pypy.objspace.std.dictmultiobject import DictStrategy, _never_equal_to_string
from pypy.objspace.std.dictmultiobject import ObjectDictStrategy
from pypy.rlib import jit, rerased
-class ModuleCell(object):
+class VersionTag(object):
+ pass
+
+class ModuleCell(W_Root):
def __init__(self, w_value=None):
self.w_value = w_value
- def invalidate(self):
- w_value = self.w_value
- self.w_value = None
- return w_value
-
def __repr__(self):
return "<ModuleCell: %s>" % (self.w_value, )
+def unwrap_cell(w_value):
+ if isinstance(w_value, ModuleCell):
+ return w_value.w_value
+ return w_value
+
class ModuleDictStrategy(DictStrategy):
erase, unerase = rerased.new_erasing_pair("modulecell")
erase = staticmethod(erase)
unerase = staticmethod(unerase)
+ _immutable_fields_ = ["version?"]
+
def __init__(self, space):
self.space = space
+ self.version = VersionTag()
def get_empty_storage(self):
return self.erase({})
- def getcell(self, w_dict, key, makenew):
- if makenew or jit.we_are_jitted():
- # when we are jitting, we always go through the pure function
- # below, to ensure that we have no residual dict lookup
- w_dict = jit.promote(w_dict)
- self = jit.promote(self)
- return self._getcell_makenew(w_dict, key)
+ def mutated(self):
+ self.version = VersionTag()
+
+ def getdictvalue_no_unwrapping(self, w_dict, key):
+ # NB: it's important to promote self here, so that self.version is a
+ # no-op due to the quasi-immutable field
+ self = jit.promote(self)
+ return self._getdictvalue_no_unwrapping_pure(self.version, w_dict, key)
+
+ @jit.elidable_promote('0,1,2')
+ def _getdictvalue_no_unwrapping_pure(self, version, w_dict, key):
return self.unerase(w_dict.dstorage).get(key, None)
- @jit.elidable
- def _getcell_makenew(self, w_dict, key):
- return self.unerase(w_dict.dstorage).setdefault(key, ModuleCell())
-
def setitem(self, w_dict, w_key, w_value):
space = self.space
if space.is_w(space.type(w_key), space.w_str):
@@ -54,15 +61,24 @@
w_dict.setitem(w_key, w_value)
def setitem_str(self, w_dict, key, w_value):
- self.getcell(w_dict, key, True).w_value = w_value
+ cell = self.getdictvalue_no_unwrapping(w_dict, key)
+ if isinstance(cell, ModuleCell):
+ cell.w_value = w_value
+ return
+ if cell is not None:
+ w_value = ModuleCell(w_value)
+ self.mutated()
+ self.unerase(w_dict.dstorage)[key] = w_value
def setdefault(self, w_dict, w_key, w_default):
space = self.space
if space.is_w(space.type(w_key), space.w_str):
- cell = self.getcell(w_dict, space.str_w(w_key), True)
- if cell.w_value is None:
- cell.w_value = w_default
- return cell.w_value
+ key = space.str_w(w_key)
+ w_result = self.getitem_str(w_dict, key)
+ if w_result is not None:
+ return w_result
+ self.setitem_str(w_dict, key, w_default)
+ return w_default
else:
self.switch_to_object_strategy(w_dict)
return w_dict.setdefault(w_key, w_default)
@@ -72,14 +88,13 @@
w_key_type = space.type(w_key)
if space.is_w(w_key_type, space.w_str):
key = space.str_w(w_key)
- cell = self.getcell(w_dict, key, False)
- if cell is None or cell.w_value is None:
- raise KeyError
- # note that we don't remove the cell from self.content, to make
- # sure that a key that was found at any point in the dict, still
- # maps to the same cell later (even if this cell no longer
- # represents a key)
- cell.invalidate()
+ dict_w = self.unerase(w_dict.dstorage)
+ try:
+ del dict_w[key]
+ except KeyError:
+ raise
+ else:
+ self.mutated()
elif _never_equal_to_string(space, w_key_type):
raise KeyError
else:
@@ -87,12 +102,7 @@
w_dict.delitem(w_key)
def length(self, w_dict):
- # inefficient, but do we care?
- res = 0
- for cell in self.unerase(w_dict.dstorage).itervalues():
- if cell.w_value is not None:
- res += 1
- return res
+ return len(self.unerase(w_dict.dstorage))
def getitem(self, w_dict, w_key):
space = self.space
@@ -107,11 +117,8 @@
return w_dict.getitem(w_key)
def getitem_str(self, w_dict, key):
- res = self.getcell(w_dict, key, False)
- if res is None:
- return None
- # note that even if the res.w_value is None, the next line is fine
- return res.w_value
+ w_res = self.getdictvalue_no_unwrapping(w_dict, key)
+ return unwrap_cell(w_res)
def iter(self, w_dict):
return ModuleDictIteratorImplementation(self.space, self, w_dict)
@@ -119,44 +126,34 @@
def keys(self, w_dict):
space = self.space
iterator = self.unerase(w_dict.dstorage).iteritems
- return [space.wrap(key) for key, cell in iterator()
- if cell.w_value is not None]
+ return [space.wrap(key) for key, cell in iterator()]
def values(self, w_dict):
iterator = self.unerase(w_dict.dstorage).itervalues
- return [cell.w_value for cell in iterator()
- if cell.w_value is not None]
+ return [unwrap_cell(cell) for cell in iterator()]
def items(self, w_dict):
space = self.space
iterator = self.unerase(w_dict.dstorage).iteritems
- return [space.newtuple([space.wrap(key), cell.w_value])
- for (key, cell) in iterator()
- if cell.w_value is not None]
+ return [space.newtuple([space.wrap(key), unwrap_cell(cell)])
+ for key, cell in iterator()]
def clear(self, w_dict):
- iterator = self.unerase(w_dict.dstorage).iteritems
- for k, cell in iterator():
- cell.invalidate()
+ iterator = self.unerase(w_dict.dstorage).clear()
+ self.mutated()
def popitem(self, w_dict):
- # This is O(n) if called repeatadly, you probably shouldn't be on a
- # Module's dict though
- for k, cell in self.unerase(w_dict.dstorage).iteritems():
- if cell.w_value is not None:
- w_value = cell.w_value
- cell.invalidate()
- return self.space.wrap(k), w_value
- else:
- raise KeyError
+ d = self.unerase(w_dict.dstorage)
+ key, w_value = d.popitem()
+ self.mutated()
+ return self.space.wrap(key), unwrap_cell(w_value)
def switch_to_object_strategy(self, w_dict):
d = self.unerase(w_dict.dstorage)
strategy = self.space.fromcache(ObjectDictStrategy)
d_new = strategy.unerase(strategy.get_empty_storage())
for key, cell in d.iteritems():
- if cell.w_value is not None:
- d_new[self.space.wrap(key)] = cell.w_value
+ d_new[self.space.wrap(key)] = unwrap_cell(cell)
w_dict.strategy = strategy
w_dict.dstorage = strategy.erase(d_new)
@@ -168,7 +165,6 @@
def next_entry(self):
for key, cell in self.iterator:
- if cell.w_value is not None:
- return (self.space.wrap(key), cell.w_value)
+ return (self.space.wrap(key), unwrap_cell(cell))
else:
return None, None
diff --git a/pypy/objspace/std/dictmultiobject.py b/pypy/objspace/std/dictmultiobject.py
--- a/pypy/objspace/std/dictmultiobject.py
+++ b/pypy/objspace/std/dictmultiobject.py
@@ -38,7 +38,9 @@
if space.config.objspace.std.withcelldict and module:
from pypy.objspace.std.celldict import ModuleDictStrategy
assert w_type is None
- strategy = space.fromcache(ModuleDictStrategy)
+ # every module needs its own strategy, because the strategy stores
+ # the version tag
+ strategy = ModuleDictStrategy(space)
elif instance or strdict or module:
assert w_type is None
diff --git a/pypy/objspace/std/test/test_celldict.py b/pypy/objspace/std/test/test_celldict.py
--- a/pypy/objspace/std/test/test_celldict.py
+++ b/pypy/objspace/std/test/test_celldict.py
@@ -2,42 +2,111 @@
from pypy.conftest import gettestobjspace, option
from pypy.objspace.std.dictmultiobject import W_DictMultiObject
from pypy.objspace.std.celldict import ModuleCell, ModuleDictStrategy
-from pypy.objspace.std.test.test_dictmultiobject import FakeSpace
+from pypy.objspace.std.test.test_dictmultiobject import FakeSpace, \
+ BaseTestRDictImplementation, BaseTestDevolvedDictImplementation
from pypy.interpreter import gateway
+from pypy.conftest import gettestobjspace, option
+
space = FakeSpace()
class TestCellDict(object):
- def test_basic_property(self):
+ def test_basic_property_cells(self):
strategy = ModuleDictStrategy(space)
storage = strategy.get_empty_storage()
d = W_DictMultiObject(space, strategy, storage)
- # replace getcell with getcell from strategy
- def f(key, makenew):
- return strategy.getcell(d, key, makenew)
- d.getcell = f
+ v1 = strategy.version
+ d.setitem("a", 1)
+ v2 = strategy.version
+ assert v1 is not v2
+ assert d.getitem("a") == 1
+ assert d.strategy.getdictvalue_no_unwrapping(d, "a") == 1
- d.setitem("a", 1)
- assert d.getcell("a", False) is d.getcell("a", False)
- acell = d.getcell("a", False)
- d.setitem("b", 2)
- assert d.getcell("b", False) is d.getcell("b", False)
- assert d.getcell("c", True) is d.getcell("c", True)
+ d.setitem("a", 2)
+ v3 = strategy.version
+ assert v2 is not v3
+ assert d.getitem("a") == 2
+ assert d.strategy.getdictvalue_no_unwrapping(d, "a").w_value == 2
- assert d.getitem("a") == 1
- assert d.getitem("b") == 2
+ d.setitem("a", 3)
+ v4 = strategy.version
+ assert v3 is v4
+ assert d.getitem("a") == 3
+ assert d.strategy.getdictvalue_no_unwrapping(d, "a").w_value == 3
d.delitem("a")
- py.test.raises(KeyError, d.delitem, "a")
+ v5 = strategy.version
+ assert v5 is not v4
assert d.getitem("a") is None
- assert d.getcell("a", False) is acell
- assert d.length() == 1
+ assert d.strategy.getdictvalue_no_unwrapping(d, "a") is None
- d.clear()
- assert d.getitem("a") is None
- assert d.getcell("a", False) is acell
- assert d.length() == 0
+class AppTestModuleDict(object):
+ def setup_class(cls):
+ cls.space = gettestobjspace(**{"objspace.std.withcelldict": True})
+
+ def w_impl_used(self, obj):
+ if option.runappdirect:
+ py.test.skip("__repr__ doesn't work on appdirect")
+ import __pypy__
+ assert "ModuleDictStrategy" in __pypy__.internal_repr(obj)
+
+ def test_check_module_uses_module_dict(self):
+ m = type(__builtins__)("abc")
+ self.impl_used(m.__dict__)
+
+ def test_key_not_there(self):
+ d = type(__builtins__)("abc").__dict__
+ raises(KeyError, "d['def']")
+
+ def test_fallback_evil_key(self):
+ class F(object):
+ def __hash__(self):
+ return hash("s")
+ def __eq__(self, other):
+ return other == "s"
+ d = type(__builtins__)("abc").__dict__
+ d["s"] = 12
+ assert d["s"] == 12
+ assert d[F()] == d["s"]
+
+ d = type(__builtins__)("abc").__dict__
+ x = d.setdefault("s", 12)
+ assert x == 12
+ x = d.setdefault(F(), 12)
+ assert x == 12
+
+ d = type(__builtins__)("abc").__dict__
+ x = d.setdefault(F(), 12)
+ assert x == 12
+
+ d = type(__builtins__)("abc").__dict__
+ d["s"] = 12
+ del d[F()]
+
+ assert "s" not in d
+ assert F() not in d
+
+
+class TestModuleDictImplementation(BaseTestRDictImplementation):
+ StrategyClass = ModuleDictStrategy
+
+class TestModuleDictImplementationWithBuiltinNames(BaseTestRDictImplementation):
+ StrategyClass = ModuleDictStrategy
+
+ string = "int"
+ string2 = "isinstance"
+
+
+class TestDevolvedModuleDictImplementation(BaseTestDevolvedDictImplementation):
+ StrategyClass = ModuleDictStrategy
+
+class TestDevolvedModuleDictImplementationWithBuiltinNames(BaseTestDevolvedDictImplementation):
+ StrategyClass = ModuleDictStrategy
+
+ string = "int"
+ string2 = "isinstance"
+
class AppTestCellDict(object):
OPTIONS = {"objspace.std.withcelldict": True}
@@ -67,4 +136,4 @@
d["a"] = 3
del d["a"]
d[object()] = 5
- assert d.values() == [5]
\ No newline at end of file
+ assert d.values() == [5]
diff --git a/pypy/objspace/std/test/test_dictmultiobject.py b/pypy/objspace/std/test/test_dictmultiobject.py
--- a/pypy/objspace/std/test/test_dictmultiobject.py
+++ b/pypy/objspace/std/test/test_dictmultiobject.py
@@ -5,7 +5,6 @@
W_DictMultiObject, setitem__DictMulti_ANY_ANY, getitem__DictMulti_ANY, \
StringDictStrategy, ObjectDictStrategy
-from pypy.objspace.std.celldict import ModuleDictStrategy
from pypy.conftest import gettestobjspace
from pypy.conftest import option
@@ -731,52 +730,6 @@
set([('a', 1), ('b', 2), ('d', 4), ('e', 5)]))
-class AppTestModuleDict(object):
- def setup_class(cls):
- cls.space = gettestobjspace(**{"objspace.std.withcelldict": True})
- if option.runappdirect:
- py.test.skip("__repr__ doesn't work on appdirect")
-
- def w_impl_used(self, obj):
- import __pypy__
- assert "ModuleDictStrategy" in __pypy__.internal_repr(obj)
-
- def test_check_module_uses_module_dict(self):
- m = type(__builtins__)("abc")
- self.impl_used(m.__dict__)
-
- def test_key_not_there(self):
- d = type(__builtins__)("abc").__dict__
- raises(KeyError, "d['def']")
-
- def test_fallback_evil_key(self):
- class F(object):
- def __hash__(self):
- return hash("s")
- def __eq__(self, other):
- return other == "s"
- d = type(__builtins__)("abc").__dict__
- d["s"] = 12
- assert d["s"] == 12
- assert d[F()] == d["s"]
-
- d = type(__builtins__)("abc").__dict__
- x = d.setdefault("s", 12)
- assert x == 12
- x = d.setdefault(F(), 12)
- assert x == 12
-
- d = type(__builtins__)("abc").__dict__
- x = d.setdefault(F(), 12)
- assert x == 12
-
- d = type(__builtins__)("abc").__dict__
- d["s"] = 12
- del d[F()]
-
- assert "s" not in d
- assert F() not in d
-
class AppTestStrategies(object):
def setup_class(cls):
if option.runappdirect:
@@ -1071,16 +1024,6 @@
## ImplementionClass = MeasuringDictImplementation
## DevolvedClass = MeasuringDictImplementation
-class TestModuleDictImplementation(BaseTestRDictImplementation):
- StrategyClass = ModuleDictStrategy
-
-class TestModuleDictImplementationWithBuiltinNames(BaseTestRDictImplementation):
- StrategyClass = ModuleDictStrategy
-
- string = "int"
- string2 = "isinstance"
-
-
class BaseTestDevolvedDictImplementation(BaseTestRDictImplementation):
def fill_impl(self):
BaseTestRDictImplementation.fill_impl(self)
@@ -1092,15 +1035,6 @@
class TestDevolvedStrDictImplementation(BaseTestDevolvedDictImplementation):
StrategyClass = StringDictStrategy
-class TestDevolvedModuleDictImplementation(BaseTestDevolvedDictImplementation):
- StrategyClass = ModuleDictStrategy
-
-class TestDevolvedModuleDictImplementationWithBuiltinNames(BaseTestDevolvedDictImplementation):
- StrategyClass = ModuleDictStrategy
-
- string = "int"
- string2 = "isinstance"
-
def test_module_uses_strdict():
fakespace = FakeSpace()
diff --git a/pypy/objspace/test/test_descroperation.py b/pypy/objspace/test/test_descroperation.py
--- a/pypy/objspace/test/test_descroperation.py
+++ b/pypy/objspace/test/test_descroperation.py
@@ -377,7 +377,26 @@
setattr(P, "__weakref__", 0)
+ def test_subclass_addition(self):
+ # the __radd__ is never called (compare with the next test)
+ l = []
+ class A(object):
+ def __add__(self, other):
+ l.append(self.__class__)
+ l.append(other.__class__)
+ return 123
+ def __radd__(self, other):
+ # should never be called!
+ return 456
+ class B(A):
+ pass
+ res1 = A() + B()
+ res2 = B() + A()
+ assert res1 == res2 == 123
+ assert l == [A, B, B, A]
+
def test_subclass_comparison(self):
+ # the __eq__ *is* called with reversed arguments
l = []
class A(object):
def __eq__(self, other):
@@ -395,7 +414,27 @@
A() == B()
A() < B()
- assert l == [B, A, A, B]
+ B() < A()
+ assert l == [B, A, A, B, B, A]
+
+ def test_subclass_comparison_more(self):
+ # similarly, __gt__(b,a) is called instead of __lt__(a,b)
+ l = []
+ class A(object):
+ def __lt__(self, other):
+ l.append(self.__class__)
+ l.append(other.__class__)
+ return '<'
+ def __gt__(self, other):
+ l.append(self.__class__)
+ l.append(other.__class__)
+ return '>'
+ class B(A):
+ pass
+ res1 = A() < B()
+ res2 = B() < A()
+ assert res1 == '>' and res2 == '<'
+ assert l == [B, A, B, A]
def test_rich_comparison(self):
# Old-style
@@ -434,6 +473,84 @@
assert not(C(1) == D(2))
assert not(D(1) == C(2))
+ def test_partial_ordering(self):
+ class A(object):
+ def __lt__(self, other):
+ return self
+ a1 = A()
+ a2 = A()
+ assert (a1 < a2) is a1
+ assert (a1 > a2) is a2
+
+ def test_eq_order(self):
+ class A(object):
+ def __eq__(self, other): return self.__class__.__name__+':A.eq'
+ def __ne__(self, other): return self.__class__.__name__+':A.ne'
+ def __lt__(self, other): return self.__class__.__name__+':A.lt'
+ def __le__(self, other): return self.__class__.__name__+':A.le'
+ def __gt__(self, other): return self.__class__.__name__+':A.gt'
+ def __ge__(self, other): return self.__class__.__name__+':A.ge'
+ class B(object):
+ def __eq__(self, other): return self.__class__.__name__+':B.eq'
+ def __ne__(self, other): return self.__class__.__name__+':B.ne'
+ def __lt__(self, other): return self.__class__.__name__+':B.lt'
+ def __le__(self, other): return self.__class__.__name__+':B.le'
+ def __gt__(self, other): return self.__class__.__name__+':B.gt'
+ def __ge__(self, other): return self.__class__.__name__+':B.ge'
+ #
+ assert (A() == B()) == 'A:A.eq'
+ assert (A() != B()) == 'A:A.ne'
+ assert (A() < B()) == 'A:A.lt'
+ assert (A() <= B()) == 'A:A.le'
+ assert (A() > B()) == 'A:A.gt'
+ assert (A() >= B()) == 'A:A.ge'
+ #
+ assert (B() == A()) == 'B:B.eq'
+ assert (B() != A()) == 'B:B.ne'
+ assert (B() < A()) == 'B:B.lt'
+ assert (B() <= A()) == 'B:B.le'
+ assert (B() > A()) == 'B:B.gt'
+ assert (B() >= A()) == 'B:B.ge'
+ #
+ class C(A):
+ def __eq__(self, other): return self.__class__.__name__+':C.eq'
+ def __ne__(self, other): return self.__class__.__name__+':C.ne'
+ def __lt__(self, other): return self.__class__.__name__+':C.lt'
+ def __le__(self, other): return self.__class__.__name__+':C.le'
+ def __gt__(self, other): return self.__class__.__name__+':C.gt'
+ def __ge__(self, other): return self.__class__.__name__+':C.ge'
+ #
+ assert (A() == C()) == 'C:C.eq'
+ assert (A() != C()) == 'C:C.ne'
+ assert (A() < C()) == 'C:C.gt'
+ assert (A() <= C()) == 'C:C.ge'
+ assert (A() > C()) == 'C:C.lt'
+ assert (A() >= C()) == 'C:C.le'
+ #
+ assert (C() == A()) == 'C:C.eq'
+ assert (C() != A()) == 'C:C.ne'
+ assert (C() < A()) == 'C:C.lt'
+ assert (C() <= A()) == 'C:C.le'
+ assert (C() > A()) == 'C:C.gt'
+ assert (C() >= A()) == 'C:C.ge'
+ #
+ class D(A):
+ pass
+ #
+ assert (A() == D()) == 'D:A.eq'
+ assert (A() != D()) == 'D:A.ne'
+ assert (A() < D()) == 'D:A.gt'
+ assert (A() <= D()) == 'D:A.ge'
+ assert (A() > D()) == 'D:A.lt'
+ assert (A() >= D()) == 'D:A.le'
+ #
+ assert (D() == A()) == 'D:A.eq'
+ assert (D() != A()) == 'D:A.ne'
+ assert (D() < A()) == 'D:A.lt'
+ assert (D() <= A()) == 'D:A.le'
+ assert (D() > A()) == 'D:A.gt'
+ assert (D() >= A()) == 'D:A.ge'
+
def test_addition(self):
# Old-style
class A:
diff --git a/pypy/pytest-A-stackless.cfg b/pypy/pytest-A-stackless.cfg
deleted file mode 100644
--- a/pypy/pytest-A-stackless.cfg
+++ /dev/null
@@ -1,10 +0,0 @@
-# run for some directories a file at a time
-
-def collect_one_testdir(testdirs, reldir, tests):
- if (reldir.startswith('module/_stackless/') or
- reldir.startswith('lib')):
- testdirs.extend(tests)
- else:
- testdirs.append(reldir)
-
-
diff --git a/pypy/rlib/_rffi_stacklet.py b/pypy/rlib/_rffi_stacklet.py
new file mode 100644
--- /dev/null
+++ b/pypy/rlib/_rffi_stacklet.py
@@ -0,0 +1,49 @@
+import py
+from pypy.tool.autopath import pypydir
+from pypy.rpython.lltypesystem import lltype, llmemory, rffi
+from pypy.translator.tool.cbuild import ExternalCompilationInfo
+from pypy.rpython.tool import rffi_platform
+
+
+cdir = py.path.local(pypydir) / 'translator' / 'c'
+
+
+eci = ExternalCompilationInfo(
+ include_dirs = [cdir],
+ includes = ['src/stacklet/stacklet.h'],
+ separate_module_sources = ['#include "src/stacklet/stacklet.c"\n'],
+)
+rffi_platform.verify_eci(eci.convert_sources_to_files())
+
+def llexternal(name, args, result, **kwds):
+ return rffi.llexternal(name, args, result, compilation_info=eci,
+ _nowrapper=True, **kwds)
+
+# ----- types -----
+
+handle = rffi.COpaquePtr(typedef='stacklet_handle', compilation_info=eci)
+thread_handle = rffi.COpaquePtr(typedef='stacklet_thread_handle',
+ compilation_info=eci)
+run_fn = lltype.Ptr(lltype.FuncType([handle, llmemory.Address], handle))
+
+# ----- constants -----
+
+null_handle = lltype.nullptr(handle.TO)
+
+def is_empty_handle(h):
+ return rffi.cast(lltype.Signed, h) == -1
+
+# ----- functions -----
+
+newthread = llexternal('stacklet_newthread', [], thread_handle)
+deletethread = llexternal('stacklet_deletethread',[thread_handle], lltype.Void)
+
+new = llexternal('stacklet_new', [thread_handle, run_fn, llmemory.Address],
+ handle, random_effects_on_gcobjs=True)
+switch = llexternal('stacklet_switch', [thread_handle, handle], handle,
+ random_effects_on_gcobjs=True)
+destroy = llexternal('stacklet_destroy', [thread_handle, handle], lltype.Void)
+
+_translate_pointer = llexternal("_stacklet_translate_pointer",
+ [handle, llmemory.Address],
+ llmemory.Address)
diff --git a/pypy/rlib/_rsocket_rffi.py b/pypy/rlib/_rsocket_rffi.py
--- a/pypy/rlib/_rsocket_rffi.py
+++ b/pypy/rlib/_rsocket_rffi.py
@@ -489,10 +489,10 @@
getnameinfo = external('getnameinfo', [sockaddr_ptr, socklen_t, CCHARP,
size_t, CCHARP, size_t, rffi.INT], rffi.INT)
-htonl = external('htonl', [rffi.UINT], rffi.UINT)
-htons = external('htons', [rffi.USHORT], rffi.USHORT)
-ntohl = external('ntohl', [rffi.UINT], rffi.UINT)
-ntohs = external('ntohs', [rffi.USHORT], rffi.USHORT)
+htonl = external('htonl', [rffi.UINT], rffi.UINT, threadsafe=False)
+htons = external('htons', [rffi.USHORT], rffi.USHORT, threadsafe=False)
+ntohl = external('ntohl', [rffi.UINT], rffi.UINT, threadsafe=False)
+ntohs = external('ntohs', [rffi.USHORT], rffi.USHORT, threadsafe=False)
if _POSIX:
inet_aton = external('inet_aton', [CCHARP, lltype.Ptr(in_addr)],
diff --git a/pypy/rlib/_stacklet_asmgcc.py b/pypy/rlib/_stacklet_asmgcc.py
new file mode 100644
--- /dev/null
+++ b/pypy/rlib/_stacklet_asmgcc.py
@@ -0,0 +1,277 @@
+from pypy.rlib import _rffi_stacklet as _c
+from pypy.rlib.debug import ll_assert
+from pypy.rpython.lltypesystem import lltype, llmemory, rffi
+from pypy.rpython.lltypesystem.lloperation import llop
+from pypy.rpython.annlowlevel import llhelper
+
+
+_asmstackrootwalker = None # BIG HACK: monkey-patched by asmgcroot.py
+_stackletrootwalker = None
+
+def get_stackletrootwalker():
+ # lazily called, to make the following imports lazy
+ global _stackletrootwalker
+ if _stackletrootwalker is not None:
+ return _stackletrootwalker
+
+ from pypy.rpython.memory.gctransform.asmgcroot import (
+ WALKFRAME, CALLEE_SAVED_REGS, INDEX_OF_EBP, sizeofaddr)
+
+ assert _asmstackrootwalker is not None, "should have been monkey-patched"
+ basewalker = _asmstackrootwalker
+
+ class StackletRootWalker(object):
+ _alloc_flavor_ = "raw"
+
+ enumerating = False
+
+ def setup(self, obj):
+ # initialization: read the SUSPSTACK object
+ p = llmemory.cast_adr_to_ptr(obj, lltype.Ptr(SUSPSTACK))
+ if not p.handle:
+ return False
+ self.context = p.handle
+ anchor = p.anchor
+ del p
+ self.curframe = lltype.malloc(WALKFRAME, flavor='raw')
+ self.otherframe = lltype.malloc(WALKFRAME, flavor='raw')
+ self.fill_initial_frame(self.curframe, anchor)
+ return True
+
+ def fill_initial_frame(self, curframe, initialframedata):
+ # Copy&paste :-(
+ initialframedata += 2*sizeofaddr
+ reg = 0
+ while reg < CALLEE_SAVED_REGS:
+ curframe.regs_stored_at[reg] = initialframedata+reg*sizeofaddr
+ reg += 1
+ retaddraddr = initialframedata + CALLEE_SAVED_REGS * sizeofaddr
+ retaddraddr = self.translateptr(retaddraddr)
+ curframe.frame_address = retaddraddr.address[0]
+
+ def teardown(self):
+ lltype.free(self.curframe, flavor='raw')
+ lltype.free(self.otherframe, flavor='raw')
+ self.context = lltype.nullptr(_c.handle.TO)
+ return llmemory.NULL
+
+ def next(self, obj, prev):
+ #
+ # Pointers to the stack can be "translated" or not:
+ #
+ # * Non-translated pointers point to where the data would be
+ # if the stack was installed and running.
+ #
+ # * Translated pointers correspond to where the data
+ # is now really in memory.
+ #
+ # Note that 'curframe' contains non-translated pointers, and
+ # of course the stack itself is full of non-translated pointers.
+ #
+ while True:
+ if not self.enumerating:
+ if not prev:
+ if not self.setup(obj): # one-time initialization
+ return llmemory.NULL
+ prev = obj # random value, but non-NULL
+ callee = self.curframe
+ retaddraddr = self.translateptr(callee.frame_address)
+ retaddr = retaddraddr.address[0]
+ basewalker.locate_caller_based_on_retaddr(retaddr)
+ self.enumerating = True
+ #
+ # not really a loop, but kept this way for similarity
+ # with asmgcroot:
+ callee = self.curframe
+ ebp_in_caller = callee.regs_stored_at[INDEX_OF_EBP]
+ ebp_in_caller = self.translateptr(ebp_in_caller)
+ ebp_in_caller = ebp_in_caller.address[0]
+ while True:
+ location = basewalker._shape_decompressor.next()
+ if location == 0:
+ break
+ addr = basewalker.getlocation(callee, ebp_in_caller,
+ location)
+ # yield the translated addr of the next GCREF in the stack
+ return self.translateptr(addr)
+ #
+ self.enumerating = False
+ caller = self.otherframe
+ reg = CALLEE_SAVED_REGS - 1
+ while reg >= 0:
+ location = basewalker._shape_decompressor.next()
+ addr = basewalker.getlocation(callee, ebp_in_caller,
+ location)
+ caller.regs_stored_at[reg] = addr # non-translated
+ reg -= 1
+
+ location = basewalker._shape_decompressor.next()
+ caller.frame_address = basewalker.getlocation(callee,
+ ebp_in_caller,
+ location)
+ # ^^^ non-translated
+ if caller.frame_address == llmemory.NULL:
+ return self.teardown() # completely done with this stack
+ #
+ self.otherframe = callee
+ self.curframe = caller
+ # loop back
+
+ def translateptr(self, addr):
+ return _c._translate_pointer(self.context, addr)
+
+ _stackletrootwalker = StackletRootWalker()
+ return _stackletrootwalker
+get_stackletrootwalker._annspecialcase_ = 'specialize:memo'
+
+
+def customtrace(obj, prev):
+ stackletrootwalker = get_stackletrootwalker()
+ return stackletrootwalker.next(obj, prev)
+
+
+SUSPSTACK = lltype.GcStruct('SuspStack',
+ ('handle', _c.handle),
+ ('anchor', llmemory.Address),
+ rtti=True)
+NULL_SUSPSTACK = lltype.nullptr(SUSPSTACK)
+CUSTOMTRACEFUNC = lltype.FuncType([llmemory.Address, llmemory.Address],
+ llmemory.Address)
+customtraceptr = llhelper(lltype.Ptr(CUSTOMTRACEFUNC), customtrace)
+lltype.attachRuntimeTypeInfo(SUSPSTACK, customtraceptr=customtraceptr)
+
+ASM_FRAMEDATA_HEAD_PTR = lltype.Ptr(lltype.ForwardReference())
+ASM_FRAMEDATA_HEAD_PTR.TO.become(lltype.Struct('ASM_FRAMEDATA_HEAD',
+ ('prev', ASM_FRAMEDATA_HEAD_PTR),
+ ('next', ASM_FRAMEDATA_HEAD_PTR)
+ ))
+alternateanchor = lltype.malloc(ASM_FRAMEDATA_HEAD_PTR.TO,
+ immortal=True)
+alternateanchor.prev = alternateanchor
+alternateanchor.next = alternateanchor
+
+FUNCNOARG_P = lltype.Ptr(lltype.FuncType([], _c.handle))
+pypy_asm_stackwalk2 = rffi.llexternal('pypy_asm_stackwalk',
+ [FUNCNOARG_P,
+ ASM_FRAMEDATA_HEAD_PTR],
+ _c.handle, sandboxsafe=True,
+ _nowrapper=True)
+
+
+def _new_callback():
+ # Here, we just closed the stack. Get the stack anchor, store
+ # it in the gcrootfinder.suspstack.anchor, and create a new
+ # stacklet with stacklet_new(). If this call fails, then we
+ # are just returning NULL.
+ _stack_just_closed()
+ return _c.new(gcrootfinder.thrd, llhelper(_c.run_fn, _new_runfn),
+ llmemory.NULL)
+
+def _stack_just_closed():
+ # Immediately unlink the new stackanchor from the doubly-linked
+ # chained list. When returning from pypy_asm_stackwalk2, the
+ # assembler code will try to unlink it again, which should be
+ # a no-op given that the doubly-linked list is empty.
+ stackanchor = llmemory.cast_ptr_to_adr(alternateanchor.next)
+ gcrootfinder.suspstack.anchor = stackanchor
+ alternateanchor.prev = alternateanchor
+ alternateanchor.next = alternateanchor
+
+def _new_runfn(h, _):
+ # Here, we are in a fresh new stacklet.
+ llop.gc_stack_bottom(lltype.Void) # marker for trackgcroot.py
+ #
+ # There is a fresh suspstack object waiting on the gcrootfinder,
+ # so populate it with data that represents the parent suspended
+ # stacklet and detach the suspstack object from gcrootfinder.
+ suspstack = gcrootfinder.attach_handle_on_suspstack(h)
+ #
+ # Call the main function provided by the (RPython) user.
+ suspstack = gcrootfinder.runfn(suspstack, gcrootfinder.arg)
+ #
+ # Here, suspstack points to the target stacklet to which we want
+ # to jump to next. Read the 'handle' and forget about the
+ # suspstack object.
+ return _consume_suspstack(suspstack)
+
+def _consume_suspstack(suspstack):
+ h = suspstack.handle
+ ll_assert(bool(h), "_consume_suspstack: null handle")
+ suspstack.handle = _c.null_handle
+ return h
+
+def _switch_callback():
+ # Here, we just closed the stack. Get the stack anchor, store
+ # it in the gcrootfinder.suspstack.anchor, and switch to this
+ # suspstack with stacklet_switch(). If this call fails, then we
+ # are just returning NULL.
+ oldanchor = gcrootfinder.suspstack.anchor
+ _stack_just_closed()
+ h = _consume_suspstack(gcrootfinder.suspstack)
+ #
+ # gcrootfinder.suspstack.anchor is left with the anchor of the
+ # previous place (i.e. before the call to switch()).
+ h2 = _c.switch(gcrootfinder.thrd, h)
+ #
+ if not h2: # MemoryError: restore
+ gcrootfinder.suspstack.anchor = oldanchor
+ gcrootfinder.suspstack.handle = h
+ return h2
+
+
+class StackletGcRootFinder(object):
+ suspstack = NULL_SUSPSTACK
+
+ def new(self, thrd, callback, arg):
+ self.thrd = thrd._thrd
+ self.runfn = callback
+ self.arg = arg
+ # make a fresh new clean SUSPSTACK
+ newsuspstack = lltype.malloc(SUSPSTACK)
+ newsuspstack.handle = _c.null_handle
+ self.suspstack = newsuspstack
+ # Invoke '_new_callback' by closing the stack
+ h = pypy_asm_stackwalk2(llhelper(FUNCNOARG_P, _new_callback),
+ alternateanchor)
+ return self.get_result_suspstack(h)
+
+ def switch(self, thrd, suspstack):
+ self.thrd = thrd._thrd
+ self.suspstack = suspstack
+ h = pypy_asm_stackwalk2(llhelper(FUNCNOARG_P, _switch_callback),
+ alternateanchor)
+ return self.get_result_suspstack(h)
+
+ def attach_handle_on_suspstack(self, handle):
+ s = self.suspstack
+ self.suspstack = NULL_SUSPSTACK
+ ll_assert(bool(s.anchor), "s.anchor should not be null")
+ s.handle = handle
+ llop.gc_assume_young_pointers(lltype.Void, llmemory.cast_ptr_to_adr(s))
+ return s
+
+ def get_result_suspstack(self, h):
+ #
+ # Return from a new() or a switch(): 'h' is a handle, possibly
+ # an empty one, that says from where we switched to.
+ if not h:
+ raise MemoryError
+ elif _c.is_empty_handle(h):
+ return NULL_SUSPSTACK
+ else:
+ # This is a return that gave us a real handle. Store it.
+ return self.attach_handle_on_suspstack(h)
+
+ def destroy(self, thrd, suspstack):
+ h = suspstack.handle
+ suspstack.handle = _c.null_handle
+ _c.destroy(thrd._thrd, h)
+
+ def is_empty_handle(self, suspstack):
+ return not suspstack
+
+ def get_null_handle(self):
+ return NULL_SUSPSTACK
+
+
+gcrootfinder = StackletGcRootFinder()
diff --git a/pypy/rlib/_stacklet_n_a.py b/pypy/rlib/_stacklet_n_a.py
new file mode 100644
--- /dev/null
+++ b/pypy/rlib/_stacklet_n_a.py
@@ -0,0 +1,31 @@
+from pypy.rlib import _rffi_stacklet as _c
+from pypy.rpython.annlowlevel import llhelper
+from pypy.tool.staticmethods import StaticMethods
+
+
+class StackletGcRootFinder:
+ __metaclass__ = StaticMethods
+
+ def new(thrd, callback, arg):
+ h = _c.new(thrd._thrd, llhelper(_c.run_fn, callback), arg)
+ if not h:
+ raise MemoryError
+ return h
+ new._annspecialcase_ = 'specialize:arg(1)'
+
+ def switch(thrd, h):
+ h = _c.switch(thrd._thrd, h)
+ if not h:
+ raise MemoryError
+ return h
+
+ def destroy(thrd, h):
+ _c.destroy(thrd._thrd, h)
+
+ is_empty_handle = _c.is_empty_handle
+
+ def get_null_handle():
+ return _c.null_handle
+
+
+gcrootfinder = StackletGcRootFinder # class object
diff --git a/pypy/rlib/_stacklet_shadowstack.py b/pypy/rlib/_stacklet_shadowstack.py
new file mode 100644
--- /dev/null
+++ b/pypy/rlib/_stacklet_shadowstack.py
@@ -0,0 +1,108 @@
+from pypy.rlib import _rffi_stacklet as _c
+from pypy.rlib.debug import ll_assert
+from pypy.rpython.annlowlevel import llhelper
+from pypy.rpython.lltypesystem import lltype, llmemory
+from pypy.rpython.lltypesystem.lloperation import llop
+from pypy.tool.staticmethods import StaticMethods
+
+
+NULL_SUSPSTACK = lltype.nullptr(llmemory.GCREF.TO)
+
+
+def _new_callback(h, arg):
+ # We still have the old shadowstack active at this point; save it
+ # away, and start a fresh new one
+ oldsuspstack = gcrootfinder.oldsuspstack
+ llop.gc_save_current_state_away(lltype.Void,
+ oldsuspstack, h)
+ llop.gc_start_fresh_new_state(lltype.Void)
+ gcrootfinder.oldsuspstack = NULL_SUSPSTACK
+ #
+ newsuspstack = gcrootfinder.callback(oldsuspstack, arg)
+ #
+ # Finishing this stacklet.
+ gcrootfinder.oldsuspstack = NULL_SUSPSTACK
+ gcrootfinder.newsuspstack = newsuspstack
+ h = llop.gc_shadowstackref_context(llmemory.Address, newsuspstack)
+ return llmemory.cast_adr_to_ptr(h, _c.handle)
+
+def prepare_old_suspstack():
+ if not gcrootfinder.oldsuspstack: # else reuse the one still there
+ _allocate_old_suspstack()
+
+def _allocate_old_suspstack():
+ suspstack = llop.gc_shadowstackref_new(llmemory.GCREF)
+ gcrootfinder.oldsuspstack = suspstack
+_allocate_old_suspstack._dont_inline_ = True
+
+def get_result_suspstack(h):
+ # Now we are in the target, after the switch() or the new().
+ # Note that this whole module was carefully written in such a way as
+ # not to invoke pushing/popping things off the shadowstack at
+ # unexpected moments...
+ oldsuspstack = gcrootfinder.oldsuspstack
+ newsuspstack = gcrootfinder.newsuspstack
+ gcrootfinder.oldsuspstack = NULL_SUSPSTACK
+ gcrootfinder.newsuspstack = NULL_SUSPSTACK
+ if not h:
+ raise MemoryError
+ # We still have the old shadowstack active at this point; save it
+ # away, and restore the new one
+ if oldsuspstack:
+ ll_assert(not _c.is_empty_handle(h),"unexpected empty stacklet handle")
+ llop.gc_save_current_state_away(lltype.Void, oldsuspstack, h)
+ else:
+ ll_assert(_c.is_empty_handle(h),"unexpected non-empty stacklet handle")
+ llop.gc_forget_current_state(lltype.Void)
+ #
+ llop.gc_restore_state_from(lltype.Void, newsuspstack)
+ #
+ # From this point on, 'newsuspstack' is consumed and done, its
+ # shadow stack installed as the current one. It should not be
+ # used any more. For performance, we avoid it being deallocated
+ # by letting it be reused on the next switch.
+ gcrootfinder.oldsuspstack = newsuspstack
+ # Return.
+ return oldsuspstack
+
+
+class StackletGcRootFinder:
+ __metaclass__ = StaticMethods
+
+ def new(thrd, callback, arg):
+ gcrootfinder.callback = callback
+ thread_handle = thrd._thrd
+ prepare_old_suspstack()
+ h = _c.new(thread_handle, llhelper(_c.run_fn, _new_callback), arg)
+ return get_result_suspstack(h)
+ new._dont_inline_ = True
+
+ def switch(thrd, suspstack):
+ # suspstack has a handle to target, i.e. where to switch to
+ ll_assert(suspstack != gcrootfinder.oldsuspstack,
+ "stacklet: invalid use")
+ gcrootfinder.newsuspstack = suspstack
+ thread_handle = thrd._thrd
+ h = llop.gc_shadowstackref_context(llmemory.Address, suspstack)
+ h = llmemory.cast_adr_to_ptr(h, _c.handle)
+ prepare_old_suspstack()
+ h = _c.switch(thread_handle, h)
+ return get_result_suspstack(h)
+ switch._dont_inline_ = True
+
+ def destroy(thrd, suspstack):
+ h = llop.gc_shadowstackref_context(llmemory.Address, suspstack)
+ h = llmemory.cast_adr_to_ptr(h, _c.handle)
+ llop.gc_shadowstackref_destroy(lltype.Void, suspstack)
+ _c.destroy(thrd._thrd, h)
+
+ def is_empty_handle(suspstack):
+ return not suspstack
+
+ def get_null_handle():
+ return NULL_SUSPSTACK
+
+
+gcrootfinder = StackletGcRootFinder()
+gcrootfinder.oldsuspstack = NULL_SUSPSTACK
+gcrootfinder.newsuspstack = NULL_SUSPSTACK
diff --git a/pypy/rlib/debug.py b/pypy/rlib/debug.py
--- a/pypy/rlib/debug.py
+++ b/pypy/rlib/debug.py
@@ -26,6 +26,7 @@
llop.debug_print_traceback(lltype.Void)
llop.debug_fatalerror(lltype.Void, msg)
fatalerror._dont_inline_ = True
+fatalerror._annspecialcase_ = 'specialize:arg(1)'
class DebugLog(list):
diff --git a/pypy/rlib/parsing/makepackrat.py b/pypy/rlib/parsing/makepackrat.py
--- a/pypy/rlib/parsing/makepackrat.py
+++ b/pypy/rlib/parsing/makepackrat.py
@@ -251,9 +251,11 @@
return "ErrorInformation(%s, %s)" % (self.pos, self.expected)
def get_line_column(self, source):
- uptoerror = source[:self.pos]
+ pos = self.pos
+ assert pos >= 0
+ uptoerror = source[:pos]
lineno = uptoerror.count("\n")
- columnno = self.pos - uptoerror.rfind("\n")
+ columnno = pos - uptoerror.rfind("\n")
return lineno, columnno
def nice_error_message(self, filename='<filename>', source=""):
diff --git a/pypy/rlib/rcoroutine.py b/pypy/rlib/rcoroutine.py
--- a/pypy/rlib/rcoroutine.py
+++ b/pypy/rlib/rcoroutine.py
@@ -29,6 +29,11 @@
The type of a switch is determined by the target's costate.
"""
+import py; py.test.skip("fixme: rewrite using rlib.rstacklet")
+# XXX ^^^ the reason it is not done is that pypy.rlib.rcoroutine
+# plus pypy/module/_stackless look like faaaaaar too much code
+# to me :-(
+
from pypy.rlib.rstack import yield_current_frame_to_caller
from pypy.rlib.objectmodel import we_are_translated
diff --git a/pypy/rlib/rgc.py b/pypy/rlib/rgc.py
--- a/pypy/rlib/rgc.py
+++ b/pypy/rlib/rgc.py
@@ -15,132 +15,8 @@
pass
# ____________________________________________________________
-# Framework GC features
-
-class GcPool(object):
- pass
-
-def gc_swap_pool(newpool):
- """Set newpool as the current pool (create one if newpool is None).
- All malloc'ed objects are put into the current pool;this is a
- way to separate objects depending on when they were allocated.
- """
- raise NotImplementedError("only works in stacklessgc translated versions")
-
-def gc_clone(gcobject, pool):
- """Recursively clone the gcobject and everything it points to,
- directly or indirectly -- but stops at objects that are not
- in the specified pool. Pool can be None to mean the current one.
- A new pool is built to contain the copies. Return (newobject, newpool).
- """
- raise NotImplementedError("only works in stacklessgc translated versions")
-
-# ____________________________________________________________
# Annotation and specialization
-class GcPoolEntry(ExtRegistryEntry):
- "Link GcPool to its Repr."
- _type_ = GcPool
-
- def get_repr(self, rtyper, s_pool):
- config = rtyper.getconfig()
- # if the gc policy doesn't support allocation pools, lltype
- # pools as Void.
- if config.translation.gc != 'marksweep':
- from pypy.annotation.model import s_None
- return rtyper.getrepr(s_None)
- else:
- from pypy.rpython.rmodel import SimplePointerRepr
- from pypy.rpython.memory.gc.marksweep import X_POOL_PTR
- return SimplePointerRepr(X_POOL_PTR)
-
-
-class SwapPoolFnEntry(ExtRegistryEntry):
- "Annotation and specialization of gc_swap_pool()."
- _about_ = gc_swap_pool
-
- def compute_result_annotation(self, s_newpool):
- from pypy.annotation import model as annmodel
- return annmodel.SomeExternalObject(GcPool)
-
- def specialize_call(self, hop):
- from pypy.annotation import model as annmodel
- s_pool_ptr = annmodel.SomeExternalObject(GcPool)
- r_pool_ptr = hop.rtyper.getrepr(s_pool_ptr)
-
- opname = 'gc_x_swap_pool'
- config = hop.rtyper.getconfig()
- if config.translation.gc != 'marksweep':
- # when the gc policy doesn't support pools, just return
- # the argument (which is lltyped as Void anyway)
- opname = 'same_as'
-
- s_pool_ptr = annmodel.SomeExternalObject(GcPool)
- r_pool_ptr = hop.rtyper.getrepr(s_pool_ptr)
- vlist = hop.inputargs(r_pool_ptr)
- return hop.genop(opname, vlist, resulttype = r_pool_ptr)
-
-def _raise():
- raise RuntimeError
-
-class CloneFnEntry(ExtRegistryEntry):
- "Annotation and specialization of gc_clone()."
- _about_ = gc_clone
-
- def compute_result_annotation(self, s_gcobject, s_pool):
- from pypy.annotation import model as annmodel
- return annmodel.SomeTuple([s_gcobject,
- annmodel.SomeExternalObject(GcPool)])
-
- def specialize_call(self, hop):
- from pypy.rpython.error import TyperError
- from pypy.rpython.lltypesystem import rtuple
- from pypy.annotation import model as annmodel
- from pypy.rpython.memory.gc.marksweep import X_CLONE, X_CLONE_PTR
-
- config = hop.rtyper.getconfig()
- if config.translation.gc != 'marksweep':
- # if the gc policy does not support allocation pools,
- # gc_clone always raises RuntimeError
- hop.exception_is_here()
- hop.gendirectcall(_raise)
- s_pool_ptr = annmodel.SomeExternalObject(GcPool)
- r_pool_ptr = hop.rtyper.getrepr(s_pool_ptr)
- r_tuple = hop.r_result
- v_gcobject, v_pool = hop.inputargs(hop.args_r[0], r_pool_ptr)
- return rtuple.newtuple(hop.llops, r_tuple, [v_gcobject, v_pool])
-
- r_gcobject = hop.args_r[0]
- if (not isinstance(r_gcobject.lowleveltype, lltype.Ptr) or
- r_gcobject.lowleveltype.TO._gckind != 'gc'):
- raise TyperError("gc_clone() can only clone a dynamically "
- "allocated object;\ngot %r" % (r_gcobject,))
- s_pool_ptr = annmodel.SomeExternalObject(GcPool)
- r_pool_ptr = hop.rtyper.getrepr(s_pool_ptr)
- r_tuple = hop.r_result
-
- c_CLONE = hop.inputconst(lltype.Void, X_CLONE)
- c_flags = hop.inputconst(lltype.Void, {'flavor': 'gc'})
- c_gcobjectptr = hop.inputconst(lltype.Void, "gcobjectptr")
- c_pool = hop.inputconst(lltype.Void, "pool")
-
- v_gcobject, v_pool = hop.inputargs(hop.args_r[0], r_pool_ptr)
- v_gcobjectptr = hop.genop('cast_opaque_ptr', [v_gcobject],
- resulttype = llmemory.GCREF)
- v_clonedata = hop.genop('malloc', [c_CLONE, c_flags],
- resulttype = X_CLONE_PTR)
- hop.genop('setfield', [v_clonedata, c_gcobjectptr, v_gcobjectptr])
- hop.genop('setfield', [v_clonedata, c_pool, v_pool])
- hop.exception_is_here()
- hop.genop('gc_x_clone', [v_clonedata])
- v_gcobjectptr = hop.genop('getfield', [v_clonedata, c_gcobjectptr],
- resulttype = llmemory.GCREF)
- v_pool = hop.genop('getfield', [v_clonedata, c_pool],
- resulttype = r_pool_ptr)
- v_gcobject = hop.genop('cast_opaque_ptr', [v_gcobjectptr],
- resulttype = r_tuple.items_r[0])
- return rtuple.newtuple(hop.llops, r_tuple, [v_gcobject, v_pool])
-
# Support for collection.
class CollectEntry(ExtRegistryEntry):
diff --git a/pypy/rlib/rstack.py b/pypy/rlib/rstack.py
--- a/pypy/rlib/rstack.py
+++ b/pypy/rlib/rstack.py
@@ -14,25 +14,6 @@
from pypy.rpython.controllerentry import Controller, SomeControlledInstance
from pypy.translator.tool.cbuild import ExternalCompilationInfo
-def stack_unwind():
- if we_are_translated():
- return llop.stack_unwind(lltype.Void)
- raise RuntimeError("cannot unwind stack in non-translated versions")
-
-
-def stack_capture():
- if we_are_translated():
- ptr = llop.stack_capture(OPAQUE_STATE_HEADER_PTR)
- return frame_stack_top_controller.box(ptr)
- raise RuntimeError("cannot unwind stack in non-translated versions")
-
-
-def stack_frames_depth():
- if we_are_translated():
- return llop.stack_frames_depth(lltype.Signed)
- else:
- return len(inspect.stack())
-
# ____________________________________________________________
compilation_info = ExternalCompilationInfo(includes=['src/stack.h'])
@@ -88,78 +69,6 @@
@rgc.no_collect
def stack_check_slowpath(current):
if ord(_stack_too_big_slowpath(current)):
- # Now we are sure that the stack is really too big. Note that the
- # stack_unwind implementation is different depending on if stackless
- # is enabled. If it is it unwinds the stack, otherwise it simply
- # raises a RuntimeError.
- stack_unwind()
+ from pypy.rlib.rstackovf import _StackOverflow
+ raise _StackOverflow
stack_check_slowpath._dont_inline_ = True
-
-# ____________________________________________________________
-
-def yield_current_frame_to_caller():
- raise NotImplementedError("only works in translated versions")
-
-
-class frame_stack_top(object):
- def switch(self):
- raise NotImplementedError("only works in translated versions")
-
-
-class BoundSwitchOfFrameStackTop(object): pass
-class BoundSwitchOfFrameStackTopController(Controller):
- knowntype = BoundSwitchOfFrameStackTop
- def call(self, real_object):
- from pypy.rpython.lltypesystem.lloperation import llop
- ptr = llop.stack_switch(OPAQUE_STATE_HEADER_PTR, real_object)
- return frame_stack_top_controller.box(ptr)
-
-
-class FrameStackTopController(Controller):
- knowntype = frame_stack_top
- can_be_None = True
-
- def is_true(self, real_object):
- return bool(real_object)
-
- def get_switch(self, real_object):
- return bound_switch_of_frame_stack_top_controller.box(real_object)
-
- def convert(self, obj):
- assert obj is None
- return lltype.nullptr(OPAQUE_STATE_HEADER_PTR.TO)
-
-frame_stack_top_controller = FrameStackTopController()
-bound_switch_of_frame_stack_top_controller = BoundSwitchOfFrameStackTopController()
-OPAQUE_STATE_HEADER = lltype.GcOpaqueType("OPAQUE_STATE_HEADER", hints={"render_structure": True})
-OPAQUE_STATE_HEADER_PTR = lltype.Ptr(OPAQUE_STATE_HEADER)
-
-
-
-class FrameStackTopReturningFnEntry(ExtRegistryEntry):
- def compute_result_annotation(self):
- from pypy.annotation import model as annmodel
- return SomeControlledInstance(annmodel.lltype_to_annotation(OPAQUE_STATE_HEADER_PTR), frame_stack_top_controller)
-
-
-class YieldCurrentFrameToCallerFnEntry(FrameStackTopReturningFnEntry):
- _about_ = yield_current_frame_to_caller
-
- def specialize_call(self, hop):
- var = hop.genop("yield_current_frame_to_caller", [], hop.r_result.lowleveltype)
- return var
-
-
-# ____________________________________________________________
-
-def get_stack_depth_limit():
- if we_are_translated():
- from pypy.rpython.lltypesystem.lloperation import llop
- return llop.get_stack_depth_limit(lltype.Signed)
- raise RuntimeError("no stack depth limit in non-translated versions")
-
-def set_stack_depth_limit(limit):
- if we_are_translated():
- from pypy.rpython.lltypesystem.lloperation import llop
- return llop.set_stack_depth_limit(lltype.Void, limit)
- raise RuntimeError("no stack depth limit in non-translated versions")
diff --git a/pypy/rlib/rstacklet.py b/pypy/rlib/rstacklet.py
new file mode 100644
--- /dev/null
+++ b/pypy/rlib/rstacklet.py
@@ -0,0 +1,58 @@
+from pypy.rlib import _rffi_stacklet as _c
+from pypy.rpython.lltypesystem import lltype, llmemory
+
+
+class StackletThread(object):
+
+ def __init__(self, config):
+ self._gcrootfinder = _getgcrootfinder(config)
+ self._thrd = _c.newthread()
+ if not self._thrd:
+ raise MemoryError
+ self._thrd_deleter = StackletThreadDeleter(self._thrd)
+
+ def new(self, callback, arg=llmemory.NULL):
+ return self._gcrootfinder.new(self, callback, arg)
+ new._annspecialcase_ = 'specialize:arg(1)'
+
+ def switch(self, stacklet):
+ return self._gcrootfinder.switch(self, stacklet)
+
+ def destroy(self, stacklet):
+ self._gcrootfinder.destroy(self, stacklet)
+
+ def is_empty_handle(self, stacklet):
+ # note that "being an empty handle" and being equal to
+ # "get_null_handle()" may be the same, or not; don't rely on it
+ return self._gcrootfinder.is_empty_handle(stacklet)
+
+ def get_null_handle(self):
+ return self._gcrootfinder.get_null_handle()
+
+
+class StackletThreadDeleter(object):
+ # quick hack: the __del__ is on another object, so that
+ # if the main StackletThread ends up in random circular
+ # references, on pypy deletethread() is only called
+ # when all that circular reference mess is gone.
+ def __init__(self, thrd):
+ self._thrd = thrd
+ def __del__(self):
+ thrd = self._thrd
+ if thrd:
+ self._thrd = lltype.nullptr(_c.thread_handle.TO)
+ _c.deletethread(thrd)
+
+# ____________________________________________________________
+
+def _getgcrootfinder(config):
+ if (config is None or
+ config.translation.gc in ('ref', 'boehm', 'none')): # for tests
+ gcrootfinder = 'n/a'
+ else:
+ gcrootfinder = config.translation.gcrootfinder
+ gcrootfinder = gcrootfinder.replace('/', '_')
+ module = __import__('pypy.rlib._stacklet_%s' % gcrootfinder,
+ None, None, ['__doc__'])
+ return module.gcrootfinder
+_getgcrootfinder._annspecialcase_ = 'specialize:memo'
diff --git a/pypy/rlib/rstruct/standardfmttable.py b/pypy/rlib/rstruct/standardfmttable.py
--- a/pypy/rlib/rstruct/standardfmttable.py
+++ b/pypy/rlib/rstruct/standardfmttable.py
@@ -198,8 +198,8 @@
@specialize.argtype(0)
def unpack_int(fmtiter):
intvalue = inttype(0)
- s = fmtiter.read(size)
- idx = 0
+ s = fmtiter.input
+ idx = fmtiter.get_pos_and_advance(size)
if fmtiter.bigendian:
for i in unroll_range_size:
x = ord(s[idx])
diff --git a/pypy/rlib/streamio.py b/pypy/rlib/streamio.py
--- a/pypy/rlib/streamio.py
+++ b/pypy/rlib/streamio.py
@@ -496,29 +496,24 @@
if bufsize == -1: # Get default from the class
bufsize = self.bufsize
self.bufsize = bufsize # buffer size (hint only)
- self.lines = [] # ready-made lines (sans "\n")
- self.buf = "" # raw data (may contain "\n")
- # Invariant: readahead == "\n".join(self.lines + [self.buf])
- # self.lines contains no "\n"
- # self.buf may contain "\n"
+ self.buf = "" # raw data
+ self.pos = 0
def flush_buffers(self):
- if self.lines or self.buf:
+ if self.buf:
try:
self.do_seek(self.tell(), 0)
except MyNotImplementedError:
pass
else:
- self.lines = []
self.buf = ""
+ self.pos = 0
def tell(self):
- bytes = self.do_tell() # This may fail
- offset = len(self.buf)
- for line in self.lines:
- offset += len(line) + 1
- assert bytes >= offset #, (locals(), self.__dict__)
- return bytes - offset
+ tellpos = self.do_tell() # This may fail
+ offset = len(self.buf) - self.pos
+ assert tellpos >= offset #, (locals(), self.__dict__)
+ return tellpos - offset
def seek(self, offset, whence):
# This may fail on the do_seek() or do_tell() call.
@@ -526,32 +521,25 @@
# Nor on a seek to the very end.
if whence == 0:
self.do_seek(offset, 0)
- self.lines = []
self.buf = ""
+ self.pos = 0
return
if whence == 1:
+ currentsize = len(self.buf) - self.pos
if offset < 0:
- self.do_seek(self.tell() + offset, 0)
- self.lines = []
- self.buf = ""
+ if self.pos + offset >= 0:
+ self.pos += offset
+ else:
+ self.do_seek(self.tell() + offset, 0)
+ self.pos = 0
+ self.buf = ""
return
- while self.lines:
- line = self.lines[-1]
- if offset <= len(line):
- intoffset = intmask(offset)
- assert intoffset >= 0
- self.lines[-1] = line[intoffset:]
- return
- offset -= len(self.lines[-1]) - 1
- self.lines.pop()
- assert not self.lines
- if offset <= len(self.buf):
- intoffset = intmask(offset)
- assert intoffset >= 0
- self.buf = self.buf[intoffset:]
+ elif offset <= currentsize:
+ self.pos += offset
return
- offset -= len(self.buf)
self.buf = ""
+ self.pos = 0
+ offset -= currentsize
try:
self.do_seek(offset, 1)
except MyNotImplementedError:
@@ -564,18 +552,18 @@
except MyNotImplementedError:
pass
else:
- self.lines = []
+ self.pos = 0
self.buf = ""
return
# Skip relative to EOF by reading and saving only just as
# much as needed
intoffset = offset2int(offset)
- self.lines.reverse()
- data = "\n".join(self.lines + [self.buf])
- total = len(data)
- buffers = [data]
- self.lines = []
+ pos = self.pos
+ assert pos >= 0
+ buffers = [self.buf[pos:]]
+ total = len(buffers[0])
self.buf = ""
+ self.pos = 0
while 1:
data = self.do_read(self.bufsize)
if not data:
@@ -589,157 +577,101 @@
if cutoff < 0:
raise StreamError("cannot seek back")
if buffers:
+ assert cutoff >= 0
buffers[0] = buffers[0][cutoff:]
self.buf = "".join(buffers)
- self.lines = []
return
+
raise StreamError("whence should be 0, 1 or 2")
def readall(self):
- self.lines.reverse()
- self.lines.append(self.buf)
- more = ["\n".join(self.lines)]
- self.lines = []
+ pos = self.pos
+ assert pos >= 0
+ chunks = [self.buf[pos:]]
self.buf = ""
+ self.pos = 0
bufsize = self.bufsize
while 1:
data = self.do_read(bufsize)
if not data:
break
- more.append(data)
+ chunks.append(data)
bufsize = min(bufsize*2, self.bigsize)
- return "".join(more)
+ return "".join(chunks)
- def read(self, n):
+ def read(self, n=-1):
assert isinstance(n, int)
- assert n >= 0
- if self.lines:
- # See if this can be satisfied from self.lines[0]
- line = self.lines[-1]
- if len(line) >= n:
- self.lines[-1] = line[n:]
- return line[:n]
-
- # See if this can be satisfied *without exhausting* self.lines
- k = 0
- i = 0
- lgt = len(self.lines)
- for linenum in range(lgt-1,-1,-1):
- line = self.lines[linenum]
- k += len(line)
- if k >= n:
- lines = self.lines[linenum + 1:]
- data = self.lines[linenum]
- cutoff = len(data) - (k-n)
- assert cutoff >= 0
- lines.reverse()
- lines.append(data[:cutoff])
- del self.lines[linenum:]
- self.lines.append(data[cutoff:])
- return "\n".join(lines)
- k += 1
-
- # See if this can be satisfied from self.lines plus self.buf
- if k + len(self.buf) >= n:
- lines = self.lines
- lines.reverse()
- self.lines = []
- cutoff = n - k
- assert cutoff >= 0
- lines.append(self.buf[:cutoff])
- self.buf = self.buf[cutoff:]
- return "\n".join(lines)
-
+ if n < 0:
+ return self.readall()
+ currentsize = len(self.buf) - self.pos
+ start = self.pos
+ assert start >= 0
+ if n <= currentsize:
+ stop = start + n
+ assert stop >= 0
+ result = self.buf[start:stop]
+ self.pos += n
+ return result
else:
- # See if this can be satisfied from self.buf
- data = self.buf
- k = len(data)
- if k >= n:
- cutoff = len(data) - (k-n)
- assert cutoff >= 0
- assert len(data) >= cutoff
- self.buf = data[cutoff:]
- return data[:cutoff]
-
- lines = self.lines
- lines.reverse()
- self.lines = []
- lines.append(self.buf)
- self.buf = ""
- data = "\n".join(lines)
- more = [data]
- k = len(data)
- while k < n:
- data = self.do_read(max(self.bufsize, n-k))
- k += len(data)
- more.append(data)
- if not data:
- break
- cutoff = len(data) - (k-n)
- assert cutoff >= 0
- if len(data) <= cutoff:
- self.buf = ""
- else:
- self.buf = data[cutoff:]
- more[-1] = data[:cutoff]
- return "".join(more)
-
- # read_next_bunch is generally this, version below is slightly faster
- #def _read_next_bunch(self):
- # self.lines = self.buf.split("\n")
- # self.buf = self.lines.pop()
- # self.lines.reverse()
-
- def _read_next_bunch(self):
- numlines = self.buf.count("\n")
- self.lines = [None] * numlines
- last = -1
- num = numlines - 1
- while True:
- start = last + 1
- assert start >= 0
- next = self.buf.find("\n", start)
- if next == -1:
- if last != -1:
- self.buf = self.buf[start:]
- break
- assert next >= 0
- self.lines[num] = self.buf[start:next]
- last = next
- num -= 1
+ chunks = [self.buf[start:]]
+ while 1:
+ self.buf = self.do_read(self.bufsize)
+ if not self.buf:
+ self.pos = 0
+ break
+ currentsize += len(self.buf)
+ if currentsize >= n:
+ self.pos = len(self.buf) - (currentsize - n)
+ stop = self.pos
+ assert stop >= 0
+ chunks.append(self.buf[:stop])
+ break
+ chunks.append(self.buf)
+ return ''.join(chunks)
def readline(self):
- if self.lines:
- return self.lines.pop() + "\n"
-
- # This block is needed because read() can leave self.buf
- # containing newlines
- self._read_next_bunch()
- if self.lines:
- return self.lines.pop() + "\n"
-
- if self.buf:
- buf = [self.buf]
- else:
- buf = []
+ pos = self.pos
+ assert pos >= 0
+ i = self.buf.find("\n", pos)
+ start = self.pos
+ assert start >= 0
+ if i >= 0: # new line found
+ i += 1
+ result = self.buf[start:i]
+ self.pos = i
+ return result
+ temp = self.buf[start:]
+ # read one buffer and most of the time a new line will be found
+ self.buf = self.do_read(self.bufsize)
+ i = self.buf.find("\n")
+ if i >= 0: # new line found
+ i += 1
+ result = temp + self.buf[:i]
+ self.pos = i
+ return result
+ if not self.buf:
+ self.pos = 0
+ return temp
+ # need to keep getting data until we find a new line
+ chunks = [temp, self.buf]
while 1:
self.buf = self.do_read(self.bufsize)
- self._read_next_bunch()
- if self.lines:
- buf.append(self.lines.pop())
- buf.append("\n")
+ if not self.buf:
+ self.pos = 0
break
- if not self.buf:
+ i = self.buf.find("\n")
+ if i >= 0:
+ i += 1
+ chunks.append(self.buf[:i])
+ self.pos = i
break
- buf.append(self.buf)
-
- return "".join(buf)
+ chunks.append(self.buf)
+ return "".join(chunks)
def peek(self):
- if self.lines:
- return self.lines[-1] + "\n"
- else:
- return self.buf
+ pos = self.pos
+ assert pos >= 0
+ return self.buf[pos:]
write = PassThrough("write", flush_buffers=True)
truncate = PassThrough("truncate", flush_buffers=True)
diff --git a/pypy/rlib/test/test_rstacklet.py b/pypy/rlib/test/test_rstacklet.py
new file mode 100644
--- /dev/null
+++ b/pypy/rlib/test/test_rstacklet.py
@@ -0,0 +1,272 @@
+import gc
+import py
+from pypy.rpython.tool.rffi_platform import CompilationError
+try:
+ from pypy.rlib import rstacklet
+except CompilationError, e:
+ py.test.skip("cannot import rstacklet: %s" % e)
+
+from pypy.rlib import rrandom
+from pypy.rlib.rarithmetic import intmask
+from pypy.rpython.lltypesystem import lltype, llmemory, rffi
+from pypy.translator.c.test.test_standalone import StandaloneTests
+
+
+
+class Runner:
+ STATUSMAX = 5000
+ config = None
+
+ def init(self, seed):
+ self.sthread = rstacklet.StackletThread(self.config)
+ self.random = rrandom.Random(seed)
+
+ def done(self):
+ self.sthread = None
+ gc.collect(); gc.collect(); gc.collect()
+
+ TESTS = []
+ def here_is_a_test(fn, TESTS=TESTS):
+ TESTS.append((fn.__name__, fn))
+ return fn
+
+ @here_is_a_test
+ def test_new(self):
+ print 'start'
+ h = self.sthread.new(empty_callback, rffi.cast(llmemory.Address, 123))
+ print 'end', h
+ assert self.sthread.is_empty_handle(h)
+
+ def nextstatus(self, nextvalue):
+ print 'expected nextvalue to be %d, got %d' % (nextvalue,
+ self.status + 1)
+ assert self.status + 1 == nextvalue
+ self.status = nextvalue
+
+ @here_is_a_test
+ def test_simple_switch(self):
+ self.status = 0
+ h = self.sthread.new(switchbackonce_callback,
+ rffi.cast(llmemory.Address, 321))
+ assert not self.sthread.is_empty_handle(h)
+ self.nextstatus(2)
+ h = self.sthread.switch(h)
+ self.nextstatus(4)
+ print 'end', h
+ assert self.sthread.is_empty_handle(h)
+
+ @here_is_a_test
+ def test_various_depths(self):
+ self.tasks = [Task(i) for i in range(10)]
+ self.nextstep = -1
+ self.comefrom = -1
+ self.status = 0
+ while self.status < self.STATUSMAX or self.any_alive():
+ self.tasks[0].withdepth(self.random.genrand32() % 50)
+ assert len(self.tasks[0].lst) == 0
+
+ def any_alive(self):
+ for task in self.tasks:
+ if task.h:
+ return True
+ return False
+
+
+class FooObj:
+ def __init__(self, n, d, next=None):
+ self.n = n
+ self.d = d
+ self.next = next
+
+
+class Task:
+ def __init__(self, n):
+ self.n = n
+ self.h = runner.sthread.get_null_handle()
+ self.lst = []
+
+ def withdepth(self, d):
+ if d > 0:
+ foo = FooObj(self.n, d)
+ foo2 = FooObj(self.n + 100, d, foo)
+ self.lst.append(foo)
+ res = self.withdepth(d-1)
+ foo = self.lst.pop()
+ assert foo2.n == self.n + 100
+ assert foo2.d == d
+ assert foo2.next is foo
+ assert foo.n == self.n
+ assert foo.d == d
+ assert foo.next is None
+ else:
+ res = 0
+ n = intmask(runner.random.genrand32() % 10)
+ if n == self.n or (runner.status >= runner.STATUSMAX and
+ not runner.tasks[n].h):
+ return 1
+
+ print "status == %d, self.n = %d" % (runner.status, self.n)
+ assert not self.h
+ assert runner.nextstep == -1
+ runner.status += 1
+ runner.nextstep = runner.status
+ runner.comefrom = self.n
+ runner.gointo = n
+ task = runner.tasks[n]
+ if not task.h:
+ # start a new stacklet
+ print "NEW", n
+ h = runner.sthread.new(variousstackdepths_callback,
+ rffi.cast(llmemory.Address, n))
+ else:
+ # switch to this stacklet
+ print "switch to", n
+ h = task.h
+ task.h = runner.sthread.get_null_handle()
+ h = runner.sthread.switch(h)
+
+ print "back in self.n = %d, coming from %d" % (self.n,
+ runner.comefrom)
+ assert runner.nextstep == runner.status
+ runner.nextstep = -1
+ assert runner.gointo == self.n
+ assert runner.comefrom != self.n
+ assert not self.h
+ if runner.comefrom != -42:
+ assert 0 <= runner.comefrom < 10
+ task = runner.tasks[runner.comefrom]
+ assert not task.h
+ task.h = h
+ else:
+ assert runner.sthread.is_empty_handle(h)
+ runner.comefrom = -1
+ runner.gointo = -1
+ assert (res & (res-1)) == 0 # to prevent a tail-call to withdepth()
+ return res
+
+
+runner = Runner()
+
+
+def empty_callback(h, arg):
+ print 'in empty_callback:', h, arg
+ assert rffi.cast(lltype.Signed, arg) == 123
+ return h
+
+def switchbackonce_callback(h, arg):
+ print 'in switchbackonce_callback:', h, arg
+ assert rffi.cast(lltype.Signed, arg) == 321
+ runner.nextstatus(1)
+ assert not runner.sthread.is_empty_handle(h)
+ h = runner.sthread.switch(h)
+ runner.nextstatus(3)
+ assert not runner.sthread.is_empty_handle(h)
+ return h
+
+def variousstackdepths_callback(h, arg):
+ assert runner.nextstep == runner.status
+ runner.nextstep = -1
+ arg = rffi.cast(lltype.Signed, arg)
+ assert arg == runner.gointo
+ self = runner.tasks[arg]
+ assert self.n == runner.gointo
+ assert not self.h
+ assert 0 <= runner.comefrom < 10
+ task = runner.tasks[runner.comefrom]
+ assert not task.h
+ assert bool(h) and not runner.sthread.is_empty_handle(h)
+ task.h = h
+ runner.comefrom = -1
+ runner.gointo = -1
+
+ while self.withdepth(runner.random.genrand32() % 20) == 0:
+ assert len(self.lst) == 0
+
+ assert len(self.lst) == 0
+ assert not self.h
+ while 1:
+ n = intmask(runner.random.genrand32() % 10)
+ h = runner.tasks[n].h
+ if h:
+ break
+
+ assert not runner.sthread.is_empty_handle(h)
+ runner.tasks[n].h = runner.sthread.get_null_handle()
+ runner.comefrom = -42
+ runner.gointo = n
+ assert runner.nextstep == -1
+ runner.status += 1
+ runner.nextstep = runner.status
+ print "LEAVING %d to go to %d" % (self.n, n)
+ return h
+
+
+def entry_point(argv):
+ seed = 0
+ if len(argv) > 1:
+ seed = int(argv[1])
+ runner.init(seed)
+ for name, meth in Runner.TESTS:
+ print '-----', name, '-----'
+ meth(runner)
+ print '----- all done -----'
+ runner.done()
+ return 0
+
+
+class BaseTestStacklet(StandaloneTests):
+
+ def setup_class(cls):
+ from pypy.config.pypyoption import get_pypy_config
+ config = get_pypy_config(translating=True)
+ config.translation.gc = cls.gc
+ if cls.gcrootfinder is not None:
+ config.translation.continuation = True
+ config.translation.gcrootfinder = cls.gcrootfinder
+ GCROOTFINDER = cls.gcrootfinder
+ cls.config = config
+ cls.old_values = Runner.config, Runner.STATUSMAX
+ Runner.config = config
+ Runner.STATUSMAX = 25000
+
+ def teardown_class(cls):
+ Runner.config, Runner.STATUSMAX = cls.old_values
+
+ def test_demo1(self):
+ t, cbuilder = self.compile(entry_point)
+
+ for i in range(15):
+ if (i & 1) == 0:
+ env = {}
+ else:
+ env = {'PYPY_GC_NURSERY': '2k'}
+ print 'running %s/%s with arg=%d and env=%r' % (
+ self.gc, self.gcrootfinder, i, env)
+ data = cbuilder.cmdexec('%d' % i, env=env)
+ assert data.endswith("----- all done -----\n")
+ for name, meth in Runner.TESTS:
+ assert ('----- %s -----\n' % name) in data
+
+
+class DONTTestStackletBoehm(BaseTestStacklet):
+ # Boehm does not work well with stacklets, probably because the
+ # moved-away copies of the stack are parsed using a different
+ # selection logic than the real stack
+ gc = 'boehm'
+ gcrootfinder = None
+
+class TestStackletAsmGcc(BaseTestStacklet):
+ gc = 'minimark'
+ gcrootfinder = 'asmgcc'
+
+class TestStackletShadowStack(BaseTestStacklet):
+ gc = 'minimark'
+ gcrootfinder = 'shadowstack'
+
+
+def target(*args):
+ return entry_point, None
+
+if __name__ == '__main__':
+ import sys
+ sys.exit(entry_point(sys.argv))
diff --git a/pypy/rpython/extfuncregistry.py b/pypy/rpython/extfuncregistry.py
--- a/pypy/rpython/extfuncregistry.py
+++ b/pypy/rpython/extfuncregistry.py
@@ -44,32 +44,28 @@
('log10', [float], float),
('sin', [float], float),
('cos', [float], float),
+ ('atan2', [float, float], float),
+ ('hypot', [float, float], float),
+ ('frexp', [float], (float, int)),
+ ('ldexp', [float, int], float),
+ ('modf', [float], (float, float)),
+ ('fmod', [float, float], float),
+ ('pow', [float, float], float),
]),
]
for module, methods in _register:
for name, arg_types, return_type in methods:
method_name = 'll_math_%s' % name
+ oofake = None
+ # Things with a tuple return type have a fake impl for RPython, check
+ # to see if the method has one.
+ if hasattr(oo_math, method_name):
+ oofake = getattr(oo_math, method_name)
register_external(getattr(module, name), arg_types, return_type,
export_name='ll_math.%s' % method_name,
sandboxsafe=True,
- llimpl=getattr(ll_math, method_name))
-
-
-complex_math_functions = [
- ('frexp', [float], (float, int)),
- ('ldexp', [float, int], float),
- ('modf', [float], (float, float)),
- ] + [(name, [float, float], float)
- for name in 'atan2', 'fmod', 'hypot', 'pow']
-
-for name, args, res in complex_math_functions:
- func = getattr(math, name)
- llimpl = getattr(ll_math, 'll_math_%s' % name, None)
- oofake = getattr(oo_math, 'll_math_%s' % name, None)
- register_external(func, args, res, 'll_math.ll_math_%s' % name,
- llimpl=llimpl, oofakeimpl=oofake,
- sandboxsafe=True)
-
+ llimpl=getattr(ll_math, method_name),
+ oofakeimpl=oofake)
# ___________________________
# os.path functions
diff --git a/pypy/rpython/llinterp.py b/pypy/rpython/llinterp.py
--- a/pypy/rpython/llinterp.py
+++ b/pypy/rpython/llinterp.py
@@ -675,21 +675,6 @@
#log.warn("op_indirect_call with graphs=None:", f)
return self.op_direct_call(f, *args)
- def op_adr_call(self, TGT, f, *inargs):
- checkadr(f)
- obj = self.llinterpreter.typer.type_system.deref(f.ref())
- assert hasattr(obj, 'graph') # don't want to think about that
- graph = obj.graph
- args = []
- for inarg, arg in zip(inargs, obj.graph.startblock.inputargs):
- args.append(lltype._cast_whatever(arg.concretetype, inarg))
- frame = self.newsubframe(graph, args)
- result = frame.eval()
- from pypy.translator.stackless.frame import storage_type
- assert storage_type(lltype.typeOf(result)) == TGT
- return lltype._cast_whatever(TGT, result)
- op_adr_call.need_result_type = True
-
def op_malloc(self, obj, flags):
flavor = flags['flavor']
zero = flags.get('zero', False)
@@ -840,10 +825,11 @@
def op_gc_adr_of_nursery_top(self):
raise NotImplementedError
-
def op_gc_adr_of_nursery_free(self):
raise NotImplementedError
+ def op_gc_adr_of_root_stack_base(self):
+ raise NotImplementedError
def op_gc_adr_of_root_stack_top(self):
raise NotImplementedError
@@ -894,6 +880,21 @@
def op_gc_stack_bottom(self):
pass # marker for trackgcroot.py
+ def op_gc_shadowstackref_new(self): # stacklet+shadowstack
+ raise NotImplementedError("gc_shadowstackref_new")
+ def op_gc_shadowstackref_context(self):
+ raise NotImplementedError("gc_shadowstackref_context")
+ def op_gc_shadowstackref_destroy(self):
+ raise NotImplementedError("gc_shadowstackref_destroy")
+ def op_gc_save_current_state_away(self):
+ raise NotImplementedError("gc_save_current_state_away")
+ def op_gc_forget_current_state(self):
+ raise NotImplementedError("gc_forget_current_state")
+ def op_gc_restore_state_from(self):
+ raise NotImplementedError("gc_restore_state_from")
+ def op_gc_start_fresh_new_state(self):
+ raise NotImplementedError("gc_start_fresh_new_state")
+
def op_gc_get_type_info_group(self):
raise NotImplementedError("gc_get_type_info_group")
@@ -930,27 +931,6 @@
def op_get_write_barrier_from_array_failing_case(self):
raise NotImplementedError("get_write_barrier_from_array_failing_case")
- def op_yield_current_frame_to_caller(self):
- raise NotImplementedError("yield_current_frame_to_caller")
-
- def op_stack_frames_depth(self):
- return len(self.llinterpreter.frame_stack)
-
- def op_stack_switch(self, frametop):
- raise NotImplementedError("stack_switch")
-
- def op_stack_unwind(self):
- raise NotImplementedError("stack_unwind")
-
- def op_stack_capture(self):
- raise NotImplementedError("stack_capture")
-
- def op_get_stack_depth_limit(self):
- raise NotImplementedError("get_stack_depth_limit")
-
- def op_set_stack_depth_limit(self):
- raise NotImplementedError("set_stack_depth_limit")
-
def op_stack_current(self):
return 0
@@ -1131,16 +1111,6 @@
assert isinstance(x, (int, Symbolic))
return bool(x)
- # read frame var support
-
- def op_get_frame_base(self):
- self._obj0 = self # hack
- return llmemory.fakeaddress(self)
-
- def op_frame_info(self, *vars):
- pass
- op_frame_info.specialform = True
-
# hack for jit.codegen.llgraph
def op_check_and_clear_exc(self):
diff --git a/pypy/rpython/lltypesystem/ll2ctypes.py b/pypy/rpython/lltypesystem/ll2ctypes.py
--- a/pypy/rpython/lltypesystem/ll2ctypes.py
+++ b/pypy/rpython/lltypesystem/ll2ctypes.py
@@ -1098,6 +1098,8 @@
for i in range(len(FUNCTYPE.ARGS)):
if FUNCTYPE.ARGS[i] is lltype.Void:
void_arguments.append(i)
+ def callme(cargs): # an extra indirection: workaround for rlib.rstacklet
+ return cfunc(*cargs)
def invoke_via_ctypes(*argvalues):
global _callback_exc_info
cargs = []
@@ -1109,7 +1111,7 @@
cargs.append(cvalue)
_callback_exc_info = None
_restore_c_errno()
- cres = cfunc(*cargs)
+ cres = callme(cargs)
_save_c_errno()
if _callback_exc_info:
etype, evalue, etb = _callback_exc_info
diff --git a/pypy/rpython/lltypesystem/lloperation.py b/pypy/rpython/lltypesystem/lloperation.py
--- a/pypy/rpython/lltypesystem/lloperation.py
+++ b/pypy/rpython/lltypesystem/lloperation.py
@@ -9,7 +9,7 @@
class LLOp(object):
def __init__(self, sideeffects=True, canfold=False, canraise=(),
- pyobj=False, canunwindgc=False, canrun=False, oo=False,
+ pyobj=False, canmallocgc=False, canrun=False, oo=False,
tryfold=False):
# self.opname = ... (set afterwards)
@@ -36,12 +36,12 @@
# The operation manipulates PyObjects
self.pyobj = pyobj
- # The operation can unwind the stack in a stackless gc build
- self.canunwindgc = canunwindgc
- if canunwindgc:
- if (StackException not in self.canraise and
+ # The operation can go a GC malloc
+ self.canmallocgc = canmallocgc
+ if canmallocgc:
+ if (MemoryError not in self.canraise and
Exception not in self.canraise):
- self.canraise += (StackException,)
+ self.canraise += (MemoryError,)
# The operation can be run directly with __call__
self.canrun = canrun or canfold
@@ -175,10 +175,6 @@
return hop.genop(op.opname, args_v, resulttype=hop.r_result.lowleveltype)
-class StackException(Exception):
- """Base for internal exceptions possibly used by the stackless
- implementation."""
-
# ____________________________________________________________
#
# This list corresponds to the operations implemented by the LLInterpreter.
@@ -356,10 +352,10 @@
# __________ pointer operations __________
- 'malloc': LLOp(canraise=(MemoryError,), canunwindgc=True),
- 'malloc_varsize': LLOp(canraise=(MemoryError,), canunwindgc=True),
- 'malloc_nonmovable': LLOp(canraise=(MemoryError,), canunwindgc=True),
- 'malloc_nonmovable_varsize':LLOp(canraise=(MemoryError,),canunwindgc=True),
+ 'malloc': LLOp(canmallocgc=True),
+ 'malloc_varsize': LLOp(canmallocgc=True),
+ 'malloc_nonmovable': LLOp(canmallocgc=True),
+ 'malloc_nonmovable_varsize':LLOp(canmallocgc=True),
'shrink_array': LLOp(canrun=True),
'zero_gc_pointers_inside': LLOp(),
'free': LLOp(),
@@ -414,7 +410,6 @@
'adr_ne': LLOp(canfold=True),
'adr_gt': LLOp(canfold=True),
'adr_ge': LLOp(canfold=True),
- 'adr_call': LLOp(canraise=(Exception,)),
'cast_ptr_to_adr': LLOp(sideeffects=False),
'cast_adr_to_ptr': LLOp(canfold=True),
'cast_adr_to_int': LLOp(sideeffects=False),
@@ -436,8 +431,8 @@
'jit_force_quasi_immutable': LLOp(canrun=True),
'get_exception_addr': LLOp(),
'get_exc_value_addr': LLOp(),
- 'do_malloc_fixedsize_clear':LLOp(canraise=(MemoryError,),canunwindgc=True),
- 'do_malloc_varsize_clear': LLOp(canraise=(MemoryError,),canunwindgc=True),
+ 'do_malloc_fixedsize_clear':LLOp(canmallocgc=True),
+ 'do_malloc_varsize_clear': LLOp(canmallocgc=True),
'get_write_barrier_failing_case': LLOp(sideeffects=False),
'get_write_barrier_from_array_failing_case': LLOp(sideeffects=False),
'gc_get_type_info_group': LLOp(sideeffects=False),
@@ -445,7 +440,7 @@
# __________ GC operations __________
- 'gc__collect': LLOp(canunwindgc=True),
+ 'gc__collect': LLOp(canmallocgc=True),
'gc_free': LLOp(),
'gc_fetch_exception': LLOp(),
'gc_restore_exception': LLOp(),
@@ -455,17 +450,12 @@
'gc_pop_alive_pyobj': LLOp(),
'gc_reload_possibly_moved': LLOp(),
# see rlib/objectmodel for gc_identityhash and gc_id
- 'gc_identityhash': LLOp(canraise=(MemoryError,), sideeffects=False,
- canunwindgc=True),
- 'gc_id': LLOp(canraise=(MemoryError,), sideeffects=False),
- # ^^^ but canunwindgc=False, as it is
- # allocating non-GC structures only
+ 'gc_identityhash': LLOp(sideeffects=False, canmallocgc=True),
+ 'gc_id': LLOp(sideeffects=False, canmallocgc=True),
'gc_obtain_free_space': LLOp(),
'gc_set_max_heap_size': LLOp(),
'gc_can_move' : LLOp(sideeffects=False),
- 'gc_thread_prepare' : LLOp(canraise=(MemoryError,)),
- # ^^^ but canunwindgc=False, as it is
- # allocating non-GC structures only
+ 'gc_thread_prepare' : LLOp(canmallocgc=True),
'gc_thread_run' : LLOp(),
'gc_thread_start' : LLOp(),
'gc_thread_die' : LLOp(),
@@ -473,7 +463,7 @@
'gc_thread_after_fork': LLOp(), # arguments: (result_of_fork, opaqueaddr)
'gc_assume_young_pointers': LLOp(canrun=True),
'gc_writebarrier_before_copy': LLOp(canrun=True),
- 'gc_heap_stats' : LLOp(canunwindgc=True),
+ 'gc_heap_stats' : LLOp(canmallocgc=True),
'gc_get_rpy_roots' : LLOp(),
'gc_get_rpy_referents': LLOp(),
@@ -489,50 +479,37 @@
# ^^^ returns an address of nursery free pointer, for later modifications
'gc_adr_of_nursery_top' : LLOp(),
# ^^^ returns an address of pointer, since it can change at runtime
+ 'gc_adr_of_root_stack_base': LLOp(),
'gc_adr_of_root_stack_top': LLOp(),
- # ^^^ returns the address of gcdata.root_stack_top (for shadowstack only)
-
- # experimental operations in support of thread cloning, only
- # implemented by the Mark&Sweep GC
- 'gc_x_swap_pool': LLOp(canraise=(MemoryError,), canunwindgc=True),
- 'gc_x_clone': LLOp(canraise=(MemoryError, RuntimeError),
- canunwindgc=True),
- 'gc_x_size_header': LLOp(),
+ # returns the address of gcdata.root_stack_base/top (for shadowstack only)
# for asmgcroot support to get the address of various static structures
# see translator/c/src/mem.h for the valid indices
'gc_asmgcroot_static': LLOp(sideeffects=False),
'gc_stack_bottom': LLOp(canrun=True),
- # NOTE NOTE NOTE! don't forget *** canunwindgc=True *** for anything that
- # can go through a stack unwind, in particular anything that mallocs!
+ # for stacklet+shadowstack support
+ 'gc_shadowstackref_new': LLOp(canmallocgc=True),
+ 'gc_shadowstackref_context': LLOp(),
+ 'gc_shadowstackref_destroy': LLOp(),
+ 'gc_save_current_state_away': LLOp(),
+ 'gc_forget_current_state': LLOp(),
+ 'gc_restore_state_from': LLOp(),
+ 'gc_start_fresh_new_state': LLOp(),
+
+ # NOTE NOTE NOTE! don't forget *** canmallocgc=True *** for anything that
+ # can malloc a GC object.
# __________ weakrefs __________
- 'weakref_create': LLOp(canraise=(MemoryError,), sideeffects=False,
- canunwindgc=True),
+ 'weakref_create': LLOp(sideeffects=False, canmallocgc=True),
'weakref_deref': LLOp(sideeffects=False),
'cast_ptr_to_weakrefptr': LLOp(sideeffects=False), # no-op type hiding
'cast_weakrefptr_to_ptr': LLOp(sideeffects=False), # no-op type revealing
- # __________ stackless operation(s) __________
-
- 'yield_current_frame_to_caller': LLOp(canraise=(StackException,
- RuntimeError)),
- # can always unwind, not just if stackless gc
-
- 'stack_frames_depth': LLOp(sideeffects=False, canraise=(StackException,
- RuntimeError)),
- 'stack_switch': LLOp(canraise=(StackException, RuntimeError)),
- 'stack_unwind': LLOp(canraise=(StackException, RuntimeError)),
- 'stack_capture': LLOp(canraise=(StackException, RuntimeError)),
- 'get_stack_depth_limit':LLOp(sideeffects=False),
- 'set_stack_depth_limit':LLOp(),
+ # __________ misc operations __________
'stack_current': LLOp(sideeffects=False),
-
- # __________ misc operations __________
-
'keepalive': LLOp(),
'same_as': LLOp(canfold=True),
'hint': LLOp(),
@@ -591,10 +568,6 @@
'ooparse_int': LLOp(oo=True, canraise=(ValueError,)),
'ooparse_float': LLOp(oo=True, canraise=(ValueError,)),
'oounicode': LLOp(oo=True, canraise=(UnicodeDecodeError,)),
-
- # _____ read frame var support ___
- 'get_frame_base': LLOp(sideeffects=False),
- 'frame_info': LLOp(sideeffects=False),
}
# ***** Run test_lloperation after changes. *****
diff --git a/pypy/rpython/lltypesystem/lltype.py b/pypy/rpython/lltypesystem/lltype.py
--- a/pypy/rpython/lltypesystem/lltype.py
+++ b/pypy/rpython/lltypesystem/lltype.py
@@ -362,7 +362,8 @@
about=self)._obj
Struct._install_extras(self, **kwds)
- def _attach_runtime_type_info_funcptr(self, funcptr, destrptr):
+ def _attach_runtime_type_info_funcptr(self, funcptr, destrptr,
+ customtraceptr):
if self._runtime_type_info is None:
raise TypeError("attachRuntimeTypeInfo: %r must have been built "
"with the rtti=True argument" % (self,))
@@ -376,7 +377,7 @@
raise TypeError("expected a runtime type info function "
"implementation, got: %s" % funcptr)
self._runtime_type_info.query_funcptr = funcptr
- if destrptr is not None :
+ if destrptr is not None:
T = typeOf(destrptr)
if (not isinstance(T, Ptr) or
not isinstance(T.TO, FuncType) or
@@ -386,6 +387,18 @@
raise TypeError("expected a destructor function "
"implementation, got: %s" % destrptr)
self._runtime_type_info.destructor_funcptr = destrptr
+ if customtraceptr is not None:
+ from pypy.rpython.lltypesystem import llmemory
+ T = typeOf(customtraceptr)
+ if (not isinstance(T, Ptr) or
+ not isinstance(T.TO, FuncType) or
+ len(T.TO.ARGS) != 2 or
+ T.TO.RESULT != llmemory.Address or
+ T.TO.ARGS[0] != llmemory.Address or
+ T.TO.ARGS[1] != llmemory.Address):
+ raise TypeError("expected a custom trace function "
+ "implementation, got: %s" % customtraceptr)
+ self._runtime_type_info.custom_trace_funcptr = customtraceptr
class GcStruct(RttiStruct):
_gckind = 'gc'
@@ -2042,10 +2055,12 @@
raise ValueError("only odd integers can be cast back to ptr")
return _ptr(PTRTYPE, oddint, solid=True)
-def attachRuntimeTypeInfo(GCSTRUCT, funcptr=None, destrptr=None):
+def attachRuntimeTypeInfo(GCSTRUCT, funcptr=None, destrptr=None,
+ customtraceptr=None):
if not isinstance(GCSTRUCT, RttiStruct):
raise TypeError, "expected a RttiStruct: %s" % GCSTRUCT
- GCSTRUCT._attach_runtime_type_info_funcptr(funcptr, destrptr)
+ GCSTRUCT._attach_runtime_type_info_funcptr(funcptr, destrptr,
+ customtraceptr)
return _ptr(Ptr(RuntimeTypeInfo), GCSTRUCT._runtime_type_info)
def getRuntimeTypeInfo(GCSTRUCT):
diff --git a/pypy/rpython/lltypesystem/module/ll_math.py b/pypy/rpython/lltypesystem/module/ll_math.py
--- a/pypy/rpython/lltypesystem/module/ll_math.py
+++ b/pypy/rpython/lltypesystem/module/ll_math.py
@@ -223,22 +223,13 @@
def ll_math_fmod(x, y):
- if isinf(y):
- if isinf(x):
- raise ValueError("math domain error")
- return x # fmod(x, +/-Inf) returns x for finite x (or if x is a NaN).
+ if isinf(x) and not isnan(y):
+ raise ValueError("math domain error")
- _error_reset()
- r = math_fmod(x, y)
- errno = rposix.get_errno()
- if isnan(r):
- if isnan(x) or isnan(y):
- errno = 0
- else:
- errno = EDOM
- if errno:
- _likely_raise(errno, r)
- return r
+ if y == 0:
+ raise ValueError("math domain error")
+
+ return math_fmod(x, y)
def ll_math_hypot(x, y):
diff --git a/pypy/rpython/lltypesystem/rffi.py b/pypy/rpython/lltypesystem/rffi.py
--- a/pypy/rpython/lltypesystem/rffi.py
+++ b/pypy/rpython/lltypesystem/rffi.py
@@ -56,7 +56,7 @@
sandboxsafe=False, threadsafe='auto',
_nowrapper=False, calling_conv='c',
oo_primitive=None, elidable_function=False,
- macro=None):
+ macro=None, random_effects_on_gcobjs='auto'):
"""Build an external function that will invoke the C function 'name'
with the given 'args' types and 'result' type.
@@ -112,13 +112,19 @@
# sandboxsafe is a hint for "too-small-ness" (e.g. math functions).
invoke_around_handlers = not sandboxsafe
+ if random_effects_on_gcobjs not in (False, True):
+ random_effects_on_gcobjs = (
+ invoke_around_handlers or # because it can release the GIL
+ has_callback) # because the callback can do it
+
funcptr = lltype.functionptr(ext_type, name, external='C',
compilation_info=compilation_info,
_callable=_callable,
_safe_not_sandboxed=sandboxsafe,
_debugexc=True, # on top of llinterp
canraise=False,
- releases_gil=invoke_around_handlers,
+ random_effects_on_gcobjs=
+ random_effects_on_gcobjs,
**kwds)
if isinstance(_callable, ll2ctypes.LL2CtypesCallable):
_callable.funcptr = funcptr
diff --git a/pypy/rpython/lltypesystem/test/test_lloperation.py b/pypy/rpython/lltypesystem/test/test_lloperation.py
--- a/pypy/rpython/lltypesystem/test/test_lloperation.py
+++ b/pypy/rpython/lltypesystem/test/test_lloperation.py
@@ -144,6 +144,4 @@
for opname, llop in LL_OPERATIONS.items():
if llop.canrun:
continue
- if opname.startswith('gc_x_'):
- continue # ignore experimental stuff
assert opname in LL_INTERP_OPERATIONS
diff --git a/pypy/rpython/memory/gc/base.py b/pypy/rpython/memory/gc/base.py
--- a/pypy/rpython/memory/gc/base.py
+++ b/pypy/rpython/memory/gc/base.py
@@ -69,7 +69,10 @@
varsize_offsets_to_gcpointers_in_var_part,
weakpointer_offset,
member_index,
- is_rpython_class):
+ is_rpython_class,
+ has_custom_trace,
+ get_custom_trace,
+ fast_path_tracing):
self.getfinalizer = getfinalizer
self.is_varsize = is_varsize
self.has_gcptr_in_varsize = has_gcptr_in_varsize
@@ -83,6 +86,9 @@
self.weakpointer_offset = weakpointer_offset
self.member_index = member_index
self.is_rpython_class = is_rpython_class
+ self.has_custom_trace = has_custom_trace
+ self.get_custom_trace = get_custom_trace
+ self.fast_path_tracing = fast_path_tracing
def get_member_index(self, type_id):
return self.member_index(type_id)
@@ -145,13 +151,13 @@
else:
malloc_varsize = self.malloc_varsize
ref = malloc_varsize(typeid, length, size, itemsize,
- offset_to_length, True)
+ offset_to_length)
else:
if zero or not hasattr(self, 'malloc_fixedsize'):
malloc_fixedsize = self.malloc_fixedsize_clear
else:
malloc_fixedsize = self.malloc_fixedsize
- ref = malloc_fixedsize(typeid, size, True, needs_finalizer,
+ ref = malloc_fixedsize(typeid, size, needs_finalizer,
contains_weakptr)
# lots of cast and reverse-cast around...
return llmemory.cast_ptr_to_adr(ref)
@@ -181,16 +187,25 @@
Typically, 'callback' is a bound method and 'arg' can be None.
"""
typeid = self.get_type_id(obj)
- if self.is_gcarrayofgcptr(typeid):
- # a performance shortcut for GcArray(gcptr)
- length = (obj + llmemory.gcarrayofptr_lengthoffset).signed[0]
- item = obj + llmemory.gcarrayofptr_itemsoffset
- while length > 0:
- if self.points_to_valid_gc_object(item):
- callback(item, arg)
- item += llmemory.gcarrayofptr_singleitemoffset
- length -= 1
- return
+ #
+ # First, look if we need more than the simple fixed-size tracing
+ if not self.fast_path_tracing(typeid):
+ #
+ # Yes. Two cases: either we are just a GcArray(gcptr), for
+ # which we have a special case for performance, or we call
+ # the slow path version.
+ if self.is_gcarrayofgcptr(typeid):
+ length = (obj + llmemory.gcarrayofptr_lengthoffset).signed[0]
+ item = obj + llmemory.gcarrayofptr_itemsoffset
+ while length > 0:
+ if self.points_to_valid_gc_object(item):
+ callback(item, arg)
+ item += llmemory.gcarrayofptr_singleitemoffset
+ length -= 1
+ return
+ self._trace_slow_path(obj, callback, arg)
+ #
+ # Do the tracing on the fixed-size part of the object.
offsets = self.offsets_to_gc_pointers(typeid)
i = 0
while i < len(offsets):
@@ -198,6 +213,10 @@
if self.points_to_valid_gc_object(item):
callback(item, arg)
i += 1
+ trace._annspecialcase_ = 'specialize:arg(2)'
+
+ def _trace_slow_path(self, obj, callback, arg):
+ typeid = self.get_type_id(obj)
if self.has_gcptr_in_varsize(typeid):
item = obj + self.varsize_offset_to_variable_part(typeid)
length = (obj + self.varsize_offset_to_length(typeid)).signed[0]
@@ -212,7 +231,16 @@
j += 1
item += itemlength
length -= 1
- trace._annspecialcase_ = 'specialize:arg(2)'
+ if self.has_custom_trace(typeid):
+ generator = self.get_custom_trace(typeid)
+ item = llmemory.NULL
+ while True:
+ item = generator(obj, item)
+ if not item:
+ break
+ if self.points_to_valid_gc_object(item):
+ callback(item, arg)
+ _trace_slow_path._annspecialcase_ = 'specialize:arg(2)'
def trace_partial(self, obj, start, stop, callback, arg):
"""Like trace(), but only walk the array part, for indices in
@@ -317,7 +345,7 @@
break
obj = self.run_finalizers.popleft()
finalizer = self.getfinalizer(self.get_type_id(obj))
- finalizer(obj)
+ finalizer(obj, llmemory.NULL)
finally:
self.finalizer_lock_count -= 1
diff --git a/pypy/rpython/memory/gc/env.py b/pypy/rpython/memory/gc/env.py
--- a/pypy/rpython/memory/gc/env.py
+++ b/pypy/rpython/memory/gc/env.py
@@ -116,6 +116,10 @@
def get_total_memory():
return get_total_memory_darwin(get_darwin_sysctl_signed('hw.memsize'))
+elif sys.platform.startswith('freebsd'):
+ def get_total_memory():
+ return get_total_memory_darwin(get_darwin_sysctl_signed('hw.usermem'))
+
else:
def get_total_memory():
return addressable_size # XXX implement me for other platforms
diff --git a/pypy/rpython/memory/gc/generation.py b/pypy/rpython/memory/gc/generation.py
--- a/pypy/rpython/memory/gc/generation.py
+++ b/pypy/rpython/memory/gc/generation.py
@@ -166,9 +166,9 @@
return False
return self.nursery <= addr < self.nursery_top
- def malloc_fixedsize_clear(self, typeid, size, can_collect,
+ def malloc_fixedsize_clear(self, typeid, size,
has_finalizer=False, contains_weakptr=False):
- if (has_finalizer or not can_collect or
+ if (has_finalizer or
(raw_malloc_usage(size) > self.lb_young_fixedsize and
raw_malloc_usage(size) > self.largest_young_fixedsize)):
# ^^^ we do two size comparisons; the first one appears redundant,
@@ -178,7 +178,6 @@
ll_assert(not contains_weakptr, "wrong case for mallocing weakref")
# "non-simple" case or object too big: don't use the nursery
return SemiSpaceGC.malloc_fixedsize_clear(self, typeid, size,
- can_collect,
has_finalizer,
contains_weakptr)
size_gc_header = self.gcheaderbuilder.size_gc_header
@@ -195,7 +194,7 @@
return llmemory.cast_adr_to_ptr(result+size_gc_header, llmemory.GCREF)
def malloc_varsize_clear(self, typeid, length, size, itemsize,
- offset_to_length, can_collect):
+ offset_to_length):
# Only use the nursery if there are not too many items.
if not raw_malloc_usage(itemsize):
too_many_items = False
@@ -214,8 +213,7 @@
maxlength = maxlength_for_minimal_nursery << self.nursery_scale
too_many_items = length > maxlength
- if (not can_collect or
- too_many_items or
+ if (too_many_items or
(raw_malloc_usage(size) > self.lb_young_var_basesize and
raw_malloc_usage(size) > self.largest_young_var_basesize)):
# ^^^ we do two size comparisons; the first one appears redundant,
@@ -223,8 +221,7 @@
# it almost always folds down to False, which kills the
# second comparison as well.
return SemiSpaceGC.malloc_varsize_clear(self, typeid, length, size,
- itemsize, offset_to_length,
- can_collect)
+ itemsize, offset_to_length)
# with the above checks we know now that totalsize cannot be more
# than about half of the nursery size; in particular, the + and *
# cannot overflow
diff --git a/pypy/rpython/memory/gc/hybrid.py b/pypy/rpython/memory/gc/hybrid.py
--- a/pypy/rpython/memory/gc/hybrid.py
+++ b/pypy/rpython/memory/gc/hybrid.py
@@ -129,11 +129,7 @@
# 'large'.
def malloc_varsize_clear(self, typeid, length, size, itemsize,
- offset_to_length, can_collect):
- if not can_collect:
- return SemiSpaceGC.malloc_varsize_clear(self, typeid, length, size,
- itemsize, offset_to_length,
- can_collect)
+ offset_to_length):
size_gc_header = self.gcheaderbuilder.size_gc_header
nonvarsize = size_gc_header + size
@@ -225,9 +221,9 @@
totalsize)
return result
- def _check_rawsize_alloced(self, size_estimate, can_collect=True):
+ def _check_rawsize_alloced(self, size_estimate):
self.large_objects_collect_trigger -= size_estimate
- if can_collect and self.large_objects_collect_trigger < 0:
+ if self.large_objects_collect_trigger < 0:
debug_start("gc-rawsize-collect")
debug_print("allocated", (self._initial_trigger -
self.large_objects_collect_trigger),
diff --git a/pypy/rpython/memory/gc/markcompact.py b/pypy/rpython/memory/gc/markcompact.py
--- a/pypy/rpython/memory/gc/markcompact.py
+++ b/pypy/rpython/memory/gc/markcompact.py
@@ -88,6 +88,9 @@
def __init__(self, config, space_size=4096,
min_next_collect_after=128, **kwds):
+ import py
+ py.test.skip("the 'markcompact' gc needs fixing for custom tracers")
+ #
MovingGCBase.__init__(self, config, **kwds)
self.space_size = space_size
self.min_next_collect_after = min_next_collect_after
@@ -177,14 +180,14 @@
return llmemory.cast_adr_to_ptr(result+size_gc_header, llmemory.GCREF)
_setup_object._always_inline_ = True
- def malloc_fixedsize(self, typeid16, size, can_collect,
+ def malloc_fixedsize(self, typeid16, size,
has_finalizer=False, contains_weakptr=False):
size_gc_header = self.gcheaderbuilder.size_gc_header
totalsize = size_gc_header + size
result = self._get_memory(totalsize)
return self._setup_object(result, typeid16, has_finalizer)
- def malloc_fixedsize_clear(self, typeid16, size, can_collect,
+ def malloc_fixedsize_clear(self, typeid16, size,
has_finalizer=False, contains_weakptr=False):
size_gc_header = self.gcheaderbuilder.size_gc_header
totalsize = size_gc_header + size
@@ -193,7 +196,7 @@
return self._setup_object(result, typeid16, has_finalizer)
def malloc_varsize_clear(self, typeid16, length, size, itemsize,
- offset_to_length, can_collect):
+ offset_to_length):
size_gc_header = self.gcheaderbuilder.size_gc_header
nonvarsize = size_gc_header + size
totalsize = self._get_totalsize_var(nonvarsize, itemsize, length)
diff --git a/pypy/rpython/memory/gc/marksweep.py b/pypy/rpython/memory/gc/marksweep.py
--- a/pypy/rpython/memory/gc/marksweep.py
+++ b/pypy/rpython/memory/gc/marksweep.py
@@ -13,14 +13,14 @@
import sys, os
-X_POOL = lltype.GcOpaqueType('gc.pool')
-X_POOL_PTR = lltype.Ptr(X_POOL)
-X_CLONE = lltype.GcStruct('CloneData', ('gcobjectptr', llmemory.GCREF),
- ('pool', X_POOL_PTR))
-X_CLONE_PTR = lltype.Ptr(X_CLONE)
+##X_POOL = lltype.GcOpaqueType('gc.pool')
+##X_POOL_PTR = lltype.Ptr(X_POOL)
+##X_CLONE = lltype.GcStruct('CloneData', ('gcobjectptr', llmemory.GCREF),
+## ('pool', X_POOL_PTR))
+##X_CLONE_PTR = lltype.Ptr(X_CLONE)
FL_WITHHASH = 0x01
-FL_CURPOOL = 0x02
+##FL_CURPOOL = 0x02
memoryError = MemoryError()
class MarkSweepGC(GCBase):
@@ -92,10 +92,9 @@
def write_free_statistics(self, typeid16, result):
pass
- def malloc_fixedsize(self, typeid16, size, can_collect,
+ def malloc_fixedsize(self, typeid16, size,
has_finalizer=False, contains_weakptr=False):
- if can_collect:
- self.maybe_collect()
+ self.maybe_collect()
size_gc_header = self.gcheaderbuilder.size_gc_header
try:
tot_size = size_gc_header + size
@@ -128,10 +127,9 @@
return llmemory.cast_adr_to_ptr(result, llmemory.GCREF)
malloc_fixedsize._dont_inline_ = True
- def malloc_fixedsize_clear(self, typeid16, size, can_collect,
+ def malloc_fixedsize_clear(self, typeid16, size,
has_finalizer=False, contains_weakptr=False):
- if can_collect:
- self.maybe_collect()
+ self.maybe_collect()
size_gc_header = self.gcheaderbuilder.size_gc_header
try:
tot_size = size_gc_header + size
@@ -166,9 +164,8 @@
malloc_fixedsize_clear._dont_inline_ = True
def malloc_varsize(self, typeid16, length, size, itemsize,
- offset_to_length, can_collect):
- if can_collect:
- self.maybe_collect()
+ offset_to_length):
+ self.maybe_collect()
size_gc_header = self.gcheaderbuilder.size_gc_header
try:
fixsize = size_gc_header + size
@@ -200,9 +197,8 @@
malloc_varsize._dont_inline_ = True
def malloc_varsize_clear(self, typeid16, length, size, itemsize,
- offset_to_length, can_collect):
- if can_collect:
- self.maybe_collect()
+ offset_to_length):
+ self.maybe_collect()
size_gc_header = self.gcheaderbuilder.size_gc_header
try:
fixsize = size_gc_header + size
@@ -450,7 +446,7 @@
hdr.next = self.malloced_objects
self.malloced_objects = hdr
#llop.debug_view(lltype.Void, self.malloced_objects, self.malloced_objects_with_finalizer, size_gc_header)
- finalizer(obj)
+ finalizer(obj, llmemory.NULL)
if not self.collect_in_progress: # another collection was caused?
debug_print("outer collect interrupted "
"by recursive collect")
@@ -526,178 +522,10 @@
# experimental support for thread cloning
def x_swap_pool(self, newpool):
- # Set newpool as the current pool (create one if newpool == NULL).
- # All malloc'ed objects are put into the current pool;this is a
- # way to separate objects depending on when they were allocated.
- size_gc_header = self.gcheaderbuilder.size_gc_header
- # invariant: each POOL GcStruct is at the _front_ of a linked list
- # of malloced objects.
- oldpool = self.curpool
- #llop.debug_print(lltype.Void, 'x_swap_pool',
- # lltype.cast_ptr_to_int(oldpool),
- # lltype.cast_ptr_to_int(newpool))
- if not oldpool:
- # make a fresh pool object, which is automatically inserted at the
- # front of the current list
- oldpool = lltype.malloc(self.POOL)
- addr = llmemory.cast_ptr_to_adr(oldpool)
- addr -= size_gc_header
- hdr = llmemory.cast_adr_to_ptr(addr, self.HDRPTR)
- # put this new POOL object in the poolnodes list
- node = lltype.malloc(self.POOLNODE, flavor="raw")
- node.linkedlist = hdr
- node.nextnode = self.poolnodes
- self.poolnodes = node
- else:
- # manually insert oldpool at the front of the current list
- addr = llmemory.cast_ptr_to_adr(oldpool)
- addr -= size_gc_header
- hdr = llmemory.cast_adr_to_ptr(addr, self.HDRPTR)
- hdr.next = self.malloced_objects
-
- newpool = lltype.cast_opaque_ptr(self.POOLPTR, newpool)
- if newpool:
- # newpool is at the front of the new linked list to install
- addr = llmemory.cast_ptr_to_adr(newpool)
- addr -= size_gc_header
- hdr = llmemory.cast_adr_to_ptr(addr, self.HDRPTR)
- self.malloced_objects = hdr.next
- # invariant: now that objects in the hdr.next list are accessible
- # through self.malloced_objects, make sure they are not accessible
- # via poolnodes (which has a node pointing to newpool):
- hdr.next = lltype.nullptr(self.HDR)
- else:
- # start a fresh new linked list
- self.malloced_objects = lltype.nullptr(self.HDR)
- self.curpool = newpool
- return lltype.cast_opaque_ptr(X_POOL_PTR, oldpool)
+ raise NotImplementedError("old operation deprecated")
def x_clone(self, clonedata):
- # Recursively clone the gcobject and everything it points to,
- # directly or indirectly -- but stops at objects that are not
- # in the specified pool. A new pool is built to contain the
- # copies, and the 'gcobjectptr' and 'pool' fields of clonedata
- # are adjusted to refer to the result.
-
- # install a new pool into which all the mallocs go
- curpool = self.x_swap_pool(lltype.nullptr(X_POOL))
-
- size_gc_header = self.gcheaderbuilder.size_gc_header
- oldobjects = self.AddressStack()
- # if no pool specified, use the current pool as the 'source' pool
- oldpool = clonedata.pool or curpool
- oldpool = lltype.cast_opaque_ptr(self.POOLPTR, oldpool)
- addr = llmemory.cast_ptr_to_adr(oldpool)
- addr -= size_gc_header
-
- hdr = llmemory.cast_adr_to_ptr(addr, self.HDRPTR)
- hdr = hdr.next # skip the POOL object itself
- while hdr:
- next = hdr.next
- # mark all objects from malloced_list
- hdr.flags = chr(ord(hdr.flags) | FL_CURPOOL)
- hdr.next = lltype.nullptr(self.HDR) # abused to point to the copy
- oldobjects.append(llmemory.cast_ptr_to_adr(hdr))
- hdr = next
-
- # a stack of addresses of places that still points to old objects
- # and that must possibly be fixed to point to a new copy
- stack = self.AddressStack()
- stack.append(llmemory.cast_ptr_to_adr(clonedata)
- + llmemory.offsetof(X_CLONE, 'gcobjectptr'))
- while stack.non_empty():
- gcptr_addr = stack.pop()
- oldobj_addr = gcptr_addr.address[0]
- if not oldobj_addr:
- continue # pointer is NULL
- oldhdr = llmemory.cast_adr_to_ptr(oldobj_addr - size_gc_header,
- self.HDRPTR)
- if not (ord(oldhdr.flags) & FL_CURPOOL):
- continue # ignore objects that were not in the malloced_list
- newhdr = oldhdr.next # abused to point to the copy
- if not newhdr:
- typeid = oldhdr.typeid16
- size = self.fixed_size(typeid)
- # XXX! collect() at the beginning if the free heap is low
- if self.is_varsize(typeid):
- itemsize = self.varsize_item_sizes(typeid)
- offset_to_length = self.varsize_offset_to_length(typeid)
- length = (oldobj_addr + offset_to_length).signed[0]
- newobj = self.malloc_varsize(typeid, length, size,
- itemsize, offset_to_length,
- False)
- size += length*itemsize
- else:
- newobj = self.malloc_fixedsize(typeid, size, False)
- length = -1
-
- newobj_addr = llmemory.cast_ptr_to_adr(newobj)
-
- #llop.debug_print(lltype.Void, 'clone',
- # llmemory.cast_adr_to_int(oldobj_addr),
- # '->', llmemory.cast_adr_to_int(newobj_addr),
- # 'typeid', typeid,
- # 'length', length)
-
- newhdr_addr = newobj_addr - size_gc_header
- newhdr = llmemory.cast_adr_to_ptr(newhdr_addr, self.HDRPTR)
-
- saved_id = newhdr.typeid16 # XXX hack needed for genc
- saved_flg1 = newhdr.mark
- saved_flg2 = newhdr.flags
- saved_next = newhdr.next # where size_gc_header == 0
- raw_memcopy(oldobj_addr, newobj_addr, size)
- newhdr.typeid16 = saved_id
- newhdr.mark = saved_flg1
- newhdr.flags = saved_flg2
- newhdr.next = saved_next
-
- offsets = self.offsets_to_gc_pointers(typeid)
- i = 0
- while i < len(offsets):
- pointer_addr = newobj_addr + offsets[i]
- stack.append(pointer_addr)
- i += 1
-
- if length > 0:
- offsets = self.varsize_offsets_to_gcpointers_in_var_part(
- typeid)
- itemlength = self.varsize_item_sizes(typeid)
- offset = self.varsize_offset_to_variable_part(typeid)
- itembaseaddr = newobj_addr + offset
- i = 0
- while i < length:
- item = itembaseaddr + itemlength * i
- j = 0
- while j < len(offsets):
- pointer_addr = item + offsets[j]
- stack.append(pointer_addr)
- j += 1
- i += 1
-
- oldhdr.next = newhdr
- newobj_addr = llmemory.cast_ptr_to_adr(newhdr) + size_gc_header
- gcptr_addr.address[0] = newobj_addr
- stack.delete()
-
- # re-create the original linked list
- next = lltype.nullptr(self.HDR)
- while oldobjects.non_empty():
- hdr = llmemory.cast_adr_to_ptr(oldobjects.pop(), self.HDRPTR)
- hdr.flags = chr(ord(hdr.flags) &~ FL_CURPOOL) # reset the flag
- hdr.next = next
- next = hdr
- oldobjects.delete()
-
- # consistency check
- addr = llmemory.cast_ptr_to_adr(oldpool)
- addr -= size_gc_header
- hdr = llmemory.cast_adr_to_ptr(addr, self.HDRPTR)
- assert hdr.next == next
-
- # build the new pool object collecting the new objects, and
- # reinstall the pool that was current at the beginning of x_clone()
- clonedata.pool = self.x_swap_pool(curpool)
+ raise NotImplementedError("old operation deprecated")
def identityhash(self, obj):
obj = llmemory.cast_ptr_to_adr(obj)
diff --git a/pypy/rpython/memory/gc/minimark.py b/pypy/rpython/memory/gc/minimark.py
--- a/pypy/rpython/memory/gc/minimark.py
+++ b/pypy/rpython/memory/gc/minimark.py
@@ -456,9 +456,8 @@
debug_stop("gc-debug")
- def malloc_fixedsize_clear(self, typeid, size, can_collect=True,
+ def malloc_fixedsize_clear(self, typeid, size,
needs_finalizer=False, contains_weakptr=False):
- ll_assert(can_collect, "!can_collect")
size_gc_header = self.gcheaderbuilder.size_gc_header
totalsize = size_gc_header + size
rawtotalsize = raw_malloc_usage(totalsize)
@@ -507,8 +506,7 @@
def malloc_varsize_clear(self, typeid, length, size, itemsize,
- offset_to_length, can_collect):
- ll_assert(can_collect, "!can_collect")
+ offset_to_length):
size_gc_header = self.gcheaderbuilder.size_gc_header
nonvarsize = size_gc_header + size
#
@@ -1704,7 +1702,7 @@
None) # we don't need the static in all prebuilt gc objects
#
# If we are in an inner collection caused by a call to a finalizer,
- # the 'run_finalizers' objects also need to kept alive.
+ # the 'run_finalizers' objects also need to be kept alive.
self.run_finalizers.foreach(self._collect_obj,
self.objects_to_trace)
diff --git a/pypy/rpython/memory/gc/semispace.py b/pypy/rpython/memory/gc/semispace.py
--- a/pypy/rpython/memory/gc/semispace.py
+++ b/pypy/rpython/memory/gc/semispace.py
@@ -92,14 +92,12 @@
# This class only defines the malloc_{fixed,var}size_clear() methods
# because the spaces are filled with zeroes in advance.
- def malloc_fixedsize_clear(self, typeid16, size, can_collect,
+ def malloc_fixedsize_clear(self, typeid16, size,
has_finalizer=False, contains_weakptr=False):
size_gc_header = self.gcheaderbuilder.size_gc_header
totalsize = size_gc_header + size
result = self.free
if raw_malloc_usage(totalsize) > self.top_of_space - result:
- if not can_collect:
- raise memoryError
result = self.obtain_free_space(totalsize)
llarena.arena_reserve(result, totalsize)
self.init_gc_object(result, typeid16)
@@ -111,7 +109,7 @@
return llmemory.cast_adr_to_ptr(result+size_gc_header, llmemory.GCREF)
def malloc_varsize_clear(self, typeid16, length, size, itemsize,
- offset_to_length, can_collect):
+ offset_to_length):
size_gc_header = self.gcheaderbuilder.size_gc_header
nonvarsize = size_gc_header + size
try:
@@ -121,8 +119,6 @@
raise memoryError
result = self.free
if raw_malloc_usage(totalsize) > self.top_of_space - result:
- if not can_collect:
- raise memoryError
result = self.obtain_free_space(totalsize)
llarena.arena_reserve(result, totalsize)
self.init_gc_object(result, typeid16)
diff --git a/pypy/rpython/memory/gctransform/asmgcroot.py b/pypy/rpython/memory/gctransform/asmgcroot.py
--- a/pypy/rpython/memory/gctransform/asmgcroot.py
+++ b/pypy/rpython/memory/gctransform/asmgcroot.py
@@ -147,6 +147,11 @@
self._extra_gcmapend = lambda: llmemory.NULL
self._extra_mark_sorted = lambda: True
+ def need_stacklet_support(self, gctransformer, getfn):
+ # stacklet support: BIG HACK for rlib.rstacklet
+ from pypy.rlib import _stacklet_asmgcc
+ _stacklet_asmgcc._asmstackrootwalker = self # as a global! argh
+
def need_thread_support(self, gctransformer, getfn):
# Threads supported "out of the box" by the rest of the code.
# The whole code in this function is only there to support
@@ -361,12 +366,13 @@
# found! Enumerate the GC roots in the caller frame
#
collect_stack_root = self.gcdata._gc_collect_stack_root
+ ebp_in_caller = callee.regs_stored_at[INDEX_OF_EBP].address[0]
gc = self.gc
while True:
location = self._shape_decompressor.next()
if location == 0:
break
- addr = self.getlocation(callee, location)
+ addr = self.getlocation(callee, ebp_in_caller, location)
if gc.points_to_valid_gc_object(addr):
collect_stack_root(gc, addr)
#
@@ -376,12 +382,13 @@
reg = CALLEE_SAVED_REGS - 1
while reg >= 0:
location = self._shape_decompressor.next()
- addr = self.getlocation(callee, location)
+ addr = self.getlocation(callee, ebp_in_caller, location)
caller.regs_stored_at[reg] = addr
reg -= 1
location = self._shape_decompressor.next()
- caller.frame_address = self.getlocation(callee, location)
+ caller.frame_address = self.getlocation(callee, ebp_in_caller,
+ location)
# we get a NULL marker to mean "I'm the frame
# of the entry point, stop walking"
return caller.frame_address != llmemory.NULL
@@ -429,7 +436,7 @@
return
llop.debug_fatalerror(lltype.Void, "cannot find gc roots!")
- def getlocation(self, callee, location):
+ def getlocation(self, callee, ebp_in_caller, location):
"""Get the location in the 'caller' frame of a variable, based
on the integer 'location' that describes it. All locations are
computed based on information saved by the 'callee'.
@@ -447,10 +454,8 @@
esp_in_caller = callee.frame_address + sizeofaddr
return esp_in_caller + offset
elif kind == LOC_EBP_PLUS: # in the caller stack frame at N(%ebp)
- ebp_in_caller = callee.regs_stored_at[INDEX_OF_EBP].address[0]
return ebp_in_caller + offset
else: # kind == LOC_EBP_MINUS: at -N(%ebp)
- ebp_in_caller = callee.regs_stored_at[INDEX_OF_EBP].address[0]
return ebp_in_caller - offset
diff --git a/pypy/rpython/memory/gctransform/framework.py b/pypy/rpython/memory/gctransform/framework.py
--- a/pypy/rpython/memory/gctransform/framework.py
+++ b/pypy/rpython/memory/gctransform/framework.py
@@ -7,7 +7,7 @@
from pypy.rpython.memory.gc import marksweep
from pypy.rpython.memory.gcheader import GCHeaderBuilder
from pypy.rlib.rarithmetic import ovfcheck
-from pypy.rlib import rstack, rgc
+from pypy.rlib import rgc
from pypy.rlib.debug import ll_assert
from pypy.rlib.objectmodel import we_are_translated
from pypy.translator.backendopt import graphanalyze
@@ -33,8 +33,6 @@
except AttributeError:
pass
else:
- if func is rstack.stack_check:
- return self.translator.config.translation.stackless
if getattr(func, '_gctransformer_hint_cannot_collect_', False):
return False
if getattr(func, '_gctransformer_hint_close_stack_', False):
@@ -50,10 +48,10 @@
def analyze_simple_operation(self, op, graphinfo):
if op.opname in ('malloc', 'malloc_varsize'):
flags = op.args[1].value
- return flags['flavor'] == 'gc' and not flags.get('nocollect', False)
+ return flags['flavor'] == 'gc'
else:
return (op.opname in LL_OPERATIONS and
- LL_OPERATIONS[op.opname].canunwindgc)
+ LL_OPERATIONS[op.opname].canmallocgc)
def find_initializing_stores(collect_analyzer, graph):
from pypy.objspace.flow.model import mkentrymap
@@ -134,8 +132,7 @@
return result
class FrameworkGCTransformer(GCTransformer):
- use_stackless = False
- root_stack_depth = 163840
+ root_stack_depth = None # for tests to override
def __init__(self, translator):
from pypy.rpython.memory.gc.base import choose_gc_from_config
@@ -152,13 +149,8 @@
# for regular translation: pick the GC from the config
GCClass, GC_PARAMS = choose_gc_from_config(translator.config)
- self.root_stack_jit_hook = None
if hasattr(translator, '_jit2gc'):
self.layoutbuilder = translator._jit2gc['layoutbuilder']
- try:
- self.root_stack_jit_hook = translator._jit2gc['rootstackhook']
- except KeyError:
- pass
else:
self.layoutbuilder = TransformerLayoutBuilder(translator, GCClass)
self.layoutbuilder.transformer = self
@@ -265,7 +257,7 @@
malloc_fixedsize_clear_meth,
[s_gc, s_typeid16,
annmodel.SomeInteger(nonneg=True),
- annmodel.SomeBool(), annmodel.SomeBool(),
+ annmodel.SomeBool(),
annmodel.SomeBool()], s_gcref,
inline = False)
if hasattr(GCClass, 'malloc_fixedsize'):
@@ -274,7 +266,7 @@
malloc_fixedsize_meth,
[s_gc, s_typeid16,
annmodel.SomeInteger(nonneg=True),
- annmodel.SomeBool(), annmodel.SomeBool(),
+ annmodel.SomeBool(),
annmodel.SomeBool()], s_gcref,
inline = False)
else:
@@ -283,12 +275,11 @@
## self.malloc_varsize_ptr = getfn(
## GCClass.malloc_varsize.im_func,
## [s_gc] + [annmodel.SomeInteger(nonneg=True) for i in range(5)]
-## + [annmodel.SomeBool(), annmodel.SomeBool()], s_gcref)
+## + [annmodel.SomeBool()], s_gcref)
self.malloc_varsize_clear_ptr = getfn(
GCClass.malloc_varsize_clear.im_func,
[s_gc, s_typeid16]
- + [annmodel.SomeInteger(nonneg=True) for i in range(4)]
- + [annmodel.SomeBool()], s_gcref)
+ + [annmodel.SomeInteger(nonneg=True) for i in range(4)], s_gcref)
self.collect_ptr = getfn(GCClass.collect.im_func,
[s_gc, annmodel.SomeInteger()], annmodel.s_None)
self.can_move_ptr = getfn(GCClass.can_move.im_func,
@@ -342,13 +333,11 @@
malloc_fast_meth,
"malloc_fast")
s_False = annmodel.SomeBool(); s_False.const = False
- s_True = annmodel.SomeBool(); s_True .const = True
self.malloc_fast_ptr = getfn(
malloc_fast,
[s_gc, s_typeid16,
annmodel.SomeInteger(nonneg=True),
- s_True, s_False,
- s_False], s_gcref,
+ s_False, s_False], s_gcref,
inline = True)
else:
self.malloc_fast_ptr = None
@@ -362,15 +351,13 @@
GCClass.malloc_varsize_clear.im_func,
"malloc_varsize_clear_fast")
s_False = annmodel.SomeBool(); s_False.const = False
- s_True = annmodel.SomeBool(); s_True .const = True
self.malloc_varsize_clear_fast_ptr = getfn(
malloc_varsize_clear_fast,
[s_gc, s_typeid16,
annmodel.SomeInteger(nonneg=True),
annmodel.SomeInteger(nonneg=True),
annmodel.SomeInteger(nonneg=True),
- annmodel.SomeInteger(nonneg=True),
- s_True], s_gcref,
+ annmodel.SomeInteger(nonneg=True)], s_gcref,
inline = True)
else:
self.malloc_varsize_clear_fast_ptr = None
@@ -491,21 +478,9 @@
[s_gc, annmodel.SomeInteger()],
annmodel.SomeInteger())
- # experimental gc_x_* operations
- s_x_pool = annmodel.SomePtr(marksweep.X_POOL_PTR)
- s_x_clone = annmodel.SomePtr(marksweep.X_CLONE_PTR)
- # the x_*() methods use some regular mallocs that must be
- # transformed in the normal way
- self.x_swap_pool_ptr = getfn(GCClass.x_swap_pool.im_func,
- [s_gc, s_x_pool],
- s_x_pool,
- minimal_transform = False)
- self.x_clone_ptr = getfn(GCClass.x_clone.im_func,
- [s_gc, s_x_clone],
- annmodel.s_None,
- minimal_transform = False)
-
# thread support
+ if translator.config.translation.continuation:
+ root_walker.need_stacklet_support(self, getfn)
if translator.config.translation.thread:
root_walker.need_thread_support(self, getfn)
@@ -547,8 +522,8 @@
# this method is attached to the instance and redirects to
# layoutbuilder.get_type_id().
- def finalizer_funcptr_for_type(self, TYPE):
- return self.layoutbuilder.finalizer_funcptr_for_type(TYPE)
+ def special_funcptr_for_type(self, TYPE):
+ return self.layoutbuilder.special_funcptr_for_type(TYPE)
def gc_header_for(self, obj, needs_hash=False):
hdr = self.gcdata.gc.gcheaderbuilder.header_of_object(obj)
@@ -682,7 +657,6 @@
def gct_fv_gc_malloc(self, hop, flags, TYPE, *args):
op = hop.spaceop
flavor = flags['flavor']
- c_can_collect = rmodel.inputconst(lltype.Bool, not flags.get('nocollect', False))
PTRTYPE = op.result.concretetype
assert PTRTYPE.TO == TYPE
@@ -691,21 +665,23 @@
c_type_id = rmodel.inputconst(TYPE_ID, type_id)
info = self.layoutbuilder.get_info(type_id)
c_size = rmodel.inputconst(lltype.Signed, info.fixedsize)
- has_finalizer = bool(self.finalizer_funcptr_for_type(TYPE))
+ kind_and_fptr = self.special_funcptr_for_type(TYPE)
+ has_finalizer = (kind_and_fptr is not None and
+ kind_and_fptr[0] == "finalizer")
c_has_finalizer = rmodel.inputconst(lltype.Bool, has_finalizer)
if not op.opname.endswith('_varsize') and not flags.get('varsize'):
#malloc_ptr = self.malloc_fixedsize_ptr
zero = flags.get('zero', False)
if (self.malloc_fast_ptr is not None and
- c_can_collect.value and not c_has_finalizer.value and
+ not c_has_finalizer.value and
(self.malloc_fast_is_clearing or not zero)):
malloc_ptr = self.malloc_fast_ptr
elif zero:
malloc_ptr = self.malloc_fixedsize_clear_ptr
else:
malloc_ptr = self.malloc_fixedsize_ptr
- args = [self.c_const_gc, c_type_id, c_size, c_can_collect,
+ args = [self.c_const_gc, c_type_id, c_size,
c_has_finalizer, rmodel.inputconst(lltype.Bool, False)]
else:
assert not c_has_finalizer.value
@@ -718,17 +694,15 @@
if flags.get('nonmovable') and self.malloc_varsize_nonmovable_ptr:
# we don't have tests for such cases, let's fail
# explicitely
- assert c_can_collect.value
malloc_ptr = self.malloc_varsize_nonmovable_ptr
args = [self.c_const_gc, c_type_id, v_length]
else:
- if (self.malloc_varsize_clear_fast_ptr is not None and
- c_can_collect.value):
+ if self.malloc_varsize_clear_fast_ptr is not None:
malloc_ptr = self.malloc_varsize_clear_fast_ptr
else:
malloc_ptr = self.malloc_varsize_clear_ptr
args = [self.c_const_gc, c_type_id, v_length, c_size,
- c_varitemsize, c_ofstolength, c_can_collect]
+ c_varitemsize, c_ofstolength]
livevars = self.push_roots(hop)
v_result = hop.genop("direct_call", [malloc_ptr] + args,
resulttype=llmemory.GCREF)
@@ -768,8 +742,13 @@
resultvar=op.result)
def gct_gc_assume_young_pointers(self, hop):
+ if not hasattr(self, 'assume_young_pointers_ptr'):
+ return
op = hop.spaceop
v_addr = op.args[0]
+ if v_addr.concretetype != llmemory.Address:
+ v_addr = hop.genop('cast_ptr_to_adr',
+ [v_addr], resulttype=llmemory.Address)
hop.genop("direct_call", [self.assume_young_pointers_ptr,
self.c_const_gc, v_addr])
@@ -788,68 +767,90 @@
hop.genop("direct_call", [self.get_member_index_ptr, self.c_const_gc,
v_typeid], resultvar=op.result)
- def gct_gc_adr_of_nursery_free(self, hop):
- if getattr(self.gcdata.gc, 'nursery_free', None) is None:
- raise NotImplementedError("gc_adr_of_nursery_free only for generational gcs")
+ def _gc_adr_of_gc_attr(self, hop, attrname):
+ if getattr(self.gcdata.gc, attrname, None) is None:
+ raise NotImplementedError("gc_adr_of_%s only for generational gcs"
+ % (attrname,))
op = hop.spaceop
ofs = llmemory.offsetof(self.c_const_gc.concretetype.TO,
- 'inst_nursery_free')
+ 'inst_' + attrname)
c_ofs = rmodel.inputconst(lltype.Signed, ofs)
v_gc_adr = hop.genop('cast_ptr_to_adr', [self.c_const_gc],
resulttype=llmemory.Address)
hop.genop('adr_add', [v_gc_adr, c_ofs], resultvar=op.result)
+ def gct_gc_adr_of_nursery_free(self, hop):
+ self._gc_adr_of_gc_attr(hop, 'nursery_free')
def gct_gc_adr_of_nursery_top(self, hop):
- if getattr(self.gcdata.gc, 'nursery_top', None) is None:
- raise NotImplementedError("gc_adr_of_nursery_top only for generational gcs")
- op = hop.spaceop
- ofs = llmemory.offsetof(self.c_const_gc.concretetype.TO,
- 'inst_nursery_top')
- c_ofs = rmodel.inputconst(lltype.Signed, ofs)
- v_gc_adr = hop.genop('cast_ptr_to_adr', [self.c_const_gc],
- resulttype=llmemory.Address)
- hop.genop('adr_add', [v_gc_adr, c_ofs], resultvar=op.result)
+ self._gc_adr_of_gc_attr(hop, 'nursery_top')
- def gct_gc_adr_of_root_stack_top(self, hop):
+ def _gc_adr_of_gcdata_attr(self, hop, attrname):
op = hop.spaceop
ofs = llmemory.offsetof(self.c_const_gcdata.concretetype.TO,
- 'inst_root_stack_top')
+ 'inst_' + attrname)
c_ofs = rmodel.inputconst(lltype.Signed, ofs)
v_gcdata_adr = hop.genop('cast_ptr_to_adr', [self.c_const_gcdata],
resulttype=llmemory.Address)
hop.genop('adr_add', [v_gcdata_adr, c_ofs], resultvar=op.result)
- def gct_gc_x_swap_pool(self, hop):
+ def gct_gc_adr_of_root_stack_base(self, hop):
+ self._gc_adr_of_gcdata_attr(hop, 'root_stack_base')
+ def gct_gc_adr_of_root_stack_top(self, hop):
+ self._gc_adr_of_gcdata_attr(hop, 'root_stack_top')
+
+ def gct_gc_shadowstackref_new(self, hop):
op = hop.spaceop
- [v_malloced] = op.args
+ livevars = self.push_roots(hop)
+ hop.genop("direct_call", [self.root_walker.gc_shadowstackref_new_ptr],
+ resultvar=op.result)
+ self.pop_roots(hop, livevars)
+
+ def gct_gc_shadowstackref_context(self, hop):
+ op = hop.spaceop
hop.genop("direct_call",
- [self.x_swap_pool_ptr, self.c_const_gc, v_malloced],
+ [self.root_walker.gc_shadowstackref_context_ptr, op.args[0]],
resultvar=op.result)
+ def gct_gc_shadowstackref_destroy(self, hop):
+ hop.genop("direct_call",
+ [self.root_walker.gc_shadowstackref_destroy_ptr, op.args[0]])
+
+ def gct_gc_save_current_state_away(self, hop):
+ op = hop.spaceop
+ hop.genop("direct_call",
+ [self.root_walker.gc_save_current_state_away_ptr,
+ op.args[0], op.args[1]])
+
+ def gct_gc_forget_current_state(self, hop):
+ hop.genop("direct_call",
+ [self.root_walker.gc_forget_current_state_ptr])
+
+ def gct_gc_restore_state_from(self, hop):
+ op = hop.spaceop
+ hop.genop("direct_call",
+ [self.root_walker.gc_restore_state_from_ptr,
+ op.args[0]])
+
+ def gct_gc_start_fresh_new_state(self, hop):
+ hop.genop("direct_call",
+ [self.root_walker.gc_start_fresh_new_state_ptr])
+
+ def gct_gc_x_swap_pool(self, hop):
+ raise NotImplementedError("old operation deprecated")
def gct_gc_x_clone(self, hop):
- op = hop.spaceop
- [v_clonedata] = op.args
- hop.genop("direct_call",
- [self.x_clone_ptr, self.c_const_gc, v_clonedata],
- resultvar=op.result)
-
+ raise NotImplementedError("old operation deprecated")
def gct_gc_x_size_header(self, hop):
- op = hop.spaceop
- c_result = rmodel.inputconst(lltype.Signed,
- self.gcdata.gc.size_gc_header())
- hop.genop("same_as",
- [c_result],
- resultvar=op.result)
+ raise NotImplementedError("old operation deprecated")
def gct_do_malloc_fixedsize_clear(self, hop):
# used by the JIT (see pypy.jit.backend.llsupport.gc)
op = hop.spaceop
- [v_typeid, v_size, v_can_collect,
+ [v_typeid, v_size,
v_has_finalizer, v_contains_weakptr] = op.args
livevars = self.push_roots(hop)
hop.genop("direct_call",
[self.malloc_fixedsize_clear_ptr, self.c_const_gc,
- v_typeid, v_size, v_can_collect,
+ v_typeid, v_size,
v_has_finalizer, v_contains_weakptr],
resultvar=op.result)
self.pop_roots(hop, livevars)
@@ -858,12 +859,12 @@
# used by the JIT (see pypy.jit.backend.llsupport.gc)
op = hop.spaceop
[v_typeid, v_length, v_size, v_itemsize,
- v_offset_to_length, v_can_collect] = op.args
+ v_offset_to_length] = op.args
livevars = self.push_roots(hop)
hop.genop("direct_call",
[self.malloc_varsize_clear_ptr, self.c_const_gc,
v_typeid, v_length, v_size, v_itemsize,
- v_offset_to_length, v_can_collect],
+ v_offset_to_length],
resultvar=op.result)
self.pop_roots(hop, livevars)
@@ -911,8 +912,8 @@
c_size = rmodel.inputconst(lltype.Signed, info.fixedsize)
malloc_ptr = self.malloc_fixedsize_ptr
c_has_finalizer = rmodel.inputconst(lltype.Bool, False)
- c_has_weakptr = c_can_collect = rmodel.inputconst(lltype.Bool, True)
- args = [self.c_const_gc, c_type_id, c_size, c_can_collect,
+ c_has_weakptr = rmodel.inputconst(lltype.Bool, True)
+ args = [self.c_const_gc, c_type_id, c_size,
c_has_finalizer, c_has_weakptr]
# push and pop the current live variables *including* the argument
@@ -979,24 +980,28 @@
v_size])
def gct_gc_thread_prepare(self, hop):
- assert self.translator.config.translation.thread
- if hasattr(self.root_walker, 'thread_prepare_ptr'):
- hop.genop("direct_call", [self.root_walker.thread_prepare_ptr])
+ pass # no effect any more
def gct_gc_thread_run(self, hop):
assert self.translator.config.translation.thread
if hasattr(self.root_walker, 'thread_run_ptr'):
+ livevars = self.push_roots(hop)
hop.genop("direct_call", [self.root_walker.thread_run_ptr])
+ self.pop_roots(hop, livevars)
def gct_gc_thread_start(self, hop):
assert self.translator.config.translation.thread
if hasattr(self.root_walker, 'thread_start_ptr'):
+ # only with asmgcc. Note that this is actually called after
+ # the first gc_thread_run() in the new thread.
hop.genop("direct_call", [self.root_walker.thread_start_ptr])
def gct_gc_thread_die(self, hop):
assert self.translator.config.translation.thread
if hasattr(self.root_walker, 'thread_die_ptr'):
+ livevars = self.push_roots(hop)
hop.genop("direct_call", [self.root_walker.thread_die_ptr])
+ self.pop_roots(hop, livevars)
def gct_gc_thread_before_fork(self, hop):
if (self.translator.config.translation.thread
@@ -1011,8 +1016,10 @@
def gct_gc_thread_after_fork(self, hop):
if (self.translator.config.translation.thread
and hasattr(self.root_walker, 'thread_after_fork_ptr')):
+ livevars = self.push_roots(hop)
hop.genop("direct_call", [self.root_walker.thread_after_fork_ptr]
+ hop.spaceop.args)
+ self.pop_roots(hop, livevars)
def gct_gc_get_type_info_group(self, hop):
return hop.cast_result(self.c_type_info_group)
@@ -1246,28 +1253,39 @@
def has_finalizer(self, TYPE):
rtti = get_rtti(TYPE)
- return rtti is not None and hasattr(rtti._obj, 'destructor_funcptr')
+ return rtti is not None and getattr(rtti._obj, 'destructor_funcptr',
+ None)
+
+ def has_custom_trace(self, TYPE):
+ rtti = get_rtti(TYPE)
+ return rtti is not None and getattr(rtti._obj, 'custom_trace_funcptr',
+ None)
def make_finalizer_funcptr_for_type(self, TYPE):
- if self.has_finalizer(TYPE):
- rtti = get_rtti(TYPE)
- destrptr = rtti._obj.destructor_funcptr
- DESTR_ARG = lltype.typeOf(destrptr).TO.ARGS[0]
- else:
- destrptr = None
- DESTR_ARG = None
+ if not self.has_finalizer(TYPE):
+ return None
+ rtti = get_rtti(TYPE)
+ destrptr = rtti._obj.destructor_funcptr
+ DESTR_ARG = lltype.typeOf(destrptr).TO.ARGS[0]
+ assert not type_contains_pyobjs(TYPE), "not implemented"
+ typename = TYPE.__name__
+ def ll_finalizer(addr, ignored):
+ v = llmemory.cast_adr_to_ptr(addr, DESTR_ARG)
+ ll_call_destructor(destrptr, v, typename)
+ return llmemory.NULL
+ fptr = self.transformer.annotate_finalizer(ll_finalizer,
+ [llmemory.Address, llmemory.Address], llmemory.Address)
+ return fptr
- assert not type_contains_pyobjs(TYPE), "not implemented"
- if destrptr:
- typename = TYPE.__name__
- def ll_finalizer(addr):
- v = llmemory.cast_adr_to_ptr(addr, DESTR_ARG)
- ll_call_destructor(destrptr, v, typename)
- fptr = self.transformer.annotate_finalizer(ll_finalizer,
- [llmemory.Address],
- lltype.Void)
- else:
- fptr = lltype.nullptr(gctypelayout.GCData.FINALIZERTYPE.TO)
+ def make_custom_trace_funcptr_for_type(self, TYPE):
+ if not self.has_custom_trace(TYPE):
+ return None
+ rtti = get_rtti(TYPE)
+ fptr = rtti._obj.custom_trace_funcptr
+ if not hasattr(fptr._obj, 'graph'):
+ ll_func = fptr._obj._callable
+ fptr = self.transformer.annotate_finalizer(ll_func,
+ [llmemory.Address, llmemory.Address], llmemory.Address)
return fptr
@@ -1333,6 +1351,10 @@
if collect_stack_root:
self.walk_stack_roots(collect_stack_root) # abstract
+ def need_stacklet_support(self):
+ raise Exception("%s does not support stacklets" % (
+ self.__class__.__name__,))
+
def need_thread_support(self, gctransformer, getfn):
raise Exception("%s does not support threads" % (
self.__class__.__name__,))
diff --git a/pypy/rpython/memory/gctransform/shadowstack.py b/pypy/rpython/memory/gctransform/shadowstack.py
--- a/pypy/rpython/memory/gctransform/shadowstack.py
+++ b/pypy/rpython/memory/gctransform/shadowstack.py
@@ -1,17 +1,16 @@
from pypy.rpython.memory.gctransform.framework import BaseRootWalker
from pypy.rpython.memory.gctransform.framework import sizeofaddr
+from pypy.rpython.annlowlevel import llhelper
+from pypy.rpython.lltypesystem import lltype, llmemory
from pypy.rlib.debug import ll_assert
-from pypy.rpython.lltypesystem import llmemory
from pypy.annotation import model as annmodel
class ShadowStackRootWalker(BaseRootWalker):
need_root_stack = True
- collect_stacks_from_other_threads = None
def __init__(self, gctransformer):
BaseRootWalker.__init__(self, gctransformer)
- self.rootstacksize = sizeofaddr * gctransformer.root_stack_depth
# NB. 'self' is frozen, but we can use self.gcdata to store state
gcdata = self.gcdata
@@ -27,13 +26,33 @@
return top
self.decr_stack = decr_stack
- self.rootstackhook = gctransformer.root_stack_jit_hook
- if self.rootstackhook is None:
- def collect_stack_root(callback, gc, addr):
- if gc.points_to_valid_gc_object(addr):
+ translator = gctransformer.translator
+ if (hasattr(translator, '_jit2gc') and
+ 'root_iterator' in translator._jit2gc):
+ root_iterator = translator._jit2gc['root_iterator']
+ def jit_walk_stack_root(callback, addr, end):
+ root_iterator.context = llmemory.NULL
+ gc = self.gc
+ while True:
+ addr = root_iterator.next(gc, addr, end)
+ if addr == llmemory.NULL:
+ return
callback(gc, addr)
- return sizeofaddr
- self.rootstackhook = collect_stack_root
+ addr += sizeofaddr
+ self.rootstackhook = jit_walk_stack_root
+ else:
+ def default_walk_stack_root(callback, addr, end):
+ gc = self.gc
+ while addr != end:
+ if gc.points_to_valid_gc_object(addr):
+ callback(gc, addr)
+ addr += sizeofaddr
+ self.rootstackhook = default_walk_stack_root
+
+ self.shadow_stack_pool = ShadowStackPool(gcdata)
+ rsd = gctransformer.root_stack_depth
+ if rsd is not None:
+ self.shadow_stack_pool.root_stack_depth = rsd
def push_stack(self, addr):
top = self.incr_stack(1)
@@ -43,26 +62,14 @@
top = self.decr_stack(1)
return top.address[0]
- def allocate_stack(self):
- return llmemory.raw_malloc(self.rootstacksize)
-
def setup_root_walker(self):
- stackbase = self.allocate_stack()
- ll_assert(bool(stackbase), "could not allocate root stack")
- self.gcdata.root_stack_top = stackbase
- self.gcdata.root_stack_base = stackbase
+ self.shadow_stack_pool.initial_setup()
BaseRootWalker.setup_root_walker(self)
def walk_stack_roots(self, collect_stack_root):
gcdata = self.gcdata
- gc = self.gc
- rootstackhook = self.rootstackhook
- addr = gcdata.root_stack_base
- end = gcdata.root_stack_top
- while addr != end:
- addr += rootstackhook(collect_stack_root, gc, addr)
- if self.collect_stacks_from_other_threads is not None:
- self.collect_stacks_from_other_threads(collect_stack_root)
+ self.rootstackhook(collect_stack_root,
+ gcdata.root_stack_base, gcdata.root_stack_top)
def need_thread_support(self, gctransformer, getfn):
from pypy.module.thread import ll_thread # xxx fish
@@ -70,121 +77,84 @@
from pypy.rpython.memory.support import copy_without_null_values
gcdata = self.gcdata
# the interfacing between the threads and the GC is done via
- # three completely ad-hoc operations at the moment:
- # gc_thread_prepare, gc_thread_run, gc_thread_die.
- # See docstrings below.
+ # two completely ad-hoc operations at the moment:
+ # gc_thread_run and gc_thread_die. See docstrings below.
- def get_aid():
- """Return the thread identifier, cast to an (opaque) address."""
- return llmemory.cast_int_to_adr(ll_thread.get_ident())
+ shadow_stack_pool = self.shadow_stack_pool
+ SHADOWSTACKREF = get_shadowstackref(gctransformer)
+
+ # this is a dict {tid: SHADOWSTACKREF}, where the tid for the
+ # current thread may be missing so far
+ gcdata.thread_stacks = None
+
+ # Return the thread identifier, as an integer.
+ get_tid = ll_thread.get_ident
def thread_setup():
- """Called once when the program starts."""
- aid = get_aid()
- gcdata.main_thread = aid
- gcdata.active_thread = aid
- gcdata.thread_stacks = AddressDict() # {aid: root_stack_top}
- gcdata._fresh_rootstack = llmemory.NULL
- gcdata.dead_threads_count = 0
-
- def thread_prepare():
- """Called just before thread.start_new_thread(). This
- allocates a new shadow stack to be used by the future
- thread. If memory runs out, this raises a MemoryError
- (which can be handled by the caller instead of just getting
- ignored if it was raised in the newly starting thread).
- """
- if not gcdata._fresh_rootstack:
- gcdata._fresh_rootstack = self.allocate_stack()
- if not gcdata._fresh_rootstack:
- raise MemoryError
+ tid = get_tid()
+ gcdata.main_tid = tid
+ gcdata.active_tid = tid
def thread_run():
"""Called whenever the current thread (re-)acquired the GIL.
This should ensure that the shadow stack installed in
gcdata.root_stack_top/root_stack_base is the one corresponding
to the current thread.
+ No GC operation here, e.g. no mallocs or storing in a dict!
"""
- aid = get_aid()
- if gcdata.active_thread != aid:
- switch_shadow_stacks(aid)
+ tid = get_tid()
+ if gcdata.active_tid != tid:
+ switch_shadow_stacks(tid)
def thread_die():
"""Called just before the final GIL release done by a dying
thread. After a thread_die(), no more gc operation should
occur in this thread.
"""
- aid = get_aid()
- if aid == gcdata.main_thread:
+ tid = get_tid()
+ if tid == gcdata.main_tid:
return # ignore calls to thread_die() in the main thread
# (which can occur after a fork()).
- gcdata.thread_stacks.setitem(aid, llmemory.NULL)
- old = gcdata.root_stack_base
- if gcdata._fresh_rootstack == llmemory.NULL:
- gcdata._fresh_rootstack = old
+ # we need to switch somewhere else, so go to main_tid
+ gcdata.active_tid = gcdata.main_tid
+ thread_stacks = gcdata.thread_stacks
+ new_ref = thread_stacks[gcdata.active_tid]
+ try:
+ del thread_stacks[tid]
+ except KeyError:
+ pass
+ # no more GC operation from here -- switching shadowstack!
+ shadow_stack_pool.forget_current_state()
+ shadow_stack_pool.restore_state_from(new_ref)
+
+ def switch_shadow_stacks(new_tid):
+ # we have the wrong shadowstack right now, but it should not matter
+ thread_stacks = gcdata.thread_stacks
+ try:
+ if thread_stacks is None:
+ gcdata.thread_stacks = thread_stacks = {}
+ raise KeyError
+ new_ref = thread_stacks[new_tid]
+ except KeyError:
+ new_ref = lltype.nullptr(SHADOWSTACKREF)
+ try:
+ old_ref = thread_stacks[gcdata.active_tid]
+ except KeyError:
+ # first time we ask for a SHADOWSTACKREF for this active_tid
+ old_ref = shadow_stack_pool.allocate(SHADOWSTACKREF)
+ thread_stacks[gcdata.active_tid] = old_ref
+ #
+ # no GC operation from here -- switching shadowstack!
+ shadow_stack_pool.save_current_state_away(old_ref, llmemory.NULL)
+ if new_ref:
+ shadow_stack_pool.restore_state_from(new_ref)
else:
- llmemory.raw_free(old)
- install_new_stack(gcdata.main_thread)
- # from time to time, rehash the dictionary to remove
- # old NULL entries
- gcdata.dead_threads_count += 1
- if (gcdata.dead_threads_count & 511) == 0:
- copy = copy_without_null_values(gcdata.thread_stacks)
- gcdata.thread_stacks.delete()
- gcdata.thread_stacks = copy
-
- def switch_shadow_stacks(new_aid):
- save_away_current_stack()
- install_new_stack(new_aid)
+ shadow_stack_pool.start_fresh_new_state()
+ # done
+ #
+ gcdata.active_tid = new_tid
switch_shadow_stacks._dont_inline_ = True
- def save_away_current_stack():
- old_aid = gcdata.active_thread
- # save root_stack_base on the top of the stack
- self.push_stack(gcdata.root_stack_base)
- # store root_stack_top into the dictionary
- gcdata.thread_stacks.setitem(old_aid, gcdata.root_stack_top)
-
- def install_new_stack(new_aid):
- # look for the new stack top
- top = gcdata.thread_stacks.get(new_aid, llmemory.NULL)
- if top == llmemory.NULL:
- # first time we see this thread. It is an error if no
- # fresh new stack is waiting.
- base = gcdata._fresh_rootstack
- gcdata._fresh_rootstack = llmemory.NULL
- ll_assert(base != llmemory.NULL, "missing gc_thread_prepare")
- gcdata.root_stack_top = base
- gcdata.root_stack_base = base
- else:
- # restore the root_stack_base from the top of the stack
- gcdata.root_stack_top = top
- gcdata.root_stack_base = self.pop_stack()
- # done
- gcdata.active_thread = new_aid
-
- def collect_stack(aid, stacktop, callback):
- if stacktop != llmemory.NULL and aid != gcdata.active_thread:
- # collect all valid stacks from the dict (the entry
- # corresponding to the current thread is not valid)
- gc = self.gc
- rootstackhook = self.rootstackhook
- end = stacktop - sizeofaddr
- addr = end.address[0]
- while addr != end:
- addr += rootstackhook(callback, gc, addr)
-
- def collect_more_stacks(callback):
- ll_assert(get_aid() == gcdata.active_thread,
- "collect_more_stacks(): invalid active_thread")
- gcdata.thread_stacks.foreach(collect_stack, callback)
-
- def _free_if_not_current(aid, stacktop, _):
- if stacktop != llmemory.NULL and aid != gcdata.active_thread:
- end = stacktop - sizeofaddr
- base = end.address[0]
- llmemory.raw_free(base)
-
def thread_after_fork(result_of_fork, opaqueaddr):
# we don't need a thread_before_fork in this case, so
# opaqueaddr == NULL. This is called after fork().
@@ -192,28 +162,210 @@
# We are in the child process. Assumes that only the
# current thread survived, so frees the shadow stacks
# of all the other ones.
- gcdata.thread_stacks.foreach(_free_if_not_current, None)
- # Clears the dict (including the current thread, which
- # was an invalid entry anyway and will be recreated by
- # the next call to save_away_current_stack()).
gcdata.thread_stacks.clear()
# Finally, reset the stored thread IDs, in case it
# changed because of fork(). Also change the main
# thread to the current one (because there is not any
# other left).
- aid = get_aid()
- gcdata.main_thread = aid
- gcdata.active_thread = aid
+ tid = get_tid()
+ gcdata.main_tid = tid
+ gcdata.active_tid = tid
self.thread_setup = thread_setup
- self.thread_prepare_ptr = getfn(thread_prepare, [], annmodel.s_None)
self.thread_run_ptr = getfn(thread_run, [], annmodel.s_None,
- inline=True)
- # no thread_start_ptr here
- self.thread_die_ptr = getfn(thread_die, [], annmodel.s_None)
+ inline=True, minimal_transform=False)
+ self.thread_die_ptr = getfn(thread_die, [], annmodel.s_None,
+ minimal_transform=False)
# no thread_before_fork_ptr here
self.thread_after_fork_ptr = getfn(thread_after_fork,
[annmodel.SomeInteger(),
annmodel.SomeAddress()],
- annmodel.s_None)
- self.collect_stacks_from_other_threads = collect_more_stacks
+ annmodel.s_None,
+ minimal_transform=False)
+
+ def need_stacklet_support(self, gctransformer, getfn):
+ shadow_stack_pool = self.shadow_stack_pool
+ SHADOWSTACKREF = get_shadowstackref(gctransformer)
+
+ def gc_shadowstackref_new():
+ ssref = shadow_stack_pool.allocate(SHADOWSTACKREF)
+ return lltype.cast_opaque_ptr(llmemory.GCREF, ssref)
+
+ def gc_shadowstackref_context(gcref):
+ ssref = lltype.cast_opaque_ptr(lltype.Ptr(SHADOWSTACKREF), gcref)
+ return ssref.context
+
+ def gc_shadowstackref_destroy(gcref):
+ ssref = lltype.cast_opaque_ptr(lltype.Ptr(SHADOWSTACKREF), gcref)
+ shadow_stack_pool.destroy(ssref)
+
+ def gc_save_current_state_away(gcref, ncontext):
+ ssref = lltype.cast_opaque_ptr(lltype.Ptr(SHADOWSTACKREF), gcref)
+ shadow_stack_pool.save_current_state_away(ssref, ncontext)
+
+ def gc_forget_current_state():
+ shadow_stack_pool.forget_current_state()
+
+ def gc_restore_state_from(gcref):
+ ssref = lltype.cast_opaque_ptr(lltype.Ptr(SHADOWSTACKREF), gcref)
+ shadow_stack_pool.restore_state_from(ssref)
+
+ def gc_start_fresh_new_state():
+ shadow_stack_pool.start_fresh_new_state()
+
+ s_gcref = annmodel.SomePtr(llmemory.GCREF)
+ s_addr = annmodel.SomeAddress()
+ self.gc_shadowstackref_new_ptr = getfn(gc_shadowstackref_new,
+ [], s_gcref,
+ minimal_transform=False)
+ self.gc_shadowstackref_context_ptr = getfn(gc_shadowstackref_context,
+ [s_gcref], s_addr,
+ inline=True)
+ self.gc_shadowstackref_destroy_ptr = getfn(gc_shadowstackref_destroy,
+ [s_gcref], annmodel.s_None,
+ inline=True)
+ self.gc_save_current_state_away_ptr = getfn(gc_save_current_state_away,
+ [s_gcref, s_addr],
+ annmodel.s_None,
+ inline=True)
+ self.gc_forget_current_state_ptr = getfn(gc_forget_current_state,
+ [], annmodel.s_None,
+ inline=True)
+ self.gc_restore_state_from_ptr = getfn(gc_restore_state_from,
+ [s_gcref], annmodel.s_None,
+ inline=True)
+ self.gc_start_fresh_new_state_ptr = getfn(gc_start_fresh_new_state,
+ [], annmodel.s_None,
+ inline=True)
+ # fish...
+ translator = gctransformer.translator
+ if hasattr(translator, '_jit2gc'):
+ from pypy.rlib._rffi_stacklet import _translate_pointer
+ root_iterator = translator._jit2gc['root_iterator']
+ root_iterator.translateptr = _translate_pointer
+
+# ____________________________________________________________
+
+class ShadowStackPool(object):
+ """Manages a pool of shadowstacks. The MAX most recently used
+ shadowstacks are fully allocated and can be directly jumped into.
+ The rest are stored in a more virtual-memory-friendly way, i.e.
+ with just the right amount malloced. Before they can run, they
+ must be copied into a full shadowstack. XXX NOT IMPLEMENTED SO FAR!
+ """
+ _alloc_flavor_ = "raw"
+ root_stack_depth = 163840
+
+ #MAX = 20 not implemented yet
+
+ def __init__(self, gcdata):
+ self.unused_full_stack = llmemory.NULL
+ self.gcdata = gcdata
+
+ def initial_setup(self):
+ self._prepare_unused_stack()
+ self.start_fresh_new_state()
+
+ def allocate(self, SHADOWSTACKREF):
+ """Allocate an empty SHADOWSTACKREF object."""
+ return lltype.malloc(SHADOWSTACKREF, zero=True)
+
+ def save_current_state_away(self, shadowstackref, ncontext):
+ """Save the current state away into 'shadowstackref'.
+ This either works, or raise MemoryError and nothing is done.
+ To do a switch, first call save_current_state_away() or
+ forget_current_state(), and then call restore_state_from()
+ or start_fresh_new_state().
+ """
+ self._prepare_unused_stack()
+ shadowstackref.base = self.gcdata.root_stack_base
+ shadowstackref.top = self.gcdata.root_stack_top
+ shadowstackref.context = ncontext
+ ll_assert(shadowstackref.base <= shadowstackref.top,
+ "save_current_state_away: broken shadowstack")
+ #shadowstackref.fullstack = True
+ #
+ # cannot use llop.gc_assume_young_pointers() here, because
+ # we are in a minimally-transformed GC helper :-/
+ gc = self.gcdata.gc
+ if hasattr(gc.__class__, 'assume_young_pointers'):
+ shadowstackadr = llmemory.cast_ptr_to_adr(shadowstackref)
+ gc.assume_young_pointers(shadowstackadr)
+ #
+ self.gcdata.root_stack_top = llmemory.NULL # to detect missing restore
+
+ def forget_current_state(self):
+ if self.unused_full_stack:
+ llmemory.raw_free(self.unused_full_stack)
+ self.unused_full_stack = self.gcdata.root_stack_base
+ self.gcdata.root_stack_top = llmemory.NULL # to detect missing restore
+
+ def restore_state_from(self, shadowstackref):
+ ll_assert(bool(shadowstackref.base), "empty shadowstackref!")
+ ll_assert(shadowstackref.base <= shadowstackref.top,
+ "restore_state_from: broken shadowstack")
+ self.gcdata.root_stack_base = shadowstackref.base
+ self.gcdata.root_stack_top = shadowstackref.top
+ self.destroy(shadowstackref)
+
+ def start_fresh_new_state(self):
+ self.gcdata.root_stack_base = self.unused_full_stack
+ self.gcdata.root_stack_top = self.unused_full_stack
+ self.unused_full_stack = llmemory.NULL
+
+ def destroy(self, shadowstackref):
+ shadowstackref.base = llmemory.NULL
+ shadowstackref.top = llmemory.NULL
+ shadowstackref.context = llmemory.NULL
+
+ def _prepare_unused_stack(self):
+ if self.unused_full_stack == llmemory.NULL:
+ root_stack_size = sizeofaddr * self.root_stack_depth
+ self.unused_full_stack = llmemory.raw_malloc(root_stack_size)
+ if self.unused_full_stack == llmemory.NULL:
+ raise MemoryError
+
+
+def get_shadowstackref(gctransformer):
+ if hasattr(gctransformer, '_SHADOWSTACKREF'):
+ return gctransformer._SHADOWSTACKREF
+
+ SHADOWSTACKREFPTR = lltype.Ptr(lltype.GcForwardReference())
+ SHADOWSTACKREF = lltype.GcStruct('ShadowStackRef',
+ ('base', llmemory.Address),
+ ('top', llmemory.Address),
+ ('context', llmemory.Address),
+ #('fullstack', lltype.Bool),
+ rtti=True)
+ SHADOWSTACKREFPTR.TO.become(SHADOWSTACKREF)
+
+ translator = gctransformer.translator
+ if hasattr(translator, '_jit2gc'):
+ gc = gctransformer.gcdata.gc
+ root_iterator = translator._jit2gc['root_iterator']
+ def customtrace(obj, prev):
+ obj = llmemory.cast_adr_to_ptr(obj, SHADOWSTACKREFPTR)
+ if not prev:
+ root_iterator.context = obj.context
+ next = obj.base
+ else:
+ next = prev + sizeofaddr
+ return root_iterator.next(gc, next, obj.top)
+ else:
+ def customtrace(obj, prev):
+ # a simple but not JIT-ready version
+ if not prev:
+ next = llmemory.cast_adr_to_ptr(obj, SHADOWSTACKREFPTR).base
+ else:
+ next = prev + sizeofaddr
+ if next == llmemory.cast_adr_to_ptr(obj, SHADOWSTACKREFPTR).top:
+ next = llmemory.NULL
+ return next
+
+ CUSTOMTRACEFUNC = lltype.FuncType([llmemory.Address, llmemory.Address],
+ llmemory.Address)
+ customtraceptr = llhelper(lltype.Ptr(CUSTOMTRACEFUNC), customtrace)
+ lltype.attachRuntimeTypeInfo(SHADOWSTACKREF, customtraceptr=customtraceptr)
+
+ gctransformer._SHADOWSTACKREF = SHADOWSTACKREF
+ return SHADOWSTACKREF
diff --git a/pypy/rpython/memory/gctransform/test/test_framework.py b/pypy/rpython/memory/gctransform/test/test_framework.py
--- a/pypy/rpython/memory/gctransform/test/test_framework.py
+++ b/pypy/rpython/memory/gctransform/test/test_framework.py
@@ -70,23 +70,6 @@
gg = graphof(t, g)
assert not CollectAnalyzer(t).analyze_direct_call(gg)
-def test_cancollect_stack_check():
- from pypy.rlib import rstack
-
- def with_check():
- rstack.stack_check()
-
- t = rtype(with_check, [])
- with_check_graph = graphof(t, with_check)
-
- assert not t.config.translation.stackless
- can_collect = CollectAnalyzer(t).analyze_direct_call(with_check_graph)
- assert not can_collect
-
- t.config.translation.stackless = True
- can_collect = CollectAnalyzer(t).analyze_direct_call(with_check_graph)
- assert can_collect
-
def test_cancollect_external():
fext1 = rffi.llexternal('fext1', [], lltype.Void, threadsafe=False)
def g():
diff --git a/pypy/rpython/memory/gctransform/transform.py b/pypy/rpython/memory/gctransform/transform.py
--- a/pypy/rpython/memory/gctransform/transform.py
+++ b/pypy/rpython/memory/gctransform/transform.py
@@ -307,7 +307,6 @@
if backendopt:
self.mixlevelannotator.backend_optimize()
# Make sure that the database also sees all finalizers now.
- # XXX we need to think more about the interaction with stackless...
# It is likely that the finalizers need special support there
newgcdependencies = self.ll_finalizers_ptrs
return newgcdependencies
diff --git a/pypy/rpython/memory/gctypelayout.py b/pypy/rpython/memory/gctypelayout.py
--- a/pypy/rpython/memory/gctypelayout.py
+++ b/pypy/rpython/memory/gctypelayout.py
@@ -17,13 +17,21 @@
_alloc_flavor_ = 'raw'
OFFSETS_TO_GC_PTR = lltype.Array(lltype.Signed)
- ADDRESS_VOID_FUNC = lltype.FuncType([llmemory.Address], lltype.Void)
- FINALIZERTYPE = lltype.Ptr(ADDRESS_VOID_FUNC)
+
+ # When used as a finalizer, the following functions only take one
+ # address and ignore the second, and return NULL. When used as a
+ # custom tracer (CT), it enumerates the addresses that contain GCREFs.
+ # It is called with the object as first argument, and the previous
+ # returned address (or NULL the first time) as the second argument.
+ FINALIZER_OR_CT_FUNC = lltype.FuncType([llmemory.Address,
+ llmemory.Address],
+ llmemory.Address)
+ FINALIZER_OR_CT = lltype.Ptr(FINALIZER_OR_CT_FUNC)
# structure describing the layout of a typeid
TYPE_INFO = lltype.Struct("type_info",
("infobits", lltype.Signed), # combination of the T_xxx consts
- ("finalizer", FINALIZERTYPE),
+ ("finalizer_or_customtrace", FINALIZER_OR_CT),
("fixedsize", lltype.Signed),
("ofstoptrs", lltype.Ptr(OFFSETS_TO_GC_PTR)),
hints={'immutable': True},
@@ -71,7 +79,11 @@
return (infobits & T_IS_GCARRAY_OF_GCPTR) != 0
def q_finalizer(self, typeid):
- return self.get(typeid).finalizer
+ typeinfo = self.get(typeid)
+ if typeinfo.infobits & T_HAS_FINALIZER:
+ return typeinfo.finalizer_or_customtrace
+ else:
+ return lltype.nullptr(GCData.FINALIZER_OR_CT_FUNC)
def q_offsets_to_gc_pointers(self, typeid):
return self.get(typeid).ofstoptrs
@@ -105,6 +117,25 @@
infobits = self.get(typeid).infobits
return infobits & T_IS_RPYTHON_INSTANCE != 0
+ def q_has_custom_trace(self, typeid):
+ infobits = self.get(typeid).infobits
+ return infobits & T_HAS_CUSTOM_TRACE != 0
+
+ def q_get_custom_trace(self, typeid):
+ ll_assert(self.q_has_custom_trace(typeid),
+ "T_HAS_CUSTOM_TRACE missing")
+ typeinfo = self.get(typeid)
+ return typeinfo.finalizer_or_customtrace
+
+ def q_fast_path_tracing(self, typeid):
+ # return True if none of the flags T_HAS_GCPTR_IN_VARSIZE,
+ # T_IS_GCARRAY_OF_GCPTR or T_HAS_CUSTOM_TRACE is set
+ T_ANY_SLOW_FLAG = (T_HAS_GCPTR_IN_VARSIZE |
+ T_IS_GCARRAY_OF_GCPTR |
+ T_HAS_CUSTOM_TRACE)
+ infobits = self.get(typeid).infobits
+ return infobits & T_ANY_SLOW_FLAG == 0
+
def set_query_functions(self, gc):
gc.set_query_functions(
self.q_is_varsize,
@@ -119,18 +150,23 @@
self.q_varsize_offsets_to_gcpointers_in_var_part,
self.q_weakpointer_offset,
self.q_member_index,
- self.q_is_rpython_class)
+ self.q_is_rpython_class,
+ self.q_has_custom_trace,
+ self.q_get_custom_trace,
+ self.q_fast_path_tracing)
# the lowest 16bits are used to store group member index
-T_MEMBER_INDEX = 0xffff
-T_IS_VARSIZE = 0x10000
-T_HAS_GCPTR_IN_VARSIZE = 0x20000
-T_IS_GCARRAY_OF_GCPTR = 0x40000
-T_IS_WEAKREF = 0x80000
+T_MEMBER_INDEX = 0xffff
+T_IS_VARSIZE = 0x010000
+T_HAS_GCPTR_IN_VARSIZE = 0x020000
+T_IS_GCARRAY_OF_GCPTR = 0x040000
+T_IS_WEAKREF = 0x080000
T_IS_RPYTHON_INSTANCE = 0x100000 # the type is a subclass of OBJECT
+T_HAS_FINALIZER = 0x200000
+T_HAS_CUSTOM_TRACE = 0x400000
T_KEY_MASK = intmask(0xFF000000)
-T_KEY_VALUE = intmask(0x7A000000) # bug detection only
+T_KEY_VALUE = intmask(0x5A000000) # bug detection only
def _check_valid_type_info(p):
ll_assert(p.infobits & T_KEY_MASK == T_KEY_VALUE, "invalid type_id")
@@ -151,7 +187,18 @@
offsets = offsets_to_gc_pointers(TYPE)
infobits = index
info.ofstoptrs = builder.offsets2table(offsets, TYPE)
- info.finalizer = builder.make_finalizer_funcptr_for_type(TYPE)
+ #
+ kind_and_fptr = builder.special_funcptr_for_type(TYPE)
+ if kind_and_fptr is not None:
+ kind, fptr = kind_and_fptr
+ info.finalizer_or_customtrace = fptr
+ if kind == "finalizer":
+ infobits |= T_HAS_FINALIZER
+ elif kind == "custom_trace":
+ infobits |= T_HAS_CUSTOM_TRACE
+ else:
+ assert 0, kind
+ #
if not TYPE._is_varsize():
info.fixedsize = llarena.round_up_for_allocation(
llmemory.sizeof(TYPE), builder.GCClass.object_minimal_size)
@@ -216,7 +263,7 @@
# for debugging, the following list collects all the prebuilt
# GcStructs and GcArrays
self.all_prebuilt_gc = []
- self.finalizer_funcptrs = {}
+ self._special_funcptrs = {}
self.offsettable_cache = {}
def make_type_info_group(self):
@@ -317,16 +364,29 @@
self.offsettable_cache = None
return self.type_info_group
- def finalizer_funcptr_for_type(self, TYPE):
- if TYPE in self.finalizer_funcptrs:
- return self.finalizer_funcptrs[TYPE]
- fptr = self.make_finalizer_funcptr_for_type(TYPE)
- self.finalizer_funcptrs[TYPE] = fptr
- return fptr
+ def special_funcptr_for_type(self, TYPE):
+ if TYPE in self._special_funcptrs:
+ return self._special_funcptrs[TYPE]
+ fptr1 = self.make_finalizer_funcptr_for_type(TYPE)
+ fptr2 = self.make_custom_trace_funcptr_for_type(TYPE)
+ assert not (fptr1 and fptr2), (
+ "type %r needs both a finalizer and a custom tracer" % (TYPE,))
+ if fptr1:
+ kind_and_fptr = "finalizer", fptr1
+ elif fptr2:
+ kind_and_fptr = "custom_trace", fptr2
+ else:
+ kind_and_fptr = None
+ self._special_funcptrs[TYPE] = kind_and_fptr
+ return kind_and_fptr
def make_finalizer_funcptr_for_type(self, TYPE):
# must be overridden for proper finalizer support
- return lltype.nullptr(GCData.ADDRESS_VOID_FUNC)
+ return None
+
+ def make_custom_trace_funcptr_for_type(self, TYPE):
+ # must be overridden for proper custom tracer support
+ return None
def initialize_gc_query_function(self, gc):
return GCData(self.type_info_group).set_query_functions(gc)
diff --git a/pypy/rpython/memory/gcwrapper.py b/pypy/rpython/memory/gcwrapper.py
--- a/pypy/rpython/memory/gcwrapper.py
+++ b/pypy/rpython/memory/gcwrapper.py
@@ -196,17 +196,28 @@
DESTR_ARG = lltype.typeOf(destrptr).TO.ARGS[0]
destrgraph = destrptr._obj.graph
else:
- return lltype.nullptr(gctypelayout.GCData.FINALIZERTYPE.TO)
+ return None
assert not type_contains_pyobjs(TYPE), "not implemented"
- def ll_finalizer(addr):
+ def ll_finalizer(addr, dummy):
+ assert dummy == llmemory.NULL
try:
v = llmemory.cast_adr_to_ptr(addr, DESTR_ARG)
self.llinterp.eval_graph(destrgraph, [v], recursive=True)
except llinterp.LLException:
raise RuntimeError(
"a finalizer raised an exception, shouldn't happen")
- return llhelper(gctypelayout.GCData.FINALIZERTYPE, ll_finalizer)
+ return llmemory.NULL
+ return llhelper(gctypelayout.GCData.FINALIZER_OR_CT, ll_finalizer)
+
+ def make_custom_trace_funcptr_for_type(self, TYPE):
+ from pypy.rpython.memory.gctransform.support import get_rtti, \
+ type_contains_pyobjs
+ rtti = get_rtti(TYPE)
+ if rtti is not None and hasattr(rtti._obj, 'custom_trace_funcptr'):
+ return rtti._obj.custom_trace_funcptr
+ else:
+ return None
def collect_constants(graphs):
diff --git a/pypy/rpython/memory/test/test_gc.py b/pypy/rpython/memory/test/test_gc.py
--- a/pypy/rpython/memory/test/test_gc.py
+++ b/pypy/rpython/memory/test/test_gc.py
@@ -237,6 +237,46 @@
res = self.interpret(f, [5])
assert 160 <= res <= 165
+ def test_custom_trace(self):
+ from pypy.rpython.annlowlevel import llhelper
+ from pypy.rpython.lltypesystem import llmemory
+ from pypy.rpython.lltypesystem.llarena import ArenaError
+ #
+ S = lltype.GcStruct('S', ('x', llmemory.Address),
+ ('y', llmemory.Address), rtti=True)
+ T = lltype.GcStruct('T', ('z', lltype.Signed))
+ offset_of_x = llmemory.offsetof(S, 'x')
+ def customtrace(obj, prev):
+ if not prev:
+ return obj + offset_of_x
+ else:
+ return llmemory.NULL
+ CUSTOMTRACEFUNC = lltype.FuncType([llmemory.Address, llmemory.Address],
+ llmemory.Address)
+ customtraceptr = llhelper(lltype.Ptr(CUSTOMTRACEFUNC), customtrace)
+ lltype.attachRuntimeTypeInfo(S, customtraceptr=customtraceptr)
+ #
+ for attrname in ['x', 'y']:
+ def setup():
+ s1 = lltype.malloc(S)
+ tx = lltype.malloc(T)
+ tx.z = 42
+ ty = lltype.malloc(T)
+ s1.x = llmemory.cast_ptr_to_adr(tx)
+ s1.y = llmemory.cast_ptr_to_adr(ty)
+ return s1
+ def f():
+ s1 = setup()
+ llop.gc__collect(lltype.Void)
+ return llmemory.cast_adr_to_ptr(getattr(s1, attrname),
+ lltype.Ptr(T))
+ if attrname == 'x':
+ res = self.interpret(f, [])
+ assert res.z == 42
+ else:
+ py.test.raises((RuntimeError, ArenaError),
+ self.interpret, f, [])
+
def test_weakref(self):
import weakref, gc
class A(object):
diff --git a/pypy/rpython/memory/test/test_transformed_gc.py b/pypy/rpython/memory/test/test_transformed_gc.py
--- a/pypy/rpython/memory/test/test_transformed_gc.py
+++ b/pypy/rpython/memory/test/test_transformed_gc.py
@@ -7,7 +7,6 @@
from pypy.rpython.lltypesystem import lltype, llmemory, llarena, rffi, llgroup
from pypy.rpython.memory.gctransform import framework
from pypy.rpython.lltypesystem.lloperation import llop, void
-from pypy.rpython.memory.gc.marksweep import X_CLONE, X_POOL, X_POOL_PTR
from pypy.rlib.objectmodel import compute_unique_id, we_are_translated
from pypy.rlib.debug import ll_assert
from pypy.rlib import rgc
@@ -18,15 +17,13 @@
WORD = LONG_BIT // 8
-def rtype(func, inputtypes, specialize=True, gcname='ref', stacklessgc=False,
+def rtype(func, inputtypes, specialize=True, gcname='ref',
backendopt=False, **extraconfigopts):
from pypy.translator.translator import TranslationContext
t = TranslationContext()
# XXX XXX XXX mess
t.config.translation.gc = gcname
t.config.translation.gcremovetypeptr = True
- if stacklessgc:
- t.config.translation.gcrootfinder = "stackless"
t.config.set(**extraconfigopts)
ann = t.buildannotator(policy=annpolicy.StrictAnnotatorPolicy())
ann.build_types(func, inputtypes)
@@ -44,7 +41,6 @@
class GCTest(object):
gcpolicy = None
- stacklessgc = False
GC_CAN_MOVE = False
GC_CAN_MALLOC_NONMOVABLE = True
taggedpointers = False
@@ -103,7 +99,6 @@
s_args = annmodel.SomePtr(lltype.Ptr(ARGS))
t = rtype(entrypoint, [s_args], gcname=cls.gcname,
- stacklessgc=cls.stacklessgc,
taggedpointers=cls.taggedpointers)
for fixup in mixlevelstuff:
@@ -410,6 +405,40 @@
res = run([5, 42]) #XXX pure lazyness here too
assert 160 <= res <= 165
+ def define_custom_trace(cls):
+ from pypy.rpython.annlowlevel import llhelper
+ from pypy.rpython.lltypesystem import llmemory
+ #
+ S = lltype.GcStruct('S', ('x', llmemory.Address), rtti=True)
+ T = lltype.GcStruct('T', ('z', lltype.Signed))
+ offset_of_x = llmemory.offsetof(S, 'x')
+ def customtrace(obj, prev):
+ if not prev:
+ return obj + offset_of_x
+ else:
+ return llmemory.NULL
+ CUSTOMTRACEFUNC = lltype.FuncType([llmemory.Address, llmemory.Address],
+ llmemory.Address)
+ customtraceptr = llhelper(lltype.Ptr(CUSTOMTRACEFUNC), customtrace)
+ lltype.attachRuntimeTypeInfo(S, customtraceptr=customtraceptr)
+ #
+ def setup():
+ s1 = lltype.malloc(S)
+ tx = lltype.malloc(T)
+ tx.z = 4243
+ s1.x = llmemory.cast_ptr_to_adr(tx)
+ return s1
+ def f():
+ s1 = setup()
+ llop.gc__collect(lltype.Void)
+ return llmemory.cast_adr_to_ptr(s1.x, lltype.Ptr(T)).z
+ return f
+
+ def test_custom_trace(self):
+ run = self.runner("custom_trace")
+ res = run([])
+ assert res == 4243
+
def define_weakref(cls):
import weakref, gc
class A(object):
@@ -777,7 +806,6 @@
if op.opname == 'do_malloc_fixedsize_clear':
op.args = [Constant(type_id, llgroup.HALFWORD),
Constant(llmemory.sizeof(P), lltype.Signed),
- Constant(True, lltype.Bool), # can_collect
Constant(False, lltype.Bool), # has_finalizer
Constant(False, lltype.Bool)] # contains_weakptr
break
@@ -814,7 +842,6 @@
if op.opname == 'do_malloc_fixedsize_clear':
op.args = [Constant(type_id, llgroup.HALFWORD),
Constant(llmemory.sizeof(P), lltype.Signed),
- Constant(True, lltype.Bool), # can_collect
Constant(False, lltype.Bool), # has_finalizer
Constant(False, lltype.Bool)] # contains_weakptr
break
@@ -910,234 +937,6 @@
root_stack_depth = 200
- def define_cloning(cls):
- B = lltype.GcStruct('B', ('x', lltype.Signed))
- A = lltype.GcStruct('A', ('b', lltype.Ptr(B)),
- ('unused', lltype.Ptr(B)))
- def make(n):
- b = lltype.malloc(B)
- b.x = n
- a = lltype.malloc(A)
- a.b = b
- return a
- def func():
- a1 = make(111)
- # start recording mallocs in a new pool
- oldpool = llop.gc_x_swap_pool(X_POOL_PTR, lltype.nullptr(X_POOL))
- # the following a2 goes into the new list
- a2 = make(222)
- # now put the old pool back and get the new pool
- newpool = llop.gc_x_swap_pool(X_POOL_PTR, oldpool)
- a3 = make(333)
- # clone a2
- a2ref = lltype.cast_opaque_ptr(llmemory.GCREF, a2)
- clonedata = lltype.malloc(X_CLONE)
- clonedata.gcobjectptr = a2ref
- clonedata.pool = newpool
- llop.gc_x_clone(lltype.Void, clonedata)
- a2copyref = clonedata.gcobjectptr
- a2copy = lltype.cast_opaque_ptr(lltype.Ptr(A), a2copyref)
- a2copy.b.x = 444
- return a1.b.x * 1000000 + a2.b.x * 1000 + a3.b.x
-
- return func
-
- def test_cloning(self):
- run = self.runner("cloning")
- res = run([])
- assert res == 111222333
-
- def define_cloning_varsize(cls):
- B = lltype.GcStruct('B', ('x', lltype.Signed))
- A = lltype.GcStruct('A', ('b', lltype.Ptr(B)),
- ('more', lltype.Array(lltype.Ptr(B))))
- def make(n):
- b = lltype.malloc(B)
- b.x = n
- a = lltype.malloc(A, 2)
- a.b = b
- a.more[0] = lltype.malloc(B)
- a.more[0].x = n*10
- a.more[1] = lltype.malloc(B)
- a.more[1].x = n*10+1
- return a
- def func():
- oldpool = llop.gc_x_swap_pool(X_POOL_PTR, lltype.nullptr(X_POOL))
- a2 = make(22)
- newpool = llop.gc_x_swap_pool(X_POOL_PTR, oldpool)
- # clone a2
- a2ref = lltype.cast_opaque_ptr(llmemory.GCREF, a2)
- clonedata = lltype.malloc(X_CLONE)
- clonedata.gcobjectptr = a2ref
- clonedata.pool = newpool
- llop.gc_x_clone(lltype.Void, clonedata)
- a2copyref = clonedata.gcobjectptr
- a2copy = lltype.cast_opaque_ptr(lltype.Ptr(A), a2copyref)
- a2copy.b.x = 44
- a2copy.more[0].x = 440
- a2copy.more[1].x = 441
- return a2.b.x * 1000000 + a2.more[0].x * 1000 + a2.more[1].x
-
- return func
-
- def test_cloning_varsize(self):
- run = self.runner("cloning_varsize")
- res = run([])
- assert res == 22220221
-
- def define_cloning_highlevel(cls):
- class A:
- pass
- class B(A):
- pass
- def func(n, dummy):
- if n > 5:
- x = A()
- else:
- x = B()
- x.bvalue = 123
- x.next = A()
- x.next.next = x
- y, newpool = rgc.gc_clone(x, None)
- assert y is not x
- assert y.next is not x
- assert y is not x.next
- assert y.next is not x.next
- assert y is not y.next
- assert y is y.next.next
- if isinstance(y, B):
- assert n <= 5
- assert y.bvalue == 123
- else:
- assert n > 5
- return 1
-
- return func
-
- def test_cloning_highlevel(self):
- run = self.runner("cloning_highlevel")
- res = run([3, 0])
- assert res == 1
- res = run([7, 0])
- assert res == 1
-
- def define_cloning_highlevel_varsize(cls):
- class A:
- pass
- def func(n, dummy):
- lst = [A() for i in range(n)]
- for a in lst:
- a.value = 1
- lst2, newpool = rgc.gc_clone(lst, None)
- for i in range(n):
- a = A()
- a.value = i
- lst.append(a)
- lst[i].value = 4 + i
- lst2[i].value = 7 + i
-
- n = 0
- for a in lst:
- n = n*10 + a.value
- for a in lst2:
- n = n*10 + a.value
- return n
-
- return func
-
- def test_cloning_highlevel_varsize(self):
- run = self.runner("cloning_highlevel_varsize")
- res = run([3, 0])
- assert res == 456012789
-
- def define_tree_cloning(cls):
- import os
- # this makes a tree of calls. Each leaf stores its path (a linked
- # list) in 'result'. Paths are mutated in-place but the leaves don't
- # see each other's mutations because of x_clone.
- STUFF = lltype.FixedSizeArray(lltype.Signed, 21)
- NODE = lltype.GcForwardReference()
- NODE.become(lltype.GcStruct('node', ('index', lltype.Signed),
- ('counter', lltype.Signed),
- ('next', lltype.Ptr(NODE)),
- ('use_some_space', STUFF)))
- PATHARRAY = lltype.GcArray(lltype.Ptr(NODE))
- clonedata = lltype.malloc(X_CLONE)
-
- def clone(node):
- # that's for testing if the test is correct...
- if not node:
- return node
- newnode = lltype.malloc(NODE)
- newnode.index = node.index
- newnode.counter = node.counter
- newnode.next = clone(node.next)
- return newnode
-
- def do_call(result, path, index, remaining_depth):
- # clone the while path
- clonedata.gcobjectptr = lltype.cast_opaque_ptr(llmemory.GCREF,
- path)
- clonedata.pool = lltype.nullptr(X_POOL)
- llop.gc_x_clone(lltype.Void, clonedata)
- # install the new pool as the current one
- parentpool = llop.gc_x_swap_pool(X_POOL_PTR, clonedata.pool)
- path = lltype.cast_opaque_ptr(lltype.Ptr(NODE),
- clonedata.gcobjectptr)
-
- # The above should have the same effect as:
- # path = clone(path)
-
- # bump all the path node counters by one
- p = path
- while p:
- p.counter += 1
- p = p.next
-
- if remaining_depth == 0:
- llop.debug_print(lltype.Void, "setting", index, "with", path)
- result[index] = path # leaf
- else:
- node = lltype.malloc(NODE)
- node.index = index * 2
- node.counter = 0
- node.next = path
- do_call(result, node, index * 2, remaining_depth - 1)
- node.index += 1 # mutation!
- do_call(result, node, index * 2 + 1, remaining_depth - 1)
-
- # restore the parent pool
- llop.gc_x_swap_pool(X_POOL_PTR, parentpool)
-
- def check(path, index, level, depth):
- if level == depth:
- assert index == 0
- assert not path
- else:
- assert path.index == index
- assert path.counter == level + 1
- check(path.next, index >> 1, level + 1, depth)
-
- def func(depth, dummy):
- result = lltype.malloc(PATHARRAY, 1 << depth)
- os.write(2, 'building tree... ')
- do_call(result, lltype.nullptr(NODE), 0, depth)
- os.write(2, 'checking tree... ')
- #from pypy.rpython.lltypesystem.lloperation import llop
- #llop.debug_view(lltype.Void, result,
- # llop.gc_x_size_header(lltype.Signed))
- for i in range(1 << depth):
- check(result[i], i, 0, depth)
- os.write(2, 'ok\n')
- return 1
- return func
-
- def test_tree_cloning(self):
- run = self.runner("tree_cloning")
- res = run([3, 0])
- assert res == 1
-
-
class TestPrintingGC(GenericGCTests):
gcname = "statistics"
diff --git a/pypy/rpython/module/ll_os.py b/pypy/rpython/module/ll_os.py
--- a/pypy/rpython/module/ll_os.py
+++ b/pypy/rpython/module/ll_os.py
@@ -383,6 +383,20 @@
return extdef([int, int], s_None, llimpl=dup2_llimpl,
export_name="ll_os.ll_os_dup2")
+ @registering_if(os, "getlogin", condition=not _WIN32)
+ def register_os_getlogin(self):
+ os_getlogin = self.llexternal('getlogin', [], rffi.CCHARP)
+
+ def getlogin_llimpl():
+ result = os_getlogin()
+ if not result:
+ raise OSError(rposix.get_errno(), "getlogin failed")
+
+ return rffi.charp2str(result)
+
+ return extdef([], str, llimpl=getlogin_llimpl,
+ export_name="ll_os.ll_os_getlogin")
+
@registering_str_unicode(os.utime)
def register_os_utime(self, traits):
UTIMBUFP = lltype.Ptr(self.UTIMBUF)
diff --git a/pypy/rpython/module/test/test_ll_os.py b/pypy/rpython/module/test/test_ll_os.py
--- a/pypy/rpython/module/test/test_ll_os.py
+++ b/pypy/rpython/module/test/test_ll_os.py
@@ -35,6 +35,16 @@
for value in times:
assert isinstance(value, float)
+def test_getlogin():
+ if not hasattr(os, 'getlogin'):
+ py.test.skip('posix specific function')
+ try:
+ expected = os.getlogin()
+ except OSError, e:
+ py.test.skip("the underlying os.getlogin() failed: %s" % e)
+ data = getllimpl(os.getlogin)()
+ assert data == expected
+
def test_utimes():
if os.name != 'nt':
py.test.skip('Windows specific feature')
diff --git a/pypy/rpython/test/test_stack.py b/pypy/rpython/test/test_stack.py
deleted file mode 100644
--- a/pypy/rpython/test/test_stack.py
+++ /dev/null
@@ -1,16 +0,0 @@
-
-from pypy.rpython.test.test_llinterp import interpret
-from pypy.rlib.rstack import stack_frames_depth
-
-
-def test_interp_c():
- def f():
- return stack_frames_depth()
-
- def g():
- return f()
- res_f = interpret(f, [])
- res_g = interpret(g, [])
- assert res_f == 2
- assert res_g == 3
-
diff --git a/pypy/tool/pytest/test/test_appsupport.py b/pypy/tool/pytest/test/test_appsupport.py
--- a/pypy/tool/pytest/test/test_appsupport.py
+++ b/pypy/tool/pytest/test/test_appsupport.py
@@ -39,7 +39,7 @@
setpypyconftest(testdir)
testdir.makepyfile("""
class AppTestClass:
- spaceconfig = {"objspace.usemodules._stackless": True}
+ spaceconfig = {"objspace.usemodules._random": True}
def setup_class(cls):
assert 0
def test_applevel(self):
@@ -48,7 +48,7 @@
result = testdir.runpytest("-A")
assert result.ret == 0
if hasattr(sys, 'pypy_translation_info') and \
- sys.pypy_translation_info.get('objspace.usemodules._stackless'):
+ sys.pypy_translation_info.get('objspace.usemodules._random'):
result.stdout.fnmatch_lines(["*1 error*"])
else:
# setup_class didn't get called, otherwise it would error
@@ -58,9 +58,9 @@
setpypyconftest(testdir)
p = testdir.makepyfile("""
class TestClass:
- spaceconfig = {"objspace.usemodules._stackless": False}
+ spaceconfig = {"objspace.usemodules._random": False}
def setup_class(cls):
- assert not cls.space.config.objspace.usemodules._stackless
+ assert not cls.space.config.objspace.usemodules._random
def test_interp(self, space):
assert self.space is space
def test_interp2(self, space):
diff --git a/pypy/tool/release/package.py b/pypy/tool/release/package.py
--- a/pypy/tool/release/package.py
+++ b/pypy/tool/release/package.py
@@ -133,6 +133,8 @@
if copy_to_dir is not None:
print "Copying %s to %s" % (archive, copy_to_dir)
shutil.copy(archive, str(copy_to_dir))
+ else:
+ print "Ready in %s" % (builddir,)
return builddir # for tests
if __name__ == '__main__':
diff --git a/pypy/translator/backendopt/inline.py b/pypy/translator/backendopt/inline.py
--- a/pypy/translator/backendopt/inline.py
+++ b/pypy/translator/backendopt/inline.py
@@ -540,7 +540,6 @@
OP_WEIGHTS = {'same_as': 0,
'cast_pointer': 0,
'malloc': 2,
- 'yield_current_frame_to_caller': sys.maxint, # XXX bit extreme
'instrument_count': 0,
'debug_assert': -1,
}
diff --git a/pypy/translator/c/database.py b/pypy/translator/c/database.py
--- a/pypy/translator/c/database.py
+++ b/pypy/translator/c/database.py
@@ -29,13 +29,11 @@
def __init__(self, translator=None, standalone=False,
gcpolicyclass=None,
- stacklesstransformer=None,
thread_enabled=False,
sandbox=False):
self.translator = translator
self.standalone = standalone
self.sandbox = sandbox
- self.stacklesstransformer = stacklesstransformer
if gcpolicyclass is None:
gcpolicyclass = gc.RefcountingGcPolicy
self.gcpolicy = gcpolicyclass(self, thread_enabled)
@@ -251,8 +249,8 @@
else:
show_i = -1
- # The order of database completion is fragile with stackless and
- # gc transformers. Here is what occurs:
+ # The order of database completion is fragile with gc transformers.
+ # Here is what occurs:
#
# 1. follow dependencies recursively from the entry point: data
# structures pointing to other structures or functions, and
@@ -270,24 +268,12 @@
# ll_finalize(). New FuncNodes are built for them. No more
# FuncNodes can show up after this step.
#
- # 4. stacklesstransform.finish() - freeze the stackless resume point
- # table.
+ # 4. gctransformer.finish_tables() - freeze the gc types table.
#
- # 5. follow new dependencies (this should be only the new frozen
- # table, which contains only numbers and already-seen function
- # pointers).
- #
- # 6. gctransformer.finish_tables() - freeze the gc types table.
- #
- # 7. follow new dependencies (this should be only the gc type table,
+ # 5. follow new dependencies (this should be only the gc type table,
# which contains only numbers and pointers to ll_finalizer
# functions seen in step 3).
#
- # I think that there is no reason left at this point that force
- # step 4 to be done before step 6, nor to have a follow-new-
- # dependencies step inbetween. It is important though to have step 3
- # before steps 4 and 6.
- #
# This is implemented by interleaving the follow-new-dependencies
# steps with calls to the next 'finish' function from the following
# list:
@@ -295,10 +281,6 @@
if self.gctransformer:
finish_callbacks.append(('GC transformer: finished helpers',
self.gctransformer.finish_helpers))
- if self.stacklesstransformer:
- finish_callbacks.append(('Stackless transformer: finished',
- self.stacklesstransformer.finish))
- if self.gctransformer:
finish_callbacks.append(('GC transformer: finished tables',
self.gctransformer.get_finish_tables()))
diff --git a/pypy/translator/c/funcgen.py b/pypy/translator/c/funcgen.py
--- a/pypy/translator/c/funcgen.py
+++ b/pypy/translator/c/funcgen.py
@@ -46,9 +46,6 @@
self.gcpolicy = db.gcpolicy
self.exception_policy = exception_policy
self.functionname = functionname
- # apply the stackless transformation
- if db.stacklesstransformer:
- db.stacklesstransformer.transform_graph(graph)
# apply the exception transformation
if self.db.exctransformer:
self.db.exctransformer.create_exception_handling(self.graph)
diff --git a/pypy/translator/c/gc.py b/pypy/translator/c/gc.py
--- a/pypy/translator/c/gc.py
+++ b/pypy/translator/c/gc.py
@@ -11,7 +11,6 @@
from pypy.translator.tool.cbuild import ExternalCompilationInfo
class BasicGcPolicy(object):
- requires_stackless = False
stores_hash_at_the_end = False
def __init__(self, db, thread_enabled=False):
@@ -320,8 +319,10 @@
# still important to see it so that it can be followed as soon as
# the mixlevelannotator resolves it.
gctransf = self.db.gctransformer
- fptr = gctransf.finalizer_funcptr_for_type(structdefnode.STRUCT)
- self.db.get(fptr)
+ TYPE = structdefnode.STRUCT
+ kind_and_fptr = gctransf.special_funcptr_for_type(TYPE)
+ if kind_and_fptr:
+ self.db.get(kind_and_fptr[1])
def array_setup(self, arraydefnode):
pass
@@ -391,6 +392,9 @@
fieldname,
funcgen.expr(c_skipoffset)))
+ def OP_GC_ASSUME_YOUNG_POINTERS(self, funcgen, op):
+ raise Exception("the FramewokGCTransformer should handle this")
+
class AsmGcRootFrameworkGcPolicy(FrameworkGcPolicy):
transformerclass = asmgcroot.AsmGcRootFrameworkGCTransformer
diff --git a/pypy/translator/c/genc.py b/pypy/translator/c/genc.py
--- a/pypy/translator/c/genc.py
+++ b/pypy/translator/c/genc.py
@@ -120,8 +120,6 @@
self.originalentrypoint = entrypoint
self.config = config
self.gcpolicy = gcpolicy # for tests only, e.g. rpython/memory/
- if gcpolicy is not None and gcpolicy.requires_stackless:
- config.translation.stackless = True
self.eci = self.get_eci()
self.secondary_entrypoints = secondary_entrypoints
@@ -139,21 +137,8 @@
if not self.standalone:
raise NotImplementedError("--gcrootfinder=asmgcc requires standalone")
- if self.config.translation.stackless:
- if not self.standalone:
- raise Exception("stackless: only for stand-alone builds")
-
- from pypy.translator.stackless.transform import StacklessTransformer
- stacklesstransformer = StacklessTransformer(
- translator, self.originalentrypoint,
- stackless_gc=gcpolicyclass.requires_stackless)
- self.entrypoint = stacklesstransformer.slp_entry_point
- else:
- stacklesstransformer = None
-
db = LowLevelDatabase(translator, standalone=self.standalone,
gcpolicyclass=gcpolicyclass,
- stacklesstransformer=stacklesstransformer,
thread_enabled=self.config.translation.thread,
sandbox=self.config.translation.sandbox)
self.db = db
diff --git a/pypy/translator/c/src/mem.h b/pypy/translator/c/src/mem.h
--- a/pypy/translator/c/src/mem.h
+++ b/pypy/translator/c/src/mem.h
@@ -39,8 +39,9 @@
#define pypy_asm_keepalive(v) asm volatile ("/* keepalive %0 */" : : \
"g" (v))
-/* marker for trackgcroot.py */
-#define pypy_asm_stack_bottom() asm volatile ("/* GC_STACK_BOTTOM */" : : )
+/* marker for trackgcroot.py, and inhibits tail calls */
+#define pypy_asm_stack_bottom() asm volatile ("/* GC_STACK_BOTTOM */" : : : \
+ "memory")
#define OP_GC_ASMGCROOT_STATIC(i, r) r = \
i == 0 ? (void*)&__gcmapstart : \
diff --git a/pypy/translator/c/src/stack.h b/pypy/translator/c/src/stack.h
--- a/pypy/translator/c/src/stack.h
+++ b/pypy/translator/c/src/stack.h
@@ -15,7 +15,6 @@
extern long _LLstacktoobig_stack_length;
extern char _LLstacktoobig_report_error;
-void LL_stack_unwind(void);
char LL_stack_too_big_slowpath(long); /* returns 0 (ok) or 1 (too big) */
void LL_stack_set_length_fraction(double);
diff --git a/pypy/translator/c/src/stacklet/Makefile b/pypy/translator/c/src/stacklet/Makefile
new file mode 100644
--- /dev/null
+++ b/pypy/translator/c/src/stacklet/Makefile
@@ -0,0 +1,49 @@
+
+all: stacklet.so
+
+stacklet.so: stacklet.c stacklet.h
+ gcc -fPIC -shared -O2 -o $@ stacklet.c
+
+stacklet_g.so: stacklet.c stacklet.h
+ gcc -fPIC -shared -g -o $@ stacklet.c -DDEBUG_DUMP
+
+clean:
+ rm -fr stacklet.so stacklet_g.so
+ rm -fr run_tests_*_[go]
+
+
+DEBUG = -DDEBUG_DUMP
+
+tests: clean
+ make -j1 run-all-tests
+
+ALL_TESTS = tests-static-g \
+ tests-static-o \
+ tests-dynamic-g \
+ tests-dynamic-o
+
+run-all-tests: $(ALL_TESTS)
+ @echo "*** All test suites passed ***"
+
+tests-static-g: stacklet.c stacklet.h tests.c
+ gcc -Wall -g -o run_tests_static_g stacklet.c tests.c ${DEBUG}
+ run_tests_static_g
+
+tests-static-o: stacklet.c stacklet.h tests.c
+ gcc -Wall -g -O2 -o run_tests_static_o stacklet.c tests.c ${DEBUG}
+ run_tests_static_o
+
+tests-dynamic-g: stacklet_g.so tests.c
+ gcc -Wall -g -o run_tests_dynamic_g stacklet_g.so tests.c ${DEBUG}
+ LD_LIBRARY_PATH=. run_tests_dynamic_g
+
+tests-dynamic-o: stacklet.so tests.c
+ gcc -Wall -g -O2 -o run_tests_dynamic_o stacklet.so tests.c ${DEBUG}
+ LD_LIBRARY_PATH=. run_tests_dynamic_o
+
+tests-repeat: tests
+ python runtests.py run_tests_static_g > /dev/null
+ python runtests.py run_tests_static_o > /dev/null
+ LD_LIBRARY_PATH=. python runtests.py run_tests_dynamic_g > /dev/null
+ LD_LIBRARY_PATH=. python runtests.py run_tests_dynamic_o > /dev/null
+ @echo "*** All tests passed repeatedly ***"
diff --git a/pypy/translator/c/src/stacklet/runtests.py b/pypy/translator/c/src/stacklet/runtests.py
new file mode 100644
--- /dev/null
+++ b/pypy/translator/c/src/stacklet/runtests.py
@@ -0,0 +1,8 @@
+import os, sys
+
+
+
+for i in range(2000):
+ err = os.system("%s %d" % (sys.argv[1], i))
+ if err != 0:
+ raise OSError("return code %r" % (err,))
diff --git a/pypy/translator/c/src/stacklet/slp_platformselect.h b/pypy/translator/c/src/stacklet/slp_platformselect.h
new file mode 100644
--- /dev/null
+++ b/pypy/translator/c/src/stacklet/slp_platformselect.h
@@ -0,0 +1,12 @@
+
+#if defined(_M_IX86)
+#include "switch_x86_msvc.h" /* MS Visual Studio on X86 */
+#elif defined(_M_X64)
+#include "switch_x64_msvc.h" /* MS Visual Studio on X64 */
+#elif defined(__GNUC__) && defined(__amd64__)
+#include "switch_x86_64_gcc.h" /* gcc on amd64 */
+#elif defined(__GNUC__) && defined(__i386__)
+#include "switch_x86_gcc.h" /* gcc on X86 */
+#else
+#error "Unsupported platform!"
+#endif
diff --git a/pypy/translator/c/src/stacklet/stacklet.c b/pypy/translator/c/src/stacklet/stacklet.c
new file mode 100644
--- /dev/null
+++ b/pypy/translator/c/src/stacklet/stacklet.c
@@ -0,0 +1,340 @@
+/********** A really minimal coroutine package for C **********
+ * By Armin Rigo
+ */
+
+#include "stacklet.h"
+
+#include <stddef.h>
+#include <assert.h>
+#include <string.h>
+
+/************************************************************
+ * platform specific code
+ */
+
+/* The default stack direction is downwards, 0, but platforms
+ * can redefine it to upwards growing, 1.
+ */
+#define STACK_DIRECTION 0
+
+#include "slp_platformselect.h"
+
+#if STACK_DIRECTION != 0
+# error "review this whole code, which depends on STACK_DIRECTION==0 so far"
+#endif
+
+/************************************************************/
+
+/* #define DEBUG_DUMP */
+
+#ifdef DEBUG_DUMP
+#include <stdio.h>
+#endif
+
+/************************************************************/
+
+struct stacklet_s {
+ /* The portion of the real stack claimed by this paused tealet. */
+ char *stack_start; /* the "near" end of the stack */
+ char *stack_stop; /* the "far" end of the stack */
+
+ /* The amount that has been saved away so far, just after this struct.
+ * There is enough allocated space for 'stack_stop - stack_start'
+ * bytes.
+ */
+ ptrdiff_t stack_saved; /* the amount saved */
+
+ /* Internally, some stacklets are arranged in a list, to handle lazy
+ * saving of stacks: if the stacklet has a partially unsaved stack,
+ * this points to the next stacklet with a partially unsaved stack,
+ * creating a linked list with each stacklet's stack_stop higher
+ * than the previous one. The last entry in the list is always the
+ * main stack.
+ */
+ struct stacklet_s *stack_prev;
+};
+
+void *(*_stacklet_switchstack)(void*(*)(void*, void*),
+ void*(*)(void*, void*), void*) = NULL;
+void (*_stacklet_initialstub)(struct stacklet_thread_s *,
+ stacklet_run_fn, void *) = NULL;
+
+struct stacklet_thread_s {
+ struct stacklet_s *g_stack_chain_head; /* NULL <=> running main */
+ char *g_current_stack_stop;
+ char *g_current_stack_marker;
+ struct stacklet_s *g_source;
+ struct stacklet_s *g_target;
+};
+
+/***************************************************************/
+
+static void g_save(struct stacklet_s* g, char* stop
+#ifdef DEBUG_DUMP
+ , int overwrite_stack_for_debug
+#endif
+ )
+{
+ /* Save more of g's stack into the heap -- at least up to 'stop'
+
+ In the picture below, the C stack is on the left, growing down,
+ and the C heap on the right. The area marked with xxx is the logical
+ stack of the stacklet 'g'. It can be half in the C stack (its older
+ part), and half in the heap (its newer part).
+
+ g->stack_stop |________|
+ |xxxxxxxx|
+ |xxx __ stop .........
+ |xxxxxxxx| ==> : :
+ |________| :_______:
+ | | |xxxxxxx|
+ | | |xxxxxxx|
+ g->stack_start | | |_______| g+1
+
+ */
+ ptrdiff_t sz1 = g->stack_saved;
+ ptrdiff_t sz2 = stop - g->stack_start;
+ assert(stop <= g->stack_stop);
+
+ if (sz2 > sz1) {
+ char *c = (char *)(g + 1);
+#if STACK_DIRECTION == 0
+ memcpy(c+sz1, g->stack_start+sz1, sz2-sz1);
+# ifdef DEBUG_DUMP
+ if (overwrite_stack_for_debug)
+ memset(g->stack_start+sz1, 0xdb, sz2-sz1);
+# endif
+#else
+ xxx;
+#endif
+ g->stack_saved = sz2;
+ }
+}
+
+/* Allocate and store in 'g_source' a new stacklet, which has the C
+ * stack from 'old_stack_pointer' to 'g_current_stack_stop'. It is
+ * initially completely unsaved, so it is attached to the head of the
+ * chained list of 'stack_prev'.
+ */
+static int g_allocate_source_stacklet(void *old_stack_pointer,
+ struct stacklet_thread_s *thrd)
+{
+ struct stacklet_s *stacklet;
+ ptrdiff_t stack_size = (thrd->g_current_stack_stop -
+ (char *)old_stack_pointer);
+
+ thrd->g_source = malloc(sizeof(struct stacklet_s) + stack_size);
+ if (thrd->g_source == NULL)
+ return -1;
+
+ stacklet = thrd->g_source;
+ stacklet->stack_start = old_stack_pointer;
+ stacklet->stack_stop = thrd->g_current_stack_stop;
+ stacklet->stack_saved = 0;
+ stacklet->stack_prev = thrd->g_stack_chain_head;
+ thrd->g_stack_chain_head = stacklet;
+ return 0;
+}
+
+/* Save more of the C stack away, up to 'target_stop'.
+ */
+static void g_clear_stack(struct stacklet_s *g_target,
+ struct stacklet_thread_s *thrd)
+{
+ struct stacklet_s *current = thrd->g_stack_chain_head;
+ char *target_stop = g_target->stack_stop;
+
+ /* save and unlink tealets that are completely within
+ the area to clear. */
+ while (current != NULL && current->stack_stop <= target_stop) {
+ struct stacklet_s *prev = current->stack_prev;
+ current->stack_prev = NULL;
+ if (current != g_target) {
+ /* don't bother saving away g_target, because
+ it would be immediately restored */
+ g_save(current, current->stack_stop
+#ifdef DEBUG_DUMP
+ , 1
+#endif
+ );
+ }
+ current = prev;
+ }
+
+ /* save a partial stack */
+ if (current != NULL && current->stack_start < target_stop)
+ g_save(current, target_stop
+#ifdef DEBUG_DUMP
+ , 1
+#endif
+ );
+
+ thrd->g_stack_chain_head = current;
+}
+
+/* This saves the current state in a new stacklet that gets stored in
+ * 'g_source', and save away enough of the stack to allow a jump to
+ * 'g_target'.
+ */
+static void *g_save_state(void *old_stack_pointer, void *rawthrd)
+{
+ struct stacklet_thread_s *thrd = (struct stacklet_thread_s *)rawthrd;
+ if (g_allocate_source_stacklet(old_stack_pointer, thrd) < 0)
+ return NULL;
+ g_clear_stack(thrd->g_target, thrd);
+ return thrd->g_target->stack_start;
+}
+
+/* This saves the current state in a new stacklet that gets stored in
+ * 'g_source', but returns NULL, to not do any restoring yet.
+ */
+static void *g_initial_save_state(void *old_stack_pointer, void *rawthrd)
+{
+ struct stacklet_thread_s *thrd = (struct stacklet_thread_s *)rawthrd;
+ if (g_allocate_source_stacklet(old_stack_pointer, thrd) == 0)
+ g_save(thrd->g_source, thrd->g_current_stack_marker
+#ifdef DEBUG_DUMP
+ , 0
+#endif
+ );
+ return NULL;
+}
+
+/* Save away enough of the stack to allow a jump to 'g_target'.
+ */
+static void *g_destroy_state(void *old_stack_pointer, void *rawthrd)
+{
+ struct stacklet_thread_s *thrd = (struct stacklet_thread_s *)rawthrd;
+ thrd->g_source = EMPTY_STACKLET_HANDLE;
+ g_clear_stack(thrd->g_target, thrd);
+ return thrd->g_target->stack_start;
+}
+
+/* Restore the C stack by copying back from the heap in 'g_target',
+ * and free 'g_target'.
+ */
+static void *g_restore_state(void *new_stack_pointer, void *rawthrd)
+{
+ /* Restore the heap copy back into the C stack */
+ struct stacklet_thread_s *thrd = (struct stacklet_thread_s *)rawthrd;
+ struct stacklet_s *g = thrd->g_target;
+ ptrdiff_t stack_saved = g->stack_saved;
+
+ assert(new_stack_pointer == g->stack_start);
+#if STACK_DIRECTION == 0
+ memcpy(g->stack_start, g+1, stack_saved);
+#else
+ memcpy(g->stack_start - stack_saved, g+1, stack_saved);
+#endif
+ thrd->g_current_stack_stop = g->stack_stop;
+ free(g);
+ return EMPTY_STACKLET_HANDLE;
+}
+
+static void g_initialstub(struct stacklet_thread_s *thrd,
+ stacklet_run_fn run, void *run_arg)
+{
+ struct stacklet_s *result;
+
+ /* The following call returns twice! */
+ result = (struct stacklet_s *) _stacklet_switchstack(g_initial_save_state,
+ g_restore_state,
+ thrd);
+ if (result == NULL && thrd->g_source != NULL) {
+ /* First time it returns. Only g_initial_save_state() has run
+ and has created 'g_source'. Call run(). */
+ thrd->g_current_stack_stop = thrd->g_current_stack_marker;
+ result = run(thrd->g_source, run_arg);
+
+ /* Then switch to 'result'. */
+ thrd->g_target = result;
+ _stacklet_switchstack(g_destroy_state, g_restore_state, thrd);
+
+ assert(!"stacklet: we should not return here");
+ abort();
+ }
+ /* The second time it returns. */
+}
+
+/************************************************************/
+
+stacklet_thread_handle stacklet_newthread(void)
+{
+ struct stacklet_thread_s *thrd;
+
+ if (_stacklet_switchstack == NULL) {
+ /* set up the following global with an indirection, which is needed
+ to prevent any inlining */
+ _stacklet_initialstub = g_initialstub;
+ _stacklet_switchstack = slp_switch;
+ }
+
+ thrd = malloc(sizeof(struct stacklet_thread_s));
+ if (thrd != NULL)
+ memset(thrd, 0, sizeof(struct stacklet_thread_s));
+ return thrd;
+}
+
+void stacklet_deletethread(stacklet_thread_handle thrd)
+{
+ free(thrd);
+}
+
+stacklet_handle stacklet_new(stacklet_thread_handle thrd,
+ stacklet_run_fn run, void *run_arg)
+{
+ long stackmarker;
+ assert((char *)NULL < (char *)&stackmarker);
+ if (thrd->g_current_stack_stop <= (char *)&stackmarker)
+ thrd->g_current_stack_stop = ((char *)&stackmarker) + 1;
+
+ thrd->g_current_stack_marker = (char *)&stackmarker;
+ _stacklet_initialstub(thrd, run, run_arg);
+ return thrd->g_source;
+}
+
+stacklet_handle stacklet_switch(stacklet_thread_handle thrd,
+ stacklet_handle target)
+{
+ long stackmarker;
+ if (thrd->g_current_stack_stop <= (char *)&stackmarker)
+ thrd->g_current_stack_stop = ((char *)&stackmarker) + 1;
+
+ thrd->g_target = target;
+ _stacklet_switchstack(g_save_state, g_restore_state, thrd);
+ return thrd->g_source;
+}
+
+void stacklet_destroy(stacklet_thread_handle thrd, stacklet_handle target)
+{
+ /* remove 'target' from the chained list 'unsaved_stack', if it is there */
+ struct stacklet_s **pp = &thrd->g_stack_chain_head;
+ for (; *pp != NULL; pp = &(*pp)->stack_prev)
+ if (*pp == target) {
+ *pp = target->stack_prev;
+ break;
+ }
+ free(target);
+}
+
+char **_stacklet_translate_pointer(stacklet_handle context, char **ptr)
+{
+ if (context == NULL)
+ return ptr;
+ char *p = (char *)ptr;
+ long delta = p - context->stack_start;
+ if (((unsigned long)delta) < ((unsigned long)context->stack_saved)) {
+ /* a pointer to a saved away word */
+ char *c = (char *)(context + 1);
+ return (char **)(c + delta);
+ }
+ if (((unsigned long)delta) >=
+ (unsigned long)(context->stack_stop - context->stack_start)) {
+ /* out-of-stack pointer! it's only ok if we are the main stacklet
+ and we are reading past the end, because the main stacklet's
+ stack stop is not exactly known. */
+ assert(delta >= 0);
+ assert(((long)context->stack_stop) & 1);
+ }
+ return ptr;
+}
diff --git a/pypy/translator/c/src/stacklet/stacklet.h b/pypy/translator/c/src/stacklet/stacklet.h
new file mode 100644
--- /dev/null
+++ b/pypy/translator/c/src/stacklet/stacklet.h
@@ -0,0 +1,62 @@
+/********** A really minimal coroutine package for C **********/
+#ifndef _STACKLET_H_
+#define _STACKLET_H_
+
+#include <stdlib.h>
+
+
+/* A "stacklet handle" is an opaque pointer to a suspended stack.
+ * Whenever we suspend the current stack in order to switch elsewhere,
+ * stacklet.c passes to the target a 'stacklet_handle' argument that points
+ * to the original stack now suspended. The handle must later be passed
+ * back to this API once, in order to resume the stack. It is only
+ * valid once.
+ */
+typedef struct stacklet_s *stacklet_handle;
+
+#define EMPTY_STACKLET_HANDLE ((stacklet_handle) -1)
+
+
+/* Multithread support.
+ */
+typedef struct stacklet_thread_s *stacklet_thread_handle;
+
+stacklet_thread_handle stacklet_newthread(void);
+void stacklet_deletethread(stacklet_thread_handle thrd);
+
+
+/* The "run" function of a stacklet. The first argument is the handle
+ * of the stack from where we come. When such a function returns, it
+ * must return a (non-empty) stacklet_handle that tells where to go next.
+ */
+typedef stacklet_handle (*stacklet_run_fn)(stacklet_handle, void *);
+
+/* Call 'run(source, run_arg)' in a new stack. See stacklet_switch()
+ * for the return value.
+ */
+stacklet_handle stacklet_new(stacklet_thread_handle thrd,
+ stacklet_run_fn run, void *run_arg);
+
+/* Switch to the target handle, resuming its stack. This returns:
+ * - if we come back from another call to stacklet_switch(), the source handle
+ * - if we come back from a run() that finishes, EMPTY_STACKLET_HANDLE
+ * - if we run out of memory, NULL
+ * Don't call this with an already-used target, with EMPTY_STACKLET_HANDLE,
+ * or with a stack handle from another thread (in multithreaded apps).
+ */
+stacklet_handle stacklet_switch(stacklet_thread_handle thrd,
+ stacklet_handle target);
+
+/* Delete a stack handle without resuming it at all.
+ * (This works even if the stack handle is of a different thread)
+ */
+void stacklet_destroy(stacklet_thread_handle thrd, stacklet_handle target);
+
+/* stacklet_handle _stacklet_switch_to_copy(stacklet_handle) --- later */
+
+/* Hack: translate a pointer into the stack of a stacklet into a pointer
+ * to where it is really stored so far. Only to access word-sized data.
+ */
+char **_stacklet_translate_pointer(stacklet_handle context, char **ptr);
+
+#endif /* _STACKLET_H_ */
diff --git a/pypy/translator/c/src/stacklet/switch_x64_msvc.asm b/pypy/translator/c/src/stacklet/switch_x64_msvc.asm
new file mode 100644
--- /dev/null
+++ b/pypy/translator/c/src/stacklet/switch_x64_msvc.asm
@@ -0,0 +1,101 @@
+;
+; stack switching code for MASM on x64
+; Kristjan Valur Jonsson, apr 2011
+;
+
+include macamd64.inc
+
+pop_reg MACRO reg
+ pop reg
+ENDM
+
+load_xmm128 macro Reg, Offset
+ movdqa Reg, Offset[rsp]
+endm
+
+.code
+
+;arguments save_state, restore_state, extra are passed in rcx, rdx, r8 respectively
+;slp_switch PROC FRAME
+NESTED_ENTRY slp_switch, _TEXT$00
+ ; save all registers that the x64 ABI specifies as non-volatile.
+ ; This includes some mmx registers. May not always be necessary,
+ ; unless our application is doing 3D, but better safe than sorry.
+ alloc_stack 168; 10 * 16 bytes, plus 8 bytes to make stack 16 byte aligned
+ save_xmm128 xmm15, 144
+ save_xmm128 xmm14, 128
+ save_xmm128 xmm13, 112
+ save_xmm128 xmm12, 96
+ save_xmm128 xmm11, 80
+ save_xmm128 xmm10, 64
+ save_xmm128 xmm9, 48
+ save_xmm128 xmm8, 32
+ save_xmm128 xmm7, 16
+ save_xmm128 xmm6, 0
+
+ push_reg r15
+ push_reg r14
+ push_reg r13
+ push_reg r12
+
+ push_reg rbp
+ push_reg rbx
+ push_reg rdi
+ push_reg rsi
+
+ sub rsp, 20h ;allocate shadow stack space for the arguments (must be multiple of 16)
+ .allocstack 20h
+.endprolog
+
+ ;save argments in nonvolatile registers
+ mov r12, rcx ;save_state
+ mov r13, rdx
+ mov r14, r8
+
+ ; load stack base that we are saving minus the callee argument
+ ; shadow stack. We don't want that clobbered
+ lea rcx, [rsp+20h]
+ mov rdx, r14
+ call r12 ;pass stackpointer, return new stack pointer in eax
+
+ ; an null value means that we don't restore.
+ test rax, rax
+ jz exit
+
+ ;actual stack switch (and re-allocating the shadow stack):
+ lea rsp, [rax-20h]
+
+ mov rcx, rax ;pass new stack pointer
+ mov rdx, r14
+ call r13
+ ;return the rax
+EXIT:
+
+ add rsp, 20h
+ pop_reg rsi
+ pop_reg rdi
+ pop_reg rbx
+ pop_reg rbp
+
+ pop_reg r12
+ pop_reg r13
+ pop_reg r14
+ pop_reg r15
+
+ load_xmm128 xmm15, 144
+ load_xmm128 xmm14, 128
+ load_xmm128 xmm13, 112
+ load_xmm128 xmm12, 96
+ load_xmm128 xmm11, 80
+ load_xmm128 xmm10, 64
+ load_xmm128 xmm9, 48
+ load_xmm128 xmm8, 32
+ load_xmm128 xmm7, 16
+ load_xmm128 xmm6, 0
+ add rsp, 168
+ ret
+
+NESTED_END slp_switch, _TEXT$00
+;slp_switch ENDP
+
+END
\ No newline at end of file
diff --git a/pypy/translator/c/src/stacklet/switch_x64_msvc.h b/pypy/translator/c/src/stacklet/switch_x64_msvc.h
new file mode 100644
--- /dev/null
+++ b/pypy/translator/c/src/stacklet/switch_x64_msvc.h
@@ -0,0 +1,7 @@
+/* The actual stack saving function, which just stores the stack,
+ * this declared in an .asm file
+ */
+extern void *slp_switch(void *(*save_state)(void*, void*),
+ void *(*restore_state)(void*, void*),
+ void *extra);
+
diff --git a/pypy/translator/c/src/stacklet/switch_x86_64_gcc.h b/pypy/translator/c/src/stacklet/switch_x86_64_gcc.h
new file mode 100644
--- /dev/null
+++ b/pypy/translator/c/src/stacklet/switch_x86_64_gcc.h
@@ -0,0 +1,55 @@
+
+static void *slp_switch(void *(*save_state)(void*, void*),
+ void *(*restore_state)(void*, void*),
+ void *extra)
+{
+ void *result, *garbage1, *garbage2;
+ __asm__ volatile (
+ "pushq %%rbp\n"
+ "pushq %%rbx\n" /* push the registers specified as caller-save */
+ "pushq %%r12\n"
+ "pushq %%r13\n"
+ "pushq %%r14\n"
+ "pushq %%r15\n"
+
+ "movq %%rax, %%r12\n" /* save 'restore_state' for later */
+ "movq %%rsi, %%r13\n" /* save 'extra' for later */
+
+ /* arg 2: extra (already in rsi) */
+ "movq %%rsp, %%rdi\n" /* arg 1: current (old) stack pointer */
+ "call *%%rcx\n" /* call save_state() */
+
+ "testq %%rax, %%rax\n" /* skip the rest if the return value is null */
+ "jz 0f\n"
+
+ "movq %%rax, %%rsp\n" /* change the stack pointer */
+
+ /* From now on, the stack pointer is modified, but the content of the
+ stack is not restored yet. It contains only garbage here. */
+
+ "movq %%r13, %%rsi\n" /* arg 2: extra */
+ "movq %%rax, %%rdi\n" /* arg 1: current (new) stack pointer */
+ "call *%%r12\n" /* call restore_state() */
+
+ /* The stack's content is now restored. */
+
+ "0:\n"
+ "popq %%r15\n"
+ "popq %%r14\n"
+ "popq %%r13\n"
+ "popq %%r12\n"
+ "popq %%rbx\n"
+ "popq %%rbp\n"
+
+ : "=a"(result), /* output variables */
+ "=c"(garbage1),
+ "=S"(garbage2)
+ : "a"(restore_state), /* input variables */
+ "c"(save_state),
+ "S"(extra)
+ : "memory", "rdx", "rdi", "r8", "r9", "r10", "r11",
+ "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7",
+ "xmm8", "xmm9", "xmm10","xmm11","xmm12","xmm13","xmm14","xmm15"
+ );
+ return result;
+}
diff --git a/pypy/translator/c/src/stacklet/switch_x86_gcc.h b/pypy/translator/c/src/stacklet/switch_x86_gcc.h
new file mode 100644
--- /dev/null
+++ b/pypy/translator/c/src/stacklet/switch_x86_gcc.h
@@ -0,0 +1,56 @@
+
+static void *slp_switch(void *(*save_state)(void*, void*),
+ void *(*restore_state)(void*, void*),
+ void *extra)
+{
+ void *result, *garbage1, *garbage2;
+ __asm__ volatile (
+ "pushl %%ebp\n"
+ "pushl %%ebx\n" /* push some registers that may contain */
+ "pushl %%esi\n" /* some value that is meant to be saved */
+ "pushl %%edi\n"
+
+ "movl %%eax, %%esi\n" /* save 'restore_state' for later */
+ "movl %%edx, %%edi\n" /* save 'extra' for later */
+
+ "movl %%esp, %%eax\n"
+
+ "pushl %%edx\n" /* arg 2: extra */
+ "pushl %%eax\n" /* arg 1: current (old) stack pointer */
+ "call *%%ecx\n" /* call save_state() */
+
+ "testl %%eax, %%eax\n"/* skip the rest if the return value is null */
+ "jz 0f\n"
+
+ "movl %%eax, %%esp\n" /* change the stack pointer */
+
+ /* From now on, the stack pointer is modified, but the content of the
+ stack is not restored yet. It contains only garbage here. */
+
+ "pushl %%edi\n" /* arg 2: extra */
+ "pushl %%eax\n" /* arg 1: current (new) stack pointer */
+ "call *%%esi\n" /* call restore_state() */
+
+ /* The stack's content is now restored. */
+
+ "0:\n"
+ "addl $8, %%esp\n"
+ "popl %%edi\n"
+ "popl %%esi\n"
+ "popl %%ebx\n"
+ "popl %%ebp\n"
+
+ : "=a"(result), /* output variables */
+ "=c"(garbage1),
+ "=d"(garbage2)
+ : "a"(restore_state), /* input variables */
+ "c"(save_state),
+ "d"(extra)
+ : "memory"
+ );
+ /* Note: we should also list all fp/xmm registers, but is there a way
+ to list only the ones used by the current compilation target?
+ For now we will just ignore the issue and hope (reasonably) that
+ this function is never inlined all the way into 3rd-party user code. */
+ return result;
+}
diff --git a/pypy/translator/c/src/stacklet/switch_x86_msvc.asm b/pypy/translator/c/src/stacklet/switch_x86_msvc.asm
new file mode 100644
--- /dev/null
+++ b/pypy/translator/c/src/stacklet/switch_x86_msvc.asm
@@ -0,0 +1,44 @@
+
+.386
+.model flat, c
+
+.code
+
+slp_switch_raw PROC save_state:DWORD, restore_state:DWORD, extra:DWORD
+
+ ;save registers. EAX ECX and EDX are available for function use and thus
+ ;do not have to be stored.
+ push ebx
+ push esi
+ push edi
+ push ebp
+
+ mov esi, restore_state ; /* save 'restore_state' for later */
+ mov edi, extra ; /* save 'extra' for later */
+
+ mov eax, esp
+
+ push edi ; /* arg 2: extra */
+ push eax ; /* arg 1: current (old) stack pointer */
+ mov ecx, save_state
+ call ecx ; /* call save_state() */
+
+ test eax, eax; /* skip the restore if the return value is null */
+ jz exit
+
+ mov esp, eax; /* change the stack pointer */
+
+ push edi ; /* arg 2: extra */
+ push eax ; /* arg 1: current (new) stack pointer */
+ call esi ; /* call restore_state() */
+
+exit:
+ add esp, 8
+ pop ebp
+ pop edi
+ pop esi
+ pop ebx
+ ret
+slp_switch_raw ENDP
+
+end
\ No newline at end of file
diff --git a/pypy/translator/c/src/stacklet/switch_x86_msvc.h b/pypy/translator/c/src/stacklet/switch_x86_msvc.h
new file mode 100644
--- /dev/null
+++ b/pypy/translator/c/src/stacklet/switch_x86_msvc.h
@@ -0,0 +1,26 @@
+/* The actual stack saving function, which just stores the stack,
+ * this declared in an .asm file
+ */
+extern void *slp_switch_raw(void *(*save_state)(void*, void*),
+ void *(*restore_state)(void*, void*),
+ void *extra);
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+/* Store any other runtime information on the local stack */
+#pragma optimize("", off) /* so that autos are stored on the stack */
+#pragma warning(disable:4733) /* disable warning about modifying FS[0] */
+
+static void *slp_switch(void *(*save_state)(void*, void*),
+ void *(*restore_state)(void*, void*),
+ void *extra)
+{
+ /* store the structured exception state for this stack */
+ DWORD seh_state = __readfsdword(FIELD_OFFSET(NT_TIB, ExceptionList));
+ void * result = slp_switch_raw(save_state, restore_state, extra);
+ __writefsdword(FIELD_OFFSET(NT_TIB, ExceptionList), seh_state);
+ return result;
+}
+#pragma warning(default:4733) /* disable warning about modifying FS[0] */
+#pragma optimize("", on)
diff --git a/pypy/translator/c/src/stacklet/tests.c b/pypy/translator/c/src/stacklet/tests.c
new file mode 100644
--- /dev/null
+++ b/pypy/translator/c/src/stacklet/tests.c
@@ -0,0 +1,671 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <assert.h>
+#include "stacklet.h"
+
+
+static stacklet_thread_handle thrd;
+
+/************************************************************/
+
+stacklet_handle empty_callback(stacklet_handle h, void *arg)
+{
+ assert(arg == (void *)123);
+ return h;
+}
+
+void test_new(void)
+{
+ stacklet_handle h = stacklet_new(thrd, empty_callback, (void *)123);
+ assert(h == EMPTY_STACKLET_HANDLE);
+}
+
+/************************************************************/
+
+static int status;
+
+stacklet_handle switchbackonce_callback(stacklet_handle h, void *arg)
+{
+ assert(arg == (void *)123);
+ assert(status == 0);
+ status = 1;
+ assert(h != EMPTY_STACKLET_HANDLE);
+ h = stacklet_switch(thrd, h);
+ assert(status == 2);
+ assert(h != EMPTY_STACKLET_HANDLE);
+ status = 3;
+ return h;
+}
+
+void test_simple_switch(void)
+{
+ status = 0;
+ stacklet_handle h = stacklet_new(thrd, switchbackonce_callback, (void *)123);
+ assert(h != EMPTY_STACKLET_HANDLE);
+ assert(status == 1);
+ status = 2;
+ h = stacklet_switch(thrd, h);
+ assert(status == 3);
+ assert(h == EMPTY_STACKLET_HANDLE);
+}
+
+/************************************************************/
+
+static stacklet_handle handles[10];
+static int nextstep, comefrom, gointo;
+static const int statusmax = 5000;
+
+int withdepth(int self, float d);
+
+stacklet_handle variousdepths_callback(stacklet_handle h, void *arg)
+{
+ int self, n;
+ assert(nextstep == status);
+ nextstep = -1;
+ self = (ptrdiff_t)arg;
+ assert(self == gointo);
+ assert(0 <= self && self < 10);
+ assert(handles[self] == NULL);
+ assert(0 <= comefrom && comefrom < 10);
+ assert(handles[comefrom] == NULL);
+ assert(h != NULL && h != EMPTY_STACKLET_HANDLE);
+ handles[comefrom] = h;
+ comefrom = -1;
+ gointo = -1;
+
+ while (withdepth(self, rand() % 20) == 0)
+ ;
+
+ assert(handles[self] == NULL);
+
+ do {
+ n = rand() % 10;
+ } while (handles[n] == NULL);
+
+ h = handles[n];
+ assert(h != EMPTY_STACKLET_HANDLE);
+ handles[n] = NULL;
+ comefrom = -42;
+ gointo = n;
+ assert(nextstep == -1);
+ nextstep = ++status;
+ //printf("LEAVING %d to go to %d\n", self, n);
+ return h;
+}
+
+typedef struct foo_s {
+ int self;
+ float d;
+ struct foo_s *next;
+} foo_t;
+
+int withdepth(int self, float d)
+{
+ int res = 0;
+ if (d > 0.0)
+ {
+ foo_t *foo = malloc(sizeof(foo_t));
+ foo_t *foo2 = malloc(sizeof(foo_t));
+ foo->self = self;
+ foo->d = d;
+ foo->next = foo2;
+ foo2->self = self + 100;
+ foo2->d = d;
+ foo2->next = NULL;
+ res = withdepth(self, d - 1.1);
+ assert(foo->self == self);
+ assert(foo->d == d);
+ assert(foo->next == foo2);
+ assert(foo2->self == self + 100);
+ assert(foo2->d == d);
+ assert(foo2->next == NULL);
+ free(foo2);
+ free(foo);
+ }
+ else
+ {
+ stacklet_handle h;
+ int n = rand() % 10;
+ if (n == self || (status >= statusmax && handles[n] == NULL))
+ return 1;
+
+ //printf("status == %d, self = %d\n", status, self);
+ assert(handles[self] == NULL);
+ assert(nextstep == -1);
+ nextstep = ++status;
+ comefrom = self;
+ gointo = n;
+ if (handles[n] == NULL)
+ {
+ /* start a new stacklet */
+ //printf("new %d\n", n);
+ h = stacklet_new(thrd, variousdepths_callback, (void *)(ptrdiff_t)n);
+ }
+ else
+ {
+ /* switch to this stacklet */
+ //printf("switch to %d\n", n);
+ h = handles[n];
+ handles[n] = NULL;
+ h = stacklet_switch(thrd, h);
+ }
+ //printf("back in self = %d, coming from %d\n", self, comefrom);
+ assert(nextstep == status);
+ nextstep = -1;
+ assert(gointo == self);
+ assert(comefrom != self);
+ assert(handles[self] == NULL);
+ if (comefrom != -42)
+ {
+ assert(0 <= comefrom && comefrom < 10);
+ assert(handles[comefrom] == NULL);
+ handles[comefrom] = h;
+ }
+ else
+ assert(h == EMPTY_STACKLET_HANDLE);
+ comefrom = -1;
+ gointo = -1;
+ }
+ assert((res & (res-1)) == 0); /* to prevent a tail-call to withdepth() */
+ return res;
+}
+
+int any_alive(void)
+{
+ int i;
+ for (i=0; i<10; i++)
+ if (handles[i] != NULL)
+ return 1;
+ return 0;
+}
+
+void test_various_depths(void)
+{
+ int i;
+ for (i=0; i<10; i++)
+ handles[i] = NULL;
+
+ nextstep = -1;
+ comefrom = -1;
+ status = 0;
+ while (status < statusmax || any_alive())
+ withdepth(0, rand() % 50);
+}
+
+/************************************************************/
+#if 0
+
+static tealet_t *runner1(tealet_t *cur)
+{
+ abort();
+}
+
+void test_new_pending(void)
+{
+ tealet_t *g1 = tealet_new();
+ tealet_t *g2 = tealet_new();
+ int r1 = tealet_fill(g1, runner1);
+ int r2 = tealet_fill(g2, runner1);
+ assert(r1 == TEALET_OK);
+ assert(r2 == TEALET_OK);
+ assert(g1->suspended == 1);
+ assert(g2->suspended == 1);
+ tealet_delete(g1);
+ tealet_delete(g2);
+}
+
+/************************************************************/
+
+void test_not_switched(void)
+{
+ tealet_t *g1 = tealet_new();
+ tealet_t *g2 = tealet_new();
+ tealet_t *g3 = tealet_switch(g2, g1);
+ assert(!TEALET_ERROR(g3));
+ assert(g3 == g1);
+ tealet_delete(g1);
+ tealet_delete(g2);
+}
+
+/************************************************************/
+
+static tealet_t *g_main;
+
+static void step(int newstatus)
+{
+ assert(status == newstatus - 1);
+ status = newstatus;
+}
+
+static tealet_t *simple_run(tealet_t *t1)
+{
+ assert(t1 != g_main);
+ step(2);
+ tealet_delete(t1);
+ return g_main;
+}
+
+void test_simple(void)
+{
+ tealet_t *t1, *tmain;
+ int res;
+
+ status = 0;
+ g_main = tealet_new();
+ t1 = tealet_new();
+ res = tealet_fill(t1, simple_run);
+ assert(res == TEALET_OK);
+ step(1);
+ tmain = tealet_switch(g_main, t1);
+ step(3);
+ assert(tmain == g_main);
+ tealet_delete(g_main);
+ step(4);
+}
+
+/************************************************************/
+
+static tealet_t *simple_exit(tealet_t *t1)
+{
+ int res;
+ assert(t1 != g_main);
+ step(2);
+ tealet_delete(t1);
+ res = tealet_exit_to(g_main);
+ assert(!"oups");
+}
+
+void test_exit(void)
+{
+ tealet_t *t1, *tmain;
+ int res;
+
+ status = 0;
+ g_main = tealet_new();
More information about the pypy-commit
mailing list