add a SETUP_WITH opcode

It speeds up the with statement and correctly looks up the special
methods involved.
This commit is contained in:
Benjamin Peterson 2009-05-25 13:13:44 +00:00
parent 179bf213ea
commit 1880d8b823
9 changed files with 86 additions and 66 deletions

View file

@ -532,6 +532,18 @@ Miscellaneous opcodes.
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 ()
Cleans up the stack when a :keyword:`with` statement block exits. On top of

View file

@ -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 manager's :meth:`__exit__` is loaded for later use.
#. The context manager's :meth:`__enter__` method is invoked.
#. 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:
The :keyword:`with` statement guarantees that if the :meth:`__enter__` method
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
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.

View file

@ -141,8 +141,10 @@ extern "C" {
#define CALL_FUNCTION_KW 141 /* #args + (#kwargs<<8) */
#define CALL_FUNCTION_VAR_KW 142 /* #args + (#kwargs<<8) */
#define SETUP_WITH 143
/* 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,

View file

@ -181,7 +181,10 @@ hasfree.append(137)
def_op('CALL_FUNCTION_VAR', 140) # #args + (#kwargs << 8)
def_op('CALL_FUNCTION_KW', 141) # #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

View file

@ -1689,6 +1689,7 @@ order (MRO) for bases """
return isinstance(int, obj)
def do_issubclass(obj):
return issubclass(int, obj)
def swallow(*args): pass
# 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
@ -1702,11 +1703,8 @@ order (MRO) for bases """
("__instancecheck__", do_isinstance, return_true, set(), {}),
("__subclasscheck__", do_issubclass, return_true,
set(("__bases__",)), {}),
# These two fail because the compiler generates LOAD_ATTR to look
# them up. We'd have to add a new opcode to fix this, and it's
# probably not worth it.
# ("__enter__", run_context, iden),
# ("__exit__", run_context, iden),
("__enter__", run_context, iden, set(), {"__exit__" : swallow}),
("__exit__", run_context, swallow, set(), {"__enter__" : iden}),
]
class Checker(object):

View file

@ -12,6 +12,9 @@ What's New in Python 2.7 alpha 1
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
makes it consistent with float("1e500") and interpretation of real
and imaginary literals.

View file

@ -128,6 +128,7 @@ static void format_exc_check_arg(PyObject *, char *, PyObject *);
static PyObject * string_concatenate(PyObject *, PyObject *,
PyFrameObject *, unsigned char *);
static PyObject * kwd_as_string(PyObject *);
static PyObject * special_lookup(PyObject *, char *, PyObject **);
#define NAME_ERROR_MSG \
"name '%.200s' is not defined"
@ -2467,6 +2468,33 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
STACK_LEVEL());
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:
{
/* At the top of the stack are 1-3 values indicating
@ -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 *
kwd_as_string(PyObject *kwd) {

View file

@ -778,6 +778,8 @@ opcode_stack_effect(int opcode, int oparg)
return -1;
case BREAK_LOOP:
return 0;
case SETUP_WITH:
return 1;
case WITH_CLEANUP:
return -1; /* XXX Sometimes more */
case LOAD_LOCALS:
@ -2821,81 +2823,32 @@ expr_constant(expr_ty e)
static int
compiler_with(struct compiler *c, stmt_ty s)
{
static identifier enter_attr, exit_attr;
basicblock *block, *finally;
identifier tmpvalue = NULL;
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);
finally = compiler_new_block(c);
if (!block || !finally)
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 */
VISIT(c, expr, s->v.With.context_expr);
ADDOP_JREL(c, SETUP_WITH, finally);
/* Squirrel away context.__exit__ by stuffing it under context */
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);
/* SETUP_WITH pushes a finally block. */
compiler_use_next_block(c, block);
if (!compiler_push_fblock(c, FINALLY_TRY, block)) {
return 0;
}
if (s->v.With.optional_vars) {
/* Bind saved result of context.__enter__() to VAR */
if (!compiler_nameop(c, tmpvalue, Load) ||
!compiler_nameop(c, tmpvalue, Del))
return 0;
VISIT(c, expr, s->v.With.optional_vars);
}
else {
/* Discard result from context.__enter__() */
ADDOP(c, POP_TOP);
}
/* BLOCK code */
VISIT_SEQ(c, stmt, s->v.With.body);

View file

@ -74,9 +74,10 @@ typedef unsigned short mode_t;
Python 2.7a0: 62171 (optimize list comprehensions/change LIST_APPEND)
Python 2.7a0: 62181 (optimize conditional branches:
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
value of this global to accommodate for alterations of how the