[pypy-commit] pypy default: Make sys.setrecursionlimit() have an effect again: now, setting

arigo noreply at buildbot.pypy.org
Fri May 27 11:22:27 CEST 2011


Author: Armin Rigo <arigo at tunes.org>
Branch: 
Changeset: r44532:aadddddc702d
Date: 2011-05-26 23:33 +0200
http://bitbucket.org/pypy/pypy/changeset/aadddddc702d/

Log:	Make sys.setrecursionlimit() have an effect again: now, setting it
	to a value N sets the low-level maximum to N/1000 times 768KB.
	Approximative but hopefully good enough.

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
@@ -144,10 +144,10 @@
                                                           lltype.Void))
         def insert_stack_check():
             startaddr = rstack._stack_get_start_adr()
-            length = rstack._stack_get_length()
+            lengthaddr = rstack._stack_get_length_adr()
             f = llhelper(STACK_CHECK_SLOWPATH, rstack.stack_check_slowpath)
             slowpathaddr = rffi.cast(lltype.Signed, f)
-            return startaddr, length, slowpathaddr
+            return startaddr, lengthaddr, slowpathaddr
 
         self.pos_exception = pos_exception
         self.pos_exc_value = pos_exc_value
diff --git a/pypy/jit/backend/x86/assembler.py b/pypy/jit/backend/x86/assembler.py
--- a/pypy/jit/backend/x86/assembler.py
+++ b/pypy/jit/backend/x86/assembler.py
@@ -621,10 +621,10 @@
         if self.stack_check_slowpath == 0:
             pass                # no stack check (e.g. not translated)
         else:
-            startaddr, length, _ = self.cpu.insert_stack_check()
+            startaddr, lengthaddr, _ = self.cpu.insert_stack_check()
             self.mc.MOV(eax, esp)                       # MOV eax, current
-            self.mc.SUB(eax, heap(startaddr))           # SUB eax, [startaddr]
-            self.mc.CMP(eax, imm(length))               # CMP eax, length
+            self.mc.SUB(eax, heap(startaddr))           # SUB eax, [start]
+            self.mc.CMP(eax, heap(lengthaddr))          # CMP eax, [length]
             self.mc.J_il8(rx86.Conditions['B'], 0)      # JB .skip
             jb_location = self.mc.get_relative_pos()
             self.mc.CALL(imm(self.stack_check_slowpath))# CALL slowpath
diff --git a/pypy/module/sys/vm.py b/pypy/module/sys/vm.py
--- a/pypy/module/sys/vm.py
+++ b/pypy/module/sys/vm.py
@@ -43,21 +43,20 @@
     return space.wrap(f)
 
 def setrecursionlimit(space, w_new_limit):
-    """setrecursionlimit() is ignored (and not needed) on PyPy.
-
-On CPython it would set the maximum number of nested calls that can
-occur before a RuntimeError is raised.  On PyPy overflowing the stack
-also causes RuntimeErrors, but the limit is checked at a lower level.
-(The limit is currenty hard-coded at 768 KB, corresponding to roughly
-1480 Python calls on Linux.)"""
+    """setrecursionlimit() sets the maximum number of nested calls that
+can occur before a RuntimeError is raised.  On PyPy the limit is
+approximative and checked at a lower level.  The default 1000
+reserves 768KB of stack space, which should suffice (on Linux,
+depending on the compiler settings) for ~1400 calls.  Setting the
+value to N reserves N/1000 times 768KB of stack space.
+"""
+    from pypy.rlib.rstack import _stack_set_length_fraction
     new_limit = space.int_w(w_new_limit)
     if new_limit <= 0:
         raise OperationError(space.w_ValueError,
                              space.wrap("recursion limit must be positive"))
-    # for now, don't rewrite a warning but silently ignore the
-    # recursion limit.
-    #space.warn('setrecursionlimit() is ignored (and not needed) on PyPy', space.w_RuntimeWarning)
     space.sys.recursionlimit = new_limit
+    _stack_set_length_fraction(new_limit * 0.001)
 
 def getrecursionlimit(space):
     """Return the last value set by setrecursionlimit().
diff --git a/pypy/rlib/rstack.py b/pypy/rlib/rstack.py
--- a/pypy/rlib/rstack.py
+++ b/pypy/rlib/rstack.py
@@ -46,11 +46,15 @@
                               lambda: 0)
 _stack_get_length = llexternal('LL_stack_get_length', [], lltype.Signed,
                                lambda: 1)
+_stack_set_length_fraction = llexternal('LL_stack_set_length_fraction',
+                                        [lltype.Float], lltype.Void,
+                                        lambda frac: None)
 _stack_too_big_slowpath = llexternal('LL_stack_too_big_slowpath',
                                      [lltype.Signed], lltype.Char,
                                      lambda cur: '\x00')
 # the following is used by the JIT
 _stack_get_start_adr = llexternal('LL_stack_get_start_adr', [], lltype.Signed)
+_stack_get_length_adr= llexternal('LL_stack_get_length_adr',[], lltype.Signed)
 
 
 def stack_check():
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
@@ -12,14 +12,17 @@
 #include "thread.h"
 
 extern char *_LLstacktoobig_stack_start;
+extern long _LLstacktoobig_stack_length;
 
 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);
 
 /* some macros referenced from pypy.rlib.rstack */
 #define LL_stack_get_start() ((long)_LLstacktoobig_stack_start)
-#define LL_stack_get_length() MAX_STACK_SIZE
+#define LL_stack_get_length() _LLstacktoobig_stack_length
 #define LL_stack_get_start_adr() ((long)&_LLstacktoobig_stack_start)  /* JIT */
+#define LL_stack_get_length_adr() ((long)&_LLstacktoobig_stack_length)/* JIT */
 
 
 #ifdef __GNUC__
@@ -51,12 +54,18 @@
 }
 
 char *_LLstacktoobig_stack_start = NULL;
+long _LLstacktoobig_stack_length = MAX_STACK_SIZE;
 int stack_direction = 0;
 RPyThreadStaticTLS start_tls_key;
 
+void LL_stack_set_length_fraction(double fraction)
+{
+	_LLstacktoobig_stack_length = (long)(MAX_STACK_SIZE * fraction);
+}
+
 char LL_stack_too_big_slowpath(long current)
 {
-	long diff;
+	long diff, max_stack_size;
 	char *baseptr, *curptr = (char*)current;
 
 	/* The stack_start variable is updated to match the current value
@@ -83,22 +92,23 @@
 	}
 
 	baseptr = (char *) RPyThreadStaticTLS_Get(start_tls_key);
+	max_stack_size = _LLstacktoobig_stack_length;
 	if (baseptr != NULL) {
 		diff = curptr - baseptr;
-		if (((unsigned long)diff) < (unsigned long)MAX_STACK_SIZE) {
+		if (((unsigned long)diff) < (unsigned long)max_stack_size) {
 			/* within bounds, probably just had a thread switch */
 			_LLstacktoobig_stack_start = baseptr;
 			return 0;
 		}
 
 		if (stack_direction > 0) {
-			if (diff < 0 && diff > -MAX_STACK_SIZE)
+			if (diff < 0 && diff > -max_stack_size)
 				;           /* stack underflow */
 			else
 				return 1;   /* stack overflow (probably) */
 		}
 		else {
-			if (diff >= MAX_STACK_SIZE && diff < 2*MAX_STACK_SIZE)
+			if (diff >= max_stack_size && diff < 2*max_stack_size)
 				;           /* stack underflow */
 			else
 				return 1;   /* stack overflow (probably) */
@@ -115,7 +125,7 @@
 	}
 	else {
 		/* the valid range is [curptr-MAX_STACK_SIZE+1:curptr+1] */
-		baseptr = curptr - MAX_STACK_SIZE + 1;
+		baseptr = curptr - max_stack_size + 1;
 	}
 	RPyThreadStaticTLS_Set(start_tls_key, baseptr);
 	_LLstacktoobig_stack_start = baseptr;
diff --git a/pypy/translator/c/test/test_standalone.py b/pypy/translator/c/test/test_standalone.py
--- a/pypy/translator/c/test/test_standalone.py
+++ b/pypy/translator/c/test/test_standalone.py
@@ -689,6 +689,39 @@
         out = cbuilder.cmdexec("")
         assert out.strip() == "hi!"
 
+    def test_set_length_fraction(self):
+        # check for pypy.rlib.rstack._stack_set_length_fraction()
+        from pypy.rlib.rstack import _stack_set_length_fraction
+        from pypy.rlib.rstackovf import StackOverflow
+        class A:
+            n = 0
+        glob = A()
+        def f(n):
+            glob.n += 1
+            if n <= 0:
+                return 42
+            return f(n+1)
+        def entry_point(argv):
+            _stack_set_length_fraction(float(argv[1]))
+            try:
+                return f(1)
+            except StackOverflow:
+                print glob.n
+                return 0
+        t, cbuilder = self.compile(entry_point, stackcheck=True)
+        counts = {}
+        for fraction in [0.1, 0.4, 1.0]:
+            out = cbuilder.cmdexec(str(fraction))
+            print 'counts[%s]: %r' % (fraction, out)
+            counts[fraction] = int(out.strip())
+        #
+        assert counts[1.0] >= 1000
+        # ^^^ should actually be much more than 1000 for this small test
+        assert counts[0.1] < counts[0.4] / 3
+        assert counts[0.4] < counts[1.0] / 2
+        assert counts[0.1] > counts[0.4] / 7
+        assert counts[0.4] > counts[1.0] / 4
+
 class TestMaemo(TestStandalone):
     def setup_class(cls):
         py.test.skip("TestMaemo: tests skipped for now")


More information about the pypy-commit mailing list