Hướng dẫn how does python execute bytecode? - python thực thi bytecode như thế nào?

Nếu bạn muốn xem mã byte của một số mã [cho dù mã nguồn, đối tượng hàm trực tiếp hoặc đối tượng mã, v.v.], mô -đun

// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
5 sẽ cho bạn biết chính xác những gì bạn cần. Ví dụ:

>>> dis.dis['i/3']
  1           0 LOAD_NAME                0 [i]
              3 LOAD_CONST               0 [3]
              6 BINARY_TRUE_DIVIDE
              7 RETURN_VALUE

Các tài liệu

// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
5 giải thích mỗi mã byte có nghĩa là gì. Ví dụ:
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
7:

Đẩy giá trị liên quan đến

// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
8 lên ngăn xếp.

Để hiểu điều này, bạn phải biết rằng trình thông dịch bytecode là một máy ngăn xếp ảo và

// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
9 là gì. Các tài liệu mô -đun
struct PyCodeObject {
    PyObject_HEAD
    int co_argcount;            /* #arguments, except *args */
    int co_posonlyargcount;     /* #positional only arguments */
    int co_kwonlyargcount;      /* #keyword only arguments */
    int co_nlocals;             /* #local variables */
    int co_stacksize;           /* #entries needed for evaluation stack */
    int co_flags;               /* CO_..., see below */
    int co_firstlineno;         /* first source line number */
    PyObject *co_code;          /* instruction opcodes */
    PyObject *co_consts;        /* list [constants used] */
    PyObject *co_names;         /* list of strings [names used] */
    PyObject *co_varnames;      /* tuple of strings [local variable names] */
    PyObject *co_freevars;      /* tuple of strings [free variable names] */
    PyObject *co_cellvars;      /* tuple of strings [cell variable names] */
    /* The rest aren't used in either hash or comparisons, except for co_name,
       used in both. This is done to preserve the name and line number
       for tracebacks and debuggers; otherwise, constant de-duplication
       would collapse identical functions/lambdas defined on different lines.
    */
    Py_ssize_t *co_cell2arg;    /* Maps cell vars which are arguments. */
    PyObject *co_filename;      /* unicode [where it was loaded from] */
    PyObject *co_name;          /* unicode [name, for reference] */
    PyObject *co_lnotab;        /* string [encoding addrlineno mapping] See
                                   Objects/lnotab_notes.txt for details. */
    void *co_zombieframe;       /* for optimization only [see frameobject.c] */
    PyObject *co_weakreflist;   /* to support weakrefs to code objects */
    /* Scratch space for extra data relating to the code object.
       Type is a void* to keep the format private in codeobject.c to force
       people to go through the proper APIs. */
    void *co_extra;

    /* Per opcodes just-in-time cache
     *
     * To reduce cache size, we use indirect mapping from opcode index to
     * cache object:
     *   cache = co_opcache[co_opcache_map[next_instr - first_instr] - 1]
     */

    // co_opcache_map is indexed by [next_instr - first_instr].
    //  * 0 means there is no cache for this opcode.
    //  * n > 0 means there is cache in co_opcache[n-1].
    unsigned char *co_opcache_map;
    _PyOpcache *co_opcache;
    int co_opcache_flag;  // used to determine when create a cache.
    unsigned char co_opcache_size;  // length of co_opcache.
};
0 có một bảng đẹp cho thấy các thuộc tính quan trọng nhất của các đối tượng nội bộ quan trọng nhất, vì vậy bạn có thể thấy rằng
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
9 là một thuộc tính của các đối tượng ____22 chứa một bộ biến của các biến cục bộ. Nói cách khác,
struct PyCodeObject {
    PyObject_HEAD
    int co_argcount;            /* #arguments, except *args */
    int co_posonlyargcount;     /* #positional only arguments */
    int co_kwonlyargcount;      /* #keyword only arguments */
    int co_nlocals;             /* #local variables */
    int co_stacksize;           /* #entries needed for evaluation stack */
    int co_flags;               /* CO_..., see below */
    int co_firstlineno;         /* first source line number */
    PyObject *co_code;          /* instruction opcodes */
    PyObject *co_consts;        /* list [constants used] */
    PyObject *co_names;         /* list of strings [names used] */
    PyObject *co_varnames;      /* tuple of strings [local variable names] */
    PyObject *co_freevars;      /* tuple of strings [free variable names] */
    PyObject *co_cellvars;      /* tuple of strings [cell variable names] */
    /* The rest aren't used in either hash or comparisons, except for co_name,
       used in both. This is done to preserve the name and line number
       for tracebacks and debuggers; otherwise, constant de-duplication
       would collapse identical functions/lambdas defined on different lines.
    */
    Py_ssize_t *co_cell2arg;    /* Maps cell vars which are arguments. */
    PyObject *co_filename;      /* unicode [where it was loaded from] */
    PyObject *co_name;          /* unicode [name, for reference] */
    PyObject *co_lnotab;        /* string [encoding addrlineno mapping] See
                                   Objects/lnotab_notes.txt for details. */
    void *co_zombieframe;       /* for optimization only [see frameobject.c] */
    PyObject *co_weakreflist;   /* to support weakrefs to code objects */
    /* Scratch space for extra data relating to the code object.
       Type is a void* to keep the format private in codeobject.c to force
       people to go through the proper APIs. */
    void *co_extra;

    /* Per opcodes just-in-time cache
     *
     * To reduce cache size, we use indirect mapping from opcode index to
     * cache object:
     *   cache = co_opcache[co_opcache_map[next_instr - first_instr] - 1]
     */

    // co_opcache_map is indexed by [next_instr - first_instr].
    //  * 0 means there is no cache for this opcode.
    //  * n > 0 means there is cache in co_opcache[n-1].
    unsigned char *co_opcache_map;
    _PyOpcache *co_opcache;
    int co_opcache_flag;  // used to determine when create a cache.
    unsigned char co_opcache_size;  // length of co_opcache.
};
3 đẩy giá trị liên quan đến biến cục bộ 0 [và
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
5 nhìn một cách hữu ích và thấy rằng biến cục bộ thứ 0 được đặt tên là
struct PyCodeObject {
    PyObject_HEAD
    int co_argcount;            /* #arguments, except *args */
    int co_posonlyargcount;     /* #positional only arguments */
    int co_kwonlyargcount;      /* #keyword only arguments */
    int co_nlocals;             /* #local variables */
    int co_stacksize;           /* #entries needed for evaluation stack */
    int co_flags;               /* CO_..., see below */
    int co_firstlineno;         /* first source line number */
    PyObject *co_code;          /* instruction opcodes */
    PyObject *co_consts;        /* list [constants used] */
    PyObject *co_names;         /* list of strings [names used] */
    PyObject *co_varnames;      /* tuple of strings [local variable names] */
    PyObject *co_freevars;      /* tuple of strings [free variable names] */
    PyObject *co_cellvars;      /* tuple of strings [cell variable names] */
    /* The rest aren't used in either hash or comparisons, except for co_name,
       used in both. This is done to preserve the name and line number
       for tracebacks and debuggers; otherwise, constant de-duplication
       would collapse identical functions/lambdas defined on different lines.
    */
    Py_ssize_t *co_cell2arg;    /* Maps cell vars which are arguments. */
    PyObject *co_filename;      /* unicode [where it was loaded from] */
    PyObject *co_name;          /* unicode [name, for reference] */
    PyObject *co_lnotab;        /* string [encoding addrlineno mapping] See
                                   Objects/lnotab_notes.txt for details. */
    void *co_zombieframe;       /* for optimization only [see frameobject.c] */
    PyObject *co_weakreflist;   /* to support weakrefs to code objects */
    /* Scratch space for extra data relating to the code object.
       Type is a void* to keep the format private in codeobject.c to force
       people to go through the proper APIs. */
    void *co_extra;

    /* Per opcodes just-in-time cache
     *
     * To reduce cache size, we use indirect mapping from opcode index to
     * cache object:
     *   cache = co_opcache[co_opcache_map[next_instr - first_instr] - 1]
     */

    // co_opcache_map is indexed by [next_instr - first_instr].
    //  * 0 means there is no cache for this opcode.
    //  * n > 0 means there is cache in co_opcache[n-1].
    unsigned char *co_opcache_map;
    _PyOpcache *co_opcache;
    int co_opcache_flag;  // used to determine when create a cache.
    unsigned char co_opcache_size;  // length of co_opcache.
};
5].

Và điều đó là đủ để thấy rằng một chuỗi các byte không đủ; Trình thông dịch cũng cần các thuộc tính khác của đối tượng mã và trong một số trường hợp, các thuộc tính của đối tượng hàm [cũng là nơi môi trường địa phương và toàn cầu đến từ].

Mô -đun

struct PyCodeObject {
    PyObject_HEAD
    int co_argcount;            /* #arguments, except *args */
    int co_posonlyargcount;     /* #positional only arguments */
    int co_kwonlyargcount;      /* #keyword only arguments */
    int co_nlocals;             /* #local variables */
    int co_stacksize;           /* #entries needed for evaluation stack */
    int co_flags;               /* CO_..., see below */
    int co_firstlineno;         /* first source line number */
    PyObject *co_code;          /* instruction opcodes */
    PyObject *co_consts;        /* list [constants used] */
    PyObject *co_names;         /* list of strings [names used] */
    PyObject *co_varnames;      /* tuple of strings [local variable names] */
    PyObject *co_freevars;      /* tuple of strings [free variable names] */
    PyObject *co_cellvars;      /* tuple of strings [cell variable names] */
    /* The rest aren't used in either hash or comparisons, except for co_name,
       used in both. This is done to preserve the name and line number
       for tracebacks and debuggers; otherwise, constant de-duplication
       would collapse identical functions/lambdas defined on different lines.
    */
    Py_ssize_t *co_cell2arg;    /* Maps cell vars which are arguments. */
    PyObject *co_filename;      /* unicode [where it was loaded from] */
    PyObject *co_name;          /* unicode [name, for reference] */
    PyObject *co_lnotab;        /* string [encoding addrlineno mapping] See
                                   Objects/lnotab_notes.txt for details. */
    void *co_zombieframe;       /* for optimization only [see frameobject.c] */
    PyObject *co_weakreflist;   /* to support weakrefs to code objects */
    /* Scratch space for extra data relating to the code object.
       Type is a void* to keep the format private in codeobject.c to force
       people to go through the proper APIs. */
    void *co_extra;

    /* Per opcodes just-in-time cache
     *
     * To reduce cache size, we use indirect mapping from opcode index to
     * cache object:
     *   cache = co_opcache[co_opcache_map[next_instr - first_instr] - 1]
     */

    // co_opcache_map is indexed by [next_instr - first_instr].
    //  * 0 means there is no cache for this opcode.
    //  * n > 0 means there is cache in co_opcache[n-1].
    unsigned char *co_opcache_map;
    _PyOpcache *co_opcache;
    int co_opcache_flag;  // used to determine when create a cache.
    unsigned char co_opcache_size;  // length of co_opcache.
};
0 cũng có một số công cụ có thể giúp bạn tiếp tục điều tra mã trực tiếp.

Điều này là đủ để tìm ra rất nhiều thứ thú vị. Ví dụ: bạn có thể biết rằng Python tìm ra tại thời điểm biên dịch cho dù một biến trong một hàm là cục bộ, đóng hoặc toàn cầu, dựa trên việc bạn có gán cho nó ở bất cứ đâu trong cơ thể chức năng [và trên bất kỳ câu lệnh

struct PyCodeObject {
    PyObject_HEAD
    int co_argcount;            /* #arguments, except *args */
    int co_posonlyargcount;     /* #positional only arguments */
    int co_kwonlyargcount;      /* #keyword only arguments */
    int co_nlocals;             /* #local variables */
    int co_stacksize;           /* #entries needed for evaluation stack */
    int co_flags;               /* CO_..., see below */
    int co_firstlineno;         /* first source line number */
    PyObject *co_code;          /* instruction opcodes */
    PyObject *co_consts;        /* list [constants used] */
    PyObject *co_names;         /* list of strings [names used] */
    PyObject *co_varnames;      /* tuple of strings [local variable names] */
    PyObject *co_freevars;      /* tuple of strings [free variable names] */
    PyObject *co_cellvars;      /* tuple of strings [cell variable names] */
    /* The rest aren't used in either hash or comparisons, except for co_name,
       used in both. This is done to preserve the name and line number
       for tracebacks and debuggers; otherwise, constant de-duplication
       would collapse identical functions/lambdas defined on different lines.
    */
    Py_ssize_t *co_cell2arg;    /* Maps cell vars which are arguments. */
    PyObject *co_filename;      /* unicode [where it was loaded from] */
    PyObject *co_name;          /* unicode [name, for reference] */
    PyObject *co_lnotab;        /* string [encoding addrlineno mapping] See
                                   Objects/lnotab_notes.txt for details. */
    void *co_zombieframe;       /* for optimization only [see frameobject.c] */
    PyObject *co_weakreflist;   /* to support weakrefs to code objects */
    /* Scratch space for extra data relating to the code object.
       Type is a void* to keep the format private in codeobject.c to force
       people to go through the proper APIs. */
    void *co_extra;

    /* Per opcodes just-in-time cache
     *
     * To reduce cache size, we use indirect mapping from opcode index to
     * cache object:
     *   cache = co_opcache[co_opcache_map[next_instr - first_instr] - 1]
     */

    // co_opcache_map is indexed by [next_instr - first_instr].
    //  * 0 means there is no cache for this opcode.
    //  * n > 0 means there is cache in co_opcache[n-1].
    unsigned char *co_opcache_map;
    _PyOpcache *co_opcache;
    int co_opcache_flag;  // used to determine when create a cache.
    unsigned char co_opcache_size;  // length of co_opcache.
};
7 hoặc
struct PyCodeObject {
    PyObject_HEAD
    int co_argcount;            /* #arguments, except *args */
    int co_posonlyargcount;     /* #positional only arguments */
    int co_kwonlyargcount;      /* #keyword only arguments */
    int co_nlocals;             /* #local variables */
    int co_stacksize;           /* #entries needed for evaluation stack */
    int co_flags;               /* CO_..., see below */
    int co_firstlineno;         /* first source line number */
    PyObject *co_code;          /* instruction opcodes */
    PyObject *co_consts;        /* list [constants used] */
    PyObject *co_names;         /* list of strings [names used] */
    PyObject *co_varnames;      /* tuple of strings [local variable names] */
    PyObject *co_freevars;      /* tuple of strings [free variable names] */
    PyObject *co_cellvars;      /* tuple of strings [cell variable names] */
    /* The rest aren't used in either hash or comparisons, except for co_name,
       used in both. This is done to preserve the name and line number
       for tracebacks and debuggers; otherwise, constant de-duplication
       would collapse identical functions/lambdas defined on different lines.
    */
    Py_ssize_t *co_cell2arg;    /* Maps cell vars which are arguments. */
    PyObject *co_filename;      /* unicode [where it was loaded from] */
    PyObject *co_name;          /* unicode [name, for reference] */
    PyObject *co_lnotab;        /* string [encoding addrlineno mapping] See
                                   Objects/lnotab_notes.txt for details. */
    void *co_zombieframe;       /* for optimization only [see frameobject.c] */
    PyObject *co_weakreflist;   /* to support weakrefs to code objects */
    /* Scratch space for extra data relating to the code object.
       Type is a void* to keep the format private in codeobject.c to force
       people to go through the proper APIs. */
    void *co_extra;

    /* Per opcodes just-in-time cache
     *
     * To reduce cache size, we use indirect mapping from opcode index to
     * cache object:
     *   cache = co_opcache[co_opcache_map[next_instr - first_instr] - 1]
     */

    // co_opcache_map is indexed by [next_instr - first_instr].
    //  * 0 means there is no cache for this opcode.
    //  * n > 0 means there is cache in co_opcache[n-1].
    unsigned char *co_opcache_map;
    _PyOpcache *co_opcache;
    int co_opcache_flag;  // used to determine when create a cache.
    unsigned char co_opcache_size;  // length of co_opcache.
};
8 nào]; Nếu bạn viết ba chức năng khác nhau và so sánh sự tháo gỡ của chúng [và các thuộc tính khác có liên quan], bạn có thể dễ dàng tìm ra chính xác những gì nó phải làm.

.

Để hiểu cách giải thích mã byte và cách thức hoạt động của máy Stack [trong CPython], bạn cần xem mã nguồn

struct PyCodeObject {
    PyObject_HEAD
    int co_argcount;            /* #arguments, except *args */
    int co_posonlyargcount;     /* #positional only arguments */
    int co_kwonlyargcount;      /* #keyword only arguments */
    int co_nlocals;             /* #local variables */
    int co_stacksize;           /* #entries needed for evaluation stack */
    int co_flags;               /* CO_..., see below */
    int co_firstlineno;         /* first source line number */
    PyObject *co_code;          /* instruction opcodes */
    PyObject *co_consts;        /* list [constants used] */
    PyObject *co_names;         /* list of strings [names used] */
    PyObject *co_varnames;      /* tuple of strings [local variable names] */
    PyObject *co_freevars;      /* tuple of strings [free variable names] */
    PyObject *co_cellvars;      /* tuple of strings [cell variable names] */
    /* The rest aren't used in either hash or comparisons, except for co_name,
       used in both. This is done to preserve the name and line number
       for tracebacks and debuggers; otherwise, constant de-duplication
       would collapse identical functions/lambdas defined on different lines.
    */
    Py_ssize_t *co_cell2arg;    /* Maps cell vars which are arguments. */
    PyObject *co_filename;      /* unicode [where it was loaded from] */
    PyObject *co_name;          /* unicode [name, for reference] */
    PyObject *co_lnotab;        /* string [encoding addrlineno mapping] See
                                   Objects/lnotab_notes.txt for details. */
    void *co_zombieframe;       /* for optimization only [see frameobject.c] */
    PyObject *co_weakreflist;   /* to support weakrefs to code objects */
    /* Scratch space for extra data relating to the code object.
       Type is a void* to keep the format private in codeobject.c to force
       people to go through the proper APIs. */
    void *co_extra;

    /* Per opcodes just-in-time cache
     *
     * To reduce cache size, we use indirect mapping from opcode index to
     * cache object:
     *   cache = co_opcache[co_opcache_map[next_instr - first_instr] - 1]
     */

    // co_opcache_map is indexed by [next_instr - first_instr].
    //  * 0 means there is no cache for this opcode.
    //  * n > 0 means there is cache in co_opcache[n-1].
    unsigned char *co_opcache_map;
    _PyOpcache *co_opcache;
    int co_opcache_flag;  // used to determine when create a cache.
    unsigned char co_opcache_size;  // length of co_opcache.
};
9. Các câu trả lời của Thy435 và Eyquem đã đề cập đến điều này.

Hiểu cách các tệp

PyObject*
_PyEval_EvalFrameDefault[PyThreadState *tstate, PyFrameObject *f, int throwflag]
{
    // ... declarations and initialization of local variables
    // ... macros definitions
    // ... call depth handling
    // ... code for tracing and profiling

    for [;;] {
        // ... check if the bytecode execution must be suspended,
        // e.g. other thread requested the GIL

        // NEXTOPARG[] macro
        _Py_CODEUNIT word = *next_instr; // _Py_CODEUNIT is a typedef for uint16_t
        opcode = _Py_OPCODE[word];
        oparg = _Py_OPARG[word];
        next_instr++;

        switch [opcode] {
            case TARGET[NOP] {
                FAST_DISPATCH[]; // more on this later
            }

            case TARGET[LOAD_FAST] {
                // ... code for loading local variable
            }

            // ... 117 more cases for every possible opcode
        }

        // ... error handling
    }

    // ... termination
}
0 được đọc chỉ cần thêm một chút thông tin. Ned Batchelder có một bài đăng trên blog tuyệt vời [nếu hơi lỗi] được gọi là cấu trúc của các tệp .pyc, bao gồm tất cả các bộ phận khó khăn và không được ghi nhận. .

Để hiểu làm thế nào nguồn được biên dịch cho mã byte, đó là phần thú vị.

Thiết kế trình biên dịch của Cpython giải thích cách mọi thứ hoạt động. [Một số phần khác của hướng dẫn nhà phát triển Python cũng hữu ích.]

Đối với những thứ ban đầu, việc nói và phân tích cú pháp, bạn chỉ có thể sử dụng mô -đun

PyObject*
_PyEval_EvalFrameDefault[PyThreadState *tstate, PyFrameObject *f, int throwflag]
{
    // ... declarations and initialization of local variables
    // ... macros definitions
    // ... call depth handling
    // ... code for tracing and profiling

    for [;;] {
        // ... check if the bytecode execution must be suspended,
        // e.g. other thread requested the GIL

        // NEXTOPARG[] macro
        _Py_CODEUNIT word = *next_instr; // _Py_CODEUNIT is a typedef for uint16_t
        opcode = _Py_OPCODE[word];
        oparg = _Py_OPARG[word];
        next_instr++;

        switch [opcode] {
            case TARGET[NOP] {
                FAST_DISPATCH[]; // more on this later
            }

            case TARGET[LOAD_FAST] {
                // ... code for loading local variable
            }

            // ... 117 more cases for every possible opcode
        }

        // ... error handling
    }

    // ... termination
}
3 để nhảy ngay đến điểm mà đã đến lúc thực hiện biên dịch thực tế. Sau đó, xem
PyObject*
_PyEval_EvalFrameDefault[PyThreadState *tstate, PyFrameObject *f, int throwflag]
{
    // ... declarations and initialization of local variables
    // ... macros definitions
    // ... call depth handling
    // ... code for tracing and profiling

    for [;;] {
        // ... check if the bytecode execution must be suspended,
        // e.g. other thread requested the GIL

        // NEXTOPARG[] macro
        _Py_CODEUNIT word = *next_instr; // _Py_CODEUNIT is a typedef for uint16_t
        opcode = _Py_OPCODE[word];
        oparg = _Py_OPARG[word];
        next_instr++;

        switch [opcode] {
            case TARGET[NOP] {
                FAST_DISPATCH[]; // more on this later
            }

            case TARGET[LOAD_FAST] {
                // ... code for loading local variable
            }

            // ... 117 more cases for every possible opcode
        }

        // ... error handling
    }

    // ... termination
}
4 để biết cách mà AST bị biến thành mã byte.

Các macro có thể hơi khó khăn để làm việc, nhưng một khi bạn nắm bắt được ý tưởng về cách trình biên dịch sử dụng một ngăn xếp để hạ xuống các khối, và cách nó sử dụng các

PyObject*
_PyEval_EvalFrameDefault[PyThreadState *tstate, PyFrameObject *f, int throwflag]
{
    // ... declarations and initialization of local variables
    // ... macros definitions
    // ... call depth handling
    // ... code for tracing and profiling

    for [;;] {
        // ... check if the bytecode execution must be suspended,
        // e.g. other thread requested the GIL

        // NEXTOPARG[] macro
        _Py_CODEUNIT word = *next_instr; // _Py_CODEUNIT is a typedef for uint16_t
        opcode = _Py_OPCODE[word];
        oparg = _Py_OPARG[word];
        next_instr++;

        switch [opcode] {
            case TARGET[NOP] {
                FAST_DISPATCH[]; // more on this later
            }

            case TARGET[LOAD_FAST] {
                // ... code for loading local variable
            }

            // ... 117 more cases for every possible opcode
        }

        // ... error handling
    }

    // ... termination
}
5 và bạn bè để phát ra các byte ở cấp độ hiện tại, tất cả đều có ý nghĩa.

Một điều khiến hầu hết mọi người ngạc nhiên là cách hoạt động của chức năng. Cơ thể của định nghĩa chức năng được biên dịch thành một đối tượng mã. Sau đó, định nghĩa hàm được biên dịch thành mã [bên trong phần thân hàm, mô -đun, v.v.], khi được thực thi, xây dựng một đối tượng hàm từ đối tượng mã đó. .

Và bây giờ bạn đã sẵn sàng để bắt đầu vá Cpython để thêm các câu lệnh của riêng bạn, phải không? Chà, khi thay đổi các chương trình ngữ pháp của Cpython, có rất nhiều thứ để hiểu đúng [và thậm chí còn nhiều hơn nếu bạn cần tạo mã hóa mới]. Bạn có thể thấy dễ dàng hơn khi học Pypy cũng như CPython và bắt đầu hack trên Pypy trước và chỉ quay lại CPython một khi bạn biết rằng những gì bạn đang làm là hợp lý và có thể thực hiện được.

Chúng tôi đã bắt đầu loạt bài này với một cái nhìn tổng quan về VM Cpython. Chúng tôi đã học được rằng để chạy một chương trình Python, Cpython trước tiên biên dịch nó thành mã byte và chúng tôi đã nghiên cứu cách trình biên dịch hoạt động trong phần hai. Lần trước chúng tôi đã bước qua mã nguồn cpython bắt đầu với hàm

PyObject*
_PyEval_EvalFrameDefault[PyThreadState *tstate, PyFrameObject *f, int throwflag]
{
    // ... declarations and initialization of local variables
    // ... macros definitions
    // ... call depth handling
    // ... code for tracing and profiling

    for [;;] {
        // ... check if the bytecode execution must be suspended,
        // e.g. other thread requested the GIL

        // NEXTOPARG[] macro
        _Py_CODEUNIT word = *next_instr; // _Py_CODEUNIT is a typedef for uint16_t
        opcode = _Py_OPCODE[word];
        oparg = _Py_OPARG[word];
        next_instr++;

        switch [opcode] {
            case TARGET[NOP] {
                FAST_DISPATCH[]; // more on this later
            }

            case TARGET[LOAD_FAST] {
                // ... code for loading local variable
            }

            // ... 117 more cases for every possible opcode
        }

        // ... error handling
    }

    // ... termination
}
6 cho đến khi chúng tôi đạt được vòng lặp đánh giá, một nơi mà mã byte Python được thực thi. Lý do chính tại sao chúng tôi dành thời gian nghiên cứu những điều này là để chuẩn bị cho cuộc thảo luận mà chúng tôi bắt đầu ngày hôm nay. Mục tiêu của cuộc thảo luận này là để hiểu cách CPython làm những gì chúng ta bảo nó làm, đó là cách nó thực thi mã byte mà mã chúng ta viết biên dịch.

Lưu ý: Trong bài đăng này, tôi đang đề cập đến CPython 3.9. Một số chi tiết thực hiện chắc chắn sẽ thay đổi khi CPython phát triển. Tôi sẽ cố gắng theo dõi các thay đổi quan trọng và thêm ghi chú cập nhật.: In this post I'm referring to CPython 3.9. Some implementation details will certainly change as CPython evolves. I'll try to keep track of important changes and add update notes.

Điểm khởi đầu

Hãy nhớ lại ngắn gọn những gì chúng ta đã học được trong các phần trước. Chúng tôi nói với Cpython phải làm gì bằng cách viết mã Python. Tuy nhiên, VM Cpython chỉ hiểu mã Python byte. Đây là công việc của trình biên dịch để dịch mã python sang mã byte. Trình biên dịch lưu trữ mã byte trong một đối tượng mã, là một cấu trúc mô tả đầy đủ những gì một khối mã, như một mô -đun hoặc một hàm, làm. Để thực thi một đối tượng mã, trước tiên, CPython tạo ra trạng thái thực thi cho nó được gọi là đối tượng khung. Sau đó, nó chuyển một đối tượng khung cho chức năng đánh giá khung để thực hiện tính toán thực tế. Hàm đánh giá khung mặc định là

PyObject*
_PyEval_EvalFrameDefault[PyThreadState *tstate, PyFrameObject *f, int throwflag]
{
    // ... declarations and initialization of local variables
    // ... macros definitions
    // ... call depth handling
    // ... code for tracing and profiling

    for [;;] {
        // ... check if the bytecode execution must be suspended,
        // e.g. other thread requested the GIL

        // NEXTOPARG[] macro
        _Py_CODEUNIT word = *next_instr; // _Py_CODEUNIT is a typedef for uint16_t
        opcode = _Py_OPCODE[word];
        oparg = _Py_OPARG[word];
        next_instr++;

        switch [opcode] {
            case TARGET[NOP] {
                FAST_DISPATCH[]; // more on this later
            }

            case TARGET[LOAD_FAST] {
                // ... code for loading local variable
            }

            // ... 117 more cases for every possible opcode
        }

        // ... error handling
    }

    // ... termination
}
7 được xác định trong Python/ceval.c. Hàm này thực hiện lõi của VM Cpython. Cụ thể, nó thực hiện logic để thực hiện mã byte Python. Vì vậy, chức năng này là những gì chúng ta sẽ nghiên cứu ngày hôm nay.

Để hiểu cách

PyObject*
_PyEval_EvalFrameDefault[PyThreadState *tstate, PyFrameObject *f, int throwflag]
{
    // ... declarations and initialization of local variables
    // ... macros definitions
    // ... call depth handling
    // ... code for tracing and profiling

    for [;;] {
        // ... check if the bytecode execution must be suspended,
        // e.g. other thread requested the GIL

        // NEXTOPARG[] macro
        _Py_CODEUNIT word = *next_instr; // _Py_CODEUNIT is a typedef for uint16_t
        opcode = _Py_OPCODE[word];
        oparg = _Py_OPARG[word];
        next_instr++;

        switch [opcode] {
            case TARGET[NOP] {
                FAST_DISPATCH[]; // more on this later
            }

            case TARGET[LOAD_FAST] {
                // ... code for loading local variable
            }

            // ... 117 more cases for every possible opcode
        }

        // ... error handling
    }

    // ... termination
}
7 hoạt động, điều quan trọng là phải có ý tưởng về đầu vào của nó, một đối tượng khung, là gì. Một đối tượng khung là một đối tượng Python được xác định bởi cấu trúc C sau:

// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};

Trường

PyObject*
_PyEval_EvalFrameDefault[PyThreadState *tstate, PyFrameObject *f, int throwflag]
{
    // ... declarations and initialization of local variables
    // ... macros definitions
    // ... call depth handling
    // ... code for tracing and profiling

    for [;;] {
        // ... check if the bytecode execution must be suspended,
        // e.g. other thread requested the GIL

        // NEXTOPARG[] macro
        _Py_CODEUNIT word = *next_instr; // _Py_CODEUNIT is a typedef for uint16_t
        opcode = _Py_OPCODE[word];
        oparg = _Py_OPARG[word];
        next_instr++;

        switch [opcode] {
            case TARGET[NOP] {
                FAST_DISPATCH[]; // more on this later
            }

            case TARGET[LOAD_FAST] {
                // ... code for loading local variable
            }

            // ... 117 more cases for every possible opcode
        }

        // ... error handling
    }

    // ... termination
}
9 của đối tượng khung chỉ vào một đối tượng mã. Một đối tượng mã cũng là một đối tượng Python. Đây là định nghĩa của nó:

struct PyCodeObject {
    PyObject_HEAD
    int co_argcount;            /* #arguments, except *args */
    int co_posonlyargcount;     /* #positional only arguments */
    int co_kwonlyargcount;      /* #keyword only arguments */
    int co_nlocals;             /* #local variables */
    int co_stacksize;           /* #entries needed for evaluation stack */
    int co_flags;               /* CO_..., see below */
    int co_firstlineno;         /* first source line number */
    PyObject *co_code;          /* instruction opcodes */
    PyObject *co_consts;        /* list [constants used] */
    PyObject *co_names;         /* list of strings [names used] */
    PyObject *co_varnames;      /* tuple of strings [local variable names] */
    PyObject *co_freevars;      /* tuple of strings [free variable names] */
    PyObject *co_cellvars;      /* tuple of strings [cell variable names] */
    /* The rest aren't used in either hash or comparisons, except for co_name,
       used in both. This is done to preserve the name and line number
       for tracebacks and debuggers; otherwise, constant de-duplication
       would collapse identical functions/lambdas defined on different lines.
    */
    Py_ssize_t *co_cell2arg;    /* Maps cell vars which are arguments. */
    PyObject *co_filename;      /* unicode [where it was loaded from] */
    PyObject *co_name;          /* unicode [name, for reference] */
    PyObject *co_lnotab;        /* string [encoding addrlineno mapping] See
                                   Objects/lnotab_notes.txt for details. */
    void *co_zombieframe;       /* for optimization only [see frameobject.c] */
    PyObject *co_weakreflist;   /* to support weakrefs to code objects */
    /* Scratch space for extra data relating to the code object.
       Type is a void* to keep the format private in codeobject.c to force
       people to go through the proper APIs. */
    void *co_extra;

    /* Per opcodes just-in-time cache
     *
     * To reduce cache size, we use indirect mapping from opcode index to
     * cache object:
     *   cache = co_opcache[co_opcache_map[next_instr - first_instr] - 1]
     */

    // co_opcache_map is indexed by [next_instr - first_instr].
    //  * 0 means there is no cache for this opcode.
    //  * n > 0 means there is cache in co_opcache[n-1].
    unsigned char *co_opcache_map;
    _PyOpcache *co_opcache;
    int co_opcache_flag;  // used to determine when create a cache.
    unsigned char co_opcache_size;  // length of co_opcache.
};

Trường quan trọng nhất của đối tượng mã là

struct _ceval_runtime_state {
    /* Request for checking signals. It is shared by all interpreters [see
       bpo-40513]. Any thread of any interpreter can receive a signal, but only
       the main thread of the main interpreter can handle signals: see
       _Py_ThreadCanHandleSignals[]. */
    _Py_atomic_int signals_pending;
    struct _gil_runtime_state gil;
};
0. Đó là một con trỏ tới đối tượng Byte Python đại diện cho mã byte. Mã byte là một chuỗi các hướng dẫn hai byte: một byte cho một opcode và một byte cho một đối số.

Đừng lo lắng nếu một số thành viên của các cấu trúc trên vẫn là một bí ẩn đối với bạn. Chúng ta sẽ thấy những gì chúng được sử dụng khi chúng ta tiến lên trong nỗ lực của chúng ta để hiểu cách thức VM CPython thực thi mã byte.

Tổng quan về vòng lặp đánh giá

Vấn đề thực hiện mã Bytepy Python có vẻ không có trí tuệ đối với bạn. Thật vậy, tất cả các VM phải làm là lặp lại các hướng dẫn và hành động theo chúng. Và đây là những gì về cơ bản

PyObject*
_PyEval_EvalFrameDefault[PyThreadState *tstate, PyFrameObject *f, int throwflag]
{
    // ... declarations and initialization of local variables
    // ... macros definitions
    // ... call depth handling
    // ... code for tracing and profiling

    for [;;] {
        // ... check if the bytecode execution must be suspended,
        // e.g. other thread requested the GIL

        // NEXTOPARG[] macro
        _Py_CODEUNIT word = *next_instr; // _Py_CODEUNIT is a typedef for uint16_t
        opcode = _Py_OPCODE[word];
        oparg = _Py_OPARG[word];
        next_instr++;

        switch [opcode] {
            case TARGET[NOP] {
                FAST_DISPATCH[]; // more on this later
            }

            case TARGET[LOAD_FAST] {
                // ... code for loading local variable
            }

            // ... 117 more cases for every possible opcode
        }

        // ... error handling
    }

    // ... termination
}
7 làm. Nó chứa một vòng lặp
struct _ceval_runtime_state {
    /* Request for checking signals. It is shared by all interpreters [see
       bpo-40513]. Any thread of any interpreter can receive a signal, but only
       the main thread of the main interpreter can handle signals: see
       _Py_ThreadCanHandleSignals[]. */
    _Py_atomic_int signals_pending;
    struct _gil_runtime_state gil;
};
2 vô hạn mà chúng tôi gọi là vòng đánh giá. Bên trong vòng lặp đó có một tuyên bố khổng lồ
struct _ceval_runtime_state {
    /* Request for checking signals. It is shared by all interpreters [see
       bpo-40513]. Any thread of any interpreter can receive a signal, but only
       the main thread of the main interpreter can handle signals: see
       _Py_ThreadCanHandleSignals[]. */
    _Py_atomic_int signals_pending;
    struct _gil_runtime_state gil;
};
3 trên tất cả các mã hóa có thể. Mỗi opcode có một khối
struct _ceval_runtime_state {
    /* Request for checking signals. It is shared by all interpreters [see
       bpo-40513]. Any thread of any interpreter can receive a signal, but only
       the main thread of the main interpreter can handle signals: see
       _Py_ThreadCanHandleSignals[]. */
    _Py_atomic_int signals_pending;
    struct _gil_runtime_state gil;
};
4 tương ứng chứa mã để thực thi mã hóa đó. Mã byte được biểu thị bằng một mảng các số nguyên không dấu 16 bit, một số nguyên cho mỗi hướng dẫn. VM theo dõi hướng dẫn tiếp theo được thực thi bằng biến
struct _ceval_runtime_state {
    /* Request for checking signals. It is shared by all interpreters [see
       bpo-40513]. Any thread of any interpreter can receive a signal, but only
       the main thread of the main interpreter can handle signals: see
       _Py_ThreadCanHandleSignals[]. */
    _Py_atomic_int signals_pending;
    struct _gil_runtime_state gil;
};
5, đây là một con trỏ tới mảng hướng dẫn. Khi bắt đầu mỗi lần lặp của vòng lặp đánh giá, VM tính toán opcode tiếp theo và đối số của nó bằng cách sử dụng byte ít có ý nghĩa nhất và quan trọng nhất của lệnh tiếp theo và tăng
struct _ceval_runtime_state {
    /* Request for checking signals. It is shared by all interpreters [see
       bpo-40513]. Any thread of any interpreter can receive a signal, but only
       the main thread of the main interpreter can handle signals: see
       _Py_ThreadCanHandleSignals[]. */
    _Py_atomic_int signals_pending;
    struct _gil_runtime_state gil;
};
5. Hàm
PyObject*
_PyEval_EvalFrameDefault[PyThreadState *tstate, PyFrameObject *f, int throwflag]
{
    // ... declarations and initialization of local variables
    // ... macros definitions
    // ... call depth handling
    // ... code for tracing and profiling

    for [;;] {
        // ... check if the bytecode execution must be suspended,
        // e.g. other thread requested the GIL

        // NEXTOPARG[] macro
        _Py_CODEUNIT word = *next_instr; // _Py_CODEUNIT is a typedef for uint16_t
        opcode = _Py_OPCODE[word];
        oparg = _Py_OPARG[word];
        next_instr++;

        switch [opcode] {
            case TARGET[NOP] {
                FAST_DISPATCH[]; // more on this later
            }

            case TARGET[LOAD_FAST] {
                // ... code for loading local variable
            }

            // ... 117 more cases for every possible opcode
        }

        // ... error handling
    }

    // ... termination
}
7 dài gần 3000 dòng, nhưng bản chất của nó có thể được ghi lại bằng phiên bản đơn giản hóa sau:

PyObject*
_PyEval_EvalFrameDefault[PyThreadState *tstate, PyFrameObject *f, int throwflag]
{
    // ... declarations and initialization of local variables
    // ... macros definitions
    // ... call depth handling
    // ... code for tracing and profiling

    for [;;] {
        // ... check if the bytecode execution must be suspended,
        // e.g. other thread requested the GIL

        // NEXTOPARG[] macro
        _Py_CODEUNIT word = *next_instr; // _Py_CODEUNIT is a typedef for uint16_t
        opcode = _Py_OPCODE[word];
        oparg = _Py_OPARG[word];
        next_instr++;

        switch [opcode] {
            case TARGET[NOP] {
                FAST_DISPATCH[]; // more on this later
            }

            case TARGET[LOAD_FAST] {
                // ... code for loading local variable
            }

            // ... 117 more cases for every possible opcode
        }

        // ... error handling
    }

    // ... termination
}

Để có được một bức tranh thực tế hơn, hãy thảo luận về một số tác phẩm bị bỏ qua chi tiết hơn.

lý do để đình chỉ vòng lặp

Thỉnh thoảng, luồng hiện đang chạy dừng thực thi mã byte để làm việc khác hoặc không làm gì cả. Điều này có thể xảy ra do một trong bốn lý do:

  • Có tín hiệu để xử lý. Khi bạn đăng ký một chức năng làm trình xử lý tín hiệu bằng cách sử dụng
    struct _ceval_runtime_state {
        /* Request for checking signals. It is shared by all interpreters [see
           bpo-40513]. Any thread of any interpreter can receive a signal, but only
           the main thread of the main interpreter can handle signals: see
           _Py_ThreadCanHandleSignals[]. */
        _Py_atomic_int signals_pending;
        struct _gil_runtime_state gil;
    };
    
    8, CPython sẽ lưu trữ chức năng này trong mảng trình xử lý. Hàm thực sự sẽ được gọi khi một luồng nhận được tín hiệu là
    struct _ceval_runtime_state {
        /* Request for checking signals. It is shared by all interpreters [see
           bpo-40513]. Any thread of any interpreter can receive a signal, but only
           the main thread of the main interpreter can handle signals: see
           _Py_ThreadCanHandleSignals[]. */
        _Py_atomic_int signals_pending;
        struct _gil_runtime_state gil;
    };
    
    9 [nó được chuyển đến chức năng thư viện
    struct _ceval_state {
        int recursion_limit;
        /* Records whether tracing is on for any thread.  Counts the number
           of threads for which tstate->c_tracefunc is non-NULL, so if the
           value is 0, we know we don't have to check this thread's
           c_tracefunc.  This speeds up the if statement in
           _PyEval_EvalFrameDefault[] after fast_next_opcode. */
        int tracing_possible;
        /* This single variable consolidates all requests to break out of
           the fast path in the eval loop. */
        _Py_atomic_int eval_breaker;
        /* Request for dropping the GIL */
        _Py_atomic_int gil_drop_request;
        struct _pending_calls pending;
    };
    
    0 trên các hệ thống giống UNIX]. Khi được gọi,
    struct _ceval_runtime_state {
        /* Request for checking signals. It is shared by all interpreters [see
           bpo-40513]. Any thread of any interpreter can receive a signal, but only
           the main thread of the main interpreter can handle signals: see
           _Py_ThreadCanHandleSignals[]. */
        _Py_atomic_int signals_pending;
        struct _gil_runtime_state gil;
    };
    
    9 đặt một biến Boolean nói rằng hàm trong mảng trình xử lý tương ứng với tín hiệu nhận được phải được gọi. Theo định kỳ, luồng chính của trình thông dịch chính gọi trình xử lý vấp.
  • Có những cuộc gọi đang chờ xử lý để gọi. Các cuộc gọi đang chờ xử lý là một cơ chế cho phép lên lịch một chức năng được thực thi từ luồng chính. Cơ chế này được phơi bày bởi API Python/C thông qua hàm
    struct _ceval_state {
        int recursion_limit;
        /* Records whether tracing is on for any thread.  Counts the number
           of threads for which tstate->c_tracefunc is non-NULL, so if the
           value is 0, we know we don't have to check this thread's
           c_tracefunc.  This speeds up the if statement in
           _PyEval_EvalFrameDefault[] after fast_next_opcode. */
        int tracing_possible;
        /* This single variable consolidates all requests to break out of
           the fast path in the eval loop. */
        _Py_atomic_int eval_breaker;
        /* Request for dropping the GIL */
        _Py_atomic_int gil_drop_request;
        struct _pending_calls pending;
    };
    
    2.
  • Ngoại lệ không đồng bộ được nâng lên. Ngoại lệ không đồng bộ là một ngoại lệ được đặt trong một luồng từ một luồng khác. Điều này có thể được thực hiện bằng cách sử dụng hàm
    struct _ceval_state {
        int recursion_limit;
        /* Records whether tracing is on for any thread.  Counts the number
           of threads for which tstate->c_tracefunc is non-NULL, so if the
           value is 0, we know we don't have to check this thread's
           c_tracefunc.  This speeds up the if statement in
           _PyEval_EvalFrameDefault[] after fast_next_opcode. */
        int tracing_possible;
        /* This single variable consolidates all requests to break out of
           the fast path in the eval loop. */
        _Py_atomic_int eval_breaker;
        /* Request for dropping the GIL */
        _Py_atomic_int gil_drop_request;
        struct _pending_calls pending;
    };
    
    3 được cung cấp bởi API Python/C.
  • Chủ đề hiện đang chạy được yêu cầu thả GIL. Khi nó nhìn thấy một yêu cầu như vậy, nó sẽ giảm GIL và đợi cho đến khi nó có được Gil một lần nữa.

CPython có các chỉ số cho mỗi sự kiện này. Biến chỉ ra rằng có những người xử lý để gọi là thành viên của

struct _ceval_state {
    int recursion_limit;
    /* Records whether tracing is on for any thread.  Counts the number
       of threads for which tstate->c_tracefunc is non-NULL, so if the
       value is 0, we know we don't have to check this thread's
       c_tracefunc.  This speeds up the if statement in
       _PyEval_EvalFrameDefault[] after fast_next_opcode. */
    int tracing_possible;
    /* This single variable consolidates all requests to break out of
       the fast path in the eval loop. */
    _Py_atomic_int eval_breaker;
    /* Request for dropping the GIL */
    _Py_atomic_int gil_drop_request;
    struct _pending_calls pending;
};
4, đó là một cấu trúc
struct _ceval_state {
    int recursion_limit;
    /* Records whether tracing is on for any thread.  Counts the number
       of threads for which tstate->c_tracefunc is non-NULL, so if the
       value is 0, we know we don't have to check this thread's
       c_tracefunc.  This speeds up the if statement in
       _PyEval_EvalFrameDefault[] after fast_next_opcode. */
    int tracing_possible;
    /* This single variable consolidates all requests to break out of
       the fast path in the eval loop. */
    _Py_atomic_int eval_breaker;
    /* Request for dropping the GIL */
    _Py_atomic_int gil_drop_request;
    struct _pending_calls pending;
};
5:

struct _ceval_runtime_state {
    /* Request for checking signals. It is shared by all interpreters [see
       bpo-40513]. Any thread of any interpreter can receive a signal, but only
       the main thread of the main interpreter can handle signals: see
       _Py_ThreadCanHandleSignals[]. */
    _Py_atomic_int signals_pending;
    struct _gil_runtime_state gil;
};

Các chỉ số khác là thành viên của

struct _ceval_state {
    int recursion_limit;
    /* Records whether tracing is on for any thread.  Counts the number
       of threads for which tstate->c_tracefunc is non-NULL, so if the
       value is 0, we know we don't have to check this thread's
       c_tracefunc.  This speeds up the if statement in
       _PyEval_EvalFrameDefault[] after fast_next_opcode. */
    int tracing_possible;
    /* This single variable consolidates all requests to break out of
       the fast path in the eval loop. */
    _Py_atomic_int eval_breaker;
    /* Request for dropping the GIL */
    _Py_atomic_int gil_drop_request;
    struct _pending_calls pending;
};
6 là một cấu trúc
struct _ceval_state {
    int recursion_limit;
    /* Records whether tracing is on for any thread.  Counts the number
       of threads for which tstate->c_tracefunc is non-NULL, so if the
       value is 0, we know we don't have to check this thread's
       c_tracefunc.  This speeds up the if statement in
       _PyEval_EvalFrameDefault[] after fast_next_opcode. */
    int tracing_possible;
    /* This single variable consolidates all requests to break out of
       the fast path in the eval loop. */
    _Py_atomic_int eval_breaker;
    /* Request for dropping the GIL */
    _Py_atomic_int gil_drop_request;
    struct _pending_calls pending;
};
7:

struct _ceval_state {
    int recursion_limit;
    /* Records whether tracing is on for any thread.  Counts the number
       of threads for which tstate->c_tracefunc is non-NULL, so if the
       value is 0, we know we don't have to check this thread's
       c_tracefunc.  This speeds up the if statement in
       _PyEval_EvalFrameDefault[] after fast_next_opcode. */
    int tracing_possible;
    /* This single variable consolidates all requests to break out of
       the fast path in the eval loop. */
    _Py_atomic_int eval_breaker;
    /* Request for dropping the GIL */
    _Py_atomic_int gil_drop_request;
    struct _pending_calls pending;
};

Kết quả của việc cung cấp tất cả các chỉ số cùng nhau được lưu trữ trong biến

struct _ceval_state {
    int recursion_limit;
    /* Records whether tracing is on for any thread.  Counts the number
       of threads for which tstate->c_tracefunc is non-NULL, so if the
       value is 0, we know we don't have to check this thread's
       c_tracefunc.  This speeds up the if statement in
       _PyEval_EvalFrameDefault[] after fast_next_opcode. */
    int tracing_possible;
    /* This single variable consolidates all requests to break out of
       the fast path in the eval loop. */
    _Py_atomic_int eval_breaker;
    /* Request for dropping the GIL */
    _Py_atomic_int gil_drop_request;
    struct _pending_calls pending;
};
8. Nó cho biết liệu có bất kỳ lý do nào cho chủ đề đang chạy hiện đang dừng thực thi mã byte bình thường của nó hay không. Mỗi lần lặp của vòng đánh giá bắt đầu với việc kiểm tra xem
struct _ceval_state {
    int recursion_limit;
    /* Records whether tracing is on for any thread.  Counts the number
       of threads for which tstate->c_tracefunc is non-NULL, so if the
       value is 0, we know we don't have to check this thread's
       c_tracefunc.  This speeds up the if statement in
       _PyEval_EvalFrameDefault[] after fast_next_opcode. */
    int tracing_possible;
    /* This single variable consolidates all requests to break out of
       the fast path in the eval loop. */
    _Py_atomic_int eval_breaker;
    /* Request for dropping the GIL */
    _Py_atomic_int gil_drop_request;
    struct _pending_calls pending;
};
8 có đúng không. Nếu đó là sự thật, luồng kiểm tra các chỉ số để xác định chính xác nó được yêu cầu làm gì, có phải điều đó và tiếp tục thực thi mã byte.

tính toán gotos

Mã cho vòng đánh giá có đầy đủ các macro như

for [;;] {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG[] macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE[word];
    oparg = _Py_OPARG[word];
    next_instr++;

    switch [opcode] {
        // TARGET[NOP] expands to NOP
        case NOP: {
            goto fast_next_opcode; // FAST_DISPATCH[] macro
        }

        // ...

        case BINARY_MULTIPLY: {
            // ... code for binary multiplication
            continue; // DISPATCH[] macro
        }

        // ...
    }

    // ... error handling
}
0 và
for [;;] {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG[] macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE[word];
    oparg = _Py_OPARG[word];
    next_instr++;

    switch [opcode] {
        // TARGET[NOP] expands to NOP
        case NOP: {
            goto fast_next_opcode; // FAST_DISPATCH[] macro
        }

        // ...

        case BINARY_MULTIPLY: {
            // ... code for binary multiplication
            continue; // DISPATCH[] macro
        }

        // ...
    }

    // ... error handling
}
1. Đây không phải là phương tiện để làm cho mã nhỏ gọn hơn. Chúng mở rộng sang mã khác nhau tùy thuộc vào việc tối ưu hóa nhất định, được gọi là "gotos được tính toán" [a.k.a. "Mã luồng"], được sử dụng. Mục tiêu của chúng là tối ưu hóa này là tăng tốc độ thực thi mã byte bằng cách viết mã theo cách đó, để CPU có thể sử dụng cơ chế dự đoán nhánh của nó để dự đoán mã hóa tiếp theo.

Sau khi thực hiện bất kỳ lệnh nào đã cho, VM thực hiện một trong ba điều:

  • Nó trở về từ chức năng đánh giá. Điều này xảy ra khi VM thực thi hướng dẫn
    for [;;] {
        // ... check if the bytecode execution must be suspended
    
    fast_next_opcode:
        // NEXTOPARG[] macro
        _Py_CODEUNIT word = *next_instr;
        opcode = _Py_OPCODE[word];
        oparg = _Py_OPARG[word];
        next_instr++;
    
        switch [opcode] {
            // TARGET[NOP] expands to NOP
            case NOP: {
                goto fast_next_opcode; // FAST_DISPATCH[] macro
            }
    
            // ...
    
            case BINARY_MULTIPLY: {
                // ... code for binary multiplication
                continue; // DISPATCH[] macro
            }
    
            // ...
        }
    
        // ... error handling
    }
    
    2,
    for [;;] {
        // ... check if the bytecode execution must be suspended
    
    fast_next_opcode:
        // NEXTOPARG[] macro
        _Py_CODEUNIT word = *next_instr;
        opcode = _Py_OPCODE[word];
        oparg = _Py_OPARG[word];
        next_instr++;
    
        switch [opcode] {
            // TARGET[NOP] expands to NOP
            case NOP: {
                goto fast_next_opcode; // FAST_DISPATCH[] macro
            }
    
            // ...
    
            case BINARY_MULTIPLY: {
                // ... code for binary multiplication
                continue; // DISPATCH[] macro
            }
    
            // ...
        }
    
        // ... error handling
    }
    
    3 hoặc
    for [;;] {
        // ... check if the bytecode execution must be suspended
    
    fast_next_opcode:
        // NEXTOPARG[] macro
        _Py_CODEUNIT word = *next_instr;
        opcode = _Py_OPCODE[word];
        oparg = _Py_OPARG[word];
        next_instr++;
    
        switch [opcode] {
            // TARGET[NOP] expands to NOP
            case NOP: {
                goto fast_next_opcode; // FAST_DISPATCH[] macro
            }
    
            // ...
    
            case BINARY_MULTIPLY: {
                // ... code for binary multiplication
                continue; // DISPATCH[] macro
            }
    
            // ...
        }
    
        // ... error handling
    }
    
    4.
  • Nó xử lý lỗi và tiếp tục thực thi hoặc trả về từ chức năng đánh giá với bộ ngoại lệ. Lỗi có thể xảy ra khi, ví dụ, VM thực hiện lệnh
    for [;;] {
        // ... check if the bytecode execution must be suspended
    
    fast_next_opcode:
        // NEXTOPARG[] macro
        _Py_CODEUNIT word = *next_instr;
        opcode = _Py_OPCODE[word];
        oparg = _Py_OPARG[word];
        next_instr++;
    
        switch [opcode] {
            // TARGET[NOP] expands to NOP
            case NOP: {
                goto fast_next_opcode; // FAST_DISPATCH[] macro
            }
    
            // ...
    
            case BINARY_MULTIPLY: {
                // ... code for binary multiplication
                continue; // DISPATCH[] macro
            }
    
            // ...
        }
    
        // ... error handling
    }
    
    5 và các đối tượng được thêm vào không thực hiện các phương thức
    for [;;] {
        // ... check if the bytecode execution must be suspended
    
    fast_next_opcode:
        // NEXTOPARG[] macro
        _Py_CODEUNIT word = *next_instr;
        opcode = _Py_OPCODE[word];
        oparg = _Py_OPARG[word];
        next_instr++;
    
        switch [opcode] {
            // TARGET[NOP] expands to NOP
            case NOP: {
                goto fast_next_opcode; // FAST_DISPATCH[] macro
            }
    
            // ...
    
            case BINARY_MULTIPLY: {
                // ... code for binary multiplication
                continue; // DISPATCH[] macro
            }
    
            // ...
        }
    
        // ... error handling
    }
    
    6 và
    for [;;] {
        // ... check if the bytecode execution must be suspended
    
    fast_next_opcode:
        // NEXTOPARG[] macro
        _Py_CODEUNIT word = *next_instr;
        opcode = _Py_OPCODE[word];
        oparg = _Py_OPARG[word];
        next_instr++;
    
        switch [opcode] {
            // TARGET[NOP] expands to NOP
            case NOP: {
                goto fast_next_opcode; // FAST_DISPATCH[] macro
            }
    
            // ...
    
            case BINARY_MULTIPLY: {
                // ... code for binary multiplication
                continue; // DISPATCH[] macro
            }
    
            // ...
        }
    
        // ... error handling
    }
    
    7.
  • Nó tiếp tục thực hiện. Làm thế nào để thực hiện VM thực hiện lệnh tiếp theo? Giải pháp đơn giản nhất sẽ là kết thúc mỗi khối
    struct _ceval_runtime_state {
        /* Request for checking signals. It is shared by all interpreters [see
           bpo-40513]. Any thread of any interpreter can receive a signal, but only
           the main thread of the main interpreter can handle signals: see
           _Py_ThreadCanHandleSignals[]. */
        _Py_atomic_int signals_pending;
        struct _gil_runtime_state gil;
    };
    
    4 không hoàn nguyên với câu lệnh
    for [;;] {
        // ... check if the bytecode execution must be suspended
    
    fast_next_opcode:
        // NEXTOPARG[] macro
        _Py_CODEUNIT word = *next_instr;
        opcode = _Py_OPCODE[word];
        oparg = _Py_OPARG[word];
        next_instr++;
    
        switch [opcode] {
            // TARGET[NOP] expands to NOP
            case NOP: {
                goto fast_next_opcode; // FAST_DISPATCH[] macro
            }
    
            // ...
    
            case BINARY_MULTIPLY: {
                // ... code for binary multiplication
                continue; // DISPATCH[] macro
            }
    
            // ...
        }
    
        // ... error handling
    }
    
    9. Giải pháp thực sự, mặc dù, phức tạp hơn một chút.

Để xem vấn đề với câu lệnh

for [;;] {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG[] macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE[word];
    oparg = _Py_OPARG[word];
    next_instr++;

    switch [opcode] {
        // TARGET[NOP] expands to NOP
        case NOP: {
            goto fast_next_opcode; // FAST_DISPATCH[] macro
        }

        // ...

        case BINARY_MULTIPLY: {
            // ... code for binary multiplication
            continue; // DISPATCH[] macro
        }

        // ...
    }

    // ... error handling
}
9 đơn giản, chúng ta cần hiểu những gì
struct _ceval_runtime_state {
    /* Request for checking signals. It is shared by all interpreters [see
       bpo-40513]. Any thread of any interpreter can receive a signal, but only
       the main thread of the main interpreter can handle signals: see
       _Py_ThreadCanHandleSignals[]. */
    _Py_atomic_int signals_pending;
    struct _gil_runtime_state gil;
};
3 biên dịch. Opcode là một số nguyên từ 0 đến 255. Vì phạm vi dày đặc, trình biên dịch có thể tạo một bảng nhảy lưu trữ các địa chỉ của các khối
struct _ceval_runtime_state {
    /* Request for checking signals. It is shared by all interpreters [see
       bpo-40513]. Any thread of any interpreter can receive a signal, but only
       the main thread of the main interpreter can handle signals: see
       _Py_ThreadCanHandleSignals[]. */
    _Py_atomic_int signals_pending;
    struct _gil_runtime_state gil;
};
4 và sử dụng các opcode làm chỉ số vào bảng đó. Các trình biên dịch hiện đại thực sự làm điều đó, vì vậy việc gửi các trường hợp được thực hiện một cách hiệu quả như một bước nhảy gián tiếp. Đây là một cách hiệu quả để thực hiện
struct _ceval_runtime_state {
    /* Request for checking signals. It is shared by all interpreters [see
       bpo-40513]. Any thread of any interpreter can receive a signal, but only
       the main thread of the main interpreter can handle signals: see
       _Py_ThreadCanHandleSignals[]. */
    _Py_atomic_int signals_pending;
    struct _gil_runtime_state gil;
};
3. Tuy nhiên, việc đặt
struct _ceval_runtime_state {
    /* Request for checking signals. It is shared by all interpreters [see
       bpo-40513]. Any thread of any interpreter can receive a signal, but only
       the main thread of the main interpreter can handle signals: see
       _Py_ThreadCanHandleSignals[]. */
    _Py_atomic_int signals_pending;
    struct _gil_runtime_state gil;
};
3 bên trong vòng lặp và thêm các câu
for [;;] {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG[] macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE[word];
    oparg = _Py_OPARG[word];
    next_instr++;

    switch [opcode] {
        // TARGET[NOP] expands to NOP
        case NOP: {
            goto fast_next_opcode; // FAST_DISPATCH[] macro
        }

        // ...

        case BINARY_MULTIPLY: {
            // ... code for binary multiplication
            continue; // DISPATCH[] macro
        }

        // ...
    }

    // ... error handling
}
9 tạo ra hai sự thiếu hiệu quả:

  • Câu lệnh

    for [;;] {
        // ... check if the bytecode execution must be suspended
    
    fast_next_opcode:
        // NEXTOPARG[] macro
        _Py_CODEUNIT word = *next_instr;
        opcode = _Py_OPCODE[word];
        oparg = _Py_OPARG[word];
        next_instr++;
    
        switch [opcode] {
            // TARGET[NOP] expands to NOP
            case NOP: {
                goto fast_next_opcode; // FAST_DISPATCH[] macro
            }
    
            // ...
    
            case BINARY_MULTIPLY: {
                // ... code for binary multiplication
                continue; // DISPATCH[] macro
            }
    
            // ...
        }
    
        // ... error handling
    }
    
    9 vào cuối khối
    struct _ceval_runtime_state {
        /* Request for checking signals. It is shared by all interpreters [see
           bpo-40513]. Any thread of any interpreter can receive a signal, but only
           the main thread of the main interpreter can handle signals: see
           _Py_ThreadCanHandleSignals[]. */
        _Py_atomic_int signals_pending;
        struct _gil_runtime_state gil;
    };
    
    4 thêm một bước nhảy khác. Do đó, để thực thi một opcode, VM phải nhảy hai lần: đến khi bắt đầu vòng lặp và sau đó đến khối
    struct _ceval_runtime_state {
        /* Request for checking signals. It is shared by all interpreters [see
           bpo-40513]. Any thread of any interpreter can receive a signal, but only
           the main thread of the main interpreter can handle signals: see
           _Py_ThreadCanHandleSignals[]. */
        _Py_atomic_int signals_pending;
        struct _gil_runtime_state gil;
    };
    
    4 tiếp theo.

  • Vì tất cả các opcode được gửi bởi một bước nhảy, một CPU có một ít cơ hội dự đoán opcode tiếp theo. Điều tốt nhất có thể làm là chọn opcode cuối cùng hoặc, có thể là cái thường xuyên nhất.

Ý tưởng về việc tối ưu hóa là đặt một bước nhảy riêng biệt vào cuối mỗi khối

struct _ceval_runtime_state {
    /* Request for checking signals. It is shared by all interpreters [see
       bpo-40513]. Any thread of any interpreter can receive a signal, but only
       the main thread of the main interpreter can handle signals: see
       _Py_ThreadCanHandleSignals[]. */
    _Py_atomic_int signals_pending;
    struct _gil_runtime_state gil;
};
4 không hoàn trả. Đầu tiên, nó tiết kiệm một bước nhảy. Thứ hai, CPU có thể dự đoán opcode tiếp theo là mã opcode có thể xảy ra nhất sau mã hiện tại.

Việc tối ưu hóa có thể được bật hoặc tắt. Nó phụ thuộc vào việc trình biên dịch có hỗ trợ tiện ích mở rộng GCC C được gọi là "nhãn là giá trị" hay không. Hiệu quả của việc cho phép tối ưu hóa là các macro nhất định, chẳng hạn như

for [;;] {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG[] macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE[word];
    oparg = _Py_OPARG[word];
    next_instr++;

    switch [opcode] {
        // TARGET[NOP] expands to NOP
        case NOP: {
            goto fast_next_opcode; // FAST_DISPATCH[] macro
        }

        // ...

        case BINARY_MULTIPLY: {
            // ... code for binary multiplication
            continue; // DISPATCH[] macro
        }

        // ...
    }

    // ... error handling
}
0,
for [;;] {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG[] macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE[word];
    oparg = _Py_OPARG[word];
    next_instr++;

    switch [opcode] {
        // TARGET[NOP] expands to NOP
        case NOP: {
            goto fast_next_opcode; // FAST_DISPATCH[] macro
        }

        // ...

        case BINARY_MULTIPLY: {
            // ... code for binary multiplication
            continue; // DISPATCH[] macro
        }

        // ...
    }

    // ... error handling
}
1 và
for [;;] {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG[] macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE[word];
    oparg = _Py_OPARG[word];
    next_instr++;

    switch [opcode] {
        // TARGET[NOP] expands to NOP: TARGET_NOP:
        // TARGET_NOP is a label
        case NOP: TARGET_NOP: {
            // FAST_DISPATCH[] macro
            // when tracing is disabled
            f->f_lasti = INSTR_OFFSET[];
            NEXTOPARG[];
            goto *opcode_targets[opcode];
        }

        // ...

        case BINARY_MULTIPLY: TARGET_BINARY_MULTIPLY: {
            // ... code for binary multiplication
            // DISPATCH[] macro
            if [!_Py_atomic_load_relaxed[eval_breaker]] {
              FAST_DISPATCH[];
            }
            continue;
        }

        // ...
    }

    // ... error handling
}
2, mở rộng theo cách khác nhau. Các macro này được sử dụng rộng rãi trong toàn bộ mã của vòng đánh giá. Mỗi biểu thức trường hợp có dạng
for [;;] {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG[] macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE[word];
    oparg = _Py_OPARG[word];
    next_instr++;

    switch [opcode] {
        // TARGET[NOP] expands to NOP: TARGET_NOP:
        // TARGET_NOP is a label
        case NOP: TARGET_NOP: {
            // FAST_DISPATCH[] macro
            // when tracing is disabled
            f->f_lasti = INSTR_OFFSET[];
            NEXTOPARG[];
            goto *opcode_targets[opcode];
        }

        // ...

        case BINARY_MULTIPLY: TARGET_BINARY_MULTIPLY: {
            // ... code for binary multiplication
            // DISPATCH[] macro
            if [!_Py_atomic_load_relaxed[eval_breaker]] {
              FAST_DISPATCH[];
            }
            continue;
        }

        // ...
    }

    // ... error handling
}
3, trong đó
for [;;] {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG[] macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE[word];
    oparg = _Py_OPARG[word];
    next_instr++;

    switch [opcode] {
        // TARGET[NOP] expands to NOP: TARGET_NOP:
        // TARGET_NOP is a label
        case NOP: TARGET_NOP: {
            // FAST_DISPATCH[] macro
            // when tracing is disabled
            f->f_lasti = INSTR_OFFSET[];
            NEXTOPARG[];
            goto *opcode_targets[opcode];
        }

        // ...

        case BINARY_MULTIPLY: TARGET_BINARY_MULTIPLY: {
            // ... code for binary multiplication
            // DISPATCH[] macro
            if [!_Py_atomic_load_relaxed[eval_breaker]] {
              FAST_DISPATCH[];
            }
            continue;
        }

        // ...
    }

    // ... error handling
}
4 là một macro cho số nguyên theo nghĩa đen đại diện cho một opcode. Và mỗi khối
struct _ceval_runtime_state {
    /* Request for checking signals. It is shared by all interpreters [see
       bpo-40513]. Any thread of any interpreter can receive a signal, but only
       the main thread of the main interpreter can handle signals: see
       _Py_ThreadCanHandleSignals[]. */
    _Py_atomic_int signals_pending;
    struct _gil_runtime_state gil;
};
4 không hoàn nguyên kết thúc bằng macro
for [;;] {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG[] macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE[word];
    oparg = _Py_OPARG[word];
    next_instr++;

    switch [opcode] {
        // TARGET[NOP] expands to NOP
        case NOP: {
            goto fast_next_opcode; // FAST_DISPATCH[] macro
        }

        // ...

        case BINARY_MULTIPLY: {
            // ... code for binary multiplication
            continue; // DISPATCH[] macro
        }

        // ...
    }

    // ... error handling
}
1 hoặc
for [;;] {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG[] macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE[word];
    oparg = _Py_OPARG[word];
    next_instr++;

    switch [opcode] {
        // TARGET[NOP] expands to NOP: TARGET_NOP:
        // TARGET_NOP is a label
        case NOP: TARGET_NOP: {
            // FAST_DISPATCH[] macro
            // when tracing is disabled
            f->f_lasti = INSTR_OFFSET[];
            NEXTOPARG[];
            goto *opcode_targets[opcode];
        }

        // ...

        case BINARY_MULTIPLY: TARGET_BINARY_MULTIPLY: {
            // ... code for binary multiplication
            // DISPATCH[] macro
            if [!_Py_atomic_load_relaxed[eval_breaker]] {
              FAST_DISPATCH[];
            }
            continue;
        }

        // ...
    }

    // ... error handling
}
2. Trước tiên, chúng ta hãy nhìn vào những gì các macro này mở rộng đến khi tối ưu hóa bị vô hiệu hóa:

for [;;] {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG[] macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE[word];
    oparg = _Py_OPARG[word];
    next_instr++;

    switch [opcode] {
        // TARGET[NOP] expands to NOP
        case NOP: {
            goto fast_next_opcode; // FAST_DISPATCH[] macro
        }

        // ...

        case BINARY_MULTIPLY: {
            // ... code for binary multiplication
            continue; // DISPATCH[] macro
        }

        // ...
    }

    // ... error handling
}

Macro

for [;;] {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG[] macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE[word];
    oparg = _Py_OPARG[word];
    next_instr++;

    switch [opcode] {
        // TARGET[NOP] expands to NOP: TARGET_NOP:
        // TARGET_NOP is a label
        case NOP: TARGET_NOP: {
            // FAST_DISPATCH[] macro
            // when tracing is disabled
            f->f_lasti = INSTR_OFFSET[];
            NEXTOPARG[];
            goto *opcode_targets[opcode];
        }

        // ...

        case BINARY_MULTIPLY: TARGET_BINARY_MULTIPLY: {
            // ... code for binary multiplication
            // DISPATCH[] macro
            if [!_Py_atomic_load_relaxed[eval_breaker]] {
              FAST_DISPATCH[];
            }
            continue;
        }

        // ...
    }

    // ... error handling
}
2 được sử dụng cho một số opcode khi không mong muốn để treo vòng lặp đánh giá sau khi thực hiện opcode đó. Nếu không, việc thực hiện rất đơn giản.

Nếu trình biên dịch hỗ trợ tiện ích mở rộng "Nhãn là giá trị", chúng ta có thể sử dụng toán tử

for [;;] {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG[] macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE[word];
    oparg = _Py_OPARG[word];
    next_instr++;

    switch [opcode] {
        // TARGET[NOP] expands to NOP: TARGET_NOP:
        // TARGET_NOP is a label
        case NOP: TARGET_NOP: {
            // FAST_DISPATCH[] macro
            // when tracing is disabled
            f->f_lasti = INSTR_OFFSET[];
            NEXTOPARG[];
            goto *opcode_targets[opcode];
        }

        // ...

        case BINARY_MULTIPLY: TARGET_BINARY_MULTIPLY: {
            // ... code for binary multiplication
            // DISPATCH[] macro
            if [!_Py_atomic_load_relaxed[eval_breaker]] {
              FAST_DISPATCH[];
            }
            continue;
        }

        // ...
    }

    // ... error handling
}
9 Unary trên nhãn để lấy địa chỉ của nó. Nó có giá trị loại
case TARGET[BINARY_ADD]: {
    PyObject *right = POP[];
    PyObject *left = TOP[];
    PyObject *sum;
    // ... special case of string addition
    sum = PyNumber_Add[left, right];
    Py_DECREF[left];
    Py_DECREF[right];
    SET_TOP[sum];
    if [sum == NULL]
        goto error;
    DISPATCH[];
}
0, vì vậy chúng tôi có thể lưu trữ nó trong một con trỏ:

Sau đó chúng ta có thể vào nhãn bằng cách phân tích con trỏ:

Phần mở rộng này cho phép thực hiện một bảng nhảy trong C dưới dạng một mảng các con trỏ nhãn. Và đó là những gì Cpython làm:

static void *opcode_targets[256] = {
    &&_unknown_opcode,
    &&TARGET_POP_TOP,
    &&TARGET_ROT_TWO,
    &&TARGET_ROT_THREE,
    &&TARGET_DUP_TOP,
    &&TARGET_DUP_TOP_TWO,
    &&TARGET_ROT_FOUR,
    &&_unknown_opcode,
    &&_unknown_opcode,
    &&TARGET_NOP,
    &&TARGET_UNARY_POSITIVE,
    &&TARGET_UNARY_NEGATIVE,
    &&TARGET_UNARY_NOT,
    // ... quite a few more
};

Đây là phiên bản tối ưu của vòng lặp đánh giá trông như thế nào:

for [;;] {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG[] macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE[word];
    oparg = _Py_OPARG[word];
    next_instr++;

    switch [opcode] {
        // TARGET[NOP] expands to NOP: TARGET_NOP:
        // TARGET_NOP is a label
        case NOP: TARGET_NOP: {
            // FAST_DISPATCH[] macro
            // when tracing is disabled
            f->f_lasti = INSTR_OFFSET[];
            NEXTOPARG[];
            goto *opcode_targets[opcode];
        }

        // ...

        case BINARY_MULTIPLY: TARGET_BINARY_MULTIPLY: {
            // ... code for binary multiplication
            // DISPATCH[] macro
            if [!_Py_atomic_load_relaxed[eval_breaker]] {
              FAST_DISPATCH[];
            }
            continue;
        }

        // ...
    }

    // ... error handling
}

Phần mở rộng được hỗ trợ bởi các trình biên dịch GCC và Clang. Vì vậy, khi bạn chạy

case TARGET[BINARY_ADD]: {
    PyObject *right = POP[];
    PyObject *left = TOP[];
    PyObject *sum;
    // ... special case of string addition
    sum = PyNumber_Add[left, right];
    Py_DECREF[left];
    Py_DECREF[right];
    SET_TOP[sum];
    if [sum == NULL]
        goto error;
    DISPATCH[];
}
1, bạn có thể đã bật tối ưu hóa. Câu hỏi, tất nhiên, là nó ảnh hưởng đến hiệu suất như thế nào. Ở đây, tôi sẽ dựa vào nhận xét từ mã nguồn:

Tại thời điểm viết bài này, phiên bản "mã luồng" nhanh hơn tới 15-20% so với phiên bản "chuyển đổi" bình thường, tùy thuộc vào trình biên dịch và kiến ​​trúc CPU.

Phần này sẽ cho chúng ta một ý tưởng về cách VM CPython đi từ hướng dẫn này sang hướng dẫn tiếp theo và những gì nó có thể làm ở giữa. Bước hợp lý tiếp theo là nghiên cứu sâu hơn về cách VM thực hiện một hướng dẫn duy nhất. CPython 3.9 có 119 mã hóa khác nhau. Tất nhiên, chúng tôi sẽ không nghiên cứu việc triển khai từng opcode trong bài đăng này. Thay vào đó, chúng tôi sẽ tập trung vào các nguyên tắc chung mà VM sử dụng để thực hiện chúng.

Giá trị ngăn xếp

Điều quan trọng nhất và may mắn thay, rất đơn giản về VM CPython là dựa trên ngăn xếp. Điều này có nghĩa là để tính toán mọi thứ, các giá trị VM POP [hoặc peek] từ ngăn xếp, thực hiện tính toán trên chúng và đẩy kết quả trở lại. Đây là một số ví dụ:

  • Giá trị Opcode ____92 Pops từ ngăn xếp, phủ nhận nó và đẩy kết quả.
  • Giá trị Opcode Opcode
    case TARGET[BINARY_ADD]: {
        PyObject *right = POP[];
        PyObject *left = TOP[];
        PyObject *sum;
        // ... special case of string addition
        sum = PyNumber_Add[left, right];
        Py_DECREF[left];
        Py_DECREF[right];
        SET_TOP[sum];
        if [sum == NULL]
            goto error;
        DISPATCH[];
    }
    
    3 từ ngăn xếp, gọi
    case TARGET[BINARY_ADD]: {
        PyObject *right = POP[];
        PyObject *left = TOP[];
        PyObject *sum;
        // ... special case of string addition
        sum = PyNumber_Add[left, right];
        Py_DECREF[left];
        Py_DECREF[right];
        SET_TOP[sum];
        if [sum == NULL]
            goto error;
        DISPATCH[];
    }
    
    4 trên đó và đẩy kết quả.
  • Giá trị Opcode Opcode
    for [;;] {
        // ... check if the bytecode execution must be suspended
    
    fast_next_opcode:
        // NEXTOPARG[] macro
        _Py_CODEUNIT word = *next_instr;
        opcode = _Py_OPCODE[word];
        oparg = _Py_OPARG[word];
        next_instr++;
    
        switch [opcode] {
            // TARGET[NOP] expands to NOP
            case NOP: {
                goto fast_next_opcode; // FAST_DISPATCH[] macro
            }
    
            // ...
    
            case BINARY_MULTIPLY: {
                // ... code for binary multiplication
                continue; // DISPATCH[] macro
            }
    
            // ...
        }
    
        // ... error handling
    }
    
    5 từ ngăn xếp, peek một giá trị khác từ trên cùng, thêm giá trị thứ nhất vào thứ hai và thay thế giá trị trên cùng với kết quả.

Ngăn xếp giá trị nằm trong một đối tượng khung. Nó được thực hiện như một phần của mảng được gọi là

case TARGET[BINARY_ADD]: {
    PyObject *right = POP[];
    PyObject *left = TOP[];
    PyObject *sum;
    // ... special case of string addition
    sum = PyNumber_Add[left, right];
    Py_DECREF[left];
    Py_DECREF[right];
    SET_TOP[sum];
    if [sum == NULL]
        goto error;
    DISPATCH[];
}
6. Mảng được chia thành một số phần để lưu trữ những thứ khác nhau, nhưng chỉ phần cuối cùng được sử dụng cho ngăn xếp giá trị. Sự khởi đầu của phần này là đáy của ngăn xếp. Trường
case TARGET[BINARY_ADD]: {
    PyObject *right = POP[];
    PyObject *left = TOP[];
    PyObject *sum;
    // ... special case of string addition
    sum = PyNumber_Add[left, right];
    Py_DECREF[left];
    Py_DECREF[right];
    SET_TOP[sum];
    if [sum == NULL]
        goto error;
    DISPATCH[];
}
7 của một đối tượng khung chỉ vào nó. Để xác định vị trí đỉnh của ngăn xếp, CPython giữ biến cục bộ
case TARGET[BINARY_ADD]: {
    PyObject *right = POP[];
    PyObject *left = TOP[];
    PyObject *sum;
    // ... special case of string addition
    sum = PyNumber_Add[left, right];
    Py_DECREF[left];
    Py_DECREF[right];
    SET_TOP[sum];
    if [sum == NULL]
        goto error;
    DISPATCH[];
}
8, chỉ vào vị trí tiếp theo sau đỉnh của ngăn xếp. Các yếu tố của mảng
case TARGET[BINARY_ADD]: {
    PyObject *right = POP[];
    PyObject *left = TOP[];
    PyObject *sum;
    // ... special case of string addition
    sum = PyNumber_Add[left, right];
    Py_DECREF[left];
    Py_DECREF[right];
    SET_TOP[sum];
    if [sum == NULL]
        goto error;
    DISPATCH[];
}
6 là con trỏ đến các đối tượng Python và con trỏ đến các đối tượng Python là những gì VM Cpython thực sự hoạt động.

Xử lý lỗi và ngăn chặn ngăn chặn

Không phải tất cả các tính toán được thực hiện bởi VM đều thành công. Giả sử chúng ta cố gắng thêm một số vào một chuỗi như

// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
00. Trình biên dịch tạo ra opcode
for [;;] {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG[] macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE[word];
    oparg = _Py_OPARG[word];
    next_instr++;

    switch [opcode] {
        // TARGET[NOP] expands to NOP
        case NOP: {
            goto fast_next_opcode; // FAST_DISPATCH[] macro
        }

        // ...

        case BINARY_MULTIPLY: {
            // ... code for binary multiplication
            continue; // DISPATCH[] macro
        }

        // ...
    }

    // ... error handling
}
5 để thêm hai đối tượng. Khi VM thực thi opcode này, nó gọi
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
02 để tính toán kết quả:

case TARGET[BINARY_ADD]: {
    PyObject *right = POP[];
    PyObject *left = TOP[];
    PyObject *sum;
    // ... special case of string addition
    sum = PyNumber_Add[left, right];
    Py_DECREF[left];
    Py_DECREF[right];
    SET_TOP[sum];
    if [sum == NULL]
        goto error;
    DISPATCH[];
}

Điều quan trọng đối với chúng tôi bây giờ không phải là cách

// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
02 được thực hiện, mà là cuộc gọi đến nó dẫn đến một lỗi. Lỗi có nghĩa là hai điều:

  • // typedef struct _frame PyFrameObject; in other place
    struct _frame {
        PyObject_VAR_HEAD
        struct _frame *f_back;      /* previous frame, or NULL */
        PyCodeObject *f_code;       /* code segment */
        PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
        PyObject *f_globals;        /* global symbol table [PyDictObject] */
        PyObject *f_locals;         /* local symbol table [any mapping] */
        PyObject **f_valuestack;    /* points after the last local */
        /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
           Frame evaluation usually NULLs it, but a frame that yields sets it
           to the current stack top. */
        PyObject **f_stacktop;
        PyObject *f_trace;          /* Trace function */
        char f_trace_lines;         /* Emit per-line trace events? */
        char f_trace_opcodes;       /* Emit per-opcode trace events? */
    
        /* Borrowed reference to a generator, or NULL */
        PyObject *f_gen;
    
        int f_lasti;                /* Last instruction if called */
        int f_lineno;               /* Current line number */
        int f_iblock;               /* index in f_blockstack */
        char f_executing;           /* whether the frame is still executing */
        PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
        PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
    };
    
    02 Trả về
    // typedef struct _frame PyFrameObject; in other place
    struct _frame {
        PyObject_VAR_HEAD
        struct _frame *f_back;      /* previous frame, or NULL */
        PyCodeObject *f_code;       /* code segment */
        PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
        PyObject *f_globals;        /* global symbol table [PyDictObject] */
        PyObject *f_locals;         /* local symbol table [any mapping] */
        PyObject **f_valuestack;    /* points after the last local */
        /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
           Frame evaluation usually NULLs it, but a frame that yields sets it
           to the current stack top. */
        PyObject **f_stacktop;
        PyObject *f_trace;          /* Trace function */
        char f_trace_lines;         /* Emit per-line trace events? */
        char f_trace_opcodes;       /* Emit per-opcode trace events? */
    
        /* Borrowed reference to a generator, or NULL */
        PyObject *f_gen;
    
        int f_lasti;                /* Last instruction if called */
        int f_lineno;               /* Current line number */
        int f_iblock;               /* index in f_blockstack */
        char f_executing;           /* whether the frame is still executing */
        PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
        PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
    };
    
    05.
  • // typedef struct _frame PyFrameObject; in other place
    struct _frame {
        PyObject_VAR_HEAD
        struct _frame *f_back;      /* previous frame, or NULL */
        PyCodeObject *f_code;       /* code segment */
        PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
        PyObject *f_globals;        /* global symbol table [PyDictObject] */
        PyObject *f_locals;         /* local symbol table [any mapping] */
        PyObject **f_valuestack;    /* points after the last local */
        /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
           Frame evaluation usually NULLs it, but a frame that yields sets it
           to the current stack top. */
        PyObject **f_stacktop;
        PyObject *f_trace;          /* Trace function */
        char f_trace_lines;         /* Emit per-line trace events? */
        char f_trace_opcodes;       /* Emit per-opcode trace events? */
    
        /* Borrowed reference to a generator, or NULL */
        PyObject *f_gen;
    
        int f_lasti;                /* Last instruction if called */
        int f_lineno;               /* Current line number */
        int f_iblock;               /* index in f_blockstack */
        char f_executing;           /* whether the frame is still executing */
        PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
        PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
    };
    
    02 đặt ngoại lệ hiện tại cho ngoại lệ
    // typedef struct _frame PyFrameObject; in other place
    struct _frame {
        PyObject_VAR_HEAD
        struct _frame *f_back;      /* previous frame, or NULL */
        PyCodeObject *f_code;       /* code segment */
        PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
        PyObject *f_globals;        /* global symbol table [PyDictObject] */
        PyObject *f_locals;         /* local symbol table [any mapping] */
        PyObject **f_valuestack;    /* points after the last local */
        /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
           Frame evaluation usually NULLs it, but a frame that yields sets it
           to the current stack top. */
        PyObject **f_stacktop;
        PyObject *f_trace;          /* Trace function */
        char f_trace_lines;         /* Emit per-line trace events? */
        char f_trace_opcodes;       /* Emit per-opcode trace events? */
    
        /* Borrowed reference to a generator, or NULL */
        PyObject *f_gen;
    
        int f_lasti;                /* Last instruction if called */
        int f_lineno;               /* Current line number */
        int f_iblock;               /* index in f_blockstack */
        char f_executing;           /* whether the frame is still executing */
        PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
        PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
    };
    
    07. Điều này liên quan đến việc thiết lập
    // typedef struct _frame PyFrameObject; in other place
    struct _frame {
        PyObject_VAR_HEAD
        struct _frame *f_back;      /* previous frame, or NULL */
        PyCodeObject *f_code;       /* code segment */
        PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
        PyObject *f_globals;        /* global symbol table [PyDictObject] */
        PyObject *f_locals;         /* local symbol table [any mapping] */
        PyObject **f_valuestack;    /* points after the last local */
        /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
           Frame evaluation usually NULLs it, but a frame that yields sets it
           to the current stack top. */
        PyObject **f_stacktop;
        PyObject *f_trace;          /* Trace function */
        char f_trace_lines;         /* Emit per-line trace events? */
        char f_trace_opcodes;       /* Emit per-opcode trace events? */
    
        /* Borrowed reference to a generator, or NULL */
        PyObject *f_gen;
    
        int f_lasti;                /* Last instruction if called */
        int f_lineno;               /* Current line number */
        int f_iblock;               /* index in f_blockstack */
        char f_executing;           /* whether the frame is still executing */
        PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
        PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
    };
    
    08,
    // typedef struct _frame PyFrameObject; in other place
    struct _frame {
        PyObject_VAR_HEAD
        struct _frame *f_back;      /* previous frame, or NULL */
        PyCodeObject *f_code;       /* code segment */
        PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
        PyObject *f_globals;        /* global symbol table [PyDictObject] */
        PyObject *f_locals;         /* local symbol table [any mapping] */
        PyObject **f_valuestack;    /* points after the last local */
        /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
           Frame evaluation usually NULLs it, but a frame that yields sets it
           to the current stack top. */
        PyObject **f_stacktop;
        PyObject *f_trace;          /* Trace function */
        char f_trace_lines;         /* Emit per-line trace events? */
        char f_trace_opcodes;       /* Emit per-opcode trace events? */
    
        /* Borrowed reference to a generator, or NULL */
        PyObject *f_gen;
    
        int f_lasti;                /* Last instruction if called */
        int f_lineno;               /* Current line number */
        int f_iblock;               /* index in f_blockstack */
        char f_executing;           /* whether the frame is still executing */
        PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
        PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
    };
    
    09 và
    // typedef struct _frame PyFrameObject; in other place
    struct _frame {
        PyObject_VAR_HEAD
        struct _frame *f_back;      /* previous frame, or NULL */
        PyCodeObject *f_code;       /* code segment */
        PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
        PyObject *f_globals;        /* global symbol table [PyDictObject] */
        PyObject *f_locals;         /* local symbol table [any mapping] */
        PyObject **f_valuestack;    /* points after the last local */
        /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
           Frame evaluation usually NULLs it, but a frame that yields sets it
           to the current stack top. */
        PyObject **f_stacktop;
        PyObject *f_trace;          /* Trace function */
        char f_trace_lines;         /* Emit per-line trace events? */
        char f_trace_opcodes;       /* Emit per-opcode trace events? */
    
        /* Borrowed reference to a generator, or NULL */
        PyObject *f_gen;
    
        int f_lasti;                /* Last instruction if called */
        int f_lineno;               /* Current line number */
        int f_iblock;               /* index in f_blockstack */
        char f_executing;           /* whether the frame is still executing */
        PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
        PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
    };
    
    10.

// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
05 là một chỉ số cho một lỗi. VM nhìn thấy nó và đi đến nhãn
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
12 ở cuối vòng đánh giá. Điều gì xảy ra tiếp theo phụ thuộc vào việc chúng tôi có thiết lập bất kỳ trình xử lý ngoại lệ nào hay không. Nếu chúng ta không có, VM đạt được câu lệnh
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
13 và chức năng đánh giá trả về
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
05 với ngoại lệ được đặt trên trạng thái luồng. Cpython in các chi tiết của ngoại lệ và thoát. Chúng tôi nhận được kết quả dự kiến:

// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
0

Nhưng giả sử rằng chúng ta đặt cùng một mã bên trong mệnh đề

// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
15 của câu lệnh
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
16. Trong trường hợp này, mã bên trong mệnh đề
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
17 cũng được thực thi:

// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
1

Làm thế nào VM có thể tiếp tục thực thi sau khi lỗi đã xảy ra? Chúng ta hãy xem mã byte được tạo bởi trình biên dịch cho câu lệnh

// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
16:

// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
2

Lưu ý các mã hóa

// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
19 và
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
20. Cái đầu tiên thiết lập trình xử lý ngoại lệ và cái thứ hai loại bỏ nó. Nếu xảy ra lỗi trong khi VM thực hiện các hướng dẫn giữa chúng, việc thực thi tiếp tục với lệnh ở Offset 22, đó là khởi đầu của mệnh đề
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
17. Mặt khác, mệnh đề
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
17 được thực thi sau mệnh đề
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
15. Trong cả hai trường hợp, mã byte cho mệnh đề
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
17 gần như giống hệt nhau. Sự khác biệt duy nhất là người xử lý tái tạo ngoại lệ được đặt trong mệnh đề
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
15.

Một trình xử lý ngoại lệ được triển khai dưới dạng cấu trúc C đơn giản được gọi là khối:

// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
3

VM giữ các khối trong ngăn xếp khối. Để thiết lập một trình xử lý ngoại lệ có nghĩa là đẩy một khối mới vào ngăn xếp khối. Đây là những gì opcodes như

// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
19 làm. Nhãn
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
12 trỏ đến một đoạn mã cố gắng xử lý lỗi bằng cách sử dụng các khối trên ngăn xếp khối. VM thư giãn ngăn xếp khối cho đến khi nó tìm thấy khối cao nhất loại
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
19. Nó khôi phục mức của ngăn xếp giá trị về mức được chỉ định bởi trường
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
29 của khối và tiếp tục thực thi mã byte với lệnh tại Offset
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
30. Đây về cơ bản là cách CPYThon thực hiện các câu như
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
31,
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
16 và
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
33.

Có một điều nữa để nói về xử lý ngoại lệ. Hãy nghĩ về những gì xảy ra khi xảy ra lỗi trong khi VM xử lý một ngoại lệ:

// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
4

Đúng như dự đoán, Cpython in ngoại lệ ban đầu. Để thực hiện hành vi đó, khi CPython xử lý một ngoại lệ bằng cách sử dụng khối

// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
19, nó sẽ thiết lập một khối khác của loại
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
35. Nếu xảy ra lỗi khi một khối loại này nằm trên ngăn xếp khối, VM sẽ nhận được ngoại lệ ban đầu từ ngăn xếp giá trị và đặt nó thành hiện tại. Cpython đã từng có các loại khối khác nhau nhưng bây giờ chỉ là
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
19 và
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
35.

Ngăn xếp khối được triển khai dưới dạng mảng

// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
38 trong một đối tượng khung. Kích thước của mảng được xác định tĩnh là 20. Vì vậy, nếu bạn làm tổ hơn 20 mệnh đề
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
15, bạn sẽ nhận được
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
40.

Bản tóm tắt

Hôm nay, chúng tôi đã học được rằng CPython VM thực thi các hướng dẫn mã byte từng cái một trong một vòng lặp vô hạn. Vòng lặp chứa một câu lệnh

struct _ceval_runtime_state {
    /* Request for checking signals. It is shared by all interpreters [see
       bpo-40513]. Any thread of any interpreter can receive a signal, but only
       the main thread of the main interpreter can handle signals: see
       _Py_ThreadCanHandleSignals[]. */
    _Py_atomic_int signals_pending;
    struct _gil_runtime_state gil;
};
3 trên tất cả các opcodes có thể. Mỗi opcode được thực thi trong khối
struct _ceval_runtime_state {
    /* Request for checking signals. It is shared by all interpreters [see
       bpo-40513]. Any thread of any interpreter can receive a signal, but only
       the main thread of the main interpreter can handle signals: see
       _Py_ThreadCanHandleSignals[]. */
    _Py_atomic_int signals_pending;
    struct _gil_runtime_state gil;
};
4 tương ứng. Hàm đánh giá chạy trong một luồng và đôi khi luồng đó treo vòng lặp để làm một cái gì đó khác. Ví dụ, một luồng có thể cần phải phát hành GIL, để luồng khác có thể lấy nó và tiếp tục thực thi mã byte của nó. Để tăng tốc độ thực thi mã byte, CPython sử dụng tối ưu hóa cho phép sử dụng cơ chế dự đoán nhánh của CPU. Một nhận xét nói rằng nó làm cho CPython nhanh hơn 15-20%.

Chúng tôi cũng đã xem xét hai cấu trúc dữ liệu quan trọng cho việc thực thi mã byte:

  • Giá trị ngăn xếp mà VM sử dụng để tính toán mọi thứ; và
  • ngăn xếp khối mà VM sử dụng để xử lý các ngoại lệ.

Kết luận quan trọng nhất từ ​​bài viết là: Nếu bạn muốn nghiên cứu việc thực hiện một số khía cạnh của Python, vòng lặp đánh giá là một nơi hoàn hảo để bắt đầu. Bạn muốn biết điều gì xảy ra khi bạn viết

// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
43? Hãy xem mã cho opcode
for [;;] {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG[] macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE[word];
    oparg = _Py_OPARG[word];
    next_instr++;

    switch [opcode] {
        // TARGET[NOP] expands to NOP
        case NOP: {
            goto fast_next_opcode; // FAST_DISPATCH[] macro
        }

        // ...

        case BINARY_MULTIPLY: {
            // ... code for binary multiplication
            continue; // DISPATCH[] macro
        }

        // ...
    }

    // ... error handling
}
5. Bạn muốn biết tuyên bố
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
33 được thực hiện như thế nào? Xem
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
46. Quan tâm đến ngữ nghĩa chính xác của một cuộc gọi chức năng? Opcode
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table [PyDictObject] */
    PyObject *f_globals;        /* global symbol table [PyDictObject] */
    PyObject *f_locals;         /* local symbol table [any mapping] */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
47 là những gì bạn đang tìm kiếm. Chúng tôi sẽ áp dụng phương pháp này vào lần tới khi nghiên cứu cách các biến được thực hiện trong CPython.

Nếu bạn có bất kỳ câu hỏi, nhận xét hoặc đề xuất nào, vui lòng liên hệ với tôi tại

Cập nhật từ ngày 10 tháng 11 năm 2020: Tôi đã được chỉ ra trong các bình luận về HN rằng

case TARGET[BINARY_ADD]: {
    PyObject *right = POP[];
    PyObject *left = TOP[];
    PyObject *sum;
    // ... special case of string addition
    sum = PyNumber_Add[left, right];
    Py_DECREF[left];
    Py_DECREF[right];
    SET_TOP[sum];
    if [sum == NULL]
        goto error;
    DISPATCH[];
}
2 và
case TARGET[BINARY_ADD]: {
    PyObject *right = POP[];
    PyObject *left = TOP[];
    PyObject *sum;
    // ... special case of string addition
    sum = PyNumber_Add[left, right];
    Py_DECREF[left];
    Py_DECREF[right];
    SET_TOP[sum];
    if [sum == NULL]
        goto error;
    DISPATCH[];
}
3 opcodes nhìn thấy giá trị trên đầu ngăn xếp và thay thế nó bằng kết quả thay vì bật giá trị và đẩy kết quả. Điều này thực sự là như vậy. Về mặt ngữ nghĩa, hai cách tiếp cận là tương đương. Sự khác biệt duy nhất là cách tiếp cận pop/đẩy đầu tiên và sau đó tăng
case TARGET[BINARY_ADD]: {
    PyObject *right = POP[];
    PyObject *left = TOP[];
    PyObject *sum;
    // ... special case of string addition
    sum = PyNumber_Add[left, right];
    Py_DECREF[left];
    Py_DECREF[right];
    SET_TOP[sum];
    if [sum == NULL]
        goto error;
    DISPATCH[];
}
8. Cpython tránh các hoạt động dư thừa này.
: I was pointed out in the comments on HN that the
case TARGET[BINARY_ADD]: {
    PyObject *right = POP[];
    PyObject *left = TOP[];
    PyObject *sum;
    // ... special case of string addition
    sum = PyNumber_Add[left, right];
    Py_DECREF[left];
    Py_DECREF[right];
    SET_TOP[sum];
    if [sum == NULL]
        goto error;
    DISPATCH[];
}
2 and
case TARGET[BINARY_ADD]: {
    PyObject *right = POP[];
    PyObject *left = TOP[];
    PyObject *sum;
    // ... special case of string addition
    sum = PyNumber_Add[left, right];
    Py_DECREF[left];
    Py_DECREF[right];
    SET_TOP[sum];
    if [sum == NULL]
        goto error;
    DISPATCH[];
}
3 opcodes peek the value on top of the stack and replace it with the result instead of popping the value and pushing the result. This is indeed so. Semantically, the two approaches are equivalent. The only difference is that the pop/push approach first decrements and then increments
case TARGET[BINARY_ADD]: {
    PyObject *right = POP[];
    PyObject *left = TOP[];
    PyObject *sum;
    // ... special case of string addition
    sum = PyNumber_Add[left, right];
    Py_DECREF[left];
    Py_DECREF[right];
    SET_TOP[sum];
    if [sum == NULL]
        goto error;
    DISPATCH[];
}
8. CPython avoids these redundant operations.

Python có sử dụng mã byte không?

Thay vì dịch mã nguồn sang mã máy như C ++, mã python được dịch thành mã byte. Bytecode này là một tập hợp các hướng dẫn cấp thấp có thể được thực thi bởi một trình thông dịch. Trong hầu hết các PC, trình thông dịch Python được cài đặt tại/usr/local/bin/python3.

Làm thế nào là mã byte python được tạo ra?

Mã byte được tự động tạo trong cùng một thư mục với tệp .py, khi một mô -đun Python lần đầu tiên được nhập hoặc khi nguồn gần đây hơn tệp được biên dịch hiện tại.Lần tới, khi chương trình được chạy, người thông báo Python sử dụng tệp này để bỏ qua bước biên dịch.automatically created in the same directory as . py file, when a module of python is imported for the first time, or when the source is more recent than the current compiled file. Next time, when the program is run, python interpretator use this file to skip the compilation step.

Tại sao Python sử dụng mã byte?

Khi trình thông dịch CPython thực thi chương trình của bạn, trước tiên nó sẽ dịch sang một chuỗi các hướng dẫn mã byte.Bytecode là ngôn ngữ trung gian cho máy ảo Python được sử dụng làm tối ưu hóa hiệu suất.used as a performance optimization.

Bytecode được biên dịch như thế nào?

Trình biên dịch chuyển đổi mã nguồn thành mã byte, một mã trung gian thu hẹp khoảng cách giữa mã nguồn cấp cao và mã máy cấp thấp.Trình biên dịch là một loại chương trình đặc biệt dịch các câu lệnh trong mã nguồn sang mã byte, mã máy hoặc ngôn ngữ lập trình khác., an intermediary code that bridges the gap between the high-level source code and low-level machine code. The compiler is a special type of program that translates statements in the source code to bytecode, machine code or another programming language.

Bài Viết Liên Quan

Chủ Đề