[pypy-commit] pypy vmprof: (arigo, antocuni): implement the _vmprof module, based on vmprof.c; it patches PyFrame.execute_frame so that it's called through a trampoline, which is then recognized by vmprof's signal handler

antocuni noreply at buildbot.pypy.org
Fri Oct 24 18:15:15 CEST 2014


Author: Antonio Cuni <anto.cuni at gmail.com>
Branch: vmprof
Changeset: r74183:73fc0dc6f6e1
Date: 2014-10-24 17:09 +0100
http://bitbucket.org/pypy/pypy/changeset/73fc0dc6f6e1/

Log:	(arigo, antocuni): implement the _vmprof module, based on vmprof.c;
	it patches PyFrame.execute_frame so that it's called through a
	trampoline, which is then recognized by vmprof's signal handler

diff --git a/pypy/interpreter/pycode.py b/pypy/interpreter/pycode.py
--- a/pypy/interpreter/pycode.py
+++ b/pypy/interpreter/pycode.py
@@ -83,6 +83,7 @@
         self.magic = magic
         self._signature = cpython_code_signature(self)
         self._initialize()
+        self._vmprof_setup_maybe()
 
     def _initialize(self):
         if self.co_cellvars:
@@ -124,6 +125,10 @@
             from pypy.objspace.std.mapdict import init_mapdict_cache
             init_mapdict_cache(self)
 
+    def _vmprof_setup_maybe(self):
+        # this is overridden only if _vmprof is enabled
+        pass
+
     def _cleanup_(self):
         if (self.magic == cpython_magic and
             '__pypy__' not in sys.builtin_module_names):
diff --git a/pypy/module/_vmprof/__init__.py b/pypy/module/_vmprof/__init__.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/_vmprof/__init__.py
@@ -0,0 +1,14 @@
+from pypy.interpreter.mixedmodule import MixedModule
+
+class Module(MixedModule):
+    """
+    Write me :)
+    """
+
+    appleveldefs = {
+    }
+
+    interpleveldefs = {
+        'enable': 'interp_vmprof.enable',
+        'disable': 'interp_vmprof.disable',
+    }
diff --git a/pypy/module/_vmprof/interp_vmprof.py b/pypy/module/_vmprof/interp_vmprof.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/_vmprof/interp_vmprof.py
@@ -0,0 +1,149 @@
+import py
+from rpython.rtyper.lltypesystem import lltype, rffi, llmemory
+from rpython.translator.tool.cbuild import ExternalCompilationInfo
+from rpython.rtyper.annlowlevel import cast_instance_to_gcref, cast_base_ptr_to_instance
+from rpython.rlib.objectmodel import we_are_translated, CDefinedIntSymbolic
+from rpython.tool.pairtype import extendabletype
+from pypy.interpreter.baseobjspace import W_Root
+from pypy.interpreter.error import OperationError, oefmt
+from pypy.interpreter.gateway import interp2app, unwrap_spec
+from pypy.interpreter.pyframe import PyFrame
+from pypy.interpreter.pycode import PyCode
+
+FALSE_BUT_NON_CONSTANT = CDefinedIntSymbolic('0', default=0)
+
+ROOT = py.path.local(__file__).join('..')
+SRC = ROOT.join('src')
+
+eci = ExternalCompilationInfo(
+    includes=['vmprof.h', 'trampoline.h'],
+    include_dirs = [SRC],
+    separate_module_files = [SRC.join('vmprof.c'), SRC.join('trampoline.s')],
+    libraries = ['unwind'],
+    post_include_bits=["""
+        void* pypy_vmprof_get_virtual_ip(void*);
+        void pypy_vmprof_init(void);
+    """],
+    separate_module_sources=["""
+        void pypy_vmprof_init(void) {
+            vmprof_set_mainloop(pypy_execute_frame_trampoline, 0, pypy_vmprof_get_virtual_ip);
+        }
+    """]
+)
+
+pypy_execute_frame_trampoline = rffi.llexternal(
+    "pypy_execute_frame_trampoline",
+    [llmemory.GCREF, llmemory.GCREF, llmemory.GCREF],
+    llmemory.GCREF,
+    compilation_info=eci,
+    _nowrapper=True, sandboxsafe=True)
+
+pypy_vmprof_init = rffi.llexternal("pypy_vmprof_init", [], lltype.Void, compilation_info=eci)
+vmprof_enable = rffi.llexternal("vmprof_enable", [rffi.CCHARP, rffi.LONG], lltype.Void, compilation_info=eci)
+vmprof_disable = rffi.llexternal("vmprof_disable", [], lltype.Void, compilation_info=eci)
+
+vmprof_register_virtual_function = rffi.llexternal("vmprof_register_virtual_function",
+                                                   [rffi.CCHARP, rffi.VOIDP, rffi.VOIDP],
+                                                   lltype.Void,
+                                                   compilation_info=eci)
+
+original_execute_frame = PyFrame.execute_frame.im_func
+original_execute_frame.c_name = 'pypy_pyframe_execute_frame'
+
+class __extend__(PyFrame):
+    def execute_frame(frame, w_inputvalue=None, operr=None):
+        if we_are_translated() and not FALSE_BUT_NON_CONSTANT:
+            # if we are translated, call the trampoline
+            gc_frame = cast_instance_to_gcref(frame)
+            gc_inputvalue = cast_instance_to_gcref(w_inputvalue)
+            gc_operr = cast_instance_to_gcref(operr)
+            gc_result = pypy_execute_frame_trampoline(gc_frame, gc_inputvalue, gc_operr)
+            return cast_base_ptr_to_instance(W_Root, gc_result)
+        else:
+            # else, just call the original function. The FALSE_BUT_NON_CONSTANT is
+            # needed to convince the annotator to always see
+            # original_execute_frame
+            return original_execute_frame(frame, w_inputvalue, operr)
+
+
+
+class __extend__(PyCode):
+    __metaclass__ = extendabletype
+
+    def _vmprof_setup_maybe(self):
+        self._vmprof_virtual_ip = _vmprof.get_next_virtual_IP()
+        self._vmprof_registered = 0
+
+
+def get_virtual_ip(gc_frame):
+    frame = cast_base_ptr_to_instance(PyFrame, gc_frame)
+    virtual_ip = do_get_virtual_ip(frame)
+    return rffi.cast(rffi.VOIDP, virtual_ip)
+
+def do_get_virtual_ip(frame):
+    virtual_ip = frame.pycode._vmprof_virtual_ip
+    if frame.pycode._vmprof_registered != _vmprof.counter:
+        # we need to register this code object
+        name = frame.pycode.co_name
+        start = rffi.cast(rffi.VOIDP, virtual_ip)
+        end = start # ignored for now
+        #
+        # manually fill the C buffer; we cannot use str2charp because we
+        # cannot call malloc from a signal handler
+        strbuf = _vmprof.strbuf
+        strbuf[0] = 'p'
+        strbuf[1] = 'y'
+        strbuf[2] = ':'
+        maxbuflen = min(len(name), 124)
+        i = 0
+        while i < maxbuflen:
+            strbuf[i+3] = name[i]
+            i += 1
+        strbuf[i+3] = '\0'
+        #
+        vmprof_register_virtual_function(strbuf, start, end)
+        frame.pycode._vmprof_registered = _vmprof.counter
+    #
+    return virtual_ip
+get_virtual_ip.c_name = 'pypy_vmprof_get_virtual_ip'
+
+
+class VMProf(object):
+    def __init__(self):
+        self.virtual_ip = 0
+        self.counter = 0 # the number of times we called enable()
+        self.is_enabled = False
+        self.strbuf = lltype.malloc(rffi.CCHARP.TO, 128, flavor='raw', immortal=True, zero=True)
+
+    def get_next_virtual_IP(self):
+        self.virtual_ip -= 1
+        return self.virtual_ip
+
+    def _annotate_get_virtual_ip(self):
+        if FALSE_BUT_NON_CONSTANT:
+            # make sure it's annotated
+            get_virtual_ip(lltype.nullptr(llmemory.GCREF.TO))
+
+    def enable(self, filename, period):
+        self._annotate_get_virtual_ip()
+        if self.is_enabled:
+            # XXX raise an app-level exception
+            print 'IGNORING _vmprof.enable()'
+            return
+        self.is_enabled = True
+        pypy_vmprof_init()
+        self.counter += 1
+        vmprof_enable(filename, period)
+
+    def disable(self):
+        vmprof_disable()
+        self.is_enabled = False
+
+_vmprof = VMProf()
+
+ at unwrap_spec(filename=str, period=int)
+def enable(space, filename, period=-1):
+    _vmprof.enable(filename, period)
+
+def disable(space):
+    _vmprof.disable()
diff --git a/pypy/module/_vmprof/src/trampoline.h b/pypy/module/_vmprof/src/trampoline.h
new file mode 100644
--- /dev/null
+++ b/pypy/module/_vmprof/src/trampoline.h
@@ -0,0 +1,1 @@
+void* pypy_execute_frame_trampoline(void*, void*, void*);
diff --git a/pypy/module/_vmprof/src/trampoline.s b/pypy/module/_vmprof/src/trampoline.s
new file mode 100644
--- /dev/null
+++ b/pypy/module/_vmprof/src/trampoline.s
@@ -0,0 +1,15 @@
+	.text
+    .p2align 4,,-1
+	.globl	pypy_execute_frame_trampoline
+	.type	pypy_execute_frame_trampoline, @function
+pypy_execute_frame_trampoline:
+	.cfi_startproc
+    pushq   %rdi
+	.cfi_def_cfa_offset 16
+    movabs  pypy_pyframe_execute_frame, %rax
+	callq	*%rax
+    popq    %rdi
+	.cfi_def_cfa_offset 8
+    ret
+	.cfi_endproc
+	.size	pypy_execute_frame_trampoline, .-pypy_execute_frame_trampoline
diff --git a/pypy/module/_vmprof/test/__init__.py b/pypy/module/_vmprof/test/__init__.py
new file mode 100644
diff --git a/pypy/module/_vmprof/test/test__vmprof.py b/pypy/module/_vmprof/test/test__vmprof.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/_vmprof/test/test__vmprof.py
@@ -0,0 +1,53 @@
+from rpython.rtyper.lltypesystem import rffi, lltype
+from pypy.module._vmprof import interp_vmprof
+from pypy.module._vmprof.interp_vmprof import do_get_virtual_ip, _vmprof
+
+class FakePyFrame(object):
+
+    def __init__(self, pycode):
+        self.pycode = pycode
+
+class FakePyCode(object):
+
+    _vmprof_setup_maybe = interp_vmprof.PyCode._vmprof_setup_maybe.im_func
+
+    def __init__(self, co_name):
+        self.co_name = co_name
+        self._vmprof_setup_maybe()
+
+def test_get_virtual_ip(monkeypatch):
+    functions = []
+    def register_virtual_function(name, start, end):
+        name = rffi.charp2str(name)
+        start = rffi.cast(lltype.Signed, start)
+        end = rffi.cast(lltype.Signed, end)
+        functions.append((name, start, end))
+    monkeypatch.setattr(interp_vmprof, 'vmprof_register_virtual_function', register_virtual_function)
+    #
+    mycode = FakePyCode('foo')
+    assert mycode._vmprof_virtual_ip < 0
+    myframe = FakePyFrame(mycode)
+
+    _vmprof.counter = 42
+    ip = do_get_virtual_ip(myframe)
+    assert ip == mycode._vmprof_virtual_ip
+    assert functions == [('py:foo', ip, ip)]
+
+    # the second time, we don't register it again
+    functions = []
+    ip = do_get_virtual_ip(myframe)
+    assert ip == mycode._vmprof_virtual_ip
+    assert functions == []
+
+    # now, let's try with a long name
+    mycode = FakePyCode('abcde' * 200)
+    myframe = FakePyFrame(mycode)
+    functions = []
+    ip2 = do_get_virtual_ip(myframe)
+    assert ip2 == mycode._vmprof_virtual_ip
+    assert ip2 < ip # because it was generated later
+    assert len(functions) == 1
+    name, start, end = functions[0]
+    assert len(name) == 127
+    assert name == 'py:' + ('abcde'*200)[:124]
+    


More information about the pypy-commit mailing list