Fundamentals 21 min read

How Python Implements the if Statement at the Bytecode Level

The article dissects Python's CPython implementation of the if‑elif‑else control‑flow construct, detailing the generated bytecode, the POP_JUMP_IF_FALSE/TRUE instructions, jump macros, helper truth‑testing functions, computed‑goto support, and the role of instruction prediction in the virtual machine.

Satori Komeiji's Programming Classroom
Satori Komeiji's Programming Classroom
Satori Komeiji's Programming Classroom
How Python Implements the if Statement at the Bytecode Level

if bytecode

Running the following Python snippet with dis.dis(compile(...)) yields the bytecode shown below. The comments annotate each step.

import dis

code_string = """
score = 90

if score >= 85:
    print("Good")

elif score >= 60:
    print("Normal")

else:
    print("Bad")
"""

dis.dis(compile(code_string, "<file>", "exec"))
0 RESUME                 0
      2 LOAD_CONST             0 (90)
      4 STORE_NAME             0 (score)
      6 LOAD_NAME              0 (score)
      8 LOAD_CONST             1 (85)
     10 COMPARE_OP            92 (>=)
     14 POP_JUMP_IF_FALSE     9 (to 34)
     16 PUSH_NULL
     18 LOAD_NAME             1 (print)
     20 LOAD_CONST            2 ('Good')
     22 CALL                  1
     30 POP_TOP
     32 RETURN_CONST          6 (None)
     34 LOAD_NAME             0 (score)
     36 LOAD_CONST            3 (60)
     38 COMPARE_OP            92 (>=)
     42 POP_JUMP_IF_FALSE     9 (to 62)
     44 PUSH_NULL
     46 LOAD_NAME             1 (print)
     48 LOAD_CONST            4 ('Normal')
     50 CALL                  1
     58 POP_TOP
     60 RETURN_CONST          6 (None)
     62 PUSH_NULL
     64 LOAD_NAME             1 (print)
     66 LOAD_CONST            5 ('Bad')
     68 CALL                  1
     76 POP_TOP
     78 RETURN_CONST          6 (None)

Each >> marker denotes the start of a new branch in the if‑elif‑else chain. The interpreter evaluates the condition, then uses a conditional jump to skip to the next branch when the condition is false.

POP_JUMP_IF_FALSE

The C implementation (excerpted from Python/bytecodes.c) pops the top‑of‑stack object, checks its truth value, and jumps when the result is false.

TARGET(POP_JUMP_IF_FALSE) {
    PyObject *cond = stack_pointer[-1];
    if (Py_IsFalse(cond)) {
        JUMPBY(oparg);
    } else {
        int err = PyObject_IsTrue(cond);
        Py_DECREF(cond);
        if (err > 0) {
            JUMPBY(oparg);
        } else if (err < 0) {
            goto pop_1_error;  // error path (rare)
        }
    }
    STACK_SHRINK(1);
    DISPATCH();
}

Truth testing relies on helper functions such as Py_IsTrue, Py_IsFalse, PyObject_IsTrue, and PyObject_Not (see Objects/object.c).

POP_JUMP_IF_TRUE

The symmetric opcode is used for the not keyword. Its logic mirrors the false‑case version, jumping when the evaluated truth value is true.

TARGET(POP_JUMP_IF_TRUE) {
    PyObject *cond = stack_pointer[-1];
    if (Py_IsTrue(cond)) {
        JUMPBY(oparg);
    } else if (!Py_IsFalse(cond)) {
        int err = PyObject_IsTrue(cond);
        Py_DECREF(cond);
        if (err > 0) {
            JUMPBY(oparg);
        } else if (err < 0) {
            goto pop_1_error;
        }
    }
    STACK_SHRINK(1);
    DISPATCH();
}

Jump macros

Relative forward jumps are performed by #define JUMPBY(x) (next_instr += (x)). Absolute jumps use

#define JUMPTO(x) (next_instr = _PyCode_CODE(frame->f_code) + (x))

. JUMPTO can move both forward and backward, while JUMPBY only moves forward.

Computed goto (CPython 3.12)

Starting with CPython 3.12 the interpreter can dispatch opcodes via computed‑goto, which requires GCC’s “label‑as‑value” extension (e.g., goto *label_a). This eliminates the switch‑case lookup and makes the next‑instruction address known at runtime.

Instruction prediction (fallback)

When computed‑goto is disabled, the VM falls back to a switch dispatch. For opcode pairs that are highly correlated, a prediction hint is inserted using the macro PREDICTED(op) and the helper PREDICT(op):

#define PREDICTED(op)   PRED_##op
#define PREDICT(op) \
    do { \
        _Py_CODEUNIT word = *next_instr; \
        opcode = word.op.code; \
        if (opcode == op) { \
            oparg = word.op.arg; \
            INSTRUCTION_START(op); \
            goto PRED_##op; \
        } \
    } while (0)

Only pairs with a very high likelihood (e.g., MATCH_SEQUENCE followed by POP_JUMP_IF_FALSE) receive such predictions, because the success probability must justify the extra code.

Examples of conditional jumps

Simple if 2 > 1 produces a POP_JUMP_IF_FALSE:

import dis
code_string = """
if 2 > 1:
    print("ok")
"""
# prints a subset of bytecode
dis.dis(compile(code_string, "<file>", "exec"))
"""
     2 LOAD_CONST               0 (2)
     4 LOAD_CONST               1 (1)
     6 COMPARE_OP              68 (>)
    10 POP_JUMP_IF_FALSE       9 (to 30)
"""

Using not flips the opcode to POP_JUMP_IF_TRUE:

import dis
code_string = """
if not 2 > 1:
    print("ok")
"""
# prints a subset of bytecode

dis.dis(compile(code_string, "<file>", "exec"))
"""
     2 LOAD_CONST               0 (2)
     4 LOAD_CONST               1 (1)
     6 COMPARE_OP              68 (>)
    10 POP_JUMP_IF_TRUE        9 (to 30)
"""

Multiple not operators illustrate how the VM collapses even numbers of not into a false‑jump and odd numbers into a true‑jump:

# even number of nots → POP_JUMP_IF_FALSE
code_string = """
if not not not not 2 > 1:
    print("ok")
"""
# bytecode shows POP_JUMP_IF_FALSE

# odd number of nots → POP_JUMP_IF_TRUE
code_string = """
if not not not not not 2 > 1:
    print("ok")
"""
# bytecode shows POP_JUMP_IF_TRUE

Jump offset calculation

The POP_JUMP_IF_FALSE instruction at offset 14 has an operand of 9, meaning “jump forward 9 instructions”. Each instruction is 2 bytes, so the target address is 14 + 9*2 = 32. The apparent mismatch with the annotated target 34 is due to the TARGET macro expanding to an additional next_instr++ before the jump.

Summary

The if‑elif‑else construct compiles to a linear sequence of bytecode that evaluates each condition, then uses POP_JUMP_IF_FALSE (or POP_JUMP_IF_TRUE for not) to skip to the next branch when the condition fails. Jump offsets are computed with JUMPBY (relative) or JUMPTO (absolute). CPython 3.12’s computed‑goto dispatch removes the need for instruction prediction; when computed‑goto is unavailable, the interpreter employs a lightweight prediction mechanism for opcode pairs with a strong correlation, allowing a direct jump to the predicted handler and avoiding a full switch lookup.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

PythonbytecodeCPythonif statementcomputed gotoinstruction predictionPOP_JUMP_IF_FALSE
Satori Komeiji's Programming Classroom
Written by

Satori Komeiji's Programming Classroom

Python and Rust developer; I write about any topics you're interested in. Follow me! (#^.^#)

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.