LLVM の RISC-V での 25% の性能劣化の原因追跡

2026/04/13 4:52

LLVM の RISC-V での 25% の性能劣化の原因追跡

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

LLVM コマパイラへの最新更新(コミット #190235)により、RISC-V ターゲットにおいて顕著なパフォーマンスの回帰が発生し、特に乱数命令実行を備えた SiFive P550 CPU に多大な影響を与えました。根本原因は、指令結合器の誤ったロジックによる最適化範囲の不適切な縮小であり、具体的には

fpext
(float への double 変換)操作が整数から double への直接キャスト(
uitofp
)に誤って統合され、精度の削減に不可欠な下流の
fptrunc
(double への float 変換)ヒントが削除されてしまいました。

このエラーにより、コンパイラは遅い 33 サイクルの倍精度除算指令(

fdiv.d
)を出力せざるを得なくなり、より速い 19 サイクル単精度のそれ(
fdiv.s
)を採用することになりました。その結果、この特定のベンチマークにおける実行時間は約 24% 悪化し、影響を受けるコードにおいて LLVM は GCC よりも約 8% スローになりました。この問題は、
sitofp/uitofp
の連鎖に続いて
fpext
が存在する場合、それを直接キャストに安全に削減できるかが認識できなかった点にあると判明しており、特にその後に
fptrunc
が続く場合においてでした。

元のパッチの作成者はこの過ちを認め、PR #190550 によって修復を行いました。解決策は、進んだ範囲解析を拡張して

getMinimumFPType
を実装し、複雑な連鎖におけるキャスト認識を正しく処理するために新しいヘルパー関数
canBeCastedExactlyIntToFP
を導入することで成されていました。修復後のベンチマークでは、cc-perf における実行時間が約 25% 減少することが確認され、高遅延の
fdiv.d
指令を取り除くことに成功し、これらのワークロードについて LLVM のパフォーマンスを GCC と同等に回復させることができました。

本文

前回の投稿と同様に、本稿では RISC-V ターゲットにおけるベンチマークに関する私の分析結果について記述します。前回とは異なり、この特定のベンチマークにおいて GCC とのパフォーマンスのギャップを消滅させるパッチに合意することができました!

まとめ (TLDR) 最近の LLVM コミットにより、

isKnownExactCastIntToFP
fpext(sitofp x to float)
を倍精度浮動小数点に変換してから単一の直接
uitofp x to double
キャストへ折りたたむよう向上しました。しかしながら、この変更は誤って、
fpext
を介して倍精度から単精度へ狭める(narrowing)という下流の最適化
visitFPTrunc
を破損させてしまいました。その結果、RISC-V ターゲットにおいて約 24% のパフォーマンス低下が生じ、
fdiv.d
(33 サイクルの遅延)が、
fdiv.s
(19 サイクルの遅延)の代わりに展開されてしまったという問題です。私の修正では、範囲分析を拡張して
getMinimumFPType
への機能追加を行い、「倍精度浮動小数点への整数キャストから単精度浮動小数点への変換」が「単精度への整数キャスト」へと簡約可能であることを認識させました。これにより、前述の狭める最適化が復元され、パフォーマンス向上を達成しました。

分析

Igalia のサイトで、RISC-V ターゲットにおける LLVM と GCC パフォーマンスの比較を調査していた際、この特定のベンチマークに注意を向けていました。

以下の画像(※実際には挿入不可ですが)に示すように、Sifive P550 CPU 上で特定の本番タスクにおいて、LLVM は GCC よりも約 ~8% 多くのサイクルを必要としていました。

関連する基本ブロックアセンブリの断片を含めています。実質的にすべてのサイクルは以下のアセンブリによって消費されていました。

  • LLVM: [アセンブリ断片]
  • GCC: [アセンブリ断片]

この 2 つのアセンブリを比較しても、なぜ GCC が優れているのかは直ちに明らかではありませんでした。むしろ非常に似ており、LLVM はここでのブランチ論理さえ最適化できているように見えました。私が確認した大きな違いは、LLVM が倍精度浮動小数点(f64)の除算である

fdiv.d
を使用していたことです。

これは有望な兆候だったので、より詳細な状況を把握するために

llvm-mca
をソースコードに実行することにしました。なお、私は上流から数日分古くなったものですが、ビルドされたソースコード版の
llvm-mca
を使用しています。
llvm-mca
による分析の結果、ループ内では
fdiv.d
インドレクションは出現していませんでした(上記には表示していませんが)、GCC および LLVM の両方がより後の基本ブロックに
fdiv.d
インドレクションを含んでいましたが、これはメインループの外側であり、パフォーマンスの差異とは無関係です。

情報

llvm-mca
は LLVM スイートに含まれるツールであり、特定の CPU 向けのマシンコードのパフォーマンスを静的に測定する用途で利用できます。

$LLVM_BUILD_DIR/bin/llvm-mca -mtriple=riscv64 -mcpu=sifive-p550 pi.s

本来

fdiv.d
が存在する領域では、GCC のように 2 つの
fdiv.s
が存在していました。このことから、ループ内の
fdiv.d
インドレクションは間違いなく最近の回帰(regression)であるとの結論に達しました。LLVM は以前より倍精度を単精度へ狭めることが可能でしたが、最新ビルドでは double を float への変換ができなくなっていたのです。

これを事実上の回帰であることを確認するために、同じ CPU およびベンチマークに対する以前の LLVM ビルドと比較を行いました。 https://cc-perf.igalia.com/db_default/v4/nts/profile/260/406/4

以下は、以前ビルドされた LLVM が生成したアセンブリです。 以前のビルドでは

fdiv.d
インドレクションはなく、代わりに
fdiv.s
が使用されました。

乱次実装 (Out-of-Order Execution)

上記のアセンブリと新しい LLVM ビルドのアセンブリの順序が異なる理由を気にされている場合は、ターゲット CPU の Sifive P550 は乱次実行 CPU(out of order CPU)であるためです。前回の投稿で言及された Banana Pi の CPU とは異なり、インオーダー CPU であり、このターゲットはより高いスループットを生み出せる順序で命令を実行することができます。

なぜ

fdiv.d
が高いのかについては完全に確信が持てませんが、私の推測では、倍精度除算の遅延が非常に大きいため、CPU は他の命令をスケジュールしてその遅延を「隠そう」としているためと考えられます。
fdiv.d
の値を使用する
fcvt.s.d
インドレクション(ft1)は 33 サイクル待機が必要となるため、おそらく CPU はそれらの間に命令をスケジュールすることを決定したのでしょう。

ここでは何が起きているのか?

現時点ではなぜこれが起きたのかは分かりませんが、正しい方向への一歩としては、どこで起こっているかを特定することになるでしょう。もしかすると RISC-V バックエンドの変更かもしれませんか。以下のコマンドが RISC-V に関連するコミットを表示してくれます。

git log --after="2026-04-01 00:11" --before="2026-04-04 00:10" | grep -E "RISC"

  • [RISCV] P 拡張に対して add(vec, splat(scalar)) を PADD_*S に選択 (#190303)
  • [RISCV] vsetvli のバリアント化を許可する LI に coalesceVSETVLIs を移動させることを許可 (#190287)
  • [RISC-V][TTI] vector.extract.last.active のコストを更新し、m8 超過を防ぐ (#188160)
  • [RISCV] RISCSIInsertVSETVLI::transferBefore 内で EnsureWholeVectorRegisterMoveValidVTYPE をチェック (#190022)
  • [RISCV] vp_ctlz, vp_cttz, vp_ctpop のコード生成を削除 (#189904)
    • RISC-V から自明な VP 内在関数を削除する一環として行われました。
  • [RISCV] RISCVLoadStoreOptimizer でペアになっていない命令を後ろに移動 (#189912)
    • RISCVLoadStoreOptimizer
      は他の命令に隣接する命令を移動させます。
  • [RISCV] 圧縮ターゲットに対してスタックマップシャドウの NOP サイズを修正 (#189774)
  • [RISCV] convertSameMaskVMergeToVMv の VL 制約を緩和 (#189797)
  • [RISCV] RISCVOptWInstrs に SATI_RV64/USATI_RV64 を追加 (#190030)
    • RISCVISD::SATI エンコーディングはタイプ幅から 1 を減らします。
  • [RISCV][MCA] Sifive-p670 テストをファイル入力を使用するように更新 (#189785)
  • [RISCV] vp_minnum, vp_maxnum のコード生成を削除 (#189899)
    • RISC-V から自明な VP 内在関数を削除する一環として行われました。
  • [RISCV] ComputeKnownBitsForTargetNode/ComputeNumSignBitsForTargetNode に RISCVISD::USATI/SATI を追加 (#189702)
  • [RISCV] VSETVLIInfo::hasSEWLMULRatioOnly() へのアサーションを追加。NFC (#189799)
  • [RISCV] combine-is_fpclass.ll - ISD::IS_FPCLASS ノードの定数畳み込み失敗を示す初期テストを追加 (#189940)
  • [RISCV] VP 浮動小数点丸め内在関数のコード生成を削除 (#189896)
    • RISC-V から自明な VP 内在関数を削除する一環として行われました。
  • [RISCV] vp_lrint, vp_llrint のコード生成を削除 (#189714)
    • RISC-V から自明な VP 内在関数を削除する一環として行われました。
  • [RISCV] SATI および USATI のコード生成サポートを追加 (#189532)

これらのコミットのどれもがすぐに目立ちませんでしたので、一旦保留しました。ミドルエンドの調査に戻り、ローカルの LLVM バージョン(数日前のものである)で

opt
によって最終的に生成された IR を見てみることにしました。念のため、私のビルドは「動作している」ビルドです。これはパイプラインの最初に生成された LLVM IR(clang の直後)と、最後に生成されたものです。

私が何をしているのか混乱されている場合を想定して、上記の図がこれをより明確に示しているはずです。もしミドルエンドが double を float へ狭めるのに責任があるなら、IR に対してどのような最適化が行われ、それが生じたのかを理解しようとしています。

代わりに、特定のパスで print-before および print-after を使用することもできます。これで IR がどのように変更されているかをパースすることが可能です。

これは最適化パイプラインの最初に生成された IR の関連する断片です。

%conv  = sitofp i64 %5 to float        ; int -> float
%conv2 = fpext float %conv to double   ; float -> double 
%div3  = fdiv double %conv2, 7.438300e+04  ; fdiv.d 
%conv4 = fptrunc double %div3 to float ; double -> float

これは、パイプラインの終了時点では以下のようになります。

%conv  = uitofp nneg i64 %0 to float   ; int -> float 
%conv4 = fdiv float %conv, 7.438300e+04 ; fdiv.s 

これらを見てわかるように、ミドルエンドの終わりまでには double が float へと狭められていたことがわかります。これらの断片から、初期の double 値を float へと狭めるのはこのミドルエンドが責任を負っていることは明瞭であると確信します。

なぜ、これらの一見して冗長なキャスト操作が最初に生成されたのか混乱されている場合を想定して、ソースコードに一瞥するだけで理解が深まります。

ソース

int main(int argc, char *argv[]) {
  float ztot, yran, ymult, ymod, x, y, z, pi, prod;
  long int low, ixran, itot, j, iprod;

  ...

  for(j=1; j<=itot; j++) {
    iprod = 27611 * ixran;
    ixran = iprod - 74383*(long int)(iprod/74383);
    x = (float)ixran / 74383.0;

    ...

  }
    
  ...

}

この特定の行では、ソースコードにおいて

74383.0
は double ですが、float の範囲内に収まります。
x = (float)ixran / 74383.0;
これはなぜ LLVM IR で float から double への変換 (
fpext
) と、その後から float への倍精度の逆変換 (
fptrunc
) が発生するのかを説明します。 これは明らかなことかもしれませんが、念のため言っておきます。もし上記の定数自体が元々 float のように以下であれば、
fdiv.d
は決して生成されなかったでしょう。
x = (float)ixran / 74383.0f;
十分な最適化レベルがある場合でも、コンパイラはこのようなものを検知できるはずです。しかし、コードにおけるわずかな変化がこのような劇的な違いを引き起こす様子は見事です。このケースではサイクル数で +19% 以上の改善となりました!

以下の表は LLVM IR から C ソースへのマッピングを示し、指定された IR が何を行うかについての注記を簡潔に示しています。

LLVM IRC ソース注釈
%mul = mul nuw nsw i64 %ixran.053, 27611
iprod = 27611 * ixran
整数乗算
%0 = urem i64 %mul, 74383
ixran = iprod - 74383*(long int)(iprod/74383)
コンパイラによって最適化された modulo(urem)
%conv2 = uitofp nneg i64 %0 to double
(float)ixran
定数
74383.0
が double なので、まず double へキャストされます
%div3 = fdiv double %conv2, 7.438300e+04
/ 74383.0
C で
74383.0
が double 定数なので、倍精度除算を行います
%conv4 = fptrunc double %div3 to float
x = (float)...
明示的な
(float)
キャストが結果を再び float へトリンクします

当時のものであった LLVM ビルドでは、以下の IR が生成されていました。

%mul = mul nuw nsw i64 %ixran.053, 27611
%0 = urem i64 %mul, 74383
%conv2 = uitofp nneg i64 %0 to double
%div3 = fdiv double %conv2, 7.438300e+04
%conv4 = fptrunc double %div3 to float

div3
オペランドをみると、
%conv2
と小数値が見られます。
%conv2
%0
を double へ変換したキャスト操作の結果ですが、
%0
の最大値(74383)は float の範囲内に収まります。
llvm-mca
から得られた結果を見ると、以下の
fdiv.d
が見られます。

1      33    32.00                       fdiv.d   ft1, ft1, fa3

これは

fdiv.d
が 33 サイクルの遅延を持ち、
fdiv.s
の 19 サイクルよりも著しく長いことを示しています。パフォーマンス比較では、古い LLVM ビルドは Reciprocal Throughput (RThroughput) で 86.0 を報告したのに対し、新しいビルドでは 100.0 へと増加しました。RThroughput は、プロセッサが次の同種の命令を執行開始する前に待機しなければならないクロックサイクル数を表しています。したがって、値が低いほど良いです。 これは double 除算が float 除算に比べて非常に高価であることを示しています。

上記の過去数日のコミットリストから、以下のものだけが直ちに目立ちました。 [InstCombine] ComputeNumSignBits in isKnownExactCastIntToFP を使用 (#190235)

符号付き整数から FP への変換において、

ComputeNumSignBits
は個々の既知ビットがすべて不明な場合であっても、符号伝播を正確に追跡することで
ashr(shl x, a), b
のようなケースにおいて、計算不能のビット情報を使用して正確さを証明することができます。

InstCombine は LLVM ミドルエンドの最適化パスであり、隣接または関連する命令を単一のより効率的な操作へと結合します。その任務は幅広いので、InstCombine の最適化例は多岐にわたります。例えば、InstCombine は

x = x * 2
x = x << 1;
に簡約することができます。

最新ビルドではもはやその整数を float へ変換できないため、この commit メッセージで言及されている int-to-FP キャスト (

sitofp/uitofp
) がすぐに疑いを抱かせてくれました。これが実際に回帰を引き起こしているかどうかは、そのコミット前後に LLVM をビルドして確認できました。この変更前にはすべて動作しましたが、その後
fdiv.d
インドレクションがアセンブリに登場しました。

また、このパッチ自体が改善点であることも付け加えておきます。InstCombiner パスにはより多くの情報がありますが、場合によっては改善点が予期せぬ場所での回帰を引き起こす可能性があります。私は、私のような人々が貢献する機会を提供することを提案します 😅

なぜこれが起きたのか?

このコミットの差分を見ると、パッチの著者が関数

isKnownExactCastIntToFP
に既知ビット分析を追加したことがわかります。

static bool isKnownExactCastIntToFP(CastInst &I, InstCombinerImpl &IC) {
  
  ...

  // For sitofp, the sign maps to the FP sign bit, so only magnitude bits
  // (BitWidth - NumSignBits) consume mantissa.
  if (IsSigned) {
    SigBits =
        (int)SrcTy->getScalarSizeInBits() - IC.ComputeNumSignBits(Src, &I);
    if (SigBits <= DestNumSigBits)
      return true;
  }

  return false;
}

この変更の詳細な理解は必要ありませんが、推測すると

isKnownExactCastIntToFP
が以前よりも
true
を返すようになっています。したがって、私は
isKnownExactCastIntToFP
のすべての呼び出しサイトを調査し、これがどうやって回帰を引き起こすのかをより深く理解することにしました。

以下の関数が

isKnownExactCastIntToFP
を呼び出します。これは、先行する命令が整数から FP への変換を行っている場合、
fpext
インドレクションを削減するために呼ばれます:
itofp i64 x to float
->
fpext float x to double
。これは単に
itofp i64 x to double
へと簡約することができます。

Instruction *InstCombinerImpl::visitFPExt(CastInst &FPExt) {
  // If the source operand is a cast from integer to FP and known exact, then
  // cast the integer operand directly to the destination type.
  Type *Ty = FPExt.getType();
  Value *Src = FPExt.getOperand(0);
  if (isa<SIToFPInst>(Src) || isa<UIToFPInst>(Src)) {
    auto *FPCast = cast<CastInst>(Src);
    if (isKnownExactCastIntToFP(*FPCast))
      return CastInst::Create(FPCast->getOpcode(), FPCast->getOperand(0), Ty);
  }

  return commonCastTransforms(FPExt);
}

そしてこれはそれを組み立てます。 パイプラインの最初に生成された LLVM IR を参照します。

%conv  = sitofp i64 %5 to float        ; int -> float
%conv2 = fpext float %conv to double   ; float -> double 
%div3  = fdiv double %conv2, 7.438300e+04  ; fdiv.d 
%conv4 = fptrunc double %div3 to float ; double -> float

そして、パイプラインの最後に生成された LLVM IR です。

%conv2 = uitofp nneg i64 %0 to double
%div3 = fdiv double %conv2, 7.438300e+04
%conv4 = fptrunc double %div3 to float

そのパッチ後の InstCombiner パスは、

sitofp i64
を float へ変換し、その後
fpext float
を double へと変換するものを、単一の
uitofp nneg i64 %0 to double
へと簡約することができました。しかしながら、
visitFPTrunc
がコード内のコメントのとおりに後続のパターンを最適化しました。

// 我々は fptrunc(OpI (fpextend x), (fpextend y)) を持つ場合、 // トリンク/拡張操作のうち一つ以上の操作なしに数値的結果を変更しないようにすることができれば、 // この式を簡略化することを望むべきである。 // // オペランドの幅が相互に作用して、何を実行し、何を安全に行えないかを制限する具体的な方法は、 // 操作ごとに異なり、以下の変なケースステートメントで説明されています。

最適化された LLVM IR はもはやその

fpext
インドレクションを持っていないため、InstCombiner パス、特に
visitFPTrunc
は double を float へ狭めることができなくなりました。

ソリューションの実装 (Landing a solution)

したがって、私たちは問題を診断しました - 最近の InstCombine パッチは論理を改善しましたが、それはパスが別の最適化を実行する能力を失う原因となりました。今後必要とされるのは、

fptrunc
が後に来ている場合、より早い段階で
uitofp/sitofp
を狭めるように InstCombiner に教えることです。

最初に私は GitHub でイシューを立ち上げました。これは妥当な問題であると自信を持っていましたが、これが修正に値するのかを確認したかったのです。私以上に先に言及したパッチの著者である @SavchenkoValeriy には感謝しております。彼は私にこの問題を解決するためのガイダンスを提供し、私の PR に対してレビューを提供してくださったからです。私の最初のソリューションはかなり複雑なものになっていたでしょうが、彼は私にはるかにシンプルなアプローチを提供してくれました。

レビュー者によって指摘された通り、現在の

isKnownExactCastIntToFP
の問題点は、そのキャスト命令
CastInst
のみをチェックしていることです。

  %0 = urem i64 %mul, 74383
  %conv2 = uitofp nneg i64 %0 to double
  %div3 = fdiv double %conv2, 7.438300e+04
  %conv4 = fptrunc double %conv2 to float

以下は

visitFPTrunc
関数のコードです。異なる操作を含むスウィッチステートメントを見ることができます(FDiv を含む)。これは
fdiv.d/fdiv.s
に対応します。FPT は
fptrunc
インドレクションを表し、BO は
FPT.getOperand(0)
に相当するため、それは
%conv2
を指します。私たちは代わりにこの
uitofp
を float へと変換できるか確認する必要があるため、キャスト操作も合わせてチェックするように
getMinimumFPType
を修正する必要があります。

Instruction *InstCombinerImpl::visitFPTrunc(FPTruncInst &FPT) {
  if (Instruction *I = commonCastTransforms(FPT))
    return I;
  
  ...

  Type *Ty = FPT.getType();
  auto *BO = dyn_cast<BinaryOperator>(FPT.getOperand(0));
  if (BO && BO->hasOneUse()) {
    Type *LHSMinType = getMinimumFPType(BO->getOperand(0), PreferBFloat);
    Type *RHSMinType = getMinimumFPType(BO->getOperand(1), PreferBFloat);

    switch (BO->getOpcode()) {
      default: break;
      case Instruction::FAdd:
      case Instruction::FSub:
      ...
  ...

私の初期のアイデアの一つは、

isKnownExactCastIntToFP
に別のタイプ(私の場合は f32)を持つパラメータを受け取るように修正することでしたが、デフォルト値として
nullptr
を使用します。これにより、ヘッダー定義と実装のみを変更できることになります。代わりにレビュー者は、
isKnownExactCastIntToFP
のバリエーションを作成するよう提案しました。
canBeCastedExactlyIntToFP
です。これは実際のアナリシスをタイプに与え行うものであり、
isKnownExactCastIntToFP
はそれを呼び出すことができます。私はコミュニティと相互作用し、他の人々からアイデアを求めると良いことを示すためにこの点を言及します。彼らはさまざまな理由によってより優れたアイデアを発見することができます。

以下が最終的な git diff です。私たちは

isKnownExactCastIntToFP
を分割し、実際の分析を実行する
canBeCastedExactlyIntToFP
を作成しました。そして、
isKnownExactCastIntToFP
がそれをつかいます。最後に、
getMinimumFPType
canBeCastedExactlyIntToFP
を呼び出します。

--- a/llvm/include/llvm/Transforms/InstCombine/InstCombiner.h
+++ b/llvm/include/llvm/Transforms/InstCombine/InstCombiner.h
@@ -481,6 +481,8 @@ public:
   /// Return true if the cast from integer to FP can be proven to be exact
   /// for all possible inputs (the conversion does not lose any precision).
   bool isKnownExactCastIntToFP(CastInst &I) const;
+  bool canBeCastedExactlyIntToFP(Value *V, Type *FPTy, bool IsSigned,
+                                 const Instruction *CxtI = nullptr) const;

   OverflowResult computeOverflowForUnsignedMul(const Value *LHS,
                                                const Value *RHS,
--- a/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
@@ -2039,10 +2039,17 @@ static Type *shrinkFPConstantVector(Value *V, bool PreferBFloat) {
 }

 /// Find the minimum FP type we can safely truncate to.
-static Type *getMinimumFPType(Value *V, bool PreferBFloat) {
+static Type *getMinimumFPType(Value *V, Type *PreferredTy, InstCombiner &IC) {
   if (auto *FPExt = dyn_cast<FPExtInst>(V))
     return FPExt->getOperand(0)->getType();

+  Value *Src;
+  if (match(V, m_IToFP(m_Value(Src))) &&
+      IC.canBeCastedExactlyIntToFP(Src, PreferredTy, isa<SIToFPInst>(V),
+                                   cast<Instruction>(V)))
+    return PreferredTy;
+

+  bool PreferBFloat = PreferredTy->getScalarType()->isBFloatTy();

見てわかる通り、私たちは今や

fptrunc
インドレクションのタイプが渡されるように
canBeCastedExactlyIntToFP
を呼び出しています。この場合、Src は
BO->getOperand(0)
の入力であり、BO は
fptruc
インドレクションの 0 番目のオペランドです。以前見た LLVM IRを思い出す必要があります:

%0 = urem i64 %mul, 74383
%conv2 = uitofp nneg i64 %0 to double
%div3 = fdiv double %conv2, 7.438300e+04
%conv4 = fptrunc double %div3 to float

fptrunc
は入力を変数へ変換するものであり、したがってタイプ Ty は f32 です。BO は
%div3
で、
BO->getOperand(0)
%conv2
であり、
m_IToFP(m_Value(Src))
BO->getOperand(0)
の入力である
%0
を Src へ置きます。

visitFPTrunc 変数LLVM IR 変数
BO
%div3
fdiv double %conv2, 7.438300e+04
BO->getOperand(0)
%conv2
uitofp nneg i64 %0 to double
BO->getOperand(1)
7.438300e+04
double 定数
Src
%0
urem i64 %mul, 74383

結果

これは機能しましたか?以下のコマンドを実行することで、私のパッチ後の LLVM IR を確認できます。

$LLVM_BUILD_DIR/bin/clang -O3 \
  --target=riscv64-unknown-linux-gnu \
  -march=rv64gc_zba_zbb \
  --sysroot=/usr/riscv64-linux-gnu \
  -S -emit-llvm pi.c -o pi_fixed.ll

そして、これは関連する LLVM IR です。

%mul = mul nuw nsw i64 %ixran.053, 27611
%0 = urem i64 %mul, 74383
%1 = uitofp nneg i64 %0 to float
%conv4 = fdiv float %1, 7.438300e+04

機能しました!🥹

fptrunc
はなくなり、キャスト操作
uitofp
が今や float へ変換しています。 以下の結果は私のパッチがマージされた後のものです。ターゲットがベンチマークを 1.67 Bn サイクルで実行できていることがわかります。約 25% の改善です。 https://cc-perf.igalia.com/db_default/v4/nts/profile/260/426/422

同じ日のほかのニュース

一覧に戻る →

2026/04/14 2:54

1 つ、30 の WordPress プラグインを購入し、それぞれにバックドアを埋め込まれた。

## Japanese 翻訳: 元サマリーの原文は明確で正確であり、高レベルの概要として十分に範囲内に収まっており、改行文筆は必要ありません。

2026/04/14 5:36

GitHub で積み上がったプルリクエストを処理する。

## Japanese Translation: 本テキストでは、「Stacked PRs(スタックされたプルリクエスト)」を紹介します。これは、大規模で管理が困難なプルリクエストによる問題、例えばレビューの難易度が高いこと、マージにかかる時間が長いこと、頻繁な衝突などを解決するために GitHub が実装した機能です。開発者は一度に巨額の変更を提出するのではなく、作業を注力し独立した層に分割し、それらが互いに縦方向に積み上げられ、最終的に main ブランチへマージされるまで進めます。 GitHub 本家はこのワークフローを、ナビゲーション用の可視化「スタックマップ(stack map)」UI でサポートしており、ブランチ保護規則がターゲットとなる最終ブランチに対して自動的に適用され、継続的統合(CI)システムが各層ごとに個別にテストを実行し、main ブランチへのマージを想定する manner で動作します。また、ユーザーはスタックの全て、または一部の PR をマージすることができ、残りの PR は自動的にリベースされ、スタック構造が維持されます。 このワークフローをローカル環境で管理するためには、開発者は `gh stack` CLI ツールを使用します。これは、拡張機能をインストール(`gh extension install github/gh-stack`)し、`gs init`、`gs add`、`gs push`、`gs submit` などのコマンドを用いてブランチを作成し、カスケードリベースを管理し、スタックをレビューまたはマージのためにオープン化することを意味します。将来的には、AI コーディングエージェントもこのスタックを自律的に処理できるように、特定のスキルパッケージ(`npx skills add github/gh-stack`)をインストールすることで教育できます。Ultimately(結果として)、Stacked PRs はチームが段階的なレビューを通じてコードの品質を向上させながら、大規模なコードベースを維持するために必要な手動作業を劇的に削減することを可能にします。

2026/04/14 0:31

『何事も起きない:スポーツ市場以外では常に「いいえ」と購入する Polymarket ボット』

## Japanese Translation: 本書では、Polymarket でスポーツ以外の Yes/No マーケットにおける「NO」エントリを取引することを目的に特化して構築された非同期の Python ボット「Nothing Ever Happens」を معرفیしています。本プロジェクトは娯楽目的でのみ提供されるものであり、保証や免責事項は一切適用されません(as-is)として提供されます。ライブフラグが設定されていないデフォルト状態では `PaperExchangeClient` が採用され、シミュレーションテストが行われます。リアルな取引を実行するには、環境変数を明示的に設定する必要があるためです。具体的には、`BOT_MODE=live`、`LIVE_TRADING_ENABLED=true`、`DRY_RUN=false` などを設定することでリアル取引モードを有効化します。また、ライブオーダーの送信を有効にするには、`PRIVATE_KEY`、`FUNDER_ADDRESS`(署名タイプ 1 および 2 の場合)、データベース URL、および Polygon RPC URL などの特定のエントリが必要となります。 ボットのアーキテクチャはモジュール化されており、ランタイムロジック、取引所クライアント、ダッシュボード UI、回復ツール、そしてコア戦略モジュールである「nothing_happens」で構成されています。ローカルでのセットアップでは、`pip install -r requirements.txt` によって依存関係をインストールし、`config.example.json` を `config.json` にコピーして非機密設定をその中で構成し、秘密鍵やフラグは `.env` ファイル(パス:`strategies.nothing_happens`)に保存します。ローカル設定は意図的に git 無視されており、ユーザーは環境変数 `CONFIG_PATH` を通じてランタイムが参照する異なるパスを指定できるようにしています。 Heroku でのクラウドデプロイメントでは、特定のコマンドヘルパー(`alive.sh`、`logs.sh`、`kill.sh`)を使用して、アプリの状態を管理し、ライブ取引の有効化/無効化やロギングを行うことができます。デプロイには、bot モード、プライベートキー、RPC URL、データベースに関する環境変数を `heroku config:set` コマンドを用いて設定する必要があります。-production 環境では、誤って長時間実行タスクを実行してしまうのを防ぐためにワーカーダイノを除外し、ウェブダイノのみを使用するようにスケーリング構成されています。また、`db_stats.py`、`export_db.py`、`wallet_history.py`、`parse_logs.py` などのユーティリティスクリプトは、データベースの点検やログ分析を実行することなく市場の変動を意図的に引き起こさずにこれらの情報を可視化することを可能にするため、透明性を高めています。