Kaydet (Commit) 1880d8b8 authored tarafından Benjamin Peterson's avatar Benjamin Peterson

add a SETUP_WITH opcode

It speeds up the with statement and correctly looks up the special
methods involved.
üst 179bf213
...@@ -532,6 +532,18 @@ Miscellaneous opcodes. ...@@ -532,6 +532,18 @@ Miscellaneous opcodes.
the names of the base classes, and TOS2 the class name. the names of the base classes, and TOS2 the class name.
.. opcode:: SETUP_WITH (delta)
This opcode performs several operations before a with block starts. First,
it loads :meth:`~object.__exit__` from the context manager and pushes it onto
the stack for later use by :opcode:`WITH_CLEANUP`. Then,
:meth:`~object.__enter__` is called, and a finally block pointing to *delta*
is pushed. Finally, the result of calling the enter method is pushed onto
the stack. The next opcode will either ignore it (:opcode:`POP_TOP`), or
store it in (a) variable(s) (:opcode:`STORE_FAST`, :opcode:`STORE_NAME`, or
:opcode:`UNPACK_SEQUENCE`).
.. opcode:: WITH_CLEANUP () .. opcode:: WITH_CLEANUP ()
Cleans up the stack when a :keyword:`with` statement block exits. On top of Cleans up the stack when a :keyword:`with` statement block exits. On top of
......
...@@ -339,6 +339,8 @@ The execution of the :keyword:`with` statement proceeds as follows: ...@@ -339,6 +339,8 @@ The execution of the :keyword:`with` statement proceeds as follows:
#. The context expression is evaluated to obtain a context manager. #. The context expression is evaluated to obtain a context manager.
#. The context manager's :meth:`__exit__` is loaded for later use.
#. The context manager's :meth:`__enter__` method is invoked. #. The context manager's :meth:`__enter__` method is invoked.
#. If a target was included in the :keyword:`with` statement, the return value #. If a target was included in the :keyword:`with` statement, the return value
...@@ -349,7 +351,7 @@ The execution of the :keyword:`with` statement proceeds as follows: ...@@ -349,7 +351,7 @@ The execution of the :keyword:`with` statement proceeds as follows:
The :keyword:`with` statement guarantees that if the :meth:`__enter__` method The :keyword:`with` statement guarantees that if the :meth:`__enter__` method
returns without an error, then :meth:`__exit__` will always be called. Thus, if returns without an error, then :meth:`__exit__` will always be called. Thus, if
an error occurs during the assignment to the target list, it will be treated the an error occurs during the assignment to the target list, it will be treated the
same as an error occurring within the suite would be. See step 5 below. same as an error occurring within the suite would be. See step 6 below.
#. The suite is executed. #. The suite is executed.
......
...@@ -141,8 +141,10 @@ extern "C" { ...@@ -141,8 +141,10 @@ extern "C" {
#define CALL_FUNCTION_KW 141 /* #args + (#kwargs<<8) */ #define CALL_FUNCTION_KW 141 /* #args + (#kwargs<<8) */
#define CALL_FUNCTION_VAR_KW 142 /* #args + (#kwargs<<8) */ #define CALL_FUNCTION_VAR_KW 142 /* #args + (#kwargs<<8) */
#define SETUP_WITH 143
/* Support for opargs more than 16 bits long */ /* Support for opargs more than 16 bits long */
#define EXTENDED_ARG 143 #define EXTENDED_ARG 145
enum cmp_op {PyCmp_LT=Py_LT, PyCmp_LE=Py_LE, PyCmp_EQ=Py_EQ, PyCmp_NE=Py_NE, PyCmp_GT=Py_GT, PyCmp_GE=Py_GE, enum cmp_op {PyCmp_LT=Py_LT, PyCmp_LE=Py_LE, PyCmp_EQ=Py_EQ, PyCmp_NE=Py_NE, PyCmp_GT=Py_GT, PyCmp_GE=Py_GE,
......
...@@ -181,7 +181,10 @@ hasfree.append(137) ...@@ -181,7 +181,10 @@ hasfree.append(137)
def_op('CALL_FUNCTION_VAR', 140) # #args + (#kwargs << 8) def_op('CALL_FUNCTION_VAR', 140) # #args + (#kwargs << 8)
def_op('CALL_FUNCTION_KW', 141) # #args + (#kwargs << 8) def_op('CALL_FUNCTION_KW', 141) # #args + (#kwargs << 8)
def_op('CALL_FUNCTION_VAR_KW', 142) # #args + (#kwargs << 8) def_op('CALL_FUNCTION_VAR_KW', 142) # #args + (#kwargs << 8)
def_op('EXTENDED_ARG', 143)
EXTENDED_ARG = 143 jrel_op('SETUP_WITH', 143)
def_op('EXTENDED_ARG', 145)
EXTENDED_ARG = 145
del def_op, name_op, jrel_op, jabs_op del def_op, name_op, jrel_op, jabs_op
...@@ -1689,6 +1689,7 @@ order (MRO) for bases """ ...@@ -1689,6 +1689,7 @@ order (MRO) for bases """
return isinstance(int, obj) return isinstance(int, obj)
def do_issubclass(obj): def do_issubclass(obj):
return issubclass(int, obj) return issubclass(int, obj)
def swallow(*args): pass
# It would be nice to have every special method tested here, but I'm # It would be nice to have every special method tested here, but I'm
# only listing the ones I can remember outside of typeobject.c, since it # only listing the ones I can remember outside of typeobject.c, since it
...@@ -1702,11 +1703,8 @@ order (MRO) for bases """ ...@@ -1702,11 +1703,8 @@ order (MRO) for bases """
("__instancecheck__", do_isinstance, return_true, set(), {}), ("__instancecheck__", do_isinstance, return_true, set(), {}),
("__subclasscheck__", do_issubclass, return_true, ("__subclasscheck__", do_issubclass, return_true,
set(("__bases__",)), {}), set(("__bases__",)), {}),
# These two fail because the compiler generates LOAD_ATTR to look ("__enter__", run_context, iden, set(), {"__exit__" : swallow}),
# them up. We'd have to add a new opcode to fix this, and it's ("__exit__", run_context, swallow, set(), {"__enter__" : iden}),
# probably not worth it.
# ("__enter__", run_context, iden),
# ("__exit__", run_context, iden),
] ]
class Checker(object): class Checker(object):
......
...@@ -12,6 +12,9 @@ What's New in Python 2.7 alpha 1 ...@@ -12,6 +12,9 @@ What's New in Python 2.7 alpha 1
Core and Builtins Core and Builtins
----------------- -----------------
- Issue #6101: A new opcode, SETUP_WITH, has been added to speed up the with
statement and correctly lookup the __enter__ and __exit__ special methods.
- Issue #5829: complex("1e500") no longer raises OverflowError. This - Issue #5829: complex("1e500") no longer raises OverflowError. This
makes it consistent with float("1e500") and interpretation of real makes it consistent with float("1e500") and interpretation of real
and imaginary literals. and imaginary literals.
......
...@@ -128,6 +128,7 @@ static void format_exc_check_arg(PyObject *, char *, PyObject *); ...@@ -128,6 +128,7 @@ static void format_exc_check_arg(PyObject *, char *, PyObject *);
static PyObject * string_concatenate(PyObject *, PyObject *, static PyObject * string_concatenate(PyObject *, PyObject *,
PyFrameObject *, unsigned char *); PyFrameObject *, unsigned char *);
static PyObject * kwd_as_string(PyObject *); static PyObject * kwd_as_string(PyObject *);
static PyObject * special_lookup(PyObject *, char *, PyObject **);
#define NAME_ERROR_MSG \ #define NAME_ERROR_MSG \
"name '%.200s' is not defined" "name '%.200s' is not defined"
...@@ -2467,6 +2468,33 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag) ...@@ -2467,6 +2468,33 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
STACK_LEVEL()); STACK_LEVEL());
continue; continue;
case SETUP_WITH:
{
static PyObject *exit, *enter;
w = TOP();
x = special_lookup(w, "__exit__", &exit);
if (!x)
break;
SET_TOP(x);
u = special_lookup(w, "__enter__", &enter);
Py_DECREF(w);
if (!u) {
x = NULL;
break;
}
x = PyObject_CallFunctionObjArgs(u, NULL);
Py_DECREF(u);
if (!x)
break;
/* Setup the finally block before pushing the result
of __enter__ on the stack. */
PyFrame_BlockSetup(f, SETUP_FINALLY, INSTR_OFFSET() + oparg,
STACK_LEVEL());
PUSH(x);
continue;
}
case WITH_CLEANUP: case WITH_CLEANUP:
{ {
/* At the top of the stack are 1-3 values indicating /* At the top of the stack are 1-3 values indicating
...@@ -3171,6 +3199,24 @@ fail: /* Jump here from prelude on failure */ ...@@ -3171,6 +3199,24 @@ fail: /* Jump here from prelude on failure */
} }
static PyObject *
special_lookup(PyObject *o, char *meth, PyObject **cache)
{
PyObject *res;
if (PyInstance_Check(o)) {
if (!*cache)
return PyObject_GetAttrString(o, meth);
else
return PyObject_GetAttr(o, *cache);
}
res = _PyObject_LookupSpecial(o, meth, cache);
if (res == NULL && !PyErr_Occurred()) {
PyErr_SetObject(PyExc_AttributeError, *cache);
return NULL;
}
return res;
}
static PyObject * static PyObject *
kwd_as_string(PyObject *kwd) { kwd_as_string(PyObject *kwd) {
......
...@@ -778,6 +778,8 @@ opcode_stack_effect(int opcode, int oparg) ...@@ -778,6 +778,8 @@ opcode_stack_effect(int opcode, int oparg)
return -1; return -1;
case BREAK_LOOP: case BREAK_LOOP:
return 0; return 0;
case SETUP_WITH:
return 1;
case WITH_CLEANUP: case WITH_CLEANUP:
return -1; /* XXX Sometimes more */ return -1; /* XXX Sometimes more */
case LOAD_LOCALS: case LOAD_LOCALS:
...@@ -2821,80 +2823,31 @@ expr_constant(expr_ty e) ...@@ -2821,80 +2823,31 @@ expr_constant(expr_ty e)
static int static int
compiler_with(struct compiler *c, stmt_ty s) compiler_with(struct compiler *c, stmt_ty s)
{ {
static identifier enter_attr, exit_attr;
basicblock *block, *finally; basicblock *block, *finally;
identifier tmpvalue = NULL;
assert(s->kind == With_kind); assert(s->kind == With_kind);
if (!enter_attr) {
enter_attr = PyString_InternFromString("__enter__");
if (!enter_attr)
return 0;
}
if (!exit_attr) {
exit_attr = PyString_InternFromString("__exit__");
if (!exit_attr)
return 0;
}
block = compiler_new_block(c); block = compiler_new_block(c);
finally = compiler_new_block(c); finally = compiler_new_block(c);
if (!block || !finally) if (!block || !finally)
return 0; return 0;
if (s->v.With.optional_vars) {
/* Create a temporary variable to hold context.__enter__().
We need to do this rather than preserving it on the stack
because SETUP_FINALLY remembers the stack level.
We need to do the assignment *inside* the try/finally
so that context.__exit__() is called when the assignment
fails. But we need to call context.__enter__() *before*
the try/finally so that if it fails we won't call
context.__exit__().
*/
tmpvalue = compiler_new_tmpname(c);
if (tmpvalue == NULL)
return 0;
PyArena_AddPyObject(c->c_arena, tmpvalue);
}
/* Evaluate EXPR */ /* Evaluate EXPR */
VISIT(c, expr, s->v.With.context_expr); VISIT(c, expr, s->v.With.context_expr);
ADDOP_JREL(c, SETUP_WITH, finally);
/* Squirrel away context.__exit__ by stuffing it under context */ /* SETUP_WITH pushes a finally block. */
ADDOP(c, DUP_TOP);
ADDOP_O(c, LOAD_ATTR, exit_attr, names);
ADDOP(c, ROT_TWO);
/* Call context.__enter__() */
ADDOP_O(c, LOAD_ATTR, enter_attr, names);
ADDOP_I(c, CALL_FUNCTION, 0);
if (s->v.With.optional_vars) {
/* Store it in tmpvalue */
if (!compiler_nameop(c, tmpvalue, Store))
return 0;
}
else {
/* Discard result from context.__enter__() */
ADDOP(c, POP_TOP);
}
/* Start the try block */
ADDOP_JREL(c, SETUP_FINALLY, finally);
compiler_use_next_block(c, block); compiler_use_next_block(c, block);
if (!compiler_push_fblock(c, FINALLY_TRY, block)) { if (!compiler_push_fblock(c, FINALLY_TRY, block)) {
return 0; return 0;
} }
if (s->v.With.optional_vars) { if (s->v.With.optional_vars) {
/* Bind saved result of context.__enter__() to VAR */ VISIT(c, expr, s->v.With.optional_vars);
if (!compiler_nameop(c, tmpvalue, Load) || }
!compiler_nameop(c, tmpvalue, Del)) else {
return 0; /* Discard result from context.__enter__() */
VISIT(c, expr, s->v.With.optional_vars); ADDOP(c, POP_TOP);
} }
/* BLOCK code */ /* BLOCK code */
......
...@@ -74,9 +74,10 @@ typedef unsigned short mode_t; ...@@ -74,9 +74,10 @@ typedef unsigned short mode_t;
Python 2.7a0: 62171 (optimize list comprehensions/change LIST_APPEND) Python 2.7a0: 62171 (optimize list comprehensions/change LIST_APPEND)
Python 2.7a0: 62181 (optimize conditional branches: Python 2.7a0: 62181 (optimize conditional branches:
introduce POP_JUMP_IF_FALSE and POP_JUMP_IF_TRUE) introduce POP_JUMP_IF_FALSE and POP_JUMP_IF_TRUE)
Python 2.7a0 62191 (introduce SETUP_WITH)
. .
*/ */
#define MAGIC (62181 | ((long)'\r'<<16) | ((long)'\n'<<24)) #define MAGIC (62191 | ((long)'\r'<<16) | ((long)'\n'<<24))
/* Magic word as global; note that _PyImport_Init() can change the /* Magic word as global; note that _PyImport_Init() can change the
value of this global to accommodate for alterations of how the value of this global to accommodate for alterations of how the
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment