python / python/cpython

Consider doing something about `-fzero-call-used-regs` and the tail-calling interpreter

オープン
#130,961 コメント 0 件 リアクション 8 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

build interpreter-core performance type-feature
主要言語
Python
スター
77.2k
フォーク
35.9k
PR マージ指標
PR 指標を取得中

説明

I'm not certain any action is appropriate here, but I'm cross-posting to the Python bugtracker at @Fidget-Spinner's request, and for visibility. Much of this text is copied from my issue on LLVM.

GCC and clang support the -fzero-call-used-regs option / zero_call_used_regs attribute. This option causes the compiler to zero out certain registers on exit from a function, to mitigate ROP and partially protect against certain architectural side-channels.

This option is not widely used at present, but some distributions enable it by default, such as NixOS.

The tail-call interpreter emits a separate function per opcode implementation. This means that -- when compiled using clang and -fzero-call-used-regs -- each opcode emits some additional xor instructions to zero-out certain registers, prior to the tail dispatch. The computed goto interpreter does not get these instructions, even though it emits very similar indirect jumps, which could potentially be used for similar JOP gadgets.

In my experiments, I see a ~2% hit from -fzero-call-used-regs with computed gotos, and ~3% with tail-calls. However, the tail-call hit can be substantially reduced (to ~1%, in my testing) by skipping zeroing only for the bytecode functions, using __attribute__((zero_call_used_regs("skip"))). (Note that I don't trust my benchmark setup to have stability at the ~1% scale, so take these exact numbers with a grain of salt.) I tested using this patch:

diff --git i/Python/ceval_macros.h w/Python/ceval_macros.h
index 1bef2b845d0..cddad845fea 100644
--- i/Python/ceval_macros.h
+++ w/Python/ceval_macros.h
@@ -77,9 +77,10 @@
     // Note: [[clang::musttail]] works for GCC 15, but not __attribute__((musttail)) at the moment.
 #   define Py_MUSTTAIL [[clang::musttail]]
 #   define Py_PRESERVE_NONE_CC __attribute__((preserve_none))
+#   define Py_SKIP_ZERO_USED_REGS __attribute__((zero_call_used_regs("skip")))
     Py_PRESERVE_NONE_CC typedef PyObject* (*py_tail_call_funcptr)(TAIL_CALL_PARAMS);

-#   define TARGET(op) Py_PRESERVE_NONE_CC PyObject *_TAIL_CALL_##op(TAIL_CALL_PARAMS)
+#   define TARGET(op) Py_PRESERVE_NONE_CC Py_SKIP_ZERO_USED_REGS PyObject *_TAIL_CALL_##op(TAIL_CALL_PARAMS)
 #   define DISPATCH_GOTO() \
         do { \

I also note that GCC, at least at present, does not zero registers on function exit due to a tail-call. My LLVM issue (llvm/llvm-project#129764) asks for LLVM to mirror that behavior, although note that there's currently also an issue on the GCC bugtracker asking for them to change that behavior.


On reflection my current opinion is there's probably nothing to be done in the CPython codebase, and this is more an issue for visibility for the CPython devs (and potentially CPython packagers) about a minor confounder when thinking about performance and hardening. But ultimately, of course, that's for you all to decide.

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

Python/ceval_macros.h から始め、issue で説明されている tail-call インタープリターパスと computed-goto インタープリターパスを比較します。コンパイラーの動作と制約について、リンクされている LLVM と GCC の issue を確認してください。この issue では具体的な変更も受け入れ基準も定義されていないため、done とするには CPython が対応すべきかどうかについて maintainer の判断が必要になります。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
c, python
領域
compilers, performance
issue の種類
機能追加
難易度
5/5
見積もり時間
1週間以上
活発さ
停滞
明瞭さ
説明が足りない
初心者へのやさしさ
25/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。