Tier 2 optimizer may use canonical builtins for functions with a copied __builtins__ dictionary
まだ誰も着手していません。
- 主要言語
- Python
- スター
- 77.2k
- フォーク
- 35.9k
- PR マージ指標
- PR 指標を取得中
説明
Bug report
Bug description:
A function can use a builtins dictionary other than the interpreter’s canonical builtins dictionary. With the JIT enabled, _LOAD_GLOBAL_BUILTINS may nevertheless be constant-folded using interp->builtins.
A dictionary created by vars(builtins).copy() can share its keys table and keys version with the canonical dictionary while storing independent values. Replacing an existing value such as len does not necessarily change that keys version.
The optimizer validates and watches interp->builtins, then obtains the constant from that dictionary. It does not first verify that the optimized function’s func_builtins is the same dictionary. Consequently, an optimized executor can continue using the
canonical len after the function’s own builtins dictionary has been changed.
I reproduced this on main at commit a60343ed17785ebbcd43de9080cadd8e2541db6f. The non-JIT interpreter produces the expected result.
The direct reproducer is a regression introduced by GH-138379 and first appears in Python 3.15.0a1.
A related case involving distinct functions with the same function/code version but different builtins dictionaries dates back to GH-116460 and Python 3.13.0a5.
Minimal reproducer
import builtins
from _testinternalcapi import TIER2_THRESHOLD
namespace = {"__builtins__": vars(builtins).copy()}
exec(
"""
def size(value):
return len(value)
def run(value, n):
for _ in range(n):
result = size(value)
return result
""",
namespace,
)
print(namespace["run"]([0], TIER2_THRESHOLD))
namespace["__builtins__"]["len"] = lambda value: 42
print(namespace["run"]([0], 8))
Run it with a JIT-enabled build:
$ PYTHON_JIT=1 ./python repro.py
1
1
Expected output:
1
42
With the JIT disabled, the expected result is produced:
$ PYTHON_JIT=0 ./python repro.py
1
42
Proposed fix
Only constant-fold _LOAD_GLOBAL_BUILTINS when the current function uses the interpreter’s canonical builtins dictionary:
ctx->frame->func != NULL &&
ctx->frame->func->func_builtins == interp->builtins
For a custom builtins dictionary, retain the ordinary _LOAD_GLOBAL_BUILTINS operation so that it reads and guards the
function’s actual mapping.
A runtime identity guard should also accompany constants folded from the canonical builtins dictionary:
DEOPT_IF(BUILTINS() != tstate->interp->builtins);
The runtime guard is needed because function or code version checks alone do not identify the function’s builtins mapping.
A distinct function created from the same code object can use a different __builtins__ dictionary while satisfying the existing version guard.
Regression tests should cover:
- Mutating an existing value in a copied builtins dictionary after an executor has been created.
- Calling a different function with the same code/version but a different builtins dictionary through an existing optimized
executor.
CPython versions tested on:
3.15, CPython main branch
Operating systems tested on:
Linux
Linked PRs
- gh-157766
コントリビューションガイド
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
調査の方向性
まず、PYTHON_JIT=1 で最小再現ケースを実行し、JIT なしの結果と比較します。Tier 2 オプティマイザによる _LOAD_GLOBAL_BUILTINS の処理と既存のランタイムガードを追跡し、その後、コピーされた builtins 辞書と、同じコード/バージョンを共有する別々の関数の両方について回帰テストを追加します。最適化後に両方のケースで期待されるカスタム builtins の結果が生成されれば完了です。
索引モデルが issue の本文から書いたものです。
評価
- 技術スタック
- python
- 領域
- compilers, testing-qa
- issue の種類
- バグ
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 活発さ
- 停滞
- 明瞭さ
- 明確に書かれている
- 初心者へのやさしさ
- 30/100