From 7328c09cd454fb9f0aa145d8c7d9698fac527b7e Mon Sep 17 00:00:00 2001 From: ahgamut <41098605+ahgamut@users.noreply.github.com> Date: Fri, 19 Nov 2021 08:51:54 +0530 Subject: [PATCH] Added LOAD_METHOD, CALL_METHOD opcodes these were added in Python 3.7a1, so bytecode magic number has been updated refer python/cpython@f2392133eba --- third_party/python/Include/opcode.h | 2 + .../Lib/importlib/_bootstrap_external.py | 3 +- third_party/python/Lib/opcode.py | 3 + third_party/python/Lib/test/test_syntax.py | 24 +++++ third_party/python/Objects/object.c | 83 ++++++++++++++++++ third_party/python/PC/launcher.c | 1 + third_party/python/Python/ceval.c | 87 +++++++++++++++++++ third_party/python/Python/compile.c | 39 +++++++++ third_party/python/Python/opcode_targets.inc | 4 +- third_party/python/pycomp.c | 2 +- third_party/python/pyobj.c | 2 +- 11 files changed, 245 insertions(+), 5 deletions(-) diff --git a/third_party/python/Include/opcode.h b/third_party/python/Include/opcode.h index d8b41c711..9f37c1714 100644 --- a/third_party/python/Include/opcode.h +++ b/third_party/python/Include/opcode.h @@ -125,6 +125,8 @@ COSMOPOLITAN_C_START_ #define BUILD_CONST_KEY_MAP 156 #define BUILD_STRING 157 #define BUILD_TUPLE_UNPACK_WITH_CALL 158 +#define LOAD_METHOD 160 +#define CALL_METHOD 161 /* EXCEPT_HANDLER is a special, implicit block type which is created when entering an except handler. It is not an opcode but we define it here diff --git a/third_party/python/Lib/importlib/_bootstrap_external.py b/third_party/python/Lib/importlib/_bootstrap_external.py index 34bf06938..bb43cd46b 100644 --- a/third_party/python/Lib/importlib/_bootstrap_external.py +++ b/third_party/python/Lib/importlib/_bootstrap_external.py @@ -240,6 +240,7 @@ _code_type = type(_write_atomic.__code__) # Python 3.6b1 3377 (set __class__ cell from type.__new__ #23722) # Python 3.6b2 3378 (add BUILD_TUPLE_UNPACK_WITH_CALL #28257) # Python 3.6rc1 3379 (more thorough __class__ validation #23722) +# Python 3.7a1 3390 (add LOAD_METHOD and CALL_METHOD opcodes #26110) # # MAGIC must change whenever the bytecode emitted by the compiler may no # longer be understood by older implementations of the eval loop (usually @@ -248,7 +249,7 @@ _code_type = type(_write_atomic.__code__) # Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array # in PC/launcher.c must also be updated. -MAGIC_NUMBER = (3379).to_bytes(2, 'little') + b'\r\n' +MAGIC_NUMBER = (3390).to_bytes(2, 'little') + b'\r\n' _RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c _PYCACHE = '__pycache__' diff --git a/third_party/python/Lib/opcode.py b/third_party/python/Lib/opcode.py index 5bcb46703..6a5291da3 100644 --- a/third_party/python/Lib/opcode.py +++ b/third_party/python/Lib/opcode.py @@ -208,4 +208,7 @@ def_op('BUILD_CONST_KEY_MAP', 156) def_op('BUILD_STRING', 157) def_op('BUILD_TUPLE_UNPACK_WITH_CALL', 158) +name_op('LOAD_METHOD', 160) +def_op('CALL_METHOD', 161) + del def_op, name_op, jrel_op, jabs_op diff --git a/third_party/python/Lib/test/test_syntax.py b/third_party/python/Lib/test/test_syntax.py index 26f508316..ffb646ed4 100644 --- a/third_party/python/Lib/test/test_syntax.py +++ b/third_party/python/Lib/test/test_syntax.py @@ -203,6 +203,30 @@ three. Traceback (most recent call last): SyntaxError: more than 255 arguments +>>> class C: +... def meth(self, *args): +... return args +>>> obj = C() +>>> obj.meth( +... 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, +... 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, +... 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, +... 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, +... 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, +... 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, +... 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, +... 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, +... 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, +... 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, +... 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, +... 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, +... 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, +... 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, +... 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, +... 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, +... 248, 249, 250, 251, 252, 253, 254) # doctest: +ELLIPSIS +(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ..., 252, 253, 254) + >>> f(lambda x: x[0] = 3) Traceback (most recent call last): SyntaxError: lambda cannot contain assignment diff --git a/third_party/python/Objects/object.c b/third_party/python/Objects/object.c index 78f9d680e..53365f731 100644 --- a/third_party/python/Objects/object.c +++ b/third_party/python/Objects/object.c @@ -1091,6 +1091,89 @@ _PyObject_NextNotImplemented(PyObject *self) return NULL; } +/* Specialized version of _PyObject_GenericGetAttrWithDict + specifically for the LOAD_METHOD opcode. + + Return 1 if a method is found, 0 if it's a regular attribute + from __dict__ or something returned by using a descriptor + protocol. + + `method` will point to the resolved attribute or NULL. In the + latter case, an error will be set. +*/ +int +_PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method) +{ + PyTypeObject *tp = Py_TYPE(obj); + PyObject *descr; + descrgetfunc f = NULL; + PyObject **dictptr, *dict; + PyObject *attr; + int meth_found = 0; + + assert(*method == NULL); + + if (Py_TYPE(obj)->tp_getattro != PyObject_GenericGetAttr + || !PyUnicode_Check(name)) { + *method = PyObject_GetAttr(obj, name); + return 0; + } + + if (tp->tp_dict == NULL && PyType_Ready(tp) < 0) + return 0; + + descr = _PyType_Lookup(tp, name); + if (descr != NULL) { + Py_INCREF(descr); + if (PyFunction_Check(descr) || + (Py_TYPE(descr) == &PyMethodDescr_Type)) { + meth_found = 1; + } else { + f = descr->ob_type->tp_descr_get; + if (f != NULL && PyDescr_IsData(descr)) { + *method = f(descr, obj, (PyObject *)obj->ob_type); + Py_DECREF(descr); + return 0; + } + } + } + + dictptr = _PyObject_GetDictPtr(obj); + if (dictptr != NULL && (dict = *dictptr) != NULL) { + Py_INCREF(dict); + attr = PyDict_GetItem(dict, name); + if (attr != NULL) { + Py_INCREF(attr); + *method = attr; + Py_DECREF(dict); + Py_XDECREF(descr); + return 0; + } + Py_DECREF(dict); + } + + if (meth_found) { + *method = descr; + return 1; + } + + if (f != NULL) { + *method = f(descr, obj, (PyObject *)Py_TYPE(obj)); + Py_DECREF(descr); + return 0; + } + + if (descr != NULL) { + *method = descr; + return 0; + } + + PyErr_Format(PyExc_AttributeError, + "'%.50s' object has no attribute '%U'", + tp->tp_name, name); + return 0; +} + /* Generic GetAttr functions - put these in your tp_[gs]etattro slot */ PyObject * diff --git a/third_party/python/PC/launcher.c b/third_party/python/PC/launcher.c index ab4264a1c..bf7a38493 100644 --- a/third_party/python/PC/launcher.c +++ b/third_party/python/PC/launcher.c @@ -1095,6 +1095,7 @@ static PYC_MAGIC magic_values[] = { { 3250, 3310, L"3.4" }, { 3320, 3351, L"3.5" }, { 3360, 3379, L"3.6" }, + { 3390, 3399, L"3.7" }, { 0 } }; diff --git a/third_party/python/Python/ceval.c b/third_party/python/Python/ceval.c index d87c9278b..5bace5cf9 100644 --- a/third_party/python/Python/ceval.c +++ b/third_party/python/Python/ceval.c @@ -51,6 +51,7 @@ #define CHECKEXC 1 /* Double-check exception checking */ #endif +extern int _PyObject_GetMethod(PyObject *, PyObject *, PyObject **); typedef PyObject *(*callproc)(PyObject *, PyObject *, PyObject *); #ifdef LLTRACE @@ -3312,6 +3313,92 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag) DISPATCH(); } + TARGET(LOAD_METHOD) { + /* Designed to work in tamdem with CALL_METHOD. */ + PyObject *name = GETITEM(names, oparg); + PyObject *obj = TOP(); + PyObject *meth = NULL; + + int meth_found = _PyObject_GetMethod(obj, name, &meth); + + if (meth == NULL) { + /* Most likely attribute wasn't found. */ + goto error; + } + + if (meth_found) { + /* We can bypass temporary bound method object. + meth is unbound method and obj is self. + + meth | self | arg1 | ... | argN + */ + SET_TOP(meth); + PUSH(obj); // self + } + else { + /* meth is not an unbound method (but a regular attr, or + something was returned by a descriptor protocol). Set + the second element of the stack to NULL, to signal + CALL_METHOD that it's not a method call. + + NULL | meth | arg1 | ... | argN + */ + SET_TOP(NULL); + Py_DECREF(obj); + PUSH(meth); + } + DISPATCH(); + } + + TARGET(CALL_METHOD) { + /* Designed to work in tamdem with LOAD_METHOD. */ + PyObject **sp, *res, *meth; + + sp = stack_pointer; + + meth = PEEK(oparg + 2); + if (meth == NULL) { + /* `meth` is NULL when LOAD_METHOD thinks that it's not + a method call. + + Stack layout: + + ... | NULL | callable | arg1 | ... | argN + ^- TOP() + ^- (-oparg) + ^- (-oparg-1) + ^- (-oparg-2) + + `callable` will be POPed by call_function. + NULL will will be POPed manually later. + */ + res = call_function(&sp, oparg, NULL); + stack_pointer = sp; + (void)POP(); /* POP the NULL. */ + } + else { + /* This is a method call. Stack layout: + + ... | method | self | arg1 | ... | argN + ^- TOP() + ^- (-oparg) + ^- (-oparg-1) + ^- (-oparg-2) + + `self` and `method` will be POPed by call_function. + We'll be passing `oparg + 1` to call_function, to + make it accept the `self` as a first argument. + */ + res = call_function(&sp, oparg + 1, NULL); + stack_pointer = sp; + } + + PUSH(res); + if (res == NULL) + goto error; + DISPATCH(); + } + PREDICTED(CALL_FUNCTION); TARGET(CALL_FUNCTION) { PyObject **sp, *res; diff --git a/third_party/python/Python/compile.c b/third_party/python/Python/compile.c index 3cf751b30..99c643665 100644 --- a/third_party/python/Python/compile.c +++ b/third_party/python/Python/compile.c @@ -1058,6 +1058,8 @@ PyCompile_OpcodeStackEffect(int opcode, int oparg) return -oparg; case CALL_FUNCTION: return -oparg; + case CALL_METHOD: + return -oparg-1; case CALL_FUNCTION_KW: return -oparg-1; case CALL_FUNCTION_EX: @@ -1096,6 +1098,8 @@ PyCompile_OpcodeStackEffect(int opcode, int oparg) /* If there's a fmt_spec on the stack, we go from 2->1, else 1->1. */ return (oparg & FVS_MASK) == FVS_HAVE_SPEC ? -1 : 0; + case LOAD_METHOD: + return 1; default: return PY_INVALID_STACK_EFFECT; } @@ -3415,9 +3419,44 @@ compiler_compare(struct compiler *c, expr_ty e) return 1; } +// Return 1 if the method call was optimized, -1 if not, and 0 on error. +static int +maybe_optimize_method_call(struct compiler *c, expr_ty e) +{ + Py_ssize_t argsl, i; + expr_ty meth = e->v.Call.func; + asdl_seq *args = e->v.Call.args; + + /* Check that the call node is an attribute access, and that + the call doesn't have keyword parameters. */ + if (meth->kind != Attribute_kind || meth->v.Attribute.ctx != Load || + asdl_seq_LEN(e->v.Call.keywords)) + return -1; + + /* Check that there are no *varargs types of arguments. */ + argsl = asdl_seq_LEN(args); + for (i = 0; i < argsl; i++) { + expr_ty elt = asdl_seq_GET(args, i); + if (elt->kind == Starred_kind) { + return -1; + } + } + + /* Alright, we can optimize the code. */ + VISIT(c, expr, meth->v.Attribute.value); + ADDOP_NAME(c, LOAD_METHOD, meth->v.Attribute.attr, names); + VISIT_SEQ(c, expr, e->v.Call.args); + ADDOP_I(c, CALL_METHOD, asdl_seq_LEN(e->v.Call.args)); + return 1; +} + static int compiler_call(struct compiler *c, expr_ty e) { + int ret = maybe_optimize_method_call(c, e); + if (ret >= 0) { + return ret; + } VISIT(c, expr, e->v.Call.func); return compiler_call_helper(c, 0, e->v.Call.args, diff --git a/third_party/python/Python/opcode_targets.inc b/third_party/python/Python/opcode_targets.inc index 64d5cba33..a218b7a3d 100644 --- a/third_party/python/Python/opcode_targets.inc +++ b/third_party/python/Python/opcode_targets.inc @@ -167,8 +167,8 @@ static void *const opcode_targets[256] = { &&TARGET_BUILD_STRING, &&TARGET_BUILD_TUPLE_UNPACK_WITH_CALL, &&_unknown_opcode, - &&_unknown_opcode, - &&_unknown_opcode, + &&TARGET_LOAD_METHOD, + &&TARGET_CALL_METHOD, &&_unknown_opcode, &&_unknown_opcode, &&_unknown_opcode, diff --git a/third_party/python/pycomp.c b/third_party/python/pycomp.c index 7b57b32ee..c6ee9677c 100644 --- a/third_party/python/pycomp.c +++ b/third_party/python/pycomp.c @@ -137,7 +137,7 @@ main(int argc, char *argv[]) p = PyBytes_AS_STRING(marshalled); n = PyBytes_GET_SIZE(marshalled); CHECK_NE(-1, (fd = open(outpath, O_CREAT|O_TRUNC|O_WRONLY, 0644))); - WRITE16LE(m+0, 3379); /* Python 3.6rc1 */ + WRITE16LE(m+0, 3390); /* Python 3.7a1 */ WRITE16LE(m+2, READ16LE("\r\n")); WRITE32LE(m+4, st.st_mtim.tv_sec); /* tsk tsk y2038 */ WRITE32LE(m+8, n); diff --git a/third_party/python/pyobj.c b/third_party/python/pyobj.c index 91454f8ab..f3b2bf462 100644 --- a/third_party/python/pyobj.c +++ b/third_party/python/pyobj.c @@ -641,7 +641,7 @@ Objectify(void) assert(PyBytes_CheckExact(marsh)); mardata = PyBytes_AS_STRING(marsh); marsize = PyBytes_GET_SIZE(marsh); - WRITE16LE(header+0, 3379); /* Python 3.6rc1 */ + WRITE16LE(header+0, 3390); /* Python 3.7a1 */ WRITE16LE(header+2, READ16LE("\r\n")); WRITE32LE(header+4, timestamp.tv_sec); WRITE32LE(header+8, marsize);