09月19, 2020

56. VM 是如何执行 ByteCode 的?(3)

一行简单的 Python 代码 print("hello world.") 被转换为 ByteCode,第一条指令是:

LOAD_NAME 0

0 是 “print” 在 names 中的索引。这条指令将 print 函数对象压入 VM 执行栈,我们估计下来该 “hello world.” 出场了,然后让我们一口气把剩下的指令处理完,拭目以待把。

压入 String Object

当 print 函数入栈之后,继续处理下一个 ByteCode,此时 opcode 为 100:

#define LOAD_CONST              100

显然,这是一个加载 CONST 的指令,对应的 oparg 是 0,所有的常量都在 consts 元组中,consts[0] 正是 "hello world.",一个 String Object。

    // 查找 consts[0]
    *value = GETITEM(consts, oparg);

    // 增加引用计数
    Py_INCREF(value);

    // 压入栈
    PUSH(value);

此时运行栈看起来是这样:

image.png

注意:实际上栈元素是 PyObject 指针,图示只是为了方便,将所有对象的内容显示在栈中。

调用函数

参数已经顺利的入栈了,下面继续下一个指令,opcode 为 131, oparg 为 1。

#define CALL_FUNCTION           131

oparg 表示函数的参数数量为 1,只有一个参数。此时栈中保存着函数调用所需要的函数对象、参数们。实际的函数调用由下面的函数承担:

// Python/ceval.c:4949

Py_LOCAL_INLINE(PyObject *) _Py_HOT_FUNCTION
call_function(PyThreadState *tstate, PyObject ***pp_stack, Py_ssize_t oparg, PyObject *kwnames)
{
    // ··· ···

    // 调用函数
        x = _PyObject_Vectorcall(func, stack, nargs | PY_VECTORCALL_ARGUMENTS_OFFSET, kwnames);
    // ··· ···

    // 弹出函数对象和所有参数
    while ((*pp_stack) > pfunc) {
        w = EXT_POP(*pp_stack);
        Py_DECREF(w);
    }

    return x;
}

CALL_FUNCTION 指令会首先执行调用栈上指定的函数,当 _PyObject_Vectorcall 返回的时候,函数就已经执行完毕,我们也得到了 “hello world.” 的输出;函数执行完后,立刻清理函数自身和所有参数。

特别注意函数的返回值 x,它是目标函数的 “返回值”,可以看到 Python 函数实际上只能返回 1 个对象,多个返回值实际上是通过其他技术实现的(打包为 tuple)。函数返回值 x 后面还有其他用处,这里也作为 call_function 的返回值,等待后续处理。

call_function 中执行了运行栈中指定的函数,并且返回了最终的返回值。print 函数的返回值是 None,这也正是 call_function 的返回值。随后 Python 立刻将返回值入栈,如果后面还有其他操作,就可以继续使用这个返回值了。

在此期间运行栈经过这样的变化:

image.png

后续就是 Python 的一些收尾工作,不再赘述,之后我们会更详细的分析每一步中发生的细节。

总结

至此,我们可以对 Python 的具体执行有了直观的了解:

image.png

我们不由的会萌生更多的疑问:

  • Python 代码如何转换为 AST,
  • CodeObject 是如何生成的?
  • PyFrameObject 与 CodeObject 又有着怎样的关联,
  • Python 的面向对象、闭包、协程、线程在最根本上是如何实现的?
  • GPL 到底对 Python 有着怎样的意义。

每一个问题都直接决定于 Python 的本质是什么,搞定这些疑问,就算对 Python 有了相当的了解了。

北京城不是一天建成的,路还是要一步一步走,耐住性子一起继续努力吧。

本文链接:http://www.thinkinpython.com/post/deep_python_vm_56.html

-- EOF --