python / python/cpython

Tier 2 optimizer may use canonical builtins for functions with a copied __builtins__ dictionary

未关闭
#157,468 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

interpreter-core topic-JIT type-bug
主要语言
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:

  1. Mutating an existing value in a copied builtins dictionary after an executor has been created.
  2. 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

贡献指南

打开贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

调研方向

首先使用 PYTHON_JIT=1 运行最小复现用例,并将其与非 JIT 结果进行比较。跟踪 Tier 2 优化器对 _LOAD_GLOBAL_BUILTINS 的处理以及现有的运行时保护,然后为复制的 builtins 字典和共享代码/版本的不同函数分别添加回归覆盖。优化后两个用例都产生预期的自定义 builtins 结果,即表示完成。

由索引模型根据 Issue 内容生成。

评估

技术栈
python
领域
compilers, testing-qa
Issue 类型
缺陷
难度
4/5
预计耗时
3-5 天
活跃度
停滞
描述清晰度
描述清楚
新手友好度
30/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。