[pypy-svn] r51239 - in pypy/branch/asmgcroot/pypy: rpython/memory/gctransform translator/c/gcc translator/c/gcc/test

arigo at codespeak.net arigo at codespeak.net
Mon Feb 4 10:51:34 CET 2008


Author: arigo
Date: Mon Feb  4 10:51:34 2008
New Revision: 51239

Added:
   pypy/branch/asmgcroot/pypy/translator/c/gcc/test/track6.s
Modified:
   pypy/branch/asmgcroot/pypy/rpython/memory/gctransform/asmgcroot.py
   pypy/branch/asmgcroot/pypy/translator/c/gcc/test/test_trackgcroot.py
   pypy/branch/asmgcroot/pypy/translator/c/gcc/test/track0.s
   pypy/branch/asmgcroot/pypy/translator/c/gcc/test/track1.s
   pypy/branch/asmgcroot/pypy/translator/c/gcc/test/track2.s
   pypy/branch/asmgcroot/pypy/translator/c/gcc/test/track3.s
   pypy/branch/asmgcroot/pypy/translator/c/gcc/test/track4.s
   pypy/branch/asmgcroot/pypy/translator/c/gcc/test/track5.s
   pypy/branch/asmgcroot/pypy/translator/c/gcc/trackgcroot.py
Log:
Refactor of the way the "location" integers are encoded and
decoded, to allow both %esp-based and %ebp-based stack frame
positions to be used.  See the new track6.s test for why we
need it.  Simplifies some stuff too.


Modified: pypy/branch/asmgcroot/pypy/rpython/memory/gctransform/asmgcroot.py
==============================================================================
--- pypy/branch/asmgcroot/pypy/rpython/memory/gctransform/asmgcroot.py	(original)
+++ pypy/branch/asmgcroot/pypy/rpython/memory/gctransform/asmgcroot.py	Mon Feb  4 10:51:34 2008
@@ -104,31 +104,24 @@
         #
         # A "safe point" is the return address of a call.
         # The "shape" of a safe point is a list of integers
-        # as follows:
-        #
-        #   * The size of the frame of the function containing the
-        #     call (in theory it can be different from call to call).
-        #     This size includes the return address (see picture
+        # that represent "locations".  A "location" can be
+        # either in the stack or in a register.  See
+        # getlocation() for the decoding of this integer.
+        # The locations stored in a "shape" are as follows:
+        #
+        #   * The "location" of the return address.  This is just
+        #     after the end of the frame of 'callee'; it is the
+        #     first word of the frame of 'caller' (see picture
         #     below).
         #
-        #   * Four integers that specify where the function saves
+        #   * Four "locations" that specify where the function saves
         #     each of the four callee-saved registers (%ebx, %esi,
-        #     %edi, %ebp).  This is a "location", see below.
+        #     %edi, %ebp).
         #
         #   * The number of live GC roots around the call.
         #
         #   * For each GC root, an integer that specify where the
-        #     GC pointer is stored.  This is a "location", see below.
-        #
-        # A "location" can be either in the stack or in a register.
-        # If it is in the stack, it is specified as an integer offset
-        # from the current frame (so it is typically < 0, as seen
-        # in the picture below, though it can also be > 0 if the
-        # location is an input argument to the current function and
-        # thus lives at the bottom of the caller's frame).
-        # A "location" can also be in a register; in our context it
-        # can only be a callee-saved register.  This is specified
-        # as a small odd-valued integer (1=%ebx, 3=%esi, etc.)
+        #     GC pointer is stored.  This is a "location" too.
         #
         # XXX the details are completely specific to X86!!!
         # a picture of the stack may help:
@@ -159,19 +152,9 @@
             llop.debug_fatalerror(lltype.Void, "cannot find gc roots!")
             return False
         #
-        # found!  Now we can go to the caller_frame.
+        # found!  Enumerate the GC roots in the caller frame
         #
         shape = item.address[1]
-        framesize = shape.signed[0]
-        if framesize == 0:
-            # frame size not known, use %ebp to find the frame address
-            caller_ebp = callee.regs_stored_at[INDEX_OF_EBP].address[0]
-            caller.frame_address = caller_ebp + 4
-        else:
-            caller.frame_address = callee.frame_address + framesize
-        #
-        # enumerate the GC roots in the caller frame
-        #
         collect_stack_root = self.gcdata._gc_collect_stack_root
         gc = self.gc
         LIVELOCS = 1 + CALLEE_SAVED_REGS + 1  # index of the first gc root loc
@@ -179,37 +162,53 @@
         while livecount > 0:
             livecount -= 1
             location = shape.signed[LIVELOCS + livecount]
-            addr = self.getlocation(callee, caller, location)
+            addr = self.getlocation(callee, location)
             if addr.address[0] != llmemory.NULL:
                 collect_stack_root(gc, addr)
         #
         # track where the caller_frame saved the registers from its own
         # caller
         #
-        if shape.signed[1] == -1:     # a marker that means "I'm the frame
-            return False              # of the entry point, stop walking"
+        location = shape.signed[0]
+        caller.frame_address = self.getlocation(callee, location)
+        if not caller.frame_address:   # marker that means "I'm the frame
+            return False               # of the entry point, stop walking"
         reg = 0
         while reg < CALLEE_SAVED_REGS:
             location = shape.signed[1+reg]
-            addr = self.getlocation(callee, caller, location)
+            addr = self.getlocation(callee, location)
             caller.regs_stored_at[reg] = addr
             reg += 1
         return True
 
-    def getlocation(self, callee, caller, location):
+    def getlocation(self, callee, location):
         """Get the location in the 'caller' frame of a variable, based
-        on the integer 'location' that describes it.  The 'callee' is
-        only used if the location is in a register that was saved in the
-        callee.
+        on the integer 'location' that describes it.  All locations are
+        computed based on information saved by the 'callee'.
         """
-        if location & 1:     # register
-            reg = location >> 1
+        kind = location & LOC_MASK
+        if kind == LOC_REG:   # register
+            reg = location >> 2
             ll_assert(0 <= reg < CALLEE_SAVED_REGS, "bad register location")
             return callee.regs_stored_at[reg]
+        elif kind == LOC_ESP_BASED:   # in the caller stack frame at N(%esp)
+            offset = location & ~ LOC_MASK
+            ll_assert(offset >= 0, "bad %esp-based location")
+            esp_in_caller = callee.frame_address + 4
+            return esp_in_caller + offset
+        elif kind == LOC_EBP_BASED:   # in the caller stack frame at N(%ebp)
+            offset = location & ~ LOC_MASK
+            ebp_in_caller = callee.regs_stored_at[INDEX_OF_EBP].address[0]
+            return ebp_in_caller + offset
         else:
-            # in the stack frame
-            return caller.frame_address + location
+            return llmemory.NULL
+
 
+LOC_NOWHERE   = 0
+LOC_REG       = 1
+LOC_EBP_BASED = 2
+LOC_ESP_BASED = 3
+LOC_MASK      = 0x03
 
 # ____________________________________________________________
 

Modified: pypy/branch/asmgcroot/pypy/translator/c/gcc/test/test_trackgcroot.py
==============================================================================
--- pypy/branch/asmgcroot/pypy/translator/c/gcc/test/test_trackgcroot.py	(original)
+++ pypy/branch/asmgcroot/pypy/translator/c/gcc/test/test_trackgcroot.py	Mon Feb  4 10:51:34 2008
@@ -1,5 +1,9 @@
 import py
 import sys, re
+from pypy.translator.c.gcc.trackgcroot import format_location
+from pypy.translator.c.gcc.trackgcroot import format_callshape
+from pypy.translator.c.gcc.trackgcroot import LOC_NOWHERE, LOC_REG
+from pypy.translator.c.gcc.trackgcroot import LOC_EBP_BASED, LOC_ESP_BASED
 from pypy.translator.c.gcc.trackgcroot import GcRootTracker
 from pypy.translator.c.gcc.trackgcroot import FunctionGcRootTracker
 from StringIO import StringIO
@@ -7,6 +11,31 @@
 this_dir = py.path.local(__file__).dirpath()
 
 
+def test_format_location():
+    assert format_location(LOC_NOWHERE) == '?'
+    assert format_location(LOC_REG | (0<<2)) == '%ebx'
+    assert format_location(LOC_REG | (1<<2)) == '%esi'
+    assert format_location(LOC_REG | (2<<2)) == '%edi'
+    assert format_location(LOC_REG | (3<<2)) == '%ebp'
+    assert format_location(LOC_EBP_BASED + 0) == '(%ebp)'
+    assert format_location(LOC_EBP_BASED + 4) == '4(%ebp)'
+    assert format_location(LOC_EBP_BASED - 4) == '-4(%ebp)'
+    assert format_location(LOC_ESP_BASED + 0) == '(%esp)'
+    assert format_location(LOC_ESP_BASED + 4) == '4(%esp)'
+    assert format_location(LOC_ESP_BASED - 4) == '-4(%esp)'
+
+def test_format_callshape():
+    expected = ('{4(%ebp) '               # position of the return address
+                '| 8(%ebp), 12(%ebp), 16(%ebp), 20(%ebp) '  # 4 saved regs
+                '| 24(%ebp), 28(%ebp)}')                    # GC roots
+    assert format_callshape((LOC_EBP_BASED+4,
+                             LOC_EBP_BASED+8,
+                             LOC_EBP_BASED+12,
+                             LOC_EBP_BASED+16,
+                             LOC_EBP_BASED+20,
+                             LOC_EBP_BASED+24,
+                             LOC_EBP_BASED+28)) == expected
+
 def test_find_functions():
     source = """\
 \t.p2align 4,,15
@@ -46,7 +75,7 @@
         yield check_computegcmaptable, path
 
 r_globallabel = re.compile(r"([\w]+)[:]")
-r_expected = re.compile(r"\s*;;\s*expected\s*([(][-\d\s,+]+[)])")
+r_expected = re.compile(r"\s*;;\s*expected\s+([{].+[}])")
 
 def check_computegcmaptable(path):
     print
@@ -54,26 +83,24 @@
     lines = path.readlines()
     expectedlines = lines[:]
     tracker = FunctionGcRootTracker(lines)
-    tracker.is_main = tracker.funcname == 'main'
     table = tracker.computegcmaptable(verbose=sys.maxint)
     tabledict = {}
     seen = {}
     for entry in table:
-        print entry
+        print '%s: %s' % (entry[0], format_callshape(entry[1]))
         tabledict[entry[0]] = entry[1]
     # find the ";; expected" lines
     prevline = ""
     for i, line in enumerate(lines):
         match = r_expected.match(line)
         if match:
-            expected = eval(match.group(1))
-            assert isinstance(expected, tuple)
+            expected = match.group(1)
             prevmatch = r_globallabel.match(prevline)
             assert prevmatch, "the computed table is not complete"
             label = prevmatch.group(1)
             assert label in tabledict
             got = tabledict[label]
-            assert got == expected
+            assert format_callshape(got) == expected
             seen[label] = True
             expectedlines.insert(i-2, '\t.globl\t%s\n' % (label,))
             expectedlines.insert(i-1, '%s:\n' % (label,))

Modified: pypy/branch/asmgcroot/pypy/translator/c/gcc/test/track0.s
==============================================================================
--- pypy/branch/asmgcroot/pypy/translator/c/gcc/test/track0.s	(original)
+++ pypy/branch/asmgcroot/pypy/translator/c/gcc/test/track0.s	Mon Feb  4 10:51:34 2008
@@ -14,7 +14,7 @@
 	movl	%ebp, %ebx
 	movl	$pypy_g_array_16, (%esp)
 	call	open
-        ;; expected (32, -16, -12, -8, -4)
+        ;; expected {28(%esp) | 12(%esp), 16(%esp), 20(%esp), 24(%esp) | }
 	cmpl	$-1, %eax
 	movl	%eax, %edi
 	movl	32(%esp), %esi
@@ -26,7 +26,7 @@
 	movl	%esi, 4(%esp)
 	movl	%edi, (%esp)
 	call	read
-        ;; expected (32, -16, -12, -8, -4)
+        ;; expected {28(%esp) | 12(%esp), 16(%esp), 20(%esp), 24(%esp) | }
 	testl	%eax, %eax
 	jle	.L280
 .L285:
@@ -46,13 +46,13 @@
 	movl	%eax, 8(%esp)
 	movl	%edi, (%esp)
 	call	read
-        ;; expected (32, -16, -12, -8, -4)
+        ;; expected {28(%esp) | 12(%esp), 16(%esp), 20(%esp), 24(%esp) | }
 	testl	%eax, %eax
 	jg	.L288
 .L280:
 	movl	%edi, (%esp)
 	call	close
-        ;; expected (32, -16, -12, -8, -4)
+        ;; expected {28(%esp) | 12(%esp), 16(%esp), 20(%esp), 24(%esp) | }
 	movl	%ebx, %eax
 .L286:
 .L274:
@@ -65,7 +65,7 @@
 	movl	%ecx, 4(%esp)
 	movl	%esi, (%esp)
 	call	memset
-        ;; expected (32, -16, -12, -8, -4)
+        ;; expected {28(%esp) | 12(%esp), 16(%esp), 20(%esp), 24(%esp) | }
 .L270:
 	addl	$12, %esp
 	popl	%ebx

Modified: pypy/branch/asmgcroot/pypy/translator/c/gcc/test/track1.s
==============================================================================
--- pypy/branch/asmgcroot/pypy/translator/c/gcc/test/track1.s	(original)
+++ pypy/branch/asmgcroot/pypy/translator/c/gcc/test/track1.s	Mon Feb  4 10:51:34 2008
@@ -14,7 +14,7 @@
 	movl	$0, 4(%esp)
 	movl	$pypy_g_array_16, (%esp)
 	call	open
-        ;; expected (80, 1, 3, 5, -4)
+        ;; expected {4(%ebp) | %ebx, %esi, %edi, (%ebp) | }
 	movl	%eax, -12(%ebp)
 	.loc 2 1522 0
 	cmpl	$-1, -12(%ebp)
@@ -55,7 +55,7 @@
 	movl	-8(%ebp), %eax
 	movl	%eax, (%esp)
 	call	memset
-        ;; expected (80, 1, 3, 5, -4)
+        ;; expected {4(%ebp) | %ebx, %esi, %edi, (%ebp) | }
 	.loc 2 1545 0
 	jmp	.L542
 .L545:
@@ -73,7 +73,7 @@
 	movl	-12(%ebp), %eax
 	movl	%eax, (%esp)
 	call	close
-        ;; expected (80, 1, 3, 5, -4)
+        ;; expected {4(%ebp) | %ebx, %esi, %edi, (%ebp) | }
 	movl	%eax, -36(%ebp)
 	.loc 2 1556 0
 	movl	-16(%ebp), %eax
@@ -111,7 +111,7 @@
 	movl	-12(%ebp), %eax
 	movl	%eax, (%esp)
 	call	read
-        ;; expected (80, 1, 3, 5, -4)
+        ;; expected {4(%ebp) | %ebx, %esi, %edi, (%ebp) | }
 	movl	%eax, -48(%ebp)
 	.loc 2 1575 0
 	movl	-48(%ebp), %eax

Modified: pypy/branch/asmgcroot/pypy/translator/c/gcc/test/track2.s
==============================================================================
--- pypy/branch/asmgcroot/pypy/translator/c/gcc/test/track2.s	(original)
+++ pypy/branch/asmgcroot/pypy/translator/c/gcc/test/track2.s	Mon Feb  4 10:51:34 2008
@@ -54,7 +54,7 @@
 	movl	%edx, 4(%esp)
 	movl	%esi, (%esp)
 	call	pypy_g_populate
-        ;; expected (32, -8, -4, 5, 7, 1)
+        ;; expected {28(%esp) | 20(%esp), 24(%esp), %edi, %ebp | %ebx}
 	movl	%ebx, %eax
 #APP
 	/* GCROOT %eax */
@@ -73,7 +73,7 @@
 .L1351:
 .L1352:
 	call	LL_stack_too_big
-        ;; expected (32, -8, -4, 5, 7, 1)
+        ;; expected {28(%esp) | 20(%esp), 24(%esp), %edi, %ebp | %ebx}
 	testl	%eax, %eax
 	jne	.L1418
 .L1361:
@@ -100,7 +100,7 @@
 	movl	$24, %edx
 	movl	%edx, 4(%esp)
 	call	pypy_g_SemiSpaceGC_try_obtain_free_space
-        ;; expected (32, -8, -4, 5, 7, 1)
+        ;; expected {28(%esp) | 20(%esp), 24(%esp), %edi, %ebp | %ebx}
 	movl	pypy_g_ExcData, %edx
 	xorl	%ecx, %ecx
 	testl	%edx, %edx
@@ -121,7 +121,7 @@
 	movl	$24, %ecx
 	movl	%ecx, 4(%esp)
 	call	pypy_g_SemiSpaceGC_try_obtain_free_space
-        ;; expected (32, -8, -4, 5, 7, 1)
+        ;; expected {28(%esp) | 20(%esp), 24(%esp), %edi, %ebp | %ebx}
 	movl	pypy_g_ExcData, %edx
 	xorl	%ecx, %ecx
 	testl	%edx, %edx

Modified: pypy/branch/asmgcroot/pypy/translator/c/gcc/test/track3.s
==============================================================================
--- pypy/branch/asmgcroot/pypy/translator/c/gcc/test/track3.s	(original)
+++ pypy/branch/asmgcroot/pypy/translator/c/gcc/test/track3.s	Mon Feb  4 10:51:34 2008
@@ -56,7 +56,7 @@
 	movl	%edx, 4(%esp)
 	movl	%esi, (%esp)
 	call	pypy_g_populate
-        ;; expected (32, -12, -8, 5, -4, 1)
+        ;; expected {4(%ebp) | -8(%ebp), -4(%ebp), %edi, (%ebp) | %ebx}
 	movl	%ebx, %eax
 #APP
 	/* GCROOT %eax */
@@ -75,7 +75,7 @@
 .L1350:
 .L1351:
 	call	LL_stack_too_big
-        ;; expected (32, -12, -8, 5, -4, 1)
+        ;; expected {4(%ebp) | -8(%ebp), -4(%ebp), %edi, (%ebp) | %ebx}
 	testl	%eax, %eax
 	jne	.L1417
 .L1360:
@@ -102,7 +102,7 @@
 	movl	$24, %edx
 	movl	%edx, 4(%esp)
 	call	pypy_g_SemiSpaceGC_try_obtain_free_space
-        ;; expected (32, -12, -8, 5, -4, 1)
+        ;; expected {4(%ebp) | -8(%ebp), -4(%ebp), %edi, (%ebp) | %ebx}
 	movl	pypy_g_ExcData, %edx
 	xorl	%ecx, %ecx
 	testl	%edx, %edx
@@ -123,7 +123,7 @@
 	movl	$24, %ecx
 	movl	%ecx, 4(%esp)
 	call	pypy_g_SemiSpaceGC_try_obtain_free_space
-        ;; expected (32, -12, -8, 5, -4, 1)
+        ;; expected {4(%ebp) | -8(%ebp), -4(%ebp), %edi, (%ebp) | %ebx}
 	movl	pypy_g_ExcData, %edx
 	xorl	%ecx, %ecx
 	testl	%edx, %edx

Modified: pypy/branch/asmgcroot/pypy/translator/c/gcc/test/track4.s
==============================================================================
--- pypy/branch/asmgcroot/pypy/translator/c/gcc/test/track4.s	(original)
+++ pypy/branch/asmgcroot/pypy/translator/c/gcc/test/track4.s	Mon Feb  4 10:51:34 2008
@@ -7,11 +7,11 @@
 	movl	%esp, %ebp
 	pushl	%edi
 	subl	$8, %esp
-	andl	$-15, %esp
+	andl	$-16, %esp
 	movl	%ebx, -8(%ebp)
 	movl	8(%ebp), %edi
 	call	foobar
-	;; expected (20, -12, 3, -8, -4, 5)
+	;; expected {4(%ebp) | -8(%ebp), %esi, -4(%ebp), (%ebp) | %edi}
 .L1:
 	cmpl	$0, %eax
 	je	.L3
@@ -26,7 +26,7 @@
 	movl	%esi, %ebx
 	movl	$nonsense, %esi
 	call	foobar
-	;; expected (36, -12, 1, -8, -4, -28, -16)
+	;; expected {4(%ebp) | -8(%ebp), %ebx, -4(%ebp), (%ebp) | -12(%ebp), 4(%esp)}
 	addl	%edi, %eax
 	movl	4(%esp), %eax
 	movl	%ebx, %esi
@@ -39,7 +39,7 @@
 	;; end of inlined function
 .L3:
 	call	foobar
-	;; expected (20, -12, 3, -8, -4, 5)
+	;; expected {4(%ebp) | -8(%ebp), %esi, -4(%ebp), (%ebp) | %edi}
 #APP
 	/* GCROOT %edi */
 #NO_APP

Modified: pypy/branch/asmgcroot/pypy/translator/c/gcc/test/track5.s
==============================================================================
--- pypy/branch/asmgcroot/pypy/translator/c/gcc/test/track5.s	(original)
+++ pypy/branch/asmgcroot/pypy/translator/c/gcc/test/track5.s	Mon Feb  4 10:51:34 2008
@@ -15,11 +15,11 @@
 	movl	%esi, 8(%esp)
 	movl	%esi, (%esp)
 	call	pypy_g_trace___trace_copy
-	;; expected (32, -8, -4, 5, 7)
+	;; expected {28(%esp) | 20(%esp), 24(%esp), %edi, %ebp | }
 	movl	%ebx, 4(%esp)
 	movl	%esi, (%esp)
 	call	pypy_g_SemiSpaceGC_get_size
-	;; expected (32, -8, -4, 5, 7)
+	;; expected {28(%esp) | 20(%esp), 24(%esp), %edi, %ebp | }
 	addl	%eax, %ebx
 .L1216:
 	cmpl	12(%esi), %ebx
@@ -36,15 +36,15 @@
 	movl	%esi, 8(%esp)
 	movl	%esi, (%esp)
 	call	pypy_g_trace___trace_copy
-	;; expected (32, -8, -4, 5, 7)
+	;; expected {28(%esp) | 20(%esp), 24(%esp), %edi, %ebp | }
 	movl	%ebx, 4(%esp)
 	movl	%esi, (%esp)
 	call	pypy_g_SemiSpaceGC_get_size
-	;; expected (32, -8, -4, 5, 7)
+	;; expected {28(%esp) | 20(%esp), 24(%esp), %edi, %ebp | }
 	addl	%eax, %ebx
 .L1227:
 	call	RPyAbort
-	;; expected (32, -8, -4, 5, 7)
+	;; expected {28(%esp) | 20(%esp), 24(%esp), %edi, %ebp | }
 	cmpl	12(%esi), %ebx
 	jb	.L1229
 	addl	$20, %esp

Added: pypy/branch/asmgcroot/pypy/translator/c/gcc/test/track6.s
==============================================================================
--- (empty file)
+++ pypy/branch/asmgcroot/pypy/translator/c/gcc/test/track6.s	Mon Feb  4 10:51:34 2008
@@ -0,0 +1,26 @@
+	.type	main, @function
+main:
+	;; a minimal example showing what kind of code gcc
+	;; can produce for main(): some local variable accesses
+	;; are relative to %ebp, while others are relative to
+	;; %esp, and the difference %ebp-%esp is not constant
+	;; because of the 'andl' to align the stack
+	pushl	%ebp
+	movl	%esp, %ebp
+	subl	$8, %esp
+	andl	$-16, %esp
+	movl	$globalptr1, -4(%ebp)
+	movl	$globalptr2, (%esp)
+	pushl	$0
+	call	foobar
+	;; expected {4(%ebp) | %ebx, %esi, %edi, (%ebp) | -4(%ebp), 4(%esp)}
+	popl	%eax
+#APP
+	/* GCROOT -4(%ebp) */
+	/* GCROOT (%esp) */
+#NO_APP
+	movl	%ebp, %esp
+	popl	%ebp
+	ret
+
+	.size	main, .-main

Modified: pypy/branch/asmgcroot/pypy/translator/c/gcc/trackgcroot.py
==============================================================================
--- pypy/branch/asmgcroot/pypy/translator/c/gcc/trackgcroot.py	(original)
+++ pypy/branch/asmgcroot/pypy/translator/c/gcc/trackgcroot.py	Mon Feb  4 10:51:34 2008
@@ -30,6 +30,7 @@
         self.gcmaptable = []
         self.verbose = verbose
         self.seen_main = False
+        self.files_seen = 0
 
     def dump(self, output):
         assert self.seen_main
@@ -58,6 +59,11 @@
             ret
         .size pypy_asm_stackwalk_init, .-pypy_asm_stackwalk_init
         """
+        # XXX the two gcmap tables are a bit largish.  They could easily
+        # be compressed by a factor of 4 or more.  I suspect they also
+        # produce large linker tables which could be seriously reduced
+        # as well.  A key observation is that in practice most functions
+        # seem to use exactly the same call shape for each call they contain.
         print >> output, '\t.data'
         print >> output, '\t.align\t4'
         print >> output, '\t.globl\t__gcmapstart'
@@ -113,48 +119,37 @@
             if in_function:
                 lines = self.process_function(lines, entrypoint, filename)
             newfile.writelines(lines)
+        self.files_seen += 1
 
     def process_function(self, lines, entrypoint, filename):
-        tracker = FunctionGcRootTracker(lines)
-        tracker.is_main = tracker.funcname == entrypoint
+        tracker = FunctionGcRootTracker(lines, filetag = self.files_seen)
         if self.verbose:
             print >> sys.stderr, '[trackgcroot:%s] %s' % (filename,
                                                           tracker.funcname)
         table = tracker.computegcmaptable(self.verbose)
         if self.verbose > 1:
             for label, state in table:
-                print >> sys.stderr, label, '\t', state
-        if tracker.is_main:
-            fp = tracker.uses_frame_pointer
-            table = self.fixup_entrypoint_table(table, fp)
+                print >> sys.stderr, label, '\t', format_callshape(state)
+        if tracker.funcname == entrypoint:
+            table = self.fixup_entrypoint_table(table)
         self.gcmaptable.extend(table)
         return tracker.lines
 
-    def fixup_entrypoint_table(self, table, uses_frame_pointer):
+    def fixup_entrypoint_table(self, table):
         self.seen_main = True
-        # as an end marker, set the CALLEE_SAVE_REGISTERS locations to -1.
-        # this info is not useful because we don't go to main()'s caller.
+        # as an end marker, set all five initial entries of the callshapes
+        # to LOC_NOWHERE.  This info is not useful anyway because we don't
+        # go to main()'s caller.
         newtable = []
-        MARKERS = (-1, -1, -1, -1)
+        MARKERS = (LOC_NOWHERE,) * 5
         for label, shape in table:
-            newtable.append((label, shape[:1] + MARKERS + shape[5:]))
-        table = newtable
-
-        if uses_frame_pointer:
-            # the main() function may contain strange code that aligns the
-            # stack pointer to a multiple of 16, which messes up our framesize
-            # computation.  So just for this function, we use a frame size
-            # of 0 to ask asmgcroot to read %ebp to find the frame pointer.
-            newtable = []
-            for label, shape in table:
-                newtable.append((label, (0,) + shape[1:]))
-            table = newtable
-        return table
+            newtable.append((label, MARKERS + shape[5:]))
+        return newtable
 
 
 class FunctionGcRootTracker(object):
 
-    def __init__(self, lines):
+    def __init__(self, lines, filetag=0):
         match = r_functionstart.match(lines[0])
         self.funcname = match.group(1)
         match = r_functionend.match(lines[-1])
@@ -163,7 +158,7 @@
         self.lines = lines
         self.uses_frame_pointer = False
         self.r_localvar = r_localvarnofp
-        self.is_main = False
+        self.filetag = filetag
 
     def computegcmaptable(self, verbose=0):
         self.findlabels()
@@ -182,26 +177,33 @@
         return self.gettable()
 
     def gettable(self):
-        """Returns a list [(label_after_call, shape_tuple)]
-        where shape_tuple = (framesize, where_is_ebx_saved, ...
-                            ..., where_is_ebp_saved, gcroot0, gcroot1...)
+        """Returns a list [(label_after_call, callshape_tuple)]
+        See format_callshape() for more details about callshape_tuple.
         """
         table = []
         for insn in self.list_call_insns():
             if not hasattr(insn, 'framesize'):
                 continue     # calls that never end up reaching a RET
-            shape = [insn.framesize + 4]     # accounts for the return address
+            if self.uses_frame_pointer:
+                retaddr = frameloc(LOC_EBP_BASED, 4)
+            else:
+                retaddr = frameloc(LOC_ESP_BASED, insn.framesize)
+            shape = [retaddr]
             # the first gcroots are always the ones corresponding to
             # the callee-saved registers
             for reg in CALLEE_SAVE_REGISTERS:
                 shape.append(None)
-            for loc, tag in insn.gcroots.items():
-                if not isinstance(loc, int):
-                    # a special representation for a register location,
-                    # as an odd-valued number
-                    loc = CALLEE_SAVE_REGISTERS.index(loc) * 2 + 1
+            gcroots = []
+            for localvar, tag in insn.gcroots.items():
+                if isinstance(localvar, LocalVar):
+                    loc = localvar.getlocation(insn.framesize,
+                                               self.uses_frame_pointer)
+                else:
+                    assert localvar in REG2LOC
+                    loc = REG2LOC[localvar]
+                assert isinstance(loc, int)
                 if tag is None:
-                    shape.append(loc)
+                    gcroots.append(loc)
                 else:
                     regindex = CALLEE_SAVE_REGISTERS.index(tag)
                     shape[1 + regindex] = loc
@@ -209,6 +211,8 @@
                 reg = CALLEE_SAVE_REGISTERS[shape.index(None) - 1]
                 raise AssertionError("cannot track where register %s is saved"
                                      % (reg,))
+            gcroots.sort()
+            shape.extend(gcroots)
             table.append((insn.global_label, tuple(shape)))
         return table
 
@@ -281,6 +285,11 @@
         return [insn for insn in self.insns if isinstance(insn, InsnCall)]
 
     def findframesize(self):
+        # the 'framesize' attached to an instruction is the number of bytes
+        # in the frame at this point.  This doesn't count the return address
+        # which is the word immediately following the frame in memory.
+        # The 'framesize' is set to an odd value if it is only an estimate
+        # (see visit_andl()).
 
         def walker(insn, size_delta):
             check = deltas.setdefault(insn, size_delta)
@@ -318,17 +327,21 @@
                     localvar = getattr(insn, name)
                     match = r_localvar_esp.match(localvar)
                     if match:
+                        if localvar == '0(%esp)': # for pushl and popl, by
+                            hint = None           # default ebp addressing is
+                        else:                     # a bit nicer
+                            hint = 'esp'
                         ofs_from_esp = int(match.group(1) or '0')
                         localvar = ofs_from_esp - insn.framesize
                         assert localvar != 0    # that's the return address
-                        setattr(insn, name, localvar)
+                        setattr(insn, name, LocalVar(localvar, hint=hint))
                     elif self.uses_frame_pointer:
                         match = r_localvar_ebp.match(localvar)
                         if match:
                             ofs_from_ebp = int(match.group(1) or '0')
                             localvar = ofs_from_ebp - 4
                             assert localvar != 0    # that's the return address
-                            setattr(insn, name, localvar)
+                            setattr(insn, name, LocalVar(localvar, hint='ebp'))
 
     def trackgcroots(self):
 
@@ -346,7 +359,7 @@
     def dump(self):
         for insn in self.insns:
             size = getattr(insn, 'framesize', '?')
-            print '%4s  %s' % (size, insn)
+            print >> sys.stderr, '%4s  %s' % (size, insn)
 
     def walk_instructions_backwards(self, walker, initial_insn, initial_state):
         pending = []
@@ -388,7 +401,7 @@
         if label is None:
             k = call.lineno
             while 1:
-                label = '__gcmap_IN_%s_%d' % (self.funcname, k)
+                label = '__gcmap_IN%d_%s_%d' % (self.filetag, self.funcname, k)
                 if label not in self.labels:
                     break
                 k += 1
@@ -433,7 +446,9 @@
         target = match.group(2)
         if target == '%esp':
             count = match.group(1)
-            assert count.startswith('$')
+            if not count.startswith('$'):
+                # strange instruction - I've seen 'subl %eax, %esp'
+                return InsnCannotFollowEsp()
             return InsnStackAdjust(sign * int(count[1:]))
         elif self.r_localvar.match(target):
             return InsnSetLocal(target)
@@ -471,15 +486,10 @@
         target = match.group(2)
         if target == '%esp':
             # only for  andl $-16, %esp  used to align the stack in main().
-            # If gcc compiled main() with a frame pointer, then it should use
-            # %ebp-relative addressing and not %esp-relative addressing
-            # and asmgcroot will read %ebp to find the frame.  If main()
-            # is compiled without a frame pointer, the total frame size that
-            # we compute ends up being bogus but that's ok because gcc has
-            # to use %esp-relative addressing only and we don't need to walk
-            # to caller frames because it's main().
-            assert self.is_main
-            return []
+            # The exact amount of adjutment is not known yet, so we use
+            # an odd-valued estimate to make sure the real value is not used
+            # elsewhere by the FunctionGcRootTracker.
+            return InsnCannotFollowEsp()
         else:
             return self.binary_insn(line)
 
@@ -493,8 +503,10 @@
             if not match:
                 framesize = None    # strange instruction
             else:
+                if not self.uses_frame_pointer:
+                    raise UnrecognizedOperation('epilogue without prologue')
                 ofs_from_ebp = int(match.group(1) or '0')
-                assert ofs_from_ebp < 0
+                assert ofs_from_ebp <= 0
                 framesize = 4 - ofs_from_ebp
             return InsnEpilogue(framesize)
         else:
@@ -524,10 +536,10 @@
     def visit_pushl(self, line):
         match = r_unaryinsn.match(line)
         source = match.group(1)
-        return [InsnStackAdjust(-4)] + self.insns_for_copy(source, '(%esp)')
+        return [InsnStackAdjust(-4)] + self.insns_for_copy(source, '0(%esp)')
 
     def _visit_pop(self, target):
-        return self.insns_for_copy('(%esp)', target) + [InsnStackAdjust(+4)]
+        return self.insns_for_copy('0(%esp)', target) + [InsnStackAdjust(+4)]
 
     def visit_popl(self, line):
         match = r_unaryinsn.match(line)
@@ -629,6 +641,39 @@
     pass
 somenewvalue = SomeNewValue()
 
+class LocalVar(object):
+    # A local variable location at position 'ofs_from_frame_end',
+    # which is counted from the end of the stack frame (so it is always
+    # negative, unless it refers to arguments of the current function).
+    def __init__(self, ofs_from_frame_end, hint=None):
+        self.ofs_from_frame_end = ofs_from_frame_end
+        self.hint = hint
+
+    def __repr__(self):
+        return '<%+d;%s>' % (self.ofs_from_frame_end, self.hint or 'e*p')
+
+    def __hash__(self):
+        return hash(self.ofs_from_frame_end)
+
+    def __cmp__(self, other):
+        if isinstance(other, LocalVar):
+            return cmp(self.ofs_from_frame_end, other.ofs_from_frame_end)
+        else:
+            return 1
+
+    def getlocation(self, framesize, uses_frame_pointer):
+        if (self.hint == 'esp' or not uses_frame_pointer
+            or self.ofs_from_frame_end % 2 != 0):
+            # try to use esp-relative addressing
+            ofs_from_esp = framesize + self.ofs_from_frame_end
+            if ofs_from_esp % 2 == 0:
+                return frameloc(LOC_ESP_BASED, ofs_from_esp)
+            # we can get an odd value if the framesize is marked as bogus
+            # by visit_andl()
+        assert uses_frame_pointer
+        ofs_from_ebp = self.ofs_from_frame_end + 4
+        return frameloc(LOC_EBP_BASED, ofs_from_ebp)
+
 
 class Insn(object):
     _args_ = []
@@ -659,7 +704,8 @@
             self.arguments[reg] = somenewvalue
     def source_of(self, localvar, tag):
         if localvar not in self.arguments:
-            assert isinstance(localvar, int) and localvar > 0, (
+            assert (isinstance(localvar, LocalVar) and
+                    localvar.ofs_from_frame_end > 0), (
                 "must come from an argument to the function, got %r" %
                 (localvar,))
             self.arguments[localvar] = somenewvalue
@@ -692,6 +738,10 @@
         assert delta % 4 == 0
         self.delta = delta
 
+class InsnCannotFollowEsp(InsnStackAdjust):
+    def __init__(self):
+        self.delta = 7     # use an odd value as marker
+
 class InsnStop(Insn):
     pass
 
@@ -704,30 +754,34 @@
     _args_ = ['lineno', 'gcroots']
     def __init__(self, lineno):
         # 'gcroots' is a dict built by side-effect during the call to
-        # FunctionGcRootTracker.trackgcroots().  Its meaning is as follows:
-        # the keys are the location that contain gc roots (either register
-        # names like '%esi', or negative integer offsets relative to the end
-        # of the function frame).  The value corresponding to a key is the
-        # "tag", which is None for a normal gc root, or else the name of a
-        # callee-saved register.  In the latter case it means that this is
-        # only a gc root if the corresponding register in the caller was
-        # really containing a gc pointer.  A typical example:
+        # FunctionGcRootTracker.trackgcroots().  Its meaning is as
+        # follows: the keys are the locations that contain gc roots
+        # (register names or LocalVar instances).  The value
+        # corresponding to a key is the "tag", which is None for a
+        # normal gc root, or else the name of a callee-saved register.
+        # In the latter case it means that this is only a gc root if the
+        # corresponding register in the caller was really containing a
+        # gc pointer.  A typical example:
         #
-        #     InsnCall({'%ebp': '%ebp', -8: '%ebx', '%esi': None})
-        #
-        # means that %esi is a gc root across this call; that %ebp is a
-        # gc root if it was in the caller (typically because %ebp is not
-        # modified at all in the current function); and that the word at 8
-        # bytes before the end of the current stack frame is a gc root if
-        # %ebx was a gc root in the caller (typically because the current
-        # function saves and restores %ebx from there in the prologue and
-        # epilogue).
+        #   InsnCall({LocalVar(-8)': None,
+        #             '%esi': '%esi',
+        #             LocalVar(-12)': '%ebx'})
         #
+        # means that the value at -8 from the frame end is a gc root
+        # across this call; that %esi is a gc root if it was in the
+        # caller (typically because %esi is not modified at all in the
+        # current function); and that the value at -12 from the frame
+        # end is a gc root if %ebx was a gc root in the caller
+        # (typically because the current function saves and restores
+        # %ebx from there in the prologue and epilogue).
         self.gcroots = {}
         self.lineno = lineno
 
     def source_of(self, localvar, tag):
-        self.gcroots[localvar] = tag
+        tag1 = self.gcroots.setdefault(localvar, tag)
+        assert tag1 == tag, (
+            "conflicting entries for InsnCall.gcroots[%s]:\n%r and %r" % (
+            localvar, tag1, tag))
         return localvar
 
 class InsnGCROOT(Insn):
@@ -760,6 +814,66 @@
 CALLEE_SAVE_REGISTERS_NOEBP = ['%ebx', '%esi', '%edi']
 CALLEE_SAVE_REGISTERS = CALLEE_SAVE_REGISTERS_NOEBP + ['%ebp']
 
+LOC_NOWHERE   = 0
+LOC_REG       = 1
+LOC_EBP_BASED = 2
+LOC_ESP_BASED = 3
+LOC_MASK      = 0x03
+
+REG2LOC = {}
+for _i, _reg in enumerate(CALLEE_SAVE_REGISTERS):
+    REG2LOC[_reg] = LOC_REG | (_i<<2)
+
+def frameloc(base, offset):
+    assert base in (LOC_EBP_BASED, LOC_ESP_BASED)
+    assert offset % 4 == 0
+    return base | offset
+
+# __________ debugging output __________
+
+def format_location(loc):
+    # A 'location' is a single number describing where a value is stored
+    # across a call.  It can be in one of the CALLEE_SAVE_REGISTERS, or
+    # in the stack frame at an address relative to either %esp or %ebp.
+    # The last two bits of the location number are used to tell the cases
+    # apart; see format_location().
+    kind = loc & LOC_MASK
+    if kind == LOC_NOWHERE:
+        return '?'
+    elif kind == LOC_REG:
+        reg = loc >> 2
+        assert 0 <= reg <= 3
+        return CALLEE_SAVE_REGISTERS[reg]
+    else:
+        if kind == LOC_EBP_BASED:
+            result = '(%ebp)'
+        else:
+            result = '(%esp)'
+        offset = loc & ~ LOC_MASK
+        if offset != 0:
+            result = str(offset) + result
+        return result
+
+def format_callshape(shape):
+    # A 'call shape' is a tuple of locations in the sense of format_location().
+    # They describe where in a function frame interesting values are stored,
+    # when this function executes a 'call' instruction.
+    #
+    #   shape[0] is the location that stores the fn's own return address
+    #            (not the return address for the currently executing 'call')
+    #   shape[1] is where the fn saved its own caller's %ebx value
+    #   shape[2] is where the fn saved its own caller's %esi value
+    #   shape[3] is where the fn saved its own caller's %edi value
+    #   shape[4] is where the fn saved its own caller's %ebp value
+    #   shape[>=5] are GC roots: where the fn has put its local GCPTR vars
+    #
+    assert isinstance(shape, tuple)
+    assert len(shape) >= 5
+    result = [format_location(loc) for loc in shape]
+    return '{%s | %s | %s}' % (result[0],
+                               ', '.join(result[1:5]),
+                               ', '.join(result[5:]))
+
 
 if __name__ == '__main__':
     if sys.argv and sys.argv[1] == '-v':



More information about the Pypy-commit mailing list