指令初探

我们将探寻 cpython/Python/ceval.c 文件,来了解这些指令的执行过程。

 1           0 LOAD_CONST               0 (1)
             2 STORE_NAME               0 (a)

在ceval.c文件中,有一个switch入口,并通过宏定义TARGET()来确定执行op,向上看,我们会发现switch被包含在一个for(;;)循环语句中,这就是Python执行字节码的主控逻辑,Python将不断执行字节码,直到结束。

/* File: cpython/Python/ceval.c */ 
// ...
        switch (opcode) {

        /* BEWARE!
           It is essential that any operation that fails sets either
           x to NULL, err to nonzero, or why to anything but WHY_NOT,
           and that no operation that succeeds does this! */

        TARGET(NOP)
            FAST_DISPATCH();
            // ...

LOAD_CONST

我们查询第一条指令 LOAD_CONST 他在ceval.c中的执行代码如下

/* File: cpython/Python/ceval.c */ 
// ...
        TARGET(LOAD_CONST) {
            PyObject *value = GETITEM(consts, oparg);
            Py_INCREF(value);
            PUSH(value);
            FAST_DISPATCH();
        }
// ...
  1. 首先通过GETITEM()获得常数对象,存在value中,value是一个PyObject。
  2. 接着把value对应的ref增加了1
  3. 把value push到栈里
  4. 执行结束,跳转到下一个指令

这里1,2步我们会在第二章PyObject深入探讨,暂时可以理解为: 我们创建了一个PyObject的类用来保存常数1,并把它的引用计数加1。我们重点探讨第3步。

Value Stack

在Python运行过程中,python虚拟机(可以理解为一坨C实现的代码)维护了一个栈,用来保存运行时各个状态。

LOAD_CONST 指令的value push过程

这里我们在栈上新开一个空间,是一个PyObject的指针,这个指针指向通过GETITEM()创建的常数1(PyObject对象)。

Note: Value Stack本身并不存储Object,而是存储了各个对象的指针,对于CPython而言,Value Stack的基本存储单元就是C指针。这个设定保证了Value Stack的地址偏移是固定的。

看起来非常简单。

STORE_NAME

接下来我们考虑 STORE_NAME的过程,类似的,我们参考ceval.c文件中的代码段:

/* File: cpython/Python/ceval.c */ 
// ...        
        TARGET(STORE_NAME) {
            PyObject *name = GETITEM(names, oparg);
            PyObject *v = POP();
            // ...

STORE_NAME的执行如下图所示:

STORE_NAME会创建一个name的对象。接着从Value Stack中pop出指针,并把pop出的指针作为hash(key)的值,存在了对象名索引的hash表中。

至此,Python Interpreter通过 LOAD_CONST向Value Stack中push,STORE_NAME从Value Stack中pop,并存到hash表中,实现了变量的初始化。

举一反三 LOAD_NAME和CALL_FUNCTION

在上文的简单汇编指令中,我们看到这么一段

  4          16 LOAD_NAME                3 (print)
             18 LOAD_NAME                2 (c)
             20 CALL_FUNCTION            1

举一反三,我们从ceval.c的TARGET(LOAD_NAME)以及TARGET(CALL_FUNCTION)代码段查看。

LOAD_NAME 是和STORE_NAME相呼应的指令,通过计算name的hash值,从上文中提到的对象名hash表里,查询对应的PyObject,并把指针push到Value_Stack中。

第二行指令不难理解,是把变量c的值LOAD到VALUE_STACK中,那这里的第一行怎么理解呢?

函数调用指令

其实,在Python中,函数也是一种Object,他继承自PyObject,因此LOAD_NAME指令的name如果是一个函数名,也可以通过对象名hash表,查到原生的print函数对象,并把这个对象压栈。

现在,我们的栈里有两个PyObject指针了,分别是print函数的对象指针和变量c的指针。

我们执行CALL_FUNCTION指令,类似的,参考ceval.c,他从oparg中获取参数个数,从栈中把指针上移,定位到函数名,接着调用 _PyCFunction_FastCallKeywords (Python3.7中的函数调用,Python2.7函数名不同),得到函数执行的结果,并把结果压栈。

results matching ""

    No results matching ""