diff options
251 files changed, 8268 insertions, 1422 deletions
@@ -1,5 +1,4 @@ SHELL := /bin/bash -# Shared packaging target; included after chapter source variables below. .DELETE_ON_ERROR: @@ -56,7 +55,7 @@ endif default: $(default_targets) # Compile just the out-of-date files -$(main_classes): build/classes/main/%class: $(SRC)/%java $(graph_javas) $(print_javas) +$(main_classes): $(CLZDIR)/main/%class: $(SRC)/%java $(graph_javas) $(print_javas) @echo "compiling " $@ " because " $? @[ -d $(CLZDIR)/main ] || mkdir -p $(CLZDIR)/main @javac $(JAVAC_ARGS) -cp "$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(SRC) -d $(CLZDIR)/main $(main_javas) @@ -67,20 +66,32 @@ $(test_classes): $(CLZDIR)/test/%class: $(TST)/%java $(main_classes) @javac $(JAVAC_ARGS) -cp "$(CLZDIR)/test$(SEP)$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(TST) -d $(CLZDIR)/test $(test_javas) # Base launch line for JVM tests -JVM=nice java -ea -cp "build/classes/main${SEP}${jars}${SEP}$(CLZDIR)/test" +JVM=nice java -ea -cp "$(CLZDIR)/main${SEP}${jars}${SEP}$(CLZDIR)/test" tests: $(default_targets) @echo "testing " $(test_cp) @$(JVM) org.junit.runner.JUnitCore $(test_cp) @$(JVM) org.junit.runner.JUnitCore com.seaofnodes.simple.FuzzerWrap +# Report measured spill totals while retaining the tests' assertions. +spill-stats: $(default_targets) + @$(JVM) com.seaofnodes.simple.SpillStats + fuzzer: $(default_targets) @echo "fuzzing " $(test_cp) @$(JVM) org.junit.runner.JUnitCore com.seaofnodes.simple.FuzzerWrap +# Build a Simple jar +release: build/release/simple.jar + +# Build a Simple jar +build/release/simple.jar: $(main_classes) $(test_classes) + @echo "jarring " $@ " because " $? + @[ -d $(dir $@) ] || mkdir -p $(dir $@) + @jar cf build/release/simple.jar -C $(CLZDIR)/main . -C $(CLZDIR)/test . -C $(SRC)/$(SIMPLE) . -C $(TST)/$(SIMPLE) . -.PHONY: clean +.PHONY: clean spill-stats clean: rm -rf build rm -f TAGS @@ -103,7 +114,5 @@ tags: $(main_javas) $(test_javas) @rm -f TAGS @$(CTAGS) -e --recurse=yes --extra=+q --fields=+fksaiS $(SRC) $(TST) -include $(firstword $(wildcard ../build-support/chapter-release.mk build-support/chapter-release.mk)) - .PHONY: build build: $(main_classes) diff --git a/README.ja.md b/README.ja.md index c2904f40..c0345907 100644 --- a/README.ja.md +++ b/README.ja.md @@ -57,7 +57,7 @@ Simple 言語のスタイルは、C または Java のサブセットに倣っã� * [第17b章](docs/chapter17b/README.ja.md):糖衣構文。`var`、`val`、`x+=y`、`for(init; test; next) body`。 * [第18章](docs/chapter18/README.ja.md):関数と呼び出し。 * [第19章](docs/chapter19/README.ja.md):命令選択と移植可能なコンパイル。 -* 第20章:グラフ彩色によるレジスタ割り当て。 +* [第20章](docs/chapter20/README.ja.md):グラフ彩色によるレジスタ割り当て。 * 第21章:命令エンコーディングと ELF。 * 第22章:Simple の Hello, World! * 第23章:メソッドと型の再検討。 @@ -66,7 +66,7 @@ The Simple language is styled after a subset of C or Java. * [Chapter 17b](docs/chapter17b/README.md): Syntax sugar: `var`, `val`, `x+=y`, `for(init; test; next) body` * [Chapter 18](docs/chapter18/README.md): Functions and calls. * [Chapter 19](docs/chapter19/README.md): Instruction selection and portable compilation -* Chapter 20: Graph Coloring Register Allocation +* [Chapter 20](docs/chapter20/README.md): Graph Coloring Register Allocation * Chapter 21: Instruction Encodings & ELF * Chapter 22: A Simple Hello, World! * Chapter 23: Methods and Types Revisited 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. + +<!-- spill-matrix:start --> +| Test cohort | Cases D / X | Ch 20: Ops / RA / X | +|---|---:|---:| +| Ch 20 | 22 / 13 | — / — / 161 | +<!-- spill-matrix:end --> + +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 Binary files differnew file mode 100644 index 00000000..b6bdb4bd --- /dev/null +++ b/docs/chapter20/font-awesome-4.7.0.zip diff --git a/docs/chapter20/ifg_trim.pdf b/docs/chapter20/ifg_trim.pdf Binary files differnew file mode 100644 index 00000000..d952fd4f --- /dev/null +++ b/docs/chapter20/ifg_trim.pdf 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=<<B>⊤</B >>, fillcolor=black, fontcolor=white]; + BOT [label=<<B>⊥</B>>, fillcolor=black, fontcolor=white]; + + // ---------------------- + // Control + XCTRL [label=<<B>⊤</B >:ctrl>, fillcolor=yellow, shape=oval]; + CTRL [label=<<B>⊥</B>:ctrl>, fillcolor=yellow, shape=oval]; + + TOP -> XCTRL; + XCTRL -> CTRL; + CTRL -> BOT; + + // ---------------------- + // Integers + INT_TOP [label=<<B>⊤</B >:int>, fillcolor=lightblue, shape=oval]; + INT_BOT [label=<<B>⊥</B>: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<max]" , fillcolor=lightblue, shape=oval]; + + TOP -> 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=<<B>⊤</B >:flt>, fillcolor=aqua, shape=oval]; + FLT_BOT [label=<<B>⊥</B>: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=< + <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> + <TR><TD BGCOLOR="green"><FONT color="white">*ptr</FONT></TD></TR> + <TR><TD BGCOLOR="lightgreen">struct</TD></TR> + </TABLE> + >]; + PTR0 [shape=none, fillcolor=none, label=< + <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> + <TR><TD BGCOLOR="green"><FONT color="white">*ptr?</FONT></TD></TR> + <TR><TD BGCOLOR="lightgreen">struct</TD></TR> + </TABLE> + >]; + + TOP -> PTR; + PTR -> PTR0; + PTR0 -> BOT; + + // ---------------------- + // Structs + STRUCT_TOP [label=<<B>⊤</B >:struct>, fillcolor=lightgreen, shape=oval]; + STRUCT_BOT [label=<<B>⊥</B>:struct>, fillcolor=lightgreen, shape=oval]; + + STRUCT_S1 [shape=none, fillcolor=none, label=< + <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> + <TR><TD BGCOLOR="lightgreen">field</TD><TD BGCOLOR="lightblue:green" gradientangle="315">int/ptr</TD></TR> + </TABLE> + >]; + STRUCT_S2 [shape=none, fillcolor=none, label=< + <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> + <TR><TD BGCOLOR="lightgreen">field</TD><TD BGCOLOR="lightblue:green" gradientangle="315">int/ptr</TD></TR> + <TR><TD BGCOLOR="lightgreen">field</TD><TD BGCOLOR="lightblue:green" gradientangle="315">int/ptr</TD></TR> + </TABLE> + >]; + STRUCT_SN [shape=none, fillcolor=none, label=< + <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> + <TR><TD BGCOLOR="lightgreen">field</TD><TD BGCOLOR="lightblue:green" gradientangle="315">int/ptr</TD></TR> + <TR><TD BGCOLOR="lightgreen">... </TD><TD BGCOLOR="lightblue:green" gradientangle="315">int/ptr</TD></TR> + <TR><TD BGCOLOR="lightgreen">field</TD><TD BGCOLOR="lightblue:green" gradientangle="315">int/ptr</TD></TR> + </TABLE> + >]; + + + 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=<<B>⊤</B >:mem>, fillcolor=blue, fontcolor=white, shape=oval]; + MEM_BOT [label=<<B>⊥</B>: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=<<B>⊤</B >:tuple>, fillcolor=pink, shape=oval]; + TUPLE_BOT [label=<<B>⊥</B>:tuple>, fillcolor=pink, shape=oval]; + + TUPLE1 [shape=none, fillcolor=none, label=< + <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> + <TH><TD BGCOLOR="pink">Tuple</TD></TH> + <TR><TD BGCOLOR="black" ><FONT color="white">type</FONT></TD></TR> + </TABLE> + >]; + TUPLE2 [shape=none, fillcolor=none, label=< + <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> + <TH><TD BGCOLOR="pink">Tuple</TD></TH> + <TR><TD BGCOLOR="black" ><FONT color="white">type</FONT></TD></TR> + <TR><TD BGCOLOR="black" ><FONT color="white">type</FONT></TD></TR> + </TABLE> + >]; + TUPLEN [shape=none, fillcolor=none, label=< + <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> + <TH><TD BGCOLOR="pink">Tuple</TD></TH> + <TR><TD BGCOLOR="black" ><FONT color="white">type</FONT></TD></TR> + <TR><TD BGCOLOR="black" ><FONT color="white">...</FONT></TD></TR> + <TR><TD BGCOLOR="black" ><FONT color="white">type</FONT></TD></TR> + </TABLE> + >]; + + + 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 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 12.2.0 (20241103.1931)
+ -->
+<!-- Title: Lattice Pages: 1 -->
+<svg width="1330pt" height="584pt"
+ viewBox="0.00 0.00 1329.51 583.54" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 579.54)">
+<title>Lattice</title>
+<polygon fill="white" stroke="none" points="-4,4 -4,-579.54 1325.51,-579.54 1325.51,4 -4,4"/>
+<!-- TOP -->
+<g id="node1" class="node">
+<title>TOP</title>
+<ellipse fill="black" stroke="black" cx="641.51" cy="-554.42" rx="21.12" ry="21.12"/>
+<text text-anchor="start" x="633.63" y="-549.99" font-family="Times New Roman,serif" font-weight="bold" font-size="14.00" fill="white">⊤</text>
+</g>
+<!-- XCTRL -->
+<g id="node3" class="node">
+<title>XCTRL</title>
+<ellipse fill="yellow" stroke="black" cx="36.51" cy="-492.79" rx="36.51" ry="18"/>
+<text text-anchor="start" x="17.76" y="-488.74" font-family="Times New Roman,serif" font-weight="bold" font-size="14.00">⊤</text>
+<text text-anchor="start" x="33.51" y="-488.74" font-family="Times New Roman,serif" font-size="14.00">:ctrl</text>
+</g>
+<!-- TOP->XCTRL -->
+<g id="edge1" class="edge">
+<title>TOP->XCTRL</title>
+<path fill="none" stroke="black" d="M620.33,-551.33C533.82,-542.8 206.01,-510.5 83.28,-498.4"/>
+<polygon fill="black" stroke="black" points="83.86,-494.94 73.56,-497.44 83.17,-501.91 83.86,-494.94"/>
+</g>
+<!-- INT_TOP -->
+<g id="node5" class="node">
+<title>INT_TOP</title>
+<ellipse fill="lightblue" stroke="black" cx="316.51" cy="-492.79" rx="33.95" ry="18"/>
+<text text-anchor="start" x="299.63" y="-488.74" font-family="Times New Roman,serif" font-weight="bold" font-size="14.00">⊤</text>
+<text text-anchor="start" x="315.38" y="-488.74" font-family="Times New Roman,serif" font-size="14.00">:int</text>
+</g>
+<!-- TOP->INT_TOP -->
+<g id="edge4" class="edge">
+<title>TOP->INT_TOP</title>
+<path fill="none" stroke="black" d="M620.68,-549.6C568.33,-539.99 430.36,-514.68 359.64,-501.71"/>
+<polygon fill="black" stroke="black" points="360.44,-498.29 349.97,-499.93 359.18,-505.18 360.44,-498.29"/>
+</g>
+<!-- FLT_TOP -->
+<g id="node14" class="node">
+<title>FLT_TOP</title>
+<ellipse fill="aqua" stroke="black" cx="494.51" cy="-492.79" rx="32.41" ry="18"/>
+<text text-anchor="start" x="478.76" y="-488.74" font-family="Times New Roman,serif" font-weight="bold" font-size="14.00">⊤</text>
+<text text-anchor="start" x="494.51" y="-488.74" font-family="Times New Roman,serif" font-size="14.00">:flt</text>
+</g>
+<!-- TOP->FLT_TOP -->
+<g id="edge18" class="edge">
+<title>TOP->FLT_TOP</title>
+<path fill="none" stroke="black" d="M621.99,-545.5C598.79,-536.09 559.6,-520.19 530.78,-508.5"/>
+<polygon fill="black" stroke="black" points="532.29,-505.34 521.7,-504.82 529.65,-511.82 532.29,-505.34"/>
+</g>
+<!-- PTR -->
+<g id="node22" class="node">
+<title>PTR</title>
+<polygon fill="none" stroke="none" points="668.51,-519.29 614.51,-519.29 614.51,-466.29 668.51,-466.29 668.51,-519.29"/>
+<polygon fill="green" stroke="none" points="623.51,-492.79 623.51,-515.29 659.51,-515.29 659.51,-492.79 623.51,-492.79"/>
+<polygon fill="none" stroke="black" points="623.51,-492.79 623.51,-515.29 659.51,-515.29 659.51,-492.79 623.51,-492.79"/>
+<text text-anchor="start" x="630.63" y="-498.99" font-family="Times New Roman,serif" font-size="14.00" fill="white">*ptr</text>
+<polygon fill="lightgreen" stroke="none" points="623.51,-470.29 623.51,-492.79 659.51,-492.79 659.51,-470.29 623.51,-470.29"/>
+<polygon fill="none" stroke="black" points="623.51,-470.29 623.51,-492.79 659.51,-492.79 659.51,-470.29 623.51,-470.29"/>
+<text text-anchor="start" x="626.51" y="-476.49" font-family="Times New Roman,serif" font-size="14.00">struct</text>
+</g>
+<!-- TOP->PTR -->
+<g id="edge33" class="edge">
+<title>TOP->PTR</title>
+<path fill="none" stroke="black" d="M641.51,-532.9C641.51,-532.06 641.51,-531.22 641.51,-530.37"/>
+<polygon fill="black" stroke="black" points="645.01,-530.61 641.51,-520.61 638.01,-530.61 645.01,-530.61"/>
+</g>
+<!-- STRUCT_TOP -->
+<g id="node24" class="node">
+<title>STRUCT_TOP</title>
+<ellipse fill="lightgreen" stroke="black" cx="746.51" cy="-492.79" rx="44.7" ry="18"/>
+<text text-anchor="start" x="721.76" y="-488.74" font-family="Times New Roman,serif" font-weight="bold" font-size="14.00">⊤</text>
+<text text-anchor="start" x="737.51" y="-488.74" font-family="Times New Roman,serif" font-size="14.00">:struct</text>
+</g>
+<!-- TOP->STRUCT_TOP -->
+<g id="edge36" class="edge">
+<title>TOP->STRUCT_TOP</title>
+<path fill="none" stroke="black" d="M659.8,-543.03C674.04,-534.94 694.24,-523.47 711.55,-513.64"/>
+<polygon fill="black" stroke="black" points="713.07,-516.81 720.03,-508.83 709.61,-510.72 713.07,-516.81"/>
+</g>
+<!-- MEM_TOP -->
+<g id="node29" class="node">
+<title>MEM_TOP</title>
+<ellipse fill="blue" stroke="black" cx="998.51" cy="-492.79" rx="43.67" ry="18"/>
+<text text-anchor="start" x="974.51" y="-488.74" font-family="Times New Roman,serif" font-weight="bold" font-size="14.00" fill="white">⊤</text>
+<text text-anchor="start" x="990.26" y="-488.74" font-family="Times New Roman,serif" font-size="14.00" fill="white">:mem</text>
+</g>
+<!-- TOP->MEM_TOP -->
+<g id="edge46" class="edge">
+<title>TOP->MEM_TOP</title>
+<path fill="none" stroke="black" d="M662.51,-549.91C717.64,-540.7 867.72,-515.64 947.33,-502.34"/>
+<polygon fill="black" stroke="black" points="947.77,-505.82 957.06,-500.72 946.62,-498.91 947.77,-505.82"/>
+</g>
+<!-- TUPLE_TOP -->
+<g id="node34" class="node">
+<title>TUPLE_TOP</title>
+<ellipse fill="pink" stroke="black" cx="1182.51" cy="-492.79" rx="42.65" ry="18"/>
+<text text-anchor="start" x="1159.26" y="-488.74" font-family="Times New Roman,serif" font-weight="bold" font-size="14.00">⊤</text>
+<text text-anchor="start" x="1175.01" y="-488.74" font-family="Times New Roman,serif" font-size="14.00">:tuple</text>
+</g>
+<!-- TOP->TUPLE_TOP -->
+<g id="edge56" class="edge">
+<title>TOP->TUPLE_TOP</title>
+<path fill="none" stroke="black" d="M662.72,-551.08C740.91,-542.46 1014.51,-512.31 1130.21,-499.56"/>
+<polygon fill="black" stroke="black" points="1130.5,-503.05 1140.05,-498.47 1129.73,-496.09 1130.5,-503.05"/>
+</g>
+<!-- BOT -->
+<g id="node2" class="node">
+<title>BOT</title>
+<ellipse fill="black" stroke="black" cx="641.51" cy="-21.12" rx="21.12" ry="21.12"/>
+<text text-anchor="start" x="633.63" y="-16.7" font-family="Times New Roman,serif" font-weight="bold" font-size="14.00" fill="white">⊥</text>
+</g>
+<!-- CTRL -->
+<g id="node4" class="node">
+<title>CTRL</title>
+<ellipse fill="yellow" stroke="black" cx="49.51" cy="-82.75" rx="36.51" ry="18"/>
+<text text-anchor="start" x="30.76" y="-78.7" font-family="Times New Roman,serif" font-weight="bold" font-size="14.00">⊥</text>
+<text text-anchor="start" x="46.51" y="-78.7" font-family="Times New Roman,serif" font-size="14.00">:ctrl</text>
+</g>
+<!-- XCTRL->CTRL -->
+<g id="edge2" class="edge">
+<title>XCTRL->CTRL</title>
+<path fill="none" stroke="black" d="M37.09,-474.38C39.15,-409.6 46.05,-191.88 48.57,-112.25"/>
+<polygon fill="black" stroke="black" points="52.06,-112.73 48.88,-102.63 45.06,-112.51 52.06,-112.73"/>
+</g>
+<!-- CTRL->BOT -->
+<g id="edge3" class="edge">
+<title>CTRL->BOT</title>
+<path fill="none" stroke="black" d="M85.21,-78.15C191.74,-67.42 506.36,-35.74 608.84,-25.41"/>
+<polygon fill="black" stroke="black" points="609.15,-28.9 618.75,-24.42 608.45,-21.94 609.15,-28.9"/>
+</g>
+<!-- INT_HI -->
+<g id="node7" class="node">
+<title>INT_HI</title>
+<ellipse fill="lightblue" stroke="black" cx="174.51" cy="-434.29" rx="53.91" ry="18"/>
+<text text-anchor="middle" x="174.51" y="-429.24" font-family="Times New Roman,serif" font-size="14.00">[min>max]</text>
+</g>
+<!-- INT_TOP->INT_HI -->
+<g id="edge5" class="edge">
+<title>INT_TOP->INT_HI</title>
+<path fill="none" stroke="black" d="M289.8,-481.17C269.89,-473.25 242.26,-462.25 219.03,-453.01"/>
+<polygon fill="black" stroke="black" points="220.37,-449.77 209.78,-449.33 217.78,-456.28 220.37,-449.77"/>
+</g>
+<!-- INT_BOT -->
+<g id="node6" class="node">
+<title>INT_BOT</title>
+<ellipse fill="lightblue" stroke="black" cx="329.51" cy="-82.75" rx="33.95" ry="18"/>
+<text text-anchor="start" x="312.63" y="-78.7" font-family="Times New Roman,serif" font-weight="bold" font-size="14.00">⊥</text>
+<text text-anchor="start" x="328.38" y="-78.7" font-family="Times New Roman,serif" font-size="14.00">:int</text>
+</g>
+<!-- INT_BOT->BOT -->
+<g id="edge15" class="edge">
+<title>INT_BOT->BOT</title>
+<path fill="none" stroke="black" d="M361.14,-75.7C420.84,-64.3 549.47,-39.71 609.47,-28.25"/>
+<polygon fill="black" stroke="black" points="609.86,-31.73 619.03,-26.42 608.55,-24.86 609.86,-31.73"/>
+</g>
+<!-- INT_XBOOL -->
+<g id="node8" class="node">
+<title>INT_XBOOL</title>
+<ellipse fill="lightblue" stroke="black" cx="97.51" cy="-373.97" rx="33.95" ry="18"/>
+<text text-anchor="middle" x="97.51" y="-368.92" font-family="Times New Roman,serif" font-size="14.00">[1...0]</text>
+</g>
+<!-- INT_HI->INT_XBOOL -->
+<g id="edge6" class="edge">
+<title>INT_HI->INT_XBOOL</title>
+<path fill="none" stroke="black" d="M153.47,-417.36C144.79,-410.78 134.6,-403.06 125.31,-396.03"/>
+<polygon fill="black" stroke="black" points="127.71,-393.46 117.63,-390.21 123.48,-399.04 127.71,-393.46"/>
+</g>
+<!-- INT_CON -->
+<g id="node9" class="node">
+<title>INT_CON</title>
+<ellipse fill="lightblue" stroke="black" cx="174.51" cy="-282.65" rx="45.21" ry="18"/>
+<text text-anchor="middle" x="174.51" y="-277.6" font-family="Times New Roman,serif" font-size="14.00">constants</text>
+</g>
+<!-- INT_HI->INT_CON -->
+<g id="edge9" class="edge">
+<title>INT_HI->INT_CON</title>
+<path fill="none" stroke="black" d="M174.51,-416C174.51,-390.27 174.51,-343.1 174.51,-312.31"/>
+<polygon fill="black" stroke="black" points="178.01,-312.52 174.51,-302.52 171.01,-312.52 178.01,-312.52"/>
+</g>
+<!-- INT_CON0 -->
+<g id="node10" class="node">
+<title>INT_CON0</title>
+<ellipse fill="lightblue" stroke="black" cx="47.51" cy="-282.65" rx="18" ry="18"/>
+<text text-anchor="middle" x="47.51" y="-277.6" font-family="Times New Roman,serif" font-size="14.00">0</text>
+</g>
+<!-- INT_XBOOL->INT_CON0 -->
+<g id="edge7" class="edge">
+<title>INT_XBOOL->INT_CON0</title>
+<path fill="none" stroke="black" d="M88.34,-356.59C80.82,-343.17 70.07,-323.96 61.49,-308.62"/>
+<polygon fill="black" stroke="black" points="64.63,-307.08 56.69,-300.06 58.53,-310.5 64.63,-307.08"/>
+</g>
+<!-- INT_CON1 -->
+<g id="node11" class="node">
+<title>INT_CON1</title>
+<ellipse fill="lightblue" stroke="black" cx="97.51" cy="-282.65" rx="18" ry="18"/>
+<text text-anchor="middle" x="97.51" y="-277.6" font-family="Times New Roman,serif" font-size="14.00">1</text>
+</g>
+<!-- INT_XBOOL->INT_CON1 -->
+<g id="edge8" class="edge">
+<title>INT_XBOOL->INT_CON1</title>
+<path fill="none" stroke="black" d="M97.51,-355.75C97.51,-343.47 97.51,-326.68 97.51,-312.39"/>
+<polygon fill="black" stroke="black" points="101.01,-312.64 97.51,-302.64 94.01,-312.64 101.01,-312.64"/>
+</g>
+<!-- INT_LO -->
+<g id="node13" class="node">
+<title>INT_LO</title>
+<ellipse fill="lightblue" stroke="black" cx="174.51" cy="-141.25" rx="53.91" ry="18"/>
+<text text-anchor="middle" x="174.51" y="-136.2" font-family="Times New Roman,serif" font-size="14.00">[min<max]</text>
+</g>
+<!-- INT_CON->INT_LO -->
+<g id="edge13" class="edge">
+<title>INT_CON->INT_LO</title>
+<path fill="none" stroke="black" d="M174.51,-264.17C174.51,-240.45 174.51,-199.04 174.51,-170.9"/>
+<polygon fill="black" stroke="black" points="178.01,-171.25 174.51,-161.25 171.01,-171.25 178.01,-171.25"/>
+</g>
+<!-- INT_CON0->INT_CON1 -->
+<!-- INT_BOOL -->
+<g id="node12" class="node">
+<title>INT_BOOL</title>
+<ellipse fill="lightblue" stroke="black" cx="97.51" cy="-196.45" rx="33.95" ry="18"/>
+<text text-anchor="middle" x="97.51" y="-191.4" font-family="Times New Roman,serif" font-size="14.00">[0...1]</text>
+</g>
+<!-- INT_CON0->INT_BOOL -->
+<g id="edge10" class="edge">
+<title>INT_CON0->INT_BOOL</title>
+<path fill="none" stroke="black" d="M56.45,-266.6C63.5,-254.72 73.51,-237.87 81.88,-223.77"/>
+<polygon fill="black" stroke="black" points="84.75,-225.79 86.85,-215.41 78.73,-222.22 84.75,-225.79"/>
+</g>
+<!-- INT_CON1->INT_CON -->
+<!-- INT_CON1->INT_BOOL -->
+<g id="edge11" class="edge">
+<title>INT_CON1->INT_BOOL</title>
+<path fill="none" stroke="black" d="M97.51,-264.21C97.51,-253.25 97.51,-238.87 97.51,-226.25"/>
+<polygon fill="black" stroke="black" points="101.01,-226.39 97.51,-216.39 94.01,-226.39 101.01,-226.39"/>
+</g>
+<!-- INT_BOOL->INT_LO -->
+<g id="edge12" class="edge">
+<title>INT_BOOL->INT_LO</title>
+<path fill="none" stroke="black" d="M117.34,-181.75C124.98,-176.47 133.91,-170.3 142.42,-164.42"/>
+<polygon fill="black" stroke="black" points="144.27,-167.4 150.5,-158.83 140.29,-161.64 144.27,-167.4"/>
+</g>
+<!-- INT_LO->INT_BOT -->
+<g id="edge14" class="edge">
+<title>INT_LO->INT_BOT</title>
+<path fill="none" stroke="black" d="M210.07,-127.29C234.52,-118.37 266.93,-106.56 291.82,-97.49"/>
+<polygon fill="black" stroke="black" points="292.71,-100.89 300.91,-94.17 290.31,-94.31 292.71,-100.89"/>
+</g>
+<!-- FLT_CON64 -->
+<g id="node16" class="node">
+<title>FLT_CON64</title>
+<ellipse fill="aqua" stroke="black" cx="564.51" cy="-282.65" rx="55.33" ry="40.66"/>
+<text text-anchor="middle" x="564.51" y="-294.1" font-family="Times New Roman,serif" font-size="14.00">64b consts</text>
+<text text-anchor="middle" x="564.51" y="-277.6" font-family="Times New Roman,serif" font-size="14.00">[excludes</text>
+<text text-anchor="middle" x="564.51" y="-261.1" font-family="Times New Roman,serif" font-size="14.00">32b consts]</text>
+</g>
+<!-- FLT_TOP->FLT_CON64 -->
+<g id="edge19" class="edge">
+<title>FLT_TOP->FLT_CON64</title>
+<path fill="none" stroke="black" d="M500.48,-474.88C510.8,-443.88 532.35,-379.2 547.59,-333.44"/>
+<polygon fill="black" stroke="black" points="550.9,-334.57 550.74,-323.98 544.26,-332.36 550.9,-334.57"/>
+</g>
+<!-- FLT_CONPI -->
+<g id="node18" class="node">
+<title>FLT_CONPI</title>
+<ellipse fill="aqua" stroke="black" cx="446.51" cy="-282.65" rx="48.79" ry="18"/>
+<text text-anchor="middle" x="446.51" y="-277.6" font-family="Times New Roman,serif" font-size="14.00">3.14159...</text>
+</g>
+<!-- FLT_TOP->FLT_CONPI -->
+<g id="edge20" class="edge">
+<title>FLT_TOP->FLT_CONPI</title>
+<path fill="none" stroke="black" d="M490.33,-474.51C482.02,-438.11 463.32,-356.27 453.25,-312.16"/>
+<polygon fill="black" stroke="black" points="456.69,-311.49 451.05,-302.52 449.86,-313.04 456.69,-311.49"/>
+</g>
+<!-- FLT_X32 -->
+<g id="node19" class="node">
+<title>FLT_X32</title>
+<ellipse fill="aqua" stroke="black" cx="333.51" cy="-373.97" rx="28.32" ry="28.32"/>
+<text text-anchor="middle" x="333.51" y="-368.92" font-family="Times New Roman,serif" font-size="14.00">~f32</text>
+</g>
+<!-- FLT_TOP->FLT_X32 -->
+<g id="edge23" class="edge">
+<title>FLT_TOP->FLT_X32</title>
+<path fill="none" stroke="black" d="M474.77,-478.22C447.56,-458.14 398.22,-421.73 365.65,-397.69"/>
+<polygon fill="black" stroke="black" points="367.88,-394.99 357.75,-391.86 363.72,-400.62 367.88,-394.99"/>
+</g>
+<!-- FLT_BOT -->
+<g id="node15" class="node">
+<title>FLT_BOT</title>
+<ellipse fill="aqua" stroke="black" cx="507.51" cy="-82.75" rx="32.41" ry="18"/>
+<text text-anchor="start" x="491.76" y="-78.7" font-family="Times New Roman,serif" font-weight="bold" font-size="14.00">⊥</text>
+<text text-anchor="start" x="507.51" y="-78.7" font-family="Times New Roman,serif" font-size="14.00">:flt</text>
+</g>
+<!-- FLT_BOT->BOT -->
+<g id="edge29" class="edge">
+<title>FLT_BOT->BOT</title>
+<path fill="none" stroke="black" d="M532.4,-70.67C554.98,-60.63 588.29,-45.81 612.16,-35.19"/>
+<polygon fill="black" stroke="black" points="613.29,-38.51 621,-31.25 610.44,-32.12 613.29,-38.51"/>
+</g>
+<!-- FLT_CON64->FLT_BOT -->
+<g id="edge21" class="edge">
+<title>FLT_CON64->FLT_BOT</title>
+<path fill="none" stroke="black" d="M553.11,-242.68C542.05,-203.88 525.53,-145.95 515.71,-111.5"/>
+<polygon fill="black" stroke="black" points="519.17,-110.89 513.06,-102.23 512.44,-112.81 519.17,-110.89"/>
+</g>
+<!-- FLT_CON32 -->
+<g id="node17" class="node">
+<title>FLT_CON32</title>
+<ellipse fill="aqua" stroke="black" cx="333.51" cy="-282.65" rx="50.33" ry="18"/>
+<text text-anchor="middle" x="333.51" y="-277.6" font-family="Times New Roman,serif" font-size="14.00">32b consts</text>
+</g>
+<!-- FLT_CON32->FLT_CONPI -->
+<!-- FLT_32 -->
+<g id="node21" class="node">
+<title>FLT_32</title>
+<ellipse fill="aqua" stroke="black" cx="333.51" cy="-196.45" rx="23.2" ry="23.2"/>
+<text text-anchor="middle" x="333.51" y="-191.4" font-family="Times New Roman,serif" font-size="14.00">f32</text>
+</g>
+<!-- FLT_CON32->FLT_32 -->
+<g id="edge27" class="edge">
+<title>FLT_CON32->FLT_32</title>
+<path fill="none" stroke="black" d="M333.51,-264.21C333.51,-254.7 333.51,-242.59 333.51,-231.31"/>
+<polygon fill="black" stroke="black" points="337.01,-231.51 333.51,-221.51 330.01,-231.51 337.01,-231.51"/>
+</g>
+<!-- FLT_CONPI->FLT_BOT -->
+<g id="edge22" class="edge">
+<title>FLT_CONPI->FLT_BOT</title>
+<path fill="none" stroke="black" d="M452.03,-264.56C462.62,-229.86 485.86,-153.69 498.65,-111.79"/>
+<polygon fill="black" stroke="black" points="501.94,-112.98 501.51,-102.39 495.25,-110.93 501.94,-112.98"/>
+</g>
+<!-- FLT_CONPI->FLT_CON64 -->
+<!-- FLT_X32->FLT_CON32 -->
+<g id="edge25" class="edge">
+<title>FLT_X32->FLT_CON32</title>
+<path fill="none" stroke="black" d="M333.51,-345.2C333.51,-334.71 333.51,-322.76 333.51,-312.19"/>
+<polygon fill="black" stroke="black" points="337.01,-312.52 333.51,-302.52 330.01,-312.53 337.01,-312.52"/>
+</g>
+<!-- FLT_CON0 -->
+<g id="node20" class="node">
+<title>FLT_CON0</title>
+<ellipse fill="aqua" stroke="black" cx="251.51" cy="-282.65" rx="18.08" ry="18.08"/>
+<text text-anchor="middle" x="251.51" y="-277.6" font-family="Times New Roman,serif" font-size="14.00">0.</text>
+</g>
+<!-- FLT_X32->FLT_CON0 -->
+<g id="edge24" class="edge">
+<title>FLT_X32->FLT_CON0</title>
+<path fill="none" stroke="black" d="M314.9,-352.7C301.83,-338.47 284.38,-319.46 271.1,-304.99"/>
+<polygon fill="black" stroke="black" points="273.81,-302.77 264.47,-297.77 268.65,-307.5 273.81,-302.77"/>
+</g>
+<!-- FLT_CON0->FLT_CON32 -->
+<!-- FLT_CON0->FLT_32 -->
+<g id="edge26" class="edge">
+<title>FLT_CON0->FLT_32</title>
+<path fill="none" stroke="black" d="M263.96,-268.86C276.06,-256.44 294.74,-237.26 309.69,-221.91"/>
+<polygon fill="black" stroke="black" points="312.03,-224.53 316.5,-214.92 307.01,-219.64 312.03,-224.53"/>
+</g>
+<!-- FLT_32->FLT_BOT -->
+<g id="edge28" class="edge">
+<title>FLT_32->FLT_BOT</title>
+<path fill="none" stroke="black" d="M353.13,-183.63C383.19,-163.99 440.9,-126.28 476.61,-102.94"/>
+<polygon fill="black" stroke="black" points="478.34,-105.99 484.79,-97.59 474.51,-100.13 478.34,-105.99"/>
+</g>
+<!-- PTR0 -->
+<g id="node23" class="node">
+<title>PTR0</title>
+<polygon fill="none" stroke="none" points="668.51,-109.25 614.51,-109.25 614.51,-56.25 668.51,-56.25 668.51,-109.25"/>
+<polygon fill="green" stroke="none" points="623.51,-82.75 623.51,-105.25 659.51,-105.25 659.51,-82.75 623.51,-82.75"/>
+<polygon fill="none" stroke="black" points="623.51,-82.75 623.51,-105.25 659.51,-105.25 659.51,-82.75 623.51,-82.75"/>
+<text text-anchor="start" x="627.63" y="-88.95" font-family="Times New Roman,serif" font-size="14.00" fill="white">*ptr?</text>
+<polygon fill="lightgreen" stroke="none" points="623.51,-60.25 623.51,-82.75 659.51,-82.75 659.51,-60.25 623.51,-60.25"/>
+<polygon fill="none" stroke="black" points="623.51,-60.25 623.51,-82.75 659.51,-82.75 659.51,-60.25 623.51,-60.25"/>
+<text text-anchor="start" x="626.51" y="-66.45" font-family="Times New Roman,serif" font-size="14.00">struct</text>
+</g>
+<!-- PTR->PTR0 -->
+<g id="edge34" class="edge">
+<title>PTR->PTR0</title>
+<path fill="none" stroke="black" d="M641.51,-466.52C641.51,-396.01 641.51,-202.44 641.51,-120.34"/>
+<polygon fill="black" stroke="black" points="645.01,-120.58 641.51,-110.58 638.01,-120.58 645.01,-120.58"/>
+</g>
+<!-- PTR0->BOT -->
+<g id="edge35" class="edge">
+<title>PTR0->BOT</title>
+<path fill="none" stroke="black" d="M641.51,-56.46C641.51,-55.58 641.51,-54.7 641.51,-53.81"/>
+<polygon fill="black" stroke="black" points="645.01,-53.9 641.51,-43.9 638.01,-53.9 645.01,-53.9"/>
+</g>
+<!-- STRUCT_S1 -->
+<g id="node26" class="node">
+<title>STRUCT_S1</title>
+<polygon fill="none" stroke="none" points="741.38,-300.65 663.63,-300.65 663.63,-264.65 741.38,-264.65 741.38,-300.65"/>
+<polygon fill="lightgreen" stroke="none" points="671.63,-273.4 671.63,-291.9 698.38,-291.9 698.38,-273.4 671.63,-273.4"/>
+<polygon fill="none" stroke="black" points="671.63,-273.4 671.63,-291.9 698.38,-291.9 698.38,-273.4 671.63,-273.4"/>
+<text text-anchor="start" x="672.63" y="-277.6" font-family="Times New Roman,serif" font-size="14.00">field</text>
+<defs>
+<linearGradient id="l_0" gradientUnits="userSpaceOnUse" x1="703.51" y1="-289.19" x2="728.26" y2="-276.11" >
+<stop offset="0" style="stop-color:lightblue;stop-opacity:1.;"/>
+<stop offset="1" style="stop-color:green;stop-opacity:1.;"/>
+</linearGradient>
+</defs>
+<polygon fill="url(#l_0)" stroke="none" points="698.38,-273.4 698.38,-291.9 733.38,-291.9 733.38,-273.4 698.38,-273.4"/>
+<polygon fill="none" stroke="black" points="698.38,-273.4 698.38,-291.9 733.38,-291.9 733.38,-273.4 698.38,-273.4"/>
+<text text-anchor="start" x="699.38" y="-277.6" font-family="Times New Roman,serif" font-size="14.00">int/ptr</text>
+</g>
+<!-- STRUCT_TOP->STRUCT_S1 -->
+<g id="edge37" class="edge">
+<title>STRUCT_TOP->STRUCT_S1</title>
+<path fill="none" stroke="black" d="M742.68,-474.51C735.02,-437.92 717.75,-355.45 708.55,-311.5"/>
+<polygon fill="black" stroke="black" points="712.02,-311 706.54,-301.93 705.17,-312.43 712.02,-311"/>
+</g>
+<!-- STRUCT_S2 -->
+<g id="node27" class="node">
+<title>STRUCT_S2</title>
+<polygon fill="none" stroke="none" points="841.38,-309.15 755.63,-309.15 755.63,-256.15 841.38,-256.15 841.38,-309.15"/>
+<polygon fill="lightgreen" stroke="none" points="763.63,-282.65 763.63,-305.15 794.38,-305.15 794.38,-282.65 763.63,-282.65"/>
+<polygon fill="none" stroke="black" points="763.63,-282.65 763.63,-305.15 794.38,-305.15 794.38,-282.65 763.63,-282.65"/>
+<text text-anchor="start" x="766.63" y="-288.85" font-family="Times New Roman,serif" font-size="14.00">field</text>
+<defs>
+<linearGradient id="l_1" gradientUnits="userSpaceOnUse" x1="800.1" y1="-301.86" x2="827.67" y2="-285.95" >
+<stop offset="0" style="stop-color:lightblue;stop-opacity:1.;"/>
+<stop offset="1" style="stop-color:green;stop-opacity:1.;"/>
+</linearGradient>
+</defs>
+<polygon fill="url(#l_1)" stroke="none" points="794.38,-282.65 794.38,-305.15 833.38,-305.15 833.38,-282.65 794.38,-282.65"/>
+<polygon fill="none" stroke="black" points="794.38,-282.65 794.38,-305.15 833.38,-305.15 833.38,-282.65 794.38,-282.65"/>
+<text text-anchor="start" x="797.38" y="-288.85" font-family="Times New Roman,serif" font-size="14.00">int/ptr</text>
+<polygon fill="lightgreen" stroke="none" points="763.63,-260.15 763.63,-282.65 794.38,-282.65 794.38,-260.15 763.63,-260.15"/>
+<polygon fill="none" stroke="black" points="763.63,-260.15 763.63,-282.65 794.38,-282.65 794.38,-260.15 763.63,-260.15"/>
+<text text-anchor="start" x="766.63" y="-266.35" font-family="Times New Roman,serif" font-size="14.00">field</text>
+<defs>
+<linearGradient id="l_2" gradientUnits="userSpaceOnUse" x1="800.1" y1="-279.36" x2="827.67" y2="-263.45" >
+<stop offset="0" style="stop-color:lightblue;stop-opacity:1.;"/>
+<stop offset="1" style="stop-color:green;stop-opacity:1.;"/>
+</linearGradient>
+</defs>
+<polygon fill="url(#l_2)" stroke="none" points="794.38,-260.15 794.38,-282.65 833.38,-282.65 833.38,-260.15 794.38,-260.15"/>
+<polygon fill="none" stroke="black" points="794.38,-260.15 794.38,-282.65 833.38,-282.65 833.38,-260.15 794.38,-260.15"/>
+<text text-anchor="start" x="797.38" y="-266.35" font-family="Times New Roman,serif" font-size="14.00">int/ptr</text>
+</g>
+<!-- STRUCT_TOP->STRUCT_S2 -->
+<g id="edge38" class="edge">
+<title>STRUCT_TOP->STRUCT_S2</title>
+<path fill="none" stroke="black" d="M751.03,-474.51C759.53,-440.16 778.05,-365.34 789.28,-319.95"/>
+<polygon fill="black" stroke="black" points="792.67,-320.83 791.67,-310.28 785.87,-319.15 792.67,-320.83"/>
+</g>
+<!-- STRUCT_SN -->
+<g id="node28" class="node">
+<title>STRUCT_SN</title>
+<polygon fill="none" stroke="none" points="941.38,-320.4 855.63,-320.4 855.63,-244.9 941.38,-244.9 941.38,-320.4"/>
+<polygon fill="lightgreen" stroke="none" points="863.63,-293.9 863.63,-316.4 894.38,-316.4 894.38,-293.9 863.63,-293.9"/>
+<polygon fill="none" stroke="black" points="863.63,-293.9 863.63,-316.4 894.38,-316.4 894.38,-293.9 863.63,-293.9"/>
+<text text-anchor="start" x="866.63" y="-300.1" font-family="Times New Roman,serif" font-size="14.00">field</text>
+<defs>
+<linearGradient id="l_3" gradientUnits="userSpaceOnUse" x1="900.1" y1="-313.11" x2="927.67" y2="-297.2" >
+<stop offset="0" style="stop-color:lightblue;stop-opacity:1.;"/>
+<stop offset="1" style="stop-color:green;stop-opacity:1.;"/>
+</linearGradient>
+</defs>
+<polygon fill="url(#l_3)" stroke="none" points="894.38,-293.9 894.38,-316.4 933.38,-316.4 933.38,-293.9 894.38,-293.9"/>
+<polygon fill="none" stroke="black" points="894.38,-293.9 894.38,-316.4 933.38,-316.4 933.38,-293.9 894.38,-293.9"/>
+<text text-anchor="start" x="897.38" y="-300.1" font-family="Times New Roman,serif" font-size="14.00">int/ptr</text>
+<polygon fill="lightgreen" stroke="none" points="863.63,-271.4 863.63,-293.9 894.38,-293.9 894.38,-271.4 863.63,-271.4"/>
+<polygon fill="none" stroke="black" points="863.63,-271.4 863.63,-293.9 894.38,-293.9 894.38,-271.4 863.63,-271.4"/>
+<text text-anchor="start" x="869.63" y="-277.6" font-family="Times New Roman,serif" font-size="14.00">...  </text>
+<defs>
+<linearGradient id="l_4" gradientUnits="userSpaceOnUse" x1="900.1" y1="-290.61" x2="927.67" y2="-274.7" >
+<stop offset="0" style="stop-color:lightblue;stop-opacity:1.;"/>
+<stop offset="1" style="stop-color:green;stop-opacity:1.;"/>
+</linearGradient>
+</defs>
+<polygon fill="url(#l_4)" stroke="none" points="894.38,-271.4 894.38,-293.9 933.38,-293.9 933.38,-271.4 894.38,-271.4"/>
+<polygon fill="none" stroke="black" points="894.38,-271.4 894.38,-293.9 933.38,-293.9 933.38,-271.4 894.38,-271.4"/>
+<text text-anchor="start" x="897.38" y="-277.6" font-family="Times New Roman,serif" font-size="14.00">int/ptr</text>
+<polygon fill="lightgreen" stroke="none" points="863.63,-248.9 863.63,-271.4 894.38,-271.4 894.38,-248.9 863.63,-248.9"/>
+<polygon fill="none" stroke="black" points="863.63,-248.9 863.63,-271.4 894.38,-271.4 894.38,-248.9 863.63,-248.9"/>
+<text text-anchor="start" x="866.63" y="-255.1" font-family="Times New Roman,serif" font-size="14.00">field</text>
+<defs>
+<linearGradient id="l_5" gradientUnits="userSpaceOnUse" x1="900.1" y1="-268.11" x2="927.67" y2="-252.2" >
+<stop offset="0" style="stop-color:lightblue;stop-opacity:1.;"/>
+<stop offset="1" style="stop-color:green;stop-opacity:1.;"/>
+</linearGradient>
+</defs>
+<polygon fill="url(#l_5)" stroke="none" points="894.38,-248.9 894.38,-271.4 933.38,-271.4 933.38,-248.9 894.38,-248.9"/>
+<polygon fill="none" stroke="black" points="894.38,-248.9 894.38,-271.4 933.38,-271.4 933.38,-248.9 894.38,-248.9"/>
+<text text-anchor="start" x="897.38" y="-255.1" font-family="Times New Roman,serif" font-size="14.00">int/ptr</text>
+</g>
+<!-- STRUCT_TOP->STRUCT_SN -->
+<g id="edge39" class="edge">
+<title>STRUCT_TOP->STRUCT_SN</title>
+<path fill="none" stroke="black" d="M759.21,-475.24C782.17,-443.49 831.3,-375.56 864.63,-329.48"/>
+<polygon fill="black" stroke="black" points="867.37,-331.67 870.4,-321.52 861.7,-327.57 867.37,-331.67"/>
+</g>
+<!-- STRUCT_BOT -->
+<g id="node25" class="node">
+<title>STRUCT_BOT</title>
+<ellipse fill="lightgreen" stroke="black" cx="751.51" cy="-82.75" rx="44.7" ry="18"/>
+<text text-anchor="start" x="726.76" y="-78.7" font-family="Times New Roman,serif" font-weight="bold" font-size="14.00">⊥</text>
+<text text-anchor="start" x="742.51" y="-78.7" font-family="Times New Roman,serif" font-size="14.00">:struct</text>
+</g>
+<!-- STRUCT_BOT->BOT -->
+<g id="edge43" class="edge">
+<title>STRUCT_BOT->BOT</title>
+<path fill="none" stroke="black" d="M725.72,-67.77C709,-58.7 687.2,-46.89 669.97,-37.55"/>
+<polygon fill="black" stroke="black" points="671.71,-34.51 661.25,-32.82 668.37,-40.67 671.71,-34.51"/>
+</g>
+<!-- STRUCT_S1->STRUCT_BOT -->
+<g id="edge40" class="edge">
+<title>STRUCT_S1->STRUCT_BOT</title>
+<path fill="none" stroke="black" d="M706.86,-264.91C715.3,-230.49 733.97,-154.31 744.29,-112.19"/>
+<polygon fill="black" stroke="black" points="747.69,-113.04 746.67,-102.5 740.89,-111.37 747.69,-113.04"/>
+</g>
+<!-- STRUCT_S1->STRUCT_S2 -->
+<!-- STRUCT_S2->STRUCT_BOT -->
+<g id="edge41" class="edge">
+<title>STRUCT_S2->STRUCT_BOT</title>
+<path fill="none" stroke="black" d="M792.36,-256.52C783.62,-219.3 767.61,-151.25 758.45,-112.26"/>
+<polygon fill="black" stroke="black" points="761.88,-111.57 756.18,-102.64 755.07,-113.17 761.88,-111.57"/>
+</g>
+<!-- STRUCT_S2->STRUCT_SN -->
+<!-- STRUCT_SN->STRUCT_BOT -->
+<g id="edge42" class="edge">
+<title>STRUCT_SN->STRUCT_BOT</title>
+<path fill="none" stroke="black" d="M870.86,-245.05C841.74,-205.46 796.64,-144.13 771.03,-109.3"/>
+<polygon fill="black" stroke="black" points="773.99,-107.42 765.25,-101.44 768.36,-111.57 773.99,-107.42"/>
+</g>
+<!-- MEM_ALIAS1 -->
+<g id="node31" class="node">
+<title>MEM_ALIAS1</title>
+<ellipse fill="blue" stroke="black" cx="975.51" cy="-282.65" rx="20.13" ry="20.13"/>
+<text text-anchor="middle" x="975.51" y="-277.6" font-family="Times New Roman,serif" font-size="14.00" fill="white">#1</text>
+</g>
+<!-- MEM_TOP->MEM_ALIAS1 -->
+<g id="edge47" class="edge">
+<title>MEM_TOP->MEM_ALIAS1</title>
+<path fill="none" stroke="black" d="M996.51,-474.51C992.58,-438.66 983.84,-358.73 978.96,-314.18"/>
+<polygon fill="black" stroke="black" points="982.45,-313.94 977.89,-304.38 975.5,-314.7 982.45,-313.94"/>
+</g>
+<!-- MEM_ALIASDOTS -->
+<g id="node32" class="node">
+<title>MEM_ALIASDOTS</title>
+<ellipse fill="blue" stroke="black" cx="1036.51" cy="-282.65" rx="27" ry="18"/>
+<text text-anchor="middle" x="1036.51" y="-277.6" font-family="Times New Roman,serif" font-size="14.00" fill="white">...</text>
+</g>
+<!-- MEM_TOP->MEM_ALIASDOTS -->
+<g id="edge48" class="edge">
+<title>MEM_TOP->MEM_ALIASDOTS</title>
+<path fill="none" stroke="black" d="M1001.81,-474.51C1008.41,-438.02 1023.27,-355.86 1031.23,-311.83"/>
+<polygon fill="black" stroke="black" points="1034.63,-312.69 1032.97,-302.23 1027.74,-311.45 1034.63,-312.69"/>
+</g>
+<!-- MEM_ALIASN -->
+<g id="node33" class="node">
+<title>MEM_ALIASN</title>
+<ellipse fill="blue" stroke="black" cx="1097.51" cy="-282.65" rx="20.13" ry="20.13"/>
+<text text-anchor="middle" x="1097.51" y="-277.6" font-family="Times New Roman,serif" font-size="14.00" fill="white">#n</text>
+</g>
+<!-- MEM_TOP->MEM_ALIASN -->
+<g id="edge49" class="edge">
+<title>MEM_TOP->MEM_ALIASN</title>
+<path fill="none" stroke="black" d="M1006.95,-474.88C1024.11,-438.46 1063.18,-355.52 1083.95,-311.44"/>
+<polygon fill="black" stroke="black" points="1087.07,-313.03 1088.16,-302.49 1080.73,-310.05 1087.07,-313.03"/>
+</g>
+<!-- MEM_BOT -->
+<g id="node30" class="node">
+<title>MEM_BOT</title>
+<ellipse fill="blue" stroke="black" cx="998.51" cy="-82.75" rx="43.67" ry="18"/>
+<text text-anchor="start" x="974.51" y="-78.7" font-family="Times New Roman,serif" font-weight="bold" font-size="14.00" fill="white">⊥</text>
+<text text-anchor="start" x="990.26" y="-78.7" font-family="Times New Roman,serif" font-size="14.00" fill="white">:mem</text>
+</g>
+<!-- MEM_BOT->BOT -->
+<g id="edge53" class="edge">
+<title>MEM_BOT->BOT</title>
+<path fill="none" stroke="black" d="M958.6,-75.08C887.17,-63.15 738.86,-38.38 673.56,-27.48"/>
+<polygon fill="black" stroke="black" points="674.3,-24.05 663.86,-25.86 673.15,-30.96 674.3,-24.05"/>
+</g>
+<!-- MEM_ALIAS1->MEM_BOT -->
+<g id="edge50" class="edge">
+<title>MEM_ALIAS1->MEM_BOT</title>
+<path fill="none" stroke="black" d="M977.84,-262.38C981.92,-226.96 990.35,-153.62 995.09,-112.45"/>
+<polygon fill="black" stroke="black" points="998.57,-112.87 996.23,-102.53 991.61,-112.07 998.57,-112.87"/>
+</g>
+<!-- MEM_ALIAS1->MEM_ALIASDOTS -->
+<!-- MEM_ALIASDOTS->MEM_BOT -->
+<g id="edge51" class="edge">
+<title>MEM_ALIASDOTS->MEM_BOT</title>
+<path fill="none" stroke="black" d="M1033.07,-264.56C1026.49,-229.93 1012.06,-154.01 1004.08,-112.06"/>
+<polygon fill="black" stroke="black" points="1007.56,-111.6 1002.25,-102.43 1000.68,-112.91 1007.56,-111.6"/>
+</g>
+<!-- MEM_ALIASDOTS->MEM_ALIASN -->
+<!-- MEM_ALIASN->MEM_BOT -->
+<g id="edge52" class="edge">
+<title>MEM_ALIASN->MEM_BOT</title>
+<path fill="none" stroke="black" d="M1088.37,-264.2C1070.97,-229.07 1032.98,-152.35 1012.41,-110.82"/>
+<polygon fill="black" stroke="black" points="1015.58,-109.34 1008.01,-101.93 1009.31,-112.45 1015.58,-109.34"/>
+</g>
+<!-- TUPLE1 -->
+<g id="node36" class="node">
+<title>TUPLE1</title>
+<polygon fill="none" stroke="none" points="1185.51,-309.15 1131.51,-309.15 1131.51,-256.15 1185.51,-256.15 1185.51,-309.15"/>
+<polygon fill="pink" stroke="none" points="1139.76,-282.65 1139.76,-305.15 1177.26,-305.15 1177.26,-282.65 1139.76,-282.65"/>
+<polygon fill="none" stroke="black" points="1139.76,-282.65 1139.76,-305.15 1177.26,-305.15 1177.26,-282.65 1139.76,-282.65"/>
+<text text-anchor="start" x="1142.76" y="-288.85" font-family="Times New Roman,serif" font-size="14.00">Tuple</text>
+<polygon fill="black" stroke="none" points="1139.76,-260.15 1139.76,-282.65 1177.26,-282.65 1177.26,-260.15 1139.76,-260.15"/>
+<polygon fill="none" stroke="black" points="1139.76,-260.15 1139.76,-282.65 1177.26,-282.65 1177.26,-260.15 1139.76,-260.15"/>
+<text text-anchor="start" x="1146.88" y="-266.35" font-family="Times New Roman,serif" font-size="14.00" fill="white">type</text>
+</g>
+<!-- TUPLE_TOP->TUPLE1 -->
+<g id="edge57" class="edge">
+<title>TUPLE_TOP->TUPLE1</title>
+<path fill="none" stroke="black" d="M1180.42,-474.51C1176.51,-440.23 1167.99,-365.66 1162.8,-320.24"/>
+<polygon fill="black" stroke="black" points="1166.28,-319.86 1161.67,-310.32 1159.33,-320.65 1166.28,-319.86"/>
+</g>
+<!-- TUPLE2 -->
+<g id="node37" class="node">
+<title>TUPLE2</title>
+<polygon fill="none" stroke="none" points="1253.51,-320.4 1199.51,-320.4 1199.51,-244.9 1253.51,-244.9 1253.51,-320.4"/>
+<polygon fill="pink" stroke="none" points="1207.76,-293.9 1207.76,-316.4 1245.26,-316.4 1245.26,-293.9 1207.76,-293.9"/>
+<polygon fill="none" stroke="black" points="1207.76,-293.9 1207.76,-316.4 1245.26,-316.4 1245.26,-293.9 1207.76,-293.9"/>
+<text text-anchor="start" x="1210.76" y="-300.1" font-family="Times New Roman,serif" font-size="14.00">Tuple</text>
+<polygon fill="black" stroke="none" points="1207.76,-271.4 1207.76,-293.9 1245.26,-293.9 1245.26,-271.4 1207.76,-271.4"/>
+<polygon fill="none" stroke="black" points="1207.76,-271.4 1207.76,-293.9 1245.26,-293.9 1245.26,-271.4 1207.76,-271.4"/>
+<text text-anchor="start" x="1214.88" y="-277.6" font-family="Times New Roman,serif" font-size="14.00" fill="white">type</text>
+<polygon fill="black" stroke="none" points="1207.76,-248.9 1207.76,-271.4 1245.26,-271.4 1245.26,-248.9 1207.76,-248.9"/>
+<polygon fill="none" stroke="black" points="1207.76,-248.9 1207.76,-271.4 1245.26,-271.4 1245.26,-248.9 1207.76,-248.9"/>
+<text text-anchor="start" x="1214.88" y="-255.1" font-family="Times New Roman,serif" font-size="14.00" fill="white">type</text>
+</g>
+<!-- TUPLE_TOP->TUPLE2 -->
+<g id="edge58" class="edge">
+<title>TUPLE_TOP->TUPLE2</title>
+<path fill="none" stroke="black" d="M1186.34,-474.51C1192.95,-442.94 1206.72,-377.19 1216.28,-331.51"/>
+<polygon fill="black" stroke="black" points="1219.68,-332.35 1218.3,-321.85 1212.83,-330.92 1219.68,-332.35"/>
+</g>
+<!-- TUPLEN -->
+<g id="node38" class="node">
+<title>TUPLEN</title>
+<polygon fill="none" stroke="none" points="1321.51,-331.65 1267.51,-331.65 1267.51,-233.65 1321.51,-233.65 1321.51,-331.65"/>
+<polygon fill="pink" stroke="none" points="1275.76,-305.15 1275.76,-327.65 1313.26,-327.65 1313.26,-305.15 1275.76,-305.15"/>
+<polygon fill="none" stroke="black" points="1275.76,-305.15 1275.76,-327.65 1313.26,-327.65 1313.26,-305.15 1275.76,-305.15"/>
+<text text-anchor="start" x="1278.76" y="-311.35" font-family="Times New Roman,serif" font-size="14.00">Tuple</text>
+<polygon fill="black" stroke="none" points="1275.76,-282.65 1275.76,-305.15 1313.26,-305.15 1313.26,-282.65 1275.76,-282.65"/>
+<polygon fill="none" stroke="black" points="1275.76,-282.65 1275.76,-305.15 1313.26,-305.15 1313.26,-282.65 1275.76,-282.65"/>
+<text text-anchor="start" x="1282.88" y="-288.85" font-family="Times New Roman,serif" font-size="14.00" fill="white">type</text>
+<polygon fill="black" stroke="none" points="1275.76,-260.15 1275.76,-282.65 1313.26,-282.65 1313.26,-260.15 1275.76,-260.15"/>
+<polygon fill="none" stroke="black" points="1275.76,-260.15 1275.76,-282.65 1313.26,-282.65 1313.26,-260.15 1275.76,-260.15"/>
+<text text-anchor="start" x="1288.88" y="-266.35" font-family="Times New Roman,serif" font-size="14.00" fill="white">...</text>
+<polygon fill="black" stroke="none" points="1275.76,-237.65 1275.76,-260.15 1313.26,-260.15 1313.26,-237.65 1275.76,-237.65"/>
+<polygon fill="none" stroke="black" points="1275.76,-237.65 1275.76,-260.15 1313.26,-260.15 1313.26,-237.65 1275.76,-237.65"/>
+<text text-anchor="start" x="1282.88" y="-243.85" font-family="Times New Roman,serif" font-size="14.00" fill="white">type</text>
+</g>
+<!-- TUPLE_TOP->TUPLEN -->
+<g id="edge59" class="edge">
+<title>TUPLE_TOP->TUPLEN</title>
+<path fill="none" stroke="black" d="M1192.06,-474.88C1207.65,-445.62 1239.24,-386.36 1263.24,-341.32"/>
+<polygon fill="black" stroke="black" points="1266.22,-343.18 1267.83,-332.71 1260.04,-339.88 1266.22,-343.18"/>
+</g>
+<!-- TUPLE_BOT -->
+<g id="node35" class="node">
+<title>TUPLE_BOT</title>
+<ellipse fill="pink" stroke="black" cx="1192.51" cy="-82.75" rx="42.65" ry="18"/>
+<text text-anchor="start" x="1169.26" y="-78.7" font-family="Times New Roman,serif" font-weight="bold" font-size="14.00">⊥</text>
+<text text-anchor="start" x="1185.01" y="-78.7" font-family="Times New Roman,serif" font-size="14.00">:tuple</text>
+</g>
+<!-- TUPLE_BOT->BOT -->
+<g id="edge63" class="edge">
+<title>TUPLE_BOT->BOT</title>
+<path fill="none" stroke="black" d="M1151.54,-77.32C1046.53,-65.95 769.32,-35.95 674.14,-25.65"/>
+<polygon fill="black" stroke="black" points="674.52,-22.18 664.2,-24.58 673.77,-29.14 674.52,-22.18"/>
+</g>
+<!-- TUPLE1->TUPLE_BOT -->
+<g id="edge60" class="edge">
+<title>TUPLE1->TUPLE_BOT</title>
+<path fill="none" stroke="black" d="M1162.95,-256.52C1169.28,-219.3 1180.86,-151.25 1187.49,-112.26"/>
+<polygon fill="black" stroke="black" points="1190.9,-113.1 1189.12,-102.66 1184,-111.93 1190.9,-113.1"/>
+</g>
+<!-- TUPLE2->TUPLE_BOT -->
+<g id="edge61" class="edge">
+<title>TUPLE2->TUPLE_BOT</title>
+<path fill="none" stroke="black" d="M1220.11,-245.05C1213.55,-206.44 1203.46,-147.16 1197.47,-111.94"/>
+<polygon fill="black" stroke="black" points="1200.99,-111.74 1195.86,-102.47 1194.09,-112.92 1200.99,-111.74"/>
+</g>
+<!-- TUPLEN->TUPLE_BOT -->
+<g id="edge62" class="edge">
+<title>TUPLEN->TUPLE_BOT</title>
+<path fill="none" stroke="black" d="M1269.56,-233.75C1249.85,-195.12 1223.04,-142.58 1206.8,-110.75"/>
+<polygon fill="black" stroke="black" points="1210.07,-109.46 1202.41,-102.15 1203.83,-112.65 1210.07,-109.46"/>
+</g>
+</g>
+</svg>
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 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 12.2.0 (20241103.1931)
+ -->
+<!-- Title: Lattice Pages: 1 -->
+<svg width="478pt" height="501pt"
+ viewBox="0.00 0.00 477.96 500.90" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 496.9)">
+<title>Lattice</title>
+<polygon fill="white" stroke="none" points="-4,4 -4,-496.9 473.96,-496.9 473.96,4 -4,4"/>
+<!-- TOP -->
+<g id="node1" class="node">
+<title>TOP</title>
+<ellipse fill="black" stroke="black" cx="240.13" cy="-463.91" rx="59.04" ry="28.99"/>
+<text text-anchor="middle" x="240.13" y="-467.11" font-family="Times New Roman,serif" font-size="14.00" fill="white">~scalar</text>
+<text text-anchor="middle" x="240.13" y="-450.61" font-family="Times New Roman,serif" font-size="14.00" fill="white">+int+tmp+0</text>
+</g>
+<!-- PTRHQ -->
+<g id="node2" class="node">
+<title>PTRHQ</title>
+<ellipse fill="green" stroke="black" cx="43.13" cy="-376.92" rx="43.13" ry="28.99"/>
+<text text-anchor="middle" x="43.13" y="-380.12" font-family="Times New Roman,serif" font-size="14.00" fill="white">~tmp</text>
+<text text-anchor="middle" x="43.13" y="-363.62" font-family="Times New Roman,serif" font-size="14.00" fill="white">+tmp+0</text>
+</g>
+<!-- TOP->PTRHQ -->
+<g id="edge1" class="edge">
+<title>TOP->PTRHQ</title>
+<path fill="none" stroke="black" d="M196.41,-444.04C164.74,-430.38 121.99,-411.94 89.68,-398"/>
+<polygon fill="black" stroke="black" points="91.11,-394.81 80.55,-394.06 88.34,-401.24 91.11,-394.81"/>
+</g>
+<!-- VALH_ -->
+<g id="node3" class="node">
+<title>VALH_</title>
+<ellipse fill="white" stroke="black" cx="240.13" cy="-376.92" rx="48.44" ry="28.99"/>
+<text text-anchor="middle" x="240.13" y="-380.12" font-family="Times New Roman,serif" font-size="14.00">~nscalar</text>
+<text text-anchor="middle" x="240.13" y="-363.62" font-family="Times New Roman,serif" font-size="14.00">+int+tmp</text>
+</g>
+<!-- TOP->VALH_ -->
+<g id="edge2" class="edge">
+<title>TOP->VALH_</title>
+<path fill="none" stroke="black" d="M235.15,-434.61C234.93,-429.02 234.85,-423.06 234.9,-417.18"/>
+<polygon fill="black" stroke="black" points="238.39,-417.69 235.12,-407.61 231.39,-417.52 238.39,-417.69"/>
+</g>
+<!-- TOP->VALH_ -->
+<!-- INTHQ -->
+<g id="node4" class="node">
+<title>INTHQ</title>
+<ellipse fill="lightblue" stroke="black" cx="432.13" cy="-376.92" rx="37.83" ry="28.99"/>
+<text text-anchor="middle" x="432.13" y="-380.12" font-family="Times New Roman,serif" font-size="14.00">~int</text>
+<text text-anchor="middle" x="432.13" y="-363.62" font-family="Times New Roman,serif" font-size="14.00">+int+0</text>
+</g>
+<!-- TOP->INTHQ -->
+<g id="edge3" class="edge">
+<title>TOP->INTHQ</title>
+<path fill="none" stroke="black" d="M283.22,-443.83C314.91,-429.81 357.78,-410.83 389.39,-396.84"/>
+<polygon fill="black" stroke="black" points="390.53,-400.16 398.26,-392.92 387.7,-393.76 390.53,-400.16"/>
+</g>
+<!-- LEFTHI -->
+<!-- PTRHQ->LEFTHI -->
+<!-- PTRH_ -->
+<g id="node6" class="node">
+<title>PTRH_</title>
+<ellipse fill="green" stroke="black" cx="43.13" cy="-289.94" rx="36.77" ry="28.99"/>
+<text text-anchor="middle" x="43.13" y="-293.14" font-family="Times New Roman,serif" font-size="14.00" fill="white">~ntmp</text>
+<text text-anchor="middle" x="43.13" y="-276.64" font-family="Times New Roman,serif" font-size="14.00" fill="white">+tmp</text>
+</g>
+<!-- PTRHQ->PTRH_ -->
+<g id="edge8" class="edge">
+<title>PTRHQ->PTRH_</title>
+<path fill="none" stroke="black" d="M38.15,-347.62C37.93,-342.03 37.85,-336.07 37.9,-330.2"/>
+<polygon fill="black" stroke="black" points="41.39,-330.71 38.12,-320.63 34.39,-330.54 41.39,-330.71"/>
+</g>
+<!-- PTRHQ->PTRH_ -->
+<!-- NILX_ -->
+<g id="node8" class="node">
+<title>NILX_</title>
+<ellipse fill="aqua" stroke="black" cx="150.13" cy="-289.94" rx="56.39" ry="28.99"/>
+<text text-anchor="middle" x="150.13" y="-293.14" font-family="Times New Roman,serif" font-size="14.00">~nil</text>
+<text text-anchor="middle" x="150.13" y="-276.64" font-family="Times New Roman,serif" font-size="14.00">[int,tmp]+0</text>
+</g>
+<!-- PTRHQ->NILX_ -->
+<g id="edge12" class="edge">
+<title>PTRHQ->NILX_</title>
+<path fill="none" stroke="black" d="M70.41,-354.26C82.91,-344.33 97.94,-332.4 111.56,-321.58"/>
+<polygon fill="black" stroke="black" points="113.43,-324.56 119.09,-315.6 109.08,-319.08 113.43,-324.56"/>
+</g>
+<!-- VALH_->INTHQ -->
+<!-- VALH_->PTRH_ -->
+<g id="edge7" class="edge">
+<title>VALH_->PTRH_</title>
+<path fill="none" stroke="black" d="M201.21,-359.13C167.78,-344.71 119.68,-323.96 85.33,-309.14"/>
+<polygon fill="black" stroke="black" points="87.1,-306.09 76.53,-305.35 84.33,-312.52 87.1,-306.09"/>
+</g>
+<!-- INTH_ -->
+<g id="node7" class="node">
+<title>INTH_</title>
+<ellipse fill="lightblue" stroke="black" cx="432.13" cy="-289.94" rx="31.47" ry="28.99"/>
+<text text-anchor="middle" x="432.13" y="-293.14" font-family="Times New Roman,serif" font-size="14.00">~nint</text>
+<text text-anchor="middle" x="432.13" y="-276.64" font-family="Times New Roman,serif" font-size="14.00">+int</text>
+</g>
+<!-- VALH_->INTH_ -->
+<g id="edge9" class="edge">
+<title>VALH_->INTH_</title>
+<path fill="none" stroke="black" d="M278.53,-358.93C311.9,-344.16 360.02,-322.86 393.48,-308.05"/>
+<polygon fill="black" stroke="black" points="394.67,-311.35 402.4,-304.1 391.84,-304.95 394.67,-311.35"/>
+</g>
+<!-- NIL__ -->
+<g id="node9" class="node">
+<title>NIL__</title>
+<ellipse fill="pink" stroke="black" cx="324.13" cy="-289.94" rx="62.23" ry="28.99"/>
+<text text-anchor="middle" x="324.13" y="-293.14" font-family="Times New Roman,serif" font-size="14.00">nil</text>
+<text text-anchor="middle" x="324.13" y="-276.64" font-family="Times New Roman,serif" font-size="14.00">[+int+tmp],0</text>
+</g>
+<!-- VALH_->NIL__ -->
+<g id="edge13" class="edge">
+<title>VALH_->NIL__</title>
+<path fill="none" stroke="black" d="M264.43,-351.34C272.67,-343.01 282.01,-333.55 290.83,-324.64"/>
+<polygon fill="black" stroke="black" points="293.12,-327.3 297.66,-317.72 288.14,-322.38 293.12,-327.3"/>
+</g>
+<!-- VALL_ -->
+<g id="node13" class="node">
+<title>VALL_</title>
+<ellipse fill="white" stroke="black" cx="240.13" cy="-115.97" rx="39.42" ry="28.99"/>
+<text text-anchor="middle" x="240.13" y="-119.17" font-family="Times New Roman,serif" font-size="14.00">nscalar</text>
+<text text-anchor="middle" x="240.13" y="-102.67" font-family="Times New Roman,serif" font-size="14.00">int,tmp</text>
+</g>
+<!-- VALH_->VALL_ -->
+<!-- INTHQ->INTH_ -->
+<g id="edge10" class="edge">
+<title>INTHQ->INTH_</title>
+<path fill="none" stroke="black" d="M427.17,-348.1C426.94,-342.37 426.85,-336.25 426.9,-330.21"/>
+<polygon fill="black" stroke="black" points="430.39,-330.42 427.13,-320.34 423.39,-330.26 430.39,-330.42"/>
+</g>
+<!-- INTHQ->INTH_ -->
+<!-- INTHQ->NILX_ -->
+<g id="edge11" class="edge">
+<title>INTHQ->NILX_</title>
+<path fill="none" stroke="black" d="M396.99,-365.33C350,-351.17 266.03,-325.87 209.23,-308.75"/>
+<polygon fill="black" stroke="black" points="210.28,-305.41 199.69,-305.88 208.26,-312.11 210.28,-305.41"/>
+</g>
+<!-- LEFTHI->VALH_ -->
+<!-- LEFTHI->NILX_ -->
+<!-- PTRL_ -->
+<g id="node10" class="node">
+<title>PTRL_</title>
+<ellipse fill="green" stroke="black" cx="43.13" cy="-202.96" rx="31.47" ry="28.99"/>
+<text text-anchor="middle" x="43.13" y="-206.16" font-family="Times New Roman,serif" font-size="14.00" fill="white">ntmp</text>
+<text text-anchor="middle" x="43.13" y="-189.66" font-family="Times New Roman,serif" font-size="14.00" fill="white">tmp</text>
+</g>
+<!-- PTRH_->PTRL_ -->
+<g id="edge15" class="edge">
+<title>PTRH_->PTRL_</title>
+<path fill="none" stroke="black" d="M38.17,-261.11C37.94,-255.39 37.85,-249.26 37.9,-243.22"/>
+<polygon fill="black" stroke="black" points="41.39,-243.44 38.13,-233.36 34.39,-243.27 41.39,-243.44"/>
+</g>
+<!-- PTRH_->PTRL_ -->
+<!-- INTL_ -->
+<g id="node11" class="node">
+<title>INTL_</title>
+<ellipse fill="lightblue" stroke="black" cx="432.13" cy="-202.96" rx="27" ry="28.99"/>
+<text text-anchor="middle" x="432.13" y="-206.16" font-family="Times New Roman,serif" font-size="14.00">nint</text>
+<text text-anchor="middle" x="432.13" y="-189.66" font-family="Times New Roman,serif" font-size="14.00">int</text>
+</g>
+<!-- INTH_->INTL_ -->
+<g id="edge16" class="edge">
+<title>INTH_->INTL_</title>
+<path fill="none" stroke="black" d="M427.17,-261.11C426.94,-255.39 426.85,-249.26 426.9,-243.22"/>
+<polygon fill="black" stroke="black" points="430.39,-243.44 427.13,-233.36 423.39,-243.27 430.39,-243.44"/>
+</g>
+<!-- INTH_->INTL_ -->
+<!-- NILX_->NIL__ -->
+<!-- NILX_->VALL_ -->
+<g id="edge21" class="edge">
+<title>NILX_->VALL_</title>
+<path fill="none" stroke="black" d="M164.79,-261.6C180.03,-232.15 203.95,-185.91 220.74,-153.45"/>
+<polygon fill="black" stroke="black" points="223.74,-155.27 225.23,-144.78 217.53,-152.06 223.74,-155.27"/>
+</g>
+<!-- PTRL0 -->
+<g id="node12" class="node">
+<title>PTRL0</title>
+<ellipse fill="green" stroke="black" cx="43.13" cy="-115.97" rx="34.12" ry="28.99"/>
+<text text-anchor="middle" x="43.13" y="-119.17" font-family="Times New Roman,serif" font-size="14.00" fill="white">tmp</text>
+<text text-anchor="middle" x="43.13" y="-102.67" font-family="Times New Roman,serif" font-size="14.00" fill="white">tmp,0</text>
+</g>
+<!-- NIL__->PTRL0 -->
+<g id="edge18" class="edge">
+<title>NIL__->PTRL0</title>
+<path fill="none" stroke="black" d="M286.24,-266.48C232.36,-233.13 134.24,-172.38 80.6,-139.17"/>
+<polygon fill="black" stroke="black" points="82.76,-136.39 72.41,-134.1 79.07,-142.34 82.76,-136.39"/>
+</g>
+<!-- INTL0 -->
+<g id="node14" class="node">
+<title>INTL0</title>
+<ellipse fill="lightblue" stroke="black" cx="432.13" cy="-115.97" rx="28.81" ry="28.99"/>
+<text text-anchor="middle" x="432.13" y="-119.17" font-family="Times New Roman,serif" font-size="14.00">int</text>
+<text text-anchor="middle" x="432.13" y="-102.67" font-family="Times New Roman,serif" font-size="14.00">int,0</text>
+</g>
+<!-- NIL__->INTL0 -->
+<g id="edge23" class="edge">
+<title>NIL__->INTL0</title>
+<path fill="none" stroke="black" d="M341.49,-261.99C360.34,-231.61 390.49,-183.05 410.79,-150.35"/>
+<polygon fill="black" stroke="black" points="413.65,-152.38 415.95,-142.04 407.7,-148.69 413.65,-152.38"/>
+</g>
+<!-- PTRL_->PTRL0 -->
+<g id="edge17" class="edge">
+<title>PTRL_->PTRL0</title>
+<path fill="none" stroke="black" d="M38.17,-174.13C37.94,-168.47 37.85,-162.41 37.9,-156.43"/>
+<polygon fill="black" stroke="black" points="41.39,-156.75 38.12,-146.67 34.39,-156.58 41.39,-156.75"/>
+</g>
+<!-- PTRL_->PTRL0 -->
+<!-- PTRL_->VALL_ -->
+<g id="edge19" class="edge">
+<title>PTRL_->VALL_</title>
+<path fill="none" stroke="black" d="M71.36,-189.78C103.94,-175.73 157.92,-152.44 196.04,-135.99"/>
+<polygon fill="black" stroke="black" points="197.16,-139.32 204.96,-132.15 194.39,-132.9 197.16,-139.32"/>
+</g>
+<!-- INTL_->VALL_ -->
+<g id="edge20" class="edge">
+<title>INTL_->VALL_</title>
+<path fill="none" stroke="black" d="M407.43,-191.02C376.23,-177.21 322.01,-153.22 283.77,-136.29"/>
+<polygon fill="black" stroke="black" points="285.39,-133.18 274.83,-132.33 282.55,-139.58 285.39,-133.18"/>
+</g>
+<!-- INTL_->INTL0 -->
+<g id="edge22" class="edge">
+<title>INTL_->INTL0</title>
+<path fill="none" stroke="black" d="M427.17,-174.13C426.94,-168.41 426.85,-162.28 426.9,-156.24"/>
+<polygon fill="black" stroke="black" points="430.39,-156.46 427.13,-146.38 423.39,-156.29 430.39,-156.46"/>
+</g>
+<!-- INTL_->INTL0 -->
+<!-- PTRL0->VALL_ -->
+<!-- BOT -->
+<g id="node15" class="node">
+<title>BOT</title>
+<ellipse fill="black" stroke="black" cx="240.13" cy="-28.99" rx="46.85" ry="28.99"/>
+<text text-anchor="middle" x="240.13" y="-32.19" font-family="Times New Roman,serif" font-size="14.00" fill="white">scalar</text>
+<text text-anchor="middle" x="240.13" y="-15.69" font-family="Times New Roman,serif" font-size="14.00" fill="white">int,tmp,0</text>
+</g>
+<!-- PTRL0->BOT -->
+<g id="edge26" class="edge">
+<title>PTRL0->BOT</title>
+<path fill="none" stroke="black" d="M73.48,-101.88C105.02,-88.28 154.83,-66.79 191.79,-50.85"/>
+<polygon fill="black" stroke="black" points="193.14,-54.08 200.94,-46.9 190.37,-47.65 193.14,-54.08"/>
+</g>
+<!-- VALL_->INTL0 -->
+<!-- VALL_->BOT -->
+<g id="edge28" class="edge">
+<title>VALL_->BOT</title>
+<path fill="none" stroke="black" d="M235.17,-87.15C234.94,-81.48 234.85,-75.42 234.9,-69.44"/>
+<polygon fill="black" stroke="black" points="238.39,-69.76 235.12,-59.68 231.39,-69.6 238.39,-69.76"/>
+</g>
+<!-- VALL_->BOT -->
+<!-- INTL0->BOT -->
+<g id="edge27" class="edge">
+<title>INTL0->BOT</title>
+<path fill="none" stroke="black" d="M405.84,-103.34C375.52,-89.91 325.06,-67.58 287.78,-51.08"/>
+<polygon fill="black" stroke="black" points="289.59,-48.05 279.03,-47.21 286.75,-54.45 289.59,-48.05"/>
+</g>
+</g>
+</svg>
diff --git a/docs/chapter20/pardoe.docx b/docs/chapter20/pardoe.docx Binary files differnew file mode 100644 index 00000000..24fde921 --- /dev/null +++ b/docs/chapter20/pardoe.docx 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;q<x;q+=1)e[q]=j(q,l)||"null";n=0===e.length?"[]":k?"[\n"+k+e.join(",\n"+k)+"\n"+i+"]": +"["+e.join(",")+"]";k=i;return n}if(r&&"object"==typeof r){x=r.length;for(q=0;q<x;q+=1)"string"==typeof r[q]&&(t=r[q],n=j(t,l),n&&e.push(g(t)+(k?": ":":")+n))}else for(t in l)Object.prototype.hasOwnProperty.call(l,t)&&(n=j(t,l),n&&e.push(g(t)+(k?": ":":")+n));n=0===e.length?"{}":k?"{\n"+k+e.join(",\n"+k)+"\n"+i+"}":"{"+e.join(",")+"}";k=i;return n}}function g(c){n.lastIndex=0;return n.test(c)?'"'+c.replace(n,function(c){var g=D[c];return"string"==typeof g?g:"\\u"+("0000"+c.charCodeAt(0).toString(16)).slice(-4)})+ +'"':'"'+c+'"'}function c(c){return 10>c?"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<p;n+=1)v+=" ";else"string"==typeof p&&(v=p);r=g;if(!g||"function"==typeof g||"object"==typeof g&&"number"==typeof g.length)return j("",{"":c});throw Error("JSON.stringify");});"function"!= +typeof JSON.parse&&(JSON.parse=function(c,g){function j(c,k){var i,e,l=c[k];if(l&&"object"==typeof l)for(i in l)Object.prototype.hasOwnProperty.call(l,i)&&(e=j(l,i),void 0!==e?l[i]=e:delete l[i]);return g.call(c,k,l)}var k,c=String(c);p.lastIndex=0;p.test(c)&&(c=c.replace(p,function(c){return"\\u"+("0000"+c.charCodeAt(0).toString(16)).slice(-4)}));if(/^[\],:{}\s]*$/.test(c.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, +"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return k=eval("("+c+")"),"function"==typeof g?j({"":k},""):k;throw new SyntaxError("JSON.parse");})})(); +SockJS=function(){var j=document,g=window,c={},p=function(){};p.prototype.addEventListener=function(a,b){this._listeners||(this._listeners={});a in this._listeners||(this._listeners[a]=[]);var f=this._listeners[a];-1===c.arrIndexOf(f,b)&&f.push(b)};p.prototype.removeEventListener=function(a,b){if(this._listeners&&a in this._listeners){var f=this._listeners[a],d=c.arrIndexOf(f,b);-1!==d&&(1<f.length?this._listeners[a]=f.slice(0,d).concat(f.slice(d+1)):delete this._listeners[a])}};p.prototype.dispatchEvent= +function(a){var b=a.type,c=Array.prototype.slice.call(arguments,0);this["on"+b]&&this["on"+b].apply(this,c);if(this._listeners&&b in this._listeners)for(var d=0;d<this._listeners[b].length;d++)this._listeners[b][d].apply(this,c)};var n=function(a,b){this.type=a;if("undefined"!=typeof b)for(var c in b)b.hasOwnProperty(c)&&(this[c]=b[c])};n.prototype.toString=function(){var a=[],b;for(b in this)if(this.hasOwnProperty(b)){var c=this[b];"function"==typeof c&&(c="[function]");a.push(b+"="+c)}return"SimpleEvent("+ +a.join(", ")+")"};var k=function(a){this.events=a||[]};k.prototype.emit=function(a){var b=Array.prototype.slice.call(arguments,1);!this.nuked&&this["on"+a]&&this["on"+a].apply(this,b);-1===c.arrIndexOf(this.events,a)&&c.log("Event "+JSON.stringify(a)+" not listed "+JSON.stringify(this.events)+" in "+this)};k.prototype.nuke=function(){this.nuked=!0;for(var a=0;a<this.events.length;a++)delete this[this.events[a]]};c.random_string=function(a,b){var b=b||37,c,d=[];for(c=0;c<a;c++)d.push("abcdefghijklmnopqrstuvwxyz0123456789_".substr(Math.floor(Math.random()* +b),1));return d.join("")};c.random_number=function(a){return Math.floor(Math.random()*a)};c.random_number_string=function(a){var b=(""+(a-1)).length;return(Array(b+1).join("0")+c.random_number(a)).slice(-b)};c.getOrigin=function(a){return(a+"/").split("/").slice(0,3).join("/")};c.isSameOriginUrl=function(a,b){return b||(b=g.location.href),a.split("/").slice(0,3).join("/")===b.split("/").slice(0,3).join("/")};c.getParentDomain=function(a){return/^[0-9.]*$/.test(a)||/^\[/.test(a)||!/[.]/.test(a)?a: +a.split(".").slice(1).join(".")};c.objectExtend=function(a,b){for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a};c.polluteGlobalNamespace=function(){"_jp"in g||(g._jp={})};c.closeFrame=function(a,b){return"c"+JSON.stringify([a,b])};c.userSetCode=function(a){return 1E3===a||3E3<=a&&4999>=a};c.countRTO=function(a){var b;return 100<a?b=3*a:b=a+200,b};c.log=function(){g.console&&console.log&&console.log.apply&&console.log.apply(console,arguments)};c.bind=function(a,b){return a.bind?a.bind(b):function(){return a.apply(b, +arguments)}};c.flatUrl=function(a){return-1===a.indexOf("?")&&-1===a.indexOf("#")};c.amendUrl=function(a){var b=j.location;if(!a)throw Error("Wrong url for SockJS");if(!c.flatUrl(a))throw Error("Only basic urls are supported in SockJS");return 0===a.indexOf("//")&&(a=b.protocol+a),0===a.indexOf("/")&&(a=b.protocol+"//"+b.host+a),a=a.replace(/[/]+$/,""),a};c.arrIndexOf=function(a,b){for(var c=0;c<a.length;c++)if(a[c]===b)return c;return-1};c.arrSkip=function(a,b){var f=c.arrIndexOf(a,b);return-1=== +f?a.slice():a.slice(0,f).concat(a.slice(f+1))};c.isArray=Array.isArray||function(a){return 0<={}.toString.call(a).indexOf("Array")};c.delay=function(a,b){return"function"==typeof a&&(b=a,a=0),setTimeout(b,a)};var v=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,D={"\x00":"\\u0000","\u0001":"\\u0001","\u0002":"\\u0002","\u0003":"\\u0003","\u0004":"\\u0004","\u0005":"\\u0005","\u0006":"\\u0006","\u0007":"\\u0007","\b":"\\b", +"\t":"\\t","\n":"\\n","\x0B":"\\u000b","\f":"\\f","\r":"\\r","\u000e":"\\u000e","\u000f":"\\u000f","\u0010":"\\u0010","\u0011":"\\u0011","\u0012":"\\u0012","\u0013":"\\u0013","\u0014":"\\u0014","\u0015":"\\u0015","\u0016":"\\u0016","\u0017":"\\u0017","\u0018":"\\u0018","\u0019":"\\u0019","\u001a":"\\u001a","\u001b":"\\u001b","\u001c":"\\u001c","\u001d":"\\u001d","\u001e":"\\u001e","\u001f":"\\u001f",'"':'\\"',"\\":"\\\\","\u007f":"\\u007f","\u0080":"\\u0080","\u0081":"\\u0081","\u0082":"\\u0082", +"\u0083":"\\u0083","\u0084":"\\u0084","\u0085":"\\u0085","\u0086":"\\u0086","\u0087":"\\u0087","\u0088":"\\u0088","\u0089":"\\u0089","\u008a":"\\u008a","\u008b":"\\u008b","\u008c":"\\u008c","\u008d":"\\u008d","\u008e":"\\u008e","\u008f":"\\u008f","\u0090":"\\u0090","\u0091":"\\u0091","\u0092":"\\u0092","\u0093":"\\u0093","\u0094":"\\u0094","\u0095":"\\u0095","\u0096":"\\u0096","\u0097":"\\u0097","\u0098":"\\u0098","\u0099":"\\u0099","\u009a":"\\u009a","\u009b":"\\u009b","\u009c":"\\u009c","\u009d":"\\u009d", +"\u009e":"\\u009e","\u009f":"\\u009f","\u00ad":"\\u00ad","\u0600":"\\u0600","\u0601":"\\u0601","\u0602":"\\u0602","\u0603":"\\u0603","\u0604":"\\u0604","\u070f":"\\u070f","\u17b4":"\\u17b4","\u17b5":"\\u17b5","\u200c":"\\u200c","\u200d":"\\u200d","\u200e":"\\u200e","\u200f":"\\u200f","\u2028":"\\u2028","\u2029":"\\u2029","\u202a":"\\u202a","\u202b":"\\u202b","\u202c":"\\u202c","\u202d":"\\u202d","\u202e":"\\u202e","\u202f":"\\u202f","\u2060":"\\u2060","\u2061":"\\u2061","\u2062":"\\u2062","\u2063":"\\u2063", +"\u2064":"\\u2064","\u2065":"\\u2065","\u2066":"\\u2066","\u2067":"\\u2067","\u2068":"\\u2068","\u2069":"\\u2069","\u206a":"\\u206a","\u206b":"\\u206b","\u206c":"\\u206c","\u206d":"\\u206d","\u206e":"\\u206e","\u206f":"\\u206f","\ufeff":"\\ufeff","\ufff0":"\\ufff0","\ufff1":"\\ufff1","\ufff2":"\\ufff2","\ufff3":"\\ufff3","\ufff4":"\\ufff4","\ufff5":"\\ufff5","\ufff6":"\\ufff6","\ufff7":"\\ufff7","\ufff8":"\\ufff8","\ufff9":"\\ufff9","\ufffa":"\\ufffa","\ufffb":"\\ufffb","\ufffc":"\\ufffc","\ufffd":"\\ufffd", +"\ufffe":"\\ufffe","\uffff":"\\uffff"},r=/[\x00-\x1f\ud800-\udfff\ufffe\uffff\u0300-\u0333\u033d-\u0346\u034a-\u034c\u0350-\u0352\u0357-\u0358\u035c-\u0362\u0374\u037e\u0387\u0591-\u05af\u05c4\u0610-\u0617\u0653-\u0654\u0657-\u065b\u065d-\u065e\u06df-\u06e2\u06eb-\u06ec\u0730\u0732-\u0733\u0735-\u0736\u073a\u073d\u073f-\u0741\u0743\u0745\u0747\u07eb-\u07f1\u0951\u0958-\u095f\u09dc-\u09dd\u09df\u0a33\u0a36\u0a59-\u0a5b\u0a5e\u0b5c-\u0b5d\u0e38-\u0e39\u0f43\u0f4d\u0f52\u0f57\u0f5c\u0f69\u0f72-\u0f76\u0f78\u0f80-\u0f83\u0f93\u0f9d\u0fa2\u0fa7\u0fac\u0fb9\u1939-\u193a\u1a17\u1b6b\u1cda-\u1cdb\u1dc0-\u1dcf\u1dfc\u1dfe\u1f71\u1f73\u1f75\u1f77\u1f79\u1f7b\u1f7d\u1fbb\u1fbe\u1fc9\u1fcb\u1fd3\u1fdb\u1fe3\u1feb\u1fee-\u1fef\u1ff9\u1ffb\u1ffd\u2000-\u2001\u20d0-\u20d1\u20d4-\u20d7\u20e7-\u20e9\u2126\u212a-\u212b\u2329-\u232a\u2adc\u302b-\u302c\uaab2-\uaab3\uf900-\ufa0d\ufa10\ufa12\ufa15-\ufa1e\ufa20\ufa22\ufa25-\ufa26\ufa2a-\ufa2d\ufa30-\ufa6d\ufa70-\ufad9\ufb1d\ufb1f\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4e\ufff0-\uffff]/g, +B,M=JSON&&JSON.stringify||function(a){return v.lastIndex=0,v.test(a)&&(a=a.replace(v,function(a){return D[a]})),'"'+a+'"'};c.quote=function(a){a=M(a);r.lastIndex=0;if(r.test(a)){if(!B){var b,c={},d=[];for(b=0;65536>b;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<q.length;b++){var c=q[b];a[c]=e[c]&&e[c].enabled()}return a};c.detectProtocols=function(a,b,c){var d={},h=[];b||(b=q);for(var m=0;m<b.length;m++){var g=b[m];d[g]=a[g]}var e=function(a){var b=a.shift();d[b]?h.push(b):0<a.length&&e(a)};return!1!==c.websocket&&e(["websocket"]),d["xhr-streaming"]&&!c.null_origin?h.push("xhr-streaming"):d["xdr-streaming"]&&!c.cookie_needed&&!c.null_origin?h.push("xdr-streaming"):e(["iframe-eventsource","iframe-htmlfile"]), +d["xhr-polling"]&&!c.null_origin?h.push("xhr-polling"):d["xdr-polling"]&&!c.cookie_needed&&!c.null_origin?h.push("xdr-polling"):e(["iframe-xhr-polling","jsonp-polling"]),h};c.createHook=function(){var a="a"+c.random_string(8);if(!("_sockjs_global"in g)){var b={};g._sockjs_global=function(a){return a in b||(b[a]={id:a,del:function(){delete b[a]}}),b[a]}}return g._sockjs_global(a)};c.attachMessage=function(a){c.attachEvent("message",a)};c.attachEvent=function(a,b){"undefined"!=typeof g.addEventListener? +g.addEventListener(a,b,!1):(j.attachEvent("on"+a,b),g.attachEvent("on"+a,b))};c.detachMessage=function(a){c.detachEvent("message",a)};c.detachEvent=function(a,b){"undefined"!=typeof g.addEventListener?g.removeEventListener(a,b,!1):(j.detachEvent("on"+a,b),g.detachEvent("on"+a,b))};var t={},C=!1,x=function(){for(var a in t)t[a](),delete t[a]},i=function(){C||(C=!0,x())};c.attachEvent("beforeunload",i);c.attachEvent("unload",i);c.unload_add=function(a){var b=c.random_string(8);return t[b]=a,C&&c.delay(x), +b};c.unload_del=function(a){a in t&&delete t[a]};c.createIframe=function(a,b){var f=j.createElement("iframe"),d,h,m=function(){clearTimeout(d);try{f.onload=null}catch(a){}f.onerror=null},g=function(){f&&(m(),setTimeout(function(){f&&f.parentNode.removeChild(f);f=null},0),c.unload_del(h))};return f.src=a,f.style.display="none",f.style.position="absolute",f.onerror=function(){f&&(g(),b("onerror"))},f.onload=function(){clearTimeout(d);d=setTimeout(function(){f&&(g(),b("onload timeout"))},2E3)},j.body.appendChild(f), +d=setTimeout(function(){f&&(g(),b("timeout"))},15E3),h=c.unload_add(g),{post:function(a,b){try{f&&f.contentWindow&&f.contentWindow.postMessage(a,b)}catch(c){}},cleanup:g,loaded:m}};c.createHtmlfile=function(a,b){var f=new ActiveXObject("htmlfile"),d,h,m,e=function(){clearTimeout(d)},j=function(){f&&(e(),c.unload_del(h),m.parentNode.removeChild(m),m=f=null,CollectGarbage())};f.open();f.write('<html><script>document.domain="'+document.domain+'";<\/script></html>');f.close();f.parentWindow._jp=g._jp; +var i=f.createElement("div");return f.body.appendChild(i),m=f.createElement("iframe"),i.appendChild(m),m.src=a,d=setTimeout(function(){f&&(j(),b("timeout"))},15E3),h=c.unload_add(j),{post:function(a,b){try{m&&m.contentWindow&&m.contentWindow.postMessage(a,b)}catch(c){}},cleanup:j,loaded:e}};i=function(){};i.prototype=new k(["chunk","finish"]);i.prototype._start=function(a,b,f,d){var h=this;try{h.xhr=new XMLHttpRequest}catch(m){}if(!h.xhr)try{h.xhr=new g.ActiveXObject("Microsoft.XMLHTTP")}catch(e){}if(g.ActiveXObject|| +g.XDomainRequest)b+=(-1===b.indexOf("?")?"?":"&")+"t="+ +new Date;h.unload_ref=c.unload_add(function(){h._cleanup(!0)});try{h.xhr.open(a,b,!0)}catch(j){h.emit("finish",0,"");h._cleanup();return}if(!d||!d.no_credentials)h.xhr.withCredentials="true";if(d&&d.headers)for(var i in d.headers)h.xhr.setRequestHeader(i,d.headers[i]);h.xhr.onreadystatechange=function(){if(h.xhr){var a=h.xhr;switch(a.readyState){case 3:try{var b=a.status,c=a.responseText}catch(d){}c&&0<c.length&&h.emit("chunk",b,c);break;case 4:h.emit("finish", +a.status,a.responseText),h._cleanup(!1)}}};h.xhr.send(f)};i.prototype._cleanup=function(a){if(this.xhr){c.unload_del(this.unload_ref);this.xhr.onreadystatechange=function(){};if(a)try{this.xhr.abort()}catch(b){}this.unload_ref=this.xhr=null}};i.prototype.close=function(){this.nuke();this._cleanup(!0)};(c.XHRCorsObject=function(){var a=this,b=arguments;c.delay(function(){a._start.apply(a,b)})}).prototype=new i;(c.XHRLocalObject=function(a,b,f){var d=this;c.delay(function(){d._start(a,b,f,{no_credentials:!0})})}).prototype= +new i;i=c.XDRObject=function(a,b,f){var d=this;c.delay(function(){d._start(a,b,f)})};i.prototype=new k(["chunk","finish"]);i.prototype._start=function(a,b,f){var d=this,h=new XDomainRequest,b=b+((-1===b.indexOf("?")?"?":"&")+"t="+ +new Date),g=h.ontimeout=h.onerror=function(){d.emit("finish",0,"");d._cleanup(!1)};h.onprogress=function(){d.emit("chunk",200,h.responseText)};h.onload=function(){d.emit("finish",200,h.responseText);d._cleanup(!1)};d.xdr=h;d.unload_ref=c.unload_add(function(){d._cleanup(!0)}); +try{d.xdr.open(a,b),d.xdr.send(f)}catch(e){g()}};i.prototype._cleanup=function(a){if(this.xdr){c.unload_del(this.unload_ref);this.xdr.ontimeout=this.xdr.onerror=this.xdr.onprogress=this.xdr.onload=null;if(a)try{this.xdr.abort()}catch(b){}this.unload_ref=this.xdr=null}};i.prototype.close=function(){this.nuke();this._cleanup(!0)};c.isXHRCorsCapable=function(){return g.XMLHttpRequest&&"withCredentials"in new XMLHttpRequest?1:g.XDomainRequest&&j.domain?2:s.enabled()?3:4};var e=function(a,b,f){var d=this, +h;d._options={devel:!1,debug:!1,protocols_whitelist:[],info:void 0,rtt:void 0};f&&c.objectExtend(d._options,f);d._base_url=c.amendUrl(a);d._server=d._options.server||c.random_number_string(1E3);d._options.protocols_whitelist&&d._options.protocols_whitelist.length?h=d._options.protocols_whitelist:("string"==typeof b&&0<b.length?h=[b]:c.isArray(b)?h=b:h=null,h&&d._debug('Deprecated API: Use "protocols_whitelist" option instead of supplying protocol list as a second parameter to SockJS constructor.')); +d._protocols=[];d.protocol=null;d.readyState=e.CONNECTING;d._ir=N(d._base_url);d._ir.onfinish=function(a,b){d._ir=null;a?(d._options.info&&(a=c.objectExtend(a,d._options.info)),d._options.rtt&&(b=d._options.rtt),d._applyInfo(a,b,h),d._didClose()):d._didClose(1002,"Can't connect to server",!0)}};e.prototype=new p;e.version="0.3.2";e.CONNECTING=0;e.OPEN=1;e.CLOSING=2;e.CLOSED=3;e.prototype._debug=function(){this._options.debug&&c.log.apply(c,arguments)};e.prototype._dispatchOpen=function(){this.readyState=== +e.CONNECTING?(this._transport_tref&&(clearTimeout(this._transport_tref),this._transport_tref=null),this.readyState=e.OPEN,this.dispatchEvent(new n("open"))):this._didClose(1006,"Server lost session")};e.prototype._dispatchMessage=function(a){this.readyState===e.OPEN&&this.dispatchEvent(new n("message",{data:a}))};e.prototype._dispatchHeartbeat=function(){this.readyState===e.OPEN&&this.dispatchEvent(new n("heartbeat",{}))};e.prototype._didClose=function(a,b,f){var d=this;if(d.readyState!==e.CONNECTING&& +d.readyState!==e.OPEN&&d.readyState!==e.CLOSING)throw Error("INVALID_STATE_ERR");d._ir&&(d._ir.nuke(),d._ir=null);d._transport&&(d._transport.doCleanup(),d._transport=null);var h=new n("close",{code:a,reason:b,wasClean:c.userSetCode(a)});if(!c.userSetCode(a)&&d.readyState===e.CONNECTING&&!f){if(d._try_next_protocol(h))return;h=new n("close",{code:2E3,reason:"All transports failed",wasClean:!1,last_event:h})}d.readyState=e.CLOSED;c.delay(function(){d.dispatchEvent(h)})};e.prototype._didMessage=function(a){switch(a.slice(0, +1)){case "o":this._dispatchOpen();break;case "a":for(var a=JSON.parse(a.slice(1)||"[]"),b=0;b<a.length;b++)this._dispatchMessage(a[b]);break;case "m":a=JSON.parse(a.slice(1)||"null");this._dispatchMessage(a);break;case "c":a=JSON.parse(a.slice(1)||"[]");this._didClose(a[0],a[1]);break;case "h":this._dispatchHeartbeat()}};e.prototype._try_next_protocol=function(a){var b=this;b.protocol&&(b._debug("Closed transport:",b.protocol,""+a),b.protocol=null);for(b._transport_tref&&(clearTimeout(b._transport_tref), +b._transport_tref=null);;){a=b.protocol=b._protocols.shift();if(!a)return!1;if(e[a]&&!0===e[a].need_body&&(!j.body||"undefined"!=typeof j.readyState&&"complete"!==j.readyState))return b._protocols.unshift(a),b.protocol="waiting-for-load",c.attachEvent("load",function(){b._try_next_protocol()}),!0;if(e[a]&&e[a].enabled(b._options)){b._transport_tref=c.delay((b._options.rto||0)*(e[a].roundTrips||1)||5E3,function(){b.readyState===e.CONNECTING&&b._didClose(2007,"Transport timeouted")});var f=c.random_string(8), +f=b._base_url+"/"+b._server+"/"+f;return b._debug("Opening transport:",a," url:"+f," RTO:"+b._options.rto),b._transport=new e[a](b,f,b._base_url),!0}b._debug("Skipping transport:",a)}};e.prototype.close=function(a,b){if(a&&!c.userSetCode(a))throw Error("INVALID_ACCESS_ERR");return this.readyState!==e.CONNECTING&&this.readyState!==e.OPEN?!1:(this.readyState=e.CLOSING,this._didClose(a||1E3,b||"Normal closure"),!0)};e.prototype.send=function(a){if(this.readyState===e.CONNECTING)throw Error("INVALID_STATE_ERR"); +return this.readyState===e.OPEN&&this._transport.doSend(c.quote(""+a)),!0};e.prototype._applyInfo=function(a,b,f){this._options.info=a;this._options.rtt=b;this._options.rto=c.countRTO(b);this._options.info.null_origin=!j.domain;b=c.probeProtocols();this._protocols=c.detectProtocols(b,f,a)};i=e.websocket=function(a,b){var f=this,d=b+"/websocket";"https"===d.slice(0,5)?d="wss"+d.slice(5):d="ws"+d.slice(4);f.ri=a;f.url=d;f.ws=new (g.WebSocket||g.MozWebSocket)(f.url);f.ws.onmessage=function(a){f.ri._didMessage(a.data)}; +f.unload_ref=c.unload_add(function(){f.ws.close()});f.ws.onclose=function(){f.ri._didMessage(c.closeFrame(1006,"WebSocket connection broken"))}};i.prototype.doSend=function(a){this.ws.send("["+a+"]")};i.prototype.doCleanup=function(){var a=this.ws;a&&(a.onmessage=a.onclose=null,a.close(),c.unload_del(this.unload_ref),this.unload_ref=this.ri=this.ws=null)};i.enabled=function(){return!!g.WebSocket||!!g.MozWebSocket};i.roundTrips=2;var l=function(){};l.prototype.send_constructor=function(a){this.send_buffer= +[];this.sender=a};l.prototype.doSend=function(a){this.send_buffer.push(a);this.send_stop||this.send_schedule()};l.prototype.send_schedule_wait=function(){var a=this,b;a.send_stop=function(){a.send_stop=null;clearTimeout(b)};b=c.delay(25,function(){a.send_stop=null;a.send_schedule()})};l.prototype.send_schedule=function(){var a=this;if(0<a.send_buffer.length){var b="["+a.send_buffer.join(",")+"]";a.send_stop=a.sender(a.trans_url,b,function(){a.send_stop=null;a.send_schedule_wait()});a.send_buffer= +[]}};l.prototype.send_destructor=function(){this._send_stop&&this._send_stop();this._send_stop=null};var O=function(a,b,f){if(!("_send_form"in this)){var d=this._send_form=j.createElement("form"),h=this._send_area=j.createElement("textarea");h.name="d";d.style.display="none";d.style.position="absolute";d.method="POST";d.enctype="application/x-www-form-urlencoded";d.acceptCharset="UTF-8";d.appendChild(h);j.body.appendChild(d)}var d=this._send_form,h=this._send_area,g="a"+c.random_string(8);d.target= +g;d.action=a+"/jsonp_send?i="+g;var e;try{e=j.createElement('<iframe name="'+g+'">')}catch(i){e=j.createElement("iframe"),e.name=g}e.id=g;d.appendChild(e);e.style.display="none";try{h.value=b}catch(k){c.log("Your browser is seriously broken. Go home! "+k.message)}d.submit();var l=function(){e.onerror&&(e.onreadystatechange=e.onerror=e.onload=null,c.delay(500,function(){e.parentNode.removeChild(e);e=null}),h.value="",f())};return e.onerror=e.onload=l,e.onreadystatechange=function(){"complete"==e.readyState&& +l()},l},P=function(a){return function(b,c,d){return(new a("POST",b+"/xhr_send",c)).onfinish=function(a){d(a)},function(a){d(0,a)}}},i=e["jsonp-polling"]=function(a,b){c.polluteGlobalNamespace();this.ri=a;this.trans_url=b;this.send_constructor(O);this._schedule_recv()};i.prototype=new l;i.prototype._schedule_recv=function(){var a=this,b=a.trans_url+"/jsonp",f="a"+c.random_string(6),b=b+"?c="+escape("_jp."+f),d=function(b){delete g._jp[f];a._recv_stop=null;b&&(a._is_closing||a.ri._didMessage(b));a._is_closing|| +a._schedule_recv()},e,m=j.createElement("script"),i,k=function(a){i&&(i.parentNode.removeChild(i),i=null);m&&(clearTimeout(e),m.parentNode.removeChild(m),m.onreadystatechange=m.onerror=m.onload=m.onclick=null,m=null,d(a),d=null)},l=!1,p=null;m.id="a"+c.random_string(8);m.src=b;m.type="text/javascript";m.charset="UTF-8";m.onerror=function(){p||(p=setTimeout(function(){l||k(c.closeFrame(1006,"JSONP script loaded abnormally (onerror)"))},1E3))};m.onload=function(){k(c.closeFrame(1006,"JSONP script loaded abnormally (onload)"))}; +m.onreadystatechange=function(){if(/loaded|closed/.test(m.readyState)){if(m&&m.htmlFor&&m.onclick){l=!0;try{m.onclick()}catch(a){}}m&&k(c.closeFrame(1006,"JSONP script loaded abnormally (onreadystatechange)"))}};if("undefined"==typeof m.async&&j.attachEvent)if(/opera/i.test(navigator.userAgent))i=j.createElement("script"),i.text="try{var a = document.getElementById('"+m.id+"'); if(a)a.onerror();}catch(x){};",m.async=i.async=!1;else{try{m.htmlFor=m.id,m.event="onclick"}catch(n){}m.async=!0}"undefined"!= +typeof m.async&&(m.async=!0);e=setTimeout(function(){k(c.closeFrame(1006,"JSONP script loaded abnormally (timeout)"))},35E3);b=j.getElementsByTagName("head")[0];b=(b.insertBefore(m,b.firstChild),i&&b.insertBefore(i,b.firstChild),k);g._jp[f]=b;a._recv_stop=function(){g._jp[f]&&g._jp[f](c.closeFrame(1E3,"JSONP user aborted read"))}};i.enabled=function(){return!0};i.need_body=!0;i.prototype.doCleanup=function(){this._is_closing=!0;this._recv_stop&&this._recv_stop();this.ri=this._recv_stop=null;this.send_destructor()}; +i=function(){};i.prototype=new l;i.prototype.run=function(a,b,c,d,e){this.ri=a;this.trans_url=b;this.send_constructor(P(e));this.poll=new E(a,d,b+c,e)};i.prototype.doCleanup=function(){this.poll&&(this.poll.abort(),this.poll=null)};var w=e["xhr-streaming"]=function(a,b){this.run(a,b,"/xhr_streaming",y,c.XHRCorsObject)};w.prototype=new i;w.enabled=function(){return g.XMLHttpRequest&&"withCredentials"in new XMLHttpRequest&&!/opera/i.test(navigator.userAgent)};w.roundTrips=2;w.need_body=!0;l=e["xdr-streaming"]= +function(a,b){this.run(a,b,"/xhr_streaming",y,c.XDRObject)};l.prototype=new i;l.enabled=function(){return!!g.XDomainRequest};l.roundTrips=2;var F=e["xhr-polling"]=function(a,b){this.run(a,b,"/xhr",y,c.XHRCorsObject)};F.prototype=new i;F.enabled=w.enabled;F.roundTrips=2;w=e["xdr-polling"]=function(a,b){this.run(a,b,"/xhr",y,c.XDRObject)};w.prototype=new i;w.enabled=l.enabled;w.roundTrips=2;var s=function(){};s.prototype.i_constructor=function(a,b,f){var d=this;d.ri=a;d.origin=c.getOrigin(f);d.base_url= +f;d.trans_url=b;a=f+"/iframe.html";d.ri._options.devel&&(a+="?t="+ +new Date);d.window_id=c.random_string(8);a+="#"+d.window_id;d.iframeObj=c.createIframe(a,function(a){d.ri._didClose(1006,"Unable to load an iframe ("+a+")")});d.onmessage_cb=c.bind(d.onmessage,d);c.attachMessage(d.onmessage_cb)};s.prototype.doCleanup=function(){if(this.iframeObj){c.detachMessage(this.onmessage_cb);try{this.iframeObj.iframe.contentWindow&&this.postMessage("c")}catch(a){}this.iframeObj.cleanup();this.onmessage_cb=this.iframeObj= +this.iframeObj=null}};s.prototype.onmessage=function(a){if(a.origin===this.origin){var b=a.data.slice(0,8),c=a.data.slice(8,9),a=a.data.slice(9);if(b===this.window_id)switch(c){case "s":this.iframeObj.loaded();this.postMessage("s",JSON.stringify([e.version,this.protocol,this.trans_url,this.base_url]));break;case "t":this.ri._didMessage(a)}}};s.prototype.postMessage=function(a,b){this.iframeObj.post(this.window_id+a+(b||""),this.origin)};s.prototype.doSend=function(a){this.postMessage("m",a)};s.enabled= +function(){var a=navigator&&navigator.userAgent&&-1!==navigator.userAgent.indexOf("Konqueror");return("function"==typeof g.postMessage||"object"==typeof g.postMessage)&&!a};var G,H=function(a,b){parent!==g?parent.postMessage(G+a+(b||""),"*"):c.log("Can't postMessage, no parent window.",a,b)},u=function(){};u.prototype._didClose=function(a,b){H("t",c.closeFrame(a,b))};u.prototype._didMessage=function(a){H("t",a)};u.prototype._doSend=function(a){this._transport.doSend(a)};u.prototype._doCleanup=function(){this._transport.doCleanup()}; +c.parent_origin=void 0;e.bootstrap_iframe=function(){var a;G=j.location.hash.slice(1);c.attachMessage(function(b){if(b.source===parent&&("undefined"==typeof c.parent_origin&&(c.parent_origin=b.origin),b.origin===c.parent_origin)){var f=b.data.slice(0,8),d=b.data.slice(8,9),b=b.data.slice(9);if(f===G)switch(d){case "s":var h=JSON.parse(b),f=h[0],d=h[1],b=h[2],h=h[3];f!==e.version&&c.log('Incompatibile SockJS! Main site uses: "'+f+'", the iframe: "'+e.version+'".');if(!c.flatUrl(b)||!c.flatUrl(h)){c.log("Only basic urls are supported in SockJS"); +break}if(!c.isSameOriginUrl(b)||!c.isSameOriginUrl(h)){c.log("Can't connect to different domain from within an iframe. ("+JSON.stringify([g.location.href,b,h])+")");break}a=new u;a._transport=new u[d](a,b,h);break;case "m":a._doSend(b);break;case "c":a&&a._doCleanup(),a=null}}});H("s")};var z=function(a,b){var f=this;c.delay(function(){f.doXhr(a,b)})};z.prototype=new k(["finish"]);z.prototype.doXhr=function(a,b){var f=this,d=(new Date).getTime(),e=new b("GET",a+"/info"),g=c.delay(8E3,function(){e.ontimeout()}); +e.onfinish=function(a,b){clearTimeout(g);g=null;if(200===a){var c=(new Date).getTime()-d,e=JSON.parse(b);"object"!=typeof e&&(e={});f.emit("finish",e,c)}else f.emit("finish")};e.ontimeout=function(){e.close();f.emit("finish")}};var K=function(a){var b=this,f=function(){var c=new s;c.protocol="w-iframe-info-receiver";var f=function(a){"string"==typeof a&&"m"===a.substr(0,1)?(a=JSON.parse(a.substr(1)),b.emit("finish",a[0],a[1])):b.emit("finish");c.doCleanup();c=null};c.i_constructor({_options:{},_didClose:f, +_didMessage:f},a,a)};j.body?f():c.attachEvent("load",f)};K.prototype=new k(["finish"]);var L=function(){var a=this;c.delay(function(){a.emit("finish",{},2E3)})};L.prototype=new k(["finish"]);var N=function(a){if(c.isSameOriginUrl(a))return new z(a,c.XHRLocalObject);switch(c.isXHRCorsCapable()){case 1:return new z(a,c.XHRLocalObject);case 2:return new z(a,c.XDRObject);case 3:return new K(a);default:return new L}};(u["w-iframe-info-receiver"]=function(a,b,f){(new z(f,c.XHRLocalObject)).onfinish=function(b, +c){a._didMessage("m"+JSON.stringify([b,c]));a._didClose()}}).prototype.doCleanup=function(){};k=e["iframe-eventsource"]=function(){this.protocol="w-iframe-eventsource";this.i_constructor.apply(this,arguments)};k.prototype=new s;k.enabled=function(){return"EventSource"in g&&s.enabled()};k.need_body=!0;k.roundTrips=3;(u["w-iframe-eventsource"]=function(a,b){this.run(a,b,"/eventsource",I,c.XHRLocalObject)}).prototype=new i;k=e["iframe-xhr-polling"]=function(){this.protocol="w-iframe-xhr-polling";this.i_constructor.apply(this, +arguments)};k.prototype=new s;k.enabled=function(){return g.XMLHttpRequest&&s.enabled()};k.need_body=!0;k.roundTrips=3;(u["w-iframe-xhr-polling"]=function(a,b){this.run(a,b,"/xhr",y,c.XHRLocalObject)}).prototype=new i;k=e["iframe-htmlfile"]=function(){this.protocol="w-iframe-htmlfile";this.i_constructor.apply(this,arguments)};k.prototype=new s;k.enabled=function(){return s.enabled()};k.need_body=!0;k.roundTrips=3;(u["w-iframe-htmlfile"]=function(a,b){this.run(a,b,"/htmlfile",J,c.XHRLocalObject)}).prototype= +new i;var E=function(a,b,c,d){this.ri=a;this.Receiver=b;this.recv_url=c;this.AjaxObject=d;this._scheduleRecv()};E.prototype._scheduleRecv=function(){var a=this,b=a.poll=new a.Receiver(a.recv_url,a.AjaxObject);b.onmessage=function(b){a.ri._didMessage(b.data)};b.onclose=function(c){a.poll=b=b.onmessage=b.onclose=null;a.poll_is_closing||("permanent"===c.reason?a.ri._didClose(1006,"Polling error ("+c.reason+")"):a._scheduleRecv())}};E.prototype.abort=function(){this.poll_is_closing=!0;this.poll&&this.poll.abort()}; +var I=function(a){var b=this,f=new EventSource(a);f.onmessage=function(a){b.dispatchEvent(new n("message",{data:unescape(a.data)}))};b.es_close=f.onerror=function(a,e){var g=e?"user":2!==f.readyState?"network":"permanent";b.es_close=f.onmessage=f.onerror=null;f.close();f=null;c.delay(200,function(){b.dispatchEvent(new n("close",{reason:g}))})}};I.prototype=new p;I.prototype.abort=function(){this.es_close&&this.es_close({},!0)};var A,J=function(a){var b=this;c.polluteGlobalNamespace();b.id="a"+c.random_string(6, +26);a+=(-1===a.indexOf("?")?"?":"&")+"c="+escape("_jp."+b.id);if(void 0===A)if("ActiveXObject"in g)try{A=!!new ActiveXObject("htmlfile")}catch(f){}else A=!1;var d=A?c.createHtmlfile:c.createIframe,e;g._jp[b.id]={start:function(){e.loaded()},message:function(a){b.dispatchEvent(new n("message",{data:a}))},stop:function(){b.iframe_close({},"network")}};b.iframe_close=function(a,c){e.cleanup();b.iframe_close=e=null;delete g._jp[b.id];b.dispatchEvent(new n("close",{reason:c}))};e=d(a,function(){b.iframe_close({}, +"permanent")})};J.prototype=new p;J.prototype.abort=function(){this.iframe_close&&this.iframe_close({},"user")};var y=function(a,b){var c=this,d=0;c.xo=new b("POST",a,null);c.xo.onchunk=function(a,b){if(200===a)for(;;){var e=b.slice(d),g=e.indexOf("\n");if(-1===g)break;d+=g+1;e=e.slice(0,g);c.dispatchEvent(new n("message",{data:e}))}};c.xo.onfinish=function(a,b){c.xo.onchunk(a,b);c.xo=null;c.dispatchEvent(new n("close",{reason:200===a?"network":"permanent"}))}};return y.prototype=new p,y.prototype.abort= +function(){this.xo&&(this.xo.close(),this.dispatchEvent(new n("close",{reason:"user"})),this.xo=null)},e.getUtils=function(){return c},e.getIframeTransport=function(){return s},e}();"_sockjs_onload"in window&&setTimeout(_sockjs_onload,1);"function"==typeof define&&define.amd&&define("sockjs",[],function(){return SockJS});function WebTCP(j,g){this.sockets={};this.httpClients={};this.sock=new SockJS("http://"+j+":"+g+"/bridge");this.ready=!1;this.outputBuffer=[];var c=this;this.sock.onopen=function(){c.ready=!0;c.processBuffer()};this.processBuffer=function(){if(0<this.outputBuffer.length&&this.ready)for(var c in this.outputBuffer)this.sock.send(this.outputBuffer[c])};this.sock.onmessage=function(g){g.data=JSON.parse(g.data);g.data.sID?(sID=g.data.sID,c.sockets[sID].onEvent(g.data.eventName,g.data.data)):g.data.cID&& +(cID=g.data.cID,c.httpClients[cID].onEvent(g.data.eventName,g.data.data))};this.sock.onclose=function(){console.log("close")};this.createSocket=function(c,g,j){return new Socket(this,c,g,j)};this.createHTTPClient=function(){return new Http(this)}};function WebTCPIO(){this.onEvent=function(j,g){this["on"+j](g);if(this["on"+j+"Custom"])this["on"+j+"Custom"](g)};this.on=function(j,g){this["on"+j+"Custom"]=g};this.rpc=function(j,g){var c=this.createPacket();c.command=j;c.args=g;this.send(JSON.stringify(c));return c.id};this.write=function(j){var g=this.createPacket();g.data=j;this.send(JSON.stringify(g));return g.id};this.send=function(j){if(this.webtcp.ready)this.webtcp.sock.send(j);else{if(this.webtcp.outputBuffer>this.webtcp.BUFF_SIZE)throw"Output buffer is already full, but sockJS connection is not ready yet"; +this.webtcp.outputBuffer.push(j)}};this.onerror=function(j){throw j;}};
\ No newline at end of file diff --git a/src/main/java/com/seaofnodes/simple/Ary.java b/src/main/java/com/seaofnodes/simple/Ary.java index 73ada8e0..f43179ad 100644 --- a/src/main/java/com/seaofnodes/simple/Ary.java +++ b/src/main/java/com/seaofnodes/simple/Ary.java @@ -29,6 +29,10 @@ public class Ary<E> extends AbstractList<E> implements List<E> { range_check(i); return _es[i]; } + public E atX( int i ) { + if( i >= _len ) return null; + return _es[i]; + } /** @return last element */ public E last( ) { return at(_len-1); } @@ -76,6 +80,17 @@ public class Ary<E> extends AbstractList<E> implements List<E> { return e; } + /** Slow, linear-time, element insertion. Preserves order. + * @param i where to insert + * @return element inserted */ + public E insert( E e, int i ) { + setLen(_len+1); + System.arraycopy(_es,i,_es,i+1,_len-i); + _es[i] = e; + return e; + } + + /** Remove all elements */ public void clear( ) { _len=0; } @@ -158,10 +173,6 @@ public class Ary<E> extends AbstractList<E> implements List<E> { /** @return compact array version */ public E[] asAry() { return Arrays.copyOf(_es,_len); } - /** Sorts in-place - * @param c Comparator to sort by */ - public void sort_update(Comparator<? super E> c ) { Arrays.sort(_es, 0, _len, c); } - /** Find the first matching element using ==, or -1 if none. Note that * most del calls shuffle the list, so the first element might be random. * @param e Element to find diff --git a/src/main/java/com/seaofnodes/simple/IterPeeps.java b/src/main/java/com/seaofnodes/simple/IterPeeps.java index 087a990b..a21fb517 100644 --- a/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ b/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -1,7 +1,7 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.*; - import java.util.Arrays; import java.util.BitSet; import java.util.Random; @@ -46,7 +46,12 @@ public class IterPeeps { private final WorkList<Node> _work; - IterPeeps( long seed ) { _work = new WorkList<>(seed); } + private final WorkList<CallEndNode> _workInline; + + public IterPeeps( long seed ) { + _work = new WorkList<>(seed); + _workInline = new WorkList<>(seed); + } public <N extends Node> N add( N n ) { return (N)_work.push(n); } @@ -56,12 +61,24 @@ public class IterPeeps { * Iterate peepholes to a fixed point */ public void iterate( CodeGen code ) { + while( true ) { + iteratePeeps(code); + CallEndNode cend; + while( (cend=_workInline.pop())!=null ) + if( cend.inlineSmall() ) break; + if( cend==null ) return; + } + } + + // Only try growing inlines once the shrinking peepholes reach a fixed point. + private void iteratePeeps( CodeGen code ) { assert progressOnList(code); int cnt=0; Node n; while( (n=_work.pop()) != null ) { if( n.isDead() ) continue; + if( n instanceof CallEndNode cend ) _workInline.push(cend); cnt++; // Useful for debugging, searching which peephole broke things var obs = CodeGen.CODE._obs; if( obs != null ) obs.before(n); @@ -141,7 +158,7 @@ public class IterPeeps { /* Useful stat - how many nodes are processed in the post parse iterative opt */ private long _totalWork = 0; - WorkList() { this(123); } + public WorkList() { this(123); } WorkList(long seed) { _es = new Node[1]; _len=0; @@ -182,7 +199,7 @@ public class IterPeeps { /** * Removes a random Node from the WorkList; null if WorkList is empty */ - E pop() { + public E pop() { if( _len == 0 ) return null; int idx = _R.nextInt(_len); E x = (E)_es[idx]; diff --git a/src/main/java/com/seaofnodes/simple/Parser.java b/src/main/java/com/seaofnodes/simple/Parser.java index 00aa117e..697e84ce 100644 --- a/src/main/java/com/seaofnodes/simple/Parser.java +++ b/src/main/java/com/seaofnodes/simple/Parser.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; import java.text.ParseException; @@ -1435,7 +1436,7 @@ public class Parser { // A call may update any alias, so its memory result is the new whole state. _scope.mem(new ProjNode(cend,1,ScopeNode.MEM0).peephole()); // Call result - return new ProjNode(cend,2,null).peephole(); + return new ProjNode(cend,2,"#2").peephole(); } /** diff --git a/src/main/java/com/seaofnodes/simple/RegMask.java b/src/main/java/com/seaofnodes/simple/RegMask.java deleted file mode 100644 index 073b472b..00000000 --- a/src/main/java/com/seaofnodes/simple/RegMask.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.seaofnodes.simple; - -import java.util.BitSet; - -// A "register mask" - 1 bit set for each allowed register. In addition "stack -// slot" registers may be allowed, effectively making the set infinite. -public class RegMask { - - public static final RegMask EMPTY = new RegMask(); - - final BitSet _bits; - - public RegMask(long x ) { _bits = BitSet.valueOf(new long[]{x}); } - public RegMask(long[] xs) { _bits = BitSet.valueOf( xs); } - - public RegMask or(RegMask other) { - BitSet resultBits = (BitSet)this._bits.clone(); - resultBits.or(other._bits); - return new RegMask(resultBits); - } - - // Internal constructor - RegMask() { _bits = new BitSet(); } - RegMask(BitSet bs) { _bits = bs; } - - public boolean get(int r) { return _bits.get(r); } - - // Defensive writable copy - public RegMaskRW copy() { return new RegMaskRW( (BitSet)_bits.clone() ); } - - boolean size1() { return _bits.cardinality()==1; } -} - -class RegMaskRW extends RegMask { - public RegMaskRW() { super(); } - public RegMaskRW(long x) { super(new long[]{x}); } - public RegMaskRW(long[] xs) { super(xs); } - public RegMaskRW(BitSet bs) { super(bs); } - public void set(int r) { _bits.set(r); } - public void clr(int r) { _bits.clear(r); } - public void set(int r, boolean b) { _bits.set(r,b); } - public RegMaskRW and( RegMask r ) { - if( r==null ) return this; - _bits.and(r._bits); - return this; - } -} diff --git a/src/main/java/com/seaofnodes/simple/SB.java b/src/main/java/com/seaofnodes/simple/SB.java index 7d580c21..a45f0ddf 100644 --- a/src/main/java/com/seaofnodes/simple/SB.java +++ b/src/main/java/com/seaofnodes/simple/SB.java @@ -40,7 +40,6 @@ public final class SB { } // Fixed width field public SB fix( int sz, String s ) { - assert sz >=0; for( int i=0; i<sz; i++ ) _sb.append( i < s.length() ? s.charAt(i) : ' '); return this; diff --git a/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java b/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java new file mode 100644 index 00000000..746a0e6a --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java @@ -0,0 +1,96 @@ +package com.seaofnodes.simple.codegen; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeMem; + +abstract public class BuildLRG { + // Compute live ranges in a single forwards pass. Every def is a new live + // range except live ranges are joined at Phi. Also, gather the + // intersection of register masks in each live range. Example: "free + // always zero" register forces register zero, and calling conventions + // typically force registers. + + // Sets FAILED to the set of hard-conflicts (means no need for an IFG, + // since it will not color, just split the conflicted ranges now). Returns + // true if no hard-conflicts, although we still might not color. + public static boolean run(int round, RegAlloc alloc) { + for( Node bb : alloc._code._cfg ) + for( Node n : bb.outs() ) { + // A synthetic never-exit value has no runtime definition. + if( n._type==Type.TOP ) continue; + if( n instanceof PhiNode phi && !(phi._type instanceof TypeMem) ) { + // All real Phi inputs end up with the same LRG. + // Pass 1: find any pre-existing LRG, to avoid make-then-Union a LRG + LRG lrg = alloc.lrg(phi); + if( lrg == null ) + for( int i=1; i<phi.nIns(); i++ ) + if( (lrg = alloc.lrg(phi.in(i))) != null ) + break; + // If none, make one. + if( lrg==null ) lrg = alloc.newLRG(n); + if( phi instanceof MachNode mach ) defLRG(alloc,n); + // Pass 2: everybody uses the same LRG + lrg=alloc.union(lrg,phi); + for( int i=phi instanceof ParmNode ? 2 : 1; i<n.nIns(); i++ ) + // TOP keeps the type merge neutral, but needs no register. + if( n.in(i)._type!=Type.TOP ) + lrg=alloc.union(lrg,n.in(i)); + if( lrg._mask!=null && lrg._mask.isEmpty() ) + alloc.fail(lrg); + + } else if( n instanceof MachNode mach ) { + // Attempt to commute ops to keep live ranges compatible. + if( mach.commutes() && n.nOuts()==1 ) { + int uidx = n.out(0)._inputs.find(n); + RegMask mask1 = n.in (1) instanceof MachNode machx ? machx.outregmap() : alloc.lrg(n.in(1))._mask; + RegMask mask2 = n.in (2) instanceof MachNode machx ? machx.outregmap() : alloc.lrg(n.in(2))._mask; + RegMask masko = n.out(0) instanceof MachNode machx ? machx.regmap(uidx): mach.outregmap(); + if( masko!=null && !mask1.overlap(masko) && mask2.overlap(masko) ) + n.swap12(); + } + + // Define live range + defLRG(alloc,n); + + // Now, look in the opposite direction. How are incoming + // LRGs affected by this node: For all uses, make live lrgs + for( int i=1; i<n.nIns(); i++ ) + if( n.in(i)!=null ) { + LRG lrg2 = alloc.lrg(n.in(i)); + if( lrg2 != null ) { // Anti-dep or other, no LRG + RegMask use_mask = mach.regmap(i); + if( use_mask!=null && !lrg2.machUse(mach,(short)i,use_mask.size1()).and(use_mask) ) + alloc.fail(lrg2); // Empty register mask, must split + } + } + + } + + // MultiNodes have projections which set registers + if( n instanceof MultiNode && !(n instanceof CFGNode) ) + for( Node proj : n.outs() ) + if( proj instanceof MachNode ) + defLRG(alloc,proj); + } + + // Collect live ranges + alloc.unify(); + + return alloc.success(); + } + + private static void defLRG( RegAlloc alloc, Node n ) { + MachNode mach = (MachNode)n; + RegMask def_mask = mach.outregmap(); + if( def_mask == null ) return; + LRG lrg = mach.twoAddress() == 0 + ? alloc.newLRG(n) // Define a new LRG for N + : alloc.lrg2(n,mach.twoAddress()); // Use the matching 2-adr input + // Record mask and mach + if( !lrg.machDef(mach,def_mask.size1()).and(def_mask) ) + alloc.fail(lrg); // Empty register mask, must split + } + +} diff --git a/src/main/java/com/seaofnodes/simple/CodeGen.java b/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java index 0a3a60e8..78cf4c06 100644 --- a/src/main/java/com/seaofnodes/simple/CodeGen.java +++ b/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java @@ -1,9 +1,10 @@ - package com.seaofnodes.simple; +package com.seaofnodes.simple.codegen; import com.seaofnodes.graph.GraphObserver; +import com.seaofnodes.simple.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.node.cpus.x86_64_v2.MemOpX86; +import com.seaofnodes.simple.print.*; import com.seaofnodes.simple.type.*; import java.util.BitSet; import java.util.HashMap; @@ -29,14 +30,11 @@ public class CodeGen { Parse, // Parse ASCII text into Sea-of-Nodes IR Opto, // Run ideal optimizations TypeCheck, // Last check for bad programs - InstructionSelection, // Convert to target hardware nodes + InstSelect, // Convert to target hardware nodes Schedule, // Global schedule (code motion) nodes LocalSched, // Local schedule - RegAlloc; // Register allocation - } - public enum CallingConv { - Win64, - SystemV + RegAlloc, // Register allocation + Encoding, // Encoding } public Phase _phase; @@ -45,7 +43,6 @@ public class CodeGen { public final String _src; // Compile-time known initial argument type public final TypeInteger _arg; - public CallingConv _callingConv; // --------------------------- public CodeGen( String src ) { this(src, TypeInteger.BOT, 123L ); } @@ -67,9 +64,11 @@ public class CodeGen { * A counter, for unique node id generation. Starting with value 1, to * avoid bugs confusing node ID 0 with uninitialized values. */ - private int _uid = 1; + public int _uid = 1; public int UID() { return _uid; } - public int getUID() { return _uid++; } + public int getUID() { + return _uid++; + } // Next available memory alias number private int _alias = 2; // 0 is for control, 1 for memory @@ -121,11 +120,14 @@ public class CodeGen { public final Parser P; // Parse ASCII text into Sea-of-Nodes IR + public int _tParse; public CodeGen parse() { assert _phase == null; _phase = Phase.Parse; + long t0 = System.currentTimeMillis(); P.parse(); + _tParse = (int)(System.currentTimeMillis() - t0); if( _obs != null ) _obs.phase("Parse"); return this; } @@ -143,12 +145,15 @@ public class CodeGen { public void iterNop() { if( !_midAssert ) _iter_nop_cnt++; } // Run ideal optimizations + public int _tOpto; public CodeGen opto() { assert _phase == Phase.Parse; _phase = Phase.Opto; + long t0 = System.currentTimeMillis(); // Pessimistic peephole optimization on a worklist _iter.iterate(this); + _tOpto = (int)(System.currentTimeMillis() - t0); // TODO: // Optimistic @@ -163,17 +168,20 @@ public class CodeGen { // --------------------------- // Last check for bad programs + int _tTypeCheck; public CodeGen typeCheck() { // Demand phase Opto for cleaning up dead control flow at least, // required for the following GCM. assert _phase.ordinal() <= Phase.Opto.ordinal(); _phase = Phase.TypeCheck; + long t0 = System.currentTimeMillis(); Parser.ParseException err = _stop.walk(n -> { Parser.ParseException msg = n.err(); if( msg != null && _obs != null ) _obs.error(n, msg.getMessage()); return msg; }); + _tTypeCheck = (int)(System.currentTimeMillis() - t0); if( err != null ) throw err; if( _obs != null ) _obs.phase("TypeCheck"); @@ -184,25 +192,26 @@ public class CodeGen { // --------------------------- // Code generation CPU target public Machine _mach; + // + public String _callingConv; // Convert to target hardware nodes - public CodeGen instSelect( String cpu, String callingConv ) { + public int _tInsSel; + public CodeGen instSelect( String base, String cpu, String callingConv ) { assert _phase.ordinal() <= Phase.TypeCheck.ordinal(); - _phase = Phase.InstructionSelection; + _phase = Phase.InstSelect; - _callingConv = switch(callingConv) { - case "SystemV" -> CallingConv.SystemV; - case "Win64" -> CallingConv.Win64; - default -> throw Utils.TODO(); // Unknown calling convention - }; + _callingConv = callingConv; + // Not timing the class load... // Look for CPU in fixed named place: // com.seaofnodes.simple.node.cpus."cpu"."cpu.class" - String clzFile = "com.seaofnodes.simple.node.cpus."+cpu+"."+cpu; + String clzFile = base+"."+cpu+"."+cpu; try { _mach = ((Class<Machine>) Class.forName( clzFile )).getDeclaredConstructor().newInstance(); } catch( Exception e ) { throw new RuntimeException(e); } // Convert to machine ops + long t0 = System.currentTimeMillis(); _uid = 1; // All new machine nodes reset numbering var map = new IdentityHashMap<Node,Node>(); _instSelect( _stop, map ); @@ -210,6 +219,7 @@ public class CodeGen { _start = (StartNode)map.get(_start); _instOuts(_stop,visit()); _visit.clear(); + _tInsSel = (int)(System.currentTimeMillis() - t0); return this; } @@ -220,10 +230,10 @@ public class CodeGen { Node x = map.get(n); if( x !=null ) return x; // Been there, done that - // If n is a MachConcrete, then its part of a multi-node expansion. + // If n is a MachNode already, then its part of a multi-node expansion. // It does not need instruction selection (already selected!) // but it does need its inputs walked. - if( n instanceof MachConcreteNode || n instanceof MemOpX86) { + if( n instanceof MachNode ) { for( int i=0; i < n.nIns(); i++ ) n._inputs.set(i, _instSelect(n.in(i),map) ); return n; @@ -256,49 +266,89 @@ public class CodeGen { // --------------------------- // Control Flow Graph in RPO order. - Ary<CFGNode> _cfg = new Ary<>(CFGNode.class); + int _tGCM; + public Ary<CFGNode> _cfg = new Ary<>(CFGNode.class); // Global schedule (code motion) nodes public CodeGen GCM() { - assert _phase.ordinal() <= Phase.InstructionSelection.ordinal(); + assert _phase.ordinal() <= Phase.InstSelect.ordinal(); _phase = Phase.Schedule; + long t0 = System.currentTimeMillis(); // Build the loop tree, fix never-exit loops _start.buildLoopTree(_stop); GlobalCodeMotion.buildCFG(this); + _tGCM = (int)(System.currentTimeMillis() - t0); return this; } // --------------------------- // Local (basic block) scheduler phase, a classic list scheduler + int _tLocal; public CodeGen localSched() { assert _phase == Phase.Schedule; _phase = Phase.LocalSched; + long t0 = System.currentTimeMillis(); ListScheduler.sched(this); + _tLocal = (int)(System.currentTimeMillis() - t0); return this; } // --------------------------- + // Register Allocation + int _tRegAlloc; + public RegAlloc _regAlloc; + public CodeGen regAlloc() { + assert _phase == Phase.LocalSched; + _phase = Phase.RegAlloc; + long t0 = System.currentTimeMillis(); + _regAlloc = new RegAlloc(this); + _regAlloc.regAlloc(); + _tRegAlloc = (int)(System.currentTimeMillis() - t0); + return this; + } public String reg(Node n) { - if( _phase == Phase.RegAlloc ) throw Utils.TODO(); + if( _phase.ordinal() >= Phase.RegAlloc.ordinal() ) { + String s = _regAlloc.reg(n); + if( s!=null ) return s; + } return "N"+ n._nid; } + // --------------------------- + // Encoding + int _tEncode; + public CodeGen encode() { + assert _phase == Phase.RegAlloc; + _phase = Phase.Encoding; + long t0 = System.currentTimeMillis(); + + + //_tEncode = (int)(System.currentTimeMillis() - t0); + //throw Utils.TODO(); + return this; + } + // Encoded binary, no relocation info + public byte[] binary() { throw Utils.TODO(); } // --------------------------- SB asm(SB sb) { return ASMPrinter.print(sb,this); } - String asm() { return asm(new SB()).toString(); } + public String asm() { return asm(new SB()).toString(); } // Testing shortcuts - Node ctrl() { return _stop.ret().ctrl(); } - Node expr() { return _stop.ret().expr(); } - String print() { return _stop.print(); } + public Node ctrl() { return _stop.ret().ctrl(); } + public Node expr() { return _stop.ret().expr(); } + public String print() { return _stop.print(); } // Debugging helper - @Override public String toString() { return IRPrinter.prettyPrint(this); } + @Override public String toString() { + return _phase.ordinal() > Phase.Schedule.ordinal() + ? IRPrinter._prettyPrint( this ) + : IRPrinter.prettyPrint(this); + } // Debugging helper public Node f(int idx) { return _stop.find(idx); } diff --git a/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java b/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java index 95ada83d..52906afb 100644 --- a/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java +++ b/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java @@ -1,6 +1,8 @@ - package com.seaofnodes.simple; +package com.seaofnodes.simple.codegen; +import com.seaofnodes.simple.Ary; import com.seaofnodes.simple.IterPeeps.WorkList; +import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; import java.util.*; @@ -14,27 +16,28 @@ public abstract class GlobalCodeMotion { Ary<CFGNode> rpo = new Ary<>(CFGNode.class); _rpo_cfg(null, code._start, code.visit(), rpo); // Reverse in-place - for( int i=0; i< rpo.size()>>1; i++ ) { - int j = rpo.size()-1-i; - CFGNode tmp = rpo.get(i); - rpo.set(i, rpo.get(j)); - rpo.set(j, tmp); - } + for( int i=0; i< rpo.size()>>1; i++ ) + rpo.swap(i,rpo.size()-1-i); // Set global CFG code._cfg = rpo; schedEarly(code); + + // Break up shared global constants by functions breakUpGlobalConstants(code._start); + code._visit.clear(); schedLate (code); } // Post-Order of CFG private static void _rpo_cfg(CFGNode def, Node use, BitSet visit, Ary<CFGNode> rpo) { + if( use instanceof CallNode call ) call.unlink_all(); if( !(use instanceof CFGNode cfg) || visit.get(cfg._nid) ) return; // Been there, done that - if( def instanceof CallNode && cfg instanceof FunNode ) - return; // Ignore linked function calls + if( def instanceof ReturnNode && use instanceof CallEndNode ) + return; + assert !( def instanceof CallNode && use instanceof FunNode ); visit.set(cfg._nid); for( Node useuse : cfg._outputs ) _rpo_cfg(cfg,useuse,visit,rpo); @@ -57,13 +60,13 @@ public abstract class GlobalCodeMotion { for( Node con : cons ) for( Node use : con._outputs.asAry() ) { if( use==null || globals.containsKey(use) ) continue; - for( int i=0; i<use.nIns(); i++ ) { - if( use.in(i)!=con ) continue; - FunNode fun = useFun(use,i); - if( fun==null ) continue; // Unknown-caller types stay global. - var local = copies.computeIfAbsent(fun,f -> new IdentityHashMap<>()); - use.setDef(i,cloneGlobal(con,fun,globals,local)); - } + FunNode fun = useFun(use); + if( fun==null ) continue; // Global metadata has no function owner. + var local = copies.computeIfAbsent(fun,f -> new IdentityHashMap<>()); + Node copy = cloneGlobal(con,fun,globals,local); + for( int i=0; i<use.nIns(); i++ ) + if( use.in(i)==con ) + use.setDef(i,copy); } for( Node con : cons ) con.unkill(); } @@ -81,10 +84,10 @@ public abstract class GlobalCodeMotion { return copy; } - private static FunNode useFun(Node use, int idx) { + private static FunNode useFun(Node use) { if( use instanceof ReturnNode ret ) return ret.fun(); - // Calls are still linked: a Parm input is evaluated in its caller. - CFGNode cfg = use instanceof ParmNode parm ? parm.fun().cfg(idx) : use.cfg0(); + if( use instanceof ParmNode parm ) return parm.fun(); + CFGNode cfg = use.cfg0(); while( cfg!=null && !(cfg instanceof FunNode) ) cfg = cfg.idom(); return (FunNode)cfg; @@ -101,31 +104,31 @@ public abstract class GlobalCodeMotion { for( CFGNode cfg : code._cfg ) { cfg.loopDepth(); for( Node n : cfg._inputs ) - _schedEarly(n,code._visit ); + _schedEarly(n,code ); // In dead infinite loops, entire code blocks may be unreachable // from below. Reach down from the CFG to their Phis so their // inputs are scheduled too. if( cfg instanceof RegionNode ) for( Node phi : cfg._outputs ) if( phi instanceof PhiNode ) - _schedEarly(phi,code._visit ); + _schedEarly(phi,code ); } } - private static void _schedEarly(Node n, BitSet visit) { - if( n==null || visit.get(n._nid) ) return; // Been there, done that + private static void _schedEarly(Node n, CodeGen code) { + if( n==null || code._visit.get(n._nid) ) return; // Been there, done that assert !(n instanceof CFGNode); - visit.set(n._nid); + code._visit.set(n._nid); // Schedule inputs first, except Phis: following their backedges would // enter a data cycle before its control has been scheduled. for( Node def : n._inputs ) if( def!=null && !(def instanceof PhiNode) ) - _schedEarly(def,visit); + _schedEarly(def,code); // An existing edge 0 already supplies control (or a Phi/Proj binding). if( n.in(0)==null ) { // Schedule at deepest input - CFGNode early = CodeGen.CODE._start; // Maximally early, lowest idepth + CFGNode early = code._start; // Maximally early, lowest idepth for( int i=1; i<n.nIns(); i++ ) if( n.in(i)!=null && n.in(i).cfg0().idepth() > early.idepth() ) early = n.in(i).cfg0(); // Latest/deepest input @@ -221,6 +224,7 @@ public abstract class GlobalCodeMotion { // Block of use. Normally from late[] schedule, except for Phis, which go // to the matching Region input. private static CFGNode use_block(Node n, Node use, CFGNode[] late) { + if( use instanceof ParmNode ) return late[use._nid]; if( !(use instanceof PhiNode phi) ) return late[use._nid]; CFGNode found=null; diff --git a/src/main/java/com/seaofnodes/simple/codegen/IFG.java b/src/main/java/com/seaofnodes/simple/codegen/IFG.java new file mode 100644 index 00000000..849d256e --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/codegen/IFG.java @@ -0,0 +1,512 @@ +package com.seaofnodes.simple.codegen; + +import com.seaofnodes.simple.Ary; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import java.util.BitSet; +import java.util.IdentityHashMap; + +// Interference Graph +abstract public class IFG { + // Interference Graph Support + + // Map from a Basic Block to Live-Out: {a map from a Live Range to a Def} + private static final IdentityHashMap<CFGNode,IdentityHashMap<LRG,Node>> BBOUTS = new IdentityHashMap<>(); + static void resetBBLiveOut() { + for( IdentityHashMap<LRG,Node> bbout : BBOUTS.values() ) + bbout.clear(); + } + static final IdentityHashMap<LRG,Node> TMP = new IdentityHashMap<>(); + + // Inteference Graph: Array of Bitsets + private static final Ary<BitSet> IFG = new Ary<>(BitSet.class); + static void resetIFG() { + for( BitSet bs : IFG ) + if( bs!=null ) bs.clear(); + } + + // Set matching bit + private static void addIFG( LRG lrg0, LRG lrg1 ) { + short x0 = lrg0._lrg, x1 = lrg1._lrg; + // Triangulate + if( x0 < x1 ) _addIFG(x0,x1); + else _addIFG(x1,x0); + } + // Add lrg1 to lrg0's conflict set + private static void _addIFG( short x0, short x1 ) { + BitSet ifg = IFG.atX(x0); + if( ifg==null ) + IFG.setX(x0,ifg = new BitSet()); + ifg.set(x1); + } + + + private static final Ary<CFGNode> WORK = new Ary<>(CFGNode.class); + static void pushWork(CFGNode bb) { + if( WORK.find(bb) == -1 ) + WORK.push(bb); + } + + // ------------------------------------------------------------------------ + // Visit all blocks, using the live-out LRGs per-block and doing a backwards + // walk over each block. At the end of the walk, push the live-out sets to + // prior blocks. Due to loops, no single visitation order suffices. This + // problem can be fairly efficiently solved with a proper LIVE calculation + // and bitsets. In the interests of simplicity (and assumption of smaller + // programs) I am skipping LIVE and directly computing it during the IFG + // building. + + // Start by setting the live-out of the exit block (to empty), and putting + // it on a worklist. + + // - Pull from worklist a block who's live-out has changed + // - Walk backwards, adding interferences and computing live. + // - At block head, "push" the new live-out to prior blocks. + // - - If they pick up new live-outs, put them on the worklist. + + public static boolean build( int round, RegAlloc alloc ) { + // Reset all to empty + resetBBLiveOut(); + resetIFG(); + + // Last block has nothing live out + assert WORK.isEmpty(); + for( CFGNode bb : alloc._code._cfg ) + if( bb.blockHead()) + WORK.push( bb ); + + // Process blocks until no more changes + while( !WORK.isEmpty() ) + do_block(round,alloc,WORK.pop()); + + return alloc.success(); + } + + // Walk one block backwards, compute live-in from live-out, and build IFG + static void do_block( int round, RegAlloc alloc, CFGNode bb ) { + assert bb.blockHead(); + IdentityHashMap<LRG,Node> live_out = BBOUTS.get(bb); + TMP.clear(); + if( live_out != null ) + TMP.putAll(live_out); // Copy bits to temp + + // A backwards walk over instructions in the basic block + for( int inum = bb.nOuts()-1; inum >= 0; inum-- ) { + Node n = bb.out(inum); + if( n.in(0) != bb ) continue; + // In a backwards walk, proj users come before the node itself + if( n instanceof MultiNode ) + for( Node proj : n.outs() ) + if( proj instanceof ProjNode ) + do_node(alloc,proj); + if( n instanceof MachNode ) + do_node(alloc,n); + } + + // The bb head kills register, e.g. a CallEnd and caller-save registers + if( bb instanceof MachNode mach ) + kills(alloc,mach); + + // Push live-sets backwards to priors in CFG. + if( bb instanceof RegionNode ) + for( int i=1; i<bb.nIns(); i++ ) + mergeLiveOut(alloc,bb,i); + else + mergeLiveOut(alloc,bb,0); + } + + private static void do_node(RegAlloc alloc, Node n) { + // Defining means killing live LRG + LRG lrg = alloc.lrg(n); + if( lrg != null ) { + // Check for def-side self-conflict live ranges. These must split, + // and only happens during the first round a particular LRG splits. + selfConflict(alloc,n,lrg); + TMP.remove(lrg); // Kill def + } + + if( n instanceof PhiNode ) + return; + // Instructions can clobber registers without defining an LRG. + if( n instanceof MachNode m ) kills(alloc,m); + + // Interfere n with all live + if( lrg!=null ) { + // Interfere n with all live + for( LRG tlrg : TMP.keySet() ) { + assert tlrg.leader(); + // Skip self + if( lrg != tlrg && + // And register sets overlap + lrg._mask.overlap(tlrg._mask) ) + // Then tlrg and lrg interfere. If lrg needs the single + // last tlrg register at some point, either tlrg or lrg + // must fail. If *n* (a subset of lrg) needs the single + // last tlrg register then only tlrg must fail. + if( ((MachNode)n).outregmap().size1() ) { + if( !tlrg.clr(lrg._mask.firstReg()) ) alloc.fail(tlrg); + } else addIFG(lrg,tlrg); // Add interference + } + } + + + // Check for self-conflict live ranges. These must split, and only + // happens during the first round when a particular LRG splits. + // Also record use-side spills for biased coloring. + // Then make all inputs live. + for( int i=1; i<n.nIns(); i++ ) { + Node def = n.in(i); + if( def==null ) continue; + LRG lrg1 = alloc.lrg(def); + if( lrg1==null ) continue; // Anti-dependence, etc, no register + + // Check use-side for self-conflict; if so we MUST split + selfConflict(alloc,def,lrg1); + + // Look for a must-use single register conflicting with some other must-def. + if( n instanceof MachNode m ) { + RegMask ni_mask = m.regmap(i); + if( ni_mask!=null && ni_mask.size1() ) { // Must-use single register + // Search all current live + for( LRG tlrg : TMP.keySet() ) { + assert tlrg.leader(); + Node live = TMP.get(tlrg); + if( live != def && live instanceof MachNode lmach && lmach.outregmap().overlap(ni_mask) ) { + // Deny the register, since it absolutely must be used here + if( !tlrg.clr(ni_mask.firstReg()) ) + // Then direct reg-reg conflict between use here (at n.in(i)) and def (of tlrg) there. + // Fail the older live range, it must move its register. + alloc.fail( tlrg ); + } + } + } + } + + // All inputs live - except in self conflicts, where we keep the prior def alive + // until it goes. + TMP.put(lrg1,def); + } + } + + // Single-register defines *must* have their register; other live ranges + // *must* avoid this register, so instead of interfering we remove the + // single register from the other rmask. + private static void kills( RegAlloc alloc, MachNode m ) { + RegMask killMask = m.killmap(); + if( killMask==null ) return; + // Kill registers with all live + for( LRG tlrg : TMP.keySet() ) { + assert tlrg.leader(); + // Always, tlrg cannot use kills + if( tlrg._mask.overlap(killMask) ) { + if( !tlrg.sub(killMask) ) alloc.fail(tlrg); + } + } + } + + // Check for self-conflict live ranges. These must split, and only happens + // during the first round a particular LRG splits. + private static void selfConflict(RegAlloc alloc, Node n, LRG lrg) { + selfConflict(alloc,n,lrg,TMP.get(lrg)); + } + private static void selfConflict(RegAlloc alloc, Node n, LRG lrg, Node prior) { + if( prior!=null && prior != n ) { + lrg.selfConflict(prior); + lrg.selfConflict(n); + alloc.fail(lrg); // 2 unrelated values live at once same live range; self-conflict + } + } + + // Merge TMP into bb's live-out set; if changes put bb on WORK + private static void mergeLiveOut( RegAlloc alloc, CFGNode priorbb, int i ) { + CFGNode bb = priorbb.cfg(i); + if( bb == null ) return; // Start has no prior + while( !bb.blockHead() ) bb = bb.cfg0(); + //if( i==0 && !(bb instanceof StartNode) ) bb = bb.cfg0(); + assert bb.blockHead(); + + // Lazy get live-out set for bb + IdentityHashMap<LRG, Node> lrgs = BBOUTS.computeIfAbsent( bb, k -> new IdentityHashMap<>() ); + + for( LRG lrg : TMP.keySet() ) { + Node def = TMP.get(lrg); + // Effective def comes from phi input from prior block + if( def instanceof PhiNode phi && phi.cfg0()==priorbb ) { + assert i!=0; + def = phi.in( i ); + } + // The synthetic exit carries no value on this Phi arm. + if( def._type==Type.TOP ) continue; + Node def_bb = lrgs.get(lrg); + if( def_bb==null ) { + lrgs.put(lrg,def); + pushWork(bb); + } else { + // Alive twice with different definitions; self-conflict + selfConflict(alloc,def,lrg,def_bb); + } + } + } + + + // ------------------------------------------------------------------------ + // Color the inference graph. + + // Coloring works by removing "trivial" live ranges from the IFG - those + // live ranges with fewer neighbors than available colors (registers). + // These are trivial because even if every neighbor takes a unique color, + // there's at least one more available to color this live range. + + // If we hit a clique of non-trivial live ranges, we pick one to be "at + // risk" of not-coloring. Good choices include live ranges with a large + // area and few hot uses. + + // Then we reverse and put-back live ranges - picking a color as we go. + // If there's no spare color we'll have to spill this at-risk live range. + + public static boolean color(int round, RegAlloc alloc) { + + // Convert the 2-D array of bits (a 1-D array of BitSets) into an + // adjacency matrix. + int maxlrg = alloc._LRGS.length, nlrgs=0; + for( int i=1; i<maxlrg; i++ ) { + if( alloc._LRGS[i] != null ) nlrgs++; + BitSet ifg = IFG.atX(i); + if( ifg != null ) { + LRG lrg0 = alloc._LRGS[i]; + for( int lrg = ifg.nextSetBit(0); lrg>=0; lrg=ifg.nextSetBit(lrg+1) ) { + LRG lrg1 = alloc._LRGS[lrg]; + lrg0.addNeighbor(lrg1); + lrg1.addNeighbor(lrg0); + } + } + } + + // Simplify + + // Walk all the LRGS looking for some trivial ones to start with. + // During this pass all LRGS are broken into 3 disjoint sets: + // - trivial, removed from IFG; color_stack[0 to sptr] + // - trivial, not (yet) removed from IFG; color_stack[sptr to swork] + // - unknown; color_stack[work to maxlrg] + + // Gather all not-unified (not-null); separate trivial and non-trivial set. + int sptr = 0, swork = 0; + LRG[] color_stack = new LRG[nlrgs]; + for( int i=0, j=0; i<maxlrg; i++ ) { + LRG lrg = alloc._LRGS[i]; + if( lrg==null ) continue; // Unified lrgs are null here + color_stack[j++] = lrg; + if( lrg.lowDegree() ) swap(color_stack, swork++, j-1); + } + + // Pull all lrgs from IFG, in trivial order if possible + while( sptr < color_stack.length ) { + // Swap best color up front + pickColor(color_stack,sptr,swork); + + // Pick a trivial lrg, and (temporarily) remove from the IFG. + LRG lrg = color_stack[sptr++]; + // If sptr was swork, then pulled an at-risk lrg + if( sptr > swork ) + swork = sptr; + + // Walk all neighbors and remove + if( lrg._adj != null ) { + for( LRG nlrg : lrg._adj ) { + // Remove and compress out neighbor + if( nlrg.removeCompress(lrg) ) { + // Neighbor is just exactly going trivial as 'lrg' is removed from IFG + // Find "j" position in the color_stack + int jx = swork; while( color_stack[jx] != nlrg ) jx++; + // Add trivial neighbor to trivial list. Pull lrg j out of + // unknown set, since its now in the trivial set + swap(color_stack, swork++, jx); + } + } + } + } + + // Reverse simplify (unstack the color stack), and set colors (registers) for live ranges + while( sptr > 0 ) { + LRG lrg = color_stack[--sptr]; + if( lrg==null ) continue; + RegMaskRW rmask = lrg._mask.copy(); + // Walk neighbors and remove adjacent colors + if( lrg._adj!=null ) { + for( LRG nlrg : lrg._adj ) { + nlrg.reinsert(lrg); + int reg = nlrg._reg; + if( reg!= -1 ) // Failed neighbors do not count + rmask.clr(reg); // Remove neighbor from my choices + } + } + // At-risk live-range did not color? + if( rmask.isEmpty() ) { + alloc.fail(lrg); + lrg._reg = -1; + } else { + // Pick first available register + short reg = rmask.firstReg(); + // Pick a "good" color from the choices. Typically, biased-coloring + // removes some over-spilling. + if( rmask.size() > 1 ) reg = biasColor(alloc,lrg,reg,rmask); + lrg._reg = reg; // Assign the register + } + } + + return alloc.success(); + } + + // Pick LRG from color stack + private static void pickColor(LRG[] color_stack, int sptr, int swork) { + // Out of trivial colorable, pick an at-risk to pull + if( sptr==swork ) + swap(color_stack,sptr,pickRisky(color_stack,sptr)); + // When coloring, we'd like to give more choices; so when coloring we'd + // like to see the single-def first (since no choices anyway), then + // non-split related (so more live ranges get colored), then + // split-related last, so they have more colors to bias towards. + + // Working in reverse, pick first split-related with many regs, then + // those with some regs, then single-def. + int bidx=sptr; + LRG best=color_stack[bidx]; + for( int idx = sptr+1; idx < swork; idx++ ) + if( betterLRG(best,color_stack[idx]) ) + best = color_stack[bidx=idx]; + if( bidx != sptr ) + swap(color_stack,sptr,bidx); // Pick best at sptr + } + + private static boolean betterLRG( LRG best, LRG lrg ) { + // If single-def varies, keep the not-single-def + if( best.size1() != lrg.size1() ) + return best.size1(); + // If hasSplit varies, keep the hasSplit + if( best.hasSplit() != lrg.hasSplit() ) + return lrg.hasSplit(); + // Keep large register count + return best.size() < lrg.size(); + } + + // Pick a live range that hasn't already spilled, or has a single-def- + // single-use that are not adjacent. + private static int pickRisky( LRG[] color_stack, int sptr ) { + int best=sptr; + int bestScore = pickRiskyScore(color_stack[best]); + for( int i=sptr+1; i<color_stack.length; i++ ) { + if( bestScore == 999999 ) return best; // Already max score + int iScore = pickRiskyScore(color_stack[i]); + if( iScore > bestScore ) + { best = i; bestScore = iScore; } + } + return best; + } + + // Pick a live range to pull, that might not color. + // + // Picking a live range with a very large span, with defs and uses outside + // loops means spilling a relative cheap live range and getting that + // register over a large area. + // + // Picking a live range that is very close to coloring might allow it to + // color despite being risky. + private static int pickRiskyScore( LRG lrg ) { + // Always pick callee-save registers as being very large area recovered + // and very cheap to spill. + if( lrg._machDef instanceof CalleeSaveNode ) + return 999998; + if( lrg._splitDef != null && lrg._splitDef.in(1) instanceof CalleeSaveNode && + lrg._splitUse != null && lrg._splitUse.out(0) instanceof ReturnNode ) + return 999999; + + // TODO: cost/benefit model. Perhaps counting loop-depth (freq) of def/use for cost + // and "area" for benefit + return 1000; + } + + private static short biasColor( RegAlloc alloc, LRG lrg, short reg, RegMask mask ) { + if( mask.size1() ) return reg; + // Check chain of splits up the def-chain. Take first allocated + // register, and if it's available in the mask, take it. + Node defSplit = lrg._splitDef, useSplit = lrg._splitUse; + int tidx, cnt=0; + + while( (tidx=biasable(defSplit)) != 0 || biasable(useSplit) != 0 ) { + if( cnt++ > 10 ) break; + + if( tidx != 0 ) { + short bias = biasColor( alloc, defSplit, mask ); + if( bias >= 0 ) return bias; // Good bias + if( bias == -2 ) defSplit = null; // Kill this side, no more searching + } else defSplit = null; + + if( biasable(useSplit) != 0 ) { + short bias = biasColor( alloc, useSplit, mask ); + if( bias >= 0 ) return bias; // Good bias + if( bias == -2 ) useSplit = null; // Kill this side, no more searching + } else useSplit = null; + + if( defSplit != null ) { + short bias = biasColorNeighbors( alloc, defSplit, mask ); + if( bias >= 0 ) return bias; + // Advance def side + defSplit = defSplit.in(tidx); + if( alloc.lrg(defSplit)==null ) defSplit=null; + } + + if( useSplit != null ) { + short bias = biasColorNeighbors( alloc, useSplit, mask ); + if( bias >= 0 ) return bias; + useSplit = useSplit.out(0); + } + + } + return mask.firstReg(); + } + + private static int biasable(Node split) { + if( split instanceof SplitNode ) return 1; // Yes biasable, advance is slot 1 + if( split instanceof PhiNode ) return 1; // Yes biasable, advance is slot 1 + if( !(split instanceof MachNode mach) ) return 0; // Not biasable + return mach.twoAddress(); // Only biasable if 2-addr + } + + // 3-way return: + // - good bias reg, take it & exit + // - this path is cutoff; do not search here anymore + // - advance this side + private static short biasColor( RegAlloc alloc, Node split, RegMask mask ) { + short bias = alloc.lrg(split)._reg; + if( bias != -1 ) { + if( mask.test(bias) ) return bias; // Good bias + else return -2; // Kill this side + } else return -1; // Advance this side + } + + // Check if we can match "split" color, or else trim "mask" to colors + // "split" might get. + private static short biasColorNeighbors( RegAlloc alloc, Node split, RegMask mask ) { + LRG slrg = alloc.lrg(split); + if( slrg._adj == null ) return -1; // No trimming + + // Can I limit my own choices to valid neighbor choices? + for( LRG alrg : slrg._adj ) { + int reg = alrg._reg; + if( reg == -1 && alrg._mask.size1() ) + reg = alrg._mask.firstReg(); + if( reg != -1 ) { + mask.clr(reg); + if( mask.size1() ) + return mask.firstReg(); + } + } + return -1; // No obvious color choice, but mask got trimmed + } + + private static void swap( LRG[] ary, int x, int y ) { + LRG tmp = ary[x]; ary[x] = ary[y]; ary[y] = tmp; + } +} diff --git a/src/main/java/com/seaofnodes/simple/codegen/LRG.java b/src/main/java/com/seaofnodes/simple/codegen/LRG.java new file mode 100644 index 00000000..5803cad8 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/codegen/LRG.java @@ -0,0 +1,219 @@ +package com.seaofnodes.simple.codegen; + +import com.seaofnodes.simple.Ary; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.node.*; + +import java.util.IdentityHashMap; + +/** A Live Range + * <p> + * 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. + * <p> + * Most of the fields in this class are for spill heuristics, but there are a + * few key ones that define what a LRG is. 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 - + * (<a href="https://en.wikipedia.org/wiki/Disjoint-set_data_structure">this is the Union-Find algorithm</a>) + * - 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. + */ +public class LRG { + + // Dense live range numbers + final short _lrg; + + // U-F leader; null if leader + LRG _leader; + + // Choosen register + short _reg; + + // Count of single-register defs and uses + short _1regDefCnt, _1regUseCnt; + + // A sample MachNode def in the live range + public MachNode _machDef, _machUse; + short _uidx; // _machUse input + + // Some splits used in biased coloring + MachConcreteNode _splitDef, _splitUse; + + // All the self-conflicting defs for this live range + IdentityHashMap<Node,String> _selfConflicts; + + // AND of all masks involved; null if none have been applied yet + RegMask _mask; + + // Adjacent Live Range neighbors. Only valid during coloring + Ary<LRG> _adj; + + void addNeighbor(LRG lrg) { + if( _adj==null ) _adj = new Ary<>(LRG.class); + _adj.push(lrg); + } + + // Remove and compress neighbor list; store ex-neighbor past + // end for unwinding Simplify. True if exactly going low-degree + boolean removeCompress( LRG lrg ) { + _adj.swap(_adj.find(lrg),_adj._len-1); + return _adj._len-- == _mask.size(); + } + + void reinsert( LRG lrg ) { + assert _adj._es[_adj._len]==lrg; + _adj._len++; + } + + // More registers than neighbors + boolean lowDegree() { return (_adj==null ? 0 : _adj._len) < _mask.size(); } + + LRG( short lrg ) { _lrg = lrg; _reg = -1; } + + boolean leader() { return _leader == null; } + + // Fast-path Find from the Union-Find algorithm + LRG find() { + if( _leader==null ) return this; // I am the leader + if( _leader._leader==null ) // I point to the leader + return _leader; + return _rollup(); + } + // Slow-path rollup of U-F + private LRG _rollup() { + LRG ldr = _leader._leader; + // Roll-up + while( ldr._leader!=null ) ldr = ldr._leader; + LRG l2 = this; + while( l2 != ldr ) { + LRG l3 = l2._leader; + l2._leader = ldr; + l2 = l3; + } + return ldr; + } + + // Union `this` and `lrg`, keeping the lower numbered _lrg. + // Includes a number of fast-path cutouts. + LRG union( LRG lrg ) { + assert leader(); + if( lrg==null ) return this; + lrg = lrg.find(); + if( lrg==this ) return this; + return _lrg < lrg._lrg ? _union(lrg) : lrg._union(this); + } + // Union `this` and `lrg`, folding together all stats. + private LRG _union( LRG lrg ) { + // Set U-F leader + lrg._leader = this; + // Fold together stats + if( _machDef==null ) { + _machDef = lrg._machDef; + } else if( lrg._machDef!=null ) { + if( _1regDefCnt==0 ) + _machDef = lrg._machDef; + else if( _machDef==lrg._machDef ) + _1regDefCnt--; + } + _1regDefCnt += lrg._1regDefCnt; + + if( _machUse==null ) { + _machUse = lrg._machUse; + _uidx = lrg._uidx; + } else if( lrg._machUse!=null ) { + if( _1regUseCnt==0 ) { + _machUse = lrg._machUse; + _uidx = lrg._uidx; + } + else if( _machUse==lrg._machUse ) + _1regUseCnt--; + } + _1regUseCnt += lrg._1regUseCnt; + + // Fold deepest Split + _splitDef = deepSplit(_splitDef,lrg._splitDef); + _splitUse = deepSplit(_splitUse,lrg._splitUse); + + // Fold together masks + RegMask mask = _mask.and(lrg._mask); + _mask = mask==null ? _mask.copy().and(lrg._mask) : mask; + return this; + } + + private static MachConcreteNode deepSplit( MachConcreteNode s0, MachConcreteNode s1 ) { + return s0==null || (s1!=null && s0.cfg0().loopDepth() < s1.cfg0().loopDepth()) ? s1 : s0; + } + + // Record any Mach def for spilling heuristics + LRG machDef( MachNode def, boolean size1 ) { + if( _machDef==null || size1 ) + _machDef = def; + if( size1 ) + _1regDefCnt++; + if( def instanceof SplitNode split ) + _splitDef = deepSplit(_splitDef,split); + return this; + } + + // Record any Mach use for spilling heuristics + LRG machUse( MachNode use, short uidx, boolean size1 ) { + if( _machUse==null || size1 ) + { _machUse = use; _uidx = uidx; } + if( size1 ) + _1regUseCnt++; + if( use instanceof SplitNode split ) + _splitUse = deepSplit(_splitUse,split); + return this; + } + + boolean hasSplit() { return _splitDef != null || _splitUse != null; } + short size() { return _mask.size(); } + boolean size1() { return _mask.size1(); } + + + void selfConflict( Node def ) { + if( _selfConflicts == null ) _selfConflicts = new IdentityHashMap<>(); + _selfConflicts.put(def,""); + } + + + // Record intersection of all register masks. + // True if still has registers + boolean and( RegMask mask ) { + RegMask mask2 = mask.and(_mask); + if( mask2==null ) + mask2 = _mask.copy().and(mask); + _mask = mask2; + return !_mask.isEmpty(); + } + // Remove this singular register + // True if still has registers + boolean clr( int reg ) { + if( _mask.clr(reg) ) return true; + _mask = _mask.copy(); // Need a mutable copy + return _mask.clr(reg); + } + // Subtract mask (AND complement) + // True if still has registers + boolean sub( RegMask mask ) { + RegMask mask2 = _mask.sub(mask); + if( mask2==null ) + mask2 = _mask.copy().sub(mask); + _mask = mask2; + return !_mask.isEmpty(); + } + + @Override public String toString() { return toString(new SB()).toString(); } + + public SB toString( SB sb ) { + sb.p("V").p(_lrg); + if( _mask!=null ) _mask.toString(sb); + return sb; + } +} diff --git a/src/main/java/com/seaofnodes/simple/ListScheduler.java b/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java index efca945a..1796ef7c 100644 --- a/src/main/java/com/seaofnodes/simple/ListScheduler.java +++ b/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java @@ -1,6 +1,8 @@ - package com.seaofnodes.simple; +package com.seaofnodes.simple.codegen; +import com.seaofnodes.simple.Ary; import com.seaofnodes.simple.IterPeeps.WorkList; +import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; import java.util.*; @@ -8,8 +10,17 @@ import java.util.*; public abstract class ListScheduler { // eXtra per-node stuff for scheduling. + // Nodes are all block-local, and projections fold into their parent. private static final IdentityHashMap<Node,XSched> XS = new IdentityHashMap<>(); private static class XSched { + + Node _n; // Node this extra is for + int _bcnt; // Not-ready not-scheduled inputs + int _rcnt; // Ready not-scheduled inputs + boolean _ruse; // Node IS a "remote use", uses a other-block value + boolean _rdef; // Node IS a "remote def" of a value used in other block + boolean _single; // Defines a single register, likely to conflict + static final Ary<XSched> FREE = new Ary<>(XSched.class); static void alloc(CFGNode bb, Node n) { XSched x = FREE.pop(); @@ -17,7 +28,7 @@ public abstract class ListScheduler { } void free() { _n = null; FREE.push(this); } - static XSched get(Node n) { return n==null ? null : XS.get(n); } + static XSched get(Node n) { return n==null ? null : XS.get(n instanceof ProjNode && !(n.in(0) instanceof CallEndNode) ? n.in(0) : n); } private XSched init(CFGNode bb, Node n) { _n = n; @@ -29,11 +40,11 @@ public abstract class ListScheduler { for( Node def : n._inputs ) if( def != null && def != bb ) { if( def.cfg0()==bb ) _bcnt++; // Raise block-local input count - else _ruse = true; + else if( !def.isMem() ) _ruse = true; // Remote register user } // Count remote outputs and allowed registers if( n instanceof MultiNode ) - for( Node use : n._inputs ) + for( Node use : n._outputs ) computeSingleRDef(bb,use); else computeSingleRDef(bb,n); @@ -41,7 +52,14 @@ public abstract class ListScheduler { } private void computeSingleRDef(CFGNode bb, Node n) { - RegMaskRW rmask = n instanceof MachNode mach && mach.outregmap()!=null ? mach.outregmap().copy() : null; + // See if this is 2-input, and that input is single-def + if( n instanceof MachNode mach && mach.twoAddress() != 0 ) { + XSched xs = XS.get(n.in(mach.twoAddress())); + if( xs != null ) + _single = xs._single; + } + // Visit all outputs + RegMaskRW rmask = !_single && n instanceof MachNode mach && mach.outregmap() != null ? mach.outregmap().copy() : null; for( Node use : n._outputs ) { // Remote use, so this is a remote def if( use!=null && use.cfg0()!=bb ) _rdef = true; @@ -52,7 +70,7 @@ public abstract class ListScheduler { rmask.and(mach.regmap(i)); } // Defines in a single register - _single = rmask!=null && rmask.size1(); + _single |= rmask!=null && rmask.size1(); } // If _bcnt==0, declare ready; move user bcnts into rcnts. @@ -82,13 +100,6 @@ public abstract class ListScheduler { _rcnt--; return isReady(); } - - Node _n; // Node this extra is for - int _bcnt; // Not-ready not-scheduled inputs - int _rcnt; // Ready not-scheduled inputs - boolean _ruse; // Node IS a "remote use", uses a other-block value - boolean _rdef; // Node IS a "remote def" of a value used in other block - boolean _single; // Defines a single register, likely to conflict } @@ -97,11 +108,11 @@ public abstract class ListScheduler { XS.clear(); for( CFGNode bb : code._cfg ) if( bb.blockHead() ) - local(bb); + local(bb,false); } // Classic list scheduler - private static void local( CFGNode bb ) { + private static void local( CFGNode bb, boolean trace ) { assert XS.isEmpty(); int len = bb.nOuts(); @@ -126,7 +137,7 @@ public abstract class ListScheduler { int sched = 0; while( sched < len ) { assert sched < ready; - int pick = best(bb._outputs,sched,ready); // Pick best + int pick = best(bb._outputs,sched,ready,trace); // Pick best Node best = bb._outputs.swap(pick,sched++); // Swap best to head of ready and move into the scheduled set // Lower ready count of users, and since projections are ready as // soon as their multi is ready, lower those now too. @@ -145,7 +156,7 @@ public abstract class ListScheduler { } private static int ready(CFGNode bb, Node use, int ready) { - if( use!=null && !(use instanceof PhiNode) && use.in(0)==bb && XSched.get(use).decIsReady() ) + if( use!=null && use.in(0)==bb && !(use instanceof PhiNode) && XSched.get(use).decIsReady() ) bb._outputs.set(ready++, use); // Became ready, move into ready set //bb._outputs.swap(ready++,use); // Became ready, move into ready set return ready; @@ -153,14 +164,19 @@ public abstract class ListScheduler { // Pick best between sched and ready. - private static int best( Ary<Node> blk, int sched, int ready ) { + private static int best( Ary<Node> blk, int sched, int ready, boolean trace ) { int pick = sched; - int score = score(blk.at(pick)); + Node p = blk.at(pick); + int score = score(p); + if( trace ) { System.out.printf("%4d N%d ",score,p._nid); System.out.println(p); } for( int i=sched+1; i<ready; i++ ) { - int nscore = score(blk.at(i)); + Node n = blk.at(i); + int nscore = score(n); + if( trace ) { System.out.printf("%4d N%d ",nscore,n._nid); System.out.println(n); } if( nscore > score ) - { score = nscore; pick = i; } + { score = nscore; pick = i; p = n; } } + if( trace ) { System.out.printf("Pick: N%d %s\n\n",p._nid,p); } return pick; } @@ -180,9 +196,9 @@ public abstract class ListScheduler { // Register pressure local scheduling: avoid overlapping specialized // registers usages. - // Subtract 10 if this op forces a live range to exist that cannot be - // resolved immediately; i.e., live count goes up. Scale to 20 if - // multi-def. + // Subtract 10 (delay) if this op forces a live range to exist that + // cannot be resolved immediately; i.e., live count goes up. Scale to + // 20 if multi-def. // Subtract 100 if this op forces a single-def live range to exist // which might conflict on the same register with other live ranges. @@ -190,42 +206,53 @@ public abstract class ListScheduler { // ready. Scale to 200 for multi-def. CNT[1]=CNT[2]=0; XSched xn = XSched.get(n); + // If defining a remote value, just generically stall alot. Value is + // used in a later block, can we delay until the end of this block? if( xn._rdef ) score = 200; // If defining a remote value, just generically stall alot + boolean flags = false; if( n instanceof MultiNode ) { for( Node use : n._outputs ) - singleUseNotReady( use, xn._single ); - } else if( n.nOuts() == 1 ) - singleUseNotReady( n, xn._single ); + flags |= singleUseNotReady( use, xn._single ); + } else + flags |= singleUseNotReady( n, xn._single ); score += -10 * Math.min( CNT[1], 2 ); score += -100 * Math.min( CNT[2], 2 ); + if( flags ) return 10; // Add 10 if this op ends a single-def live range, add 20 for 2 or more. // Scale to 100,200 if the single-def is also single-register. CNT[1]=CNT[2]=0; for( int i=1; i<n.nIns(); i++ ) { XSched xd = XSched.get( n.in(i) ); - if( xd != null && xd._n.nOuts()==1 ) + if( xd != null && n.in(i).nOuts()==1 ) CNT[xd._single || ((MachNode)n).regmap(i)!=null && ((MachNode)n).regmap(i).size1() ? 2 : 1]++; } score += 10 * Math.min( CNT[1], 2 ); score += 100 * Math.min( CNT[2], 2 ); - //// Nothing special - //if( n instanceof Pe ) return 500; // RISC want to go after PE - //if( n instanceof Risc ) return 400; // RISC want to go after PE + // Nothing special assert 10 <= score && score <= 990; return score; } - // Single use, and that use is not ready to fire once this one does. - // If true, stalling 'n' might reduce 'n's lifetime. - // Return 0 for false, 1 if true, 10 if also single register - private static void singleUseNotReady( Node n, boolean single ) { - if( n.nOuts() != 1 ) return; - XSched xu = XSched.get(n.out(0)); - if( xu !=null && xu._bcnt==0 ) - return; - CNT[single ? 2 : 1]++; + // Single use, and that use is not ready to fire once this one does. If + // true, stalling 'n' might reduce 'n's lifetime. Stall by 1 (2 if single) + // per not-ready. + + // If CFG user in this block, it must be last, so definitely stall + // single-def (which stalls flags on x86,arm until the jmp). + private static boolean singleUseNotReady( Node n, boolean single ) { + // If n can rematerialize, assume allocator will split into private uses + // as needed. No impact on local scheduling. + if( n.nOuts()>1 && n instanceof MachNode mach && mach.isClone() ) + return false; + for( Node use : n.outs() ) { + XSched xu = XSched.get(use); + if( xu != null && xu._n instanceof CFGNode ) return true; + if( xu != null && xu._bcnt > 0 ) + CNT[single ? 2 : 1]++; // Since bcnt>0 or CFG, stall until user is more ready + } + return false; } } diff --git a/src/main/java/com/seaofnodes/simple/Machine.java b/src/main/java/com/seaofnodes/simple/codegen/Machine.java index b29e9aba..13d7abc1 100644 --- a/src/main/java/com/seaofnodes/simple/Machine.java +++ b/src/main/java/com/seaofnodes/simple/codegen/Machine.java @@ -1,9 +1,6 @@ -package com.seaofnodes.simple; +package com.seaofnodes.simple.codegen; -import com.seaofnodes.simple.RegMask; -import com.seaofnodes.simple.node.CFGNode; -import com.seaofnodes.simple.node.IfNode; -import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.*; abstract public class Machine { // Human readable machine name. Something like "x86-64" or "arm" or "risc5" @@ -11,7 +8,11 @@ abstract public class Machine { // Human-readable name for a register number, e.g. "RAX" or "R0" public abstract String reg( int reg ); // Create a split op; any register to any register, including stack slots - public abstract Node split(); + public abstract SplitNode split( String kind, byte round, LRG lrg); + // List of caller-save registers + public abstract RegMask callerSave(); + // List of callee-save registers + public abstract RegMask calleeSave(); // Return a MachNode unconditional branch public abstract CFGNode jump(); // Break an infinite loop diff --git a/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java b/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java new file mode 100644 index 00000000..af28cbb3 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java @@ -0,0 +1,557 @@ +package com.seaofnodes.simple.codegen; + +import com.seaofnodes.simple.Ary; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import java.util.Arrays; +import java.util.IdentityHashMap; + +/** + * "Briggs/Chaitin/Click". + * Graph coloring. + * <p> + * Every def and every use have a bit-set of allowed registers. + * Fully general to bizarre chips. + * Multiple outputs fully supported, e.g. add-with-carry or combined div/rem or "xor rax,rax" setting both rax and flags. + * <p> + * Intel 2-address accumulator-style ops fully supported. + * Op-to-stack-spill supported. + * All addressing modes supported. + * Register pairs could be supported with some grief. + * <p> + * Splitting instead of spilling (simpler implementation). + * Stack slots are "just another register" (tiny stack frames, simpler implementation). + * Stack/unstack during coloring with a marvelous trick (simpler implementation). + * <p> + * Both bitset and adjacency list formats for the interference graph; one of + * the few times it's faster to change data structures mid-flight rather than + * just wrap one of the two. + * <p> + * Liveness computation and interference graph built in the same one pass (one + * fewer passes per round of coloring). + * <p> + * Single-register def or use live ranges deny neighbors their required + * register and thus do not interfere, vs interfering and denying the color + * and coloring time. Basically all function calls do this, but many oddball + * registers also, e.g. older div/mod/mul ops. (5x smaller IFG, the only + * O(n^2) part of this operation). + */ + +public class RegAlloc { + // Main Coloring Algorithm: + // Repeat until colored: + // Build Live Ranges (LRGs) + // - Intersect allowed registers + // If hard conflicts (LRGs with no allowed registers) + // - Pre-split conflicted LRGs, and repeat + // Build Interference Graph (IFG) + // - Self conflicts split now + // Color (remove trivial first then any until empty; reverse assign colors + // - If color fails: + // - - Split uncolorable LRGs + + // Top-level program graph structure + final CodeGen _code; + + public int _spills, _spillScaled; + + // ----------------------- + // Live ranges with self-conflicts or no allowed registers + private final IdentityHashMap<LRG,String> _failed = new IdentityHashMap<>(); + void fail( LRG lrg ) { + assert lrg.leader(); + _failed.put(lrg,""); + } + boolean success() { return _failed.isEmpty(); } + + + // ----------------------- + // Map from Nodes to Live Ranges + private final IdentityHashMap<Node,LRG> _lrgs = new IdentityHashMap<>(); + short _lrg_num; + + // Define a new LRG, and assign n + LRG newLRG( Node n ) { + LRG lrg = lrg(n); + if( lrg!=null ) return lrg; + lrg = new LRG(_lrg_num++); + LRG old = _lrgs.put(n,lrg); assert old==null; + return lrg; + } + + // LRG for n + LRG lrg( Node n ) { + LRG lrg = _lrgs.get(n); + if( lrg==null ) return null; + LRG lrg2 = lrg.find(); + if( lrg != lrg2 ) + _lrgs.put(n,lrg2); + return lrg2; + } + + // Find LRG for n.in(idx), and also map n to it + LRG lrg2( Node n, int idx ) { + LRG lrg = lrg(n.in(idx)); + return union(lrg,n); + } + + // Union any lrg for n with lrg and map to the union + LRG union( LRG lrg, Node n ) { + LRG lrgn = _lrgs.get(n); + LRG lrg3 = lrg.union(lrgn); + _lrgs.put(n,lrg3); + return lrg3; + } + + // Force all unified to roll up; collect live ranges + LRG[] _LRGS; + void unify() { + Ary<LRG> lrgs = new Ary<>(LRG.class); + for( Node n : _lrgs.keySet() ) { + LRG lrg = lrg(n); + lrgs.setX(lrg._lrg,lrg); + } + _LRGS = lrgs.asAry(); + // Remove unified lrgs from failed set also + for( LRG lrg : _failed.keySet().toArray(new LRG[0]) ) { + if( !lrg.leader() ) { + LRG lrg2 = lrg.find(); + _failed.remove(lrg); + _failed.put(lrg2,""); + } + } + } + + + // Diagnostic lookup: do not compress leaders or rewrite the node mapping. + private LRG _lrg(Node n) { + LRG lrg = _lrgs.get(n); + if( lrg != null ) + while( lrg._leader != null ) lrg = lrg._leader; + return lrg; + } + + public short regnum( Node n ) { + LRG lrg = _lrg(n); + return lrg==null ? -1 : lrg._reg; + } + + // Printable register number for node n + String reg( Node n ) { + LRG lrg = _lrg(n); + if( lrg==null ) return null; + if( lrg._reg == -1 ) return "V"+lrg._lrg; + return _code._mach.reg(lrg._reg); + } + + // ----------------------- + RegAlloc( CodeGen code ) { _code = code; } + + public void regAlloc() { + // Insert callee-save registers + for( CFGNode bb : _code._cfg ) + if( bb instanceof FunNode fun ) + insertCalleeSave(fun); + + // Optional diagnostic for values accidentally shared across functions. + //assert verifyFunctionLocalEdges(); + + // Top driver: repeated rounds of coloring and splitting. + byte round=0; + while( !graphColor(round) ) { + split(round); + assert round < 7 : "Register allocation made no progress after eight rounds"; + round++; + } + postColor(); // Remove no-op spills + } + + boolean verifyFunctionLocalEdges() { + for( CFGNode bb : _code._cfg ) { + if( bb instanceof StartNode ) continue; + FunNode fun = bb.fun(); + for( Node use : bb._outputs ) + if( use instanceof MachNode mach && !(use instanceof ParmNode) ) + for( int i=1; i<use.nIns(); i++ ) { + Node def = use.in(i); + if( def==null || mach.regmap(i)==null || def.cfg0()==null || def.cfg0() instanceof StartNode ) continue; + assert def.cfg0().fun()==fun : "Cross-function register edge: "+def.uniqueName()+" -> "+use.uniqueName()+" input "+i; + } + } + return true; + } + + private boolean graphColor(byte round) { + _failed.clear(); + _lrgs.clear(); + _lrg_num = 1; + _LRGS=null; + + return + // Build Live Ranges + BuildLRG.run(round,this) && // if no hard register conflicts + // Build Interference Graph + IFG.build(round,this) && // If no self conflicts or uncolorable + // Color attempt + IFG.color(round,this); // If colorable + } + + // Insert callee-save registers. Walk the callee-save RegMask ignoring any + // Parms, then insert a Parm and an edge from the Ret to the Parm with the + // callee-save register. + private void insertCalleeSave( FunNode fun ) { + RegMask saves = _code._mach.calleeSave(); + ReturnNode ret = fun.ret(); + + for( short reg = saves.firstReg(); reg != -1; reg = saves.nextReg(reg) ) { + ret.addDef(new CalleeSaveNode(fun,reg,_code._mach.reg(reg))); + assert ((MachNode)ret).regmap(ret.nIns()-1).firstReg()==reg; + } + } + + + // ----------------------- + // Split conflicted live ranges. + void split(byte round) { + + // In C2, all splits are handling in one pass over the program. Here, + // in the name of clarity, we'll handle each failing live range + // independently... which generally requires a full pass over the + // program for each failing live range. i.e., might be a lot of + // passes. + LRG[] splits = _failed.keySet().toArray(new LRG[0]); + Arrays.sort(splits, (x,y) -> x._lrg - y._lrg); + for( LRG lrg : splits ) split(round,lrg); + } + + // Split this live range + boolean split( byte round, LRG lrg ) { + assert lrg.leader(); // Already rolled up + + if( lrg._selfConflicts != null ) + return splitSelfConflict(round,lrg); + + // Register mask when empty; split around defs and uses with limited + // register masks. + if( lrg._mask.isEmpty() ) { + if( lrg._1regDefCnt <= 1 && + lrg._1regUseCnt <= 1 && + (lrg._1regDefCnt + lrg._1regUseCnt) > 0 && + // A fixed-register clone with flexible uses must split at the uses. + !(lrg._1regDefCnt==1 && lrg._machDef.isClone() && lrg._1regUseCnt==0) ) + return splitEmptyMaskSimple(round,lrg); + // More complex conflicts use loop-boundary splitting. + } + + // Generic split-by-loop depth. + return splitByLoop(round,lrg); + } + + // Split live range with an empty mask. Specifically forces splits at + // single-register defs or uses and not elsewhere. + boolean splitEmptyMaskSimple( byte round, LRG lrg ) { + // Live range has a single-def single-register, and/or a single-use + // single-register. Split after the def and before the use. Does not + // require a full pass. + + // Split just after def + if( lrg._1regDefCnt==1 && !lrg._machDef.isClone() ) + // An earlier copy is reusable only if no intervening clobber. + // Being in the same block alone is insufficient. Example: + // alloc + // V1/rax - forced by alloc + // alloc + // V2/rax - kills prior RAX + // st4 [V1],len - No good, must split around + insertAfterAndReplace(makeSplit("def/empty1",round,lrg),(Node)lrg._machDef,false); + // Split just before use + if( lrg._1regUseCnt==1 ) + insertBefore((Node)lrg._machUse,lrg._uidx,"use/empty1",round,lrg); + return true; + } + + // Self conflicts require Phis (or two-address). + // Insert a split after every def. + boolean splitSelfConflict( byte round, LRG lrg ) { + // Sort conflict set, so we're deterministic + Node[] conflicts = lrg._selfConflicts.keySet().toArray(new Node[0]); + Arrays.sort(conflicts, (x,y) -> x._nid - y._nid ); + + // For all conflicts + for( Node def : conflicts ) { + assert lrg(def)==lrg; // Might be conflict use-side + // Split before each use that extends the live range; i.e. is a + // Phi or two-address + for( int i=0; i<def._outputs._len; i++ ) { + Node use = def.out(i); + if( (use instanceof PhiNode phi && + !(phi.region() instanceof LoopNode loop && phi.in(2)==def && def.cfg0().idepth() > loop.idepth() ) ) || + (use instanceof MachNode mach && mach.twoAddress()!=0 && use.in(mach.twoAddress())==def) ) + insertBefore( use, use._inputs.find(def), "use/self/use",round,lrg ); + } + // Split after the Phi and before its entry. Also isolate inputs + // shared with another Phi: otherwise the shared input keeps the + // conflicting live ranges joined, even after splitting the entry. + if( def instanceof PhiNode phi && !(def instanceof ParmNode) ) { + SplitNode split = makeSplit("def/self",round,lrg); + insertAfterAndReplace(split,def,false); + if( split.nOuts()==0 ) + split.killOrdered(); + for( int i=1; i<phi.nIns(); i++ ) + if( i==1 || sharedPhiInput(phi,i) ) + insertBefore(phi,i,"use/self/phi",round,lrg); + } + // Split before two-address ops which extend the live range + if( def instanceof MachNode mach && mach.twoAddress()!= 0 ) + insertBefore(def,mach.twoAddress(),"use/self/two",round,lrg); + } + return true; + } + + + private static boolean sharedPhiInput(PhiNode phi, int i) { + for( Node use : phi.in(i)._outputs ) + if( use instanceof PhiNode && use!=phi ) return true; + return false; + } + + // Generic: split around the outermost loop with non-split def/uses. This + // covers both self-conflicts (once we split deep enough) and register + // pressure issues. + boolean splitByLoop( byte round, LRG lrg ) { + findAllLRG(lrg); + + // Find min loop depth for all non-split defs and uses. + long ld = (-1L<<32) | 9999; + for( Node n : _ns ) { + if( lrg(n)==lrg ) // This is a LRG def + ld = ldepth(ld,n,n.cfg0()); + // PhiNodes check all CFG inputs + if( n instanceof PhiNode phi ) { + for( int i=1; i<n.nIns(); i++ ) + if( phi.in(i)._type!=Type.TOP ) + ld = ldepth(ld, phi.in(i), phi.region().cfg(i)); + } else { + // Others check uses + for( int i=1; i<n.nIns(); i++ ) + if( lrgSame(n.in(i),lrg) ) // This is a LRG use + ld = ldepth(ld,n,n.cfg0()); + } + } + int min = (int)ld; + int max = (int)(ld>>32); + + + // If the minLoopDepth is less than the maxLoopDepth: for-all defs and + // uses, if at minLoopDepth or lower, split after def and before use. + for( Node n : _ns ) { + if( n instanceof SplitNode ) continue; // Ignoring splits; since spilling need to split in a deeper loop + if( n.isDead() ) continue; // Some Clonable went dead by other spill changes + // If this is a 2-address commutable op (e.g. AddX86, MulX86) and the rhs has only a single user, + // commute the inputs... which chops the LHS live ranges' upper bound to just the RHS. + if( n instanceof MachNode mach && lrg(n)==lrg && mach.twoAddress()==1 && mach.commutes() && n.in(2).nOuts()==1 ) + n.swap12(); + + if( lrg(n)==lrg && // This is a LRG def + // At loop boundary, or splitting in inner loop + (min==max || n.cfg0().loopDepth() <= min) ) { + // Cloneable constants will be cloned at uses, not after def + if( !(n instanceof MachNode mach && mach.isClone()) && + // Single user is already a split, with no intervening clobber + !(n.nOuts()==1 && n.out(0) instanceof SplitNode split && sameBlockNoClobber(split)) ) + // Split after def in min loop nest + insertAfterAndReplace(makeSplit("def/loop",round,lrg),n,true); + } + + // PhiNodes check all CFG inputs + if( n instanceof PhiNode phi && !(n instanceof ParmNode)) { + for( int i=1; i<n.nIns(); i++ ) + // No split in front of a split + if( !(n.in(i) instanceof SplitNode) && + // splitting in inner loop or at loop border + (min==max || phi.region().cfg(i).loopDepth() <= min) && + // and not around the backedge of a loop (bad place to force a split, hard to remove) + !(phi.region() instanceof LoopNode && i==2 && phi.in(i) instanceof PhiNode pp && pp.region()==phi.region()) ) + // Split before phi-use in prior block + insertBefore(phi,i, "use/loop/phi",round,lrg); + + } else { + // Others check uses + for( int i=1; i<n.nIns(); i++ ) { + if( lrgSame(n.in(i),lrg) && + (min==max || n.in(i) instanceof MachNode mach && mach.isClone() || n.cfg0().loopDepth() <= min) ) + // Preserve the new split; folding it through can undo progress. + insertBefore(n,i,"use/loop/use",round,lrg,false); + } + } + } + return true; + } + + private boolean lrgSame(Node x, LRG lrg) { + LRG xlrg = lrg(x); + while( xlrg==null && x instanceof SplitNode ) + xlrg=lrg(x = x.in(1)); + return xlrg==lrg; + } + + private static long ldepth( long ld, Node n, CFGNode cfg ) { + // Do not count splits + if( n instanceof SplitNode ) return ld; + // Collect min/max loop depth + int min = (int)ld; + int max = (int)(ld>>32); + int d = cfg.loopDepth(); + min = Math.min(min,d); + max = Math.max(max,d); + return ((long)max<<32) | min; + } + + // Find all members of a live range, both defs and uses + private final Ary<Node> _ns = new Ary<>(Node.class); + void findAllLRG( LRG lrg ) { + _ns.clear(); + int wd = 0; + if( lrg._machDef!=null ) _ns.push((Node)lrg._machDef); + if( lrg._machUse!=null ) _ns.push((Node)lrg._machUse); + while( wd < _ns._len ) { + Node n = _ns.at(wd++); + if( lrg(n)!=lrg ) continue; + for( Node def : n._inputs ) + if( lrg(def)==lrg && _ns.find(def)== -1 ) + _ns.push(def); + for( Node use : n._outputs ) + if( _ns.find(use)== -1 ) + _ns.push(use); + } + for( Node n : _ns ) assert !n.isDead(); + } + + void insertBefore(Node n, int i, String kind, byte round, LRG lrg) { insertBefore(n,i,kind,round,lrg,true); } + void insertBefore(Node n, int i, String kind, byte round, LRG lrg, boolean skip) { + Node def = n.in(i); + // Phi splitting may visit the synthetic never-exit arm. + if( def._type==Type.TOP ) return; + // Effective block for use + CFGNode cfg = n instanceof PhiNode phi ? phi.region().cfg(i) : n.cfg0(); + // Def is a split ? + if( skip && def instanceof SplitNode ) { + boolean singleReg = n instanceof MachNode mach && mach.regmap(i)!=null && mach.regmap(i).size1(); + // Same block, multiple registers, split is only used by n, + // assume this is good enough and do not split again. + if( cfg==def.cfg0() && def.nOuts()==1 && !singleReg ) + return; + } + RegMask use = n instanceof MachNode mach ? mach.regmap(i) : null; + makeSplit(def,kind,round,lrg,use).insertBefore(n, i); + // Skip split-of-split same block + if( skip && def instanceof SplitNode && cfg==def.cfg0() ) + n.in(i).setDefOrdered(1,def.in(1)); + } + + // Replace uses of `def` with `split`, and insert `split` immediately after + // `def` in the basic block. + public void insertAfterAndReplace( Node split, Node def, boolean must ) { + split.insertAfter(def); + if( split.nIns()>1 ) split.setDef(1,def); + for( int j=def.nOuts()-1; j>=0; j-- ) { + Node use = def.out(j); + if( use==split ) continue; // Skip self + // Can we avoid a split of a split? 'this' split is used by + // another split in the same block. + if( !must && use instanceof SplitNode split2 && sameBlockNoClobber(split2) ) + continue; + int idx = use._inputs.find(def); + use.setDefOrdered(idx,split); + if( j < def.nOuts() ) j++; + } + } + + private Node makeSplit( Node def, String kind, byte round, LRG lrg, RegMask use ) { + // A clone must be able to satisfy the use's register class. + Node split = def instanceof MachNode mach && mach.isClone() && (use==null || mach.outregmap().overlap(use)) + ? mach.copy() + : _code._mach.split(kind,round,lrg); + _lrgs.put(split,lrg); + return split; + } + private SplitNode makeSplit( String kind, byte round, LRG lrg ) { + SplitNode split = _code._mach.split(kind,round,lrg); + _lrgs.put(split,lrg); + return split; + } + + + // ----------------------- + // POST PASS: Remove empty spills that biased-coloring made + private void postColor() { + for( CFGNode bb : _code._cfg ) { // For all ops + for( int j=0; j<bb.nOuts(); j++ ) { + Node n = bb.out(j); + if( !(n instanceof SplitNode lo) ) continue; + int defreg = lrg(n )._reg; + int usereg = lrg(n.in(1))._reg; + // Attempt to bypass split + if( defreg != usereg && splitBypass(bb,j,n,defreg) ) + usereg = lrg(n.in(1))._reg; + + // Split has same reg? Useless! Can remove it! + if( defreg == usereg ) { + n.removeSplit(); + j--; + continue; + } + // Split is kept; count against split score + _spills++; + _spillScaled += (1<<bb.loopDepth()*3); + assert _spillScaled >= 0; + } + } + } + + private boolean splitBypass( CFGNode bb, int j, Node lo, int defreg ) { + // Attempt to bypass split + Node hi = lo.in(1); + while( true ) { + if( !(hi instanceof SplitNode && lo.cfg0() == hi.cfg0()) ) + return false; + if( lrg(hi.in(1))._reg==defreg ) + break; + hi = hi.in(1); + } + // Walk backward from the later copy, including instructions without results. + for( int idx = j-1; bb.out(idx) != hi; idx--) { + Node n = bb.out(idx); + if( clobbers(n,defreg) ) + return false; // Clobbered + } + lo.setDefOrdered(1,hi.in(1)); + return true; + } + private boolean clobbers(Node n, int reg) { + LRG lrg = lrg(n); + return lrg!=null && (lrg._reg==reg || lrg._reg==-1 && lrg._mask.size1() && lrg._mask.firstReg()==reg) || + n instanceof MachNode mach && mach.killmap()!=null && mach.killmap().test(reg); + } + + public boolean sameBlockNoClobber( SplitNode split ) { + Node def = split.in(1); + CFGNode cfg = def.cfg0(); + if( cfg != split.cfg0() ) return false; // Not same block + // Get multinode head + Node def0 = def instanceof ProjNode ? def.in(0) : def; + int defreg = lrg(def)._reg; + if( defreg == -1 ) defreg = lrg(def)._mask.firstReg(); + if( defreg == -1 ) return false; // no allowed registers -> clobbered + for( int idx = cfg._outputs.find(split) -1; idx >= 0; idx-- ) { + Node n = cfg.out(idx); + if( n==def0 ) return true; // No clobbers + if( lrg(n) == lrg(def) ) return false; // Self conflict + if( clobbers(n,defreg) ) + return false; // Clobbered + } + throw Utils.TODO(); + } + +} diff --git a/src/main/java/com/seaofnodes/simple/codegen/RegMask.java b/src/main/java/com/seaofnodes/simple/codegen/RegMask.java new file mode 100644 index 00000000..5227c11e --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/codegen/RegMask.java @@ -0,0 +1,139 @@ +package com.seaofnodes.simple.codegen; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.SB; + +/** RegMask + * A "register mask" - 1 bit set for each allowed register. In addition + * "stack slot" registers may be allowed, effectively making the set infinite. + * <p> + * 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. +*/ +public class RegMask { + + long _bits0, _bits1; + + private static final RegMask EMPTY = new RegMask(0L); + public static final RegMask FULL = new RegMask(-1L); + + public RegMask(int bit) { + if( bit < 64 ) _bits0 = 1L<<bit; + else _bits1 = 1L<<(bit-64); + } + public RegMask(long bits ) { _bits0 = bits; } + public RegMask(long bits0, long bits1 ) { _bits0 = bits0; _bits1 = bits1; } + private RegMask() { _bits0 = _bits1 = 0; } + + // AND, with copy-on-write if changing + RegMask and( RegMask mask ) { + if( mask==null ) return this; + long bits0 = _bits0 & mask._bits0; + long bits1 = _bits1 & mask._bits1; + if( bits0==_bits0 && bits1==_bits1 ) return this; + if( bits0==mask._bits0 && bits1==mask._bits1 ) return mask; + if( bits0==0 && bits1==0 ) return EMPTY; + // Update-in-place a mutable mask, or make a defensive copy + return null; + } + RegMask sub( RegMask mask ) { + if( mask==null ) return this; + long bits0 = _bits0 & ~mask._bits0; + long bits1 = _bits1 & ~mask._bits1; + if( bits0==_bits0 && bits1==_bits1 ) return this; + if( bits0==mask._bits0 && bits1==mask._bits1 ) return mask; + if( bits0==0 && bits1==0 ) return EMPTY; + // Update-in-place a mutable mask, or make a defensive copy + return null; + } + + // Fails if bit is set, because this is immutable + public boolean clr( int reg ) { + if( reg < 64 ) return ((_bits0 >> reg)&1)==0; + if( reg < 128 ) return ((_bits1 >> reg)&1)==0; + return true; + } + + + public short firstReg() { + if( isEmpty() ) return -1; + return (short)(_bits0 != 0 + ? Long.numberOfTrailingZeros(_bits0) + : Long.numberOfTrailingZeros(_bits1)+64); + } + public short nextReg(short reg) { + if( reg < 63 ) { + long bits0 = _bits0 & (-1L << (reg+1)); + if( bits0!=0 ) return (short)Long.numberOfTrailingZeros(bits0); + } + long bits1 = reg < 64 ? _bits1 : reg < 127 ? _bits1 & (-1L << (reg-63)) : 0; + return bits1==0 ? -1 : (short)(64+Long.numberOfTrailingZeros(bits1)); + } + + boolean isEmpty() { return _bits0==0 && _bits1==0; } + + boolean test( int reg ) { + return ((reg<64 ? (_bits0 >> reg) : (_bits1 >> (reg-64))) & 1) != 0; + } + + // checks if the 2 masks have at least 1 bit in common + public boolean overlap( RegMask mask ) { return (_bits0 & mask._bits0)!=0 || (_bits1 & mask._bits1)!=0; } + + // Defensive writable copy + public RegMaskRW copy() { return new RegMaskRW( _bits0, _bits1 ); } + + // Has exactly 1 bit set + public boolean size1() { + return !isEmpty() && (((_bits0 & -_bits0)==_bits0 && _bits1==0) || + ((_bits1 & -_bits1)==_bits1 && _bits0==0)); + } + + // Cardinality + public short size() { return (short)(Long.bitCount(_bits0)+Long.bitCount(_bits1)); } + + @Override public String toString() { return toString(new SB()).toString(); } + public SB toString(SB sb) { + Machine mach = CodeGen.CODE._mach; + if( _bits0==0 && _bits1==0 ) return sb.p("[]"); + sb.p("["); + for( int i=0; i<64; i++ ) + if( ((_bits0 >> i)&1) != 0 ) + sb.p(mach.reg(i)).p(","); + for( int i=0; i<64; i++ ) + if( ((_bits1 >> i)&1) != 0 ) + sb.p(mach.reg(i+64)).p(","); + return sb.unchar().p("]"); + } +} + +// Mutable regmask(writable) +class RegMaskRW extends RegMask { + public RegMaskRW(long x, long y) { super(x,y); } + // clears bit at position r. Returns true if the mask is still not empty. + public boolean clr(int r) { + if( r < 64 ) _bits0 &= ~(1L<<(r )); + else _bits1 &= ~(1L<<(r-64)); + return _bits0!=0 || _bits1!=0; + } + @Override RegMaskRW and( RegMask mask ) { + if( mask==null ) return this; + _bits0 &= mask._bits0; + _bits1 &= mask._bits1; + return this; + } + @Override RegMaskRW sub( RegMask mask ) { + if( mask==null ) return this; + _bits0 &= ~mask._bits0; + _bits1 &= ~mask._bits1; + return this; + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/AddNode.java b/src/main/java/com/seaofnodes/simple/node/AddNode.java index 2dfedbb5..0fc3bad6 100644 --- a/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -4,6 +4,9 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.*; + +import static com.seaofnodes.simple.Parser.con; + public class AddNode extends Node { public AddNode(Node lhs, Node rhs) { super(null, lhs, rhs); } @@ -37,7 +40,8 @@ public class AddNode extends Node { public Node idealize () { Node lhs = in(1); Node rhs = in(2); - if( rhs.err()!=null ) return null; + if( rhs instanceof AddNode add && add.err()!=null ) + return null; Type t2 = rhs._type; // Add of 0. We do not check for (0+x) because this will already diff --git a/src/main/java/com/seaofnodes/simple/node/AndNode.java b/src/main/java/com/seaofnodes/simple/node/AndNode.java index 7cb16c71..b425a6ce 100644 --- a/src/main/java/com/seaofnodes/simple/node/AndNode.java +++ b/src/main/java/com/seaofnodes/simple/node/AndNode.java @@ -34,14 +34,17 @@ public class AndNode extends LogicalNode { Node rhs = in(2); Type t1 = lhs._type; Type t2 = rhs._type; + if( !(t1 instanceof TypeInteger && t2 instanceof TypeInteger t2i) ) + return null; // Malformed, e.g. (17 & 3.14) // And of -1. We do not check for (-1&x) because this will already - // canonicalize to (x&-1) - if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==-1 && lhs._type instanceof TypeInteger ) + // canonicalize to (x&-1). We do not check for zero, because + // the compute() call will return a zero already. + if( t2i.isConstant() && t2i.value()==-1 ) return lhs; // Move constants to RHS: con*arg becomes arg*con - if ( t1.isConstant() && !t2.isConstant() ) + if( t1.isConstant() && !t2.isConstant() ) return swap12(); // Do we have ((x & (phi cons)) & con) ? diff --git a/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/src/main/java/com/seaofnodes/simple/node/BoolNode.java index c0ccfd9b..85bcae0f 100644 --- a/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -44,6 +44,8 @@ abstract public class BoolNode extends Node { TypeInteger doOp(TypeInteger t1, TypeInteger t2) { throw Utils.TODO(); } boolean doOp(double lhs, double rhs) { throw Utils.TODO(); } + Node copyF(Node lhs, Node rhs) { return null; } + public boolean isFloat() { return false; } @Override public Node idealize() { @@ -84,6 +86,7 @@ abstract public class BoolNode extends Node { Node copy(Node lhs, Node rhs) { return new EQ(lhs,rhs); } Node copyF() { return new EQF(null,null); } } + public static class LT extends BoolNode { public LT(Node lhs, Node rhs) { super(lhs,rhs); } public String op() { return "<" ; } @@ -95,6 +98,7 @@ abstract public class BoolNode extends Node { Node copy(Node lhs, Node rhs) { return new LT(lhs,rhs); } Node copyF() { return new LTF(null,null); } } + public static class LE extends BoolNode { public LE(Node lhs, Node rhs) { super(lhs,rhs); } public String op() { return "<="; } @@ -107,9 +111,9 @@ abstract public class BoolNode extends Node { Node copyF() { return new LEF(null,null); } } - public static class EQF extends EQ { public EQF(Node lhs, Node rhs) { super(lhs,rhs); } boolean doOp(double lhs, double rhs) { return lhs == rhs; } } - public static class LTF extends LT { public LTF(Node lhs, Node rhs) { super(lhs,rhs); } boolean doOp(double lhs, double rhs) { return lhs < rhs; } } - public static class LEF extends LE { public LEF(Node lhs, Node rhs) { super(lhs,rhs); } boolean doOp(double lhs, double rhs) { return lhs <= rhs; } } + public static class EQF extends EQ { public EQF(Node lhs, Node rhs) { super(lhs,rhs); } boolean doOp(double lhs, double rhs) { return lhs == rhs; } public boolean isFloat() { return true; } } + public static class LTF extends LT { public LTF(Node lhs, Node rhs) { super(lhs,rhs); } boolean doOp(double lhs, double rhs) { return lhs < rhs; } public boolean isFloat() { return true; } } + public static class LEF extends LE { public LEF(Node lhs, Node rhs) { super(lhs,rhs); } boolean doOp(double lhs, double rhs) { return lhs <= rhs; } public boolean isFloat() { return true; } } // Unsigned less that, for range checks. Not directly user writable. public static class ULT extends BoolNode { diff --git a/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java index 5d8a4aba..e9d4b897 100644 --- a/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java +++ b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java @@ -1,6 +1,6 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.CodeGen; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeMem; import java.util.BitSet; diff --git a/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/src/main/java/com/seaofnodes/simple/node/CFGNode.java index 383f18d0..0e770659 100644 --- a/src/main/java/com/seaofnodes/simple/node/CFGNode.java +++ b/src/main/java/com/seaofnodes/simple/node/CFGNode.java @@ -1,6 +1,7 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.CodeGen; +import com.seaofnodes.simple.codegen.CodeGen; + import com.seaofnodes.simple.Ary; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.Utils; @@ -19,16 +20,27 @@ import java.util.HashSet; * <p> * loop_depth is computed after optimization as part of scheduling. * + * Start - Block head; has constants and trailing Funs + * Region - Block head; has Phis. Includes Fun and Parms. + * CallEnd - Block head; followed by CProj and Proj; also linked RPC Parms + * CProj - Block head + * + * If - Block tail; followed by CProj same block + * Call - Block tail; followed by CallEnd and linked Funs + * New/intrinsic - If followed by CProj: block tail else mid-block; followed by Proj + * */ public abstract class CFGNode extends Node { public CFGNode(Node... nodes) { super(nodes); } public CFGNode(CFGNode cfg) { super(cfg); - _idepth = cfg._idepth; - _idepthVersion = cfg._idepthVersion; - _ltree = cfg._ltree; - _pre = cfg._pre; + if( cfg != null ) { + _idepth = cfg._idepth; + _idepthVersion = cfg._idepthVersion; + _ltree = cfg._ltree; + _pre = cfg._pre; + } } public CFGNode cfg(int idx) { return (CFGNode)in(idx); } @@ -85,6 +97,14 @@ public abstract class CFGNode extends Node { return lhs; } + // Find nearest enclosing FunNode + public FunNode fun() { + CFGNode cfg = this; + while( !(cfg instanceof FunNode fun) ) + cfg = cfg.idom(); + return fun; + } + // ------------------------------------------------------------------------ // Loop nesting public LoopNode loop() { return _ltree._head; } @@ -169,12 +189,11 @@ public abstract class CFGNode extends Node { return pre; } - boolean skip(CFGNode usecfg) { + private boolean skip(CFGNode usecfg) { // Only walk control users that are alive. // Do not walk from a Call to linked Fun's. return usecfg instanceof XCtrlNode || (this instanceof CallNode && usecfg instanceof FunNode) || (this instanceof ReturnNode && usecfg instanceof CallEndNode); } - } diff --git a/src/main/java/com/seaofnodes/simple/node/CProjNode.java b/src/main/java/com/seaofnodes/simple/node/CProjNode.java index c338ff4c..bfdcb04b 100644 --- a/src/main/java/com/seaofnodes/simple/node/CProjNode.java +++ b/src/main/java/com/seaofnodes/simple/node/CProjNode.java @@ -3,6 +3,7 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeMem; import com.seaofnodes.simple.type.TypeTuple; @@ -26,7 +27,6 @@ public class CProjNode extends CFGNode { @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p(_label); } - @Override public boolean isMultiTail() { return in(0).isMultiHead(); } @Override public boolean blockHead() { return true; } public CFGNode ctrl() { return cfg(0); } diff --git a/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/src/main/java/com/seaofnodes/simple/node/CallEndNode.java index 83c98b97..0ee3d418 100644 --- a/src/main/java/com/seaofnodes/simple/node/CallEndNode.java +++ b/src/main/java/com/seaofnodes/simple/node/CallEndNode.java @@ -3,6 +3,7 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.*; /** @@ -13,13 +14,12 @@ public class CallEndNode extends CFGNode implements MultiNode { // When set true, this Call/CallEnd/Fun/Return is being trivially inlined private boolean _folding; public boolean folding() { return _folding; } + public final TypeRPC _rpc; - public CallEndNode(CallNode call) { super(new Node[]{call}); } - public CallEndNode(CallEndNode cend) { super(cend); } + public CallEndNode(CallNode call) { super(new Node[]{call}); _rpc = TypeRPC.constant(_nid); } + public CallEndNode(CallEndNode cend) { super(cend); _rpc = cend._rpc; } - @Override - public String label() { return "CallEnd"; } - @Override public boolean isMultiHead() { return true; } + @Override public String label() { return "CallEnd"; } @Override public boolean blockHead() { return true; } public CallNode call() { return (CallNode)in(0); } @@ -37,7 +37,17 @@ public class CallEndNode extends CFGNode implements MultiNode { public Type compute() { if( !(in(0) instanceof CallNode call) ) return TypeTuple.RET.dual(); - Type ret = call.fptr().addDep(this)._type instanceof TypeFunPtr tfp ? tfp.ret() : Type.BOTTOM; + Type ret = Type.BOTTOM; + TypeMem mem = TypeMem.BOT; + if( call.fptr().addDep(this)._type instanceof TypeFunPtr tfp ) { + ret = tfp.ret(); + // Here, if I can figure out I've found *all* callers, then I can meet + // across the linked returns and join with the function return type. + if( tfp.isConstant() && nIns()>1 ) { + assert nIns()==2; // Linked exactly once for a constant + ret = ((TypeTuple)in(1)._type).ret(); // Return type + } + } return TypeTuple.make(call._type,TypeMem.BOT,ret); } @@ -86,6 +96,99 @@ public class CallEndNode extends CFGNode implements MultiNode { return null; } + // Clone only tiny, straight-line boilerplate, after ordinary peeps settle. + // Larger bodies still use the existing single-caller, non-cloning inline. + public boolean inlineSmall() { + if( isDead() || _folding || nIns()!=2 || !(in(0) instanceof CallNode call) ) return false; + Node fptr = call.fptr(); + if( !(fptr instanceof ConstantNode) || + !(fptr._type instanceof TypeFunPtr tfp && tfp.notNull() && tfp.isConstant()) ) + { fptr.addDep(this); return false; } + if( call.err()!=null ) return false; + ReturnNode ret = (ReturnNode)in(1); + FunNode fun = ret.fun(); + if( fun._folding ) return false; + int len = smallBody(fun); + if( len==0 ) return false; + // All callers consume the three normal return projections. + for( Node use : _outputs ) + if( !((use instanceof ProjNode p && p._idx>=0 && p._idx<3) || + (use instanceof CProjNode cp && cp._idx==0)) ) return false; + + // Substitute control and arguments directly; no new function identity, + // parameter Phis, or return address is needed for this straight line. + Node[] copy = new Node[len]; + for( int i=0; i<len; i++ ) { + Node n = TINYBODY[i]; + copy[i] = n==fun ? call.ctrl() : + n instanceof ParmNode parm ? (parm._idx==0 ? null : call.arg(parm._idx)) : + n==ret ? null : n.copyEmpty(); + } + for( int i=0; i<len; i++ ) { + Node n = TINYBODY[i]; + if( n instanceof StoreNode || n instanceof MemMergeNode ) { + for( Node in : n._inputs ) copy[i].addDef(mapped(in,copy)); + CodeGen.CODE.add(copy[i]); + } + } + Node[] result = new Node[3]; + for( int i=0; i<3; i++ ) result[i] = mapped(ret.in(i),copy).keep(); + keep(); + call.unlink_all(); + CodeGen.CODE.add(fun); + CodeGen.CODE.addAll(fun._outputs); + // subsume kills each projection; retain CallEnd until all are replaced. + while( nOuts()>1 ) { + Node use = out(0)==null ? out(1) : out(0); + int idx = use instanceof ProjNode p ? p._idx : ((CProjNode)use)._idx; + CodeGen.CODE.addAll(use._outputs); + use.subsume(result[idx]); + } + unkill(); + for( Node n : result ) n.unkill(); + CodeGen.CODE.invalidateIDepthCaches(); + return true; + } + + private static Node mapped(Node n, Node[] copy) { + for( int i=0; i<copy.length; i++ ) + if( TINYBODY[i]==n ) return copy[i]; + return n; // Constants outside the body are shared. + } + + // Bounded recognition of initializer boilerplate. Walk uses from the entry + // and stop at Return, never following callers or constant inputs. This also + // avoids scanning sparse MemMerge input arrays while a large body shrinks. + // Watch this small prefix, including an unsupported node that may fold. + private static final Node[] TINYBODY = new Node[15]; // Tiny worklist + private int smallBody(FunNode fun) { + ReturnNode ret = fun.ret(); + for( Node in : ret._inputs ) + if( in!=null ) in.addDep(this); + if( ret.ctrl()!=fun ) return 0; + TINYBODY[0] = fun; + int len=1; + for( int i=0; i<len; i++ ) { + Node n = TINYBODY[i]; + n.addDep(this); + if( n==ret ) continue; + if( n instanceof ParmNode parm && parm.region()!=fun ) return 0; + if( n!=fun && !(n instanceof ParmNode) && + !(n instanceof StoreNode) && !(n instanceof MemMergeNode) ) + return 0; + if( n.nOuts()>TINYBODY.length ) return 0; + for( Node use : n._outputs ) { + if( use==null ) continue; + int j=0; + while( j<len && TINYBODY[j]!=use ) j++; + if( j<len ) continue; + if( len==TINYBODY.length ) { use.addDep(this); return 0; } + TINYBODY[len++] = use; + } + } + return len; + } + @Override public Node pcopy(int idx) { return _folding ? in(1).in(idx) : null; } diff --git a/src/main/java/com/seaofnodes/simple/node/CallNode.java b/src/main/java/com/seaofnodes/simple/node/CallNode.java index 22cef22d..371a1d69 100644 --- a/src/main/java/com/seaofnodes/simple/node/CallNode.java +++ b/src/main/java/com/seaofnodes/simple/node/CallNode.java @@ -2,7 +2,7 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; -import com.seaofnodes.simple.CodeGen; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.Utils; @@ -20,8 +20,7 @@ public class CallNode extends CFGNode { public CallNode(Parser.Lexer loc, Node... nodes) { super(nodes); _loc = loc; } public CallNode(CallNode call) { super(call); _loc = call._loc; } - @Override - public String label() { return "Call"; } + @Override public String label() { return "Call"; } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { String fname = name(); @@ -45,9 +44,9 @@ public class CallNode extends CFGNode { Node ctrl() { return in(0); } Node mem () { return in(1); } // Args mapped 1-to-1 on inputs, so conceptually start at 2 - Node arg(int idx) { return in(idx); } + public Node arg(int idx) { return in(idx); } // Same arg accounting as TFPs, although the numbering starts at 2 - int nargs() { return nIns()-3; } // Minus control, memory, fptr + public int nargs() { return nIns()-3; } // Minus control, memory, fptr // args from input 2 to last; last is function input public Node fptr() { return _inputs.last(); } // Error if not a TFP @@ -124,12 +123,28 @@ public class CallNode extends CFGNode { fun.addDef(this); for( Node use : fun._outputs ) if( use instanceof ParmNode parm ) - parm.addDef(parm._idx==0 ? cend() : arg(parm._idx)); + parm.addDef(parm._idx==0 ? new ConstantNode(cend()._rpc).peephole() : arg(parm._idx)); // Call end points to function return CodeGen.CODE.add(cend()).addDef(fun.ret()); assert linked(fun); } + // Unlink all linked functions + public void unlink_all() { + for( int i=0; i<_outputs._len; i++ ) + if( out(i) instanceof FunNode fun ) { + assert linked(fun); + int idx = fun._inputs.find(this); + for( Node use : fun._outputs ) + if( use instanceof ParmNode parm ) + use.delDef(idx); + fun.delDef(idx); + cend().delDef(cend()._inputs.find(fun.ret())); + assert !linked(fun); + i--; + } + } + @Override public Parser.ParseException err() { if( !(fptr()._type instanceof TypeFunPtr tfp) ) diff --git a/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java b/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java new file mode 100644 index 00000000..d4a937bd --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java @@ -0,0 +1,25 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.type.Type; +import java.util.BitSet; +import java.io.ByteArrayOutputStream; + +public class CalleeSaveNode extends ProjNode implements MachNode { + final RegMask _mask; + public CalleeSaveNode(FunNode fun, int reg, String label) { + super(fun,reg,label); + fun._outputs.pop(); + int i=0; + while( fun.out(i) instanceof PhiNode ) i++; + fun._outputs.insert(this,i); + _mask = new RegMask(reg); + } + @Override public RegMask regmap(int i) { return null; } + @Override public RegMask outregmap() { return _mask; } + @Override public int encoding(ByteArrayOutputStream bytes) { return 0; } + + @Override public Type compute() { return Type.BOTTOM; } + @Override public Node idealize() { return null; } +} diff --git a/src/main/java/com/seaofnodes/simple/node/CastNode.java b/src/main/java/com/seaofnodes/simple/node/CastNode.java index 6bf8ee90..9aedf53e 100644 --- a/src/main/java/com/seaofnodes/simple/node/CastNode.java +++ b/src/main/java/com/seaofnodes/simple/node/CastNode.java @@ -10,13 +10,19 @@ import com.seaofnodes.simple.type.TypeInteger; // Upcast (join) the input to a t. Used after guard test to lift an input. // Can also be used to make a type-assertion if ctrl is null. public class CastNode extends Node { - private final Type _t; + public Type _t; public CastNode(Type t, Node ctrl, Node in) { super(ctrl, in); _t = t; setType(compute()); } + public CastNode(CastNode c) { + super(c.in(0), c.in(1)); // Call parent constructor + this._t = c._t; // Copy the Type field + setType(compute()); // Ensure type is recomputed + } + @Override public String label() { return "("+_t.str()+")"; } diff --git a/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/src/main/java/com/seaofnodes/simple/node/ConstantNode.java index 89014205..cadb6511 100644 --- a/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -2,7 +2,7 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; -import com.seaofnodes.simple.CodeGen; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.SB; import com.seaofnodes.simple.type.Type; @@ -27,7 +27,8 @@ public class ConstantNode extends Node { super(new Node[]{CodeGen.CODE._start}); _con = _type = type; } - public ConstantNode( ConstantNode con ) { super(con); _con = con._type; } + public ConstantNode( Node con, Type t ) { super(con); _con = t; } + public ConstantNode( ConstantNode con ) { this(con,con._type); } public static Node make( Type type ) { if( type==Type. CONTROL ) return new CtrlNode(); @@ -51,7 +52,6 @@ public class ConstantNode extends Node { return p.p(_con.print(new SB())); } - @Override public boolean isMultiTail() { return true; } @Override public boolean isConst() { return true; } @Override diff --git a/src/main/java/com/seaofnodes/simple/node/CtrlNode.java b/src/main/java/com/seaofnodes/simple/node/CtrlNode.java index e647a68a..cf630ad4 100644 --- a/src/main/java/com/seaofnodes/simple/node/CtrlNode.java +++ b/src/main/java/com/seaofnodes/simple/node/CtrlNode.java @@ -2,7 +2,7 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; -import com.seaofnodes.simple.CodeGen; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.Type; diff --git a/src/main/java/com/seaofnodes/simple/node/DivFNode.java b/src/main/java/com/seaofnodes/simple/node/DivFNode.java index a3f4318d..149499f9 100644 --- a/src/main/java/com/seaofnodes/simple/node/DivFNode.java +++ b/src/main/java/com/seaofnodes/simple/node/DivFNode.java @@ -24,14 +24,9 @@ public class DivFNode extends Node { @Override public Node idealize() { - Node lhs = in(1); - Node rhs = in(2); - Type t1 = lhs._type; - Type t2 = rhs._type; - - // Div of 1. - if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==1 ) - return lhs; + // Div of constant + if( in(2)._type instanceof TypeFloat f && f.isConstant() ) + return new MulFNode(in(1),new ConstantNode(TypeFloat.constant(1.0/f.value())).peephole()); return null; } diff --git a/src/main/java/com/seaofnodes/simple/node/FunNode.java b/src/main/java/com/seaofnodes/simple/node/FunNode.java index d24f4829..2c91d1fc 100644 --- a/src/main/java/com/seaofnodes/simple/node/FunNode.java +++ b/src/main/java/com/seaofnodes/simple/node/FunNode.java @@ -3,8 +3,10 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.*; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeFunPtr; +import com.seaofnodes.simple.type.TypeTuple; + import java.util.BitSet; -import static com.seaofnodes.simple.CodeGen.CODE; +import static com.seaofnodes.simple.codegen.CodeGen.CODE; public class FunNode extends RegionNode { @@ -18,10 +20,19 @@ public class FunNode extends RegionNode { public String _name; // Debug name public FunNode( Parser.Lexer loc, TypeFunPtr sig, Node... nodes ) { super(loc,nodes); _sig = sig; } - public FunNode( FunNode fun ) { super( fun, fun._loc ); _sig = fun.sig(); _name = fun._name; } + public FunNode( FunNode fun ) { + super( fun, fun==null ? null : fun._loc ); + if( fun!=null ) { + _sig = fun.sig(); + _name = fun._name; + } else { + _sig = TypeFunPtr.BOT; + _name = ""; + } + } @Override - public String label() { return _name == null ? "$fun" : _name; } + public String label() { return _name == null ? "$fun"+_sig.fidx() : _name; } // Find the one CFG user from Fun. It's not always the Return, but always // the Return *is* a CFG user of Fun. @@ -44,11 +55,11 @@ public class FunNode extends RegionNode { // Cannot create the Return and Fun at the same time; one has to be first. // So setting the return requires a second step. public void setRet(ReturnNode ret) { _ret=ret; } - ReturnNode ret() { assert _ret!=null; return _ret; } + public ReturnNode ret() { assert _ret!=null; return _ret; } // Signature can improve over time public TypeFunPtr sig() { return _sig; } - void setSig( TypeFunPtr sig ) { + public void setSig( TypeFunPtr sig ) { assert sig.isa(_sig); if( _sig != sig ) { CODE.add(this); @@ -73,6 +84,10 @@ public class FunNode extends RegionNode { return progress; } + // Upgrade inferred or user-written return type to actual + if( _ret!=null && _ret._type instanceof TypeTuple tt && tt.ret() != _sig.ret() ) + throw Utils.TODO(); + // When can we assume no callers? Or no other callers (except main)? // In a partial compilation, we assume Start gets access to any/all // top-level public structures and recursively what they point to. diff --git a/src/main/java/com/seaofnodes/simple/node/IfNode.java b/src/main/java/com/seaofnodes/simple/node/IfNode.java index a01eb8bd..2c4cad70 100644 --- a/src/main/java/com/seaofnodes/simple/node/IfNode.java +++ b/src/main/java/com/seaofnodes/simple/node/IfNode.java @@ -1,7 +1,7 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.CodeGen; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.type.*; import java.util.HashSet; @@ -19,8 +19,6 @@ public class IfNode extends CFGNode implements MultiNode { @Override protected String format() { return "if( %1 )"; } - @Override public boolean isMultiHead() { return true; } - public Node ctrl() { return in(0); } public Node pred() { return in(1); } diff --git a/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/src/main/java/com/seaofnodes/simple/node/LoadNode.java index a06d1291..b9905912 100644 --- a/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -2,7 +2,7 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; -import com.seaofnodes.simple.CodeGen; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.type.*; @@ -227,9 +227,9 @@ public class LoadNode extends MemOpNode { return new AndNode(null,val,con(ti._max)); // Signed extension int shift = Long.numberOfLeadingZeros(ti._max)-1; - if( shift==0 ) - return val; Node shf = con(shift); + if( shf._type==TypeInteger.ZERO ) + return val; Node shl = new ShlNode(null,val,shf.keep()).peephole(); return new SarNode(null,shl,shf.unkeep()); } diff --git a/src/main/java/com/seaofnodes/simple/node/LogicalNode.java b/src/main/java/com/seaofnodes/simple/node/LogicalNode.java index c59bd948..6ac16d31 100644 --- a/src/main/java/com/seaofnodes/simple/node/LogicalNode.java +++ b/src/main/java/com/seaofnodes/simple/node/LogicalNode.java @@ -20,8 +20,8 @@ public abstract class LogicalNode extends Node { @Override public Parser.ParseException err() { if( in(1)._type.isHigh() || in(2)._type.isHigh() ) return null; - if( !(in(1)._type instanceof TypeInteger) ) return Parser.error("Cannot '"+op()+"' " + in(1)._type.glb(),_loc); - if( !(in(2)._type instanceof TypeInteger) ) return Parser.error("Cannot '"+op()+"' " + in(2)._type.glb(), _loc); + if( !(in(1)._type instanceof TypeInteger) ) return Parser.error("Cannot '"+op()+"' " + in(1)._type,_loc); + if( !(in(2)._type instanceof TypeInteger) ) return Parser.error("Cannot '"+op()+"' " + in(2)._type,_loc); return null; } } diff --git a/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java b/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java index c1f007e8..4f5d9151 100644 --- a/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java @@ -8,7 +8,7 @@ import java.io.ByteArrayOutputStream; // Generic machine-specific class, has a few Node implementations that have to // exist (abstract) but are not useful past the optimizer. -public abstract class MachConcreteNode extends Node implements MachNode{ +public abstract class MachConcreteNode extends Node implements MachNode { public MachConcreteNode(Node node) { super(node); } public MachConcreteNode(Node[]nodes) { super(nodes); } diff --git a/src/main/java/com/seaofnodes/simple/node/MachNode.java b/src/main/java/com/seaofnodes/simple/node/MachNode.java index 9f2fee85..ecdbc651 100644 --- a/src/main/java/com/seaofnodes/simple/node/MachNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MachNode.java @@ -1,6 +1,9 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.Machine; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import java.io.ByteArrayOutputStream; public interface MachNode { @@ -12,13 +15,16 @@ public interface MachNode { default void postSelect() { } // Register mask allowed on input i. 0 for no register. - abstract public RegMask regmap(int i); + RegMask regmap( int i ); // Register mask allowed as a result. 0 for no register. - abstract public RegMask outregmap(); + RegMask outregmap(); // Multi-reg-defining machine op; idx comes from Proj. // Sometimes these Projs have no uses, and just exist to kill a register. default RegMask outregmap(int idx) { throw Utils.TODO(); } + // Set of killed registers + default RegMask killmap() { return null; } + // If this op updates the same register as input 'idx', e.g. many X86 "R1 += // R2" style ops. This forces the output live range to equal the input live // range, so they both get the same register. @@ -32,11 +38,17 @@ public interface MachNode { // Returns either 0 for not-two-adr or the updated input; for projections // this is their Multi's updated input. default int twoAddress( ) { return 0; } + // Ok to switch arguments + default boolean commutes() { return false; } // Instructions cheaper to recreate than to spill, such as loading small constants default boolean isClone() { return false; } + + // Make a clone of a cheap instruction + default Node copy() { return null; } + // Encoding is appended into the byte array; size is returned - abstract public int encoding(ByteArrayOutputStream bytes); + int encoding(ByteArrayOutputStream bytes); // Human-readable form appended to the SB. Things like the encoding, // indentation, leading address or block labels not printed here. diff --git a/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java index 5f4cf128..c8a76714 100644 --- a/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java @@ -2,9 +2,10 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; -import com.seaofnodes.simple.CodeGen; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMem; +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.type.*; +import java.util.*; /** A default slice and explicit alias overrides. A null default means partial * memory: absent aliases are not covered, rather than having unknown contents. diff --git a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java index e9fccc74..4f404846 100644 --- a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java @@ -36,14 +36,14 @@ public abstract class MemOpNode extends Node { } public MemOpNode( Node mach, MemOpNode mop ) { super(mach); - _name = mop._name; - _alias = mop._alias; - _declaredType = mop._declaredType; - _loc = mop._loc; + _name = mop==null ? null : mop._name; + _alias = mop==null ? 0 : mop._alias; + _loc = mop==null ? null : mop._loc; + _declaredType = mop==null ? Type.BOTTOM : mop._declaredType; } // - static String mlabel(String name) { return name.equals("[]") ? "ary" : (name.equals("#") ? "len" : name); } + static String mlabel(String name) { return "[]".equals(name) ? "ary" : ("#".equals(name) ? "len" : name); } String mlabel() { return mlabel(_name); } public Node mem() { return in(1); } diff --git a/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java index 38cbcd5b..df2c72f7 100644 --- a/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java @@ -1,6 +1,6 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.CodeGen; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeMem; diff --git a/src/main/java/com/seaofnodes/simple/node/MulNode.java b/src/main/java/com/seaofnodes/simple/node/MulNode.java index 720f15d7..ac97725c 100644 --- a/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -73,8 +73,8 @@ public class MulNode extends Node { @Override Node copyF() { return new MulFNode(null,null); } @Override public Parser.ParseException err() { if( in(1)._type.isHigh() || in(2)._type.isHigh() ) return null; - if( !(in(1)._type instanceof TypeInteger) ) return Parser.error("Cannot '"+label()+"' " + in(1)._type,null); - if( !(in(2)._type instanceof TypeInteger) ) return Parser.error("Cannot '"+label()+"' " + in(2)._type,null); + if( !(in(1)._type instanceof TypeInteger) ) return Parser.error("Cannot '"+label()+"' " + in(1)._type.glb(),null); + if( !(in(2)._type instanceof TypeInteger) ) return Parser.error("Cannot '"+label()+"' " + in(2)._type.glb(),null); return null; } } diff --git a/src/main/java/com/seaofnodes/simple/node/Node.java b/src/main/java/com/seaofnodes/simple/node/Node.java index dd37b051..e038d351 100644 --- a/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/src/main/java/com/seaofnodes/simple/node/Node.java @@ -2,14 +2,15 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.BaseNode; -import com.seaofnodes.simple.CodeGen; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.print.IRPrinter; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeFloat; import com.seaofnodes.simple.type.TypeInteger; import java.util.*; import java.util.function.Function; -import static com.seaofnodes.simple.CodeGen.CODE; +import static com.seaofnodes.simple.codegen.CodeGen.CODE; /** * All Nodes in the Sea of Nodes IR inherit from the Node class. @@ -58,10 +59,10 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { // Used by any pass rewriting all Node classes but not the edges. Node( Node n ) { super(CODE.getUID()); // allocate unique dense ID - assert CodeGen.CODE._phase == CodeGen.Phase.InstructionSelection; - _inputs = new Ary<>(n._inputs.asAry()); + assert CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.InstSelect.ordinal(); + _inputs = new Ary<>(n==null ? new Node[0] : n._inputs.asAry()); _outputs = new Ary<>(Node.class); - _type = n._type; + _type = n==null ? Type.BOTTOM : n._type; _deps = null; _hash = 0; } @@ -71,9 +72,7 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { public String p(int depth) { return IRPrinter.prettyPrint(this,depth); } - public boolean isMultiHead() { return false; } - public boolean isMultiTail() { return false; } - public boolean isConst () { return false; } + public boolean isConst() { return false; } // ------------------------------------------------------------------------ // Graph Node & Edge manipulation @@ -133,12 +132,16 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { // Return new_def for easy flow-coding return new_def; } + public <N extends Node> N setDefX(int idx, N new_def ) { + while( nIns() <= idx ) addDef(null); + return setDef(idx,new_def); + } // Remove the numbered input, compressing the inputs in-place. This // shuffles the order deterministically - which is suitable for Region and // Phi, but not for every Node. If the def goes dead, it is recursively // killed, which may include 'this' Node. - Node delDef(int idx) { + public Node delDef(int idx) { unlock(); Node old_def = in(idx); _inputs.del(idx); @@ -244,6 +247,67 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { kill(); } + // insert `this` immediately after `def` in the same basic block. + public void insertAfter( Node def ) { + CFGNode cfg = def.cfg0(); + int i = cfg._outputs.find(def)+1; + if( cfg instanceof CallEndNode ) { + cfg = cfg.uctrl(); i=0; + } else if( def.in(0) instanceof MultiNode ) { + assert i==0; + i = cfg._outputs.find(def.in(0))+1; + } + + while( cfg.out(i) instanceof PhiNode || cfg.out(i) instanceof CalleeSaveNode ) i++; + cfg._outputs.insert(this,i); + _inputs.set(0,cfg); + } + + // Insert this in front of use.in(uidx) with this, and insert this + // immediately before use in the basic block. + public void insertBefore( Node use, int uidx ) { + CFGNode cfg = use.cfg0(); + int i; + if( use instanceof PhiNode phi ) { + cfg = phi.region().cfg(uidx); + i = cfg.nOuts()-1; + } else { + i = cfg._outputs.find(use); + } + cfg._outputs.insert(this,i); + _inputs.set(0,cfg); + if( _inputs._len > 1 && this instanceof SplitNode ) + setDefOrdered(1,use.in(uidx)); + use.setDefOrdered(uidx,this); + } + + public void setDefOrdered(int idx, Node def) { + // If old is dying, remove from CFG ordered + Node old = in(idx); + if( old!=null && old.nOuts()==1 ) { + CFGNode cfg = old.cfg0(); + if( cfg!=null ) { + cfg._outputs.remove(cfg._outputs.find(old)); + old._inputs.set(0,null); + } + } + setDef(idx,def); + } + + public void killOrdered() { + CFGNode cfg = cfg0(); + cfg._outputs.remove(cfg._outputs.find(this)); + _inputs.set(0,null); + kill(); + } + + public void removeSplit() { + CFGNode cfg = cfg0(); + cfg._outputs.remove(cfg._outputs.find(this)); + _inputs.set(0,null); + if( _inputs._len > 1 ) subsume(in(1)); + } + // ------------------------------------------------------------------------ // Graph-based optimizations @@ -531,7 +595,7 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { // Peephole utilities // Swap inputs without letting either input go dead during the swap. - Node swap12() { + public Node swap12() { unlock(); // Hash is order dependent _inputs.swap(1,2); return this; @@ -600,7 +664,14 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { /** * Debugging utility to find a Node by index */ - public Node find(int nid) { - return walk( n -> n._nid==nid ? n : null ); + public Node find(int nid) { return _find(nid, new BitSet()); } + private Node _find(int nid, BitSet bs) { + if( bs.get(_nid) ) return null; // Been there, done that + bs.set(_nid); + if( _nid==nid ) return this; + Node x; + for( Node def : _inputs ) if( def != null && (x = def._find(nid,bs)) != null ) return x; + for( Node use : _outputs ) if( use != null && (x = use._find(nid,bs)) != null ) return x; + return null; } } diff --git a/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/src/main/java/com/seaofnodes/simple/node/PhiNode.java index 6fb315b4..a752977b 100644 --- a/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -41,7 +41,6 @@ public class PhiNode extends Node { } public CFGNode region() { return (CFGNode)in(0); } - @Override public boolean isMultiTail() { return true; } @Override public boolean isMem() { return _declaredType instanceof TypeMem; } @Override @@ -72,6 +71,11 @@ public class PhiNode extends Node { if (live != null) return live; + // No bother if region is going to fold dead paths soon + for( int i=1; i<nIns(); i++ ) + if( r.in(i)._type == Type.XCONTROL ) + return null; + // Phi(op(a,b,...),op(c,d,...)) -> op(Phi(a,c),Phi(b,d),...). Node down; if( same_op() && (down=drop_same_op())!=null ) return down; @@ -168,6 +172,9 @@ public class PhiNode extends Node { } // Factoring must not widen the result (e.g. correlated And operands). if( cp.compute().isa(compute()) ) return cp; + for( int i=1; i<nIns(); i++ ) + for( int j=1; j<in(i).nIns(); j++ ) + if( in(i).in(j)!=null ) in(i).in(j).addDep(this); cp.kill(); return null; } diff --git a/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/src/main/java/com/seaofnodes/simple/node/ProjNode.java index 6ae4a60d..da5841f6 100644 --- a/src/main/java/com/seaofnodes/simple/node/ProjNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ProjNode.java @@ -36,7 +36,6 @@ public class ProjNode extends Node { return in(0) instanceof CFGNode cfg ? cfg : in(0).cfg0(); } - @Override public boolean isMultiTail() { return in(0).isMultiHead(); } @Override public boolean isMem() { return _type instanceof TypeMem; } @Override diff --git a/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/src/main/java/com/seaofnodes/simple/node/RegionNode.java index e1f095a9..8993a49f 100644 --- a/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ b/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -3,6 +3,7 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.Type; import java.util.*; @@ -12,7 +13,7 @@ public class RegionNode extends CFGNode { public RegionNode(Parser.Lexer loc, Node... nodes) { super(nodes); _loc = loc; } public RegionNode(RegionNode r, Parser.Lexer loc) { super(r); _loc = loc; } - public RegionNode(RegionNode r) { super(r); _loc = r._loc; } + public RegionNode(RegionNode r) { super(r); if( r!=null ) _loc = r._loc; } @Override public String label() { return "Region"; } @@ -21,7 +22,6 @@ public class RegionNode extends CFGNode { return p.p(label()); } - @Override public boolean isMultiHead() { return true; } @Override public boolean blockHead() { return true; } @Override diff --git a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index 0716aa36..e101ee09 100644 --- a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -30,7 +30,8 @@ public class ReturnNode extends CFGNode { @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { p.p("return "); - p.n(expr()); + if( expr()==null ) p.p("----"); + else p.n(expr()); return p.p(";"); } @@ -40,7 +41,7 @@ public class ReturnNode extends CFGNode { @Override public Type compute() { if( inProgress () ) return TypeTuple.RET; // In progress - if( _fun.isDead() ) return TypeTuple.RET.dual(); // Dead another way + // Inlining can delete the entry while callers still use this return. return TypeTuple.make(ctrl()._type,TypeMem.BOT,expr()._type); } diff --git a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index 881f65d6..8d3e3089 100644 --- a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -3,6 +3,7 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.*; import java.util.*; diff --git a/src/main/java/com/seaofnodes/simple/node/SplitNode.java b/src/main/java/com/seaofnodes/simple/node/SplitNode.java new file mode 100644 index 00000000..07166a7c --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/SplitNode.java @@ -0,0 +1,26 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.type.Type; + +public abstract class SplitNode extends MachConcreteNode { + public final String _kind; // Kind of split + public final byte _round; + public SplitNode(String kind, byte round, Node[] nodes) { super(nodes); _kind = kind; _round = round; } + @Override public String op() { return "mov"; } + @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + p.p("mov("); + if( in(1)==null ) p.p("---"); + else p.n(in(1)); + return p.close(); + } + @Override public Type compute() { return in(0)._type; } + @Override public Node idealize() { return null; } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))); + } + @Override public String comment() { return _kind + " #"+ _round; } +} diff --git a/src/main/java/com/seaofnodes/simple/node/StartNode.java b/src/main/java/com/seaofnodes/simple/node/StartNode.java index d1151090..cbaefa6a 100644 --- a/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -2,7 +2,7 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; -import com.seaofnodes.simple.CodeGen; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.*; import java.util.HashSet; @@ -14,7 +14,7 @@ public class StartNode extends LoopNode implements MultiNode { final Type _arg; public StartNode(Type arg) { super((Parser.Lexer)null,null); _arg = arg; _type = compute(); } - public StartNode(StartNode start) { super(start); _arg = start._arg; } + public StartNode(StartNode start) { super(start); _arg = start==null ? null : start._arg; } @Override public String label() { return "Start"; } @@ -22,7 +22,6 @@ public class StartNode extends LoopNode implements MultiNode { return p.p(label()); } - @Override public boolean isMultiHead() { return true; } @Override public boolean blockHead() { return true; } @Override public CFGNode cfg0() { return null; } diff --git a/src/main/java/com/seaofnodes/simple/node/StopNode.java b/src/main/java/com/seaofnodes/simple/node/StopNode.java index 01199e9b..524c6a9e 100644 --- a/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ b/src/main/java/com/seaofnodes/simple/node/StopNode.java @@ -14,7 +14,7 @@ public class StopNode extends CFGNode { _src = src; _type = compute(); } - public StopNode(StopNode stop) { super(stop); _src = stop._src; } + public StopNode(StopNode stop) { super(stop); _src = stop==null ? null : stop._src; } @Override public String label() { diff --git a/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/src/main/java/com/seaofnodes/simple/node/StoreNode.java index 86c759bd..9e6689f0 100644 --- a/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -2,6 +2,7 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.*; import com.seaofnodes.simple.type.*; diff --git a/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java b/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java index 891291d0..477b9af8 100644 --- a/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java +++ b/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java @@ -2,7 +2,7 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; -import com.seaofnodes.simple.CodeGen; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.Type; @@ -11,7 +11,6 @@ public class XCtrlNode extends CFGNode { @Override public String label() { return "Xctrl"; } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("Xctrl"); } @Override public boolean isConst() { return true; } - @Override public boolean isMultiTail() { return true; } @Override public Type compute() { return Type.XCONTROL; } @Override public Node idealize() { return null; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java new file mode 100644 index 00000000..c4014571 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java @@ -0,0 +1,34 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class AddARM extends MachConcreteNode implements MachNode { + + AddARM( Node add) {super(add); } + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { + assert i== 1 || i == 2 || i == 3; + return arm.RMASK; + } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form: "rd = rs1 + rs2" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2))); + } + + @Override public String op() { + return "add";} +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java new file mode 100644 index 00000000..098ab8c1 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java @@ -0,0 +1,31 @@ +package com.seaofnodes.simple.node.cpus.arm; + + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class AddFARM extends MachConcreteNode implements MachNode { + AddFARM(Node addf) { super(addf); } + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { assert i==1 || i==2; return arm.DMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.DMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // Default on double precision for now(64 bits) + // General form: "vadd.32 rd = src1 + src2 + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2))); + } + + @Override public String op() { return "addf"; } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java new file mode 100644 index 00000000..c8f5f8dd --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java @@ -0,0 +1,51 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class AddIARM extends MachConcreteNode implements MachNode { + final TypeInteger _ti; + + AddIARM(Node add, TypeInteger ti) { + super(add); + _inputs.pop(); + _ti = ti; + } + + // Register mask allowed on input i. + @Override + public RegMask regmap(int i) { + // assert i== i; + return arm.RMASK; + } + + // Register mask allowed as a result. 0 for no register. + @Override + public RegMask outregmap() { + return arm.RMASK; + } + + // Encoding is appended into the byte array; size is returned + @Override + public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form: "addi rd = rs1 + imm" + @Override + public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + #"); + _ti.print(sb); + } + + @Override + public String op() { + return _ti.value() == 1 ? "inc" : (_ti.value() == -1 ? "dec" : "addi"); + + } + +}
\ No newline at end of file diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java new file mode 100644 index 00000000..7655df40 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java @@ -0,0 +1,38 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.Node; + +import java.io.ByteArrayOutputStream; + +public class AndARM extends MachConcreteNode implements MachNode { + AndARM(Node and) { + super(and); + } + + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { + assert i==1 || i==2; + return arm.RMASK; + } + + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: #rd = rs1 & rs2 + @Override public void asm(CodeGen code, SB sb){ + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" & ").p(code.reg(in(2))); + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java new file mode 100644 index 00000000..ee05654b --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java @@ -0,0 +1,56 @@ +package com.seaofnodes.simple.node.cpus.arm; + + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; +import java.util.BitSet; +import java.lang.StringBuilder; + +public class AndIARM extends MachConcreteNode implements MachNode { + final TypeInteger _ti; + + AndIARM(AndNode and, TypeInteger ti) { + super(and); + _inputs.pop(); + _ti = ti; + } + + // Register mask allowed on input i. + // This is the normal calling convention + @Override + public RegMask regmap(int i) { + assert i == 1 || i == 2; + return arm.RMASK; + } + + // Register mask allowed as a result. 0 for no register. + @Override + public RegMask outregmap() { + return arm.RMASK; + } + + // Encoding is appended into the byte array; size is returned + @Override + public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: "andi rd = rs1 & imm" + @Override + public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" & #"); + _ti.print(sb); + } + + @Override + public String op() { + return "andi"; + } + +}
\ No newline at end of file diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java new file mode 100644 index 00000000..a79de49b --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java @@ -0,0 +1,40 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; +import java.util.BitSet; +import java.lang.StringBuilder; + +// Arithmetic Shift Right (register) +public class AsrARM extends MachConcreteNode implements MachNode { + AsrARM(Node asr) {super(asr);} + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { + // assert i==1; + return arm.RMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } + + // Output is same register as input#1 + @Override public int twoAddress() { return 1; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: "asr rd, rs1, rs2" + @Override public void asm(CodeGen code, SB sb) { + + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> ").p(code.reg(in(2))); + } + + @Override public String op() { return "asr"; } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java new file mode 100644 index 00000000..099c3699 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java @@ -0,0 +1,41 @@ +package com.seaofnodes.simple.node.cpus.arm; + + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; +import java.util.BitSet; +import java.lang.StringBuilder; + +// Arithmetic Shift Right (immediate) +public class AsrIARM extends MachConcreteNode implements MachNode { + final TypeInteger _ti; + AsrIARM(Node asri, TypeInteger ti) {super(asri); _inputs.pop(); _ti = ti;} + + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { + // assert i==1; + return arm.RMASK; } + + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: "asri rd, rs1, imm" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> #"); + _ti.print(sb); + } + + @Override public String op() { return "asri"; } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CBranchRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java index d7e23c6d..1a5108cf 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CBranchRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java @@ -1,16 +1,17 @@ -package com.seaofnodes.simple.node.cpus.riscv; - +package com.seaofnodes.simple.node.cpus.arm; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; + import java.io.ByteArrayOutputStream; -// Conditional branch such as: BEQ -public class CBranchRISC extends IfNode implements MachNode{ + + +// Jump on flags, uses flags +public class BranchARM extends IfNode implements MachNode{ final String _bop; - // label is obtained implicitly - CBranchRISC( IfNode iff, String bop ) { + BranchARM(IfNode iff, String bop ) { super(iff); _bop = bop; } @@ -21,28 +22,26 @@ public class CBranchRISC extends IfNode implements MachNode{ Node set = in(1); Node cmp = set.in(1); // Bypass an expected Set and just reference the cmp directly - if( set instanceof SetRISC) + if( set instanceof SetARM) _inputs.set(1,cmp); else throw Utils.TODO(); } - @Override public RegMask regmap(int i) { assert i==1; return riscv.RMASK; } + @Override public RegMask regmap(int i) { assert i==1; return arm.FLAGS_MASK; } @Override public RegMask outregmap() { return null; } // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { throw Utils.TODO(); } @Override public void asm(CodeGen code, SB sb) { String src = code.reg(in(1)); - if( src!="FLAGS" ) sb.p(src); + if( src!="flags" ) sb.p(src); } - @Override public String op() { return "b"+_bop; } - + @Override public String op() { return "j"+_bop; } @Override public String comment() { return "L"+cproj(1)._nid+", L"+cproj(0)._nid; } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java new file mode 100644 index 00000000..0f883847 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java @@ -0,0 +1,45 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeFunPtr; +import java.io.ByteArrayOutputStream; + +public class CallARM extends CallNode implements MachNode { + final TypeFunPtr _tfp; + final String _name; + + CallARM(CallNode call, TypeFunPtr tfp) { + super(call); + _inputs.pop(); // Pop constant target + assert tfp.isConstant(); + _tfp = tfp; + _name = CodeGen.CODE.link(tfp)._name; + } + + @Override public String label() { return op(); } + @Override public RegMask regmap(int i) { + return arm.callInMask(_tfp,i); // Normal argument + } + @Override public RegMask outregmap() { return null; } + + @Override public String name() { return _name; } + @Override public TypeFunPtr tfp() { return _tfp; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(_name).p(" "); + for( int i=0; i<nargs(); i++ ) + sb.p(code.reg(arg(i))).p(" "); + sb.unchar(2); + } + + @Override public String op() { return "call"; } + +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallEndARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallEndARM.java new file mode 100644 index 00000000..6177c5ea --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallEndARM.java @@ -0,0 +1,35 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.CallEndNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.type.TypeFloat; +import com.seaofnodes.simple.type.TypeFunPtr; +import java.io.ByteArrayOutputStream; + +public class CallEndARM extends CallEndNode implements MachNode { + final TypeFunPtr _tfp; + CallEndARM( CallEndNode cend ) { + super(cend); + _tfp = (TypeFunPtr)(cend.call().fptr()._type); + } + + @Override public String label() { return op(); } + @Override public RegMask regmap(int i) { return null; } + @Override public RegMask outregmap() { return null; } + @Override public RegMask outregmap(int idx) { return idx == 2 ? arm.retMask(_tfp,2) : null; } + @Override public RegMask killmap() { return arm.armCallerSave(); } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { } + + @Override public String op() { return "cend"; } + +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java new file mode 100644 index 00000000..db85a9a9 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java @@ -0,0 +1,32 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import java.io.ByteArrayOutputStream; + +public class CallRRARM extends CallNode implements MachNode { + CallRRARM(CallNode call) {super(call);} + @Override public String op() { return "callr"; } + @Override public String label() { return op(); } + @Override public RegMask regmap(int i) { + // Todo: float or int? + return i==_inputs._len + ? arm.RMASK // Function call target + : arm.callInMask(tfp(),i); // Normal argument + } + @Override public RegMask outregmap() { return null; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(fptr())).p(" "); + for( int i=0; i<nargs(); i++ ) + sb.p(code.reg(arg(i))).p(" "); + sb.unchar(2); + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CmpRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java index 3bd4008e..f4f6242d 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CmpRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java @@ -1,28 +1,30 @@ -package com.seaofnodes.simple.node.cpus.riscv; +package com.seaofnodes.simple.node.cpus.arm; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeFunPtr; import java.io.ByteArrayOutputStream; -// Compare. Sets flags.(RFLAGS) -public class CmpRISC extends MachConcreteNode implements MachNode{ - CmpRISC( Node cmp ) { super(cmp); } +public class CmpARM extends MachConcreteNode implements MachNode { + CmpARM( Node cmp ) { super(cmp); } + @Override public String op() { return "cmp"; } + + + @Override public RegMask regmap(int i) { assert i==1 || i==2; return arm.RMASK; } + @Override public RegMask outregmap() { return arm.FLAGS_MASK; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { throw Utils.TODO(); } - // General form: "cmp rs1, rs2" @Override public void asm(CodeGen code, SB sb) { String dst = code.reg(this); - if( dst!="FLAGS" ) sb.p(dst).p(" = "); + if( dst!="flags" ) sb.p(dst).p(" = "); sb.p(code.reg(in(1))).p(", ").p(code.reg(in(2))); } - - @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.RMASK; } - @Override public RegMask outregmap() { return riscv.FLAGS_MASK; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java new file mode 100644 index 00000000..9c287fcb --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java @@ -0,0 +1,31 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFunPtr; +import java.io.ByteArrayOutputStream; + +// compare instruction on float regs input(D-registers) +public class CmpFARM extends MachConcreteNode implements MachNode{ + CmpFARM(Node cmp) {super(cmp);} + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form: "cmp d0, d1" + @Override public void asm(CodeGen code, SB sb) { + String dst = code.reg(this); + if( dst!="flags" ) sb.p(dst).p(" = "); + sb.p(code.reg(in(1))).p(", ").p(code.reg(in(2))); + } + + @Override public RegMask regmap(int i) { assert i==1 || i==2; return arm.DMASK; } + @Override public RegMask outregmap() { return arm.FLAGS_MASK; } + + @Override public String op() { return "cmpf"; } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java new file mode 100644 index 00000000..a0b10ba4 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java @@ -0,0 +1,54 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +// Compare with immediate. +// Conditional compare (immediate)? e.g CCMP(immediate) +public class CmpIARM extends MachConcreteNode implements MachNode { + final int _imm; + final String _bop; + CmpIARM(BoolNode bool, TypeInteger ti) { + super(bool); + _inputs.pop(); + _bop = bool.op(); + _imm = (int)ti.value(); + assert _imm == ti.value(); + } + // Copy constructor does not set reverse edges in, because the basic block + // is changing from the original node - and it must be properly placed in a + // new block. + CmpIARM( CmpIARM cmp ) { + super(cmp); + _bop = cmp._bop; + _imm = cmp._imm; + // While in(0) will be handled by the caller, all other edges must be + // handled in the constructor. + cmp.in(1)._outputs.push(this); + } + + @Override public RegMask regmap(int i) { assert i==1; return arm.RMASK; } + @Override public RegMask outregmap() { return arm.FLAGS_MASK; } + @Override public boolean isClone() { return true; } + @Override public Node copy() { return new CmpIARM(this); } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form: "cmp rs1, 1" + @Override public void asm(CodeGen code, SB sb) { + String dst = code.reg(this); + if( dst!="flags" ) sb.p(dst).p(" = "); + sb.p(code.reg(in(1))); + if( _imm != 0 ) sb.p(", #").p(_imm); + } + @Override public String op() { return _imm==0 ? "test" : "cmp"; } + + } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpMemARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpMemARM.java new file mode 100644 index 00000000..47f68ccc --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpMemARM.java @@ -0,0 +1,4 @@ +package com.seaofnodes.simple.node.cpus.arm; + +public class CmpMemARM { +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java new file mode 100644 index 00000000..7c67e2dd --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java @@ -0,0 +1,33 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class DivARM extends MachConcreteNode implements MachNode { + DivARM( Node div ) { super(div); } + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { + assert i==1 || i==2; + return arm.RMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } + // Output is same register as input#1 + @Override public int twoAddress() { return 1; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form: "div dst /= src" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2))); + } + + @Override public String op() { return "div"; } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java new file mode 100644 index 00000000..2137c01c --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java @@ -0,0 +1,30 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class DivFARM extends MachConcreteNode implements MachNode { + DivFARM( Node divf) { super(divf); } + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { assert i==1 || i==2; return arm.DMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.DMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + + // General form: "VDIF = dst /= src" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2))); + } + + @Override public String op() { return "divf"; } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java new file mode 100644 index 00000000..937c437b --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java @@ -0,0 +1,28 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +// VCVT (between floating-point and integer) +public class F8ToIARM extends MachConcreteNode implements MachNode { + F8ToIARM(Node f8toi) {super(f8toi);} + @Override public String op() { return "f8toi"; } + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { assert i==1; return arm.DMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.WMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1))); + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java new file mode 100644 index 00000000..7fafc31c --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java @@ -0,0 +1,36 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.ConstantNode; +import com.seaofnodes.simple.node.MachNode; +import java.io.ByteArrayOutputStream; + +//FMOV (scalar, immediate) +//Floating-point move immediate. +public class FloatARM extends ConstantNode implements MachNode { + FloatARM(ConstantNode con) { super(con); } + @Override public String op() { return "fld"; } + // Register mask allowed on input i. 0 for no register. + @Override public RegMask regmap(int i) { return null; } + // General int registers + @Override public RegMask outregmap() { return arm.DMASK; } + + @Override public boolean isClone() { return true; } + @Override public FloatARM copy() { return new FloatARM(this); } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // Human-readable form appended to the SB. Things like the encoding, + // indentation, leading address or block labels not printed here. + // Just something like "ld4\tR17=[R18+12] // Load array base". + // General form: "op\tdst=src+src" + @Override public void asm(CodeGen code, SB sb) { + _con.print(sb.p(code.reg(this)).p(" #")); + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java new file mode 100644 index 00000000..0134911f --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java @@ -0,0 +1,21 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.node.FunNode; +import com.seaofnodes.simple.node.MachNode; +import java.io.ByteArrayOutputStream; + +public class FunARM extends FunNode implements MachNode{ + FunARM(FunNode fun) {super(fun);} + // Register mask allowed on input i. 0 for no register. + @Override public RegMask regmap(int i) { return null; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return null; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java new file mode 100644 index 00000000..b106ab8a --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java @@ -0,0 +1,29 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +// VCVT (between floating-point and integer) +public class I2F8ARM extends MachConcreteNode implements MachNode { + I2F8ARM(Node i2f8) {super(i2f8);} + @Override public String op() { return "i2f8"; } + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { assert i==1; return arm.RMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.DMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form: "i2f8 (flt)int_value" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p("(flt)").p(code.reg(in(1))); + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java new file mode 100644 index 00000000..1f81300e --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java @@ -0,0 +1,38 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.ConstantNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +// Integer constants +public class IntARM extends ConstantNode implements MachNode { + IntARM( ConstantNode con ) { super(con); } + @Override public String op() { return "ldi"; } + + // Register mask allowed on input i. 0 for no register. + @Override public RegMask regmap(int i) { return null; } + // General int registers + @Override public RegMask outregmap() { return arm.WMASK; } + + @Override public boolean isClone() { return true; } + @Override public IntARM copy() { return new IntARM(this); } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // Human-readable form appended to the SB. Things like the encoding, + // indentation, leading address or block labels not printed here. + // Just something like "ld4\tR17=[R18+12] // Load array base". + // General form: "op\tdst=src+src" + @Override public void asm(CodeGen code, SB sb) { + String reg = code.reg(this); + _con.print(sb.p(reg).p(" = #")); + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java new file mode 100644 index 00000000..0d59cc47 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java @@ -0,0 +1,37 @@ +package com.seaofnodes.simple.node.cpus.arm; + + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.LoadNode; +import com.seaofnodes.simple.node.Node; + + +// Load memory addressing on ARM +// Support imm, reg(direct), or reg+off(indirect) addressing +// Base = base - base pointer, offset is added to base +// idx = null +// off = off - offset added to base + +public class LoadARM extends MemOpARM{ + LoadARM(LoadNode ld,Node base, Node idx, int off) { + super(ld, base, idx, off, 0); + } + + @Override public RegMask regmap(int i) { + return arm.RMASK; + } + + // Wide mask loads both ints and floats; encoding varies. + @Override public RegMask outregmap() { return arm.MEM_MASK; } + + + @Override public String op() { return "ld"+_sz; } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(","); + asm_address(code,sb); + } + +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java new file mode 100644 index 00000000..6108c889 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java @@ -0,0 +1,41 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; +import java.util.BitSet; +import java.lang.StringBuilder; + + +// Logical Shift Left (register) +public class LslARM extends MachConcreteNode implements MachNode { + LslARM(Node asr) {super(asr);} + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { + // assert i==1; + return arm.RMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } + + // Output is same register as input#1 + @Override public int twoAddress() { return 1; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: "lsl rd, rs1, rs2" + @Override public void asm(CodeGen code, SB sb) { + + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" << ").p(code.reg(in(2))); + } + + @Override public String op() { return "lsl"; } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java new file mode 100644 index 00000000..7a7014f4 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java @@ -0,0 +1,42 @@ +package com.seaofnodes.simple.node.cpus.arm; + + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; +import java.util.BitSet; +import java.lang.StringBuilder; + + +// Logical Shift Left (immediate) +public class LslIARM extends MachConcreteNode implements MachNode { + final TypeInteger _ti; + LslIARM(Node lsli, TypeInteger ti) {super(lsli); _inputs.pop(); _ti = ti;} + + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { + // assert i==1; + return arm.RMASK; } + + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: "lsli rd, rs1, imm" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" << #"); + _ti.print(sb); + } + + @Override public String op() { return "lsli"; } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java new file mode 100644 index 00000000..e6168dee --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java @@ -0,0 +1,40 @@ +package com.seaofnodes.simple.node.cpus.arm; + + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; +import java.util.BitSet; +import java.lang.StringBuilder; + +// Logical Shift Right (register) +public class LsrARM extends MachConcreteNode implements MachNode { + LsrARM(Node asr) {super(asr);} + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { + // assert i==1; + return arm.RMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } + + // Output is same register as input#1 + @Override public int twoAddress() { return 1; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: "lsr rd, rs1, rs2" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >>> ").p(code.reg(in(2))); + } + + @Override public String op() { return "lsr"; } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java new file mode 100644 index 00000000..8fb20cb2 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java @@ -0,0 +1,41 @@ +package com.seaofnodes.simple.node.cpus.arm; + + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; +import java.util.BitSet; +import java.lang.StringBuilder; + +// Logical Shift Right (immediate) +public class LsrIARM extends MachConcreteNode implements MachNode{ + final TypeInteger _ti; + LsrIARM(Node lsri, TypeInteger ti) {super(lsri); _inputs.pop(); _ti = ti;} + + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { + // assert i==1; + return arm.RMASK; } + + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: "lsrl rd, rs1, imm" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >>> #"); + _ti.print(sb); + } + + @Override public String op() { return "lsrl"; } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java new file mode 100644 index 00000000..c22e71f6 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java @@ -0,0 +1,61 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.*; +import java.io.ByteArrayOutputStream; +import java.lang.StringBuilder; + + +public class MemOpARM extends MemOpNode implements MachNode { + final int _off; // Limit 32 bits + final int _imm; // Limit 32 bits + final char _sz = (char)('0'+(1<<_declaredType.log_size())); + MemOpARM(MemOpNode mop, Node base, Node idx, int off, int imm) { + super(mop,mop); + assert base._type instanceof TypeMemPtr && !(base instanceof AddNode); + assert ptr() == base; + _inputs.setX(3,idx); + _off = off; + _imm = imm; + } + + // Store-based flavors have a value edge + MemOpARM( MemOpNode mop, Node base, Node idx, int off, int imm, Node val ) { + this(mop,base,idx,off,imm); + _inputs.setX(4,val); + } + + Node idx() { return in(3); } + Node val() { return in(4); } // Only for stores, includin + + @Override protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) { return p.p(".").p(_name); } + + @Override public boolean isMem() { return _type instanceof TypeMem; } + + @Override public String label() { return op(); } + @Override public Type compute() { throw Utils.TODO(); } + @Override public Node idealize() { throw Utils.TODO(); } + + + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { + throw Utils.TODO(); + } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { throw Utils.TODO(); } + + @Override public int encoding(ByteArrayOutputStream bytes) { throw Utils.TODO(); } + + SB asm_address(CodeGen code, SB sb) { + sb.p("[").p(code.reg(ptr())).p("+"); + if( idx() != null ) sb.p(code.reg(idx())); + else sb.p(_off); + return sb.p("]"); + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java new file mode 100644 index 00000000..582b97d4 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java @@ -0,0 +1,34 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + + +// mulh signed multiply instruction(no-imm form) +public class MulARM extends MachConcreteNode implements MachNode{ + MulARM(Node mul) {super(mul);} + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { assert i==1 || i==2; return arm.RMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } + // Output is same register as input#1 + @Override public int twoAddress() { return 1; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form: "rd = rs1 * rs2" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2))); + } + + @Override public String op() { return "mul"; } + +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java new file mode 100644 index 00000000..b71b412d --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java @@ -0,0 +1,30 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import java.io.ByteArrayOutputStream; + +public class MulFARM extends MachConcreteNode implements MachNode{ + MulFARM(Node mulf) {super(mulf);} + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { assert i==1 || i==2; return arm.DMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.DMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // Default on double precision for now(64 bits) + // General form: "VMUL.f32 rd = src1 * src2 + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2))); + } + + @Override public String op() { return "mulf"; } + +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulIRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulIARM.java index 8735068e..c118b00a 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulIRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulIARM.java @@ -1,29 +1,37 @@ -package com.seaofnodes.simple.node.cpus.riscv; +package com.seaofnodes.simple.node.cpus.arm; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; -public class MulIRISC extends MachConcreteNode implements MachNode { +public class MulIARM extends MachConcreteNode implements MachNode{ final TypeInteger _ti; - MulIRISC(Node mul, TypeInteger ti) {super(mul); _inputs.pop(); _ti = ti;} + MulIARM(Node mul, TypeInteger ti) { + super(mul); + _inputs.pop(); + _ti = ti; + } // Register mask allowed on input i. // This is the normal calling convention @Override public RegMask regmap(int i) { - return riscv.RMASK; } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return riscv.RMASK; } + // assert i==1; + return arm.RMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } // Output is same register as input#1 - @Override public int twoAddress() { return 0; } + @Override public int twoAddress() { return 1; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { throw Utils.TODO(); } + // General form // General form: "muli dst * #imm" @Override public void asm(CodeGen code, SB sb) { diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java new file mode 100644 index 00000000..c145f348 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java @@ -0,0 +1,31 @@ +package com.seaofnodes.simple.node.cpus.arm; + + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; + +import java.io.ByteArrayOutputStream; + +public class NewARM extends NewNode implements MachNode { + // A pre-zeroed chunk of memory. + NewARM(NewNode nnn) { super(nnn); } + // Size and pointer result in standard calling convention; null for all the + // memory and ordering edges + @Override public RegMask regmap(int i) { return i == 2 ? arm. X0_MASK : null; } + @Override public RegMask outregmap(int i) { return i == 0 ? arm. X0_MASK : null; } + @Override public RegMask outregmap() { return null; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form: "alloc #bytes" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(size())); + } + + @Override public String op() { return "alloc"; } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java new file mode 100644 index 00000000..28ebd60d --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java @@ -0,0 +1,26 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.LRG; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +import com.seaofnodes.simple.node.MachConcreteNode; + +public class NotARM extends MachConcreteNode implements MachNode{ + NotARM(NotNode not) {super(not);} + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.RMASK; } + + @Override public int twoAddress( ) { return 0; } + + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); } + @Override public String op() { return "not"; } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java new file mode 100644 index 00000000..12ea8265 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java @@ -0,0 +1,42 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.Node; + +import java.io.ByteArrayOutputStream; + +// Not using the ORRS variant. +public class OrARM extends MachConcreteNode implements MachNode { + OrARM(Node and) { + super(and); + } + + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { + assert i==1 || i==2; + return arm.RMASK; + } + + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: #rd = rs1 & rs2 + @Override public void asm(CodeGen code, SB sb) { + + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" | ").p(code.reg(in(2))); + } + + @Override public String op() { return "orr"; } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java new file mode 100644 index 00000000..f257a867 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java @@ -0,0 +1,40 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.Node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class OrIARM extends MachConcreteNode implements MachNode{ + final TypeInteger _ti; + OrIARM(Node or, TypeInteger ti) {super(or); _inputs.pop(); _ti = ti;} + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { + assert i==1 || i==2; + return arm.RMASK; + } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: "rd = rs1 | imm" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" | #"); + _ti.print(sb); + } + + @Override public String op() { return "ori"; } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java new file mode 100644 index 00000000..c3fa132c --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java @@ -0,0 +1,25 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.ParmNode; +import java.io.ByteArrayOutputStream; + +public class ParmARM extends ParmNode implements MachNode { + final RegMask _rmask; + ParmARM(ParmNode parm) { + super(parm); + // Assume int + _rmask = arm.callInMask(fun().sig(),_idx); + } + // Register mask allowed on input i. 0 for no register. + @Override public RegMask regmap(int i) { return null; } + // Register mask allowed as a result. Calling convention register + @Override public RegMask outregmap() { return _rmask; } + + // Encoding is appended into the byte array. Returns size + @Override public int encoding(ByteArrayOutputStream bytes) { return 0; } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java new file mode 100644 index 00000000..b2add015 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java @@ -0,0 +1,23 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.ProjNode; +import java.io.ByteArrayOutputStream; + +public class ProjARM extends ProjNode implements MachNode { + ProjARM( ProjNode p ) { super(p); } + + // Register mask allowed on input i. 0 for no register. + @Override public RegMask regmap(int i) { return null; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { + return ((MachNode)in(0)).outregmap(_idx); + } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java new file mode 100644 index 00000000..c7d0dd4c --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java @@ -0,0 +1,49 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.FunNode; +import com.seaofnodes.simple.node.ReturnNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.type.TypeFloat; + +import java.io.ByteArrayOutputStream; + + +public class RetARM extends ReturnNode implements MachNode { + RetARM(ReturnNode ret, FunNode fun) { super(ret, fun); fun.setRet(this); } + + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { return arm.retMask(_fun.sig(),i); } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return null; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { + // Post code-gen, just print the "ret" + if( code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() ) + // Prints return reg (either X0 or D0), RPC (always R30) and + // then the callee-save registers. + for( int i=2; i<nIns(); i++ ) + sb.p(code.reg(in(i))).p(" "); + + // Post-allocation, if we did not get the expected return register, print what we got + else if( code._regAlloc.regnum(in(3)) != arm.X30 ) + sb.p("[").p(code.reg(in(3))).p("]"); + } + + // Correct Nodes outside the normal edges + @Override public void postSelect() { + FunNode fun = (FunNode)rpc().in(0); + _fun = fun; + fun.setRet(this); + } + + @Override public String op() { return "ret"; } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java new file mode 100644 index 00000000..c3945d1a --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java @@ -0,0 +1,41 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + + +// conditional set? +// e.g CSET, stores flag info into GPRS after compare + +public class SetARM extends MachConcreteNode implements MachNode { + final String _bop; // One of <,<=,== + + SetARM(Node cmp, String bop) { + super(cmp); + _inputs.setLen(1); // Pop the cmp inputs + // Replace with the matched cmp + _inputs.push(cmp); + _bop = bop; + } + + @Override public RegMask regmap(int i) { assert i==1; return arm.FLAGS_MASK; } + @Override public RegMask outregmap() { return arm.RMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)); + String src = code.reg(in(1)); + if( src!="flags" ) sb.p(" = ").p(src); + } + + @Override public String op() { return "set"+_bop; } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java new file mode 100644 index 00000000..85771842 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java @@ -0,0 +1,22 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.SplitNode; +import java.io.ByteArrayOutputStream; + +public class SplitARM extends SplitNode { + SplitARM(String kind, byte round) { super(kind,round,new Node[2]);} + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { return arm.SPLIT_MASK; } + + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.SPLIT_MASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java new file mode 100644 index 00000000..d78fdcad --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java @@ -0,0 +1,52 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.StoreNode; +import java.io.ByteArrayOutputStream; +import java.util.BitSet; + +// Store memory addressing on ARM +// Support imm, reg(direct), or reg+off(indirect) addressing +// Base = base - base pointer, offset is added to base +// idx = null +// off = off - offset added to base) +// imm = imm - immediate value to store +// val = Node of immediate value to store(null if its a constant immediate) + +//e.g s.cs[0] = 67; // C +// base = s.cs, off = 4, imm = 67, val = null +public class StoreARM extends MemOpARM { + StoreARM(StoreNode st, Node base, Node idx, int off, int imm, Node val) { + super(st, base, idx, off, imm, val); + } + + @Override public String op() { return "st"+_sz; } + + // Wider mask to store both GPRs and FPRs + @Override public RegMask regmap(int i) { + if( i==1 ) return arm.MEM_MASK; + if( i==2 ) return arm.RMASK; + if( i==3 ) return arm.RMASK; + if( i==4 ) return arm.RMASK; + return null; // Trailing anti-dependence edges + } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return null; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { + asm_address(code,sb).p(","); + if( val()==null ) sb.p("#").p(_imm); + else sb.p(code.reg(val())); + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java new file mode 100644 index 00000000..27555b53 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java @@ -0,0 +1,30 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + + +public class SubARM extends MachConcreteNode implements MachNode { + SubARM(Node sub) {super(sub);} + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { assert i==1 || i==2; return arm.RMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form: "sub # rd = rs1 - rs2" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - ").p(code.reg(in(2))); + } + + @Override public String op() { return "sub"; } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java new file mode 100644 index 00000000..8ae05b40 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java @@ -0,0 +1,32 @@ +package com.seaofnodes.simple.node.cpus.arm; + + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class SubFARM extends MachConcreteNode implements MachNode{ + SubFARM(Node subf) {super(subf);} + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { assert i==1 || i==2; return arm.DMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.DMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // Default on double precision for now(64 bits) + // General form: "vsub.f32 rd = src1 + src2 + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - ").p(code.reg(in(2))); + } + + @Override public String op() { return "subf"; } + +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java new file mode 100644 index 00000000..d19210cb --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java @@ -0,0 +1,42 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.node.MachConcreteNode; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.LRG; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class SubIARM extends MachConcreteNode implements MachNode { + final TypeInteger _ti; + SubIARM(Node sub, TypeInteger ti) { + super(sub); + _inputs.pop(); + _ti = ti; + } + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { return arm.RMASK; } + + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form: "subi rd = rs1 - imm" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - #"); + _ti.print(sb); + } + + @Override public String op() { + return (_ti.value() == -1 ? "dec" : "subi"); + } + +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java new file mode 100644 index 00000000..cdb2b6ec --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java @@ -0,0 +1,37 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.ConstantNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFunPtr; +import java.io.ByteArrayOutputStream; + +public class TFPARM extends ConstantNode implements MachNode { + TFPARM( ConstantNode con ) { super(con); } + @Override public String op() { return "ldx"; } + // Register mask allowed on input i. 0 for no register. + @Override public RegMask regmap(int i) { return null; } + // General int registers + @Override public RegMask outregmap() { return arm.WMASK; } + + @Override public boolean isClone() { return true; } + @Override public TFPARM copy() { return new TFPARM(this); } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // Human-readable form appended to the SB. Things like the encoding, + // indentation, leading address or block labels not printed here. + // Just something like "ld4\tR17=[R18+12] // Load array base". + // General form: "op\tdst=src+src" + @Override public void asm(CodeGen code, SB sb) { + String reg = code.reg(this); + _con.print(sb.p(reg).p(" #")); + } + +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java new file mode 100644 index 00000000..fd127b18 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java @@ -0,0 +1,41 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class XorARM extends MachConcreteNode implements MachNode{ + XorARM(Node xor) {super(xor); _inputs.pop();} + + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { + assert i==1 || i==2; + + return arm.RMASK; } + + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } + + // Output is same register as input#1 + @Override public int twoAddress() { return 1; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: "rd = x1 ^ x2" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ^ ").p(code.reg(in(2))); + } + + @Override public String op() { return "eor"; } +}
\ No newline at end of file diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java new file mode 100644 index 00000000..d230ea47 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java @@ -0,0 +1,43 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class XorIARM extends MachConcreteNode implements MachNode{ + final TypeInteger _ti; + XorIARM(Node xor, TypeInteger ti) {super(xor); _inputs.pop(); _ti = ti;} + + // Register mask allowed on input i. + // This is the normal calling convention + @Override public RegMask regmap(int i) { + assert i==1 || i==2; + + return arm.RMASK; } + + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.RMASK; } + + // Output is same register as input#1 + @Override public int twoAddress() { return 1; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: "rd = rs1 ^ imm" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ^ #"); + _ti.print(sb); + } + + @Override public String op() { return "eor"; } +}
\ No newline at end of file diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java new file mode 100644 index 00000000..72254310 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java @@ -0,0 +1,388 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.LRG; +import com.seaofnodes.simple.codegen.Machine; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.*; + +public class arm extends Machine { + + // X86-64 V2. Includes e.g. SSE4.2 and POPCNT. + @Override public String name() { return "arm"; } + + // GPR(S) + static final int X0 = 0, X1 = 1, X2 = 2, X3 = 3, X4 = 4, X5 = 5, X6 = 6, X7 = 7; + static final int X8 = 8, X9 = 9, X10 = 10, X11 = 11, X12 = 12, X13 = 13, X14 = 14, X15 = 15; + static final int X16 = 16, X17 = 17, X18 = 18, X19 = 19, X20 = 20, X21 = 21, X22 = 22, X23 = 23; + static final int X24 = 24, X25 = 25, X26 = 26, X27 = 27, X28 = 28, X29 = 29, X30 = 30, RSP = 31; + + // Floating point registers + static final int D0 = 32, D1 = 33, D2 = 34, D3 = 35, D4 = 36, D5 = 37, D6 = 38, D7 = 39; + static final int D8 = 40, D9 = 41, D10 = 42, D11 = 43, D12 = 44, D13 = 45, D14 = 46, D15 = 47; + static final int D16 = 48, D17 = 49, D18 = 50, D19 = 51, D20 = 52, D21 = 53, D22 = 54, D23 = 55; + static final int D24 = 56, D25 = 57, D26 = 58, D27 = 59, D28 = 60, D29 = 61, D30 = 62, D31 = 63; + + static final int FLAGS = 64; + + static final String[] REGS = new String[] { + "X0", "X1", "X2", "X3", "X4", "X5", "X6", "X7", + "X8", "X9", "X10", "X11", "X12", "X13", "X14", "X15", + "X16", "X17", "X18", "X19", "X20", "X21", "X22", "X23", + "X24", "X25", "X26", "X27", "X28", "X29", "RPC", "RSP", + "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", + "D8", "D9", "D10", "D11", "D12", "D13", "D14", "D15", + "D16", "D17", "D18", "D19", "D20", "D21", "D22", "D23", + "D24", "D25", "D26", "D27", "D28", "D29", "D30", "D31", + "flags" + }; + @Override public String reg( int reg ) { + return reg < REGS.length ? REGS[reg] : "[rsp+"+(reg-REGS.length)*4+"]"; + } + + // from (x0-x30) + // General purpose register mask: pointers and ints, not floats + static final long RD_BITS = 0xFFFFFFFFL; + static final RegMask RMASK = new RegMask(RD_BITS); + static final long WR_BITS = 0x7FFFFFFFL; // All the GPRs, not RSP + static final RegMask WMASK = new RegMask(WR_BITS); + + // Float mask from(d0–d31) + static final long FP_BITS = 0xFFFFFFFFL<<D0; + static final RegMask DMASK = new RegMask(FP_BITS); + + // Load/store mask; both GPR and FPR + static final RegMask MEM_MASK = new RegMask(WR_BITS | FP_BITS); + + static final RegMask SPLIT_MASK = new RegMask(WR_BITS | FP_BITS, -1L); + + static final RegMask FLAGS_MASK = new RegMask(FLAGS); + // x30 (LR): Procedure link register, used to return from subroutines. + static final RegMask RPC_MASK = new RegMask(1L << X30); + + // Arguments masks + static final RegMask X0_MASK = new RegMask(X0); + static final RegMask X1_MASK = new RegMask(X1); + static final RegMask X2_MASK = new RegMask(X2); + static final RegMask X3_MASK = new RegMask(X3); + static final RegMask X4_MASK = new RegMask(X4); + static final RegMask X5_MASK = new RegMask(X5); + static final RegMask X6_MASK = new RegMask(X6); + static final RegMask X7_MASK = new RegMask(X7); + + // Arguments(float) masks + static final RegMask D0_MASK = new RegMask(D0); + static final RegMask D1_MASK = new RegMask(D1); + static final RegMask D2_MASK = new RegMask(D2); + static final RegMask D3_MASK = new RegMask(D3); + static final RegMask D4_MASK = new RegMask(D4); + static final RegMask D5_MASK = new RegMask(D5); + static final RegMask D6_MASK = new RegMask(D6); + static final RegMask D7_MASK = new RegMask(D7); + + // Calling convention; returns a machine-specific register + // for incoming argument idx. + // index 0 for control, 1 for memory, real args start at index 2 + static final RegMask[] CALLINMASK = new RegMask[] { + X0_MASK, + X1_MASK, + X2_MASK, + X3_MASK, + X4_MASK, + X5_MASK, + X6_MASK, + X7_MASK, + }; + static final RegMask[] XMMS = new RegMask[] { + D0_MASK, + D1_MASK, + D2_MASK, + D3_MASK, + D4_MASK, + D5_MASK, + D6_MASK, + D7_MASK, + }; + + static RegMask callInMask( TypeFunPtr tfp, int idx ) { + if( idx==0 ) return RPC_MASK; + if( idx==1 ) return null; + // Count floats in signature up to index + int fcnt=0; + for( int i=2; i<idx; i++ ) + if( tfp.arg(i-2) instanceof TypeFloat ) + fcnt++; + // Floats up to XMMS in XMM registers + if( tfp.arg(idx-2) instanceof TypeFloat ) { + if( fcnt < XMMS.length ) + return XMMS[fcnt]; + } else { + RegMask[] cargs = CALLINMASK; + if( idx-2-fcnt < cargs.length ) + return cargs[idx-2-fcnt]; + } + throw Utils.TODO(); // Pass on stack slot + } + + static final long CALLEE_SAVE = + 1L<<X19 | + 1L<<X20 | 1L<<X21 | 1L<<X22 | 1L<<X23 | + 1L<<X24 | 1L<<X25 | 1L<<X26 | 1L<<X27 | + 1L<<X28 | + 1L<<D9 | 1L<<D10 | 1L<<D11 | + 1L<<D12 | 1L<<D13 | 1L<<D14 | 1L<<D15; + + static final RegMask CALLER_SAVE_MASK; + static { + long caller = ~CALLEE_SAVE; + caller &= ~(1L<<RSP); + CALLER_SAVE_MASK = new RegMask(caller,1L<<(FLAGS-64)); + } + static RegMask armCallerSave() { return CALLER_SAVE_MASK; } + @Override public RegMask callerSave() { return armCallerSave(); } + + static final RegMask CALLEE_SAVE_MASK = new RegMask(CALLEE_SAVE); + static RegMask armCalleeSave() { return CALLEE_SAVE_MASK; } + @Override public RegMask calleeSave() { return armCalleeSave(); } + + + // Return single int/ptr register. Used by CallEnd output and Return input. + static final RegMask[] RET_MASKS; + static { + int nSaves = CALLEE_SAVE_MASK.size(); + RET_MASKS = new RegMask[4 + nSaves]; + RET_MASKS[0] = null; + RET_MASKS[1] = null; // Memory + RET_MASKS[2] = null; // Varies, either XMM0 or RAX + RET_MASKS[3] = RMASK; // Expected R30 but could be any register + short reg = CALLEE_SAVE_MASK.firstReg(); + for( int i=0; i<nSaves; i++ ) { + RET_MASKS[i+4] = new RegMask(reg); + reg = CALLEE_SAVE_MASK.nextReg(reg); + } + } + static RegMask retMask( TypeFunPtr tfp, int i ) { + return i==2 + ? (tfp.ret() instanceof TypeFloat ? D0_MASK : X0_MASK) + : RET_MASKS[i]; + } + + // Create a split op; any register to any register, including stack slots + @Override public SplitNode split(String kind, byte round, LRG lrg) { return new SplitARM(kind,round); } + + // Return a MachNode unconditional branch + @Override public CFGNode jump() { + throw Utils.TODO(); + } + + // Break an infinite loop + @Override public IfNode never( CFGNode ctrl ) { + throw Utils.TODO(); + } + + // Instruction selection + @Override public Node instSelect( Node n ) { + return switch( n ) { + case AddFNode addf -> addf(addf); + case AddNode add -> add(add); + case AndNode and -> and(and); + case BoolNode bool -> cmp(bool); + case CallNode call -> call(call); + case CastNode cast -> new CastNode(cast); + case CallEndNode cend -> new CallEndARM(cend); + case CProjNode c -> new CProjNode(c); + case ConstantNode con -> con(con); + case DivFNode divf -> new DivFARM(divf); + case DivNode div -> new DivARM(div); + case FunNode fun -> new FunARM(fun); + case IfNode iff -> jmp(iff); + case LoadNode ld -> ld(ld); + case MemMergeNode mem -> new MemMergeNode(mem); + case MulFNode mulf -> new MulFARM(mulf); + case MulNode mul -> mul(mul); + case NewNode nnn -> new NewARM(nnn); + case OrNode or -> or(or); + case ParmNode parm -> new ParmARM(parm); + case BulkMemPhiNode phi -> new BulkMemPhiNode(phi); + case MemPhiNode phi -> new MemPhiNode(phi); + case PhiNode phi -> new PhiNode(phi); + case ProjNode prj -> prj(prj); + case ReadOnlyNode read -> new ReadOnlyNode(read); + case ReturnNode ret -> new RetARM(ret,ret.fun()); + case SarNode sar -> sar(sar); + case ShlNode shl -> shl(shl); + case ShrNode shr -> shr(shr); + case StartNode start -> new StartNode(start); + case StopNode stop -> new StopNode(stop); + case StoreNode st -> st(st); + case SubFNode subf -> new SubFARM(subf); + case SubNode sub -> sub(sub); + case ToFloatNode tfn -> i2f8(tfn); + case ToIntegerNode cvt -> new F8ToIARM(cvt); + case XorNode xor -> xor(xor); + + case LoopNode loop -> new LoopNode(loop); + case RegionNode region-> new RegionNode(region); + default -> throw Utils.TODO(); + }; + } + + private Node mul(MulNode mul) { + Node rhs = mul.in(2); + if( rhs instanceof ConstantNode off && off._con instanceof TypeInteger toff ) { + return new MulIARM(mul, toff); + } + return new MulARM(mul); + } + private Node cmp(BoolNode bool){ + Node cmp = _cmp(bool); + return new SetARM(cmp, bool.op()); + } + + private Node i2f8(ToFloatNode tfn) { + assert tfn.in(1)._type instanceof TypeInteger ti; + return new I2F8ARM(tfn); + } + + private Node ld(LoadNode ld) { + return new LoadARM(address(ld), ld.ptr(), idx, off); + } + + private Node jmp(IfNode iff) { + // If/Bool combos will match to a Cmp/Set which sets flags. + // Most general arith ops will also set flags, which the Jmp needs directly. + // Loads do not set the flags, and will need an explicit TEST + if( !(iff.in(1) instanceof BoolNode) ) + iff.setDef(1,new BoolNode.EQ(iff.in(1),new ConstantNode(TypeInteger.ZERO))); + return new BranchARM(iff, ((BoolNode)iff.in(1)).op()); + } + + private Node _cmp(BoolNode bool) { + if( bool instanceof BoolNode.EQF || + bool instanceof BoolNode.LTF || + bool instanceof BoolNode.LEF ) + return new CmpFARM(bool); + + + return bool.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti + ? new CmpIARM(bool, ti) + : new CmpARM(bool); + } + + + private Node addf(AddFNode addf) { + return new AddFARM(addf); + } + + private Node add(AddNode add) { + Node rhs = add.in(2); + if( rhs instanceof ConstantNode off && off._con instanceof TypeInteger toff ) { + return new AddIARM(add, toff); + } + return new AddARM(add); + } + + private Node sub(SubNode sub) { + return sub.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti + ? new AddIARM(sub, TypeInteger.constant(-ti.value())) + : new SubARM(sub); + } + + private Node and(AndNode and) { + if( and.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti ) + return new AndIARM(and, ti); + return new AndARM(and); + } + + private Node or(OrNode or) { + if(or.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) + return new OrIARM(or, ti); + + return new OrARM(or); + } + + private Node xor(XorNode xor) { + if(xor.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) + return new XorIARM(xor, ti); + return new XorARM(xor); + } + + private Node con(ConstantNode con) { + if( !con._con.isConstant() ) return new ConstantNode( con ); // Default unknown caller inputs + return switch( con._con ) { + case TypeInteger ti -> new IntARM(con); + case TypeFloat tf -> new FloatARM(con); + case TypeFunPtr tfp -> new TFPARM(con); + case TypeMemPtr tmp -> new ConstantNode(con); + case TypeNil tn -> throw Utils.TODO(); + // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code. + case Type t -> new ConstantNode(con); + }; + } + + private Node sar(SarNode sar){ + if( sar.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) + return new AsrIARM(sar, ti); + return new AsrARM(sar); + } + + private Node shl(ShlNode shl) { + if( shl.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) + return new LslIARM(shl, ti); + return new LslARM(shl); + + } + + private Node shr(ShrNode shr) { + if( shr.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) + return new LsrIARM(shr, ti); + return new LsrARM(shr); + } + + private Node call(CallNode call){ + if(call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp) + return new CallARM(call, tfp); + return new CallRRARM(call); + } + + private Node prj(ProjNode prj) { + return new ProjARM(prj); + } + + + private static int off; + private static Node idx; + private Node st(StoreNode st) { + int imm=0; + Node xval = st.val(); + // e.g store this s.cs[0] = 67; // C + if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti ) { + xval = null; + imm = (int)ti.value(); + assert imm == ti.value(); // In 32-bit range + } + return new StoreARM(address(st),st.ptr(),idx,off,imm,xval); + } + + // Gather addressing mode bits prior to constructing. This is a builder + // pattern, but saving the bits in a *local* *global* here to keep mess + // contained. + private <N extends MemOpNode> N address( N mop ) { + off = 0; // Reset + idx = null; + Node base = mop.ptr(); + // Skip/throw-away a ReadOnly, only used to typecheck + if( base instanceof ReadOnlyNode read ) base = read.in(1); + assert !(base instanceof AddNode) && base._type instanceof TypeMemPtr; // Base ptr always, not some derived + if( mop.off() instanceof ConstantNode con && con._con instanceof TypeInteger ti ) { + off = (int)ti.value(); + assert off == ti.value(); // In 32-bit range + } else { + idx = mop.off(); + } + return mop; + } + + +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/i2f8.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/i2f8.java new file mode 100644 index 00000000..e0792669 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/i2f8.java @@ -0,0 +1,4 @@ +package com.seaofnodes.simple.node.cpus.arm; + +public class i2f8 { +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java index bc959977..2417aeb6 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.riscv; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; @@ -12,8 +14,6 @@ public class AddFRISC extends MachConcreteNode implements MachNode{ @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; } // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return riscv.FMASK; } - // Output is same register as input#1 - @Override public int twoAddress() { return 1; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java index af4bf08d..e92a7b08 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java @@ -1,39 +1,11 @@ package com.seaofnodes.simple.node.cpus.riscv; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; - -public class AddIRISC extends MachConcreteNode implements MachNode { - final TypeInteger _ti; - AddIRISC( Node add, TypeInteger ti ) { - super(add); - _inputs.pop(); - _ti = ti; - } - - // Register mask allowed on input i. - @Override public RegMask regmap(int i) { - // assert i== i; - return riscv.RMASK; } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return riscv.RMASK; } - // Output is same register as input#1 - @Override public int twoAddress() { return 0; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form: "addi rd = rs1 + imm" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + #"); - _ti.print(sb); - } - - @Override public String op() { - return _ti.value() == 1 ? "inc" : (_ti.value() == -1 ? "dec" : "addi"); - } +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.node.Node; + +public class AddIRISC extends ImmRISC { + AddIRISC( Node add, int imm ) { super(add,imm); } + @Override int opcode() { throw Utils.TODO(); } + @Override public String glabel() { return "+"; } + @Override public String op() { return "addi"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java index 482b7f11..344c217d 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java @@ -1,25 +1,20 @@ package com.seaofnodes.simple.node.cpus.riscv; - import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; - public class AddRISC extends MachConcreteNode implements MachNode { - AddRISC( Node add) {super(add); } + @Override public String op() { return "add"; } // Register mask allowed on input i. - @Override public RegMask regmap(int i) { - assert i== 1 || i == 2 || i == 3; - return riscv.RMASK; - } + @Override public RegMask regmap(int i) { return riscv.RMASK; } // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return riscv.RMASK; } - // Output is same register as input#1 - @Override public int twoAddress() { return 0; } + @Override public RegMask outregmap() { return riscv.WMASK; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { @@ -31,6 +26,4 @@ public class AddRISC extends MachConcreteNode implements MachNode { sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2))); } - @Override public String op() { - return "add";} } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java index 29c2a686..ddbf5aa4 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java @@ -1,40 +1,11 @@ package com.seaofnodes.simple.node.cpus.riscv; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; -import java.util.BitSet; -import java.lang.StringBuilder; - -public class AndIRISC extends MachConcreteNode implements MachNode{ - final TypeInteger _ti; - AndIRISC(AndNode and, TypeInteger ti) { - super(and); - _inputs.pop(); - _ti = ti; - } - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { assert i==1 || i == 2; return riscv.RMASK; } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return riscv.RMASK; } - - // Output is same register as input#1 - @Override public int twoAddress() { return 0; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form - // General form: "andi rd = rs1 & imm" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" & #"); - _ti.print(sb); - } +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.node.Node; +public class AndIRISC extends ImmRISC { + AndIRISC( Node and, int imm) { super(and,imm); } + @Override int opcode() { throw Utils.TODO(); } + @Override public String glabel() { return "&"; } @Override public String op() { return "andi"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java index e697de36..ca00bc98 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java @@ -1,9 +1,9 @@ package com.seaofnodes.simple.node.cpus.riscv; - import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.node.cpus.x86_64_v2.x86_64_v2; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; @@ -23,10 +23,7 @@ public class AndRISC extends MachConcreteNode implements MachNode{ } // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return riscv.RMASK; } - - // Output is same register as input#1 - @Override public int twoAddress() { return 0; } + @Override public RegMask outregmap() { return riscv.WMASK; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { @@ -36,6 +33,6 @@ public class AndRISC extends MachConcreteNode implements MachNode{ // General form // General form: #rd = rs1 & rs2 @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))); + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" & ").p(code.reg(in(2))); } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java new file mode 100644 index 00000000..30dfd7fe --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java @@ -0,0 +1,48 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import java.io.ByteArrayOutputStream; + +// Conditional branch such as: BEQ +public class BranchRISC extends IfNode implements MachNode { + final String _bop; + // label is obtained implicitly + BranchRISC( IfNode iff, String bop, Node n1, Node n2 ) { + super(iff); + _bop = bop; + _inputs.setX(1,n1); + _inputs.setX(2,n2); + } + + @Override public String label() { return op(); } + + @Override public RegMask regmap(int i) { return riscv.RMASK; } + @Override public RegMask outregmap() { return null; } + + @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + p.p("if( ").n(in(1)).p(_bop); + if( in(2)==null ) p.p("0"); + else p.n(in(2)); + return p.p(" )"); + } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(in(1))).p(" ").p(_bop).p(" ").p(in(2)==null ? "#0" : code.reg(in(2))); + } + + @Override public String op() { return "b"+_bop; } + + @Override public String comment() { + return "L"+cproj(1)._nid+", L"+cproj(0)._nid; + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallEndRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallEndRISC.java new file mode 100644 index 00000000..fd1ba7c3 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallEndRISC.java @@ -0,0 +1,35 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.CallEndNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.type.TypeFloat; +import com.seaofnodes.simple.type.TypeFunPtr; +import java.io.ByteArrayOutputStream; + +public class CallEndRISC extends CallEndNode implements MachNode { + final TypeFunPtr _tfp; + CallEndRISC( CallEndNode cend ) { + super(cend); + _tfp = (TypeFunPtr)(cend.call().fptr()._type); + } + + @Override public String label() { return op(); } + @Override public RegMask regmap(int i) { return null; } + @Override public RegMask outregmap() { return null; } + @Override public RegMask outregmap(int idx) { return idx == 2 ? riscv.retMask(_tfp,2) : null; } + @Override public RegMask killmap() { return riscv.riscCallerSave(); } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { } + + @Override public String op() { return "cend"; } + +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java index 7f062104..54f4fba9 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.riscv; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeFunPtr; import java.io.ByteArrayOutputStream; @@ -8,6 +10,7 @@ import java.io.ByteArrayOutputStream; public class CallRISC extends CallNode implements MachNode{ final TypeFunPtr _tfp; final String _name; + CallRISC( CallNode call, TypeFunPtr tfp ) { super(call); _inputs.pop(); // Pop constant target @@ -18,11 +21,12 @@ public class CallRISC extends CallNode implements MachNode{ @Override public String label() { return op(); } @Override public RegMask regmap(int i) { - return riscv.callInMaskInt(i); // Normal argument + return riscv.callInMask(_tfp,i); // Normal argument } - @Override public RegMask outregmap() { return riscv.RET_MASK; } + @Override public RegMask outregmap() { return null; } @Override public String name() { return _name; } + @Override public TypeFunPtr tfp() { return _tfp; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { @@ -30,7 +34,10 @@ public class CallRISC extends CallNode implements MachNode{ } @Override public void asm(CodeGen code, SB sb) { - sb.p(_name); + sb.p(_name).p(" "); + for( int i=0; i<nargs(); i++ ) + sb.p(code.reg(arg(i))).p(" "); + sb.unchar(2); } @Override public String op() { return "call"; } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java index 02850582..3bae656d 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java @@ -1,8 +1,9 @@ package com.seaofnodes.simple.node.cpus.riscv; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; - import java.io.ByteArrayOutputStream; public class CallRRISC extends CallNode implements MachNode{ @@ -12,10 +13,10 @@ public class CallRRISC extends CallNode implements MachNode{ @Override public RegMask regmap(int i) { // Todo: float or int? return i==_inputs._len - ? riscv.RMASK // Function call target - : riscv.callInMaskInt(i); // Normal argument + ? riscv.RMASK // Function call target + : riscv.callInMask(tfp(),i); // Normal argument } - @Override public RegMask outregmap() { return riscv.RET_MASK; } + @Override public RegMask outregmap() { return null; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { @@ -23,7 +24,10 @@ public class CallRRISC extends CallNode implements MachNode{ } @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(fptr())); + sb.p(code.reg(fptr())).p(" "); + for( int i=0; i<nargs(); i++ ) + sb.p(code.reg(arg(i))).p(" "); + sb.unchar(2); } @Override public String op() { return "callr"; } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CastRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CastRISC.java new file mode 100644 index 00000000..a0d0df90 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CastRISC.java @@ -0,0 +1,18 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.CastNode; +import com.seaofnodes.simple.node.MachNode; +import java.io.ByteArrayOutputStream; + +public class CastRISC extends CastNode implements MachNode { + CastRISC( CastNode cast ) { super(cast); } + @Override public RegMask regmap(int i) { assert i==1; return RegMask.FULL; } + @Override public RegMask outregmap() { return RegMask.FULL; } + @Override public int twoAddress( ) { return 1; } + @Override public int encoding(ByteArrayOutputStream bytes) { return 0; /*no encoding*/ } + @Override public void asm(CodeGen code, SB sb) { _t.print(sb.p(code.reg(in(1))).p(" isa ")); } + @Override public String op() { return label(); } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CmpIRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CmpIRISC.java deleted file mode 100644 index 39fa3a29..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CmpIRISC.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.seaofnodes.simple.node.cpus.riscv; - - -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; - -// Compare immediate. Sets flags.(RFLAGS) -public class CmpIRISC extends MachConcreteNode implements MachNode{ - final int _imm; - final String _bop; - CmpIRISC( BoolNode bool, TypeInteger ti ) { - super(bool); - _inputs.pop(); // Toss ideal ConstantNode away, embedded into machine op - _bop = bool.op(); // One of <,<=,== - _imm = (int)ti.value(); - assert _imm == ti.value(); - } - CmpIRISC( Node cmp, double ignore ) { - super(cmp); - _bop = "=="; - _imm = 0; - } - - @Override public RegMask regmap(int i) { assert i==1; return riscv.RMASK; } - @Override public RegMask outregmap() { return riscv.FLAGS_MASK; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form: "cmp rs1, 1" - @Override public void asm(CodeGen code, SB sb) { - String dst = code.reg(this); - if( dst!="FLAGS" ) sb.p(dst).p(" = "); - sb.p(code.reg(in(1))); - if( _imm != 0 ) sb.p(", #").p(_imm); - } - - @Override public String op() { return _imm==0 ? "test" : "cmp"; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java index deb2628d..d360bea6 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.riscv; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import java.io.ByteArrayOutputStream; @@ -11,8 +13,6 @@ public class DivFRISC extends MachConcreteNode implements MachNode{ @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; } // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return riscv.FMASK; } - // Output is same register as input#1 - @Override public int twoAddress() { return 1; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { @@ -22,7 +22,7 @@ public class DivFRISC extends MachConcreteNode implements MachNode{ // Default on double precision for now(64 bits) // General form: "fdiv.d rd = src1 / src2 @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2))); + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2))); } @Override public String op() { return "divf"; } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java index 98a8406f..ee49a883 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java @@ -1,8 +1,9 @@ package com.seaofnodes.simple.node.cpus.riscv; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.node.cpus.x86_64_v2.x86_64_v2; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; @@ -12,11 +13,9 @@ public class DivRISC extends MachConcreteNode implements MachNode{ // Register mask allowed on input i. @Override public RegMask regmap(int i) { assert i==1 || i==2; - return riscv.FMASK; } + return riscv.RMASK; } // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return x86_64_v2.RMASK; } - // Output is same register as input#1 - @Override public int twoAddress() { return 1; } + @Override public RegMask outregmap() { return riscv.WMASK; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java index 261a1dcd..f4de5d48 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java @@ -1,8 +1,9 @@ package com.seaofnodes.simple.node.cpus.riscv; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.node.cpus.x86_64_v2.x86_64_v2; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; @@ -12,7 +13,7 @@ public class F8ToIRISC extends MachConcreteNode implements MachNode { // Register mask allowed on input i. @Override public RegMask regmap(int i) { assert i==1; return riscv.FMASK; } // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return riscv.RMASK; } + @Override public RegMask outregmap() { return riscv.WMASK; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java index e1637eec..2832ac52 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java @@ -1,24 +1,20 @@ package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.CodeGen; -import com.seaofnodes.simple.RegMask; import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.ConstantNode; import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.cpus.x86_64_v2.x86_64_v2; - import java.io.ByteArrayOutputStream; public class FltRISC extends ConstantNode implements MachNode{ - FltRISC(ConstantNode con) { - super(con); - } + FltRISC(ConstantNode con) { super(con); } + @Override public String op() { return "flw"; } // Register mask allowed on input i. 0 for no register. @Override public RegMask regmap(int i) { return null; } - // General int registers - @Override public RegMask outregmap() { return riscv.FMASK; } + // General int registers! Must-split into the floats + @Override public RegMask outregmap() { return riscv.RMASK; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { @@ -32,8 +28,4 @@ public class FltRISC extends ConstantNode implements MachNode{ @Override public void asm(CodeGen code, SB sb) { _con.print(sb.p(code.reg(this)).p(" #")); } - - @Override public String op() { - return "flw"; // Some fancier encoding - } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java index 66e32e29..3b7d9a34 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java @@ -1,14 +1,13 @@ package com.seaofnodes.simple.node.cpus.riscv; -import com.seaofnodes.simple.RegMask; import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.FunNode; import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.cpus.x86_64_v2.FunX86; - import java.io.ByteArrayOutputStream; -public class FunRISC extends FunNode implements MachNode{ +public class FunRISC extends FunNode implements MachNode { FunRISC(FunNode fun) {super(fun);} // Register mask allowed on input i. 0 for no register. @Override public RegMask regmap(int i) { return null; } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java index df005e86..560d1b38 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java @@ -1,8 +1,9 @@ package com.seaofnodes.simple.node.cpus.riscv; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.node.cpus.x86_64_v2.x86_64_v2; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java new file mode 100644 index 00000000..330de299 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java @@ -0,0 +1,40 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public abstract class ImmRISC extends MachConcreteNode implements MachNode { + final int _imm; + ImmRISC( Node add, int imm ) { + super(add); + _inputs.pop(); + _imm = imm; + } + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { return riscv.RMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return riscv.WMASK; } + + @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + return p.p("( ").n(in(1)).p(String.format(" %s #%d )",glabel(),_imm)); + } + + abstract int opcode(); + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form: "addi rd = rs1 + imm" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ").p(glabel()).p(" #").p(_imm); + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java index bf170b22..e792b055 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java @@ -1,22 +1,23 @@ package com.seaofnodes.simple.node.cpus.riscv; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.ConstantNode; import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; - import java.io.ByteArrayOutputStream; + // Integer constants public class IntRISC extends ConstantNode implements MachNode { - IntRISC(ConstantNode con) { - super(con); - } - + IntRISC(ConstantNode con) { super(con); } + @Override public String op() { return "ldi"; } // Register mask allowed on input i. 0 for no register. @Override public RegMask regmap(int i) { return null; } // General int registers - @Override public RegMask outregmap() { return riscv.RMASK; } + @Override public RegMask outregmap() { return riscv.WMASK; } + + @Override public boolean isClone() { return true; } + @Override public IntRISC copy() { return new IntRISC(this); } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { @@ -29,15 +30,6 @@ public class IntRISC extends ConstantNode implements MachNode { // General form: "op\tdst=src+src" @Override public void asm(CodeGen code, SB sb) { String reg = code.reg(this); - if( _con == Type.NIL || _con == TypeInteger.ZERO ) - sb.p(reg).p(",").p(reg); - else - _con.print(sb.p(reg).p(" #")); - } - - @Override public String op() { - if( _con == Type.NIL || _con == TypeInteger.ZERO ) - return "xor"; - return "ldi"; // Some fancier encoding + _con.print(sb.p(reg).p(" = #")); } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java index 846e313c..3fe8f44e 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java @@ -1,22 +1,29 @@ package com.seaofnodes.simple.node.cpus.riscv; - import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; -public class LoadRISC extends MemOpRISC{ - LoadRISC(LoadNode ld) { - super(ld, ld); + +// Load memory addressing on RISC +// Support imm, reg(direct), or reg+off(indirect) addressing +// Base = base - base pointer, offset is added to base +// idx = null +// off = off - offset added to base + +public class LoadRISC extends MemOpRISC { + LoadRISC(LoadNode ld, Node base, Node idx, int off) { + super(ld, base, idx, off, 0); } @Override public RegMask regmap(int i) { - // wider mask, encode it differently, immediate handled with hard split(in RA) - return riscv.RMASK.or(riscv.FMASK); + return riscv.RMASK; } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return riscv.RMASK; } + // Wide mask loads both ints and floats; encoding varies. + @Override public RegMask outregmap() { return riscv.MEM_MASK; } // Encoding is appended into the byte array; size is returned @@ -25,10 +32,9 @@ public class LoadRISC extends MemOpRISC{ } @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(","); + asm_address(code,sb); } - @Override public String op() { - return "lw" +_sz; - } + @Override public String op() { return "ld" +_sz; } } - diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java index 3bf1cf09..19869285 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java @@ -3,18 +3,35 @@ package com.seaofnodes.simple.node.cpus.riscv; import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.node.cpus.x86_64_v2.x86_64_v2; import com.seaofnodes.simple.type.*; import java.io.ByteArrayOutputStream; import java.lang.StringBuilder; -public class MemOpRISC extends MemOpNode implements MachNode{ +public class MemOpRISC extends MemOpNode implements MachNode { + final int _off; // Limit 32 bits + final int _imm; // Limit 32 bits final char _sz = (char)('0'+(1<<_declaredType.log_size())); - MemOpRISC(Node op, MemOpNode mop) { - super(op, mop); + MemOpRISC( MemOpNode mop, Node base, Node idx, int off, int imm) { + super(mop,mop); + assert base._type instanceof TypeMemPtr && !(base instanceof AddNode); + assert ptr() == base; + _inputs.setX(3,idx); + _off = off; + _imm = imm; } + // Store-based flavors have a value edge + MemOpRISC( MemOpNode mop, Node base, Node idx, int off, int imm, Node val ) { + this(mop,base,idx,off,imm); + _inputs.setX(4,val); + } + + Node idx() { return in(3); } + Node val() { return in(4); } // Only for stores, including op-to-memory + @Override protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) { return p.p(".").p(_name); } @Override public boolean isMem() { return _type instanceof TypeMem; } @@ -23,6 +40,7 @@ public class MemOpRISC extends MemOpNode implements MachNode{ @Override public Type compute() { throw Utils.TODO(); } @Override public Node idealize() { throw Utils.TODO(); } + // Register mask allowed on input i. @Override public RegMask regmap(int i) { throw Utils.TODO(); @@ -34,6 +52,9 @@ public class MemOpRISC extends MemOpNode implements MachNode{ SB asm_address(CodeGen code, SB sb) { - return sb; + sb.p("[").p(code.reg(ptr())).p("+"); + if( idx() != null ) sb.p(code.reg(idx())); + else sb.p(_off); + return sb.p("]"); } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java index 43197b50..8a066ab1 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.riscv; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import java.io.ByteArrayOutputStream; @@ -11,8 +13,6 @@ public class MulFRISC extends MachConcreteNode implements MachNode{ @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; } // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return riscv.FMASK; } - // Output is same register as input#1 - @Override public int twoAddress() { return 1; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { @@ -22,7 +22,7 @@ public class MulFRISC extends MachConcreteNode implements MachNode{ // Default on double precision for now(64 bits) // General form: "fmul.d rd = src1 * src2 @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2))); + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2))); } @Override public String op() { return "mulf"; } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java index 8713486e..0e2d35a3 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java @@ -1,8 +1,9 @@ package com.seaofnodes.simple.node.cpus.riscv; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.node.cpus.x86_64_v2.x86_64_v2; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; @@ -14,9 +15,7 @@ public class MulRISC extends MachConcreteNode implements MachNode{ // Register mask allowed on input i. @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.RMASK; } // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return riscv.RMASK; } - // Output is same register as input#1 - @Override public int twoAddress() { return 1; } + @Override public RegMask outregmap() { return riscv.WMASK; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java index 2b8ec4f2..e8d32570 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java @@ -1,34 +1,19 @@ package com.seaofnodes.simple.node.cpus.riscv; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.node.cpus.x86_64_v2.x86_64_v2; - import java.io.ByteArrayOutputStream; - -public class NewRISC extends NewNode implements MachNode{ - +public class NewRISC extends NewNode implements MachNode { // A pre-zeroed chunk of memory. - NewRISC( NewNode nnn ) { super(nnn); } - - // Register mask allowed on input i, the size - @Override public RegMask regmap(int i) { - // Size - if( i==2 ) return riscv.RET_MASK; - // Memory has no register - return null; - } - - @Override public RegMask outregmap() { throw Utils.TODO(); } - - // Register mask allowed as a result. Pointer result in standard calling - // convention. - @Override public RegMask outregmap(int i) { - if( i == 0 ) return riscv.RET_MASK; - // Memory has no register - return null; - } + NewRISC( NewNode nnn ) { super(nnn); } + // Size and pointer result in standard calling convention; null for all the + // memory and ordering edges + @Override public RegMask regmap(int i) { return i == 2 ? riscv.A0_MASK : null; } + @Override public RegMask outregmap(int i) { return i == 0 ? riscv.A0_MASK : null; } + @Override public RegMask outregmap() { return null; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java new file mode 100644 index 00000000..6d30cd1c --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java @@ -0,0 +1,25 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.LRG; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class NotRISC extends MachConcreteNode implements MachNode { + NotRISC(NotNode not) {super(not);} + @Override public RegMask regmap(int i) { return riscv.RMASK; } + @Override public RegMask outregmap() { return riscv.RMASK; } + + @Override public int twoAddress( ) { return 0; } + + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); } + @Override public String op() { return "not"; } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java index f3f79ed1..f41d7162 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java @@ -1,43 +1,11 @@ package com.seaofnodes.simple.node.cpus.riscv; -import com.seaofnodes.simple.CodeGen; -import com.seaofnodes.simple.RegMask; -import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.node.MachConcreteNode; -import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.node.cpus.x86_64_v2.x86_64_v2; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; - -import java.io.ByteArrayOutputStream; - -public class OrIRISC extends MachConcreteNode implements MachNode{ - final TypeInteger _ti; - OrIRISC(Node or, TypeInteger ti) {super(or); _inputs.pop(); _ti = ti;} - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { - assert i==1 || i==2; - return riscv.RMASK; - } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return riscv.RMASK; } - // Output is same register as input#1 - @Override public int twoAddress() { return 0;} - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form - // General form: "rd = rs1 | imm" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" | #"); - _ti.print(sb); - } +public class OrIRISC extends ImmRISC { + OrIRISC( Node and, int imm) { super(and,imm); } + @Override int opcode() { throw Utils.TODO(); } + @Override public String glabel() { return "|"; } @Override public String op() { return "ori"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java index 8f1e9305..7f808f90 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java @@ -1,14 +1,12 @@ package com.seaofnodes.simple.node.cpus.riscv; -import com.seaofnodes.simple.node.MachConcreteNode; -import com.seaofnodes.simple.node.MachNode; - -import com.seaofnodes.simple.CodeGen; -import com.seaofnodes.simple.RegMask; import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.node.cpus.x86_64_v2.x86_64_v2; import java.io.ByteArrayOutputStream; public class OrRISC extends MachConcreteNode implements MachNode { @@ -24,10 +22,7 @@ public class OrRISC extends MachConcreteNode implements MachNode { } // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return riscv.RMASK; } - - // Output is same register as input#1 - @Override public int twoAddress() { return 0; } + @Override public RegMask outregmap() { return riscv.WMASK; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { @@ -37,7 +32,8 @@ public class OrRISC extends MachConcreteNode implements MachNode { // General form // General form: # rd = rs1 | rs2 @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))); + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" | ").p(code.reg(in(2))); } + @Override public String op() { return "or"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java index 2e20f96f..75674f37 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java @@ -1,22 +1,23 @@ package com.seaofnodes.simple.node.cpus.riscv; -import com.seaofnodes.simple.RegMask; import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.node.ParmNode; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.ParmNode; import java.io.ByteArrayOutputStream; -public class ParmRISC extends ParmNode implements MachNode{ +public class ParmRISC extends ParmNode implements MachNode { final RegMask _rmask; ParmRISC(ParmNode parm) { super(parm); // Assume int - _rmask = riscv.callInMaskInt(_idx); + _rmask = riscv.callInMask(fun().sig(),_idx); } // Register mask allowed on input i. 0 for no register. - @Override public RegMask regmap(int i) { return null; } + @Override public RegMask regmap(int i) { return _rmask; } // Register mask allowed as a result. Calling convention register @Override public RegMask outregmap() { return _rmask; } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java index a1312c4c..42d5dc9b 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java @@ -1,20 +1,22 @@ package com.seaofnodes.simple.node.cpus.riscv; - -import com.seaofnodes.simple.RegMask; import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.node.ProjNode; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.ProjNode; import java.io.ByteArrayOutputStream; -public class ProjRISC extends ProjNode implements MachNode{ - ProjRISC(ProjNode p) {super(p);} +public class ProjRISC extends ProjNode implements MachNode { + ProjRISC(ProjNode p) { super(p); } // Register mask allowed on input i. 0 for no register. @Override public RegMask regmap(int i) { return null; } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return null; } + // Register mask allowed as a result. + @Override public RegMask outregmap() { + return ((MachNode)in(0)).outregmap(_idx); + } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java index c92e6989..c9fa7fac 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java @@ -1,26 +1,21 @@ package com.seaofnodes.simple.node.cpus.riscv; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.FunNode; import com.seaofnodes.simple.node.ReturnNode; import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.type.TypeFloat; import java.io.ByteArrayOutputStream; public class RetRISC extends ReturnNode implements MachNode{ RetRISC(ReturnNode ret, FunNode fun) { super(ret, fun); fun.setRet(this); } - // Todo: postSelect + // Register mask allowed on input i. // This is the normal calling convention - @Override public RegMask regmap(int i) { - return switch( i ) { - case 0 -> null; - case 1 -> null; - case 2 -> riscv.RET_MASK; - case 3 -> null; // RPC is always on stack - default -> throw Utils.TODO(); - }; - } + @Override public RegMask regmap(int i) { return riscv.retMask(_fun.sig(),i); } // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return null; } @@ -29,12 +24,17 @@ public class RetRISC extends ReturnNode implements MachNode{ throw Utils.TODO(); } - // Human-readable form appended to the SB. Things like the encoding, - // indentation, leading address or block labels not printed here. - // Just something like "ld4\tR17=[R18+12] // Load array base". - // General form: "op\tdst=src+src" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(in(2))); + // Post code-gen, just print the "ret" + if( code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() ) + // Prints return reg (either A0 or FA0), RPC (always R1) and then + // the callee-save registers. + for( int i=2; i<nIns(); i++ ) + sb.p(code.reg(in(i))).p(" "); + + // Post-allocation, if we did not get the expected return register, print what we got + else if( code._regAlloc.regnum(in(3)) != riscv.RPC ) + sb.p("[").p(code.reg(in(3))).p("]"); } // Correct Nodes outside the normal edges diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java new file mode 100644 index 00000000..3f9fc192 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java @@ -0,0 +1,33 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.BoolNode; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; +import java.io.ByteArrayOutputStream; + +// corresponds to slt,sltu,slti,sltiu, seqz +public class SetFRISC extends MachConcreteNode implements MachNode { + final String _bop; // One of <,<=,== + SetFRISC( BoolNode bool ) { + super(bool); + assert bool.isFloat(); + _bop = bool.op(); + } + @Override public RegMask regmap(int i) { return riscv.FMASK; } + @Override public RegMask outregmap() { return riscv.WMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(in(1))).p(" ").p(_bop).p(" ").p(code.reg(in(2))); + } + + @Override public String op() { return "set"+_bop; } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java new file mode 100644 index 00000000..3c0f76ae --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java @@ -0,0 +1,17 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.node.BoolNode; + +// corresponds to slt,sltu,slti,sltiu, seqz +public class SetIRISC extends ImmRISC { + final String _bop; // One of <,<=,== + SetIRISC( BoolNode bool, int imm ) { + super(bool,imm); + assert !bool.isFloat(); + _bop = bool.op(); + } + @Override int opcode() { throw Utils.TODO(); } + @Override public String glabel() { return _bop; } + @Override public String op() { return "set"+_bop; } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java index 1d8c9de8..fb7650c7 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java @@ -1,24 +1,24 @@ package com.seaofnodes.simple.node.cpus.riscv; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.BoolNode; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; import java.io.ByteArrayOutputStream; // corresponds to slt,sltu,slti,sltiu, seqz -// RISCV doesn't have rflags. -public class SetRISC extends MachConcreteNode implements MachNode{ +public class SetRISC extends MachConcreteNode implements MachNode { final String _bop; // One of <,<=,== - SetRISC( Node cmp, String bop ) { - super(cmp); - _inputs.setLen(1); // Pop the cmp inputs - // Replace with the matched cmp - _inputs.push(cmp); - _bop = bop; + SetRISC( BoolNode bool ) { + super(bool); + assert !bool.isFloat(); + _bop = bool.op(); } - @Override public RegMask regmap(int i) { assert i==1; return riscv.FLAGS_MASK; } - @Override public RegMask outregmap() { return riscv.RMASK; } + @Override public RegMask regmap(int i) { throw Utils.TODO(); } + @Override public RegMask outregmap() { return riscv.WMASK; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { @@ -26,9 +26,7 @@ public class SetRISC extends MachConcreteNode implements MachNode{ } @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)); - String src = code.reg(in(1)); - if( src!="FLAGS" ) sb.p(" = ").p(src); + sb.p(code.reg(in(1))).p(" ").p(_bop).p(" ").p(code.reg(in(2))); } @Override public String op() { return "set"+_bop; } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java index 8927c086..b5202908 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java @@ -1,40 +1,11 @@ package com.seaofnodes.simple.node.cpus.riscv; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.node.cpus.x86_64_v2.x86_64_v2; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; -import java.util.BitSet; - -public class SllIRISC extends MachConcreteNode implements MachNode{ - final TypeInteger _ti; - SllIRISC(Node sll, TypeInteger ti) {super(sll); _inputs.pop(); _ti = ti;} - - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { - //assert i==1; - return riscv.RMASK; } - - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return riscv.RMASK; } - - // Output is same register as input#1 - @Override public int twoAddress() { return 0; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form - // General form: "slli rd, rs1, imm" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" << #"); - _ti.print(sb); - } +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.node.Node; +public class SllIRISC extends ImmRISC { + SllIRISC( Node and, int imm) { super(and,imm); } + @Override int opcode() { throw Utils.TODO(); } + @Override public String glabel() { return "<<"; } @Override public String op() { return "slli"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java index 69538433..447f604a 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.riscv; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; @@ -16,10 +18,7 @@ public class SllRISC extends MachConcreteNode implements MachNode{ //assert i==1; return riscv.RMASK; } // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return riscv.RMASK; } - - // Output is same register as input#1 - @Override public int twoAddress() { return 1; } + @Override public RegMask outregmap() { return riscv.WMASK; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { @@ -29,7 +28,9 @@ public class SllRISC extends MachConcreteNode implements MachNode{ // General form // General form: "sll rd, rs1, rs2" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))); + + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" << ").p(code.reg(in(2))); + } @Override public String op() { return "sll"; } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java new file mode 100644 index 00000000..b5076ba0 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java @@ -0,0 +1,21 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.SplitNode; +import java.io.ByteArrayOutputStream; + +public class SplitRISC extends SplitNode { + SplitRISC( String kind, byte round ) { super(kind,round, new Node[2]); } + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { return riscv.SPLIT_MASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return riscv.SPLIT_MASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java index 654dd2ae..d5ab5e08 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java @@ -1,43 +1,11 @@ package com.seaofnodes.simple.node.cpus.riscv; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.node.cpus.x86_64_v2.x86_64_v2; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; -import java.util.BitSet; -import java.lang.StringBuilder; - - -// Right Shift Arithmetic(immediate) -public class SraIRISC extends MachConcreteNode implements MachNode{ - final TypeInteger _ti; - SraIRISC(Node sra, TypeInteger ti) {super(sra); _inputs.pop(); _ti = ti;} - - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { - // assert i==1; - return riscv.RMASK; } - - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return riscv.RMASK; } - - // Output is same register as input#1 - @Override public int twoAddress() { return 0; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form - // General form: "srai rd, rs1, imm" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> #"); - _ti.print(sb); - } +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.node.Node; +public class SraIRISC extends ImmRISC { + SraIRISC( Node and, int imm) { super(and,imm); } + @Override int opcode() { throw Utils.TODO(); } + @Override public String glabel() { return ">>"; } @Override public String op() { return "srai"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java index 2d50ceaa..0726e292 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java @@ -1,14 +1,13 @@ package com.seaofnodes.simple.node.cpus.riscv; -import com.seaofnodes.simple.CodeGen; -import com.seaofnodes.simple.RegMask; import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.MachConcreteNode; import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.type.TypeInteger; - import java.io.ByteArrayOutputStream; // Right Shift Arithmetic @@ -22,10 +21,7 @@ public class SraRISC extends MachConcreteNode implements MachNode { // assert i==1; return riscv.RMASK; } // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return riscv.RMASK; } - - // Output is same register as input#1 - @Override public int twoAddress() { return 1; } + @Override public RegMask outregmap() { return riscv.WMASK; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { @@ -35,7 +31,7 @@ public class SraRISC extends MachConcreteNode implements MachNode { // General form // General form: "sra rd, rs1, rs2" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))); + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> ").p(code.reg(in(2))); } @Override public String op() { return "sra"; } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java index f7ad6c25..a4eb99bc 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java @@ -1,43 +1,11 @@ package com.seaofnodes.simple.node.cpus.riscv; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.node.cpus.x86_64_v2.x86_64_v2; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; -import java.util.BitSet; -import java.lang.StringBuilder; - - -// Right Shift Logical Immediate -public class SrlIRISC extends MachConcreteNode implements MachNode { - final TypeInteger _ti; - SrlIRISC(Node srl, TypeInteger ti) {super(srl); _inputs.pop(); _ti = ti;} - - - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { - // assert i==1; - return riscv.RMASK; } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return riscv.RMASK; } - - // Output is same register as input#1 - @Override public int twoAddress() { return 1; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form - // General form: "srli dst << #imm" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> #"); - _ti.print(sb); - } +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.node.Node; +public class SrlIRISC extends ImmRISC { + SrlIRISC( Node and, int imm) { super(and,imm); } + @Override int opcode() { throw Utils.TODO(); } + @Override public String glabel() { return ">>>"; } @Override public String op() { return "srli"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java index 25d62688..d1435540 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.riscv; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; @@ -18,10 +20,7 @@ public class SrlRISC extends MachConcreteNode implements MachNode { // assert i==1; return riscv.RMASK; } // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return riscv.RMASK; } - - // Output is same register as input#1 - @Override public int twoAddress() { return 1; } + @Override public RegMask outregmap() { return riscv.WMASK; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { @@ -31,7 +30,8 @@ public class SrlRISC extends MachConcreteNode implements MachNode { // General form // General form: "srl rd, rs1, rs2" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))); + + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >>> ").p(code.reg(in(2))); } @Override public String op() { return "srl"; } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java index 1bd40d0c..22e443fd 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java @@ -1,26 +1,40 @@ package com.seaofnodes.simple.node.cpus.riscv; -import com.seaofnodes.simple.CodeGen; -import com.seaofnodes.simple.RegMask; import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.MachConcreteNode; import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.node.StoreNode; - import java.io.ByteArrayOutputStream; import java.util.BitSet; +// Store memory addressing on ARM +// Support imm, reg(direct), or reg+off(indirect) addressing +// Base = base - base pointer, offset is added to base +// idx = null +// off = off - offset added to base) +// imm = imm - immediate value to store +// val = Node of immediate value to store(null if its a constant immediate) + +//e.g s.cs[0] = 67; // C +// base = s.cs, off = 4, imm = 67, val = null // sw rs2,offset(rs1) public class StoreRISC extends MemOpRISC { - StoreRISC( StoreNode st) { - super(st, st); + StoreRISC( StoreNode st, Node base, Node idx, int off, int imm, Node val ) { + super(st, base, idx, off, imm, val); } + // Wider mask to store both GPRs and FPRs @Override public RegMask regmap(int i) { - return i>=2 && i<=4 ? riscv.RMASK : null; + if( i==1 ) return riscv.MEM_MASK; + if( i==2 ) return riscv.RMASK; + if( i==3 ) return riscv.RMASK; + if( i==4 ) return riscv.RMASK; + return null; // Trailing anti-dependence edges } @@ -33,7 +47,9 @@ public class StoreRISC extends MemOpRISC { } @Override public void asm(CodeGen code, SB sb) { - + asm_address(code,sb).p(","); + if( val()==null ) sb.p("#").p(_imm); + else sb.p(code.reg(val())); } @Override public String op() { return "st"+_sz; } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java index e6a0b8e4..e6fe3012 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.riscv; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; @@ -12,8 +14,6 @@ public class SubFRISC extends MachConcreteNode implements MachNode{ @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; } // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return riscv.FMASK; } - // Output is same register as input#1 - @Override public int twoAddress() { return 0; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java index 6fb0e0ef..c37aea98 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.riscv; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; @@ -11,9 +13,7 @@ public class SubRISC extends MachConcreteNode implements MachNode { // Register mask allowed on input i. @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.RMASK; } // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return riscv.RMASK; } - // Output is same register as input#1 - @Override public int twoAddress() { return 0; } + @Override public RegMask outregmap() { return riscv.WMASK; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java index b508541a..23fb6a74 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java @@ -1,22 +1,24 @@ package com.seaofnodes.simple.node.cpus.riscv; - import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.ConstantNode; import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.cpus.x86_64_v2.TFPX86; -import com.seaofnodes.simple.node.cpus.x86_64_v2.x86_64_v2; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeFunPtr; - import java.io.ByteArrayOutputStream; public class TFPRISC extends ConstantNode implements MachNode{ TFPRISC(ConstantNode con) {super(con);} + @Override public String op() { return _con == Type.NIL ? "xor" : "ldx"; } // Register mask allowed on input i. 0 for no register. @Override public RegMask regmap(int i) { return null; } // General int registers - @Override public RegMask outregmap() { return x86_64_v2.RMASK; } + @Override public RegMask outregmap() { return riscv.WMASK; } + + @Override public boolean isClone() { return true; } + @Override public TFPRISC copy() { return new TFPRISC(this); } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { @@ -34,10 +36,4 @@ public class TFPRISC extends ConstantNode implements MachNode{ else _con.print(sb.p(reg).p(" #")); } - - @Override public String op() { - if( _con == Type.NIL ) - return "xor"; - return "ldx"; // Some fancier encoding - } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java index 3d53ee64..a2e32f06 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java @@ -1,45 +1,11 @@ package com.seaofnodes.simple.node.cpus.riscv; -import com.seaofnodes.simple.CodeGen; -import com.seaofnodes.simple.RegMask; -import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.node.MachConcreteNode; -import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.node.cpus.x86_64_v2.x86_64_v2; -import com.seaofnodes.simple.type.TypeInteger; - -import java.io.ByteArrayOutputStream; - -public class XorIRISC extends MachConcreteNode implements MachNode{ - final TypeInteger _ti; - XorIRISC(Node xor, TypeInteger ti) {super(xor); _inputs.pop(); _ti = ti;} - - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { - assert i==1 || i==2; - - return riscv.RMASK; } - - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return riscv.RMASK; } - - // Output is same register as input#1 - @Override public int twoAddress() { return 1; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form - // General form: "rd = rs1 ^ imm" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ^ #"); - _ti.print(sb); - } +public class XorIRISC extends ImmRISC { + XorIRISC( Node and, int imm) { super(and,imm); } + @Override int opcode() { throw Utils.TODO(); } + @Override public String glabel() { return "^"; } @Override public String op() { return "xori"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java index 2f34529e..f4ecb430 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java @@ -1,14 +1,13 @@ package com.seaofnodes.simple.node.cpus.riscv; -import com.seaofnodes.simple.CodeGen; -import com.seaofnodes.simple.RegMask; import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.MachConcreteNode; import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.type.TypeInteger; - import java.io.ByteArrayOutputStream; public class XorRISC extends MachConcreteNode implements MachNode { @@ -25,10 +24,7 @@ public class XorRISC extends MachConcreteNode implements MachNode { } // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return riscv.RMASK; } - - // Output is same register as input#1 - @Override public int twoAddress() { return 0; } + @Override public RegMask outregmap() { return riscv.WMASK; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { @@ -38,6 +34,8 @@ public class XorRISC extends MachConcreteNode implements MachNode { // General form // General form: # # rd = rs1 ^ rs2 @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))); + + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ^ ").p(code.reg(in(2))); + } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java index ad6e1c0f..0542085c 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java @@ -1,63 +1,84 @@ package com.seaofnodes.simple.node.cpus.riscv; -import com.seaofnodes.simple.Machine; -import com.seaofnodes.simple.RegMask; import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.LRG; +import com.seaofnodes.simple.codegen.Machine; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.node.cpus.x86_64_v2.*; import com.seaofnodes.simple.type.*; -public class riscv extends Machine{ +public class riscv extends Machine { @Override public String name() {return "riscv";} // Using ABI names instead of register names - public static int ZERO = 0, RA = 1, SP = 2, GP = 3, TP = 4, T0 = 5, T1 = 6, T2 = 7; - public static int S0 = 8, S1 = 9, A0 = 10, A1 = 11, A2 = 12, A3 = 13, A4 = 14, A5 = 15; - public static int A6 = 16, A7 = 17, S2 = 18, S3 = 19, S4 = 20, S5 = 21, S6 = 22, S7 = 23; - public static int S8 = 24, S9 = 25, S10 = 26, S11 = 27, T3 = 28, T4 = 29, T5 = 30, T6 = 31; + static final int RPC= 1, SP = 2, GP = 3, TP = 4, T0 = 5, T1 = 6, T2 = 7; + static final int S0 = 8, S1 = 9, A0 = 10, A1 = 11, A2 = 12, A3 = 13, A4 = 14, A5 = 15; + static final int A6 = 16, A7 = 17, S2 = 18, S3 = 19, S4 = 20, S5 = 21, S6 = 22, S7 = 23; + static final int S8 = 24, S9 = 25, S10 = 26, S11 = 27, T3 = 28, T4 = 29, T5 = 30, T6 = 31; // FP registers - public static int F0 = 32, F1 = 33, F2 = 34, F3 = 35, F4 = 36, F5 = 37, F6 = 38, F7 = 39; - public static int FS0 = 40, FS1 = 41, FA0 = 42, FA1 = 43, FA2 = 44, FA3 = 45, FA4 = 46, FA5 = 47; - public static int FA6 = 48, FA7 = 49, FS2 = 50, FS3 = 51, FS4 = 52, FS5 = 53, FS6 = 54, FS7 = 55; - public static int FS8 = 56, FS9 = 57, FS10 = 58, FS11 = 59, FT8 = 60, FT9 = 61, FT10 = 62, FT11 = 63; + static final int F0 = 32, F1 = 33, F2 = 34, F3 = 35, F4 = 36, F5 = 37, F6 = 38, F7 = 39; + static final int FS0 = 40, FS1 = 41, FA0 = 42, FA1 = 43, FA2 = 44, FA3 = 45, FA4 = 46, FA5 = 47; + static final int FA6 = 48, FA7 = 49, FS2 = 50, FS3 = 51, FS4 = 52, FS5 = 53, FS6 = 54, FS7 = 55; + static final int FS8 = 56, FS9 = 57, FS10 = 58, FS11 = 59, FT8 = 60, FT9 = 61, FT10 = 62, FT11 = 63; + + static final int MAX_REG = 61; + + static final String[] REGS = new String[] { + "zero", "r1" , "rsp" , "r3" , "r4" , "r5" , "r6" , "r7" , + "r8" , "r9" , "r10" , "r11" , "r12" , "r13" , "r14" , "r15" , + "r16" , "r17" , "r18" , "r19" , "r20" , "r21" , "r22" , "r23" , + "r24" , "r25" , "r26" , "r27" , "r28" , "r29" , "r30" , "r31" , + "f0" , "f1" , "f2" , "f3" , "f4" , "f5" , "f6" , "f7" , + "fs0" , "fs1" , "fa0" , "fa1" , "fa2" , "fa3" , "fa4" , "fa5" , + "fa6" , "fa7" , "fs2" , "fs3" , "fs4" , "fs5" , "fs6" , "fs7" , + "fs8" , "fs9" , "fs10", "fs11", "ft8" , "ft9" , "ft10", "ft11" + }; + @Override public String reg( int reg ) { + return reg < REGS.length ? REGS[reg] : "[rsp+"+(reg-REGS.length)*4+"]"; + } - public static int FLAGS = 64; // General purpose register mask: pointers and ints, not floats - public static RegMask RMASK = new RegMask(0b11111111111111111111111111111111); - // Float mask from(ft0–ft11) - public static RegMask FMASK = new RegMask(0b11111111111111111111111111111111); + static final long RD_BITS = 0b11111111111111111111111111111111L; // All the GPRs + static final RegMask RMASK = new RegMask(RD_BITS); + + static final long WR_BITS = 0b11111111111111111111111111111010L; // All the GPRs, minus ZERO and SP + static final RegMask WMASK = new RegMask(WR_BITS); + // Float mask from(f0-ft10). TODO: ft10,ft11 needs a larger RegMask + static final long FP_BITS = 0b11111111111111111111111111111111L<<F0; + static final RegMask FMASK = new RegMask(FP_BITS); + + // Load/store mask; both GPR and FPR + static final RegMask MEM_MASK = new RegMask(WR_BITS | FP_BITS); + + static final RegMask SPLIT_MASK = new RegMask( WR_BITS | FP_BITS, -1L ); - // Return single int/ptr register - public static RegMask RET_MASK = new RegMask(A0); // Arguments masks - public static RegMask A0_MASK = new RegMask(1L<<A0); - public static RegMask A1_MASK = new RegMask(1L<<A1); - public static RegMask A2_MASK = new RegMask(1L<<A2); - public static RegMask A3_MASK = new RegMask(1L<<A3); - public static RegMask A4_MASK = new RegMask(1L<<A4); - public static RegMask A5_MASK = new RegMask(1L<<A5); - public static RegMask A6_MASK = new RegMask(1L<<A6); - public static RegMask A7_MASK = new RegMask(1L<<A7); + static final RegMask A0_MASK = new RegMask(A0); + static final RegMask A1_MASK = new RegMask(A1); + static final RegMask A2_MASK = new RegMask(A2); + static final RegMask A3_MASK = new RegMask(A3); + static final RegMask A4_MASK = new RegMask(A4); + static final RegMask A5_MASK = new RegMask(A5); + static final RegMask A6_MASK = new RegMask(A6); + static final RegMask A7_MASK = new RegMask(A7); - public static RegMask FLAGS_MASK = new RegMask(1L << FLAGS); + static final RegMask RPC_MASK = new RegMask(RPC); // Float arguments masks - public static RegMask FA0_MASK = new RegMask(1L<<FA0); - public static RegMask FA1_MASK = new RegMask(1L<<FA1); - public static RegMask FA2_MASK = new RegMask(1L<<FA2); - public static RegMask FA3_MASK = new RegMask(1L<<FA3); - public static RegMask FA4_MASK = new RegMask(1L<<FA4); - public static RegMask FA5_MASK = new RegMask(1L<<FA5); - public static RegMask FA6_MASK = new RegMask(1L<<FA6); - public static RegMask FA7_MASK = new RegMask(1L<<FA7); + static final RegMask FA0_MASK = new RegMask(FA0); + static final RegMask FA1_MASK = new RegMask(FA1); + static final RegMask FA2_MASK = new RegMask(FA2); + static final RegMask FA3_MASK = new RegMask(FA3); + static final RegMask FA4_MASK = new RegMask(FA4); + static final RegMask FA5_MASK = new RegMask(FA5); + static final RegMask FA6_MASK = new RegMask(FA6); + static final RegMask FA7_MASK = new RegMask(FA7); // Int arguments calling conv - static RegMask[] CALLINMASK_RISCV_INT = new RegMask[] { - null, - null, + static RegMask[] CALLINMASK = new RegMask[] { A0_MASK, A1_MASK, A2_MASK, @@ -68,31 +89,7 @@ public class riscv extends Machine{ A7_MASK }; - static int[] CALLINARG_RISCV_INT = new int[] { - 0, // Control, no register - 0, // Memory, no register - A0, - A1, - A2, - A3, - A4, - A5, - A6, - A7 - }; - - static int callInArgInt(int idx) { - return CALLINARG_RISCV_INT[idx]; - } - - static RegMask callInMaskInt(int idx) { - return CALLINMASK_RISCV_INT[idx]; - } - - // Float arguments - static RegMask[] CALLINMASK_RISCV_FLOAT = new RegMask[] { - null, - null, + static RegMask[] XMMS = new RegMask[] { FA0_MASK, FA1_MASK, FA2_MASK, @@ -103,111 +100,133 @@ public class riscv extends Machine{ FA7_MASK }; - - static int[] CALLINARG_RISCV_FLOAT = new int[] { - 0, // Control, no register - 0, // Memory, no register - FA0, - FA1, - FA2, - FA3, - FA4, - FA5, - FA6, - FA7 - }; - - - static int callInArgFloat(int idx) { - return CALLINARG_RISCV_FLOAT[idx]; + static RegMask callInMask( TypeFunPtr tfp, int idx ) { + if( idx==0 ) return RPC_MASK; + if( idx==1 ) return null; + // Count floats in signature up to index + int fcnt=0; + for( int i=2; i<idx; i++ ) + if( tfp.arg(i-2) instanceof TypeFloat ) + fcnt++; + // Floats up to XMMS in XMM registers + if( tfp.arg(idx-2) instanceof TypeFloat ) { + if( fcnt < XMMS.length ) + return XMMS[fcnt]; + } else { + RegMask[] cargs = CALLINMASK; + if( idx-2-fcnt < cargs.length ) + return cargs[idx-2-fcnt]; + } + throw Utils.TODO(); // Pass on stack slot } - static RegMask callInMaskFloat(int idx) { - return CALLINMASK_RISCV_FLOAT[idx]; - } - // caller saved(riscv) - //public static final long RISCV_CALLER_SAVED= TBD // callee saved(riscv) - public static final long RISCV_CALLEE_SAVED = - (1L << FS0) | (1L << FS1) | (1L << FS2) | (1L << FS3) | (1L << FS4) - | (1L << FS5) | (1L << FS6) | (1L << FS7) | (1L << FS8) | (1L << FS9) | (1L << FS10); - - - // Calling conv metadata - public int GPR_COUNT_CONV_RISCV = 7; // A0, A1, A2, A3, A4, A5, A6, A7 - public int FLOAT_COUNT_CONV_RISCV = 7; // FA0, FA1, FA2, FA3, FA4, FA5, FA6, FA7 - - public static final String[] REGS = new String[] { - "zero", "ra" , "sp" , "gp" , "tp" , "t0" , "t1" , "t2" , - "s0" , "s1" , "a0" , "a1" , "a2" , "a3" , "a4" , "a5" , - "a6" , "a7" , "s2" , "s3" , "s4" , "s5" , "s6" , "s7" , - "s8" , "s9" , "s10" , "s11" , "t3" , "t4" , "t5" , "t6" , - "f0" , "f1" , "f2" , "f3" , "f4" , "f5" , "f6" , "f7" , - "fs0" , "fs1" , "fa0" , "fa1" , "fa2" , "fa3" , "fa4" , "fa5" , - "fa6" , "fa7" , "fs2" , "fs3" , "fs4" , "fs5" , "fs6" , "fs7" , - "fs8" , "fs9" , "fs10", "fs11", "ft8" , "ft9" , "ft10", "ft11", - "flags" - }; + static final long CALLEE_SAVE = + (1L<< S0) | (1L<< S1) | (1L<< S2 ) | (1L<< S3 ) | + (1L<< S4) | (1L<< S5) | (1L<< S6 ) | (1L<< S7 ) | + (1L<< S8) | (1L<< S9) | (1L<< S10) | (1L<< S11) | + (1L<<FS0) | (1L<<FS1) | (1L<<FS2 ) | (1L<<FS3 ) | + (1L<<FS4) | (1L<<FS5) | (1L<<FS6 ) | (1L<<FS7 ) | + (1L<<FS8) | (1L<<FS9) | (1L<<FS10) | (1L<<FS11); + + static final RegMask CALLER_SAVE_MASK; + static { + long caller = ~CALLEE_SAVE; + caller &= ~(1L<<SP); + CALLER_SAVE_MASK = new RegMask(caller,0); + } + static RegMask riscCallerSave() { return CALLER_SAVE_MASK; } + @Override public RegMask callerSave() { return riscCallerSave(); } + + static final RegMask CALLEE_SAVE_MASK = new RegMask(CALLEE_SAVE); + static RegMask riscCalleeSave() { return CALLEE_SAVE_MASK; } + @Override public RegMask calleeSave() { return riscCalleeSave(); } + + // Return single int/ptr register. Used by CallEnd output and Return input. + static final RegMask[] RET_MASKS; + static { + int nSaves = CALLEE_SAVE_MASK.size(); + RET_MASKS = new RegMask[4 + nSaves]; + RET_MASKS[0] = null; + RET_MASKS[1] = null; // Memory + RET_MASKS[2] = null; // Varies, either XMM0 or RAX + RET_MASKS[3] = RMASK; // Expected R1 but could be any register really + short reg = CALLEE_SAVE_MASK.firstReg(); + for( int i=0; i<nSaves; i++ ) { + RET_MASKS[i+4] = new RegMask(reg); + reg = CALLEE_SAVE_MASK.nextReg(reg); + } + } + static RegMask retMask( TypeFunPtr tfp, int i ) { + return i==2 + ? (tfp.ret() instanceof TypeFloat ? FA0_MASK : A0_MASK) + : RET_MASKS[i]; + } + - // General purpose register mask: - @Override public String reg( int reg ) { return REGS[reg]; } // Return a MachNode unconditional branch @Override public CFGNode jump() { throw Utils.TODO(); } // Create a split op; any register to any register, including stack slots - @Override public Node split() { - throw Utils.TODO(); - } + @Override public SplitNode split(String kind, byte round, LRG lrg) { return new SplitRISC(kind,round); } // Break an infinite loop @Override public IfNode never( CFGNode ctrl ) { throw Utils.TODO(); } + // True if signed 12-bit immediate + private static boolean imm12(TypeInteger ti) { + // 64-12==52 + return ti.isConstant() && ((ti.value()<<52)>>52) == ti.value(); + } + + @Override public Node instSelect( Node n ) { return switch (n) { - case AddFNode addf -> addf(addf); - case AddNode add -> add(add); - case AndNode and -> and(and); - case BoolNode bool -> cmp(bool); - case CallEndNode cend -> new CallEndNode((CallNode) cend.in(0)); - case CallNode call -> call(call); - case CProjNode c -> new CProjNode(c); - case ConstantNode con -> con(con); - case DivFNode divf -> new DivFRISC(divf); - case DivNode div -> new DivRISC(div); - case FunNode fun -> new FunRISC(fun); - case IfNode iff -> jmp(iff); - case LoadNode ld -> ld(ld); - case MemMergeNode mem -> new MemMergeNode(mem); - case MulFNode mulf -> new MulFRISC(mulf); - case MulNode mul -> mul(mul); - case NewNode nnn -> new NewRISC(nnn); - case OrNode or -> or(or); - case ParmNode parm -> new ParmRISC(parm); - case BulkMemPhiNode phi -> new BulkMemPhiNode(phi); - case MemPhiNode phi -> new MemPhiNode(phi); - case PhiNode phi -> new PhiNode(phi); - case ProjNode prj -> prj(prj); - case ReadOnlyNode read -> new ReadOnlyNode(read); - case ReturnNode ret -> new RetRISC(ret, ret.fun()); - case SarNode sar -> sra(sar); - case ShlNode shl -> sll(shl); - case ShrNode shr -> srl(shr); - case StartNode start -> new StartNode(start); - case StopNode stop -> new StopNode(stop); - case StoreNode st -> st(st); - case SubFNode subf -> new SubFRISC(subf); - case SubNode sub -> sub(sub); - case ToFloatNode tfn -> i2f8(tfn); + case AddFNode addf -> addf(addf); + case AddNode add -> add(add); + case AndNode and -> and(and); + case BoolNode bool -> cmp(bool); + case CallNode call -> call(call); + case CastNode cast -> new CastRISC(cast); + case CallEndNode cend -> new CallEndRISC(cend); + case CProjNode c -> new CProjNode(c); + case ConstantNode con -> con(con); + case DivFNode divf -> new DivFRISC(divf); + case DivNode div -> new DivRISC(div); + case FunNode fun -> new FunRISC(fun); + case IfNode iff -> jmp(iff); + case LoadNode ld -> ld(ld); + case MemMergeNode mem -> new MemMergeNode(mem); + case MulFNode mulf -> new MulFRISC(mulf); + case MulNode mul -> new MulRISC(mul); + case NewNode nnn -> new NewRISC(nnn); + case OrNode or -> or(or); + case ParmNode parm -> new ParmRISC(parm); + case BulkMemPhiNode phi -> new BulkMemPhiNode(phi); + case MemPhiNode phi -> new MemPhiNode(phi); + case PhiNode phi -> new PhiNode(phi); + case ProjNode prj -> prj(prj); + case ReadOnlyNode read -> new ReadOnlyNode(read); + case ReturnNode ret -> new RetRISC(ret, ret.fun()); + case SarNode sar -> sra(sar); + case ShlNode shl -> sll(shl); + case ShrNode shr -> srl(shr); + case StartNode start -> new StartNode(start); + case StopNode stop -> new StopNode(stop); + case StoreNode st -> st(st); + case SubFNode subf -> new SubFRISC(subf); + case SubNode sub -> sub(sub); + case ToFloatNode tfn -> i2f8(tfn); case ToIntegerNode cvt -> new F8ToIRISC(cvt); - case XorNode xor -> xor(xor); + case XorNode xor -> xor(xor); - case LoopNode loop -> new LoopNode(loop); - case RegionNode region -> new RegionNode(region); - default -> throw Utils.TODO(); + case LoopNode loop -> new LoopNode(loop); + case RegionNode region -> new RegionNode(region); + default -> throw Utils.TODO(); }; } @@ -216,94 +235,86 @@ public class riscv extends Machine{ } private Node add(AddNode add) { - Node rhs = add.in(2); - if( rhs instanceof ConstantNode off && off._con instanceof TypeInteger toff ) { - return new AddIRISC(add, toff); - } + if( add.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && imm12(ti) ) + return new AddIRISC(add, (int)ti.value()); return new AddRISC(add); } private Node and(AndNode and) { - if( and.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti ) - return new AndIRISC(and, ti); + if( and.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) ) + return new AndIRISC(and, (int)ti.value()); return new AndRISC(and); } - private Node cmp(BoolNode bool) { - Node cmp = _cmp(bool); - return new SetRISC(cmp, bool.op()); - } - private Node call(CallNode call) { if( call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp ) return new CallRISC(call, tfp); return new CallRRISC(call); } - private Node _cmp(BoolNode bool) { - Node rhs = bool.in(2); - - return rhs instanceof ConstantNode con && con._con instanceof TypeInteger ti - ? new CmpIRISC(bool, ti) - : new CmpRISC(bool); + private Node cmp(BoolNode bool) { + if( bool.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger toff ) + return new SetIRISC(bool, (int)toff.value()); + // Float variant + if( bool.isFloat() ) + return new SetFRISC(bool); + return new SetRISC(bool); } private Node con( ConstantNode con ) { + if( !con._con.isConstant() ) return new ConstantNode( con ); // Default unknown caller inputs return switch( con._con ) { - case TypeInteger ti -> new IntRISC(con); - case TypeFloat tf -> new IntRISC(con); - case TypeFunPtr tfp -> new TFPRISC(con); - case TypeMemPtr tmp -> new ConstantNode(con); - case TypeNil tn -> throw Utils.TODO(); - // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code. - case Type t -> new ConstantNode(con); + case TypeInteger ti -> new IntRISC(con); + case TypeFloat tf -> new FltRISC(con); + case TypeFunPtr tfp -> new TFPRISC(con); + case TypeMemPtr tmp -> throw Utils.TODO(); + case TypeNil tn -> throw Utils.TODO(); + // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code. + case Type t -> t==Type.NIL ? new IntRISC(con) : new ConstantNode(con); }; } private Node jmp( IfNode iff ) { - // If/Bool combos will match to a Cmp/Set which sets flags. - // Most general arith ops will also set flags, which the Jmp needs directly. - // Loads do not set the flags, and will need an explicit TEST - if( !(iff.in(1) instanceof BoolNode) ) - iff.setDef(1,new BoolNode.EQ(iff.in(1),new ConstantNode(TypeInteger.ZERO))); - return new CBranchRISC(iff, ((BoolNode)iff.in(1)).op()); + if( iff.in(1) instanceof BoolNode bool && !bool.isFloat() ) + return new BranchRISC(iff, bool.op(), bool.in(1), bool.in(2)); + // Vs zero + return new BranchRISC(iff, "==", iff.in(1), null); } private Node or(OrNode or) { - if(or.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) - return new OrIRISC(or, ti); - + if( or.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) ) + return new OrIRISC(or, (int)ti.value()); return new OrRISC(or); } private Node xor(XorNode xor) { - if(xor.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) - return new XorIRISC(xor, ti); + if( xor.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) ) + return new XorIRISC(xor, (int)ti.value()); return new XorRISC(xor); } private Node sra(SarNode sar) { - if( sar.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) - return new SraIRISC(sar, ti); + if( sar.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) ) + return new SraIRISC(sar, (int)ti.value()); return new SraRISC(sar); } private Node srl(ShrNode shr) { - if( shr.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) - return new SrlIRISC(shr, ti); + if( shr.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) ) + return new SrlIRISC(shr, (int)ti.value()); return new SrlRISC(shr); } private Node sll(ShlNode sll) { - if( sll.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) - return new SllIRISC(sll, ti); + if( sll.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) ) + return new SllIRISC(sll, (int)ti.value()); return new SllRISC(sll); } private Node sub(SubNode sub) { - return sub.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti - ? new AddIRISC(sub, TypeInteger.constant(-ti.value())) - : new SubRISC(sub); + return sub.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) + ? new AddIRISC(sub, (int)(-ti.value())) + : new SubRISC(sub); } private Node i2f8(ToFloatNode tfn) { @@ -316,17 +327,38 @@ public class riscv extends Machine{ } private Node ld(LoadNode ld) { - return new LoadRISC(ld); + return new LoadRISC(address(ld),ld.ptr(),idx,off); } private Node st(StoreNode st) { - return new StoreRISC(st); + int imm=0; + Node xval = st.val(); + if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) ) { + xval = null; + imm = (int)ti.value(); + assert imm == ti.value(); // In 32-bit range + } + return new StoreRISC(address(st),st.ptr(),idx,off,imm,xval); } - private Node mul(MulNode mul) { - return mul.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti - ? new MulIRISC(mul, ti) - : new MulRISC(mul); + // Gather addressing mode bits prior to constructing. This is a builder + // pattern, but saving the bits in a *local* *global* here to keep mess + // contained. + private static int off; + private static Node idx; + private <N extends MemOpNode> N address( N mop ) { + off = 0; // Reset + idx = null; + Node base = mop.ptr(); + // Skip/throw-away a ReadOnly, only used to typecheck + if( base instanceof ReadOnlyNode read ) base = read.in(1); + assert !(base instanceof AddNode) && base._type instanceof TypeMemPtr; // Base ptr always, not some derived + if( mop.off() instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) ) { + off = (int)ti.value(); + } else { + idx = mop.off(); + } + return mop; } -} +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java index 4bb24fa3..ffc6966e 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java @@ -1,9 +1,9 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.CodeGen; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.RegMask; import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; public class AddFMemX86 extends MemOpX86 { @@ -12,10 +12,10 @@ public class AddFMemX86 extends MemOpX86 { } @Override public RegMask regmap(int i) { - if( i==1 ) return null; // Memory - if( i==2 ) return x86_64_v2.RMASK; // base - if( i==3 ) return x86_64_v2.RMASK; // index - if( i==4 ) return x86_64_v2.XMASK; // value + if( i==1 ) return null; // Memory + if( i==2 ) return x86_64_v2.RMASK; // base + if( i==3 ) return x86_64_v2.RMASK; // index + if( i==4 ) return x86_64_v2.XMASK; // value throw Utils.TODO(); } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java index a3f5c7b3..d4ea9162 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; @@ -14,6 +16,8 @@ public class AddFX86 extends MachConcreteNode implements MachNode { @Override public RegMask outregmap() { return x86_64_v2.XMASK; } // Output is same register as input#1 @Override public int twoAddress() { return 1; } + // Ok to swap arguments + @Override public boolean commutes() { return true; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java index 6a332ff5..c7f92ff1 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java index 7d45d757..3d4fac41 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java @@ -1,8 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.CodeGen; -import com.seaofnodes.simple.RegMask; import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; public class AddMemX86 extends MemOpX86 { @@ -14,7 +14,8 @@ public class AddMemX86 extends MemOpX86 { // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - + // Output is same register as val() + @Override public int twoAddress() { return 4; } // General form: "add dst = src + [base + idx<<2 + 12]" @Override public void asm(CodeGen code, SB sb) { diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java index 61aa5a9e..9553d499 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java @@ -1,11 +1,13 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; -public class AddX86 extends MachConcreteNode implements MachNode { +public class AddX86 extends MachConcreteNode { AddX86( Node add ) { super(add); } // Register mask allowed on input i. @@ -14,6 +16,8 @@ public class AddX86 extends MachConcreteNode implements MachNode { @Override public RegMask outregmap() { return x86_64_v2.WMASK; } // Output is same register as input#1 @Override public int twoAddress() { return 1; } + // Ok to swap arguments + @Override public boolean commutes() { return true; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java index f2ee1c66..a7f1801e 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java index 45cf2b22..d7fb3613 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import java.io.ByteArrayOutputStream; @@ -23,11 +25,10 @@ public class AndX86 extends MachConcreteNode implements MachNode{ } // General form - // General form: "and dst & #imm" + // General form: "and dst & src" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))); + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" & ").p(code.reg(in(2)));; } @Override public String op() { return "and"; } } - diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallEndX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallEndX86.java index 55ab90c9..b9cc5659 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallEndX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallEndX86.java @@ -2,8 +2,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.CodeGen; -import com.seaofnodes.simple.RegMask; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.CallEndNode; import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.type.TypeFloat; @@ -14,16 +14,14 @@ public class CallEndX86 extends CallEndNode implements MachNode { final TypeFunPtr _tfp; CallEndX86( CallEndNode cend ) { super(cend); - _tfp = (TypeFunPtr)cend.call().fptr()._type; + _tfp = (TypeFunPtr)(cend.call().fptr()._type); } @Override public String label() { return op(); } @Override public RegMask regmap(int i) { return null; } - @Override public RegMask outregmap() { throw Utils.TODO(); } - @Override public RegMask outregmap(int idx) { - if( idx != 2 ) return null; - return _tfp._ret instanceof TypeFloat ? x86_64_v2.RET_FMASK : x86_64_v2.RET_MASK; - } + @Override public RegMask outregmap() { return null; } + @Override public RegMask outregmap(int idx) { return idx == 2 ? x86_64_v2.retMask(_tfp,2) : null; } + @Override public RegMask killmap() { return x86_64_v2.x86CallerSave(); } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java index 99260a27..ac35d17b 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java @@ -1,9 +1,11 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFunPtr; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.CallNode; +import com.seaofnodes.simple.node.MachNode; import java.io.ByteArrayOutputStream; public class CallRX86 extends CallNode implements MachNode { @@ -15,17 +17,24 @@ public class CallRX86 extends CallNode implements MachNode { ? x86_64_v2.WMASK // Function call target : x86_64_v2.callInMask(tfp(),i); // Normal argument } - @Override public RegMask outregmap() { return x86_64_v2.RET_MASK; } + @Override public RegMask outregmap() { return null; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + bytes.write(0xe8); + int beforeSize = bytes.size(); + // address + //TODO: relocs + bytes.write(0x00); + return bytes.size() - beforeSize; } @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(fptr())); + sb.p(code.reg(fptr())).p(" "); + for( int i=0; i<nargs(); i++ ) + sb.p(code.reg(arg(i))).p(" "); + sb.unchar(2); } @Override public String op() { return "callr"; } - } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java index b3e3bbe8..e2f68c1f 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java @@ -1,11 +1,13 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.CallNode; +import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.type.TypeFunPtr; import java.io.ByteArrayOutputStream; -import java.util.BitSet; public class CallX86 extends CallNode implements MachNode { final TypeFunPtr _tfp; @@ -20,20 +22,30 @@ public class CallX86 extends CallNode implements MachNode { @Override public String label() { return op(); } @Override public RegMask regmap(int i) { - return x86_64_v2.callInMask(tfp(),i); // Normal argument + return x86_64_v2.callInMask(_tfp,i); // Normal argument } - @Override public RegMask outregmap() { return x86_64_v2.RET_MASK; } + @Override public RegMask outregmap() { return null; } + @Override public int nargs() { return nIns()-2; } // Minus control, memory, fptr @Override public String name() { return _name; } @Override public TypeFunPtr tfp() { return _tfp; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + // linker will fix this up + bytes.write(0xe8); + int beforeSize = bytes.size(); + // TODO: relocs + // address + bytes.write(0x00); + return bytes.size() - beforeSize; } @Override public void asm(CodeGen code, SB sb) { - sb.p(_name); + sb.p(_name).p(" "); + for( int i=0; i<nargs(); i++ ) + sb.p(code.reg(in(i+2))).p(" "); + sb.unchar(2); } @Override public String op() { return "call"; } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CastX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CastX86.java new file mode 100644 index 00000000..b620b89c --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CastX86.java @@ -0,0 +1,18 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.CastNode; +import com.seaofnodes.simple.node.MachNode; +import java.io.ByteArrayOutputStream; + +public class CastX86 extends CastNode implements MachNode { + CastX86( CastNode cast ) { super(cast); } + @Override public RegMask regmap(int i) { assert i==1; return RegMask.FULL; } + @Override public RegMask outregmap() { return RegMask.FULL; } + @Override public int twoAddress( ) { return 1; } + @Override public int encoding(ByteArrayOutputStream bytes) { return 0; /*no encoding*/ } + @Override public void asm(CodeGen code, SB sb) { _t.print(sb.p(code.reg(in(1))).p(" isa ")); } + @Override public String op() { return label(); } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java new file mode 100644 index 00000000..e9b0dacd --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java @@ -0,0 +1,29 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class CmpFX86 extends MachConcreteNode implements MachNode { + CmpFX86( Node cmp ) { super(cmp); } + + @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; } + @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form: "cmp src1,src2" + @Override public void asm(CodeGen code, SB sb) { + String dst = code.reg(this); + if( dst!="flags" ) sb.p(dst).p(" = "); + sb.p(code.reg(in(1))).p(", ").p(code.reg(in(2))); + } + + @Override public String op() { return "cmpf"; } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java index ea526f33..fcba397d 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java @@ -1,12 +1,14 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; -// Compare immediate. Sets flags.(RFLAGS) +// Compare immediate. Sets flags. public class CmpIX86 extends MachConcreteNode implements MachNode { final int _imm; final String _bop; @@ -17,14 +19,22 @@ public class CmpIX86 extends MachConcreteNode implements MachNode { _imm = (int)ti.value(); assert _imm == ti.value(); } - CmpIX86( Node cmp, double ignore ) { + // Copy constructor does not set reverse edges in, because the basic block + // is changing from the original node - and it must be properly placed in a + // new block. + CmpIX86( CmpIX86 cmp ) { super(cmp); - _bop = "=="; - _imm = 0; + _bop = cmp._bop; + _imm = cmp._imm; + // While in(0) will be handled by the caller, all other edges must be + // handled in the constructor. + cmp.in(1)._outputs.push(this); } @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.RMASK; } @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; } + @Override public boolean isClone() { return true; } + @Override public Node copy() { return new CmpIX86(this); } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { @@ -33,7 +43,7 @@ public class CmpIX86 extends MachConcreteNode implements MachNode { @Override public void asm(CodeGen code, SB sb) { String dst = code.reg(this); - if( dst!="FLAGS" ) sb.p(dst).p(" = "); + if( dst!="flags" ) sb.p(dst).p(" = "); sb.p(code.reg(in(1))); if( _imm != 0 ) sb.p(", #").p(_imm); } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java index 873ca9df..f9ace702 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java @@ -1,8 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.CodeGen; -import com.seaofnodes.simple.RegMask; import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; public class CmpMemX86 extends MemOpX86 { diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java index d8ebb12d..41ce33d2 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java @@ -1,11 +1,12 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; -// Compare. Sets flags.(RFLAGS) public class CmpX86 extends MachConcreteNode implements MachNode { CmpX86( Node cmp ) { super(cmp); } @@ -17,7 +18,7 @@ public class CmpX86 extends MachConcreteNode implements MachNode { throw Utils.TODO(); } - // General form: "cmp reg1, reg2" + // General form: "add dst += src" @Override public void asm(CodeGen code, SB sb) { String dst = code.reg(this); if( dst!="FLAGS" ) sb.p(dst).p(" = "); diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java index d73bfd5b..f74b7348 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java index 1dc20017..54372629 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; @@ -10,12 +12,13 @@ public class DivX86 extends MachConcreteNode implements MachNode { // Register mask allowed on input i. @Override public RegMask regmap(int i) { - assert i==1 || i==2; - return x86_64_v2.RMASK; } + return (i==1) ? x86_64_v2.RAX_MASK : x86_64_v2.RMASK; + } // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - // Output is same register as input#1 - @Override public int twoAddress() { return 1; } + @Override public RegMask outregmap() { return x86_64_v2.RAX_MASK; } + + // DIV encodes a CQO which sign extends RAX into RDX, killing RDX. + @Override public RegMask killmap() { return x86_64_v2.RDX_MASK; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { @@ -28,4 +31,5 @@ public class DivX86 extends MachConcreteNode implements MachNode { } @Override public String op() { return "div"; } + @Override public String comment() { return "kill rdx"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java index 33a77d4c..49d666b7 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; @@ -11,6 +13,7 @@ public class F8ToIX86 extends MachConcreteNode implements MachNode { // Register mask allowed on input i. @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.XMASK; } // Register mask allowed as a result. 0 for no register. + @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; } @Override public RegMask outregmap() { return x86_64_v2.WMASK; } // Encoding is appended into the byte array; size is returned diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java index 5e1190da..47f7149d 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java @@ -1,21 +1,23 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.CodeGen; -import com.seaofnodes.simple.RegMask; import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.ConstantNode; import com.seaofnodes.simple.node.MachNode; - import java.io.ByteArrayOutputStream; public class FltX86 extends ConstantNode implements MachNode { FltX86(ConstantNode con ) { super(con); } + @Override public String op() { return "fld"; } // Register mask allowed on input i. 0 for no register. @Override public RegMask regmap(int i) { return null; } // General int registers @Override public RegMask outregmap() { return x86_64_v2.XMASK; } + @Override public boolean isClone() { return true; } + @Override public FltX86 copy() { return new FltX86(this); } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { @@ -30,7 +32,4 @@ public class FltX86 extends ConstantNode implements MachNode { _con.print(sb.p(code.reg(this)).p(" #")); } - @Override public String op() { - return "fld"; // Some fancier encoding - } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java index 8b0f016a..7346ae05 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java @@ -1,6 +1,7 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.RegMask; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.node.FunNode; import com.seaofnodes.simple.node.MachNode; @@ -16,6 +17,6 @@ public class FunX86 extends FunNode implements MachNode { // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + return 0; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java index 983f817d..d99c92a8 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java index 566a7293..fcd39e3f 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java @@ -1,8 +1,11 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.ConstantNode; import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; @@ -10,9 +13,10 @@ import java.io.ByteArrayOutputStream; // Integer constants public class IntX86 extends ConstantNode implements MachNode { + IntX86( ConstantNode con ) { super(con); } - IntX86( ConstantNode con ) { - super(con); + @Override public String op() { + return _con == Type.NIL || _con == TypeInteger.ZERO ? "xor" : "ldi"; } // Register mask allowed on input i. 0 for no register. @@ -20,6 +24,14 @@ public class IntX86 extends ConstantNode implements MachNode { // General int registers @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + // Zero-set uses XOR kills flags + @Override public RegMask killmap() { + return _con == Type.NIL || _con == TypeInteger.ZERO ? x86_64_v2.FLAGS_MASK : null; + } + + @Override public boolean isClone() { return true; } + @Override public Node copy() { return new IntX86(this); } + // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { throw Utils.TODO(); @@ -34,12 +46,6 @@ public class IntX86 extends ConstantNode implements MachNode { if( _con == Type.NIL || _con == TypeInteger.ZERO ) sb.p(reg).p(",").p(reg); else - _con.print(sb.p(reg).p(" #")); - } - - @Override public String op() { - if( _con == Type.NIL || _con == TypeInteger.ZERO ) - return "xor"; - return "ldi"; // Some fancier encoding + _con.print(sb.p(reg).p(" = #")); } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java index 2262e847..173f9ee7 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; @@ -20,7 +22,7 @@ public class JmpX86 extends IfNode implements MachNode { Node set = in(1); Node cmp = set.in(1); // Bypass an expected Set and just reference the cmp directly - if( set instanceof SetX86 && (cmp instanceof CmpX86 || cmp instanceof CmpIX86 || cmp instanceof CmpMemX86) ) + if( set instanceof SetX86 && (cmp instanceof CmpX86 || cmp instanceof CmpIX86 || cmp instanceof CmpMemX86 || cmp instanceof CmpFX86) ) _inputs.set(1,cmp); else throw Utils.TODO(); @@ -35,7 +37,7 @@ public class JmpX86 extends IfNode implements MachNode { @Override public void asm(CodeGen code, SB sb) { String src = code.reg(in(1)); - if( src!="FLAGS" ) sb.p(src); + if( src!="flags" ) sb.p(src); } @Override public String op() { return "j"+_bop; } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java index a49b8d7f..229c0151 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java index 02a9a4fe..893772a6 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java @@ -1,8 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.CodeGen; -import com.seaofnodes.simple.RegMask; import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.LoadNode; import com.seaofnodes.simple.node.Node; @@ -14,7 +14,7 @@ public class LoadX86 extends MemOpX86 { @Override public String op() { return "ld"+_sz; } // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + @Override public RegMask outregmap() { return x86_64_v2.MEM_MASK; } // General form: "ldN dst,[base + idx<<2 + 12]" diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java index a6cdbd05..6d39a2b4 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java @@ -1,8 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.CodeGen; -import com.seaofnodes.simple.RegMask; import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; public class MemAddX86 extends MemOpX86 { diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java index 5a79fb09..e74de677 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java @@ -3,6 +3,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; import java.io.ByteArrayOutputStream; @@ -50,9 +52,11 @@ public abstract class MemOpX86 extends MemOpNode implements MachNode { } Node idx() { return in(3); } - Node val() { return in(4); } + Node val() { return in(4); } // Only for stores, including op-to-memory - @Override protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) { return p.p(".").p(_name); } + @Override protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) { + return p.p(".").p(_name); + } @Override public boolean isMem() { return _type instanceof TypeMem; } @@ -62,14 +66,12 @@ public abstract class MemOpX86 extends MemOpNode implements MachNode { // Register mask allowed on input i. @Override public RegMask regmap(int i) { - if( i==1 ) return null; // Memory - if( i==2 ) return x86_64_v2.RMASK; // base - if( i==3 ) return x86_64_v2.RMASK; // index - if( i==4 ) return x86_64_v2.RMASK; // value - return null; // Trailing anti-dependence edges + if( i==1 ) return null; // Memory + if( i==2 ) return x86_64_v2.RMASK; // base in GPR + if( i==3 ) return x86_64_v2.RMASK; // index in GPR + if( i==4 ) return _sz >= '4' ? x86_64_v2.MEM_MASK : x86_64_v2.RMASK; // Narrow stores need GPRs + return null; // Trailing anti-dependence edges } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { throw Utils.TODO(); } @Override public int encoding(ByteArrayOutputStream bytes) { throw Utils.TODO(); } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java index aef813e1..9e996130 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeFloat; import com.seaofnodes.simple.type.TypeInteger; @@ -15,6 +17,8 @@ public class MulFX86 extends MachConcreteNode implements MachNode { @Override public RegMask outregmap() { return x86_64_v2.XMASK; } // Output is same register as input#1 @Override public int twoAddress() { return 1; } + // Ok to swap arguments + @Override public boolean commutes() { return true; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java index e9eb17fc..b41a5720 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java @@ -1,7 +1,12 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; @@ -12,7 +17,7 @@ public class MulIX86 extends MachConcreteNode implements MachNode { // Register mask allowed on input i. // This is the normal calling convention @Override public RegMask regmap(int i) { - assert i==1; + // assert i==1; return x86_64_v2.WMASK; } // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return x86_64_v2.WMASK; } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java index 15fd9496..d880208e 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java @@ -1,12 +1,13 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; public class MulX86 extends MachConcreteNode implements MachNode { - MulX86( Node mul ) { super(mul); } // Register mask allowed on input i. @@ -15,6 +16,8 @@ public class MulX86 extends MachConcreteNode implements MachNode { @Override public RegMask outregmap() { return x86_64_v2.WMASK; } // Output is same register as input#1 @Override public int twoAddress() { return 1; } + // Ok to swap arguments + @Override public boolean commutes() { return true; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java index c70e1c54..f4803b2e 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java @@ -1,30 +1,19 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import java.io.ByteArrayOutputStream; public class NewX86 extends NewNode implements MachNode { - // A pre-zeroed chunk of memory. - NewX86( NewNode nnn ) { super(nnn); } - - // Register mask allowed on input i, the size - @Override public RegMask regmap(int i) { - // Size - if( i==2 ) return x86_64_v2.RDI_MASK; - // Memory has no register - return null; - } - @Override public RegMask outregmap() { throw Utils.TODO(); } - - // Register mask allowed as a result. Pointer result in standard calling - // convention. - @Override public RegMask outregmap(int i) { - if( i == 0 ) return x86_64_v2.RET_MASK; - // Memory has no register - return null; - } + NewX86( NewNode nnn ) { super(nnn); } + // Size and pointer result in standard calling convention; null for all the + // memory and ordering edges + @Override public RegMask regmap(int i) { return i == 2 ? x86_64_v2.RDI_MASK : null; } + @Override public RegMask outregmap(int i) { return i == 0 ? x86_64_v2.RAX_MASK : null; } + @Override public RegMask outregmap() { return null; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java new file mode 100644 index 00000000..a4b82de8 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java @@ -0,0 +1,26 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.LRG; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import java.io.ByteArrayOutputStream; + +public class NotX86 extends MachConcreteNode implements MachNode { + NotX86(NotNode not) {super(not);} + @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } + @Override public RegMask outregmap() { return x86_64_v2.RMASK; } + + @Override public int twoAddress( ) { return 0; } + + // NotNode(BoolNode) = Inverted BoolNode(1) + // NotNode(NotNode(BoolNode)) = BoolNode(2) + // anything else to test & setz(3) + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); } + @Override public String op() { return "not"; } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java index d3940838..949ac6c2 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java @@ -1,14 +1,13 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.CodeGen; -import com.seaofnodes.simple.RegMask; import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.MachConcreteNode; import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.type.TypeInteger; - import java.io.ByteArrayOutputStream; public class OrIX86 extends MachConcreteNode implements MachNode { @@ -19,7 +18,7 @@ public class OrIX86 extends MachConcreteNode implements MachNode { // Register mask allowed on input i. // This is the normal calling convention @Override public RegMask regmap(int i) { - assert i==1; + //assert i==1; return x86_64_v2.WMASK; } // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return x86_64_v2.WMASK; } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java index 7c25bdae..6d99fe3b 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import java.io.ByteArrayOutputStream; @@ -24,7 +26,7 @@ public class OrX86 extends MachConcreteNode implements MachNode{ } // General form - // General form: "and dst & #imm" + // General form: "or dst | src" @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))); } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java index 0fcc922d..69ebb74e 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java @@ -1,23 +1,27 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.RegMask; -import com.seaofnodes.simple.node.ParmNode; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.ParmNode; import java.io.ByteArrayOutputStream; public class ParmX86 extends ParmNode implements MachNode { final RegMask _rmask; ParmX86( ParmNode parm ) { super(parm); - // default to int _rmask = x86_64_v2.callInMask(fun().sig(),_idx); } // Register mask allowed on input i. 0 for no register. - @Override public RegMask regmap(int i) { return null; } + @Override public RegMask regmap(int i) { return _rmask; } // Register mask allowed as a result. Calling convention register @Override public RegMask outregmap() { return _rmask; } // Encoding is appended into the byte array. Returns size - @Override public int encoding(ByteArrayOutputStream bytes) { return 0; } + @Override public int encoding(ByteArrayOutputStream bytes) { + return 0; + } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java index 77fd7d34..7ac014e6 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java @@ -1,14 +1,12 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.RegMask; -import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.node.ProjNode; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.ProjNode; import java.io.ByteArrayOutputStream; public class ProjX86 extends ProjNode implements MachNode { - ProjX86( ProjNode p ) { super(p); } // Register mask allowed on input i. 0 for no register. @@ -20,6 +18,6 @@ public class ProjX86 extends ProjNode implements MachNode { // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + return 0; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java index 21db209c..433f64cc 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java @@ -1,9 +1,12 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.FunNode; import com.seaofnodes.simple.node.ReturnNode; import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.type.TypeFloat; import java.io.ByteArrayOutputStream; // Return @@ -19,29 +22,27 @@ public class RetX86 extends ReturnNode implements MachNode { // Register mask allowed on input i. // This is the normal calling convention - @Override public RegMask regmap(int i) { - return switch( i ) { - case 0 -> null; - case 1 -> null; - case 2 -> x86_64_v2.RET_MASK; - case 3 -> null; // RPC is always on stack - default -> throw Utils.TODO(); - }; - } + @Override public RegMask regmap(int i) { return x86_64_v2.retMask(_fun.sig(),i); } // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return null; } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + // Without imm form + // Near return + int beforeSize = bytes.size(); + bytes.write(0xC3); // opcode + + return bytes.size() - beforeSize; } - // Human-readable form appended to the SB. Things like the encoding, - // indentation, leading address or block labels not printed here. - // Just something like "ld4\tR17=[R18+12] // Load array base". - // General form: "op\tdst=src+src" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(in(2))); + // Post code-gen, just print the "ret" + if( code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() ) + // Prints return reg (either RAX or XMM0), RPC (always [rsp-4]) and + // then the callee-save registers. + for( int i=2; i<nIns(); i++ ) + sb.p(code.reg(in(i))).p(" "); } @Override public String op() { return "ret"; } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java index f5337ee8..9f942cc6 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java index cb0b18bf..ee7a2f56 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; @@ -8,19 +10,18 @@ import java.io.ByteArrayOutputStream; import java.util.BitSet; import java.lang.StringBuilder; -public class SarX86 extends MachConcreteNode implements MachNode{ - SarX86(Node sar) {super(sar);} + +public class SarX86 extends MachConcreteNode implements MachNode { + SarX86(Node sar) { super(sar); } // Register mask allowed on input i. // This is the normal calling convention - - // CL register must be used for input(2) @Override public RegMask regmap(int i) { if(i == 1) return x86_64_v2.WMASK; if(i == 2) return x86_64_v2.RCX_MASK; throw Utils.TODO(); } - + // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return x86_64_v2.WMASK; } // Output is same register as input#1 @@ -32,11 +33,9 @@ public class SarX86 extends MachConcreteNode implements MachNode{ } // General form - // General form: "sar dst >> src" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))); + sb.p(code.reg(this)).p(" >> ").p(code.reg(in(1))); } @Override public String op() { return "sar"; } - } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java index 25367e03..53237538 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; @@ -31,7 +33,7 @@ public class SetX86 extends MachConcreteNode implements MachNode { @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); String src = code.reg(in(1)); - if( src!="FLAGS" ) sb.p(" = ").p(src); + if( src!="flags" ) sb.p(" = ").p(src); } @Override public String op() { return "set"+_bop; } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java index 61e0d262..c18007cd 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java index b95f9f46..a3de5347 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java @@ -1,13 +1,13 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import java.io.ByteArrayOutputStream; public class ShlX86 extends MachConcreteNode implements MachNode { - ShlX86(Node shl) { - super(shl); - }; + ShlX86(Node shl) { super(shl); }; // Register mask allowed on input i. // This is the normal calling convention @@ -32,7 +32,7 @@ public class ShlX86 extends MachConcreteNode implements MachNode { // General form // General form: "shl dst << src" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))); + sb.p(code.reg(this)).p(" << ").p(code.reg(in(1))); } @Override public String op() { return "shl"; } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java index 80115ff2..d17ee734 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; @@ -8,7 +10,6 @@ import java.io.ByteArrayOutputStream; import java.util.BitSet; import java.lang.StringBuilder; -// Logical Right Shift public class ShrIX86 extends MachConcreteNode implements MachNode{ final TypeInteger _ti; ShrIX86(Node shr, TypeInteger ti) {super(shr); _inputs.pop(); _ti = ti;} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java index 43fc730b..757d9b8e 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import java.io.ByteArrayOutputStream; @@ -31,7 +33,7 @@ public class ShrX86 extends MachConcreteNode implements MachNode { // General form // General form: "shr dst >>> src" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))); + sb.p(code.reg(this)).p(" >>> ").p(code.reg(in(1))); } @Override public String op() { return "shr"; } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java new file mode 100644 index 00000000..ea283a87 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java @@ -0,0 +1,21 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.SplitNode; +import java.io.ByteArrayOutputStream; + +public class SplitX86 extends SplitNode { + SplitX86( String kind, byte round ) { super(kind,round, new Node[2]); } + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { return x86_64_v2.SPLIT_MASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return x86_64_v2.SPLIT_MASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java index 76bd2afb..08d481fa 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java @@ -2,9 +2,9 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.print.ExprPrinter; -import com.seaofnodes.simple.CodeGen; -import com.seaofnodes.simple.RegMask; import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.node.StoreNode; @@ -16,7 +16,11 @@ public class StoreX86 extends MemOpX86 { @Override public String op() { return "st"+_sz; } @Override protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) { - return p.p(".").p(_name).p("=").p(val()==null ? _imm : val()).p(";"); + Node val = val(); + p.p(".").p(_name).p("="); + if( val==null ) p.p(_imm); + else p.n(val); + return p.p(";"); } // Register mask allowed as a result. 0 for no register. diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java index ed19ad79..ceaf0636 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import java.io.ByteArrayOutputStream; diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java index cac59d3b..da44a838 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import java.io.ByteArrayOutputStream; diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java index c7edfcc4..2b4de2ca 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.ConstantNode; import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.type.Type; @@ -11,10 +13,13 @@ import java.io.ByteArrayOutputStream; // Function constants public class TFPX86 extends ConstantNode implements MachNode { TFPX86( ConstantNode con ) { super(con); } + @Override public String op() { return _con == Type.NIL ? "xor" : "ldx"; } // Register mask allowed on input i. 0 for no register. @Override public RegMask regmap(int i) { return null; } // General int registers @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + @Override public boolean isClone() { return true; } + @Override public TFPX86 copy() { return new TFPX86(this); } // Encoding is appended into the byte array; size is returned @Override public int encoding(ByteArrayOutputStream bytes) { @@ -32,10 +37,4 @@ public class TFPX86 extends ConstantNode implements MachNode { else _con.print(sb.p(reg).p(" #")); } - - @Override public String op() { - if( _con == Type.NIL ) - return "xor"; - return "ldx"; // Some fancier encoding - } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java index d71a49dc..47c6be73 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java @@ -1,17 +1,16 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.CodeGen; -import com.seaofnodes.simple.RegMask; import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.MachConcreteNode; import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.type.TypeInteger; - import java.io.ByteArrayOutputStream; -public class XorIX86 extends MachConcreteNode implements MachNode { +public class XorIX86 extends MachConcreteNode implements MachNode { final TypeInteger _ti; XorIX86(Node xor, TypeInteger ti) {super(xor); _inputs.pop(); _ti = ti;} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java new file mode 100644 index 00000000..3322d951 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java @@ -0,0 +1,36 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.LRG; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class XorX86 extends MachConcreteNode implements MachNode{ + XorX86(Node xor) {super(xor);} + + @Override public RegMask regmap(int i) { return x86_64_v2.WMASK; } + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + + // Output is same register as input#1 + @Override public int twoAddress() { return 1; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + // General form + // General form: "xor ^ reg + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ^ ").p(code.reg(in(2))); + } + + @Override public String op() { return "xor"; } + +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java index e7e0b874..c66aed9f 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -1,9 +1,10 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.CodeGen; -import com.seaofnodes.simple.Machine; -import com.seaofnodes.simple.RegMask; import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.LRG; +import com.seaofnodes.simple.codegen.Machine; +import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; @@ -11,119 +12,66 @@ public class x86_64_v2 extends Machine { // X86-64 V2. Includes e.g. SSE4.2 and POPCNT. @Override public String name() { return "x86_64_v2"; } - public static int RAX = 0, RCX = 1, RDX = 2, RBX = 3, RSP = 4, RBP = 5, RSI = 6, RDI = 7; - public static int R08 = 8, R09 = 9, R10 = 10, R11 = 11, R12 = 12, R13 = 13, R14 = 14, R15 = 15; - public static int FLAGS = 32; + static final int RAX = 0, RCX = 1, RDX = 2, RBX = 3, RSP = 4, RBP = 5, RSI = 6, RDI = 7; + static final int R08 = 8, R09 = 9, R10 = 10, R11 = 11, R12 = 12, R13 = 13, R14 = 14, R15 = 15; - public static int XMM0 = 16, XMM1 = 17, XMM2 = 18, XMM3 = 19, XMM4 = 20, XMM5 = 21, XMM6 = 22, XMM7 = 23; - public static int XMM8 = 24, XMM9 = 25, XMM10 = 26, XMM11 = 27, XMM12 = 28, XMM13 = 29, XMM14 = 30, XMM15 = 31; + static final int XMM0 = 16, XMM1 = 17, XMM2 = 18, XMM3 = 19, XMM4 = 20, XMM5 = 21, XMM6 = 22, XMM7 = 23; + static final int XMM8 = 24, XMM9 = 25, XMM10 = 26, XMM11 = 27, XMM12 = 28, XMM13 = 29, XMM14 = 30, XMM15 = 31; + static final int FLAGS = 32; + static final int MAX_REG = 33; + static final int RPC = 33; // General purpose register mask: pointers and ints, not floats - public static RegMask RMASK = new RegMask(0b1111111111111111); + static final long RD_BITS = 0b1111111111111111L; // All the GPRs + static RegMask RMASK = new RegMask(RD_BITS); // No RSP in the *write* general set. - public static RegMask WMASK = new RegMask(0b1111111111101111); + static final long WR_BITS = 0b1111111111101111L; // All the GPRs minus RSP + static RegMask WMASK = new RegMask(WR_BITS); // Xmm register mask - public static RegMask XMASK = new RegMask( 0b1111111111111111L << XMM0); - - public static RegMask FLAGS_MASK = new RegMask(1L<<FLAGS); - - // Return single int/ptr register - public static RegMask RET_MASK = new RegMask(1<<RAX); - public static RegMask RET_FMASK = new RegMask(1<<XMM0); - - public static RegMask RDI_MASK = new RegMask(1L<<RDI); - public static RegMask RCX_MASK = new RegMask(1L<<RCX); - public static RegMask RDX_MASK = new RegMask(1L<<RDX); - public static RegMask R08_MASK = new RegMask(1L<<R08); - public static RegMask R09_MASK = new RegMask(1L<<R09); - public static RegMask RSI_MASK = new RegMask(1L<<RSI); - - public static RegMask XMM0_MASK = new RegMask(1L<<XMM0); - public static RegMask XMM1_MASK = new RegMask(1L<<XMM1); - public static RegMask XMM2_MASK = new RegMask(1L<<XMM2); - public static RegMask XMM3_MASK = new RegMask(1L<<XMM3); - public static RegMask XMM4_MASK = new RegMask(1L<<XMM4); - public static RegMask XMM5_MASK = new RegMask(1L<<XMM5); - public static RegMask XMM6_MASK = new RegMask(1L<<XMM6); - public static RegMask XMM7_MASK = new RegMask(1L<<XMM7); - public static RegMask[] XMMS = new RegMask[]{ - XMM0_MASK, XMM1_MASK, XMM2_MASK, XMM3_MASK, - XMM4_MASK, XMM5_MASK, XMM6_MASK, XMM7_MASK, - }; + static final long FP_BITS = 0b1111111111111111L << XMM0; // All the XMMs + static final RegMask XMASK = new RegMask(FP_BITS); + static final RegMask FLAGS_MASK = new RegMask(FLAGS); + static final RegMask RPC_MASK = new RegMask(RPC); + + static final long SPILLS = -(1L << MAX_REG); + static final RegMask SPLIT_MASK = new RegMask(WR_BITS | FP_BITS | (1L<<FLAGS) | SPILLS, -1L ); + + // Load/store mask; both GPR and FPR + static RegMask MEM_MASK = new RegMask(WR_BITS | FP_BITS); - // Calling conv metadata - public int GPR_COUNT_CONV_WIN64 = 4; // RCX, RDX, R9, R9 - public int XMM_COUNT_CONV_WIN64 = 4; // XMM0L, XMM1L, XMM2L, XMM3L + static final RegMask RAX_MASK = new RegMask(RAX); + static final RegMask RCX_MASK = new RegMask(RCX); + static final RegMask RDX_MASK = new RegMask(RDX); + static final RegMask RDI_MASK = new RegMask(RDI); + static final RegMask R08_MASK = new RegMask(R08); + static final RegMask R09_MASK = new RegMask(R09); + static final RegMask RSI_MASK = new RegMask(RSI); + + static final RegMask XMM0_MASK = new RegMask(XMM0); - public int GPR_COUNT_CONV_SYSTEM_V = 6; // RDI, RSI, RDX, RCX, R8, R9 - public int XMM_COUNT_CONV_SYSTEM_V = 4; // XMM0, XMM1, XMM2, XMM3 .... // Human-readable name for a register number, e.g. "RAX". // Hard crash for bad register number, fix yer bugs! - public static final String[] REGS = new String[] { + static final String[] REGS = new String[] { "rax" , "rcx" , "rdx" , "rbx" , "rsp" , "rbp" , "rsi" , "rdi" , "r8" , "r9" , "r10" , "r11" , "r12" , "r13" , "r14" , "r15" , "xmm0", "xmm1", "xmm2" , "xmm3" , "xmm4" , "xmm5" , "xmm6" , "xmm7" , "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15", - "flags", + "flags","[rsp-4]", }; @Override public String reg( int reg ) { return reg < REGS.length ? REGS[reg] : "[rsp+"+(reg-REGS.length)*4+"]"; } - // Calling convention; returns a machine-specific register - // for incoming argument idx. - // index 0 for control, 1 for memory, real args start at index 2 - static int callInArg( TypeFunPtr tfp, int idx ) { - int[] cargs = switch( CodeGen.CODE._callingConv ) { - case CodeGen.CallingConv.SystemV -> CALLINARG_SYSTEMV_INT; - case CodeGen.CallingConv.Win64 -> CALLINARG_WIN64_INT; - }; - if( idx < 2 ) return -1; - if( idx >= cargs.length ) - throw Utils.TODO(); // Pass on stack slot - if( tfp.arg(idx-2) instanceof TypeFloat ) - return XMM0 + (idx-2); - return cargs[idx]; - } - // WIN64(param passing) - static int[] CALLINARG_WIN64_INT = new int[] { - -1, // Control, no register - -1, // Memory, no register - RCX, - RDX, - R08, - R09, - }; - static RegMask[] CALLINARG_WIN64_MASK = new RegMask[] { - null, // Control, no register - null, // Memory, no register + static RegMask[] CALLINMASK_WIN64 = new RegMask[] { RCX_MASK, RDX_MASK, R08_MASK, R09_MASK, }; - // caller saved(win64) - public static final long WIN64_ABI_CALLER_SAVED = - (1L << RAX) | (1L << RCX) | (1L << RDX) | (1L << R08) | (1L << R09) | (1L << R10) | (1L << R11); - - // callee saved(win64) - public static final long WIN64_ABI_CALLEE_SAVED = ~WIN64_ABI_CALLER_SAVED; - - static int[] CALLINARG_SYSTEMV_INT = new int[] { - -1, // Control, no register - -1, // Memory, no register - RDI, - RSI, - RDX, - RCX, - R08, - R09, - }; - static RegMask[] CALLINARG_SYSTEMV_MASK = new RegMask[] { - null, // Control, no register - null, // Memory, no register + // SystemV(param passing) + static RegMask[] CALLINMASK_SYSTEMV = new RegMask[] { RDI_MASK, RSI_MASK, RDX_MASK, @@ -131,32 +79,128 @@ public class x86_64_v2 extends Machine { R08_MASK, R09_MASK, }; + + // Limit of float args passed in registers + static RegMask[] XMMS4 = new RegMask[]{ + new RegMask(XMM0), new RegMask(XMM1), new RegMask(XMM2), new RegMask(XMM3), + }; + + // Map from function signature and argument index to register. + // Used to set input registers to CallNodes, and ParmNode outputs. + static RegMask callInMask( TypeFunPtr tfp, int idx ) { + if( idx==0 ) return RPC_MASK; + if( idx==1 ) return null; + // Count floats in signature up to index + int fcnt=0; + for( int i=2; i<idx; i++ ) + if( tfp.arg(i-2) instanceof TypeFloat ) + fcnt++; + // Floats up to XMMS in XMM registers + if( tfp.arg(idx-2) instanceof TypeFloat ) { + if( fcnt < XMMS4.length ) + return XMMS4[fcnt]; + } else { + RegMask[] cargs = switch( CodeGen.CODE._callingConv ) { + case "SystemV" -> CALLINMASK_SYSTEMV; + case "Win64" -> CALLINMASK_WIN64; + default -> throw new IllegalArgumentException("Unknown calling convention: "+CodeGen.CODE._callingConv); + }; + if( idx-2-fcnt < cargs.length ) + return cargs[idx-2-fcnt]; + } + throw Utils.TODO(); // Pass on stack slot + } + // caller saved(systemv) + static final long SYSTEM5_CALLER_SAVE = + (1L<< RAX) | (1L<< RCX) | (1L<< RDX) | + (1L<< RDI) | (1L<< RSI) | + (1L<< R08) | (1L<< R09) | (1L<< R10) | (1L<< R11) | + (1L<<FLAGS)| // Flags are killed + // All FP regs are killed + FP_BITS; + static final RegMask SYSTEM5_CALLER_SAVE_MASK = new RegMask(SYSTEM5_CALLER_SAVE); + // caller saved(win64) - public static final long SYSTEMV_ABI_CALLER_SAVED = - (1L << RAX) | (1L << RDI) | (1L << RSI) | (1L << RCX) | (1L << RDX) | (1L << R08) | (1L << R09) << (1L << R10) << (1L << R11); - // callee saved(systemv) - public static final long SYSTEMV_ABI_CALLE_SAVED = ~SYSTEMV_ABI_CALLER_SAVED; + static final long WIN64_CALLER_SAVE = + (1L<< RAX) | (1L<< RCX) | (1L<< RDX) | + (1L<< R08) | (1L<< R09) | (1L<< R10) | (1L<< R11) | + (1L<<FLAGS)| // Flags are killed + // Only XMM0-XMM5 are killed; XMM6-XMM15 are preserved + (1L<<XMM0) | (1L<<XMM1) | (1L<<XMM2) | (1L<<XMM3) | + (1L<<XMM4) | (1L<<XMM5); + static final RegMask WIN64_CALLER_SAVE_MASK = new RegMask(WIN64_CALLER_SAVE); + + static RegMask x86CallerSave() { + return switch( CodeGen.CODE._callingConv ) { + case "SystemV" -> SYSTEM5_CALLER_SAVE_MASK; + case "Win64" -> WIN64_CALLER_SAVE_MASK; + default -> throw new IllegalArgumentException("Unknown calling convention: "+CodeGen.CODE._callingConv); + }; + } + @Override public RegMask callerSave() { return x86CallerSave(); } + + static final RegMask SYSTEM5_CALLEE_SAVE_MASK; + static final RegMask WIN64_CALLEE_SAVE_MASK; + static { + long callee = ~SYSTEM5_CALLER_SAVE; + // Remove the spills + callee &= (1L<<MAX_REG)-1; + callee &= ~(1L<<FLAGS); + callee &= ~(1L<<RSP); + SYSTEM5_CALLEE_SAVE_MASK = new RegMask(callee); + + callee = ~WIN64_CALLER_SAVE; + // Remove the spills + callee &= (1L<<MAX_REG)-1; + callee &= ~(1L<<FLAGS); + callee &= ~(1L<<RSP); + WIN64_CALLEE_SAVE_MASK = new RegMask(callee); + } + static RegMask x86CalleeSave() { + return switch( CodeGen.CODE._callingConv ) { + case "SystemV" -> SYSTEM5_CALLEE_SAVE_MASK; + case "Win64" -> WIN64_CALLEE_SAVE_MASK; + default -> throw new IllegalArgumentException("Unknown calling convention: "+CodeGen.CODE._callingConv); + }; + } + @Override public RegMask calleeSave() { return x86CalleeSave(); } - static RegMask callInMask( TypeFunPtr tfp, int idx ) { - RegMask[] cargs = switch( CodeGen.CODE._callingConv ) { - case CodeGen.CallingConv.SystemV -> CALLINARG_SYSTEMV_MASK; - case CodeGen.CallingConv.Win64 -> CALLINARG_WIN64_MASK; + static final RegMask[] WIN64_RET_MASKS, SYS5_RET_MASKS; + static { + WIN64_RET_MASKS = makeRetMasks( WIN64_CALLEE_SAVE_MASK); + SYS5_RET_MASKS = makeRetMasks(SYSTEM5_CALLEE_SAVE_MASK); + } + private static RegMask[] makeRetMasks(RegMask mask) { + int nSaves = mask.size(); + RegMask[] masks = new RegMask[4 + nSaves]; + masks[0] = null; + masks[1] = null; // Memory + masks[2] = null; // Varies, either XMM0 or RAX + masks[3] = RPC_MASK; + short reg = mask.firstReg(); + for( int i=0; i<nSaves; i++ ) { + masks[i+4] = new RegMask(reg); + reg = mask.nextReg(reg); + } + return masks; + } + + // Return single int/ptr register. Used by CallEnd output and Return input. + static RegMask retMask( TypeFunPtr tfp, int i ) { + if( i==2 ) + return tfp.ret() instanceof TypeFloat ? XMM0_MASK : RAX_MASK; + RegMask[] masks = switch( CodeGen.CODE._callingConv ) { + case "SystemV" -> SYS5_RET_MASKS; + case "Win64" -> WIN64_RET_MASKS; + default -> throw new IllegalArgumentException("Unknown calling convention: "+CodeGen.CODE._callingConv); }; - if( idx < 2 ) return null; - if( idx >= cargs.length ) - throw Utils.TODO(); // Pass on stack slot - if( tfp.arg(idx-2) instanceof TypeFloat ) - return XMMS[idx-2]; - return cargs[idx]; + return masks[i]; } // Create a split op; any register to any register, including stack slots - @Override - public Node split() { - throw Utils.TODO(); - } + @Override public SplitNode split(String kind, byte round, LRG lrg) { return new SplitX86(kind,round); } // Return a MachNode unconditional branch @Override public CFGNode jump() { @@ -177,6 +221,7 @@ public class x86_64_v2 extends Machine { case BoolNode bool -> cmp(bool); case CallEndNode cend -> new CallEndX86(cend); case CallNode call -> call(call); + case CastNode cast -> new CastX86(cast); case CProjNode c -> new CProjNode(c); case ConstantNode con -> con(con); case DivFNode divf -> new DivFX86(divf); @@ -219,11 +264,12 @@ public class x86_64_v2 extends Machine { private Node add( AddNode add ) { Node lhs = add.in(1); Node rhs = add.in(2); + // The other addend must occupy the result register, even if constant. if( lhs instanceof LoadNode ld && ld.nOuts()==1 ) - return new AddMemX86(add,address(ld),ld.ptr(),idx,off,scale, imm(rhs),val); + return new AddMemX86(add,address(ld),ld.ptr(),idx,off,scale, 0,rhs); if( rhs instanceof LoadNode ld && ld.nOuts()==1 ) - return new AddMemX86(add,address(ld),ld.ptr(),idx,off,scale, imm(lhs),val); + return new AddMemX86(add,address(ld),ld.ptr(),idx,off,scale, 0,lhs); // Attempt a full LEA-style break down. // Returns one of AddX86, AddIX86, LeaX86, or LHS @@ -288,6 +334,10 @@ public class x86_64_v2 extends Machine { return new SetX86(cmp,bool.op()); } private Node _cmp( BoolNode bool ) { + // Float variant + if( bool.isFloat() ) + return new CmpFX86(bool); + Node lhs = bool.in(1); Node rhs = bool.in(2); if( lhs instanceof LoadNode ld && ld.nOuts()==1 ) @@ -296,20 +346,23 @@ public class x86_64_v2 extends Machine { if( rhs instanceof LoadNode ld && ld.nOuts()==1 ) return new CmpMemX86(bool,address(ld),ld.ptr(),idx,off,scale, imm(lhs),val,true); - return rhs instanceof ConstantNode con && con._con instanceof TypeInteger ti - ? new CmpIX86(bool, ti) - : new CmpX86(bool); + // Vs immediate + if( rhs instanceof ConstantNode con && con._con instanceof TypeInteger ti ) + return new CmpIX86(bool, ti); + // x vs y + return new CmpX86(bool); } private Node con( ConstantNode con ) { + if( !con._con.isConstant() ) return new ConstantNode( con ); // Default unknown caller inputs return switch( con._con ) { case TypeInteger ti -> new IntX86(con); case TypeFloat tf -> new FltX86(con); case TypeFunPtr tfp -> new TFPX86(con); - case TypeMemPtr tmp -> new ConstantNode(con); + case TypeMemPtr tmp -> throw Utils.TODO(); case TypeNil tn -> throw Utils.TODO(); // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code. - case Type t -> new ConstantNode(con); + case Type t -> t==Type.NIL ? new IntX86(con) : new ConstantNode(con); }; } @@ -428,6 +481,7 @@ public class x86_64_v2 extends Machine { } return mop; } + private int imm( Node xval ) { assert val==null && imm==0; if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti ) { diff --git a/src/main/java/com/seaofnodes/simple/ASMPrinter.java b/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java index 3c4162ea..a4f768c5 100644 --- a/src/main/java/com/seaofnodes/simple/ASMPrinter.java +++ b/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java @@ -1,9 +1,9 @@ -package com.seaofnodes.simple; +package com.seaofnodes.simple.print; import com.seaofnodes.print.AssemblyAdapter; import com.seaofnodes.print.ASMPrinter.Data; import com.seaofnodes.print.ASMPrinter.Pool; -import com.seaofnodes.simple.CodeGen; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.SB; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; @@ -45,7 +45,9 @@ public final class ASMPrinter extends AssemblyAdapter<Node> { @Override public boolean hidden(Node n) { if( n instanceof PhiNode || n instanceof ProjNode ) if( n._type instanceof TypeMem || n._type instanceof TypeRPC ) return true; + if( postAlloc() && n instanceof CalleeSaveNode ) return true; if( n instanceof MemMergeNode ) return true; return n.getClass()==ConstantNode.class; } + @Override public boolean postAlloc() { return _code._phase.ordinal()>CodeGen.Phase.RegAlloc.ordinal(); } } diff --git a/src/main/java/com/seaofnodes/simple/IRPrinter.java b/src/main/java/com/seaofnodes/simple/print/IRPrinter.java index f0ac717a..e4d719f4 100644 --- a/src/main/java/com/seaofnodes/simple/IRPrinter.java +++ b/src/main/java/com/seaofnodes/simple/print/IRPrinter.java @@ -1,15 +1,16 @@ -package com.seaofnodes.simple; +package com.seaofnodes.simple.print; import com.seaofnodes.print.IRAdapter; import com.seaofnodes.simple.node.*; import java.util.ArrayList; import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.CodeGen; +import com.seaofnodes.simple.codegen.CodeGen; /** This chapter's read-only IR facts and the familiar debugger entry points. */ public final class IRPrinter extends IRAdapter<Node> { private static final com.seaofnodes.print.IRPrinter<Node> PRINT = new com.seaofnodes.print.IRPrinter<>(new IRPrinter()); + @Override public boolean global(Node n) { return n.isConst(); } @Override public String type(Node n) { return n._type==null ? "" : n._type.str(); } @Override public String inputMark(Node n, Node def) { return n instanceof MemMergeNode && def instanceof MemMergeNode ? "^" : " "; } @Override public Kind kind(Node n) { @@ -51,4 +52,10 @@ public final class IRPrinter extends IRAdapter<Node> { units.add(new com.seaofnodes.print.IRPrinter.Unit<>(null,null,null,funs)); return PRINT.program(code._start,code._stop,units); } + public static String _prettyPrint(CodeGen code) { + if( code._cfg==null ) return prettyPrint(code); + var blocks=new ArrayList<Node>(); + for( Node n : code._cfg ) blocks.add(n); + return PRINT.scheduled(blocks); + } } diff --git a/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index 1992a0c0..0357ff61 100644 --- a/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -6,7 +6,7 @@ import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.type.TypeMem; -/** Chapter 19's view of the IR; browser, transport and layout are shared. */ +/** Chapter 20's view of the IR; browser, transport and layout are shared. */ public class SimpleGraphAdapter extends GraphAdapter<Node> { @Override protected String edgeName(Node n, int i) { diff --git a/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java index 4ec55f4d..9dce0b75 100644 --- a/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java +++ b/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java @@ -2,12 +2,12 @@ package com.seaofnodes.simple.print; import com.seaofnodes.graph.GraphCapture; import com.seaofnodes.graph.GraphViewer; -import com.seaofnodes.simple.CodeGen; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.Node; import java.util.ArrayList; import java.io.IOException; -/** Chapter 19's compilation context; capture and event bookkeeping are shared. */ +/** Chapter 20's compilation context; capture and event bookkeeping are shared. */ public class SimpleGraphObserver extends GraphCapture<Node> { private CodeGen _code; public SimpleGraphObserver() { super(new SimpleGraphAdapter()); } diff --git a/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java b/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java index 94352790..609f2443 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java @@ -43,7 +43,8 @@ public class TypeFunPtr extends TypeNil { public static TypeFunPtr BOT = make((byte)3,TypeTuple.BOT,Type.BOTTOM,-1); public static TypeFunPtr TEST = make((byte)2,TypeTuple.TEST,TypeInteger.BOT,1); public static TypeFunPtr TEST0 = make((byte)3,TypeTuple.TEST,TypeInteger.BOT,3); - public static void gather(ArrayList<Type> ts) { ts.add(TEST); ts.add(TEST0); ts.add(BOT); } + public static TypeFunPtr MAIN = make((byte)3,TypeTuple.MAIN,TypeInteger.BOT,-1); + public static void gather(ArrayList<Type> ts) { ts.add(TEST); ts.add(TEST0); ts.add(BOT); ts.add(MAIN); } @Override Type xmeet(Type t) { @@ -68,6 +69,7 @@ public class TypeFunPtr extends TypeNil { public long fidxs() { return _fidxs; } public Type ret() { return _ret; } public int nargs() { return _sig._types.length; } + public int fidx() { assert Long.bitCount(_fidxs)==1; return Long.numberOfTrailingZeros(_fidxs); } @Override int hash() { return Utils.fold(_sig.hashCode() ^ _ret.hashCode() ^ _fidxs ^ super.hash()); } diff --git a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/src/main/java/com/seaofnodes/simple/type/TypeStruct.java index 89c79a1f..dc92e086 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeStruct.java @@ -186,6 +186,7 @@ public class TypeStruct extends Type { if( this==TOP ) return sb.p("StructTop"); sb.p(_name); if( _fields == null ) return sb; // Forward reference struct, just print the name + if( isAry() ) return sb; // Skip printing generic array fields sb.p(" {"); for( Field f : _fields ) f._type.print(sb).p(f._final ? " " : " !").p(f._fname).p("; "); diff --git a/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/src/main/java/com/seaofnodes/simple/type/TypeTuple.java index 6d3a2b69..95e35e99 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeTuple.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeTuple.java @@ -55,6 +55,8 @@ public class TypeTuple extends Type { return make(ts); } + public Type ret() { assert _types.length==3; return _types[2]; } + @Override public String str() { return print(new SB()).toString(); } @Override public SB print(SB sb) { diff --git a/src/test/java/com/seaofnodes/simple/BrainFuckTest.java b/src/test/java/com/seaofnodes/simple/BrainFuckTest.java index 8f09cb3d..c23bb301 100644 --- a/src/test/java/com/seaofnodes/simple/BrainFuckTest.java +++ b/src/test/java/com/seaofnodes/simple/BrainFuckTest.java @@ -1,10 +1,11 @@ package com.seaofnodes.simple; -import com.seaofnodes.simple.evaluator.Evaluator; +import com.seaofnodes.simple.codegen.CodeGen; import java.nio.charset.StandardCharsets; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; +import static com.seaofnodes.simple.Main.PORTS; public class BrainFuckTest { @@ -17,8 +18,7 @@ public class BrainFuckTest { encoded.append("program[").append(i).append("] = ").append(value).append(";"); } - CodeGen code = new CodeGen( - encoded + """ + String src = encoded + """ int d = 0; u8[] !output = new u8[0]; @@ -62,8 +62,10 @@ for( int pc = 0; pc < program#; pc++ ) { } } return output; - """); - code.parse().opto().typeCheck()/*.instSelect("x86_64_v2", "SystemV")*/.GCM().localSched(); - assertEquals("Hello World!\n", Eval2.eval(code, 0, 10000)); +"""; + Chapter20Test.testCPU(src,"x86_64_v2", "SystemV",28,null); + Chapter20Test.testCPU(src,"riscv" , "SystemV",18,null); + Chapter20Test.testCPU(src,"arm" , "SystemV",12,null); + //assertEquals("Hello World!\n", Eval2.eval(code, 0, 10000)); } } diff --git a/src/test/java/com/seaofnodes/simple/Chapter01Test.java b/src/test/java/com/seaofnodes/simple/Chapter01Test.java index 934eda76..cddbdfcf 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter01Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter01Test.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import org.junit.Test; diff --git a/src/test/java/com/seaofnodes/simple/Chapter02Test.java b/src/test/java/com/seaofnodes/simple/Chapter02Test.java index 8b7a2f66..a51ac202 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter02Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter02Test.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import org.junit.Test; diff --git a/src/test/java/com/seaofnodes/simple/Chapter03Test.java b/src/test/java/com/seaofnodes/simple/Chapter03Test.java index 8212d246..8923566a 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter03Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter03Test.java @@ -1,7 +1,7 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import org.junit.Test; - import static org.junit.Assert.*; public class Chapter03Test { diff --git a/src/test/java/com/seaofnodes/simple/Chapter04Test.java b/src/test/java/com/seaofnodes/simple/Chapter04Test.java index 934e794f..e5a16680 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter04Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter04Test.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import org.junit.Test; diff --git a/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/src/test/java/com/seaofnodes/simple/Chapter05Test.java index 7a9414a2..70cc6121 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter05Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter05Test.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.*; import org.junit.Test; diff --git a/src/test/java/com/seaofnodes/simple/Chapter06Test.java b/src/test/java/com/seaofnodes/simple/Chapter06Test.java index 117531f0..24d12921 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter06Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter06Test.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import org.junit.Test; diff --git a/src/test/java/com/seaofnodes/simple/Chapter07Test.java b/src/test/java/com/seaofnodes/simple/Chapter07Test.java index 6d6a3ce7..88554323 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter07Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter07Test.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.*; import org.junit.Test; diff --git a/src/test/java/com/seaofnodes/simple/Chapter08Test.java b/src/test/java/com/seaofnodes/simple/Chapter08Test.java index 350df18b..7e90d484 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter08Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter08Test.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.*; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/com/seaofnodes/simple/Chapter09Test.java b/src/test/java/com/seaofnodes/simple/Chapter09Test.java index 10f4a78b..401f346f 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter09Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter09Test.java @@ -1,6 +1,6 @@ package com.seaofnodes.simple; -import com.seaofnodes.simple.evaluator.Evaluator; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; import org.junit.Assert; diff --git a/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/src/test/java/com/seaofnodes/simple/Chapter10Test.java index bcae373b..91505f41 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import org.junit.Test; import com.seaofnodes.simple.node.*; import java.util.BitSet; diff --git a/src/test/java/com/seaofnodes/simple/Chapter11Test.java b/src/test/java/com/seaofnodes/simple/Chapter11Test.java index 14819869..439333ca 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter11Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter11Test.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; import org.junit.Test; import static org.junit.Assert.*; diff --git a/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/src/test/java/com/seaofnodes/simple/Chapter12Test.java index 90aaf631..4abc9117 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.assertEquals; diff --git a/src/test/java/com/seaofnodes/simple/Chapter13Test.java b/src/test/java/com/seaofnodes/simple/Chapter13Test.java index 584377e4..975946da 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter13Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter13Test.java @@ -1,9 +1,9 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.print.IRPrinter; import org.junit.Test; - - import static org.junit.Assert.assertEquals; public class Chapter13Test { diff --git a/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java index a40b4f04..6377bc14 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java +++ b/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java @@ -1,6 +1,6 @@ package com.seaofnodes.simple; -import com.seaofnodes.simple.evaluator.Evaluator; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.StopNode; import org.junit.Ignore; import org.junit.Test; @@ -84,7 +84,7 @@ while( 1 ) { return guess; """); code.parse().opto(); - assertEquals("return Phi(Loop,(flt)arg,(((ToFloat/Phi_guess)+Phi_guess)/2.0f));", code.print()); + assertEquals("return Phi(Loop,(flt)arg,(((ToFloat/Phi_guess)+Phi_guess)*0.5f));", code.print()); assertEquals("3.0", Eval2.eval(code, 9)); assertEquals("1.414213562373095", Eval2.eval(code, 2)); } diff --git a/src/test/java/com/seaofnodes/simple/Chapter14Test.java b/src/test/java/com/seaofnodes/simple/Chapter14Test.java index 789a2113..f0fe7dcd 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter14Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter14Test.java @@ -1,9 +1,9 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.StopNode; import org.junit.Ignore; import org.junit.Test; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; diff --git a/src/test/java/com/seaofnodes/simple/Chapter15Test.java b/src/test/java/com/seaofnodes/simple/Chapter15Test.java index c38dfb00..6a22ff97 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter15Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter15Test.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.*; @@ -302,18 +303,20 @@ return is[1]; @Test public void testBad3() { - CodeGen code = new CodeGen( -""" + CodeGen code = new CodeGen(""" int[] is = new int[arg]; return is[1]; """); code.parse().opto(); assertEquals("return 0;", code.print()); assertEquals("0", Eval2.eval(code, 4)); - try { Eval2.eval(code,0); } - catch( ArrayIndexOutOfBoundsException e ) { assertEquals("Array index 1 out of bounds for array length 0",e.getMessage()); } - try { Eval2.eval(code,-1); } + try { Eval2.eval(code,-1); fail(); } catch( NegativeArraySizeException e ) { assertEquals("-1",e.getMessage()); } + // This test will not pass (i.e., failed range-check) until we have range checks. + // Without the range check, the load `is[1]` optimizes against the `new int[]` + // and assumes it loads a zero then folds away, and the Eval2 never sees an OOB load. + //try { Eval2.eval(code,0); fail(); } + //catch( ArrayIndexOutOfBoundsException e ) { assertEquals("Array index 1 out of bounds for array length 0",e.getMessage()); } } @Test diff --git a/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/src/test/java/com/seaofnodes/simple/Chapter16Test.java index fd028d57..362388d6 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter16Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter16Test.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.evaluator.Evaluator; import com.seaofnodes.simple.node.StopNode; import org.junit.Ignore; diff --git a/src/test/java/com/seaofnodes/simple/Chapter17aTest.java b/src/test/java/com/seaofnodes/simple/Chapter17aTest.java index ab92d439..16ffe967 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter17aTest.java +++ b/src/test/java/com/seaofnodes/simple/Chapter17aTest.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.evaluator.Evaluator; import com.seaofnodes.simple.node.StopNode; import org.junit.Test; diff --git a/src/test/java/com/seaofnodes/simple/Chapter17bTest.java b/src/test/java/com/seaofnodes/simple/Chapter17bTest.java index 19cc25d9..da619ed3 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter17bTest.java +++ b/src/test/java/com/seaofnodes/simple/Chapter17bTest.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -12,11 +13,9 @@ public class Chapter17bTest { public void testJig() { CodeGen code = new CodeGen( """ -int i = 0; -i=i=1; -return i; +return 1; """); - code.parse().opto(); + code.parse().opto().typeCheck(); assertEquals("return 1;", code.print()); assertEquals("1", Eval2.eval(code, 0)); } diff --git a/src/test/java/com/seaofnodes/simple/Chapter18Test.java b/src/test/java/com/seaofnodes/simple/Chapter18Test.java index f965edd4..dafd2f3a 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter18Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter18Test.java @@ -3,10 +3,11 @@ import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.codegen.CodeGen; +import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.*; import static org.junit.Assert.fail; -import org.junit.Ignore; public class Chapter18Test { @Test public void testNeverExitLoopDepth() { @@ -193,7 +194,7 @@ var sq = { int x -> return sq(arg)+sq(3); """); code.parse().opto().typeCheck().GCM().localSched(); - assertEquals("Stop[ return (sq( 3)+sq( arg)); return (Parm_x(sq,int,3,arg)*x); ]", code._stop.toString()); + assertEquals("Stop[ return (#2+#2); return (Parm_x(sq,int)*x); ]", code._stop.toString()); assertEquals("13", Eval2.eval(code, 2)); } @@ -231,7 +232,7 @@ var fcn = arg ? { int x -> x*x; } : { int x -> x+x; }; return fcn(3); """); code.parse().opto(); - assertEquals("Stop[ return Phi(Region,{ int -> int #1},{ int -> int #2})( 3); return (Parm_x($fun,int,3)*x); return (Parm_x($fun,int,3)<<1); ]", code._stop.toString()); + assertEquals("Stop[ return #2; return (Parm_x($fun1,int,3)*x); return (Parm_x($fun2,int,3)<<1); ]", code._stop.toString()); assertEquals("6", Eval2.eval(code, 0)); assertEquals("9", Eval2.eval(code, 1)); } @@ -241,7 +242,7 @@ return fcn(3); public void testFcn5() { CodeGen code = new CodeGen("val fact = { int x -> x <= 1 ? 1 : x*fact(x-1); }; return fact(arg);"); code.parse().opto().typeCheck(); - assertEquals("Stop[ return fact( arg); return Phi(Region,1,(Parm_x(fact,int,arg,(x-1))*fact( Sub))); ]", code._stop.toString()); + assertEquals("Stop[ return #2; return Phi(Region,1,(Parm_x(fact,int,arg,(x-1))*#2)); ]", code._stop.toString()); assertEquals( "1", Eval2.eval(code, 0)); assertEquals( "1", Eval2.eval(code, 1)); assertEquals( "2", Eval2.eval(code, 2)); @@ -291,7 +292,7 @@ for(;;) { } """); code.parse().opto().typeCheck().GCM().localSched(); - assertEquals("Stop[ return x( Phi(Loop,arg,x( 3))); return Parm_i(x,int,3,Phi_arg); ]", code._stop.toString()); + assertEquals("Stop[ return #2; return Parm_i(x,int); ]", code._stop.toString()); assertEquals("3", Eval2.eval(code, 0)); } @@ -462,7 +463,7 @@ if (i2i) return i2i(arg); return f2f(o)(1); """); code.parse().opto().typeCheck().GCM().localSched(); - assertEquals("Stop[ return Phi(Region,o( arg),o( 1)); return Parm_i(o,int,arg,1); ]", code._stop.toString()); + assertEquals("Stop[ return Phi(Region,#2,1); return Parm_i(o,int); ]", code._stop.toString()); assertEquals("1", Eval2.eval(code, 2)); } diff --git a/src/test/java/com/seaofnodes/simple/Chapter19Test.java b/src/test/java/com/seaofnodes/simple/Chapter19Test.java index 1abda81f..9bd75207 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter19Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter19Test.java @@ -3,10 +3,12 @@ import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.codegen.CodeGen; +import org.junit.Ignore; import org.junit.Test; +import static com.seaofnodes.simple.Main.PORTS; import static org.junit.Assert.*; import static org.junit.Assert.fail; -import org.junit.Ignore; public class Chapter19Test { @Test public void testPrintingFunctionLookup() { @@ -78,107 +80,107 @@ val _hashCodeString = { !String self -> !String !s = new String { cs = new u8[17]; }; s.cs[0] = 67; // C s.cs[1] = 108; // l -hashCode(s);"""); +hashCode(s)+hashCode(s);"""); code.parse().opto().typeCheck().GCM().localSched(); - assertEquals("Stop[ return Phi(Region,123456789,Phi(Loop,0,(.[]+((Phi_hash<<5)-Phi_hash)))); return Phi(Region,1,0,0,1); ]", code._stop.toString()); - assertEquals("-2449306563677080489", Eval2.eval(code, 2)); + assertEquals("Stop[ return (#2+#2); return Phi(Region,1,0,0,1); return Phi(Region,._hashCode,Phi(Region,123456789,Phi(Loop,0,(.[]+((Phi_hash<<5)-Phi_hash))))); ]", code._stop.toString()); + assertEquals("-4898613127354160978", Eval2.eval(code, 2)); } @Test public void testBasic0() { - CodeGen code = new CodeGen("return 0;").parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return 0;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return 0;", code._stop.toString()); } @Test public void testBasic1() { - CodeGen code = new CodeGen("return arg+1;").parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg+1;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (inc,arg);", code._stop.toString()); } @Test public void testBasic2() { - CodeGen code = new CodeGen("return -17;").parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return -17;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return -17;", code._stop.toString()); } @Test public void testBasic3() { - CodeGen code = new CodeGen("return arg==1;").parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg==1;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (set==,(cmp,arg));", code._stop.toString()); } @Test public void testBasic4() { - CodeGen code = new CodeGen("return arg<<1;").parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg<<1;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (shli,arg);", code._stop.toString()); } @Test public void testBasic5() { - CodeGen code = new CodeGen("return arg >> 1;").parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg >> 1;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (sari,arg);", code._stop.toString()); } @Test public void testBasic6() { - CodeGen code = new CodeGen("return arg >>> 1;").parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg >>> 1;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (shri,arg);", code._stop.toString()); } @Test public void testBasic7() { - CodeGen code = new CodeGen("return arg / 2;").parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg / 2;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (div,arg,2);", code._stop.toString()); } @Test public void testBasic8() { - CodeGen code = new CodeGen("return arg * 6;").parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg * 6;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (muli,arg);", code._stop.toString()); } @Test public void testBasic9() { - CodeGen code = new CodeGen("return arg & 2;").parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg & 2;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (andi,arg);", code._stop.toString()); } @Test public void testBasic10() { - CodeGen code = new CodeGen("return arg | 2;").parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg | 2;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (ori,arg);", code._stop.toString()); } @Test public void testBasic11() { - CodeGen code = new CodeGen("return arg ^ 2;").parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg ^ 2;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (xori,arg);", code._stop.toString()); } @Test public void testBasic12() { - CodeGen code = new CodeGen("return arg + 2.0;").parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg + 2.0;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (addf,(i2f8,arg),2.0f);", code._stop.toString()); } @Test public void testBasic13() { - CodeGen code = new CodeGen("return arg - 2.0;").parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg - 2.0;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (subf,(i2f8,arg),2.0f);", code._stop.toString()); } @Test public void testBasic14() { - CodeGen code = new CodeGen("return arg * 2.0;").parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg * 2.0;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (mulf,(i2f8,arg),2.0f);", code._stop.toString()); } @Test public void testBasic15() { - CodeGen code = new CodeGen("return arg / 2.0;").parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); - assertEquals("return (divf,(i2f8,arg),2.0f);", code._stop.toString()); + CodeGen code = new CodeGen("return arg / 2.0;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + assertEquals("return (mulf,(i2f8,arg),0.5f);", code._stop.toString()); } @Test @@ -187,7 +189,7 @@ hashCode(s);"""); """ int arg1 = arg + 1; return arg1 / arg;"""); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (div,(inc,arg),arg);", code._stop.toString()); } @@ -198,50 +200,17 @@ return arg1 / arg;"""); int arg1 = arg + 1; return arg1 * arg; """); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (mul,(inc,arg),arg);", code._stop.toString()); } @Test - public void testBasic18() { - CodeGen code = new CodeGen( - """ - int arg1 = arg + 1; - return arg1 << arg; - """); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); - assertEquals("return (shl,(inc,arg),arg);", code._stop.toString()); - } - - @Test - public void testBasic19() { - CodeGen code = new CodeGen( - """ - int arg1 = arg + 1; - return arg1 >> arg; - """); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); - assertEquals("return (sar,(inc,arg),arg);", code._stop.toString()); - } - - @Test - public void testBasic20() { - CodeGen code = new CodeGen( - """ - int arg1 = arg + 1; - return arg1 >>> arg; - """); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); - assertEquals("return (shr,(inc,arg),arg);", code._stop.toString()); - } - - @Test public void testToFloat() { CodeGen code = new CodeGen(""" int a = arg; return a + 2.0; """ - ).parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + ).parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (addf,(i2f8,arg),2.0f);", code._stop.toString()); } @@ -256,7 +225,7 @@ else { a = arg-3; } return a;"""); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return Phi(Region,(addi,arg),(addi,arg));", code.print()); } @@ -271,7 +240,7 @@ if( arg==1 ) else a=b+1; return a+b;"""); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (add,(add,Phi(Region,(shli,arg),arg),arg),Phi(Region,4,5));", code.print()); } @@ -283,7 +252,7 @@ int sum=0; for( int i=0; i<arg; i++ ) sum += i; return sum;"""); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return Phi(Loop,0,(add,Phi_sum,Phi(Loop,0,(inc,Phi_i))));", code.print()); } @@ -293,42 +262,42 @@ return sum;"""); """ struct S { int a; !S? !c; }; return new S;"""); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return S;", code.print()); } @Test public void testLea1() { CodeGen code = new CodeGen("int x = arg/3; return arg+x+7;"); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (lea,arg,(div,arg,3));", code.print()); } @Test public void testLea2() { CodeGen code = new CodeGen("int x = arg/3; return arg+x*4+7;"); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (lea,arg,(div,arg,3));", code.print()); } @Test public void testLea3() { CodeGen code = new CodeGen("int x = arg/3; return x*4+arg;"); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return (lea,arg,(div,arg,3));", code.print()); } @Test public void testAlloc2() { CodeGen code = new CodeGen("int[] !xs = new int[3]; xs[arg]=1; return xs[arg&1];"); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return .[];", code.print()); } @Test public void testAlloc3() { CodeGen code = new CodeGen("int[] !xs = new int[3]; xs[arg]=1; return xs[arg&1]+3;"); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return .[];", code.print()); } @@ -345,7 +314,7 @@ for( int i=0; i<ary#-1; i++ ) ary[i+1] += ary[i]; return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6 """); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return .[];", code.print()); } @@ -360,7 +329,7 @@ for( int i=0; i<A#; i++ ) for( int i=0; i<A#; i++ ) B[i] += A[i]; """); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return 0;", code.print()); } @@ -373,7 +342,7 @@ for( int i=0; i<A#; i++ ) A[i]++; return A[1]; """); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return .[];", code.print()); } @@ -393,8 +362,8 @@ val sqrt = { flt x -> flt farg = arg; return sqrt(farg); """); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); - assertEquals("return Phi(Loop,(i2f8,arg),(divf,(addf,(divf,i2f8,Phi_guess),Phi_guess),2.0f));", code.print()); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + assertEquals("return Phi(Loop,(i2f8,arg),(mulf,(addf,(divf,i2f8,Phi_guess),Phi_guess),0.5f));", code.print()); }; @@ -424,7 +393,7 @@ for( int j=0; j<nprimes; j++ ) rez[j] = primes[j]; return rez; """); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("return [int];", code.print()); //assertEquals("int[ 2,3,5,7,11,13,17,19]",Eval2.eval(code, 20)); } @@ -437,8 +406,8 @@ return rez; val fcn = arg ? { int x -> x*x; } : { int x -> x+x; }; return fcn(2)*10 + fcn(3); """); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); - assertEquals("Stop[ return (add,Phi(Region,{ int -> int #1},{ int -> int #2})( 3),(muli,Phi_( 2))); return (mul,Parm_x($fun,int,3,2),x); return (shli,Parm_x($fun,int,3,2)); ]", code.print()); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + assertEquals("Stop[ return (add,#2,(muli,#2)); return (mul,Parm_x($fun1,int),x); return (shli,Parm_x($fun2,int)); ]", code.print()); } @Test @@ -448,17 +417,17 @@ return fcn(2)*10 + fcn(3); val sq = { int x -> x*x; }; return sq(arg) + sq(3); """); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); - assertEquals("Stop[ return (add,sq(),sq()); return (mul,Parm_x(sq,int,3,arg),x); ]", code.print()); + code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + assertEquals("Stop[ return (add,#2,#2); return (mul,Parm_x(sq,int),x); ]", code.print()); } @Test public void testSelectedMemory() { - for( String cpu : new String[]{"x86_64_v2","riscv"} ) + for( String cpu : new String[]{"x86_64_v2","riscv","arm"} ) for( String src : new String[]{Chapter10Test.NESTED_MEMORY,Chapter16Test.CONSTRUCTOR_MEMORY, Chapter18Test.CALL_MEMORY,Chapter18Test.RECURSIVE_MEMORY} ) { var code = new CodeGen(src).parse().opto().typeCheck(); var aliases = memoryPhis(code._stop); - code.instSelect(cpu,"SystemV"); + code.instSelect(PORTS,cpu,"SystemV"); assertEquals(aliases,memoryPhis(code._stop)); code.GCM().localSched(); code._stop.walk(n -> { @@ -511,7 +480,7 @@ return sq(arg) + sq(3); int before = p.x + arg; a.x=33; b.x=44; return before; - """).parse().opto().typeCheck().instSelect("x86_64_v2","SystemV").GCM().localSched(); + """).parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2","SystemV").GCM().localSched(); var read = (MemOpNode)code._stop.walk(n -> n instanceof com.seaofnodes.simple.node.cpus.x86_64_v2.AddMemX86 ? n : null); assertNotNull(read); diff --git a/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/src/test/java/com/seaofnodes/simple/Chapter20Test.java new file mode 100644 index 00000000..104be01e --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/Chapter20Test.java @@ -0,0 +1,272 @@ +package com.seaofnodes.simple; + + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.print.ASMPrinter; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; +import static com.seaofnodes.simple.Main.PORTS; +import static org.junit.Assert.*; + +public class Chapter20Test { + @Test public void testAllocatorTopPhi() { com.seaofnodes.simple.codegen.RegAllocTestSupport.topPhi(); } + + @Test public void testAllocatorFixedNeighbor() throws Exception { com.seaofnodes.simple.codegen.RegAllocTestSupport.uncoloredFixedNeighbor(); } + + @Test public void testNarrowStoreMasks() { + for( String type : new String[]{"i8","u8","i16","u16"} ) { + CodeGen code = new CodeGen("struct S { "+type+" x; }; !S !s=new S; s.x=arg; return 0;") + .parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2","SystemV").GCM().localSched().regAlloc(); + int stores=0; + for( var bb : code._cfg ) + for( var node : bb.outs() ) + if( node instanceof com.seaofnodes.simple.node.cpus.x86_64_v2.StoreX86 st ) { + stores++; + assertEquals(-1,st.regmap(4).nextReg((short)15)); + } + assertEquals(1,stores); + } + } + + @Test public void testInlinedReturnValue() { + String src = """ +struct S { int x; }; +val f = { !S s -> s.x = g(); }; +val g = { -> 123; }; +!S !s = new S; +return f(s); +"""; + // The entry of g can disappear before its Return is folded into f. + // Exercise both worklist orders; deleting the entry does not kill 123. + for( int seed=0; seed<64; seed++ ) { + CodeGen code = new CodeGen(src,com.seaofnodes.simple.type.TypeInteger.BOT,seed).parse().opto().typeCheck(); + assertEquals("seed "+seed, "123", Eval2.eval(code,0)); + } + } + + + @org.junit.Rule public final org.junit.rules.ErrorCollector _errors = new org.junit.rules.ErrorCollector(); + @Test public void testAllocatorMasks() { com.seaofnodes.simple.codegen.RegAllocTestSupport.masks(); } + @Test public void testAllocatorUnion() { com.seaofnodes.simple.codegen.RegAllocTestSupport.union(); } + @Test public void testAllocatorCopyClobber() throws Exception { com.seaofnodes.simple.codegen.RegAllocTestSupport.copyClobber(); } + @Test public void testAllocatorCommutativePhi() { com.seaofnodes.simple.codegen.RegAllocTestSupport.commutativePhi(); } + @Test public void testAllocatorKills() { com.seaofnodes.simple.codegen.RegAllocTestSupport.killWithoutResult(); } + @Test public void testAllocatorDependencies() { com.seaofnodes.simple.codegen.RegAllocTestSupport.nullUseMask(); } + @Test public void testAllocatorCloneClass() { com.seaofnodes.simple.codegen.RegAllocTestSupport.cloneRegisterClass(); } + + @Test public void testPrintingRegisters() throws Exception { + com.seaofnodes.simple.codegen.PrintRegTestSupport.check(); + } + + + @Ignore + @Test + public void testJig() { + CodeGen code = new CodeGen("return 0;"); + code.parse().opto().typeCheck(); + assertEquals("return 0;", code._stop.toString()); + assertEquals("0", Eval2.eval(code, 2)); + } + + // Collect differences so every target runs; JUnit still fails the test. + private void testTarget(String src, String cpu, String os, int spills, String stop) { + _errors.checkSucceeds(() -> { testCPU(src,cpu,os,spills,stop); return null; }); + } + + static void testCPU(String src, String cpu, String os, int spills, String stop) { + CodeGen code = new CodeGen(src); + code.parse().opto().typeCheck().instSelect(PORTS,cpu,os).GCM().localSched().regAlloc().encode(); + com.seaofnodes.simple.codegen.RegAllocTestSupport.checkRegisters(code); + SpillStats.record(code,"Chapter20",cpu,os); + assertEquals("Expect spills: "+cpu,spills,code._regAlloc._spillScaled,Math.max(1,spills>>3)); + if( stop!=null ) assertEquals(stop,code._stop.toString()); + } + + private void testAllCPUs( String src, int spills, String stop ) { + testTarget(src,"x86_64_v2", "SystemV",spills,stop); + testTarget(src,"riscv" , "SystemV",spills,stop); + testTarget(src,"arm" , "SystemV",spills,stop); + } + + @Test public void testAlloc0() { + testAllCPUs("return new u8[arg];", 1, "return [u8];"); + } + + @Test public void testBasic1() { + String src = "return arg | 2;"; + testTarget(src,"x86_64_v2", "SystemV",1,"return (ori,mov(arg));"); + testTarget(src,"riscv" , "SystemV",0,"return ( arg | #2 );"); + testTarget(src,"arm" , "SystemV",0,"return (ori,arg);"); + } + + @Test + public void testNewtonInteger() { + String src = +""" +// Newtons approximation to the square root +val sqrt = { int x -> + int guess = x; + while( 1 ) { + int next = (x/guess + guess)/2; + if( next == guess ) return guess; + guess = next; + } +}; +return sqrt(arg) + sqrt(arg+2); +"""; + testTarget(src,"x86_64_v2", "SystemV",26,null); + testTarget(src,"riscv" , "SystemV",19,null); + testTarget(src,"arm" , "SystemV",26,null); + } + + @Test + public void testNewtonFloat() { + String src = +""" +// Newtons approximation to the square root +val sqrt = { flt x -> + flt guess = x; + while( 1 ) { + flt next = (x/guess + guess)/2; + if( next == guess ) return guess; + guess = next; + } +}; +flt farg = arg; +return sqrt(farg) + sqrt(farg+2.0); +"""; + testTarget(src,"x86_64_v2", "SystemV",25,null); + testTarget(src,"riscv" , "SystemV",21,null); + testTarget(src,"arm" , "SystemV",18,null); + } + + @Test + public void testAlloc2() { + String src = "int[] !xs = new int[3]; xs[arg]=1; return xs[arg&1];"; + testAllCPUs(src,1,"return .[];"); + } + + @Test + public void testArray1() { + String src = +""" +int[] !ary = new int[arg]; +// Fill [0,1,2,3,4,...] +for( int i=0; i<ary#; i++ ) + ary[i] = i; +// Fill [0,1,3,6,10,...] +for( int i=0; i<ary#-1; i++ ) + ary[i+1] += ary[i]; +return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6 +"""; + testTarget(src,"x86_64_v2", "SystemV",13,"return .[];"); + testTarget(src,"riscv" , "SystemV",1,"return (add,.[],(mul,.[],1000));"); + testTarget(src,"arm" , "SystemV",1,"return (add,.[],(muli,.[]));"); + } + + @Test + public void testString() { + String src = """ +struct String { + u8[] !cs; + int _hashCode; +}; + +val equals = { !String !self, !String s -> + if( self == s ) return true; + if( self.cs# != s.cs# ) return false; + for( int i=0; i< self.cs#; i++ ) + if( self.cs[i] != s.cs[i] ) + return false; + return true; +}; + +val hashCode = { !String self -> + self._hashCode + ? self._hashCode + : (self._hashCode = _hashCodeString(self)); +}; + +val _hashCodeString = { !String self -> + int hash=0; + if( self.cs ) { + for( int i=0; i< self.cs#; i++ ) + hash = hash*31 + self.cs[i]; + } + if( !hash ) hash = 123456789; + return hash; +}; + +!String !s = new String { cs = new u8[17]; }; +s.cs[0] = 67; // C +s.cs[1] = 108; // l +hashCode(s); +"""; + testTarget(src,"x86_64_v2", "SystemV",21,null); + testTarget(src,"riscv" , "SystemV", 3,null); + testTarget(src,"arm" , "SystemV", 3,null); + } + + @Test + public void testCast() { + String src = "struct Bar { int x; }; var b = arg ? new Bar; return b ? b.x++ + b.x++ : -1;"; + testAllCPUs(src,0,null); + } + + @Test + public void testFltArg() { + String src = "return {int i, flt f, int j->return i+f+j;};"; + testAllCPUs(src,0,null); + } + + @Test + public void testFlags1() { + String src = """ +bool b1 = arg == 1; +bool b2 = arg == 2; +if (b2) if (b1) return 1; +if (b1) return 2; +return 0; +"""; + testTarget(src,"x86_64_v2", "SystemV",0,"return Phi(Region,1,2,0);"); + testTarget(src,"riscv" , "SystemV",0,"return Phi(Region,1,2,0);"); + testTarget(src,"arm" , "SystemV",0,"return Phi(Region,1,2,0);"); + } + + @Test + public void testFlags2() { + String src = """ +bool b1 = arg == 1; +while (arg > 0) { + arg--; + if (b1) arg--; +} +return arg; +"""; + testTarget(src,"x86_64_v2", "SystemV",3,null); + testTarget(src,"riscv" , "SystemV",2,null); + testTarget(src,"arm" , "SystemV",2,null); + } + + @Test public void testMemoryAllocation() { + for( String cpu : new String[]{"x86_64_v2","riscv","arm"} ) + for( String src : new String[]{Chapter10Test.NESTED_MEMORY,Chapter16Test.CONSTRUCTOR_MEMORY, + Chapter18Test.CALL_MEMORY,Chapter18Test.RECURSIVE_MEMORY, + "int[] !a = new int[3]; a[arg]=arg; return a[0]+1;"} ) { + var code = new CodeGen(src).parse().opto().typeCheck() + .instSelect(PORTS,cpu,"SystemV").GCM().localSched().regAlloc(); + com.seaofnodes.simple.codegen.RegAllocTestSupport.checkRegisters(code); + code._stop.walk(n -> { + // Whole memory, precise Phis, and memory projections need no register. + if( n._type instanceof com.seaofnodes.simple.type.TypeMem ) + assertEquals(-1,code._regAlloc.regnum(n)); + if( n instanceof com.seaofnodes.simple.node.NewNode nn ) { + assertTrue(code._regAlloc.regnum(nn.proj(0))>=0); + assertEquals(-1,code._regAlloc.regnum(nn.proj(1))); + } + return null; + }); + } + } +} diff --git a/src/test/java/com/seaofnodes/simple/CompoundAssignmentTest.java b/src/test/java/com/seaofnodes/simple/CompoundAssignmentTest.java index 860827d7..48f739cf 100644 --- a/src/test/java/com/seaofnodes/simple/CompoundAssignmentTest.java +++ b/src/test/java/com/seaofnodes/simple/CompoundAssignmentTest.java @@ -1,6 +1,6 @@ package com.seaofnodes.simple; - +import com.seaofnodes.simple.codegen.CodeGen; import org.junit.Test; import static org.junit.Assert.*; diff --git a/src/test/java/com/seaofnodes/simple/Eval2.java b/src/test/java/com/seaofnodes/simple/Eval2.java index 46251cef..3312ec6b 100644 --- a/src/test/java/com/seaofnodes/simple/Eval2.java +++ b/src/test/java/com/seaofnodes/simple/Eval2.java @@ -1,7 +1,9 @@ package com.seaofnodes.simple; -import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.print.IRPrinter; +import com.seaofnodes.simple.type.*; import java.util.*; public abstract class Eval2 { diff --git a/src/test/java/com/seaofnodes/simple/Main.java b/src/test/java/com/seaofnodes/simple/Main.java index 523bd306..f3a6784c 100644 --- a/src/test/java/com/seaofnodes/simple/Main.java +++ b/src/test/java/com/seaofnodes/simple/Main.java @@ -1,9 +1,11 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import java.nio.file.Files; import java.nio.file.Path; public class Main { + public static final String PORTS = "com.seaofnodes.simple.node.cpus"; // Compile and run a simple program public static void main( String[] args ) throws Exception { // First arg is file, 2nd+ args are program args diff --git a/src/test/java/com/seaofnodes/simple/MergeSortTest.java b/src/test/java/com/seaofnodes/simple/MergeSortTest.java index a352530f..31fbe3a4 100644 --- a/src/test/java/com/seaofnodes/simple/MergeSortTest.java +++ b/src/test/java/com/seaofnodes/simple/MergeSortTest.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -8,7 +9,7 @@ public class MergeSortTest { @Test public void testMergeSort() { - CodeGen code = new CodeGen( + String src = """ // based on the top-down version from https://en.wikipedia.org/wiki/Merge_sort @@ -56,10 +57,11 @@ for (int i = 0; i < a#; i++) merge_sort(a, b, a#); return a; -"""); - code.parse().opto(); - assertEquals("Stop[ return [int]; return 0; ]", code._stop.toString()); - assertEquals("int[ 1,2,3,4,5,6,7,8,9,10,11]", Eval2.eval(code, 11)); +"""; + Chapter20Test.testCPU(src,"x86_64_v2", "SystemV",46,"Stop[ return mov(mov([int])); return 0; ]"); + Chapter20Test.testCPU(src,"riscv" , "SystemV",36,"Stop[ return mov(mov([int])); return 0; ]"); + Chapter20Test.testCPU(src,"arm" , "SystemV",37,"Stop[ return mov(mov([int])); return 0; ]"); +//assertEquals("int[ 1,2,3,4,5,6,7,8,9,10,11]", Eval2.eval(code, 11)); } } diff --git a/src/test/java/com/seaofnodes/simple/MutabilityTest.java b/src/test/java/com/seaofnodes/simple/MutabilityTest.java index 0861424f..7ae80314 100644 --- a/src/test/java/com/seaofnodes/simple/MutabilityTest.java +++ b/src/test/java/com/seaofnodes/simple/MutabilityTest.java @@ -2,6 +2,7 @@ package com.seaofnodes.simple; import org.junit.Test; import static org.junit.Assert.*; +import com.seaofnodes.simple.codegen.CodeGen; public class MutabilityTest { private static void ok(String src, String result) { diff --git a/src/test/java/com/seaofnodes/simple/SchedulerTest.java b/src/test/java/com/seaofnodes/simple/SchedulerTest.java index d29166a7..3b19857a 100644 --- a/src/test/java/com/seaofnodes/simple/SchedulerTest.java +++ b/src/test/java/com/seaofnodes/simple/SchedulerTest.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.evaluator.Evaluator; import com.seaofnodes.simple.node.StopNode; import org.junit.Assert; diff --git a/src/test/java/com/seaofnodes/simple/SpillStats.java b/src/test/java/com/seaofnodes/simple/SpillStats.java new file mode 100644 index 00000000..9b03da7b --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/SpillStats.java @@ -0,0 +1,48 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import java.util.Map; +import java.util.TreeMap; +import org.junit.runner.*; +import org.junit.runner.notification.*; + +// Run existing tests and sum actual allocations, including ones without goldens. +// Helpers call record after allocation; ordinary JUnit runs collect nothing. +public class SpillStats extends RunListener { + private static SpillStats ACTIVE; + private Description _test; + private final Map<String,long[]> _totals = new TreeMap<>(); + + @Override public void testStarted(Description test) { _test = test; } + public static void record(CodeGen code, String cohort, String cpu, String abi) { + if( ACTIVE==null ) return; + String test = ACTIVE._test.getTestClass().getSimpleName()+"."+ACTIVE._test.getMethodName(); + for( String key : new String[]{cohort+","+cpu+","+abi,cohort+",ALL,ALL"} ) { + long[] sum = ACTIVE._totals.computeIfAbsent(key,k -> new long[3]); + sum[0]++; sum[1] += code._regAlloc._spills; sum[2] += code._regAlloc._spillScaled; + } + System.out.println("allocation,"+cohort+","+test+","+cpu+","+abi+","+ + code._regAlloc._spills+","+code._regAlloc._spillScaled); + } + + public static void main(String[] args) throws ClassNotFoundException { + if( args.length==0 ) args = new String[]{"Chapter20Test","BrainFuckTest","MergeSortTest"}; + Class<?>[] tests = new Class<?>[args.length]; + for( int i=0; i<args.length; i++ ) + tests[i] = Class.forName("com.seaofnodes.simple."+args[i]); + ACTIVE = new SpillStats(); + JUnitCore junit = new JUnitCore(); + junit.addListener(ACTIVE); + Result result = junit.run(tests); + if( !result.wasSuccessful() ) System.out.println("FAILED test run: totals are not a complete comparison."); + System.out.println("cohort,cpu,abi,allocations,splits,scaled"); + for( var row : ACTIVE._totals.entrySet() ) { + long[] sum = row.getValue(); + System.out.println(row.getKey()+","+sum[0]+","+sum[1]+","+sum[2]); + } + for( Failure failure : result.getFailures() ) System.err.println(failure.getTrace()); + System.out.println("Tests: "+result.getRunCount()+", failures: "+result.getFailureCount()); + ACTIVE = null; + if( !result.wasSuccessful() ) System.exit(1); + } +} diff --git a/src/test/java/com/seaofnodes/simple/codegen/PrintRegTestSupport.java b/src/test/java/com/seaofnodes/simple/codegen/PrintRegTestSupport.java new file mode 100644 index 00000000..b9b49c9f --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/codegen/PrintRegTestSupport.java @@ -0,0 +1,26 @@ +package com.seaofnodes.simple.codegen; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import static org.junit.Assert.*; +public class PrintRegTestSupport { + public static void check() throws Exception { + var code = new CodeGen("return 0;").parse(); + var alloc = new RegAlloc(code); + var n = new ConstantNode(TypeInteger.ZERO); + var a = new LRG((short)1); + var b = new LRG((short)2); + var c = new LRG((short)3); + a._reg = -1; + c._leader = b; + b._leader = a; + var field = RegAlloc.class.getDeclaredField("_lrgs"); + field.setAccessible(true); + var map = (java.util.Map<Node,LRG>)field.get(alloc); + map.put(n,c); + assertEquals("V1",alloc.reg(n)); + assertEquals(-1,alloc.regnum(n)); + assertSame(c,map.get(n)); + assertSame(b,c._leader); + assertSame(a,b._leader); + } +} diff --git a/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java b/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java new file mode 100644 index 00000000..2966cb00 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java @@ -0,0 +1,195 @@ +package com.seaofnodes.simple.codegen; + +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.cpus.x86_64_v2.x86_64_v2; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; +import static org.junit.Assert.*; + +// Small scheduled machine graphs isolate register constraints from peepholes. +public class RegAllocTestSupport { + private static final RegMask A = new RegMask(0), B = new RegMask(1); + private static class Op extends MachConcreteNode { + final RegMask _def, _use, _kill; + final boolean _clone; + Op(RegMask def, RegMask use, RegMask kill, boolean clone, Node... ins) { + super(ins); _def=def; _use=use; _kill=kill; _clone=clone; _type=TypeInteger.BOT; + } + @Override public String op() { return "testOp"; } + @Override public RegMask outregmap() { return _def; } + @Override public RegMask regmap(int i) { return _use; } + @Override public RegMask killmap() { return _kill; } + @Override public boolean isClone() { return _clone; } + @Override public Node copy() { return new Op(_def,_use,_kill,_clone,(Node)null); } + @Override public int encoding(ByteArrayOutputStream bytes) { throw new AssertionError(); } + } + private static CodeGen graph() { + CodeGen code = new CodeGen(""); + code._mach = new x86_64_v2(); + code._cfg.add(code._start); + return code; + } + + public static void uncoloredFixedNeighbor() throws Exception { + CodeGen code = graph(); + RegAlloc alloc = new RegAlloc(code); + Op def = new Op(new RegMask(3L),null,null,false,code._start); + LRG lrg = alloc.newLRG(def); + LRG neighbor = new LRG((short)99); + neighbor._mask = A; + lrg.addNeighbor(neighbor); // Uncolored, but its only possible color is A. + var bias = IFG.class.getDeclaredMethod("biasColorNeighbors",RegAlloc.class,Node.class,RegMask.class); + bias.setAccessible(true); + assertEquals((short)1,bias.invoke(null,alloc,def,new RegMaskRW(3L,0L))); + } + + public static void masks() { + for( int reg : new int[]{0,63,64,65,127} ) { + RegMask mask = new RegMask(reg); + assertEquals(reg,mask.firstReg()); + assertEquals(1,mask.size()); + } + assertEquals(-1,new RegMask(0L).firstReg()); + assertFalse(new RegMask(0L).size1()); + RegMask boundary = new RegMask(Long.MIN_VALUE,Long.MIN_VALUE|1); + assertEquals(64,boundary.nextReg((short)63)); + assertEquals(127,boundary.nextReg((short)64)); + assertEquals(-1,boundary.nextReg((short)127)); + } + + public static void union() { + CodeGen code = graph(); + Op use = new Op(null,B,null,false,code._start); + LRG a = new LRG((short)1), b = new LRG((short)2); + a._mask = new RegMask(3L); + b._mask = new RegMask(6L); + b.machUse(use,(short)2,true); + LRG both = a.union(b); + assertNotNull(both._mask); + assertEquals(1,both._mask.firstReg()); + assertEquals(1,both._mask.size()); + assertSame(use,both._machUse); + assertEquals(2,both._uidx); + } + + public static void killWithoutResult() { + CodeGen code = graph(); + Op def = new Op(A,null,null,false,code._start); + new Op(null,null,A,false,code._start); // Clobbers A, produces no value. + new Op(null,A,null,false,code._start,def); + RegAlloc alloc = new RegAlloc(code); + assertTrue(BuildLRG.run(0,alloc)); + assertFalse("A live value cannot survive a register kill",IFG.build(0,alloc)); + } + + public static void nullUseMask() { + CodeGen code = graph(); + Op def = new Op(A,null,null,false,code._start); + new Op(null,null,null,false,code._start,def); // Scheduling dependency only. + RegAlloc alloc = new RegAlloc(code); + assertTrue(BuildLRG.run(0,alloc)); + assertTrue(IFG.build(0,alloc)); + } + + public static void cloneRegisterClass() { + for( RegMask required : new RegMask[]{B,new RegMask(6L)} ) { + CodeGen code = graph(); + Op def = new Op(A,null,null,true,code._start); + Op use = new Op(null,required,null,false,code._start,def); + Op use2 = new Op(null,required,null,false,code._start,def); + RegAlloc alloc = new RegAlloc(code); + alloc.regAlloc(); // Re-cloning an A-only value cannot satisfy B/C uses. + assertEquals(0,alloc.regnum(def)); + assertTrue(required.test(alloc.regnum(use.in(1)))); + assertTrue(required.test(alloc.regnum(use2.in(1)))); + assertTrue(use.in(1) instanceof SplitNode); + } + } + + public static void commutativePhi() { + CodeGen code = graph(); + Op a = new Op(A,null,null,false,code._start); + Op b = new Op(A,null,null,false,code._start); + Op add = new Op(A,A,null,false,code._start,a,b) { + @Override public boolean commutes() { return true; } + }; + RegionNode region = new RegionNode(null,code._start,code._start); + new PhiNode("sum",TypeInteger.BOT,region,add,a); + code._cfg.add(region); + assertTrue(BuildLRG.run(0,new RegAlloc(code))); + } + + public static void topPhi() { + // Either arm can be the synthetic exit. Both predecessors share a + // block here so making TOP live would conflict with the real value. + for( int slot=1; slot<=2; slot++ ) { + CodeGen code = graph(); + RegAlloc alloc = new RegAlloc(code); + Op def = new Op(A,null,null,false,code._start); + Node top = new ConstantNode(com.seaofnodes.simple.type.Type.TOP); + RegionNode region = new RegionNode(null,code._start,code._start); + PhiNode phi = new PhiNode("value",TypeInteger.BOT,region,slot==1 ? top : def,slot==2 ? top : def); + phi._type = TypeInteger.BOT; + new Op(null,A,null,false,region,phi); + code._cfg.add(region); + assertTrue(BuildLRG.run(0,alloc)); + assertNull(alloc.lrg(top)); + assertSame(alloc.lrg(def),alloc.lrg(phi)); + assertTrue(IFG.build(0,alloc)); + alloc.insertBefore(phi,slot,"test",(byte)0,alloc.lrg(phi)); + assertSame(top,phi.in(slot)); + } + } + + public static void checkRegisters(CodeGen code) { + assertTrue(code._regAlloc.verifyFunctionLocalEdges()); + for( CFGNode bb : code._cfg ) + for( Node n : bb._outputs ) { + if( n instanceof MachNode mach ) { + if( code._regAlloc.regnum(n)>=0 && mach.outregmap()!=null ) + assertTrue(n.getClass().getSimpleName()+"#"+n._nid,code._regAlloc.regnum(n)>=0 && mach.outregmap().test(code._regAlloc.regnum(n))); + for( int i=n instanceof ParmNode ? 2 : 1; i<n.nIns(); i++ ) + if( n.in(i)!=null && code._regAlloc.regnum(n.in(i))>=0 && mach.regmap(i)!=null ) + assertTrue(n.getClass().getSimpleName()+"#"+n._nid+" input "+i,code._regAlloc.regnum(n.in(i))>=0 && mach.regmap(i).test(code._regAlloc.regnum(n.in(i)))); + if( mach.twoAddress()!=0 ) + assertEquals(code._regAlloc.regnum(n),code._regAlloc.regnum(n.in(mach.twoAddress()))); + } + if( n instanceof PhiNode && code._regAlloc.regnum(n)>=0 ) + for( int i=n instanceof ParmNode ? 2 : 1; i<n.nIns(); i++ ) + if( n.in(i)._type==com.seaofnodes.simple.type.Type.TOP ) + assertNull(code._regAlloc.lrg(n.in(i))); + else + assertEquals(code._regAlloc.regnum(n),code._regAlloc.regnum(n.in(i))); + } + } + + public static void copyClobber() throws Exception { + for( boolean killed : new boolean[]{false,true} ) { + CodeGen code = graph(); + RegAlloc alloc = new RegAlloc(code); + Op def = new Op(A,null,null,false,code._start); + alloc.newLRG(def)._reg=0; + SplitNode hi = code._mach.split("test",(byte)0,null); + hi.setDef(0,code._start); hi.setDef(1,def); + alloc.newLRG(hi)._reg=1; + new Op(null,null,killed ? A : null,false,code._start); + SplitNode lo = code._mach.split("test",(byte)0,null); + lo.setDef(0,code._start); lo.setDef(1,hi); + alloc.newLRG(lo)._reg=0; + Op use = new Op(null,A,null,false,code._start,lo); + var post = RegAlloc.class.getDeclaredMethod("postColor"); + post.setAccessible(true); post.invoke(alloc); + assertSame(killed ? lo : def,use.in(1)); + } + CodeGen pre = graph(); + RegAlloc uncolored = new RegAlloc(pre); + Op def0 = new Op(A,null,null,false,pre._start); + uncolored.newLRG(def0)._mask=A; + Op fixed = new Op(A,null,null,false,pre._start); + uncolored.newLRG(fixed)._mask=A; + SplitNode copy = pre._mach.split("test",(byte)0,null); + copy.setDef(0,pre._start); copy.setDef(1,def0); + assertFalse("A fixed definition clobbers even before coloring",uncolored.sameBlockNoClobber(copy)); + } + +} diff --git a/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java b/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java index 851380cf..586924b2 100644 --- a/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java +++ b/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java @@ -2,7 +2,7 @@ package com.seaofnodes.simple.fuzzer; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.Eval2; -import com.seaofnodes.simple.CodeGen; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.Node; import java.util.ArrayList; import java.util.Objects; diff --git a/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java b/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java index 5c89a485..a2137314 100644 --- a/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java +++ b/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java @@ -1,7 +1,7 @@ package com.seaofnodes.simple.fuzzer; -import com.seaofnodes.simple.CodeGen; import com.seaofnodes.simple.IterPeeps; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.node.StopNode; import com.seaofnodes.simple.type.TypeInteger; |
