29. Thực thi dựa trên ngăn xếp
29. Thực thi dựa trên ngăn xếp
CPython thực thi hầu hết mã byte bằng máy xếp chồng. Máy ngăn xếp sử dụng ngăn xếp toán hạng ngầm thay vì đặt tên rõ ràng cho các thanh ghi nguồn và đích trong mỗi lệnh.
Trong CPython, ngăn xếp này thuộc về khung hiện tại. Nó lưu trữ các tham chiếu đến các đối tượng Python. Hướng dẫn mã byte đẩy đối tượng, bật đối tượng, kiểm tra đối tượng, thay thế đối tượng và sử dụng đối tượng để tính toán kết quả mới.
Một biểu thức đơn giản:python id="kz6m7x" x = a + b được thực hiện về mặt khái niệm như: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 Luồng hướng dẫn không nói:text id="ardj57" add local_a, local_b, local_x Thay vào đó, nó nói:```text id="jzzlmf"
load a
load b
add top two stack values
store result
## 29.1 Tại sao CPython sử dụng máy xếp chồng
Máy ngăn xếp cung cấp cho mã byte một biểu diễn nhỏ gọn. Nhiều lệnh không cần vị trí toán hạng rõ ràng vì chúng hoạt động trên đỉnh ngăn xếp.
Ví dụ:```python id="e4954y"
return (a + b) * c
```có thể được biểu diễn dưới dạng:```text id="nppt1h"
LOAD_FAST a
LOAD_FAST b
BINARY_OP +
LOAD_FAST c
BINARY_OP *
RETURN_VALUE
```các`BINARY_OP`lệnh không cần đặt tên cho các thanh ghi đầu vào của nó. Nó luôn lấy toán hạng từ ngăn xếp.
Điều này giữ cho mã byte đơn giản:```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
```Máy đăng ký sẽ mã hóa các vị trí toán hạng rõ ràng hơn:```text id="ugbzwm"
r3 = add r1, r2
r4 = mul r3, r0
return r4
```Điều đó có thể làm giảm số lượng lệnh trong một số thiết kế, nhưng nó yêu cầu định dạng mã byte khác và chiến lược biên dịch khác. CPython trước đây đã sử dụng mô hình hướng ngăn xếp.
## 29.2 Ngăn xếp giá trị khung
Mỗi khung thực thi có bộ lưu trữ cho các giá trị tạm thời.
Về mặt khái niệm:```text id="ia1aa5"
frame
fast locals
cell variables
free variables
value stack
```Vì:```python id="39cqdr"
def f(a, b):
return a + b
```khung có thể được hiểu là:```text id="mq1wny"
localsplus
[0] a
[1] b
[2...] value stack
```Trong quá trình thực hiện:```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
```Các cửa hàng ngăn xếp`PyObject *`tài liệu tham khảo. Nó không lưu trữ các số nguyên C không được đóng hộp, các số nguyên gấp đôi hoặc các từ máy biểu thị các giá trị Python trong việc thực thi mã byte thông thường.
Vì`a = 2`Và`b = 3`, ngăn xếp chứa các con trỏ tới các đối tượng số nguyên Python.```text id="4h720d"
stack slot 0 -> PyLongObject(2)
stack slot 1 -> PyLongObject(3)
```## 29.3 Con trỏ ngăn xếp
Trình thông dịch duy trì một con trỏ ngăn xếp cho khung hiện tại.
Về mặt khái niệm:```c id="l1xymk"
PyObject **stack_pointer;
```Một cú đẩy ghi một giá trị vào khe ngăn xếp hiện tại và nâng cao con trỏ.```c id="0gozoa"
*stack_pointer = value;
stack_pointer++;
```Một cửa sổ bật lên di chuyển con trỏ trở lại và đọc giá trị.```c id="xj0vaq"
stack_pointer--;
value = *stack_pointer;
```Việc triển khai CPython thực sự sử dụng macro, bộ nhớ chuyên dụng, quy ước về quyền sở hữu tham chiếu và mã hướng dẫn được tạo. Nhưng hoạt động cốt lõi vẫn là đẩy và bật.
Một dấu vết ngăn xếp đơn giản:```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
```Con trỏ ngăn xếp đánh dấu vị trí ngăn xếp trống tiếp theo.
## 29.4 Hiệu ứng ngăn xếp
Mỗi lệnh đều có hiệu ứng ngăn xếp.
Hiệu ứng ngăn xếp cho biết lệnh thay đổi chiều cao ngăn xếp như thế nào.
| Hướng dẫn | Pops | Đẩy | Hiệu ứng ròng |
|---|---:|---:|---:|
|`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 | thoát khỏi khung |
|`CALL`| có thể gọi và lập luận | kết quả | biến |
|`BUILD_LIST`| N | 1 |`1 - N`|
Trình biên dịch sử dụng hiệu ứng ngăn xếp để tính toán kích thước ngăn xếp tối đa mà một đối tượng mã cần.
Giá trị đó được lưu trữ trong đối tượng mã dưới dạng`co_stacksize`.
```python id="e077y8"
def f(a, b, c):
return (a + b) * c
print(f.__code__.co_stacksize)
co_stacksizecho CPython biết mã này có thể yêu cầu bao nhiêu dung lượng ngăn xếp tạm thời trong quá trình thực thi.
29.5 Đánh giá một biểu thức nhị phân
Hãy xem xét:```python id="xjqe7p" def f(a, b, c): return a + b * c
Việc thực thi ngăn xếp khái niệm là:```text id="0lb9i7"
LOAD_FAST a
LOAD_FAST b
LOAD_FAST c
BINARY_OP *
BINARY_OP +
RETURN_VALUE
```Từng bước một:
| Bước | Hướng dẫn | Xếp chồng trước | Xếp chồng sau |
|---:|---|---|---|
| 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]`| trở lại |
Trình biên dịch chọn thứ tự lệnh duy trì ngữ nghĩa biểu thức Python trong khi sử dụng ngăn xếp.
## 29.6 Dấu ngoặc đơn và thứ tự ngăn xếp
Dấu ngoặc đơn thay đổi thứ tự đánh giá.```python id="g8ade6"
def f(a, b, c):
return (a + b) * c
```Mã byte khái niệm:```text id="1fshyd"
LOAD_FAST a
LOAD_FAST b
BINARY_OP +
LOAD_FAST c
BINARY_OP *
RETURN_VALUE
```Từng bước một:
| Bước | Hướng dẫn | Xếp chồng sau |
|---:|---|---|
| 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`| trở lại |
Máy ngăn xếp biểu diễn các biểu thức lồng nhau một cách tự nhiên bằng cách đánh giá các biểu thức con và để lại kết quả của chúng trên ngăn xếp.
## 29.7 Thứ tự toán hạng
Đối với các phép toán nhị phân, thứ tự toán hạng rất quan trọng.
Vì:```python id="dm51wp"
left - right
```trình thông dịch phải gọi phép trừ với toán hạng trái và phải chính xác.
Ngăn xếp trước`BINARY_OP -`là:```text id="ghn1ei"
[left, right]
```Thao tác sẽ bật toán hạng bên phải trước, sau đó đến toán hạng bên trái:```c id="8cs8ys"
right = pop();
left = pop();
result = subtract(left, right);
push(result);
```Nếu thứ tự bị đảo ngược, phép trừ, chia, so sánh và nhiều thao tác do người dùng xác định sẽ sai.
Vấn đề tương tự xuất hiện trong các cuộc gọi, lập chỉ mục, cài đặt thuộc tính và giải nén.
## 29.8 Bài tập tiêu thụ các giá trị ngăn xếp
Bài tập sử dụng các giá trị ngăn xếp được tạo bởi các biểu thức.```python id="dk9je6"
x = a + b
```Thực hiện khái niệm:```text id="nw82k0"
LOAD_FAST a stack: [a]
LOAD_FAST b stack: [a, b]
BINARY_OP + stack: [result]
STORE_FAST x stack: []
STORE_FASTtiêu thụ giá trị ngăn xếp trên cùng và lưu trữ nó vào một vị trí cục bộ.
Đối với nhiều nhiệm vụ:```python id="7bq1ez" x = y = value
Về mặt khái niệm:```text id="tc6ix1"
LOAD_FAST value
COPY
STORE_FAST x
STORE_FAST y
```Mã byte chính xác thay đổi tùy theo phiên bản Python, nhưng nguyên tắc ngăn xếp giống nhau: mục tiêu gán sử dụng các giá trị từ ngăn xếp.
## 29.9 Lệnh gọi hàm trên ngăn xếp
Các cuộc gọi hàm sử dụng ngăn xếp để sắp xếp các đối số có thể gọi được.
Vì:```python id="zluswf"
result = f(a, b)
```ngăn xếp phải chứa đối số có thể gọi được và các đối số theo bố cục mà lệnh gọi mong đợi.
Về mặt khái niệm:```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: []
```Lệnh gọi biết số lượng đối số vị trí. Nó sử dụng đối số và có thể gọi được, gọi bộ máy gọi và đẩy giá trị trả về.
Các lệnh gọi hàm phức tạp hơn khi chúng bao gồm:```text id="wi0q0p"
keyword arguments
default values
starred arguments
double-starred mappings
bound methods
callable classes
C functions
Python functions
coroutines
```Nhưng ngăn xếp đánh giá vẫn mang các toán hạng gọi ngay lập tức.
## 29.10 Cuộc gọi phương thức
Các cuộc gọi phương thức được tối ưu hóa vì chúng phổ biến.
Vì:```python id="4dpcx3"
obj.method(arg)
```mô hình ngây thơ là:```text id="bxncuk"
load obj
load attribute method
create bound method object
load arg
call bound method
```Việc tạo một đối tượng phương thức ràng buộc cho mỗi cuộc gọi có thể tốn kém.
CPython sử dụng mã byte gọi phương thức chuyên dụng và đường dẫn cuộc gọi để tránh các đối tượng tạm thời không cần thiết khi có thể.
Về mặt khái niệm, đường dẫn được tối ưu hóa cố gắng thể hiện:```text id="2vrzhp"
call underlying function with self and args
```thay vì:```text id="a30j23"
create bound method object
then call it
```Do đó, bố cục ngăn xếp cho các lệnh gọi phương thức có thể khác với các lệnh gọi hàm đơn giản về chi tiết triển khai. Mục tiêu vẫn giữ nguyên: sắp xếp trạng thái và đối số có thể gọi được để bộ máy gọi có thể chạy hiệu quả.
## 29.11 Truy cập thuộc tính và giá trị ngăn xếp
Truy cập thuộc tính sử dụng một đối tượng và tạo ra một giá trị thuộc tính.```python id="yr0lx4"
value = obj.name
```Về mặt khái niệm:```text id="k5xpeh"
LOAD_FAST obj stack: [obj]
LOAD_ATTR name stack: [value]
STORE_FAST value stack: []
LOAD_ATTRbật hoặc đọc đối tượng, thực hiện tra cứu thuộc tính và đưa ra kết quả.
Tra cứu thuộc tính có thể liên quan đến:text id="mh8xew" type lookup descriptor protocol instance dictionary lookup slots properties custom __getattribute__ custom __getattr__ inline cache checks Từ quan điểm của máy xếp chồng, thao tác rất đơn giản:```text id="x0kf3f"
object in
attribute value out
## 29.12 Đăng ký và lập chỉ mục
Đăng ký cũng sử dụng ngăn xếp.```python id="rey7pr"
value = xs[i]
```Thực hiện khái niệm:```text id="r32lp4"
LOAD_FAST xs stack: [xs]
LOAD_FAST i stack: [xs, i]
BINARY_SUBSCR stack: [value]
STORE_FAST value stack: []
```Đối với nhiệm vụ:```python id="enkjpe"
xs[i] = value
```ngăn xếp phải chứa vùng chứa, chỉ mục và giá trị được gán.
Về mặt khái niệm:```text id="8q3nbv"
LOAD_FAST value stack: [value]
LOAD_FAST xs stack: [value, xs]
LOAD_FAST i stack: [value, xs, i]
STORE_SUBSCR stack: []
```Thứ tự chính xác được chọn như vậy`STORE_SUBSCR`có thể bật toán hạng một cách nhất quán.
## 29.13 Container xây dựng
Danh sách, bộ dữ liệu, tập hợp và ký tự chính tả sử dụng ngăn xếp.```python id="8qazhy"
xs = [a, b, c]
```Về mặt khái niệm:```text id="q3tfjh"
LOAD_FAST a
LOAD_FAST b
LOAD_FAST c
BUILD_LIST 3
STORE_FAST xs
```Sự phát triển của ngăn xếp:
| Hướng dẫn | Xếp chồng sau |
|---|---|
|`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`tiêu thụ ba giá trị ngăn xếp và đẩy một đối tượng danh sách.
Đối với từ điển:```python id="u3ds6p"
d = {"a": x, "b": y}
```ngăn xếp có thể chứa các đối tượng khóa và giá trị trước khi lệnh xây dựng chính tả sử dụng chúng.
## 29.14 Giải nén
Việc giải nén đảo ngược việc xây dựng container.```python id="ooj3e0"
a, b = pair
```Về mặt khái niệm:```text id="ro1rmt"
LOAD_FAST pair
UNPACK_SEQUENCE 2
STORE_FAST a
STORE_FAST b
```Lệnh giải nén sử dụng một lần lặp và đẩy các phần tử của nó.
Trình biên dịch phải sắp xếp thứ tự lưu trữ sao cho các biến phù hợp nhận được giá trị chính xác.
Vì:```python id="4wc70r"
a, b = [1, 2]
```ngăn xếp sau khi giải nén có thể có khái niệm:```text id="ajf8h4"
[1, 2]
```Sau đó các cửa hàng tiêu thụ giá trị.
Giải nén lồng nhau:```python id="mvd1oq"
a, (b, c) = value
```yêu cầu nhiều thao tác giải nén và sắp xếp ngăn xếp cẩn thận.
## 29.15 So sánh
Việc so sánh sử dụng toán hạng và đẩy kết quả Boolean hoặc kết quả so sánh khác.```python id="z499ha"
x < y
```Về mặt khái niệm:```text id="58s9qc"
LOAD_FAST x
LOAD_FAST y
COMPARE_OP <
```ngăn xếp:```text id="8lxjax"
before COMPARE_OP: [x, y]
after COMPARE_OP: [result]
```So sánh theo chuỗi thì tinh tế hơn.```python id="97mjg8"
a < b < c
```Python đánh giá`b`chỉ một lần và giữ lại cho lần so sánh thứ hai.
Về mặt khái niệm:```text id="202tno"
load a
load b
compare a < b
if false, stop
keep b
load c
compare b < c
```Máy xếp chồng phải sao chép hoặc xoay các giá trị để tránh đánh giá`b`hai lần.
## 29.16 Đoản mạch Boolean
Biểu thức Boolean sử dụng bước nhảy và ngăn xếp.```python id="u61h0m"
a and b
```Nếu như`a`là sai, Python trả về`a`mà không đánh giá`b`.
Về mặt khái niệm:```text id="j7lc2d"
LOAD_FAST a
if false, jump to end and keep a
POP_TOP
LOAD_FAST b
end:
```Vì:```python id="v0ux9n"
a or b
```Nếu như`a`đúng, Python trả về`a`mà không đánh giá`b`.
```text id="38razm"
LOAD_FAST a
if true, jump to end and keep a
POP_TOP
LOAD_FAST b
end:
```Ngăn xếp được sử dụng để bảo toàn kết quả đã chọn.
Điều này giải thích tại sao Python`and`Và`or`trả về một trong các toán hạng của họ, không nhất thiết`True`hoặc`False`.
```python id="n4qhf6"
result = [] or [1, 2]
```trả về:```text id="n0s1kg"
[1, 2]
```## 29.17 Biểu thức điều kiện
Một biểu thức có điều kiện:```python id="77rz4v"
x if cond else y
```sử dụng các nhánh.
Mã byte khái niệm:```text id="3epj6l"
LOAD_FAST cond
POP_JUMP_IF_FALSE else_branch
LOAD_FAST x
JUMP end
else_branch:
LOAD_FAST y
end:
```Ngăn xếp sau biểu thức chứa chính xác một giá trị:`x`hoặc`y`.
Đây là một bất biến trình biên dịch chính. Bất kể nhánh nào thực thi, hình dạng ngăn xếp tại điểm hợp nhất phải tương thích.
## 29.18 Bất biến hình dạng ngăn xếp
Tại các điểm hợp nhất luồng điều khiển, trình biên dịch phải duy trì hình dạng ngăn xếp nhất quán.
Ví dụ:```python id="hq40fy"
if cond:
x = a
else:
x = b
```Cả hai nhánh phải rời khỏi ngăn xếp ở trạng thái tương thích trước khi luồng điều khiển tham gia lại.
Đối với các nhánh cấp biểu thức:```python id="25hx6g"
result = a if cond else b
```cả hai nhánh phải để lại một giá trị trên ngăn xếp.```text id="6wovt1"
then branch leaves: [a]
else branch leaves: [b]
merge expects: [one value]
```Nếu một nhánh để lại hai giá trị và một nhánh khác để lại một giá trị, thì mã byte sau này sẽ không biết phải tiêu thụ cái gì.
Do đó, tính đúng đắn của ngăn xếp là trách nhiệm của người biên dịch cũng như trách nhiệm của người phiên dịch.
## 29.19 Vòng lặp và ngăn xếp
Vòng lặp sử dụng bước nhảy. Ngăn xếp phải được cân bằng ở ranh giới vòng lặp.
Ví dụ:```python id="0n8atk"
while i < n:
i += 1
```Thực hiện khái niệm:```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:
```Tại`loop_start`, ngăn xếp phải ở dạng mong đợi sau mỗi lần lặp. Thông thường nó trống đối với các vòng lặp cấp câu lệnh.
Nếu thân vòng lặp để lại các giá trị ngăn xếp bổ sung thì lần lặp tiếp theo sẽ bắt đầu bị hỏng.
## 29,20 cho vòng lặp
A`for`vòng lặp dựa trên giao thức iterator.```python id="n5tak3"
for x in xs:
body(x)
```Về mặt khái niệm:```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:
```Trình lặp vẫn còn trên ngăn xếp qua các lần lặp vòng lặp.
Chế độ xem ngăn xếp đơn giản hóa:```text id="hiog6p"
after GET_ITER:
[iterator]
FOR_ITER success:
[iterator, next_value]
STORE_FAST x:
[iterator]
loop body runs:
[iterator]
FOR_ITER exhausted:
[]
```Đây là trường hợp quan trọng trong đó một giá trị được cố ý giữ nguyên trên ngăn xếp trên vùng luồng điều khiển.
## 29.21 Thử, Ngoại trừ và Kỷ luật xếp chồng
Xử lý ngoại lệ cũng phụ thuộc vào trạng thái ngăn xếp.
Vì:```python id="ehr6ve"
try:
risky()
except ValueError:
recover()
```Người phiên dịch phải biết:```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
```Khi một ngoại lệ xảy ra, CPython sẽ chuyển sang trạng thái ngăn xếp đã biết trước khi vào trình xử lý.
Điều này là cần thiết vì ngoại lệ có thể xảy ra khi ngăn xếp chứa các giá trị tạm thời.
Ví dụ:```python id="mkevyx"
x = f(g(), h())
```Nếu như`h()`tăng lên, ngăn xếp có thể đã chứa`f`và kết quả của`g()`. Xử lý ngoại lệ phải dọn sạch các phần tạm thời này một cách chính xác.
## 29.22 Khối cuối cùng
A`finally`khối phải chạy trong quá trình trả về, lan truyền ngoại lệ,`break`, hoặc`continue`.
```python id="jvfmzp"
def f():
try:
return 1
finally:
cleanup()
```Trình thông dịch phải bảo toàn kết quả trả về đang chờ xử lý trong khi thực hiện việc dọn dẹp.
Về mặt khái niệm:```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
```Trạng thái ngăn xếp và khung phải thể hiện chính xác luồng điều khiển đang chờ xử lý này.`finally`không chỉ là một chi nhánh. Đây là điểm chặn luồng điều khiển.
## 29.23 Với Tuyên bố
A`with`câu lệnh biên dịch thành các cuộc gọi đến`__enter__`Và`__exit__`.
```python id="swotvs"
with cm() as value:
body(value)
```Hành vi khái niệm:```text id="29ed2g"
manager = cm()
exit = manager.__exit__
value = manager.__enter__()
try:
body(value)
finally:
exit(...)
```Ngăn xếp được sử dụng để giữ cho máy thoát khỏi trình quản lý bối cảnh có sẵn cho đến khi khối thoát ra.
Điều này cho phép CPython gọi chính xác`__exit__`để hoàn thành bình thường và hoàn thành ngoại lệ.
## 29,24 Giá trị ngăn xếp là tài liệu tham khảo
Mỗi khe ngăn xếp chứa một tham chiếu đến một đối tượng Python.
Điều đó có nghĩa là các hoạt động ngăn xếp tương tác với việc đếm tham chiếu.
Khi một lệnh đẩy một đối tượng, nó phải đảm bảo đối tượng đó vẫn còn tồn tại.
Khi một lệnh xóa một đối tượng khỏi ngăn xếp và không còn sở hữu nó nữa, nó phải giải phóng tham chiếu nếu thích hợp.
Một phép toán nhị phân được đơn giản hóa:```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);
```Các quy tắc sở hữu chính xác khác nhau tùy theo mã opcode và chức năng trợ giúp. Nhưng tính bất biến là nghiêm ngặt:```text id="2wi5o6"
objects on the stack must remain alive
objects removed from the stack must be released when no longer needed
```Lỗi ngăn xếp có thể dẫn đến rò rỉ bộ nhớ, hết hạn sử dụng, lỗi hoặc kết quả sai.
## 29.25 Tài liệu tham khảo được mượn và sở hữu
Hướng dẫn CPython thường phân biệt các tham chiếu mượn với các tham chiếu sở hữu.
Tham chiếu mượn có nghĩa là mã có thể sử dụng đối tượng tạm thời nhưng không sở hữu tham chiếu mới.
Một tham chiếu được sở hữu có nghĩa là mã cuối cùng phải giải phóng nó.
Các mục trong ngăn xếp thường cần được coi là tài liệu tham khảo thuộc sở hữu hoặc trực tiếp theo cách ngăn chặn việc phân bổ sớm.
Ví dụ: tải một hằng số từ`co_consts`có thể đọc một tham chiếu mượn từ bộ hằng số, sau đó tăng số lượng tham chiếu của nó trước khi đặt nó vào ngăn xếp.
Về mặt khái niệm:```c id="83om3h"
value = code->consts[index]; /* borrowed */
Py_INCREF(value); /* now owned */
push(value);
```Một lần nữa, CPython hiện đại có thể sử dụng các macro được tối ưu hóa, nhưng quy tắc trọn đời vẫn được giữ nguyên.
## 29.26 Thu gom rác và rác
Ngăn xếp giá trị là một phần của tập gốc trực tiếp.
Nếu một đối tượng nằm trong ngăn xếp của khung đang hoạt động thì đối tượng đó có thể truy cập được và không được thu thập.
Ví dụ:```python id="mz56t7"
result = f(g(), h())
```Trong khi đánh giá cuộc gọi, các đối tượng trung gian có thể chỉ tồn tại trên ngăn xếp. Chúng có thể chưa được lưu trữ trong bất kỳ biến cục bộ hoặc vùng chứa nào.
Về mặt khái niệm:```text id="ioz8o6"
stack:
f
result_of_g
result_of_h
```Những đối tượng đó đang hoạt động vì ngăn xếp khung tham chiếu đến chúng.
Khi khung bị giãn do trả về hoặc ngoại lệ, CPython sẽ giải phóng các tham chiếu được giữ trong ngăn xếp.
## 29.27 Ngăn xếp và truy nguyên
Nếu một ngoại lệ xảy ra, việc dọn dẹp ngăn xếp phải diễn ra trước khi truy nguyên được hiển thị.
Ví dụ:```python id="2054au"
def f():
return g(h())
```Nếu như`h()`tăng lên, lệnh gọi được đánh giá một phần tới`g`phải bị bỏ rơi. Các giá trị tạm thời trên ngăn xếp phải được giảm đi.
Truy nguyên bảo tồn thông tin khung và hướng dẫn, nhưng không lưu giữ các giá trị ngăn xếp tạm thời chết tùy ý.
Đây là một phần lý do tại sao mã xử lý ngoại lệ trong trình thông dịch lại rất phức tạp.
## 29.28 Thực thi và chuyên môn hóa dựa trên ngăn xếp
Chuyên môn hóa không loại bỏ mô hình máy xếp chồng.
Một hoạt động chung:```text id="w24zf8"
BINARY_OP +
```có thể trở thành một phép toán chuyên biệt để cộng số nguyên, nối chuỗi hoặc trường hợp phổ biến khác.
Nhưng hợp đồng ngăn xếp vẫn giữ nguyên:```text id="5e8nnk"
input stack: [left, right]
output stack: [result]
```Điều này rất quan trọng. Chuyên môn hóa có thể thay đổi đường dẫn nhanh nội bộ trong khi vẫn duy trì hành vi ngăn xếp cấp mã byte.
Ví dụ:```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
```Nếu các biện pháp bảo vệ không thành công, trình thông dịch có thể quay trở lại đường dẫn chung.
## 29,29 Bộ nhớ đệm nội tuyến và hiệu ứng ngăn xếp
Bộ nhớ đệm nội tuyến lưu trữ siêu dữ liệu gần các hướng dẫn mã byte nhưng chúng không hoạt động giống như các giá trị ngăn xếp thông thường.
Vì:```text id="6a9ugx"
LOAD_ATTR name
CACHE
CACHE
```các mục trong bộ đệm có thể chiếm không gian mã byte hoặc siêu dữ liệu trình thông dịch, nhưng chúng không phải là các giá trị được đẩy lên ngăn xếp đánh giá Python.
Hiệu ứng ngăn xếp của`LOAD_ATTR`còn lại:```text id="08gvhh"
input: [object]
output: [attribute_value]
```Bộ đệm tăng tốc quá trình chuyển đổi từ đầu vào sang đầu ra.
Sự khác biệt này rất quan trọng khi đọc phần tháo gỡ. Một số mục được hiển thị có thể đại diện cho máy bộ nhớ đệm của trình thông dịch thay vì các hoạt động mà người dùng có thể nhìn thấy.
## 29.30 Ví dụ về độ sâu ngăn xếp
Hãy xem xét:```python id="w9gktu"
def f(a, b, c, d):
return (a + b) * (c + d)
```Mã byte khái niệm:```text id="2b4bdv"
LOAD_FAST a
LOAD_FAST b
BINARY_OP +
LOAD_FAST c
LOAD_FAST d
BINARY_OP +
BINARY_OP *
RETURN_VALUE
```Sự phát triển của ngăn xếp:
| Bước | Hướng dẫn | Ngăn xếp |
|---:|---|---|
| 0 | bắt đầu |`[]`|
| 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`| trở lại |
Độ sâu ngăn xếp tối đa là 3 trong chuỗi khái niệm này.
Trình biên dịch tính toán điều này một cách tĩnh.
## 29.31 Lệnh đánh giá
Python đã xác định thứ tự đánh giá. Mã byte CPython phải bảo toàn nó.
Đối với các đối số gọi hàm:```python id="mm75rh"
f(a(), b(), c())
```các cuộc gọi xảy ra từ trái sang phải.
Về mặt khái niệm:```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
```Nếu như`b()`tăng,`c()`không được chạy.
Máy ngăn xếp phải duy trì thứ tự này đồng thời dọn dẹp các giá trị tạm thời khi có lỗi.
## 29.32 Tác dụng phụ
Vì biểu thức Python có thể có tác dụng phụ nên việc thực thi ngăn xếp không thể sắp xếp lại thứ tự các thao tác một cách tùy ý.
Ví dụ:```python id="1d9mjp"
items[i()] = value()
```Các cuộc gọi tới`i()`Và`value()`phải xảy ra theo thứ tự được chỉ định của Python để đánh giá bài tập.
Một ví dụ khác:```python id="hz0uwv"
f(x, x := 10)
```Các biểu thức gán, lệnh gọi hàm, truy cập thuộc tính và lập chỉ mục đều có thể ảnh hưởng đến trạng thái chương trình.
Trình biên dịch phải phát ra mã byte tôn trọng các tác dụng phụ này. Ngăn xếp chỉ là một cơ chế thực thi chứ không phải là giấy phép để sắp xếp lại ngữ nghĩa.
## 29.33 Máy ngăn xếp so với ngăn xếp cuộc gọi Python
Ngăn xếp đánh giá và ngăn xếp lệnh gọi Python khác nhau.
Ngăn xếp đánh giá nằm trong một khung:```text id="xm2eri"
frame f
value stack: temporary operands
```Ngăn xếp cuộc gọi Python là một chuỗi các khung:```text id="6gz9pq"
frame a
calls frame b
calls frame c
```Lệnh gọi hàm sẽ tạo một khung mới. Khung mới đó có ngăn xếp đánh giá riêng.
Ví dụ:```python id="hh6rmw"
def a():
return b(1 + 2)
def b(x):
return x * 10
```Trong lúc`a`, biểu thức`1 + 2`công dụng`a`ngăn xếp giá trị của. Khi`b`được gọi là,`b`có khung riêng và ngăn xếp giá trị riêng.
## 29.34 Máy xếp chồng so với C Stack
Ngăn xếp giá trị CPython cũng khác với ngăn xếp C gốc.
| Ngăn xếp | Ý nghĩa |
|---|---|
| Ngăn xếp giá trị Python | Toán hạng đối tượng Python tạm thời bên trong một khung |
| Ngăn xếp cuộc gọi Python | Chuỗi khung Python |
| Ngăn xếp C | Các khung lệnh gọi gốc được trình thông dịch và hàm C sử dụng |
Truy nguyên hiển thị ngăn xếp lệnh gọi Python chứ không phải ngăn xếp C đầy đủ.
Ngăn xếp giá trị thường không hiển thị trong truy nguyên. Nó là một cấu trúc thực thi nội bộ thông dịch viên.
## 29,35 Tham nhũng ngăn xếp
Trình thông dịch dựa vào kỷ luật ngăn xếp chính xác.
Nếu một opcode hiển thị quá nhiều giá trị, không hiển thị đủ giá trị, đẩy sai số lượng giá trị hoặc xử lý sai đường dẫn lỗi thì khung sẽ trở nên không hợp lệ.
Hậu quả có thể xảy ra:```text id="lbfom7"
wrong Python result
crash
memory leak
use-after-free
invalid traceback
corrupted exception state
security-sensitive memory bug
```Đây là lý do tại sao các lệnh mã byte CPython phải có hiệu ứng ngăn xếp được xác định rõ ràng và tại sao đầu ra của trình biên dịch phải nhất quán.
Để phát triển cốt lõi, việc kiểm tra hiệu ứng ngăn xếp không mang tính thẩm mỹ. Đó là một điều kiện đúng đắn.
## 29.36 Tháo rời hành vi ngăn xếp
sử dụng`dis`nghiên cứu thực thi ngăn xếp.```python id="ufy4lp"
import dis
def f(a, b, c):
return (a + b) * c
dis.dis(f)
```Sau đó theo dõi ngăn xếp theo cách thủ công:```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
```Bài tập này hữu ích vì nó kết nối mã nguồn, mã byte và thực thi khung.
## 29.37 Một trình thông dịch ngăn xếp nhỏ
Trình thông dịch ngăn xếp tối thiểu cho số học có thể hiển thị mô hình.```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")
```Chạy:```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))
```Điều này tính toán:```text id="jwwxxd"
(2 + 3) * 4
```Mô hình này nhỏ hơn nhiều so với CPython, nhưng ý tưởng về ngăn xếp toán hạng thì giống nhau.
## 29.38 Thêm người dân địa phương vào Trình thông dịch đồ chơi
Một thông dịch viên phong phú hơn một chút có thể hỗ trợ người dân địa phương.```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")
```Vì:```python id="7k6vgh"
x = a + 1
return x
```mã byte có thể là:```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}))
```Điều này thể hiện mô hình tương tự như các cục bộ nhanh cộng với ngăn xếp giá trị của CPython, mặc dù CPython sử dụng các chỉ mục và con trỏ đối tượng thay vì từ điển Python trong đường dẫn nóng.
## 29.39 Mô hình ngăn xếp giải thích điều gì
Mô hình ngăn xếp giải thích nhiều hành vi của 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
```Khi bạn có thể theo dõi ngăn xếp cho một hàm, mã byte CPython sẽ trở nên dễ đọc hơn nhiều.
## 29.40 Những hiểu lầm thường gặp
| Hiểu lầm | Đúng mẫu |
|---|---|
| Ngăn xếp lưu trữ số nguyên máy thô | Nó lưu trữ các tham chiếu đến các đối tượng Python |
| Ngăn xếp giá trị giống như ngăn xếp cuộc gọi | Mỗi khung hình có ngăn xếp giá trị riêng |
| Hướng dẫn mã byte đặt tên tất cả các toán hạng | Nhiều toán hạng được ẩn trên ngăn xếp |
| Thứ tự ngăn xếp không quan trọng | Thứ tự toán hạng rất quan trọng |
| Các nhánh có thể để lại hình dạng ngăn xếp tùy ý | Điểm hợp nhất yêu cầu hình dạng ngăn xếp tương thích |
| Chuyên môn hóa loại bỏ mô hình ngăn xếp | Nó giữ cùng một hợp đồng ngăn xếp |
|`co_stacksize`năng động | Nó được tính toán từ các hiệu ứng ngăn xếp bytecode |
| Những người tạm thời luôn sống trong các biến cục bộ | Nhiều người chỉ sống nhờ vào giá trị |
## 29.41 Chiến lược đọc
Để nghiên cứu cách thực thi dựa trên ngăn xếp, hãy sử dụng các ví dụ nhỏ.
Bắt đầu với:```python id="5xvmyd"
def f(a, b):
return a + b
```Sau đó:```python id="l91624"
def g(a, b, c):
return (a + b) * c
```Sau đó:```python id="0pq8z6"
def h(xs):
total = 0
for x in xs:
total += x
return total
```Đối với mỗi chức năng:```python id="fl0izq"
import dis
dis.dis(function_name)
```Theo dõi:```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
```Quá trình này xây dựng một mô hình tinh thần cụ thể của người phiên dịch.
## 29.42 Tóm tắt chương
Việc thực thi mã byte của CPython dựa trên ngăn xếp. Mỗi khung có một ngăn xếp giá trị được sử dụng để tham chiếu đối tượng Python tạm thời. Các lệnh mã byte đẩy các giá trị vào ngăn xếp, sử dụng các giá trị từ ngăn xếp và để lại kết quả cho các lệnh sau.
Mẫu thiết yếu là:```text id="ebvtmi"
load operands
operate on top of stack
push result
store, return, call, branch, or continue
```Mô hình này giải thích việc đánh giá biểu thức, truy cập biến cục bộ, lệnh gọi, vòng lặp, dọn dẹp ngoại lệ, xây dựng vùng chứa, giải nén, chuyên môn hóa và phân tích ngăn xếp trình biên dịch.
Máy xếp chồng có hình dạng đơn giản nhưng nó là trung tâm của quá trình thực thi CPython. Việc hiểu nó làm cho mã byte có thể đọc được và làm cho vòng lặp đánh giá trở nên cụ thể.