Author: Amaury Forgeot d'Arc <amaur...@gmail.com> Branch: more-rposix Changeset: r77014:8a0a9728af1b Date: 2015-05-03 18:37 +0200 http://bitbucket.org/pypy/pypy/changeset/8a0a9728af1b/
Log: hg merge default diff too long, truncating to 2000 out of 4936 lines diff --git a/LICENSE b/LICENSE --- a/LICENSE +++ b/LICENSE @@ -420,3 +420,10 @@ the terms of the GPL license version 2 or any later version. Thus the gdbm module, provided in the file lib_pypy/gdbm.py, is redistributed under the terms of the GPL license as well. + +License for 'pypy/module/_vmprof/src' +-------------------------------------- + +The code is based on gperftools. You may see a copy of the License for it at + + https://code.google.com/p/gperftools/source/browse/COPYING diff --git a/pypy/config/pypyoption.py b/pypy/config/pypyoption.py --- a/pypy/config/pypyoption.py +++ b/pypy/config/pypyoption.py @@ -38,6 +38,10 @@ "_csv", "cppyy", "_pypyjson" ]) +if sys.platform.startswith('linux') and sys.maxint > 2147483647: + if 0: # XXX disabled until we fix the absurd .so mess + working_modules.add('_vmprof') + translation_modules = default_modules.copy() translation_modules.update([ "fcntl", "time", "select", "signal", "_rawffi", "zlib", "struct", "_md5", @@ -99,6 +103,7 @@ "_hashlib" : ["pypy.module._ssl.interp_ssl"], "_minimal_curses": ["pypy.module._minimal_curses.fficurses"], "_continuation": ["rpython.rlib.rstacklet"], + "_vmprof" : ["pypy.module._vmprof.interp_vmprof"], } def get_module_validator(modname): diff --git a/pypy/doc/whatsnew-head.rst b/pypy/doc/whatsnew-head.rst --- a/pypy/doc/whatsnew-head.rst +++ b/pypy/doc/whatsnew-head.rst @@ -67,3 +67,10 @@ .. branch: object-dtype2 Extend numpy dtypes to allow using objects with associated garbage collection hook + +.. branch: vmprof2 +Add backend support for vmprof - a lightweight statistical profiler - +to linux64, see client at https://vmprof.readthedocs.org + +.. branch: jit_hint_docs +Add more detail to @jit.elidable and @jit.promote in rpython/rlib/jit.py diff --git a/pypy/interpreter/baseobjspace.py b/pypy/interpreter/baseobjspace.py --- a/pypy/interpreter/baseobjspace.py +++ b/pypy/interpreter/baseobjspace.py @@ -11,7 +11,7 @@ INT_MIN, INT_MAX, UINT_MAX, USHRT_MAX from pypy.interpreter.executioncontext import (ExecutionContext, ActionFlag, - UserDelAction) + UserDelAction, CodeUniqueIds) from pypy.interpreter.error import OperationError, new_exception_class, oefmt from pypy.interpreter.argument import Arguments from pypy.interpreter.miscutils import ThreadLocals, make_weak_value_dictionary @@ -388,6 +388,7 @@ self.actionflag = ActionFlag() # changed by the signal module self.check_signal_action = None # changed by the signal module self.user_del_action = UserDelAction(self) + self.code_unique_ids = CodeUniqueIds() self._code_of_sys_exc_info = None # can be overridden to a subclass @@ -666,6 +667,16 @@ assert ec is not None return ec + def register_code_callback(self, callback): + cui = self.code_unique_ids + cui.code_callback = callback + + def register_code_object(self, pycode): + cui = self.code_unique_ids + if cui.code_callback is None: + return + cui.code_callback(self, pycode) + def _freeze_(self): return True diff --git a/pypy/interpreter/executioncontext.py b/pypy/interpreter/executioncontext.py --- a/pypy/interpreter/executioncontext.py +++ b/pypy/interpreter/executioncontext.py @@ -579,3 +579,11 @@ # there is no list of length n: if n is large, then the GC # will run several times while walking the list, but it will # see lower and lower memory usage, with no lower bound of n. + +class CodeUniqueIds(object): + def __init__(self): + if sys.maxint == 2147483647: + self.code_unique_id = 0 # XXX this is wrong, it won't work on 32bit + else: + self.code_unique_id = 0x7000000000000000 + self.code_callback = None diff --git a/pypy/interpreter/pycode.py b/pypy/interpreter/pycode.py --- a/pypy/interpreter/pycode.py +++ b/pypy/interpreter/pycode.py @@ -14,9 +14,10 @@ CO_OPTIMIZED, CO_NEWLOCALS, CO_VARARGS, CO_VARKEYWORDS, CO_NESTED, CO_GENERATOR, CO_KILL_DOCSTRING, CO_YIELD_INSIDE_TRY) from pypy.tool.stdlib_opcode import opcodedesc, HAVE_ARGUMENT -from rpython.rlib.rarithmetic import intmask +from rpython.rlib.rarithmetic import intmask, r_longlong from rpython.rlib.objectmodel import compute_hash from rpython.rlib import jit +from rpython.rlib.debug import debug_start, debug_stop, debug_print class BytecodeCorruption(Exception): @@ -54,8 +55,9 @@ "CPython-style code objects." _immutable_ = True _immutable_fields_ = ["co_consts_w[*]", "co_names_w[*]", "co_varnames[*]", - "co_freevars[*]", "co_cellvars[*]", "_args_as_cellvars[*]"] - + "co_freevars[*]", "co_cellvars[*]", + "_args_as_cellvars[*]"] + def __init__(self, space, argcount, nlocals, stacksize, flags, code, consts, names, varnames, filename, name, firstlineno, lnotab, freevars, cellvars, @@ -83,6 +85,7 @@ self.magic = magic self._signature = cpython_code_signature(self) self._initialize() + space.register_code_object(self) def _initialize(self): if self.co_cellvars: @@ -124,6 +127,15 @@ from pypy.objspace.std.mapdict import init_mapdict_cache init_mapdict_cache(self) + cui = self.space.code_unique_ids + self._unique_id = cui.code_unique_id + cui.code_unique_id += 4 # so we have two bits that we can mark stuff + # with + + def _get_full_name(self): + return "py:%s:%d:%s" % (self.co_name, self.co_firstlineno, + self.co_filename) + def _cleanup_(self): if (self.magic == cpython_magic and '__pypy__' not in sys.builtin_module_names): diff --git a/pypy/interpreter/pyframe.py b/pypy/interpreter/pyframe.py --- a/pypy/interpreter/pyframe.py +++ b/pypy/interpreter/pyframe.py @@ -49,14 +49,35 @@ last_instr = -1 last_exception = None f_backref = jit.vref_None + # For tracing w_f_trace = None - # For tracing instr_lb = 0 instr_ub = 0 instr_prev_plus_one = 0 + # end of tracing + is_being_profiled = False escaped = False # see mark_as_escaped() + w_globals = None + w_locals = None # dict containing locals, if forced or necessary + pycode = None # code object executed by that frame + locals_stack_w = None # the list of all locals and valuestack + valuestackdepth = 0 # number of items on valuestack + lastblock = None + # default to False + f_lineno = 0 # current lineno + cells = None # cells + + # other fields: + + # builtin - builtin cache, only if honor__builtins__ is True, + + # there is also self.space which is removed by the annotator + + # additionally JIT uses vable_token field that is representing + # frame current virtualizable state as seen by the JIT + def __init__(self, space, code, w_globals, outer_func): if not we_are_translated(): assert type(self) == space.FrameClass, ( @@ -65,11 +86,9 @@ assert isinstance(code, pycode.PyCode) self.space = space self.w_globals = w_globals - self.w_locals = None self.pycode = code self.locals_stack_w = [None] * (code.co_nlocals + code.co_stacksize) self.valuestackdepth = code.co_nlocals - self.lastblock = None make_sure_not_resized(self.locals_stack_w) check_nonneg(self.valuestackdepth) # 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,18 @@ +from pypy.interpreter.mixedmodule import MixedModule + +class Module(MixedModule): + """ + Write me :) + """ + appleveldefs = { + } + + interpleveldefs = { + 'enable': 'interp_vmprof.enable', + 'disable': 'interp_vmprof.disable', + } + + def setup_after_space_initialization(self): + # force the __extend__ hacks to occur early + from pypy.module._vmprof.interp_vmprof import VMProf + self.vmprof = VMProf() 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,240 @@ +import py, os, sys +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 +from rpython.rlib import jit, rposix, rgc +from rpython.rlib.rarithmetic import ovfcheck_float_to_int +from rpython.rtyper.tool import rffi_platform as platform +from rpython.rlib.rstring import StringBuilder +from pypy.interpreter.baseobjspace import W_Root +from pypy.interpreter.error import oefmt, wrap_oserror, OperationError +from pypy.interpreter.gateway import unwrap_spec +from pypy.interpreter.pyframe import PyFrame +from pypy.interpreter.pycode import PyCode + +ROOT = py.path.local(__file__).join('..') +SRC = ROOT.join('src') + +# by default, we statically link vmprof.c into pypy; however, if you set +# DYNAMIC_VMPROF to True, it will be dynamically linked to the libvmprof.so +# which is expected to be inside pypy/module/_vmprof/src: this is very useful +# during development. Note that you have to manually build libvmprof by +# running make inside the src dir +DYNAMIC_VMPROF = False + +eci_kwds = dict( + include_dirs = [SRC], + includes = ['vmprof.h', 'trampoline.h'], + separate_module_files = [SRC.join('trampoline.asmgcc.s')], + link_files = ['-Wl,-Bstatic', '-lunwind', '-llzma','-Wl,-Bdynamic'], + + post_include_bits=[""" + void pypy_vmprof_init(void); + """], + + separate_module_sources=[""" + void pypy_vmprof_init(void) { + vmprof_set_mainloop(pypy_execute_frame_trampoline, 0, + NULL); + } + """], + ) + + +if DYNAMIC_VMPROF: + eci_kwds['libraries'] += ['vmprof'] + eci_kwds['link_extra'] = ['-Wl,-rpath,%s' % SRC, '-L%s' % SRC] +else: + eci_kwds['separate_module_files'] += [SRC.join('vmprof.c')] + +eci = ExternalCompilationInfo(**eci_kwds) + +check_eci = eci.merge(ExternalCompilationInfo(separate_module_files=[ + SRC.join('fake_pypy_api.c')])) + +platform.verify_eci(check_eci) + +pypy_execute_frame_trampoline = rffi.llexternal( + "pypy_execute_frame_trampoline", + [llmemory.GCREF, llmemory.GCREF, llmemory.GCREF, lltype.Signed], + llmemory.GCREF, + compilation_info=eci, + _nowrapper=True, sandboxsafe=True, + random_effects_on_gcobjs=True) + +pypy_vmprof_init = rffi.llexternal("pypy_vmprof_init", [], lltype.Void, + compilation_info=eci) +vmprof_enable = rffi.llexternal("vmprof_enable", + [rffi.INT, rffi.LONG, rffi.INT, + rffi.CCHARP, rffi.INT], + rffi.INT, compilation_info=eci, + save_err=rffi.RFFI_SAVE_ERRNO) +vmprof_disable = rffi.llexternal("vmprof_disable", [], rffi.INT, + compilation_info=eci, + save_err=rffi.RFFI_SAVE_ERRNO) + +vmprof_register_virtual_function = rffi.llexternal( + "vmprof_register_virtual_function", + [rffi.CCHARP, rffi.VOIDP, rffi.VOIDP], lltype.Void, + compilation_info=eci, _nowrapper=True) + +original_execute_frame = PyFrame.execute_frame.im_func +original_execute_frame.c_name = 'pypy_pyframe_execute_frame' +original_execute_frame._dont_inline_ = True + +class __extend__(PyFrame): + def execute_frame(frame, w_inputvalue=None, operr=None): + # go through the asm trampoline ONLY if we are translated but not being JITted. + # + # If we are not translated, we obviously don't want to go through the + # trampoline because there is no C function it can call. + # + # If we are being JITted, we want to skip the trampoline, else the JIT + # cannot see throug it + if we_are_translated() and not jit.we_are_jitted(): + # 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) + unique_id = frame.pycode._unique_id + gc_result = pypy_execute_frame_trampoline(gc_frame, gc_inputvalue, + gc_operr, unique_id) + return cast_base_ptr_to_instance(W_Root, gc_result) + else: + return original_execute_frame(frame, w_inputvalue, operr) + + + +def write_long_to_string_builder(l, b): + if sys.maxint == 2147483647: + b.append(chr(l & 0xff)) + b.append(chr((l >> 8) & 0xff)) + b.append(chr((l >> 16) & 0xff)) + b.append(chr((l >> 24) & 0xff)) + else: + b.append(chr(l & 0xff)) + b.append(chr((l >> 8) & 0xff)) + b.append(chr((l >> 16) & 0xff)) + b.append(chr((l >> 24) & 0xff)) + b.append(chr((l >> 32) & 0xff)) + b.append(chr((l >> 40) & 0xff)) + b.append(chr((l >> 48) & 0xff)) + b.append(chr((l >> 56) & 0xff)) + +def try_cast_to_pycode(gcref): + return rgc.try_cast_gcref_to_instance(PyCode, gcref) + +MAX_CODES = 1000 + +class VMProf(object): + def __init__(self): + self.is_enabled = False + self.ever_enabled = False + self.fileno = -1 + self.current_codes = [] + + def enable(self, space, fileno, period_usec): + if self.is_enabled: + raise oefmt(space.w_ValueError, "_vmprof already enabled") + self.fileno = fileno + self.is_enabled = True + self.write_header(fileno, period_usec) + if not self.ever_enabled: + if we_are_translated(): + pypy_vmprof_init() + self.ever_enabled = True + self.gather_all_code_objs(space) + space.register_code_callback(vmprof_register_code) + if we_are_translated(): + # does not work untranslated + res = vmprof_enable(fileno, period_usec, 0, + lltype.nullptr(rffi.CCHARP.TO), 0) + else: + res = 0 + if res == -1: + raise wrap_oserror(space, OSError(rposix.get_saved_errno(), + "_vmprof.enable")) + + def gather_all_code_objs(self, space): + all_code_objs = rgc.do_get_objects(try_cast_to_pycode) + for code in all_code_objs: + self.register_code(space, code) + + def write_header(self, fileno, period_usec): + assert period_usec > 0 + b = StringBuilder() + write_long_to_string_builder(0, b) + write_long_to_string_builder(3, b) + write_long_to_string_builder(0, b) + write_long_to_string_builder(period_usec, b) + write_long_to_string_builder(0, b) + b.append('\x04') # interp name + b.append(chr(len('pypy'))) + b.append('pypy') + os.write(fileno, b.build()) + + def register_code(self, space, code): + if self.fileno == -1: + raise OperationError(space.w_RuntimeError, + space.wrap("vmprof not running")) + self.current_codes.append(code) + if len(self.current_codes) >= MAX_CODES: + self._flush_codes(space) + + def _flush_codes(self, space): + b = StringBuilder() + for code in self.current_codes: + name = code._get_full_name() + b.append('\x02') + write_long_to_string_builder(code._unique_id, b) + write_long_to_string_builder(len(name), b) + b.append(name) + os.write(self.fileno, b.build()) + self.current_codes = [] + + def disable(self, space): + if not self.is_enabled: + raise oefmt(space.w_ValueError, "_vmprof not enabled") + self.is_enabled = False + space.register_code_callback(None) + self._flush_codes(space) + self.fileno = -1 + if we_are_translated(): + # does not work untranslated + res = vmprof_disable() + else: + res = 0 + if res == -1: + raise wrap_oserror(space, OSError(rposix.get_saved_errno(), + "_vmprof.disable")) + +def vmprof_register_code(space, code): + from pypy.module._vmprof import Module + mod_vmprof = space.getbuiltinmodule('_vmprof') + assert isinstance(mod_vmprof, Module) + mod_vmprof.vmprof.register_code(space, code) + +@unwrap_spec(fileno=int, period=float) +def enable(space, fileno, period=0.01): # default 100 Hz + from pypy.module._vmprof import Module + mod_vmprof = space.getbuiltinmodule('_vmprof') + assert isinstance(mod_vmprof, Module) + # + try: + period_usec = ovfcheck_float_to_int(period * 1000000.0 + 0.5) + if period_usec <= 0 or period_usec >= 1e6: + # we don't want seconds here at all + raise ValueError + except (ValueError, OverflowError): + raise OperationError(space.w_ValueError, + space.wrap("'period' too large or non positive")) + # + mod_vmprof.vmprof.enable(space, fileno, period_usec) + +def disable(space): + from pypy.module._vmprof import Module + mod_vmprof = space.getbuiltinmodule('_vmprof') + assert isinstance(mod_vmprof, Module) + mod_vmprof.vmprof.disable(space) + diff --git a/pypy/module/_vmprof/src/config.h b/pypy/module/_vmprof/src/config.h new file mode 100644 --- /dev/null +++ b/pypy/module/_vmprof/src/config.h @@ -0,0 +1,2 @@ +#define HAVE_SYS_UCONTEXT_H +#define PC_FROM_UCONTEXT uc_mcontext.gregs[REG_RIP] diff --git a/pypy/module/_vmprof/src/fake_pypy_api.c b/pypy/module/_vmprof/src/fake_pypy_api.c new file mode 100644 --- /dev/null +++ b/pypy/module/_vmprof/src/fake_pypy_api.c @@ -0,0 +1,21 @@ + +long pypy_jit_stack_depth_at_loc(long x) +{ + return 0; +} + +void *pypy_find_codemap_at_addr(long x) +{ + return (void *)0; +} + +long pypy_yield_codemap_at_addr(void *x, long y, long *a) +{ + return 0; +} + +void pypy_pyframe_execute_frame(void) +{ +} + +volatile int pypy_codemap_currently_invalid = 0; diff --git a/pypy/module/_vmprof/src/get_custom_offset.c b/pypy/module/_vmprof/src/get_custom_offset.c new file mode 100644 --- /dev/null +++ b/pypy/module/_vmprof/src/get_custom_offset.c @@ -0,0 +1,66 @@ + +extern volatile int pypy_codemap_currently_invalid; + +void *pypy_find_codemap_at_addr(long addr, long *start_addr); +long pypy_yield_codemap_at_addr(void *codemap_raw, long addr, + long *current_pos_addr); +long pypy_jit_stack_depth_at_loc(long loc); + + +void vmprof_set_tramp_range(void* start, void* end) +{ +} + +int custom_sanity_check() +{ + return !pypy_codemap_currently_invalid; +} + +static ptrdiff_t vmprof_unw_get_custom_offset(void* ip, void *cp) { + intptr_t ip_l = (intptr_t)ip; + return pypy_jit_stack_depth_at_loc(ip_l); +} + +static long vmprof_write_header_for_jit_addr(void **result, long n, + void *ip, int max_depth) +{ + void *codemap; + long current_pos = 0; + intptr_t id; + long start_addr = 0; + intptr_t addr = (intptr_t)ip; + int start, k; + void *tmp; + + codemap = pypy_find_codemap_at_addr(addr, &start_addr); + if (codemap == NULL) + // not a jit code at all + return n; + + // modify the last entry to point to start address and not the random one + // in the middle + result[n - 1] = (void*)start_addr; + result[n] = (void*)2; + n++; + start = n; + while (n < max_depth) { + id = pypy_yield_codemap_at_addr(codemap, addr, ¤t_pos); + if (id == -1) + // finish + break; + if (id == 0) + continue; // not main codemap + result[n++] = (void *)id; + } + k = 0; + while (k < (n - start) / 2) { + tmp = result[start + k]; + result[start + k] = result[n - k - 1]; + result[n - k - 1] = tmp; + k++; + } + if (n < max_depth) { + result[n++] = (void*)3; + } + return n; +} diff --git a/pypy/module/_vmprof/src/getpc.h b/pypy/module/_vmprof/src/getpc.h new file mode 100644 --- /dev/null +++ b/pypy/module/_vmprof/src/getpc.h @@ -0,0 +1,187 @@ +// -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- +// Copyright (c) 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// --- +// Author: Craig Silverstein +// +// This is an internal header file used by profiler.cc. It defines +// the single (inline) function GetPC. GetPC is used in a signal +// handler to figure out the instruction that was being executed when +// the signal-handler was triggered. +// +// To get this, we use the ucontext_t argument to the signal-handler +// callback, which holds the full context of what was going on when +// the signal triggered. How to get from a ucontext_t to a Program +// Counter is OS-dependent. + +#ifndef BASE_GETPC_H_ +#define BASE_GETPC_H_ + +#include "config.h" + +// On many linux systems, we may need _GNU_SOURCE to get access to +// the defined constants that define the register we want to see (eg +// REG_EIP). Note this #define must come first! +#define _GNU_SOURCE 1 +// If #define _GNU_SOURCE causes problems, this might work instead. +// It will cause problems for FreeBSD though!, because it turns off +// the needed __BSD_VISIBLE. +//#define _XOPEN_SOURCE 500 + +#include <string.h> // for memcmp +#if defined(HAVE_SYS_UCONTEXT_H) +#include <sys/ucontext.h> +#elif defined(HAVE_UCONTEXT_H) +#include <ucontext.h> // for ucontext_t (and also mcontext_t) +#elif defined(HAVE_CYGWIN_SIGNAL_H) +#include <cygwin/signal.h> +typedef ucontext ucontext_t; +#endif + + +// Take the example where function Foo() calls function Bar(). For +// many architectures, Bar() is responsible for setting up and tearing +// down its own stack frame. In that case, it's possible for the +// interrupt to happen when execution is in Bar(), but the stack frame +// is not properly set up (either before it's done being set up, or +// after it's been torn down but before Bar() returns). In those +// cases, the stack trace cannot see the caller function anymore. +// +// GetPC can try to identify this situation, on architectures where it +// might occur, and unwind the current function call in that case to +// avoid false edges in the profile graph (that is, edges that appear +// to show a call skipping over a function). To do this, we hard-code +// in the asm instructions we might see when setting up or tearing +// down a stack frame. +// +// This is difficult to get right: the instructions depend on the +// processor, the compiler ABI, and even the optimization level. This +// is a best effort patch -- if we fail to detect such a situation, or +// mess up the PC, nothing happens; the returned PC is not used for +// any further processing. +struct CallUnrollInfo { + // Offset from (e)ip register where this instruction sequence + // should be matched. Interpreted as bytes. Offset 0 is the next + // instruction to execute. Be extra careful with negative offsets in + // architectures of variable instruction length (like x86) - it is + // not that easy as taking an offset to step one instruction back! + int pc_offset; + // The actual instruction bytes. Feel free to make it larger if you + // need a longer sequence. + unsigned char ins[16]; + // How many bytes to match from ins array? + int ins_size; + // The offset from the stack pointer (e)sp where to look for the + // call return address. Interpreted as bytes. + int return_sp_offset; +}; + + +// The dereferences needed to get the PC from a struct ucontext were +// determined at configure time, and stored in the macro +// PC_FROM_UCONTEXT in config.h. The only thing we need to do here, +// then, is to do the magic call-unrolling for systems that support it. + +// -- Special case 1: linux x86, for which we have CallUnrollInfo +#if defined(__linux) && defined(__i386) && defined(__GNUC__) +static const CallUnrollInfo callunrollinfo[] = { + // Entry to a function: push %ebp; mov %esp,%ebp + // Top-of-stack contains the caller IP. + { 0, + {0x55, 0x89, 0xe5}, 3, + 0 + }, + // Entry to a function, second instruction: push %ebp; mov %esp,%ebp + // Top-of-stack contains the old frame, caller IP is +4. + { -1, + {0x55, 0x89, 0xe5}, 3, + 4 + }, + // Return from a function: RET. + // Top-of-stack contains the caller IP. + { 0, + {0xc3}, 1, + 0 + } +}; + +inline void* GetPC(ucontext_t *signal_ucontext) { + // See comment above struct CallUnrollInfo. Only try instruction + // flow matching if both eip and esp looks reasonable. + const int eip = signal_ucontext->uc_mcontext.gregs[REG_EIP]; + const int esp = signal_ucontext->uc_mcontext.gregs[REG_ESP]; + if ((eip & 0xffff0000) != 0 && (~eip & 0xffff0000) != 0 && + (esp & 0xffff0000) != 0) { + char* eip_char = reinterpret_cast<char*>(eip); + for (int i = 0; i < sizeof(callunrollinfo)/sizeof(*callunrollinfo); ++i) { + if (!memcmp(eip_char + callunrollinfo[i].pc_offset, + callunrollinfo[i].ins, callunrollinfo[i].ins_size)) { + // We have a match. + void **retaddr = (void**)(esp + callunrollinfo[i].return_sp_offset); + return *retaddr; + } + } + } + return (void*)eip; +} + +// Special case #2: Windows, which has to do something totally different. +#elif defined(_WIN32) || defined(__CYGWIN__) || defined(__CYGWIN32__) || defined(__MINGW32__) +// If this is ever implemented, probably the way to do it is to have +// profiler.cc use a high-precision timer via timeSetEvent: +// http://msdn2.microsoft.com/en-us/library/ms712713.aspx +// We'd use it in mode TIME_CALLBACK_FUNCTION/TIME_PERIODIC. +// The callback function would be something like prof_handler, but +// alas the arguments are different: no ucontext_t! I don't know +// how we'd get the PC (using StackWalk64?) +// http://msdn2.microsoft.com/en-us/library/ms680650.aspx + +#include "base/logging.h" // for RAW_LOG +#ifndef HAVE_CYGWIN_SIGNAL_H +typedef int ucontext_t; +#endif + +inline void* GetPC(ucontext_t *signal_ucontext) { + RAW_LOG(ERROR, "GetPC is not yet implemented on Windows\n"); + return NULL; +} + +// Normal cases. If this doesn't compile, it's probably because +// PC_FROM_UCONTEXT is the empty string. You need to figure out +// the right value for your system, and add it to the list in +// configure.ac (or set it manually in your config.h). +#else +inline void* GetPC(ucontext_t *signal_ucontext) { + return (void*)signal_ucontext->PC_FROM_UCONTEXT; // defined in config.h +} + +#endif + +#endif // BASE_GETPC_H_ diff --git a/pypy/module/_vmprof/src/trampoline.asmgcc.s b/pypy/module/_vmprof/src/trampoline.asmgcc.s new file mode 100644 --- /dev/null +++ b/pypy/module/_vmprof/src/trampoline.asmgcc.s @@ -0,0 +1,16 @@ +// NOTE: you need to use TABs, not spaces! + + .text + .p2align 4,,-1 + .globl pypy_execute_frame_trampoline + .type pypy_execute_frame_trampoline, @function +pypy_execute_frame_trampoline: + .cfi_startproc + pushq %rcx + .cfi_def_cfa_offset 16 + call pypy_pyframe_execute_frame@PLT + popq %rcx + .cfi_def_cfa_offset 8 + ret + .cfi_endproc + .size pypy_execute_frame_trampoline, .-pypy_execute_frame_trampoline 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*, long); diff --git a/pypy/module/_vmprof/src/vmprof.c b/pypy/module/_vmprof/src/vmprof.c new file mode 100644 --- /dev/null +++ b/pypy/module/_vmprof/src/vmprof.c @@ -0,0 +1,398 @@ +/* VMPROF + * + * statistical sampling profiler specifically designed to profile programs + * which run on a Virtual Machine and/or bytecode interpreter, such as Python, + * etc. + * + * The logic to dump the C stack traces is partly stolen from the code in gperftools. + * The file "getpc.h" has been entirely copied from gperftools. + * + * Tested only on gcc, linux, x86_64. + * + * Copyright (C) 2014-2015 + * Antonio Cuni - anto.c...@gmail.com + * Maciej Fijalkowski - fij...@gmail.com + * + */ + + +#include "getpc.h" // should be first to get the _GNU_SOURCE dfn +#include <signal.h> +#include <stdio.h> +#include <string.h> +#include <stddef.h> +#include <assert.h> +#include <unistd.h> +#include <sys/time.h> +#include <sys/types.h> +#include <errno.h> +#include <pthread.h> + +#define UNW_LOCAL_ONLY +#include <libunwind.h> + +#include "vmprof.h" + +#define _unused(x) ((void)x) + +#define MAX_FUNC_NAME 128 +#define MAX_STACK_DEPTH 1024 +#define BUFFER_SIZE 8192 + + +static int profile_file = 0; +static char profile_write_buffer[BUFFER_SIZE]; +static int profile_buffer_position = 0; +void* vmprof_mainloop_func; +static ptrdiff_t mainloop_sp_offset; +static vmprof_get_virtual_ip_t mainloop_get_virtual_ip; +static long last_period_usec = 0; +static int atfork_hook_installed = 0; + + +/* ************************************************************* + * functions to write a profile file compatible with gperftools + * ************************************************************* + */ + +#define MARKER_STACKTRACE '\x01' +#define MARKER_VIRTUAL_IP '\x02' +#define MARKER_TRAILER '\x03' + +static void prof_word(long x) { + ((long*)(profile_write_buffer + profile_buffer_position))[0] = x; + profile_buffer_position += sizeof(long); +} + +static void prof_header(long period_usec) { + // XXX never used here? + prof_word(0); + prof_word(3); + prof_word(0); + prof_word(period_usec); + prof_word(0); + write(profile_file, profile_write_buffer, profile_buffer_position); + profile_buffer_position = 0; +} + +static void prof_write_stacktrace(void** stack, int depth, int count) { + int i; + char marker = MARKER_STACKTRACE; + + profile_write_buffer[profile_buffer_position++] = MARKER_STACKTRACE; + prof_word(count); + prof_word(depth); + for(i=0; i<depth; i++) + prof_word((long)stack[i]); + write(profile_file, profile_write_buffer, profile_buffer_position); + profile_buffer_position = 0; +} + + +/* ****************************************************** + * libunwind workaround for process JIT frames correctly + * ****************************************************** + */ + +#include "get_custom_offset.c" + +typedef struct { + void* _unused1; + void* _unused2; + void* sp; + void* ip; + void* _unused3[sizeof(unw_cursor_t)/sizeof(void*) - 4]; +} vmprof_hacked_unw_cursor_t; + +static int vmprof_unw_step(unw_cursor_t *cp, int first_run) { + void* ip; + void* sp; + ptrdiff_t sp_offset; + unw_get_reg (cp, UNW_REG_IP, (unw_word_t*)&ip); + unw_get_reg (cp, UNW_REG_SP, (unw_word_t*)&sp); + if (!first_run) + // make sure we're pointing to the CALL and not to the first + // instruction after. If the callee adjusts the stack for us + // it's not safe to be at the instruction after + ip -= 1; + sp_offset = vmprof_unw_get_custom_offset(ip, cp); + + if (sp_offset == -1) { + // it means that the ip is NOT in JITted code, so we can use the + // stardard unw_step + return unw_step(cp); + } + else { + // this is a horrible hack to manually walk the stack frame, by + // setting the IP and SP in the cursor + vmprof_hacked_unw_cursor_t *cp2 = (vmprof_hacked_unw_cursor_t*)cp; + void* bp = (void*)sp + sp_offset; + cp2->sp = bp; + bp -= sizeof(void*); + cp2->ip = ((void**)bp)[0]; + // the ret is on the top of the stack minus WORD + return 1; + } +} + + +/* ************************************************************* + * functions to dump the stack trace + * ************************************************************* + */ + +// The original code here has a comment, "stolen from pprof", +// about a "__thread int recursive". But general __thread +// variables are not really supposed to be accessed from a +// signal handler. Moreover, we are using SIGPROF, which +// should not be recursively called on the same thread. +//static __thread int recursive; + +int get_stack_trace(void** result, int max_depth, ucontext_t *ucontext) { + void *ip; + int n = 0; + unw_cursor_t cursor; + unw_context_t uc = *ucontext; + //if (recursive) { + // return 0; + //} + if (!custom_sanity_check()) { + return 0; + } + //++recursive; + + int ret = unw_init_local(&cursor, &uc); + assert(ret >= 0); + _unused(ret); + int first_run = 1; + + while (n < max_depth) { + if (unw_get_reg(&cursor, UNW_REG_IP, (unw_word_t *) &ip) < 0) { + break; + } + + unw_proc_info_t pip; + unw_get_proc_info(&cursor, &pip); + + /* char funcname[4096]; */ + /* unw_word_t offset; */ + /* unw_get_proc_name(&cursor, funcname, 4096, &offset); */ + /* printf("%s+%#lx <%p>\n", funcname, offset, ip); */ + + /* if n==0, it means that the signal handler interrupted us while we + were in the trampoline, so we are not executing (yet) the real main + loop function; just skip it */ + if (vmprof_mainloop_func && + (void*)pip.start_ip == (void*)vmprof_mainloop_func && + n > 0) { + // found main loop stack frame + void* sp; + unw_get_reg(&cursor, UNW_REG_SP, (unw_word_t *) &sp); + void *arg_addr = (char*)sp + mainloop_sp_offset; + void **arg_ptr = (void**)arg_addr; + // fprintf(stderr, "stacktrace mainloop: rsp %p &f2 %p offset %ld\n", + // sp, arg_addr, mainloop_sp_offset); + if (mainloop_get_virtual_ip) { + ip = mainloop_get_virtual_ip(*arg_ptr); + } else { + ip = *arg_ptr; + } + } + + result[n++] = ip; + n = vmprof_write_header_for_jit_addr(result, n, ip, max_depth); + if (vmprof_unw_step(&cursor, first_run) <= 0) { + break; + } + first_run = 0; + } + //--recursive; + return n; +} + + +static int __attribute__((noinline)) frame_forcer(int rv) { + return rv; +} + +static void sigprof_handler(int sig_nr, siginfo_t* info, void *ucontext) { + void* stack[MAX_STACK_DEPTH]; + int saved_errno = errno; + stack[0] = GetPC((ucontext_t*)ucontext); + int depth = frame_forcer(get_stack_trace(stack+1, MAX_STACK_DEPTH-1, ucontext)); + depth++; // To account for pc value in stack[0]; + prof_write_stacktrace(stack, depth, 1); + errno = saved_errno; +} + +/* ************************************************************* + * functions to enable/disable the profiler + * ************************************************************* + */ + +static int open_profile(int fd, long period_usec, int write_header, char *s, + int slen) { + if ((fd = dup(fd)) == -1) { + return -1; + } + profile_buffer_position = 0; + profile_file = fd; + if (write_header) + prof_header(period_usec); + if (s) + write(profile_file, s, slen); + return 0; +} + +static int close_profile(void) { + // XXX all of this can happily fail + FILE* src; + char buf[BUFSIZ]; + size_t size; + int marker = MARKER_TRAILER; + write(profile_file, &marker, 1); + + // copy /proc/PID/maps to the end of the profile file + sprintf(buf, "/proc/%d/maps", getpid()); + src = fopen(buf, "r"); + while ((size = fread(buf, 1, BUFSIZ, src))) { + write(profile_file, buf, size); + } + fclose(src); + close(profile_file); + return 0; +} + + +static int install_sigprof_handler(void) { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = sigprof_handler; + sa.sa_flags = SA_RESTART | SA_SIGINFO; + if (sigemptyset(&sa.sa_mask) == -1 || + sigaction(SIGPROF, &sa, NULL) == -1) { + return -1; + } + return 0; +} + +static int remove_sigprof_handler(void) { + sighandler_t res = signal(SIGPROF, SIG_DFL); + if (res == SIG_ERR) { + return -1; + } + return 0; +}; + +static int install_sigprof_timer(long period_usec) { + static struct itimerval timer; + last_period_usec = period_usec; + timer.it_interval.tv_sec = 0; + timer.it_interval.tv_usec = period_usec; + timer.it_value = timer.it_interval; + if (setitimer(ITIMER_PROF, &timer, NULL) != 0) { + return -1; + } + return 0; +} + +static int remove_sigprof_timer(void) { + static struct itimerval timer; + last_period_usec = 0; + timer.it_interval.tv_sec = 0; + timer.it_interval.tv_usec = 0; + timer.it_value.tv_sec = 0; + timer.it_value.tv_usec = 0; + if (setitimer(ITIMER_PROF, &timer, NULL) != 0) { + return -1; + } + return 0; +} + +static void atfork_disable_timer(void) { + remove_sigprof_timer(); +} + +static void atfork_enable_timer(void) { + install_sigprof_timer(last_period_usec); +} + +static int install_pthread_atfork_hooks(void) { + /* this is needed to prevent the problems described there: + - http://code.google.com/p/gperftools/issues/detail?id=278 + - http://lists.debian.org/debian-glibc/2010/03/msg00161.html + + TL;DR: if the RSS of the process is large enough, the clone() syscall + will be interrupted by the SIGPROF before it can complete, then + retried, interrupted again and so on, in an endless loop. The + solution is to disable the timer around the fork, and re-enable it + only inside the parent. + */ + if (atfork_hook_installed) + return 0; + int ret = pthread_atfork(atfork_disable_timer, atfork_enable_timer, NULL); + if (ret != 0) + return -1; + atfork_hook_installed = 1; + return 0; +} + +/* ************************************************************* + * public API + * ************************************************************* + */ + +void vmprof_set_mainloop(void* func, ptrdiff_t sp_offset, + vmprof_get_virtual_ip_t get_virtual_ip) { + mainloop_sp_offset = sp_offset; + mainloop_get_virtual_ip = get_virtual_ip; + vmprof_mainloop_func = func; +} + +int vmprof_enable(int fd, long period_usec, int write_header, char *s, + int slen) +{ + assert(period_usec > 0); + if (open_profile(fd, period_usec, write_header, s, slen) == -1) { + return -1; + } + if (install_sigprof_handler() == -1) { + return -1; + } + if (install_sigprof_timer(period_usec) == -1) { + return -1; + } + if (install_pthread_atfork_hooks() == -1) { + return -1; + } + return 0; +} + +int vmprof_disable(void) { + if (remove_sigprof_timer() == -1) { + return -1; + } + if (remove_sigprof_handler() == -1) { + return -1; + } + if (close_profile() == -1) { + return -1; + } + return 0; +} + +void vmprof_register_virtual_function(const char* name, void* start, void* end) { + // XXX unused by pypy + // for now *end is simply ignored + char buf[1024]; + int lgt = strlen(name) + 2 * sizeof(long) + 1; + + if (lgt > 1024) { + lgt = 1024; + } + buf[0] = MARKER_VIRTUAL_IP; + ((void **)(((void*)buf) + 1))[0] = start; + ((long *)(((void*)buf) + 1 + sizeof(long)))[0] = lgt - 2 * sizeof(long) - 1; + strncpy(buf + 2 * sizeof(long) + 1, name, 1024 - 2 * sizeof(long) - 1); + write(profile_file, buf, lgt); +} diff --git a/pypy/module/_vmprof/src/vmprof.h b/pypy/module/_vmprof/src/vmprof.h new file mode 100644 --- /dev/null +++ b/pypy/module/_vmprof/src/vmprof.h @@ -0,0 +1,22 @@ +#ifndef VMPROF_VMPROF_H_ +#define VMPROF_VMPROF_H_ + +#include <stddef.h> + +typedef void* (*vmprof_get_virtual_ip_t)(void*); + +extern void* vmprof_mainloop_func; +void vmprof_set_mainloop(void* func, ptrdiff_t sp_offset, + vmprof_get_virtual_ip_t get_virtual_ip); + +void vmprof_register_virtual_function(const char* name, void* start, void* end); + + +int vmprof_enable(int fd, long period_usec, int write_header, char* vips, + int vips_len); +int vmprof_disable(void); + +// XXX: this should be part of _vmprof (the CPython extension), not vmprof (the library) +void vmprof_set_tramp_range(void* start, void* end); + +#endif 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,72 @@ + +import tempfile +from pypy.tool.pytest.objspace import gettestobjspace + +class AppTestVMProf(object): + def setup_class(cls): + cls.space = gettestobjspace(usemodules=['_vmprof', 'struct']) + cls.tmpfile = tempfile.NamedTemporaryFile() + cls.w_tmpfileno = cls.space.wrap(cls.tmpfile.fileno()) + cls.w_tmpfilename = cls.space.wrap(cls.tmpfile.name) + cls.tmpfile2 = tempfile.NamedTemporaryFile() + cls.w_tmpfileno2 = cls.space.wrap(cls.tmpfile2.fileno()) + cls.w_tmpfilename2 = cls.space.wrap(cls.tmpfile2.name) + + def test_import_vmprof(self): + import struct, sys + + WORD = struct.calcsize('l') + + def count(s): + i = 0 + count = 0 + i += 5 * WORD # header + assert s[i] == '\x04' + i += 1 # marker + assert s[i] == '\x04' + i += 1 # length + i += len('pypy') + while i < len(s): + if s[i] == '\x03': + break + if s[i] == '\x01': + xxx + assert s[i] == '\x02' + i += 1 + _, size = struct.unpack("ll", s[i:i + 2 * WORD]) + count += 1 + i += 2 * WORD + size + return count + + import _vmprof + _vmprof.enable(self.tmpfileno) + _vmprof.disable() + s = open(self.tmpfilename).read() + no_of_codes = count(s) + assert no_of_codes > 10 + d = {} + + exec """def foo(): + pass + """ in d + + _vmprof.enable(self.tmpfileno2) + + exec """def foo2(): + pass + """ in d + + _vmprof.disable() + s = open(self.tmpfilename2).read() + no_of_codes2 = count(s) + assert "py:foo:" in s + assert "py:foo2:" in s + assert no_of_codes2 >= no_of_codes + 2 # some extra codes from tests + + def test_enable_ovf(self): + import _vmprof + raises(ValueError, _vmprof.enable, 999, 0) + raises(ValueError, _vmprof.enable, 999, -2.5) + raises(ValueError, _vmprof.enable, 999, 1e300) + raises(ValueError, _vmprof.enable, 999, 1e300 * 1e300) + raises(ValueError, _vmprof.enable, 999, (1e300*1e300) / (1e300*1e300)) diff --git a/pypy/module/_vmprof/test/test_direct.py b/pypy/module/_vmprof/test/test_direct.py new file mode 100644 --- /dev/null +++ b/pypy/module/_vmprof/test/test_direct.py @@ -0,0 +1,71 @@ + +import py +try: + import cffi +except ImportError: + py.test.skip('cffi required') + +srcdir = py.path.local(__file__).join("..", "..", "src") + +ffi = cffi.FFI() +ffi.cdef(""" +long vmprof_write_header_for_jit_addr(void **, long, void*, int); +void *pypy_find_codemap_at_addr(long addr, long *start_addr); +long pypy_yield_codemap_at_addr(void *codemap_raw, long addr, + long *current_pos_addr); +long buffer[]; +""") + +lib = ffi.verify(""" +volatile int pypy_codemap_currently_invalid = 0; + +long buffer[] = {0, 0, 0, 0, 0}; + + + +void *pypy_find_codemap_at_addr(long addr, long *start_addr) +{ + return (void*)buffer; +} + +long pypy_yield_codemap_at_addr(void *codemap_raw, long addr, + long *current_pos_addr) +{ + long c = *current_pos_addr; + if (c >= 5) + return -1; + *current_pos_addr = c + 1; + return *((long*)codemap_raw + c); +} + + +""" + open(str(srcdir.join("get_custom_offset.c"))).read()) + +class TestDirect(object): + def test_infrastructure(self): + cont = ffi.new("long[1]", [0]) + buf = lib.pypy_find_codemap_at_addr(0, cont) + assert buf + cont[0] = 0 + next_addr = lib.pypy_yield_codemap_at_addr(buf, 0, cont) + assert cont[0] == 1 + assert not next_addr + lib.buffer[0] = 13 + cont[0] = 0 + next_addr = lib.pypy_yield_codemap_at_addr(buf, 0, cont) + assert int(ffi.cast("long", next_addr)) == 13 + + def test_write_header_for_jit_addr(self): + lib.buffer[0] = 4 + lib.buffer[1] = 8 + lib.buffer[2] = 12 + lib.buffer[3] = 16 + lib.buffer[4] = 0 + buf = ffi.new("long[10]", [0] * 10) + result = ffi.cast("void**", buf) + res = lib.vmprof_write_header_for_jit_addr(result, 0, ffi.NULL, 100) + assert res == 6 + assert buf[0] == 2 + assert buf[1] == 16 + assert buf[2] == 12 + assert buf[3] == 8 diff --git a/pypy/module/gc/referents.py b/pypy/module/gc/referents.py --- a/pypy/module/gc/referents.py +++ b/pypy/module/gc/referents.py @@ -44,30 +44,6 @@ return OperationError(space.w_NotImplementedError, space.wrap("operation not implemented by this GC")) -# ____________________________________________________________ - -def clear_gcflag_extra(fromlist): - pending = fromlist[:] - while pending: - gcref = pending.pop() - if rgc.get_gcflag_extra(gcref): - rgc.toggle_gcflag_extra(gcref) - pending.extend(rgc.get_rpy_referents(gcref)) - -def do_get_objects(): - roots = [gcref for gcref in rgc.get_rpy_roots() if gcref] - pending = roots[:] - result_w = [] - while pending: - gcref = pending.pop() - if not rgc.get_gcflag_extra(gcref): - rgc.toggle_gcflag_extra(gcref) - w_obj = try_cast_gcref_to_w_root(gcref) - if w_obj is not None: - result_w.append(w_obj) - pending.extend(rgc.get_rpy_referents(gcref)) - clear_gcflag_extra(roots) - return result_w # ____________________________________________________________ @@ -116,8 +92,8 @@ break # done. Clear flags carefully rgc.toggle_gcflag_extra(gcarg) - clear_gcflag_extra(roots) - clear_gcflag_extra([gcarg]) + rgc.clear_gcflag_extra(roots) + rgc.clear_gcflag_extra([gcarg]) return result_w # ____________________________________________________________ @@ -189,8 +165,7 @@ """Return a list of all app-level objects.""" if not rgc.has_gcflag_extra(): raise missing_operation(space) - result_w = do_get_objects() - rgc.assert_no_more_gcflags() + result_w = rgc.do_get_objects(try_cast_gcref_to_w_root) return space.newlist(result_w) def get_referents(space, args_w): diff --git a/pypy/module/micronumpy/concrete.py b/pypy/module/micronumpy/concrete.py --- a/pypy/module/micronumpy/concrete.py +++ b/pypy/module/micronumpy/concrete.py @@ -328,11 +328,8 @@ return ArrayBuffer(self, readonly) def astype(self, space, dtype): - # we want to create a new array, but must respect the strides - # in self. So find a factor of the itemtype.elsize, and use this - factor = float(dtype.elsize) / self.dtype.elsize - strides = [int(factor*s) for s in self.get_strides()] - backstrides = [int(factor*s) for s in self.get_backstrides()] + strides, backstrides = calc_strides(self.get_shape(), dtype, + self.order) impl = ConcreteArray(self.get_shape(), dtype, self.order, strides, backstrides) loop.setslice(space, impl.get_shape(), impl, self) diff --git a/pypy/module/micronumpy/test/test_ndarray.py b/pypy/module/micronumpy/test/test_ndarray.py --- a/pypy/module/micronumpy/test/test_ndarray.py +++ b/pypy/module/micronumpy/test/test_ndarray.py @@ -2183,8 +2183,7 @@ assert b.dtype == 'bool' a = arange(6, dtype='f4').reshape(2,3) - b = a.T.astype('i4') - assert (a.T.strides == b.strides) + b = a.astype('i4') a = array('x').astype('S3').dtype assert a.itemsize == 3 diff --git a/pypy/module/micronumpy/test/test_object_arrays.py b/pypy/module/micronumpy/test/test_object_arrays.py --- a/pypy/module/micronumpy/test/test_object_arrays.py +++ b/pypy/module/micronumpy/test/test_object_arrays.py @@ -1,7 +1,12 @@ from pypy.module.micronumpy.test.test_base import BaseNumpyAppTest +from pypy.conftest import option class AppTestObjectDtypes(BaseNumpyAppTest): + def setup_class(cls): + BaseNumpyAppTest.setup_class.im_func(cls) + cls.w_runappdirect = cls.space.wrap(option.runappdirect) + def test_scalar_from_object(self): from numpy import array import sys @@ -109,6 +114,8 @@ def test_array_interface(self): import numpy as np + if self.runappdirect: + skip('requires numpy.core, test with numpy test suite instead') import sys class DummyArray(object): def __init__(self, interface, base=None): 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 @@ -35,6 +35,9 @@ name = opcode_method_names[ord(bytecode.co_code[next_instr])] return '%s #%d %s' % (bytecode.get_repr(), next_instr, name) +def get_unique_id(next_instr, is_being_profiled, bytecode): + return bytecode._unique_id + def should_unroll_one_iteration(next_instr, is_being_profiled, bytecode): return (bytecode.co_flags & CO_GENERATOR) != 0 @@ -45,6 +48,7 @@ virtualizables = ['frame'] pypyjitdriver = PyPyJitDriver(get_printable_location = get_printable_location, + get_unique_id = get_unique_id, should_unroll_one_iteration = should_unroll_one_iteration, name='pypyjit') diff --git a/pypy/module/pypyjit/test_pypy_c/model.py b/pypy/module/pypyjit/test_pypy_c/model.py --- a/pypy/module/pypyjit/test_pypy_c/model.py +++ b/pypy/module/pypyjit/test_pypy_c/model.py @@ -134,7 +134,8 @@ def _ops_for_chunk(self, chunk, include_guard_not_invalidated): for op in chunk.operations: - if op.name != 'debug_merge_point' and \ + if op.name not in ('debug_merge_point', 'enter_portal_frame', + 'leave_portal_frame') and \ (op.name != 'guard_not_invalidated' or include_guard_not_invalidated): yield op 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 @@ -65,9 +65,7 @@ assert loop.match(""" i7 = int_gt(i4, 1) guard_true(i7, descr=...) - p9 = call(ConstClass(fromint), i4, descr=...) - guard_no_exception(descr=...) - p11 = call(ConstClass(rbigint.mul), p5, p9, descr=...) + p11 = call(ConstClass(rbigint.int_mul), p5, i4, descr=...) guard_no_exception(descr=...) i13 = int_sub(i4, 1) --TICK-- diff --git a/pypy/objspace/std/complexobject.py b/pypy/objspace/std/complexobject.py --- a/pypy/objspace/std/complexobject.py +++ b/pypy/objspace/std/complexobject.py @@ -270,7 +270,7 @@ imag = space.float_w(space.getattr(self, space.wrap("imag"))) real_b = rbigint.fromrarith_int(float2longlong(real)) imag_b = rbigint.fromrarith_int(r_ulonglong(float2longlong(imag))) - val = real_b.lshift(64).or_(imag_b).lshift(3).or_(rbigint.fromint(tag)) + val = real_b.lshift(64).or_(imag_b).lshift(3).int_or_(tag) return space.newlong_from_rbigint(val) def int(self, space): diff --git a/pypy/objspace/std/floatobject.py b/pypy/objspace/std/floatobject.py --- a/pypy/objspace/std/floatobject.py +++ b/pypy/objspace/std/floatobject.py @@ -185,7 +185,7 @@ from pypy.objspace.std.util import IDTAG_FLOAT as tag val = float2longlong(space.float_w(self)) b = rbigint.fromrarith_int(val) - b = b.lshift(3).or_(rbigint.fromint(tag)) + b = b.lshift(3).int_or_(tag) return space.newlong_from_rbigint(b) def __repr__(self): diff --git a/pypy/objspace/std/intobject.py b/pypy/objspace/std/intobject.py --- a/pypy/objspace/std/intobject.py +++ b/pypy/objspace/std/intobject.py @@ -46,7 +46,7 @@ if self.user_overridden_class: return None b = space.bigint_w(self) - b = b.lshift(3).or_(rbigint.fromint(IDTAG_INT)) + b = b.lshift(3).int_or_(IDTAG_INT) return space.newlong_from_rbigint(b) def int(self, space): diff --git a/pypy/objspace/std/longobject.py b/pypy/objspace/std/longobject.py --- a/pypy/objspace/std/longobject.py +++ b/pypy/objspace/std/longobject.py @@ -45,7 +45,7 @@ if self.user_overridden_class: return None b = space.bigint_w(self) - b = b.lshift(3).or_(rbigint.fromint(IDTAG_LONG)) + b = b.lshift(3).int_or_(IDTAG_LONG) return space.newlong_from_rbigint(b) def unwrap(self, space): @@ -350,8 +350,13 @@ def _make_descr_cmp(opname): op = getattr(rbigint, opname) - @delegate_other + intop = getattr(rbigint, "int_" + opname) + def descr_impl(self, space, w_other): + if isinstance(w_other, W_AbstractIntObject): + return space.newbool(intop(self.num, w_other.int_w(space))) + elif not isinstance(w_other, W_AbstractLongObject): + return space.w_NotImplemented return space.newbool(op(self.num, w_other.asbigint())) return func_with_new_name(descr_impl, "descr_" + opname) @@ -362,7 +367,7 @@ descr_gt = _make_descr_cmp('gt') descr_ge = _make_descr_cmp('ge') - def _make_generic_descr_binop(opname): + def _make_generic_descr_binop_noncommutative(opname): methname = opname + '_' if opname in ('and', 'or') else opname descr_rname = 'descr_r' + opname op = getattr(rbigint, methname) @@ -372,33 +377,65 @@ def descr_binop(self, space, w_other): return W_LongObject(op(self.num, w_other.asbigint())) - if opname in COMMUTATIVE_OPS: - @func_renamer(descr_rname) - def descr_rbinop(self, space, w_other): - return descr_binop(self, space, w_other) - else: - @func_renamer(descr_rname) - @delegate_other - def descr_rbinop(self, space, w_other): - return W_LongObject(op(w_other.asbigint(), self.num)) + @func_renamer(descr_rname) + @delegate_other + def descr_rbinop(self, space, w_other): + return W_LongObject(op(w_other.asbigint(), self.num)) return descr_binop, descr_rbinop + def _make_generic_descr_binop(opname): + if opname not in COMMUTATIVE_OPS: + raise Exception("Not supported") + + methname = opname + '_' if opname in ('and', 'or') else opname + descr_rname = 'descr_r' + opname + op = getattr(rbigint, methname) + intop = getattr(rbigint, "int_" + methname) + + @func_renamer('descr_' + opname) + def descr_binop(self, space, w_other): + if isinstance(w_other, W_AbstractIntObject): + return W_LongObject(intop(self.num, w_other.int_w(space))) + elif not isinstance(w_other, W_AbstractLongObject): + return space.w_NotImplemented + + return W_LongObject(op(self.num, w_other.asbigint())) + + @func_renamer(descr_rname) + def descr_rbinop(self, space, w_other): + if isinstance(w_other, W_AbstractIntObject): + return W_LongObject(intop(self.num, w_other.int_w(space))) + elif not isinstance(w_other, W_AbstractLongObject): + return space.w_NotImplemented + + return W_LongObject(op(w_other.asbigint(), self.num)) + + return descr_binop, descr_rbinop + descr_add, descr_radd = _make_generic_descr_binop('add') - descr_sub, descr_rsub = _make_generic_descr_binop('sub') + descr_sub, descr_rsub = _make_generic_descr_binop_noncommutative('sub') descr_mul, descr_rmul = _make_generic_descr_binop('mul') descr_and, descr_rand = _make_generic_descr_binop('and') descr_or, descr_ror = _make_generic_descr_binop('or') descr_xor, descr_rxor = _make_generic_descr_binop('xor') - def _make_descr_binop(func): + def _make_descr_binop(func, int_func=None): opname = func.__name__[1:] - @delegate_other - @func_renamer('descr_' + opname) - def descr_binop(self, space, w_other): - return func(self, space, w_other) - + if int_func: + @func_renamer('descr_' + opname) + def descr_binop(self, space, w_other): + if isinstance(w_other, W_AbstractIntObject): + return int_func(self, space, w_other.int_w(space)) + elif not isinstance(w_other, W_AbstractLongObject): + return space.w_NotImplemented + return func(self, space, w_other) + else: + @delegate_other + @func_renamer('descr_' + opname) + def descr_binop(self, space, w_other): + return func(self, space, w_other) @delegate_other @func_renamer('descr_r' + opname) def descr_rbinop(self, space, w_other): @@ -417,7 +454,13 @@ except OverflowError: # b too big raise oefmt(space.w_OverflowError, "shift count too large") return W_LongObject(self.num.lshift(shift)) - descr_lshift, descr_rlshift = _make_descr_binop(_lshift) + + def _int_lshift(self, space, w_other): + if w_other < 0: + raise oefmt(space.w_ValueError, "negative shift count") + return W_LongObject(self.num.lshift(w_other)) + + descr_lshift, descr_rlshift = _make_descr_binop(_lshift, _int_lshift) def _rshift(self, space, w_other): if w_other.asbigint().sign < 0: @@ -427,8 +470,22 @@ except OverflowError: # b too big # XXX maybe just return 0L instead? raise oefmt(space.w_OverflowError, "shift count too large") return newlong(space, self.num.rshift(shift)) - descr_rshift, descr_rrshift = _make_descr_binop(_rshift) + + def _int_rshift(self, space, w_other): + if w_other < 0: + raise oefmt(space.w_ValueError, "negative shift count") + return newlong(space, self.num.rshift(w_other)) + descr_rshift, descr_rrshift = _make_descr_binop(_rshift, _int_rshift) + + def _floordiv(self, space, w_other): + try: + z = self.num.floordiv(w_other.asbigint()) + except ZeroDivisionError: + raise oefmt(space.w_ZeroDivisionError, + "long division or modulo by zero") + return newlong(space, z) + def _floordiv(self, space, w_other): try: z = self.num.floordiv(w_other.asbigint()) @@ -448,7 +505,15 @@ raise oefmt(space.w_ZeroDivisionError, "long division or modulo by zero") return newlong(space, z) - descr_mod, descr_rmod = _make_descr_binop(_mod) + + def _int_mod(self, space, w_other): + try: + z = self.num.int_mod(w_other) + except ZeroDivisionError: + raise oefmt(space.w_ZeroDivisionError, + "long division or modulo by zero") + return newlong(space, z) + descr_mod, descr_rmod = _make_descr_binop(_mod, _int_mod) def _divmod(self, space, w_other): try: diff --git a/rpython/bin/rpython-vmprof b/rpython/bin/rpython-vmprof new file mode 100755 --- /dev/null +++ b/rpython/bin/rpython-vmprof @@ -0,0 +1,28 @@ +#!/usr/bin/env pypy + +"""RPython translation usage: + +rpython <translation options> target <targetoptions> + +run with --help for more information +""" + +import sys, os +sys.path.insert(0, os.path.dirname(os.path.dirname( + os.path.dirname(os.path.realpath(__file__))))) +from rpython.translator.goal.translate import main + +# no implicit targets +if len(sys.argv) == 1: + print __doc__ + sys.exit(1) + +import _vmprof, subprocess +x = subprocess.Popen('gzip > vmprof.log.gz', shell=True, stdin=subprocess.PIPE) +_vmprof.enable(x.stdin.fileno(), 0.001) +try: + main() +finally: + _vmprof.disable() + x.stdin.close() + x.wait() diff --git a/rpython/jit/backend/arm/assembler.py b/rpython/jit/backend/arm/assembler.py --- a/rpython/jit/backend/arm/assembler.py +++ b/rpython/jit/backend/arm/assembler.py @@ -57,6 +57,7 @@ BaseAssembler.setup_once(self) def setup(self, looptoken): + BaseAssembler.setup(self, looptoken) assert self.memcpy_addr != 0, 'setup_once() not called?' if we_are_translated(): self.debug = False @@ -71,7 +72,6 @@ self.mc.datablockwrapper = self.datablockwrapper self.target_tokens_currently_compiling = {} self.frame_depth_to_patch = [] - self._finish_gcmap = lltype.nullptr(jitframe.GCMAP) def teardown(self): self.current_clt = None @@ -102,7 +102,7 @@ self.store_reg(mc, r.r0, r.fp, ofs) mc.MOV_rr(r.r0.value, r.fp.value) self.gen_func_epilog(mc) - rawstart = mc.materialize(self.cpu.asmmemmgr, []) + rawstart = mc.materialize(self.cpu, []) self.propagate_exception_path = rawstart def _store_and_reset_exception(self, mc, excvalloc=None, exctploc=None, @@ -198,7 +198,7 @@ mc.ADD_ri(r.sp.value, r.sp.value, (len(r.argument_regs) + 2) * WORD) mc.B(self.propagate_exception_path) # - rawstart = mc.materialize(self.cpu.asmmemmgr, []) + rawstart = mc.materialize(self.cpu, []) self.stack_check_slowpath = rawstart def _build_wb_slowpath(self, withcards, withfloats=False, for_frame=False): @@ -255,7 +255,7 @@ # mc.POP([r.ip.value, r.pc.value]) # - rawstart = mc.materialize(self.cpu.asmmemmgr, []) + rawstart = mc.materialize(self.cpu, []) if for_frame: self.wb_slowpath[4] = rawstart else: @@ -276,7 +276,7 @@ callee_only) # return mc.POP([r.ip.value, r.pc.value]) - return mc.materialize(self.cpu.asmmemmgr, []) + return mc.materialize(self.cpu, []) def _build_malloc_slowpath(self, kind): """ While arriving on slowpath, we have a gcpattern on stack 0. @@ -352,7 +352,7 @@ mc.POP([r.ip.value, r.pc.value]) # - rawstart = mc.materialize(self.cpu.asmmemmgr, []) + rawstart = mc.materialize(self.cpu, []) return rawstart def _reload_frame_if_necessary(self, mc): @@ -473,7 +473,7 @@ mc.MOV_rr(r.r0.value, r.fp.value) # self.gen_func_epilog(mc) - rawstart = mc.materialize(self.cpu.asmmemmgr, []) + rawstart = mc.materialize(self.cpu, []) self.failure_recovery_code[exc + 2 * withfloats] = rawstart def generate_quick_failure(self, guardtok): @@ -575,8 +575,8 @@ self.mc.BL(self.stack_check_slowpath, c=c.HI) # call if ip > lr # cpu interface - def assemble_loop(self, logger, loopname, inputargs, operations, looptoken, - log): + def assemble_loop(self, jd_id, unique_id, logger, loopname, inputargs, + operations, looptoken, log): clt = CompiledLoopToken(self.cpu, looptoken.number) looptoken.compiled_loop_token = clt clt._debug_nbargs = len(inputargs) @@ -586,6 +586,9 @@ assert len(set(inputargs)) == len(inputargs) self.setup(looptoken) + #self.codemap_builder.enter_portal_frame(jd_id, unique_id, + # self.mc.get_relative_pos()) + frame_info = self.datablockwrapper.malloc_aligned( jitframe.JITFRAMEINFO_SIZE, alignment=WORD) @@ -659,6 +662,7 @@ assert len(set(inputargs)) == len(inputargs) self.setup(original_loop_token) + #self.codemap.inherit_code_from_position(faildescr.adr_jump_offset) descr_number = compute_unique_id(faildescr) if log: operations = self._inject_debugging_code(faildescr, operations, @@ -850,7 +854,7 @@ # restore registers self._pop_all_regs_from_jitframe(mc, [], self.cpu.supports_floats) mc.POP([r.ip.value, r.pc.value]) # return - self._frame_realloc_slowpath = mc.materialize(self.cpu.asmmemmgr, []) + self._frame_realloc_slowpath = mc.materialize(self.cpu, []) def _load_shadowstack_top(self, mc, reg, gcrootmap): rst = gcrootmap.get_root_stack_top_addr() @@ -879,8 +883,12 @@ self.datablockwrapper.done() # finish using cpu.asmmemmgr self.datablockwrapper = None allblocks = self.get_asmmemmgr_blocks(looptoken) - return self.mc.materialize(self.cpu.asmmemmgr, allblocks, + size = self.mc.get_relative_pos() + res = self.mc.materialize(self.cpu, allblocks, self.cpu.gc_ll_descr.gcrootmap) + #self.cpu.codemap.register_codemap( + # self.codemap.get_final_bytecode(res, size)) + return res def update_frame_depth(self, frame_depth): baseofs = self.cpu.get_baseofs_of_frame_field() diff --git a/rpython/jit/backend/arm/opassembler.py b/rpython/jit/backend/arm/opassembler.py --- a/rpython/jit/backend/arm/opassembler.py +++ b/rpython/jit/backend/arm/opassembler.py @@ -459,6 +459,8 @@ return fcond emit_op_jit_debug = emit_op_debug_merge_point emit_op_keepalive = emit_op_debug_merge_point + emit_op_enter_portal_frame = emit_op_debug_merge_point + emit_op_leave_portal_frame = emit_op_debug_merge_point def emit_op_cond_call_gc_wb(self, op, arglocs, regalloc, fcond): self._write_barrier_fastpath(self.mc, op.getdescr(), arglocs, fcond) diff --git a/rpython/jit/backend/arm/regalloc.py b/rpython/jit/backend/arm/regalloc.py --- a/rpython/jit/backend/arm/regalloc.py +++ b/rpython/jit/backend/arm/regalloc.py @@ -373,6 +373,12 @@ return gcmap # ------------------------------------------------------------ + def perform_enter_portal_frame(self, op): + self.assembler.enter_portal_frame(op) + + def perform_leave_portal_frame(self, op): + self.assembler.leave_portal_frame(op) + def perform_extra(self, op, args, fcond): return self.assembler.regalloc_emit_extra(op, args, fcond, self) @@ -1149,6 +1155,8 @@ prepare_op_debug_merge_point = void prepare_op_jit_debug = void prepare_op_keepalive = void + prepare_op_enter_portal_frame = void + prepare_op_leave_portal_frame = void def prepare_op_cond_call_gc_wb(self, op, fcond): assert op.result is None diff --git a/rpython/jit/backend/arm/runner.py b/rpython/jit/backend/arm/runner.py --- a/rpython/jit/backend/arm/runner.py +++ b/rpython/jit/backend/arm/runner.py @@ -50,16 +50,12 @@ def setup_once(self): self.cpuinfo.arch_version = detect_arch_version() self.cpuinfo.hf_abi = detect_hardfloat() + #self.codemap.setup() self.assembler.setup_once() def finish_once(self): self.assembler.finish_once() - def compile_loop(self, inputargs, operations, looptoken, - log=True, name='', logger=None): - return self.assembler.assemble_loop(logger, name, inputargs, operations, - looptoken, log=log) - def compile_bridge(self, faildescr, inputargs, operations, original_loop_token, log=True, logger=None): clt = original_loop_token.compiled_loop_token diff --git a/rpython/jit/backend/arm/test/support.py b/rpython/jit/backend/arm/test/support.py --- a/rpython/jit/backend/arm/test/support.py +++ b/rpython/jit/backend/arm/test/support.py @@ -24,7 +24,7 @@ def run_asm(asm): BOOTSTRAP_TP = lltype.FuncType([], lltype.Signed) - addr = asm.mc.materialize(asm.cpu.asmmemmgr, [], None) + addr = asm.mc.materialize(asm.cpu, [], None) assert addr % 8 == 0 func = rffi.cast(lltype.Ptr(BOOTSTRAP_TP), addr) asm.mc._dump_trace(addr, 'test.asm') diff --git a/rpython/jit/backend/arm/test/test_calling_convention.py b/rpython/jit/backend/arm/test/test_calling_convention.py --- a/rpython/jit/backend/arm/test/test_calling_convention.py +++ b/rpython/jit/backend/arm/test/test_calling_convention.py @@ -29,7 +29,7 @@ mc = InstrBuilder() mc.MOV_rr(r.r0.value, r.sp.value) mc.MOV_rr(r.pc.value, r.lr.value) - return mc.materialize(self.cpu.asmmemmgr, []) + return mc.materialize(self.cpu, []) def get_alignment_requirements(self): return 8 diff --git a/rpython/jit/backend/llgraph/runner.py b/rpython/jit/backend/llgraph/runner.py --- a/rpython/jit/backend/llgraph/runner.py +++ b/rpython/jit/backend/llgraph/runner.py @@ -245,8 +245,8 @@ self.stats = stats or MiniStats() self.vinfo_for_tests = kwds.get('vinfo_for_tests', None) - def compile_loop(self, inputargs, operations, looptoken, log=True, - name='', logger=None): + def compile_loop(self, inputargs, operations, looptoken, jd_id=0, + unique_id=0, log=True, name='', logger=None): _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit