Commits

Armin Rigo committed 8e12fcb

First implementation. The results are not great on targetgcbench.

  • Participants
  • Parent commits ff5ea72
  • Branches inline-shadowstack

Comments (0)

Files changed (10)

File pypy/config/translationoption.py

                default=IS_64_BITS, cmdline="--gcremovetypeptr"),
     ChoiceOption("gcrootfinder",
                  "Strategy for finding GC Roots (framework GCs only)",
-                 ["n/a", "shadowstack", "asmgcc"],
+                 ["n/a", "shadowstack", "asmgcc", "inlinestack"],
                  "shadowstack",
                  cmdline="--gcrootfinder",
                  requires={
-                     "shadowstack": [("translation.gctransformer", "framework")],
-                     "asmgcc": [("translation.gctransformer", "framework"),
-                                ("translation.backend", "c")],
-                    }),
+                   "shadowstack": [("translation.gctransformer", "framework")],
+                   "asmgcc": [("translation.gctransformer", "framework"),
+                              ("translation.backend", "c")],
+                   "inlinestack": [("translation.gctransformer", "framework"),
+                                   ("translation.backend", "c")],
+                   }),
 
     # other noticeable options
     BoolOption("thread", "enable use of threading primitives",

File pypy/jit/codewriter/codewriter.py

 from pypy.jit.codewriter import support, heaptracker
-from pypy.jit.codewriter.regalloc import perform_register_allocation
+from pypy.tool.algo.regalloc import perform_register_allocation
+from pypy.jit.metainterp.history import getkind
 from pypy.jit.codewriter.flatten import flatten_graph, KINDS
 from pypy.jit.codewriter.assembler import Assembler, JitCode
 from pypy.jit.codewriter.jtransform import transform_graph
         # step 2: perform register allocation on it
         regallocs = {}
         for kind in KINDS:
-            regallocs[kind] = perform_register_allocation(graph, kind)
+            kind_filter = lambda v: getkind(v.concretetype) == kind
+            regallocs[kind] = perform_register_allocation(graph, kind_filter)
         #
         # step 3: flatten the graph to produce human-readable "assembler",
         # which means mostly producing a linear list of operations and

File pypy/jit/codewriter/regalloc.py

-import sys
-from pypy.objspace.flow.model import Variable
-from pypy.tool.algo.color import DependencyGraph
-from pypy.tool.algo.unionfind import UnionFind
-from pypy.jit.metainterp.history import getkind
-from pypy.jit.codewriter.flatten import ListOfKind
-
-def perform_register_allocation(graph, kind):
-    """Perform register allocation for the Variables of the given 'kind'
-    in the 'graph'."""
-    regalloc = RegAllocator(graph, kind)
-    regalloc.make_dependencies()
-    regalloc.coalesce_variables()
-    regalloc.find_node_coloring()
-    return regalloc
-
-
-class RegAllocator(object):
-    DEBUG_REGALLOC = False
-
-    def __init__(self, graph, kind):
-        self.graph = graph
-        self.kind = kind
-
-    def make_dependencies(self):
-        dg = DependencyGraph()
-        for block in self.graph.iterblocks():
-            # Compute die_at = {Variable: index_of_operation_with_last_usage}
-            die_at = dict.fromkeys(block.inputargs, 0)
-            for i, op in enumerate(block.operations):
-                for v in op.args:
-                    if isinstance(v, Variable):
-                        die_at[v] = i
-                    elif isinstance(v, ListOfKind):
-                        for v1 in v:
-                            if isinstance(v1, Variable):
-                                die_at[v1] = i
-                if op.result is not None:
-                    die_at[op.result] = i
-            if isinstance(block.exitswitch, tuple):
-                for x in block.exitswitch:
-                    die_at.pop(x, None)
-            else:
-                die_at.pop(block.exitswitch, None)
-            for link in block.exits:
-                for v in link.args:
-                    die_at.pop(v, None)
-            die_at = [(value, key) for (key, value) in die_at.items()]
-            die_at.sort()
-            die_at.append((sys.maxint,))
-            # Done.  XXX the code above this line runs 3 times
-            # (for kind in KINDS) to produce the same result...
-            livevars = [v for v in block.inputargs
-                          if getkind(v.concretetype) == self.kind]
-            # Add the variables of this block to the dependency graph
-            for i, v in enumerate(livevars):
-                dg.add_node(v)
-                for j in range(i):
-                    dg.add_edge(livevars[j], v)
-            livevars = set(livevars)
-            die_index = 0
-            for i, op in enumerate(block.operations):
-                while die_at[die_index][0] == i:
-                    try:
-                        livevars.remove(die_at[die_index][1])
-                    except KeyError:
-                        pass
-                    die_index += 1
-                if (op.result is not None and
-                    getkind(op.result.concretetype) == self.kind):
-                    dg.add_node(op.result)
-                    for v in livevars:
-                        if getkind(v.concretetype) == self.kind:
-                            dg.add_edge(v, op.result)
-                    livevars.add(op.result)
-        self._depgraph = dg
-
-    def coalesce_variables(self):
-        self._unionfind = UnionFind()
-        pendingblocks = list(self.graph.iterblocks())
-        while pendingblocks:
-            block = pendingblocks.pop()
-            # Aggressively try to coalesce each source variable with its
-            # target.  We start from the end of the graph instead of
-            # from the beginning.  This is a bit arbitrary, but the idea
-            # is that the end of the graph runs typically more often
-            # than the start, given that we resume execution from the
-            # middle during blackholing.
-            for link in block.exits:
-                if link.last_exception is not None:
-                    self._depgraph.add_node(link.last_exception)
-                if link.last_exc_value is not None:
-                    self._depgraph.add_node(link.last_exc_value)
-                for i, v in enumerate(link.args):
-                    self._try_coalesce(v, link.target.inputargs[i])
-
-    def _try_coalesce(self, v, w):
-        if isinstance(v, Variable) and getkind(v.concretetype) == self.kind:
-            dg = self._depgraph
-            uf = self._unionfind
-            v0 = uf.find_rep(v)
-            w0 = uf.find_rep(w)
-            if v0 is not w0 and v0 not in dg.neighbours[w0]:
-                _, rep, _ = uf.union(v0, w0)
-                assert uf.find_rep(v0) is uf.find_rep(w0) is rep
-                if rep is v0:
-                    dg.coalesce(w0, v0)
-                else:
-                    assert rep is w0
-                    dg.coalesce(v0, w0)
-
-    def find_node_coloring(self):
-        self._coloring = self._depgraph.find_node_coloring()
-        if self.DEBUG_REGALLOC:
-            for block in self.graph.iterblocks():
-                print block
-                for v in block.getvariables():
-                    print '\t', v, '\t', self.getcolor(v)
-
-    def getcolor(self, v):
-        return self._coloring[self._unionfind.find_rep(v)]
-
-    def swapcolors(self, col1, col2):
-        for key, value in self._coloring.items():
-            if value == col1:
-                self._coloring[key] = col2
-            elif value == col2:
-                self._coloring[key] = col1

File pypy/jit/codewriter/test/test_regalloc.py

 import py, sys
 from pypy.jit.codewriter import support
-from pypy.jit.codewriter.regalloc import perform_register_allocation
+from pypy.tool.algo.regalloc import perform_register_allocation
 from pypy.jit.codewriter.flatten import flatten_graph, ListOfKind
 from pypy.jit.codewriter.format import assert_format
-from pypy.jit.metainterp.history import AbstractDescr
+from pypy.jit.metainterp.history import AbstractDescr, getkind
 from pypy.objspace.flow.model import Variable, Constant, SpaceOperation
 from pypy.objspace.flow.model import FunctionGraph, Block, Link
 from pypy.objspace.flow.model import c_last_exception
         self.rtyper = support.annotate(func, values, type_system=type_system)
         return self.rtyper.annotator.translator.graphs
 
+    def perform_register_allocation(self, graph, kind):
+        kind_filter = lambda v: getkind(v.concretetype) == kind
+        return perform_register_allocation(graph, kind_filter)
+
     def check_assembler(self, graph, expected, transform=False,
                         callcontrol=None):
         # 'transform' can be False only for simple graphs.  More complex
         if transform:
             from pypy.jit.codewriter.jtransform import transform_graph
             transform_graph(graph, callcontrol=callcontrol)
-        regalloc = perform_register_allocation(graph, 'int')
-        regalloc2 = perform_register_allocation(graph, 'ref')
+        regalloc = self.perform_register_allocation(graph, 'int')
+        regalloc2 = self.perform_register_allocation(graph, 'ref')
         ssarepr = flatten_graph(graph, {'int': regalloc,
                                         'ref': regalloc2})
         assert_format(ssarepr, expected)
         def f(a, b):
             return a + b
         graph = self.make_graphs(f, [5, 6])[0]
-        regalloc = perform_register_allocation(graph, 'int')
+        regalloc = self.perform_register_allocation(graph, 'int')
         va, vb = graph.startblock.inputargs
         vc = graph.startblock.operations[0].result
         assert regalloc.getcolor(va) == 0
                 a -= 1
             return b
         graph = self.make_graphs(f, [5, 6])[0]
-        regalloc = perform_register_allocation(graph, 'float')
+        regalloc = self.perform_register_allocation(graph, 'float')
         # assert did not crash
 
     def test_regalloc_loop(self):
         self.check_assembler(graph, """
             residual_call_r_r $<* fn bar>, <Descr>, R[%r0] -> %r1
             -live-
-            residual_call_ir_r $<* fn g>, <Descr>, I[%i0], R[] -> %r2
+            residual_call_ir_r $<* fn g>, <Descr>, I[%i0], R[] -> %r1
             -live-
             catch_exception L1
-            ref_return %r2
+            ref_return %r1
             ---
             L1:
             goto_if_exception_mismatch $<* struct object_vtable>, L2
-            ref_copy %r0 -> %r2
+            ref_copy %r0 -> %r1
             last_exc_value -> %r0
             residual_call_r_r $<* fn foo>, <Descr>, R[%r0] -> %r0
             -live-
-            ref_return %r2
+            ref_return %r1
             ---
             L2:
             reraise

File pypy/tool/algo/regalloc.py

+import sys
+from pypy.objspace.flow.model import Variable
+from pypy.tool.algo.color import DependencyGraph
+from pypy.tool.algo.unionfind import UnionFind
+
+def perform_register_allocation(graph, kind_filter, identity_op_filter=None):
+    """Perform register allocation for the Variables of the given kind
+    in the graph.  
+    """
+    regalloc = RegAllocator(graph, kind_filter, identity_op_filter)
+    regalloc.make_dependencies()
+    regalloc.coalesce_variables()
+    regalloc.find_node_coloring()
+    return regalloc
+
+
+class RegAllocator(object):
+    DEBUG_REGALLOC = False
+
+    def __init__(self, graph, kind_filter, identity_op_filter=None):
+        self.graph = graph
+        self.kind_filter = kind_filter
+        if identity_op_filter is not None:
+            self.identity_op_filter = identity_op_filter
+
+    def identity_op_filter(self, op):
+        return False     # default implementation
+
+    def make_dependencies(self):
+        dg = DependencyGraph()
+        uf = UnionFind()
+        for block in self.graph.iterblocks():
+            # Compute die_at = {Variable: index_of_operation_with_last_usage}
+            die_at = dict.fromkeys(block.inputargs, 0)
+            for i, op in enumerate(block.operations):
+                for v in op.args:
+                    if isinstance(v, Variable):
+                        die_at[v] = i
+                    elif is_iterable(v):
+                        for v1 in v:
+                            if isinstance(v1, Variable):
+                                die_at[v1] = i
+                if op.result is not None:
+                    die_at[op.result] = i + 1
+            if is_iterable(block.exitswitch):
+                for x in block.exitswitch:
+                    die_at.pop(x, None)
+            else:
+                die_at.pop(block.exitswitch, None)
+            for link in block.exits:
+                for v in link.args:
+                    die_at.pop(v, None)
+            die_at = [(value, key) for (key, value) in die_at.items()]
+            die_at.sort()
+            die_at.append((sys.maxint,))
+            # Done.  XXX if we need to perform register allocation on
+            # the same graph with various 'kinds', the code above this
+            # line runs several times to produce the same result...
+            livevars = [v for v in block.inputargs if self.kind_filter(v)]
+            # Add the variables of this block to the dependency graph
+            for i, v in enumerate(livevars):
+                dg.add_node(v)
+                for j in range(i):
+                    dg.add_edge(livevars[j], v)
+            livevars = set(livevars)
+            die_index = 0
+            for i, op in enumerate(block.operations):
+                while die_at[die_index][0] == i:
+                    try:
+                        livevars.remove(die_at[die_index][1])
+                    except KeyError:
+                        pass
+                    die_index += 1
+                if op.result is not None and self.kind_filter(op.result):
+                    if self.identity_op_filter(op):
+                        rep1 = uf.find_rep(op.args[0])
+                        _, rep2, _ = uf.union(rep1, op.result)
+                        assert rep2 is rep1
+                    else:
+                        dg.add_node(op.result)
+                        for v in livevars:
+                            assert self.kind_filter(v)
+                            dg.add_edge(uf.find_rep(v), op.result)
+                    livevars.add(op.result)
+        self._depgraph = dg
+        self._unionfind = uf
+
+    def coalesce_variables(self):
+        pendingblocks = list(self.graph.iterblocks())
+        while pendingblocks:
+            block = pendingblocks.pop()
+            # Aggressively try to coalesce each source variable with its
+            # target.  We start from the end of the graph instead of
+            # from the beginning.  This is a bit arbitrary, but the idea
+            # is that the end of the graph runs typically more often
+            # than the start, given that we resume execution from the
+            # middle during blackholing.
+            for link in block.exits:
+                if link.last_exception is not None:
+                    self._depgraph.add_node(link.last_exception)
+                if link.last_exc_value is not None:
+                    self._depgraph.add_node(link.last_exc_value)
+                for i, v in enumerate(link.args):
+                    self._try_coalesce(v, link.target.inputargs[i])
+
+    def _try_coalesce(self, v, w):
+        if isinstance(v, Variable) and self.kind_filter(v):
+            dg = self._depgraph
+            uf = self._unionfind
+            v0 = uf.find_rep(v)
+            w0 = uf.find_rep(w)
+            if v0 is not w0 and v0 not in dg.neighbours[w0]:
+                _, rep, _ = uf.union(v0, w0)
+                assert uf.find_rep(v0) is uf.find_rep(w0) is rep
+                if rep is v0:
+                    dg.coalesce(w0, v0)
+                else:
+                    assert rep is w0
+                    dg.coalesce(v0, w0)
+
+    def find_node_coloring(self):
+        self._coloring = self._depgraph.find_node_coloring()
+        if self.DEBUG_REGALLOC:
+            for block in self.graph.iterblocks():
+                print block
+                for v in block.getvariables():
+                    print '\t', v, '\t', self.getcolor(v)
+
+    def getcolor(self, v):
+        return self._coloring[self._unionfind.find_rep(v)]
+
+    def swapcolors(self, col1, col2):
+        for key, value in self._coloring.items():
+            if value == col1:
+                self._coloring[key] = col2
+            elif value == col2:
+                self._coloring[key] = col1
+
+
+def is_iterable(x):
+    try:
+        iter(x)
+        return True
+    except (TypeError, AttributeError):
+        return False

File pypy/tool/algo/test/test_regalloc.py

+
+# XXX not enough tests, but see also jit/codewriter/test/test_regalloc.py
+
+import py
+from pypy.tool.algo.regalloc import perform_register_allocation
+from pypy.rpython.test.test_llinterp import gengraph
+from pypy.rpython.lltypesystem import lltype
+from pypy.rlib.jit import hint
+
+
+def test_simple():
+    def f(a, b, c):
+        return (b * c) + a
+    def kind_float(v):
+        return v.concretetype == lltype.Float
+
+    t, rtyper, graph = gengraph(f, [int, int, float])
+    assert [op.opname for op in graph.startblock.operations] == [
+        "cast_int_to_float",    # (1) = b
+        "float_mul",            # (0) = (1) * (0)
+        "cast_int_to_float",    # (1) = a
+        "float_add"]            # (0) = (1) + (0)
+
+    regalloc = perform_register_allocation(graph, kind_float)
+
+    py.test.raises(KeyError, regalloc.getcolor, graph.getargs()[0])
+    py.test.raises(KeyError, regalloc.getcolor, graph.getargs()[1])
+
+    ops = graph.startblock.operations
+    assert regalloc.getcolor(graph.getargs()[2]) == 0
+    assert regalloc.getcolor(ops[0].result) == 1
+    assert regalloc.getcolor(ops[1].result) == 0
+    assert regalloc.getcolor(ops[2].result) == 1
+    assert regalloc.getcolor(ops[3].result) == 0
+    assert regalloc.getcolor(graph.getreturnvar()) == 0
+
+def test_unused_result():
+    def f(x):
+        hint(x, blah=True)
+        hint(x, blah=True)
+        hint(x, blah=True)
+        return x
+    def kind_float(v):
+        return v.concretetype == lltype.Float
+
+    t, rtyper, graph = gengraph(f, [lltype.Float])
+    assert [op.opname for op in graph.startblock.operations] == [
+        "hint",   # (1) = hint(0)
+        "hint",   # (1) = hint(0)
+        "hint"]   # (1) = hint(0)
+
+    regalloc = perform_register_allocation(graph, kind_float)
+    ops = graph.startblock.operations
+    assert regalloc.getcolor(graph.getargs()[0]) == 0
+    assert regalloc.getcolor(ops[0].result) == 1
+    assert regalloc.getcolor(ops[1].result) == 1
+    assert regalloc.getcolor(ops[2].result) == 1
+    assert regalloc.getcolor(graph.getreturnvar()) == 0
+
+def test_identity_op():
+    def f(x):
+        y = hint(x, blah=True)
+        z = hint(y, blah=True)
+        t = hint(x, blah=True)
+        return 0
+    def kind_float(v):
+        return v.concretetype == lltype.Float
+    def identity_op(op):
+        return op.opname == 'hint'
+
+    t, rtyper, graph = gengraph(f, [lltype.Float])
+    assert [op.opname for op in graph.startblock.operations] == [
+        "hint",   # (0) = hint(0)
+        "hint",   # (0) = hint(0)
+        "hint"]   # (0) = hint(0)
+
+    regalloc = perform_register_allocation(graph, kind_float, identity_op)
+    ops = graph.startblock.operations
+    assert regalloc.getcolor(graph.getargs()[0]) == 0
+    assert regalloc.getcolor(ops[0].result) == 0
+    assert regalloc.getcolor(ops[1].result) == 0
+    assert regalloc.getcolor(ops[2].result) == 0

File pypy/translator/c/funcgen.py

             if self.lltypemap(v) is Void and special_case_void:
                 return '/* nothing */'
             else:
+                s = self.gcpolicy.special_ref_name_for_local(v)
+                if s:
+                    return s
                 return LOCALVAR % v.name
         elif isinstance(v, Constant):
             value = llvalue_from_constant(v)
 
     def cfunction_declarations(self):
         # declare the local variables, excluding the function arguments
+        self.gcpolicy.prepare_declarations_in_function(self)
         seen = {}
         for a in self.graph.getargs():
             seen[a.name] = True
             name = v.name
             if name not in seen:
                 seen[name] = True
+                if self.gcpolicy.special_ref_name_for_local(v):
+                    continue
                 result = cdecl(self.lltypename(v), LOCALVAR % name) + ';'
                 if self.lltypemap(v) is Void:
                     continue  #result = '/*%s*/' % result
     # ____________________________________________________________
 
     def cfunction_body(self):
+        for line in self.gcpolicy.extra_declarations_in_function(self):
+            yield line
         graph = self.graph
         yield 'goto block0;'    # to avoid a warning "this label is not used"
 
                 retval = self.expr(block.inputargs[0])
                 if self.exception_policy != "exc_helper":
                     yield 'RPY_DEBUG_RETURN();'
+                for line in self.gcpolicy.extra_code_at_return(self):
+                    yield line
                 yield 'return %s;' % retval
                 continue
             elif block.exitswitch is None:
             if a2type is Void:
                 continue
             src = self.expr(a1)
-            dest = LOCALVAR % a2.name
+            dest = self.expr(a2)
             assignments.append((a2typename, dest, src))
         for line in gen_assignments(assignments):
             yield line

File pypy/translator/c/gc.py

 import sys
-from pypy.objspace.flow.model import Constant
+from pypy.objspace.flow.model import Constant, Variable
 from pypy.translator.c.support import cdecl
 from pypy.translator.c.node import ContainerNode
 from pypy.rpython.lltypesystem.lltype import \
      typeOf, Ptr, ContainerType, RttiStruct, \
      RuntimeTypeInfo, getRuntimeTypeInfo, top_container
 from pypy.rpython.memory.gctransform import \
-     refcounting, boehm, framework, asmgcroot
+     refcounting, boehm, framework, asmgcroot, inlinestack
 from pypy.rpython.lltypesystem import lltype, llmemory
 from pypy.translator.tool.cbuild import ExternalCompilationInfo
 
     def rtti_type(self):
         return ''
 
+    def prepare_declarations_in_function(self, funcgen):
+        pass
+    def special_ref_name_for_local(self, v):
+        return False
+    def extra_declarations_in_function(self, funcgen):
+        return []
+    def extra_code_at_return(self, funcgen):
+        return []
+
     def OP_GC_PUSH_ALIVE_PYOBJ(self, funcgen, op):
         expr = funcgen.expr(op.args[0])
         if expr == 'NULL':
     def OP_GC_STACK_BOTTOM(self, funcgen, op):
         return 'pypy_asm_stack_bottom();'
 
+class InlineStackFrameworkGcPolicy(FrameworkGcPolicy):
+    transformerclass = inlinestack.InlineStackFrameworkGCTransformer
+
+    def prepare_declarations_in_function(self, funcgen):
+        self.gcvar2index = {}    # xxx should be stored on 'funcgen', ideally
+        self.all_gcvars = []
+        for block in funcgen.graph.iterblocks():
+            for op in block.operations:
+                if op.opname == 'gc_push_roots':
+                    for v in op.args:
+                        if not isinstance(v, Variable):
+                            continue
+                        assert funcgen.lltypemap(v) is not lltype.Void
+                        vname = v.name
+                        if vname not in self.gcvar2index:
+                            self.gcvar2index[vname] = len(self.all_gcvars)
+                            self.all_gcvars.append((funcgen.lltypename(v),
+                                                    vname))
+
+    def special_ref_name_for_local(self, v):
+        if v.name in self.gcvar2index:
+            return 'ref.%s' % v.name
+        return None
+
+    def extra_declarations_in_function(self, funcgen):
+        if self.all_gcvars:
+            yield 'struct {'
+            yield '\tstruct pypy_stackref_s hdr;'
+            assert len(self.all_gcvars) < 32      # XXX fix this limitation
+            for vtype, vname in self.all_gcvars:
+                yield '\t%s;' % cdecl(vtype, vname)
+            yield '} ref;'
+            #
+            yield 'ref.hdr.prev = pypy_stackref;'
+            yield 'pypy_stackref = &ref.hdr;'
+            for argname, v in zip(funcgen.argnames(), funcgen.graph.getargs()):
+                s = self.special_ref_name_for_local(v)
+                if s:
+                    yield '%s = %s;' % (s, argname)
+
+    def extra_code_at_return(self, funcgen):
+        if self.all_gcvars:
+            yield 'pypy_stackref = ref.hdr.prev;'
+
+    def OP_GC_PUSH_ROOTS(self, funcgen, op):
+        bitfield = 0
+        vars = []
+        for v in op.args:
+            if isinstance(v, Variable):
+                bitfield |= 1 << self.gcvar2index[v.name]
+                vars.append(v.name)
+        if not self.all_gcvars:
+            assert not vars
+            return ''
+        result = 'ref.hdr.bitfield = %d;' % bitfield
+        if vars:
+            result += ' /* %s */' % (' '.join(vars),)
+        return result
+
 
 name_to_gcpolicy = {
     'boehm': BoehmGcPolicy,
     'none': NoneGcPolicy,
     'framework': FrameworkGcPolicy,
     'framework+asmgcroot': AsmGcRootFrameworkGcPolicy,
+    'framework+inlinestack': InlineStackFrameworkGcPolicy,
 }
-
-

File pypy/translator/c/genc.py

             name = self.config.translation.gctransformer
             if self.config.translation.gcrootfinder == "asmgcc":
                 name = "%s+asmgcroot" % (name,)
+            elif self.config.translation.gcrootfinder == "inlinestack":
+                name = "%s+inlinestack" % (name,)
             return gc.name_to_gcpolicy[name]
         return self.gcpolicy
 

File pypy/translator/c/src/mem.h

 extern long pypy_asm_stackwalk(void*, void*);
 #define __gcnoreorderhack __gcmapend
 
+struct pypy_stackref_s {
+  struct pypy_stackref_s *prev;
+  long bitfield;
+};
+extern struct pypy_stackref_s *pypy_stackref;
+extern void *get_pypy_stackref(void);
+
+#ifndef PYPY_NOT_MAIN_FILE
+struct pypy_stackref_s *pypy_stackref = NULL;
+void *get_pypy_stackref(void) { return pypy_stackref; }
+#endif
+
 /* The following pseudo-instruction is used by --gcrootfinder=asmgcc
    just after a call to tell gcc to put a GCROOT mark on each gc-pointer
    local variable.  All such local variables need to go through a "v =