diff -r 77d24f51effc Include/funcobject.h
--- a/Include/funcobject.h Tue Jan 12 06:18:32 2016 -0800
+++ b/Include/funcobject.h Wed Jan 13 13:52:07 2016 +0100
@@ -7,6 +7,44 @@
extern "C" {
#endif
+/* Function guard */
+
+typedef struct {
+ PyObject ob_base;
+
+ /* Initialize a guard:
+ *
+ * - Return 0 on success
+ * - Return 1 if the guard will always fail
+ * - Raise an exception and return -1 on error */
+ int (*init) (PyObject *guard, PyObject *func);
+
+ /* Check a guard:
+ *
+ * - Return 0 on success
+ * - Return 1 if the guard failed temporarely
+ * - Return 2 if the guard will always fail
+ * - Raise an exception and return -1 on error
+ *
+ * stack is an array of arguments: indexed arguments followed by (key,
+ * value) pairs of keyword arguments. na is the number of indexed
+ * arguments. nk is the number of keyword arguments: the number of (key,
+ * value) pairs. stack contains na + nk * 2 objects. */
+ int (*check) (PyObject *guard, PyObject **stack, int na, int nk);
+} PyFuncGuardObject;
+
+PyAPI_DATA(PyTypeObject) PyFuncGuard_Type;
+
+
+/* Specialized function */
+
+typedef struct {
+ PyObject *code; /* callable or code object */
+ Py_ssize_t nb_guard;
+ PyObject **guards; /* PyFuncGuardObject objects */
+} PySpecializedCode;
+
+
/* Function objects and code objects should not be confused with each other:
*
* Function objects are created by the execution of the 'def' statement.
@@ -33,6 +71,9 @@ typedef struct {
PyObject *func_annotations; /* Annotations, a dict or NULL */
PyObject *func_qualname; /* The qualified name */
+ Py_ssize_t nb_specialized;
+ PySpecializedCode *specialized;
+
/* Invariant:
* func_closure contains the bindings for func_code->co_freevars, so
* PyTuple_Size(func_closure) == PyCode_GetNumFree(func_code)
@@ -42,7 +83,11 @@ typedef struct {
PyAPI_DATA(PyTypeObject) PyFunction_Type;
-#define PyFunction_Check(op) (Py_TYPE(op) == &PyFunction_Type)
+#define PyFunction_Check(op) \
+ (Py_TYPE(op) == &PyFunction_Type \
+ || PyType_IsSubtype(Py_TYPE(op), &PyFunction_Type))
+#define PyFunction_CheckExact(op) \
+ (Py_TYPE(op) == &PyFunction_Type)
PyAPI_FUNC(PyObject *) PyFunction_New(PyObject *, PyObject *);
PyAPI_FUNC(PyObject *) PyFunction_NewWithQualName(PyObject *, PyObject *, PyObject *);
@@ -58,6 +103,35 @@ PyAPI_FUNC(int) PyFunction_SetClosure(Py
PyAPI_FUNC(PyObject *) PyFunction_GetAnnotations(PyObject *);
PyAPI_FUNC(int) PyFunction_SetAnnotations(PyObject *, PyObject *);
+/* Specialize a function: add a specialized code with guards. Result:
+ *
+ * - Return 0 on success
+ * - Return 1 if the specialization has been ignored
+ * - Raise an exception and return -1 on error */
+PyAPI_DATA(int) PyFunction_Specialize(PyObject *func,
+ PyObject *code, PyObject *guards);
+
+/* Get the list of specialized codes.
+ *
+ * Return a list of (code, guards) tuples where code is a callable or code
+ * object and guards is a list of PyFuncGuard objects.
+ *
+ * Raise an exception and return NULL on error. */
+PyAPI_FUNC(PyObject*) PyFunction_GetSpecializedCodes(PyObject *func);
+
+/* Get the specialized code of a function.
+ *
+ * stack is a an array of PyObject* objects: indexed arguments followed by
+ * (key, value) pairs of keyword arguments. na is the number of indexed
+ * arguments. nk is the number of keyword arguments: the number of (key, value)
+ * pairs. stack contains na + nk * 2 objects.
+ *
+ * Return a callable or a code object on success.
+ * Raise an exception and return NULL on error. */
+PyAPI_FUNC(PyObject*) PyFunction_GetSpecializedCode(PyObject *func,
+ PyObject **stack,
+ int na, int nk);
+
/* Macros for direct access to these values. Type checks are *not*
done, so use with care. */
#define PyFunction_GET_CODE(func) \
diff -r 77d24f51effc Lib/dis.py
--- a/Lib/dis.py Tue Jan 12 06:18:32 2016 -0800
+++ b/Lib/dis.py Wed Jan 13 13:52:07 2016 +0100
@@ -4,6 +4,7 @@ import sys
import types
import collections
import io
+import struct
from opcode import *
from opcode import __all__ as _opcodes_all
@@ -397,8 +398,10 @@ def findlinestarts(code):
Generate pairs (offset, lineno) as described in Python/compile.c.
"""
- byte_increments = list(code.co_lnotab[0::2])
- line_increments = list(code.co_lnotab[1::2])
+ lnotab = code.co_lnotab
+ byte_increments = list(lnotab[0::2])
+ line_increments = [(struct.unpack('b', lnotab[i:i+1])[0])
+ for i in range(1, len(lnotab), 2)]
lastlineno = None
lineno = code.co_firstlineno
diff -r 77d24f51effc Lib/test/test_pep510.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/test/test_pep510.py Wed Jan 13 13:52:07 2016 +0100
@@ -0,0 +1,203 @@
+import unittest
+from test import support
+
+# this test is written for CPython: it must be skipped if _testcapi is missing
+_testcapi = support.import_module('_testcapi')
+
+
+class MyGuard(_testcapi.PyGuard):
+ def __init__(self):
+ self.init_call = None
+ self.init_result = 0
+
+ self.check_call = None
+ self.check_result = 0
+
+ def check(self, args, kw):
+ self.check_call = (args, kw)
+ return self.check_result
+
+ def init(self, func):
+ self.init_call = func
+ return self.init_result
+
+
+class GuardError(Exception):
+ pass
+
+
+class GuardCheck(unittest.TestCase):
+ def test_check_result(self):
+ guard = MyGuard()
+ self.assertEqual(guard(), 0)
+
+ guard.check_result = 1
+ self.assertEqual(guard(), 1)
+
+ guard.check_result = 2
+ self.assertEqual(guard(), 2)
+
+ with self.assertRaises(RuntimeError):
+ guard.check_result = 3
+ guard()
+
+ def test_check_args(self):
+ guard = MyGuard()
+ guard(1, 2, 3, key1='value1', key2='value2')
+
+ # FIXME: support keywords
+ #expected = ((1, 2, 3), {'key1': 'value1', 'key2': 'value2'})
+ expected = ((1, 2, 3), {})
+ self.assertEqual(guard.check_call, expected)
+
+ def test_check_error(self):
+ class GuardCheckError(MyGuard):
+ def check(self, args, kw):
+ raise GuardError
+
+ guard = GuardCheckError()
+ with self.assertRaises(GuardError):
+ guard()
+
+ def test_init_ok(self):
+ def func():
+ pass
+
+ def func2():
+ pass
+
+ guard = MyGuard()
+ _testcapi.func_specialize(func, func2.__code__, [guard])
+ self.assertEqual(len(_testcapi.func_get_specialized(func)), 1)
+
+ self.assertIs(guard.init_call, func)
+
+ def test_init_fail(self):
+ def func():
+ pass
+
+ def func2():
+ pass
+
+ guard = MyGuard()
+ guard.init_result = 1
+ _testcapi.func_specialize(func, func2.__code__, [guard])
+ self.assertEqual(len(_testcapi.func_get_specialized(func)), 0)
+
+ self.assertIs(guard.init_call, func)
+
+ def test_init_error(self):
+ def func():
+ pass
+
+ def func2():
+ pass
+
+ class GuardInitError(MyGuard):
+ def init(self, func):
+ raise GuardError
+
+ guard = GuardInitError()
+ with self.assertRaises(GuardError):
+ _testcapi.func_specialize(func, func2.__code__, [guard])
+ self.assertEqual(len(_testcapi.func_get_specialized(func)), 0)
+
+ def test_init_bug(self):
+ def func():
+ pass
+
+ def func2():
+ pass
+
+ guard = MyGuard()
+ guard.init_result = 2
+ with self.assertRaises(RuntimeError):
+ _testcapi.func_specialize(func, func2.__code__, [guard])
+ self.assertEqual(len(_testcapi.func_get_specialized(func)), 0)
+
+
+class SpecializeTests(unittest.TestCase):
+ def test_specialize_bytecode(self):
+ def func():
+ return "slow"
+
+ def fast_func():
+ return "fast"
+
+ self.assertEqual(func(), "slow")
+
+ _testcapi.func_specialize(func, fast_func.__code__, [MyGuard()])
+ self.assertEqual(len(_testcapi.func_get_specialized(func)), 1)
+ self.assertEqual(func(), "fast")
+
+ def test_specialize_builtin(self):
+ def func(obj):
+ return obj
+
+ self.assertEqual(func("abc"), "abc")
+
+ _testcapi.func_specialize(func, len, [MyGuard()])
+ self.assertEqual(len(_testcapi.func_get_specialized(func)), 1)
+ self.assertEqual(func("abc"), 3)
+
+
+class CallSpecializedTests(unittest.TestCase):
+ def setUp(self):
+ self.guard = MyGuard()
+
+ def test_guard_check_error(self):
+ def func():
+ return "slow"
+
+ def fast_func():
+ return "fast"
+
+ _testcapi.func_specialize(func, fast_func.__code__, [self.guard])
+
+ self.guard.check_result = 3
+ with self.assertRaises(RuntimeError):
+ func()
+
+ def test_temporary_guard_fail(self):
+ def func():
+ return "slow"
+
+ def fast_func():
+ return "fast"
+
+ _testcapi.func_specialize(func, fast_func.__code__, [self.guard])
+ self.assertEqual(func(), "fast")
+
+ # the guard temporarely fails
+ self.guard.check_result = 1
+ self.assertEqual(func(), "slow")
+
+ # the guard succeed again
+ self.guard.check_result = 0
+ self.assertEqual(func(), "fast")
+
+ def test_despecialize(self):
+ def func():
+ return "slow"
+
+ def fast_func():
+ return "fast"
+
+ _testcapi.func_specialize(func, fast_func.__code__, [self.guard])
+ self.assertEqual(func(), "fast")
+ self.assertEqual(len(_testcapi.func_get_specialized(func)), 1)
+
+ # guard will always fail
+ self.guard.check_result = 2
+ self.assertEqual(func(), "slow")
+
+ # the call removed the specialized code
+ self.assertEqual(len(_testcapi.func_get_specialized(func)), 0)
+
+ # check if the function still works
+ self.guard.check_result = 0
+ self.assertEqual(func(), "slow")
+
+
+if __name__ == "__main__":
+ unittest.main()
diff -r 77d24f51effc Lib/test/test_sys.py
--- a/Lib/test/test_sys.py Tue Jan 12 06:18:32 2016 -0800
+++ b/Lib/test/test_sys.py Wed Jan 13 13:52:07 2016 +0100
@@ -969,7 +969,7 @@ class SizeofTest(unittest.TestCase):
check(x, vsize('12P3ic' + CO_MAXBLOCKS*'3i' + 'P' + extras*'P'))
# function
def func(): pass
- check(func, size('12P'))
+ check(func, size('12PnP'))
class c():
@staticmethod
def foo():
diff -r 77d24f51effc Modules/_testcapimodule.c
--- a/Modules/_testcapimodule.c Tue Jan 12 06:18:32 2016 -0800
+++ b/Modules/_testcapimodule.c Wed Jan 13 13:52:07 2016 +0100
@@ -3530,6 +3530,36 @@ get_recursion_depth(PyObject *self, PyOb
}
+static PyObject *
+func_specialize(PyObject *self, PyObject *args)
+{
+ PyObject *func, *code, *guards;
+ int res;
+
+ if (!PyArg_ParseTuple(args, "OOO:specialize",
+ &func, &code, &guards))
+ return NULL;
+
+ res = PyFunction_Specialize(func, code, guards);
+ if (res < 0)
+ return NULL;
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *
+func_get_specialized(PyObject *self, PyObject *args)
+{
+ PyObject *func;
+
+ if (!PyArg_ParseTuple(args, "O!:get_specialized",
+ &PyFunction_Type, &func))
+ return NULL;
+
+ return PyFunction_GetSpecializedCodes(func);
+}
+
+
static PyMethodDef TestMethods[] = {
{"raise_exception", raise_exception, METH_VARARGS},
{"raise_memoryerror", (PyCFunction)raise_memoryerror, METH_NOARGS},
@@ -3706,6 +3736,8 @@ static PyMethodDef TestMethods[] = {
{"PyTime_AsMilliseconds", test_PyTime_AsMilliseconds, METH_VARARGS},
{"PyTime_AsMicroseconds", test_PyTime_AsMicroseconds, METH_VARARGS},
{"get_recursion_depth", get_recursion_depth, METH_NOARGS},
+ {"func_specialize", func_specialize, METH_VARARGS},
+ {"func_get_specialized", func_get_specialized, METH_VARARGS},
{NULL, NULL} /* sentinel */
};
@@ -4061,6 +4093,139 @@ static PyTypeObject awaitType = {
};
+/* PyGuard */
+
+static int
+pyguard_init(PyObject *self, PyObject* func)
+{
+ PyObject *res_obj;
+ int res;
+
+ res_obj = PyObject_CallMethod(self, "init", "O", func);
+ if (res_obj == NULL)
+ return -1;
+
+ res = PyLong_AsLong(res_obj);
+ Py_DECREF(res_obj);
+ if (res == -1 && PyErr_Occurred())
+ return -1;
+
+ return res;
+}
+
+static int
+pyguard_check(PyObject *self, PyObject** stack, int na, int nk)
+{
+ PyObject *args = NULL, *kwargs = NULL;
+ PyObject *res_obj;
+ Py_ssize_t i;
+ int res;
+
+ args = PyTuple_New(na);
+ if (args == NULL)
+ goto error;
+
+ for (i=0; i < na; i++) {
+ PyObject *item = stack[i];
+
+ Py_INCREF(item);
+ PyTuple_SET_ITEM(args, i, item);
+ }
+
+ kwargs = PyDict_New();
+ if (kwargs == NULL)
+ goto error;
+
+ for (i=0; i < nk; i++) {
+ PyObject *key = stack[na + i*2];
+ PyObject *value = stack[na + i*2 + 1];
+
+ if (PyDict_SetItem(kwargs, key, value) < 0)
+ goto error;
+ }
+
+ res_obj = PyObject_CallMethod(self, "check", "NN", args, kwargs);
+ args = NULL;
+ kwargs = NULL;
+
+ if (res_obj == NULL)
+ goto error;
+
+ res = PyLong_AsLong(res_obj);
+ Py_DECREF(res_obj);
+ if (res == -1 && PyErr_Occurred())
+ goto error;
+
+ return res;
+
+error:
+ Py_XDECREF(args);
+ Py_XDECREF(kwargs);
+ return -1;
+}
+
+static PyObject *
+pyguard_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+ PyObject *op;
+ PyFuncGuardObject *self;
+
+ op = PyFuncGuard_Type.tp_new(type, args, kwds);
+ if (op == NULL)
+ return NULL;
+
+ self = (PyFuncGuardObject *)op;
+ self->init = pyguard_init;
+ self->check = pyguard_check;
+
+ return op;
+}
+
+static PyTypeObject PyGuard_Type = {
+ PyVarObject_HEAD_INIT(&PyType_Type, 0)
+ "PyGuard",
+ sizeof(PyFuncGuardObject),
+ 0,
+ 0, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_reserved */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ 0, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ &PyFuncGuard_Type, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ pyguard_new, /* tp_new */
+ 0, /* tp_free */
+};
+
+
+
+
static struct PyModuleDef _testcapimodule = {
PyModuleDef_HEAD_INIT,
"_testcapi",
@@ -4102,6 +4267,11 @@ PyInit__testcapi(void)
Py_INCREF(&awaitType);
PyModule_AddObject(m, "awaitType", (PyObject *)&awaitType);
+ if (PyType_Ready(&PyGuard_Type) < 0)
+ return NULL;
+ Py_INCREF(&PyGuard_Type);
+ PyModule_AddObject(m, "PyGuard", (PyObject *)&PyGuard_Type);
+
PyModule_AddObject(m, "CHAR_MAX", PyLong_FromLong(CHAR_MAX));
PyModule_AddObject(m, "CHAR_MIN", PyLong_FromLong(CHAR_MIN));
PyModule_AddObject(m, "UCHAR_MAX", PyLong_FromLong(UCHAR_MAX));
diff -r 77d24f51effc Objects/funcobject.c
--- a/Objects/funcobject.c Tue Jan 12 06:18:32 2016 -0800
+++ b/Objects/funcobject.c Wed Jan 13 13:52:07 2016 +0100
@@ -5,6 +5,240 @@
#include "code.h"
#include "structmember.h"
+/* PyFuncGuard_Type */
+
+static int
+guard_check(PyObject *self, PyObject **stack, int na, int nk)
+{
+ return 0;
+}
+
+static int
+guard_init(PyObject *self, PyObject *func)
+{
+ return 0;
+}
+
+static PyObject*
+guard_call(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ PyFuncGuardObject *guard = (PyFuncGuardObject *)self;
+ PyObject *list = NULL;
+ PyObject **stack;
+ int res;
+ Py_ssize_t na;
+ Py_ssize_t nk;
+
+ assert(PyTuple_Check(args));
+
+ na = PyTuple_GET_SIZE(args);
+ if (na > INT_MAX) {
+ PyErr_SetString(PyExc_OverflowError, "too many arguments");
+ return NULL;
+ }
+
+ nk = 0;
+
+ if (kwargs) {
+ Py_ssize_t i;
+
+ list = PyList_New(na);
+ if (list == NULL)
+ return NULL;
+
+ for (i=0; i < na; i++) {
+ PyObject *item;
+
+ item = PyTuple_GET_ITEM(args, i);
+ Py_INCREF(item);
+ PyList_SET_ITEM(list, i, item);
+ }
+
+ stack = ((PyListObject *)list)->ob_item;
+
+ /* FIXME: pass keywords as well */
+ }
+ else {
+ stack = ((PyTupleObject *)args)->ob_item;
+ }
+
+ if (nk > INT_MAX) {
+ PyErr_SetString(PyExc_OverflowError, "too many keyword arguments");
+ return NULL;
+ }
+
+ res = guard->check(self, stack, (int)na, (int)nk);
+
+ Py_XDECREF(list);
+
+ if (res < 0)
+ return NULL;
+
+ if (res > 2) {
+ PyErr_Format(PyExc_RuntimeError,
+ "guard check result must be in -1..2, got %i",
+ res);
+ return NULL;
+ }
+
+ return PyLong_FromLong(res);
+}
+
+static PyObject *
+guard_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+ PyObject *op;
+ PyFuncGuardObject *self;
+
+ assert(type != NULL && type->tp_alloc != NULL);
+ op = type->tp_alloc(type, 0);
+ if (!op)
+ return NULL;
+
+ self = (PyFuncGuardObject *)op;
+ self->init = guard_init;
+ self->check = guard_check;
+
+ return op;
+}
+
+PyTypeObject PyFuncGuard_Type = {
+ PyVarObject_HEAD_INIT(&PyType_Type, 0)
+ "fat.Guard",
+ sizeof(PyFuncGuardObject),
+ 0,
+ 0, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_reserved */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ guard_call, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ 0, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ guard_new, /* tp_new */
+ 0, /* tp_free */
+};
+
+/* PySpecializedCode */
+
+static void
+specode_guards_dealloc(Py_ssize_t nguard, PyObject **guards)
+{
+ Py_ssize_t i;
+ for (i=0; i < nguard; i++)
+ Py_DECREF(guards[i]);
+ PyMem_Free(guards);
+}
+
+static void
+specode_dealloc(PySpecializedCode *f)
+{
+ Py_CLEAR(f->code);
+ specode_guards_dealloc(f->nb_guard, f->guards);
+}
+
+static int
+specode_check(PySpecializedCode *spe, PyObject **stack, int na, int nk)
+{
+ Py_ssize_t i;
+ int check;
+
+ for (i=0; i