news 2026/9/10 1:37:27

CPython JIT 关闭期断言修复解读:冷执行器(Cold Executor)的 vm_data 初始化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CPython JIT 关闭期断言修复解读:冷执行器(Cold Executor)的 vm_data 初始化

CPython JIT 关闭期断言修复解读:冷执行器(Cold Executor)的 vm_data 初始化

【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython

导读

本文围绕 CPython 源码仓库中一条针对JIT(即时编译器)与解释器关闭流程的缺陷修复记录展开,剖析"解释器关闭期间触发 JIT 断言失败"这一问题的根因与修复方式。通过阅读本文,你将理解 CPython 优化器/执行器(Executor)体系中_Py_ExecutorInit()_PyVMData字段的初始化约定,掌握"冷执行器"(Cold Executor)为何会绕过标准初始化流程、以及如何通过补齐字段初始化来消除关闭期断言。核心修复位于 Python/optimizer.c 与 Include/internal/pycore_optimizer.h。

1. 修复记录原文

本文依据的关联文档是仓库内的一条 NEWS 变更记录:

Fix a JIT assertion during interpreter shutdown by initializingvm_datafields for cold executors that bypass_Py_ExecutorInit().(来源:Misc/NEWS.d/next/Core_and_Builtins/2026-07-19-16-18-25.gh-issue-154014.CJsjVL.rst)

这条记录属于Core_and_Builtins类别,对应 GitHub Issue #154014,主要描述了两件事:

  1. 症状:解释器关闭(interpreter shutdown)期间会触发一条 JIT 断言失败;
  2. 根因与修复:某些"冷执行器"(cold executors)在创建时绕过了_Py_ExecutorInit()这一标准初始化函数,导致其vm_data字段未被完整初始化,需要在创建路径中显式补齐。

下面结合源码逐层展开,说明这条修复背后的技术机理。

2. 背景:CPython 的优化器与执行器体系

CPython 3.13+ 引入了基于字节码到微操作(uop,micro-operation)的优化器(Optimizer)执行器(Executor)机制。热点字节码片段会被优化器编译为一条"trace",封装在_PyExecutorObject中;解释器在执行时遇到ENTER_EXECUTOR指令,会尝试复用已编译的执行器以提升执行效率,这也是 CPython JIT 功能(_Py_JIT编译选项)的基础载体。

执行器的核心数据结构定义在 Include/internal/pycore_optimizer.h:

typedef struct { uint8_t opcode; uint8_t oparg; uint8_t valid; uint8_t chain_depth; // Must be big enough for MAX_CHAIN_DEPTH - 1. bool cold; uint8_t pending_deletion; int32_t index; // Index of ENTER_EXECUTOR (if code isn't NULL, below). int32_t bloom_array_idx; // Index in interp->executor_blooms/executor_ptrs. _PyExecutorLinkListNode links; // Used by deletion list. PyCodeObject *code; // Weak (NULL if no corresponding ENTER_EXECUTOR). } _PyVMData; typedef struct _PyExecutorObject { PyObject_VAR_HEAD const _PyUOpInstruction *trace; _PyVMData vm_data; /* Used by the VM, but opaque to the optimizer */ uint32_t exit_count; uint32_t code_size; size_t jit_size; void *jit_code; _PyJitCodeRegistration *jit_registration; _PyExitData exits[1]; } _PyExecutorObject;

值得注意的关键设计:

  • _PyVMData被注释为"Used by the VM, but opaque to the optimizer",即对优化器不透明、由虚拟机(VM)负责维护的元数据区;
  • 其中valid表示执行器是否仍有效(失效后会被替换为冷执行器);pending_deletion标记是否已进入待删除链表;bloom_array_idx记录该执行器在解释器级executor_blooms/executor_ptrs数组中的下标;code弱引用关联的代码对象;
  • _PyExecutorObjectexits[1]是柔性数组(flexible array),存放若干个出口(_PyExitData),每个出口指向"下一个"执行器,用于构建执行链。

3. 正常路径:_Py_ExecutorInit()的初始化约定

所有常规执行器在投入使用之前,都必须调用 Python/optimizer.c 中的_Py_ExecutorInit()完成vm_data的初始化:

/* This must be called by optimizers before using the executor */ int _Py_ExecutorInit(_PyExecutorObject *executor, const _PyBloomFilter *dependency_set) { executor->vm_data.valid = true; executor->vm_data.pending_deletion = 0; executor->vm_data.code = NULL; if (link_executor(executor, dependency_set) < 0) { return -1; } return 0; }

该函数承担三类职责:

  1. 设置有效性标志vm_data.valid = true,使执行器可被 VM 正常使用;
  2. 清零删除标记vm_data.pending_deletion = 0,确保执行器不处于"待删除"状态;
  3. 登记到解释器级数组:通过link_executor()将执行器与依赖集合(_PyBloomFilter,布隆过滤器)挂到PyInterpreterStateexecutor_blooms/executor_ptrs数组,并把数组下标写入executor->vm_data.bloom_array_idx(见 Python/optimizer.c)。

调用点在优化器完成 trace 编译之后(Python/optimizer.c):若_Py_ExecutorInit()返回负值,说明登记失败,优化器会放弃该执行器。

与之对应的注销逻辑是unlink_executor()(Python/optimizer.c):它用"交换删除"(swap-remove)方式从数组中移除执行器,并维护bloom_array_idx的一致性,最后将其置为-1

4. 冷执行器:绕过_Py_ExecutorInit()的特殊路径

当某个执行器因依赖失效、去优化(deoptimization)等原因被判定不可用时,VM 会用一个冷执行器(cold executor)替换它。冷执行器本身不再执行任何优化逻辑,而是触发重新跟踪(re-tracing)流程。该对象由make_cold_executor()创建(Python/optimizer.c):

static _PyExecutorObject * make_cold_executor(uint16_t opcode) { _PyExecutorObject *cold = allocate_executor(0, 1); if (cold == NULL) { Py_FatalError("Cannot allocate core JIT code"); } ((_PyUOpInstruction *)cold->trace)->opcode = opcode; // Cold executors bypass _Py_ExecutorInit(). cold->vm_data.valid = true; cold->vm_data.pending_deletion = 0; cold->vm_data.code = NULL; // This is initialized to false so we can prevent the executor // from being immediately detected as cold and invalidated. cold->vm_data.cold = false; #ifdef _Py_JIT cold->jit_code = NULL; cold->jit_size = 0; if (_PyJIT_Compile(cold, cold->trace, 1)) { _PyExecutor_Free(cold); Py_FatalError("Cannot allocate core JIT code"); } #endif _Py_SetImmortal((PyObject *)cold); return cold; }

源码注释明确说明:"Cold executors bypass_Py_ExecutorInit()."——即冷执行器是这条初始化约定的唯一例外。原因不难理解:

  • 冷执行器不会被登记进解释器级的executor_blooms/executor_ptrs数组(它不需要参与依赖失效追踪),因此不必调用link_executor()
  • 但它仍然要作为_PyExecutorObject被 VM 读取vm_data字段(例如validpending_deletioncold等),一旦这些字段残留未初始化(如堆上随机值或calloc零值),就可能引发错误判断。

冷执行器通过两个公开接口按需懒创建并缓存在解释器状态中(Python/optimizer.c):

  • _PyExecutor_GetColdExecutor()→ 缓存于interp->cold_executor,对应_COLD_EXIT_r00
  • _PyExecutor_GetColdDynamicExecutor()→ 缓存于interp->cold_dynamic_executor,对应_COLD_DYNAMIC_EXIT_r00

这两个缓存指针定义在 Include/internal/pycore_interp_structs.h。

5. 根因:关闭期清理为何触发断言

问题出在解释器关闭(interpreter shutdown / finalization)阶段对执行器体系的清理过程。从源码结构看,关闭期会遍历并注销仍存活(immortal)的执行器,其中就包括被替换后回收的冷执行器路径:

  • unlink_executor()中带有防御性断言assert(idx >= 0 && (size_t)idx < interp->executor_count)(Python/optimizer.c),它依赖vm_data.bloom_array_idx处于"已登记的有效下标或-1"这两个合法状态;
  • 而关闭期还会检查vm_data.pending_deletion/vm_data.valid/vm_data.cold等标志位来决定回收策略(参考 Python/bytecodes.c 中DEOPT_IF(!current_executor->vm_data.valid)以及vm_data.cold标志的读取逻辑);
  • 在启用 JIT(#ifdef _Py_JIT)的构建中,冷执行器还会携带jit_code/jit_size,关闭期需要正确释放 JIT 代码注册项。

若冷执行器的vm_data字段未初始化,这些标志位和下标在关闭期被读取时就是不确定值,从而触发断言失败(assertion failure)或错误的回收行为——这正是 NEWS 条目中 "JIT assertion during interpreter shutdown" 的直接表现。

6. 修复方式与验证

修复的核心思路是:让绕过_Py_ExecutorInit()的冷执行器,也在创建时把vm_data的关键字段显式初始化到位。对照 Python/optimizer.c 可以看到,make_cold_executor()中现在补齐了:

// Cold executors bypass _Py_ExecutorInit(). cold->vm_data.valid = true; cold->vm_data.pending_deletion = 0; cold->vm_data.code = NULL; // This is initialized to false so we can prevent the executor // from being immediately detected as cold and invalidated. cold->vm_data.cold = false;

逐项对照_Py_ExecutorInit()的初始化内容:

vm_data字段_Py_ExecutorInit()(常规路径)make_cold_executor()(冷路径)作用
validtruetrue标记执行器有效,可被 VM 使用
pending_deletion00未进入待删除链表
codeNULLNULL冷执行器不关联任何ENTER_EXECUTOR代码位置
cold不显式设置false防止冷执行器被立即识别为"已冷却"而再次失效
bloom_array_idxlink_executor()写入不登记,保持未用状态冷执行器不参与依赖布隆过滤器追踪

其中vm_data.cold = false是一个容易被忽略的关键细节:_PyExecutorObject中的cold字段若为真,VM 会将其视为"失效待替换"对象,从而立刻再次触发去优化,形成抖动(thrashing)。显式初始化为false是为了让这个冷执行器能够稳定存在,避免被立即检测为 cold 并再次失效。

7. 测试与回归验证建议

要验证该修复在本地构建的 CPython 中生效,可以按以下思路进行:

  1. 在启用 JIT 的构建配置下(--enable-jit对应的_Py_JIT宏)重新编译 CPython;
  2. 运行涉及解释器关闭路径的回归测试,重点观察 JIT 相关断言是否复现。仓库中的相关测试与调试工具包括Lib/test/test_jit(若存在)以及 Python/bytecodes.c 中针对执行器失效/冷替换逻辑的微操作定义;
  3. 用 debug 构建(启用assert)执行大量短生命周期解释器进程的启停,验证关闭期不再触发unlink_executor相关的断言。

由于冷执行器属于解释器内部实现细节,其正确性主要依赖vm_data初始化约定的一致性,因此该修复的回归风险点在于:未来若为_PyVMData新增字段,必须同步更新make_cold_executor()_Py_ExecutorInit()两处初始化点,保持两条路径语义一致。

8. 小结

这条 NEWS 记录虽然只有两句话,却精确刻画了 CPython 优化器/JIT 体系中的一个典型陷阱:"标准初始化函数之外的特殊对象创建路径,必须自行补齐 VM 依赖的元数据"。通过本文的源码级拆解可以看到:

  • 常规执行器经_Py_ExecutorInit()完成vm_data初始化并登记到解释器级数组(Python/optimizer.c);
  • 冷执行器作为失效替换的兜底对象,绕过该函数,但必须手动初始化validpending_deletioncodecold等字段(Python/optimizer.c);
  • 缺失初始化会导致解释器关闭期的清理逻辑读到不确定的标志位,进而触发 JIT 断言失败,修复方式即补齐上述字段。

理解这一模式,对后续阅读 CPython 的_PyVMData、执行器失效机制(DEOPT_IF)以及cold_executor缓存路径(Include/internal/pycore_interp_structs.h)都有直接的帮助。

【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 1:35:22

浙大食品851考研专业课备考资料体系:从初试到复试全攻略

考研这条路&#xff0c;真正拉开差距的往往不是公共课&#xff0c;而是专业课。尤其像浙大食品科学与工程这种专业&#xff0c;851专业课的复习深度和资料质量&#xff0c;直接决定了你是在复试线边缘挣扎&#xff0c;还是稳稳站在录取名单的前排。我见过太多人公共课考得不错&…

作者头像 李华
网站建设 2026/9/10 1:33:43

MATLAB实现计及碳交易与需求响应的微网虚拟电厂日前优化调度

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 1:33:13

用开源组件搭建微信用户画像系统:从数据采集到画像看板

上周有个做私域运营的朋友问我&#xff1a;"微信上几百个客户&#xff0c;聊天记录、朋友圈、小程序订单都散在各个地方&#xff0c;到底怎么整理出一份能用的用户画像&#xff1f;"这不是他一个人的问题。我见过太多人把"用户画像"理解成"把微信通讯…

作者头像 李华
网站建设 2026/9/10 1:32:44

深度学习入门学完,我用梯度检查点在8GB显存上跑通了7B模型

深度学习入门学完,我用梯度检查点在8GB显存上跑通了7B模型 周一例会后,Leader 突然丢给我一个任务:把开源的 7B 大模型部署到内部知识库做文本分类。我看了眼机器配置--一台 RTX 3060 12GB 的工作站,心里直接凉了半截。但话已经说出去了,只能硬着头皮上。 当天下午我就用 Hugg…

作者头像 李华