#29. スタックベースの実行

CPython は、ほとんどのバイトコードをスタック マシンで実行します。スタック マシンは、すべての命令でソース レジスタとデスティネーション レジスタを明示的に指定するのではなく、暗黙的なオペランド スタックを使用します。

CPython では、このスタックは現在のフレームに属します。 Python オブジェクトへの参照を保存します。バイトコード命令は、オブジェクトのプッシュ、オブジェクトのポップ、オブジェクトの検査、オブジェクトの置換、およびオブジェクトを使用した新しい結果の計算を行います。

簡単な表現:python id="kz6m7x" x = a + b 概念的には次のように実行されます。text id="eurdpx" LOAD_FAST a push a LOAD_FAST b push b BINARY_OP + pop b and a, push result STORE_FAST x pop result into local x 命令ストリームには次のような記述はありません。text id="ardj57" add local_a, local_b, local_x 代わりに、次のように書かれています。```text id="jzzlmf" load a load b add top two stack values store result


## 29.1 CPython がスタック マシンを使用する理由

スタック マシンはバイトコードをコンパクトな表現にします。多くの命令はスタックの最上位で動作するため、明示的なオペランドの位置を必要としません。

例えば:```python id="e4954y"
return (a + b) * c
```は次のように表すことができます。```text id="nppt1h"
LOAD_FAST a
LOAD_FAST b
BINARY_OP +
LOAD_FAST c
BINARY_OP *
RETURN_VALUE
````BINARY_OP`命令は入力レジスタに名前を付ける必要はありません。常にスタックからオペランドを取得します。

これにより、バイトコードが単純になります。```text id="pr4b42"
instructions are small
operands are usually indexes or small integers
temporary values do not need names
compiler stack-depth analysis is straightforward
interpreter execution model is uniform
```レジスタ マシンは、より明示的なオペランドの位置をエンコードします。```text id="ugbzwm"
r3 = add r1, r2
r4 = mul r3, r0
return r4
```これにより、一部のデザインでは命令の数を減らすことができますが、異なるバイトコード形式と異なるコンパイラ戦略が必要になります。 CPython は歴史的にスタック指向モデルを使用してきました。

## 29.2 フレーム値スタック

実行中の各フレームには、一時値を保存するストレージがあります。

概念的には:```text id="ia1aa5"
frame
    fast locals
    cell variables
    free variables
    value stack
```のために:```python id="39cqdr"
def f(a, b):
    return a + b
```フレームは次のように理解できます。```text id="mq1wny"
localsplus
    [0] a
    [1] b
    [2...] value stack
```実行中:```text id="oq18jh"
LOAD_FAST a
    stack: [a]

LOAD_FAST b
    stack: [a, b]

BINARY_OP +
    stack: [a_plus_b]

RETURN_VALUE
    stack: []
    return a_plus_b
```スタックストア`PyObject *`参考文献。通常のバイトコード実行では、ボックス化されていない C 整数、生の double、または Python 値を表すマシンワードは保存されません。

のために`a = 2`そして`b = 3`、スタックは Python 整数オブジェクトへのポインターを保持します。```text id="4h720d"
stack slot 0 -> PyLongObject(2)
stack slot 1 -> PyLongObject(3)
```## 29.3 スタックポインタ

インタプリタは、現在のフレームのスタック ポインタを維持します。

概念的には:```c id="l1xymk"
PyObject **stack_pointer;
```プッシュでは、現在のスタック スロットに値が書き込まれ、ポインタが進められます。```c id="0gozoa"
*stack_pointer = value;
stack_pointer++;
```ポップするとポインタが戻り、値が読み取られます。```c id="xj0vaq"
stack_pointer--;
value = *stack_pointer;
```実際の CPython 実装では、マクロ、特殊なストレージ、参照所有権規則、生成された命令コードが使用されます。ただし、中心となる操作は依然としてプッシュとポップです。

単純なスタック トレース:```text id="9taxcy"
initial:
    sp -> slot 0

push a:
    slot 0 = a
    sp -> slot 1

push b:
    slot 0 = a
    slot 1 = b
    sp -> slot 2

pop:
    sp -> slot 1
    value = slot 1
```スタック ポインタは、次の空きスタック スロットをマークします。

## 29.4 スタック効果

すべての命令にはスタック効果があります。

スタック効果は、命令がスタックの高さをどのように変更するかを示します。

|指示 |ポップス |プッシュ |正味の効果 |
|---|---:|---:|---:|
|`LOAD_CONST`| 0 | 1 |`+1` |
| `LOAD_FAST`| 0 | 1 |`+1` |
| `STORE_FAST`| 1 | 0 |`-1` |
| `POP_TOP`| 1 | 0 |`-1` |
| `BINARY_OP`| 2 | 1 |`-1` |
| `RETURN_VALUE`| 1 | 0 |フレームを終了します |
|`CALL`|呼び出し可能と引数 |結果 |変数 |
|`BUILD_LIST`| N | 1 |`1 - N`|

コンパイラはスタック効果を使用して、コード オブジェクトに必要な最大スタック サイズを計算します。

その値はコード オブジェクトに次のように格納されます。`co_stacksize````python id="e077y8"
def f(a, b, c):
    return (a + b) * c

print(f.__code__.co_stacksize)

co_stacksizeこのコードが実行中に必要となる一時スタック領域の量を CPython に伝えます。

29.5 バイナリ式の評価

次のことを考慮してください。```python id="xjqe7p" def f(a, b, c): return a + b * c


概念的なスタック実行は次のとおりです。```text id="0lb9i7"
LOAD_FAST a
LOAD_FAST b
LOAD_FAST c
BINARY_OP *
BINARY_OP +
RETURN_VALUE
```段階的に:

|ステップ |指示 |前にスタック | | の後にスタック
|---:|---|---|---|
| 1 |`LOAD_FAST a` | `[]` | `[a]`|
| 2 |`LOAD_FAST b` | `[a]` | `[a, b]`|
| 3 |`LOAD_FAST c` | `[a, b]` | `[a, b, c]`|
| 4 |`BINARY_OP *` | `[a, b, c]` | `[a, b*c]`|
| 5 |`BINARY_OP +` | `[a, b*c]` | `[a + b*c]`|
| 6 |`RETURN_VALUE` | `[result]`|戻る |

コンパイラはスタックの使用中に Python 式のセマンティクスを保持する命令順序を選択します

## 29.6 括弧とスタック順序

括弧は評価順序を変更します。```python id="g8ade6"
def f(a, b, c):
    return (a + b) * c
```概念的なバイトコード:```text id="1fshyd"
LOAD_FAST a
LOAD_FAST b
BINARY_OP +
LOAD_FAST c
BINARY_OP *
RETURN_VALUE
```段階的に:

|ステップ |指示 | | の後にスタック
|---:|---|---|
| 1 |`LOAD_FAST a` | `[a]`|
| 2 |`LOAD_FAST b` | `[a, b]`|
| 3 |`BINARY_OP +` | `[a+b]`|
| 4 |`LOAD_FAST c` | `[a+b, c]`|
| 5 |`BINARY_OP *` | `[(a+b)*c]`|
| 6 |`RETURN_VALUE`|戻る |

スタック マシンは部分式を評価しその結果をスタックに残すことによってネストされた式を自然に表現します

## 29.7 オペランドの順序

二項演算の場合オペランドの順序が重要です

のために:```python id="dm51wp"
left - right
```インタプリタは正しい左オペランドと右オペランドを使用して減算を呼び出す必要があります

前のスタック`BINARY_OP -`:```text id="ghn1ei"
[left, right]
```この操作では最初に右オペランドがポップされ次に左オペランドがポップされます。```c id="8cs8ys"
right = pop();
left = pop();
result = subtract(left, right);
push(result);
```順序が逆の場合減算除算比較および多くのユーザー定義の演算が間違ってしまいます

同じ問題が呼び出しインデックス作成属性設定および解凍でも発生します

## 29.8 代入によりスタック値が消費される

代入では式によって生成されたスタック値を使用します。```python id="dk9je6"
x = a + b
```概念的な実行:```text id="nw82k0"
LOAD_FAST a       stack: [a]
LOAD_FAST b       stack: [a, b]
BINARY_OP +       stack: [result]
STORE_FAST x      stack: []

STORE_FAST最上位のスタック値を消費し、それをローカル スロットに保存します。

複数の割り当ての場合:```python id="7bq1ez" x = y = value


概念的には:```text id="tc6ix1"
LOAD_FAST value
COPY
STORE_FAST x
STORE_FAST y
```正確なバイトコードは Python のバージョンによって異なりますが、スタックの規律は同じです。割り当てターゲットはスタックから値を消費します。

## 29.9 スタック上の関数呼び出し

関数呼び出しでは、スタックを使用して呼び出し可能オブジェクトと引数を配置します。

のために:```python id="zluswf"
result = f(a, b)
```スタックには、呼び出し命令によって予期されるレイアウト内の呼び出し可能な引数が含まれている必要があります。

概念的には:```text id="yvk6nb"
LOAD_FAST f       stack: [f]
LOAD_FAST a       stack: [f, a]
LOAD_FAST b       stack: [f, a, b]
CALL 2            stack: [result]
STORE_FAST result stack: []
```call 命令は位置引数の数を知っています。これは呼び出し可能オブジェクトと引数を消費し、呼び出し機構を呼び出し、戻り値をプッシュします。

関数呼び出しは、以下が含まれる場合、より複雑になります。```text id="wi0q0p"
keyword arguments
default values
starred arguments
double-starred mappings
bound methods
callable classes
C functions
Python functions
coroutines
```ただし、評価スタックには引き続き即時呼び出しオペランドが含まれています。

## 29.10 メソッド呼び出し

メソッド呼び出しは一般的であるため、最適化されています。

のために:```python id="4dpcx3"
obj.method(arg)
```単純なモデルは次のとおりです。```text id="bxncuk"
load obj
load attribute method
create bound method object
load arg
call bound method
```呼び出しごとにバインドされたメソッド オブジェクトを作成すると、コストがかかる場合があります。

CPython は、特殊なメソッド呼び出しバイトコードと呼び出しパスを使用して、不要な一時オブジェクトを可能な限り回避します。

概念的には、最適化されたパスは次のことを表現しようとします。```text id="2vrzhp"
call underlying function with self and args
```の代わりに:```text id="a30j23"
create bound method object
then call it
```したがって、メソッド呼び出しのスタック レイアウトは、実装の詳細において単純な関数呼び出しとは異なる場合があります。目標は変わりません。呼び出し可能な状態と引数を調整して、呼び出し機構が効率的に実行できるようにします。

## 29.11 属性アクセスとスタック値

属性アクセスはオブジェクトを消費し、属性値を生成します。```python id="yr0lx4"
value = obj.name
```概念的には:```text id="k5xpeh"
LOAD_FAST obj       stack: [obj]
LOAD_ATTR name      stack: [value]
STORE_FAST value    stack: []

LOAD_ATTRオブジェクトをポップまたは読み取り、属性検索を実行し、結果をプッシュします。

属性検索には以下が含まれる場合があります。text id="mh8xew" type lookup descriptor protocol instance dictionary lookup slots properties custom __getattribute__ custom __getattr__ inline cache checks スタック マシンの観点から見ると、操作は簡単です。```text id="x0kf3f" object in attribute value out


## 29.12 サブスクリプションとインデックス作成

サブスクリプションでもスタックが使用されます。```python id="rey7pr"
value = xs[i]
```概念的な実行:```text id="r32lp4"
LOAD_FAST xs       stack: [xs]
LOAD_FAST i        stack: [xs, i]
BINARY_SUBSCR      stack: [value]
STORE_FAST value   stack: []
```割り当ての場合:```python id="enkjpe"
xs[i] = value
```スタックにはコンテナ、インデックス、および割り当てられた値が保持されている必要があります。

概念的には:```text id="8q3nbv"
LOAD_FAST value    stack: [value]
LOAD_FAST xs       stack: [value, xs]
LOAD_FAST i        stack: [value, xs, i]
STORE_SUBSCR       stack: []
```正確な順序が選択されるので、`STORE_SUBSCR`一貫してオペランドをポップできます。

## 29.13 コンテナの構築

リスト、タプル、セット、および辞書リテラルはスタックを使用します。```python id="8qazhy"
xs = [a, b, c]
```概念的には:```text id="q3tfjh"
LOAD_FAST a
LOAD_FAST b
LOAD_FAST c
BUILD_LIST 3
STORE_FAST xs
```スタックの進化:

|指示 | | の後にスタック
|---|---|
|`LOAD_FAST a` | `[a]` |
| `LOAD_FAST b` | `[a, b]` |
| `LOAD_FAST c` | `[a, b, c]` |
| `BUILD_LIST 3` | `[[a, b, c]]` |
| `STORE_FAST xs` | `[]` |

`BUILD_LIST 3`3 つのスタック値を消費し、1 つのリスト オブジェクトをプッシュします。

辞書の場合:```python id="u3ds6p"
d = {"a": x, "b": y}
```辞書構築命令がそれらを使用する前に、スタックにはキーと値のオブジェクトが含まれる場合があります。

## 29.14 開梱

開梱すると、コンテナの構築が逆になります。```python id="ooj3e0"
a, b = pair
```概念的には:```text id="ro1rmt"
LOAD_FAST pair
UNPACK_SEQUENCE 2
STORE_FAST a
STORE_FAST b
```unpack 命令は 1 つの反復可能オブジェクトを消費し、その要素をプッシュします。

コンパイラは、正しい変数が正しい値を受け取るようにストア順序を調整する必要があります。

のために:```python id="4wc70r"
a, b = [1, 2]
```解凍後のスタックは概念的には次のようになります。```text id="ajf8h4"
[1, 2]
```次に、消費値を格納します。

ネストされた解凍:```python id="mvd1oq"
a, (b, c) = value
```複数のアンパック操作と慎重なスタック順序付けが必要です。

## 29.15 比較

比較ではオペランドが使用され、ブール結果または別の比較結果がプッシュされます。```python id="z499ha"
x < y
```概念的には:```text id="58s9qc"
LOAD_FAST x
LOAD_FAST y
COMPARE_OP <
```スタック:```text id="8lxjax"
before COMPARE_OP: [x, y]
after COMPARE_OP:  [result]
```連鎖比較はより微妙です。```python id="97mjg8"
a < b < c
```Python が評価する`b`1 回だけ実行し、2 回目の比較のために保存します。

概念的には:```text id="202tno"
load a
load b
compare a < b
if false, stop
keep b
load c
compare b < c
```スタック マシンは評価を避けるために値を複製またはローテーションする必要があります。`b`2回。

## 29.16 ブール値の短絡

ブール式ではジャンプとスタックが使用されます。```python id="u61h0m"
a and b
```もし`a`false の場合、Python は返します`a`評価せずに`b`

概念的には:```text id="j7lc2d"
LOAD_FAST a
if false, jump to end and keep a
POP_TOP
LOAD_FAST b
end:
```のために:```python id="v0ux9n"
a or b
```もし`a`true の場合、Python は返します`a`評価せずに`b````text id="38razm"
LOAD_FAST a
if true, jump to end and keep a
POP_TOP
LOAD_FAST b
end:
```スタックは、選択した結果を保存するために使用されます。

これは、Python がなぜ`and`そして`or`オペランドの 1 つを返します (必ずしもそうである必要はありません)`True`または`False````python id="n4qhf6"
result = [] or [1, 2]
```戻り値:```text id="n0s1kg"
[1, 2]
```## 29.17 条件式

条件式:```python id="77rz4v"
x if cond else y
```枝を使用します。

概念的なバイトコード:```text id="3epj6l"
LOAD_FAST cond
POP_JUMP_IF_FALSE else_branch

LOAD_FAST x
JUMP end

else_branch:
LOAD_FAST y

end:
```式の後のスタックには、次のいずれかの値が 1 つだけ含まれます。`x`または`y`

これは重要なコンパイラの不変条件です。どのブランチが実行されるかに関係なく、マージ ポイントのスタック形状には互換性がなければなりません。

## 29.18 スタック形状の不変条件

制御フローのマージ ポイントでは、コンパイラは一貫したスタック形状を維持する必要があります。

例:```python id="hq40fy"
if cond:
    x = a
else:
    x = b
```制御フローが再結合する前に、両方のブランチが互換性のある状態でスタックを離れる必要があります。

式レベルの分岐の場合:```python id="25hx6g"
result = a if cond else b
```両方の分岐はスタックに 1 つの値を残す必要があります。```text id="6wovt1"
then branch leaves: [a]
else branch leaves: [b]
merge expects:      [one value]
```1 つのブランチが 2 つの値を残し、もう 1 つのブランチが 1 つの値を残した場合、後のバイトコードは何を消費すればよいのかわかりません。

したがって、スタックの正確性はインタプリタの責任であると同時にコンパイラの責任でもあります。

## 29.19 ループとスタック

ループはジャンプを使用します。スタックはループ境界でバランスをとる必要があります。

例:```python id="0n8atk"
while i < n:
    i += 1
```概念的な実行:```text id="f8q5zf"
loop_start:
    LOAD_FAST i
    LOAD_FAST n
    COMPARE_OP <
    POP_JUMP_IF_FALSE loop_end

    LOAD_FAST i
    LOAD_CONST 1
    BINARY_OP +=
    STORE_FAST i

    JUMP loop_start

loop_end:
````loop_start`、スタックは反復ごとに期待された形状になっている必要があります。通常、ステートメントレベルのループの場合は空です。

ループ本体に余分なスタック値が残っている場合、次の反復で破損が開始されます。

## 29.20 for ループ

`for`ループは反復子プロトコルに基づいています。```python id="n5tak3"
for x in xs:
    body(x)
```概念的には:```text id="xowkx6"
LOAD_FAST xs
GET_ITER

loop:
    FOR_ITER end
    STORE_FAST x

    LOAD_FAST body
    LOAD_FAST x
    CALL 1
    POP_TOP

    JUMP loop

end:
```イテレータはループの繰り返し全体にわたってスタック上に残ります。

簡略化されたスタック ビュー:```text id="hiog6p"
after GET_ITER:
    [iterator]

FOR_ITER success:
    [iterator, next_value]

STORE_FAST x:
    [iterator]

loop body runs:
    [iterator]

FOR_ITER exhausted:
    []
```これは、制御フロー領域全体にわたって意図的に値がスタック上に残る重要なケースです。

## 29.21 挑戦し、例外を除き、規律を積み重ねる

例外処理はスタックの状態にも依存します。

のために:```python id="ehr6ve"
try:
    risky()
except ValueError:
    recover()
```通訳者は次のことを知っておく必要があります。```text id="tf513e"
which bytecode range is protected
where the handler starts
what stack depth to restore on exception
what exception values to make available
```例外が発生すると、CPython はハンドラーに入る前に既知のスタック状態に戻ります。

スタックに一時値が含まれているときに例外が発生する可能性があるため、これが必要です。

例:```python id="mkevyx"
x = f(g(), h())
```もし`h()`を発生させる場合、スタックにはすでに含まれている可能性があります`f`そしてその結果は`g()`。例外処理では、これらの一時ファイルを正しくクリーンアップする必要があります。

## 29.22 最後にブロック

`finally`ブロックは戻り時、例外の伝播中に実行する必要があります。`break` または`continue````python id="jvfmzp"
def f():
    try:
        return 1
    finally:
        cleanup()
```インタプリタは、クリーンアップの実行中に保留中のリターンを保持する必要があります。

概念的には:```text id="9zb2u4"
prepare return value
enter finally
run cleanup
if cleanup succeeds:
    resume return
if cleanup raises:
    discard pending return and propagate new exception
```スタックとフレームの状態は、この保留中の制御フローを正しく表す必要があります。`finally`単なる支店ではありません。これは、制御フローのインターセプト ポイントです。

## 29.23 ステートメントあり

`with`ステートメントは呼び出しにコンパイルされます`__enter__`そして`__exit__````python id="swotvs"
with cm() as value:
    body(value)
```概念的な動作:```text id="29ed2g"
manager = cm()
exit = manager.__exit__
value = manager.__enter__()
try:
    body(value)
finally:
    exit(...)
```スタックは、ブロックが終了するまでコンテキスト マネージャーの終了機構を利用可能な状態に保つために使用されます。

これにより、CPython が正しく呼び出せるようになります。`__exit__`正常完了と例外完了の場合。

## 29.24 スタック値は参照です

すべてのスタック スロットは、Python オブジェクトへの参照を保持します。

つまり、スタック操作は参照カウントと相互作用します。

命令がオブジェクトをプッシュするときは、オブジェクトが生きたままであることを確認する必要があります。

命令がスタックからオブジェクトを削除し、そのオブジェクトを所有しなくなった場合、必要に応じて参照を解放する必要があります。

単純化された二項演算:```c id="0lju6g"
right = pop();
left = pop();

result = PyNumber_Add(left, right);

Py_DECREF(left);
Py_DECREF(right);

if (result == NULL) {
    goto error;
}

push(result);
```正確な所有権ルールは、オペコードとヘルパー関数によって異なります。しかし、不変条件は厳密です。```text id="2wi5o6"
objects on the stack must remain alive
objects removed from the stack must be released when no longer needed
```スタックのバグは、メモリ リーク、解放後の使用、クラッシュ、または間違った結果になる可能性があります。

## 29.25 借用した参照と所有した参照

CPython 命令では、借用された参照と所有されている参照が区別されることがよくあります。

借用された参照は、コードがオブジェクトを一時的に使用できるが、新しい参照を所有しないことを意味します。

所有されている参照は、コードが最終的にそれを解放する必要があることを意味します。

一般に、スタック エントリは、早期の割り当て解除を防ぐ方法で、所有されている参照またはライブ参照として扱われる必要があります。

たとえば、から定数をロードします。`co_consts`定数タプルから借用した参照を読み取り、スタックに置く前にその参照カウントをインクリメントする場合があります。

概念的には:```c id="83om3h"
value = code->consts[index];   /* borrowed */
Py_INCREF(value);              /* now owned */
push(value);
```繰り返しますが、最新の CPython は最適化されたマクロを使用する可能性がありますが、有効期間ルールはそのままです。

## 29.26 スタックとガベージコレクション

値スタックはライブ ルート セットの一部です。

オブジェクトがアクティブなフレームのスタック上にある場合、そのオブジェクトは到達可能であるため、収集してはなりません。

例:```python id="mz56t7"
result = f(g(), h())
```呼び出しの評価中、中間オブジェクトはスタック上にのみ存在する可能性があります。これらはまだローカル変数またはコンテナーに格納されていない可能性があります。

概念的には:```text id="ioz8o6"
stack:
    f
    result_of_g
    result_of_h
```これらのオブジェクトは、フレーム スタックが参照しているため、ライブです。

戻りまたは例外によりフレームが巻き戻されると、CPython はスタックに保持されている参照を解放します。

## 29.27 スタックとトレースバック

例外が発生した場合は、トレースバックが公開される前にスタックのクリーンアップを実行する必要があります。

例:```python id="2054au"
def f():
    return g(h())
```もし`h()`部分的に評価された呼び出しが発生します。`g`放棄しなければなりません。スタック上の一時データはデクリメントする必要があります。

トレースバックはフレームと命令の情報を保存しますが、任意の無効な一時スタック値は保存しません。

これが、インタプリタの例外処理コードが繊細である理由の 1 つです。

## 29.28 スタックベースの実行と特殊化

特化してもスタック マシン モデルは削除されません。

一般的な操作:```text id="w24zf8"
BINARY_OP +
```整数の加算、文字列の連結、またはその他の一般的なケースに特化した演算になる場合があります。

ただし、スタック コントラクトは変わりません。```text id="5e8nnk"
input stack:  [left, right]
output stack: [result]
```これは重要です。特殊化により、バイトコード レベルのスタック動作を維持しながら、内部高速パスを変更できます。

例えば:```text id="1cdxfe"
generic BINARY_OP
    pop left and right
    call generic numeric dispatch
    push result

specialized int add
    pop left and right
    verify both are exact ints
    perform fast int path
    push result
```ガードが失敗した場合、インタープリタは汎用パスにフォールバックできます。

## 29.29 インラインキャッシュとスタック効果

インライン キャッシュはバイトコード命令の近くにメタデータを保存しますが、通常のスタック値のように動作しません。

のために:```text id="6a9ugx"
LOAD_ATTR name
CACHE
CACHE
```キャッシュ エントリはバイトコード スペースまたはインタープリタ メタデータを占有する可能性がありますが、それらは Python 評価スタックにプッシュされる値ではありません。

のスタック効果`LOAD_ATTR`残り:```text id="08gvhh"
input:  [object]
output: [attribute_value]
```キャッシュは入力から出力への移行を加速します。

この区別は、逆アセンブリを読み取るときに重要です。表示される一部のエントリは、ユーザーに表示される操作ではなく、インタープリタ キャッシュ機構を表す場合があります。

## 29.30 スタックの深さの例

次のことを考慮してください。```python id="w9gktu"
def f(a, b, c, d):
    return (a + b) * (c + d)
```概念的なバイトコード:```text id="2b4bdv"
LOAD_FAST a
LOAD_FAST b
BINARY_OP +
LOAD_FAST c
LOAD_FAST d
BINARY_OP +
BINARY_OP *
RETURN_VALUE
```スタックの進化:

|ステップ |指示 |スタック |
|---:|---|---|
| 0 |開始 |`[]`|
| 1 |`LOAD_FAST a` | `[a]`|
| 2 |`LOAD_FAST b` | `[a, b]`|
| 3 |`BINARY_OP +` | `[a+b]`|
| 4 |`LOAD_FAST c` | `[a+b, c]`|
| 5 |`LOAD_FAST d` | `[a+b, c, d]`|
| 6 |`BINARY_OP +` | `[a+b, c+d]`|
| 7 |`BINARY_OP *` | `[(a+b)*(c+d)]`|
| 8 |`RETURN_VALUE`|戻る |

この概念的なシーケンスでは、最大スタック深さは 3 です。

コンパイラはこれを静的に計算します。

## 29.31 評価順序

Python では評価順序が定義されています。 CPython バイトコードはそれを保持する必要があります。

関数呼び出しの引数の場合:```python id="mm75rh"
f(a(), b(), c())
```呼び出しは左から右に行われます。

概念的には:```text id="1fhff2"
load f
call a()
push result of a
call b()
push result of b
call c()
push result of c
call f with three arguments
```もし`b()`上げる、`c()`走ってはいけません。

スタック マシンは、エラー時に一時的な値をクリーンアップしながら、この順序を保持する必要があります。

## 29.32 副作用

Python 式には副作用がある可能性があるため、スタック実行では操作の順序を任意に変更できません。

例:```python id="1d9mjp"
items[i()] = value()
```への呼び出し`i()`そして`value()`代入評価は Python で指定された順序で行われる必要があります。

別の例:```python id="hz0uwv"
f(x, x := 10)
```代入式、関数呼び出し、属性アクセス、インデックス作成はすべてプログラムの状態に影響を与える可能性があります。

コンパイラは、これらの副作用を考慮したバイトコードを出力する必要があります。スタックは単なる実行メカニズムであり、セマンティクスを再配置するためのライセンスではありません。

## 29.33 スタックマシンと Python 呼び出しスタック

評価スタックと Python 呼び出しスタックは異なります。

評価スタックは 1 つのフレーム内にあります。```text id="xm2eri"
frame f
    value stack: temporary operands
```Python 呼び出しスタックはフレームのチェーンです。```text id="6gz9pq"
frame a
    calls frame b
        calls frame c
```関数呼び出しにより新しいフレームが作成されます。その新しいフレームには独自の評価スタックがあります。

例:```python id="hh6rmw"
def a():
    return b(1 + 2)

def b(x):
    return x * 10
```その間`a`、式`1 + 2`用途`a`の値スタック。いつ`b`と呼ばれます、`b`独自のフレームと独自の値スタックを取得します。

## 29.34 スタックマシン vs C スタック

CPython 値スタックは、ネイティブ C スタックとも異なります。

|スタック |意味 |
|---|---|
| Python 値スタック | 1 つのフレーム内の一時的な Python オブジェクト オペランド |
| Python 呼び出しスタック | Python フレームのチェーン |
| Cスタック |インタプリタと C 関数によって使用されるネイティブ呼び出しフレーム |

トレースバックには、完全な C スタックではなく、Python 呼び出しスタックが表示されます。

値スタックは通常、トレースバックには表示されません。これはインタープリタ内部の実行構造です。

## 29.35 スタックの破損

インタプリタは正確なスタック規律に依存します。

オペコードがポップする値が多すぎる場合、十分な値をポップできない場合、間違った数の値をプッシュする場合、またはエラー パスを誤って処理した場合、フレームは無効になります。

考えられる影響:```text id="lbfom7"
wrong Python result
crash
memory leak
use-after-free
invalid traceback
corrupted exception state
security-sensitive memory bug
```これが、CPython バイトコード命令に明確に定義されたスタック効果が必要であり、コンパイラ出力が一貫していなければならない理由です。

コア開発の場合、スタック効果のチェックは表面的なものではありません。それは正しさの条件です。

## 29.36 スタックの動作の逆アセンブル

使用する`dis`スタックの実行を勉強します。```python id="ufy4lp"
import dis

def f(a, b, c):
    return (a + b) * c

dis.dis(f)
```次に、スタックを手動で追跡します。```text id="e45wnw"
start: []
LOAD_FAST a       [a]
LOAD_FAST b       [a, b]
BINARY_OP +       [a+b]
LOAD_FAST c       [a+b, c]
BINARY_OP *       [(a+b)*c]
RETURN_VALUE      return
```この演習は、ソース コード、バイトコード、およびフレーム実行を結び付けるため便利です。

## 29.37 小さなスタックインタプリタ

算術用の最小スタック インタープリタでモデルを表示できます。```python id="e126pg"
LOAD_CONST = "LOAD_CONST"
ADD = "ADD"
MUL = "MUL"
RETURN = "RETURN"

def run(code, consts):
    stack = []

    for op, arg in code:
        if op == LOAD_CONST:
            stack.append(consts[arg])

        elif op == ADD:
            right = stack.pop()
            left = stack.pop()
            stack.append(left + right)

        elif op == MUL:
            right = stack.pop()
            left = stack.pop()
            stack.append(left * right)

        elif op == RETURN:
            return stack.pop()

    raise RuntimeError("missing RETURN")
```走る:```python id="57jzcg"
code = [
    (LOAD_CONST, 0),
    (LOAD_CONST, 1),
    (ADD, None),
    (LOAD_CONST, 2),
    (MUL, None),
    (RETURN, None),
]

consts = [2, 3, 4]

print(run(code, consts))
```これは以下を計算します:```text id="jwwxxd"
(2 + 3) * 4
```モデルは CPython よりもはるかに小さいですが、オペランド スタックの考え方は同じです。

## 29.38 おもちゃのインタープリターにローカルを追加する

もう少しリッチな通訳が現地の人々をサポートします。```python id="w8sn78"
LOAD_FAST = "LOAD_FAST"
STORE_FAST = "STORE_FAST"
LOAD_CONST = "LOAD_CONST"
ADD = "ADD"
RETURN = "RETURN"

def run(code, consts, locals_):
    stack = []

    for op, arg in code:
        if op == LOAD_FAST:
            stack.append(locals_[arg])

        elif op == STORE_FAST:
            locals_[arg] = stack.pop()

        elif op == LOAD_CONST:
            stack.append(consts[arg])

        elif op == ADD:
            right = stack.pop()
            left = stack.pop()
            stack.append(left + right)

        elif op == RETURN:
            return stack.pop()

    raise RuntimeError("missing RETURN")
```のために:```python id="7k6vgh"
x = a + 1
return x
```バイトコードは次のようになります。```python id="iq1afx"
code = [
    (LOAD_FAST, "a"),
    (LOAD_CONST, 0),
    (ADD, None),
    (STORE_FAST, "x"),
    (LOAD_FAST, "x"),
    (RETURN, None),
]

print(run(code, [1], {"a": 41}))
```これは、CPython の高速ローカルと値スタックと同じパターンを示していますが、CPython はホット パスで Python 辞書ではなくインデックスとオブジェクト ポインタを使用します。

## 29.39 スタックモデルが説明するもの

スタック モデルは、多くの CPython の動作を説明します。```text id="to5h59"
why bytecode instructions are small
why expression evaluation has a natural order
why co_stacksize exists
why local variables can be slot-indexed
why disassembly can be manually simulated
why exception paths must clean temporary values
why function calls have careful stack layouts
why specialization must preserve stack effects
why compiler correctness depends on stack balance
```関数のスタックをトレースできるようになると、CPython バイトコードがはるかに読みやすくなります。

## 29.40 よくある誤解

|誤解 |正しいモデル |
|---|---|
|スタックには生のマシン整数が格納されます。 Python オブジェクトへの参照を保存します。
|値スタックは呼び出しスタックと同じです |各フレームには独自の値スタックがあります。
|バイトコード命令はすべてのオペランドに名前を付けます。多くのオペランドはスタック上に暗黙的に存在します。
|スタック順序は関係ありません。オペランドの順序は重要です |
|ブランチは任意のスタック形状を残すことができます。マージポイントには互換性のあるスタック形状が必要です。
|特殊化によりスタック モデルが削除される |同じスタックコントラクトを維持します。
|`co_stacksize`動的です |これはバイトコード スタック効果から計算されます。
|一時変数は常にローカル変数に存在します。多くは値スタック上にのみ存在します。

## 29.41 読書戦略

スタックベースの実行を調べるには、小さな例を使用します。

以下から始めます:```python id="5xvmyd"
def f(a, b):
    return a + b
```それから:```python id="l91624"
def g(a, b, c):
    return (a + b) * c
```それから:```python id="0pq8z6"
def h(xs):
    total = 0
    for x in xs:
        total += x
    return total
```各機能について:```python id="fl0izq"
import dis
dis.dis(function_name)
```追跡:```text id="lfc0ha"
which instructions push
which instructions pop
where the stack is empty
where values remain across jumps
where calls consume arguments
where returns consume values
```このプロセスにより、通訳者の具体的なメンタルモデルが構築されます。

## 29.42 章の概要

CPython のバイトコード実行はスタックベースです。各フレームには、一時的な Python オブジェクト参照に使用される値スタックがあります。バイトコード命令は値をスタックにプッシュし、スタックから値を消費し、後の命令のために結果を残します。

本質的なパターンは次のとおりです。```text id="ebvtmi"
load operands
operate on top of stack
push result
store, return, call, branch, or continue
```このモデルでは、式の評価、ローカル変数へのアクセス、呼び出し、ループ、例外のクリーンアップ、コンテナーの構築、アンパック、特殊化、およびコンパイラー スタック分析について説明します。

スタック マシンの形状は単純ですが、CPython 実行の中心となります。これを理解するとバイトコードが読みやすくなり、評価ループが具体的になります。