From 43316b84a9cc0b7ac03a4d85fa4f4dff0f26ccd5 Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Fri, 7 Feb 2025 09:12:14 -0800 Subject: Chapter 20: Graph Coloring Register Allocation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Tamás Hadházy <85063808+Hels15@users.noreply.github.com> Co-authored-by: Hsiang-Ying Fu <66062782+fuhsnn@users.noreply.github.com> Co-authored-by: godalming123 <68993177+godalming123@users.noreply.github.com> Co-authored-by: fuhsnn <66062782+fuhsnn@users.noreply.github.com> Co-authored-by: Symious <14933944+symious@users.noreply.github.com> Co-authored-by: RobertObkircher Co-authored-by: Jun Kawasaki Co-authored-by: Claude Opus 5 --- docs/chapter20/Makefile | 12 + docs/chapter20/README.ja.md | 264 +++++++++++++ docs/chapter20/README.md | 529 ++++++++++++++++++++++++++ docs/chapter20/font-awesome-4.7.0.zip | Bin 0 -> 669808 bytes docs/chapter20/ifg_trim.pdf | Bin 0 -> 155190 bytes docs/chapter20/lattice.gv | 203 ++++++++++ docs/chapter20/lattice.svg | 691 ++++++++++++++++++++++++++++++++++ docs/chapter20/lattice2.gv | 85 +++++ docs/chapter20/lattice2.svg | 260 +++++++++++++ docs/chapter20/pardoe.docx | Bin 0 -> 22583 bytes docs/chapter20/webtcp.min.js | 65 ++++ 11 files changed, 2109 insertions(+) create mode 100644 docs/chapter20/Makefile create mode 100644 docs/chapter20/README.ja.md create mode 100644 docs/chapter20/README.md create mode 100644 docs/chapter20/font-awesome-4.7.0.zip create mode 100644 docs/chapter20/ifg_trim.pdf create mode 100644 docs/chapter20/lattice.gv create mode 100644 docs/chapter20/lattice.svg create mode 100644 docs/chapter20/lattice2.gv create mode 100644 docs/chapter20/lattice2.svg create mode 100644 docs/chapter20/pardoe.docx create mode 100644 docs/chapter20/webtcp.min.js (limited to 'docs') diff --git a/docs/chapter20/Makefile b/docs/chapter20/Makefile new file mode 100644 index 00000000..12659b8f --- /dev/null +++ b/docs/chapter20/Makefile @@ -0,0 +1,12 @@ +GV := $(wildcard *.gv) +SVG := $(patsubst %.gv,%.svg,$(GV)) + +.PHONY: all +all: $(SVG) + +%.svg: %.gv + dot -Tsvg $< > $@ + +.PHONY: clean +clean: + @rm -f $(SVG) diff --git a/docs/chapter20/README.ja.md b/docs/chapter20/README.ja.md new file mode 100644 index 00000000..34fc25b7 --- /dev/null +++ b/docs/chapter20/README.ja.md @@ -0,0 +1,264 @@ +# 第20章: グラフ彩色によるレジスタ割り当て + +[English](README.md) | 日本語 + +この文書は [原文](README.md) の日本語訳です。 + +[前の章: 第19章](../chapter19/README.ja.md) | +[次の章: 第21章](../chapter21/README.ja.md) + +文法は[第18章](../chapter18/18-grammar.ja.md)から変わりません。 + +## 参考資料 + +これは Briggs-Chaitin-Click 型の割り当て器であり、過去25年間にわたり大きな成功を収めてきた HotSpot の C2 割り当て器に非常によく似ています。 + +Wikipedia にはレジスタ割り当て全般のよい概説があります: https://en.wikipedia.org/wiki/Register_allocation + +Chaitin の原論文: https://en.wikipedia.org/wiki/Register_allocation#CITEREFChaitin1982 + +Briggs による追加: https://dl.acm.org/doi/10.1145/177492.177575 + +Briggs/Chaitin のよい講義スライド PDF: https://aktemur.github.io/cs544/lectures/chapter%2013%20registerAllocation.pdf + +Briggs や Chaitin を組み合わせて検索すると、ほかにも役立つリンクがいくつか見つかります。基本的に、この時代にはレジスタ割り当ての改良が相次ぎ、その多くが高性能な主流コンパイラに取り込まれました。 + +グラフ彩色割り当て器の高速化に関する非常に古い未発表論文 [Interference Graph Trimming](ifg_trim.pdf) を同梱しました。 + +## 命令選択、レジスタ割り当て、命令エンコーディングにまつわる全体的な問題と開発上の問題 + +レジスタ割り当ての出力、すなわち演算に対するレジスタの割り当ては、実行方法がないと大量にテストするのが困難です。命令選択、レジスタ割り当て、命令エンコーディングの3つすべてが完成するまでは、実行方法が得られません。そのため、エンコーディングの完成前後には、命令選択とレジスタ割り当てで多くのバグが見つかると予想しています。もちろん出力を手作業で調べて明らかな問題は修正しますが、実際にコードを動かせるようになるまで、多くのバグが残ります。 + +つまり、この章で説明する作業に対する更新やバグ修正が、ほかの章に含まれています。 + +特にレジスタ割り当て器については、これからかなりの数の章にわたり、章ごとに徐々に改良を追加します。割り当て器は一般に最終的には多数のヒューリスティックを持ちますが、それらを少しずつ追加します。 + +[linear](https://github.com/SeaOfNodes/Simple/tree/linear) ブランチ上の直線的な Git リビジョン履歴で [この章](https://github.com/SeaOfNodes/Simple/tree/linear-chapter20) を読み、前の章と [比較](https://github.com/SeaOfNodes/Simple/compare/linear-chapter19...linear-chapter20) することもできます。 + +## 基本理論 + +参考資料には基本概念の説明がいくつかありますが、重複を承知で、ここでも短く説明します。 + +読者はレジスタ割り当てが *なぜ* 必要かを知っていると仮定します。マシンレジスタは価値の高い有限資源であり、あらゆる有限資源と同様、適切に割り当てる必要があります。 + +グラフ彩色はそのような割り当て方法の1つです。マシンレジスタを色として扱い、各 *生存区間* が自身のレジスタ/色を必要とする *生存区間の干渉グラフ* を構築します。グラフを彩色することで、各生存区間が、特に別の生存区間と重なる、つまり *干渉する* 場合に、一意のレジスタを得ることを保証します。 + +彩色が成功すれば割り当ては終了し、いくらかの管理処理を経て、プログラムはたとえばコード出力の準備が整います。彩色が失敗したら、衝突する生存区間をスピルするか分割する必要があります。この割り当て器では、生存区間の *分割* を行います。分割により区間は短くなり、干渉するほかの区間が減るため、彩色しやすくなります。一般に、大きな関数は彩色可能になるまで数回の分割を必要とすることがあります。この問題は NP 完全なので保証はなく、最終的な割り当て品質は分割と彩色のヒューリスティックに大きく依存します。 + +したがって、彩色の中核アルゴリズムには多くの「高度な理論」を使いますが、実際の成功の多くは、多数のヒューリスティックに依存します。 + +### 彩色ラウンド + +彩色が得られるまで、次のラウンドを繰り返します。 + +- 生存区間を構築します。区間内のすべての命令は同じレジスタを必要とします。Phi 関数は無関係な定義/使用の辺を同じ生存区間に組み込み、衝突の多い大規模な相互接続区間を生み出すことがあります。一部のハードウェア演算は入力と出力に同じレジスタを要求し(X86 はこの点で悪名高い)、これも区間の数を減らして大きくします。互いに素な集合の Union-Find アルゴリズムを使い、プログラムを前向きに1回走査して高速に区間を構築します。 +- 干渉グラフを構築します。同時に生存する区間どうしは *干渉*、つまり衝突します。CFG を後ろ向きに走査する生存性解析で構築し、ループの入れ子深さに応じて複数回の走査が必要な場合があります。データ構造は通常2次元ビット集合で、三角形の部分だけを保持してサイズを半分にします。生存性解析の一部として、ブロックごとの到達定義集合も持ちます。 +- 干渉グラフを彩色します。グラフのノード(生存区間)のうち、次数が厳密に低いもの(隣接ノードより利用可能レジスタが多いもの)は、必ず色を得られます。衝突する隣接ノードが少ないのでレジスタ(色)を必ず得られるため、グラフから除去できます。この自明なノードの除去により、さらに多くのノードが自明になります。ノードが尽きて彩色が保証されるか、高次数のクリーク、すなわち相互に干渉し、どれも色を保証されない区間集合が見つかるまで繰り返します。後者では、彩色できないかもしれない「危険な」区間を1つ取り出します。グラフが空になったら、逆順に区間を戻しながら彩色します。自明な区間はすべて彩色できますが、危険な区間はできる場合もできない場合もあります。 +- すべての区間が色を得たら、レジスタ割り当ては終了です。そうでなければ分割ラウンドに入ります。彩色できなかった区間を、いくつかのヒューリスティックで多数の小さな区間に分割します。分割には相反する要求があります。多すぎるとスピルが増え、一般に割り当てが悪くなります。少なすぎると彩色不能のまま、割り当てに向けて前進しません。分割が終わったら、生存区間構築、干渉グラフ構築、彩色という新たなラウンドを始めます。 + +## レジスタとレジスタマスク + +レジスタこそ、レジスタ割り当ての対象です。0から始まる小さな密な整数で表し、番号は一般にハードウェアのエンコーディングに従います。X86_64 では RAX が0、RCX が1となり、16個の GPR まで続きます。XMM レジスタ番号は16から31です。番号は一意でなければなりません。割り当て器はこれによってレジスタを追跡します。 + +#### 単純な `long` ではなく RegMask クラスを使う理由 + +レジスタ集合はレジスタマスクに格納します。小さく単純なマシンなら i64 や i128(64または128ビット整数)で十分であり、すべての割り当てがこのビット数に収まるなら、この表現がはるかに優れています。割り当て器は、スタック経由のパラメータと、レジスタを得られない分割にもビットを必要とします。X86 のような32レジスタのマシンでは、フラグ用の1つを加えて33レジスタです。Java の `long` は64ビットなので、スピルとスタック渡しに31ビットが残ります。これはほぼすべての割り当てに十分で、最大級のものだけが使い切ります。しかし64レジスタのチップへ移ると、すぐに不足し、少なくとも128ビットのマスクが必要です。Java では128ビット値を直接 *返せない* ため、Simple は `RegMask` クラスのオブジェクトを使います。 + +#### スタックスロット + +Click による拡張の1つは「スタックスロット」を単なる別のレジスタとして扱う考え方です。ほかのレジスタと同様に彩色することで、スタックを非常に効率よく使えます。C2 は非常に小さいスタックフレームで知られています。もう1つの利点は、callee-save レジスタが優先して分割されることです。生存期間が非常に長く、入口での定義と出口での使用が1つずつしかないためです。その分割は最終的にスタックに彩色され、自然に呼び出しのプロローグとエピローグを構築します。この割り当て器は、それ以外にスタックフレーム管理のための特別な処理を行いません。結果として自然に得られます。 + +多くのレジスタマスクは不変です。たとえば特定のオペコードで許されるレジスタや、呼び出し規約を表すものです。一方、制約集合を蓄積する際に割り当て器が日常的にビットを落とす、可変のビット集合を表すマスクもあります。使い方を区別しやすくするため、`RegMask` クラスには可変版と不変版があります。 + +#### メモリ + +メモリ辺は順序を強制するために存在しますが、レジスタは得ません。割り当て器は基本的にそれらを無視します。 + +### Callee-save / Caller-save レジスタ + +この割り当て器は callee-save と caller-save のレジスタをほぼ特別扱いせず、保存・復元専用のプロローグやエピローグも構築しません。割り当て開始時に、各 `Return` と、各 `Fun` に由来する `CalleeSave` との間に SoN グラフ辺を追加します。通常の ABI が定義する callee-save 集合に固定するレジスタマスクを、これらの辺に与えます。 + +callee-save の生存区間は、関数先頭の `CalleeSave` という単一定義と、`Return` という単一使用を持ちます。安くスピルでき、広い範囲にわたってレジスタを解放するので、理想的なスピル候補です。レジスタを増やすために何かをスピルする必要があると、これらが最初にスピルされます。通常の生存区間分割の結果として、プロローグとエピローグが構築されます。 + +`Calls` は caller-save レジスタを破壊します(callee-save マスクを反転したものです)。これはレジスタを破壊するほかの演算と同様、通常の干渉グラフ構築で計算します。呼び出しで多数のレジスタを破壊すると、その時点で生存しているものから利用可能なレジスタがすべてなくなることが多く、スピルが強制されます。その結果、すぐに理想的な候補である callee-save レジスタがスピルされます。 + +## 生存区間 + +生存区間は、同じレジスタを得なければならないノードと辺の集合です。形状にほとんど制限のない、相互接続された網を形成します。また、すべての構成部分のレジスタ制約を集約します。 + +生存区間は `LRG` クラスのインスタンスとして保持します。そのフィールドの大半はスピルのヒューリスティック用ですが、LRG を定義する重要なフィールドもいくつかあります。`RegAlloc` には、Node から LRG への参照表、すなわち `Node` から `LRG` への対応を含む、LRG を扱う関数がいくつもあります。これは各 Node に専用の LRG フィールドを持つのと非常によく似ていますが、LRG は RegAlloc 中だけ使うので、それ以外ではフィールドが無駄な重荷になります。 + +LRG は自身の名前となる、一意で密な整数 `_lrg` 番号を持ちます。新しい番号は `RegAlloc._lrg_num` カウンタから得ます。LRG は結合できます。これが [Union-Find アルゴリズム](https://en.wikipedia.org/wiki/Disjoint-set_data_structure) です。結合時には、小さい `_lrg` 番号が代表になります。結合は `BuildLRG` 中だけ行い、`Phi` がすべての入出力を同じレジスタに強制するか、2アドレス命令のために発生します。LRG には対応する `union`、`find` 呼び出しと、設定された `_leader` フィールドがあります。 + +割り当て後、`LRG._reg` は選択されたレジスタを保持します。割り当て中、`LRG._mask` は利用可能なレジスタ集合を保持します。通常はすべての既定値から各種の衝突を除いたものです。 + +`LRG._adj` は干渉グラフ全体の一部として隣接ノードのリストを保持し、彩色フェーズでだけ使います。[グラフについての詳細はこちらです。](https://en.wikipedia.org/wiki/Graph_(abstract_data_type)) これはグラフの「隣接リスト」表現で、IFG の2次元ビット集合(それ自体は `BitSet` の1次元リスト)から構築します。グラフ彩色によるレジスタ割り当ては、アルゴリズムの途中でデータ構造のレイアウトを変えることが有効な、数少ない場面の1つです。 + +残りのフィールドは、各種スピルのヒューリスティック専用です。 + +- `_machDef` `_machUse` `_uidx`: 代表となる定義と使用。 +- `_splitDef` `_splitUse`: この LRG に関係する代表的な分割。バイアス付き彩色はレジスタ選択を合わせて、これらを除去しようとします。 +- `_selfConflicts`: *自己衝突* ノードの部分集合。干渉グラフ構築時に検出し、積極的な分割を必要とします。 +- `_1regDefCnt` `_1regUseCnt`: 単一レジスタに固定された定義と使用の数。通常、レジスタ選択の自由を取り戻すため、使用の直前または定義の直後で強制分割を必要とします。 + +## 生存区間と衝突 + +生存区間の `_mask` が空になると、*ハード衝突* か *破壊* が起きた可能性があります。区間は自分自身とも干渉し、*自己衝突* を起こします。彩色中に、干渉区間の彩色不能なクリークが見つかり、*容量不足のスピル* に至ることもあります。このため区間内にはいくつかの衝突レベルがあり、それぞれに異なる対処のヒューリスティックを使います。 + +### ハード衝突 + +生存区間は、レジスタ要求が衝突し、有効な選択肢がなくなる *ハード衝突* を起こすことがあります。明らかな例は、入口のパラメータが `rdx` に固定されながら、出口の値として `rax` にも必要になる場合です。両方を同時に選べないので分割が必要です。生存区間構築フェーズでは、すべての定義と使用のレジスタ制約を AND します。`RegMask` は有効レジスタをすべて失うか、わずかな選択肢だけを残すことがあります。 + +ハード衝突は通常、生存区間構築時に見つかり、干渉グラフ構築や彩色の前に分割ラウンドを引き起こします。干渉グラフ構築中にも見つかる場合があり、一般に1レジスタの破壊が原因です。 + +### 干渉の回避 + +Briggs-Chaitin 割り当て器に対する Click の拡張の1つは、レジスタ制約を利用して干渉グラフの次数を下げることです。そうでなければ辺集合は O(n^2) になります。LR1 が、`rdx` を必要とする呼び出し引数などによって単一レジスタに制限され、より多くの選択肢を持つ LR2 と本来衝突する場合、LR2 の選択肢から `rdx` を除きます。`rdx` がなければ両者に共通レジスタがなくなり、衝突しません。その間の干渉グラフ辺を単に追加しないのです。呼び出し引数の制限は非常に一般的で、この辺除去は O(n^2) の辺集合を大きく縮小し、割り当てを大きな定数倍だけ高速化できます。 + +### 自己衝突 + +生存区間は、異なる2つの値が同時に生存しながら同じレジスタを要求する、*自己衝突* を起こせます。この区間は *必ず* 分割し、その結果としてコピーを得ます。単純な `fib` プログラムなどの、ループをまたぐ依存関係で起きます。 + +```java + int x=1, y=1; + while( true ) { + tmp = x + y; + y = x; + x = tmp; + }; + +``` + +SoN の SSA 形式では: + +```java + int x0 = 1, y0 = 1; + while( true ) { + x1 = phi(x0,x2); + y1 = phi(y0,x1); + x2 = x1 + y1; + } +``` + +変数 `x0,x1,x2,y0,y1` はすべて `Phi` で結合されるので、同じ生存区間の一部です。しかし `x1` と `y1` は `fib` の異なる反復に由来する別々の値を保持します。SSA 形式にはコピーがありませんが、レジスタ割り当て後の形式は、元のコードと同様にコピーを必要とします。 + +自己衝突は干渉グラフ構築中に見つかり、彩色フェーズを試す前に分割ラウンドを引き起こします。 + +## 彩色のヒューリスティック + +レジスタの彩色も分割もヒューリスティックが多数あり、その質が最終的な割り当て品質を大きく左右します。グラフ彩色は NP 完全であり、効率的な彩色アルゴリズムはありません。いずれにせよ、多くのプログラムは分割しなければ彩色できません。 + +### バイアス付き彩色 + +バイアス付き彩色は、Split を除去できるように「よりよい色を選ぶ」、あるいは「色の選択に偏りを付ける」ヒューリスティックです。Split は1つの生存区間を2つに分けますが、必要のない分割が挿入されることもあります。つまり、すでに過剰な分割が起きています。片側の区間が彩色済みで、もう片側も同じ色にできれば、その分割は除去できます。どちらも未彩色でも、片側が再帰的に彩色済み区間に接続されていれば、分割の連鎖がすべて同じ色になって除去されることを期待して、色に偏りを付けられます。 + +彩色処理が逆順に戻り、区間の色を選ぶとき、多くのレジスタ選択肢があることがよくあります。まず任意に、番号が最小のレジスタを選び、それからよりよい色を探します。バイアス付き彩色は代表的な定義を先に調べ(1つしかなければその唯一定義)、次に代表的な使用を調べます。分割の「反対側」が彩色済みかを確認し、そうであり、その色を使えれば採用します。分割があっても未彩色なら、その反対側で再帰的に繰り返します。2アドレス演算のもう1つの入力や、Phi ノードの入力の1つについても行います。 + +どの場合も、現在利用でき、将来この Split の反対側にも選べるかもしれない有効な色を選ぶのが目標です。それによって両側が同じ色になり、分割を除去できます。 + +### 危険な区間の選択 + +ここでは、大きなクリークを作る複数の *生存区間* から1つを選ばざるを得ません。相互の隣接ノードが多すぎて、どれも色を保証されません。ここには相反する要求があります。 + +「面積」が大きく、つまりプログラムの広い範囲を覆い、スピルの「コスト」が低い、つまり定義と使用が少なく、ループ外にある区間を選べば、大きな効果があります。プログラムの広い範囲でレジスタが空き、ほかの場所で彩色が成功できます。通常の ABI の callee-save レジスタは理想的です。入口で定義し出口でだけ使うため、プログラム全体を覆います。スピルも入口と出口で1回ずつ分割するだけです。このような区間のスピルは、基本的に古典的な関数のプロローグ/エピローグ列を構築します。ただし、この割り当て器は、たとえば高速経路の出口より外側でスピルすることも許されます。 + +``` + // No registers saved if the initial RDI is null + test rdi + jne BIG_AND_SLOW + xor rax,rax + ret // Fast path exit: null argument means null result +BIG_AND_SLOW: + add rsp,#12 + mov [rsp+0],rbx // begin spilling callee-save registers + mov [rsp+4],r12 // + // lots of code needing callee-save registers +``` + +もう一方の要求は、偶然によって彩色できそうな区間を選ぶことです。「ほとんど彩色可能」、たとえば利用可能なレジスタ/色より隣接ノードが1つだけ多い区間なら、偶然やバイアス付き彩色によって隣接ノードのいくつかが同じ色を使い、色が空いて、この危険な区間も結局彩色できるかもしれません。 + +## 分割 + +ここでは彩色に失敗したので、分割が必要です。特定の区間が彩色できず、分割方法を決めなければなりません。ここにも相反する要求があります。分割が多すぎると割り当てが悪くなり、少なすぎると進捗しないバグになり、任意の上限を超えるほど多くの分割ラウンドとして現れます。 + +まずスピルをソートして決定的にします。そうしないと HashMap の巡回順になります。それから各スピル対象の区間を順に調べ、分割戦略を決めます。 + +### 自己衝突の分割 + +自己衝突区間は「干渉グラフ構築」段階で発見します。この場合、自己衝突がある状態での彩色には意味がないので、彩色を試さず直接分割へ進みます。IFG 構築中に、この区間の衝突する定義の部分集合を収集しました。それらの定義位置を巡回し、スピルを挿入します。 + +- Phi のスロット #1 の前(ループ入口の Phi をループ本体から切り離す)と、Phi の後。 +- 2アドレス命令の前。演算の前後で区間を分断します。 +- 生存区間を延長する使用の前。 + +この分割集合はかなり積極的ですが、列挙した各位置で分割を必要とするテストを第20章に含めています。自己衝突がある間は彩色を試すことさえできないため、素早く区間を切り分けることが重要です。過剰分割になりがちなので、割り当て器はバイアス付き彩色と最終コピー整理に頼って余分なものを除去します。保守的な併合は後の章で導入します。 + +### ハード衝突の分割 + +直接の定義/使用の衝突(たとえば `rdi` で定義され、`rax` で使われる)は「生存区間構築」走査で発見します。「干渉グラフ構築」走査でも発見できます。たとえば、`rdi` で入る引数が後で使われるものの、`Call` に破壊される場合です。 + +単一定義と単一使用の単純なハード衝突では、正確な定義と使用を生存区間自身に保持しています。定義の後と使用の前で1回ずつ、レジスタが制限された側にだけ分割を行います。 + +より複雑な場合は、容量不足のスピル/ループ入れ子の手法を使います。複製可能な値は使用の近くで再生成できますが、その複製の出力レジスタクラスが使用を満たせる場合だけです。そうでなければ移動が必要です。固定レジスタの定数に柔軟な使用がある場合も、使用で分割しなければなりません。定義で何もしないと、同じハード衝突を永遠に繰り返します。 + +よく使われる定数(たとえば0)は、異なるレジスタを要求する多くの使用を持ちます。使用をレジスタクラスごとにまとめれば移動を減らせますが、このヒューリスティックは後の章まで延期します。 + +### ループの入れ子に沿った分割 + +ここでは通常、容量不足のスピルが起きています。単にもっとレジスタが必要です。`Call` が一時レジスタをすべて破壊し、値を呼び出しの先へ運ぶ必要があるときに起きやすいですが、大きな関数で多くのことが同時に起きる場合にも発生します。 + +ヒューリスティックは、頻繁に実行されるループ本体がスピルしないよう、利用可能レジスタを残すためにループの *周囲* で分割します。分割ラウンドが進むにつれ、区間をより深い入れ子へ分割します。最後には、最内周ループであっても各定義の後と各使用の前で分割します。 + +まず、すべての定義と使用の最小・最大ループ深さを求めます。それらが異なれば最外周ループの *周囲*、すなわち境界で分割し、外側からの制約を持たない内側の区間はそのまま保持します。それでも彩色できなければ、次のラウンドでは最外周ループが1段内側へ移り、次の内側の入れ子の周囲で分割します。最内周ループ内で分割するまで、この処理を繰り返します。 + +### 補助関数 + +RA は、比較的自己完結した一連の補助関数を使います。 + +- **findAllLRG**: 区間のすべての定義と使用を、O(N) ではなく O(|LRG|) 時間で見つけます。 +- **insertBefore** と **insertAfter**: 使用の前か定義の後に分割を挿入します。基本ブロック内の正しい順序に置く処理と、すべての辺の保守を含みます。 +- **makeSplit**: 分割を作ります。複製可能な定数を複製するか、`Machine` に移植先固有の `SplitNode` を要求します。 +- **ldepth**: `long` に詰めた最小・最大ループ深さを受け取り、指定ノード `n` に対する深さ範囲を拡張します。定義や使用がループ「内」にあるかを判定し、ループ周囲の分割に使います。 + +## 割り当て後 + +彩色成功後、両側が同じレジスタの分割を取り除きます。分割を素早く走査し、レジスタを確認して不要なものを除去します。残ったものは割り当ての「採点」用に追跡します。また、分割の後の分割の一部を迂回し、冗長なコピーを除去できることもあります。 + +レジスタは `RegAlloc` オブジェクトの `alloc.regnum( Node n )` から引き続き利用でき、次の命令エンコーディング走査で使います。 + +## RegAlloc の改良: 正しさと測定された基準値 + +この章は小さな割り当て器から始まります。ハード衝突は実際に断ち切らなければなりません。同じブロックに別のコピーがあるだけで分割を省略したり、衝突を復活させるような除去をしたりしてはいけません。ループの後退辺も、特に Phi が並列代入を表す場合には同じ注意が必要です。出力値のない命令にもレジスタ破壊は適用されます。最後に、冗長なコピーの除去には、間にある定義やレジスタ破壊を探す後ろ向き走査が必要です。これらはスピル品質とは独立した、正しさの規則です。 + +分割前に失敗した区間をソートし、同一性マップの順序の変化が割り当てを決めないようにします。割り当て器には null に安全な分割表示と、任意の関数内辺の診断もあります。テストはレジスタマスク、2アドレスのオペランド、Phi のレジスタ一致を確認します。小さなマシングラフが、最適化器の選択とは独立にハード衝突を検証します。ネイティブ実行は、第21章のエンコーディングとともに導入します。 + +### テスト全体で合計したスピル数 + +`make spill-stats` を実行すると、テストを実行し、各コンパイルの数、CPU/ABI ごとの小計、以下の合計を表示します。アサーションは有効なままで、テストが失敗すればコマンドも失敗します。最適化器の既定シードは123です。 + +第20章のプログラム群は `Chapter20Test` の11のソース例すべてと、`BrainFuckTest.testBrainfuck`、`MergeSortTest.testMergeSort` を、x86-64、RISC-V、ARM の SystemV でコンパイルしたものです。スピル0のケースを含む13プログラム、39コンパイルです。人工的な割り当て器の回帰グラフは正しさの確認であり、追加のプログラムベンチマークではありません。 + +| プログラムテスト群 | 割り当て器の章 | コンパイル数 | 分割による移動 | ループ重み付き移動 | +|---|---:|---:|---:|---:| +| 第20章 | 20 | 39 | 234 | 360 | + +`_spills` はレジスタ間コピーも含む、残存した分割移動を数えます。スタックへのストアだけではありません。`_spillScaled` は各移動を `8^loopDepth` で重み付けします。これは粗いコスト推定で、測定された実行頻度ではありません。重み付き小計は x86-64 が161、RISC-V が100、ARM が99です。 + +### 測定についてのコメント + +正しさの修正前、この群は235移動/354重み付き移動でした。遅延メモリ導入前の修正済み基準値は、両方が3増えました。そのうち2移動は、最終的な x86 ストアマスク制限のバックポートによるものです。整数のバイト/short ストアは XMM レジスタを消費できません。これは正しさのためのコストで、品質の向上ではありません。固定隣接ノードへの色バイアスの修正は、この群の数を変えません。 + +遅延メモリと選択済み読み取りの順序付けにより、同じ39コンパイルは導入前の238/357から、234移動/360重み付き移動になりました。すべての対象でレジスタの合法性が確認でき、割り当て器のヒューリスティックは変更していません。変化したプログラムは次のとおりです。 + +| プログラム | 対象 | 移動の前/後 | 重み付きの前/後 | +|---|---|---:|---:| +| 配列アクセス(`testAlloc2`) | x86-64 | 1 / 2 | 1 / 2 | +| 配列の累積和(`testArray1`) | x86-64 | 5 / 6 | 5 / 13 | +| 文字列ハッシュ | x86-64 | 7 / 5 | 21 / 19 | +| 文字列ハッシュ | RISC-V | 4 / 3 | 4 / 3 | +| 文字列ハッシュ | ARM | 5 / 3 | 5 / 3 | +| マージソート | ARM | 37 / 36 | 37 / 36 | + +累積和の追加のループ移動は、選択済み読み取りを、それを破壊する書き込みより前にスケジュールすることから生じます。その順序を省く診断ビルドでは5/5に戻りますが、有効な代替スケジュールではありません。これはフロントエンドとスケジューリングのグラフの変化であり、割り当て器のヒューリスティックの比較ではありません。新しいメモリ割り当ての回帰テストは合法性を確認しますが、この測定群に項目を追加しません。 + +後の章では、品質向上の手法を1つずつ追加し、このような表で締めくくります。第21章では自身の割り当て器で第20章と第21章のプログラム群を測定し、第25章では第20〜25章の群の行を持ちます。章をまたいで行を比較するときは、プログラム/対象の構成とシードを固定し、最適化器や命令選択の変化を割り当ての変化から分けてください。全体が改善するなら局所的な悪化に価値がある場合もありますが、割り当ての失敗や違法なレジスタ割り当てに価値はありません。 diff --git a/docs/chapter20/README.md b/docs/chapter20/README.md new file mode 100644 index 00000000..543bbd84 --- /dev/null +++ b/docs/chapter20/README.md @@ -0,0 +1,529 @@ +# Chapter 20: Graph Coloring Register Allocation + +English | [日本語](README.ja.md) + +[Previous: Chapter 19](../chapter19/README.md) | +[Next: Chapter 21](../chapter21/README.md) + +The grammar is unchanged from [Chapter 18](../chapter18/18-grammar.md). + +## Some Reading Material + +This is a Briggs-Chaitin-Click allocator, and is very similar to the one used +in HotSpot's C2 allocator for the past 25 years with great success. + +Wikipedia has a nice overview of register allocation in general: https://en.wikipedia.org/wiki/Register_allocation + +Chaitin's original: https://en.wikipedia.org/wiki/Register_allocation#CITEREFChaitin1982 + +Briggs additions: https://dl.acm.org/doi/10.1145/177492.177575 + +Nice lecture slides pdf of Briggs/Chaitin: https://aktemur.github.io/cs544/lectures/chapter%2013%20registerAllocation.pdf + +There are several other useful links that can be found by searching for Briggs +or Chaitin in combination. Basically there was a spate of register allocation +improvements in this era that made it into many high-end mainstream compilers. + +I've included a very old unpublished paper on speeding up graph coloring +allocators: [Interference Graph Trimming](ifg_trim.pdf). + + +## Meta-Issues / Code-Dev Issues of Instruction Selection, Register Allocation and Encodings + +The output of Register Allocation - an allocation of registers to ops - is +difficult to test in bulk without an execution strategy. We won't have an +execution strategy until all three of Instruction Selection, Register Allocation +and Encodings are done. Hence in and around the completion of Encoding we +expect to find lots of bugs in Instruction Selection and Register Allocation. +We certainly hand-inspect the output and fix obvious problems, but still lots of +bugs linger until we can actually run the code. + +This means that other chapters have updates and bug-fixes to the work being +described here. + +Specific to the register allocator, we will be gradually adding improvements to +it, chapter by chapter for quite a few chapters. Register allocators generally +end with a large number of heuristics which we will add slowly. + +You can also read [this chapter](https://github.com/SeaOfNodes/Simple/tree/linear-chapter20) in a linear Git revision history on the [linear](https://github.com/SeaOfNodes/Simple/tree/linear) branch and [compare](https://github.com/SeaOfNodes/Simple/compare/linear-chapter19...linear-chapter20) it to the previous chapter. + + +## The Basic Theory + +The reading material includes several explanations of the basic concepts, but +at the risk of being redundant I'll present a short version again here. + +I assume you (the reader) know *why* register allocation is required: machine +registers are a high value finite resource, and as with all finite resources +they need to be allocated well. + +Graph Coloring is one such allocation method; machine registers are treated as +colors; a *live range interference graph* is built, with each *live range* +requiring its own register/color. Coloring the graph ensures every live range +gets a unique register, specifically when it overlaps or *interferes* with +another live range. + +A successful coloring ends the allocation and after some bookkeeping the +program is ready for e.g. code emission. A failed coloring requires spilling +or splitting conflicting live ranges; in this allocator we will be doing live +range *splitting*. Splitting live ranges makes them more colorable; they +become shorter with fewer interfering other live ranges. Generally large +functions might require a few rounds of splitting before becoming colorable. +Since the problem is NP-complete there are no guarantees here, the final +allocation quality becomes heavily dependent on splitting and coloring +heuristics. + +Hence, much of the "high theory" will be used to drive the core coloring +algorithm, but much of the actual success will depend on a large collection +of heuristics. + +### Coloring Rounds + +Coloring proceeds in rounds until we get a coloring: + +- Build the Live Ranges; live ranges require the same register for all + instructions in the live range. Phi functions will force unrelated def/use + edges to become part of the same live range and this can drive large + interconnected live ranges with a lot of conflicts. Some hardware ops + require the same register for both inputs and outputs (X86 is notorious here) + which also makes fewer larger live ranges. We'll be using the disjoint + Union-Find algorithm to rapidly build up live ranges in one forward pass over + the program. + +- Build the Interference Graph: every live range which is alive at the same time + as any other, now *interferes* or conflicts. This is built using a LIVE-ness + pass, which is a backwards pass over the CFG, and can require more than one + pass according to loop nesting depth. The data structure here is generally a + 2-D bitset, triangulated to cut its size in half. As part of LIVE, we'll + also have a set of reaching defs per-block. + +- Color the interference graph. Nodes (live ranges) in the graph which are + strictly low degree (more available registers than neighbors) are guaranteed + to get a color. These can be removed from the graph - since they will + guaranteed get a register (color) because they have so few conflicting + neighbors. Removing these trivial nodes makes more nodes go trivial, and + this process repeats until either we run out (and are guaranteed a coloring) + or we find a high-degree clique: a set of mutually interfering live ranges + where none of them are guaranteed a color. In this case we pull an "at risk" + live range out; this one might not color. Once the graph is emptied, we + reverse and re-insert the live ranges, coloring as we go. All the trivial + live ranges will color; the at-risk ones may or may not. + +- If every live range gets a color, then Register Allocation is done. If not, + we enter a round of splitting. Live ranges which did not color are now split + via a series of heuristics into lots of small live ranges. Splitting has a + tension: too much and you got a lot of spills (and a generally poor + allocation). Too little, and you remain uncolorable - and do not make + progress towards an allocation. Once we're done splitting, we start a new + round of coloring (Building the Live Ranges, then the Interference Graph, + then Coloring). + + +## Registers and Register Masks + +Registers are the thing that Register Allocation is all about! They are +represented as a small dense integer, starting up from 0, and the numbering +generally follows from the hardware encodings. So for an X86_64, RAX will be +register 0, RCX register 1 and so on up to the 16 GPRs. The XMM register numbers start at +16 and go through 31. Register numbers must be unique; this is how the register +allocator tracks them. + +#### Why a RegMask class and not a simple `long`? + +A collection of registers live in a Register Mask. For smaller and simpler +machines it suffices to make such masks an i64 or i128 (64 or 128 bit +integers), and this presentation is by far the better way to go... if all +register allocations can fit in this bit limitation. The allocator will need +bits for stack-based parameters and for splits which cannot get a register. +For a 32-register machine like the X86, add 1 for flags - gives 33 registers. +Using a Java `long` has 64 bits, leaving 31 for spills and stack passing. This +is adequate for nearly all allocations; only the largest allocations will run +this out. However, if we move to a chip with 64 registers we'll immediately +run out, and need at least a 128 bit mask. Since you cannot *return* a 128 +bit value directly in Java, Simple will pick up a `RegMask` class object. + +#### Stack Slots + +One of the Click extensions is this notion of treating "stack slots" as Just +Another Register. They get colored like other registers, which in turn leads +to very efficient use of the stack; C2 is known for having very small stack +frames. Another benefit is callee save registers will get split preferentially +(they have very long lifetime and only one def on entry and one use on +exit)... which the splits then end up coloring to the stack, building the call +prolog and epilog naturally. This allocator will not otherwise dedicate any +effort to stack frame management; it will "fall out in the wash". + +Many of the register masks are immutable; e.g. allowed registers for particular +opcodes, or describing registers for calling conventions. Other masks +represent mutable bitsets with the allocator routinely masking off bits when +accumulating a set of constraints. To help keep these uses apart, the +`RegMask` class includes mutable and immutable variants. + +#### Memory + +Memory edges exist to enforce ordering, but do not get any registers. +The allocator basically ignores them. + + +### Callee/Caller Save Registers + +In this allocator, callee- and caller-save registers get almost no special +treatment and no special prolog nor epilog is built to save and restore them. +At the start of the allocator, SoN graph edges are added between each `Return` +and a `CalleeSave` from each `Fun`. These edges are given register masks +pinning them to the callee-save set as defined by the normal ABI. + +Callee-save live ranges thus have a single def (a `CalleeSave` at function +start) and a single use (the `Return`), are cheap to spill and free a register +over a large area - they make ideal spill candidates. When the allocator needs +to spill something to get some more registers, these will be the first to +spill. A prolog and epilog will get built as a consequence of the normal live +range splitting process. + +`Calls` kill the caller-save registers (just the inverted callee-save mask), +and this is computed during the normal interference graph building process just +like any other operation that kills registers. Killing a bunch of registers at +a call typically removes all the free registers from whatever is currently +alive - and will thus force spilling... which will quickly end up spilling +callee-save registers as ideal candidates. + + +## Live Ranges + +A live range is a set of nodes and edges which must get the same register. +Live ranges form an interconnected web with almost no limits on their shape. +Live ranges also gather the set of register constraints from all their parts. + +Live ranges are held in `LRG` class instances. Most of the fields in this +class are for spill heuristics, but there are a few key ones that define what a +LRG is. `RegAlloc` has several functions dealing with LRGs, including a lookup +table from Nodes i.e., a mapping from `Node` to `LRG` (which is very similar to +having a dedicated LRG field in each Node... except that LRGs are only used +during RegAlloc and the field would be dead weight otherwise). + +LRGs have a unique dense integer `_lrg` number which names the LRG. New `_lrg` +numbers come from the `RegAlloc._lrg_num` counter. LRGs can be unioned +together - +[this is the Union-Find algorithm](https://en.wikipedia.org/wiki/Disjoint-set_data_structure) +- and when this happens the lower numbered `_lrg` wins. Unioning only happens +during `BuildLRG` and happens because either a `Phi` is forcing all its inputs +and outputs into the same register, or because of a 2-address instruction. +LRGs have matching `union` and `find` calls, and a set `_leader` field. + +Post-allocation the `LRG._reg` field holds the chosen register. During +allocation the `LRG._mask` field holds the set of available registers, +typically all the defaults, minus various conflicts. + +The `LRG._adj` holds a list of adjacent neighbors as part of the larger +Interference Graph, and is only used during the Coloring phase. +[See more about graphs here.](https://en.wikipedia.org/wiki/Graph_(abstract_data_type)) +This is an "adjacency list" form of a Graph description, and is built from the +IFG's 2-D collection of bits (itself a 1-D list of `BitSet`s). Graph coloring +register allocation is one of the few places where changing the layout of a +data structure mid-algorithm pays out. + +The rest of the fields are dedicated to various spilling heuristics: + +- `_machDef` `_machUse` `_uidx` - A sample def and use. +- `_splitDef` `_splitUse` - Sample splits involved with this LRG; biased + coloring attempts to align register choices to remove these. +- `_selfConflicts` - A partial set of *self-conflicting* nodes. These are + detected during the Interference Graph build phase, and require aggressive + splitting. +- `_1regDefCnt` `_1regUseCnt` - Count of defs and uses which are pinned to a + single register. These typically require a hard-split just before (use) or + after (def) to free up the register choices. + + +## Live Ranges and Conflicts + +If the live range `_mask` field goes empty, you might have a *hard conflict* or +have gotten *killed*. Live ranges can self-interfere, making a +*self-conflict*. During coloring you might discover an uncolorable clique of +interfering live ranges, leading to a *capacity spill*. This leads to a set of +levels of conflicts within a live range (and different heuristics for handling +them). + +### Hard-Conflicts + +A live range can have a *hard conflict*: conflicting register requirements, +leading to no valid register choices. The obvious case is an incoming +parameter fixed in e.g. `rdx` but also required as an exit value in `rax`. You +can't pick both registers at once, so a split is required. During the build +live ranges phase, every register constraint from every def and use are AND'd +together; the `RegMask` might lose all valid registers, or remain with just a +handful of register choices. + +Hard-conflicts are usually found while building live ranges, and will trigger a +round of splitting before building the interference graph or coloring. They +can also be found during interference graph building, generally caused by a +1-register kill. + +### Avoiding interferences + +One of the Click extensions to the Briggs-Chaitin allocator is to take +advantage of register constraints to lower the interference graph degree (which +otherwise becomes an O(n^2) edge collection). If a live range LR1 is reduced +to a single register (such as a call argument requiring `rdx`) and would +otherwise conflict with another live range LR2 which has more choices - we +remove `rdx` from the LR2's choices. Without having `rdx`, LR1 and LR2 have no +registers in common - and hence do not conflict. We just don't add the +interference graph edge between them. Call argument restrictions are very +common and this kind of edge-removal can dramatically shrink the O(n^2) edge +collection - and hence speedup allocation by a large constant factor. + +### Self-Conflicts + +A live-range can *self-conflict*: be alive twice with 2 different values but +require the same register. This live range *must* split and thus picks up a +copy from the split. This happens with loop-carried dependencies as seen in +e.g. a simple `fib` program: + +```java + int x=1, y=1; + while( true ) { + tmp = x + y; + y = x; + x = tmp; + }; + +``` + +In SoN SSA form: +```java + int x0 = 1, y0 = 1; + while( true ) { + x1 = phi(x0,x2); + y1 = phi(y0,x1); + x2 = x1 + y1; + } +``` + +The variables `x0,x1,x2,y0,y1` all form part of the same live range, since they +are all joined by `Phis`. However, `x1` and `y1` hold different values from +different iterations of `fib`. The SSA form does not have a copy, but the +register-allocated form - just like the original code - DOES require a copy. + +Self-conflicts are found during building the interference graph, and will +trigger a round of splits before attempting the coloring phase. + + +## Coloring Heuristics + +Both register coloring and register splitting are full of heuristics, and the +quality of these heuristics goes a long way to deciding the quality of the +final allocation. Graph coloring is NP-complete; there is no efficient +algorithm for coloring and many programs are simply not colorable without +splitting in any case. + + +### Biased Coloring + +Biased coloring is a heuristic that attempts to "pick a better color" or "bias +the color choice", such that a Split is removed. A Split splits one live range +into two; sometimes some are inserted that are not required (i.e., over- +splitting has already happened). If one side (live range) of a split is +already colored and we can color the other side (live range) the same color - +then this split can be removed. If neither side is colored, but one or the +other side is recursively connected to a colored live range, we can still bias +the color in the hopes that a chain of splits will all get the same color and +thus all get removed. + +When the coloring process is reversing, and colors are being picked for live +ranges, we often have many register choices. A first choice is made +arbitrarily (lowest numbered register), and then we go looking for a better +color. Biased coloring inspects first a sample def (if there is only one, then +the One Def is inspected), and then a sample use. Inspection looks to see if +"the other side" of a split is already colored; if so, and that color is +available it is chosen. If there is a split, but it is not colored - we +recursively repeat the process on that split's other side. This is also done +for the other input of two-address ops and one of a Phi node inputs. + +In all cases, the goal is to pick a valid available color - that might also +eventually be pick-able for this Split's other side - and lead to this split +having the same color for both sides, and then being removed. + + +### Pick Risky + +Here we are forced to pick one of several *live ranges* that form a large +clique: they have too many mutual neighbors so that no one of them is +guaranteed a color. There is a conflicting tension here: + +Picking a live range with a large "area" (i.e. covering much of the program), +and low "cost" to spill (i.e. fewer defs and uses, and outside of loops) will +give a large win. A register will become free over a large part of the program +and allow coloring to succeed elsewhere. The normal ABI callee-save registers +are ideal in this case, defined on entry and used only on exit they cover the +entire program. Also, spilling means only a single split on entry and another +at exit. Spilling such live ranges basically builds a classic function +prolog/epilog sequence. Here though, the allocator is allowed to e.g. spill +outside a fast-path exit. + +``` + // No registers saved if the initial RDI is null + test rdi + jne BIG_AND_SLOW + xor rax,rax + ret // Fast path exit: null argument means null result +BIG_AND_SLOW: + add rsp,#12 + mov [rsp+0],rbx // begin spilling callee-save registers + mov [rsp+4],r12 // + // lots of code needing callee-save registers +``` + +The other side of the tension is to pick live ranges that are likely to color +more by chance. If a live range is "very nearly colorable" - say only one more +neighbor than available registers/colors then by chance (and Biased Coloring) +some neighbors might use the same color... freeing up a color and allowing this +risky live range to color after all. + + +## Splitting + +Here we failed to get a coloring - and need to split. Some specific live +ranges did not color and we will need to decide how to split. Again there are +tensions here: too much splitting leads to a bad allocation, too little leads to +no-progress bugs (manifested as too many rounds of splitting past some +arbitrary cutoff). + +We start by sorting the spills to be deterministic (else they are sorted by +HashMap visitation order), then looking at each spilling live range in turn +and deciding on a splitting strategy. + + +### Split Self Conflict + +Self-conflict live ranges are discovered during the "Build the Interference +Graph" stage. If this happens we don't attempt a coloring (it's nonsensical +with self conflicts), but go straight to splitting. During the IFG building we +gathered a subset of the conflicting definitions for this live range. Now +we visit all those definition points and insert spills: + +- Before slot #1 on a Phi (which breaks loop-entry Phis from the loop-body) and + also after the Phi. +- Before a two-address instruction, breaking the live range before the op from + afterwards. +- Before any use that extends a live range. + +This set of splits is fairly aggressive... but test cases requiring a split in +each of the listed locations are included in Chapter 20's test cases. We +cannot even attempt a color while we have self conflicts, so it's important to +break up these live ranges quickly. This tends to over-split and the allocator +leans on Biased Coloring and the final copy cleanup to remove some of the +extras. Conservative coalescing will be introduced in a later chapter. + + +### Split Hard Conflict + +Hard conflicts are discovered during the "Build the Live Ranges" pass for +direct def/use conflicts (e.g. defined in `rdi` and used in `rax`). They also +can be discovered during the "Build the Interference Graph" pass (e.g. incoming +argument in `rdi` used later in the program and killed by a `Call`). + +For simple hard-conflicts (a single def and single use), we have the exact def +and use kept in the Live Range itself. We split once after the def and once +before the use (and only on sides with restricted registers). + +For more complex cases we use the capacity spilling/loop-nest technique. +Cloneable values can be recreated near their uses, but only when the clone's +output register class can satisfy that use. Otherwise we need a move. A fixed +register constant with flexible uses must also split at its uses; doing nothing +at its definition would repeat the same hard conflict forever. + +Popular constants (e.g. 0) often have many uses requiring different registers. +Grouping these uses into register classes can save moves, but we defer that +heuristic to a later chapter. + + +### Split By Loop Nest + +Here we typically see capacity spills: we just need more registers. This +easily happens when a `Call` crushes all the temporary registers and we need to +carry some values past the call - but it can also happen any time we have a +larger function with lots of things going on at once. + +The heuristic here is to split *around* loops, attempting to keep some free +registers available so the hot loop body does not spill. Live ranges are split +into successively deeper loop nests in progressive rounds of splitting, and in +the final case will split once after each def and before each use, even in the +innermost loop. + +The heuristic starts by discovering the min and max loop depth for all defs and +uses. If these vary, we will split *around* the outermost loop, putting in +splits at the loop border and keeping an inner untouched live range that has no +constraints from outside. If this fails to color, on the next round of +splitting the outermost loop will have moved in one layer, and we will be +splitting around the next inner loop nest. This process repeats until we end +up splitting in the inner-most loops. + + +### Helpers + +RA uses a series of helper functions, each is fairly self-contained. + +- **findAllLRG** - find all the defs and uses of a live range in time O(|LRG|) + instead of time O(N). +- **insertBefore** and **insertAfter** - insert a split either before a use or + after a def. Includes the work to put the split in its proper ordering in + a Basic Block and do all edge maintenance. +- **makeSplit** - Make a split (either clone a clonable constant, or ask the + `Machine` for a port-specific `SplitNode`). +- **ldepth** - Given a min&max loop depth (compacted in a `long`), expand the + loop depth range for a given node `n`. Used to decide when a def or use is + "inside" a loop, which in turn is used to split around loops. + + +## Post Allocation + +After a successful coloring, we want to pull out any same-same register splits. +A quick pass over the splits and after checking registers, we pull out the +useless splits (and track the rest for "scoring" our allocation). We'll also +bypass some split-after-splits, which can remove some redundant copying. + +The registers remain available in the `RegAlloc` object via `alloc.regnum( Node n )` +and will be used by a following instruction encoding pass. + + +## RegAlloc improvements: correctness and a measured baseline + +This chapter starts with a small allocator. Hard conflicts must actually be +broken: a split cannot be skipped just because another copy is in the same +block, nor folded away when that would restore the conflict. Loop backedges +need the same care, especially when Phis represent parallel assignments. +Register kills apply even to instructions with no output value. Finally, +removing a redundant copy requires a backward scan for intervening definitions +and register kills. These are correctness rules, independent of spill quality. + +We sort failed live ranges before splitting so changes in identity-map order do +not decide the allocation. The allocator also has null-safe split printing and +an optional function-local-edge diagnostic. The tests check register masks, +two-address operands, and Phi register agreement; tiny machine graphs exercise +hard conflicts independently of optimizer choices. Native execution comes with +encoding in Chapter 21. + +Rows are fixed test cohorts; columns are compiler chapters. Each cell gives +**Ops / RA / X**: executed ARM+RISC-V instructions, the subset emitted for +allocator copies/rematerializations, and x86's loop-weighted split-move estimate. +**Cases D / X** counts measured dynamic program/target cases and x86 compilations; +membership and inputs stay fixed across columns. Native-library work is excluded. +Chapter 20 has no encoder, hence no dynamic counts. Lower is better within each +metric; see the [measurement details](../regalloc-spills.md) for coverage. + + +| Test cohort | Cases D / X | Ch 20: Ops / RA / X | +|---|---:|---:| +| Ch 20 | 22 / 13 | — / — / 161 | + + +These Windows measurements use optimizer seed 123 for cohorts 20-24 and include +x86-64 SystemV/Win64 and ARM/RISC-V SystemV as applicable. Run `make spill-stats` +here for this compiler, or at the repository root to rebuild the full matrix +and check cohort membership. See the [complete comparison](../regalloc-spills.md) +for measurement details and reproduction commands. + +Cohort 20 contains 11 Chapter20Test programs plus BrainFuck and MergeSort, +each on three SystemV targets: 39 compilations. Synthetic allocator graphs +check correctness separately. This is the baseline; later chapters extend the +columns while keeping these programs and targets fixed. diff --git a/docs/chapter20/font-awesome-4.7.0.zip b/docs/chapter20/font-awesome-4.7.0.zip new file mode 100644 index 00000000..b6bdb4bd Binary files /dev/null and b/docs/chapter20/font-awesome-4.7.0.zip differ diff --git a/docs/chapter20/ifg_trim.pdf b/docs/chapter20/ifg_trim.pdf new file mode 100644 index 00000000..d952fd4f Binary files /dev/null and b/docs/chapter20/ifg_trim.pdf differ diff --git a/docs/chapter20/lattice.gv b/docs/chapter20/lattice.gv new file mode 100644 index 00000000..8661082d --- /dev/null +++ b/docs/chapter20/lattice.gv @@ -0,0 +1,203 @@ +digraph Lattice { + compound = true; + // Define nodes + splines=false; + node [shape=circle, style=filled]; + nodesep=0.2; + ranksep=0.2; + + TOP [label=<⊤>, fillcolor=black, fontcolor=white]; + BOT [label=<⊥>, fillcolor=black, fontcolor=white]; + + // ---------------------- + // Control + XCTRL [label=<⊤:ctrl>, fillcolor=yellow, shape=oval]; + CTRL [label=<⊥:ctrl>, fillcolor=yellow, shape=oval]; + + TOP -> XCTRL; + XCTRL -> CTRL; + CTRL -> BOT; + + // ---------------------- + // Integers + INT_TOP [label=<⊤:int>, fillcolor=lightblue, shape=oval]; + INT_BOT [label=<⊥:int>, fillcolor=lightblue, shape=oval]; + + INT_HI [label="[min>max]" , fillcolor=lightblue, shape=oval]; + INT_XBOOL [label="[1...0]", fillcolor=lightblue, shape=oval]; + INT_CON [label="constants", fillcolor=lightblue, shape=oval]; + INT_CON0 [label="0", fillcolor=lightblue]; + INT_CON1 [label="1", fillcolor=lightblue]; + INT_BOOL [label="[0...1]", fillcolor=lightblue,shape=oval]; + INT_LO [label="[min INT_TOP; + INT_TOP -> INT_HI; + INT_HI -> INT_XBOOL; + INT_XBOOL -> INT_CON0; + INT_XBOOL -> INT_CON1; + INT_HI -> INT_CON; + INT_CON0 -> INT_BOOL; + INT_CON1 -> INT_BOOL; + INT_BOOL -> INT_LO; + INT_CON -> INT_LO; + INT_LO -> INT_BOT; + INT_BOT -> BOT; + + INT_CON0 -> INT_CON1 -> INT_CON [style=invis]; + + // ---------------------- + // Floats + FLT_TOP [label=<⊤:flt>, fillcolor=aqua, shape=oval]; + FLT_BOT [label=<⊥:flt>, fillcolor=aqua, shape=oval]; + + FLT_CON64 [label="64b consts\n[excludes\n32b consts]" , fillcolor=aqua, shape=oval]; + FLT_CON32 [label="32b consts", fillcolor=aqua, shape=oval]; + FLT_CONPI [label="3.14159...", fillcolor=aqua, shape=oval]; + FLT_X32 [label="~f32", fillcolor=aqua]; + FLT_CON0 [label="0." , fillcolor=aqua]; + FLT_32 [label="f32" , fillcolor=aqua]; + + TOP -> FLT_TOP; + FLT_TOP -> FLT_CON64 + FLT_TOP -> FLT_CONPI + FLT_CON64 -> FLT_BOT + FLT_CONPI -> FLT_BOT + + FLT_TOP -> FLT_X32; + FLT_X32 -> FLT_CON0; + FLT_X32 -> FLT_CON32; + FLT_CON0 -> FLT_32; + FLT_CON32 -> FLT_32; + FLT_32 -> FLT_BOT + + FLT_BOT -> BOT; + + FLT_CON0 -> FLT_CON32 -> FLT_CONPI -> FLT_CON64 [style=invis]; + + + // ---------------------- + // Pointers, just a boolean nil/not and a struct + PTR [shape=none, fillcolor=none, label=< + + + +
*ptr
struct
+ >]; + PTR0 [shape=none, fillcolor=none, label=< + + + +
*ptr?
struct
+ >]; + + TOP -> PTR; + PTR -> PTR0; + PTR0 -> BOT; + + // ---------------------- + // Structs + STRUCT_TOP [label=<⊤:struct>, fillcolor=lightgreen, shape=oval]; + STRUCT_BOT [label=<⊥:struct>, fillcolor=lightgreen, shape=oval]; + + STRUCT_S1 [shape=none, fillcolor=none, label=< + + +
fieldint/ptr
+ >]; + STRUCT_S2 [shape=none, fillcolor=none, label=< + + + +
fieldint/ptr
fieldint/ptr
+ >]; + STRUCT_SN [shape=none, fillcolor=none, label=< + + + + +
fieldint/ptr
... int/ptr
fieldint/ptr
+ >]; + + + TOP -> STRUCT_TOP; + STRUCT_TOP -> STRUCT_S1; + STRUCT_TOP -> STRUCT_S2; + STRUCT_TOP -> STRUCT_SN; + STRUCT_S1 -> STRUCT_BOT; + STRUCT_S2 -> STRUCT_BOT; + STRUCT_SN -> STRUCT_BOT; + STRUCT_BOT -> BOT; + + STRUCT_S1 -> STRUCT_S2 -> STRUCT_SN [style=invis]; + + // ---------------------- + // Memory + MEM_TOP [label=<⊤:mem>, fillcolor=blue, fontcolor=white, shape=oval]; + MEM_BOT [label=<⊥:mem>, fillcolor=blue, fontcolor=white, shape=oval]; + MEM_ALIAS1 [label="#1" , fillcolor=blue, fontcolor=white]; + MEM_ALIASDOTS [label="..." , fillcolor=blue, fontcolor=white, shape=oval]; + MEM_ALIASN [label="#n" , fillcolor=blue, fontcolor=white]; + + TOP -> MEM_TOP; + MEM_TOP -> MEM_ALIAS1; + MEM_TOP -> MEM_ALIASDOTS; + MEM_TOP -> MEM_ALIASN; + MEM_ALIAS1 -> MEM_BOT; + MEM_ALIASDOTS -> MEM_BOT; + MEM_ALIASN -> MEM_BOT; + MEM_BOT -> BOT; + + MEM_ALIAS1 -> MEM_ALIASDOTS -> MEM_ALIASN [style=invis]; + + // ---------------------- + // Tuples + + TUPLE_TOP [label=<⊤:tuple>, fillcolor=pink, shape=oval]; + TUPLE_BOT [label=<⊥:tuple>, fillcolor=pink, shape=oval]; + + TUPLE1 [shape=none, fillcolor=none, label=< + + + +
Tuple
type
+ >]; + TUPLE2 [shape=none, fillcolor=none, label=< + + + + +
Tuple
type
type
+ >]; + TUPLEN [shape=none, fillcolor=none, label=< + + + + + +
Tuple
type
...
type
+ >]; + + + TOP -> TUPLE_TOP; + TUPLE_TOP -> TUPLE1; + TUPLE_TOP -> TUPLE2; + TUPLE_TOP -> TUPLEN; + TUPLE1 -> TUPLE_BOT; + TUPLE2 -> TUPLE_BOT; + TUPLEN -> TUPLE_BOT; + TUPLE_BOT -> BOT; + + // ---------------------- + // Additional customization + {rank=same; TOP;} + {rank=same; INT_TOP; FLT_TOP; XCTRL; PTR; STRUCT_TOP; MEM_TOP; TUPLE_TOP} + {rank=same; INT_CON; INT_CON0; INT_CON1; + FLT_CON32; FLT_CON64; FLT_CON0; FLT_CONPI; + STRUCT_S1; STRUCT_S2; STRUCT_SN; + MEM_ALIAS1; MEM_ALIASDOTS; MEM_ALIASN; + TUPLE1; TUPLE2; TUPLEN; + } + {rank=same; INT_BOT; FLT_BOT; CTRL; PTR0; STRUCT_BOT; MEM_BOT; TUPLE_BOT} + {rank=same; BOT;} +} diff --git a/docs/chapter20/lattice.svg b/docs/chapter20/lattice.svg new file mode 100644 index 00000000..030f4826 --- /dev/null +++ b/docs/chapter20/lattice.svg @@ -0,0 +1,691 @@ + + + + + + +Lattice + + + +TOP + +⊤ + + + +XCTRL + +⊤ +:ctrl + + + +TOP->XCTRL + + + + + +INT_TOP + +⊤ +:int + + + +TOP->INT_TOP + + + + + +FLT_TOP + +⊤ +:flt + + + +TOP->FLT_TOP + + + + + +PTR + + + +*ptr + + +struct + + + +TOP->PTR + + + + + +STRUCT_TOP + +⊤ +:struct + + + +TOP->STRUCT_TOP + + + + + +MEM_TOP + +⊤ +:mem + + + +TOP->MEM_TOP + + + + + +TUPLE_TOP + +⊤ +:tuple + + + +TOP->TUPLE_TOP + + + + + +BOT + +⊥ + + + +CTRL + +⊥ +:ctrl + + + +XCTRL->CTRL + + + + + +CTRL->BOT + + + + + +INT_HI + +[min>max] + + + +INT_TOP->INT_HI + + + + + +INT_BOT + +⊥ +:int + + + +INT_BOT->BOT + + + + + +INT_XBOOL + +[1...0] + + + +INT_HI->INT_XBOOL + + + + + +INT_CON + +constants + + + +INT_HI->INT_CON + + + + + +INT_CON0 + +0 + + + +INT_XBOOL->INT_CON0 + + + + + +INT_CON1 + +1 + + + +INT_XBOOL->INT_CON1 + + + + + +INT_LO + +[min<max] + + + +INT_CON->INT_LO + + + + + + +INT_BOOL + +[0...1] + + + +INT_CON0->INT_BOOL + + + + + + +INT_CON1->INT_BOOL + + + + + +INT_BOOL->INT_LO + + + + + +INT_LO->INT_BOT + + + + + +FLT_CON64 + +64b consts +[excludes +32b consts] + + + +FLT_TOP->FLT_CON64 + + + + + +FLT_CONPI + +3.14159... + + + +FLT_TOP->FLT_CONPI + + + + + +FLT_X32 + +~f32 + + + +FLT_TOP->FLT_X32 + + + + + +FLT_BOT + +⊥ +:flt + + + +FLT_BOT->BOT + + + + + +FLT_CON64->FLT_BOT + + + + + +FLT_CON32 + +32b consts + + + + +FLT_32 + +f32 + + + +FLT_CON32->FLT_32 + + + + + +FLT_CONPI->FLT_BOT + + + + + + +FLT_X32->FLT_CON32 + + + + + +FLT_CON0 + +0. + + + +FLT_X32->FLT_CON0 + + + + + + +FLT_CON0->FLT_32 + + + + + +FLT_32->FLT_BOT + + + + + +PTR0 + + + +*ptr? + + +struct + + + +PTR->PTR0 + + + + + +PTR0->BOT + + + + + +STRUCT_S1 + + + +field + + + + + + + + +int/ptr + + + +STRUCT_TOP->STRUCT_S1 + + + + + +STRUCT_S2 + + + +field + + + + + + + + +int/ptr + + +field + + + + + + + + +int/ptr + + + +STRUCT_TOP->STRUCT_S2 + + + + + +STRUCT_SN + + + +field + + + + + + + + +int/ptr + + +...   + + + + + + + + +int/ptr + + +field + + + + + + + + +int/ptr + + + +STRUCT_TOP->STRUCT_SN + + + + + +STRUCT_BOT + +⊥ +:struct + + + +STRUCT_BOT->BOT + + + + + +STRUCT_S1->STRUCT_BOT + + + + + + +STRUCT_S2->STRUCT_BOT + + + + + + +STRUCT_SN->STRUCT_BOT + + + + + +MEM_ALIAS1 + +#1 + + + +MEM_TOP->MEM_ALIAS1 + + + + + +MEM_ALIASDOTS + +... + + + +MEM_TOP->MEM_ALIASDOTS + + + + + +MEM_ALIASN + +#n + + + +MEM_TOP->MEM_ALIASN + + + + + +MEM_BOT + +⊥ +:mem + + + +MEM_BOT->BOT + + + + + +MEM_ALIAS1->MEM_BOT + + + + + + +MEM_ALIASDOTS->MEM_BOT + + + + + + +MEM_ALIASN->MEM_BOT + + + + + +TUPLE1 + + + +Tuple + + +type + + + +TUPLE_TOP->TUPLE1 + + + + + +TUPLE2 + + + +Tuple + + +type + + +type + + + +TUPLE_TOP->TUPLE2 + + + + + +TUPLEN + + + +Tuple + + +type + + +... + + +type + + + +TUPLE_TOP->TUPLEN + + + + + +TUPLE_BOT + +⊥ +:tuple + + + +TUPLE_BOT->BOT + + + + + +TUPLE1->TUPLE_BOT + + + + + +TUPLE2->TUPLE_BOT + + + + + +TUPLEN->TUPLE_BOT + + + + + diff --git a/docs/chapter20/lattice2.gv b/docs/chapter20/lattice2.gv new file mode 100644 index 00000000..6aa37fa0 --- /dev/null +++ b/docs/chapter20/lattice2.gv @@ -0,0 +1,85 @@ +digraph Lattice { + compound = true; + // Define nodes + splines=false; + node [shape=oval, style=filled]; + nodesep=0.2; + ranksep=0.4; + + TOP [label="~scalar\n+int+tmp+0", fillcolor=black, fontcolor=white, group=center]; + + // ------------------------ + PTRHQ [label="~tmp\n+tmp+0", fillcolor=green, fontcolor=white, group=left]; + TOP -> PTRHQ + + VALH_ [label="~nscalar\n+int+tmp", fillcolor=white, group=center]; + TOP -> VALH_ + + INTHQ [label="~int\n+int+0", fillcolor=lightblue, group=right]; + TOP -> INTHQ + + LEFTHI [label="junk", style=invis, group=junkleft] + + + PTRHQ -> LEFTHI-> VALH_ -> INTHQ [style=invis]; + { rank=same; PTRHQ; LEFTHI; VALH_; INTHQ } + + // ------------------------ + PTRH_ [label="~ntmp\n+tmp", fillcolor=green, fontcolor=white, group=left]; + VALH_ -> PTRH_ + PTRHQ -> PTRH_ + + INTH_ [label="~nint\n+int", fillcolor=lightblue, group=right]; + VALH_ -> INTH_ + INTHQ -> INTH_ + { rank=same; PTRH_; INTH_ } + + // ------------------------ + NILX_ [label="~nil\n[int,tmp]+0", fillcolor=aqua, group=junkleft]; + INTHQ -> NILX_ + PTRHQ -> NILX_ + + NIL__ [label="nil\n[+int+tmp],0", fillcolor=pink]; + VALH_ -> NIL__; + + NILX_ -> NIL__ [style=invis]; + { rank=same; NIL__; NILX_ } + + // ------------------------ + PTRL_ [label="ntmp\ntmp", fillcolor=green, fontcolor=white, group=left]; + PTRH_ -> PTRL_ + + INTL_ [label="nint\nint", fillcolor=lightblue, group=right]; + INTH_ -> INTL_ + { rank=same; PTRL_; INTL_ } + + // ------------------------ + PTRL0 [label="tmp\ntmp,0", fillcolor=green, fontcolor=white, group=left]; + PTRL_ -> PTRL0 + NIL__ -> PTRL0 + + VALL_ [label="nscalar\nint,tmp", fillcolor=white, group=center]; + PTRL_ -> VALL_ + INTL_ -> VALL_ + NILX_ -> VALL_ + + INTL0 [label="int\nint,0", fillcolor=lightblue, group=right]; + INTL_ -> INTL0 + NIL__ -> INTL0 + + PTRL0 -> VALL_ -> INTL0 [style=invis]; + { rank=same; PTRL0; VALL_; INTL0 } + + // ------------------------ + BOT [label="scalar\nint,tmp,0", fillcolor=black, fontcolor=white, group=center]; + PTRL0 -> BOT + INTL0 -> BOT + VALL_ -> BOT + + // ------------------------ + // Verticle alignment + TOP -> VALH_ -> VALL_ -> BOT [style=invis]; + PTRHQ -> PTRH_ -> PTRL_ -> PTRL0 [style=invis]; + INTHQ -> INTH_ -> INTL_ -> INTL0 [style=invis]; + LEFTHI -> NILX_ [style=invis] +} diff --git a/docs/chapter20/lattice2.svg b/docs/chapter20/lattice2.svg new file mode 100644 index 00000000..857bca2d --- /dev/null +++ b/docs/chapter20/lattice2.svg @@ -0,0 +1,260 @@ + + + + + + +Lattice + + + +TOP + +~scalar ++int+tmp+0 + + + +PTRHQ + +~tmp ++tmp+0 + + + +TOP->PTRHQ + + + + + +VALH_ + +~nscalar ++int+tmp + + + +TOP->VALH_ + + + + + + +INTHQ + +~int ++int+0 + + + +TOP->INTHQ + + + + + + + +PTRH_ + +~ntmp ++tmp + + + +PTRHQ->PTRH_ + + + + + + +NILX_ + +~nil +[int,tmp]+0 + + + +PTRHQ->NILX_ + + + + + + +VALH_->PTRH_ + + + + + +INTH_ + +~nint ++int + + + +VALH_->INTH_ + + + + + +NIL__ + +nil +[+int+tmp],0 + + + +VALH_->NIL__ + + + + + +VALL_ + +nscalar +int,tmp + + + + +INTHQ->INTH_ + + + + + + +INTHQ->NILX_ + + + + + + + +PTRL_ + +ntmp +tmp + + + +PTRH_->PTRL_ + + + + + + +INTL_ + +nint +int + + + +INTH_->INTL_ + + + + + + + +NILX_->VALL_ + + + + + +PTRL0 + +tmp +tmp,0 + + + +NIL__->PTRL0 + + + + + +INTL0 + +int +int,0 + + + +NIL__->INTL0 + + + + + +PTRL_->PTRL0 + + + + + + +PTRL_->VALL_ + + + + + +INTL_->VALL_ + + + + + +INTL_->INTL0 + + + + + + + +BOT + +scalar +int,tmp,0 + + + +PTRL0->BOT + + + + + + +VALL_->BOT + + + + + + +INTL0->BOT + + + + + diff --git a/docs/chapter20/pardoe.docx b/docs/chapter20/pardoe.docx new file mode 100644 index 00000000..24fde921 Binary files /dev/null and b/docs/chapter20/pardoe.docx differ diff --git a/docs/chapter20/webtcp.min.js b/docs/chapter20/webtcp.min.js new file mode 100644 index 00000000..bf23ee5a --- /dev/null +++ b/docs/chapter20/webtcp.min.js @@ -0,0 +1,65 @@ +function Http(j){this.cID=Math.random().toString(36).substr(2);this.webtcp=j;this.webtcp.httpClients[this.cID]=this;this.createPacket=function(){return{cID:this.cID,type:"http",host:this.remoteAddress,port:this.remotePort,method:null,data:null}};this.ondata=function(){};this.get=function(g,c){this.on("data",c);this.rpc("httpGet",[g])};this.post=function(g,c,j){this.on("data",j);this.rpc("httpPost",[g,c])}}Http.prototype=new WebTCPIO;function Socket(j,g,c,p){this.sID=Math.random().toString(36).substr(2);this.webtcp=j;this.closed=this.ready=!1;this.options=p;this.sockOpts={_updating:!1};this.remoteAddress=g;this.remotePort=c;this.webtcp.sockets[this.sID]=this;this.createPacket=function(){return{id:Math.random().toString(36).substr(2),sID:this.sID,type:"tcp",options:this.options,host:this.remoteAddress,port:this.remotePort,data:null}};this.onconnect=function(){this.webtcp.sockets[sID].ready=!0;this.webtcp.sockets[sID].closed=!1}; +this.ondata=function(){};this.onend=function(){};this.ontimeout=function(){};this.ondrain=function(){};this.onclose=function(){this.webtcp.sockets[sID].ready=!1;this.webtcp.sockets[sID].closed=!0};this.getSockOpts=function(){this.sockOpts._updating=!0;this.rpc("getSockOpts")};this.onSockOptsRcv=function(c){this.sockOpts=c;this.sockOpts._updating=!1}}Socket.prototype=new WebTCPIO;var JSON;JSON||(JSON={}); +(function(){function j(c,p){var q,t,n,x,i=k,e,l=p[c];l&&"object"==typeof l&&"function"==typeof l.toJSON&&(l=l.toJSON(c));"function"==typeof r&&(l=r.call(p,c,l));switch(typeof l){case "string":return g(l);case "number":return isFinite(l)?String(l):"null";case "boolean":case "null":return String(l);case "object":if(!l)return"null";k+=v;e=[];if("[object Array]"===Object.prototype.toString.apply(l)){x=l.length;for(q=0;qc?"0"+c:c}"use strict";"function"!=typeof Date.prototype.toJSON&&(Date.prototype.toJSON=function(){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+c(this.getUTCMonth()+1)+"-"+c(this.getUTCDate())+"T"+c(this.getUTCHours())+":"+c(this.getUTCMinutes())+":"+c(this.getUTCSeconds())+"Z":null},String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(){return this.valueOf()});var p=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, +n=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,k,v,D={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},r;"function"!=typeof JSON.stringify&&(JSON.stringify=function(c,g,p){var n;v=k="";if("number"==typeof p)for(n=0;n=a};c.countRTO=function(a){var b;return 100b;b++)d.push(String.fromCharCode(b));B=(r.lastIndex=0,d.join("").replace(r,function(a){return c[a]="\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4),""}),r.lastIndex=0,c)}a=a.replace(r,function(a){return B[a]})}return a};var q="websocket xdr-streaming xhr-streaming iframe-eventsource iframe-htmlfile xdr-polling xhr-polling iframe-xhr-polling jsonp-polling".split(" "); +c.probeProtocols=function(){for(var a={},b=0;b