mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	add a SETUP_WITH opcode
It speeds up the with statement and correctly looks up the special methods involved.
This commit is contained in:
		
							parent
							
								
									179bf213ea
								
							
						
					
					
						commit
						1880d8b823
					
				
					 9 changed files with 86 additions and 66 deletions
				
			
		|  | @ -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: | ||||||
| 
 | 
 | ||||||
| #. 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: | ||||||
|       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" { | ||||||
| #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 @@ def jabs_op(name, op): | ||||||
| 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 @@ def do_isinstance(obj): | ||||||
|             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 @@ def do_issubclass(obj): | ||||||
|             ("__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 | ||||||
| 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 *); | ||||||
| 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) | ||||||
| 					   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 @@ PyEval_EvalCodeEx(PyCodeObject *co, PyObject *globals, PyObject *locals, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | 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) | ||||||
| 			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) | ||||||
| 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; | ||||||
|        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 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Benjamin Peterson
						Benjamin Peterson