大师兄的Python源码学习笔记(十三): Python虚拟机中的一般表达式(二)

大师兄的Python源码学习笔记(十二): Python虚拟机中的一般表达式(一)
大师兄的Python源码学习笔记(十四): 虚拟机中的控制流(一)

三、创建非空dict

  1           0 LOAD_CONST               0 ('pp')
              2 LOAD_CONST               1 ('qq')
              4 LOAD_CONST               2 (('puppy1', 'puppy2'))
              6 BUILD_CONST_KEY_MAP      2
              8 STORE_NAME               0 (d)
  • 比对可以看出,与创建空dict的BUILD_MAP相比,非空dict使用了BUILD_CONST_KEY_MAP字节码。
  • 字节码对应的虚拟机代码如下:
ceval.c

        TARGET(BUILD_CONST_KEY_MAP) {
            Py_ssize_t i;
            PyObject *map;
            PyObject *keys = TOP();
            if (!PyTuple_CheckExact(keys) ||
                PyTuple_GET_SIZE(keys) != (Py_ssize_t)oparg) {
                PyErr_SetString(PyExc_SystemError,
                                "bad BUILD_CONST_KEY_MAP keys argument");
                goto error;
            }
            map = _PyDict_NewPresized((Py_ssize_t)oparg);
            if (map == NULL) {
                goto error;
            }
            for (i = oparg; i > 0; i--) {
                int err;
                PyObject *key = PyTuple_GET_ITEM(keys, oparg - i);
                PyObject *value = PEEK(i + 1);
                err = PyDict_SetItem(map, key, value);
                if (err != 0) {
                    Py_DECREF(map);
                    goto error;
                }
            }

            Py_DECREF(POP());
            while (oparg--) {
                Py_DECREF(POP());
            }
            PUSH(map);
            DISPATCH();
        }
  • 这段代码首先创建了一个PyDictObject
  • 将运行时栈中的key和value弹出,在PyDictObject中设置为键值对。
  • 最后将PyDictObject压入运行时栈中。

四、其它一般表达式

demo.py

a=1
b=a
c=a+b
print(c)
pycparser.py

code
   argcount 0
   nlocals 0
   stacksize 2
   flags 0040
  1           0 LOAD_CONST               0 (1)
              2 STORE_NAME               0 (a)

  2           4 LOAD_NAME                0 (a)
              6 STORE_NAME               1 (b)

  3           8 LOAD_NAME                0 (a)
             10 LOAD_NAME                1 (b)
             12 BINARY_ADD
             14 STORE_NAME               2 (c)

  4          16 LOAD_NAME                3 (print)
             18 LOAD_NAME                2 (c)
             20 CALL_FUNCTION            1
             22 POP_TOP
             24 LOAD_CONST               1 (None)
             26 RETURN_VALUE
   consts
      1
      None
   names ('a', 'b', 'c', 'print')
   varnames ()
   freevars ()
   cellvars ()
   filename D:\pythonProject\parser_learn\demo.py
   name <module>
   firstlineno 1

1. 变量之间赋值运算
  • demo.py中第二行指令b=a变量之间赋值运算的机器码如下:
  2           4 LOAD_NAME                0 (a)
              6 STORE_NAME               1 (b)
  • 与给变量赋值不同,变量间的赋值字节码是LOAD_NAME,对应的代码如下:
ceval.c

        TARGET(LOAD_NAME) {
            PyObject *name = GETITEM(names, oparg);
            PyObject *locals = f->f_locals;
            PyObject *v;
            if (locals == NULL) {
                PyErr_Format(PyExc_SystemError,
                             "no locals when loading %R", name);
                goto error;
            }
            if (PyDict_CheckExact(locals)) {
                v = PyDict_GetItem(locals, name);
                Py_XINCREF(v);
            }
            else {
                v = PyObject_GetItem(locals, name);
                if (v == NULL) {
                    if (!PyErr_ExceptionMatches(PyExc_KeyError))
                        goto error;
                    PyErr_Clear();
                }
            }
            if (v == NULL) {
                v = PyDict_GetItem(f->f_globals, name);
                Py_XINCREF(v);
                if (v == NULL) {
                    if (PyDict_CheckExact(f->f_builtins)) {
                        v = PyDict_GetItem(f->f_builtins, name);
                        if (v == NULL) {
                            format_exc_check_arg(
                                        PyExc_NameError,
                                        NAME_ERROR_MSG, name);
                            goto error;
                        }
                        Py_INCREF(v);
                    }
                    else {
                        v = PyObject_GetItem(f->f_builtins, name);
                        if (v == NULL) {
                            if (PyErr_ExceptionMatches(PyExc_KeyError))
                                format_exc_check_arg(
                                            PyExc_NameError,
                                            NAME_ERROR_MSG, name);
                            goto error;
                        }
                    }
                }
            }
            PUSH(v);
            DISPATCH();
        }
  • 在上面的代码中,虚拟机会在当前活动的PyFrameObject对象中所维护的多个名字空间中进行搜索,顺序是f_locals -> f_globals -> f_builtins
  • 如果搜索到与符号对应的元素,就将该元素压入运行时栈中,否则抛出异常。
  • 从源码中可以清楚的看出变量在不同命名空间中的调用顺序。
2. 数值运算
  • 第三行指令c=a+b数值运算的机器码如下:
  3           8 LOAD_NAME                0 (a)
             10 LOAD_NAME                1 (b)
             12 BINARY_ADD
             14 STORE_NAME               2 (c)
  • 在读取变量值后,机器码BINARY_ADD指令负责加法运算,对应的代码如下:
ceval.c

        TARGET(BINARY_ADD) {
            PyObject *right = POP();
            PyObject *left = TOP();
            PyObject *sum;
            /* NOTE(haypo): Please don't try to micro-optimize int+int on
               CPython using bytecode, it is simply worthless.
               See http://bugs.python.org/issue21955 and
               http://bugs.python.org/issue10044 for the discussion. In short,
               no patch shown any impact on a realistic benchmark, only a minor
               speedup on microbenchmarks. */
            if (PyUnicode_CheckExact(left) &&
                     PyUnicode_CheckExact(right)) {
                sum = unicode_concatenate(left, right, f, next_instr);
                /* unicode_concatenate consumed the ref to left */
            }
            else {
                sum = PyNumber_Add(left, right);
                Py_DECREF(left);
            }
            Py_DECREF(right);
            SET_TOP(sum);
            if (sum == NULL)
                goto error;
            DISPATCH();
        }
  • 这段代码首先从运行时栈弹出值,并创建了两个对应的PyObject
  • 同时,创建了一个空的PyObject用于记录结果。
  • 之后判断对象是否是字符串,如果是字符串,则执行字符串的拼接unicode_concatenate
  • 如果不是字符串,则进行加法操作PyNumber_Add,并返回结果。
  • 具体实现加法的底层代码如下:
Objects\abstract.c

PyObject *
PyNumber_Add(PyObject *v, PyObject *w)
{
    PyObject *result = binary_op1(v, w, NB_SLOT(nb_add));
    if (result == Py_NotImplemented) {
        PySequenceMethods *m = v->ob_type->tp_as_sequence;
        Py_DECREF(result);
        if (m && m->sq_concat) {
            return (*m->sq_concat)(v, w);
        }
        result = binop_type_error(v, w, "+");
    }
    return result;
}
Objects\abstract.c

static PyObject *
binary_op1(PyObject *v, PyObject *w, const int op_slot)
{
    PyObject *x;
    binaryfunc slotv = NULL;
    binaryfunc slotw = NULL;

    if (v->ob_type->tp_as_number != NULL)
        slotv = NB_BINOP(v->ob_type->tp_as_number, op_slot);
    if (w->ob_type != v->ob_type &&
        w->ob_type->tp_as_number != NULL) {
        slotw = NB_BINOP(w->ob_type->tp_as_number, op_slot);
        if (slotw == slotv)
            slotw = NULL;
    }
    if (slotv) {
        if (slotw && PyType_IsSubtype(w->ob_type, v->ob_type)) {
            x = slotw(v, w);
            if (x != Py_NotImplemented)
                return x;
            Py_DECREF(x); /* can't do it */
            slotw = NULL;
        }
        x = slotv(v, w);
        if (x != Py_NotImplemented)
            return x;
        Py_DECREF(x); /* can't do it */
    }
    if (slotw) {
        x = slotw(v, w);
        if (x != Py_NotImplemented)
            return x;
        Py_DECREF(x); /* can't do it */
    }
    Py_RETURN_NOTIMPLEMENTED;
}
  • 可以看出,Python计算加法的步骤还是很多的。
  • 除了加法外,还有以下机器码对应不同的二元操作指令:
指令 功能
BINARY_POWER 乘方
BINARY_MULTIPLY 乘法
BINARY_MATRIX_MULTIPLY 矩阵乘法
BINARY_FLOOR_DIVIDE 除法,结果向下取整
BINARY_TRUE_DIVIDE 除法
BINARY_MODULO 取余
BINARY_ADD 加法
BINARY_SUBTRACT 减法
BINARY_SUBSCR 数组取下标,栈顶为下标
BINARY_LSHIFT 左移操作符(乘2)
BINARY_RSHIFT 右移操作符(除2向下取整)
BINARY_AND 按位与
BINARY_XOR 异或
BINARY_OR 按位或
STORE_SUBSCR 列表下标存储
DELETE_SUBSCR 按下标删除元素
3. 信息输出
  • 最后一句print(c)的字节码如下:
  4          16 LOAD_NAME                3 (print)
             18 LOAD_NAME                2 (c)
             20 CALL_FUNCTION            1
             22 POP_TOP
  • 这段代码首先通过LOAD_NAME依次将指令字符串print和变量c依次塞入运行时栈。
  • 之后的CALL_FUNCTION指令对应的代码如下:
ceval.c
     
    ...
    PyObject **stack_pointer;  /* Next free slot in value stack */
    ...
    int oparg;         /* Current opcode argument, if any */
    ...
        PREDICTED(CALL_FUNCTION);
        TARGET(CALL_FUNCTION) {
            PyObject **sp, *res;
            sp = stack_pointer;
            res = call_function(&sp, oparg, NULL);
            stack_pointer = sp;
            PUSH(res);
            if (res == NULL) {
                goto error;
            }
            DISPATCH();
        }
  • 在这里,我们首先看PREDICTED(CALL_FUNCTION):
ceval.c

#define PREDICT(op) \
   do{ \
       _Py_CODEUNIT word = *next_instr; \
       opcode = _Py_OPCODE(word); \
       if (opcode == op){ \
           oparg = _Py_OPARG(word); \
           next_instr++; \
           goto PRED_##op; \
       } \
   } while(0)
#endif
#define PREDICTED(op)           PRED_##op:
  • 这个宏判定下一条指令是否为op, 如果是则跳转到该 op 分支的地方执行,用于提升代码效率。
  • 在这之后,程序将print方法作为参数调用了call_function函数。
ceval.c

Py_LOCAL_INLINE(PyObject *) _Py_HOT_FUNCTION
call_function(PyObject ***pp_stack, Py_ssize_t oparg, PyObject *kwnames)
{
   PyObject **pfunc = (*pp_stack) - oparg - 1;
   PyObject *func = *pfunc;
   PyObject *x, *w;
   Py_ssize_t nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames);
   Py_ssize_t nargs = oparg - nkwargs;
   PyObject **stack = (*pp_stack) - nargs - nkwargs;

   /* Always dispatch PyCFunction first, because these are
      presumed to be the most frequent callable object.
   */
   if (PyCFunction_Check(func)) {
       PyThreadState *tstate = PyThreadState_GET();
       C_TRACE(x, _PyCFunction_FastCallKeywords(func, stack, nargs, kwnames));
   }
   else if (Py_TYPE(func) == &PyMethodDescr_Type) {
       PyThreadState *tstate = PyThreadState_GET();
       if (nargs > 0 && tstate->use_tracing) {
           /* We need to create a temporary bound method as argument
              for profiling.

              If nargs == 0, then this cannot work because we have no
              "self". In any case, the call itself would raise
              TypeError (foo needs an argument), so we just skip
              profiling. */
           PyObject *self = stack[0];
           func = Py_TYPE(func)->tp_descr_get(func, self, (PyObject*)Py_TYPE(self));
           if (func != NULL) {
               C_TRACE(x, _PyCFunction_FastCallKeywords(func,
                                                        stack+1, nargs-1,
                                                        kwnames));
               Py_DECREF(func);
           }
           else {
               x = NULL;
           }
       }
       else {
           x = _PyMethodDescr_FastCallKeywords(func, stack, nargs, kwnames);
       }
   }
   else {
       if (PyMethod_Check(func) && PyMethod_GET_SELF(func) != NULL) {
           /* Optimize access to bound methods. Reuse the Python stack
              to pass 'self' as the first argument, replace 'func'
              with 'self'. It avoids the creation of a new temporary tuple
              for arguments (to replace func with self) when the method uses
              FASTCALL. */
           PyObject *self = PyMethod_GET_SELF(func);
           Py_INCREF(self);
           func = PyMethod_GET_FUNCTION(func);
           Py_INCREF(func);
           Py_SETREF(*pfunc, self);
           nargs++;
           stack--;
       }
       else {
           Py_INCREF(func);
       }

       if (PyFunction_Check(func)) {
           x = _PyFunction_FastCallKeywords(func, stack, nargs, kwnames);
       }
       else {
           x = _PyObject_FastCallKeywords(func, stack, nargs, kwnames);
       }
       Py_DECREF(func);
   }

   assert((x != NULL) ^ (PyErr_Occurred() != NULL));

   /* Clear the stack of the function object. */
   while ((*pp_stack) > pfunc) {
       w = EXT_POP(*pp_stack);
       Py_DECREF(w);
   }

   return x;
}
  • 这段代码比较复杂,在这里他检查了关键字,并调用了PyFuncitonObject对象,实际上就是调用了函数。
  • 最后CALL_FUNCTION会在结束后弹出栈顶对应参数数量的元素,但是函数不会被弹出栈,因此最后有一个POP_TOP用于弹出栈顶元素。
4. 更多一般指令
指令 作用
NOP 占位操作
POP_TOP 弹出栈顶元素
LOAD_CONST 将读取的值推入栈
LOAD_GLOBAL 将全局变量对象压入栈顶
STORE_FAST 将栈顶指令存入对应局部变量
COMPARE_OP 比较操作符
CALL_FUNCTION 调用函数
BUILD_SLICE 调用切片,跟的参数为切片的值的个数
JUMP_ABSOLUTE 向下跳转几句操作符,变量为跳转偏移量
UNARY_POSITIVE 实现 Val1 = +Val1
UNARY_NEGATIVE 实现 Val1 = -Val1
UNARY_NOT 实现 Val1 = not Val1
UNARY_INVERT 实现 Val1 = ~Val
FOR_ITER for 循环
GET_ITER 获取迭代器(一般后面跟循环)
GET_YIELD_FROM_ITER 获取 yield 生成器
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342