diff options
| author | Cliff Click <cliffc@acm.org> | 2025-04-24 14:38:36 -0700 |
|---|---|---|
| committer | Cliff Click <cliffc@acm.org> | 2026-10-08 16:52:27 -0700 |
| commit | 53308013c3f2bad224208805df0f54416067b8be (patch) | |
| tree | efd01b4f23d18bc40c9d4001fb399ce3567b180b | |
| parent | 43316b84a9cc0b7ac03a4d85fa4f4dff0f26ccd5 (diff) | |
| download | Simple-53308013c3f2bad224208805df0f54416067b8be.tar.gz Simple-53308013c3f2bad224208805df0f54416067b8be.zip | |
Chapter 21: Instruction Encoding and ELF
Co-authored-by: godalming123 <68993177+godalming123@users.noreply.github.com>
Co-authored-by: Hsiang-Ying Fu <66062782+fuhsnn@users.noreply.github.com>
Co-authored-by: tejasae-afk <tejasae-afk@users.noreply.github.com>
Co-authored-by: fuhsnn <66062782+fuhsnn@users.noreply.github.com>
Co-authored-by: Tejas Attarde <201814263+tejasae-afk@users.noreply.github.com>
Co-authored-by: Tamás Hadházy <85063808+Hels15@users.noreply.github.com>
Co-authored-by: Symious <14933944+symious@users.noreply.github.com>
Co-authored-by: Jun Kawasaki <jun784@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
289 files changed, 10856 insertions, 4246 deletions
@@ -38,8 +38,9 @@ test_javas := $(wildcard $(TST)/$(SIMPLE)/*java $(TST)/$(SIMPLE)/*/*java) main_classes := $(patsubst $(SRC)/%java,$(CLZDIR)/main/%class,$(main_javas)) include $(firstword $(wildcard ../graph/graph.mk graph/graph.mk)) include $(firstword $(wildcard ../print/print.mk print/print.mk)) +include $(firstword $(wildcard ../isa/isa.mk isa/isa.mk)) test_classes := $(patsubst $(TST)/%java,$(CLZDIR)/test/%class,$(test_javas)) -test_cp := $(patsubst $(TST)/$(SIMPLE)/%.java,com.seaofnodes.simple.%,$(wildcard $(TST)/$(SIMPLE)/*Test.java)) +test_cp := $(patsubst $(TST)/$(SIMPLE)/%.java,com.seaofnodes.simple.%,$(wildcard $(TST)/$(SIMPLE)/*Test.java)) com.seaofnodes.simple.codegen.X86EncodingTest classes = $(main_classes) $(test_classes) # All the libraries libs = $(wildcard lib/*jar) @@ -55,28 +56,31 @@ endif default: $(default_targets) # Compile just the out-of-date files -$(main_classes): $(CLZDIR)/main/%class: $(SRC)/%java $(graph_javas) $(print_javas) +$(main_classes): $(CLZDIR)/main/%class: $(SRC)/%java $(graph_javas) $(print_javas) $(isa_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) -$(test_classes): $(CLZDIR)/test/%class: $(TST)/%java $(main_classes) +$(test_classes): $(CLZDIR)/test/%class: $(TST)/%java $(main_classes) $(isa_test_javas) @echo "compiling " $@ " because " $? @[ -d $(CLZDIR)/test ] || mkdir -p $(CLZDIR)/test - @javac $(JAVAC_ARGS) -cp "$(CLZDIR)/test$(SEP)$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(TST) -d $(CLZDIR)/test $(test_javas) + @javac $(JAVAC_ARGS) -cp "$(CLZDIR)/test$(SEP)$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(TST) -d $(CLZDIR)/test $(test_javas) $(isa_test_javas) # Base launch line for JVM tests JVM=nice java -ea -cp "$(CLZDIR)/main${SEP}${jars}${SEP}$(CLZDIR)/test" tests: $(default_targets) @echo "testing " $(test_cp) + @[ -d build/objs ] || mkdir -p build/objs @$(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) + @[ -d build/objs ] || mkdir -p build/objs @$(JVM) com.seaofnodes.simple.SpillStats +.PHONY: spill-stats + fuzzer: $(default_targets) @echo "fuzzing " $(test_cp) @$(JVM) org.junit.runner.JUnitCore com.seaofnodes.simple.FuzzerWrap @@ -91,7 +95,7 @@ build/release/simple.jar: $(main_classes) $(test_classes) @jar cf build/release/simple.jar -C $(CLZDIR)/main . -C $(CLZDIR)/test . -C $(SRC)/$(SIMPLE) . -C $(TST)/$(SIMPLE) . -.PHONY: clean spill-stats +.PHONY: clean clean: rm -rf build rm -f TAGS diff --git a/README.ja.md b/README.ja.md index c0345907..80c288a3 100644 --- a/README.ja.md +++ b/README.ja.md @@ -58,7 +58,7 @@ Simple 言語のスタイルは、C または Java のサブセットに倣っã� * [第18章](docs/chapter18/README.ja.md):関数と呼び出し。 * [第19章](docs/chapter19/README.ja.md):命令選択と移植可能なコンパイル。 * [第20章](docs/chapter20/README.ja.md):グラフ彩色によるレジスタ割り当て。 -* 第21章:命令エンコーディングと ELF。 +* [第21章](docs/chapter21/README.ja.md):命令エンコーディングと ELF。 * 第22章:Simple の Hello, World! * 第23章:メソッドと型の再検討。 * 第24章:連鎖した条件分岐と SCCP。 @@ -67,7 +67,7 @@ The Simple language is styled after a subset of C or Java. * [Chapter 18](docs/chapter18/README.md): Functions and calls. * [Chapter 19](docs/chapter19/README.md): Instruction selection and portable compilation * [Chapter 20](docs/chapter20/README.md): Graph Coloring Register Allocation -* Chapter 21: Instruction Encodings & ELF +* [Chapter 21](docs/chapter21/README.md): Instruction Encodings & ELF * Chapter 22: A Simple Hello, World! * Chapter 23: Methods and Types Revisited * Chapter 24: Chained conditionals and SCCP diff --git a/docs/chapter21/21-grammar.ja.md b/docs/chapter21/21-grammar.ja.md new file mode 100644 index 00000000..8cb8f27b --- /dev/null +++ b/docs/chapter21/21-grammar.ja.md @@ -0,0 +1,141 @@ +# 第21章の文法 + +[English](21-grammar.md) | 日本語 + +```antlrv4 +grammar SimpleLanguage; + +// Name lookup, assignable operands, type compatibility, and access permissions +// are checked by the compiler in addition to these syntax rules. +program : statement* EOF ; + +block : '{' statement* '}' ; + +statement + : block + | returnStatement + | ifStatement + | whileStatement + | forStatement + | breakStatement + | continueStatement + | structDeclaration + | declaration ';' + | assignment ';' + | ';' + ; + +returnStatement : 'return' assignment ';' ; +ifStatement : 'if' '(' assignment ')' statement ('else' statement)? ; +whileStatement : 'while' '(' assignment ')' statement ; +breakStatement : 'break' ';' ; +continueStatement : 'continue' ';' ; + +forStatement + : 'for' '(' (declaration | assignment)? ';' assignment? ';' assignment? ')' statement + ; + +structDeclaration : 'struct' IDENTIFIER block ';' ; + +declaration : declarationType binding (',' binding)* ; +declarationType : type | 'var' | 'val' ; +binding : bindingModifier? IDENTIFIER ('=' initializer)? ; +bindingModifier : '!' | '~' ; +initializer : assignment ; + +// The prefix qualifies the named struct reference, not an enclosing array. +// Each [] / [~] suffix qualifies its own array layer. ? requires a reference. +type + : functionType + | primitiveType typeSuffix* + | accessModifier? typeName typeSuffix* + ; +accessModifier : '!' | '~' ; +typeSuffix : '?' | '[]' | '[~]' ; +typeName : IDENTIFIER ; + +primitiveType + : 'int' | 'i8' | 'i16' | 'i32' | 'i64' + | 'u1' | 'u8' | 'u16' | 'u32' | 'byte' | 'bool' + | 'flt' | 'f32' | 'f64' + ; + +assignment : expression (assignmentOperator assignment)? ; +assignmentOperator + : '=' | '+=' | '-=' | '*=' | '/=' | '&=' | '|=' | '^=' + | '<<=' | '>>=' | '>>>=' + ; + +expression : bitwiseExpression ('?' assignment (':' assignment)?)? ; + +bitwiseExpression + : comparisonExpression (('&' | '|' | '^') comparisonExpression)* + ; + +comparisonExpression + : shiftExpression (('==' | '!=' | '<' | '<=' | '>' | '>=') shiftExpression)* + ; + +shiftExpression + : additiveExpression (('<<' | '>>' | '>>>') additiveExpression)* + ; + +additiveExpression + : multiplicativeExpression (('+' | '-') multiplicativeExpression)* + ; + +multiplicativeExpression + : unaryExpression (('*' | '/') unaryExpression)* + ; + +unaryExpression + : ('-' | '!') unaryExpression + | updateOperator IDENTIFIER + | postfixExpression + ; + +// Prefix updates require a local name; postfix updates also allow fields +// and array elements. All updates require an assignable operand. +updateOperator : '++' | '--' ; +postfixExpression : primaryExpression postfixSuffix* updateOperator? ; +postfixSuffix : '.' IDENTIFIER | '[' assignment ']' | '#' | callSuffix ; + +primaryExpression + : INTEGER_LITERAL + | FLOAT_LITERAL + | 'true' + | 'false' + | 'null' + | IDENTIFIER + | '(' assignment ')' + | newExpression + | functionExpression + ; + +// A function is an expression bound with an ordinary declaration. +// Its last statement supplies the implicit result; return can exit earlier. +functionExpression : '{' parameterList? '->' statement* '}' ; +parameterList : (parameter ','?)+ ; +parameter : type bindingModifier? IDENTIFIER ; + +// Function types have unnamed, space-separated argument types. +// {int} is the zero-argument type; its function expression still uses ->. +functionType + : '{' type+ '->' type '}' '?'? + | '{' type '}' '?'? + ; + +callSuffix : '(' argumentList? ')' ; +argumentList : assignment (',' assignment)* ','? ; + +// Struct initialization uses a block; array allocation supplies a length. +newExpression : 'new' type ('[' assignment ']' | block)? ; + +INTEGER_LITERAL : '0' | [1-9] DIGIT* ; +FLOAT_LITERAL : DIGIT+ ('.' DIGIT* ('e' DIGIT+)? | 'e' '-'? DIGIT+) ; +IDENTIFIER : NON_DIGIT (NON_DIGIT | DIGIT)* ; +fragment NON_DIGIT : [a-zA-Z_] ; +fragment DIGIT : [0-9] ; +WHITESPACE : [ \t\r\n]+ -> skip ; +LINE_COMMENT : '//' ~[\r\n]* -> skip ; +``` diff --git a/docs/chapter21/21-grammar.md b/docs/chapter21/21-grammar.md new file mode 100644 index 00000000..111957a1 --- /dev/null +++ b/docs/chapter21/21-grammar.md @@ -0,0 +1,141 @@ +# Grammar for Chapter 21 + +English | [日本語](21-grammar.ja.md) + +```antlrv4 +grammar SimpleLanguage; + +// Name lookup, assignable operands, type compatibility, and access permissions +// are checked by the compiler in addition to these syntax rules. +program : statement* EOF ; + +block : '{' statement* '}' ; + +statement + : block + | returnStatement + | ifStatement + | whileStatement + | forStatement + | breakStatement + | continueStatement + | structDeclaration + | declaration ';' + | assignment ';' + | ';' + ; + +returnStatement : 'return' assignment ';' ; +ifStatement : 'if' '(' assignment ')' statement ('else' statement)? ; +whileStatement : 'while' '(' assignment ')' statement ; +breakStatement : 'break' ';' ; +continueStatement : 'continue' ';' ; + +forStatement + : 'for' '(' (declaration | assignment)? ';' assignment? ';' assignment? ')' statement + ; + +structDeclaration : 'struct' IDENTIFIER block ';' ; + +declaration : declarationType binding (',' binding)* ; +declarationType : type | 'var' | 'val' ; +binding : bindingModifier? IDENTIFIER ('=' initializer)? ; +bindingModifier : '!' | '~' ; +initializer : assignment ; + +// The prefix qualifies the named struct reference, not an enclosing array. +// Each [] / [~] suffix qualifies its own array layer. ? requires a reference. +type + : functionType + | primitiveType typeSuffix* + | accessModifier? typeName typeSuffix* + ; +accessModifier : '!' | '~' ; +typeSuffix : '?' | '[]' | '[~]' ; +typeName : IDENTIFIER ; + +primitiveType + : 'int' | 'i8' | 'i16' | 'i32' | 'i64' + | 'u1' | 'u8' | 'u16' | 'u32' | 'byte' | 'bool' + | 'flt' | 'f32' | 'f64' + ; + +assignment : expression (assignmentOperator assignment)? ; +assignmentOperator + : '=' | '+=' | '-=' | '*=' | '/=' | '&=' | '|=' | '^=' + | '<<=' | '>>=' | '>>>=' + ; + +expression : bitwiseExpression ('?' assignment (':' assignment)?)? ; + +bitwiseExpression + : comparisonExpression (('&' | '|' | '^') comparisonExpression)* + ; + +comparisonExpression + : shiftExpression (('==' | '!=' | '<' | '<=' | '>' | '>=') shiftExpression)* + ; + +shiftExpression + : additiveExpression (('<<' | '>>' | '>>>') additiveExpression)* + ; + +additiveExpression + : multiplicativeExpression (('+' | '-') multiplicativeExpression)* + ; + +multiplicativeExpression + : unaryExpression (('*' | '/') unaryExpression)* + ; + +unaryExpression + : ('-' | '!') unaryExpression + | updateOperator IDENTIFIER + | postfixExpression + ; + +// Prefix updates require a local name; postfix updates also allow fields +// and array elements. All updates require an assignable operand. +updateOperator : '++' | '--' ; +postfixExpression : primaryExpression postfixSuffix* updateOperator? ; +postfixSuffix : '.' IDENTIFIER | '[' assignment ']' | '#' | callSuffix ; + +primaryExpression + : INTEGER_LITERAL + | FLOAT_LITERAL + | 'true' + | 'false' + | 'null' + | IDENTIFIER + | '(' assignment ')' + | newExpression + | functionExpression + ; + +// A function is an expression bound with an ordinary declaration. +// Its last statement supplies the implicit result; return can exit earlier. +functionExpression : '{' parameterList? '->' statement* '}' ; +parameterList : (parameter ','?)+ ; +parameter : type bindingModifier? IDENTIFIER ; + +// Function types have unnamed, space-separated argument types. +// {int} is the zero-argument type; its function expression still uses ->. +functionType + : '{' type+ '->' type '}' '?'? + | '{' type '}' '?'? + ; + +callSuffix : '(' argumentList? ')' ; +argumentList : assignment (',' assignment)* ','? ; + +// Struct initialization uses a block; array allocation supplies a length. +newExpression : 'new' type ('[' assignment ']' | block)? ; + +INTEGER_LITERAL : '0' | [1-9] DIGIT* ; +FLOAT_LITERAL : DIGIT+ ('.' DIGIT* ('e' DIGIT+)? | 'e' '-'? DIGIT+) ; +IDENTIFIER : NON_DIGIT (NON_DIGIT | DIGIT)* ; +fragment NON_DIGIT : [a-zA-Z_] ; +fragment DIGIT : [0-9] ; +WHITESPACE : [ \t\r\n]+ -> skip ; +LINE_COMMENT : '//' ~[\r\n]* -> skip ; +``` diff --git a/docs/chapter21/Makefile b/docs/chapter21/Makefile new file mode 100644 index 00000000..12659b8f --- /dev/null +++ b/docs/chapter21/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/chapter21/README.ja.md b/docs/chapter21/README.ja.md new file mode 100644 index 00000000..058e7cff --- /dev/null +++ b/docs/chapter21/README.ja.md @@ -0,0 +1,120 @@ +# 第21章: 命令エンコーディングと ELF + +[English](README.md) | 日本語 + +この文書は [原文](README.md) の日本語訳です。 + +[前の章: 第20章](../chapter20/README.ja.md) | +[次の章: 第22章](../chapter22/README.ja.md) + +この章では、スケジューリングとレジスタ割り当てを終えたグラフを実行可能な命令に変換します。各マシンノードがバイトを書き込み、コンパイラがブロックを配置し、局所参照を解決し、リンカ向けの ELF オブジェクトを出力します。これで生成コードを実行できるため、命令選択、レジスタ割り当て、呼び出し規約も一緒にテストできます。 + +直線的なリビジョン履歴で [この章](https://github.com/SeaOfNodes/Simple/tree/linear-chapter21) を読み、[第20章と比較](https://github.com/SeaOfNodes/Simple/compare/linear-chapter20...linear-chapter21) することもできます。 + +この章の[完全な言語文法](21-grammar.ja.md)はこちらです。 + +## マシンノードからバイトへ + +命令選択は演算とレジスタ制約を選びます。レジスタ割り当ては物理レジスタとスタックスロットを割り当てます。その後、エンコーディングがオペコード、割り当て済みレジスタ、即値オペランドを命令バイトへ組み合わせます。この時点で命令は別のレジスタを選べません。周囲の命令が、すでに割り当て器の選択に依存しているためです。 + +[Encoding.java](../../src/main/java/com/seaofnodes/simple/codegen/Encoding.java) がバイトストリームを所有し、各ノードのオフセットとエンコード後の長さを記録します。ドライバは次を行います。 + +1. 基本ブロックを配置し、必要に応じて分岐を挿入または反転する。 +2. スケジュール順に `MachNode.encoding(Encoding)` を呼ぶ。 +3. 短い命令形式と長い命令形式を確定する。 +4. このコンパイル内に対象がある参照を修正する。 + +補助関数 `add1`、`add2`、`add4`、`add8` は、リトルエンディアンの値を追加します。エンコーディング関数は `Encoding.reg` で割り当て済みレジスタを得ます。スタックオペランドは、保存レジスタや出力引数の領域も含む、関数のフレーム配置を使います。彩色成功だけでは呼び出し規約の正しさは証明できません。生成プログラムは呼び出し側の値も保存しなければなりません。 + +## 3つの命令セット + +対象はこのドライバを共有します。マシンノードがコンパイラのオペランドを解決し、共有の [ISA エンコーダ](../../isa/README.md) を呼びます。 + +| 対象 | 主なエンコーディング上の課題 | +|---|---| +| RISC-V | 命令語内のレジスタと即値のフィールド。大きい定数とアドレスには複数命令が必要。 | +| AArch64 | レジスタ形式、制限された即値エンコーディング、複数命令による定数構築。 | +| x86-64 | 可変命令長、レジスタ接頭辞、アドレッシング形式、短い分岐と長い分岐。 | + +たとえば小さな整数は命令に直接収まる一方、大きな整数には複数命令や定数プールからのロードが必要なことがあります。その結果、固定幅命令を持つ対象でも、マシンノードによって異なるバイト数を出力できます。 + +[エンコーディングリファレンス](encoding-reference.ja.md) は、詳細なビット配置、具体例、ISA リンクを保持しています。個々の命令実装ではそれらの詳細が重要ですが、ここで説明するコンパイラのパイプラインは変わりません。マシンノードの接続は [node/cpus](../../src/main/java/com/seaofnodes/simple/node/cpus) にあります。共有の [X86 エンコーダ](../../isa/src/main/java/com/seaofnodes/isa/X86.java) が x86 のバイト出力を所有し、[Arm64](../../isa/src/main/java/com/seaofnodes/isa/Arm64.java) と [RiscV](../../isa/src/main/java/com/seaofnodes/isa/RiscV.java) は命令語を詰めます。 + +たとえば `AddX86` はオペコード `0x03` を選びます。その基底 `RegX86` は割り当て済みの2レジスタを読み、`X86.reg(enc,opcode(),dst,src)` を呼びます。共有メソッドは REX.W、オペコード、ModRM を `Encoding` のバイトバッファへ直接書き込みます。`add r9,r10` のバイト列は `4d 03 ca` です。レジスタ制約と2アドレスの要求は、この章のマシンノードに残ります。これによって、ADD を使う判断と、命令の固定されたビット表現を分離します。 + +ロードも同じ境界を使います。この章は、型と割り当て済みオペランドから、幅、符号拡張、レジスタバンクを解決します。`X86.load` はそれらの情報と具体的なアドレスを受け取り、この章のグラフや型の束に依存しません。後の章はコンパイラが進化しても、これらのエンコーディングを再利用します。 + +ARM と RISC-V の実行テストも `isa/src/test-support` の評価器を再利用します。この章のテストハーネスがメモリイメージを準備してリンクし、評価器がそのバイト列を実行します。こうして命令の意味を1か所に保持しつつ、イメージ構築は各章のコンパイラに従います。評価器はテストとともにコンパイルします。Make のリリース jar は、引き続きコンパイラとテストハーネスの両方を同梱します。 + +## 再配置と ELF + +前方分岐は、最初にバイトを書き込む時点では最終的な変位を知りません。対象のオフセットは、間にあるすべてのエンコーディングに依存します。別の分岐を短縮・拡張すると、対象が再び動く場合があります。配置走査が命令サイズを確定してから、局所再配置が最終的な変位を修正します。 + +外部参照には、後での修正が必要です。たとえばコンパイラは、リンカが `malloc` をどこへ配置するか知らなくても、その呼び出しを出力できます。オブジェクトファイルはシンボル、修正位置、再配置種別を記録します。種別は命令と一致しなければなりません。x86 の呼び出し変位と、分割された RISC-V のアドレスは別のフィールドを占めるので、同じ修正演算は使えません。 + +[ElfFile.java](../../src/main/java/com/seaofnodes/simple/codegen/ElfFile.java) は、オブジェクトのコード、データ、シンボル、再配置記録を書き込みます。大きな定数も外部呼び出しと同じ配置問題を生じます。最終的なアドレスを、それらをロードする命令に接続しなければなりません。局所再配置とオブジェクトファイル再配置は、参照を正しくするという同じ責務の2段階です。 + +## 結果の実行 + +第11章の遅延メモリ分割が、エンコーディングまで引き継がれます。パラメータ、呼び出し、戻りはメモリ全体を運び、`BulkMemPhi`、`MemPhi`、`MemMerge` が各関数内の正確なエイリアスを発見します。New は `{ctrl, $mem, size}` を消費し、`{ptr, $mem}` を生成します。そのメモリは割り当てた構造体のエイリアスを覆い、周囲のマージは無関係なエイリアスを保持します。非ゼロ初期化は依然として明示的な Store を使います。メモリノードと順序付け辺はレジスタを必要としません。 + +スケジューリングは集合体を通じてエイリアスをたどり、対応する Store、割り当て、呼び出しの前に読み取りを順序付けます。Call は順序付けオペランドを追加せず、引数リストを保持します。合成されたループ出口は、エイリアスごとに正確なメモリを集めます。 + +実行の回帰テストは、コンストラクタ、インライン化されない呼び出しと再帰呼び出し、インライン化、配列更新を、ネイティブ x86 と両エミュレータで検証します。整数が XMM にある場合もあるため、全幅の x86 ロードはエンコーディング選択時に割り当て済みのレジスタバンクを使います。ARM エミュレーションは、これらの確認で使う大きな定数に対する MOVZ/MOVK の半語構築を処理します。 + +`make tests` は、生成 x86 コードをリンクして実行する C ハーネスと、RISC-V/AArch64 エミュレータの確認を含みます。結果とスピル数の両方を比較します。ネイティブプロセスの失敗は完全な終了ステータスを保持します。エミュレータの確認は、自身の結果メモリを読まなければなりません。別の対象の結果と一致するだけでは不十分です。 + +`make spill-stats` を実行すると、アサーションを有効にして同じテストから割り当てを収集します。各テスト/CPU/ABI を報告してから、群の合計を表示します。スピルの期待値との不一致は、残りの対象や実行確認を省略せず記録します。そのような不一致が1つでもあればコマンドは失敗します。構造的な割り当て器の回帰テストはスピル合計から除外します。 + +## RegAlloc の改良: コピーの除去 + +分割は2つの生存区間間にコピーを挿入します。両方を安全に1区間へまとめられるなら、彩色前にコピーを除去できます。[Coalesce.java](../../src/main/java/com/seaofnodes/simple/codegen/Coalesce.java) は、区間が干渉しないことを確認し、許可レジスタマスクの共通部分を取り、隣接ノードをまとめます。結果の隣接ノード数が利用可能レジスタ数より小さいときだけ併合します。この単純で保守的な規則は、各隣接ノードが異なるレジスタを取っても、色の選択肢を残します。 + +併合は隣接するノードの隣接リストも更新し、削除したコピーへの代表参照を除去します。拒否された併合は元の隣接リストを復元します。マスクの互換性だけでは不十分です。同時に生存する2値は、両方が同じレジスタを許しているだけでは共有できません。 + +彩色後にも、直後の命令に1つだけ使用があるコピーは、そのオペランドがコピー元レジスタを受け入れ、2アドレスのオペランドでなければ除去できます。たとえば `mov rpc=s8; st1 [rpc+4],s1` は `st1 [s8+4],s1` になります。隣接していることで、間にコピー元を上書きするものがないと証明できます。Phi と制御フローの使用側は除外します。この局所整理はスピル選択を変更せず、別の CFG 解析をせずに併合を補完します。 + +互換性のある再実体化や、破壊を考慮するコピー再利用など、第20章の合法性と進捗の修正も引き継ぎます。第21章はネイティブフレームと ABI のサポートを保持します。より強い色バイアスと安いスピルの順位付けは第22章、多数の使用のグループ化は第23章、低頻度側を先にするループ分割は第24章、面積/コストの順位付けは第25章に残します。 + +以下は **この章のコンパイラ** を使い、最適化器の既定シード123で Windows x86-64 上で測定した合計です。分割はレジスタ移動を含む残存 `SplitNode` であり、必ずしもメモリスピルではありません。重み付き数は各分割に `8^loopDepth` を掛けます。 + +| プログラム群 | コンパイル数 | 残存する移動 | ループ重み付き移動 | +|---|---:|---:|---:| +| 第20章 | 39 | 380 | 576 | +| 第21章 | 52 | 467 | 1,076 | +| **合計** | **91** | **847** | **1,652** | + +第20章の行は、BrainFuck と MergeSort を含む元の13入力すべてを、3つの SystemV 対象について固定しています。これらは `Chapter20Test` にあります。以前そのファイルにあった改訂済みソース/ABI ケースは `Chapter21AllocTest` に保持し、`Chapter21Test`、ネイティブ BrainFuck/MergeSort テストと合わせて第21章の行を構成します。その行には ARM/SystemV が17、RISC-V/SystemV が17、x86/SystemV が8、x86/Win64 が10コンパイルあります。ネイティブホスト ABI の変更はこの行に影響するため、同じホストと対象で比較してください。 + +隣接コピー転送の前、メモリ移植によって、群の構成と割り当て器のヒューリスティックを変えずに、以前の合計840/1,456が845/1,559になりました。変化した行は以下のすべてです。各項目は残存/ループ重み付き分割です。 + +| 群/プログラム | 対象/ABI | 変更前 | 遅延メモリ | +|---|---|---:|---:| +| 20 / MergeSort | ARM / SystemV | 44 / 44 | 41 / 41 | +| 20 / BrainFuck | RISC-V / SystemV | 35 / 42 | 37 / 58 | +| 20 / Alloc2 | x86 / SystemV | 3 / 3 | 4 / 4 | +| 20 / Alloc2 | ARM / SystemV | 10 / 10 | 9 / 9 | +| 20 / String | x86 / SystemV | 7 / 14 | 8 / 15 | +| 20 / String | RISC-V / SystemV | 14 / 14 | 10 / 10 | +| 20 / String | ARM / SystemV | 13 / 13 | 10 / 10 | +| 21 / Sieve | x86 / Win64 | 24 / 178 | 24 / 185 | +| 21 / Sieve | RISC-V / SystemV | 19 / 89 | 22 / 92 | +| 21 / Sieve | ARM / SystemV | 23 / 93 | 21 / 91 | +| 21 / BrainFuck | RISC-V / SystemV | 35 / 42 | 46 / 130 | + +重み付き増加の大半は RISC-V BrainFuck によるものです。read-before-New の順序付けだけを省く診断実行では、その2行は35/42と37/58になります。これでコストの大半を説明できますが、コンパイラは順序制約を保持します。メモリ移植にヒューリスティックの調整は含めていません。 + +続く Load 検索の改良は、ループの N 方向メモリマージを通じて BrainFuck の固定プログラム長を畳み込みます。すべての後退辺の枝が畳み込めるか、同じポインタについて変更されていないメモリへ戻る必要があります。RISC-V の Hello World 実行で907回のヒープロードを除去し、スタックアクセスを1,006ロード/572ストアから15/15へ減らします。コピー転送前、その行は144移動/165重み付き移動で、91項目全体は943/1,594でした。隣接コピー転送は、それぞれ35/42と821/1,444へ減らします。RISC-V の Hello World 実行は、ヒープとスタックのアクセス数を変えずに、21,920ではなく21,787命令を実行します。これらはエミュレータの命令数で、ハードウェアの時間測定ではありません。現在のネイティブ BrainFuck テストと、すべての実行時アサーションは有効です。 + +その後 GCM の反依存関係の支配ノード走査を復元したことで、RISC-V BrainFuck の各行は35/42から48/146へ変わり、上の現在の表になりました。条件付き書き手は、後の Load 配置を支配するとは限らないため、書き手の正確なブロックだけを確認すると、必要な順序付けを見逃します。復元した走査は、条件付きストア前の読み取りプログラムが120でなく140を返していた問題を修正します。両方の BrainFuck 実行確認と、91項目全体のスピル監査が成功しています。移動数の増加は、割り当て器のヒューリスティックを変えずに記録しています。 + +次の併合比較は遅延メモリより前のものです。両側はその監査の同じコンパイラを使います。現在のグラフに対する新たな機能無効化比較ではありません。 + +| 以前の制御された比較、同じ91コンパイル | 分割 | ループ重み付き分割 | +|---|---:|---:| +| 併合を無効にした監査コンパイラ | 1,070 | 1,889 | +| 併合を有効にした監査コンパイラ | 840 | 1,456 | + +制御実行は `graphColor` 内の `Coalesce.coalesce` だけを無効にします。両実行で、割り当て、レジスタ制約、実行時確認が成功します。無効化した実行は、スピル期待値の9件の変化を報告し、意図どおり失敗終了します。併合は230移動(21.5%)と433重み付き移動(22.9%)を減らします。局所的な増加は起きる場合があり、1例ではなく集計がトレードオフを判断します。 + +最終監査は x86/RISC-V の狭いストアマスクをバックポートし、ARM のレジスタバンク選択と浮動小数点メモリ演算を修正しました。バイトストアを合法なレジスタに制限すると、RISC-V BrainFuck は2つの群それぞれで211から42重み付き移動となり、以前の表から44の単純移動/338の重み付き移動が減ります。これらの正しさの修正は比較の両側に含まれます。より広いマスクで行った以前の測定は過去の記録で、現在の基準値ではありません。第20章の357重み付き移動も併合の基準値ではありません。第21章は機械命令への変換を変更し、ネイティブ ABI の責務を実装しているためです。 diff --git a/docs/chapter21/README.md b/docs/chapter21/README.md new file mode 100644 index 00000000..03b25759 --- /dev/null +++ b/docs/chapter21/README.md @@ -0,0 +1,170 @@ +# Chapter 21: Instruction Encoding and ELF + +English | [日本語](README.ja.md) + +[Previous: Chapter 20](../chapter20/README.md) | +[Next: Chapter 22](../chapter22/README.md) + +This chapter turns the scheduled, register-allocated graph into executable +instructions. Each machine node writes its bytes; the compiler places blocks, +resolves local references, and exports an ELF object for the linker. We can now +run generated code, which also tests instruction selection, register allocation, +and calling conventions together. + +You can also read [this chapter](https://github.com/SeaOfNodes/Simple/tree/linear-chapter21) +in the linear revision history and +[compare it to Chapter 20](https://github.com/SeaOfNodes/Simple/compare/linear-chapter20...linear-chapter21). + +Here is the [complete language grammar](21-grammar.md) for this chapter. + +## From machine nodes to bytes + +Instruction selection chooses the operation and register constraints. Register +allocation assigns physical registers and stack slots. Encoding then combines +the opcode, assigned registers, and immediate operands into instruction bytes. +An instruction cannot choose a different register at this point: the surrounding +instructions already depend on the allocator's choices. + +[Encoding.java](../../src/main/java/com/seaofnodes/simple/codegen/Encoding.java) owns the +byte stream and records each node's offset and encoded length. Its driver: + +1. Lays out basic blocks and inserts or reverses branches as needed. +2. Calls `MachNode.encoding(Encoding)` in scheduled order. +3. Settles short and long instruction forms. +4. Patches references whose targets are inside this compilation. + +The `add1`, `add2`, `add4`, and `add8` helpers append little-endian values. Encoding +functions obtain allocated registers through `Encoding.reg`. Stack operands use +the function's frame layout, including space for saved registers and outgoing +arguments. A successful coloring alone does not establish a correct calling +convention; the generated program must preserve the caller's values too. + +## Three instruction sets + +The targets share this driver. Machine nodes resolve compiler operands, then +call the shared [ISA encoders](../../isa/README.md). + +| Target | Main encoding concerns | +|---|---| +| RISC-V | Register and immediate fields in instruction words; larger constants and addresses need multiple instructions. | +| AArch64 | Register forms, restricted immediate encodings, and multi-instruction constant construction. | +| x86-64 | Variable instruction lengths, register prefixes, addressing forms, and short versus long branches. | + +For example, a small integer can fit directly in an instruction, while a large +one may require several instructions or a load from a constant pool. Consequently, +even a target with fixed-width instructions can emit different numbers of bytes +for different machine nodes. + +The [encoding reference](encoding-reference.md) retains the detailed bit +layouts, worked examples, and ISA links. Those details matter when implementing +a particular instruction, but do not change the compiler pipeline described here. +The machine-node wiring lives under +[node/cpus](../../src/main/java/com/seaofnodes/simple/node/cpus). The shared +[X86 encoder](../../isa/src/main/java/com/seaofnodes/isa/X86.java) owns x86 byte +emission; [Arm64](../../isa/src/main/java/com/seaofnodes/isa/Arm64.java) and +[RiscV](../../isa/src/main/java/com/seaofnodes/isa/RiscV.java) pack instruction words. + +For example, `AddX86` chooses opcode `0x03`. Its `RegX86` base reads the two +allocated registers and calls `X86.reg(enc,opcode(),dst,src)`. The shared method +writes REX.W, the opcode, and ModRM directly into `Encoding`'s byte buffer. +For `add r9,r10`, the bytes are `4d 03 ca`. Register constraints and the +two-address requirement remain on the chapter's machine node. This separates +the decision to use ADD from the fixed bit representation of that instruction. + +Loads use the same boundary: the chapter resolves width, sign extension, and +register bank from its types and allocated operands. `X86.load` receives those +facts and the concrete address; it has no dependency on the chapter's graph or +type lattice. Later chapters reuse these encodings as their compiler evolves. + +ARM and RISC-V execution tests also reuse the evaluators in `isa/src/test-support`. +The chapter's test harness prepares and links a memory image; the evaluator +executes its bytes. This keeps instruction semantics in one place while image +construction follows each chapter's compiler. They compile with the tests; +Make's release jar continues to bundle the test harness as well as the compiler. + +## Relocation and ELF + +A forward branch cannot know its final displacement when its bytes are first +written. Its target's offset depends on all the intervening encodings. Shortening +or expanding another branch can move that target again. The layout pass settles +instruction sizes before local relocations patch the final displacements. + +External references need a later patch. For example, the compiler can emit a +call to `malloc` without knowing where the linker will place it. The object file +records the symbol, patch location, and relocation kind. That kind must agree +with the instruction: an x86 call displacement and a split RISC-V address occupy +different fields and cannot use the same patch operation. + +[ElfFile.java](../../src/main/java/com/seaofnodes/simple/codegen/ElfFile.java) writes the +object's code, data, symbols, and relocation records. Large constants introduce +the same placement problem as external calls: their eventual addresses must be +connected to the instructions that load them. Local relocation and object-file +relocation are two stages of the same obligation to make references correct. + +## Execute the result + +Chapter 11's lazy memory partitioning now carries through encoding. Parameters, +calls, and returns carry whole memory; `BulkMemPhi`, `MemPhi`, and `MemMerge` +discover precise aliases inside each function. New consumes `{ctrl, $mem, size}` +and produces `{ptr, $mem}`. Its memory covers the allocated struct's aliases; +the surrounding merge retains unrelated aliases. Nonzero initialization still +uses explicit Stores. Memory nodes and ordering edges require no registers. + +Scheduling follows aliases through aggregates to order reads before matching +Stores, allocations, and calls. Calls retain their argument lists, with no added +ordering operands. Synthetic loop exits collect precise memory by alias. + +The execution regressions cover constructors, non-inlined and recursive calls, +inlining, and array updates, using native x86 and both emulators. A full-width +x86 load uses the allocated register bank when choosing its encoding, since an +integer may reside in XMM. ARM emulation handles MOVZ/MOVK halfword construction +for the large constants in these checks. + +`make tests` includes C harnesses that link and run generated x86 code, and +RISC-V/AArch64 emulator checks. The tests compare results as well as spill counts. +Native process failures retain their full exit status. Emulator checks must read +their own result memory; matching another target's result is insufficient. + +Run `make spill-stats` to collect allocations from the same tests with assertions +enabled. It reports each test/CPU/ABI, then cohort totals. A spill-golden mismatch +is recorded without skipping the remaining targets or execution checks; any such +mismatch still makes the command fail. Structural allocator regressions are +excluded from the spill totals. + +## RegAlloc improvements: eliminating copies + +A split inserts a copy between two live ranges. If both can safely become one +range, the copy can disappear before coloring. [Coalesce.java](../../src/main/java/com/seaofnodes/simple/codegen/Coalesce.java) +checks that the ranges do not interfere, intersects their allowed-register +masks, and combines their neighbors. It merges only when the resulting +neighbor count is smaller than the number of available registers - meaning it +can always color. + +After coloring, a copy with one use in the immediately following instruction +can also disappear when that operand accepts the source register and is not +its two-address operand. For example, `mov rpc=s8; st1 [rpc+4],s1` becomes +`st1 [s8+4],s1`. Adjacency proves that nothing overwrites the source in between. +Phi and control-flow users are excluded. This local cleanup leaves spill +selection unchanged; it complements coalescing without another CFG analysis. + +For looking at allocator progress, we will keep a table of execution and allocation costs +a chapters' tests - and use the later chapter compiler and register allocator +on them. The later chapters also modify the graph (sometimes adding boiler +plate graph) so its not really a 100% fair comparison of how well this +*heuristic* does, but it does give us some idea that we're not losing ground. + +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 21: Ops / RA / X | +|---|---:|---:|---:| +| Ch 20 | 22 / 13 | — / — / 161 | 55,173 / 5,407 / 174 | +| Ch 21 | 20 / 18 | | 6,207 / 261 / 461 | +<!-- spill-matrix:end --> + diff --git a/docs/chapter21/encoding-reference.ja.md b/docs/chapter21/encoding-reference.ja.md new file mode 100644 index 00000000..c93d2947 --- /dev/null +++ b/docs/chapter21/encoding-reference.ja.md @@ -0,0 +1,776 @@ +# 命令エンコーディングの参考資料 + +[English](encoding-reference.md) | 日本語 + +この文書は [Instruction encoding reference](encoding-reference.md) の日本語訳です。 +元の第21章の README から、詳細な説明を残したものです。 +コンパイラのパイプラインと実験については[章の概要](README.ja.md)を参照してください。 + +# 目次 + +1. [命令エンコーディング](#命令エンコーディング) +2. [基盤](#基盤) +3. [エンディアン](#エンディアン) +4. [RISCV](#riscv) + - [命令形式](#命令形式) + - [浮動小数点数](#浮動小数点数) + - [間接参照](#間接参照memop) + - [分岐](#分岐) + - [即値](#即値) + - [関数内の定数](#関数内の定数) +5. [ARM](#arm) + - [レジスタ形式](#レジスタ形式) + - [即値シフト](#即値シフト) + - [論理演算の即値](#論理演算の即値) + - [浮動小数点数](#浮動小数点数-1) + - [大きな定数](#大きな定数) + - [関数定数](#関数定数) + - [条件フラグ](#条件フラグ) + - [間接参照](#間接参照memop-1) + - [分岐](#分岐-1) +6. [AMD64 X86-64](#amd64-x86-64) + - [REX プレフィックス](#rex-プレフィックス) + - [MODR/M](#modrm) + - [SIB](#sib) + - [変位](#変位) + - [即値](#即値-1) + - [浮動小数点数](#浮動小数点数-2) + - [大きな定数](#大きな定数-1) + - [関数定数](#関数定数-1) + - [条件フラグ](#条件フラグ-1) +7. [再配置](#再配置) + +この章では命令エンコーディングを追加します。命令エンコーディングとは、 +プロセッサの命令セットアーキテクチャ(ISA)が定義する特定のバイナリ形式で機械命令を表すことです。 + +命令選択が完了したら、ELF オブジェクトファイルに書き出す前に、命令をエンコードします。 + +曖昧さを避けるため、命令のエンコードに使う情報の出典を明示します。 +現在、次のアーキテクチャをサポートしています。 + +### RISC-V: + +*riscv* では現在、[RVA23U64](https://msyksphinz-self.github.io/riscv-isadoc/html/) を対象にしています。 + +### ARMV8/AArch64: + +*arm*(aarch64)では、この資料にまとめられた[エンコーディング規則](https://docsmirror.github.io/A64/20a23-06/index.html)を使います。 +*注*:サポートするのは64ビット ARM のエンコーディングだけです。 + +### AMD64: + +*x86-64(amd64)* では、最新の Intel マニュアルの[エンコーディング規則](https://www.felixcloutier.com/x86/)を使います。 + +-------------------------------- + +## 命令エンコーディング + +## 基盤 + +命令選択フェーズは、理想ノードから機械向けのノードを作ります。 +これらの機械向けノードには *encoding* 関数があり、CodeGen 内の Encoding ドライバから呼び出します。 + +```java +for( CFGNode bb : _code._cfg ) + for( Node n : bb._outputs ) + if( n instanceof MachNode mach ) { + int off = _bits.size(); + mach.encoding(this); + _opLen[n._nid] = (byte)(_bits.size()-off); + } +``` + +選んだターゲットに応じて、リトルエンディアンまたはビッグエンディアンのエンコーディングに注意する必要があります。 +これは `add4` が扱い、正しい順序でビットストリームに追加します。 +現時点のビットストリームは、単純な ByteArrayOutputStream です。 + +```java +@Override public void encoding( Encoding enc ) { + enc.add4(0); // adds 4 bytes to the bitstream +} +``` + +資料がビッグエンディアンでエンコーディングを示す *RISC-V* では、 +この関数がリトルエンディアンへの変換も行います。ほかのアーキテクチャでは変換は不要です。 + +## エンディアン + +実際に使う ARM、RISC-V、x86 はすべてリトルエンディアンで、最下位バイトを先に格納します。 +したがって、命令エンコーディングがリトルエンディアン形式で生成されるようにします。 + +```java +// Little endian write of a 32b opcode +public void add4( int op ) { + _bits.write(op ); + _bits.write(op>> 8); + _bits.write(op>>16); + _bits.write(op>>24); +} +``` + +たとえば、固定長命令の32ビットのエンコーディング *10110101100111101011001001101101* を考えます。 + +最下位バイトを最初に追加し、次のバイトを追加していきます。 +``` +_bits.write(op ); +``` + +`_bits.write()` が追加するのは最下位バイトだけです。 +値を右にシフトすると、次のバイトを最下位の位置へ移動でき、各バイトを順番に書き込めます。 + +-------------------------------- + +## RISCV + +RISCV の命令はすべて32ビット幅で、常にリトルエンディアンです。 +RISCV は RISC アーキテクチャなので「レジスタ間」の形式を優先し、エンコーディングは非常に規則的です。 +最も単純なエンコーディングであるため、RISCV から説明します。 + +#### 命令形式 + +##### R-TYPE + +このエンコーディング形式は、`AddRISC`、`MulRISC` などのレジスタ間の演算に使います。 +```java +public static int r_type(int opcode, int rd, int func3, int rs1, int rs2, int func7) { + return (func7 << 25) | (rs2 << 20) | (rs1 << 15) | (func3 << 12) | (rd << 7) | opcode; +} +``` + +##### I-TYPE + +この配置は、`AddIRISC` などの即値形式に使います。 + +```java + public static int i_type(int opcode, int rd, int func3, int rs1, int imm12) { + assert opcode >= 0 && rd >=0 && func3 >=0 && rs1 >=0 && imm12 >= 0; // Zero-extend by caller + return (imm12 << 20) | (rs1 << 15) | (func3 << 12) | (rd << 7) | opcode; + } +``` + +##### S-TYPE + +このエンコーディング形式は `StoreRISC` に使います。 + +```java +public static int s_type(int opcode, int func3, int rs1, int rs2, int imm12) { + assert imm12 >= 0; // Masked to high zero bits by caller + int imm_lo = imm12 & 0x1F; + int imm_hi = imm12 >> 5; + return (imm_hi << 25) | (rs2 << 20) | (rs1 << 15) | (func3 << 12) | (imm_lo << 7) | opcode; +} +``` + +##### B-TYPE + +このエンコーディングの配置は `BranchRISC` が使います。 +```java + // immf = first imm + // immd = second imm + // BRANCH +public static int b_type(int opcode, int immf, int func3, int rs1, int rs2, int immd) { + return (immd << 25 ) | (rs2 << 20) | (rs1 << 15) | (func3 << 12) | (immf << 7) | opcode; + } +``` + +##### U-TYPE + +この配置は `AUIPC` と `LUI` に使います(上位即値は20ビットです)。 + +```java + public static int u_type(int opcode, int rd, int imm20) { + return (imm20 << 12) | (rd << 7) | opcode; + } +``` + +##### J-TYPE + +無条件ジャンプ(`UJmpRISC`)に使います。 +```java + public static int j_type(int opcode, int rd, int imm20) { + return imm20 << 12 | rd << 7 | opcode; + } +``` + +#### 浮動小数点数 + +浮動小数点定数のロードには、間接メモリロード(PC 相対)を使います。 +``` +AUIPC dst,#hi20_constant_pool +Load dst,[dst+#low12_constant_pool] +``` + +#### 間接参照(MemOp) + +[ロード](https://msyksphinz-self.github.io/riscv-isadoc/html/rvi.html#lw):`lw rd,offset(rs1)` + +[ストア](https://msyksphinz-self.github.io/riscv-isadoc/html/rvi.html#sw):`sw rs2,offset(rs1)` + +#### 分岐 + +RISC-V は、分岐命令が比較と分岐先を1つの命令に含むという点で特徴的です。 + +#### 即値 + +*ALU* 演算では、即値が符号付き12ビットに収まる場合だけ、即値形式をエンコードします。 +それ以外では Load Upper Immediate(上位20ビット)を使うか、両方を使います。 + +``` +addiw a5,a5,123 +``` + +これに対して、追加のロードを使う場合は次のようになります。 +``` +addw a5,a5,a4 +``` + +### 浮動小数点定数 + +- **定数プール**に格納します。 +- **PC 相対ロード**でアクセスします。 + +### 整数定数 + +- **12ビット定数**:`ADDI` を使います。 +- **20ビット定数**:`LUI` を使います。 +- **32ビット定数**:`LUI` と `ADDI` を組み合わせます。 + +#### 関数内の定数 + +RISC-V のコードで関数内の定数をロードするときは、現在の PC をレジスタに格納し、 +それを基底として定数へアクセスします。ただし、加算のような単純な算術演算のほうが +メモリアクセスより速く効率的なので、通常はロードを避けるようにします。 + +```java +// auipc t0,0 +// addi t1,t0 + #0 +``` + +-------------------------------- + +## ARM + +Arm の命令はすべて32ビット幅で、常にリトルエンディアンです。 +Arm は RISC アーキテクチャなので、「レジスタ間」の形式を優先します。 + +#### レジスタ形式 + +レジスタ形式は、[シフト付きレジスタ](https://docsmirror.github.io/A64/2023-06/orr_log_shift.html)としてエンコードします。 +``` +orr reg1, reg2 +``` +``` +x0 = x0 | (x1 << 0) +``` + +ほかのレジスタ形式にも同じことが当てはまります。 +この共通性によって、レジスタ形式のエンコーディングを共通関数にまとめられます。 + +```java +short self = enc.reg(n); +short reg1 = enc.reg(n.in(1)); +short reg2 = enc.reg(n.in(2)); +int body = r_reg(opcode, 0, reg2, 0, reg1, self); +enc.add4(body); +``` + +レジスタ形式では *imm6* と *shift* は関係しないため、両方を0に設定します。 + +### 即値シフト + +*asri、lsli、lsri* を指します。シフト量が0以上63以下なら、即値シフトとしてエンコードします。 + +#### ASR(即値) + +即値の算術右シフトでは、*immr* をエンコードしたい即値に、imms を `63` = `111111` に設定します。 +これは [asri のエンコーディング](https://docsmirror.github.io/A64/2023-06/asr_sbfm.html)の規則に従っています。 + +`sf` ビットはオペコードに含まれ、`N` は `imm_shift` 関数の実行後、暗黙にビットストリームへ追加されます。 +```java +enc.add4(arm.imm_shift(0b100100110,_imm, 0b111111, rn, rd)); +``` + +#### LSL(即値) + +```java +// UBFM <Xd>, <Xn>, #(-<shift> MOD 64), #(63-<shift>) +// immr must be (-<shift> MOD 64) = 64 - shift +enc.add4(arm.imm_shift(0b110100110, 64 - _imm, (64 - _imm) - 1, rn, rd)); +``` + +`_imm` は負にならず、0〜63の間にあるので、`64 - _imm` を `immr` の値として安全に使えます。 +この形式の即値を64から引いて、ビットを反転します。 + +*imms* については、条件 *imms + 1 = immr* が成り立たなければなりません。 +値を代入すると、次のようになります。 + +> imms + 1 = 64 - _imm; + +> imms = (64 - _imm) - 1; + +したがって、`imms` の値は `(64 - _imm) - 1` です。 + +#### LSR(即値) + +*ASR* と同じです。 + +#### 論理演算の即値 + +論理演算の即値は *andi、xori、orri* を指します。 + +```java +// Can we encode this in ARM's 12-bit LOGICAL immediate form? +// Some combination of shifted bit-masks. +private static int imm12Logical(TypeInteger ti) { + if( !ti.isConstant() ) return -1; + if( !ti.isConstant() ) return -1; + long val = ti.value(); + if (val == 0 || val == -1) return -1; // Special cases are not allowed + int immr = 0; + // Rotate until we have 0[...]1 + while (val < 0 || (val & 1)==0) { + val = (val >>> 63) | (val << 1); + immr++; + } + int size = 32; + long pattern = val; + // Is upper half of pattern the same as the lower? + while ((pattern & ((1L<<size)-1)) == (pattern >> size)) { + // Then only take one half + pattern >>= size; + size >>= 1; + } + size <<= 1; + int imms = Long.bitCount(pattern); + // Pattern should now be zeros followed by ones 0000011111 + if (pattern != (1L<<imms)-1) return -1; + imms--; + if (size == 64) return 0x1000 | immr << 6 | imms; + return (32-size)<<1 | immr << 6 | imms; +} +``` + +imm12Logical は、エンコードできる場合は論理演算の12ビットの即値エンコーディングを返し、 +できない場合は -1 を返します。 +```java +int imm12; +return and.in(2) instanceof ConstantNode off && +off._con instanceof TypeInteger ti && (imm12 = imm12Logical(ti)) != -1 +? new AndIARM(and, imm12) +``` +```java +arm.imm_inst(enc,this,0b100100100,_imm); +``` + +#### 浮動小数点数 + +x86 と同じように、*D* レジスタに対応する汎用レジスタ(GPR)のペアを得るために、オフセットを引く必要があります。 +``` +short dst = (short)(enc.reg(this ) - arm.D_OFFSET); +``` + +浮動小数点定数をロードするには、`ldr(literal)` 命令を使います。 +オフセットを PC レジスタに加算して、リテラルのアドレスを求めます。 + +```java +// Any number that can be expressed as +/-n * 2-r,where n and r are integers, 16 <= n <= 31, 0 <= r <= 7. +int body = arm.load_pc(0b01011100, 0, dst); +``` + +#### 大きな定数 + +##### 整数:mov(movz、movk、movn)をさまざまに組み合わせて、PC 相対ロードを避けられます。 + +##### 浮動小数点数:PC 相対の *LDR(immediate, SIMD&FP)* 命令を使います。 + +#### 関数定数 + +関数定数をロードするには、現在の PC を x0 に格納し、それを使って定数へアクセスします。 +一般に、加算のほうが速いためロードを避けるようにします。 +``` +adrp x0, 0 +add x0, x0, 0 +``` + +#### 条件フラグ + +arm では比較後に条件フラグを設定します。これは x86 と同じように動作します。 + +``` +int square(int a) { +// subs +// cset w8, eq // eq = none +bool da = a == 1; + // bne + if(da == 1) { + return 1; + } + return 2; +} +``` + +arm の比較は `subss` で行い、2つのオペランドの減算とフラグの設定を同時に行います。 +`CSET` はフラグを調べ、その結果をレジスタに保存します。 + +後で `b.ne` が条件付き分岐を実行しようとするときにも、このフラグに依存します。 + +#### 間接参照(MemOp) + +Arm は次の間接参照形式をサポートします。 + +[ロード](https://docsmirror.github.io/A64/2023-06/ldr_reg_gen.html):`ldr dst, [base + index]`。base と index はどちらもレジスタです。 + +[ロード](https://docsmirror.github.io/A64/2023-06/ldr_imm_gen.html):`ldr dst, [base + #offset]`。base はレジスタ、offset は即値です。 + +[ストア](https://docsmirror.github.io/A64/2023-06/str_reg_gen.html):`str dst, [base + index]` + +[ストア](https://docsmirror.github.io/A64/2023-06/strb_imm.html):`str dst, [base + #offset]` + +オフセットがレジスタの場合は、即値オフセット形式ではなくレジスタオフセット形式を使います。 + +#### 分岐 + +`B.cond` は、PC 相対オフセットのラベルへ[条件付き](https://developer.arm.com/documentation/dui0802/b/A32-and-T32-Instructions/Condition-codes?lang=en)で分岐します。 + +> cond は標準的な条件のいずれかです。 + +-------------------------------- + +## AMD64 X86-64 + +命令幅が固定の riscv アーキテクチャとは異なり、*x86-64* の命令は可変長です。 +これは *CISC(Complex Instruction Set Computer)* アーキテクチャで一般的であり、 +命令幅は1〜15バイトまで変わります。AMD64 は多くの間接アドレッシングモードをサポートするため、 +*CISC* の一般的な目標は、できるだけ少ないアセンブリの行数で処理を完了することです。 + +### REX プレフィックス + +64ビット版の x86 を対象にしているため、このプレフィックスを扱う必要があります。 +一方、32ビットモードでは通常は不要です。 + +一般に、次の場合は *REX* プレフィックスをエンコードしなければなりません。 + +- 拡張レジスタ(R8〜R15、XMM8〜XMM15、YMM8〜YMM15、CR8〜CR15、DR8〜DR15)を使う。 +- オペランドサイズが64ビットで、命令の既定のオペランドサイズが64ビットではない(大部分の ALU 演算)。 +- RSI、RDI、RBP で8ビットのオペランドを使う。 + +次の場合は *REX* プレフィックスをエンコードしてはなりません。 + +- 上位バイトのレジスタ AH、CH、BH、DH を使う(現在の Simple では使っていません)。 + +*注*:SSE 命令のエンコーディングでは、*REX* プレフィックス(0x40)を `SSE` プレフィックスの後に置く必要があります。 +それ以外の場合は無視されます。 + +Simple では、REX プレフィックスをビットストリームの先頭に追加するだけです +(SSE の浮動小数点命令を除く)。配置は次のとおりです。 + +| **フィールド** | **長さ** | **説明** | +|:---------:|:----------:|---------------------------------------------------------------------------------| +| `0100` | 4ビット | 固定のビットパターン | +| `W` | 1ビット | 1なら64ビット、それ以外なら32ビットのオペランドサイズを使う | +| `R` | 1ビット | **MODRM.reg** フィールドの拡張 | +| `X` | 1ビット | **SIB.index** フィールドの拡張 | +| `B` | 1ビット | **MODRM.rm** または **SIB.base** フィールドの拡張 | + +```java + public static int REX_W = 0x48; +``` + +```java +public static int rex(int reg, int ptr, int idx, boolean wide) { + // assuming 64 bit by default so: 0100 1000 + int rex = wide ? REX_W : REX; + if( 8 <= reg && reg <= 15 ) rex |= 0b00000100; // REX.R + if( 8 <= ptr && ptr <= 15 ) rex |= 0b00000001; // REX.B + if( 8 <= idx && idx <= 15 ) rex |= 0b00000010; // REX.X + return rex; +} + + ... + enc.add1(x86_64_v2.rex(dst, src, 0)); +``` + +`W` ビットを設定すると、`0b01001000 = 0x48;` になります。 + +#### オペコード + +この場合、オペコードは実行する操作を指定する1バイトのフィールドです。 +``` +enc.add1(opcode()); // opcode +``` + +#### MODR/M + +| **フィールド** | **長さ** | **説明** | +|:-------------:|:----------:|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **MODRM.mod** | 2ビット | 一般に、このフィールドが `b11` ならレジスタ直接アドレッシングモード、それ以外ならレジスタ間接アドレッシングモードを使う | +| **MODRM.reg** | 3ビット | 2種類の値を取れる。<br><br>• 一部の命令が使う3ビットのオペコード拡張。ほかの命令と区別する以上の意味は持たない。<br><br>• 命令によってソースまたは宛先として使う3ビットのレジスタ参照。参照するレジスタは、命令のオペランドサイズと命令自体に依存する。`REX.R`、`VEX.~R`、`XOP.~R` フィールドによって最上位ビットを1つ追加し、合計4ビットに拡張できる | +| **MODRM.rm** | 3ビット | 直接または間接のレジスタオペランドを指定し、任意で変位を伴う。`REX.B`、`VEX.~B`、`XOP.~B` フィールドによって最上位ビットを1つ追加し、合計4ビットに拡張できる | + +MODR/M バイトも同じように扱います。 + +modrm バイトに最初に必要なのは、2ビットの mod です。 + +##### mod + +mod の配置は次のとおりです。 +``` +public enum MOD { + INDIRECT, // [mem] + INDIRECT_disp8, // [mem + 0x12] + INDIRECT_disp32,// [mem + 0x12345678] + DIRECT, // mem +}; +``` + +##### reg + +reg フィールドは3ビットで、8個のレジスタから1個を選びます。レジスタを指定する場合、 +REX.R によって4ビットに拡張され、16個のレジスタから1個を選べます。 + +##### r/m + +r/m フィールドは3ビットです。レジスタ直接指定モードでは、REX.B によって拡張され、 +16個のレジスタから1個を選べます。メモリ指定モードでは、アドレッシング形式または +ベースレジスタを選びます。一部の値は SIB バイトや RIP 相対アドレッシングを指定します。 + +```java +public static int modrm(MOD mod, int reg, int m_r) { + // combine all the bits + return (mod.ordinal() << 6) | ((reg & 0x07) << 3) | m_r & 0x07; +} +``` + +`modrm` 関数を使って `ModR/M` バイトを構築します。通常、これはオペコードの後に続きます。 + +``` +enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src)); +``` + +レジスタ形式のエンコーディングは、次のようになります。 +``` +short dst = enc.reg(this ); // src1 +short src = enc.reg(in(2)); // src2 +enc.add1(x86_64_v2.rex(dst, src, 0)); +enc.add1(opcode()); // opcode +enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src)); +``` + +#### SIB + +SIB バイトには次のフィールドがあります。 + +| **フィールド** | **長さ** | **説明** | +|:-------------:|:----------:|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **SIB.scale** | 2ビット | `SIB.index` のスケーリング係数を示す。表で使う **s** は 2^SIB.scale^ に等しい | +| **SIB.index** | 3ビット | 使用するインデックスレジスタ。各レジスタの値は Registers を参照。`REX.X`、`VEX.~X`、`XOP.~X` フィールドによって最上位ビットを1つ追加し、合計4ビットに拡張できる | +| **SIB.base** | 3ビット | 使用するベースレジスタ。各レジスタの値は Registers を参照。`REX.B`、`VEX.~B`、`XOP.~B` フィールドによって最上位ビットを1つ追加し、合計4ビットに拡張できる | + +MODR/M バイトと同じ配置です。 +```java +public static int sib(int scale, int index, int base) { + return (scale << 6) | ((index & 0x07) << 3) | base & 0x07; +} +``` + +```java +enc.add1(x86_64_v2.sib(_scale, idx, x86_64_v2.RBP)); +``` + +#### 変位 + +変位には2つの形式があります。 +```java +public enum MOD { +INDIRECT, // [mem] +INDIRECT_disp8, // [mem + 0x12] +INDIRECT_disp32,// [mem + 0x12345678] +DIRECT, // mem +}; +``` + +```java +if( mod == MOD.INDIRECT_disp8 ) { + enc.add1(offset); +} else if( mod == MOD.INDIRECT_disp32 ) { + enc.add4(offset); +} +``` + +変位は即値の前に置きます。たとえば、次のようになります。 +``` +sub [eax + 2], 4 +``` +``` +bytes.write(0x83); +bytes.write(modrm(INDIRECT8, 5, 0 /* eax */)); +bytes.write(2); // displcement goes first +bytes.write(4); +``` + +#### 即値 + +即値のバイト列は `ModR/M` バイトの後に置きます。必要に応じて4バイトの即値をエンコードできます。 +```java +// immediate(4 bytes) 32 bits or (1 byte)8 bits +if( x86_64_v2.imm8(_imm) ) enc.add1(_imm); +else enc.add4(_imm); +``` + +#### 間接参照(MemOp) + +X86-64 は複数の間接アドレッシングモードをサポートします。 + +#### 浮動小数点数 + +浮動小数点演算には、XMM レジスタまたはメモリにあるスカラーの単精度浮動小数点値を操作する +`SSE SIMD` 命令を使います。 +*注*:ALU 演算で XMM レジスタを使う場合、エンコーディングは非常に似ていますが、 +レジスタ番号から `XMM_OFFSET` を引く必要がある点が異なります。 +レジスタを連続した番号で数えているため、XMM レジスタに対応する GPR のペアを得るには、 +オフセットを引く必要があります。 + +```java +short dst = (short)(enc.reg(this ) - x86_64_v2.XMM_OFFSET); +``` + +```java +public static int XMM_OFFSET = 16; +``` + +| **X.Reg** | **8ビット GP** | **16ビット GP** | **32ビット GP** | **64ビット GP** | **80ビット x87** | **64ビット MMX** | **128ビット XMM** | **256ビット YMM** | **16ビット Segment** | **32ビット Control** | **32ビット Debug** | +|:--------:|:------------:|:-------------:|:-------------:|:-------------:|:-------------:|:-------------:|:--------------:|:--------------:|:-----------------:|:-----------------:|:---------------:| +| `0.000 (0)` | AL | AX | EAX | RAX | ST0 | MMX0 | XMM0 | YMM0 | ES | CR0 | DR0 | + +`XMM0` が `RAX` に対応し、以降も同様であることがわかります。 + +#### 大きな定数 + +##### 整数:`MOV r64, imm64` 命令形式を使うだけです。 + +##### 浮動小数点数:再配置を使い、メモリ内の定数を使います。 + +#### 関数定数 + +関数定数をロードするには lea を使います。これは、命令ポインタからの相対アドレスを指定したレジスタにロードします。 +`lea rax, [rip+disp32]` + +#### 条件フラグ + +FLAGS レジスタは、x86 CPU の現在の状態を保持するステータスレジスタです。 + +条件付きコードのためにエンコードする必要のあるすべての命令を示す、単純な例を考えます。 + +``` +int square(int a) { + // cmp DWORD PTR [rbp-0x8],0x1 + // `sete al` + bool da = a == 1; + + // cmp eax,0x1 + if(da == 1) { + return 1; + } + return 2; +} +``` + +まず、`a` と1を比較する比較命令が必要です。 +``` +cmp DWORD PTR [rbp-0x8],0x1 +``` + +この比較命令が条件フラグを設定します。 + +> 結果に従って CF、OF、SF、ZF、AF、PF フラグを設定します。 + +続いて `sete` 命令を使い、比較の結果をレジスタへ保存します。 + +`sete al` + +if 条件のために、最初の真偽値の結果に基づく追加の比較を行います。 +``` + cmp eax,0x1 +``` + +そして、比較(`a == 1`)が設定したフラグに依存する条件付き分岐を行います。 +``` +jne 1159 <square(int)+0x29> +``` + +-------------------------------- + +## 再配置 + +再配置によって、コードを新しいコードオフセットへ*移動*できます。 +コードは、ほかのコードやデータを参照することがよくあります。 +`call` 命令はサブルーチンを、`branches` はほかの命令を対象とし、 +大きな定数は多くの場合*定数プール*からロードします。 + +### 局所的な再配置 + +局所的な再配置は、局所的または自己参照的なコードを修正します。最も一般的なのは分岐先です。 +`BAOS`(Byte Array Output Stream)に命令を書き込む際、後に続く命令へ向かう前方分岐のオフセットは未知です。 +そのオフセットは、分岐と分岐先の間にあるエンコーディングのサイズに依存します。 +`riscv` と `arm` のターゲットでは簡単に見えます(命令は4バイトなので数えればよい)……。 +しかし、複数の命令を使うエンコーディングもあるため、実際には4バイトとは限りません。 +X86 では命令長が擬似的に不規則に見えるほど多様なので、オペコードの長さを求めること自体が、 +オペコードを書き出すのと同じくらい難しくなります。 + +X86 には、短形式と長形式の分岐という追加の複雑さもあります。 +分岐先が符号付きバイトの範囲(+/-127)にあるなら2バイトの分岐エンコーディングを、 +それ以外では6バイトのエンコーディングを使います。 +Simple には、分岐を縮め、できるだけ2バイト形式を使うパスがあります。 +ただし、このパスはコードをずらす必要があり、その結果、ほかの分岐オフセットもすべて変わります。 +この「圧縮」パスは実際には、すべての分岐が2バイト形式を使えると楽観的に仮定して始め、 +必要に応じて分岐を拡張します(したがって、実際にはコードを*拡張*します)。 + +コードの「形」が最終的に決まったら、局所的な再配置パスを実行して自己参照をすべて更新します。 +基本的には、バイト配列内の分岐オフセットを実際の値で上書きします。 + +このパスの終了時点で、コードはあるオフセット(通常は0)で局所的に正しくなります。 +しかし、これで終わりではありません。別のコンパイルで生成されたコードと*リンク*することが多く、 +その際にもさまざまなオフセットを調整する必要があります。 + +### 大域的な再配置 + +あるコンパイル単位のコードから、別のコンパイル単位のコードを呼びたい場合、*大域的な再配置*が必要です。 +よくある例は、*ローダー*が *ELF* ファイルをロードすることです。 +通常は新しいプログラムの実行ごとに起こり、再配置情報をディスク(たとえば ELF ファイル内)に保存しておく必要があります。 +JIT システムでも、異なるコンパイルをまたいで同じことが必要ですが、再配置情報はメモリに保持できます。 + +#### 再配置の整合性を保つ + +再配置をディスクに書き込めるので、ディスク形式、ローダー、「コードの形」がすべて一致しなければなりません。 +あるコンパイル単位から別の単位を呼ぶ場合、たとえばプログラムが `libc` の `malloc` を呼ぶ場合を考えます。 +呼び出し元は Simple の汎用 `CallNode` の機械固有版を使い、呼び出し先は C でコンパイルされたコードです。 +ロード中に、ローダーが `call` と `libc` の配置を決めます。 +2つの異なるコードブロックを何らかの順序で(通常は多くのほかのブロックとともに)メモリに書き込みます。 +そこで、`call` の呼び出し先を、`libc` のコードブロック内の `malloc` に修正する必要があります。 + +X86 の外部呼び出しは、1バイトのオペコードと4バイトの PC 相対宛先アドレスからなる5バイト命令です。 +必要な修正値は、X86 固有の `call` 命令の*末尾*から `malloc` の先頭までの差であり、 +`call` の最後の4バイトに直接書き込みます。ELF ファイル内のエンコーディングの種類は、これを記述しなければなりません。 + +RISCV の外部呼び出しは、ビットを分担する2つの4バイト命令でエンコードします。 +`LUI` が宛先の上位20ビットをロードし、`JALR` が下位12ビットを加算します(32ビットの絶対宛先範囲)。 +`LUI` を `AUIPC` に置き換えると、32ビットの PC 相対範囲を得られます。 +修正では、一方の命令の20ビットと、もう一方の命令の12ビットを更新します。 +ここでも ELF ファイルのエンコーディングの種類が、この方法を記述する必要があります。 +X86 式の修正をここに使うと、誤ったビットを壊し、プログラムが動かなくなります。 + +#### 大きな定数 + +大きな定数とは、レジスタへ簡単に直接ロードできず、代わりにメモリからロードする定数です。 +セキュリティ上の理由で、*実行*権限ではなく*読み取り専用*権限などを付けた別のアドレス空間に置くことがよくあります。 +そのため、コードに対する最終的な相対アドレスは、配置によって変わります。 +これは、コンパイル単位をまたぐ呼び出しと同じです。 + +大きな定数は、コンパイル単位間で共有することもできます(HPC コードで `pi` を繰り返し使う場合など)。 +共有すると、キャッシュの占有量を減らせます。 +いずれにせよ、コードと定数の配置が決まったら、正しいオフセットからロードするようにコードを修正する必要があります。 +上の `call` の例のように、ロード命令の種類と修正方法の種類が一致しなければなりません。 +X86 では、その種類によって4バイトまたは8バイトのアドレスを使います。 +RISCV では、おそらく `LUI` と `Load` の何らかの組み合わせで、即値を20/12ビットに分けます。 +また、RISCV では同じ `LUI` で定数プールの上位20ビットを「共有」し、 +同じプールの異なる定数には異なるロードを使いたいでしょう。ARM には、さらに別の方法があります。 diff --git a/docs/chapter21/encoding-reference.md b/docs/chapter21/encoding-reference.md new file mode 100644 index 00000000..22702004 --- /dev/null +++ b/docs/chapter21/encoding-reference.md @@ -0,0 +1,798 @@ +# Instruction encoding reference + +English | [日本語](encoding-reference.ja.md) + +Detailed notes retained from the original Chapter 21 README. +See the [chapter overview](README.md) for the compiler pipeline and experiments. + +# Table of Contents + +1. [Instruction Encodings](#instruction-encodings) +2. [Infrastructure](#infrastructure) +3. [Endianness](#endianness) +4. [RISCV](#riscv) + - [Instruction formats](#instruction-formats) + - [Float](#float) + - [Indirect](#indirectmemop) + - [Branch](#branch) + - [Immediates](#immediates) + - [Float Constants](#float-constants) + - [Int Constants](#int-constants) + - [Function Constants](#function-constants) +5. [ARM](#arm) + - [Reg-form](#reg-form) + - [Shift immediates](#shift-immediates) + - [Logical Immediates](#logical-immediates) + - [Float](#float-1) + - [Large constants](#large-constants) + - [Function constant](#function-constant) + - [Conditional flags](#conditional-flags) + - [Indirect](#indirectmemop-1) + - [Branching](#branching) +6. [AMD64 X86-64](#amd64-x86-64) + - [REX PREFIX](#rex-prefix) + - [Opcode](#opcode) + - [MODR/M](#modrm) + - [SIB](#sib) + - [Displacement](#displacement) + - [Immediate](#immediate) + - [Indirect](#indirectmemop-2) + - [Float](#float-2) + - [Large constants](#large-constants-1) + - [Function constant](#function-constant-1) + - [Conditional flags](#conditional-flags-1) +7. [Relocation](#relocation) + - [Local Relocation](#local-relocation) + - [Global Relocation](#global-relocation) + - [Getting Relocations to All Agree](#getting-relocations-to-all-agree) + - [Large Constants](#large-constants-2) + + +This chapter will add instruction encodings. Instruction encoding refers to +the representation of machine instructions in a specific binary format defined +by the processor's instruction set architecture (ISA). + + +After completing instruction selection, the next step is to encode the +instructions before writing them into an ELF object file. + +To avoid ambiguity, we define the sources of the information we use to encode the instructions: + +Currently, we support these architectures: + +### RISC-V: + +For *riscv* we currently target [RVA23U64](https://msyksphinz-self.github.io/riscv-isadoc/html/). + +### ARMV8/AArch64 : +For *arm*(aarch64) we use this collection for the [encoding rules](https://docsmirror.github.io/A64/20a23-06/index.html). +*Note*: We only support 64 bits ARM encodings. + +### AMD64: +For *x86-64(amd64):*, we use the latest Intel manual for the [encoding rules](https://www.felixcloutier.com/x86/). + +-------------------------------- + +## Instruction Encodings + + +## Infrastructure + +The instruction selection phase creates the machine friendly nodes from ideal +nodes. These machine friendly nodes define an *encoding* function that is +called from the Encoding driver found in CodeGen. + +```java +for( CFGNode bb : _code._cfg ) + for( Node n : bb._outputs ) + if( n instanceof MachNode mach ) { + int off = _bits.size(); + mach.encoding(this); + _opLen[n._nid] = (byte)(_bits.size()-off); + } +``` + +Based on the selected target, we either need to look out for little endian or +big endian encodings. These are being handled by `add4` which adds to the bit +stream in the correct order. As of now the bitstream is a simple +ByteArrayOutputStream structure. + +```java +@Override public void encoding( Encoding enc ) { + enc.add4(0); // adds 4 bytes to the bitstream +} +``` + +On *RISC-V*, where the documentation provides big-endian encodings, this +function also converts them to little-endian. For other architectures, no +conversion is necessary. + +## Endianness + +ARM, RISC-V, and x86 are all little-endian architectures in practice, meaning +the least significant byte is stored first. We ensure that instruction +encodings are generated in little-endian format accordingly. + +```java +// Little endian write of a 32b opcode +public void add4( int op ) { + _bits.write(op ); + _bits.write(op>> 8); + _bits.write(op>>16); + _bits.write(op>>24); +} +``` +For example, consider a 32-bit encoding for fixed-width instructions: +*10110101100111101011001001101101*. + +We append the least significant byte first, then the next byte, and so on. +``` +_bits.write(op ); +``` + +`_bits.write()` only appends the least significant byte. By shifting the value +to the right, we can bring the next byte into the least significant position, +allowing us to write each byte in sequence. + + +-------------------------------- + +## RISCV + +RISCV instructions are all 32 bits wide, and are always little-endian. RISCV +is a RISC architecture, it prioritizes "register-to-register" forms, and has +very regular encodings. We present RISCV first because it is the simplest +encoding. + + +#### Instruction formats + +##### R-TYPE +This encoding format is used for reg-to-reg operations. +Such as `AddRISC`, `MulRISC` etc. +```java +public static int r_type(int opcode, int rd, int func3, int rs1, int rs2, int func7) { + return (func7 << 25) | (rs2 << 20) | (rs1 << 15) | (func3 << 12) | (rd << 7) | opcode; +} +``` + +##### I-TYPE +This layout is used for immediate formats. Such as `AddIRISC` + +```java + public static int i_type(int opcode, int rd, int func3, int rs1, int imm12) { + assert opcode >= 0 && rd >=0 && func3 >=0 && rs1 >=0 && imm12 >= 0; // Zero-extend by caller + return (imm12 << 20) | (rs1 << 15) | (func3 << 12) | (rd << 7) | opcode; + } +``` + +##### S-TYPE + +This encoding format is used for `StoreRISC`. + +```java +public static int s_type(int opcode, int func3, int rs1, int rs2, int imm12) { + assert imm12 >= 0; // Masked to high zero bits by caller + int imm_lo = imm12 & 0x1F; + int imm_hi = imm12 >> 5; + return (imm_hi << 25) | (rs2 << 20) | (rs1 << 15) | (func3 << 12) | (imm_lo << 7) | opcode; +} +``` + +##### B-TYPE + +This encoding layout is used by `BranchRISC`. +```java + // immf = first imm + // immd = second imm + // BRANCH +public static int b_type(int opcode, int immf, int func3, int rs1, int rs2, int immd) { + return (immd << 25 ) | (rs2 << 20) | (rs1 << 15) | (func3 << 12) | (immf << 7) | opcode; + } +``` + +##### U-TYPE +This layout is used for `AUIPC` and `LUI`(upper immediate is 20-bits). + +```java + public static int u_type(int opcode, int rd, int imm20) { + return (imm20 << 12) | (rd << 7) | opcode; + } +``` + +##### J-TYPE +Used for unconditional jumps. (`UJmpRISC`) +```java + public static int j_type(int opcode, int rd, int imm20) { + return imm20 << 12 | rd << 7 | opcode; + } +``` + +#### FLOAT +For loading float constants, we use indirect memory load(PC-relative). +``` +AUIPC dst,#hi20_constant_pool +Load dst,[dst+#low12_constant_pool] +``` + +#### INDIRECT(MemOp) + +[Load](https://msyksphinz-self.github.io/riscv-isadoc/html/rvi.html#lw): `lw rd,offset(rs1)` + +[Store](https://msyksphinz-self.github.io/riscv-isadoc/html/rvi.html#sw): `sw rs2,offset(rs1)` + +#### BRANCH +RISC-V is unusual because branch instructions include the comparison and branch target in one instruction. + +#### Immediates +We only encode the imm form for the *ALU* operations if they fit into 12 bits (signed). +Otherwise we do a Load Upper Immediate (high 20 bits) or both. + +``` +addiw a5,a5,123 +``` +vs +(do it with extra load): +``` +addw a5,a5,a4 +``` + +### FLOAT Constants +- Stored in the **constant pool**. +- Accessed via **PC-relative loads**. + +### INT Constants +- **12-bit constants** + - Use `ADDI` +- **20-bit constants** + - Use `LUI` +- **32-bit constants** + - Use `LUI` + `ADDI` combo + - + +#### Function Constants +In our RISC-V code, when we need to load a function-local constant, we store +the current PC into a register and use that as a base to access the +constant. However, we usually try to avoid loads, since simple arithmetic like +addition is faster and more efficient than accessing memory. + +```java +// auipc t0,0 +// addi t1,t0 + #0 +``` + + +-------------------------------- + +## ARM +Arm instructions are all 32 bits wide, and are always little-endian. +Arm is a RISC architecture, it prioritizes "register-to-register" forms. + +#### Reg-form +We encode the regs forms as [shifted registers](https://docsmirror.github.io/A64/2023-06/orr_log_shift.html). +``` +orr reg1, reg2 +``` +``` +x0 = x0 | (x1 << 0) +``` +The same thing applies for all the other reg forms. +Because of this similarity, we can lift the encoding of the reg form to a common function. + +```java +short self = enc.reg(n); +short reg1 = enc.reg(n.in(1)); +short reg2 = enc.reg(n.in(2)); +int body = r_reg(opcode, 0, reg2, 0, reg1, self); +enc.add4(body); +``` +*imm6* and +*shift* +are irelevant for the reg form, so we just set them to 0. + +### Shift immediates +This refers to: *asri, lsli, lsri* +Encode to shift immediates if the shift amount is between [0, 63] inclusive. + +#### ASR(immediate) +For arithmetic Shift Right immediates, we set *immr* to the immediate we wish to encode, and imms to `63` = `111111`. +This is following the rules of [asri encoding](https://docsmirror.github.io/A64/2023-06/asr_sbfm.html). + +The `sf` bit is included in the opcode, `N` is implicitly added to the bitstream after the execution of the +`imm_shift` function. +```java +enc.add4(arm.imm_shift(0b100100110,_imm, 0b111111, rn, rd)); +``` + +#### LSL(immediate) + +```java +// UBFM <Xd>, <Xn>, #(-<shift> MOD 64), #(63-<shift>) +// immr must be (-<shift> MOD 64) = 64 - shift +enc.add4(arm.imm_shift(0b110100110, 64 - _imm, (64 - _imm) - 1, rn, rd)); +``` + +We know that `_imm` is never a negative number and is between 0 and 63, so we can safely use `64 - _imm` as the `immr` value. +We take away this form of the immediate from 64 to invert the bits. + +For *imms* - the following condition must hold *imms + 1 = immr*; +After subbing in the values we get: + +> imms + 1 = 64 - _imm; + +> imms = (64 - _imm) - 1; + +Hence, the `imms` value is `(64 - _imm) - 1`. + +#### LSR(immediate) +Same as *ASR*. + + +#### Logical Immediates +Logical immediates refer to *andi, xori, orri*. + +```java +// Can we encode this in ARM's 12-bit LOGICAL immediate form? +// Some combination of shifted bit-masks. +private static int imm12Logical(TypeInteger ti) { + if( !ti.isConstant() ) return -1; + if( !ti.isConstant() ) return -1; + long val = ti.value(); + if (val == 0 || val == -1) return -1; // Special cases are not allowed + int immr = 0; + // Rotate until we have 0[...]1 + while (val < 0 || (val & 1)==0) { + val = (val >>> 63) | (val << 1); + immr++; + } + int size = 32; + long pattern = val; + // Is upper half of pattern the same as the lower? + while ((pattern & ((1L<<size)-1)) == (pattern >> size)) { + // Then only take one half + pattern >>= size; + size >>= 1; + } + size <<= 1; + int imms = Long.bitCount(pattern); + // Pattern should now be zeros followed by ones 0000011111 + if (pattern != (1L<<imms)-1) return -1; + imms--; + if (size == 64) return 0x1000 | immr << 6 | imms; + return (32-size)<<1 | immr << 6 | imms; +} +``` + +imm12Logical returns the 12-bit immediate encoding for logical operations if it is possible to encode it, otherwise -1. +```java +int imm12; +return and.in(2) instanceof ConstantNode off && +off._con instanceof TypeInteger ti && (imm12 = imm12Logical(ti)) != -1 +? new AndIARM(and, imm12) +``` +```java +arm.imm_inst(enc,this,0b100100100,_imm); +``` + +#### Float +Similarly to x86, we have to subtract the offset to get the GPR register pair that corresponds to the *D* register. +``` +short dst = (short)(enc.reg(this ) - arm.D_OFFSET); +``` +For loading float constants we use the `ldr(literal)` instruction. +The offset is added to the PC register to get the address of the literal. + +```java +// Any number that can be expressed as +/-n * 2-r,where n and r are integers, 16 <= n <= 31, 0 <= r <= 7. +int body = arm.load_pc(0b01011100, 0, dst); +``` + +#### Large constants + +##### INT: We can use different combinations of movs(movz, movk, movn) - this allows us to avoid PC relative loading. + +##### FLOAT: We use the *LDR (immediate, SIMD&FP)* instruction relative to the PC + + +#### Function constant +To load a function constant we store the current PC into x0 and use that to access the constant. +Generally, we try to avoid load as adding is faster. +``` +adrp x0, 0 +add x0, x0, 0 +``` + +#### Conditional flags +In arm we set the conditional flags after a comparison - +this works similarly to x86. + +``` +int square(int a) { +// subs +// cset w8, eq // eq = none +bool da = a == 1; + // bne + if(da == 1) { + return 1; + } + return 2; +} +``` +In arm comparisons are done with `subss` which does a subtraction between the two +operands and also sets the flags. +`CSET` is used to look up the flags and save it out to a register. + +Later on `b.ne` relies on this flag again when it attempts to conditionally execute a branch. + +#### INDIRECT(MemOp) +Arm supports the following indirect forms: + +[Load](https://docsmirror.github.io/A64/2023-06/ldr_reg_gen.html): `ldr dst, [base + index]` where both, base and index are registers. + +[Load](https://docsmirror.github.io/A64/2023-06/ldr_imm_gen.html): `ldr dst, [base + #offset]` where base is a register and the offset is an immediate. + +[Store](https://docsmirror.github.io/A64/2023-06/str_reg_gen.html): `str dst, [base + index]` + +[Store](https://docsmirror.github.io/A64/2023-06/strb_imm.html): `str dst, [base + #offset]` + +For a register offset, use the register-offset form rather than the immediate-offset form. + +#### Branching +`B.cond` - branch [conditionally](https://developer.arm.com/documentation/dui0802/b/A32-and-T32-Instructions/Condition-codes?lang=en) to a label at a PC-relative offset. + +> cond = is one of the standard conditions. + + +-------------------------------- + +## AMD64 X86-64 + +As opposed to riscv arhitectures, where the instruction width is fixed, +*x86-64* has variable width instructions. This is common with *CISC (Complex +Instruction Set Computer)* architectures, where the instruction width can vary +from 1 to 15 bytes. Since AMD64 supports many indirect addressing modes, the +goal with *CISC* in general is to complete a task in as few lines of assembly +as possible. + +### REX PREFIX + +Since we are targeting the 64-bit version of x86, we need to handle this +prefix. In 32-bit mode, however, it is typically unnecessary. + +Generally speaking the *REX* prefix must be encoded when: + - using one of the extended registers (R8 to R15, XMM8 to XMM15, YMM8 to YMM15, CR8 to CR15 and DR8 to DR15); + - using 64-bit operand size and the instruction does not default to 64-bit operand size(most ALU ops) + - using a 8-bit operand with RSI, RDI, RBP + +A *REX* prefix must not be encoded when: + - using one of the high byte registers AH, CH, BH or DH (not done currently in Simple) + +*Note:* When encoding SSE instruction, the *REX* prefix (0x40) must come after +the `SSE` prefix. In all other cases, it is ignored. + +In Simple, the REX prefix is just appended to the beginning of the bit stream +(except SSE float instructions). The layout is the following: + +| **Field** | **Length** | **Description** | +|:---------:|:----------:|---------------------------------------------------------------------------------| +| `0100` | 4 bits | Fixed bit pattern | +| `W` | 1 bit | When 1, a 64-bit operand size is used. Otherwise, 32-bit operand size is used. | +| `R` | 1 bit | Extension to the **MODRM.reg** field. | +| `X` | 1 bit | Extension to the **SIB.index** field. | +| `B` | 1 bit | Extension to the **MODRM.rm** or **SIB.base** field. | + + +```java + public static int REX_W = 0x48; +``` + +```java +public static int rex(int reg, int ptr, int idx, boolean wide) { + // assuming 64 bit by default so: 0100 1000 + int rex = wide ? REX_W : REX; + if( 8 <= reg && reg <= 15 ) rex |= 0b00000100; // REX.R + if( 8 <= ptr && ptr <= 15 ) rex |= 0b00000001; // REX.B + if( 8 <= idx && idx <= 15 ) rex |= 0b00000010; // REX.X + return rex; +} + + ... + enc.add1(x86_64_v2.rex(dst, src, 0)); +``` + +Setting THE `W` bit gives us: +`0b01001000 = 0x48;` + +#### Opcode +In our case, the opcode is a 1 byte field that specifies the operation to be performed. +``` +enc.add1(opcode()); // opcode +``` + +#### MODR/M + +| **Field** | **Length** | **Description** | +|:-------------:|:----------:|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **MODRM.mod** | 2 bits | In general, when this field is `b11`, then register-direct addressing mode is used; otherwise register-indirect addressing mode is used. +| **MODRM.reg** | 3 bits | This field can have one of two values:<br><br>• A 3-bit opcode extension, which is used by some instructions but has no further meaning other than distinguishing the instruction from other instructions.<br><br>• A 3-bit register reference, which can be used as the source or the destination of an instruction (depending on the instruction). The referenced register depends on the operand-size of the instruction and the instruction itself. The `REX.R`, `VEX.~R` or `XOP.~R` field can extend this field with 1 most-significant bit to 4 bits total. | +| **MODRM.rm** | 3 bits | Specifies a direct or indirect register operand, optionally with a displacement. The `REX.B`, `VEX.~B` or `XOP.~B` field can extend this field with 1 most-significant bit to 4 bits total. | + + +We deal with the MODR/M byte in a similar way: + +The first thing we need for the modrm byte is the 2 bits mod. + +##### mod + +The layout for mod is the following: +``` +public enum MOD { + INDIRECT, // [mem] + INDIRECT_disp8, // [mem + 0x12] + INDIRECT_disp32,// [mem + 0x12345678] + DIRECT, // mem +}; +``` + +##### reg +The reg field has 3 bits, selecting one of 8 registers. When it names a +register, REX.R extends it to 4 bits, selecting one of 16 registers. + +##### r/m + +The r/m field has 3 bits. In register-direct mode, REX.B extends it to +select one of 16 registers. In memory modes, r/m selects the addressing form +or base register; some values select a SIB byte or RIP-relative addressing. + + +```java +public static int modrm(MOD mod, int reg, int m_r) { + // combine all the bits + return (mod.ordinal() << 6) | ((reg & 0x07) << 3) | m_r & 0x07; +} +``` + +The `modrm` function is then used to build up the `ModR/M` byte. +Usually this follows after the opcode. + +``` +enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src)); +``` + +Encoding a reg form might look like this: +``` +short dst = enc.reg(this ); // src1 +short src = enc.reg(in(2)); // src2 +enc.add1(x86_64_v2.rex(dst, src, 0)); +enc.add1(opcode()); // opcode +enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src)); +``` + +#### SIB +The SIB byte has the following fields: + +| **Field** | **Length** | **Description** | +|:-------------:|:----------:|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **SIB.scale** | 2 bits | This field indicates the scaling factor of `SIB.index`, where **s** (as used in the tables) equals 2^SIB.scale^. | +| **SIB.index** | 3 bits | The index register to use. See Registers for the values to use for each of the registers. The `REX.X`, `VEX.~X` or `XOP.~X` field can extend this field with 1 most-significant bit to 4 bits total. | +| **SIB.base** | 3 bits | The base register to use. See Registers for the values to use for each of the registers. The `REX.B`, `VEX.~B` or `XOP.~B` field can extend this field with 1 most-significant bit to 4 bits total. | + +It has the same layout as the MODR/M byte. +```java +public static int sib(int scale, int index, int base) { + return (scale << 6) | ((index & 0x07) << 3) | base & 0x07; +} +``` + +```java +enc.add1(x86_64_v2.sib(_scale, idx, x86_64_v2.RBP)); +``` + +#### Displacement +Displacement can happen in 2 forms: +```java +public enum MOD { +INDIRECT, // [mem] +INDIRECT_disp8, // [mem + 0x12] +INDIRECT_disp32,// [mem + 0x12345678] +DIRECT, // mem +}; +``` + +```java +if( mod == MOD.INDIRECT_disp8 ) { + enc.add1(offset); +} else if( mod == MOD.INDIRECT_disp32 ) { + enc.add4(offset); +} +``` +The displacement goes before the immediate, so for example: +``` +sub [eax + 2], 4 +``` +``` +bytes.write(0x83); +bytes.write(modrm(INDIRECT8, 5, 0 /* eax */)); +bytes.write(2); // displcement goes first +bytes.write(4); +``` + +#### Immediate +The immediate byte/bytes go after the `ModR/M` byte, we are capable of encoding 4 bytes of immediates depending on the need. +```java +// immediate(4 bytes) 32 bits or (1 byte)8 bits +if( x86_64_v2.imm8(_imm) ) enc.add1(_imm); +else enc.add4(_imm); +``` + +#### INDIRECT(MemOp) +X86-64 support multiple indirect addressing modes: + + +#### Float +For float operations we use `SSE SIMD` instructions that operate on scalar single precision floating-point values located in the +XMM registers or memory. +*Note*: When using XMM registers for ALU operations, the encoding is really similar with the exception that we need +to subtract the `XMM_OFFSET` from the register number. +We count the registers continuously, so we need to subtract the offset to get the GPR register pair that corresponds to the XMM register. + +```java +short dst = (short)(enc.reg(this ) - x86_64_v2.XMM_OFFSET); +``` + +```java +public static int XMM_OFFSET = 16; +``` + +| **X.Reg** | **8-bit GP** | **16-bit GP** | **32-bit GP** | **64-bit GP** | **80-bit x87** | **64-bit MMX** | **128-bit XMM** | **256-bit YMM** | **16-bit Segment** | **32-bit Control** | **32-bit Debug** | +|:--------:|:------------:|:-------------:|:-------------:|:-------------:|:-------------:|:-------------:|:--------------:|:--------------:|:-----------------:|:-----------------:|:---------------:| +| `0.000 (0)` | AL | AX | EAX | RAX | ST0 | MMX0 | XMM0 | YMM0 | ES | CR0 | DR0 | + +We see that `XMM0` corresponds to `RAX` and so on. + +#### Large constants + +##### INT: We just use `MOV r64, imm64` instruction form. + +##### FLOAT: We use relocation and use constant from memory. + +#### Function constant +To load a function constant we use lea which will load the address into the specified reg +relative to the instruction pointer. +`lea rax, [rip+disp32]` + +#### Conditional flags: +The FLAGS register is the status register that contains the current state of an x86 CPU. + +Let's consider a simple example that presents all the instructions that we need to encode for +conditional code. + +``` +int square(int a) { + // cmp DWORD PTR [rbp-0x8],0x1 + // `sete al` + bool da = a == 1; + + // cmp eax,0x1 + if(da == 1) { + return 1; + } + return 2; +} +``` + +First we need a compare instruction comparing `a` with 1. +``` +cmp DWORD PTR [rbp-0x8],0x1 +``` + +This compare instruction then sets the conditional flags: +> The CF, OF, SF, ZF, AF, and PF flags are set according to the result. + +We then use the `sete` instruction to save out the result of the comparison to a register: + +`sete al` + +For the if condition we do an extra comparison based on the result of the first bool. +``` + cmp eax,0x1 +``` + +And then a conditional branch that is relying on the flags set by the comparison(`a == 1`). +``` +jne 1159 <square(int)+0x29> +``` + + +-------------------------------- + + +## Relocation + +Relocation allows code to be *relocated* to a new code offset. Code often +refers to other code and data; `call` instructions target subroutines, +`branches` target other instructions, large constants are often loaded from a +*constant pool*. + +### Local Relocation + +Local relocation patches up local, or self-referential, code; the most common +form is branch targets. When writing instructions into the `BAOS` (Byte Array +Output Stream) forwards branches towards future instructions have an unknown +offset - the offset depends on the size of the encodings in between the branch +and target. For `riscv` and `arm` targets this sounds easy (and instructions +are 4 bytes, so just count instructions)... except that some encodings use +multiple instructions so are not actually 4 bytes long. For X86's wild +psuedo-random instruction lengths the act of figuring out the opcode length is +just as hard as writing the opcode in the first place. + +X86 adds another wrinkle: short and long form branches. If a branch target +lies within a signed byte range (+/-127) X86 will use a 2 byte branch encoding, +otherwise a 6 byte encoding. Simple includes a pass to compact these branches +and use 2 bytes wherever possible - but this pass has to slide code around +which changes all the other branch offsets. The "compact" pass actually starts +optimistically assuming all branches can use the 2 byte form, and then expands +branches as needed (so really *expands* the code). + +Once the code is finally settled into "shape", a local relocation pass is made +to update all the self-references. Basically, the branch offsets in the byte +array get overwritten with the actual values. + +At the end of this pass, the code is locally correct at some offset (generally +0), but this is not the end - code is often *linked* against other code +generated in other compilations, and again various offsets will need to be +adjusted. + +### Global Relocation + +Code in one compilation unit wants to call code in another... this requires a +*global relocation*. A common example is a *loader* loading an *ELF* file, +which mostly happens every time a new program is run, and requires the +relocation information to be stored to disk (in e.g. the ELF file). JIT +systems will neeed to do this as well across different compilations, although +the relocation information can stay in memory. + +#### Getting Relocations to All Agree + +Since the relocations can be written to disk, the disk format, loader and "code +shape" all have to agree. Suppose we want to call from one compilation unit to +another - e.g. the program is calling `malloc` from `libc`. The caller is a +Simple using a machine-specific version of generic `CallNode` with the target +being C-compiled code. During loading, the placement of the `call` and `libc` +is determined by the loader; the two different blocks of code are written into +memory in some order (generally with lots of other blocks of code). Now we +need to patch the `call`'s target to be `malloc` in the `libc` block of code. + +For an X86 external call, the encoding is a 5-byte instruction, one byte of +opcode and 4 bytes of PC-relative target address. The needed patch is the +delta between the X86-specific `call` instruction *end* and the start of +`malloc`, and is written directly on the last 4 bytes of the `call`. The +encoding type in the ELF file has to describe this. + +For a RISCV external call, the encoding is 2 4-byte instructions each carrying +some of the bits. The `LUI` loads the target upper 20 bits, and then the +`JALR` adds in the lower 12 bits (32-bit absolute target range). The `LUI` +might be replaced with a `AUIPC` to get a 32-bit pc-relative range. Patching +here updates 20bits in one instruction and 12bits in another. Again the ELF +file encoding type has to describe this; using a X86-style patch here will just +crush the wrong bits and make a broken program. + +#### Large Constants + +Large constants are those which cannot be easily directly loaded into a +register, and instead are loaded from memory. For security reasons they are +often kept in a seperate address space with e.g. *read-only* permissions instead +of *execute* permissions. This means their final address, relative to the code, +varies according to placement - same as for calls crossing compilation units. + +Also large constants can be shared across compilation units (one can imagine +repeated uses of `pi` in HPC codes); sharing reduces cache footprint. In any +case, once the locations of the code and constants are known, the code needs to +be patched to load at the correct offset. Like the `call` example above, the +load instruction variant and patch variant all need to agree. An X86 might use +either a 4-byte address or an 8-byte address depending on X86 variant; the +RISCV probably uses some variant of an `LUI` and a `Load`, with the immediate +split into 20/12 bits. Also the RISCV probably wants to "share" the constant +pool 20 bits in the same `LUI`, but have different loads for different +constants coming out of the same pool. ARM will have yet another variant. + diff --git a/docs/chapter21/font-awesome-4.7.0.zip b/docs/chapter21/font-awesome-4.7.0.zip Binary files differnew file mode 100644 index 00000000..b6bdb4bd --- /dev/null +++ b/docs/chapter21/font-awesome-4.7.0.zip diff --git a/docs/chapter21/ifg_trim.pdf b/docs/chapter21/ifg_trim.pdf Binary files differnew file mode 100644 index 00000000..d952fd4f --- /dev/null +++ b/docs/chapter21/ifg_trim.pdf diff --git a/docs/chapter21/lattice.gv b/docs/chapter21/lattice.gv new file mode 100644 index 00000000..8661082d --- /dev/null +++ b/docs/chapter21/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/chapter21/lattice.svg b/docs/chapter21/lattice.svg new file mode 100644 index 00000000..e72b473b --- /dev/null +++ b/docs/chapter21/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/chapter21/lattice2.gv b/docs/chapter21/lattice2.gv new file mode 100644 index 00000000..6aa37fa0 --- /dev/null +++ b/docs/chapter21/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/chapter21/lattice2.svg b/docs/chapter21/lattice2.svg new file mode 100644 index 00000000..8b682aa7 --- /dev/null +++ b/docs/chapter21/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/chapter21/pardoe.docx b/docs/chapter21/pardoe.docx Binary files differnew file mode 100644 index 00000000..24fde921 --- /dev/null +++ b/docs/chapter21/pardoe.docx diff --git a/docs/chapter21/webtcp.min.js b/docs/chapter21/webtcp.min.js new file mode 100644 index 00000000..bf23ee5a --- /dev/null +++ b/docs/chapter21/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 @@ -51,9 +51,18 @@ <sources> <source>${simple.graph.directory}/src/main/java</source> <source>${simple.print.directory}/src/main/java</source> + <source>${project.basedir}/isa/src/main/java</source> </sources> </configuration> </execution> + <execution> + <id>shared-isa-test-sources</id> + <phase>generate-test-sources</phase> + <goals><goal>add-test-source</goal></goals> + <configuration><sources> + <source>${project.basedir}/isa/src/test-support/java</source> + </sources></configuration> + </execution> </executions> </plugin> <plugin> diff --git a/simple.bat b/simple.bat new file mode 100644 index 00000000..563fb818 --- /dev/null +++ b/simple.bat @@ -0,0 +1 @@ +java -cp "build/classes/test;build/classes/main;lib/hamcrest-core-1.3.jar;lib/junit-4.12.jar" com.seaofnodes.simple.Simple %* diff --git a/simple.sh b/simple.sh new file mode 100755 index 00000000..d8335f44 --- /dev/null +++ b/simple.sh @@ -0,0 +1,2 @@ +#! /bin/sh +java -ea -cp "build/classes/test:build/classes/main:lib/hamcrest-core-1.3.jar:lib/junit-4.12.jar" com.seaofnodes.simple.Simple $@ diff --git a/src/main/java/com/seaofnodes/simple/IterPeeps.java b/src/main/java/com/seaofnodes/simple/IterPeeps.java index a21fb517..7c2f1b0c 100644 --- a/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ b/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -53,6 +53,7 @@ public class IterPeeps { _workInline = new WorkList<>(seed); } + @SuppressWarnings("unchecked") public <N extends Node> N add( N n ) { return (N)_work.push(n); } public void addAll( Ary<Node> ary ) { _work.addAll(ary); } @@ -147,6 +148,7 @@ public class IterPeeps { * Classic WorkList, with a fast add/remove, dup removal, random pull. * The Node's nid is used to check membership in the worklist. */ + @SuppressWarnings("unchecked") public static class WorkList<E extends Node> { private Node[] _es; diff --git a/src/main/java/com/seaofnodes/simple/Parser.java b/src/main/java/com/seaofnodes/simple/Parser.java index 697e84ce..6f307785 100644 --- a/src/main/java/com/seaofnodes/simple/Parser.java +++ b/src/main/java/com/seaofnodes/simple/Parser.java @@ -142,7 +142,25 @@ public class Parser { // Parse whole program, as-if function header "{ int arg -> body }" parseFunctionBody(_code._main,loc(),"arg"); - _code.link(_code._main)._name = "main"; + + // Kill an empty default main. Keep only if it was explicitly defined + // (programmer asked for a "main") or it has stuff (i.e. beginner + // default main). + FunNode main = _code.link(_code._main); + StopNode stop = _code._stop; + if( main.ret().expr() instanceof ConstantNode && main.ret().mem().in(0)==main && stop.nIns() > 1 ) { + // Kill an empty default main; so it does not attempt to put a + // "main" in any final ELF file + stop.delDef(stop._inputs.find(main.ret())); + } else { + // We have a non-empty default main. + // Check for an explicit main + for( Node n : stop._inputs ) + if( n instanceof FunNode fun && fun._name.equals("main") ) + // Found an explicit "main" AND we have a default "main" + throw error("Cannot define both an explicit main and a default main"); + main._name = "main"; + } if( !_lexer.isEOF() ) throw _errorSyntax("unexpected"); @@ -152,7 +170,7 @@ public class Parser { for( StructNode init : INITS.values() ) init.unkeep().kill(); INITS.clear(); - _code._stop.peephole(); + stop.peephole(); } /** @@ -1431,6 +1449,7 @@ public class Parser { // Post-call setup CallEndNode cend = (CallEndNode)new CallEndNode(call).peephole(); + call.peephole(); // Rerun peeps after CallEnd, allows early inlining // Control from CallEnd ctrl(new CProjNode(cend,0,ScopeNode.CTRL).peephole()); // A call may update any alias, so its memory result is the new whole state. @@ -1690,6 +1709,7 @@ public class Parser { while( Character.isDigit(c=nextChar()) ) ; if( !(c=='e' || c=='.') ) return --_position - old; + if( peek() == '-' ) nextChar(); while( Character.isDigit(c=nextChar()) || c=='e' || c=='.' ) ; return -(--_position - old); } diff --git a/src/main/java/com/seaofnodes/simple/SB.java b/src/main/java/com/seaofnodes/simple/SB.java index a45f0ddf..978fbed9 100644 --- a/src/main/java/com/seaofnodes/simple/SB.java +++ b/src/main/java/com/seaofnodes/simple/SB.java @@ -29,21 +29,28 @@ public final class SB { public SB p( int s ) { _sb.append(s); return this; } public SB p( long s ) { _sb.append(s); return this; } public SB p( boolean s) { _sb.append(s); return this; } - // 4 hex digits - public SB hex4(int s) { - assert (s>>4*4)==0; // Fits in 16 bits - for( int i=0; i<4; i++ ) { - int digit = (s>>((3-i)*4)) & 0xf; - _sb.append((char)((digit <= 9 ? '0' : ('A'-10))+digit)); - } + // 1 byte, 2 hex digits, 8 bits + public SB hex1(int s) { + int digit = (s>>4) & 0xf; + _sb.append((char)((digit <= 9 ? '0' : ('A'-10))+digit)); + digit = s & 0xf; + _sb.append((char)((digit <= 9 ? '0' : ('A'-10))+digit)); return this; } + // 2 bytes, 4 hex digits, 16 bits, Big Endian + public SB hex2(int s) { return hex1(s>> 8).hex1(s); } + // 4 bytes, 8 hex digits, 32 bits, Big Endian + public SB hex4(int s) { return hex2(s>>16).hex2(s); } + // 8 bytes, 16 hex digits, 64 bits, Big Endian + public SB hex8(long s) { return hex4((int)(s>>32)).hex4((int)s); } + // Fixed width field public SB fix( int sz, String s ) { for( int i=0; i<sz; i++ ) _sb.append( i < s.length() ? s.charAt(i) : ' '); return this; } + public char at(int idx ) { return _sb.charAt(idx); } // Not spelled "p" on purpose: too easy to accidentally say "p(1.0)" and // suddenly call the autoboxed version. @@ -83,6 +90,8 @@ public final class SB { // public SB unchar() { return unchar(1); } public SB unchar(int x) { _sb.setLength(_sb.length()-x); return this; } + public SB setLen(int len) { _sb.setLength(len); return this; } + public String subString(int start, int end ) { return _sb.substring(start,end); } public SB clear() { _sb.setLength(0); return this; } public int len() { return _sb.length(); } diff --git a/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java b/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java index 746a0e6a..2319714b 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java +++ b/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java @@ -1,6 +1,5 @@ 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; @@ -30,7 +29,7 @@ abstract public class BuildLRG { break; // If none, make one. if( lrg==null ) lrg = alloc.newLRG(n); - if( phi instanceof MachNode mach ) defLRG(alloc,n); + if( phi instanceof MachNode ) 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++ ) @@ -60,7 +59,7 @@ abstract public class BuildLRG { 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); + RegMask use_mask = mach.regmap(i); // use_mask is also null for anti-dep if( use_mask!=null && !lrg2.machUse(mach,(short)i,use_mask.size1()).and(use_mask) ) alloc.fail(lrg2); // Empty register mask, must split } diff --git a/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java b/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java new file mode 100644 index 00000000..e2fac779 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java @@ -0,0 +1,105 @@ +package com.seaofnodes.simple.codegen; + +import com.seaofnodes.simple.Ary; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.node.*; +import java.util.BitSet; +import java.util.IdentityHashMap; + +// Coalesce copies +abstract public class Coalesce { + + public static boolean coalesce( int round, RegAlloc alloc ) { + + // Walk all the splits, looking for coalesce chances + boolean progress = false; + for( CFGNode bb : alloc._code._cfg ) { + outer: + for( int j=0; j<bb.nOuts(); j++ ) { + Node n = bb.out(j); + if( !(n instanceof SplitNode) ) continue; + LRG v1 = alloc.lrg(n); + LRG v2 = alloc.lrg(n.in(1)); + if( v1 != v2 ) { + LRG ov1 = v1, ov2 = v2; + // Get the smaller neighbor count in v1 + if( v1.nadj() > v2.nadj() ) + { v1 = v2; v2 = alloc.lrg(n); } + int v2len = v2._adj==null ? 0 : v2._adj._len; + // See that they do not conflict (coalescing would make a self-conflict) + if( v1._adj!=null && v2._adj!=null && v1._mask.overlap(v2._mask) ) { + // Walk the smaller neighbors, and add to the larger. + // Check for direct conflicts along the way. + for( LRG v3 : v1._adj ) { + if( v3==v2 ) { + // Direct conflict, skip this LRG pair + v2._adj.setLen(v2len); + continue outer; + } + // Add missing LRGs from V1 to V2 + if( v2._adj.find(v3)== -1 ) + v2._adj.push(v3); + } + } + // Most constrained mask + RegMask mask = v1._mask==v2._mask ? v1._mask : v1._mask.copy().and(v2._mask); + // Check for capacity + if( v2.nadj() >= mask.size() ) { + // Fails capacity, will not be trivial colorable + if( v2._adj!=null ) v2._adj.setLen(v2len); + continue; + } + + // Union lrgs. + progress = true; + + // Since `n` is dying, remove from the LRGS BEFORE doing + // the union; this will let the union code keep the + // matching bits from the other side, preserving some + // info for biased coloring + if( ov1. _machDef==n ) ov1. _machDef = null; + if( ov1._splitDef==n ) ov1._splitDef = null; + if( ov2. _machUse==n ) ov2. _machUse = null; + if( ov2._splitUse==n ) ov2._splitUse = null; + // Keep larger adjacency list. + Ary<LRG> larger = v2._adj; + v2._adj = null; + + // Union lrgs. Swaps again, based on small lrg + LRG vWin = v1.union(v2); + vWin._adj = larger; + LRG vLose = v1.leader() ? v2 : v1; + + // LRGs below v2len were always in v2, but maybe not in v1. + // LRGs above v2len are in v1 but were not in v2. + + // Walk v2's neighbors, replacing vLose with vWin. + if( larger != null ) + for( LRG vn : larger ) { + int idx = vn._adj.find(vLose); + if( idx != -1 ) + vn._adj.del(idx); + if( vn._adj.find(vWin) == -1 ) + vn._adj.add(vWin); + } + } else { + + // Since `n` is dying, remove from the LRGS + if( v1. _machDef==n ) v1. _machDef=null; + if( v1. _machUse==n ) v1. _machUse=null; + if( v1._splitDef==n ) v1._splitDef=null; + if( v1._splitUse==n ) v1._splitUse=null; + } + + // Remove junk split + n.removeSplit(); + j--; + } + } + + if( progress ) + alloc.unify(); + + return true; + } +} diff --git a/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java index 78cf4c06..2c44cb05 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java +++ b/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java @@ -6,22 +6,15 @@ import com.seaofnodes.simple.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.print.*; import com.seaofnodes.simple.type.*; +import java.io.IOException; import java.util.BitSet; import java.util.HashMap; import java.util.IdentityHashMap; +@SuppressWarnings("unchecked") public class CodeGen { public GraphObserver<Node> _obs; - - // Inlining can insert CFG edges and invalidate cached depth ordering. - private char _iDepthVersion; - public char iDepthVersion() { return _iDepthVersion; } - public void invalidateIDepthCaches() { - assert _iDepthVersion < Character.MAX_VALUE : "Dominator cache version exceeds 65535"; - ++_iDepthVersion; - } - - + public static final String PORTS = "com.seaofnodes.simple.node.cpus"; // Last created CodeGen as a global; used all over to avoid passing about a // "context". public static CodeGen CODE; @@ -30,11 +23,13 @@ public class CodeGen { Parse, // Parse ASCII text into Sea-of-Nodes IR Opto, // Run ideal optimizations TypeCheck, // Last check for bad programs + LoopTree, // Build a loop tree; break infinite loops InstSelect, // Convert to target hardware nodes Schedule, // Global schedule (code motion) nodes LocalSched, // Local schedule RegAlloc, // Register allocation Encoding, // Encoding + Export, // Export } public Phase _phase; @@ -43,7 +38,6 @@ public class CodeGen { public final String _src; // Compile-time known initial argument type public final TypeInteger _arg; - // --------------------------- public CodeGen( String src ) { this(src, TypeInteger.BOT, 123L ); } public CodeGen( String src, TypeInteger arg, long workListSeed ) { @@ -59,6 +53,27 @@ public class CodeGen { } + // All passes up to Phase, except ELF + public CodeGen driver( Phase phase ) { return driver(phase,null,null); } + public CodeGen driver( Phase phase, String cpu, String callingConv ) { + if( _phase==null ) parse(); + if( _phase.ordinal() < phase.ordinal() ) opto(); + if( _phase.ordinal() < phase.ordinal() ) typeCheck(); + if( _phase.ordinal() < phase.ordinal() ) loopTree(); + if( _phase.ordinal() < phase.ordinal() && cpu != null ) instSelect(cpu,callingConv); + if( _phase.ordinal() < phase.ordinal() ) GCM(); + if( _phase.ordinal() < phase.ordinal() ) localSched(); + if( _phase.ordinal() < phase.ordinal() ) regAlloc(); + if( _phase.ordinal() < phase.ordinal() ) encode(); + return this; + } + + // Run all the phases through final ELF emission + public CodeGen driver( String cpu, String callingConv, String obj ) throws IOException { + return driver(Phase.Encoding,cpu,callingConv).exportELF(obj); + } + + // --------------------------- /** * A counter, for unique node id generation. Starting with value 1, to @@ -75,6 +90,14 @@ public class CodeGen { public int getALIAS() { return _alias++; } + // Inlining can insert CFG edges and invalidate cached depth ordering. + private char _iDepthVersion; + public char iDepthVersion() { return _iDepthVersion; } + public void invalidateIDepthCaches() { + assert _iDepthVersion < Character.MAX_VALUE : "Dominator cache version exceeds 65535"; + ++_iDepthVersion; + } + // Popular visit bitset, declared here, so it gets reused all over public final BitSet _visit = new BitSet(); public BitSet visit() { assert _visit.isEmpty(); return _visit; } @@ -168,7 +191,7 @@ public class CodeGen { // --------------------------- // Last check for bad programs - int _tTypeCheck; + public int _tTypeCheck; public CodeGen typeCheck() { // Demand phase Opto for cleaning up dead control flow at least, // required for the following GCM. @@ -188,17 +211,42 @@ public class CodeGen { return this; } + // --------------------------- + // Build the loop tree; break never-exit loops + public int _tLoopTree; + public CodeGen loopTree() { + assert _phase.ordinal() <= Phase.TypeCheck.ordinal(); + _phase = Phase.LoopTree; + long t0 = System.currentTimeMillis(); + // Build the loop tree, fix never-exit loops + _start.buildLoopTree(_stop); + _tLoopTree = (int)(System.currentTimeMillis() - t0); + return this; + } // --------------------------- // Code generation CPU target public Machine _mach; - // + // Chosen calling convention (usually either Win64 or SystemV) public String _callingConv; + // Callee save registers + public RegMask _callerSave; + + // All returns have the following inputs: + // 0 - ctrl + // 1 - memory + // 2 - varies with returning GPR,FPR + // 3 - RPC + // 4+- Caller save registers + public RegMask[] _retMasks; + // Return Program Counter + public RegMask _rpcMask; // Convert to target hardware nodes public int _tInsSel; - public CodeGen instSelect( String base, String cpu, String callingConv ) { - assert _phase.ordinal() <= Phase.TypeCheck.ordinal(); + public CodeGen instSelect( String cpu, String callingConv ) { return instSelect(cpu,callingConv,PORTS); } + public CodeGen instSelect( String cpu, String callingConv, String base ) { + assert _phase.ordinal() == Phase.LoopTree.ordinal(); _phase = Phase.InstSelect; _callingConv = callingConv; @@ -207,9 +255,30 @@ public class CodeGen { // Look for CPU in fixed named place: // com.seaofnodes.simple.node.cpus."cpu"."cpu.class" String clzFile = base+"."+cpu+"."+cpu; - try { _mach = ((Class<Machine>) Class.forName( clzFile )).getDeclaredConstructor().newInstance(); } + try { _mach = ((Class<Machine>) Class.forName( clzFile )).getDeclaredConstructor(new Class[]{CodeGen.class}).newInstance(this); } catch( Exception e ) { throw new RuntimeException(e); } + // Build global copies of common register masks. + long callerSave = _mach.callerSave(); + long neverSave = _mach. neverSave(); + int maxReg = Math.min(64,_mach.regs().length); + assert maxReg>=64 || (-1L << maxReg & callerSave)==0; // No stack slots in callerSave + _callerSave = new RegMask(callerSave); + + // Build a Return RegMask array. All returns have the following inputs: + // 0 - ctrl + // 1 - memory + // 2 - varies with returning GPR,FPR + // 3 - RPC + // 4+- Caller save registers + _retMasks = new RegMask[(maxReg-_callerSave.size()-Long.bitCount( neverSave ))+4]; + for( int reg=0, i=4; reg<maxReg; reg++ ) + if( !_callerSave.test(reg) && ((1L<<reg)&neverSave)==0 ) + _retMasks[i++] = new RegMask(reg); + _rpcMask = new RegMask(_mach.rpc()); + _retMasks[3] = _rpcMask; + + // Convert to machine ops long t0 = System.currentTimeMillis(); _uid = 1; // All new machine nodes reset numbering @@ -245,7 +314,7 @@ public class CodeGen { for( int i=0; i < x.nIns(); i++ ) x._inputs.set(i, _instSelect(x.in(i),map) ); if( x instanceof MachNode mach ) - mach.postSelect(); // Post selection action + mach.postSelect(this); // Post selection action // Updates forward edges only. n._outputs.clear(); @@ -265,8 +334,8 @@ public class CodeGen { // --------------------------- - // Control Flow Graph in RPO order. - int _tGCM; + // Control Flow Graph in Reverse Post Order. + public int _tGCM; public Ary<CFGNode> _cfg = new Ary<>(CFGNode.class); // Global schedule (code motion) nodes @@ -275,8 +344,6 @@ public class CodeGen { _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); @@ -285,7 +352,7 @@ public class CodeGen { // --------------------------- // Local (basic block) scheduler phase, a classic list scheduler - int _tLocal; + public int _tLocal; public CodeGen localSched() { assert _phase == Phase.Schedule; _phase = Phase.LocalSched; @@ -298,7 +365,7 @@ public class CodeGen { // --------------------------- // Register Allocation - int _tRegAlloc; + public int _tRegAlloc; public RegAlloc _regAlloc; public CodeGen regAlloc() { assert _phase == Phase.LocalSched; @@ -309,31 +376,46 @@ public class CodeGen { _tRegAlloc = (int)(System.currentTimeMillis() - t0); return this; } - public String reg(Node n) { + + // Human readable register name + public String reg(Node n) { return reg(n,null); } + public String reg(Node n, FunNode fun) { if( _phase.ordinal() >= Phase.RegAlloc.ordinal() ) { - String s = _regAlloc.reg(n); + String s = _regAlloc.reg(n,fun); if( s!=null ) return s; } return "N"+ n._nid; } + // --------------------------- // Encoding - int _tEncode; + public int _tEncode; + public Encoding _encoding; // Encoding object + public void preEncode() { } // overridden by alternative ports public CodeGen encode() { assert _phase == Phase.RegAlloc; _phase = Phase.Encoding; long t0 = System.currentTimeMillis(); + _encoding = new Encoding(this); + preEncode(); + _encoding.encode(); + _tEncode = (int)(System.currentTimeMillis() - t0); + return this; + } - - //_tEncode = (int)(System.currentTimeMillis() - t0); - //throw Utils.TODO(); + // --------------------------- + // Exporting to external formats + public CodeGen exportELF(String fname) throws IOException { + assert _phase == Phase.Encoding; + _phase = Phase.Export; + if( fname == null ) new LinkMem(this).link(); // In memory patching + else new ElfFile(this).export(fname); // External ELF file return this; } - // Encoded binary, no relocation info - public byte[] binary() { throw Utils.TODO(); } // --------------------------- + public boolean _asmLittle=true; SB asm(SB sb) { return ASMPrinter.print(sb,this); } public String asm() { return asm(new SB()).toString(); } @@ -352,4 +434,44 @@ public class CodeGen { // Debugging helper public Node f(int idx) { return _stop.find(idx); } + + + String printCFG() { + if( _cfg==null ) return "no CFG"; + SB sb = new SB(); + for( CFGNode cfg : _cfg ) + IRPrinter.printLine(cfg,sb); + return sb.toString(); + } + + public static void print_as_hex(Encoding enc) { + for (byte b : enc._bits.toByteArray()) { + System.out.print(String.format("%02X", b)); + } + System.out.println(); + } + + public void print_as_hex() { + for (byte b : _encoding._bits.toByteArray()) { + System.out.print(String.format("%02X", b)); + } + System.out.println(); + } + + //// Debug purposes for now +// public CodeGen printENCODING() { +// ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); +// +// for(Node bb : CodeGen.CODE._cfg) { +// for(Node n: bb.outs()) { +// if(n instanceof MachNode) { +// ((MachNode) n).encoding(E); +// } +// } +// } +// +// print_as_hex(outputStream); +// // Get the raw bytes from the output stream +// return this; +// } } diff --git a/src/main/java/com/seaofnodes/simple/codegen/ElfExpr.java b/src/main/java/com/seaofnodes/simple/codegen/ElfExpr.java new file mode 100644 index 00000000..9d9b8a1c --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/codegen/ElfExpr.java @@ -0,0 +1,87 @@ +package com.seaofnodes.simple.codegen; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class ElfExpr { + public static void main(String[] args) { + try (FileOutputStream fos = new FileOutputStream("hello.o")) { + ByteBuffer buffer = ByteBuffer.allocate(512); + buffer.order(ByteOrder.LITTLE_ENDIAN); + + // === ELF HEADER (64 bytes) === + buffer.put(new byte[]{ + 0x7F, 'E', 'L', 'F', // ELF Magic + 2, 1, 1, 0, // 64-bit, little-endian, version + 0, 0, 0, 0, 0, 0, 0, // ABI padding + 2, 0, // Type: Executable (ET_EXEC) + 0x3E, 0x00, // Machine: x86-64 + 1, 0, 0, 0, // ELF version + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Entry point + 0, 0, 0, 0, 0, 0, 0, 0, // Program header offset + 64, 0, 0, 0, 0, 0, 0, 0, // Section header offset + 0, 0, 0, 0, // Flags + 64, 0, // ELF header size + 0, 0, // Program header entry size + 0, 0, // Number of program header entries + 40, 0, // Section header entry size + 2, 0, // Number of section headers + 1, 0 // Section header string table index + }); + + // === .TEXT SECTION (Executable Code) === + byte[] textSection = { + // sys_write (write(1, message, 13)) + (byte) 0x48, (byte) 0xC7, (byte) 0xC0, 0x01, 0x00, 0x00, 0x00, // mov rax, 1 + (byte) 0x48, (byte) 0xC7, (byte) 0xC7, 0x01, 0x00, 0x00, 0x00, // mov rdi, 1 + (byte) 0x48, (byte) 0xBE, (byte)0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rsi, message + (byte) 0x48, (byte) 0xC7, (byte) 0xC2, 0x0D, 0x00, 0x00, 0x00, // mov rdx, 13 + (byte) 0x0F, (byte) 0x05, // syscall + + // sys_exit (exit(0)) + (byte) 0x48, (byte) 0xC7, (byte) 0xC0, 0x3C, 0x00, 0x00, 0x00, // mov rax, 60 + (byte) 0x48, (byte) 0x31, (byte) 0xFF, // xor rdi, rdi + (byte) 0x0F, (byte) 0x05 // syscall + }; + + // Append section data + buffer.position(256); + buffer.put(textSection); + + // === STRING MESSAGE (.data Section) === + byte[] message = "Hello, World!\n".getBytes(); + buffer.position(400); + buffer.put(message); + + // === SECTION HEADERS === + buffer.position(64); + + // Null Section (required as first entry) + buffer.put(new byte[40]); + + // .text Section + buffer.putInt(1); // Offset in section string table (".text") + buffer.putInt(1); // Type: SHT_PROGBITS + buffer.putLong(6); // Flags: Executable | Allocatable + buffer.putLong(0); // Virtual address + buffer.putLong(256); // Offset to section data + buffer.putLong(textSection.length); // Section size + buffer.putInt(0); // Link + buffer.putInt(0); // Info + buffer.putLong(4); // Address alignment + buffer.putLong(0); // No extra entries + + // === SECTION HEADER STRING TABLE === + buffer.position(500); + buffer.put(".text".getBytes()); + buffer.put((byte) 0x00); // Null terminator + + fos.write(buffer.array()); + System.out.println("ELF object file 'hello.o' created."); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java b/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java new file mode 100644 index 00000000..d1d20919 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java @@ -0,0 +1,382 @@ +package com.seaofnodes.simple.codegen; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.codegen.Encoding.BAOS; +import java.io.*; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.HashMap; +import java.util.Map; + +public class ElfFile { + + final CodeGen _code; + Ary<Section> _sections = new Ary<>(Section.class); + + ElfFile( CodeGen code ) { _code = code; } + + public static int writeCString(DataSection strtab, String name) { + // place name in the string table + int pos = strtab._contents.size(); + byte[] name_bytes = name.getBytes(); + strtab._contents.write(name_bytes, 0, name_bytes.length); + // the strings are null terminated + strtab._contents.write(0); + return pos; + } + + public static void write4( ByteArrayOutputStream bits, int op ) { + bits.write(op ); + bits.write(op>> 8); + bits.write(op>>16); + bits.write(op>>24); + } + public static void write8( ByteArrayOutputStream bits, long i64 ) { + write4(bits, (int) i64 ); + write4(bits, (int)(i64>>32)); + } + + public static final int SYMBOL_SIZE = 4+1+1+2+8+8; + + public static final int SYM_BIND_LOCAL = 0; + public static final int SYM_BIND_GLOBAL = 1; + public static final int SYM_BIND_WEAK = 2; + + public static final int SYM_TYPE_NOTYPE = 0; + public static final int SYM_TYPE_OBJECT = 1; + public static final int SYM_TYPE_FUNC = 2; + public static final int SYM_TYPE_SECTION = 3; + + public static class Symbol { + String _name; + int _name_pos; + + // index in the symbol table + int _index; + + // which section is it in + int _parent; + + // depends on the symbol type + // TODO + int _value, _size; + + // top 4bits are the "bind", bottom 4 are "type" + int _info; + + // bind 1011 + // 00001111 + // 10110000 + + Symbol(String name, int parent, int bind, int type) { + _name = name; + _parent = parent; + _info = (bind << 4) | (type & 0xF); + } + + void writeHeader(ByteBuffer out) { + out.putInt(_name_pos); // name + out.put((byte) _info); // info + // default visibility + out.put((byte) 0); // other + out.putShort((short) _parent); // shndx + out.putLong(_value); // value + out.putLong(_size); // size + } + } + + public static final int SHF_WRITE = 1; + public static final int SHF_ALLOC = 2; + public static final int SHF_EXECINSTR = 4; + public static final int SHF_STRINGS = 32; + public static final int SHF_INFO_LINK = 64; + + public static final int SECTION_HDR_SIZE = 64; + abstract public static class Section { + String _name; + int _index; + + int _type, _flags; + int _link, _info; + + // where's the name in the string table + int _name_pos; + int _file_offset; + + Section(String name, int type) { + _name = name; + _type = type; + } + + void writeHeader(ByteBuffer out) { + out.putInt(_name_pos); // name + out.putInt(_type); // type + out.putLong(_flags); // flags + out.putLong(0); // addr + out.putLong(_file_offset); // offset + out.putLong(size()); // size + out.putInt(_link); // link + out.putInt(_info); // info + out.putLong(16); // addralign + if (_type == 2) { + out.putLong(SYMBOL_SIZE);// entsize + } else if (_type == 4) { + out.putLong(24); // entsize + } else { + out.putLong(0); // entsize + } + } + + abstract void write(ByteBuffer out); + abstract int size(); + } + + public static class SymbolSection extends Section { + Ary<Symbol> _symbols = new Ary<>(Symbol.class); + Ary<Symbol> _loc = new Ary<>(Symbol.class); + + SymbolSection(String name, int type) { + super(name, type); + } + + @Override + void writeHeader(ByteBuffer out) { + // points to string table section + _link = 1; + + // number of non-local symbols + _info = _loc.len() + 1; + + super.writeHeader(out); + } + + void push(Symbol s) { + if ((s._info >> 4) != SYM_BIND_LOCAL) { + _symbols.push(s); + } else { + _loc.push(s); + } + } + + @Override + void write(ByteBuffer out) { + // Index 0 both designates the first entry in the table and serves as the undefined symbol index + for( int i = 0; i < SYMBOL_SIZE/4; i++ ) { + out.putInt(0); + } + // index is already set + for( Symbol s : _loc ) { + s.writeHeader(out); + } + for( Symbol s : _symbols ) { + s.writeHeader(out); + } + } + + @Override + int size() { + return (1 + _symbols.len() + _loc.len()) * SYMBOL_SIZE; + } + } + + public static class DataSection extends Section { + ByteArrayOutputStream _contents; + + DataSection(String name, int type) { + super(name, type); + _contents = new ByteArrayOutputStream(); + } + + DataSection(String name, int type, ByteArrayOutputStream contents) { + super(name, type); + _contents = contents; + } + + @Override + void write(ByteBuffer out) { + out.put(_contents.toByteArray()); + } + + @Override + int size() { + return _contents.size(); + } + } + + private void pushSection(Section s) { + _sections.push(s); + // ELF is effectively "1-based", due to the NULL section + s._index = _sections._len; + } + + /* creates function and stores where it starts*/ + private final HashMap<TypeFunPtr,Symbol> _funcs = new HashMap<>(); + private void encodeFunctions(SymbolSection symbols, DataSection text) { + for( int i=0; i<_code._cfg._len; i++ ) { + if( !(_code._cfg.at(i) instanceof FunNode fun) ) continue; + // skip until the function ends + while( !(_code._cfg.at(i) instanceof ReturnNode ret) ) + i++; + int end = _code._encoding._opStart[ret._nid] + _code._encoding._opLen[ret._nid]; + + if( fun._name==null ) continue; // Anonymous addresses are relocated within .text. + Symbol func = new Symbol(fun._name, text._index, SYM_BIND_GLOBAL, SYM_TYPE_FUNC); + func._value = _code._encoding._opStart[fun._nid]; + func._size = end - func._value; + symbols.push(func); + _funcs.put(fun.sig(), func); + } + } + + public void export(String fname) throws IOException { + DataSection strtab = new DataSection(".strtab", 3 /* SHT_SYMTAB */); + // first byte is reserved for an empty string + strtab._contents.write(0); + pushSection(strtab); + + // place all the symbols + SymbolSection symbols = new SymbolSection(".symtab", 2 /* SHT_STRTAB */); + pushSection(symbols); + + // zero flag by default + // we've already constructed this entire section in the encoding phase + DataSection text = new DataSection(".text", 1 /* SHT_PROGBITS */, _code._encoding._bits); + text._flags = SHF_ALLOC | SHF_WRITE | SHF_EXECINSTR; + pushSection(text); + + // Build and write constant pool + BAOS cpool = new BAOS(); + Encoding enc = _code._encoding; + enc.writeConstantPool(cpool,false); + DataSection rdata = new DataSection(".rodata", 1 /* SHT_PROGBITS */, cpool); + rdata._flags = SHF_ALLOC; + pushSection(rdata); + + // populate function symbols + encodeFunctions(symbols, text); + + int idx = 1; + for( Section s : _sections ) { + Symbol sym = new Symbol(s._name, idx++, SYM_BIND_LOCAL, SYM_TYPE_SECTION); + // we can reuse the same name pos from the actual section + sym._name_pos = s._name_pos; + sym._size = s.size(); + symbols.push(sym); + } + + // calculate local index + int num = 1; + for( Symbol s : symbols._loc ) + s._index = num++; + // extra space for .rela.text + int start_global = num+1; // Add one to skip the final .rela.text local symbol + for( Symbol a: symbols._symbols ) + a._index = start_global++; + int bigConIdx = start_global; + start_global += enc._bigCons.size(); + + // create .text relocations + DataSection text_rela = new DataSection(".rela.text", 4 /* SHT_RELA */); + for( Node n : enc._externals.keySet()) { + int nid = n._nid; + String extern = enc._externals.get(n); + + Symbol sym = new Symbol(extern, 0, SYM_BIND_GLOBAL, SYM_TYPE_NOTYPE); + sym._index = start_global++; + symbols.push(sym); + + int offset = enc._opStart[nid] + enc._opLen[nid] - 4; + + // u64 offset + write8(text_rela._contents, offset); + // u64 info + write8(text_rela._contents, ((long)sym._index << 32L) | 4L /* PLT32 */); + // i64 addend + write8(text_rela._contents, -4); + } + + // Write relocations for the constant pool + for( Encoding.Relo relo : enc._bigCons.values() ) { + Symbol glob = new Symbol("GLOB$"+bigConIdx, rdata._index, SYM_BIND_GLOBAL, SYM_TYPE_FUNC); + glob._value = relo._target; + glob._size = 1 << relo._t.log_size(); + glob._index = bigConIdx++; + symbols.push(glob); + write8(text_rela._contents, relo._opStart+relo._off); + write8(text_rela._contents, ((long)glob._index << 32L) | relo._elf ); + write8(text_rela._contents, -4); + } + + text_rela._flags = SHF_INFO_LINK; + text_rela._link = 2; + text_rela._info = text._index; + pushSection(text_rela); + + Symbol sym = new Symbol(text_rela._name, num++, SYM_BIND_LOCAL, SYM_TYPE_SECTION); + sym._name_pos = text_rela._name_pos; + sym._size = text_rela.size(); + symbols.push(sym); + + // populate string table + for( Section s : _sections ) { s._name_pos = writeCString(strtab, s._name); } + for( Symbol s : symbols._symbols ) { s._name_pos = writeCString(strtab, s._name); } + for( Symbol s : symbols._loc ) { s._name_pos = writeCString(strtab, s._name); } + + int size = 64; // ELF header + // size of all section data + for( Section s : _sections ) { + s._file_offset = size; + size += s.size(); + } + // headers go at the very end + int shdr_offset = size; + // there's a "null" section at the start of the section headers + size += SECTION_HDR_SIZE*(1 + _sections.len()); + + // System.out.printf("Hello %d, %d, %s\n", size, shdr_offset); + ByteBuffer out = ByteBuffer.allocate(size); + out.order(ByteOrder.LITTLE_ENDIAN); + + // ELF header + out.put(new byte[]{ 0x7F, 'E', 'L', 'F', 2, 1, 1, 0 }); + out.put(new byte[8]); + out.putShort((short)1 /* ET_REL */); // type + out.putShort((short)62 /* AMD64 */); // machine + out.putInt(1); // version + out.putLong(0); // entry + out.putLong(0); // phoff + out.putLong(shdr_offset); // shoff + out.putInt(0); // flags + out.putShort((short)64); // ehsize + out.putShort((short)0); // phentsize + out.putShort((short)0); // phnum + out.putShort((short)64); // shentsize + out.putShort((short) (1 + _sections.len())); // shnum + out.putShort((short)1); // shstrndx + + // raw section data + for( Section s : _sections ) { + s.write(out); + } + + // "null" section + for( int i = 0; i < 16; i++ ) { + out.putInt(0); + } + + for( Section s : _sections ) { + s.writeHeader(out); + } + assert out.position() == size; + + File file = new File(fname); + if( file.getParentFile()!=null ) + file.getParentFile().mkdirs(); + BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file)); + bos.write(out.array()); + bos.close(); + } + +} diff --git a/src/main/java/com/seaofnodes/simple/codegen/Encoding.java b/src/main/java/com/seaofnodes/simple/codegen/Encoding.java new file mode 100644 index 00000000..81f195b1 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/codegen/Encoding.java @@ -0,0 +1,475 @@ +package com.seaofnodes.simple.codegen; + +import com.seaofnodes.simple.Ary; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.print.IRPrinter; +import com.seaofnodes.simple.type.*; +import java.io.ByteArrayOutputStream; +import java.util.*; + + +/** + * Instruction encodings + * <p> + * This class holds the encoding bits, plus the relocation information needed + * to move the code to a non-zero offset, or write to an external file (ELF). + * <p> + * There are also a bunch of generic utilities for managing bits and bytes + * common in all encodings. + */ +public class Encoding implements com.seaofnodes.isa.CodeSink { + + // Top-level program graph structure + public final CodeGen _code; + + // Instruction bytes. The registers are encoded already. Relocatable data + // is in a fixed format depending on the kind of relocation. + + // - RIP-relative offsets into the same chunk are just encoded with their + // check relative offsets; no relocation info is required. + + // - Fixed address targets in the same encoding will be correct for a zero + // base; moving the entire encoding requires adding the new base address. + + // - RIP-relative to external chunks have a zero offset; the matching + // relocation info will be used to patch the correct value. + + public static class BAOS extends ByteArrayOutputStream { + public byte[] buf() { return buf; } + void set( byte[] buf0, int count0 ) { buf=buf0; count=count0; } + }; + final public BAOS _bits = new BAOS(); + + public int [] _opStart; // Start of opcodes, by _nid + public byte[] _opLen; // Length of opcodes, by _nid + + // Big Constant relocation info. + public static class Relo { + public final Node _op; + public final Type _t; // Constant type + public final int _align; // Log alignment, captured for diagnostic display + public final byte _off; // Offset from start of opcode + public final byte _elf; // ELF relocation type, e.g. 2/PC32 + public int _target; // Where constant is finally placede + public int _opStart; // Opcode start + Relo( Node op, Type t, byte off, byte elf ) { + _op=op; _t=t; _off=off; _elf=elf; + _align = t.log_size(); + } + } + public final HashMap<Node,Relo> _bigCons = new HashMap<>(); + + Encoding( CodeGen code ) { _code = code; } + + // Shortcut to the defining register + public short reg(Node n) { + return _code._regAlloc.regnum(n); + } + + public int read1(int idx) { + byte[] buf = _bits.buf(); + return (buf[idx]&0xFF); + } + public int read2(int idx) { + byte[] buf = _bits.buf(); + return (buf[idx]&0xFF) | (buf[idx+1]&0xFF) <<8; + } + public int read4(int idx) { + byte[] buf = _bits.buf(); + return (buf[idx]&0xFF) | (buf[idx+1]&0xFF) <<8 | (buf[idx+2]&0xFF)<<16 | (buf[idx+3]&0xFF)<<24; + } + public long read8(int idx) { + return (read4(idx) & 0xFFFFFFFFL) | read4(idx+4); + } + + // This buffer is invalid/moving until after all encodings are written + public byte[] bits() { return _bits.buf(); } + + // 4 byte little-endian write + public void patch4( int idx, int val ) { + byte[] buf = _bits.buf(); + buf[idx ] = (byte)(val ); + buf[idx+1] = (byte)(val>> 8); + buf[idx+2] = (byte)(val>>16); + buf[idx+3] = (byte)(val>>24); + } + + static void padN(int n, BAOS bits) { + while( (bits.size()+n-1 & -n) > bits.size() ) + bits.write(0); + } + + // Convenience for writing log-N + static void addN( int log, Type t, BAOS bits ) { + long x = t instanceof TypeInteger ti + ? ti.value() + : log==3 + ? Double.doubleToRawLongBits( ((TypeFloat)t).value()) + : Float.floatToRawIntBits((float)((TypeFloat)t).value()); + addN(log,x,bits); + } + static void addN( int log, long x, BAOS bits ) { + for( int i=0; i < 1<<log; i++ ) { + bits.write((int)x); + x >>= 8; + } + } + + public Encoding add1( int op ) { addN(0,op,_bits); return this; } + public Encoding add2( int op ) { addN(1,op,_bits); return this; } + public Encoding add4( int op ) { addN(2,op,_bits); return this; } + public Encoding add8(long op ) { addN(3,op,_bits); return this; } + + + // Nodes need "relocation" patching; things done after code is placed. + // Record src and dst Nodes. + private final HashMap<Node,CFGNode> _internals = new HashMap<>(); + // Source is a Call, destination in the Fun. + public Encoding relo( CallNode call ) { + _internals.put(call,_code.link(call.tfp())); + return this; + } + public Encoding relo( ConstantNode con ) { + TypeFunPtr tfp = (TypeFunPtr)con._con; + _internals.put(con,_code.link(tfp)); + return this; + } + public void jump( CFGNode jmp, CFGNode dst ) { + _internals.put(jmp,dst.uctrlSkipEmpty()); + } + + + final HashMap<Node,String> _externals = new HashMap<>(); + public Encoding external( Node call, String extern ) { + _externals.put(call,extern); + return this; + } + + // Store t as a 32/64 bit constant in the code space; generate RIP-relative + // addressing to load it + public void largeConstant( Node relo, Type t, int off, int elf ) { + assert t.isConstant(); + assert (byte)off == off; + assert (byte)elf == elf; + _bigCons.put(relo,new Relo(relo,t,(byte)off,(byte)elf)); + } + + // -------------------------------------------------- + void encode() { + // Basic block layout: negate branches to keep blocks in-order; insert + // unconditional jumps. Attempt to keep backwards branches taken, + // forwards not-taken (this is the default prediction on most + // hardware). Layout is still Reverse Post Order but with more + // restrictions. + basicBlockLayout(); + + // Write encoding bits in order into a big byte array. + // Record opcode start and length. + writeEncodings(); + + // Short-form RIP-relative support: replace long encodings with short + // encodings and compact the code, changing all the offsets. + compactShortForm(); + + // Patch RIP-relative and local encodings now. + patchLocalRelocations(); + } + + // -------------------------------------------------- + // Basic block layout: negate branches to keep blocks in-order; insert + // unconditional jumps. Attempt to keep backwards branches taken, forwards + // not-taken (this is the default prediction on most hardware). Layout is + // still Reverse Post Order but with more restrictions. + private void basicBlockLayout() { + IdentityHashMap<LoopNode,Ary<CFGNode>> rpos = new IdentityHashMap<>(); + Ary<CFGNode> rpo = new Ary<>(CFGNode.class); + rpos.put(_code._start.loop(),rpo); + BitSet visit = _code.visit(); + //IdentityHashMap<CFGNode,LoopNode> looptail = new IdentityHashMap<>(); + rpo.add(_code._stop); + for( Node n : _code._start._outputs ) + if( n instanceof FunNode fun ) { + int x = rpo._len; + //_rpo_cfg2(fun, visit, rpo, looptail); + _rpo_cfg(fun, visit, rpos ); + assert rpo.at(x) instanceof ReturnNode; + } + rpo.add(_code._start); + + // Reverse in-place + for( int i=0; i< rpo.size()>>1; i++ ) + rpo.swap(i,rpo.size()-1-i); + visit.clear(); + _code._cfg = rpo; // Save the new ordering + } + + + private void _rpo_cfg(CFGNode bb, BitSet visit, IdentityHashMap<LoopNode,Ary<CFGNode>> rpos ) { + if( bb==null || visit.get(bb._nid) ) return; + visit.set(bb._nid); + CFGNode next = bb.uctrl(); + + // Loops run an inner "rpo_cfg", then append the entire loop body in + // place. This keeps loop bodies completely contained, although if + // Some Day Later we have real profile data we ought to arrange the + // branch orderings based on frequency. + if( bb instanceof LoopNode loop ) { + Ary<CFGNode> body = new Ary<>(CFGNode.class); // Private RPO for the loop + rpos.put(loop.loop(),body); // Find it via loop tree + _rpo_cfg(next,visit,rpos); // RPO the loop + body.add(loop); // Include loop last (first in RPO) + Ary<CFGNode> outer = rpos.get(loop.cfg(1).loop()); + outer.addAll(body); // Append to original CFG + return; + } + + // IfNodes visit 2 sides, and may choose to reorder them in the RPO + if( bb instanceof IfNode iff ) { + // Pick out the T/F projections + CProjNode t = iff.cproj(0); + CProjNode f = iff.cproj(1); + // Invert the branch or not + if( shouldInvert(t,f,iff.loopDepth()) ) { + iff.negate(); + t.invert(); + f.invert(); + CProjNode tmp=f; f=t; t=tmp; // Swap t/f + } + + // Whichever side is visited first becomes last in the RPO. With + // no loops, visit the False side last (so True side first) so that + // when the False RPO visit returns, the IF is immediately next. + // When the RPO is reversed, the fall-through path will always be + // following the IF. + _rpo_cfg(t,visit,rpos); // True side first + next = f; // False side last + } + + // If the *next* BB has already been visited, and we are not already a + // jump, we may need an unconditional forwards jump here + Ary<CFGNode> rpo = rpos.get(bb.loop()); + if( next!=null && visit.get(next._nid) && !(bb instanceof IfNode) ) { + boolean needJump = next instanceof LoopNode + // Empty blocks from an IF will invert the IF and backwards + // branch to the loop head. If not empty, or not an IF + // will need a jump. + ? (bb.nOuts()>1 || !(bb.cfg0() instanceof IfNode) ) + // Forwards jump. If all the blocks, in RPO order, to our + // target are empty, we will fall in and not need a jump. + : !backwardsEmptyScan(rpo,next); + if( needJump ) { + CFGNode jmp = _code._mach.jump(); + jmp._ltree = bb._ltree; + jmp.setDefX(0,bb); + next.setDef(next._inputs.find(bb),jmp); + rpo.add(jmp); + } + } + + _rpo_cfg(next,visit,rpos); + rpo.add(bb); + } + + // Should this test be inverted? + private static boolean shouldInvert(CFGNode t, CFGNode f, int bld) { + int tld = t.loopDepth(), fld = f.loopDepth(); + // These next two are symmetric and can happen in any order; if `tld < + // bld` is true, the `fld < bld` must be false, or else both directions + // exit the loop... and the IF test would not be in the loop. + + // true to exit a loop usually (and false falls into Yet Another Loop + // Block), invert if false is taking an empty backwards branch. + if( tld < bld ) return forwardsEmptyScan(f,bld); + // false to exit a loop, normally invert to true (except empty back) + if( fld < bld ) return !forwardsEmptyScan(t,bld); + + // Not exiting the loop; staying at this depth (or going deeper loop). + + // Jump/true to an empty backwards block + if( forwardsEmptyScan(t,bld) ) return false; + if( forwardsEmptyScan(f,bld) ) return true ; + + // Fall/false into a full block, Jump/true to an empty block. + if( f.nOuts()>1 && t.nOuts()==1 ) return false; + if( t.nOuts()>1 && f.nOuts()==1 ) return true ; + // Everything else equal, use pre-order + return t._pre > f._pre; + } + + // Do we continue forwards from 'c' to the Loop back edge, without doing + // anything else? Then this branch can directly jump to the loop head. + private static boolean forwardsEmptyScan( CFGNode c, int bld ) { + if( c.nOuts()!=1 || c.loopDepth()!=bld ) return false; + return c.uctrl() instanceof RegionNode cfg && + ((cfg instanceof LoopNode && cfg._ltree==c._ltree) || forwardsEmptyScan(cfg,bld)); + } + + // Is the CFG from "next" to the end empty? This means jumping to "next" + // will naturally fall into the end. + private static boolean backwardsEmptyScan(Ary<CFGNode> rpo, CFGNode next) { + for( int i=rpo._len-1; rpo.at(i)!=next; i-- ) + if( rpo.at(i).nOuts()!=1 ) + return false; + return true; + } + + // -------------------------------------------------- + // Write encoding bits in order into a big byte array. + // Record opcode start and length. + public FunNode _fun; // Currently encoding function + private void writeEncodings() { + _opStart= new int [_code.UID()]; + _opLen = new byte[_code.UID()]; + for( CFGNode bb : _code._cfg ) { + if( !(bb instanceof MachNode mach0) ) + _opStart[bb._nid] = _bits.size(); + else if( bb instanceof FunNode fun ) { + _fun = fun; // Currently encoding function + _opStart[bb._nid] = _bits.size(); + mach0.encoding( this ); + _opLen[bb._nid] = (byte) (_bits.size() - _opStart[bb._nid]); + } + for( Node n : bb._outputs ) { + if( n instanceof MachNode mach && !(n instanceof FunNode) ) { + _opStart[n._nid] = _bits.size(); + mach.encoding( this ); + _opLen[n._nid] = (byte) (_bits.size() - _opStart[n._nid]); + } + } + } + } + + // -------------------------------------------------- + // Short-form RIP-relative support: replace short encodings with long + // encodings and expand the code, changing all the offsets. + private void compactShortForm() { + int len = _code._cfg._len; + int[] oldStarts = new int[len]; + for( int i=0; i<len; i++ ) + oldStarts[i] = _opStart[_code._cfg.at(i)._nid]; + + // TODO: Rewrite this algo to use the small "_jmps" list of just the + // jumps instead of walking all blocks. + // TODO2: Allow short encodings on non-jumps. This algo doesn't + // really care jumps vs other rip-relative things. + + // Check the size of short jumps, adjust the block starts for a whole + // pass over all blocks. If some jumps go long, they might stretch the + // distance for other jumps, so another pass is needed. + int slide = -1; + while( slide != 0) { // While no fails + slide = 0; + for( int i=0; i<len; i++ ) { + CFGNode bb = _code._cfg.at(i); + // Functions pad to align 16 + if( bb instanceof FunNode ) { + int newStart = _opStart[bb._nid]+slide; + slide += (newStart+15 & -16)-newStart; + } + _opStart[bb._nid] += slide; + // Slide down all other (non-CFG) ops in the block + for( Node n : bb._outputs ) + if( n instanceof MachNode && !(n instanceof CFGNode) ) + _opStart[n._nid] += slide; + if( bb instanceof RIPRelSize riprel ) { + CFGNode target = (bb instanceof IfNode iff ? iff.cproj(0) : (CFGNode)bb.out(0)).uctrlSkipEmpty(); + // Delta is from opStart to opStart. X86 at least counts + // the delta from the opEnd, but we don't have the end until + // we decide the size - so the encSize has to deal + int delta = _opStart[target._nid] - _opStart[bb._nid]; + byte opLen = riprel.encSize(delta); + // Recorded size is smaller than the current size? + if( _opLen[bb._nid] < opLen ) { + // Start sliding the code down; record slide amount and new size + slide += opLen - _opLen[bb._nid]; + _opLen[bb._nid] = opLen; + } + } + } + } + + + // Copy/slide the bits to make space for all the longer branches + int grow = _opStart[_code._cfg.at(len-1)._nid] - oldStarts[len-1]; + if( grow > 0 ) { // If no short-form ops, nothing to do here + int end = _bits.size(); + byte[] bits = new byte[end+grow]; + for( int i=len-1; i>=0; i-- ) { + int start = oldStarts[i]; + if( start==0 && i>1 ) continue; + int oldStart = oldStarts[i]; + int newStart = _opStart[_code._cfg.at(i)._nid]; + System.arraycopy(_bits.buf(),oldStart,bits,newStart,end-start); + end = start; + } + _bits.set(bits,bits.length); + } + } + + + // -------------------------------------------------- + // Patch local encodings now + void patchLocalRelocations() { + // Walk the local code-address relocations + for( Node src : _internals.keySet() ) { + int start = _opStart[src._nid]; + Node dst = _internals.get(src); + int target = _opStart[dst._nid]; + ((RIPRelSize)src).patch(this, start, _opLen[src._nid], target - start); + } + } + + // -------------------------------------------------- + // Write the constant pool into the BAOS and optionally patch locally + void writeConstantPool( BAOS bits, boolean patch ) { + padN(16,bits); + + HashMap<Type,Integer> targets = new HashMap<>(); + + // By log size + for( int log = 3; log >= 0; log-- ) { + // Write the 8-byte constants + for( Node op : _bigCons.keySet() ) { + Relo relo = _bigCons.get(op); + if( relo._t.log_size()==log ) { + // Map from relo to constant start and patch + Integer target = targets.get(relo._t); + if( target==null ) { + targets.put(relo._t,target = bits.size()); + // Put constant into code space. + if( relo._t instanceof TypeTuple tt ) // Constant tuples put all entries + for( Type tx : tt._types ) + addN(log,tx,bits); + else + addN(log,relo._t,bits); + } + relo._target = target; + relo._opStart= _opStart[op._nid]; + // Go ahead and locally patch in-memory + if( patch ) + ((RIPRelSize)op).patch(this, relo._opStart, _opLen[op._nid], relo._target - relo._opStart); + } + } + } + } + + + // A series of libc/external calls that Simple can link against in a JIT. + // Since no runtime in the JVM process, using magic numbers for the CPU + // emulators to pick up on. + public static int SENTINAL_CALLOC = -4; + + void patchGlobalRelocations() { + for( Node src : _externals.keySet() ) { + int start = _opStart[src._nid]; + String dst = _externals.get(src); + int target = switch( dst ) { + case "calloc" -> SENTINAL_CALLOC; + default -> throw Utils.TODO(); + }; + ((RIPRelSize)src).patch(this, start, _opLen[src._nid], target - start); + } + } +} diff --git a/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java index 52906afb..447a6df1 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java +++ b/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java @@ -172,7 +172,7 @@ public abstract class GlobalCodeMotion { continue outer; // Nope, await all uses done // Loads need their memory inputs' uses also done - if( n instanceof MemOpNode ld && !ld.isMem() ) + if( n instanceof MemOpNode ld && ld._isLoad ) for( Node memuse : ld.antiDeps() ) if( late[memuse._nid]==null ) continue outer; @@ -205,9 +205,10 @@ public abstract class GlobalCodeMotion { lca = use_block(n,use, late).domLCA(lca,null); // Loads may need anti-dependencies, raising their LCA - if( n instanceof MemOpNode load && !load.isMem() ) + if( n instanceof MemOpNode load && load._isLoad ) lca = find_anti_dep(lca,load,early,late,anti); + // Walk up from the LCA to the early, looking for best place. This is // the lowest execution frequency, approximated by least loop depth and // deepest control flow. @@ -250,7 +251,7 @@ public abstract class GlobalCodeMotion { if( visit.get(def._nid) ) return; visit.set(def._nid); for( Node out : def._outputs ) - if( out instanceof MemOpNode ld && !ld.isMem() && late[ld._nid]==null ) work.push(ld); + if( out instanceof MemOpNode ld && ld._isLoad && late[ld._nid]==null ) work.push(ld); if( def instanceof MemMergeNode ) for( int i=1; i<def.nIns(); i++ ) if( def.in(i)!=null ) wakeLoads(def.in(i),late,work,visit); @@ -291,6 +292,8 @@ public abstract class GlobalCodeMotion { // private static CFGNode anti_dep( MemOpNode load, CFGNode stblk, CFGNode defblk, CFGNode lca, Node st, int[] anti ) { + // A conditional writer can precede the load without dominating its late block. + // Walk back to the first overlap with the load's placement range. for( ; stblk != defblk.idom(); stblk = stblk.idom() ) { // Store and Load overlap, need anti-dependence if( anti[stblk._nid]==load._nid ) { diff --git a/src/main/java/com/seaofnodes/simple/codegen/IFG.java b/src/main/java/com/seaofnodes/simple/codegen/IFG.java index 849d256e..bc5fae8d 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/IFG.java +++ b/src/main/java/com/seaofnodes/simple/codegen/IFG.java @@ -20,7 +20,7 @@ abstract public class IFG { static final IdentityHashMap<LRG,Node> TMP = new IdentityHashMap<>(); // Inteference Graph: Array of Bitsets - private static final Ary<BitSet> IFG = new Ary<>(BitSet.class); + static final Ary<BitSet> IFG = new Ary<>(BitSet.class); static void resetIFG() { for( BitSet bs : IFG ) if( bs!=null ) bs.clear(); @@ -80,6 +80,8 @@ abstract public class IFG { while( !WORK.isEmpty() ) do_block(round,alloc,WORK.pop()); + if( alloc.success() ) + convert2DAdjacency(alloc); return alloc.success(); } @@ -126,10 +128,15 @@ abstract public class IFG { TMP.remove(lrg); // Kill def } + // Phis use and define the same live range, i.e. these LRGs already + // marked conflicted, no need to mark again if( n instanceof PhiNode ) return; - // Instructions can clobber registers without defining an LRG. - if( n instanceof MachNode m ) kills(alloc,m); + + // Kill any killed registers; milli-code routines like New can kill + // will not being a CFG. + if( n instanceof MachNode m ) + kills(alloc,m); // Interfere n with all live if( lrg!=null ) { @@ -145,7 +152,8 @@ abstract public class IFG { // 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); + if( !tlrg.clr(lrg._mask.firstReg()) ) // Clear bit, no interference + alloc.fail(tlrg); // Clearing drives mask to empty } else addIFG(lrg,tlrg); // Add interference } } @@ -223,13 +231,11 @@ abstract public class IFG { 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<>() ); + IdentityHashMap<LRG, Node> lrgs = BBOUTS.computeIfAbsent( bb, k -> new IdentityHashMap<>() ); - for( LRG lrg : TMP.keySet() ) { + 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 ) { @@ -250,6 +256,24 @@ abstract public class IFG { } + static void convert2DAdjacency( RegAlloc alloc ) { + // Convert the 2-D array of bits (a 1-D array of BitSets) into an + // adjacency matrix. + int maxlrg = alloc._LRGS.length; + for( int i=1; i<maxlrg; i++ ) { + 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); + } + } + } + } + + // ------------------------------------------------------------------------ // Color the inference graph. @@ -266,22 +290,10 @@ abstract public class IFG { // 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); - } - } - } + for( int i=1; i<maxlrg; i++ ) + if( alloc._LRGS[i] != null ) + nlrgs++; // Simplify @@ -494,7 +506,7 @@ abstract public class IFG { // Can I limit my own choices to valid neighbor choices? for( LRG alrg : slrg._adj ) { - int reg = alrg._reg; + short reg = alrg._reg; if( reg == -1 && alrg._mask.size1() ) reg = alrg._mask.firstReg(); if( reg != -1 ) { diff --git a/src/main/java/com/seaofnodes/simple/codegen/LRG.java b/src/main/java/com/seaofnodes/simple/codegen/LRG.java index 5803cad8..ec254a2e 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/LRG.java +++ b/src/main/java/com/seaofnodes/simple/codegen/LRG.java @@ -2,6 +2,7 @@ package com.seaofnodes.simple.codegen; import com.seaofnodes.simple.Ary; import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.node.*; import java.util.IdentityHashMap; @@ -37,6 +38,8 @@ public class LRG { // Count of single-register defs and uses short _1regDefCnt, _1regUseCnt; + // Count of all defs, uses. Mostly interested 1 vs many + boolean _multiDef, _multiUse; // A sample MachNode def in the live range public MachNode _machDef, _machUse; @@ -54,6 +57,8 @@ public class LRG { // Adjacent Live Range neighbors. Only valid during coloring Ary<LRG> _adj; + public int nadj() { return _adj==null ? 0 : _adj._len; } + void addNeighbor(LRG lrg) { if( _adj==null ) _adj = new Ary<>(LRG.class); _adj.push(lrg); @@ -72,7 +77,7 @@ public class LRG { } // More registers than neighbors - boolean lowDegree() { return (_adj==null ? 0 : _adj._len) < _mask.size(); } + boolean lowDegree() { return nadj() < _mask.size(); } LRG( short lrg ) { _lrg = lrg; _reg = -1; } @@ -116,17 +121,20 @@ public class LRG { if( _machDef==null ) { _machDef = lrg._machDef; } else if( lrg._machDef!=null ) { + if( _machDef != lrg._machDef ) _multiDef=true; if( _1regDefCnt==0 ) _machDef = lrg._machDef; else if( _machDef==lrg._machDef ) _1regDefCnt--; } _1regDefCnt += lrg._1regDefCnt; + _multiDef |= lrg._multiDef; if( _machUse==null ) { _machUse = lrg._machUse; _uidx = lrg._uidx; } else if( lrg._machUse!=null ) { + if( _machUse != lrg._machUse ) _multiUse=true; if( _1regUseCnt==0 ) { _machUse = lrg._machUse; _uidx = lrg._uidx; @@ -135,6 +143,7 @@ public class LRG { _1regUseCnt--; } _1regUseCnt += lrg._1regUseCnt; + _multiUse |= lrg._multiUse; // Fold deepest Split _splitDef = deepSplit(_splitDef,lrg._splitDef); @@ -142,7 +151,9 @@ public class LRG { // Fold together masks RegMask mask = _mask.and(lrg._mask); - _mask = mask==null ? _mask.copy().and(lrg._mask) : mask; + if( mask==null ) + mask = _mask.copy().and(lrg._mask); + _mask = mask; return this; } @@ -152,6 +163,8 @@ public class LRG { // Record any Mach def for spilling heuristics LRG machDef( MachNode def, boolean size1 ) { + if( _machDef!=null && _machDef!=def ) + _multiDef = true; if( _machDef==null || size1 ) _machDef = def; if( size1 ) @@ -163,6 +176,8 @@ public class LRG { // Record any Mach use for spilling heuristics LRG machUse( MachNode use, short uidx, boolean size1 ) { + if( _machUse!=null && _machUse!=use ) + _multiUse = true; if( _machUse==null || size1 ) { _machUse = use; _uidx = uidx; } if( size1 ) diff --git a/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java b/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java new file mode 100644 index 00000000..e1e849e6 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java @@ -0,0 +1,23 @@ +package com.seaofnodes.simple.codegen; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.node.Node; + +// In-memory linking +public class LinkMem { + final CodeGen _code; + LinkMem( CodeGen code ) { _code = code; } + + public CodeGen link() { + Encoding enc = _code._encoding; + + // Patch external calls internally + enc.patchGlobalRelocations(); + + // Write any large constants into a constant pool; they + // are accessed by RIP-relative addressing. + enc.writeConstantPool(enc._bits,true); + + return _code; + } +} diff --git a/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java b/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java index 1796ef7c..de4fadc4 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java +++ b/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java @@ -48,6 +48,9 @@ public abstract class ListScheduler { computeSingleRDef(bb,use); else computeSingleRDef(bb,n); + + if( n instanceof NewNode || n instanceof CallNode ) + bb.fun()._hasCalls = true; return this; } diff --git a/src/main/java/com/seaofnodes/simple/codegen/Machine.java b/src/main/java/com/seaofnodes/simple/codegen/Machine.java index 13d7abc1..2766414f 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/Machine.java +++ b/src/main/java/com/seaofnodes/simple/codegen/Machine.java @@ -1,22 +1,97 @@ package com.seaofnodes.simple.codegen; import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeFunPtr; abstract public class Machine { // Human readable machine name. Something like "x86-64" or "arm" or "risc5" public abstract String name(); + // Default opcode size for printing; 4 bytes for most 32-bit risc chips + public int defaultOpSize() { return 4; } // Human-readable name for a register number, e.g. "RAX" or "R0" - public abstract String reg( int reg ); + public abstract String[] regs(); // Create a split op; any register to any register, including stack slots 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(); + // List of caller-save registers, as a 64bit mask + public abstract long callerSave(); + // List of never-save registers, e.g. RSP or a ZERO register if you have one + public abstract long neverSave(); + // Call Argument Mask. Passed in the function signature and argument + // number (2-based; 0 is for control and 1 for memory). Also passed in a 0 + // for the function itself, or for *outgoing* calls, the maximum stack slot + // given to the incoming function arguments (stack slots reserved for + // incoming arguments). + public abstract RegMask callArgMask(TypeFunPtr tfp, int arg, int maxArgSlot); + // Return register mask, based on signature (GPR vs FPR) + public abstract RegMask retMask(TypeFunPtr tfp); + // Return PC register + public abstract int rpc(); // Return a MachNode unconditional branch public abstract CFGNode jump(); - // Break an infinite loop - public abstract IfNode never( CFGNode ctrl ); // Instruction select from ideal nodes public abstract Node instSelect( Node n ); + + /** Stack Slot Numbering + +RA gives all registers a number, starting at 0. Stack slots continue this +numbering going up from the last register number. Common register numbers are +0-15 for GPRs, 16-31 for FPRs, 32 for flags, and stack slots starting at +register#33 going up. These numbers are machine-specific, YMMV, etc; e.g. RPC +is only in stack slot 0 on X86; other cpus start with the rpc in a register +which may spill into any generic spill slot. + +For ease of reading and printing, stack slots start again at slot#0 - but +during RA they are actually biased by CPU.MAX_REG. + +Stack Layout during Reg Alloc: + + Slot Value Description + ---- ------- ----------- +MAXREG+ R+1 argR maxArgSlot(calleR tfp), can be 0 + ... +MAXREG+ 1 arg0 + ---- -- Caller ------ +MAXREG+ 0 RPC +MAXREG+ R+E+N PAD optional alignment + +MAXREG+ + Spills Caller Frame Space; N spills +MAXREG+ R+E+1 Spills + +MAXREG+ R+E argE maxArgSlot(calleE tfp), can be 0 + ... +MAXREG+ R+2 arg0 + ---- -- Callee ------- + ... + +Slots defined by maxArgSlot do not have to contain the actual passed argument; +they can be "shadow slots". If they exist, they are property of the frame / +FunNode where the TFP comes from. + +For a given calleR, there are generally many calleE's and they all share the +low-range stack slots. + +Example:, Win64, main(int arg) { calloc(1,size); } + + Post-Warp: SP+56 shadow-main-arg +Pre-alloc Post-Alloc SP+48 RPC +--- ------- ---------- ------------- +40 spill#3 SP+40 SP+40 spill#3 +39 spill#2 SP+32 "" +38 spill#1 SP+24 "" +37 spill#0 SP+16 "" spill#0 +36 shadow-calloc#size SP+ 8 SP+ 8 shadow-calloc#size +35 shadow-calloc#1 SP+ 0 SP+ 0 shadow-calloc#1 +--- --- FRAME BASE ---------- ---- FRAME BASE ------- +34 shadow-main-arg SP+56 +33 RPC SP+48 + + + */ + + + // Maximum stack slot (or 0) for the args in this TFP. This will include + // shadow slots if defined in the ABI, even if all arguments are passed in + // registers. + public abstract short maxArgSlot(TypeFunPtr tfp); + } diff --git a/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java b/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java new file mode 100644 index 00000000..777182dc --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java @@ -0,0 +1,14 @@ +package com.seaofnodes.simple.codegen; + +// This Node rip-relative encodings has sizes that vary by delta +public interface RIPRelSize { + // delta is the distance from the opcode *start* to the target start. Each + // hardware target adjusts as needed, X86 measures from the opcode *end* + // and small jumps are 2 bytes, so they'll need to subtract 2. + byte encSize(int delta); + + // Patch full encoding in. Used for both short and long forms. delta + // again is from the opcode *start*. + void patch( Encoding enc, int opStart, int opLen, int delta ); + +} diff --git a/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java b/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java index af28cbb3..dd3d2acb 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java +++ b/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java @@ -5,6 +5,7 @@ import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.Type; import java.util.Arrays; +import java.util.BitSet; import java.util.IdentityHashMap; /** @@ -138,11 +139,18 @@ public class RegAlloc { } // Printable register number for node n - String reg( Node n ) { + String reg( Node n ) { return reg(n,null); } + String reg( Node n, FunNode fun ) { LRG lrg = _lrg(n); if( lrg==null ) return null; + // No register yet, use LRG if( lrg._reg == -1 ) return "V"+lrg._lrg; - return _code._mach.reg(lrg._reg); + // Chosen machine register unless stack-slot and past RA + String[] regs = _code._mach.regs(); + if( lrg._reg < regs.length || _code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() || fun==null ) + return RegMask.reg(regs,lrg._reg); + // Stack-slot past RA uses the frame layout logic + return "[rsp+"+fun.computeStackOffset(_code,lrg._reg)+"]"; } // ----------------------- @@ -150,9 +158,24 @@ public class RegAlloc { public void regAlloc() { // Insert callee-save registers + String[] regs = _code._mach.regs(); + long neverSave= _code._mach.neverSave(); for( CFGNode bb : _code._cfg ) - if( bb instanceof FunNode fun ) - insertCalleeSave(fun); + if( bb instanceof FunNode fun ) { + ReturnNode ret = fun.ret(); + int len = Math.min(regs.length,64); + for( int reg=0; reg<len; reg++ ) + if( !_code._callerSave.test(reg) && ((1L<<reg)&neverSave)==0 ) { + ret.addDef(new CalleeSaveNode(fun,reg,regs[reg])); + assert ret.regmap(ret.nIns()-1).firstReg()==reg; + } + } + // Cache reg masks for New and Call + for( CFGNode bb : _code._cfg ) { + if( bb instanceof CallEndNode cend ) cend.cacheRegs(_code); + for( Node n : bb._outputs ) + if( n instanceof NewNode nnn ) nnn.cacheRegs(_code); + } // Optional diagnostic for values accidentally shared across functions. //assert verifyFunctionLocalEdges(); @@ -161,7 +184,8 @@ public class RegAlloc { byte round=0; while( !graphColor(round) ) { split(round); - assert round < 7 : "Register allocation made no progress after eight rounds"; + if( round >= 7 ) // Really expect to be done soon + throw new IllegalStateException("Register allocation made no progress after eight rounds"); round++; } postColor(); // Remove no-op spills @@ -193,24 +217,12 @@ public class RegAlloc { BuildLRG.run(round,this) && // if no hard register conflicts // Build Interference Graph IFG.build(round,this) && // If no self conflicts or uncolorable + // Conservative coalesce copies + Coalesce.coalesce(round,this) && // 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) { @@ -220,12 +232,15 @@ public class RegAlloc { // independently... which generally requires a full pass over the // program for each failing live range. i.e., might be a lot of // passes. + + // Sort, to avoid non-deterministic HashMap ordering LRG[] splits = _failed.keySet().toArray(new LRG[0]); - Arrays.sort(splits, (x,y) -> x._lrg - y._lrg); - for( LRG lrg : splits ) split(round,lrg); + Arrays.sort(splits, (x,y) -> x._lrg - y._lrg ); + for( LRG lrg : splits ) + split(round,lrg); } - // Split this live range + // Split this live range, top level heuristic boolean split( byte round, LRG lrg ) { assert lrg.leader(); // Already rolled up @@ -234,7 +249,7 @@ public class RegAlloc { // Register mask when empty; split around defs and uses with limited // register masks. - if( lrg._mask.isEmpty() ) { + if( lrg._mask.isEmpty() && (!lrg._multiDef || lrg._1regUseCnt==1) ) { if( lrg._1regDefCnt <= 1 && lrg._1regUseCnt <= 1 && (lrg._1regDefCnt + lrg._1regUseCnt) > 0 && @@ -358,10 +373,10 @@ public class RegAlloc { (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)) ) + // Single user is already a split adjacent + !(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); + insertAfterAndReplace( makeSplit("def/loop",round,lrg), n,true); } // PhiNodes check all CFG inputs @@ -372,17 +387,19 @@ public class RegAlloc { // 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()) ) + !(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); + // This is a LRG use + // splitting in inner loop or at loop border + if( lrgSame( n.in( i ), lrg ) && + (min == max || (n.in(i) instanceof MachNode mach && mach.isClone()) || n.cfg0().loopDepth() <= min) ) + // Split before in this block + insertBefore( n, i, "use/loop/use", round,lrg, false ); } } } @@ -403,6 +420,22 @@ public class RegAlloc { int min = (int)ld; int max = (int)(ld>>32); int d = cfg.loopDepth(); + // if n will lower the min loop and is in the tail end of the loop + // header, splitting "around" the loop will not help. Treat n as being + // in the loop. + if( d < min ) { + if( cfg.uctrl() instanceof LoopNode loop && loop.entry()==cfg ) { + for( int i=cfg.nOuts()-2; i>=0; i-- ) { + Node out = cfg.out(i); + if( n==out ) + { d = loop.loopDepth(); break; } // Treat n as being "in the loop" + if( !((out instanceof MachNode mach && mach.isClone()) || out instanceof SplitNode ) ) + break; // Treat b as "normal", out of loop + } + } + } + + // lower min, raise max, and re-fold min = Math.min(min,d); max = Math.max(max,d); return ((long)max<<32) | min; @@ -428,7 +461,6 @@ public class RegAlloc { 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. @@ -449,6 +481,9 @@ public class RegAlloc { if( skip && def instanceof SplitNode && cfg==def.cfg0() ) n.in(i).setDefOrdered(1,def.in(1)); } + void insertBefore(Node n, int i, String kind, byte round, LRG lrg) { + insertBefore(n,i,kind,round,lrg,true); + } // Replace uses of `def` with `split`, and insert `split` immediately after // `def` in the basic block. @@ -486,18 +521,34 @@ public class RegAlloc { // ----------------------- // POST PASS: Remove empty spills that biased-coloring made private void postColor() { + int maxReg = -1; for( CFGNode bb : _code._cfg ) { // For all ops + if( bb instanceof FunNode fun ) + maxReg = -1; // Reset for new function + // Compute frame size, based on arguments and largest reg seen + if( bb instanceof ReturnNode ret ) + ret.fun().computeFrameAdjust(_code,maxReg); + // Raise frame size by max stack args passed, even if ignored + if( bb instanceof CallEndNode cend ) + maxReg = Math.max(maxReg,cend._xslot); + for( int j=0; j<bb.nOuts(); j++ ) { Node n = bb.out(j); - if( !(n instanceof SplitNode lo) ) continue; - int defreg = lrg(n )._reg; + if( lrg(n)!=null ) + maxReg = Math.max(maxReg,lrg(n)._reg+1); + // Raise frame size by max stack args passed to New + if( n instanceof NewNode nnn ) + maxReg = Math.max(maxReg,nnn._xslot); + + if( !(n instanceof SplitNode ) ) 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 ) { + // Same register, or the next instruction can use the source directly. + if( defreg == usereg || splitForward(bb,j,n,usereg) ) { n.removeSplit(); j--; continue; @@ -510,6 +561,18 @@ public class RegAlloc { } } + // A sole, adjacent use can read the source register directly. Nothing can + // clobber it in between; only the operand mask and two-address tie remain. + private boolean splitForward( CFGNode bb, int j, Node split, int reg ) { + if( split.nOuts()!=1 || j+1==bb.nOuts() ) return false; + Node use = split.out(0); + if( use!=bb.out(j+1) || use instanceof PhiNode || use instanceof CFGNode || + !(use instanceof MachNode mach) ) return false; + int i = use._inputs.find(split); + RegMask mask = mach.regmap(i); + return mask!=null && mask.test(reg) && mach.twoAddress()!=i; + } + private boolean splitBypass( CFGNode bb, int j, Node lo, int defreg ) { // Attempt to bypass split Node hi = lo.in(1); @@ -529,6 +592,8 @@ public class RegAlloc { 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) || @@ -553,5 +618,4 @@ public class RegAlloc { } 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 index 5227c11e..f73cd9a1 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/RegMask.java +++ b/src/main/java/com/seaofnodes/simple/codegen/RegMask.java @@ -4,11 +4,11 @@ import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.SB; /** RegMask - * A "register mask" - 1 bit set for each allowed register. In addition + * 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 + * 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 @@ -32,6 +32,7 @@ public class RegMask { } public RegMask(long bits ) { _bits0 = bits; } public RegMask(long bits0, long bits1 ) { _bits0 = bits0; _bits1 = bits1; } + public RegMask(RegMask r) { _bits0 = r._bits0; _bits1 = r._bits1; } private RegMask() { _bits0 = _bits1 = 0; } // AND, with copy-on-write if changing @@ -81,7 +82,7 @@ public class RegMask { boolean isEmpty() { return _bits0==0 && _bits1==0; } - boolean test( int reg ) { + public boolean test( int reg ) { return ((reg<64 ? (_bits0 >> reg) : (_bits1 >> (reg-64))) & 1) != 0; } @@ -105,35 +106,17 @@ public class RegMask { Machine mach = CodeGen.CODE._mach; if( _bits0==0 && _bits1==0 ) return sb.p("[]"); sb.p("["); + String[] regs = mach.regs(); for( int i=0; i<64; i++ ) if( ((_bits0 >> i)&1) != 0 ) - sb.p(mach.reg(i)).p(","); + sb.p(reg(regs,i)).p(","); for( int i=0; i<64; i++ ) if( ((_bits1 >> i)&1) != 0 ) - sb.p(mach.reg(i+64)).p(","); + sb.p(reg(regs,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; + public static String reg(String[] regs, int i) { + return i<regs.length ? regs[i] : "[stk#"+i+"]"; } } diff --git a/src/main/java/com/seaofnodes/simple/codegen/RegMaskRW.java b/src/main/java/com/seaofnodes/simple/codegen/RegMaskRW.java new file mode 100644 index 00000000..1da9a3b1 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/codegen/RegMaskRW.java @@ -0,0 +1,36 @@ +package com.seaofnodes.simple.codegen; + +// Mutable regmask(writable) +public 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; + } + // sets bit at position r. + public void set(int r) { + if( r < 64 ) _bits0 |= (1L<<(r )); + else _bits1 |= (1L<<(r-64)); + } + @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; + } + public RegMaskRW or( RegMask mask ) { + if( mask!=null ) { + _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 0fc3bad6..62b33ada 100644 --- a/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -89,7 +89,7 @@ public class AddNode extends Node { // If lhs.in(2) is not a constant, we add ourselves as a dependency // because if it later became a constant then we could make this // transformation. - if( lhs.in(2).addDep(this)._type.isConstant() && rhs._type.isConstant() ) + if( addDep(lhs.in(2))._type.isConstant() && rhs._type.isConstant() ) return new AddNode(lhs.in(1),new AddNode(lhs.in(2),rhs).peephole()); diff --git a/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/src/main/java/com/seaofnodes/simple/node/BoolNode.java index 85bcae0f..d6a126f5 100644 --- a/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -87,6 +87,18 @@ abstract public class BoolNode extends Node { Node copyF() { return new EQF(null,null); } } + public static class NE extends BoolNode { + public NE(Node lhs, Node rhs) { super(lhs,rhs); } + public String op() { return "!="; } + TypeInteger doOp(TypeInteger i1, TypeInteger i2) { + if( i1==i2 && i1.isConstant() ) return TRUE; + if( i1._max < i2._min || i1._min > i2._max ) return FALSE; + return BOOL; + } + Node copy(Node lhs, Node rhs) { return new NE(lhs,rhs); } + Node copyF() { throw Utils.TODO(); } + } + public static class LT extends BoolNode { public LT(Node lhs, Node rhs) { super(lhs,rhs); } public String op() { return "<" ; } diff --git a/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java index e9d4b897..7d4f1fb8 100644 --- a/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java +++ b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java @@ -111,7 +111,7 @@ public class BulkMemPhiNode extends PhiNode { // Normal graph-neighbor enqueueing flows from defs to uses, so retain // an explicit forward dependency to revisit this Phi when the user // sharpens. - use.addDepForwards(this); + addDepForwards(use); return switch(use) { case ScopeNode scope -> 0; case MemMergeNode mmm -> missingAlias(mmm); diff --git a/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/src/main/java/com/seaofnodes/simple/node/CFGNode.java index 0e770659..47dfa311 100644 --- a/src/main/java/com/seaofnodes/simple/node/CFGNode.java +++ b/src/main/java/com/seaofnodes/simple/node/CFGNode.java @@ -1,10 +1,9 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.codegen.CodeGen; - import com.seaofnodes.simple.Ary; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.*; import java.util.BitSet; import java.util.HashMap; @@ -58,6 +57,13 @@ public abstract class CFGNode extends Node { return c; } + // Used by the encoding / final BB layout + public CFGNode uctrlSkipEmpty() { + CFGNode x = this, y; + while( x.nOuts() == 1 && (y=x.uctrl())!=null ) // Skip empty blocks + x = y; + return x; + } // ------------------------------------------------------------------------ /** @@ -91,8 +97,8 @@ public abstract class CFGNode extends Node { while( lhs != rhs ) { if( lhs==null || rhs==null ) return null; int comp = lhs.idepth() - rhs.idepth(); - if( comp >= 0 ) lhs = (dep==null ? lhs : (CFGNode)lhs.addDep(dep)).idom(); - if( comp <= 0 ) rhs = (dep==null ? rhs : (CFGNode)rhs.addDep(dep)).idom(); + if( comp >= 0 ) lhs = (dep==null ? lhs : dep.addDep(lhs)).idom(); + if( comp <= 0 ) rhs = (dep==null ? rhs : dep.addDep(rhs)).idom(); } return lhs; } @@ -110,9 +116,9 @@ public abstract class CFGNode extends Node { public LoopNode loop() { return _ltree._head; } public int loopDepth() { return _ltree==null ? 0 : _ltree.depth(); } - LoopTree _ltree; - int _pre; // Pre-order numbers for loop tree finding - private static class LoopTree { + public LoopTree _ltree; + public int _pre; // Pre-order numbers for loop tree finding + static class LoopTree { LoopTree _par; final LoopNode _head; int _depth; @@ -196,4 +202,9 @@ public abstract class CFGNode extends Node { (this instanceof CallNode && usecfg instanceof FunNode) || (this instanceof ReturnNode && usecfg instanceof CallEndNode); } + + + public String label( CFGNode target ) { + return (target instanceof LoopNode ? "LOOP" : "L")+target._nid; + } } diff --git a/src/main/java/com/seaofnodes/simple/node/CProjNode.java b/src/main/java/com/seaofnodes/simple/node/CProjNode.java index bfdcb04b..4d3d8d0d 100644 --- a/src/main/java/com/seaofnodes/simple/node/CProjNode.java +++ b/src/main/java/com/seaofnodes/simple/node/CProjNode.java @@ -11,10 +11,10 @@ import com.seaofnodes.simple.type.TypeTuple; public class CProjNode extends CFGNode { // Which slice of the incoming multipart value - public final int _idx; + public int _idx; // Debugging label - public final String _label; + public String _label; public CProjNode(Node ctrl, int idx, String label) { super(ctrl); @@ -47,15 +47,21 @@ public class CProjNode extends CFGNode { } // Flip a negating if-test, to remove the not - if( ctrl() instanceof IfNode iff && iff.pred().addDep(this) instanceof NotNode not ) + if( ctrl() instanceof IfNode iff && addDep(iff.pred()) instanceof NotNode not ) return new CProjNode(new IfNode(iff.ctrl(),not.in(1)).peephole(),1-_idx,_idx==0 ? "False" : "True"); // Copy of some other input return ((MultiNode)ctrl()).pcopy(_idx); } + // Only called during basic-block layout, inverts a T/F CProj + public void invert() { + _label = _idx == 0 ? "False" : "True"; + _idx = 1-_idx; + } + @Override - boolean eq( Node n ) { return _idx == ((CProjNode)n)._idx; } + public boolean eq( Node n ) { return _idx == ((CProjNode)n)._idx; } @Override int hash() { return _idx; } diff --git a/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/src/main/java/com/seaofnodes/simple/node/CallEndNode.java index 0ee3d418..47e80ca9 100644 --- a/src/main/java/com/seaofnodes/simple/node/CallEndNode.java +++ b/src/main/java/com/seaofnodes/simple/node/CallEndNode.java @@ -3,7 +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.codegen.*; import com.seaofnodes.simple.type.*; /** @@ -39,7 +39,7 @@ public class CallEndNode extends CFGNode implements MultiNode { return TypeTuple.RET.dual(); Type ret = Type.BOTTOM; TypeMem mem = TypeMem.BOT; - if( call.fptr().addDep(this)._type instanceof TypeFunPtr tfp ) { + if( addDep(call.fptr())._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. @@ -71,8 +71,9 @@ public class CallEndNode extends CFGNode implements MultiNode { assert fun.in(1) instanceof StartNode && fun.in(2)==call; // Disallow self-recursive inlining (loop unrolling by another name) CFGNode idom = call; - while( !(idom instanceof FunNode fun2) ) + while( !(idom instanceof FunNode) ) idom = idom.idom(); + // Inline? if( idom != fun ) { // Trivial inline: rewrite _folding = true; @@ -82,14 +83,16 @@ public class CallEndNode extends CFGNode implements MultiNode { fun.setDef(2,call.ctrl()); // Bypass the Call; fun.ret().setDef(3,null); // Return is folding also CodeGen.CODE.addAll(fun._outputs); + // Inlining immediately blows all cache idepth fields past the inline point. + // Bump the global version number invalidating them en-masse. CodeGen.CODE.invalidateIDepthCaches(); return this; } } else { - fun.addDep(this); + addDep(fun); } } else { // Function ptr has multiple users (so maybe multiple call sites) - fptr.addDep(this); + addDep(fptr); } } @@ -103,7 +106,7 @@ public class CallEndNode extends CFGNode implements MultiNode { Node fptr = call.fptr(); if( !(fptr instanceof ConstantNode) || !(fptr._type instanceof TypeFunPtr tfp && tfp.notNull() && tfp.isConstant()) ) - { fptr.addDep(this); return false; } + { addDep(fptr); return false; } if( call.err()!=null ) return false; ReturnNode ret = (ReturnNode)in(1); FunNode fun = ret.fun(); @@ -164,13 +167,13 @@ public class CallEndNode extends CFGNode implements MultiNode { private int smallBody(FunNode fun) { ReturnNode ret = fun.ret(); for( Node in : ret._inputs ) - if( in!=null ) in.addDep(this); + if( in!=null ) addDep(in); 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); + addDep(n); if( n==ret ) continue; if( n instanceof ParmNode parm && parm.region()!=fun ) return 0; if( n!=fun && !(n instanceof ParmNode) && @@ -182,7 +185,7 @@ public class CallEndNode extends CFGNode implements MultiNode { int j=0; while( j<len && TINYBODY[j]!=use ) j++; if( j<len ) continue; - if( len==TINYBODY.length ) { use.addDep(this); return 0; } + if( len==TINYBODY.length ) { addDep(use); return 0; } TINYBODY[len++] = use; } } @@ -192,4 +195,34 @@ public class CallEndNode extends CFGNode implements MultiNode { @Override public Node pcopy(int idx) { return _folding ? in(1).in(idx) : null; } + + // ------------ + // MachNode specifics, shared across all CPUs + public int _xslot; + private RegMask _retMask; + private RegMask _kills; + public void cacheRegs(CodeGen code) { + // Return mask depends on TFP (either GPR or FPR) + _retMask = code._mach.retMask(call().tfp()); + // Kill mask is all caller-saves, and any mirror stack slots for args + // in registers. + RegMaskRW kills = code._callerSave.copy(); + // Start of stack slots + int maxReg = code._mach.regs().length; + // Incoming function arg slots, all low numbered in the RA + int fslot = fun()._maxArgSlot; + // Killed slots for this calls outgoing args + int xslot = code._mach.maxArgSlot(call().tfp()); + _xslot = (maxReg+fslot)+xslot; + for( int i=0; i<xslot; i++ ) + kills.set((maxReg+fslot)+i); + _kills = kills; + } + public String op() { return "cend"; } + public RegMask regmap(int i) { return null; } + public RegMask outregmap() { return null; } + public RegMask outregmap(int idx) { return idx==2 ? _retMask : null; } + public RegMask killmap() { return _kills; } + public void encoding( Encoding enc ) { } + public void asm(CodeGen code, SB sb) { } } diff --git a/src/main/java/com/seaofnodes/simple/node/CallNode.java b/src/main/java/com/seaofnodes/simple/node/CallNode.java index 371a1d69..70c71d43 100644 --- a/src/main/java/com/seaofnodes/simple/node/CallNode.java +++ b/src/main/java/com/seaofnodes/simple/node/CallNode.java @@ -52,6 +52,10 @@ public class CallNode extends CFGNode { // Error if not a TFP public TypeFunPtr tfp() { return (TypeFunPtr)fptr()._type; } + // Call is to an externally supplied code + public boolean external() { return false; } + + // Find the Call End from the Call public CallEndNode cend() { // Always in use slot 0 @@ -89,6 +93,7 @@ public class CallNode extends CFGNode { // Link: call calls target function. Linking makes the target FunNode // point to this Call, and all his Parms point to the call arguments; // also the CallEnd points to the Return. + Node progress = null; if( fptr()._type instanceof TypeFunPtr tfp && tfp.nargs() == nargs() ) { // If fidxs is negative, then infinite unknown functions long fidxs = tfp.fidxs(); @@ -100,12 +105,12 @@ public class CallNode extends CFGNode { TypeFunPtr tfp0 = tfp.makeFrom(fidx); FunNode fun = CodeGen.CODE.link(tfp0); if( fun!=null && !fun._folding && !linked(fun) ) - link(fun); + progress = link(fun); } } } - return null; + return progress; } // True if Fun is linked to this Call @@ -118,7 +123,7 @@ public class CallNode extends CFGNode { // Link so this calls fun - private void link( FunNode fun ) { + private Node link( FunNode fun ) { assert !linked(fun); fun.addDef(this); for( Node use : fun._outputs ) @@ -126,7 +131,7 @@ public class CallNode extends CFGNode { 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); + return this; } // Unlink all linked functions @@ -136,7 +141,7 @@ public class CallNode extends CFGNode { assert linked(fun); int idx = fun._inputs.find(this); for( Node use : fun._outputs ) - if( use instanceof ParmNode parm ) + if( use instanceof ParmNode ) use.delDef(idx); fun.delDef(idx); cend().delDef(cend()._inputs.find(fun.ret())); diff --git a/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java b/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java index d4a937bd..0afc5827 100644 --- a/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java +++ b/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java @@ -1,10 +1,8 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.Encoding; 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; @@ -18,7 +16,7 @@ public class CalleeSaveNode extends ProjNode implements MachNode { } @Override public RegMask regmap(int i) { return null; } @Override public RegMask outregmap() { return _mask; } - @Override public int encoding(ByteArrayOutputStream bytes) { return 0; } + @Override public void encoding( Encoding enc ) { } @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 9aedf53e..5598acd8 100644 --- a/src/main/java/com/seaofnodes/simple/node/CastNode.java +++ b/src/main/java/com/seaofnodes/simple/node/CastNode.java @@ -43,7 +43,7 @@ public class CastNode extends Node { } @Override - boolean eq(Node n) { + public boolean eq(Node n) { CastNode cast = (CastNode)n; // Contract return _t==cast._t; } diff --git a/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/src/main/java/com/seaofnodes/simple/node/ConstantNode.java index cadb6511..c4a41e5d 100644 --- a/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -49,7 +49,7 @@ public class ConstantNode extends Node { if( fun!=null && fun._name != null ) return p.p("{ ").p(fun._name).p("}"); } - return p.p(_con.print(new SB())); + return p.p(_con==null ? "---" : _con.print(new SB())); } @Override public boolean isConst() { return true; } @@ -61,7 +61,7 @@ public class ConstantNode extends Node { public Node idealize() { return null; } @Override - boolean eq(Node n) { + public boolean eq(Node n) { ConstantNode con = (ConstantNode)n; // Contract return _con==con._con; } diff --git a/src/main/java/com/seaofnodes/simple/node/FRefNode.java b/src/main/java/com/seaofnodes/simple/node/FRefNode.java index bf1c5a00..e7333c23 100644 --- a/src/main/java/com/seaofnodes/simple/node/FRefNode.java +++ b/src/main/java/com/seaofnodes/simple/node/FRefNode.java @@ -30,6 +30,6 @@ public class FRefNode extends ConstantNode { public Parser.ParseException err() { return Parser.error("Undefined name '"+_n._name+"'",_n._loc); } - @Override boolean eq(Node n) { return this==n; } + @Override public boolean eq(Node n) { return this==n; } @Override int hash() { return _n._idx; } } diff --git a/src/main/java/com/seaofnodes/simple/node/FunNode.java b/src/main/java/com/seaofnodes/simple/node/FunNode.java index 2c91d1fc..eb1e8259 100644 --- a/src/main/java/com/seaofnodes/simple/node/FunNode.java +++ b/src/main/java/com/seaofnodes/simple/node/FunNode.java @@ -1,10 +1,13 @@ package com.seaofnodes.simple.node; + import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Encoding; +import com.seaofnodes.simple.codegen.RegMask; 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.CodeGen.CODE; @@ -110,7 +113,8 @@ public class FunNode extends RegionNode { return null; } - // Unfolded functions are roots; inlining reconnects their depth to the caller. + // Bypass Region idom, always assume depth == 1, one more than Start, + // unless folding then just a ID on input#1 @Override public int idepth() { return _folding ? super.idepth() : 1; } // Bypass Region idom, always assume idom is Start @Override public CFGNode idom(Node dep) { return cfg(1); } @@ -162,4 +166,53 @@ public class FunNode extends RegionNode { if( in ) body.set(n._nid); return in; } + + + // ------------ + // MachNode specifics, shared across all CPUs + + // Function may call other functions? X86 requires 16b aligned SP on + // outbound calls, but not leaf. Set during an unrelated RA scan. + public boolean _hasCalls; + + // Maximum incoming argument slot, based on function type + public int _maxArgSlot; + + // Frame adjust, including padding, set during early Encoding + public int _frameAdjust; + + public String op() { return _frameAdjust==0 ? "entry" : "subi"; } + public void postSelect(CodeGen code) { + code.link(this); + // Max slot seen past space reserved for incoming arguments + _maxArgSlot = code._mach.maxArgSlot(sig()); + } + public RegMask regmap(int i) { return null; } + public RegMask outregmap() { return null; } + public void encoding( Encoding enc ) { throw Utils.TODO(); } + public void asm(CodeGen code, SB sb) { + if( _frameAdjust!=0 ) + sb.p("rsp -= #").p(_frameAdjust); + } + + public void computeFrameAdjust(CodeGen code, int maxReg) { + // Max slot seen, or 0. + int maxSlot = Math.max(maxReg - code._mach.regs().length,_maxArgSlot); + // Frame adjust before rounding, can be negative for no spills + int size = maxSlot - _maxArgSlot; + _frameAdjust = size*8; + } + + // Given a high register number and during encoding compute the stack + // pointer offset. Pre-RA we lack the fun._frameAdjust. We also need a + // FunNode. + public int computeStackOffset(CodeGen code, int reg) { + String[] regs = code._mach.regs(); + int stackSlot = reg - regs.length; + int slotRotate = stackSlot < _maxArgSlot + ? stackSlot + (_frameAdjust>>3) + : stackSlot - _maxArgSlot; + return slotRotate*8; + } + } diff --git a/src/main/java/com/seaofnodes/simple/node/IfNode.java b/src/main/java/com/seaofnodes/simple/node/IfNode.java index 2c4cad70..81fbe170 100644 --- a/src/main/java/com/seaofnodes/simple/node/IfNode.java +++ b/src/main/java/com/seaofnodes/simple/node/IfNode.java @@ -1,10 +1,10 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.print.ExprPrinter; +import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.type.*; -import java.util.HashSet; public class IfNode extends CFGNode implements MultiNode { @@ -17,7 +17,13 @@ public class IfNode extends CFGNode implements MultiNode { @Override public String label() { return "If"; } - @Override protected String format() { return "if( %1 )"; } + @Override + protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + p.p("if( "); + if( in(1)==null ) p.p("never"); + else p.n(in(1)); + return p.p(" )"); + } public Node ctrl() { return in(0); } public Node pred() { return in(1); } @@ -53,11 +59,40 @@ public class IfNode extends CFGNode implements MultiNode { // test on either the true or false branch, that side wins. if( !pred()._type.isHighOrConst() ) for( CFGNode dom = idom(), prior=this; dom!=null; prior = dom, dom = dom.idom() ) - if( dom.addDep(this) instanceof IfNode iff && iff.pred().addDep(this)==pred() && prior instanceof CProjNode prj ) { + if( addDep(dom) instanceof IfNode iff && addDep(iff.pred())==pred() && prior instanceof CProjNode prj ) { setDef(1,con( prj._idx==0 ? 1 : 0 )); return this; } return null; } + // MachNode variants need to support this and negate the conditional test. + // The following CProjs will be inverted by the caller. + public void negate() { throw Utils.TODO(); } + + // Negate the sense of a test + public static String negate( String bop ) { + return switch( bop ) { + case "<" -> ">="; + case "<=" -> ">" ; + case "==" -> "!="; + case "!=" -> "=="; + case ">" -> "<="; + case ">=" -> "<" ; + default -> throw Utils.TODO(); + }; + } + + // Swap compare operands + public static String swap( String bop ) { + return switch( bop ) { + case "<" -> ">" ; + case "<=" -> ">="; + case "==" -> "!="; + case "!=" -> "=="; + case ">" -> "<" ; + case ">=" -> "<="; + default -> throw Utils.TODO(); + }; + } } diff --git a/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/src/main/java/com/seaofnodes/simple/node/LoadNode.java index b9905912..3084b4f7 100644 --- a/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -4,8 +4,8 @@ import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.type.*; +import java.util.BitSet; /** * Load represents extracting a value from inside a memory object, @@ -22,7 +22,7 @@ public class LoadNode extends MemOpNode { * @param off The offset inside the struct base */ public LoadNode(Parser.Lexer loc, String name, int alias, Type glb, Node mem, Node ptr, Node off) { - super(loc, name, alias, glb, mem, ptr, off); + super(loc, name, alias, true, glb, mem, ptr, off); } @Override boolean canDrop(MemOpNode other, Node dep) { @@ -33,10 +33,10 @@ public class LoadNode extends MemOpNode { // aggregates might lead to a clobber, so stop rather than search further. private boolean clobbered(Node dep) { Node mem = mem(); - mem.addDepForwards(dep); + dep.addDepForwards(mem); for( Node use : mem._outputs ) { if( use==null ) continue; - use.addDepForwards(dep); + dep.addDepForwards(use); if( use instanceof StoreNode || use instanceof PhiNode || use instanceof MemMergeNode || use instanceof NewNode || use instanceof CallNode ) return true; @@ -69,17 +69,8 @@ public class LoadNode extends MemOpNode { Node ptr = ptr(); Node mem = mem(); - // Simple Load-after-Store on same address. - if( mem() instanceof StoreNode st && - _alias==st._alias && ptr == st.ptr() && off() == st.off() ) { // Must check same object - assert _name.equals(st._name); // Equiv class aliasing is perfect - return extend(st.val()); - } - - // Simple Load-after-New on same address. - if( mem instanceof ProjNode p && p.in(0) instanceof NewNode nnn && - ptr == nnn.proj(0) ) // Must check same object - return zero(nnn); // Load zero from new + Node win = find(mem,ptr,null,null); + if( win!=null ) return folded(win); // Uplift control to a prior dominating load. for( Node memuse : mem._outputs ) @@ -88,52 +79,7 @@ public class LoadNode extends MemOpNode { cfg0()!=null && cfg0().domLCA(ld.cfg0(),this) == ld.cfg0() ) // Higher control means load is legal earlier return ld; - // Load-after-Store on same address, but bypassing provably unrelated - // stores. This is a more complex superset of the above two peeps. - // "Provably unrelated" is really weak. - if( ptr instanceof ReadOnlyNode ro ) - ptr = ro.in(1); - outer: - while( true ) { - mem.addDep(this); - switch( mem ) { - case MemMergeNode merge: - mem = merge.alias(_alias); - break; - case StoreNode st: - if( _alias==st._alias && ptr == st.ptr().addDep(this) && off() == st.off() ) - return extend(castRO(st.val())); // Proved equal - // Can we prove unequal? Offsets do not overlap? - if( _alias==st._alias && !off()._type.join(st.off()._type).isHigh() && // Offsets overlap - !neverAlias(ptr,st.ptr()) ) // And might alias - break outer; // Cannot tell, stop trying - // Pointers cannot overlap - mem = st.mem(); // Proved never equal - break; - case PhiNode phi: - // Assume related - phi.addDep(this); - break outer; - case ConstantNode top: break outer; // Assume shortly dead - case ProjNode mproj: // Memory projection - switch( mproj.in(0) ) { - case NewNode nnn1: - if( ptr instanceof ProjNode pproj && pproj.in(0) == mproj.in(0) ) - return zero(nnn1); - if( !(ptr instanceof ProjNode pproj && pproj.in(0) instanceof NewNode) ) - break outer; // Cannot tell, ptr not related to New - mem = nnn1.mem();// Bypass unrelated New - break; - case StartNode start: break outer; - case CallEndNode cend: break outer; // TODO: Bypass no-alias call - default: throw Utils.TODO(); - } - break; - - default: - break outer; - } - } + if( ptr instanceof ReadOnlyNode ro ) ptr=ro.in(1); // Push a Load up through a Phi, as long as it collapses on at least // one arm. If at a Loop, the backedge MUST collapse - else we risk @@ -143,18 +89,26 @@ public class LoadNode extends MemOpNode { // else ptr.x = e1; : e1; // val = ptr.x; ptr.x = val; if( mem() instanceof MemPhiNode memphi && memphi.region()._type == Type.CONTROL && memphi.nIns()== 3 && + // Array access control must not be moved across the merge. + in(0)==null && // Offset can be hoisted off() instanceof ConstantNode && // Pointer can be hoisted hoistPtr(ptr,memphi) ) { - // Profit on RHS/Loop backedge - if( profit(memphi,2) || - // Else must not be a loop to count profit on LHS. - (!(memphi.region() instanceof LoopNode) && profit(memphi,1)) ) { - Node ld1 = ld(1); - Node ld2 = ld(2); - return new PhiNode(_name,_declaredType,memphi.region(),ld1,ld2); + // Returning to this memory means no change only if the pointer + // is also loop invariant. A pointer Phi may select another object. + Node stop = memphi.region() instanceof LoopNode && + !(ptr instanceof PhiNode p && p.region()==memphi.region()) ? memphi : null; + if( find(memphi.in(2),ptr,stop,new BitSet())!=null || + (!(memphi.region() instanceof LoopNode) && find(memphi.in(1),ptr,null,new BitSet())!=null) ) { + PhiNode phi = new PhiNode(_name,_declaredType,memphi.region()).keep(); + phi.setType(_type); // Backedge searches may return this value Phi. + for( int i=1; i<memphi.nIns(); i++ ) { + Node p = ptr instanceof PhiNode pp && pp.region()==memphi.region() ? pp.in(i) : ptr; + phi.addDef(load(memphi.in(i),p,stop,phi)); + } + return CodeGen.CODE.add(phi.unkeep()); } } @@ -169,9 +123,64 @@ public class LoadNode extends MemOpNode { return castRO(new ConstantNode(zero).peephole()); } - private Node ld( int idx ) { - Node mem = mem(), ptr = ptr(); - LoadNode ld = new LoadNode(_loc,_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr,off()); + // Search only; no new nodes or rewiring. Return a Store/New that folds, + // the unchanged loop memory, or a merge whose every arm folds. Null fails. + // Register dependencies so a later pointer/offset rewrite retries the query. + private Node find(Node mem, Node ptr, Node stop, BitSet visit) { + if( ptr instanceof ReadOnlyNode ro ) ptr=ro.in(1); + while( mem!=null ) { + addDep(mem); + if( mem==stop ) return mem; + switch( mem ) { + case MemMergeNode merge: mem=merge.alias(_alias); break; + case StoreNode st: + addDep(st.ptr()); + if( _alias==st._alias && ptr==st.ptr() && off()==st.off() ) return st; + if( _alias==st._alias && !neverAlias(ptr,st.ptr()) && + !addDep(off())._type.join(addDep(st.off())._type).isHigh() ) return null; + mem=st.mem(); + break; + case ProjNode proj: + if( !(proj.in(0) instanceof NewNode nnn) ) return null; + if( ptr instanceof ProjNode p && p.in(0)==nnn ) return nnn; + if( !(ptr instanceof ProjNode p && p.in(0) instanceof NewNode) ) return null; + mem=nnn.mem(); // Distinct allocations cannot overlap. + break; + case MemPhiNode phi: + addDep(phi.region()); + // Only the original loop closes a successful cycle. Another + // loop or an unfinished merge needs a separate proof. + if( visit==null || !(phi.region() instanceof RegionNode r) || r instanceof LoopNode || + r.inProgress() || phi.inProgress() || r._type!=Type.CONTROL ) return null; + if( visit.get(phi._nid) ) return phi; // Already proved every arm. + for( int i=1; i<phi.nIns(); i++ ) + if( find(phi.in(i),ptr,stop,visit)==null ) return null; + visit.set(phi._nid); + return phi; + default: return null; + } + } + return null; + } + + private Node folded(Node win) { + return win instanceof StoreNode st ? extend(castRO(st.val())) : zero((NewNode)win); + } + + // Materialize only after the profitability search succeeds. The value Phi + // stands for a load which comes back around the loop with unchanged memory. + private Node load(Node mem, Node ptr, Node stop, PhiNode value) { + Node win=find(mem,ptr,stop,new BitSet()); + if( win!=null ) { + if( win==stop ) return value; + if( win instanceof MemPhiNode mp ) { + PhiNode phi=new PhiNode(_name,_declaredType,mp.region()).keep(); + for( int i=1; i<mp.nIns(); i++ ) phi.addDef(load(mp.in(i),ptr,stop,value)); + return phi.unkeep().peephole(); + } + return folded(win).peephole(); + } + LoadNode ld=new LoadNode(_loc,_name,_alias,_declaredType,mem,ptr,off()); ld.setDef(0,in(0)); return ld.peephole(); } @@ -202,16 +211,6 @@ public class LoadNode extends MemOpNode { return false; } - // Profitable if we find a matching Store on this Phi arm. - private boolean profit(PhiNode phi, int idx) { - Node px = phi.in(idx); - if( px==null ) return false; - px.addDep(this); - if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() ) return true; - if( px instanceof StoreNode st1 && _alias==st1._alias && ptr()==st1.ptr() && off()==st1.off() ) return true; - return false; - } - // Read-Only is a deep property, and cannot be cast-away private Node castRO(Node rez) { if( (ptr()._type.isFinal() || _declaredType.isFinal()) && !rez._type.isFinal() ) diff --git a/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/src/main/java/com/seaofnodes/simple/node/LoopNode.java index db353c1d..f86d7518 100644 --- a/src/main/java/com/seaofnodes/simple/node/LoopNode.java +++ b/src/main/java/com/seaofnodes/simple/node/LoopNode.java @@ -2,9 +2,10 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMem; import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.type.TypeMem; import java.util.BitSet; import java.util.HashSet; @@ -25,7 +26,7 @@ public class LoopNode extends RegionNode { } // Bypass Region idom, same as the default idom() using use in(1) instead of in(0) - @Override public int idepth() { return validIDepth() ? _idepth : cacheIDepth(entry().idepth()+1); } + public int idepth() { return validIDepth() ? _idepth : cacheIDepth(entry().idepth()+1); } // Bypass Region idom, same as the default idom() using use in(1) instead of in(0) @Override public CFGNode idom(Node dep) { return entry(); } @@ -39,51 +40,63 @@ public class LoopNode extends RegionNode { while( x != this ) { if( x instanceof CProjNode exit && exit.in(0) instanceof IfNode iff ) { CFGNode other = iff.cproj(1-exit._idx); - if( other!=null && other.loopDepth() < loopDepth() ) + if( other!=null && other._ltree != _ltree && nested(_ltree,other._ltree) ) return fun; // Found an exit, not an infinite loop } x = x.idom(); } // Found a no-exit loop. Insert an exit - NeverNode iff = new NeverNode(back()); - for( Node use : _outputs ) - if( use instanceof PhiNode ) - iff.addDef(use); + NeverNode iff = new NeverNode(back()); // Ideal never-branch CProjNode t = new CProjNode(iff,0,"True" ).init(); CProjNode f = new CProjNode(iff,1,"False").init(); - setDef(2,f); + setDef(2,t); // True continues loop, False (never) exits loop + ReturnNode ret = fun.ret(); + iff._ltree = t._ltree = _ltree; + ret._ltree = f._ltree = fun._ltree; // Now fold control into the exit. Might have 1 valid exit, or an // XCtrl or a bunch of prior NeverNode exits. Node top = new ConstantNode(Type.TOP).peephole(); - ReturnNode ret = fun.ret(); - iff._ltree = f._ltree = _ltree; - ret._ltree = t._ltree = fun._ltree; + MemMergeNode memout = new MemMergeNode(new ConstantNode(TypeMem.BOT).peephole()); + memout.setDef(0,f); + for( Node u : _outputs ) + if( u instanceof BulkMemPhiNode ) memout.setDef(1,u); + else if( u instanceof MemPhiNode phi ) memout.alias(phi._alias,phi); + memout.init(); + Node ctrl = ret.ctrl(), mem = ret.mem(), expr = ret.expr(); - if( ctrl._type != Type.XCONTROL ) { + if( ctrl!=null && ctrl._type != Type.XCONTROL ) { // Perfect aligned exit? if( !(ctrl instanceof RegionNode r && mem instanceof PhiNode pmem && pmem.region()==r && expr instanceof PhiNode prez && prez.region()==r ) ) { // Nope, insert an aligned exit layer - ctrl = new RegionNode(_loc,null,ctrl).init(); - mem = new PhiNode((RegionNode)ctrl,mem ).init(); - expr = new PhiNode((RegionNode)ctrl,expr).init(); + RegionNode r = new RegionNode(_loc,null,ctrl).init(); + ctrl = r; r._ltree = fun._ltree; + mem = new BulkMemPhiNode(ScopeNode.MEM0,r,mem).init(); + expr = new PhiNode(r,expr).init(); } // Append new Never exit - ctrl.addDef(t ); - mem .addDef(top); + ctrl.addDef(f ); + mem .addDef(memout); expr.addDef(top); } else { - ctrl = t; - mem = top; + ctrl = f; + mem = memout; expr = top; } - ((CFGNode)ctrl)._ltree = fun._ltree; ret.setDef(0,ctrl); ret.setDef(1,mem ); ret.setDef(2,expr); return fun; } + + private static boolean nested(LoopTree inner, LoopTree outer) { + for( ; inner!=null; inner = inner._par ) + if( inner == outer ) + return true; + return false; + } + } diff --git a/src/main/java/com/seaofnodes/simple/node/MachNode.java b/src/main/java/com/seaofnodes/simple/node/MachNode.java index ecdbc651..f63b3651 100644 --- a/src/main/java/com/seaofnodes/simple/node/MachNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MachNode.java @@ -1,10 +1,7 @@ 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; +import com.seaofnodes.simple.codegen.*; public interface MachNode { @@ -12,7 +9,7 @@ public interface MachNode { default Machine machine() { return CodeGen.CODE._mach; } // Run a post-instruction-selection action - default void postSelect() { } + default void postSelect( CodeGen code ) { } // Register mask allowed on input i. 0 for no register. RegMask regmap( int i ); @@ -47,8 +44,8 @@ public interface MachNode { // Make a clone of a cheap instruction default Node copy() { return null; } - // Encoding is appended into the byte array; size is returned - int encoding(ByteArrayOutputStream bytes); + // Encoding is appended, relocations gathered. + void encoding( Encoding enc ); // 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 c8a76714..421e4864 100644 --- a/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java @@ -18,7 +18,6 @@ public class MemMergeNode extends Node { this(bulk); alias(alias,precise); } - public Node alias(int alias) { Node n = alias < nIns() ? in(alias) : null; assert n!=null || !(in(1) instanceof BulkMemPhiNode bulk) || !bulk.isSplit(alias); @@ -37,12 +36,12 @@ public class MemMergeNode extends Node { // join it with the incoming contents for all previously allocated objects. // Phis use their cached types, so this query does not recurse around loops. static Type contents(Node mem, int alias, Node dep) { - mem.addDep(dep); + dep.addDep(mem); if( mem instanceof MemMergeNode merge ) return contents(merge.alias(alias),alias,dep); if( mem instanceof ProjNode proj && proj.in(0) instanceof NewNode nnn ) { assert proj._idx==1 && nnn.field(alias)!=null; - nnn.addDep(dep); + dep.addDep(nnn); return contents(nnn.mem(),alias,dep).meet(nnn.field(alias)._type.makeZero()); } // Function parameters and call results may contain arbitrary heap values. diff --git a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java index 4f404846..adfc80a5 100644 --- a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java @@ -12,26 +12,49 @@ import java.util.ArrayList; /** * Convenience common base for Load and Store. + * + * 0 - Control + * 1 - Memory + * 2 - Base of object, OOP + * 3 - Offset, integer types + * 4 - Value, for Stores only */ public abstract class MemOpNode extends Node { - public final String _name; + // The equivalence alias class public final int _alias; + + // True if load-like, false if store-like. + // + // Stores produce memory (maybe as part of a tuple with other things), + // loads do not. + // + // Loads might pick up anti-dependences on prior Stores, and never cause an + // anti-dependence themselves. + // + // Stores must maximally sink to the least dominator use. Loads can be + // opportunistically hoisted. + public final boolean _isLoad; + // Declared type; not final because it might be a forward-reference // which will be lazily improved when the reference is declared. public Type _declaredType; + + // A debug name, no semantic meaning + public final String _name; // Source location for late reported errors public final Parser.Lexer _loc; - public MemOpNode(Parser.Lexer loc, String name, int alias, Type glb, Node mem, Node ptr, Node off) { + public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type glb, Node mem, Node ptr, Node off) { super(null, mem, ptr, off); _name = name; _alias = alias; _declaredType = glb; _loc = loc; + _isLoad = isLoad; } - public MemOpNode(Parser.Lexer loc, String name, int alias, Type glb, Node mem, Node ptr, Node off, Node value) { - this(loc,name, alias, glb, mem, ptr, off); + public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type glb, Node mem, Node ptr, Node off, Node value) { + this(loc,name, alias, isLoad, glb, mem, ptr, off); addDef(value); } public MemOpNode( Node mach, MemOpNode mop ) { @@ -39,13 +62,28 @@ public abstract class MemOpNode extends Node { _name = mop==null ? null : mop._name; _alias = mop==null ? 0 : mop._alias; _loc = mop==null ? null : mop._loc; + _isLoad= mop==null ? true : mop._isLoad; _declaredType = mop==null ? Type.BOTTOM : mop._declaredType; + if( mop==null ) + throw Utils.TODO("Load or not"); + } + + // Used by M2 when translating graph to Simple + public MemOpNode( boolean isLoad ) { + super((Node)null); + _name = null; + _alias = 0; + _loc = null; + _isLoad = isLoad; + _declaredType = Type.BOTTOM; } // static String mlabel(String name) { return "[]".equals(name) ? "ary" : ("#".equals(name) ? "len" : name); } String mlabel() { return mlabel(_name); } + @Override public boolean isMem() { return !_isLoad; } + public Node mem() { return in(1); } public Node ptr() { return in(2); } public Node off() { return in(3); } @@ -57,12 +95,12 @@ public abstract class MemOpNode extends Node { // Extra conditions for factoring matching memory operations through a Phi. // The caller has already checked the opcode, input shape and control. boolean canDrop(MemOpNode other, Node dep) { - ptr().addDep(dep); + dep.addDep(ptr()); return _alias==other._alias && _declaredType==other._declaredType && err()==null; } @Override - boolean eq(Node n) { + public boolean eq(Node n) { MemOpNode mem = (MemOpNode)n; // Invariant return _alias==mem._alias; // When comparing types error to use "equals"; always use "==" } @@ -98,7 +136,7 @@ public abstract class MemOpNode extends Node { if( (slice==mem) || (slice==null && merge.in(1)==mem) ) antiDeps(merge,deps,visit); } else if( use instanceof CallNode || - (use instanceof MemOpNode st && st.isMem() && st._alias==_alias) || + (use instanceof MemOpNode st && !st._isLoad && st._alias==_alias) || (use instanceof NewNode nnn && nnn.field(_alias)!=null) || (use instanceof MemPhiNode phi && phi._alias==_alias) || (use instanceof BulkMemPhiNode phi && !phi.isSplit(_alias)) ) { diff --git a/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java index df2c72f7..1349cadb 100644 --- a/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java @@ -25,7 +25,7 @@ public class MemPhiNode extends PhiNode { if( r.inProgress() ) return _declaredType; Type t = Type.TOP; for( int i=1; i<nIns(); i++ ) - if( r.in(i).addDep(this)._type!=Type.XCONTROL ) + if( addDep(r.in(i))._type!=Type.XCONTROL ) t = t.meet(MemMergeNode.contents(in(i),_alias,this)); return TypeMem.make(_alias,t); } @@ -44,6 +44,6 @@ public class MemPhiNode extends PhiNode { return super.idealize(); } - @Override boolean eq(Node n) { return _alias==((MemPhiNode)n)._alias && super.eq(n); } + @Override public boolean eq(Node n) { return _alias==((MemPhiNode)n)._alias && super.eq(n); } @Override int hash() { return _alias; } } diff --git a/src/main/java/com/seaofnodes/simple/node/NeverNode.java b/src/main/java/com/seaofnodes/simple/node/NeverNode.java index 7e42ccf9..66879b66 100644 --- a/src/main/java/com/seaofnodes/simple/node/NeverNode.java +++ b/src/main/java/com/seaofnodes/simple/node/NeverNode.java @@ -10,7 +10,8 @@ import com.seaofnodes.simple.type.TypeTuple; // "Never true" for infinite loop exits public class NeverNode extends IfNode { - public NeverNode(Node ctrl) { super(ctrl,Parser.ZERO); } + public NeverNode(NeverNode ctrl) { super(ctrl); } + public NeverNode(Node ctrl) { super(ctrl,null); } @Override public String label() { return "Never"; } diff --git a/src/main/java/com/seaofnodes/simple/node/NewNode.java b/src/main/java/com/seaofnodes/simple/node/NewNode.java index 68871918..4179821c 100644 --- a/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -2,6 +2,9 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.type.*; /** Allocate a zeroed object. Inputs {ctrl, $mem, size}; @@ -49,8 +52,42 @@ public class NewNode extends Node implements MultiNode { public Node idealize() { return null; } @Override - boolean eq(Node n) { return this == n; } + public boolean eq(Node n) { return this == n; } @Override int hash() { return _ptr.hashCode(); } + + // ------------ + // MachNode specifics, shared across all CPUs + public int _arg2Reg, _xslot; + private RegMask _arg3Mask; + private RegMask _retMask; + private RegMask _kills; + public void cacheRegs(CodeGen code) { + _arg2Reg = code._mach.callArgMask(TypeFunPtr.CALLOC,2,0).firstReg(); + _arg3Mask = code._mach.callArgMask(TypeFunPtr.CALLOC,3,0); + // Return mask depends on TFP (either GPR or FPR) + _retMask = code._mach.retMask(TypeFunPtr.CALLOC); + // Kill mask is all caller-saves, and any mirror stack slots for args + // in registers. + RegMaskRW kills = code._callerSave.copy(); + // Start of stack slots + int maxReg = code._mach.regs().length; + // Incoming function arg slots, all low numbered in the RA + int fslot = cfg0().fun()._maxArgSlot; + // Killed slots for this calls outgoing args + int xslot = code._mach.maxArgSlot(TypeFunPtr.CALLOC); + _xslot = (maxReg+fslot)+xslot; + for( int i=0; i<xslot; i++ ) + kills.set((maxReg+fslot)+i); + _kills = kills; + } + public String op() { return "alloc"; } + public RegMask regmap(int i) { return i==2 ? _arg3Mask : null; } + public RegMask outregmap() { return null; } + public RegMask outregmap(int idx) { return idx==0 ? _retMask : null; } + public RegMask killmap() { return _kills; } + public void asm(CodeGen code, SB sb) { + sb.p("#calloc, ").p(code.reg(size())); + } } diff --git a/src/main/java/com/seaofnodes/simple/node/Node.java b/src/main/java/com/seaofnodes/simple/node/Node.java index e038d351..8b0647d0 100644 --- a/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/src/main/java/com/seaofnodes/simple/node/Node.java @@ -217,13 +217,23 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { assert isDead(); // Really dead now } + // Preserve CFG use-ordering when killing + public void killOrdered() { + CFGNode cfg = cfg0(); + cfg._outputs.remove(cfg._outputs.find(this)); + _inputs.set(0,null); + kill(); + } + // Mostly used for asserts and printing. public boolean isDead() { return isUnused() && nIns()==0 && _type==null; } // Shortcuts to stop DCE mid-parse // Add bogus null use to keep node alive + @SuppressWarnings("unchecked") public <N extends Node> N keep() { addUse(null); return (N)this; } // Remove bogus null. + @SuppressWarnings("unchecked") public <N extends Node> N unkeep() { delUse(null); return (N)this; } // Test "keep" status public boolean iskeep() { return _outputs.find(null) != -1; } @@ -270,6 +280,8 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { int i; if( use instanceof PhiNode phi ) { cfg = phi.region().cfg(uidx); + if( cfg instanceof CProjNode && cfg.in(0) instanceof NeverNode nvr ) + cfg = nvr.cfg0(); i = cfg.nOuts()-1; } else { i = cfg._outputs.find(use); @@ -281,7 +293,7 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { use.setDefOrdered(uidx,this); } - public void setDefOrdered(int idx, Node def) { + 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 ) { @@ -294,13 +306,6 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { 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)); @@ -427,6 +432,7 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { return old; } + @SuppressWarnings("unchecked") public <N extends Node> N init() { _type = compute(); return (N)this; } /** @@ -490,27 +496,23 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { * or output of this node, that is, it is at least one step away. The node * being added must benefit from this node being peepholed. */ - Node addDep( Node dep ) { - return addDep(dep,false); - } + <N extends Node> N addDep( N dep ) { return addDep(dep,false); } - // A bulk Phi inspects its users' partitions. Def-to-use propagation does - // not revisit that producer when an immediate user's partition changes. - Node addDepForwards(Node dep) { return addDep(dep,true); } + // Keep an explicit dependency when inspecting an immediate user's shape. + <N extends Node> N addDepForwards(N dep) { return addDep(dep,true); } - private Node addDep(Node dep, boolean forwards) { + private <N extends Node> N addDep(N dep, boolean forwards) { // Running peepholes during the big assert cannot have side effects // like adding dependencies. - if( CODE._midAssert ) return this; + if( CODE._midAssert ) return dep; var obs = CodeGen.CODE._midAssert ? null : CodeGen.CODE._obs; if( obs != null ) obs.dep(this, dep); - if( dep == null ) return this; - if( _deps==null ) _deps = new Ary<>(Node.class); - if( _deps .find(dep) != -1 ) return this; // Already on list - if( !forwards && _inputs .find(dep) != -1 ) return this; // No need for deps on immediate neighbors - if( !forwards && _outputs.find(dep) != -1 ) return this; - _deps.add(dep); - return this; + if( dep._deps==null ) dep._deps = new Ary<>(Node.class); + if( dep._deps .find(this) != -1 ) return dep; // Already on list + if( !forwards && dep._inputs .find(this) != -1 ) return dep; // No need for deps on immediate neighbors + if( !forwards && dep._outputs.find(this) != -1 ) return dep; + dep._deps.add(this); + return dep; } // Move the dependents onto a worklist, and clear for future dependents. @@ -535,7 +537,7 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { } // Subclasses add extra checks (such as ConstantNodes have same constant), // and can assume "this!=n" and has the same Java class. - boolean eq( Node n ) { return true; } + public boolean eq( Node n ) { return true; } // Cached hash. If zero, then not computed AND this Node is NOT in the GVN // table - and can have its edges hacked (which will change his hash @@ -611,7 +613,7 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { boolean allCons(Node dep) { for( int i=1; i<nIns(); i++ ) if( !(in(i)._type.isConstant()) ) { - in(i).addDep(dep); // If in(i) becomes a constant later, will trigger some peephole + dep.addDep(in(i)); // If in(i) becomes a constant later, will trigger some peephole return false; } return true; @@ -622,6 +624,8 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { // Does not need to be implemented in isCFG() nodes. Node copy(Node lhs, Node rhs) { throw Utils.TODO("Binary ops need to implement copy"); } + public Node copy() { return copyEmpty(); } + // Exact-class copy with fresh identity and no edges. Unlike machine // rematerialization copies, this never registers or copies input edges. public final Node copyEmpty() { diff --git a/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/src/main/java/com/seaofnodes/simple/node/PhiNode.java index a752977b..e075ea5c 100644 --- a/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -14,7 +14,7 @@ public class PhiNode extends Node { final Type _declaredType; public PhiNode(String label, Type declaredType, Node... inputs) { super(inputs); _label = label; assert declaredType!=null; _declaredType = declaredType; } - public PhiNode(PhiNode phi, String label, Type declaredType) { super(phi); _label = label; _declaredType = declaredType; } + public PhiNode(PhiNode phi, String label, Type declaredType) { super(phi); _label = label; _type = _declaredType = declaredType; } public PhiNode(PhiNode phi) { super(phi); _label = phi._label; _declaredType = phi._declaredType; } public PhiNode(RegionNode r, Node sample) { @@ -54,7 +54,7 @@ public class PhiNode extends Node { for (int i = 1; i < nIns(); i++) // If the region's control input is live, add this as a dependency // to the control because we can be peeped should it become dead. - if( r.in(i).addDep(this)._type != Type.XCONTROL ) + if( addDep(r.in(i))._type != Type.XCONTROL ) t = t.meet(in(i)._type); return t; } @@ -82,8 +82,8 @@ public class PhiNode extends Node { // If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove. if( nIns()==3 ) { - if( in(1) instanceof CastNode cast && cast.in(1).addDep(this)==in(2) ) return in(2); - if( in(2) instanceof CastNode cast && cast.in(1).addDep(this)==in(1) ) return in(1); + if( in(1) instanceof CastNode cast && addDep(cast.in(1))==in(2) ) return in(2); + if( in(2) instanceof CastNode cast && addDep(cast.in(1))==in(1) ) return in(1); } // If merging a null-checked null and the checked value, just use the value. // if( val ) ..; phi(Region,False=0/null,True=val); @@ -96,7 +96,7 @@ public class PhiNode extends Node { Node val = in(3-nullx); if( val instanceof CastNode cast ) val = cast.in(1); - if( r.idom(this).addDep(this) instanceof IfNode iff && iff.pred().addDep(this)==val ) { + if( addDep(r.idom(this)) instanceof IfNode iff && addDep(iff.pred())==val ) { // Must walk the idom on the null side to make sure we hit False. CFGNode idom = (CFGNode)r.in(nullx); while( idom != null && idom.nIns() > 0 && idom.in(0) != iff ) idom = idom.idom(); @@ -109,6 +109,8 @@ public class PhiNode extends Node { return null; } + // Same op on all Phi paths; all ops have only the Phi as a use. + // None have a control input. private boolean same_op() { Node op = in(1); if( op instanceof CFGNode || op instanceof ConstantNode || op instanceof PhiNode || @@ -121,22 +123,22 @@ public class PhiNode extends Node { if( op instanceof MemOpNode ) for( Node use : region()._outputs ) if( use instanceof BulkMemPhiNode ) { - use.addDepForwards(this); + addDepForwards(use); return false; } Node busy = null; for( int i=1; i<nIns(); i++ ) { Node n = in(i); - if( region().in(i).addDep(this)._type==Type.XCONTROL || + if( addDep(region().in(i))._type==Type.XCONTROL || op.getClass()!=n.getClass() || n.nIns()!=op.nIns() || n.in(0)!=null || !op.eq(n) ) return false; if( n instanceof MemOpNode mem && !mem.canDrop((MemOpNode)op,this) ) return false; // Moving a Store must remove the old effect, not duplicate it. // Allow one shared arm: the other arms disappear into the single // factored operation, usually reducing the total operation count. - n.addDepForwards(this); + addDepForwards(n); if( n.nOuts()>1 ) { for( Node use : n._outputs ) - if( use!=null && use!=this ) use.addDepForwards(this); + if( use!=null && use!=this ) addDepForwards(use); if( n instanceof StoreNode || busy!=null ) return false; busy=n; } @@ -174,7 +176,7 @@ public class PhiNode extends Node { 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); + if( in(i).in(j)!=null ) addDep(in(i).in(j)); cp.kill(); return null; } @@ -189,7 +191,7 @@ public class PhiNode extends Node { for( int i=1; i<nIns(); i++ ) { // If the region's control input is live, add this as a dependency // to the control because we can be peeped should it become dead. - if( region().in(i).addDep(this)._type != Type.XCONTROL && in(i) != this ) + if( addDep(region().in(i))._type != Type.XCONTROL && in(i) != this ) if( live == null || live == in(i) ) live = in(i); else return null; } @@ -201,7 +203,7 @@ public class PhiNode extends Node { if( !(region() instanceof RegionNode r) ) return false; // When the region completes (is no longer in progress) the Phi can // become a "all constants" Phi, and the "dep" might make progress. - addDep(dep); + dep.addDep(this); if( r.inProgress() ) return false; return super.allCons(dep); } @@ -212,7 +214,7 @@ public class PhiNode extends Node { } // Never equal if inProgress - @Override boolean eq( Node n ) { + @Override public boolean eq( Node n ) { return !inProgress(); } diff --git a/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/src/main/java/com/seaofnodes/simple/node/ProjNode.java index da5841f6..26b3a522 100644 --- a/src/main/java/com/seaofnodes/simple/node/ProjNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ProjNode.java @@ -47,7 +47,7 @@ public class ProjNode extends Node { @Override public Node idealize() { return ((MultiNode)in(0)).pcopy(_idx); } @Override - boolean eq( Node n ) { return _idx == ((ProjNode)n)._idx; } + public boolean eq( Node n ) { return _idx == ((ProjNode)n)._idx; } @Override int hash() { return _idx; } diff --git a/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/src/main/java/com/seaofnodes/simple/node/RegionNode.java index 8993a49f..79b7f53a 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.Parser; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.Type; import java.util.*; @@ -48,7 +49,7 @@ public class RegionNode extends CFGNode { if( !hasPhi() && // No Phi users, just a control user in(1) instanceof CProjNode p1 && in(2) instanceof CProjNode p2 && - p1.addDepForwards(this).in(0)==p2.addDepForwards(this).in(0) && + addDepForwards(p1).in(0)==addDepForwards(p2).in(0) && p1.in(0) instanceof IfNode iff ) { // Replace with the iff.ctrl directly if( nIns()==3 ) return iff.ctrl(); @@ -57,6 +58,29 @@ public class RegionNode extends CFGNode { return delDef(2); } + // Flatten stack regions (no loops involved) + if( getClass() == RegionNode.class ) { + for( int i=1; i<nIns(); i++ ) + if( cfg(i) instanceof RegionNode region && region.getClass() == RegionNode.class && region.nIns()>2 && !hasMidUser(region) ) { + assert !region.inProgress(); + // Fold Phis + for( Node use : _outputs ) { + if( use instanceof PhiNode phi ) { + PhiNode phi2 = phi.in(i) instanceof PhiNode phi2x && phi2x.region()==region ? phi2x : null; + for( int j=1; j<region.nIns(); j++ ) + phi.addDef(phi2==null ? phi.in(i) : phi2.in(j)); + CodeGen.CODE.add(phi); + } + } + + // Fold Region + for( int j=1; j<region.nIns(); j++ ) + addDef(region.in(j)); + setDef(i,Parser.XCTRL); + return this; + } + } + return null; } @@ -99,6 +123,23 @@ public class RegionNode extends CFGNode { return false; } + // Is there a value in-between 2 Regions? Difficult to correctly fuse + // without extensive testing, so just ignore. + boolean hasMidUser(RegionNode r) { + for( Node use : r._outputs ) { + if( use==this ) continue; + if( use instanceof PhiNode ) { + for( Node data : use._outputs ) { + if( !(data instanceof PhiNode phi2) || phi2.region()!=this ) + { addDep(use); addDep(data); return true; } + } + } else + { addDep(use); return true; } // Control user + } + return false; + } + + // Immediate dominator of Region is a little more complicated. @Override public int idepth() { if( validIDepth() ) return _idepth; @@ -121,6 +162,6 @@ public class RegionNode extends CFGNode { public boolean inProgress() { return nIns()>1 && in(nIns()-1) == null; } // Never equal if inProgress - @Override boolean eq( Node n ) { return !inProgress(); } + @Override public boolean eq( Node n ) { return !inProgress(); } } diff --git a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index e101ee09..e0ab863a 100644 --- a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -5,6 +5,9 @@ import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.Parser; 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.codegen.Encoding; import com.seaofnodes.simple.type.*; /** Keeps all preceding memory effects and the return value alive: {ctrl, $mem, value, rpc}. */ @@ -22,7 +25,7 @@ public class ReturnNode extends CFGNode { public Node mem () { return in(1); } public Node expr() { return in(2); } public Node rpc () { return in(3); } - public FunNode fun() { return _fun; } + @Override public FunNode fun() { return _fun; } @Override public String label() { return "Return"; } @@ -118,4 +121,39 @@ public class ReturnNode extends CFGNode { if( tp || tn ) sb.p("reference and "); return Parser.error(sb.unchar(5).toString(),loc); } + + + // ------------ + // MachNode specifics, shared across all CPUs + public String op() { + return _fun._frameAdjust > 0 ? "addi" : "ret"; + } + // Correct Nodes outside the normal edges + public void postSelect(CodeGen code) { + FunNode fun = (FunNode)rpc().in(0); + _fun = fun; + fun.setRet(this); + } + public RegMask regmap(int i) { + return i==2 + ? CodeGen.CODE._mach.retMask(_fun.sig()) + : CodeGen.CODE._retMasks[i]; + } + public RegMask outregmap() { return null; } + public void encoding( Encoding enc ) { throw Utils.TODO(); } + public void asm(CodeGen code, SB sb) { + int frameAdjust = fun()._frameAdjust; + if( frameAdjust>0 ) + sb.p("rsp += #").p(frameAdjust).p("\nret"); + // Post code-gen, just print the "ret" + if( code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() ) + // Prints return reg (either RAX or XMM0), RPC and then the + // callee-save registers. + for( int i=2; i<nIns(); i++ ) + sb.p(code.reg(in(i),fun())).p(" "); + // If we did not get the expected rpc, print which one we got + else if( code._regAlloc.regnum(rpc()) != code._mach.rpc() ) + sb.p("[").p(code.reg(rpc())).p("]"); + } + } diff --git a/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/src/main/java/com/seaofnodes/simple/node/ShlNode.java index 84c19247..afd20062 100644 --- a/src/main/java/com/seaofnodes/simple/node/ShlNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ShlNode.java @@ -36,7 +36,7 @@ public class ShlNode extends LogicalNode { if( (shl.value()&63)==0 && lhs._type instanceof TypeInteger ) return lhs; // (x + c) << i => (x << i) + (c << i) - if( lhs instanceof AddNode add && add.addDep(this).in(2)._type instanceof TypeInteger c && c.isConstant() ) { + if( lhs instanceof AddNode add && addDep(add).in(2)._type instanceof TypeInteger c && c.isConstant() ) { long sum = c.value() << shl.value(); if( Integer.MIN_VALUE <= sum && sum <= Integer.MAX_VALUE ) return new AddNode(new ShlNode(_loc,add.in(1),rhs).peephole(), Parser.con(sum) ); diff --git a/src/main/java/com/seaofnodes/simple/node/SplitNode.java b/src/main/java/com/seaofnodes/simple/node/SplitNode.java index 07166a7c..ff18b292 100644 --- a/src/main/java/com/seaofnodes/simple/node/SplitNode.java +++ b/src/main/java/com/seaofnodes/simple/node/SplitNode.java @@ -13,7 +13,7 @@ public abstract class SplitNode extends MachConcreteNode { @Override public String op() { return "mov"; } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { p.p("mov("); - if( in(1)==null ) p.p("---"); + if( in(1) == null ) p.p("---"); else p.n(in(1)); return p.close(); } diff --git a/src/main/java/com/seaofnodes/simple/node/StopNode.java b/src/main/java/com/seaofnodes/simple/node/StopNode.java index 524c6a9e..c88dbf51 100644 --- a/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ b/src/main/java/com/seaofnodes/simple/node/StopNode.java @@ -27,7 +27,8 @@ public class StopNode extends CFGNode { if( ret()!=null ) return p.n(ret()); p.p("Stop[ "); for( Node ret : _inputs ) - p.n(ret).p(" "); + if( ret!=null ) + p.n(ret).p(" "); return p.p("]"); } diff --git a/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/src/main/java/com/seaofnodes/simple/node/StoreNode.java index 9e6689f0..a5b09663 100644 --- a/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -22,7 +22,7 @@ public class StoreNode extends MemOpNode { * @param value Value to be stored */ public StoreNode(Parser.Lexer loc, String name, int alias, Type glb, Node mem, Node ptr, Node off, Node value, boolean init) { - super(loc, name, alias, glb, mem, ptr, off, value); + super(loc, name, alias, false, glb, mem, ptr, off, value); _init = init; } @@ -85,6 +85,11 @@ public class StoreNode extends MemOpNode { } } + // Store of zero after alloc + if( mem() instanceof ProjNode prj && prj.in(0) instanceof NewNode && + prj.in(0)==ptr().in(0) && // Same NewNode memory & pointer + (val()._type==TypeInteger.ZERO || val()._type==Type.NIL ) ) + return mem(); return null; } @@ -93,9 +98,9 @@ public class StoreNode extends MemOpNode { // With no observers of the backedge store, only the last value matters. // The entry load preserves the field even when the loop takes zero trips. Node sink(MemPhiNode phi) { - addDepForwards(phi); + phi.addDepForwards(this); if( nOuts()!=1 ) return null; - ptr().addDep(phi); + phi.addDep(ptr()); // Start with a fixed field of an allocation dominating the loop. // In particular, do not sink stores through a loop-varying address. if( !(ptr() instanceof ProjNode p) || !(p.in(0) instanceof NewNode obj) || @@ -106,20 +111,20 @@ public class StoreNode extends MemOpNode { // before replacing this memory point with a Store. for( Node use : loop._outputs ) if( use instanceof BulkMemPhiNode ) { - use.addDepForwards(phi); + phi.addDepForwards(use); return null; } - obj.addDep(phi); + phi.addDep(obj); CFGNode ctrl = loop.entry(); while( ctrl!=null && ctrl!=obj.cfg0() ) { - ctrl.addDep(phi); + phi.addDep(ctrl); ctrl=ctrl.idom(phi); } if( ctrl==null ) return null; Node mem = phi.in(1); Node init = new LoadNode(_loc,_name,_alias,_declaredType,mem,ptr(),off()).peephole(); - Node value = new PhiNode(_name,_declaredType,loop,init,val()).peephole(); + Node value = CodeGen.CODE.add(new PhiNode(_name,_declaredType,loop,init,val()).peephole()); Node sink = new StoreNode(_loc,_name,_alias,_declaredType,mem,ptr(),off(),value,_init); // Break the memory cycle, killing the old store. Keep the Phi alive // even if that store was its last use, until our caller replaces it. @@ -136,7 +141,7 @@ public class StoreNode extends MemOpNode { // when the other uses go away we can retry. for( Node use : mem._outputs ) if( use != this ) - use.addDep(this); + addDep(use); return false; } @@ -149,7 +154,7 @@ public class StoreNode extends MemOpNode { TypeMemPtr tmp = (TypeMemPtr)ptr()._type; if( (tmp._ro || tmp._obj.field(_name)._final) && !_init ) return Parser.error("Cannot modify final field '"+_name+"'",_loc); - Type t = val()._type; + //Type t = val()._type; //return _init || t.isa(_declaredType) ? null : Parser.error("Cannot store "+t+" into field "+_declaredType+" "+_name,_loc); return null; } diff --git a/src/main/java/com/seaofnodes/simple/node/StructNode.java b/src/main/java/com/seaofnodes/simple/node/StructNode.java index b9801f63..3782d64a 100644 --- a/src/main/java/com/seaofnodes/simple/node/StructNode.java +++ b/src/main/java/com/seaofnodes/simple/node/StructNode.java @@ -39,7 +39,7 @@ public class StructNode extends Node { public Node idealize() { return null; } @Override - boolean eq(Node n) { return _ts == ((StructNode)n)._ts; } + public boolean eq(Node n) { return _ts == ((StructNode)n)._ts; } @Override int hash() { return _ts==null ? 0 : _ts.hashCode(); } 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 index c4014571..76e7b9b5 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java @@ -1,34 +1,21 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; 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); } + @Override public String op() { return "add"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } - 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(); - } - + // ADD (shifted register) + @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,Arm64.OP_ADD); } // 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 index 098ab8c1..9c2f64c0 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java @@ -1,31 +1,23 @@ 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.isa.Arm64; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.*; 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 String op() { return "addf"; } + @Override public RegMask regmap(int i) { return arm.DMASK; } @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(); - } + //FADD (scalar) + @Override public void encoding( Encoding enc ) { arm.f_scalar(enc,this,Arm64.OPF_OP_ADD);} // Default on double precision for now(64 bits) - // General form: "vadd.32 rd = src1 + src2 + // General form: "addf 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 index c8f5f8dd..52cf36ea 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java @@ -1,51 +1,28 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; 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) { + final int _imm; + AddIARM(Node add, int imm) { super(add); _inputs.pop(); - _ti = ti; + _imm = imm; } - - // Register mask allowed on input i. - @Override - public RegMask regmap(int i) { - // assert i== i; - return arm.RMASK; + @Override public String op() { + return _imm == 1 ? "inc" : (_imm == -1 ? "dec" : "addi"); } - - // 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(); + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + //ADD (immediate) + @Override public void encoding( Encoding enc ) { + arm.imm_inst(enc,this, in(1), Arm64.OPI_ADD,_imm); } - // 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 void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + #").p(_imm); } - - @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 index 7655df40..40812d07 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java @@ -1,36 +1,20 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; 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.codegen.*; 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 + AndARM(Node and) { super(and); } + @Override public String op() { return "and"; } + @Override public String glabel() { return "&"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + // AND (shifted register) + @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,Arm64.OP_AND); } // 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 index ee05654b..82d553aa 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java @@ -1,56 +1,25 @@ package com.seaofnodes.simple.node.cpus.arm; - +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; 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) { +public class AndIARM extends MachConcreteNode implements MachNode { + final int _imm; + AndIARM(Node and, int imm) { 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; + _imm = imm; } - - // Register mask allowed as a result. 0 for no register. - @Override - public RegMask outregmap() { - return arm.RMASK; + @Override public String op() { return "andi"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public void encoding( Encoding enc ) { + arm.imm_inst_n(enc,this, in(1), Arm64.OPI_AND,_imm); } - - // 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 void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" & #").p(Arm64.decodeImm12(_imm)); } - - @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 index a79de49b..cce868f9 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java @@ -1,40 +1,19 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; 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 String op() { return "sar"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.DMASK; } + @Override public void encoding( Encoding enc ) { arm.shift_reg(enc,this,Arm64.OP_ASR); } @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 index 099c3699..2d50e967 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java @@ -1,41 +1,32 @@ package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.*; +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.SB; import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Encoding; 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; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.Node; -// 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(); + final int _imm; + AsrIARM(Node asr, int imm) { + super(asr); + _inputs.pop(); + _imm = imm; } - - // General form - // General form: "asri rd, rs1, imm" + @Override public String op() { return "asri"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public void encoding( Encoding enc ) { + short rd = enc.reg(this); + short rn = enc.reg(in(1)); + assert _imm > 0; + enc.add4(Arm64.imm_shift(Arm64.OPI_ASR,_imm, 0b111111, rn, rd)); + } + // 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); + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> #").p(_imm); } - - @Override public String op() { return "asri"; } -} +}
\ No newline at end of file diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java index 1a5108cf..5a660c90 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java @@ -1,25 +1,23 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import java.io.ByteArrayOutputStream; - - // Jump on flags, uses flags -public class BranchARM extends IfNode implements MachNode{ - final String _bop; +public class BranchARM extends IfNode implements MachNode, RIPRelSize { + String _bop; BranchARM(IfNode iff, String bop ) { super(iff); _bop = bop; } - + @Override public String op() { return "j"+_bop; } @Override public String label() { return op(); } - @Override public void postSelect() { + @Override public void postSelect(CodeGen code) { Node set = in(1); + if( set==null ) return; // Never-node cutout Node cmp = set.in(1); // Bypass an expected Set and just reference the cmp directly if( set instanceof SetARM) @@ -30,19 +28,50 @@ public class BranchARM extends IfNode implements MachNode{ @Override public RegMask regmap(int i) { assert i==1; return arm.FLAGS_MASK; } @Override public RegMask outregmap() { return null; } + @Override public void negate() { _bop = negate(_bop); } + + @Override public void encoding( Encoding enc ) { + // Assuming that condition flags are already set. These flags are set + // by comparison (or sub). No need for regs because it uses flags + if( in(1)!=null ) { + // B.cond + enc.add4( Arm64.b_cond(Arm64.OP_BRANCH, 0, Arm64.make_condition(_bop)) ); + } else { + if( _bop=="!=" ) return; // Inverted, no code + enc.add4(Arm64.b(Arm64.OP_UJMP, 0)); + } + enc.jump(this,cproj(0)); + } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { + // Delta is from opcode start + @Override public byte encSize(int delta) { + if( in(1)==null && _bop=="!=" ) return 0; // Inverted never-node, no code + if( -(1<<19) <= delta && delta < (1<<19) ) return 4; + // 2 word encoding needs a tmp register, must teach RA throw Utils.TODO(); } - @Override public void asm(CodeGen code, SB sb) { - String src = code.reg(in(1)); - if( src!="flags" ) sb.p(src); + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + assert !( in(1)==null && _bop=="!=" ); // Inverted never-node, no code no patch + if( opLen==4 ) { + enc.patch4(opStart,Arm64.b_cond(Arm64.OP_BRANCH, delta, Arm64.make_condition(_bop))); + } else { + throw Utils.TODO(); + } } - @Override public String op() { return "j"+_bop; } - @Override public String comment() { - return "L"+cproj(1)._nid+", L"+cproj(0)._nid; + @Override public void asm(CodeGen code, SB sb) { + if( in(1)!=null ) { // Never-node + String src = code.reg(in(1)); + if( src!="flags" ) sb.p(src).p(" "); + } else if( _bop=="!=" ) { + sb.p("never"); + return; + } + CFGNode prj = cproj(0).uctrlSkipEmpty(); + if( !prj.blockHead() ) prj = prj.cfg0(); + sb.p(label(prj)); } + @Override public String comment() { return "L"+cproj(1)._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 index 0f883847..57d7ef7c 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java @@ -1,13 +1,12 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeFunPtr; -import java.io.ByteArrayOutputStream; -public class CallARM extends CallNode implements MachNode { +public class CallARM extends CallNode implements MachNode, RIPRelSize { final TypeFunPtr _tfp; final String _name; @@ -19,27 +18,31 @@ public class CallARM extends CallNode implements MachNode { _name = CodeGen.CODE.link(tfp)._name; } + @Override public String op() { return "call"; } @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; } + @Override public RegMask regmap(int i) { return arm.callInMask(_tfp,i,fun()._maxArgSlot); } + @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 encoding( Encoding enc ) { + enc.relo(this); + // BL + enc.add4(Arm64.b(Arm64.OP_CALL,0)); // Target patched at link time + } + + // Delta is from opcode start, but X86 measures from the end of the 5-byte encoding + @Override public byte encSize(int delta) { return 4; } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + enc.patch4(opStart,Arm64.b(Arm64.OP_CALL,delta)); } @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.p(code.reg(arg(i+2))).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 index 6177c5ea..b3c2f6c2 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallEndARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallEndARM.java @@ -1,35 +1,8 @@ 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"; } - + CallEndARM( CallEndNode cend ) { super(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 index db85a9a9..fa0fbd81 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java @@ -1,26 +1,26 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import java.io.ByteArrayOutputStream; public class CallRRARM extends CallNode implements MachNode { - CallRRARM(CallNode call) {super(call);} + 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 + : arm.callInMask(tfp(),i,fun()._maxArgSlot); // 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 encoding( Encoding enc ) { + // Needs a register, typically a jump-and-link-register opcode + // blr + short self = enc.reg(this); + enc.add4(Arm64.blr(Arm64.OP_CALLRARM, self)); } @Override public void asm(CodeGen code, SB sb) { @@ -29,4 +29,5 @@ public class CallRRARM extends CallNode implements MachNode { sb.p(code.reg(arg(i))).p(" "); sb.unchar(2); } + } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CastARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CastARM.java new file mode 100644 index 00000000..a5a4e112 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CastARM.java @@ -0,0 +1,18 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Encoding; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.CastNode; +import com.seaofnodes.simple.node.MachNode; + +public class CastARM extends CastNode implements MachNode { + public CastARM( CastNode cast ) { super(cast); } + @Override public String op() { return label(); } + @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 void encoding( Encoding enc ) { } + @Override public void asm(CodeGen code, SB sb) { _t.print(sb.p(code.reg(in(1))).p(" isa ")); } +} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java index f4f6242d..ec5e8ff0 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java @@ -1,25 +1,18 @@ 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.isa.Arm64; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFunPtr; -import java.io.ByteArrayOutputStream; 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 regmap(int i) { 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(); - } + // SUBS (shifted register) + @Override public void encoding( Encoding enc ) { arm.r_reg_subs(enc,this,Arm64.OP_CMP); } // General form: "cmp rs1, rs2" @Override public void asm(CodeGen code, SB sb) { 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 index 9c287fcb..e82a2ee3 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java @@ -1,31 +1,22 @@ 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.codegen.*; 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);} + @Override public String op() { return "cmpf"; } + @Override public RegMask regmap(int i) { return arm.DMASK; } + @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(); - } - + // FCMP + @Override public void encoding( Encoding enc ) { arm.f_cmp(enc,this); } // 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 index a0b10ba4..dbf9c2e3 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java @@ -1,46 +1,35 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; 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) +// CMP (immediate) - SUBS public class CmpIARM extends MachConcreteNode implements MachNode { final int _imm; final String _bop; - CmpIARM(BoolNode bool, TypeInteger ti) { + CmpIARM(BoolNode bool, int imm) { super(bool); _inputs.pop(); _bop = bool.op(); - _imm = (int)ti.value(); - assert _imm == ti.value(); + _imm = imm; } - // 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); + super((Node)null); + addDef(null); + addDef(cmp.in(1)); _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 String op() { return _imm==0 ? "test" : "cmp"; } + @Override public RegMask regmap(int i) { 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(); - } + @Override public void encoding( Encoding enc ) { arm.imm_inst_subs(enc,in(1),in(1), Arm64.OPI_CMP,_imm); } // General form: "cmp rs1, 1" @Override public void asm(CodeGen code, SB sb) { @@ -49,6 +38,4 @@ public class CmpIARM extends MachConcreteNode implements MachNode { 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 deleted file mode 100644 index 47f68ccc..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpMemARM.java +++ /dev/null @@ -1,4 +0,0 @@ -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 index 7c67e2dd..91bd347f 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java @@ -1,33 +1,20 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; 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(); - } - + @Override public String op() { return "div"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + // SDIV + @Override public void encoding( Encoding enc ) { arm.madd(enc,this,Arm64.OP_DIV,3); } // 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 index 2137c01c..6b86e722 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java @@ -1,30 +1,20 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; 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 String op() { return "divf"; } + @Override public RegMask regmap(int i) { return arm.DMASK; } @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(); - } - - + // FDIV (scalar) + @Override public void encoding( Encoding enc ) { arm.f_scalar(enc,this,Arm64.OPF_DIV); } // 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 index 937c437b..098c1353 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java @@ -1,28 +1,21 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; 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. + F8ToIARM(Node f8toi) { super(f8toi); } + @Override public String op() { return "cvti"; } + @Override public RegMask regmap(int i) { return arm.DMASK; } @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 encoding( Encoding enc ) { + enc.add4(Arm64.floatToInteger(enc.reg(in(1))-arm.D_OFFSET,enc.reg(this))); } @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 index 7fafc31c..6209397d 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java @@ -1,29 +1,38 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; 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.codegen.*; import com.seaofnodes.simple.node.ConstantNode; import com.seaofnodes.simple.node.MachNode; -import java.io.ByteArrayOutputStream; +import com.seaofnodes.simple.type.TypeFloat; //FMOV (scalar, immediate) //Floating-point move immediate. -public class FloatARM extends ConstantNode implements MachNode { +public class FloatARM extends ConstantNode implements MachNode, RIPRelSize { FloatARM(ConstantNode con) { super(con); } - @Override public String op() { return "fld"; } - // Register mask allowed on input i. 0 for no register. + @Override public String op() { return "ld8"; } @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(); + @Override public void encoding( Encoding enc ) { + enc.largeConstant(this,_con,0,-1/*TODO: ARM-style ELF patching*/); + short dst = (short)(enc.reg(this) - arm.D_OFFSET); + double d = ((TypeFloat)_con).value(); + long x = Double.doubleToRawLongBits(d); + enc.add4(Arm64.load_pc(Arm64.OPF_ARM, 0, dst)); + } + + // Delta is from opcode start. + @Override public byte encSize(int delta) { return 4; } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + short dst = (short)(enc.reg(this) - arm.D_OFFSET); + enc.patch4(opStart, Arm64.load_pc(Arm64.OPF_ARM, delta, dst)); } // Human-readable form appended to the SB. Things like the encoding, @@ -31,6 +40,7 @@ public class FloatARM extends ConstantNode implements MachNode { // 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(" #")); + _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 index 0134911f..c49875f4 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java @@ -1,21 +1,22 @@ package com.seaofnodes.simple.node.cpus.arm; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.*; 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(); +public class FunARM extends FunNode implements MachNode { + FunARM(FunNode fun) { super(fun); } + @Override public void computeFrameAdjust(CodeGen code, int maxReg) { + super.computeFrameAdjust(code,maxReg); + _frameAdjust = (_frameAdjust+15) & -16; // Keep SP aligned, including leaf spills. + } + @Override public void encoding( Encoding enc ) { + int sz = _frameAdjust; + if( sz == 0 ) return; // Skip if no frame adjust + if( sz >= 1L<<12 ) throw Utils.TODO(); + enc.add4(Arm64.imm_inst(Arm64.OPI_SUB, sz, arm.RSP, arm.RSP)); } } 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 index b106ab8a..a7fa7a6a 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java @@ -1,29 +1,26 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; 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. + I2F8ARM(Node i2f8) { super(i2f8); } + @Override public String op() { return "cvt"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } @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(); + @Override public void encoding( Encoding enc ) { + // SCVTF + short self = (short)(enc.reg(this )-arm.D_OFFSET); + short reg1 = enc.reg(in(1)); + int body = Arm64.float_cast(Arm64.OP_FLOAT_C, 1, reg1, self); + enc.add4(body); } // 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 index 1f81300e..64d00d57 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java @@ -1,30 +1,28 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; 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 String op() { return "ldi"; } @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(); + @Override public void encoding( Encoding enc ) { + short self = enc.reg(this); + long x = _con==Type.NIL ? 0 : ((TypeInteger)_con).value(); + Arm64.constant(enc,self,x); } // Human-readable form appended to the SB. Things like the encoding, 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 index 0d59cc47..6fd4274a 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java @@ -1,37 +1,34 @@ package com.seaofnodes.simple.node.cpus.arm; - +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; 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) { +public class LoadARM extends MemOpARM { + LoadARM(LoadNode ld, Node base, Node idx, int off) { super(ld, base, idx, off, 0); } + @Override public String op() { return "ld"+_sz; } + @Override public RegMask outregmap() { return size() >= 4 ? arm.MEM_MASK : arm.WMASK; } - @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; } - + private static final int[] OP_LOADS = new int[]{ Arm64.OP_LOAD_IMM_8, Arm64.OP_LOAD_IMM_16, Arm64.OP_LOAD_IMM_32, Arm64.OP_LOAD_IMM_64, }; - @Override public String op() { return "ld"+_sz; } + private static final int[] OP_LOAD_RS = new int[]{ Arm64.OP_LOAD_R_8 , Arm64.OP_LOAD_R_16 , Arm64.OP_LOAD_R_32 , Arm64.OP_LOAD_R_64, }; + // ldr(immediate - unsigned offset) | ldr(register) + @Override public void encoding( Encoding enc ) { + ldst_encode(enc, OP_LOADS[Integer.numberOfTrailingZeros(size())], OP_LOAD_RS[_declaredType.log_size()], this, size()); + } @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 index 6108c889..7624554e 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java @@ -1,41 +1,20 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; 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; } + @Override public String op() { return "shr"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @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(); - } - - // General form - // General form: "lsl rd, rs1, rs2" + @Override public void encoding( Encoding enc ) { arm.shift_reg(enc,this,Arm64.OP_LSL); } @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 index 7a7014f4..bf394172 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java @@ -1,42 +1,34 @@ package com.seaofnodes.simple.node.cpus.arm; - -import com.seaofnodes.simple.*; +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.SB; import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Encoding; 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; - +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.Node; -// 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(); + final int _imm; + LslIARM(Node lsl, int imm) { + super(lsl); + _inputs.pop(); + _imm = imm; } - - // General form - // General form: "lsli rd, rs1, imm" + @Override public String op() { return "lsli"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public void encoding( Encoding enc ) { + short rd = enc.reg(this); + short rn = enc.reg(in(1)); + assert _imm > 0; + // UBFM <Xd>, <Xn>, #(-<shift> MOD 64), #(63-<shift>) + // immr must be (-<shift> MOD 64) = 64 - shift + enc.add4(Arm64.imm_shift(Arm64.OPI_LSL, 64 - _imm, (64 - _imm) - 1, rn, rd)); + } + // 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); + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" << #").p(_imm); } - - @Override public String op() { return "lsli"; } -} +}
\ No newline at end of file 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 index e6168dee..2d5fa52c 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java @@ -1,40 +1,20 @@ package com.seaofnodes.simple.node.cpus.arm; - +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; 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(); - } + @Override public String op() { return "shr"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } - // General form - // General form: "lsr rd, rs1, rs2" + @Override public void encoding( Encoding enc ) { arm.shift_reg(enc,this,Arm64.OP_LSR); } @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 index 8fb20cb2..6a12e4a5 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java @@ -1,41 +1,30 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; 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(); +// logical right shift immediate +public class LsrIARM extends MachConcreteNode implements MachNode { + final int _imm; + LsrIARM(Node lsr, int imm) { + super(lsr); + _inputs.pop(); + _imm = imm; } - - // General form - // General form: "lsrl rd, rs1, imm" + @Override public String op() { return "lsri"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public void encoding( Encoding enc ) { + short rd = enc.reg(this); + short rn = enc.reg(in(1)); + assert _imm > 0; + enc.add4(Arm64.imm_shift(Arm64.OPI_LSR,_imm, 0b111111, rn,rd)); + } + // General form: "lsri 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); + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >>> #").p(_imm); } - - @Override public String op() { return "lsrl"; } -} +}
\ No newline at end of file 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 index c22e71f6..5e175b1e 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java @@ -1,60 +1,79 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; 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.codegen.*; 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 +public abstract class MemOpARM extends MemOpNode implements MachNode { + final int _off; // Limit 9 bits sized, or (13 bits<<logsize) unsigned + final int _imm; // Limit ? bits final char _sz = (char)('0'+(1<<_declaredType.log_size())); - MemOpARM(MemOpNode mop, Node base, Node idx, int off, int imm) { + MemOpARM(MemOpNode mop, Node ptr, Node idx, int off, int imm) { super(mop,mop); - assert base._type instanceof TypeMemPtr && !(base instanceof AddNode); - assert ptr() == base; - _inputs.setX(3,idx); + assert ptr._type instanceof TypeMemPtr && !(ptr instanceof AddNode); + assert ptr() == ptr; + _inputs.setX(2, ptr); + _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); + MemOpARM( MemOpNode mop, Node ptr, Node idx, int off, int imm, Node val ) { + this(mop,ptr,idx,off,imm); _inputs.setX(4,val); } - Node idx() { return in(3); } - Node val() { return in(4); } // Only for stores, includin + @Override public String label() { return op();} + Node val() { return in(4); } // Only for stores @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(); } + int size() { return 1<<_declaredType.log_size(); } - - // Register mask allowed on input i. + // Wider mask to store both GPRs and FPRs @Override public RegMask regmap(int i) { - throw Utils.TODO(); + // 0 - ctrl + if( i==1 ) return null; // memory + if( i==2 ) return arm.RMASK; // ptr/base + if( i==3 ) return arm.RMASK; // off/index + if( i==4 ) return size() >= 4 ? arm.MEM_MASK : arm.RMASK; // value + return null; // Anti-dependence } - // 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(); } + // Shared encoding for loads and stores(int and float) + public void ldst_encode( Encoding enc, int opcode_imm, int opcode_reg, Node xval, int size) { + short ptr = enc.reg(ptr()); + short off = enc.reg(off()); + short val = enc.reg(xval); + // The allocated register selects integer vs SIMD/FP encoding, even for + // bitwise copies. ARM numbers each register bank from zero in the opcode. + boolean fp = val >= arm.D0; + assert !fp || size >= 4; + if( fp ) { + opcode_imm |= 1<<4; + opcode_reg |= 1<<5; + val -= arm.D0; + } + int body = off() == null + ? Arm64.load_str_imm(opcode_imm, _off, ptr, val, size) + : Arm64.indr_adr(opcode_reg, off, Arm64.STORE_LOAD_OPTION.SXTX, 0, ptr, val); + enc.add4(body); + } SB asm_address(CodeGen code, SB sb) { sb.p("[").p(code.reg(ptr())).p("+"); - if( idx() != null ) sb.p(code.reg(idx())); + if( off() != null ) sb.p(code.reg(off())); 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 index 582b97d4..a0955565 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java @@ -1,34 +1,20 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; 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{ +public class MulARM extends MachConcreteNode implements MachNode { MulARM(Node mul) {super(mul);} + @Override public String op() { return "mul"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } - // 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(); - } - + @Override public void encoding( Encoding enc ) { arm.madd(enc,this,Arm64.OP_MUL,31); } // 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 index b71b412d..33d32bd4 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java @@ -1,30 +1,19 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; 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 String op() { return "mulf"; } + @Override public RegMask regmap(int i) { return arm.DMASK; } @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(); - } - + @Override public void encoding( Encoding enc ) { arm.f_scalar(enc,this,Arm64.OPF_MUL); } // 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/arm/MulIARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulIARM.java deleted file mode 100644 index c118b00a..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulIARM.java +++ /dev/null @@ -1,43 +0,0 @@ -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 MulIARM extends MachConcreteNode implements MachNode{ - final TypeInteger _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) { - // 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: "muli 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); - } - - @Override public String op() { return "muli"; } -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java new file mode 100644 index 00000000..b7c3c803 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java @@ -0,0 +1,25 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.type.TypeInteger; + +public class NegARM extends MachConcreteNode implements MachNode { + NegARM(Node sub) { + super(sub); + //_inputs.pop(); + } + @Override public String op() { return "neg"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public void encoding( Encoding enc ) { + // reverse subtract with immediate 0 + arm.imm_inst(enc,this, in(1), 0b00100110, 0); + } + // 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))); + } +} 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 index c145f348..0dac086b 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java @@ -1,31 +1,37 @@ package com.seaofnodes.simple.node.cpus.arm; - +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import java.io.ByteArrayOutputStream; - -public class NewARM extends NewNode implements MachNode { +public class NewARM extends NewNode implements MachNode, RIPRelSize { // 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(); + @Override public void encoding( Encoding enc ) { + // BL(branch with link) + enc.external(this,"calloc"). + add4(Arm64.mov(Arm64.OP_MOVZ, 0, 1, 0)). // movz x0,#1 + add4(Arm64.b(Arm64.OP_CALL, 0)); } - // General form: "alloc #bytes" + // Patch is for running "new" in a JIT. + // Delta is from opcode start + @Override public byte encSize(int delta) { return 4*2; } + + // Patch is for running "new" in a JIT. + // Delta is from opcode start + @Override public void patch(Encoding enc, int opStart, int opLen, int delta ) { + // Negative patches are JIT emulator targets. + if( opStart+delta < 0 ) { + enc.patch4(opStart+4, Arm64.b_calloc(Arm64.OP_CALL, delta-4)); + } else { + throw Utils.TODO(); + } + } + // General form: "alloc #bytes PC" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(size())); + sb.p("ldi x0=#1\n"); + sb.p("call #calloc"); } - - @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 index 28ebd60d..ec017032 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java @@ -1,26 +1,29 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; 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.codegen.*; 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 String op() { return "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 RegMask outregmap() { return arm.WMASK; } + @Override public RegMask killmap() { return arm.FLAGS_MASK; } + @Override public void encoding( Encoding enc ) { + // subs xzr, rs, #0 + // cset rd, eq // Set rd to 1 if rs == 0 (equal), else 0 + // subtracting zero from rs will just yield rs, it sets the zero flag and then it's used in cset + short self = enc.reg(this ); + short reg1 = enc.reg(in(1)); + int subs = Arm64.imm_inst(Arm64.OP_SUBS, 0, reg1, 31); + enc.add4(subs); + int cset = Arm64.cset(Arm64.OP_CSET, 31, Arm64.COND.EQ, 31, self); + enc.add4(cset); } @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 index 12ea8265..ba3197c1 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java @@ -1,42 +1,21 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; 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; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; // 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 + OrARM(Node and) { super(and); } + @Override public String op() { return "or"; } + @Override public String glabel() { return "|"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,Arm64.OP_OR); } // 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 index f257a867..9c7cb8cc 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java @@ -1,40 +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.MachConcreteNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.Node; +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.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; +public class OrIARM extends MachConcreteNode implements MachNode { + final int _imm; + OrIARM(Node or, int imm) { + super(or); + _inputs.pop(); + _imm = imm; } - // 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(); + @Override public String op() { return "ori"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public void encoding( Encoding enc ) { + arm.imm_inst_n(enc, this, in(1), Arm64.OPI_OR, _imm); } - - // General form - // General form: "rd = rs1 | imm" + // General form: "ori 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); + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" | #").p(_imm); } - - @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 index c3fa132c..b419fe0a 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java @@ -1,25 +1,17 @@ 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.Encoding; 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); + _rmask = arm.callInMask(fun().sig(),_idx,0); } - // 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; } + @Override public void encoding( Encoding enc ) { } } 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 index b2add015..4b7d6f13 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java @@ -1,23 +1,15 @@ package com.seaofnodes.simple.node.cpus.arm; -import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.Encoding; 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(); - } + @Override public void encoding( Encoding enc ) { } } 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 index c7d0dd4c..a7ed5073 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java @@ -1,49 +1,16 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; 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; - +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; 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("]"); + @Override public void encoding( Encoding enc ) { + int frameAdjust = ((FunARM)fun())._frameAdjust; + if( frameAdjust > 0 ) + enc.add4(Arm64.imm_inst(Arm64.OPI_ADD, frameAdjust, arm.RSP, arm.RSP)); + enc.add4(Arm64.ret(Arm64.OP_RET)); } - - // 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 index c3945d1a..6809bb1c 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java @@ -1,20 +1,14 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; 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 @@ -22,13 +16,13 @@ public class SetARM extends MachConcreteNode implements MachNode { _inputs.push(cmp); _bop = bop; } - + @Override public String op() { return "set"+_bop; } @Override public RegMask regmap(int i) { assert i==1; return arm.FLAGS_MASK; } - @Override public RegMask outregmap() { return arm.RMASK; } + @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 encoding( Encoding enc ) { + int body = Arm64.cset(Arm64.OP_CSET,0b11111, Arm64.make_condition(_bop), 0b011111, enc.reg(this)); + enc.add4(body); } @Override public void asm(CodeGen code, SB sb) { @@ -36,6 +30,4 @@ public class SetARM extends MachConcreteNode implements MachNode { 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 index 85771842..c0c60ea2 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java @@ -1,22 +1,67 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.FunNode; import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.node.SplitNode; -import java.io.ByteArrayOutputStream; +import com.seaofnodes.simple.node.cpus.riscv.riscv; 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(); + @Override public void encoding( Encoding enc ) { + short dst = enc.reg(this ); + short src = enc.reg(in(1)); + + boolean dstX = dst >= arm.D_OFFSET; + boolean srcX = src >= arm.D_OFFSET; + + if (dst >= arm.MAX_REG) { + // store to sp + if(src >= arm.MAX_REG) { + throw Utils.TODO(); + } + int off = enc._fun.computeStackOffset(enc._code,dst); + if( srcX ) src -= arm.D_OFFSET; + enc.add4(Arm64.load_str_imm(Arm64.OP_STORE_IMM_64, off, arm.RSP, src, 8)); + return; + } + + if(src >= arm.MAX_REG) { + // Load from SP + int off = enc._fun.computeStackOffset(enc._code,src); + if( dstX ) dst -= arm.D_OFFSET; + enc.add4(Arm64.load_str_imm(Arm64.OP_LOAD_IMM_64, off, arm.RSP, dst, 8)); + return; + } + + // pick opcode based on regs + if(!dstX && !srcX) { + // GPR->GPR + enc.add4(Arm64.mov_reg(Arm64.OP_MOV, src, dst)); + } else if(dstX && srcX) { + // FPR->FPR + // fmov reg + enc.add4(Arm64.f_mov_reg(Arm64.OP_FMOV_REG, src - arm.D_OFFSET,dst - arm.D_OFFSET)); + } else if(!srcX && dstX) { + // GPR->FPR + // FMOV(general) 64 bits to DOUBLE-PRECISION + enc.add4(Arm64.f_mov_general(Arm64.OP_FMOV, 0b01, 0, 0b111, src, dst - arm.D_OFFSET)); + } else if( srcX && !dstX) { + //FPF->GPR + // FMOV(general) DOUBLE-PRECISION to 64 bits + enc.add4(Arm64.f_mov_general(Arm64.OP_FMOV, 0b01, 0, 0b110, src - arm.D_OFFSET, dst)); + } + } + + // General form: "mov dst = src" + @Override public void asm(CodeGen code, SB sb) { + FunNode fun = code._encoding==null ? null : code._encoding._fun; + sb.p(code.reg(this,fun)).p(" = ").p(code.reg(in(1),fun)); } } 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 index d78fdcad..cf858c36 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java @@ -1,49 +1,35 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; 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.codegen.*; import com.seaofnodes.simple.node.StoreNode; -import java.io.ByteArrayOutputStream; -import java.util.BitSet; +import com.seaofnodes.simple.node.Node; // 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) +// base - base pointer, offset is added to base +// null - index never allowed (no [reg+reg] mode) +// off - offset added to base +// imm - immediate value to store, only if val is null +// val - value to store or null //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); + StoreARM(StoreNode st, Node base, Node idx, int off, Node val) { + super(st, base, idx, off, 0, 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(); - } + private static final int[] OP_STORES = new int[]{ Arm64.OP_STORE_IMM_8, Arm64.OP_STORE_IMM_16, Arm64.OP_STORE_IMM_32, Arm64.OP_STORE_IMM_64, }; + private static final int[] OP_STORE_RS = new int[]{ Arm64.OP_STORE_R_8, Arm64.OP_STORE_R_16, Arm64.OP_STORE_R_32, Arm64.OP_STORE_R_64, }; + + @Override public void encoding( Encoding enc ) { + ldst_encode(enc, OP_STORES[Integer.numberOfTrailingZeros(size())], OP_STORE_RS[_declaredType.log_size()], val(), size()); + } @Override public void asm(CodeGen code, SB sb) { asm_address(code,sb).p(","); if( val()==null ) sb.p("#").p(_imm); 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 index 27555b53..6024df68 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java @@ -1,30 +1,20 @@ 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.isa.Arm64; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.*; 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; } + SubARM(Node sub) { super(sub); } + @Override public String op() { return "sub"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @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 encoding( Encoding enc ) { arm.r_reg(enc,this,Arm64.OP_SUB); } // 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 index 8ae05b40..dd147544 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java @@ -1,32 +1,19 @@ 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.isa.Arm64; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; -public class SubFARM extends MachConcreteNode implements MachNode{ +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 String op() { return "subf"; } + @Override public RegMask regmap(int i) { return arm.DMASK; } @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(); - } - + @Override public void encoding( Encoding enc ) { arm.f_scalar(enc,this,Arm64.OPF_SUB); } // 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 index d19210cb..e45bfa63 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java @@ -1,42 +1,30 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; 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) { + final int _imm; + SubIARM(Node sub, int imm) { super(sub); _inputs.pop(); - _ti = ti; + _imm = imm; } - - // Register mask allowed on input i. + @Override public String op() { return _imm == -1 ? "dec" : "subi"; } @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } - // 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(); + @Override public void encoding( Encoding enc ) { + arm.imm_inst(enc,this, in(1), Arm64.OPI_SUB,_imm); } // 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); + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - #").p(_imm); } - - @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 index cdb2b6ec..1f2b6a37 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java @@ -1,37 +1,54 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.ConstantNode; import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.node.cpus.riscv.riscv; import com.seaofnodes.simple.type.TypeFunPtr; -import java.io.ByteArrayOutputStream; -public class TFPARM extends ConstantNode implements MachNode { +public class TFPARM extends ConstantNode implements MachNode, RIPRelSize { 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); } + @Override public void encoding( Encoding enc ) { + enc.relo(this); + short self = enc.reg(this); + // adrp x0, 0 + int adrp = Arm64.adrp(1,0, Arm64.OP_ADRP, 0,self); + // add x0, x0, 0 + enc.add4(adrp); + arm.imm_inst(enc,Arm64.OPI_ADD,0, 0); + } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { + @Override public byte encSize(int delta) { + if( -(1L<<12) <= delta && delta < (1L<<12) ) return 4; 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" + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + short rpc = enc.reg(this); + if(opLen == 8 ) { + // opstart of add + int next = opStart + opLen; + int adrp_delta = delta >> 12; + // patch upper 20 bits via adrp + enc.patch4(opStart, Arm64.adrp(1, adrp_delta & 0b11, 0b10000, adrp_delta >> 2, rpc)); + // low 12 bits via add + enc.patch4(next, Arm64.imm_inst_l(Arm64.OPI_ADD, delta & 0xfff, rpc)); + } else { + // should not happen as one instruction is 4 byte, and TFP arm encodes 2. + throw Utils.TODO(); + } + } + @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/UJmpARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java new file mode 100644 index 00000000..acb32ddb --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java @@ -0,0 +1,47 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; + +// unconditional jump +public class UJmpARM extends CFGNode implements MachNode, RIPRelSize { + UJmpARM() { } + @Override public String op() { return "jmp"; } + @Override public String label() { return op(); } + @Override protected String format() { return "jmp "; } + @Override public RegMask regmap(int i) {return null; } + @Override public RegMask outregmap() { return null; } + @Override public Type compute() { throw Utils.TODO(); } + @Override public Node idealize() { throw Utils.TODO(); } + @Override public void encoding( Encoding enc ) { + enc.jump(this,uctrl()); + int body = Arm64.b(Arm64.OP_UJMP, 0); + enc.add4(body); + } + + // Delta is from opcode start + @Override public byte encSize(int delta) { + if( -(1<<26) <= delta && delta < (1<<26) ) return 4; + // 2 word encoding needs a tmp register, must teach RA + throw Utils.TODO(); + } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + if( opLen==4 ) { + enc.patch4(opStart,Arm64.b(Arm64.OP_UJMP, delta)); + } else { + throw Utils.TODO(); + } + } + + @Override public void asm(CodeGen code, SB sb) { + CFGNode target = uctrl(); + //assert target.nOuts() > 1; // Should optimize jmp to empty targets + sb.p(label(target)); + } +} 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 index fd127b18..5b635303 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java @@ -1,41 +1,20 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; 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; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; 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 + XorARM(Node xor) {super(xor);} + @Override public String op() { return "xor"; } + @Override public String glabel() { return "^"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,Arm64.OP_XOR); } // 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 index d230ea47..017815f3 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java @@ -1,43 +1,28 @@ 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.isa.Arm64; 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;} +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; - // 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(); +public class XorIARM extends MachConcreteNode implements MachNode { + final int _imm; + XorIARM(Node xor, int imm) { + super(xor); + _inputs.pop(); + _imm = imm; } - // General form - // General form: "rd = rs1 ^ imm" + @Override public String op() { return "xori"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + + // General form: "xori rd = rs1 ^ imm" + @Override public void encoding( Encoding enc ) { arm.imm_inst_n(enc,this, in(1), Arm64.OPI_XOR,_imm); } @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ^ #"); - _ti.print(sb); + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ^ #").p(_imm); } - - @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 index 72254310..66a59fde 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java @@ -1,30 +1,36 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.isa.Arm64; +import static com.seaofnodes.isa.Arm64.*; 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.codegen.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; public class arm extends Machine { + public arm( CodeGen code ) { + if( !"SystemV".equals(code._callingConv) ) + throw new IllegalArgumentException("Unknown calling convention "+code._callingConv); + } - // X86-64 V2. Includes e.g. SSE4.2 and POPCNT. + // ARM64 @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; + public static final int X0 = 0, X1 = 1, X2 = 2, X3 = 3, X4 = 4, X5 = 5, X6 = 6, X7 = 7; + public static final int X8 = 8, X9 = 9, X10 = 10, X11 = 11, X12 = 12, X13 = 13, X14 = 14, X15 = 15; + public static final int X16 = 16, X17 = 17, X18 = 18, X19 = 19, X20 = 20, X21 = 21, X22 = 22, X23 = 23; + public 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; + public static final int D0 = 32, D1 = 33, D2 = 34, D3 = 35, D4 = 36, D5 = 37, D6 = 38, D7 = 39; + public static final int D8 = 40, D9 = 41, D10 = 42, D11 = 43, D12 = 44, D13 = 45, D14 = 46, D15 = 47; + public static final int D16 = 48, D17 = 49, D18 = 50, D19 = 51, D20 = 52, D21 = 53, D22 = 54, D23 = 55; + public 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 int MAX_REG = 65; + public static final int D_OFFSET = 32; static final String[] REGS = new String[] { "X0", "X1", "X2", "X3", "X4", "X5", "X6", "X7", @@ -37,9 +43,7 @@ public class arm extends Machine { "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+"]"; - } + @Override public String[] regs() { return REGS; } // from (x0-x30) // General purpose register mask: pointers and ints, not floats @@ -55,11 +59,9 @@ public class arm extends Machine { // 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 SPLIT_MASK = new RegMask(WR_BITS | FP_BITS, -2L/*skip flags*/); 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); @@ -105,13 +107,126 @@ public class arm extends Machine { D7_MASK, }; - static RegMask callInMask( TypeFunPtr tfp, int idx ) { - if( idx==0 ) return RPC_MASK; + // ARM ENCODING + + // True if signed 9-bit immediate + private static boolean imm9(TypeInteger ti) { + // 55 = 64-9 + return ti.isConstant() && ((ti.value()<<55)>>55) == ti.value(); + } + // True if signed 12-bit immediate + private static boolean imm12(TypeInteger ti) { + // 52 = 64-12 + return ti.isConstant() && ((ti.value()<<52)>>52) == ti.value(); + } + + // Can we encode this in ARM's 12-bit LOGICAL immediate form? + // Some combination of shifted bit-masks. + private static int imm12Logical(TypeInteger ti) { + return ti.isConstant() ? Arm64.logicalImmediate(ti.value()) : -1; + } + + public static void imm_inst(Encoding enc, Node n,Node n2, int opcode, int imm12) { + short self = enc.reg(n); + short reg1 = enc.reg(n2); + + // ADD/SUB encode an unsigned magnitude; switch operation for a negative addend. + if( imm12<0 && (opcode==OPI_ADD || opcode==OPI_SUB) ) { + opcode ^= OPI_ADD ^ OPI_SUB; + imm12 = -imm12; + } + int body = Arm64.imm_inst(opcode, imm12&0xFFF, reg1, self); + enc.add4(body); + } + + public static void imm_inst_subs(Encoding enc, Node n,Node n2, int opcode, int imm12) { + short reg1 = enc.reg(n2); + // 31 = 11111 + int body = Arm64.imm_inst(opcode, imm12&0xFFF, reg1, 31); + enc.add4(body); + } + + // for cases where rs1 and dst are the same, eg add x0, x0, 1 + public static void imm_inst(Encoding enc, int opcode, int imm12, int self) { + int body = Arm64.imm_inst(opcode, imm12&0xFFF, self, self); + enc.add4(body); + } + + public static void imm_inst_n(Encoding enc, Node n, Node n2, int opcode, int imm13) { + short self = enc.reg(n); + short reg1 = enc.reg(n2); + int body = Arm64.imm_inst_n(opcode, imm13, reg1, self); + enc.add4(body); + } + + // for cases where rs1 and dst are the same, eg add x0, x0, 1 + public static int imm_inst_l(Encoding enc, Node n, int opcode, int imm12) { + short self = enc.reg(n); + short reg1 = enc.reg(n.in(1)); + int body = Arm64.imm_inst(opcode, imm12&0xFFF, reg1, self); + return body; + } + + // for normal add, reg1, reg2 cases (reg-to-reg) + // using shifted-reg form + + public static void r_reg(Encoding enc, Node n, int opcode) { + short self = enc.reg(n); + short reg1 = enc.reg(n.in(1)); + short reg2 = enc.reg(n.in(2)); + int body = Arm64.r_reg(opcode, 0, reg2, 0, reg1, self >= 32 ? reg1: self); + enc.add4(body); + } + + public static void r_reg_subs(Encoding enc, Node n, int opcode) { + short reg1 = enc.reg(n.in(1)); + short reg2 = enc.reg(n.in(2)); + // 31 = 11111 + int body = Arm64.r_reg(opcode, 0, reg2, 0, reg1, 31); + enc.add4(body); + } + + public static void shift_reg(Encoding enc, Node n, int op2) { + short self = enc.reg(n); + short reg1 = enc.reg(n.in(1)); + short reg2 = enc.reg(n.in(2)); + int body = Arm64.shift_reg(0b10011010110, reg2, op2, reg1, self); + enc.add4(body); + } + + // MUL can be considered an alias for MADD with the third operand Ra being set to 0 + + public static void madd(Encoding enc, Node n, int opcode, int ra) { + short self = enc.reg(n); + short reg1 = enc.reg(n.in(1)); + short reg2 = enc.reg(n.in(2)); + int body = Arm64.madd(opcode, reg2, ra, reg1, self); + enc.add4(body); + } + + public static void f_scalar(Encoding enc, Node n, int op ) { + short self = (short)(enc.reg(n) -D_OFFSET); + short reg1 = (short)(enc.reg(n.in(1))-D_OFFSET); + short reg2 = (short)(enc.reg(n.in(2))-D_OFFSET); + int body = Arm64.f_scalar(0b00011110, 1, reg2, op, reg1, self); + enc.add4(body); + } + + public static void f_cmp(Encoding enc, Node n) { + short reg1 = (short)(enc.reg(n.in(1))-D_OFFSET); + short reg2 = (short)(enc.reg(n.in(2))-D_OFFSET); + int body = Arm64.f_cmp(0b00011110, 3, reg2, reg1); + enc.add4(body); + } + + @Override public RegMask callArgMask(TypeFunPtr tfp, int idx, int maxArgSlot ) { return callInMask(tfp,idx,maxArgSlot); } + static RegMask callInMask(TypeFunPtr tfp, int idx, int maxArgSlot ) { + if( idx==0 ) return CodeGen.CODE._rpcMask; 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 ) + if( tfp.arg(i-2) instanceof TypeFloat) fcnt++; // Floats up to XMMS in XMM registers if( tfp.arg(idx-2) instanceof TypeFloat ) { @@ -125,124 +240,99 @@ public class arm extends Machine { throw Utils.TODO(); // Pass on stack slot } - static final long CALLEE_SAVE = + // Return the max stack slot used by this signature, or 0 + @Override public short maxArgSlot( TypeFunPtr tfp ) { + int icnt=0, fcnt=0; // Count of ints, floats + for( int i=0; i<tfp.nargs(); i++ ) { + if( tfp.arg(i) instanceof TypeFloat ) fcnt++; + else icnt++; + } + int nstk = Math.max(icnt-8,0)+Math.max(fcnt-8,0); + return (short)nstk; + } + + private 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]; + static final long CALLER_SAVE = ~CALLEE_SAVE & ~(1L<<RSP); + @Override public long callerSave() { return CALLER_SAVE; } + @Override public long neverSave() { return 1L<<RSP; } + @Override public RegMask retMask( TypeFunPtr tfp ) { + return tfp.ret() instanceof TypeFloat ? D0_MASK : X0_MASK; } + @Override public int rpc() { return X30; } // 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(); + return new UJmpARM(); } // Instruction selection - @Override public Node instSelect( Node n ) { + @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 AddFNode addf -> new AddFARM(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 CastARM(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 MinusNode neg -> new NegARM(neg); + case MulFNode mulf -> new MulFARM(mulf); + case MulNode mul -> new MulARM(mul); + case NewNode nnn -> new NewARM(nnn); + case NotNode not -> new NotARM(not); + 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 PhiNode phi -> new PhiNode(phi); + case ProjNode prj -> new ProjARM(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 ReturnNode ret -> new RetARM(ret,ret.fun()); + case SarNode sar -> asr(sar); + case ShlNode shl -> lsl(shl); + case ShrNode shr -> lsr(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-> new I2F8ARM(tfn); case ToIntegerNode cvt -> new F8ToIARM(cvt); - case XorNode xor -> xor(xor); + case XorNode xor -> xor(xor); - case LoopNode loop -> new LoopNode(loop); - case RegionNode region-> new RegionNode(region); + 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 _cmp(BoolNode bool) { + if( bool.isFloat() ) + return new CmpFARM(bool); + return bool.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti + ? new CmpIARM(bool, (int)ti.value()) + : new CmpARM(bool); } private Node ld(LoadNode ld) { @@ -253,129 +343,100 @@ public class arm extends Machine { // 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); + String op = "!="; + if( iff.in(1) instanceof BoolNode bool ) op = bool.op(); + else if( iff.in(1)==null ) op = "=="; // Never-node cutout + else iff.setDef(1, new BoolNode.NE(iff.in(1), new ConstantNode(TypeInteger.ZERO))); + return new BranchARM(iff, op); } 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); + return add.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && imm12(ti) + ? new AddIARM(add, (int)ti.value()) + : 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())) + return sub.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) + ? new SubIARM(sub, (int)(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) { + 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(); + 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 Type t -> t==Type.NIL ? new IntARM(con) : 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 call(CallNode call){ + return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp + ? new CallARM(call, tfp) + : new CallRRARM(call); } - 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 or(OrNode or) { + int imm12; + return or.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && (imm12 = imm12Logical(ti)) != -1 + ? new OrIARM(or, imm12) + : new OrARM(or); + } + private Node xor(XorNode xor) { + int imm12; + return xor.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && (imm12 = imm12Logical(ti)) != -1 + ? new XorIARM(xor, imm12) + : new XorARM(xor); } - 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 and(AndNode and) { + int imm12; + return and.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && (imm12 = imm12Logical(ti)) != -1 + ? new AndIARM(and, imm12) + : new AndARM(and); } - 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 asr(SarNode asr) { + return asr.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && ti.value() >= 0 && ti.value() < 63 + ? new AsrIARM(asr, (int)ti.value()) + : new AsrARM(asr); } - private Node prj(ProjNode prj) { - return new ProjARM(prj); + private Node lsl(ShlNode lsl) { + return lsl.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && ti.value() >= 0 && ti.value() < 63 + ? new LslIARM(lsl, (int)ti.value()) + : new LslARM(lsl); } + private Node lsr(ShrNode lsr) { + return lsr.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && ti.value() >= 0 && ti.value() < 63 + ? new LsrIARM(lsr, (int)ti.value()) + : new LsrARM(lsr); + } 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); + return new StoreARM(address(st),st.ptr(),idx,off,st.val()); } // 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 ) { + 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 ) { + if( mop.off() instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm9(ti) ) { off = (int)ti.value(); assert off == ti.value(); // In 32-bit range } else { @@ -384,5 +445,4 @@ public class arm extends Machine { 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 deleted file mode 100644 index e0792669..00000000 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/i2f8.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.seaofnodes.simple.node.cpus.arm; - -public class i2f8 { -} diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java new file mode 100644 index 00000000..fc96cbe9 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java @@ -0,0 +1,42 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.isa.RiscV; +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.ConstantNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.type.Type; + +// Add upper 20bits to PC. Immediate comes from the relocation info. +public class AUIPC extends ConstantNode implements MachNode, RIPRelSize { + AUIPC( Type tfp ) { super(tfp); } + @Override public RegMask regmap(int i) { return null; } + @Override public RegMask outregmap() { return riscv.WMASK; } + @Override public boolean isClone() { return true; } + @Override public AUIPC copy() { return new AUIPC(_con); } + @Override public String op() { return "auipc"; } + @Override public void encoding( Encoding enc ) { + enc.relo(this); + short dst = enc.reg(this); + enc.add4(RiscV.u_type(RiscV.OP_AUIPC, dst, 0)); + } + + // Delta is from opcode start, but X86 measures from the end of the 5-byte encoding + @Override public byte encSize(int delta) { return 4; } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + //short rpc = enc.reg(this); + //// High half is where the TFP constant used to be, the last input + //short auipc = enc.reg(in(_inputs._len-1)); + //enc.patch4(opStart,RiscV.i_type(0x67, rpc, 0, auipc, delta)); + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { + String reg = code.reg(this); + sb.p(reg).p(" = PC+#"); + if( _con == null ) sb.p("---"); + else _con.print(sb); + } +} 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 2417aeb6..b0921fa0 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,30 +1,18 @@ 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.isa.RiscV; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; -public class AddFRISC extends MachConcreteNode implements MachNode{ +// fadd.d +public class AddFRISC extends MachConcreteNode implements MachNode { AddFRISC(Node addf) {super(addf);} - - // Register mask allowed on input i. + @Override public String op() { return "addf"; } @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; } - - // 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: "fadd.d rd = src1 + src2 + @Override public void encoding( Encoding enc ) { riscv.rf_type(enc,this,RiscV.RM.RNE,1); } @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/riscv/AddIRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java index e92a7b08..c3aa1276 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,11 +1,22 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; 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 "+"; } + // Used to inst-selection as a direct match against an ideal Add/Sub + public AddIRISC( Node add, int imm12, boolean pop ) { super(add,imm12,pop); } @Override public String op() { return "addi"; } + @Override public String glabel() { return "+"; } + @Override int opcode() { return RiscV.OP_IMM; } + @Override int func3() {return 0;} + @Override public AddIRISC copy() { + // Clone the AddI, using the same inputs-only code used during inst select. + // Output edges are missing. + AddIRISC add = new AddIRISC(in(1),_imm12,false); + // Copys happen when output edges should be valid, so correct missing output edges. + in(1)._outputs.add(add); + return add; + } } 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 344c217d..df392a0e 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,29 +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.SB; +import com.seaofnodes.simple.codegen.*; 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); } + public AddRISC( Node add ) { super(add); } + AddRISC( Node base, Node off ) { + super(new Node[3]); + _inputs.set(1,base); + _inputs.set(2,off ); + } @Override public String op() { return "add"; } - - // 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; } - - // 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 encoding( Encoding enc ) { riscv.r_type(enc,this,0,0); } @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/riscv/AndIRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java index ddbf5aa4..7639ea94 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,11 +1,13 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; 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 "&"; } + public AndIRISC( Node and, int imm) { super(and,imm); } @Override public String op() { return "andi"; } + @Override public String glabel() { return "&"; } + @Override int opcode() { return RiscV.OP_IMM; } + @Override int func3() {return 7;} } 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 ca00bc98..93ef95c0 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,37 +1,15 @@ 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.SB; +import com.seaofnodes.simple.codegen.*; 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 AndRISC extends MachConcreteNode implements MachNode{ - AndRISC(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 riscv.RMASK; - } - - // Register mask allowed as a result. 0 for no register. +public class AndRISC extends MachConcreteNode implements MachNode { + public AndRISC(Node and) { super(and); } + @Override public String op() { return "and"; } + @Override public RegMask regmap(int i) { 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) { - throw Utils.TODO(); - } - - // General form - // General form: #rd = rs1 & rs2 + @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,0b111,0); } @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/riscv/BranchRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java index 30dfd7fe..b5ad5f59 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java @@ -1,48 +1,94 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.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.codegen.*; import com.seaofnodes.simple.node.*; -import java.io.ByteArrayOutputStream; // Conditional branch such as: BEQ -public class BranchRISC extends IfNode implements MachNode { - final String _bop; +public class BranchRISC extends IfNode implements MachNode, RIPRelSize { + String _bop; // label is obtained implicitly - BranchRISC( IfNode iff, String bop, Node n1, Node n2 ) { + public BranchRISC( IfNode iff, String bop, Node n1, Node n2 ) { super(iff); _bop = bop; _inputs.setX(1,n1); _inputs.setX(2,n2); } + @Override public String op() { return "b"+_bop; } @Override public String label() { return op(); } - + @Override public String comment() { return "L"+cproj(1)._nid; } @Override public RegMask regmap(int i) { return riscv.RMASK; } @Override public RegMask outregmap() { return null; } + @Override public void negate() { + _bop = negate(_bop); + // Cannot encode ">" or "<=", so flip these + if( _bop.equals(">") || _bop.equals("<=") ) { + swap12(); // Cannot negate the test, so swap the operands + _bop = swap(_bop); // Swap test to match + } + } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - p.p("if( ").n(in(1)).p(_bop); + p.p("if( "); + if( in(1)==null ) p.p("0"); + else p.n(in(1)); + p.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 encoding( Encoding enc ) { + if( in(1)==null && _bop=="!=" ) return; // Inverted never-node, no code + enc.jump(this,cproj(0)); + if( in(1)==null ) // Never node + enc.add4(RiscV.j_type(RiscV.OP_JAL, 0, 0)); + else { + // Todo: relocs (for offset - immf) + short src1 = enc.reg(in(1)); + short src2 = in(2)==null ? (short)riscv.ZERO : enc.reg(in(2)); + enc.add4(RiscV.b_type(RiscV.OP_BRANCH, RiscV.jumpop(_bop), src1, src2, 0)); + } } - @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))); + // Delta is from opcode start + @Override public byte encSize(int delta) { + if( in(1)==null && _bop=="!=" ) return 0; // Inverted never-node, no code + if( -4*1024 <= delta && delta < 4*1024 ) return 4; + // 2 word encoding needs a tmp register, must teach RA + throw Utils.TODO(); } - @Override public String op() { return "b"+_bop; } + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + assert !( in(1)==null && _bop=="!=" ); // Inverted never-node, no code no patch + if( in(1)==null ) { // Never node + enc.patch4(opStart,RiscV.j_type(RiscV.OP_JAL, 0, delta)); + } else { + short src1 = enc.reg(in(1)); + short src2 = in(2)==null ? (short)riscv.ZERO : enc.reg(in(2)); + if( opLen==4 ) { + enc.patch4(opStart,RiscV.b_type(RiscV.OP_BRANCH, RiscV.jumpop(_bop), src1, src2, delta)); + } else { + throw Utils.TODO(); + } + } + } - @Override public String comment() { - return "L"+cproj(1)._nid+", L"+cproj(0)._nid; + @Override public void asm(CodeGen code, SB sb) { + if( in(1)==null && _bop=="!=" ) { + sb.p("never"); + return; + } + String src1 = in(1)==null ? "#0" : code.reg(in(1)); + String src2 = in(2)==null ? "#0" : code.reg(in(2)); + sb.p(src1).p(" ").p(_bop).p(" ").p(src2).p(" "); + CFGNode prj = cproj(0).uctrlSkipEmpty(); + if( !prj.blockHead() ) prj = prj.cfg0(); + sb.p(label(prj)); } } 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 index fd1ba7c3..7df68e77 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallEndRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallEndRISC.java @@ -1,35 +1,8 @@ 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"; } - + CallEndRISC( CallEndNode cend ) { super(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 54f4fba9..2b3232c3 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,44 +1,59 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeFunPtr; -import java.io.ByteArrayOutputStream; -public class CallRISC extends CallNode implements MachNode{ +public class CallRISC extends CallNode implements MachNode, RIPRelSize { final TypeFunPtr _tfp; final String _name; - CallRISC( CallNode call, TypeFunPtr tfp ) { super(call); - _inputs.pop(); // Pop constant target assert tfp.isConstant(); + _inputs.pop(); // Pop constant target _tfp = tfp; _name = CodeGen.CODE.link(tfp)._name; } + @Override public String op() { return "call"; } @Override public String label() { return op(); } @Override public RegMask regmap(int i) { - return riscv.callInMask(_tfp,i); // Normal argument + return riscv.callInMask(_tfp,i,fun()._maxArgSlot); } - @Override public RegMask outregmap() { return null; } - + @Override public RegMask outregmap() { return riscv.RPC_MASK; } @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) { + @Override public void encoding( Encoding enc ) { + // Short form +/-4K: beq r0,r0,imm12 + // Long form: auipc rX,imm20/32; jal r0,[rX+imm12/32] + enc.relo(this); + enc.add4(RiscV.j_type(RiscV.OP_JAL, riscv.RPC, 0)); + } + + // Delta is from opcode start + @Override public byte encSize(int delta) { + if( -(1L<<20) <= delta && delta < (1L<<20) ) return 4; + // 2 word encoding needs a tmp register, must teach RA throw Utils.TODO(); } + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + if( opLen==4 ) { + enc.patch4(opStart,RiscV.j_type(RiscV.OP_JAL, riscv.RPC, delta)); + } else { + 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); - } + sb.p(code.reg(arg(i+2))).p(", "); + sb.unchar(2).p(" ").p(code.reg(fptr())); - @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 3bae656d..55e36ebf 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,26 +1,26 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import java.io.ByteArrayOutputStream; -public class CallRRISC extends CallNode implements MachNode{ +public class CallRRISC extends CallNode implements MachNode { CallRRISC( 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 + return i==_inputs._len-1 ? riscv.RMASK // Function call target - : riscv.callInMask(tfp(),i); // Normal argument + : riscv.callInMask(tfp(),i,fun()._maxArgSlot); // 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 encoding( Encoding enc ) { + short rpc = enc.reg(this); + short src = enc.reg(in(_inputs._len-1)); + int body = RiscV.i_type(RiscV.OP_JALR, rpc, 0, src, 0); + enc.add4(body); } @Override public void asm(CodeGen code, SB sb) { @@ -29,7 +29,4 @@ public class CallRRISC extends CallNode implements MachNode{ 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 index a0d0df90..c404aaa5 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CastRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CastRISC.java @@ -2,17 +2,17 @@ package com.seaofnodes.simple.node.cpus.riscv; import com.seaofnodes.simple.SB; import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Encoding; 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); } + public CastRISC( CastNode cast ) { super(cast); } + @Override public String op() { return label(); } @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 encoding( Encoding enc ) { } @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/DivFRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java index d360bea6..12a798b5 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,30 +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.isa.RiscV; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import java.io.ByteArrayOutputStream; +// fdiv.d public class DivFRISC extends MachConcreteNode implements MachNode{ DivFRISC(Node divf) {super(divf);} - - // Register mask allowed on input i. + @Override public String op() { return "divf"; } @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; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - + @Override public void encoding( Encoding enc ) { riscv.rf_type(enc,this,RiscV.RM.RUP,13); } // 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))); } - - @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 ee49a883..8367ee68 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,32 +1,17 @@ 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.SB; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; public class DivRISC extends MachConcreteNode implements MachNode{ - DivRISC(Node div) {super(div);} - - // 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. + public DivRISC(Node div) {super(div);} + @Override public String op() { return "div"; } + @Override public RegMask regmap(int i) { 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) { - throw Utils.TODO(); - } - - // Signed division - // General form: "rd = rs1 / rs" + @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,4,1); } @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/riscv/F8ToIRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java index f4de5d48..b2d4efea 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,28 +1,19 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; public class F8ToIRISC extends MachConcreteNode implements MachNode { F8ToIRISC(Node f8toi) {super(f8toi);} - - // Register mask allowed on input i. + @Override public String op() { return "cvti"; } @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.WMASK; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + RiscV.floatToInteger(enc,enc.reg(this),enc.reg(in(1))-riscv.F_OFFSET); } - @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1))); } - - @Override public String op() { return "f8toi"; } } 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 2832ac52..9f6dec78 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,31 +1,48 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.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.codegen.*; import com.seaofnodes.simple.node.ConstantNode; import com.seaofnodes.simple.node.MachNode; -import java.io.ByteArrayOutputStream; +import com.seaofnodes.simple.type.TypeFloat; -public class FltRISC extends ConstantNode implements MachNode{ - FltRISC(ConstantNode con) { super(con); } - @Override public String op() { return "flw"; } - // Register mask allowed on input i. 0 for no register. +public class FltRISC extends ConstantNode implements MachNode, RIPRelSize { + FltRISC(ConstantNode con) { super(con); } + @Override public String op() { return "ld8"; } @Override public RegMask regmap(int i) { return null; } - // General int registers! Must-split into the floats - @Override public RegMask outregmap() { return riscv.RMASK; } + @Override public RegMask outregmap() { return riscv.FMASK; } + @Override public boolean isClone() { return true; } + @Override public FltRISC copy() { return new FltRISC(this); } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + enc.largeConstant(this,_con, 0, -1/*TODO: RISC5 style patching*/); + short dst = (short)(enc.reg(this) - riscv.F_OFFSET); + short tmp = (short)riscv.T6; + // AUIPC dst,#hi20_constant_pool + enc.add4(RiscV.u_type(RiscV.OP_AUIPC, tmp, 0)); + // Load dst,[dst+#low12_constant_pool] + enc.add4(RiscV.i_type(RiscV.OP_LOADFP, dst, 0b11, tmp, 0)); + } + @Override public RegMask killmap() { return new RegMask(riscv.T6); } + + // Delta is from opcode start. + // TODO: always size 8? + @Override public byte encSize(int delta) { return 8; } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + short dst = (short)(enc.reg(this) - riscv.F_OFFSET); + short tmp = (short)riscv.T6; + // AUIPC dst,#hi20_constant_pool + enc.patch4(opStart , RiscV.u_type(RiscV.OP_AUIPC, tmp, delta>>12)); + // Load dst,[dst+#low12_constant_pool] + enc.patch4(opStart+4, RiscV.i_type(RiscV.OP_LOADFP, dst, 0b11, tmp, delta & 0xFFF)); } - // 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(" #")); + _con.print(sb.p(code.reg(this)).p(" = #")); } + } 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 3b7d9a34..4ebdec15 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,21 +1,23 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.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.codegen.*; import com.seaofnodes.simple.node.FunNode; import com.seaofnodes.simple.node.MachNode; -import java.io.ByteArrayOutputStream; 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; } - // 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 computeFrameAdjust(CodeGen code, int maxReg) { + super.computeFrameAdjust(code,maxReg); + if( _hasCalls ) // If non-leaf, pad to 16b + _frameAdjust = ((_frameAdjust+8) & -16); + } + @Override public void encoding( Encoding enc ) { + int sz = _frameAdjust; + if( sz == 0 ) return; // Skip if no frame adjust + if( sz >= 1L<<12 ) throw Utils.TODO(); + enc.add4(RiscV.i_type(RiscV.OP_IMM, riscv.RSP, 0, riscv.RSP, -sz & 0xFFF)); } } 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 560d1b38..c9b20939 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,30 +1,24 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; // fcvt.d.w +// Converts a 32-bit signed integer, in integer register rs1 into a double-precision floating-point number in floating-point register rd. public class I2F8RISC extends MachConcreteNode implements MachNode { I2F8RISC(Node i2f8) {super(i2f8);} - - // Register mask allowed on input i. + @Override public String op() { return "cvtf"; } @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.FMASK; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + short dst = (short)(enc.reg(this )-riscv.F_OFFSET); + short src1 = enc.reg(in(1)); + int body = RiscV.r_type(RiscV.OP_FP,dst,RiscV.RM.RNE.ordinal(),src1,0,0x69); + enc.add4(body); } - - // 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))); } - - @Override public String op() { return "i2f8"; } } 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 index 330de299..dd41589c 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java @@ -1,40 +1,56 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.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.codegen.*; 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 ) { +abstract public class ImmRISC extends MachConcreteNode implements MachNode { + public final int _imm12; + ImmRISC( Node add, int imm12, boolean pop ) { super(add); - _inputs.pop(); - _imm = imm; + _imm12 = imm12; + // If pop, we are moving from an ideal Node to MachNode; the ideal Node + // inputs got copied here, but we are absorbing the immediate into the + // MachNode and so do not want the constant input. + if( pop ) _inputs.pop(); + // if NOT pop, we are the 2nd-half of a 2-part expansion, reset the + // input edges to no-control + else { _inputs.set(0,null); _inputs.setX(1,add); } } - - // Register mask allowed on input i. + ImmRISC( Node add, int imm12 ) { this(add,imm12,true); } @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 public boolean isClone() { + // Allow cloning of the 2nd half of a 2-part constant; this might + // trigger cloning the first part also. + return in(1) instanceof LUI || in(1) instanceof AUIPC; + } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - return p.p("( ").n(in(1)).p(String.format(" %s #%d )",glabel(),_imm)); + int imm12 = (_imm12<<20)>>20; // Sign extend 12 bits + p.p("( ").n(in(1)); + return p.p(String.format(" %s #%d )",glabel(),imm12)); } abstract int opcode(); + abstract int func3(); - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + short dst = enc.reg(this ); + short src = enc.reg(in(1)); + int body = RiscV.i_type(opcode(), dst, func3(), src, _imm12 & 0xFFF); + enc.add4(body); } // 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); + int imm12 = (_imm12<<20)>>20; // Sign extend 12 bits + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ").p(glabel()).p(" #").p(imm12); + if( in(1) instanceof LUI lui ) + sb.p(" // #").hex4((int)(((TypeInteger)lui._con).value()) + imm12); } } 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 e792b055..e4dd39dd 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,33 +1,29 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.ConstantNode; import com.seaofnodes.simple.node.MachNode; -import java.io.ByteArrayOutputStream; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; -// Integer constants +// 12-bit integer constant. Larger constants are made up in the instruction +// selection by adding with a LUI. public class IntRISC extends ConstantNode implements MachNode { - IntRISC(ConstantNode con) { super(con); } + public 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.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) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + short dst = enc.reg(this); + int val = _con==Type.NIL ? 0 : (int)(((TypeInteger)_con).value() & 0xFFF); + // Explicit truncation of larger immediates; this will sign-extend on + // load and this is handled during instruction selection. + enc.add4(RiscV.i_type(RiscV.OP_IMM, dst, 0, riscv.ZERO, val)); } - - // 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/riscv/LUI.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java new file mode 100644 index 00000000..596e11cd --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java @@ -0,0 +1,33 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.isa.RiscV; +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.ConstantNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.type.TypeInteger; + +// Load upper 20bits. +public class LUI extends ConstantNode implements MachNode { + public LUI( int imm20 ) { + super(TypeInteger.constant(imm20)); + assert riscv.imm20Exact((TypeInteger)_con); + } + @Override public RegMask regmap(int i) { return null; } + @Override public RegMask outregmap() { return riscv.WMASK; } + @Override public boolean isClone() { return true; } + @Override public LUI copy() { return new LUI((int)((TypeInteger)_con).value()); } + @Override public String op() { return "lui"; } + @Override public void encoding( Encoding enc ) { + long x = ((TypeInteger)_con).value(); + int imm20 = (int)(x>>12) & 0xFFFFF; + short dst = enc.reg(this); + int lui = RiscV.u_type(RiscV.OP_LUI, dst, imm20); + enc.add4(lui); + } + + @Override public void asm(CodeGen code, SB sb) { + String reg = code.reg(this); + sb.p(reg).p(" = #").hex4((int)(((TypeInteger)_con).value())); + } +} 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 3fe8f44e..5e1845b8 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,40 +1,30 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; - // 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 - +// off = off - imm12 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) { - return riscv.RMASK; - } - // Wide mask loads both ints and floats; encoding varies. + public LoadRISC(LoadNode ld, Node base, int off) { super(ld, base, off, null); } + @Override public String op() { return "ld" +_sz; } + @Override public RegMask regmap(int i) { return riscv.RMASK; } @Override public RegMask outregmap() { return riscv.MEM_MASK; } - - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + short dst = enc.reg(this ); + short ptr = enc.reg(ptr()); + int op = dst >= riscv.F_OFFSET ? RiscV.OP_LOADFP : RiscV.OP_LOAD; + if( dst >= riscv.F_OFFSET ) dst -= riscv.F_OFFSET; + enc.add4(RiscV.i_type(op, dst, func3(), ptr, _off)); } @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)).p(","); asm_address(code,sb); } - - @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 19869285..41ab37be 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 @@ -2,59 +2,74 @@ 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.SB; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; -import java.io.ByteArrayOutputStream; -import java.lang.StringBuilder; - -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( MemOpNode mop, Node base, Node idx, int off, int imm) { +public abstract class MemOpRISC extends MemOpNode implements MachNode { + final int _off; // Limit 12 bits + final char _sz; + MemOpRISC(MemOpNode mop, Node base, int off, Node val) { super(mop,mop); - assert base._type instanceof TypeMemPtr && !(base instanceof AddNode); - assert ptr() == base; - _inputs.setX(3,idx); + assert base._type instanceof TypeMemPtr; + _inputs.setX(2, base ); // Base can be an Add, is no longer raw object base + _inputs.setX(3, null); // Never an index + _inputs.setX(4, val ); _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); + _sz = (char)('0'+(1<<_declaredType.log_size())); } - Node idx() { return in(3); } - Node val() { return in(4); } // Only for stores, including op-to-memory + @Override public String label() { return op(); } + Node val() { return in(4); } // Only for stores @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(); } + // func3 is based on load/store size and extend + int func3() { + int func3 = -1; + // no unsigned flavour for store, so both signed and unsigned trigger the same + if(this instanceof StoreRISC) { + if( _declaredType == TypeInteger. I8 || _declaredType == TypeInteger.U8 || _declaredType == TypeInteger.BOOL) func3=0; // SB + if( _declaredType == TypeInteger.I16 || _declaredType == TypeInteger.U16 ) func3=1; // SH + if( _declaredType == TypeInteger.I32 || _declaredType == TypeInteger.U32 || _declaredType instanceof TypeMemPtr) func3=2; // SW + if( _declaredType == TypeInteger.BOT ) func3=3; // SD + if( func3 == -1 ) throw Utils.TODO(); + return func3; + } + if( _declaredType == TypeInteger. I8 ) func3=0; // LB + if( _declaredType == TypeInteger.I16 ) func3=1; // LH + if( _declaredType == TypeInteger.I32 ) func3=2; // LW + if( _declaredType == TypeInteger.BOT ) func3=3; // LD + if( _declaredType == TypeInteger. U8 ) func3=4; // LBU + if( _declaredType == TypeInteger.BOOL) func3=4; // LBU + if( _declaredType == TypeInteger.U16 ) func3=5; // LHU + if( _declaredType == TypeInteger.U32 ) func3=6; // LWU + + // float + if(_declaredType == TypeFloat.F32) func3 = 2; // fLW fSW + if(_declaredType == TypeFloat.F64) func3 = 3; // fLD fSD + if( _declaredType instanceof TypeMemPtr ) func3=3; // 8 byte pointers + if( func3 == -1 ) throw Utils.TODO(); + return func3; + } // Register mask allowed on input i. @Override public RegMask regmap(int i) { + // 0 - ctrl + // 1 - memory + if( i==2 ) return riscv.RMASK; // base + // 2 - index + if( i==4 ) return riscv.MEM_MASK; // value 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("]"); + return sb.p(_off).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 8a066ab1..40609f96 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,30 +1,18 @@ 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.isa.RiscV; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import java.io.ByteArrayOutputStream; -public class MulFRISC extends MachConcreteNode implements MachNode{ +// fmul.d +public class MulFRISC extends MachConcreteNode implements MachNode { MulFRISC(Node mulf) {super(mulf);} - - // Register mask allowed on input i. + @Override public String op() { return "mulf"; } @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; } - - // 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: "fmul.d rd = src1 * src2 + @Override public void encoding( Encoding enc ) { riscv.rf_type(enc,this,RiscV.RM.RNE,9); } @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/MulRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java index 0e2d35a3..a7961627 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,32 +1,17 @@ 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.SB; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; - // mulh signed multiply instruction(no-imm form) public class MulRISC extends MachConcreteNode implements MachNode{ - MulRISC(Node mul) {super(mul);} - - // Register mask allowed on input i. + public MulRISC(Node mul) {super(mul);} + @Override public String op() { return "mul"; } @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.WMASK; } - - // 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 encoding( Encoding enc ) { riscv.r_type(enc,this,0,1); } @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/riscv/NegRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java new file mode 100644 index 00000000..58340b32 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java @@ -0,0 +1,21 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.isa.RiscV; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; + +public class NegRISC extends MachConcreteNode implements MachNode { + public NegRISC( Node neg ) { super(neg); } + @Override public String op() { return "neg"; } + @Override public RegMask regmap(int i) { assert i==1; return riscv.RMASK; } + @Override public RegMask outregmap() { return riscv.WMASK; } + @Override public void encoding( Encoding enc ) { + short dst = enc.reg(this ); + short src = enc.reg(in(1)); + RiscV.r_type(RiscV.OP,dst,0,0,src,0x20); + } + @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/riscv/NewRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java index e8d32570..8e3daaba 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,29 +1,47 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import java.io.ByteArrayOutputStream; +import com.seaofnodes.simple.print.ASMPrinter; -public class NewRISC extends NewNode implements MachNode { +public class NewRISC extends NewNode implements MachNode, RIPRelSize { // A pre-zeroed chunk of memory. 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; } + @Override public void encoding( Encoding enc ) { + // Generic external encoding; 2 ops. + enc.external(this,"calloc"); + // A1 is a caller-save, allowed to crush building external address + // auipc + enc.add4(RiscV.i_type(RiscV.OP_IMM , _arg2Reg , 0, riscv.ZERO, 1)); + enc.add4(RiscV.u_type(RiscV.OP_AUIPC, riscv.A2 , 0)); + enc.add4(RiscV.i_type(RiscV.OP_JALR , riscv.RPC, 0, riscv.A2, 0)); + } + + // Patch is for running "new" in a JIT. + // Delta is from opcode start + @Override public byte encSize(int delta) { return 4*3; } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + // Patch is for running "new" in a JIT. + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + // Negative patches are JIT emulator targets. + if( opStart+delta < 0 ) { + delta -= 4; // Offset from the AUIPC start + int imm20 = delta>>12; + if( ((delta>>11)&1)==1 ) imm20++; // Correct accidental sign extension + int imm12 = delta&0xFFF; + enc.patch4(opStart+4,RiscV.u_type(RiscV.OP_AUIPC, riscv.A2 , imm20)); + enc.patch4(opStart+8,RiscV.i_type(RiscV.OP_JALR , riscv.RPC, 0, riscv.A2, imm12)); + } else + throw Utils.TODO(); } - // General form: "alloc #bytes" + // General form: "alloc #bytes PC" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(size())); + sb.p("ldi a1=#1\n"); + sb.p("auipc a2=#calloc\n"); + sb.p("call a2+#calloc, a0"); } - - @Override public String op() { return "alloc"; } } 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 index 6d30cd1c..6aefc54b 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java @@ -1,25 +1,22 @@ 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.isa.RiscV; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.MachConcreteNode; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.NotNode; public class NotRISC extends MachConcreteNode implements MachNode { - NotRISC(NotNode not) {super(not);} + public NotRISC(NotNode not) { super(not); } + @Override public String op() { return "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 RegMask outregmap() { return riscv.WMASK; } + @Override public void encoding( Encoding enc ) { + short dst = enc.reg(this ); + short src = enc.reg(in(1)); + // SEQZ is unsigned: negative nonzero inputs must also produce false. + enc.add4(RiscV.i_type(RiscV.OP_IMM, dst, 3, src, 1)); } - @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 f41d7162..53abcf0b 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,11 +1,13 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.node.Node; public class OrIRISC extends ImmRISC { - OrIRISC( Node and, int imm) { super(and,imm); } - @Override int opcode() { throw Utils.TODO(); } + public OrIRISC( Node and, int imm) { super(and,imm); } + @Override int opcode() { return RiscV.OP_IMM; } + @Override int func3() { return 6; } @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 7f808f90..103d4c7d 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,39 +1,16 @@ 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.MachConcreteNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.Node; -import java.io.ByteArrayOutputStream; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; public class OrRISC extends MachConcreteNode implements MachNode { - OrRISC(Node or) { - super(or); - } - - // 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. + public OrRISC(Node or) { super(or); } + @Override public String op() { return "or"; } + @Override public RegMask regmap(int i) { 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) { - throw Utils.TODO(); - } - - // General form - // General form: # rd = rs1 | rs2 + @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,6,0); } @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 "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 75674f37..0f8db4e5 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,26 +1,17 @@ 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.Encoding; 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 { final RegMask _rmask; ParmRISC(ParmNode parm) { super(parm); - // Assume int - _rmask = riscv.callInMask(fun().sig(),_idx); + _rmask = riscv.callInMask(fun().sig(),_idx,0); } - - // Register mask allowed on input i. 0 for no register. - @Override public RegMask regmap(int i) { return _rmask; } - // Register mask allowed as a result. Calling convention register + @Override public RegMask regmap(int i) { return null; } @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 void encoding( Encoding enc ) { } } 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 42d5dc9b..2f9ac469 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,25 +1,15 @@ 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.Encoding; 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); } - - // Register mask allowed on input i. 0 for no register. @Override public RegMask regmap(int i) { 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) { - throw Utils.TODO(); - } + @Override public void encoding( Encoding enc ) { } } 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 c9fa7fac..d28311dc 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,48 +1,22 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.*; import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Encoding; 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); } - - // Register mask allowed on input i. - // This is the normal calling convention - @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; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); +public class RetRISC extends ReturnNode implements MachNode { + public RetRISC(ReturnNode ret, FunNode fun) { super(ret, fun); fun.setRet(this); } + @Override public void encoding( Encoding enc ) { + int sz = fun()._frameAdjust; + if( sz >= 1L<<12 ) throw Utils.TODO(); + if( sz != 0 ) + enc.add4(RiscV.i_type(RiscV.OP_IMM, riscv.RSP, 0, riscv.RSP, sz & 0xFFF)); + short rpc = enc.reg(rpc()); + enc.add4(RiscV.i_type(RiscV.OP_JALR, riscv.ZERO, 0, rpc, 0)); } - - @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 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 - @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/riscv/SetFRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java index 3f9fc192..fabb9f92 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java @@ -1,15 +1,15 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.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.codegen.*; 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 +// corresponds to feq.d = equal +// fle.d = less than or equal +// flt.d = less than public class SetFRISC extends MachConcreteNode implements MachNode { final String _bop; // One of <,<=,== SetFRISC( BoolNode bool ) { @@ -17,17 +17,18 @@ public class SetFRISC extends MachConcreteNode implements MachNode { assert bool.isFloat(); _bop = bool.op(); } + @Override public String op() { return "set"+_bop; } @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 encoding( Encoding enc ) { + short dst = enc.reg(this ); + short src1 = (short)(enc.reg(in(1))-riscv.F_OFFSET); + short src2 = (short)(enc.reg(in(2))-riscv.F_OFFSET); + int body = RiscV.r_type(RiscV.OP_FP,dst,RiscV.fsetop(_bop),src1,src2,0b1010001/*FCMP*/); + enc.add4(body); } @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(in(1))).p(" ").p(_bop).p(" ").p(code.reg(in(2))); + sb.p(code.reg(this)).p(" = ").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 index 3c0f76ae..8b6512e1 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java @@ -1,17 +1,21 @@ package com.seaofnodes.simple.node.cpus.riscv; import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.node.BoolNode; +import com.seaofnodes.simple.node.Node; -// corresponds to slt,sltu,slti,sltiu, seqz +// corresponds to slti,sltiu public class SetIRISC extends ImmRISC { - final String _bop; // One of <,<=,== - SetIRISC( BoolNode bool, int imm ) { - super(bool,imm); - assert !bool.isFloat(); - _bop = bool.op(); + final boolean _unsigned; // slti vs sltiu + public SetIRISC( Node src, int imm12, boolean unsigned ) { + this(src,imm12,unsigned,true); } - @Override int opcode() { throw Utils.TODO(); } - @Override public String glabel() { return _bop; } - @Override public String op() { return "set"+_bop; } + public SetIRISC( Node src, int imm12, boolean unsigned, boolean pop ) { + super(src,imm12,pop); + if( !pop ) _inputs.setLen(2); + _unsigned = unsigned; + } + @Override public String op() { return "slt" + (_unsigned ? "u":"") + "i"; } + @Override public String glabel() { return "<"+ (_unsigned ? "u":""); } + @Override int opcode() { return 0b0010011; } + @Override int func3() { return _unsigned ? 0b011 : 0b010; } } 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 fb7650c7..8d4a2f71 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,33 +1,20 @@ 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; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; -// corresponds to slt,sltu,slti,sltiu, seqz +// corresponds to slt sltu public class SetRISC extends MachConcreteNode implements MachNode { - final String _bop; // One of <,<=,== - SetRISC( BoolNode bool ) { - super(bool); - assert !bool.isFloat(); - _bop = bool.op(); - } - @Override public RegMask regmap(int i) { throw Utils.TODO(); } + final boolean _unsigned; + public SetRISC( Node cmp, boolean unsigned ) { super(cmp); _unsigned=unsigned; } + @Override public String op() { return "slt" + (_unsigned ? "u":""); } + @Override public RegMask regmap(int i) { 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) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + riscv.r_type(enc,this,_unsigned ? 0b011 : 0b010,0); } - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(in(1))).p(" ").p(_bop).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 "set"+_bop; } -} +}
\ No newline at end of file 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 b5202908..52b238ec 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,11 +1,13 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; 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(); } + public SllIRISC( Node and, int imm) { super(and,imm); } + @Override int opcode() { return RiscV.OP_IMM; } + @Override int func3() { return 1; } @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 447f604a..87e6693e 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,37 +1,17 @@ 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.SB; +import com.seaofnodes.simple.codegen.*; 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; -public class SllRISC extends MachConcreteNode implements MachNode{ - SllRISC(Node sll) {super(sll);} - - // 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. +// Shift left logical +public class SllRISC extends MachConcreteNode implements MachNode { + public SllRISC(Node sll) { super(sll); } + @Override public String op() { return "shl"; } + @Override public RegMask regmap(int i) { 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) { - throw Utils.TODO(); - } - - // General form - // General form: "sll rd, rs1, rs2" + @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,1,0); } @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 "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 index b5076ba0..ca1bf614 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java @@ -1,21 +1,67 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; +import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.FunNode; 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; } + @Override public void encoding( Encoding enc ) { + short dst = enc.reg(this ); + short src = enc.reg(in(1)); + + // Flag for being either XMM or stack + boolean dstX = dst >= riscv.F_OFFSET; + boolean srcX = src >= riscv.F_OFFSET; + + if( dst >= riscv.MAX_REG ) { + // Store to SP + if( src >= riscv.MAX_REG ) { + throw Utils.TODO(); // Very rare stack-stack move + } + int off = enc._fun.computeStackOffset(enc._code,dst); + int op = srcX ? RiscV.OP_STOREFP : RiscV.OP_STORE; + if( srcX ) src -= riscv.F_OFFSET; + enc.add4(RiscV.s_type(op, 0b011, riscv.RSP, src, off)); + return; + } + if( src >= riscv.MAX_REG ) { + // Load from SP + int off = enc._fun.computeStackOffset(enc._code,src); + int op = dstX ? RiscV.OP_LOADFP : RiscV.OP_LOAD; + if( dstX ) dst -= riscv.F_OFFSET; + enc.add4(RiscV.i_type(op, dst, 0b011, riscv.RSP, off)); + return; + } + // pick opcode based on regs + if( !dstX && !srcX ) { + // GPR->GPR + enc.add4(RiscV.r_type(RiscV.OP,dst,0,src,riscv.ZERO,0)); + } else if( dstX && srcX ) { + // FPR->FPR + src -= riscv.F_OFFSET; + dst -= riscv.F_OFFSET; + enc.add4(RiscV.r_type(RiscV.OP_FP, dst, 0b000, src, src, 0b0010101)); // dst = FMIN(src,src) + } else if(!srcX && dstX) { + //GPR->FPR + // fmv.d.x + enc.add4(RiscV.r_type(RiscV.OP_FP, dst - riscv.F_OFFSET, 0, src, 0, 0b1111001)); + } else if(srcX && !dstX) { + //FPR->GPR + //fmv.x.d + enc.add4(RiscV.r_type(RiscV.OP_FP, dst, 0, src - riscv.F_OFFSET, 0, 0b1110001)); + } + } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + // General form: "mov dst = src" + @Override public void asm(CodeGen code, SB sb) { + FunNode fun = code._encoding==null ? null : code._encoding._fun; + sb.p(code.reg(this,fun)).p(" = ").p(code.reg(in(1),fun)); } } 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 d5ab5e08..49b1cad9 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,11 +1,13 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; 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(); } + public SraIRISC( Node and, int imm) { super(and,imm); } + @Override int opcode() { return RiscV.OP_IMM; } + @Override int func3() { return 5;} @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 0726e292..1e930e72 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,38 +1,17 @@ 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.MachConcreteNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; // Right Shift Arithmetic public class SraRISC extends MachConcreteNode implements MachNode { - - SraRISC(Node sra) {super(sra);} - - // 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. + public SraRISC(Node sra) { super(sra); } + @Override public String op() { return "sar"; } + @Override public RegMask regmap(int i) { 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) { - throw Utils.TODO(); - } - - // General form - // General form: "sra rd, rs1, rs2" + @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,0b101,0b00100000); } @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 "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 a4eb99bc..985abe2c 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,11 +1,13 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; 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(); } + public SrlIRISC( Node and, int imm, boolean pop) { super(and,imm,pop); } + @Override int opcode() { return RiscV.OP_IMM; } + @Override int func3() { return 5; } @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 d1435540..47712585 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,38 +1,17 @@ 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.SB; +import com.seaofnodes.simple.codegen.*; 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; // Right Shift Logical public class SrlRISC extends MachConcreteNode implements MachNode { - SrlRISC(Node srli) {super(srli);} - - // 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. + public SrlRISC(Node srli) { super(srli); } + @Override public String op() { return "shr"; } + @Override public RegMask regmap(int i) { 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) { - throw Utils.TODO(); - } - - // General form - // General form: "srl rd, rs1, rs2" + @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,7,0); } @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 "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 22e443fd..3cb7cc2d 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,15 +1,11 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.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; +import com.seaofnodes.simple.node.*; // Store memory addressing on ARM // Support imm, reg(direct), or reg+off(indirect) addressing @@ -24,33 +20,29 @@ import java.util.BitSet; // sw rs2,offset(rs1) public class StoreRISC extends MemOpRISC { - 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 + StoreRISC( StoreNode st, Node base, int off, Node val ) { super(st, base, off, val); } + @Override public String op() { return "st"+_sz; } @Override public RegMask regmap(int i) { - 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 + // 0 - ctrl + if( i==1 ) return null; // mem + if( i==2 ) return riscv.RMASK; // ptr + // 2 - index + if( i==4 ) return _sz >= '4' ? riscv.MEM_MASK : riscv.RMASK; // No byte/short FP stores + return null; // Anti-dependence } - - - // 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 encoding( Encoding enc ) { + short val = enc.reg(val()); + short ptr = enc.reg(ptr()); + int op = val >= riscv.F_OFFSET ? RiscV.OP_STOREFP : RiscV.OP_STORE; + if( val >= riscv.F_OFFSET ) val -= riscv.F_OFFSET; + enc.add4(RiscV.s_type(op, func3()&7, ptr, val == -1 ? riscv.ZERO : val, _off)); } @Override public void asm(CodeGen code, SB sb) { asm_address(code,sb).p(","); - if( val()==null ) sb.p("#").p(_imm); + if( val()==null ) sb.p("#").p("0"); 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 e6fe3012..caf751bb 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,30 +1,18 @@ 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.isa.RiscV; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; -public class SubFRISC extends MachConcreteNode implements MachNode{ +// fsub.d +public class SubFRISC extends MachConcreteNode implements MachNode { SubFRISC(Node subf) {super(subf);} - - // Register mask allowed on input i. + @Override public String op() { return "subf"; } @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; } - - // 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: "fsub.d rd = src1 - src2 + @Override public void encoding( Encoding enc ) { riscv.rf_type(enc,this,RiscV.RM.RNE,5); } @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/riscv/SubRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java index c37aea98..7ab4f904 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,29 +1,16 @@ 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.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; public class SubRISC extends MachConcreteNode implements MachNode { - SubRISC( Node sub ) { super(sub); } - - // Register mask allowed on input i. + public SubRISC( Node sub ) { super(sub); } + @Override public String op() { return "sub"; } @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.WMASK; } - - // 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 encoding( Encoding enc ) { riscv.r_type(enc,this,0,0x20); } @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/riscv/TFPRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java index 23fb6a74..1d3c5920 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,39 +1,49 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; 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 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. +public class TFPRISC extends ConstantNode implements MachNode, RIPRelSize { + TFPRISC(ConstantNode con) { super(con); } + @Override public String op() { return "ldx"; } @Override public RegMask regmap(int i) { return null; } - // General int registers @Override public RegMask outregmap() { return riscv.WMASK; } - @Override public boolean isClone() { return true; } @Override public TFPRISC copy() { return new TFPRISC(this); } + @Override public void encoding( Encoding enc ) { + enc.relo(this); + // TODO: 1 op encoding, plus a TODO if it does not fit + short dst = enc.reg(this); + TypeFunPtr tfp = (TypeFunPtr)_con; + // auipc t0,0 + int auipc = RiscV.u_type(RiscV.OP_AUIPC, dst, 0); + // addi t1,t0 + #0 + int addi = RiscV.i_type(RiscV.OP_IMM, dst, 0, dst, 0); + enc.add4(auipc); + enc.add4(addi); + } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public byte encSize(int delta) { return 8; } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + short rpc = enc.reg(this); + if( opLen==8 ) { + // ADDI sign-extends its low 12 bits, so round the AUIPC high part. + enc.patch4(opStart ,RiscV.u_type(RiscV.OP_AUIPC, rpc, (delta+0x800)>>12)); + enc.patch4(opStart+4,RiscV.i_type(RiscV.OP_IMM, rpc, 0, rpc, delta & 0xFFF)); + } else { + // should not happen as one instruction is 4 byte, and TFP arm encodes 2. + 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); - if( _con == Type.NIL ) - sb.p(reg).p(",").p(reg); - else - _con.print(sb.p(reg).p(" #")); + _con.print(sb.p(code.reg(this)).p(" #")); } + } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java new file mode 100644 index 00000000..cc47b7ed --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java @@ -0,0 +1,48 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.isa.RiscV; +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; + +// unconditional jump +public class UJmpRISC extends CFGNode implements MachNode, RIPRelSize { + @Override public String op() { return "jmp"; } + @Override public String label() { return op(); } + @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("jmp "); } + @Override public RegMask regmap(int i) {return null; } + @Override public RegMask outregmap() { return null; } + @Override public Type compute() { throw Utils.TODO(); } + @Override public Node idealize() { throw Utils.TODO(); } + @Override public void encoding( Encoding enc ) { + // Short form +/-4K: beq r0,r0,imm12 + // Long form: auipc rX,imm20/32; jal r0,[rX+imm12/32] + enc.jump(this,uctrl()); + enc.add4(RiscV.j_type(RiscV.OP_JAL, 0, 0)); + } + + // Delta is from opcode start + @Override public byte encSize(int delta) { + if( -(1L<<20) <= delta && delta < (1L<<20) ) return 4; + // 2 word encoding needs a tmp register, must teach RA + throw Utils.TODO(); + } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + if( opLen==4 ) { + enc.patch4(opStart,RiscV.j_type(RiscV.OP_JAL, 0, delta)); + } else { + throw Utils.TODO(); + } + } + + @Override public void asm(CodeGen code, SB sb) { + CFGNode target = uctrl(); + //assert target.nOuts() > 1; // Should optimize jmp to empty targets + sb.p(label(target)); + } +} 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 a2e32f06..df96d10b 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,11 +1,13 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.node.Node; public class XorIRISC extends ImmRISC { - XorIRISC( Node and, int imm) { super(and,imm); } - @Override int opcode() { throw Utils.TODO(); } + public XorIRISC( Node and, int imm) { super(and,imm); } + @Override int opcode() { return RiscV.OP_IMM; } + @Override int func3() { return 4; } @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 f4ecb430..725d2b78 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,41 +1,16 @@ 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.MachConcreteNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; public class XorRISC extends MachConcreteNode implements MachNode { - - XorRISC(Node or) { - super(or); - } - - // 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. + public XorRISC(Node or) { super(or); } + @Override public String op() { return "xor"; } + @Override public RegMask regmap(int i) { 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) { - throw Utils.TODO(); - } - - // General form - // General form: # # rd = rs1 ^ rs2 + @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,4,0); } @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/riscv/riscv.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java index 0542085c..335347e9 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,49 +1,65 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.isa.RiscV; +import static com.seaofnodes.isa.RiscV.*; 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.codegen.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; public class riscv extends Machine { + + public riscv(CodeGen code) { + if( !"SystemV".equals(code._callingConv) ) + throw new IllegalArgumentException("Unknown calling convention "+code._callingConv); + } + @Override public String name() {return "riscv";} + //@Override public int maxReg() { return MAX_REG; } // Using ABI names instead of register names - 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; + public static int ZERO = 0, RPC= 1, RSP= 2, S12= 3, S13= 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; // FP registers - 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; + 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 MAX_REG = 64; - static final int MAX_REG = 61; + public static final int F_OFFSET = 32; 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" , + "zero","rpc" , "rsp" , "s12" , "s13" , "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" + // Register-based naming hella easier to debug than "official" register + // names when single-stepping in debugger. + //"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+"]"; - } - + @Override public String[] regs() { return REGS; } // General purpose register mask: pointers and ints, not floats 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 long WR_BITS = 0b11111111111111111111111111111010L; // All the GPRs, minus ZERO and RSP 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; @@ -54,28 +70,27 @@ public class riscv extends Machine { static final RegMask SPLIT_MASK = new RegMask( WR_BITS | FP_BITS, -1L ); - // Arguments masks - 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 A0_MASK = new RegMask(A0); + public static RegMask A1_MASK = new RegMask(A1); + public static RegMask A2_MASK = new RegMask(A2); + public static RegMask A3_MASK = new RegMask(A3); + public static RegMask A4_MASK = new RegMask(A4); + public static RegMask A5_MASK = new RegMask(A5); + public static RegMask A6_MASK = new RegMask(A6); + public static RegMask A7_MASK = new RegMask(A7); - static final RegMask RPC_MASK = new RegMask(RPC); + public static final RegMask RPC_MASK = new RegMask(RPC); // Float arguments masks - 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); + public static RegMask FA0_MASK = new RegMask(FA0); + public static RegMask FA1_MASK = new RegMask(FA1); + public static RegMask FA2_MASK = new RegMask(FA2); + public static RegMask FA3_MASK = new RegMask(FA3); + public static RegMask FA4_MASK = new RegMask(FA4); + public static RegMask FA5_MASK = new RegMask(FA5); + public static RegMask FA6_MASK = new RegMask(FA6); + public static RegMask FA7_MASK = new RegMask(FA7); // Int arguments calling conv static RegMask[] CALLINMASK = new RegMask[] { @@ -90,17 +105,36 @@ public class riscv extends Machine { }; static RegMask[] XMMS = new RegMask[] { - FA0_MASK, - FA1_MASK, - FA2_MASK, - FA3_MASK, - FA4_MASK, - FA5_MASK, - FA6_MASK, - FA7_MASK + FA0_MASK, + FA1_MASK, + FA2_MASK, + FA3_MASK, + FA4_MASK, + FA5_MASK, + FA6_MASK, + FA7_MASK }; - static RegMask callInMask( TypeFunPtr tfp, int idx ) { + // Since riscv instructions are fixed we can just or them together + + public static void r_type(Encoding enc, Node n, int func3, int func7) { + short dst = enc.reg(n); + short src1 = enc.reg(n.in(1)); + short src2 = enc.reg(n.in(2)); + int body = RiscV.r_type(OP,dst,func3,src1,src2,func7); + enc.add4(body); + } + + public static void rf_type(Encoding enc, Node n, RM func3, int func7) { + short dst = (short)(enc.reg(n )-F_OFFSET); + short src1 = (short)(enc.reg(n.in(1))-F_OFFSET); + short src2 = (short)(enc.reg(n.in(2))-F_OFFSET); + int body = RiscV.r_type(OP_FP,dst,func3.ordinal(),src1,src2,func7); + enc.add4(body); + } + + @Override public RegMask callArgMask( TypeFunPtr tfp, int idx, int maxArgSlot ) { return callInMask(tfp,idx,maxArgSlot); } + static RegMask callInMask( TypeFunPtr tfp, int idx, int maxArgSlot ) { if( idx==0 ) return RPC_MASK; if( idx==1 ) return null; // Count floats in signature up to index @@ -117,112 +151,96 @@ public class riscv extends Machine { if( idx-2-fcnt < cargs.length ) return cargs[idx-2-fcnt]; } - throw Utils.TODO(); // Pass on stack slot + // Pass on stack slot(8 and higher) + if( maxArgSlot>0 ) throw Utils.TODO(); + return new RegMask(MAX_REG + 1 + (idx - 2)); } - // callee saved(riscv) - 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); + @Override public short maxArgSlot( TypeFunPtr tfp ) { + int icnt=0, fcnt=0; // Count of ints, floats + for( int i=0; i<tfp.nargs(); i++ ) { + if( tfp.arg(i) instanceof TypeFloat ) fcnt++; + else icnt++; } - } - static RegMask retMask( TypeFunPtr tfp, int i ) { - return i==2 - ? (tfp.ret() instanceof TypeFloat ? FA0_MASK : A0_MASK) - : RET_MASKS[i]; + int nstk = Math.max(icnt-8,0)+Math.max(fcnt-8,0); + return (short)nstk; } + static final long CALLER_SAVE = + (1L<<RPC) | + (1L<<A0 ) | (1L<<A1 ) | (1L<<A2 ) | (1L<<A3 ) | (1L<<A4 ) | (1L<<A5 ) | (1L<<A6 ) | (1L<<A7 ) | + (1L<<T0 ) | (1L<<T1 ) | (1L<<T2 ) | (1L<<T3 ) | (1L<<T4 ) | (1L<<T5 ) | (1L<<T6 ) | + (1L<<FA0) | (1L<<FA1) | (1L<<FA2) | (1L<<FA3) | (1L<<FA4) | (1L<<FA5) | (1L<<FA6) | (1L<<FA7) | + (1L<<F0 ) | (1L<<F1 ) | (1L<<F2 ) | (1L<<F3 ) | (1L<<F4 ) | (1L<<F5 ) | (1L<<F6 ) | (1L<<F7 ) | + (1L<<FT8) | (1L<<FT9) | (1L<<FT10)| (1L<<FT11); + + @Override public long callerSave() { return CALLER_SAVE; } + @Override public long neverSave() { return (1L<<RSP) | (1L<<ZERO); } + @Override public RegMask retMask( TypeFunPtr tfp ) { + return tfp.ret() instanceof TypeFloat ? FA0_MASK : A0_MASK; + } + @Override public int rpc() { return RPC; } // Return a MachNode unconditional branch - @Override public CFGNode jump() { - throw Utils.TODO(); - } + @Override public CFGNode jump() { return new UJmpRISC(); } // Create a split op; any register to any register, including stack slots @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 + public static boolean imm12(TypeInteger ti) { + // 52 = 64-12 return ti.isConstant() && ((ti.value()<<52)>>52) == ti.value(); } - + // True if HIGH 20-bit signed immediate, with all zeros low. + public static boolean imm20Exact(TypeInteger ti) { + // shift left 32 to clear out the upper 32 bits. + // shift right SIGNED to sign-extend upper 32 bits; then shift 12 more to clear out lower 12 bits. + // shift left 12 to re-center the bits. + return ti.isConstant() && (((ti.value()<<32)>>>44)<<12) == 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 CallNode call -> call(call); - case CastNode cast -> new CastRISC(cast); + 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 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 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 MinusNode neg -> new NegRISC(neg); + case MulFNode mulf -> new MulFRISC(mulf); + case MulNode mul -> new MulRISC(mul); + case NewNode nnn -> nnn(nnn); + case NotNode not -> new NotRISC(not); + 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 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); @@ -235,36 +253,81 @@ public class riscv extends Machine { } private Node add(AddNode add) { - if( add.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && imm12(ti) ) - return new AddIRISC(add, (int)ti.value()); + if( add.in(2) instanceof ConstantNode off2 && off2._con instanceof TypeInteger ti && imm12(ti) ) + return new AddIRISC(add, (int)ti.value(),true); return new AddRISC(add); } private Node and(AndNode and) { - if( and.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) ) - return new AndIRISC(and, (int)ti.value()); + if( and.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti ) { + if( imm12(ti) ) + return new AndIRISC(and, (int)ti.value()); + // Could be any size low bit mask + if( ti.value() == 0xFFFFFFFFL ) + return new SrlIRISC(new SllIRISC(and,32),32,false); + } return new AndRISC(and); } 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); + return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp + ? new CallRISC(call, tfp) + : new CallRRISC(call); + } + private Node nnn(NewNode nnn) { + // TODO: pass in the TFP for alloc + return new NewRISC(nnn); } 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 + // Float variant directly implemented in hardware if( bool.isFloat() ) return new SetFRISC(bool); - return new SetRISC(bool); + + // Only < and <u are implemented in hardware. + // x < y - as-is + // x <= y - flip and negate; !(y < x); `slt tmp=y,x; xori dst=tmp,#1` + // x == y - sub and vs0 == `sub tmp=x-y; sltu dst=tmp,#1` + + // x > y - swap; y < x + // x >= y - swap and negate; !(x < y); `slt tmp=y,x;` then NOT. + // x != y - sub and vs0 == `sub tmp=x-y; sltu dst=tmp,#1` then NOT. + + // The ">", ">=" and "!=" in Simple include a NotNode, which can be + // implemented with a XOR. If one of the above is followed by a NOT + // we can remove the double XOR in the encodings. + + return switch( bool.op() ) { + case "<" -> bool.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) + ? new SetIRISC(bool, (int)ti.value(),false) + : new SetRISC(bool, false); + // x <= y - flip and negate; !(y < x); `slt tmp=y,x; xori dst=tmp,#1` + case "<=" -> new XorIRISC(new SetRISC(bool.swap12(), false),1); + // x == y - sub and vs0 == `sub tmp=x-y; sltu dst=tmp,#1` + case "==" -> new SetIRISC(new SubRISC(bool),1,true,false); + default -> throw Utils.TODO(); + }; } 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 TypeInteger ti -> { + if( imm12(ti) ) yield new IntRISC(con); + long x = ti.value(); + if( imm20Exact(ti) ) yield new LUI((int)x); + if( (x<<32)>>32 == x ) { // Signed lower 32-bit immediate + // Here, the low 12 bits get sign-extended, which means if + // bit11 is set, the value is negative and lowers the LUI + // value. Add a bit 12 to compensate + if( ((x>>11)&1)==1 ) x += 0x1000; + yield new AddIRISC(new LUI((int)(x & ~0xFFF)), (int)(x & 0xFFF),false); + } + // Need more complex sequence for larger constants... or a load + // from a constant pool, which does not need an extra register + throw Utils.TODO(); + } + // Load from constant pool case TypeFloat tf -> new FltRISC(con); case TypeFunPtr tfp -> new TFPRISC(con); case TypeMemPtr tmp -> throw Utils.TODO(); @@ -275,45 +338,50 @@ public class riscv extends Machine { } private Node jmp( IfNode iff ) { - if( iff.in(1) instanceof BoolNode bool && !bool.isFloat() ) - return new BranchRISC(iff, bool.op(), bool.in(1), bool.in(2)); + if( iff.in(1) instanceof BoolNode bool && !bool.isFloat() ) { + // if less than or equal switch inputs + String bop = bool.op(); + if( bop.equals("<=") || bop.equals(">") ) + return new BranchRISC(iff, IfNode.swap(bop), bool.in(2), bool.in(1)); + return new BranchRISC(iff, bop, bool.in(1), bool.in(2)); + } // Vs zero - return new BranchRISC(iff, "==", iff.in(1), null); + return new BranchRISC(iff, iff.in(1)==null ? "==" : "!=", iff.in(1), null); } private Node or(OrNode or) { - if( or.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(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 && imm12(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 && imm12(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 && imm12(ti) ) - return new SrlIRISC(shr, (int)ti.value()); + if( shr.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti)) + return new SrlIRISC(shr, (int)ti.value(),true); return new SrlRISC(shr); } private Node sll(ShlNode sll) { - if( sll.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(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 && imm12(ti) - ? new AddIRISC(sub, (int)(-ti.value())) + ? new AddIRISC(sub, (int)(-ti.value()),true) : new SubRISC(sub); } @@ -327,28 +395,20 @@ public class riscv extends Machine { } private Node ld(LoadNode ld) { - return new LoadRISC(address(ld),ld.ptr(),idx,off); + return new LoadRISC(ld,address(ld),off); } private Node st(StoreNode 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); + Node xval = st.val() instanceof ConstantNode con && con._con == TypeInteger.ZERO ? null : st.val(); + return new StoreRISC(st,address(st), off, 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 static int off; - private static Node idx; - private <N extends MemOpNode> N address( N mop ) { + private Node address( MemOpNode 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); @@ -356,9 +416,10 @@ public class riscv extends Machine { if( mop.off() instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) ) { off = (int)ti.value(); } else { - idx = mop.off(); + base = new AddRISC(base,mop.off()); + base._type = mop.ptr()._type; } - return mop; + return base; } } 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 ffc6966e..76a2d3f7 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,16 +1,16 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; 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.codegen.*; import com.seaofnodes.simple.node.*; public class AddFMemX86 extends MemOpX86 { AddFMemX86( AddFNode add, LoadNode ld , Node base, Node idx, int off, int scale, Node val ) { super(add,ld, base, idx, off, scale, 0, val ); } - + @Override public String op() { return "addf"+_sz; } @Override public RegMask regmap(int i) { if( i==1 ) return null; // Memory if( i==2 ) return x86_64_v2.RMASK; // base @@ -18,10 +18,11 @@ public class AddFMemX86 extends MemOpX86 { if( i==4 ) return x86_64_v2.XMASK; // value throw Utils.TODO(); } - - // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return x86_64_v2.XMASK; } - + @Override public int twoAddress() { return 4; } + @Override public void encoding( Encoding enc ) { + X86.memory(enc,0xF2,0x0F58,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(ptr()),enc.reg(idx()),_off,_scale); + } // General form: "add dst = src + [base + idx<<2 + 12]" @Override public void asm(CodeGen code, SB sb) { @@ -29,5 +30,4 @@ public class AddFMemX86 extends MemOpX86 { sb.p(val()==null ? "#"+_imm : code.reg(val())).p(" + "); asm_address(code,sb); } - @Override public String op() { return "addf"+_sz; } } 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 d4ea9162..b3266cb0 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,33 +1,24 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; public class AddFX86 extends MachConcreteNode implements MachNode { AddFX86( Node addf ) { super(addf); } - - // Register mask allowed on input i. + @Override public String op() { return "addf"; } @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; } - // Register mask allowed as a result. 0 for no register. @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) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + X86.sse(enc,0xF2,0x0F58,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET); } // General form: "addf 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))); + sb.p(code.reg(this)).p(" += ").p(code.reg(in(2))); } - - @Override public String op() { return "addf"; } } 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 c7f92ff1..569be754 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,41 +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 AddIX86 extends MachConcreteNode implements MachNode { - final TypeInteger _ti; - AddIX86( 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 x86_64_v2.WMASK; } - // 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; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form: "addi 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.node.Node; +public class AddIX86 extends ImmX86 { + AddIX86( Node add, int imm ) { super(add,imm); } @Override public String op() { - return _ti.value() == 1 ? "inc" : (_ti.value() == -1 ? "dec" : "addi"); + return _imm == 1 ? "inc" : (_imm == -1 ? "dec" : "addi"); } + @Override public String glabel() { return "+"; } + @Override int opcode() { return 0x81; } + @Override int mod() { return 0; } } 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 3d4fac41..d2111d07 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,26 +1,24 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; public class AddMemX86 extends MemOpX86 { AddMemX86( AddNode add, LoadNode ld , Node base, Node idx, int off, int scale, int imm, Node val ) { super(add,ld, base, idx, off, scale, imm, val ); } - @Override public String op() { return "add"+_sz; } - - // 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; } + @Override public void encoding( Encoding enc ) { + X86.memory(enc,0,0x03,true,enc.reg(this),enc.reg(ptr()),enc.reg(idx()),_off,_scale); + } - // General form: "add dst = src + [base + idx<<2 + 12]" + // General form: "add dst += [base + idx<<2 + 12]" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = "); - sb.p(val()==null ? "#"+_imm : code.reg(val())).p(" + "); + sb.p(code.reg(this)).p(" += "); asm_address(code,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 9553d499..84543e8f 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,33 +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.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; +import com.seaofnodes.simple.node.Node; -public class AddX86 extends MachConcreteNode { +public class AddX86 extends RegX86 { AddX86( Node add ) { super(add); } - - // Register mask allowed on input i. - @Override public RegMask regmap(int i) { assert i==1 || i==2; return 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; } - // 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) { - throw Utils.TODO(); - } - - // General form: "add 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 "add"; } + @Override public String glabel() { return "+"; } + @Override int opcode() { return 0x03; } + @Override public boolean commutes() { return true; } } 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 a7f1801e..3c1b567c 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,44 +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.*; -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 AndIX86 extends MachConcreteNode implements MachNode { - final TypeInteger _ti; - AndIX86(Node 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; return x86_64_v2.WMASK; } - // 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; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form - // General form: "andi 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.node.Node; +public class AndIX86 extends ImmX86 { + AndIX86( Node add, int imm ) { super(add,imm); } @Override public String op() { return "andi"; } + @Override public String glabel() { return "&"; } + @Override int opcode() { return 0x81; } + @Override int mod() { return 4; } } 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 d7fb3613..dedbce45 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,34 +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.*; -import java.io.ByteArrayOutputStream; - -public class AndX86 extends MachConcreteNode implements MachNode{ - AndX86(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; 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: "and 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)));; - } +import com.seaofnodes.simple.node.Node; +public class AndX86 extends RegX86 { + AndX86( Node add ) { super(add); } @Override public String op() { return "and"; } + @Override public String glabel() { return "&"; } + @Override int opcode() { return 0x23; } + @Override public boolean commutes() { return true; } } 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 b9cc5659..ce8b993f 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 @@ -1,35 +1,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.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 CallEndX86 extends CallEndNode implements MachNode { - final TypeFunPtr _tfp; - CallEndX86( 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 ? 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) { - throw Utils.TODO(); - } - - @Override public void asm(CodeGen code, SB sb) { } - - @Override public String op() { return "cend"; } - + CallEndX86( CallEndNode cend ) { super(cend); } } 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 ac35d17b..15b83bfc 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,32 +1,23 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; 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.codegen.*; import com.seaofnodes.simple.node.CallNode; import com.seaofnodes.simple.node.MachNode; -import java.io.ByteArrayOutputStream; public class CallRX86 extends CallNode implements MachNode { CallRX86( CallNode call ) { super(call); } - + @Override public String op() { return "callr"; } @Override public String label() { return op(); } @Override public RegMask regmap(int i) { return i==_inputs._len ? x86_64_v2.WMASK // Function call target - : x86_64_v2.callInMask(tfp(),i); // Normal argument + : x86_64_v2.callInMask(tfp(),i,fun()._maxArgSlot); // Normal argument } @Override public RegMask outregmap() { return null; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - bytes.write(0xe8); - int beforeSize = bytes.size(); - // address - //TODO: relocs - bytes.write(0x00); - return bytes.size() - beforeSize; + @Override public void encoding( Encoding enc ) { + X86.callRegister(enc,enc.reg(fptr())); } @Override public void asm(CodeGen code, SB sb) { @@ -36,5 +27,4 @@ public class CallRX86 extends CallNode implements MachNode { 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 e2f68c1f..c97037e2 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,15 +1,12 @@ 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.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.CallNode; import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.type.TypeFunPtr; -import java.io.ByteArrayOutputStream; -public class CallX86 extends CallNode implements MachNode { +public class CallX86 extends CallNode implements MachNode, RIPRelSize { final TypeFunPtr _tfp; final String _name; CallX86( CallNode call, TypeFunPtr tfp ) { @@ -19,26 +16,24 @@ public class CallX86 extends CallNode implements MachNode { _tfp = tfp; _name = CodeGen.CODE.link(tfp)._name; } - + @Override public String op() { return "call"; } @Override public String label() { return op(); } - @Override public RegMask regmap(int i) { - return x86_64_v2.callInMask(_tfp,i); // Normal argument - } + @Override public String name() { return _name; } + @Override public TypeFunPtr tfp() { return _tfp; } + @Override public RegMask regmap(int i) { return x86_64_v2.callInMask(_tfp,i,fun()._maxArgSlot); } @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; } + @Override public void encoding( Encoding enc ) { + enc.relo(this).add1(0xe8).add4(0); + } + + // Delta is from opcode start, but X86 measures from the end of the 5-byte encoding + @Override public byte encSize(int delta) { return 5; } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - // linker will fix this up - bytes.write(0xe8); - int beforeSize = bytes.size(); - // TODO: relocs - // address - bytes.write(0x00); - return bytes.size() - beforeSize; + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + enc.patch4(opStart+1, delta-5); } @Override public void asm(CodeGen code, SB sb) { @@ -47,7 +42,4 @@ public class CallX86 extends CallNode implements MachNode { 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 index b620b89c..9537cfb3 100644 --- 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 @@ -1,18 +1,16 @@ 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.codegen.*; 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 String op() { return label(); } @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 encoding( Encoding enc ) { } @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 index e9b0dacd..5a0c7a37 100644 --- 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 @@ -1,21 +1,18 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; 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 String op() { return "cmpf"; } + @Override public RegMask regmap(int i) { 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(); + @Override public void encoding( Encoding enc ) { + X86.sse(enc,0x66,0x0F2E,false,enc.reg(in(1))-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET); } // General form: "cmp src1,src2" @@ -24,6 +21,4 @@ public class CmpFX86 extends MachConcreteNode implements MachNode { 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 fcba397d..2811a640 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,53 +1,38 @@ 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.isa.X86; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.*; 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. public class CmpIX86 extends MachConcreteNode implements MachNode { final int _imm; - final String _bop; - CmpIX86( 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(); + CmpIX86( Node cmp, int imm, boolean swap ) { + super(cmp); + _inputs.del(swap ? 1 : 2); + _imm = imm; } - // 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 = cmp._bop; + super((Node)null); + addDef(null); + addDef(cmp.in(1)); _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 String op() { return _imm==0 ? "test" : "cmp"; } + @Override public RegMask regmap(int i) { 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) { - throw Utils.TODO(); + @Override public final void encoding( Encoding enc ) { + X86.imm(enc,0x81,7,enc.reg(in(1)),_imm); } - @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/x86_64_v2/CmpMemX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java index f9ace702..bf8f1631 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,33 +1,44 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; public class CmpMemX86 extends MemOpX86 { - final boolean _invert; // Op switched LHS, RHS - CmpMemX86( BoolNode bool, LoadNode ld , Node base, Node idx, int off, int scale, int imm, Node val, boolean invert ) { + final boolean _swap; // Op switched LHS, RHS + CmpMemX86( BoolNode bool, LoadNode ld, Node base, Node idx, int off, int scale, int imm, Node val, boolean swap ) { super(bool,ld, base, idx, off, scale, imm, val ); - _invert = invert; + _swap = swap; + assert !_swap || val() == null || bool.op()=="==" || bool.op()=="!="; // Cannot swap with immediate } - - // Register mask allowed as a result. 0 for no register. + @Override public String op() { return ((val()==null && _imm==0) ? "test" : "cmp") + _sz; } @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; } + @Override public void encoding( Encoding enc ) { + short ptr=enc.reg(ptr()), idx=enc.reg(idx()), src=enc.reg(val()); + if( src==-1 ) { + assert !_swap; + X86.cmpImm(enc,_declaredType.log_size(),_imm,ptr,idx,_off,_scale); + } else encVal(enc,_declaredType,ptr,idx,src,_off,_scale,_swap); + } + + static void encVal(Encoding enc, Type decl, short ptr, short idx, short src, int off, int scale, boolean _swap) { + assert !(decl instanceof TypeFloat); // Selected separately as CmpFX86. + X86.cmpMem(enc,decl.log_size(),src,ptr,idx,off,scale,_swap); + } // General form: "cmp dst = src, [base + idx<<2 + 12]" @Override public void asm(CodeGen code, SB sb) { String dst = code.reg(this); - if( dst!="FLAGS" ) sb.p(dst).p(" = "); - if( _invert ) asm_address(code,sb).p(","); + if( dst!="flags" ) sb.p(dst).p(" = "); + if( _swap ) asm_address(code,sb).p(","); if( val()==null ) { if( _imm!=0 ) sb.p("#").p(_imm); } else { sb.p(code.reg(val())); } - if( !_invert ) asm_address(code,sb.p(", ")); + if( !_swap ) asm_address(code,sb.p(", ")); } - - @Override public String op() { return ((val()==null && _imm==0) ? "test" : "cmp") + _sz; } - } 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 41ce33d2..e3cbe75f 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,29 +1,22 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; public class CmpX86 extends MachConcreteNode implements MachNode { - CmpX86( Node cmp ) { super(cmp); } - - @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.RMASK; } + CmpX86( Node add ) { super(add); } + @Override public String op() { return "cmp"; } + @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } @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(); + @Override public void encoding( Encoding enc ) { + X86.reg(enc,0x3B,enc.reg(in(1)),enc.reg(in(2))); } - - // 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(" = "); + if( dst!="flags" ) sb.p(dst).p(" = "); sb.p(code.reg(in(1))).p(", ").p(code.reg(in(2))); } - - @Override public String op() { return "cmp"; } } 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 f74b7348..962ec44e 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,35 +1,24 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; public class DivFX86 extends MachConcreteNode implements MachNode { DivFX86( Node divf) { super(divf); } - - // Register mask allowed on input i. - @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; } - // Register mask allowed as a result. 0 for no register. - @Override public RegMask outregmap() { return x86_64_v2.XMASK; } - // Output is same register as input#1 + @Override public String op() { return "divf"; } + @Override public RegMask regmap(int i) { return x86_64_v2.XMASK; } + @Override public RegMask outregmap() { return x86_64_v2.XMASK; } @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(); + @Override public void encoding( Encoding enc ) { + X86.sse(enc,0xF2,0x0F5E,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET); } // General form: "divf 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() { - // 8 bytes = 64 bits - return "divf"; + sb.p(code.reg(this)).p(" /= ").p(code.reg(in(2))); } } 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 54372629..e61f576c 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,35 +1,25 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; public class DivX86 extends MachConcreteNode implements MachNode { DivX86( Node div ) { super(div); } - - // Register mask allowed on input i. + @Override public String op() { return "div"; } @Override public RegMask regmap(int i) { 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.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) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + X86.divide(enc,enc.reg(in(2))); } - // 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"; } @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 49d666b7..44bcbe7b 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,30 +1,22 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; public class F8ToIX86 extends MachConcreteNode implements MachNode { F8ToIX86(Node f8toi ) { super(f8toi); } - - // Register mask allowed on input i. + @Override public String op() { return "cvti"; } @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 - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + X86.floatToInteger(enc,enc.reg(this),enc.reg(in(1))-x86_64_v2.XMM_OFFSET); } - @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1))); } - - @Override public String op() { return "f8toi"; } } 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 47f7149d..b17ea305 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,35 +1,36 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; 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; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; -public class FltX86 extends ConstantNode implements MachNode { +public class FltX86 extends ConstantNode implements MachNode, RIPRelSize{ + private byte _encSize; FltX86(ConstantNode con ) { super(con); } - @Override public String op() { return "fld"; } - - // Register mask allowed on input i. 0 for no register. + @Override public String op() { return "ld8"; } @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); } + @Override public Node copy() { return new FltX86(this); } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + _encSize=X86.rip(enc,0xF2,0x0F10,false,enc.reg(this)-x86_64_v2.XMM_OFFSET); + enc.largeConstant(this,_con,_encSize-4,2/*ELF PC32*/); } - // 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(" #")); + // Delta is from opcode start + @Override public byte encSize(int delta) { + // TODO: 1-byte RIP offset? + return (byte)_encSize; + } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + enc.patch4(opStart+opLen-4,delta-opLen); } + @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/x86_64_v2/FunX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java index 7346ae05..24c2a78b 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,22 +1,31 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.SB; import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.Encoding; import com.seaofnodes.simple.node.FunNode; import com.seaofnodes.simple.node.MachNode; -import java.io.ByteArrayOutputStream; public class FunX86 extends FunNode implements MachNode { FunX86( FunNode fun ) { super(fun); } + @Override public void postSelect(CodeGen code) { + super.postSelect(code); + // One more for RPC pre-pushed; X86 special + _maxArgSlot++; + } - // 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; } + @Override public void computeFrameAdjust(CodeGen code, int maxReg) { + super.computeFrameAdjust(code,maxReg); + // Alignment, but off by RPC + if( _hasCalls ) // If non-leaf, pad to 16b + _frameAdjust = ((_frameAdjust+8+8) & -16)-8; + } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - return 0; + @Override public void encoding( Encoding enc ) { + int sz = _frameAdjust; + if( sz == 0 ) return; // Skip if no frame adjust + X86.imm(enc,0x81,5,x86_64_v2.RSP,sz); } + } 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 d99c92a8..4a17b92b 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,30 +1,21 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; public class I2f8X86 extends MachConcreteNode implements MachNode { I2f8X86(Node i2f8 ) { super(i2f8); } - - // Register mask allowed on input i. + @Override public String op() { return "cvtf"; } @Override public RegMask regmap(int i) { 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.XMASK; } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + X86.sse(enc,0xF2,0x0F2A,true,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(1))); } - // 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))); } - - @Override public String op() { return "i2f8"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java new file mode 100644 index 00000000..b87e7f47 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java @@ -0,0 +1,31 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; + +public abstract class ImmX86 extends MachConcreteNode implements MachNode { + final int _imm; + ImmX86( Node add, int imm ) { + super(add); + _inputs.pop(); // Pop constant input off + _imm = imm; + } + @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + @Override public int twoAddress() { return 1; } + + abstract int opcode(); + abstract int mod(); + + @Override public final void encoding( Encoding enc ) { + X86.imm(enc,opcode(),mod(),enc.reg(this),_imm); + } + + // General form: "addi dst += #imm" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm); + } +} 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 fcd39e3f..29513d98 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,40 +1,29 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; 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.codegen.*; +import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; - // Integer constants public class IntX86 extends ConstantNode implements MachNode { 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. + @Override public boolean isClone() { return true; } + @Override public Node copy() { return new IntX86(this); } @Override public RegMask regmap(int i) { return null; } - // 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(); + @Override public void encoding( Encoding enc ) { + X86.constant(enc,enc.reg(this),_con==Type.NIL ? 0 : ((TypeInteger)_con).value()); } // Human-readable form appended to the SB. Things like the encoding, 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 173f9ee7..92156c58 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,48 +1,64 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; // Jump on flags, uses flags -public class JmpX86 extends IfNode implements MachNode { - final String _bop; +public class JmpX86 extends IfNode implements MachNode, RIPRelSize { + String _bop; JmpX86( IfNode iff, String bop ) { super(iff); _bop = bop; } - + @Override public String op() { return "j"+_bop; } @Override public String label() { return op(); } - - @Override public void postSelect() { + @Override public void postSelect(CodeGen code) { Node set = in(1); + if( set==null ) return; // Never-node cutout 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 || cmp instanceof CmpFX86) ) - _inputs.set(1,cmp); - else + if( set instanceof SetX86 setx && (cmp instanceof CmpX86 || cmp instanceof CmpIX86 || cmp instanceof CmpMemX86 || cmp instanceof CmpFX86) ) { + _inputs.set( 1, cmp ); + _bop = setx._bop; + } else throw Utils.TODO(); } @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.FLAGS_MASK; } @Override public RegMask outregmap() { return null; } + @Override public void negate() { _bop = negate(_bop); } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + if( in(1)==null && _bop=="!=" ) return; + X86.branch(enc,in(1)==null ? -1 : x86_64_v2.jumpop(_bop)&15); + enc.jump(this,cproj(0)); } - @Override public void asm(CodeGen code, SB sb) { - String src = code.reg(in(1)); - if( src!="flags" ) sb.p(src); + // Delta is from opcode start, but X86 measures from the end of the 2-byte encoding + @Override public byte encSize(int delta) { + if( in(1)==null && _bop=="!=" ) return 0; + return X86.branchSize(delta,in(1)!=null); } - @Override public String op() { return "j"+_bop; } + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + assert !(in(1)==null && _bop=="!="); + X86.patchBranch(enc.bits(),opStart,opLen,delta,in(1)==null ? -1 : x86_64_v2.jumpop(_bop)&15); + } - @Override public String comment() { - return "L"+cproj(1)._nid+", L"+cproj(0)._nid; + @Override public void asm(CodeGen code, SB sb) { + if( in(1)!=null ) { // Never-node + String src = code.reg(in(1)); + if( src!="flags" ) sb.p(src).p(" "); + } else if( _bop=="!=" ) { + sb.p("never"); + return; + } + CFGNode prj = cproj(0).uctrlSkipEmpty(); // 0 is True is jump target + if( !prj.blockHead() ) prj = prj.cfg0(); + sb.p(label(prj)); } + + @Override public String comment() { return "L"+cproj(1)._nid; } } 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 229c0151..e20fd1ec 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,18 +1,16 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; public class LeaX86 extends MachConcreteNode implements MachNode { final int _scale; - final long _offset; - LeaX86( Node add, Node base, Node idx, int scale, long offset ) { + final int _offset; + LeaX86( Node add, Node base, Node idx, int scale, int offset ) { super(add); - assert scale==1 || scale==2 || scale==4 || scale==8; + assert scale==0 || scale==1 || scale==2 || scale==3; _inputs.pop(); _inputs.pop(); _inputs.push(base); @@ -21,14 +19,12 @@ public class LeaX86 extends MachConcreteNode implements MachNode { _offset = offset; } - // Register mask allowed on input i. + @Override public String op() { return "lea"; } @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.RMASK; } - // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + X86.lea(enc,enc.reg(this),enc.reg(in(1)),enc.reg(in(2)),_offset,_scale); } // General form: "lea dst = base + 4*idx + 12" @@ -36,9 +32,7 @@ public class LeaX86 extends MachConcreteNode implements MachNode { sb.p(code.reg(this)).p(" = "); if( in(1) != null ) sb.p(code.reg(in(1))).p(" + "); - sb.p(code.reg(in(2))).p("*").p(_scale); + sb.p(code.reg(in(2))).p("<<").p(_scale); if( _offset!=0 ) sb.p(" + #").p(_offset); } - - @Override public String op() { return "lea"; } } 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 893772a6..c5329e1d 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,20 +1,42 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.LoadNode; import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.type.TypeMemPtr; public class LoadX86 extends MemOpX86 { LoadX86( LoadNode ld, Node base, Node idx, int off, int scale ) { super(ld,ld, base, idx, off, scale, 0); } - @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.MEM_MASK; } + @Override public void encoding( Encoding enc ) { + // REX.W + 8B /r MOV r64, r/m64 + // Zero extension for u8, u16 and u32 but sign extension i8, i16, i32 + // Use movsx and movzx + short dst = enc.reg(this ); + short ptr = enc.reg(ptr()); + short idx = enc.reg(idx()); + enc(enc, _declaredType, dst, ptr, idx, _off, _scale); + } + + static void enc( Encoding enc, Type decl, short dst, short ptr, short idx, int off, int scale ) { + // The chapter chooses the register bank and extension from its own type facts. + boolean xmm=dst>=x86_64_v2.XMM_OFFSET; + if( xmm && decl.log_size()==3 ) decl=TypeFloat.F64; + else if( !xmm && decl instanceof TypeFloat ) + decl=decl==TypeFloat.F32 ? TypeInteger.U32 : TypeInteger.BOT; + boolean signed=decl.isa(TypeInteger.I8) || decl==TypeInteger.I16 || decl==TypeInteger.I32; + X86.load(enc,decl.log_size(),signed,xmm, + xmm ? dst-x86_64_v2.XMM_OFFSET : dst,ptr,idx,off,scale); + } // 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 6d39a2b4..6c7ecbbc 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,19 +1,22 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; public class MemAddX86 extends MemOpX86 { MemAddX86( StoreNode st, Node base, Node idx, int off, int scale, int imm, Node val ) { super(st, st, base, idx, off, scale, imm, val ); } - + @Override public String op() { + return (_imm == 1 ? "inc" : (_imm == -1 ? "dec" : "add")) + _sz; + } // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return null; } - - + @Override public void encoding( Encoding enc ) { + X86.addMem(enc,enc.reg(val()),_imm,enc.reg(ptr()),enc.reg(idx()),_off,_scale); + } // General form: "add [base + idx<<2 + 12] += src" @Override public void asm(CodeGen code, SB sb) { asm_address(code,sb); @@ -21,8 +24,4 @@ public class MemAddX86 extends MemOpX86 { if( _imm != 1 && _imm != -1 ) sb.p(" += #").p(_imm); } else sb.p(" += ").p(code.reg(val())); } - - @Override public String op() { - return (_imm == 1 ? "inc" : (_imm == -1 ? "dec" : "add")) + _sz; - } } 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 e74de677..fa3cdd13 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,11 +3,10 @@ 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.codegen.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; -import java.io.ByteArrayOutputStream; + import java.lang.StringBuilder; // Generic X86 memory operand base. @@ -16,7 +15,7 @@ import java.lang.StringBuilder; // mem - memory dependence edge // base - Basic object pointer // idx/null - Scaled index offset, or null if none -// val/null - Value to store, as part of a op-to-mem or op-from-mem. Null for loads, or if an immediate is being used +// val/null - Value to store, as part of an op-to-mem or op-from-mem. Null for loads, or if an immediate is being used // Constants: // offset - offset added to base. Can be zero. // scale - scale on index; only 0,1,2,4,8 allowed, 0 is only when index is null @@ -25,13 +24,13 @@ public abstract class MemOpX86 extends MemOpNode implements MachNode { final int _off; // Limit 32 bits final int _scale; // Limit 0,1,2,3 final int _imm; // Limit 32 bits - final char _sz = (char)('0'+(1<<_declaredType.log_size())); + final char _sz; // Handy print name MemOpX86( Node op, MemOpNode mop, Node base, Node idx, int off, int scale, int imm ) { super(op,mop); assert base._type instanceof TypeMemPtr && !(base instanceof AddNode); assert (idx==null && scale==0) || (idx!=null && 0<= scale && scale<=3); - // Copy memory parts from eg the LoadNode over the opcode, e.g. an Add + // Copy memory parts from e.g. the LoadNode over the opcode, e.g. an Add if( op != mop ) { _inputs.set(0,mop.in(0)); // Control from mem op _inputs.set(1,mop.in(1)); // Memory from mem op @@ -43,6 +42,7 @@ public abstract class MemOpX86 extends MemOpNode implements MachNode { _off = off; _scale = scale; _imm = imm; + _sz = (char)('0'+(1<<_declaredType.log_size())); } // Store-based flavors have a value edge @@ -58,8 +58,6 @@ public abstract class MemOpX86 extends MemOpNode implements MachNode { 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(); } @@ -69,20 +67,19 @@ public abstract class MemOpX86 extends MemOpNode implements MachNode { 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 + if( i==4 ) return _sz >= '4' + ? x86_64_v2.MEM_MASK // value in GPR or XMM + : x86_64_v2.RMASK; // Bytes and shorts in GPR only + return null; // Anti-dependence } - @Override public int encoding(ByteArrayOutputStream bytes) { throw Utils.TODO(); } - - // "[base + idx<<2 + 12]" SB asm_address(CodeGen code, SB sb) { sb.p("[").p(code.reg(ptr())); if( idx() != null ) { sb.p("+").p(code.reg(idx())); if( _scale != 0 ) - sb.p("<<").p(_scale); + sb.p("*").p((1<<_scale)); } if( _off != 0 ) sb.p("+").p(_off); 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 9e996130..3371a8ba 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,34 +1,24 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeFloat; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; public class MulFX86 extends MachConcreteNode implements MachNode { MulFX86( Node mulf) { super(mulf); } - - // Register mask allowed on input i. + @Override public String op() { return "mulf"; } @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; } - // Register mask allowed as a result. 0 for no register. @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) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + X86.sse(enc,0xF2,0x0F59,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET); } // General form: "mulf 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))); + sb.p(code.reg(this)).p(" *= ").p(code.reg(in(2))); } - - @Override public String op() { return "mulf"; } } 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 b41a5720..2c1ce261 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,41 +1,30 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; 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; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.codegen.*; -public class MulIX86 extends MachConcreteNode implements MachNode { - final TypeInteger _ti; - MulIX86(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) { - // assert i==1; - return x86_64_v2.WMASK; } - // Register mask allowed as a result. 0 for no register. +public class MulIX86 extends MachConcreteNode implements MachNode { + final int _imm; + MulIX86( Node mul, int imm ) { + super(mul); + _inputs.pop(); // Pop the constant input + _imm = imm; // Record the constant + } + @Override public String op() { return "muli"; } + @Override public String glabel() { return "*"; } + @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - - - // Output is same register as input#1 + // Retain Chapter 21's allocation contract: output is also 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(); + // IMUL encodes a destination register, unlike ImmX86's opcode extension. + @Override public final void encoding( Encoding enc ) { + X86.imul(enc,enc.reg(this),enc.reg(in(1)),_imm); } - // General form - // General form: "muli dst * #imm" + // General form: "muli dst = src * #imm" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * #"); - _ti.print(sb); + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * #").p(_imm); } - - @Override public String op() { return "muli"; } } 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 d880208e..cdc67ea8 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,33 +1,24 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; 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. + @Override public String op() { return "mul"; } @Override public RegMask regmap(int i) { assert i==1 || i==2; return 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; } - // 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) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + X86.reg(enc,0x0FAF,enc.reg(this),enc.reg(in(2))); } // General form: "mul 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 "mul"; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java new file mode 100644 index 00000000..3d3798cd --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java @@ -0,0 +1,19 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; + +public class NegX86 extends MachConcreteNode implements MachNode { + NegX86(MinusNode not) { super(not); } + @Override public String op() { return "neg"; } + @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } + @Override public RegMask outregmap() { return x86_64_v2.RMASK; } + @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; } + + @Override public void encoding( Encoding enc ) { + X86.unary(enc,0xF7,3,enc.reg(this)); + } + @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); } +} 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 f4803b2e..877ec224 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,29 +1,23 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import java.io.ByteArrayOutputStream; +import com.seaofnodes.simple.type.TypeFunPtr; public class NewX86 extends NewNode implements MachNode { // A pre-zeroed chunk of memory. 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) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + enc.external(this,"calloc"); + X86.constant(enc,_arg2Reg,1); + X86.call(enc); } - - // General form: "alloc #bytes" + // General form: "alloc #bytes PC" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(size())); + sb.p("ldi rcx = #1\n"); + sb.p("call #calloc"); } - @Override public String op() { return "alloc"; } } 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 index a4b82de8..46227dd4 100644 --- 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 @@ -1,26 +1,21 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; 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.codegen.*; import com.seaofnodes.simple.node.*; -import java.io.ByteArrayOutputStream; public class NotX86 extends MachConcreteNode implements MachNode { - NotX86(NotNode not) {super(not);} + NotX86(NotNode not) { super(not); } + @Override public String op() { return "not"; } @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } @Override public RegMask outregmap() { return x86_64_v2.RMASK; } + @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; } - @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 encoding( Encoding enc ) { + assert !(in(1) instanceof NotNode); // Cleared out by peeps + assert !(in(1) instanceof BoolNode); + X86.not(enc,enc.reg(this),enc.reg(in(1))); } @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 949ac6c2..15f4824d 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,43 +1,11 @@ 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.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 { - - final TypeInteger _ti; - OrIX86(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; - return x86_64_v2.WMASK; } - // 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; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form - // General form: "ori ori | #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 OrIX86 extends ImmX86 { + OrIX86( Node add, int imm ) { super(add,imm); } @Override public String op() { return "ori"; } + @Override public String glabel() { return "|"; } + @Override int opcode() { return 0x81; } + @Override int mod() { return 1; } } 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 6d99fe3b..bc8f0ea8 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,35 +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.*; -import java.io.ByteArrayOutputStream; - -public class OrX86 extends MachConcreteNode implements MachNode{ - OrX86(Node or) { - super(or); - } - - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { assert i==1; 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: "or dst | src" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))); - } +import com.seaofnodes.simple.node.Node; +public class OrX86 extends RegX86 { + OrX86( Node add ) { super(add); } @Override public String op() { return "or"; } + @Override public String glabel() { return "|"; } + @Override int opcode() { return 0x0B; } + @Override public boolean commutes() { return true; } } 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 69ebb74e..5cfdd804 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,27 +1,17 @@ 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.Encoding; 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); - _rmask = x86_64_v2.callInMask(fun().sig(),_idx); + _rmask = x86_64_v2.callInMask(fun().sig(),_idx,1/*RPC*/); } - - // Register mask allowed on input i. 0 for no register. - @Override public RegMask regmap(int i) { return _rmask; } - // Register mask allowed as a result. Calling convention register + @Override public RegMask regmap(int i) { return null; } @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 void encoding( Encoding enc ) { } } 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 7ac014e6..046c7b4c 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,23 +1,15 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.Encoding; 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. @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) { - return 0; - } + @Override public void encoding( Encoding enc ) {} } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java new file mode 100644 index 00000000..8dc749ee --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java @@ -0,0 +1,21 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; + +public abstract class RegX86 extends MachConcreteNode { + RegX86( Node add ) { super(add); } + @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + @Override public int twoAddress() { return 1; } + abstract int opcode(); + @Override public void encoding( Encoding enc ) { + X86.reg(enc,opcode(),enc.reg(in(1)),enc.reg(in(2))); + } + // General form: "add dst += src" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" ").p(glabel()).p("= ").p(code.reg(in(2))); + } +} 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 433f64cc..e4467bb4 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,49 +1,15 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; 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; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; -// Return public class RetX86 extends ReturnNode implements MachNode { RetX86( ReturnNode ret, FunNode fun ) { super(ret, fun); fun.setRet(this); } - - // Correct Nodes outside the normal edges - @Override public void postSelect() { - FunNode fun = (FunNode)rpc().in(0); - _fun = fun; - fun.setRet(this); - } - - // Register mask allowed on input i. - // This is the normal calling convention - @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) { - // Without imm form - // Near return - int beforeSize = bytes.size(); - bytes.write(0xC3); // opcode - - return bytes.size() - beforeSize; - } - - @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 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 void encoding( Encoding enc ) { + int sz = fun()._frameAdjust; + if( sz!=0 ) X86.imm(enc,0x81,0,x86_64_v2.RSP,sz); + X86.ret(enc); } - - @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 9f942cc6..43ad6bbd 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,39 +1,33 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; -import com.seaofnodes.simple.*; +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.SB; import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Encoding; import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.Node; + import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import java.io.ByteArrayOutputStream; // Arithmetic Right Shift public class SarIX86 extends MachConcreteNode implements MachNode { - final TypeInteger _ti; - SarIX86(Node sar, TypeInteger ti) {super(sar); _inputs.pop(); _ti = ti;} + final int _imm; + SarIX86( Node sari, int imm ) { super(sari); assert x86_64_v2.imm8(imm); _inputs.pop(); _imm = imm; } + @Override public String op() { return "sari"; } + @Override public String glabel() { return ">>"; } + int opcode() { return 0xC1; } + int mod() { return 7; } - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { - // assert i==1; - return x86_64_v2.RMASK; } - // Register mask allowed as a result. 0 for no register. + @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } @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(); + @Override public void encoding(Encoding enc) { + X86.shift(enc,mod(),enc.reg(this),_imm); } - // General form - // General form: "sari 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); + sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm); } - - @Override public String op() { return "sari"; } } 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 ee7a2f56..50693034 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,41 +1,21 @@ 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; -import java.util.BitSet; -import java.lang.StringBuilder; +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.Node; - -public class SarX86 extends MachConcreteNode implements MachNode { - SarX86(Node sar) { super(sar); } - - // Register mask allowed on input i. - // This is the normal calling convention +public class SarX86 extends RegX86 { + SarX86( Node add ) { super(add); } + @Override public String op() { return "sar"; } + @Override public String glabel() { return ">>"; } @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 - @Override public int twoAddress() { return 1; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { + if (i == 1) return x86_64_v2.WMASK; + if (i == 2) return x86_64_v2.RCX_MASK; throw Utils.TODO(); } - - // General form - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" >> ").p(code.reg(in(1))); + @Override int opcode() { return 0xD3; } + @Override public void encoding( Encoding enc ) { + X86.unary(enc,opcode(),7,enc.reg(this)); } - - @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 53237538..591bf8da 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,33 +1,37 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; 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. // Corresponds to the x86 instruction "sete && setne". // Use result of comparison without jump. public class SetX86 extends MachConcreteNode implements MachNode { final String _bop; // One of <,<=,== + final boolean _unsigned; // Constructor expects input is an X86 and not an Ideal node. - SetX86( Node cmp, String bop ) { + SetX86( Node cmp, String bop, boolean unsigned ) { super(cmp); _inputs.setLen(1); // Pop the cmp inputs // Replace with the matched cmp _inputs.push(cmp); _bop = bop; + _unsigned = unsigned; } - + @Override public String op() { return "set"+_bop; } @Override public RegMask regmap(int i) { assert i==1; 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 - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + @Override public void encoding( Encoding enc ) { + int op=switch (_bop) { + case "==" -> 0x94; // SETE + case "<" -> _unsigned ? 0x92 : 0x9C; // SETB /SETL + case "<=" -> _unsigned ? 0x96 : 0x9E; // SETBE/SETLE + case ">=" -> _unsigned ? 0x93 : 0x9D; // SETAE/SETGE + default -> throw Utils.TODO(); + }; + X86.set(enc,op&15,enc.reg(this)); } @Override public void asm(CodeGen code, SB sb) { @@ -35,7 +39,4 @@ public class SetX86 extends MachConcreteNode implements MachNode { 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/x86_64_v2/ShlIX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java index c18007cd..f31832e6 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,40 +1,33 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.node.Node; + import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; 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; -public class ShlIX86 extends MachConcreteNode implements MachNode{ - final TypeInteger _ti; - ShlIX86(Node shl, TypeInteger ti) {super(shl); _inputs.pop(); _ti = ti;} +public class ShlIX86 extends MachConcreteNode implements MachNode { + final int _imm; + ShlIX86( Node shli, int imm ) { + super(shli); + assert x86_64_v2.imm8(imm); + _inputs.pop(); // Pop constant input off + _imm = imm; + } + public int opcode() { return 0xC1; } + public int mod() { return 4; } - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { - //assert i==1; - return x86_64_v2.RMASK; } - // Register mask allowed as a result. 0 for no register. + @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } @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(); + @Override public String op() { return "shli"; } + @Override public String glabel() { return "<<"; } + @Override public void encoding(Encoding enc) { + X86.shift(enc,mod(),enc.reg(this),_imm); } - - // General form - // General form: "shli 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); + sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm); } - - @Override public String op() { return "shli"; } } 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 a3de5347..08c4db25 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,39 +1,21 @@ 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; +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.Node; -public class ShlX86 extends MachConcreteNode implements MachNode { - ShlX86(Node shl) { super(shl); }; - - // Register mask allowed on input i. - // This is the normal calling convention - - // CL register must be used for input(2) +public class ShlX86 extends RegX86 { + ShlX86( Node add ) { super(add); } + @Override public String op() { return "shl"; } + @Override public String glabel() { return "<<"; } @Override public RegMask regmap(int i) { - if(i == 1) return x86_64_v2.WMASK; - if(i == 2) return x86_64_v2.RCX_MASK; + if (i == 1) return x86_64_v2.WMASK; + if (i == 2) return x86_64_v2.RCX_MASK; throw Utils.TODO(); } - - @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: "shl dst << src" - @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)).p(" << ").p(code.reg(in(1))); + @Override int opcode() { return 0xD3; } + @Override public final void encoding( Encoding enc ) { + X86.unary(enc,opcode(),4,enc.reg(this)); } - - @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 d17ee734..218542f3 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,41 +1,28 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.node.Node; + import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; 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 ShrIX86 extends MachConcreteNode implements MachNode{ - final TypeInteger _ti; - ShrIX86(Node shr, TypeInteger ti) {super(shr); _inputs.pop(); _ti = ti;} +public class ShrIX86 extends MachConcreteNode implements MachNode { + final int _imm; + ShrIX86( Node shri, int imm ) { super(shri); assert x86_64_v2.imm8(imm); _inputs.pop(); _imm = imm; } + @Override public String op() { return "shri"; } + @Override public String glabel() { return ">>>"; } + int opcode() { return 0xC1; } + int mod() { return 5; } - // Register mask allowed on input i. - // This is the normal calling convention - @Override public RegMask regmap(int i) { - //assert i==1; - return x86_64_v2.RMASK; } - // Register mask allowed as a result. 0 for no register. + @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } @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(); + @Override public void encoding(Encoding enc) { + X86.shift(enc,mod(),enc.reg(this),_imm); } - - // General form - // General form: "shri 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); + sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm); } - - @Override public String op() { return "shri"; } } 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 757d9b8e..c72f21e2 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,40 +1,21 @@ 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; +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.Node; -public class ShrX86 extends MachConcreteNode implements MachNode { - ShrX86(Node shr) { - super(shr); - }; - // Register mask allowed on input i. - // This is the normal calling convention - - // CL register must be used for input(2) +public class ShrX86 extends RegX86 { + ShrX86( Node add ) { super(add); } + @Override public String op() { return "shr"; } + @Override public String glabel() { return ">>>"; } @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(); - } - - @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) { + if (i == 1) return x86_64_v2.WMASK; + if (i == 2) return x86_64_v2.RCX_MASK; throw Utils.TODO(); } - - // 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))); + @Override int opcode() { return 0xD3; } + @Override public final void encoding( Encoding enc ) { + X86.unary(enc,opcode(),5,enc.reg(this)); } - - @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 index ea283a87..5cf668d1 100644 --- 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 @@ -1,21 +1,54 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.FunNode; import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.node.SplitNode; -import java.io.ByteArrayOutputStream; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; +import com.seaofnodes.simple.type.TypeInteger; public class SplitX86 extends SplitNode { SplitX86( String kind, byte round ) { super(kind,round, new Node[2]); } - - // Register mask allowed on input i. + @Override public String op() { return "mov"; } @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(); + // Need to handle 8 cases: . reg->reg, reg->xmm, reg->flags, xmm->reg, xmm->xmm, xmm->flags, flags->reg, flags->xmm, + // flags->flags. + @Override public void encoding( Encoding enc ) { + short dst=enc.reg(this), src=enc.reg(in(1)); + if( dst==x86_64_v2.FLAGS ) { X86.flags(enc,src,true); return; } + if( src==x86_64_v2.FLAGS ) { X86.flags(enc,dst,false); return; } + boolean dstX=dst>=x86_64_v2.XMM_OFFSET, srcX=src>=x86_64_v2.XMM_OFFSET; + // Stack spills + if( dst >= x86_64_v2.MAX_REG ) { + int off = enc._fun.computeStackOffset(enc._code,dst); + if( src >= x86_64_v2.MAX_REG ) { + // Rare stack-stack move. push [RSP+soff]; pop [RSP+doff] + int soff = enc._fun.computeStackOffset(enc._code,src); + X86.stackCopy(enc,soff,off); + return; + } + StoreX86.encVal(enc, srcX ? TypeFloat.F64 : TypeInteger.BOT, (short)x86_64_v2.RSP, (short)-1/*index*/, src, off, 0); + return; + } + if( src >= x86_64_v2.MAX_REG ) { + int off = enc._fun.computeStackOffset(enc._code,src); + LoadX86.enc(enc, dstX ? TypeFloat.F64 : TypeInteger.BOT, dst, (short)x86_64_v2.RSP, (short)-1, off, 0); + return; + } + + X86.move(enc,dstX,srcX,dstX ? dst-x86_64_v2.XMM_OFFSET : dst, + srcX ? src-x86_64_v2.XMM_OFFSET : src); + } + + // General form: "mov dst = src" + @Override public void asm(CodeGen code, SB sb) { + FunNode fun = code._encoding==null ? null : code._encoding._fun; + sb.p(code.reg(this,fun)).p(" = ").p(code.reg(in(1),fun)); } } 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 08d481fa..4796c0fe 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 @@ -1,20 +1,21 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.ConstantNode; import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.node.StoreNode; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; public class StoreX86 extends MemOpX86 { StoreX86( StoreNode st, Node base, Node idx, int off, int scale, int imm, Node val ) { super(st,st, base, idx, off, scale, imm, val); } - @Override public String op() { return "st"+_sz; } - @Override protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) { Node val = val(); p.p(".").p(_name).p("="); @@ -22,9 +23,21 @@ public class StoreX86 extends MemOpX86 { else p.n(val); return p.p(";"); } - // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return null; } + @Override public void encoding( Encoding enc ) { + short ptr=enc.reg(ptr()), idx=enc.reg(idx()), src=enc.reg(val()); + if( src==-1 ) X86.storeImm(enc,_declaredType.log_size(),_imm,ptr,idx,_off,_scale); + else encVal(enc,_declaredType,ptr,idx,src,_off,_scale); + } + + // Non-immediate encoding + static void encVal( Encoding enc, Type decl, short ptr, short idx, short src, int off, int scale ) { + boolean xmm=src>=x86_64_v2.XMM_OFFSET; + X86.store(enc,decl.log_size(),xmm, + xmm ? src-x86_64_v2.XMM_OFFSET : src,ptr,idx,off,scale); + } + // General form: "stN [base + idx<<2 + 12],val" @Override public void asm(CodeGen code, SB sb) { 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 ceaf0636..6a008551 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,31 +1,23 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import java.io.ByteArrayOutputStream; public class SubFX86 extends MachConcreteNode implements MachNode { SubFX86( Node subf) { super(subf); } - - - // Register mask allowed on input i. + @Override public String op() { return "subf"; } @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; } - // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return x86_64_v2.XMASK; } - // 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(); + @Override public void encoding( Encoding enc ) { + X86.sse(enc,0xF2,0x0F5C,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET); } // General form: "subf 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))); + sb.p(code.reg(this)).p(" -= ").p(code.reg(in(2))); } - - @Override public String op() { return "subf"; } } 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 da44a838..8da34bf5 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,31 +1,10 @@ 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 SubX86 extends MachConcreteNode implements MachNode { - SubX86( Node sub ) { super(sub); } - - // Register mask allowed on input i. - @Override public RegMask regmap(int i) { assert i==1 || i==2; return 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; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form: "sub 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))); - } +import com.seaofnodes.simple.node.Node; +public class SubX86 extends RegX86 { + SubX86( Node add ) { super(add); } @Override public String op() { return "sub"; } + @Override public String glabel() { return "-"; } + @Override int opcode() { return 0x2B; } } 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 2b4de2ca..d0c6157c 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,29 +1,41 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.isa.X86; 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.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.cpus.riscv.riscv; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeFunPtr; -import java.io.ByteArrayOutputStream; - // Function constants -public class TFPX86 extends ConstantNode implements MachNode { +public class TFPX86 extends ConstantNode implements MachNode, RIPRelSize { 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 String op() { + return _con == Type.NIL ? "xor" : "ldx"; + } + @Override public boolean isClone() { return true; } + @Override public Node copy() { return new TFPX86(this); } @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); } + // Zero-set uses XOR kills flags + @Override public RegMask killmap() { + return _con == Type.NIL ? x86_64_v2.FLAGS_MASK : null; + } + + @Override public void encoding( Encoding enc ) { + if( _con==Type.NIL ) { X86.constant(enc,enc.reg(this),0); return; } + enc.relo(this); + X86.rip(enc,0,0x8D,true,enc.reg(this)); + } + + @Override public byte encSize(int delta) { + return 7; + } - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + enc.patch4(opStart + 3, delta - 7); } // Human-readable form appended to the SB. Things like the encoding, @@ -35,6 +47,6 @@ public class TFPX86 extends ConstantNode implements MachNode { if( _con == Type.NIL ) sb.p(reg).p(",").p(reg); else - _con.print(sb.p(reg).p(" #")); + _con.print(sb.p(reg).p(" = #")); } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java new file mode 100644 index 00000000..d991e40a --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java @@ -0,0 +1,40 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.isa.X86; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; + +// unconditional jump +public class UJmpX86 extends CFGNode implements MachNode, RIPRelSize { + UJmpX86() { } + @Override public String op() { return "jmp"; } + @Override public String label() { return op(); } + @Override protected String format() { return "jmp "; } + @Override public RegMask regmap(int i) {return null; } + @Override public RegMask outregmap() { return null; } + @Override public Type compute() { throw Utils.TODO(); } + @Override public Node idealize() { throw Utils.TODO(); } + @Override public void encoding( Encoding enc ) { + enc.jump(this,uctrl()); + X86.branch(enc,-1); + } + + // Delta is from opcode start, but X86 measures from the end of the 2-byte encoding + @Override public byte encSize(int delta) { + return X86.branchSize(delta,false); + } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + X86.patchBranch(enc.bits(),opStart,opLen,delta,-1); + } + + @Override public void asm(CodeGen code, SB sb) { + CFGNode target = uctrl(); + //assert target.nOuts() > 1; // Should optimize jmp to empty targets + sb.p(label(target)); + } +} 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 47c6be73..2d989a50 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,41 +1,11 @@ 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.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 { - - final TypeInteger _ti; - XorIX86(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; return x86_64_v2.WMASK; } - // 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; } - - // Encoding is appended into the byte array; size is returned - @Override public int encoding(ByteArrayOutputStream bytes) { - throw Utils.TODO(); - } - - // General form - // General form: "ori xori ^ #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 XorIX86 extends ImmX86 { + XorIX86( Node add, int imm ) { super(add,imm); } @Override public String op() { return "xori"; } + @Override public String glabel() { return "^"; } + @Override int opcode() { return 0x81; } + @Override int mod() { return 6; } } 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 index 3322d951..86703903 100644 --- 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 @@ -1,36 +1,11 @@ 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))); - } +public class XorX86 extends RegX86 { + XorX86( Node add ) { super(add); } @Override public String op() { return "xor"; } - + @Override public String glabel() { return "^"; } + @Override int opcode() { return 0x33; } + @Override public boolean commutes() { return true; } } 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 c66aed9f..1b7cf775 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,26 +1,37 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; 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.codegen.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; public class x86_64_v2 extends Machine { + public x86_64_v2( CodeGen code ) {} // X86-64 V2. Includes e.g. SSE4.2 and POPCNT. @Override public String name() { return "x86_64_v2"; } + @Override public int defaultOpSize() { return 5; } - 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 final int RAX = 0, RCX = 1, RDX = 2, RBX = 3, RSP = 4, RBP = 5, RSI = 6, RDI = 7; + public static final int R08 = 8, R09 = 9, R10 = 10, R11 = 11, R12 = 12, R13 = 13, R14 = 14, R15 = 15; - 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; + public static final int XMM0 = 16, XMM1 = 17, XMM2 = 18, XMM3 = 19, XMM4 = 20, XMM5 = 21, XMM6 = 22, XMM7 = 23; + public static final int XMM8 = 24, XMM9 = 25, XMM10 = 26, XMM11 = 27, XMM12 = 28, XMM13 = 29, XMM14 = 30, XMM15 = 31; + public static final int FLAGS = 32; + public static final int MAX_REG = 33; + public static final int RPC = 33; + // Human-readable name for a register number, e.g. "RAX". + public 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", + }; + // Hard crash for bad register number, fix yer bugs! + @Override public String[] regs() { return REGS; } + + public static int XMM_OFFSET = 16; // General purpose register mask: pointers and ints, not floats static final long RD_BITS = 0b1111111111111111L; // All the GPRs static RegMask RMASK = new RegMask(RD_BITS); @@ -29,49 +40,138 @@ public class x86_64_v2 extends Machine { static RegMask WMASK = new RegMask(WR_BITS); // Xmm register 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 RegMask XMASK = new RegMask(FP_BITS); + static RegMask FLAGS_MASK = new RegMask(FLAGS); + static 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 ); + 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); - 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 static RegMask RAX_MASK = new RegMask(RAX); + public static RegMask RCX_MASK = new RegMask(RCX); + public static RegMask RDX_MASK = new RegMask(RDX); + public static RegMask RDI_MASK = new RegMask(RDI); + public static RegMask R08_MASK = new RegMask(R08); + public static RegMask R09_MASK = new RegMask(R09); + public static RegMask RSI_MASK = new RegMask(RSI); + + public static RegMask XMM0_MASK = new RegMask(XMM0); + + // Encoding + public static int REX = 0x40; + public static int REX_W = 0x48; + public static int REX_WR = 0x4C; + public static int REX_WRB= 0x4D; + public static int REX_WB = 0x49; + + // opcode included here + // 0F 84 cd JE rel32 + // 0F 85 cd JNE rel32 + // 0F 8F cd JG rel32 + // 0F 8C cd JL rel32 + // 0F 8E cd JLE rel32 + // 0F 8D cd JGE rel32 + static public int jumpop(String op) { + return switch (op) { + case "==" -> 0x84; + case "!=" -> 0x85; + case ">" -> 0x8F; + case "<" -> 0x8C; + case "<=" -> 0x8E; + case ">=" -> 0x8D; + default -> throw new IllegalArgumentException("Too many arguments"); + }; + } - // Human-readable name for a register number, e.g. "RAX". - // Hard crash for bad register number, fix yer bugs! - 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","[rsp-4]", + // Limit of float args passed in registers + static RegMask[] XMMS8 = new RegMask[]{ + new RegMask(XMM0), new RegMask(XMM1), new RegMask(XMM2), new RegMask(XMM3), + new RegMask(XMM4), new RegMask(XMM5), new RegMask(XMM6), new RegMask(XMM7), }; - @Override public String reg( int reg ) { - return reg < REGS.length ? REGS[reg] : "[rsp+"+(reg-REGS.length)*4+"]"; + + // Map from function signature and argument index to register. + // Used to set input registers to CallNodes, and ParmNode outputs. + @Override public RegMask callArgMask( TypeFunPtr tfp, int idx, int maxArgSlot ) { return callInMask(tfp,idx,maxArgSlot); } + static RegMask callInMask( TypeFunPtr tfp, int idx, int maxArgSlot ) { + if( idx==0 ) return RPC_MASK; + if( idx==1 ) return null; + return switch( CodeGen.CODE._callingConv ) { + case "SystemV" -> callSys5 (tfp,idx,maxArgSlot); + case "Win64" -> callWin64(tfp,idx,maxArgSlot); + default -> throw Utils.TODO(); + }; } - // WIN64(param passing) - static RegMask[] CALLINMASK_WIN64 = new RegMask[] { + // Maximum stack args used by this signature + @Override public short maxArgSlot( TypeFunPtr tfp ) { + return switch( CodeGen.CODE._callingConv ) { + case "SystemV" -> maxArgSlotSys5 (tfp); + case "Win64" -> maxArgSlotWin64(tfp); + default -> throw Utils.TODO(); + }; + } + + // Win64 - max *4* args in registers of all kinds; after that on stack. + // Stack args land in increasing memory order with empty/mirror slots for + // every register. + // + // foo( int i0, flt f1, int i2, flt f3, int i4, flt f5, int i6, ... ) + // + // -- Prior Frame -- + // i6 + // f5 + // i4 + // empty mirror, XMM3= f3 + // empty mirror, R08 = i2 + // empty mirror, XMM1= f1 + // empty mirror, RCX = i0 + // -- Caller Frame; 16b align -- + // RPC + // PAD/ALIGN + // -- Callee Frame; 16b align -- + static RegMask[] WIN64_CALL = new RegMask[] { RCX_MASK, RDX_MASK, R08_MASK, R09_MASK, }; + static RegMask callWin64(TypeFunPtr tfp, int idx, int maxArgSlot ) { + // idx 2,3,4,5 passed in registers, with stack slot mirrors. + // idx >= 6 passed on stack, starting at slot#1 (#0 reserved for RPC). + if( idx >= 6 ) + return new RegMask(MAX_REG+maxArgSlot+(idx-2)); + return tfp.arg(idx-2) instanceof TypeFloat + ? XMMS8 [idx-2] + : WIN64_CALL[idx-2]; + } + static short maxArgSlotWin64(TypeFunPtr tfp) { + return (short)tfp.nargs(); + } + + // Sys5: max 6 GPRs and 8 FPRS filled first. Extra args land in increasing + // memory order as needed - no mirror space. + // + // foo( int i0, flt f1, int i2, flt f3, int i4, flt f5, int i6, ... ) + // RDI =i0, RSI =i2, RDX =i4, RCX =i6, R08 =i8, R09 =i10 + // XMM0=f1, XMM1=f3, XMM2=f5, XMM3=f7, XMM4=f9, XMM5=f11, XMM6=f13, XMM7=f15 + // + // -- Prior Frame -- + // f18 + // i18 + // f17 + // i16 + // i14 + // i12 + // -- Caller Frame; 16b align -- + // RPC + // PAD/ALIGN + // -- Callee Frame; 16b align -- // SystemV(param passing) - static RegMask[] CALLINMASK_SYSTEMV = new RegMask[] { + static RegMask[] SYS5_CALL = new RegMask[] { RDI_MASK, RSI_MASK, RDX_MASK, @@ -80,289 +180,245 @@ public class x86_64_v2 extends Machine { 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]; + static RegMask callSys5(TypeFunPtr tfp, int idx, int maxArgSlot ) { + // First 6 integers passed in registers: rdi,rsi,rdx,rcx,r08,r09 + // First 8 floats passed in registers: xmm0-xmm7 + int icnt=0, fcnt=0; // Count of ints, floats + for( int i=2; i<idx; i++ ) { + if( tfp.arg(i-2) instanceof TypeFloat ) fcnt++; + else icnt++; + } + int nstk = Math.max(icnt-6,0)+Math.max(fcnt-8,0); + return tfp.arg(idx-2) instanceof TypeFloat + ? fcnt<8 ? XMMS8 [fcnt] : new RegMask(MAX_REG+maxArgSlot+nstk) + : icnt<6 ? SYS5_CALL[icnt] : new RegMask(MAX_REG+maxArgSlot+nstk); + } + static short maxArgSlotSys5(TypeFunPtr tfp) { + int icnt=0, fcnt=0; // Count of ints, floats + for( int i=0; i<tfp.nargs(); i++ ) { + if( tfp.arg(i) instanceof TypeFloat ) fcnt++; + else icnt++; } - throw Utils.TODO(); // Pass on stack slot + int nstk = Math.max(icnt-6,0)+Math.max(fcnt-8,0); + return (short)nstk; } - // 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 + (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) 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); + (1L<<XMM4) | (1L<<XMM5) ; - 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 long callerSave() { + return switch (CodeGen.CODE._callingConv) { + case "SystemV" -> SYSTEM5_CALLER_SAVE; + case "Win64" -> WIN64_CALLER_SAVE; + 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 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; + @Override public long neverSave() { return 1L<<RSP; } + @Override public RegMask retMask( TypeFunPtr tfp ) { + return tfp.ret() instanceof TypeFloat ? XMM0_MASK : RAX_MASK; } + @Override public int rpc() { return RPC; } - // 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); - }; - return 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 SplitX86(kind,round); } + @Override public SplitNode split(String kind, byte round, LRG lrg) { + return new SplitX86(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(); + return new UJmpX86(); } // 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 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); - case DivNode div -> new DivX86(div); - case FunNode fun -> new FunX86(fun); - case IfNode iff -> jmp(iff); - case LoadNode ld -> ld(ld); - case MemMergeNode mem -> new MemMergeNode(mem); - case MulFNode mulf -> new MulFX86(mulf); - case MulNode mul -> mul(mul); - case NewNode nnn -> new NewX86(nnn); - case OrNode or -> or(or); - case ParmNode parm -> new ParmX86(parm); + @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 CProjNode c -> new CProjNode(c); + case CallEndNode cend -> new CallEndX86(cend); + case CallNode call -> call(call); + case CastNode cast -> new CastX86(cast); + case ConstantNode con -> con(con); + case DivFNode divf -> new DivFX86(divf); + case DivNode div -> new DivX86(div); + case FunNode fun -> new FunX86(fun); + case IfNode iff -> jmp(iff); + case LoadNode ld -> ld(ld); + case MemMergeNode mem -> new MemMergeNode(mem); + case MinusNode neg -> new NegX86(neg); + case MulFNode mulf -> new MulFX86(mulf); + case MulNode mul -> mul(mul); + case NewNode nnn -> new NewX86(nnn); + case NotNode not -> new NotX86(not); + case OrNode or -> or(or); + case ParmNode parm -> new ParmX86(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 RetX86(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 SubFX86(subf); - case SubNode sub -> sub(sub); - case ToFloatNode tfn -> i2f8(tfn); + case PhiNode phi -> new PhiNode(phi); + case ProjNode prj -> prj(prj); + case ReadOnlyNode read-> new ReadOnlyNode(read); + case ReturnNode ret -> new RetX86(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 SubFX86(subf); + case SubNode sub -> sub(sub); + case ToFloatNode tfn -> i2f8(tfn); case ToIntegerNode cvt -> new F8ToIX86(cvt); - case XorNode xor -> xor(xor); + case XCtrlNode x -> new ConstantNode(Type.XCONTROL); + case XorNode xor -> xor(xor); - case LoopNode loop -> new LoopNode(loop); - case RegionNode region-> new RegionNode(region); + case LoopNode loop -> new LoopNode(loop); + case RegionNode region -> new RegionNode(region); default -> throw Utils.TODO(); }; } + public static boolean imm8( long imm ) { return -128 <= imm && imm <= 127; } + + public static boolean imm32( long imm ) { return (int)imm==imm; } // Attempt a full LEA-style break down. - private Node add( AddNode add ) { + 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, 0,rhs); + if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && ld._declaredType.log_size() >= 3) + return new AddMemX86(add, address(ld), ld.ptr(), idx, off, scale, 0, rhs); - if( rhs instanceof LoadNode ld && ld.nOuts()==1 ) + if( rhs instanceof LoadNode ld && ld.nOuts()==1 && ld._declaredType.log_size()>=3 ) 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 - if( rhs instanceof ConstantNode off && off._con instanceof TypeInteger toff ) { + if( rhs instanceof ConstantNode off2 && off2._con instanceof TypeInteger toff ) { + long imm = toff.value(); + assert imm!=0; // Folded in peeps + if( (int)imm != imm ) // Full 64bit immediate + return new AddX86(add); + // Now imm <= 32bits if( lhs instanceof AddNode ladd ) // ((base + (idx << scale)) + off) - return _lea(add,ladd.in(1),ladd.in(2),toff.value()); + return _lea(add, ladd.in(1), ladd.in(2), (int)imm); if( lhs instanceof ShlNode shift ) // (idx << scale) + off; no base - return _lea(add,null,shift,toff.value()); + return _lea(add, null, shift, (int)imm); // lhs + rhs1 - if( toff.value()==0 ) return add; - return new AddIX86(add, toff); + return new AddIX86(add, (int)imm); } - return _lea(add,lhs,rhs,0); + return _lea(add, lhs, rhs, 0); } - private Node addf( AddFNode addf ) { - if( addf.in(1) instanceof LoadNode ld && ld.nOuts()==1 ) - return new AddFMemX86(addf,address(ld),ld.ptr(),idx,off,scale, addf.in(2)); + private Node addf(AddFNode addf) { + if(addf.in(1) instanceof LoadNode ld && ld.nOuts() == 1) + return new AddFMemX86(addf, address(ld), ld.ptr(), idx, off, scale, addf.in(2)); - if( addf.in(2) instanceof LoadNode ld && ld.nOuts()==1 ) - throw Utils.TODO(); // Swap load sides +// if(addf.in(2) instanceof LoadNode ld && ld.nOuts() == 1) +// throw Utils.TODO(); // Swap load sides return new AddFX86(addf); } - private Node _lea( Node add, Node base, Node idx, long off ) { - int scale = 1; - if( base instanceof ShlNode && !(idx instanceof ShlNode) ) throw Utils.TODO(); // Bug in canonicalization, should on RHS + private Node _lea(Node add, Node base, Node idx, int off) { + int scale = 0; + if( base instanceof ShlNode && !(idx instanceof ShlNode) ) + throw Utils.TODO(); // Bug in canonicalization, should on RHS if( idx instanceof ShlNode shift && shift.in(2) instanceof ConstantNode shfcon && shfcon._con instanceof TypeInteger tscale && 0 <= tscale.value() && tscale.value() <= 3 ) { idx = shift.in(1); - scale = 1 << ((int)tscale.value()); + scale = ((int) tscale.value()); } // (base + idx) + off - return off==0 && scale==1 + return off == 0 && scale == 0 ? new AddX86(add) - : new LeaX86(add,base,idx,scale,off); + : new LeaX86(add, base, idx, scale, off); } private Node and(AndNode and) { - if( and.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti ) - return new AndIX86(and, ti); + if( and.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) ) + return new AndIX86(and, (int)ti.value()); return new AndX86(and); } private Node call(CallNode call) { - if( call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp ) - return new CallX86(call, tfp); - return new CallRX86( call ); + return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp + ? new CallX86(call, tfp) + : new CallRX86(call); } // Because X86 flags, a normal ideal Bool is 2 X86 ops: a "cmp" and at "setz". // Ideal If reading from a setz will skip it and use the "cmp" instead. + private static boolean swap, unsigned; private Node cmp( BoolNode bool ) { + swap = unsigned = false; Node cmp = _cmp(bool); - return new SetX86(cmp,bool.op()); + return new SetX86(cmp, swap ? IfNode.swap(bool.op()) : bool.op(), unsigned); } - private Node _cmp( BoolNode bool ) { + + private Node _cmp(BoolNode bool) { // Float variant - if( bool.isFloat() ) + if( bool.isFloat() ) { + unsigned = true; return new CmpFX86(bool); - + } Node lhs = bool.in(1); Node rhs = bool.in(2); - if( lhs instanceof LoadNode ld && ld.nOuts()==1 ) - return new CmpMemX86(bool,address(ld),ld.ptr(),idx,off,scale, imm(rhs),val,false); - if( rhs instanceof LoadNode ld && ld.nOuts()==1 ) - return new CmpMemX86(bool,address(ld),ld.ptr(),idx,off,scale, imm(lhs),val,true); + // Since cmp does not record a BOP, SetX/Jmp need to know if the CMP is swapped. + + // Vs memory + if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && rhs._type.isa(ld._declaredType) ) + return new CmpMemX86(bool, address(ld), ld.ptr(), idx, off, scale, imm(rhs), val, false); + + // Operands swap in the encoding directly, no need for Set/Jmp to swap `bop` + if( rhs instanceof LoadNode ld && ld.nOuts() == 1 && lhs._type.isa(ld._declaredType)) + return new CmpMemX86(bool, address(ld), ld.ptr(), idx, off, scale, imm(lhs), val, true ); // Vs immediate - if( rhs instanceof ConstantNode con && con._con instanceof TypeInteger ti ) - return new CmpIX86(bool, ti); + if( rhs instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) ) + return new CmpIX86(bool, (int)ti.value(), false); + + // Operand swap compare; Set and Jmp need to swap `bop` + if( lhs instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) ) + return new CmpIX86(bool, (int)ti.value(), swap=true); + // 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 -> 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 IntX86(con) : new 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 -> 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 IntX86(con) : new ConstantNode(con); }; } @@ -371,28 +427,30 @@ public class x86_64_v2 extends Machine { return new I2f8X86(tfn); } - private Node jmp( IfNode iff ) { + 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 JmpX86(iff, ((BoolNode)iff.in(1)).op()); + String op = "!="; + if( iff.in(1) instanceof BoolNode bool ) op = swap ? IfNode.swap(bool.op()) : bool.op(); + else if( iff.in(1)==null ) op = "=="; // Never-node cutout + else iff.setDef(1, new BoolNode.NE(iff.in(1), new ConstantNode(TypeInteger.ZERO))); + return new JmpX86(iff, op); } - private Node ld( LoadNode ld ) { - return new LoadX86(address(ld),ld.ptr(),idx,off,scale); + private Node ld(LoadNode ld) { + return new LoadX86(address(ld), ld.ptr(), idx, off, scale); } private Node mul(MulNode mul) { - return mul.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti - ? new MulIX86(mul, ti) - : new MulX86(mul); + if( mul.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) ) + return new MulIX86(mul, (int)ti.value()); + return new MulX86(mul); } private Node or(OrNode or) { - if( or.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) - return new OrIX86(or, ti); + if( or.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) ) + return new OrIX86(or, (int)ti.value()); return new OrX86(or); } @@ -400,21 +458,21 @@ public class x86_64_v2 extends Machine { return new ProjX86(prj); } - private Node sar( SarNode sar ) { - if( sar.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) - return new SarIX86(sar, ti); + private Node sar(SarNode sar) { + if( sar.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm8(ti.value())) + return new SarIX86(sar, (int)(ti.value() & 0xff) ); return new SarX86(sar); } - private Node shl( ShlNode shl ) { - if( shl.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti ) - return new ShlIX86(shl, ti); + private Node shl(ShlNode shl) { + if( shl.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm8(ti.value())) + return new ShlIX86(shl, (int)(ti.value() & 0xff) ); return new ShlX86(shl); } - private Node shr(ShrNode shr) { - if( shr.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti ) - return new ShrIX86(shr, ti); + private Node shr( ShrNode shr ) { + if( shr.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm8(ti.value()) ) + return new ShrIX86(shr, (int)(ti.value() & 0xff) ); return new ShrX86(shr); } @@ -422,58 +480,58 @@ public class x86_64_v2 extends Machine { // Look for "*ptr op= val" Node op = st.val(); if( op instanceof AddNode ) { - if( op.in(1) instanceof LoadNode ld && - ld.in(0)==st.in(0) && - ld.mem()==st.mem() && - ld.ptr()==st.ptr() && - ld.off()==st.off() ) { - if( op instanceof AddNode ) { - return new MemAddX86(address(st),st.ptr(),idx,off,scale,imm(op.in(2)),val); - } - throw Utils.TODO(); - } + if( op.in(1) instanceof LoadNode ld && stld_match(st,ld) ) + return new MemAddX86(address(st), st.ptr(), idx, off, scale, imm(op.in(2)), val); + if( op.in(2) instanceof LoadNode ld && stld_match(st,ld) ) + return new MemAddX86(address(st), st.ptr(), idx, off, scale, imm(op.in(1)), val); } - - return new StoreX86(address(st),st.ptr(),idx,off,scale,imm(st.val()),val); + return new StoreX86(address(st), st.ptr(), idx, off, scale, imm(st.val()), val); } + private static boolean stld_match(StoreNode st, LoadNode ld ) { + return + ld.in(0) == st.in(0) && + ld.mem() == st.mem() && + ld.ptr() == st.ptr() && + ld.off() == st.off(); + } + - private Node sub( SubNode sub ) { + private Node sub (SubNode sub ){ return sub.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti - ? new AddIX86(sub, TypeInteger.constant(-ti.value())) + ? new AddIX86(sub, (int)-ti.value()) : new SubX86(sub); } - private Node xor(XorNode xor) { - if(xor.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) - return new XorIX86(xor, ti); - throw Utils.TODO(); + private Node xor (XorNode xor){ + if( xor.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) ) + return new XorIX86(xor, (int)ti.value()); + return new XorX86(xor); } - // Gather X86 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, scale, imm; private static Node idx, val; - private <N extends MemOpNode> N address( N mop ) { + private <N extends MemOpNode > N address(N mop) { off = scale = imm = 0; // Reset idx = val = null; Node base = mop.ptr(); // Skip/throw-away a ReadOnly, only used to typecheck - if( base instanceof ReadOnlyNode read ) base = read.in(1); + 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 AddNode add && add.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti ) { - off = (int)ti.value(); + if(mop.off() instanceof AddNode add && add.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) { + off = (int) ti.value(); assert off == ti.value(); // In 32-bit range idx = add.in(1); - if( idx instanceof ShlNode shift && shift.in(2) instanceof ConstantNode shfcon && - shfcon._con instanceof TypeInteger tscale && 0 <= tscale.value() && tscale.value() <= 3 ) { + if(idx instanceof ShlNode shift && shift.in(2) instanceof ConstantNode shfcon && + shfcon._con instanceof TypeInteger tscale && 0 <= tscale.value() && tscale.value() <= 3) { idx = shift.in(1); - scale = (int)tscale.value(); + scale = (int) tscale.value(); } } else { - if( mop.off() instanceof ConstantNode con && con._con instanceof TypeInteger ti ) { - off = (int)ti.value(); + 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(); @@ -483,10 +541,10 @@ public class x86_64_v2 extends Machine { } private int imm( Node xval ) { - assert val==null && imm==0; - if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti ) { + assert val == null && imm == 0; + if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti) { val = null; - imm = (int)ti.value(); + imm = (int) ti.value(); assert imm == ti.value(); // In 32-bit range } else { val = xval; diff --git a/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java b/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java index a4f768c5..09ebd51b 100644 --- a/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java +++ b/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java @@ -8,13 +8,16 @@ import com.seaofnodes.simple.SB; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; import java.util.ArrayList; +import com.seaofnodes.simple.codegen.Encoding; /** Machine-specific facts; traversal, columns and byte display live in print/. */ public final class ASMPrinter extends AssemblyAdapter<Node> { private final CodeGen _code; + private final Encoding _enc; private ASMPrinter(CodeGen code) { super(new IRPrinter()); _code=code; + _enc=code._encoding; } public static SB print(SB sb, CodeGen code) { return sb.p(new com.seaofnodes.print.ASMPrinter<>(new ASMPrinter(code)).print()); @@ -50,4 +53,33 @@ public final class ASMPrinter extends AssemblyAdapter<Node> { return n.getClass()==ConstantNode.class; } @Override public boolean postAlloc() { return _code._phase.ordinal()>CodeGen.Phase.RegAlloc.ordinal(); } + @Override public boolean encoded() { return _enc!=null && _code._phase.ordinal()>=CodeGen.Phase.Encoding.ordinal(); } + @Override public int defaultSize() { return _code._mach==null ? 2 : _code._mach.defaultOpSize(); } + @Override public boolean littleEndian() { return _code._asmLittle; } + @Override public boolean prologue(Node n) { return ((FunNode)n)._frameAdjust!=0; } + @Override public byte[] bytes() { return encoded() ? _enc._bits.buf() : null; } + @Override public int offset(Node n) { + return encoded() && _enc._opStart!=null && n._nid<_enc._opStart.length ? _enc._opStart[n._nid] : -1; + } + @Override public int size(Node n) { + return encoded() && _enc._opLen!=null && n._nid<_enc._opLen.length ? _enc._opLen[n._nid]&255 : 0; + } + private static ArrayList<Data> entries(Iterable<Encoding.Relo> relos) { + var data=new ArrayList<Data>(); + for( Encoding.Relo r : relos ) { + int align=1<<r._align; + int size=align; + if( r._t instanceof TypeTuple t ) size=align*t._types.length; + data.add(new Data(r._t,r._t.str(),r._align,size)); + } + return data; + } + @Override public ArrayList<Pool> pools(int codeEnd) { + var pools=new ArrayList<Pool>(); + if( !encoded() ) return pools; + int base=(codeEnd+15)&-16; + if( !_enc._bigCons.isEmpty() && base<_enc._bits.size() ) + pools.add(new Pool("Constant Pool",_enc._bits.buf(),base,base,entries(_enc._bigCons.values()))); + return pools; + } } diff --git a/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index 0357ff61..b9aabba3 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 20's view of the IR; browser, transport and layout are shared. */ +/** Chapter 21'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 9dce0b75..d8d0e216 100644 --- a/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java +++ b/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java @@ -7,7 +7,7 @@ import com.seaofnodes.simple.node.Node; import java.util.ArrayList; import java.io.IOException; -/** Chapter 20's compilation context; capture and event bookkeeping are shared. */ +/** Chapter 21'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/Type.java b/src/main/java/com/seaofnodes/simple/type/Type.java index 1ce5dbd0..96b2c6ef 100644 --- a/src/main/java/com/seaofnodes/simple/type/Type.java +++ b/src/main/java/com/seaofnodes/simple/type/Type.java @@ -103,6 +103,7 @@ public class Type { // checks with every type compare, only during interning. // Factory method which interns "this" + @SuppressWarnings("unchecked") public <T extends Type> T intern() { T nnn = (T)INTERN.get(this); if( nnn==null ) @@ -221,7 +222,7 @@ public class Type { // Sizes are expected to be between 1 and 64 bits. // Size 0 means this either takes no space (such as a known-zero field) // or isn't a scalar to be stored in memory. - public int log_size() { throw Utils.TODO(); } + public int log_size() { return 3; } // ---------------------------------------------------------- // Useful in the debugger, which calls toString everywhere. diff --git a/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java b/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java index 609f2443..1bd73732 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java @@ -44,6 +44,7 @@ public class TypeFunPtr extends TypeNil { 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 TypeFunPtr MAIN = make((byte)3,TypeTuple.MAIN,TypeInteger.BOT,-1); + public static TypeFunPtr CALLOC= make((byte)3,TypeTuple.CALLOC,TypeMemPtr.BOT,-1); public static void gather(ArrayList<Type> ts) { ts.add(TEST); ts.add(TEST0); ts.add(BOT); ts.add(MAIN); } @Override diff --git a/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java index 61b020cb..98e92bca 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java @@ -98,7 +98,7 @@ public class TypeMemPtr extends TypeNil { // Is forward-reference @Override public boolean isFRef() { return _obj.isFRef(); } - @Override public int log_size() { return 2; } // (1<<2)==4-byte pointers + @Override public int log_size() { return 3; } // (1<<3)==8-byte pointers @Override int hash() { return (_ro ? 8192 : 0) ^ _obj.hashCode() ^ 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 dc92e086..990c5fcf 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeStruct.java @@ -184,9 +184,12 @@ public class TypeStruct extends Type { public SB print(SB sb) { if( this==BOT ) return sb.p("StructBot"); if( this==TOP ) return sb.p("StructTop"); + if( _fields == null ) return sb.p(_name); // Forward reference struct, just print the name + if( isAry() ) { + if( !isFinal() ) sb.p("!"); + return sb.p(_name); // Skip printing generic array fields + } 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 95e35e99..d3b19b52 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeTuple.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeTuple.java @@ -17,6 +17,7 @@ public class TypeTuple extends Type { public static final TypeTuple START= make(Type.CONTROL,TypeMem.TOP,TypeInteger.BOT); public static final TypeTuple MAIN = make(TypeInteger.BOT); public static final TypeTuple RET = make(Type.CONTROL,TypeMem.BOT,Type.BOTTOM); + public static final TypeTuple CALLOC = make(TypeInteger.BOT,TypeInteger.BOT); public static final TypeTuple IF_BOTH = make(new Type[]{Type. CONTROL,Type. CONTROL}); public static final TypeTuple IF_NEITHER = make(new Type[]{Type.XCONTROL,Type.XCONTROL}); @@ -55,6 +56,21 @@ public class TypeTuple extends Type { return make(ts); } + @Override public boolean isConstant() { + for( Type t : _types ) + if( !t.isConstant() ) + return false; + return true; + } + + @Override public int log_size() { + assert isConstant(); + int log_size = 0; + for( Type t : _types ) + log_size = Math.max(log_size,t.log_size()); + return log_size; + } + public Type ret() { assert _types.length==3; return _types[2]; } @Override public String str() { return print(new SB()).toString(); } diff --git a/src/test/java/com/seaofnodes/simple/BrainFuckTest.java b/src/test/java/com/seaofnodes/simple/BrainFuckTest.java index c23bb301..aa6fada0 100644 --- a/src/test/java/com/seaofnodes/simple/BrainFuckTest.java +++ b/src/test/java/com/seaofnodes/simple/BrainFuckTest.java @@ -1,71 +1,42 @@ package com.seaofnodes.simple; +import com.seaofnodes.isa.eval.EvalRisc5; + +import com.seaofnodes.isa.eval.EvalArm64; + import com.seaofnodes.simple.codegen.CodeGen; -import java.nio.charset.StandardCharsets; +import com.seaofnodes.simple.node.cpus.arm.arm; +import com.seaofnodes.simple.node.cpus.riscv.riscv; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; 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 { - @Test - public void testBrainfuck() { - var program = "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.".getBytes(StandardCharsets.UTF_8); - var encoded = new StringBuilder("u8[] !program = new u8[").append(program.length).append("];"); - for (int i = 0; i < program.length; i++) { - int value = program[i] & 0xFF; - encoded.append("program[").append(i).append("] = ").append(value).append(";"); - } + @Test public void testBrainFuck() throws IOException { + String brain_fuck = "Hello World!\n"; + TestC.run("brain_fuck", brain_fuck, 40); - String src = encoded + """ + EvalRisc5 R5 = TestRisc5.build("brain_fuck", 0, 146, false); + int trap = R5.step(100000); + assertEquals(0,trap); + int ptr = (int)R5.regs[riscv.A0]; + assertEquals(brain_fuck.length(),R5.ld4s(ptr)); + for( int i=0; i<brain_fuck.length(); i++ ) + assertEquals(brain_fuck.charAt(i), R5.ld1z(ptr+4+i)); -int d = 0; -u8[] !output = new u8[0]; -u8[] !data = new u8[100]; + EvalArm64 A5 = TestArm64.build("brain_fuck", 0, 34, false); + int trap_arm = A5.step(100000); + assertEquals(0,trap_arm); + assertEquals(0,trap); + int ptr_arm = (int)A5.regs[arm.X0]; + assertEquals(brain_fuck.length(),A5.ld4s(ptr_arm)); -for( int pc = 0; pc < program#; pc++ ) { - var command = program[pc]; - if (command == 62) { - d++; - } else if (command == 60) { - d--; - } else if (command == 43) { - data[d]++; - } else if (command == 45) { - data[d]--; - } else if (command == 46) { - // Output a byte; increase the output array size - var old = output; - output = new u8[output# + 1]; - for( int i = 0; i < old#; i++ ) - output[i] = old[i]; - output[old#] = data[d]; // Add the extra byte on the end - } else if (command == 44) { - data[d] = 42; - } else if (command == 91) { - if (data[d] == 0) { - for( int d = 1; d > 0; ) { - command = program[++pc]; - if (command == 91) d++; - if (command == 93) d--; - } - } - } else if (command == 93) { - if (data[d]) { - for( int d = 1; d > 0; ) { - command = program[--pc]; - if (command == 93) d++; - if (command == 91) d--; - } - } - } -} -return output; -"""; - 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)); + for( int i=0; i<brain_fuck.length(); i++ ) + assertEquals(brain_fuck.charAt(i), A5.ld1z(ptr_arm+4+i)); } + } diff --git a/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/src/test/java/com/seaofnodes/simple/Chapter05Test.java index 70cc6121..6ff4ac95 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter05Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter05Test.java @@ -100,7 +100,7 @@ else return a; """); code.parse().opto(); - assertEquals("return Phi(Region,Phi(Region,2,3),Phi(Region,4,5));", code.print()); + assertEquals("return Phi(Region,3,5,2,4);", code.print()); } @Test diff --git a/src/test/java/com/seaofnodes/simple/Chapter06Test.java b/src/test/java/com/seaofnodes/simple/Chapter06Test.java index 24d12921..b6bc2bf3 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter06Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter06Test.java @@ -145,7 +145,7 @@ else return a; """); code.parse().opto(); - assertEquals("return Phi(Region,3,Phi(Region,4,5));", code.print()); + assertEquals("return Phi(Region,3,5,4);", code.print()); } @Test @@ -258,7 +258,7 @@ if( arg ) { return a+b+c; """); code.parse().opto(); - assertEquals("return (Phi(Region,Phi(Region,2,3),0)+Phi(Region,3,1));", code.print()); + assertEquals("return Phi(Region,6,1,5);", code.print()); } diff --git a/src/test/java/com/seaofnodes/simple/Chapter08Test.java b/src/test/java/com/seaofnodes/simple/Chapter08Test.java index 7e90d484..09669cc7 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter08Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter08Test.java @@ -23,7 +23,7 @@ while(arg < 10) { return arg; """); code.parse().opto(); - assertEquals("return Phi(Region,Phi(Region,Phi(Loop,arg,(Phi_arg+1)),Add),Add);", code.print()); + assertEquals("return Phi(Region,(Phi(Loop,arg,Add)+1),Add,Phi_arg);", code.print()); assertTrue(code.ctrl() instanceof RegionNode); Assert.assertEquals( "5", Eval2.eval(code, 1)); Assert.assertEquals("10", Eval2.eval(code, 6)); @@ -46,7 +46,7 @@ while(arg < 10) { return a; """); code.parse().opto(); - assertEquals("return Phi(Loop,1,Phi(Region,Phi_a,(Phi_a+1)));", code.print()); + assertEquals("return Phi(Loop,1,Phi(Region,Phi_a,(Phi_a+1),Phi_a));", code.print()); assertTrue(code.ctrl() instanceof CProjNode); } diff --git a/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/src/test/java/com/seaofnodes/simple/Chapter12Test.java index 4abc9117..0b4ad8ec 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -35,7 +35,7 @@ while( arg ) { return head.next.i; """); try { code.parse().opto().typeCheck(); fail(); } - catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); } + catch( Exception e ) { assertEquals("Might be null accessing 'next'",e.getMessage()); } } @Test diff --git a/src/test/java/com/seaofnodes/simple/Chapter13Test.java b/src/test/java/com/seaofnodes/simple/Chapter13Test.java index 975946da..66e12e42 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter13Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter13Test.java @@ -291,7 +291,7 @@ else { } """); code.parse().opto().typeCheck().GCM(); - assertEquals("return Phi(Region,9,Top,Top,Top,Top);", code.print()); + assertEquals("return 9;", code.print()); } @@ -323,7 +323,7 @@ while( prime <= arg ) { return primeCount; """); code.parse().opto(); - assertEquals("return Phi(Region,0,Phi(Loop,1,Phi(Region,Phi_primeCount,Phi(Region,(Phi_primeCount+1),Phi_primeCount))));", code.print()); + assertEquals("return Phi(Region,0,Phi(Loop,1,Phi(Region,Phi_primeCount,Phi_primeCount,(Phi_primeCount+1))));", code.print()); assertEquals("0", Eval2.eval(code, 1)); // No primes 1 or below assertEquals("1", Eval2.eval(code, 2)); // 2 assertEquals("2", Eval2.eval(code, 3)); // 2, 3 diff --git a/src/test/java/com/seaofnodes/simple/Chapter15Test.java b/src/test/java/com/seaofnodes/simple/Chapter15Test.java index 6a22ff97..cb92277f 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter15Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter15Test.java @@ -1,6 +1,9 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.codegen.CodeGen; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.*; @@ -129,7 +132,7 @@ else { return rez; """); code.parse().opto(); - assertEquals("return Phi(Region,1.2,Phi(Region,2.3,.y));", code.print()); + assertEquals("return Phi(Region,1.2,.y,2.3);", code.print()); assertEquals("3.14", Eval2.eval(code, 0)); assertEquals("1.2", Eval2.eval(code, 1)); } @@ -154,7 +157,7 @@ if( iss[arg] ) rez = iss[arg][2].y; return rez;"""); code.parse().opto().typeCheck(); - assertEquals("return Phi(Region,Phi(Region,.y,1.2),1.2);", code.print()); + assertEquals("return Phi(Region,1.2,1.2,.y);", code.print()); assertEquals("3.14", Eval2.eval(code, 0)); try { Eval2.eval(code, 10); fail(); } // Fails AIOOBE in Eval2, no range checks catch( Exception e ) { assertEquals("Index 10 out of bounds for length 2",e.getMessage()); } @@ -212,44 +215,12 @@ return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6 } @Test - public void sieveOEratosthenes() { - CodeGen code = new CodeGen( -""" -int[] !ary = new int[arg], !primes = new int[arg]; -int nprimes = 0, p=2; -// Find primes while p^2 < arg -while( p*p < arg ) { - // skip marked non-primes - while( ary[p]==1 ) p = p + 1; - // p is now a prime - primes[nprimes] = p; nprimes = nprimes + 1; - // Mark out the rest non-primes - int i = p + p; - while( i < ary# ) { - ary[i] = 1; - i = i + p; - } - p = p + 1; -} -// Now just collect the remaining primes, no more marking -while( p < arg ) { - if( ary[p] == 0 ) { - primes[nprimes] = p; nprimes = nprimes + 1; - } - p = p + 1; -} -// Copy/shrink the result array -int[] !rez = new int[nprimes]; -int j = 0; -while( j < nprimes ) { - rez[j] = primes[j]; - j = j + 1; -} -return rez; -"""); + public void sieveOEratosthenes() throws IOException { + String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/sieve.smp")); + CodeGen code = new CodeGen(src+"return sieve(arg);"); code.parse().opto(); - assertEquals("return [int];", code.print()); - assertEquals("int[ 2,3,5,7,11,13,17,19]",Eval2.eval(code, 20)); + assertEquals("return [u32];", code.print()); + assertEquals("u32[ 2,3,5,7,11,13,17,19]",Eval2.eval(code, 20)); } @Test diff --git a/src/test/java/com/seaofnodes/simple/Chapter17bTest.java b/src/test/java/com/seaofnodes/simple/Chapter17bTest.java index da619ed3..e8f6e0bd 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter17bTest.java +++ b/src/test/java/com/seaofnodes/simple/Chapter17bTest.java @@ -370,7 +370,7 @@ var b = arg ? new Bar { next = (arg==2) ? new Bar{x=2;}; x=1; }; return b ? b.next ? b.next.x : b.x; // parses "b ? (b.next ? b.next.x : b.x) : 0" """); code.parse().opto(); - assertEquals("return Phi(Region,Phi(Region,.x,.x),0);", code.print()); + assertEquals("return Phi(Region,.x,0,.x);", code.print()); assertEquals("0", Eval2.eval(code, 0)); assertEquals("1", Eval2.eval(code, 1)); assertEquals("2", Eval2.eval(code, 2)); diff --git a/src/test/java/com/seaofnodes/simple/Chapter18Test.java b/src/test/java/com/seaofnodes/simple/Chapter18Test.java index dafd2f3a..7c0af028 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter18Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter18Test.java @@ -3,6 +3,7 @@ import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.codegen.CodeGen.Phase; import com.seaofnodes.simple.codegen.CodeGen; import org.junit.Ignore; import org.junit.Test; @@ -18,8 +19,7 @@ public class Chapter18Test { "int x=0; while(arg) { if(arg==2) while(1) { x++; } x++; arg--; } return x;" }; for( String src : sources ) { - CodeGen code = new CodeGen(src).parse().opto().typeCheck(); - code._start.buildLoopTree(code._stop); + CodeGen code = new CodeGen(src).driver(CodeGen.Phase.LoopTree); int[] nevers = {0}; code._stop.walk(n -> { if( n instanceof FunNode fun ) @@ -51,26 +51,18 @@ public class Chapter18Test { "val g = { int x -> x ? g(x-1)/305420988+x*305420988 : 1; }; "+ "return f(arg)+g(arg);"; CodeGen code = new CodeGen(src); - code.parse().opto().typeCheck(); - Node con = code._stop.walk(n -> n instanceof ConstantNode && - n._type==TypeInteger.constant(305420988) ? n : null); - assertNotNull(con); - Node[] uses = con._outputs.asAry(); - // Preserve a stacked Cast/Constant chain through scheduling. - Node cast = new CastNode(TypeInteger.BOT,code._start,con); - cast = new CastNode(TypeInteger.BOT,code._start,cast); - for( Node use : uses ) - for( int i=1; i<use.nIns(); i++ ) - if( use.in(i)==con ) use.setDef(i,cast); - code.GCM(); + code.driver(CodeGen.Phase.Schedule,"riscv","SystemV"); var owners = new java.util.IdentityHashMap<CFGNode,Boolean>(); code._stop.walk(n -> { - if( !(n instanceof CastNode) || !(n.in(1) instanceof CastNode) ) return null; + if( !(n instanceof com.seaofnodes.simple.node.cpus.riscv.AddIRISC add) || + !(add.in(1) instanceof com.seaofnodes.simple.node.cpus.riscv.LUI upper) || + ((TypeInteger)upper._con).value()+((add._imm12<<20)>>20)!=305420988 ) + return null; CFGNode fun = n.cfg0(); while( fun!=null && !(fun instanceof FunNode) ) fun = fun.idom(); assertTrue("Constant must belong to a function",fun instanceof FunNode); assertNull("Share one constant chain within each function",owners.put(fun,true)); - // Follow all parts of the constant chain, including shared inputs. + // Follow all parts of a machine constant, including shared inputs. var todo = new java.util.ArrayList<Node>(); var seen = new java.util.IdentityHashMap<Node,Boolean>(); todo.add(n); @@ -130,7 +122,7 @@ public class Chapter18Test { """ return 0; """); - code.parse().opto().typeCheck().GCM().localSched(); + code.driver(Phase.LocalSched); assertEquals("return 0;", code._stop.toString()); assertEquals("0", Eval2.eval(code, 2)); } @@ -177,11 +169,10 @@ return x2; {int -> int}? sq = { int x -> x*x; }; -return sq; """); code.parse().opto(); - assertEquals("Stop[ return { sq}; return (Parm_x(sq,int)*x); ]", code._stop.toString()); - assertEquals("{ int -> int #1}", Eval2.eval(code, 3)); + assertEquals("return (Parm_x(sq,int)*x);", code._stop.toString()); + //assertEquals("{ int -> int #1}", Eval2.eval(code, 3)); } @Test @@ -193,7 +184,7 @@ var sq = { int x -> }; return sq(arg)+sq(3); """); - code.parse().opto().typeCheck().GCM().localSched(); + code.driver(Phase.LocalSched); assertEquals("Stop[ return (#2+#2); return (Parm_x(sq,int)*x); ]", code._stop.toString()); assertEquals("13", Eval2.eval(code, 2)); } @@ -291,7 +282,7 @@ for(;;) { i2i = id(x); } """); - code.parse().opto().typeCheck().GCM().localSched(); + code.driver(Phase.LocalSched); assertEquals("Stop[ return #2; return Parm_i(x,int); ]", code._stop.toString()); assertEquals("3", Eval2.eval(code, 0)); } @@ -307,9 +298,9 @@ for(;;) { arg = x(3); } """); - code.parse().opto().typeCheck().GCM().localSched(); + code.driver(Phase.LocalSched); assertEquals("return Top;", code._stop.toString()); - assertEquals(null, Eval2.eval(code, 0)); + assertNull(Eval2.eval(code,0)); // Infinite loop: i2i remains null. } @@ -332,7 +323,7 @@ ps[0] = new Person; ps[1] = new Person; fcn(ps,1); """); - code.parse().opto().typeCheck().GCM().localSched(); + code.driver(Phase.LocalSched); assertEquals("return 0;", code._stop.toString()); assertEquals("0", Eval2.eval(code, 0)); } @@ -462,7 +453,7 @@ if (arg) i2i = null; if (i2i) return i2i(arg); return f2f(o)(1); """); - code.parse().opto().typeCheck().GCM().localSched(); + code.driver(Phase.LocalSched); assertEquals("Stop[ return Phi(Region,#2,1); return Parm_i(o,int); ]", code._stop.toString()); assertEquals("1", Eval2.eval(code, 2)); } @@ -478,7 +469,7 @@ struct Person { p.coffee_count += 1; return p.coffee_count; """); - code.parse().opto().typeCheck().GCM().localSched(); + code.driver(Phase.LocalSched); assertEquals("return 1;", 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 9bd75207..c53a8e11 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter19Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter19Test.java @@ -3,12 +3,14 @@ import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.codegen.CodeGen.Phase; import com.seaofnodes.simple.codegen.CodeGen; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; 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; public class Chapter19Test { @Test public void testPrintingFunctionLookup() { @@ -44,143 +46,108 @@ return 0; @Test - public void testString() { - CodeGen code = new CodeGen( -""" -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)+hashCode(s);"""); - code.parse().opto().typeCheck().GCM().localSched(); - 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)); + public void testString() throws IOException { + String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/stringHash.smp")); + CodeGen code = new CodeGen(src).parse().opto().typeCheck().GCM().localSched(); + assertEquals("Stop[ return Phi(Region,._hashCode,Phi(Region,123456789,Phi(Loop,0,(.[]+((Phi_hash<<5)-Phi_hash))))); return Phi(Region,1,0,0,1); ]", code._stop.toString()); + //assertEquals("-4898613127354160978", Eval2.eval(code, 2)); } @Test public void testBasic0() { - CodeGen code = new CodeGen("return 0;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return 0;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return 0;", code._stop.toString()); } @Test public void testBasic1() { - CodeGen code = new CodeGen("return arg+1;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg+1;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return (inc,arg);", code._stop.toString()); } @Test public void testBasic2() { - CodeGen code = new CodeGen("return -17;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return -17;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return -17;", code._stop.toString()); } @Test public void testBasic3() { - CodeGen code = new CodeGen("return arg==1;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg==1;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return (set==,(cmp,arg));", code._stop.toString()); } @Test public void testBasic4() { - CodeGen code = new CodeGen("return arg<<1;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg<<1;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return (shli,arg);", code._stop.toString()); } @Test public void testBasic5() { - CodeGen code = new CodeGen("return arg >> 1;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg >> 1;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return (sari,arg);", code._stop.toString()); } @Test public void testBasic6() { - CodeGen code = new CodeGen("return arg >>> 1;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg >>> 1;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return (shri,arg);", code._stop.toString()); } @Test public void testBasic7() { - CodeGen code = new CodeGen("return arg / 2;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg / 2;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return (div,arg,2);", code._stop.toString()); } @Test public void testBasic8() { - CodeGen code = new CodeGen("return arg * 6;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg * 6;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return (muli,arg);", code._stop.toString()); } @Test public void testBasic9() { - CodeGen code = new CodeGen("return arg & 2;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg & 2;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return (andi,arg);", code._stop.toString()); } @Test public void testBasic10() { - CodeGen code = new CodeGen("return arg | 2;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg | 2;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return (ori,arg);", code._stop.toString()); } @Test public void testBasic11() { - CodeGen code = new CodeGen("return arg ^ 2;").parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + CodeGen code = new CodeGen("return arg ^ 2;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return (xori,arg);", code._stop.toString()); } @Test public void testBasic12() { - 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()); + CodeGen code = new CodeGen("return arg + 2.0;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); + assertEquals("return (addf,(cvtf,arg),2.0f);", code._stop.toString()); } @Test public void testBasic13() { - 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()); + CodeGen code = new CodeGen("return arg - 2.0;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); + assertEquals("return (subf,(cvtf,arg),2.0f);", code._stop.toString()); } @Test public void testBasic14() { - 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()); + CodeGen code = new CodeGen("return arg * 2.0;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); + assertEquals("return (mulf,(cvtf,arg),2.0f);", code._stop.toString()); } @Test public void testBasic15() { - 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()); + CodeGen code = new CodeGen("return arg / 2.0;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); + assertEquals("return (mulf,(cvtf,arg),0.5f);", code._stop.toString()); } @Test @@ -189,7 +156,7 @@ hashCode(s)+hashCode(s);"""); """ int arg1 = arg + 1; return arg1 / arg;"""); - code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return (div,(inc,arg),arg);", code._stop.toString()); } @@ -200,18 +167,18 @@ return arg1 / arg;"""); int arg1 = arg + 1; return arg1 * arg; """); - code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return (mul,(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(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); - assertEquals("return (addf,(i2f8,arg),2.0f);", code._stop.toString()); +int a = arg; +return a + 2.0; +""" + ).driver(Phase.LocalSched,"x86_64_v2", "SystemV"); + assertEquals("return (addf,(cvtf,arg),2.0f);", code._stop.toString()); } @Test @@ -225,7 +192,7 @@ else { a = arg-3; } return a;"""); - code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return Phi(Region,(addi,arg),(addi,arg));", code.print()); } @@ -240,7 +207,7 @@ if( arg==1 ) else a=b+1; return a+b;"""); - code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return (add,(add,Phi(Region,(shli,arg),arg),arg),Phi(Region,4,5));", code.print()); } @@ -252,7 +219,7 @@ int sum=0; for( int i=0; i<arg; i++ ) sum += i; return sum;"""); - code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return Phi(Loop,0,(add,Phi_sum,Phi(Loop,0,(inc,Phi_i))));", code.print()); } @@ -262,42 +229,42 @@ return sum;"""); """ struct S { int a; !S? !c; }; return new S;"""); - code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); 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(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); 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(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); 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(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); 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(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); 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(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return .[];", code.print()); } @@ -314,7 +281,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(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return .[];", code.print()); } @@ -329,7 +296,7 @@ for( int i=0; i<A#; i++ ) for( int i=0; i<A#; i++ ) B[i] += A[i]; """); - code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return 0;", code.print()); } @@ -342,60 +309,25 @@ for( int i=0; i<A#; i++ ) A[i]++; return A[1]; """); - code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("return .[];", code.print()); } @Test - public void testNewton() { - CodeGen code = new CodeGen( -""" -// 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); -"""); - 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()); + public void testNewton() throws IOException { + String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/newtonFloat.smp")) + + "flt farg = arg; return test_sqrt(farg);"; + CodeGen code = new CodeGen(src).driver(Phase.LocalSched,"x86_64_v2", "SystemV"); + assertEquals("return Phi(Loop,(cvtf,arg),(mulf,(addf,(divf,cvtf,Phi_guess),Phi_guess),0.5f));", code.print()); }; @Test - public void sieveOfEratosthenes() { - CodeGen code = new CodeGen( -""" -var ary = new bool[arg], primes = new int[arg]; -var nprimes=0, p=0; -// Find primes while p^2 < arg -for( p=2; p*p < arg; p++ ) { - // skip marked non-primes - while( ary[p] ) p++; - // p is now a prime - primes[nprimes++] = p; - // Mark out the rest non-primes - for( int i = p + p; i < ary#; i += p ) - ary[i] = true; -} -// Now just collect the remaining primes, no more marking -for( ; p < arg; p++ ) - if( !ary[p] ) - primes[nprimes++] = p; -// Copy/shrink the result array -var !rez = new int[nprimes]; -for( int j=0; j<nprimes; j++ ) - rez[j] = primes[j]; -return rez; -"""); - 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)); + public void sieveOfEratosthenes() throws IOException { + String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/sieve.smp")); + CodeGen code = new CodeGen(src).driver(Phase.LocalSched,"x86_64_v2", "SystemV"); + assertEquals("return [u32];", code.print()); + //assertEquals("u32[ 2,3,5,7,11,13,17,19]",Eval2.eval(code, 20)); } @@ -406,7 +338,7 @@ return rez; val fcn = arg ? { int x -> x*x; } : { int x -> x+x; }; return fcn(2)*10 + fcn(3); """); - code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("Stop[ return (add,#2,(muli,#2)); return (mul,Parm_x($fun1,int),x); return (shli,Parm_x($fun2,int)); ]", code.print()); } @@ -417,7 +349,7 @@ return fcn(2)*10 + fcn(3); val sq = { int x -> x*x; }; return sq(arg) + sq(3); """); - code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); + code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("Stop[ return (add,#2,#2); return (mul,Parm_x(sq,int),x); ]", code.print()); } @@ -427,7 +359,7 @@ return sq(arg) + sq(3); Chapter18Test.CALL_MEMORY,Chapter18Test.RECURSIVE_MEMORY} ) { var code = new CodeGen(src).parse().opto().typeCheck(); var aliases = memoryPhis(code._stop); - code.instSelect(PORTS,cpu,"SystemV"); + code.loopTree().instSelect(cpu,"SystemV"); assertEquals(aliases,memoryPhis(code._stop)); code.GCM().localSched(); code._stop.walk(n -> { @@ -437,6 +369,7 @@ return sq(arg) + sq(3); assertEquals(2,((TypeTuple)nn._type)._types.length); assertTrue(nn.mem() instanceof MemMergeNode); assertNull(nn.mem().in(1)); // Partial allocation input. + nn.cacheRegs(code); MachNode mach = (MachNode)nn; assertNull(mach.regmap(1)); assertNotNull(mach.regmap(2)); @@ -480,7 +413,7 @@ return sq(arg) + sq(3); int before = p.x + arg; a.x=33; b.x=44; return before; - """).parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2","SystemV").GCM().localSched(); + """).parse().opto().typeCheck().loopTree().instSelect("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 index 104be01e..e63fb32e 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter20Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter20Test.java @@ -6,18 +6,47 @@ 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 testNeverReturnAllocation() { + String[] sources = { + // A real integer return merged with a synthetic exit. + "if(arg) while(1) {} return arg+1;", + // TOP must not widen a non-null pointer or constrain a float register. + "struct S { int x; }; !S !s=new S; if(arg) while(1) {} return s;", + "if(arg) while(1) {} return 1.5;", + // Several synthetic exits into the same return. + "if(arg==1) while(1) {} if(arg==2) while(1) {} return arg+3;", + // No real return, including a merge of only synthetic exits. + "while(1) { arg+=1; }", + "if(arg) while(1) {} while(1) {}", + // Loop-carried memory and scalar self-conflicts exercise spilling. + "struct S { int x; int y; }; !S !a=new S; if(arg) while(1) { a.x+=arg; a.y+=a.x; } return a.x+a.y;" + }; + for( String target : new String[]{"x86_64_v2","riscv","arm"} ) + for( String src : sources ) { + CodeGen code = new CodeGen(src).driver(CodeGen.Phase.RegAlloc,target,"SystemV"); + com.seaofnodes.simple.codegen.RegAllocTestSupport.checkRegisters(code); + assertNotNull(code._stop.walk(n -> { + if( n._type!=com.seaofnodes.simple.type.Type.TOP ) return null; + assertEquals(-1,code._regAlloc.regnum(n)); + for( var use : n.outs() ) + assertFalse(use instanceof com.seaofnodes.simple.node.SplitNode); + return n; + })); + code.encode(); + } + } + @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(); + .driver(CodeGen.Phase.RegAlloc,"x86_64_v2","SystemV"); int stores=0; for( var bb : code._cfg ) for( var node : bb.outs() ) @@ -76,10 +105,10 @@ return f(s); 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(); + code.driver(CodeGen.Phase.RegAlloc,cpu,os); 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)); + SpillStats.checkSpills(spills,code._regAlloc._spillScaled); if( stop!=null ) assertEquals(stop,code._stop.toString()); } @@ -90,7 +119,9 @@ return f(s); } @Test public void testAlloc0() { - testAllCPUs("return new u8[arg];", 1, "return [u8];"); + testTarget("return new u8[arg];","x86_64_v2","SystemV",4,"return [u8];"); + testTarget("return new u8[arg];","riscv","SystemV",5,"return [u8];"); + testTarget("return new u8[arg];","arm","SystemV",5,"return [u8];"); } @Test public void testBasic1() { @@ -115,9 +146,9 @@ val sqrt = { int x -> }; 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); + testTarget(src,"x86_64_v2", "SystemV",23,null); + testTarget(src,"riscv" , "SystemV",17,null); + testTarget(src,"arm" , "SystemV",18,null); } @Test @@ -136,15 +167,17 @@ val sqrt = { flt x -> 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,"x86_64_v2", "SystemV",23,null); + testTarget(src,"riscv" , "SystemV",18,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 .[];"); + testTarget(src,"x86_64_v2","SystemV",4,"return .[];"); + testTarget(src,"riscv","SystemV",8,"return .[];"); + testTarget(src,"arm","SystemV",9,"return .[];"); } @Test @@ -160,9 +193,9 @@ 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,.[]));"); + testTarget(src,"x86_64_v2", "SystemV",7,"return .[];"); + testTarget(src,"riscv" , "SystemV",7,"return (add,.[],(mul,.[],1000));"); + testTarget(src,"arm" , "SystemV",5,"return (add,.[],(mul,.[],1000));"); } @Test @@ -203,15 +236,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); + testTarget(src,"x86_64_v2", "SystemV",15,null); + testTarget(src,"riscv" , "SystemV", 10,null); + testTarget(src,"arm" , "SystemV", 10,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); + testTarget(src,"x86_64_v2","SystemV",1,null); + testTarget(src,"riscv","SystemV",2,null); + testTarget(src,"arm","SystemV",2,null); } @Test @@ -248,14 +283,132 @@ return arg; testTarget(src,"riscv" , "SystemV",2,null); testTarget(src,"arm" , "SystemV",2,null); } + // Original Chapter 20 allocation workload, kept fixed for cohort comparisons. + @Test + public void testBrainfuck() { + var program = "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.".getBytes(java.nio.charset.StandardCharsets.UTF_8); + var encoded = new StringBuilder("u8[] !program = new u8[").append(program.length).append("];"); + for (int i = 0; i < program.length; i++) { + int value = program[i] & 0xFF; + encoded.append("program[").append(i).append("] = ").append(value).append(";"); + } + + String src = encoded + """ + +int d = 0; +u8[] !output = new u8[0]; +u8[] !data = new u8[100]; + +for( int pc = 0; pc < program#; pc++ ) { + var command = program[pc]; + if (command == 62) { + d++; + } else if (command == 60) { + d--; + } else if (command == 43) { + data[d]++; + } else if (command == 45) { + data[d]--; + } else if (command == 46) { + // Output a byte; increase the output array size + var old = output; + output = new u8[output# + 1]; + for( int i = 0; i < old#; i++ ) + output[i] = old[i]; + output[old#] = data[d]; // Add the extra byte on the end + } else if (command == 44) { + data[d] = 42; + } else if (command == 91) { + if (data[d] == 0) { + for( int d = 1; d > 0; ) { + command = program[++pc]; + if (command == 91) d++; + if (command == 93) d--; + } + } + } else if (command == 93) { + if (data[d]) { + for( int d = 1; d > 0; ) { + command = program[--pc]; + if (command == 93) d++; + if (command == 91) d--; + } + } + } +} +return output; +"""; + testTarget(src,"x86_64_v2", "SystemV",40,null); + testTarget(src,"riscv" , "SystemV",146,null); + testTarget(src,"arm" , "SystemV",34,null); + //assertEquals("Hello World!\n", Eval2.eval(code, 0, 10000)); + } + // Original Chapter 20 allocation workload, kept fixed for cohort comparisons. + @Test + public void testMergeSort() { + String src = +""" +// based on the top-down version from https://en.wikipedia.org/wiki/Merge_sort + +val merge_sort = { int[] !a, int[] !b, int n -> + copy_array(a, 0, n, b); + split_merge(a, 0, n, b); +}; + +val split_merge = { int[] !b, int begin, int end, int[] a -> + if (end - begin <= 1) + return 0; + int middle = (end + begin) / 2; + split_merge(a, begin, middle, b); + split_merge(a, middle, end, b); + merge(b, begin, middle, end, a); + return 0; +}; + +val merge = { int[] !b, int begin, int middle, int end, int[] a -> + int i = begin, j = middle; + + for (int k = begin; k < end; k++) { + // && and || + bool cond = false; + if (i < middle) { + if (j >= end) cond = true; + else if (a[i] <= a[j]) cond = true; + } + if (cond) b[k] = a[i++]; + else b[k] = a[j++]; + } +}; + +val copy_array = { int[] !a, int begin, int end, int[] b -> + for (int k = begin; k < end; k++) + b[k] = a[k]; +}; + +int[] !a = new int[arg]; +int[] !b = new int[a#]; + +for (int i = 0; i < a#; i++) + a[i] = a# - i; + +merge_sort(a, b, a#); + +return a; +"""; + testTarget(src,"x86_64_v2", "SystemV",52,null); + testTarget(src,"riscv" , "SystemV",44,null); + testTarget(src,"arm" , "SystemV",41,null); +//assertEquals("int[ 1,2,3,4,5,6,7,8,9,10,11]", Eval2.eval(code, 11)); + } + @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(); + var code = new CodeGen(src).parse().opto().typeCheck().loopTree() + .instSelect(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. diff --git a/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java b/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java new file mode 100644 index 00000000..bc300217 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java @@ -0,0 +1,55 @@ +package com.seaofnodes.simple; + +import java.io.IOException; +import java.nio.file.*; +import org.junit.Test; + +// Revised Chapter 20 examples introduced with native encoding. +public class Chapter21AllocTest { + @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); +"""; + Chapter21Test.testCPU(src,"x86_64_v2", "Win64" ,23,null); + Chapter21Test.testCPU(src,"riscv" , "SystemV",19,null); + Chapter21Test.testCPU(src,"arm" , "SystemV",19,null); + } + + @Test + public void testNewtonFloat() throws IOException { + String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/newtonFloat.smp")) + + "flt farg = arg; return test_sqrt(farg) + test_sqrt(farg+2.0);"; + Chapter21Test.testCPU(src,"x86_64_v2", "SystemV",39,null); + Chapter21Test.testCPU(src,"riscv" , "SystemV",17,null); + Chapter21Test.testCPU(src,"arm" , "SystemV",18,null); + } + + @Test + public void testArray1() throws IOException { + String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/array1.smp")); + Chapter21Test.testCPU(src,"x86_64_v2", "SystemV",-1,"return .[];"); + Chapter21Test.testCPU(src,"riscv" , "SystemV", 8,"return (add,.[],(mul,.[],1000));"); + Chapter21Test.testCPU(src,"arm" , "SystemV", 5,"return (add,.[],(mul,.[],1000));"); + } + + + @Test + public void testString() throws IOException { + String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/stringHash.smp")); + Chapter21Test.testCPU(src,"x86_64_v2", "SystemV", 9,null); + Chapter21Test.testCPU(src,"riscv" , "SystemV", 3,null); + Chapter21Test.testCPU(src,"arm" , "SystemV", 1,null); + } + +} diff --git a/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/src/test/java/com/seaofnodes/simple/Chapter21Test.java new file mode 100644 index 00000000..a51ae3bc --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/Chapter21Test.java @@ -0,0 +1,532 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.isa.eval.EvalRisc5; + +import com.seaofnodes.isa.eval.EvalArm64; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.node.cpus.arm.arm; +import com.seaofnodes.simple.node.cpus.riscv.riscv; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.Ignore; +import org.junit.Test; +import static org.junit.Assert.*; + +public class Chapter21Test { + + @Test public void testReadBeforeConditionalStore() { + var code = new CodeGen(""" + struct S { int x; }; !S !a=new S; !S !b=new S; + a.x=10; b.x=20; !S !p=a; if(arg&1) p=b; + int sum=0; + for(int i=0; i<6; i++) { + sum+=a.x; + if(i==2) p.x=30; + else if(i==4) b.x=40; + } + return sum; + """).parse().opto(); + // The i==2 read still sees 10: three reads of 10, then three of 30. + assertEquals("120",Eval2.eval(code,0)); + assertEquals("60",Eval2.eval(code,1)); + } + + + @Test public void testArmBitfieldExtracts() { + // Independent SXT/UXT instruction words, plus ASR, LSR and LSL aliases. + int[] ops = {0x93401c20,0xd3401c20,0x93403c20,0xd3403c20, + 0x93407c20,0xd3407c20,0x9344fc20,0xd344fc20,0xd37cec20}; + long value = 0xa5a5a5a580008080L; + long[] expected = {(byte)value,value&255,(short)value,value&65535, + (int)value,value&0xffffffffL,value>>4,value>>>4,value<<4}; + for( int i=0; i<ops.length; i++ ) { + EvalArm64 cpu = new EvalArm64(new byte[1<<16],1<<15); + cpu.st4(0,ops[i]); cpu.regs[1]=value; + assertEquals(0,cpu.step(1)); + assertEquals(expected[i],cpu.regs[0]); + } + } + + + @Test public void testReturnedFunctionEncoding() throws IOException { + String src = "if(arg) return {->42;}; return {->43;};"; + CodeGen code = new CodeGen(src).driver("riscv","SystemV",null); + for( int arg : new int[]{0,1} ) { + byte[] image = new byte[1<<16]; + byte[] bits = code._encoding.bits(); + System.arraycopy(bits,0,image,0,bits.length); + EvalRisc5 cpu = new EvalRisc5(image,1<<15); + cpu._pc = code._encoding._opStart[code.link(code._main)._nid]; + cpu.regs[riscv.A0] = arg; + assertEquals(0,cpu.step(100)); + assertEquals(0,cpu._pc); + cpu._pc = (int)cpu.regs[riscv.A0]; + assertEquals(0,cpu.step(100)); + assertEquals(0,cpu._pc); + assertEquals(arg==0 ? 43 : 42,cpu.regs[riscv.A0]); + } + // Anonymous bodies need code, but no exported ELF name. + new CodeGen(src).driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,"build/objs/anonymous.o"); + } + + @Test public void testArmFloatMemory() throws IOException { + for( String type : new String[]{"f32","f64"} ) { + String src = type+"[] !a=new "+type+"[3]; a[arg]=1.25; return a[1];"; + CodeGen code = new CodeGen(src).driver("arm","SystemV",null); + byte[] image = new byte[1<<20]; + byte[] bits = code._encoding.bits(); + System.arraycopy(bits,0,image,0,bits.length); + EvalArm64 cpu = new EvalArm64(image,1<<16); + cpu.regs[arm.X0]=1; + assertEquals(0,cpu.step(1000)); + assertEquals(1.25,cpu.fregs[0],0); + } + // Independently exercise both SIMD widths and addressing modes. Keep + // the GPR value different so accidentally reading that bank is visible. + for( boolean wide : new boolean[]{false,true} ) + for( boolean indexed : new boolean[]{false,true} ) { + EvalArm64 cpu = new EvalArm64(new byte[512],256); + cpu.regs[1]=128; cpu.regs[2]=16; cpu.regs[3]=99; + cpu.fregs[3]=-13.25; + int store = (wide ? 0xFD000000 : 0xBD000000) | ((16/(wide?8:4))<<10) | (1<<5) | 3; + if( indexed ) store=(wide ? 0xFC226823 : 0xBC226823); // STR D3/S3,[X1,X2] + cpu.st4(0,store); + cpu.st4(4,store | (1<<22)); // LDR to the same SIMD register. + assertEquals(0,cpu.step(1)); + assertEquals(-13.25,wide ? cpu.ld8f(144) : cpu.ld4f(144),0); + cpu.fregs[3]=0; + assertEquals(0,cpu.step(1)); + assertEquals(-13.25,cpu.fregs[3],0); + } + } + + + @Test public void testNarrowStores() throws IOException { + for( String type : new String[]{"i8","u8","i16","u16"} ) { + String src = "struct S { "+type+" x; }; !S !s = new S; s.x = arg; return 0;"; + CodeGen code = new CodeGen(src).driver("riscv","SystemV",null); + int stores=0; + for( var bb : code._cfg ) + for( var node : bb.outs() ) + if( node instanceof com.seaofnodes.simple.node.cpus.riscv.StoreRISC st ) { + stores++; + assertEquals("Byte/short stores have no FP encoding",-1,st.regmap(4).nextReg((short)31)); + } + assertEquals(1,stores); + byte[] image = new byte[1<<20]; + byte[] bits = code._encoding.bits(); + System.arraycopy(bits,0,image,0,bits.length); + EvalRisc5 cpu = new EvalRisc5(image,1<<16); + cpu.regs[riscv.A0] = 0x8765; + assertEquals(0,cpu.step(100)); + assertEquals(type.endsWith("8") ? 0x65 : 0x8765, + type.endsWith("8") ? cpu.ld1z(1<<16) : cpu.ld2z(1<<16)); + code = new CodeGen(src).driver(CodeGen.Phase.Encoding,"x86_64_v2",TestC.CALL_CONVENTION); + 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("Byte/short stores have no XMM encoding",-1,st.regmap(4).nextReg((short)15)); + } + assertEquals(1,stores); + } + } + + + @Test public void testArmCallInstructions() { + EvalArm64 cpu = new EvalArm64(new byte[512],256); + for( int delta : new int[]{4,32,-4} ) { + cpu._pc = 128; + for( int i=0; i<32; i++ ) cpu.regs[i]=1000+i; + cpu.st4(128,0x94000000 | ((delta>>2)&0x03FFFFFF)); // BL + assertEquals(0,cpu.step(1)); + assertEquals(128+delta,cpu._pc); + for( int i=0; i<32; i++ ) assertEquals(i==30 ? 132 : 1000+i,cpu.regs[i]); + } + for( int rn : new int[]{9,30} ) + for( boolean call : new boolean[]{false,true} ) { + cpu._pc=4; + cpu.regs[rn]=64; + cpu.st4(4,(call ? 0xD63F0000 : 0xD65F0000) | (rn<<5)); // BLR / RET + assertEquals(0,cpu.step(1)); + assertEquals(64,cpu._pc); + if( call ) assertEquals(8,cpu.regs[30]); + } + } + + @Test public void testArmImmediateArithmetic() { + EvalArm64 cpu = new EvalArm64(new byte[16],16); + // Unsigned imm12, including bit 11 and the optional 12-bit shift. + for( int[] test : new int[][]{{0x91200020,2148},{0xD1200020,-1948}, + {0x91400420,4196},{0xD1400420,-3996}} ) { + cpu._pc=0; cpu.regs[1]=100; + cpu.N=true; cpu.Z=false; cpu.C=true; cpu.V=true; + cpu.st4(0,test[0]); + assertEquals(0,cpu.step(1)); + assertEquals(test[1],cpu.regs[0]); + assertTrue(cpu.N); assertFalse(cpu.Z); assertTrue(cpu.C); assertTrue(cpu.V); + } + } + + @Test public void testArmCallsAndFrames() throws IOException { + String src = """ + val sum = { int n -> + if( n<=0 ) return 1; + return sum(n-1)+n; + }; + val run = { int n -> return sum(n)+sum(n+1); }; + """; + assertTrue("Ordinary calls must survive optimization",checkArmCalls(src,"run",4,27)>0); + } + + private static int checkArmCalls(String src, String entryName, int arg, int result) throws IOException { + CodeGen code = new CodeGen(src).driver("arm","SystemV",null); + byte[] image = new byte[1<<20]; + System.arraycopy(code._encoding.bits(),0,image,0,code._encoding.bits().length); + EvalArm64 cpu = new EvalArm64(image,1<<16); + boolean entry=false; + int frames=0, calls=0; + for( var bb : code._cfg ) { + if( bb instanceof com.seaofnodes.simple.node.FunNode fun ) { + int off = code._encoding._opStart[fun._nid]; + if( entryName.equals(fun._name) ) { cpu._pc=off; entry=true; } + int frame=fun._frameAdjust; + assertEquals(0,frame&15); + if( frame>0 ) { + frames++; + assertEquals(0xD10003FF | (frame<<10),cpu.ld4s(off)); // SUB SP,SP,#bytes + } + } + if( bb instanceof com.seaofnodes.simple.node.ReturnNode ret ) { + int off=code._encoding._opStart[ret._nid]; + int frame=ret.fun()._frameAdjust; + if( frame>0 ) { + assertEquals(0x910003FF | (frame<<10),cpu.ld4s(off)); // ADD SP,SP,#bytes + off+=4; + } + assertEquals(0xD65F03C0,cpu.ld4s(off)); // RET X30 + } + if( bb instanceof com.seaofnodes.simple.node.cpus.arm.CallARM ) calls++; + } + assertTrue("Must execute the program entry",entry); + assertTrue("Exercise stack frames",frames>0); + for( int i=19; i<=29; i++ ) cpu.regs[i]=1000+i; + cpu.regs[0]=arg; + assertEquals(0,cpu.step(10000)); + assertEquals("Must return to the harness",0,cpu._pc); + assertEquals(result,cpu.regs[0]); + assertEquals(1<<16,cpu.regs[31]); + for( int i=19; i<=29; i++ ) assertEquals(1000+i,cpu.regs[i]); + return calls; + } + + + @Test public void testArmSubtractRegisters() { + EvalArm64 cpu = new EvalArm64(new byte[16],16); + cpu.st4(0,0xCB020020); // SUB X0,X1,X2, no shift or flag update. + for( long[] pair : new long[][]{{7,3},{3,7},{Long.MIN_VALUE,1},{1L<<40,3}} ) { + cpu._pc = 0; + cpu.regs[1] = pair[0]; cpu.regs[2] = pair[1]; + cpu.N=true; cpu.Z=false; cpu.C=true; cpu.V=true; + assertEquals(0,cpu.step(1)); + assertEquals(pair[0]-pair[1],cpu.regs[0]); + assertTrue(cpu.N); assertFalse(cpu.Z); assertTrue(cpu.C); assertTrue(cpu.V); + } + } + + + @Test public void testCoalescing() { com.seaofnodes.simple.codegen.RegAllocTestSupport.coalescing(); } + @Test public void testCopyForward() throws Exception { com.seaofnodes.simple.codegen.RegAllocTestSupport.copyForward(); } + @Test public void testRisc64BitStore() { + byte[] mem = new byte[24]; + EvalRisc5 cpu = new EvalRisc5(mem,mem.length); + check64BitStore(mem,cpu::st8,cpu::ld8); + } + + @Test public void testArm64BitStore() { + byte[] mem = new byte[24]; + EvalArm64 cpu = new EvalArm64(mem,mem.length); + check64BitStore(mem,cpu::st8,cpu::ld8); + } + + // Check bytes independently of ld8, including overwrite and adjacent memory. + private static void check64BitStore(byte[] mem, + java.util.function.BiConsumer<Integer,Long> store, + java.util.function.IntToLongFunction load) { + java.util.Arrays.fill(mem,(byte)0xA5); + for( long value : new long[]{0x0123456789ABCDEFL,0xFEDCBA9876543210L, + -1L,0L,Long.MIN_VALUE,Long.MAX_VALUE} ) { + store.accept(8,value); + for( int i=0; i<mem.length; i++ ) { + int expected = i>=8 && i<16 ? (int)(value >>> (8*(i-8)))&0xFF : 0xA5; + assertEquals("byte "+i,expected,mem[i]&0xFF); + } + assertEquals(value,load.applyAsLong(8)); + } + } + + + @Test @Ignore + public void testJig() throws IOException { + String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/jig.smp")); + testCPU(src,"x86_64_v2", "Win64" ,-1,null); + testCPU(src,"riscv" , "SystemV",-1,null); + testCPU(src,"arm" , "SystemV",-1,null); + } + + static void testCPU( String src, String cpu, String os, int spills, String stop ) { + CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Encoding,cpu,os); + com.seaofnodes.simple.codegen.RegAllocTestSupport.checkRegisters(code); + SpillStats.record(code,"Chapter21",cpu,os); + SpillStats.checkSpills(spills,code._regAlloc._spillScaled); + if( stop != null ) + assertEquals(stop, code._stop.toString()); + } + + + @Test public void testBasic1() { + String src = "return arg | 2;"; + testCPU(src,"x86_64_v2", "SystemV",1,"return (ori,mov(arg));"); + testCPU(src,"riscv" , "SystemV",0,"return ( arg | #2 );"); + testCPU(src,"arm" , "SystemV",0,"return (ori,arg);"); + } + + @Test public void testInfinite() { + String src = "struct S { int i; }; !S !s = new S; while(1) s.i++;"; + testCPU(src,"x86_64_v2", "SystemV",0,"return Top;"); + testCPU(src,"riscv" , "SystemV",0,"return Top;"); + testCPU(src,"arm" , "SystemV",0,"return Top;"); + } + + @Test + public void testArray1() throws IOException { + String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/array1.smp")); + testCPU(src,"x86_64_v2", "SystemV",-1,"return .[];"); + testCPU(src,"riscv" , "SystemV", 7,"return (add,.[],(mul,.[],1000));"); + testCPU(src,"arm" , "SystemV", 5,"return (add,.[],(mul,.[],1000));"); + } + + @Test + public void testAntiDeps1() throws IOException { + String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/antiDep1.smp")); + testCPU(src,"x86_64_v2", "SystemV", 7,"return mov(mov(S));"); + testCPU(src,"riscv" , "SystemV",10,"return mov(mov(S));"); + testCPU(src,"arm" , "SystemV",10,"return mov(mov(S));"); + } + + @Test + public void testString() throws IOException { + String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/stringHash.smp")); + testCPU(src,"x86_64_v2", "SystemV", 9,null); + testCPU(src,"riscv" , "SystemV", 3,null); + testCPU(src,"arm" , "SystemV", 1,null); + } + + @Test public void testStringExport() throws IOException { + TestC.run("stringHash", 9); + } + + @Test public void testLoop2() throws IOException { + String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/loop2.smp")); + testCPU(src,"x86_64_v2", "Win64" ,0,"return (inc,Phi(Loop,0,inc));"); + testCPU(src,"riscv" , "SystemV",0,"return ( Phi(Loop,0,addi) + #1 );"); + testCPU(src,"arm" , "SystemV",0,"return (inc,Phi(Loop,0,inc));"); + } + + @Test public void testNewtonExport() throws IOException { + String result = """ +0 0.000000 (0) +1 1.000000 (0) +2 1.414214 (2.22045e-16) +3 1.732051 (0) +4 2.000000 (0) +5 2.236068 (0) +6 2.449490 (0) +7 2.645751 (0) +8 2.828427 (4.44089e-16) +9 3.000000 (0) +"""; + TestC.run("newtonFloat",result,34); + + EvalRisc5 R5 = TestRisc5.build("newtonFloat", 0, 10, false); + R5.fregs[riscv.FA0 - riscv.F_OFFSET] = 3.0; + int trap_r5 = R5.step(1000); + assertEquals(0,trap_r5); + // Return register A0 holds fib(8)==55 + assertEquals(1.732051,R5.fregs[riscv.FA0 - riscv.F_OFFSET], 0.00001); + + // arm + EvalArm64 A5 = TestArm64.build("newtonFloat", 0, 10, false); + A5.fregs[arm.D0 - arm.D_OFFSET] = 3.0; + int trap_arm = A5.step(1000); + assertEquals(0,trap_arm); + assertEquals(1.732051, A5.fregs[arm.D0 - arm.D_OFFSET], 0.00001); + } + + + @Test public void testSieve() throws IOException { + // The primes + int[] primes = new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, }; + SB sb = new SB().p(primes.length).p("["); + for( int prime : primes ) + sb.p(prime).p(", "); + String sprimes = sb.p("]").toString(); + + // Compile, link against native C; expect the above string of primes to be printed out by C + TestC.run("sieve",sprimes, 185); + + // Evaluate on RISC5 emulator; expect return of an array of primes in + // the simulated heap. + EvalRisc5 R5 = TestRisc5.build("sieve", 100, 92, false); + int trap = R5.step(10000); + assertEquals(0,trap); + // Return register A0 holds sieve(100) + int ary = (int)R5.regs[riscv.A0]; + // Memory layout starting at ary(length,pad, prime1, primt2, prime3, prime4) + assertEquals(primes.length, R5.ld4s(ary)); + for( int i=0; i<primes.length; i++ ) + assertEquals(primes[i], R5.ld4s(ary + 4 + i*4)); + + // Evaluate on ARM5 emulator; expect return of an array of primes in + // the simulated heap. + EvalArm64 A5 = TestArm64.build("sieve", 100, 91, false); + int trap_arm = A5.step(10000); + assertEquals(0, trap_arm); + int ary_arm = (int)A5.regs[arm.X0]; + // Memory layout starting at ary(length,pad, prime1, primt2, prime3, prime4) + assertEquals(primes.length, A5.ld4s(ary_arm)); + for( int i = 0; i<primes.length; i++ ) + assertEquals(primes[i], A5.ld4s(ary_arm + 4 + i * 4)); + } + + @Test public void testFibExport() throws IOException { + String fib = "[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]"; + TestC.run("fib", fib, 24); + + EvalRisc5 R5 = TestRisc5.build("fib", 9, 16, false); + int trap = R5.step(100); + assertEquals(0,trap); + // Return register A0 holds fib(8)==55 + assertEquals(55,R5.regs[riscv.A0]); + + // arm + EvalArm64 A5 = TestArm64.build("fib", 9, 16, false); + int trap_arm = A5.step(100); + assertEquals(0,trap_arm); + // Return register X0 holds fib(8)==55 + assertEquals(55, A5.regs[arm.X0]); + } + + @Test public void testPerson() throws IOException { + String person = "6\n"; + TestC.run("person", person, 0); + + // Memory layout starting at PS: + int ps = 1<<16; // Person array pointer starts at heap start + // Person[3] = { len,pad,P0,P1,P2 }; // sizeof = 4*8 + // P0 = { age } // sizeof=8 + int p0 = ps+4*8+0*8; + // P1 = { age } // sizeof=8 + int p1 = ps+4*8+1*8; + // P2 = { age } // sizeof=8 + int p2 = ps+4*8+2*8; + EvalRisc5 R5 = TestRisc5.build("person", ps, 0, false); + R5.regs[riscv.A1] = 1; // Index 1 + R5.st8(ps,3); // Length + R5.st8(ps+1*8,p0); + R5.st8(ps+2*8,p1); + R5.st8(ps+3*8,p2); + R5.st8(p0, 5); // age= 5 + R5.st8(p1,17); // age=17 + R5.st8(p2,60); // age=60 + + int trap = R5.step(100); + assertEquals(0,trap); + assertEquals( 5+0,R5.ld8(p0)); + assertEquals(17+1,R5.ld8(p1)); + assertEquals(60+0,R5.ld8(p2)); + + EvalArm64 A5 = TestArm64.build("person", ps, 0, false); + A5.regs[arm.X1] = 1; // Index 1 + A5.st8(ps, 3); + A5.st8(ps+1*8,p0); + A5.st8(ps+2*8,p1); + A5.st8(ps+3*8,p2); + A5.st8(p0, 5); // age= 5 + A5.st8(p1,17); // age=17 + A5.st8(p2,60); // age=60 + + int trap_arm = A5.step(100); + assertEquals(0,trap_arm); + assertEquals( 5+0, A5.ld8(p0)); + assertEquals(17+1, A5.ld8(p1)); + assertEquals(60+0, A5.ld8(p2)); + } + + @Test public void testArgCount() throws IOException { + // Test passes more args than registers in Sys5, which is far far more + // than what Win64 allows - so Win64 gets a lot more spills here. + String arg_count = "191.000000\n"; + TestC.run("arg_count", arg_count, + TestC.CALL_CONVENTION.equals("Win64") ? 32 : 9); + + + EvalRisc5 R5 = TestRisc5.build("no_stack_arg_count", 0, 0, false); + + // Todo: handle stack(imaginary stack in emulator) + // pass in float arguments + R5.fregs[riscv.FA0 - riscv.F_OFFSET] = 1.1; + R5.fregs[riscv.FA1 - riscv.F_OFFSET] = 1.1; + R5.fregs[riscv.FA2 - riscv.F_OFFSET] = 1.1; + R5.fregs[riscv.FA3 - riscv.F_OFFSET] = 1.1; + R5.fregs[riscv.FA4 - riscv.F_OFFSET] = 1.1; + R5.fregs[riscv.FA5 - riscv.F_OFFSET] = 1.1; + R5.fregs[riscv.FA6 - riscv.F_OFFSET] = 1.1; + R5.fregs[riscv.FA7 - riscv.F_OFFSET] = 1.1; + + // a0 is passed in arg + R5.regs[riscv.A1] = 2; + R5.regs[riscv.A2] = 2; + R5.regs[riscv.A3] = 2; + R5.regs[riscv.A4] = 2; + R5.regs[riscv.A5] = 2; + R5.regs[riscv.A6] = 2; + R5.regs[riscv.A7] = 2; + + int trap = R5.step(100); + assertEquals(0,trap); + + double result = R5.fregs[riscv.FA0 - riscv.F_OFFSET]; + + assertEquals(22.8, result, 0.00001); + + // arm + EvalArm64 A5 = TestArm64.build("no_stack_arg_count", 0, 0, false); + + A5.fregs[arm.D0 - arm.D_OFFSET] = 1.1; + A5.fregs[arm.D1 - arm.D_OFFSET] = 1.1; + A5.fregs[arm.D2 - arm.D_OFFSET] = 1.1; + A5.fregs[arm.D3 - arm.D_OFFSET] = 1.1; + A5.fregs[arm.D4 - arm.D_OFFSET] = 1.1; + A5.fregs[arm.D5 - arm.D_OFFSET] = 1.1; + A5.fregs[arm.D6 - arm.D_OFFSET] = 1.1; + A5.fregs[arm.D7 - arm.D_OFFSET] = 1.1; + + A5.regs[arm.X1] = 2; + A5.regs[arm.X2] = 2; + A5.regs[arm.X3] = 2; + A5.regs[arm.X4] = 2; + A5.regs[arm.X5] = 2; + A5.regs[arm.X6] = 2; + A5.regs[arm.X7] = 2; + + int trap_arm = A5.step(100); + assertEquals(0,trap_arm); + + double result1 = A5.fregs[arm.D0 - arm.D_OFFSET]; + assertEquals(22.8, result1, 0.00001); + } +} diff --git a/src/test/java/com/seaofnodes/simple/Compile.java b/src/test/java/com/seaofnodes/simple/Compile.java new file mode 100644 index 00000000..088055f9 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/Compile.java @@ -0,0 +1,65 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.node.FunNode; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import org.junit.Ignore; +import org.junit.Test; +import static org.junit.Assert.*; + +public class Compile { + + private static Process run(String simple, String c) throws IOException, InterruptedException { + boolean USE_WSL = System.getProperty("os.name").startsWith("Windows"); + CodeGen code = new CodeGen(simple); + code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched().regAlloc().encode(); + if (c != null) for (var n:code._start._outputs) if (n instanceof FunNode f && f._name.equals("main")) f._name = "simple_main"; + code.exportELF("test.o"); + var params = new ArrayList<String>(); + if (USE_WSL) params.add("wsl.exe"); + params.add("gcc"); + params.add("-o"); + params.add("test"); + if (c != null) { + Files.writeString(Path.of("test.c"), c); + params.add("test.c"); + } + params.add("test.o"); + Process p = Runtime.getRuntime().exec(params.toArray(String[]::new)); + assertEquals(0, p.waitFor()); + return Runtime.getRuntime().exec(USE_WSL ? new String[]{"wsl.exe", "./test"}:new String[]{"./test"}); + } + + @Test + @Ignore + public void testCompile() throws IOException, InterruptedException { + var p = run(""" + val testMy = { flt x -> + flt guess = x; + while( 1 ) { + flt next = (x/guess + guess)/2; + if( next == guess ) return guess; + guess = next; + } + }; + """,""" + + #include <stdio.h> + + extern double testMy(double); + + int main(int argc, char** argv) { + printf("%f\\n", testMy(2.0)); + return 0; + } + + """); + assertEquals(0, p.waitFor()); + assertEquals("1.414214\n", new String(p.getInputStream().readAllBytes(), StandardCharsets.UTF_8)); + } + +} diff --git a/src/test/java/com/seaofnodes/simple/Eval2.java b/src/test/java/com/seaofnodes/simple/Eval2.java index 3312ec6b..a855350e 100644 --- a/src/test/java/com/seaofnodes/simple/Eval2.java +++ b/src/test/java/com/seaofnodes/simple/Eval2.java @@ -117,6 +117,7 @@ public abstract class Eval2 { SB trace = null; // new SB(); // TRACE, set to null for off, new SB() for on // Force local scheduling phase if( code._phase.ordinal() < CodeGen.Phase.TypeCheck .ordinal() ) code.typeCheck(); + if( code._phase.ordinal() < CodeGen.Phase.LoopTree .ordinal() ) code.loopTree(); if( code._phase.ordinal() < CodeGen.Phase.Schedule .ordinal() ) code.GCM(); if( code._phase.ordinal() < CodeGen.Phase.LocalSched.ordinal() ) code.localSched(); // Set global, so don't have to pass everywhere @@ -159,6 +160,7 @@ public abstract class Eval2 { switch( prior ) { case StartNode start: F = new Frame(F); break; // Frame for the call to main, as-if Called case CallNode call : BB = call(call); break; + case NeverNode never: BB = never.cproj(0); break; // Synthetic exit is never taken. case IfNode iff : Object p = val(iff.pred()); BB = iff.cproj( p==null || (p instanceof Long x && x==0L) ? 1 : 0); break; case ReturnNode ret : if( clj(ret.rpc()).isStop() ) return exit(ret); else BB = ret(ret); break; case FunNode fun : if( loopCnt++ > timeout ) return null; break; // Timeout diff --git a/src/test/java/com/seaofnodes/simple/Main.java b/src/test/java/com/seaofnodes/simple/Main.java index f3a6784c..589c043b 100644 --- a/src/test/java/com/seaofnodes/simple/Main.java +++ b/src/test/java/com/seaofnodes/simple/Main.java @@ -5,7 +5,6 @@ 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/MemoryEncodingTest.java b/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java new file mode 100644 index 00000000..1ce2cd34 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java @@ -0,0 +1,180 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.isa.eval.EvalRisc5; + +import com.seaofnodes.isa.eval.EvalArm64; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegAllocTestSupport; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.cpus.arm.arm; +import com.seaofnodes.simple.node.cpus.riscv.riscv; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.Test; +import static org.junit.Assert.*; + +// Execution regressions are separate from the fixed spill-measurement cohorts. +public class MemoryEncodingTest { + @Test public void testLoopLoadSearch() { + String[] sources = { + """ + u8[] !program=new u8[106]; u8[] !out=new u8[0]; + int sum=0; + for(int i=0; i<arg; i++) { + sum+=program#; + if(i==1) out=new u8[i+1]; + else if(i==3) out=new u8[2]; + } + return sum+out#; + """, + // One possibly aliasing arm must reject the unchanged-memory proof. + """ + struct S { int x; }; !S !a=new S; !S !b=new S; + a.x=10; b.x=20; !S !p=a; if(arg&1) p=b; + int sum=0; + for(int i=0; i<6; i++) { + if(i==2) p.x=30; + else if(i==4) b.x=40; + sum+=a.x; + } + return sum; + """, + // Unchanged memory does not imply an unchanged loop-carried pointer. + """ + struct S { int x; }; !S !a=new S; !S !b=new S; + a.x=10; b.x=20; !S !p=a; int sum=0; + for(int i=0; i<6; i++) { + if(i==2) a.x=30; + sum+=p.x; + p=b; if(i&1) p=a; + } + return sum; + """, + // All arms fold, but their values differ: build a value Phi. + """ + struct S { int x; }; !S !a=new S; a.x=1; int sum=0; + for(int i=0; i<6; i++) { + sum+=a.x; + if(i&1) a.x=2; + else if(i==2) a.x=3; + else a.x=4; + } + return sum; + """ + }; + for( int p=0; p<sources.length; p++ ) { + CodeGen code=new CodeGen(sources[p]).parse().opto(); + if( p==0 ) assertNull(code._stop.walk(n -> + n instanceof LoadNode ld && ld.ptr() instanceof ProjNode ptr && + ptr.in(0) instanceof NewNode nn && + nn.size()._type==com.seaofnodes.simple.type.TypeInteger.constant(110) ? ld : null)); + for( int arg=0; arg<7; arg++ ) { + long expected=switch(p) { + case 0 -> 106*arg+(arg>=2 ? 2 : 0); + case 1 -> (arg&1)==0 ? 140 : 60; + case 2 -> 130; + default -> 16; + }; + assertEquals(Long.toString(expected),Eval2.eval(code,arg)); + } + } + } + + private static final String[] SOURCES = { + Chapter16Test.CONSTRUCTOR_MEMORY, + Chapter18Test.CALL_MEMORY, + Chapter18Test.RECURSIVE_MEMORY, + Chapter18Test.INLINE_MEMORY, + "int[] !a = new int[7]; a[arg]=arg+3; return a[0]+1;", + """ + int[] !a = new int[7]; + for( int i=0; i<7; i++ ) a[i]=i+arg; + for( int i=0; i<6; i++ ) a[i+1]+=a[i]; + return a[6]; + """ + }; + + private static long expected(int program, long arg) { + return switch(program) { + case 0 -> arg==0 ? 111249 : 222351+arg; + case 1 -> arg==0 ? 10101325 : 20202322; + case 2 -> (10*(arg+1)+arg*(arg+1)/2)*100+10+arg; + case 3 -> 400944+arg*10001; + case 4 -> arg==0 ? 4 : 1; + case 5 -> 21+7*arg; + default -> throw new AssertionError(); + }; + } + + @Test public void testEmulatedMemory() throws IOException { + for( String target : new String[]{"riscv","arm"} ) + for( int p=0; p<SOURCES.length; p++ ) { + CodeGen code = new CodeGen(SOURCES[p]).driver(target,"SystemV",null); + RegAllocTestSupport.checkRegisters(code); + int entry=0; + for( var n : code._start._outputs ) + if( n instanceof FunNode fun && "main".equals(fun._name) ) + entry=code._encoding._opStart[fun._nid]; + for( int arg=0; arg<7; arg++ ) { + byte[] image = new byte[1<<20]; + byte[] bits = code._encoding.bits(); + System.arraycopy(bits,0,image,0,bits.length); + long result; + if( target.equals("riscv") ) { + EvalRisc5 cpu = new EvalRisc5(image,1<<16); + cpu._pc=entry; + cpu.regs[riscv.A0]=arg; + assertEquals(target+" program "+p+" arg "+arg,0,cpu.step(100000)); + result=cpu.regs[riscv.A0]; + } else { + EvalArm64 cpu = new EvalArm64(image,1<<16); + cpu._pc=entry; + cpu.regs[arm.X0]=arg; + assertEquals(target+" program "+p+" arg "+arg,0,cpu.step(100000)); + result=cpu.regs[arm.X0]; + } + assertEquals(target+" program "+p+" arg "+arg,expected(p,arg),result); + } + } + } + + @Test public void testNeverMemory() { + // A varying pointer prevents store sinking: the synthetic exit needs both slices. + for( String target : new String[]{"x86_64_v2","riscv","arm"} ) { + CodeGen code = new CodeGen(""" + struct S { int x; int y; }; + !S !a=new S; !S !b=new S; !S !p=a; + if( arg ) while( 1 ) { p.x+=arg; p.y+=p.x; p=p==a ? b : a; } + return a.x+a.y; + """).driver(CodeGen.Phase.LoopTree); + assertNotNull(code._stop.walk(n -> { + if( n instanceof MemMergeNode mem && + mem.in(0) instanceof CProjNode exit && exit.in(0) instanceof NeverNode ) { + assertTrue(mem.alias(2) instanceof MemPhiNode); + assertTrue(mem.alias(3) instanceof MemPhiNode); + return mem; + } + return null; + })); + code.instSelect(target,"SystemV").GCM().localSched().regAlloc(); + RegAllocTestSupport.checkRegisters(code); + code.encode(); + } + } + + @Test public void testNativeMemory() throws IOException { + Files.createDirectories(Path.of("build/objs")); + for( int p=0; p<SOURCES.length; p++ ) { + String file="build/objs/memory"+p; + StringBuilder c = new StringBuilder("extern long long memory(long long);\nint main() {\n"); + for( int arg=0; arg<7; arg++ ) + c.append("if(memory(").append(arg).append(")!=").append(expected(p,arg)).append("LL) return ").append(arg+1).append(";\n"); + c.append("return 0;\n}\n"); + Files.writeString(Path.of(file+".c"),c); + TestC._run("val memory = { int arg -> "+SOURCES[p]+" };", + TestC.CALL_CONVENTION,"",file+".c",file,"S","",-1); + } + } +} diff --git a/src/test/java/com/seaofnodes/simple/MergeSortTest.java b/src/test/java/com/seaofnodes/simple/MergeSortTest.java index 31fbe3a4..788bd9c2 100644 --- a/src/test/java/com/seaofnodes/simple/MergeSortTest.java +++ b/src/test/java/com/seaofnodes/simple/MergeSortTest.java @@ -1,67 +1,46 @@ package com.seaofnodes.simple; +import com.seaofnodes.isa.eval.EvalRisc5; + import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.node.cpus.riscv.riscv; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; public class MergeSortTest { - @Test - public void testMergeSort() { - String src = -""" -// based on the top-down version from https://en.wikipedia.org/wiki/Merge_sort - -val merge_sort = { int[] !a, int[] !b, int n -> - copy_array(a, 0, n, b); - split_merge(a, 0, n, b); -}; - -val split_merge = { int[] !b, int begin, int end, int[] a -> - if (end - begin <= 1) - return 0; - int middle = (end + begin) / 2; - split_merge(a, begin, middle, b); - split_merge(a, middle, end, b); - merge(b, begin, middle, end, a); - return 0; -}; - -val merge = { int[] !b, int begin, int middle, int end, int[] a -> - int i = begin, j = middle; - - for (int k = begin; k < end; k++) { - // && and || - bool cond = false; - if (i < middle) { - if (j >= end) cond = true; - else if (a[i] <= a[j]) cond = true; - } - if (cond) b[k] = a[i++]; - else b[k] = a[j++]; - } -}; - -val copy_array = { int[] !a, int begin, int end, int[] b -> - for (int k = begin; k < end; k++) - b[k] = a[k]; -}; - -int[] !a = new int[arg]; -int[] !b = new int[a#]; - -for (int i = 0; i < a#; i++) - a[i] = a# - i; - -merge_sort(a, b, a#); - -return a; -"""; - 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)); + @Test public void testMergeSort0() throws IOException { + int[] primes = new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, }; + SB sb = new SB().p(primes.length).p("["); + for( int prime : primes ) + sb.p(prime).p(", "); + String sprimes = sb.p("]").toString(); + TestC.run("sort", sprimes, 34); + + + EvalRisc5 R5 = TestRisc5.build("src/test/java/com/seaofnodes/simple/progs", "sort", "merge_sort", 0, 36, false); + // Allocate an array of primes, filled in reverse order + int ps = R5._heap; + R5._heap += (primes.length+1)*8; // 25 primes, and length + R5.st4(ps,primes.length); + for( int i=0; i<primes.length; i++ ) + R5.st8(ps+(primes.length-1-i+1)*8,primes[i]); + // Pass array base when calling `merge_sort` + R5.regs[riscv.A0] = ps; // Array base to be sorted + + int trap = R5.step(100000); + assertEquals(0,trap); + + assertEquals(primes.length,R5.ld4s(ps)); + for( int i=0; i<primes.length; i++ ) + assertEquals(primes[i], R5.ld8(ps+(i+1)*8)); + + // TODO: replace with ARM Eval + String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/sort.smp")); + Chapter21Test.testCPU(src,"arm", "SystemV",36,null); } - } diff --git a/src/test/java/com/seaofnodes/simple/Simple.java b/src/test/java/com/seaofnodes/simple/Simple.java new file mode 100644 index 00000000..705b086a --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/Simple.java @@ -0,0 +1,287 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.print.*; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.io.InputStreamReader; +import java.io.BufferedReader; +import java.io.IOException; +import java.util.stream.Collectors; + +public class Simple { + public static final String PORTS = "com.seaofnodes.simple.node.cpus"; + + static final int DUMP_AFTER_PARSE = 1<<0; + static final int DUMP_AFTER_OPTO = 1<<1; + static final int DUMP_AFTER_TYPE_CHECK = 1<<2; + static final int DUMP_AFTER_LOOP_TREE = 1<<3; + static final int DUMP_AFTER_INSTR_SELECT = 1<<4; + static final int DUMP_AFTER_GCM = 1<<5; + static final int DUMP_AFTER_LOCAL_SCHED = 1<<6; + static final int DUMP_AFTER_REG_ALLOC = 1<<7; + static final int DUMP_AFTER_ENCODE = 1<<8; + + static final int DUMP_FINAL = 1<<16; + + static final int DUMP_PASS_NAME = 1<<30; + static final int DUMP_AFTER_ALL = 0xff | DUMP_PASS_NAME; + + static final int DUMP_DISASSEMBLE = 1<<31; + + static String system_cpu = null; + static String system_abi = null; + + static void usage() { + System.out.println( +""" +simple <input-file> [options] [--eval|--run ...] +Options: + --dump - dump final intermediate representation + --dump-after-parse - dump intermediate representation after parse + --dump-after-opto - dump intermediate representation after opto pass + --dump-after-type-check - dump intermediate representation after type check pass + --dump-after-instr-select - dump intermediate representation after instrution selection pass + --dump-after-gcm - dump intermediate representation after GCM pass + --dump-after-local-sched - dump intermediate representation after local scheduling pass + --dump-after-reg-alloc - dump intermediate representation after register allocation pass + --dump-after-encode - dump intermediate representation after encoding pass + --dump-after-all - dump intermediate representation after all passes + -S - dump generated assembler code + --eval ... - evaluate the compiled code in emulator + --run ... - run the compiled code natively + --dump-size - print the size of generated code + --dump-time - print compilation and execution times + --cpu <cpu-name> - use specific CPU (x86_64_v2, riscv, arm) + --abi <abi-name> - use speific ABI variant (SystemV) + --target - print native CPU and ABI + --version + --help +""" +); + System.exit(0); + } + + static void bad_usage() { + System.err.println("ERROR: Invalid usage (use --help)"); + System.exit(1); + } + + static void target() { + if (system_cpu == null || system_abi == null) { + System.err.println("ERROR: Unknown target"); + System.exit(1); + } + System.out.print(system_cpu); + System.out.print("-"); + System.out.println(system_abi); + System.exit(0); + } + + static void dump(CodeGen code, int dump, int pass) { + if ((dump & pass) != 0) { + if ((dump & DUMP_PASS_NAME) != 0) { + System.err.println(switch (pass) { + case DUMP_AFTER_PARSE -> "After Parse:"; + case DUMP_AFTER_OPTO -> "After OPTO:"; + case DUMP_AFTER_TYPE_CHECK -> "After Type Check:"; + case DUMP_AFTER_LOOP_TREE -> "After Loop Tree:"; + case DUMP_AFTER_INSTR_SELECT -> "After Instruction Selection:"; + case DUMP_AFTER_GCM -> "After GCM:"; + case DUMP_AFTER_LOCAL_SCHED -> "After Local Scheduling:"; + case DUMP_AFTER_REG_ALLOC -> "After Register Allocation:"; + case DUMP_AFTER_ENCODE -> "After Instruction Encoding:"; + case DUMP_FINAL -> "Final:"; + default -> throw Utils.TODO(); + }); + } + + System.err.println(IRPrinter.prettyPrint(code._stop, 9999)); + + } + } + + static void print_compilation_times(CodeGen code) { + double t, total = 0; + + total += t = code._tParse / 1e3; + System.out.println(String.format("Parsing Time: %.3f sec", t)); + total += t = code._tOpto / 1e3; + System.out.println(String.format("Optimization Time: %.3f sec", t)); + total += t = code._tTypeCheck / 1e3; + System.out.println(String.format("Type Checking Time: %.3f sec", t)); + total += t = code._tLoopTree / 1e3; + System.out.println(String.format("Loop Tree Time: %.3f sec", t)); + total += t = code._tInsSel / 1e3; + System.out.println(String.format("Instruction Selection Time: %.3f sec", t)); + total += t = code._tGCM / 1e3; + System.out.println(String.format("GCM Time: %.3f sec", t)); + total += t = code._tLocal / 1e3; + System.out.println(String.format("Local Scheduling Time: %.3f sec", t)); + total += t = code._tRegAlloc / 1e3; + System.out.println(String.format("Register Allocation Time: %.3f sec", t)); + total += t = code._tEncode / 1e3; + System.out.println(String.format("Instruction Encoding Time: %.3f sec", t)); + System.out.println(String.format("TOTAL COMPILATION TIME: %.3f sec", total)); + } + + static String getInput() throws IOException { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) { + return reader.lines().collect(Collectors.joining("\n")); + } + } + + public static void main(String[] args) throws Exception { + String input_filename = null; + boolean use_stdin = false; + boolean do_eval = false; + boolean do_run = false; + boolean do_codegen = false; + boolean do_print_size = false; + boolean do_print_time = false; + int dump = 0; + int first_arg = 0; + String src = null; + String cpu = null; + String abi = null; + + // TODO: autodetect + system_cpu = "x86_64_v2"; + system_abi = "SystemV"; + + // Parse command line +loop: for (int i = 0; i < args.length; i++) { + String arg = args[i]; + if (arg.charAt(0) == '-') { + switch (arg) { + case "-": use_stdin = true; break; + case "--dump": dump |= DUMP_FINAL; break; + case "--dump-after-parse": dump |= DUMP_AFTER_PARSE; break; + case "--dump-after-opto": dump |= DUMP_AFTER_OPTO; break; + case "--dump-after-type-check": dump |= DUMP_AFTER_TYPE_CHECK; break; + case "--dump-after-loop-tree": dump |= DUMP_AFTER_LOOP_TREE; break; + case "--dump-after-instr-select": dump |= DUMP_AFTER_INSTR_SELECT; break; + case "--dump-after-gcm": dump |= DUMP_AFTER_GCM; break; + case "--dump-after-local-sched": dump |= DUMP_AFTER_LOCAL_SCHED; break; + case "--dump-after-reg-alloc": dump |= DUMP_AFTER_REG_ALLOC; break; + case "--dump-after-encode": dump |= DUMP_AFTER_ENCODE; break; + case "--dump-after-all": dump |= DUMP_AFTER_ALL; break; + case "-S": dump |= DUMP_DISASSEMBLE; break; + case "--eval": do_eval = true; first_arg = i + 1; break loop; + case "--run": do_run = true; first_arg = i + 1; break loop; + case "--dump-size": do_print_size = true; break; + case "--dump-time": do_print_time = true; break; + case "--cpu": if (cpu != null + || i + 1 >= args.length + || args[i + 1].charAt(0) == '-') bad_usage(); + cpu = args[i + 1]; + i++; + break; + case "--abi": if (abi != null + || i + 1 >= args.length + || args[i + 1].charAt(0) == '-') bad_usage(); + abi = args[i + 1]; + i++; + break; + case "--target": target(); + case "--version": throw Utils.TODO(); + case "--help": usage(); + default: bad_usage(); + } + } else { + if (input_filename != null || use_stdin) bad_usage(); + input_filename = arg; + } + } + + if (input_filename == null && !use_stdin) { + System.err.println("ERROR: no input file' (use --help)"); + System.exit(1); + } + + if (do_run || (dump & DUMP_DISASSEMBLE) != 0 || do_print_size) { + if (cpu == null) cpu = system_cpu; + if (abi == null) abi = system_abi; + if (cpu == null || abi == null) { + System.err.println("ERROR: Cannot compile for unknown target"); + System.exit(1); + } + do_codegen = true; + } + + // Read input file + try { + src = use_stdin ? getInput() : Files.readString(Path.of(input_filename)); + } catch(IOException e) { + System.err.println("ERROR: Cannot read input file"); + System.exit(1); + } + + // Compilation pipeline + CodeGen code = new CodeGen(src); + + code.parse(); + dump(code, dump, DUMP_AFTER_PARSE); + + code.opto(); + dump(code, dump, DUMP_AFTER_OPTO); + + code.typeCheck(); + dump(code, dump, DUMP_AFTER_TYPE_CHECK); + + code.loopTree(); + dump(code, dump, DUMP_AFTER_LOOP_TREE); + + if (do_codegen) { + code.instSelect(PORTS, cpu, abi); + dump(code, dump, DUMP_AFTER_INSTR_SELECT); + } + + code.GCM(); + dump(code, dump, DUMP_AFTER_GCM); + + code.localSched(); + dump(code, dump, DUMP_AFTER_LOCAL_SCHED); + + if (do_codegen) { + code.regAlloc(); + dump(code, dump, DUMP_AFTER_REG_ALLOC); + + code.encode(); + dump(code, dump, DUMP_AFTER_ENCODE); + } + + dump(code, dump, DUMP_FINAL); + + if (do_codegen && (dump & DUMP_DISASSEMBLE) != 0) { + System.out.println(code.asm()); + } + + if (do_print_size) { + System.out.println(String.format("Code Size: %d", code._encoding._bits.size())); + } + + if (do_print_time) { + print_compilation_times(code); + } + + if (do_eval) { + // TODO: Support for evaluation of functions with different argument numbers and types + long t = System.currentTimeMillis(); + long arg = (first_arg < args.length) ? Integer.valueOf(args[first_arg]) : 0; + System.out.println(Eval2.eval(code, arg, 100000)); + if (do_print_time) { + System.out.println(String.format("EXECUTION TIME: %.3f sec", + (System.currentTimeMillis() - t) / 1e3)); + } + } else if (do_run) { + if (cpu != system_cpu || abi != system_abi) { + System.err.println("ERROR: cannot run code on not native target"); + System.exit(1); + } + + throw Utils.TODO(); + } + } +} diff --git a/src/test/java/com/seaofnodes/simple/SpillStats.java b/src/test/java/com/seaofnodes/simple/SpillStats.java index 9b03da7b..318a6e33 100644 --- a/src/test/java/com/seaofnodes/simple/SpillStats.java +++ b/src/test/java/com/seaofnodes/simple/SpillStats.java @@ -11,6 +11,7 @@ import org.junit.runner.notification.*; public class SpillStats extends RunListener { private static SpillStats ACTIVE; private Description _test; + private int _qualityFailures; private final Map<String,long[]> _totals = new TreeMap<>(); @Override public void testStarted(Description test) { _test = test; } @@ -25,8 +26,20 @@ public class SpillStats extends RunListener { code._regAlloc._spills+","+code._regAlloc._spillScaled); } + // Keep running every CPU and native result check when measuring quality. + // A changed golden still makes spill-stats fail at the end. + public static void checkSpills(int expected, int actual) { + if( expected==-1 ) return; + try { org.junit.Assert.assertEquals("Expect spills:",expected,actual,Math.max(1,expected>>3)); } + catch( AssertionError error ) { + if( ACTIVE==null ) throw error; + ACTIVE._qualityFailures++; + System.err.println(ACTIVE._test+": "+error.getMessage()); + } + } + public static void main(String[] args) throws ClassNotFoundException { - if( args.length==0 ) args = new String[]{"Chapter20Test","BrainFuckTest","MergeSortTest"}; + if( args.length==0 ) args = new String[]{"Chapter20Test","Chapter21Test","Chapter21AllocTest","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]); @@ -34,7 +47,8 @@ public class SpillStats extends RunListener { 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."); + boolean passed = result.wasSuccessful() && ACTIVE._qualityFailures==0; + if( !passed ) 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(); @@ -42,7 +56,8 @@ public class SpillStats extends RunListener { } for( Failure failure : result.getFailures() ) System.err.println(failure.getTrace()); System.out.println("Tests: "+result.getRunCount()+", failures: "+result.getFailureCount()); + System.out.println("Spill golden failures: "+ACTIVE._qualityFailures); ACTIVE = null; - if( !result.wasSuccessful() ) System.exit(1); + if( !passed ) System.exit(1); } } diff --git a/src/test/java/com/seaofnodes/simple/TestArm64.java b/src/test/java/com/seaofnodes/simple/TestArm64.java new file mode 100644 index 00000000..e4bc38a0 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/TestArm64.java @@ -0,0 +1,44 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.isa.eval.EvalArm64; + + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.node.cpus.arm.arm; +import junit.framework.Assert; +import java.nio.file.Files; +import java.nio.file.Path; +import java.io.IOException; + +import static org.junit.Assert.assertEquals; + +// Runs 32-bit ARM code in an emulator + +public class TestArm64 { + public static EvalArm64 build( String file, int arg, int spills, boolean print ) throws IOException { + return build("src/test/java/com/seaofnodes/simple/progs",file, arg, spills, print); + } + + // Compile and run a simple program + public static EvalArm64 build( String dir, String file, int arg, int spills, boolean print ) throws IOException { + // Compile and export Simple + String src = Files.readString(Path.of(dir+"/"+file+".smp")); + CodeGen code = new CodeGen(src).driver("arm", "SystemV",null); + com.seaofnodes.simple.codegen.RegAllocTestSupport.checkRegisters(code); + SpillStats.record(code,"Chapter21","arm","SystemV"); + if( print ) { code.print_as_hex(); System.out.print(code.asm()); } + + // Allocation quality not degraded + SpillStats.checkSpills(spills,code._regAlloc._spillScaled); + + // Image + byte[] image = new byte[1<<20]; // A megabyte (1024*1024 bytes) + EvalArm64 ARM = new EvalArm64(image, 1<<16); + // Code at offset 0 + System.arraycopy(code._encoding.bits(), 0, image, 0, code._encoding.bits().length); + // Initial incoming int arg + ARM.regs[arm.X0] = arg; + // malloc memory starts at 64K and goes up + return ARM; + } +} diff --git a/src/test/java/com/seaofnodes/simple/TestC.java b/src/test/java/com/seaofnodes/simple/TestC.java new file mode 100644 index 00000000..b3e21ea7 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/TestC.java @@ -0,0 +1,88 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import java.nio.file.Files; +import java.nio.file.Path; +import java.io.InputStream; +import java.io.IOException; +import static org.junit.Assert.*; + +// Call a C driver program, which calls a Simple-generated .o, and self-checks. +// Expects GCC from the default CLI, compiles C, compiles Simple, links and runs. +public abstract class TestC { + + public static final String OS = System.getProperty("os.name"); + public static final String CPU = System.getProperty("os.arch"); + + public static final String CALL_CONVENTION = OS.startsWith("Windows") ? "Win64" : "SystemV"; + public static final String CPU_PORT = switch( CPU ) { + case "amd64" -> "x86_64_v2"; + default -> throw Utils.TODO("Map Yer CPU Port Here"); + }; + + public static void run( String file, int spills ) throws IOException { run(file,"",spills); } + + public static void run( String file, String expected, int spills ) throws IOException { + run("src/test/java/com/seaofnodes/simple/progs",file,expected,spills); + } + + // Compile and run a simple program + public static void run( String dir, String file, String expected, int spills ) throws IOException { + // Files + String cfile = dir+"/"+file+".c" ; + String sfile = dir+"/"+file+".smp"; + String efile = "build/objs/"+file; + + // Compile and export Simple + String src = Files.readString(Path.of(sfile)); + _run(src,CALL_CONVENTION,"",cfile,efile,"S",expected,spills); + } + + static void _run( String src, String simple_conv, String c_conv, String cfile, String efile, String xtn, String expected, int spills ) throws IOException { + String bin = efile+xtn; + String obj = bin+".o"; + // Compile simple, emit ELF + CodeGen code = new CodeGen(src).driver( CPU_PORT, simple_conv, obj); + com.seaofnodes.simple.codegen.RegAllocTestSupport.checkRegisters(code); + SpillStats.record(code,"Chapter21",CPU_PORT,simple_conv); + + // Compile the C program + var params = new String[] { + //if (USE_WSL) "wsl.exe"; + "gcc", + cfile, + obj, + "-lm", // Picks up 'sqrt' for newtonFloat tests to compare + "-g", + "-o", + bin, + "-D", + "CALL_CONV="+c_conv, + }; + Process gcc = new ProcessBuilder(params).redirectErrorStream(true).start(); + int error; + try { error = gcc.waitFor(); } catch( InterruptedException e ) { throw new IOException("interrupted"); } + String result = new String(gcc.getInputStream().readAllBytes()); + if( error!=0 ) { + System.err.println("gcc error code: "+error); + System.err.println(result); + } + assertEquals( 0, error ); + //assertTrue(result.isEmpty()); // No data in error stream + + // Execute results + Process smp = new ProcessBuilder(bin).redirectErrorStream(true).start(); + try { error = smp.waitFor(); } catch( InterruptedException e ) { throw new IOException("interrupted"); } + result = new String(smp.getInputStream().readAllBytes()); + if( error!=0 ) { + System.err.println("exec error code: "+error); + System.err.println(result); + } + assertEquals( 0, error ); + assertEquals(expected,result); + + // Allocation quality not degraded + SpillStats.checkSpills(spills,code._regAlloc._spillScaled); + + } +} diff --git a/src/test/java/com/seaofnodes/simple/TestRisc5.java b/src/test/java/com/seaofnodes/simple/TestRisc5.java new file mode 100644 index 00000000..1289575d --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/TestRisc5.java @@ -0,0 +1,51 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.isa.eval.EvalRisc5; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.node.FunNode; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.cpus.riscv.riscv; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import static org.junit.Assert.*; + +// Runs 32-bit R5 code in an emulator +public abstract class TestRisc5 { + + public static EvalRisc5 build( String file, int arg, int spills, boolean print ) throws IOException { + return build("src/test/java/com/seaofnodes/simple/progs",file, null, arg, spills, print); + } + + // Compile and run a simple program + public static EvalRisc5 build( String dir, String file, String main, int arg, int spills, boolean print ) throws IOException { + // Compile and export Simple + String src = Files.readString(Path.of(dir+"/"+file+".smp")); + CodeGen code = new CodeGen(src).driver("riscv", "SystemV",null); + com.seaofnodes.simple.codegen.RegAllocTestSupport.checkRegisters(code); + SpillStats.record(code,"Chapter21","riscv","SystemV"); + if( print ) { code.print_as_hex(); System.out.print(code.asm()); } + + // Allocation quality not degraded + SpillStats.checkSpills(spills,code._regAlloc._spillScaled); + + // Image + byte[] image = new byte[1<<20]; // A megabyte (1024*1024 bytes) + EvalRisc5 R5 = new EvalRisc5(image, 1<<16); + // Code at offset 0 + System.arraycopy(code._encoding.bits(), 0, image, 0, code._encoding.bits().length); + // Initial incoming int arg + R5.regs[riscv.A0] = arg; + // malloc memory starts at 64K and goes up + + // Look up starting PC or zero if not given + if( main!=null ) + for( Node use : code._start._outputs ) + if( use instanceof FunNode fun && main.equals(fun._name) ) + R5._pc = code._encoding._opStart[fun._nid]; + + return R5; + } + +} diff --git a/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java b/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java index 2966cb00..739eba3b 100644 --- a/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java +++ b/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java @@ -3,7 +3,6 @@ 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. @@ -21,11 +20,11 @@ public class RegAllocTestSupport { @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(); } + @Override public void encoding(Encoding enc) { throw new AssertionError(); } } private static CodeGen graph() { CodeGen code = new CodeGen(""); - code._mach = new x86_64_v2(); + code._mach = new x86_64_v2(code); code._cfg.add(code._start); return code; } @@ -192,4 +191,71 @@ public class RegAllocTestSupport { assertFalse("A fixed definition clobbers even before coloring",uncolored.sameBlockNoClobber(copy)); } + public static void copyForward() throws Exception { + // Forwarding, fixed operand, two-address tie, intervening clobber, + // another user, and a scheduling-only edge. + for( int kind=0; kind<6; kind++ ) { + CodeGen code = graph(); + RegAlloc alloc = new RegAlloc(code); + Op def = new Op(A,null,null,false,code._start); + alloc.newLRG(def)._reg=0; + SplitNode copy = code._mach.split("test",(byte)0,null); + copy.setDef(0,code._start); copy.setDef(1,def); + alloc.newLRG(copy)._reg=1; + if( kind==3 ) new Op(null,null,A,false,code._start); + int twoAddress = kind==2 ? 1 : 0; + RegMask mask = kind==1 ? B : kind==5 ? null : new RegMask(3L); + Op use = new Op(null,mask,null,false,code._start,copy) { + @Override public int twoAddress() { return twoAddress; } + }; + if( kind==4 ) new Op(null,B,null,false,code._start,copy); + var post = RegAlloc.class.getDeclaredMethod("postColor"); + post.setAccessible(true); post.invoke(alloc); + assertSame("copy case "+kind,kind==0 ? def : copy,use.in(1)); + assertEquals(kind==0 ? 0 : 1,alloc._spills); + } + } + + public static void coalescing() { + // Merge, incompatible masks, interference, capacity, and adjacency remapping. + for( int kind=0; kind<5; kind++ ) { + CodeGen code = graph(); + RegAlloc alloc = new RegAlloc(code); + Op def = new Op(new RegMask(3L),null,null,false,code._start); + SplitNode copy = code._mach.split("test",(byte)0,null); + copy.setDef(0,code._start); copy.setDef(1,def); + Op use = new Op(null,new RegMask(3L),null,false,code._start,copy); + LRG from = alloc.newLRG(def), to = alloc.newLRG(copy); + from._mask = kind==1 ? A : new RegMask(3L); + to._mask = kind==1 ? B : new RegMask(3L); + from.machDef(def,false); from.machUse(copy,(short)1,false); + to.machDef(copy,false); to.machUse(use,(short)1,false); + LRG left = new LRG((short)100), right = new LRG((short)101); + if( kind==2 ) { from.addNeighbor(to); to.addNeighbor(from); } + if( kind>=3 ) { + from.addNeighbor(left); left.addNeighbor(from); + if( kind==3 ) { to.addNeighbor(right); right.addNeighbor(to); } + else { to.addNeighbor(left); left.addNeighbor(to); } + } + int fcnt=from.nadj(), tcnt=to.nadj(); + assertTrue(Coalesce.coalesce(0,alloc)); + if( kind==0 || kind==4 ) { + assertSame(def,use.in(1)); + assertSame(alloc.lrg(def),alloc.lrg(copy)); + assertSame(use,alloc.lrg(def)._machUse); + assertEquals(1,alloc.lrg(def)._uidx); + if( kind==4 ) { + assertEquals(1,left.nadj()); + assertSame(alloc.lrg(def),left._adj.at(0)); + assertSame(left,alloc.lrg(def)._adj.at(0)); + } + } else { + assertSame(copy,use.in(1)); + assertNotSame(alloc.lrg(def),alloc.lrg(copy)); + assertEquals(fcnt,from.nadj()); + assertEquals(tcnt,to.nadj()); + } + } + } + } diff --git a/src/test/java/com/seaofnodes/simple/codegen/X86EncodingTest.java b/src/test/java/com/seaofnodes/simple/codegen/X86EncodingTest.java new file mode 100644 index 00000000..368c243b --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/codegen/X86EncodingTest.java @@ -0,0 +1,97 @@ +package com.seaofnodes.simple.codegen; + +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.cpus.x86_64_v2.CmpMemX86; +import com.seaofnodes.simple.node.cpus.x86_64_v2.NotX86; +import org.junit.Test; +import static org.junit.Assert.*; + +public class X86EncodingTest { + + @Test + public void testLogicalNotRegisters() { + CodeGen code = new CodeGen("return !arg;") + .driver(CodeGen.Phase.InstSelect, "x86_64_v2", "SystemV"); + var not = code._stop.walk(n -> n instanceof NotX86 ? (NotX86)n : null); + assertNotNull("Must exercise logical not", not); + int[][] registers = {{0,0}, {1,1}, {7,7}, {9,9}, {1,9}, {9,1}, {6,0}, {15,8}}; + // TEST src,src; SETZ dstb; MOVZX dst32,dstb. Include overlapping + // registers, low-byte registers requiring REX, and extended registers. + String[] expected = { + "4885c00f94c00fb6c0", "4885c90f94c10fb6c9", + "4885ff400f94c7400fb6ff", "4d85c9410f94c1450fb6c9", + "4d85c90f94c10fb6c9", "4885c9410f94c1450fb6c9", + "4885c0400f94c6400fb6f6", "4d85c0410f94c7450fb6ff" + }; + for (int i = 0; i < registers.length; i++) { + int dst = registers[i][0], src = registers[i][1]; + var enc = new FixedRegisterEncoding(code, not, dst, not.in(1), src); + not.encoding(enc); + assertArrayEquals("Logical not dst=" + dst + ", src=" + src, + java.util.HexFormat.of().parseHex(expected[i]), enc._bits.toByteArray()); + } + } + + @Test + public void testLogicalNotExecution() throws java.io.IOException { + var dir = java.nio.file.Path.of("build/objs"); + java.nio.file.Files.createDirectories(dir); + java.nio.file.Files.writeString(dir.resolve("x86LogicalNot.smp"), + "val logicalNot = { int x -> return !x; };"); + java.nio.file.Files.writeString(dir.resolve("x86LogicalNot.c"), """ + #include <limits.h> + extern long long logicalNot(long long); + int main(void) { + long long inputs[] = {0, 1, -1, LLONG_MIN, LLONG_MAX, 1LL << 32}; + for (unsigned i = 0; i < sizeof(inputs)/sizeof(inputs[0]); i++) + if (logicalNot(inputs[i]) != (inputs[i] == 0)) return 1; + return 0; + } + """); + com.seaofnodes.simple.TestC.run("build/objs", "x86LogicalNot", "", -1); + } + @Test + public void testMemoryCompare64Immediate32() { + for (int immediate : new int[]{123456789, -123456789}) { + String src = "int[] !a = new int[2]; a[0] = arg; return a[arg & 1] == " + immediate + ";"; + CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Encoding, "x86_64_v2", "SystemV"); + var cmp = code._stop.walk(n -> n instanceof CmpMemX86 ? n : null); + assertNotNull("Must exercise a memory comparison", cmp); + var enc = code._encoding; + int start = enc._opStart[cmp._nid]; + + // REX.W, 81 /7, SIB with scale 8, disp8, and a sign-extended imm32. + assertEquals(0x48, enc.read1(start) & 0xF8); + assertEquals(0x81, enc.read1(start + 1)); + assertEquals(0x7C, enc.read1(start + 2)); + assertEquals(0xC0, enc.read1(start + 3) & 0xC0); + assertEquals(8, enc.read1(start + 4)); + assertEquals(immediate, enc.read4(start + 5)); + assertEquals("CMP r/m64 uses four immediate bytes", 9, enc._opLen[cmp._nid]); + + // SETE must immediately follow the CPU's nine-byte CMP instruction. + assertEquals(0x0F, enc.read1(start + 9)); + assertEquals(0x94, enc.read1(start + 10)); + } + } + + /** Exercise instruction encoders with explicit registers, independent of allocator choices. */ + private static class FixedRegisterEncoding extends Encoding { + private final Node _dst, _src; + private final short _dstReg, _srcReg; + + FixedRegisterEncoding(CodeGen code, Node dst, int dstReg, Node src, int srcReg) { + super(code); + _dst = dst; + _src = src; + _dstReg = (short)dstReg; + _srcReg = (short)srcReg; + } + + @Override public short reg(Node n) { + if (n == _dst) return _dstReg; + if (n == _src) return _srcReg; + throw new AssertionError("Unexpected register lookup"); + } + } +} diff --git a/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java b/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java index 4bfa7628..7ba9272f 100644 --- a/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java +++ b/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java @@ -248,20 +248,20 @@ public class Scheduler { assert data.block != null; assert data.users == 0; - if (data.node instanceof LoadNode l) { + if (data.node instanceof MemOpNode l && l._isLoad) { assert isValid(data); // Handle anti-deps of load nodes. // At this point all anti-dep nodes are scheduled, // but they did not refine the placement // so do that now. - var mem = l.in(1); + var mem = data.node.in(1); for(var out : mem._outputs) { if (out instanceof PhiNode p) { var r = p.in(0); for (int i = 1; i < p.nIns(); i++) { if (p.in(i) == mem) optionalRefinePlacement(data, r.in(i)); } - } else if (!(out instanceof LoadNode)) { + } else if (!(out instanceof MemOpNode ld && ld._isLoad)) { optionalRefinePlacement(data, out); } } @@ -273,11 +273,11 @@ public class Scheduler { for(var in:data.node._inputs) { if (in!=null) update(d(in), data.block); } - if (data.node instanceof StoreNode s) { + if (data.node instanceof MemOpNode s && !s._isLoad) { // Store nodes have anti-deps to load nodes. // So decrease the uses of these loads when the store is placed. - for (var out: s.in(1)._outputs) { - if (out instanceof LoadNode) od(out).ifPresent(this::decUsers); + for (var out: ((Node)s).in(1)._outputs) { + if (out instanceof MemOpNode ld && ld._isLoad) od(out).ifPresent(this::decUsers); } } } @@ -471,14 +471,14 @@ public class Scheduler { } else { for (var in : node._inputs) if (in != null && !(in instanceof CFGNode)) markAlive(dataQueue, in, false); } - if (node instanceof StoreNode) mem.push(data); + if (node instanceof MemOpNode s && !s._isLoad) mem.push(data); } // Handle store nodes and increase load with an anti-dep to the store. while (!mem.isEmpty()) { var data = mem.pop(); node = data.node; for(var out:node.in(1)._outputs) { - if (out instanceof LoadNode) od(out).ifPresent(d->d.users++); + if (out instanceof MemOpNode ld && ld._isLoad) od(out).ifPresent(d->d.users++); } } } diff --git a/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java b/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java index a2137314..b2be5b5f 100644 --- a/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java +++ b/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java @@ -101,7 +101,7 @@ class FuzzerUtils { System.setErr(NULL_PRINT_STREAM); System.setOut(NULL_PRINT_STREAM); var code = new CodeGen(script, TypeInteger.BOT, workListSeed); - return code.parse().opto().typeCheck().GCM().localSched(); + return code.driver(CodeGen.Phase.LocalSched); } finally { System.setErr(err); System.setOut(out); diff --git a/src/test/java/com/seaofnodes/simple/progs/antiDep1.smp b/src/test/java/com/seaofnodes/simple/progs/antiDep1.smp new file mode 100644 index 00000000..52bfe29b --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/antiDep1.smp @@ -0,0 +1,11 @@ +// -*- mode: java; -*- +struct S { int f; }; +var v0 = new S; +!S? !v1; +if (arg) v1 = new S; +if (v1) { + v0.f = v1.f; +} else { + v0.f = 2; +} +return v0; diff --git a/src/test/java/com/seaofnodes/simple/progs/arg_count.c b/src/test/java/com/seaofnodes/simple/progs/arg_count.c new file mode 100644 index 00000000..e247e28a --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/arg_count.c @@ -0,0 +1,10 @@ +#include <stdio.h> + +__attribute__ ((CALL_CONV)) +extern double addAll( long long i0, double f1, long long i2, double f3, long long i4, double f5, long long i6, double f7, long long i8, double f9, long long i10, double f11, long long i12, double f13, long long i14, double f15, long long i16, double f17, long long i18, double f19 ); + +int main() { + double result = addAll( 0, 1.1, 2, 3.1, 4, 5.1, 6, 7.1, 8, 9.1, 10, 11.1, 12, 13.1, 14, 15.1, 16, 17.1, 18, 19.1 ); + printf("%f\n", result); + return 0; +} diff --git a/src/test/java/com/seaofnodes/simple/progs/arg_count.smp b/src/test/java/com/seaofnodes/simple/progs/arg_count.smp new file mode 100644 index 00000000..6b6bc0ae --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/arg_count.smp @@ -0,0 +1,6 @@ +// -*- mode: java; -*- +val addAll = { int i0, flt f1, int i2, flt f3, int i4, flt f5, int i6, flt f7, int x8, flt f9, int i10, flt f11, int i12, flt f13, int i14, flt f15, int x16, flt f17 int x18, flt f19 -> + return + i0 + f1+ i2+ f3+ i4+ f5+ i6+ f7+ x8 +f9 + + i10+f11+i12+f13+i14+f15+x16+f17+x18+f19 ; +}; diff --git a/src/test/java/com/seaofnodes/simple/progs/array1.smp b/src/test/java/com/seaofnodes/simple/progs/array1.smp new file mode 100644 index 00000000..6b10f92c --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/array1.smp @@ -0,0 +1,9 @@ +// -*- mode: java; -*- +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 diff --git a/src/test/java/com/seaofnodes/simple/progs/brain_fuck.c b/src/test/java/com/seaofnodes/simple/progs/brain_fuck.c new file mode 100644 index 00000000..127cbe73 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/brain_fuck.c @@ -0,0 +1,18 @@ +#include <stdio.h> +#include <stdbool.h> + +typedef struct AryInt { + unsigned int len; + unsigned char data[]; +} AryInt; + +__attribute__ ((CALL_CONV)) +extern AryInt* brain_fuck(); + +int main() { + AryInt* ps = brain_fuck(); + + printf("%.*s", ps->len, ps->data); + + return 0; +} diff --git a/src/test/java/com/seaofnodes/simple/progs/brain_fuck.smp b/src/test/java/com/seaofnodes/simple/progs/brain_fuck.smp new file mode 100644 index 00000000..ed90cb84 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/brain_fuck.smp @@ -0,0 +1,46 @@ +// -*- mode: java; -*- +val brain_fuck = { -> + u8[] !program = new u8[106];program[0] = 43;program[1] = 43;program[2] = 43;program[3] = 43;program[4] = 43;program[5] = 43;program[6] = 43;program[7] = 43;program[8] = 91;program[9] = 62;program[10] = 43;program[11] = 43;program[12] = 43;program[13] = 43;program[14] = 91;program[15] = 62;program[16] = 43;program[17] = 43;program[18] = 62;program[19] = 43;program[20] = 43;program[21] = 43;program[22] = 62;program[23] = 43;program[24] = 43;program[25] = 43;program[26] = 62;program[27] = 43;program[28] = 60;program[29] = 60;program[30] = 60;program[31] = 60;program[32] = 45;program[33] = 93;program[34] = 62;program[35] = 43;program[36] = 62;program[37] = 43;program[38] = 62;program[39] = 45;program[40] = 62;program[41] = 62;program[42] = 43;program[43] = 91;program[44] = 60;program[45] = 93;program[46] = 60;program[47] = 45;program[48] = 93;program[49] = 62;program[50] = 62;program[51] = 46;program[52] = 62;program[53] = 45;program[54] = 45;program[55] = 45;program[56] = 46;program[57] = 43;program[58] = 43;program[59] = 43;program[60] = 43;program[61] = 43;program[62] = 43;program[63] = 43;program[64] = 46;program[65] = 46;program[66] = 43;program[67] = 43;program[68] = 43;program[69] = 46;program[70] = 62;program[71] = 62;program[72] = 46;program[73] = 60;program[74] = 45;program[75] = 46;program[76] = 60;program[77] = 46;program[78] = 43;program[79] = 43;program[80] = 43;program[81] = 46;program[82] = 45;program[83] = 45;program[84] = 45;program[85] = 45;program[86] = 45;program[87] = 45;program[88] = 46;program[89] = 45;program[90] = 45;program[91] = 45;program[92] = 45;program[93] = 45;program[94] = 45;program[95] = 45;program[96] = 45;program[97] = 46;program[98] = 62;program[99] = 62;program[100] = 43;program[101] = 46;program[102] = 62;program[103] = 43;program[104] = 43;program[105] = 46; + int d = 0; + u8[] !output = new u8[0]; + u8[] !data = new u8[100]; + + for( int pc = 0; pc < program#; pc++ ) { + var command = program[pc]; + if (command == 62) { + d++; + } else if (command == 60) { + d--; + } else if (command == 43) { + data[d]++; + } else if (command == 45) { + data[d]--; + } else if (command == 46) { + // Output a byte; increase the output array size + var old = output; + output = new u8[output# + 1]; + for( int i = 0; i < old#; i++ ) + output[i] = old[i]; + output[old#] = data[d]; // Add the extra byte on the end + } else if (command == 44) { + data[d] = 42; + } else if (command == 91) { + if (data[d] == 0) { + for( int d = 1; d > 0; ) { + command = program[++pc]; + if (command == 91) d++; + if (command == 93) d--; + } + } + } else if (command == 93) { + if (data[d]) { + for( int d = 1; d > 0; ) { + command = program[--pc]; + if (command == 93) d++; + if (command == 91) d--; + } + } + } + } + return output; +}; diff --git a/src/test/java/com/seaofnodes/simple/progs/fcn.smp b/src/test/java/com/seaofnodes/simple/progs/fcn.smp deleted file mode 100644 index 43d398b3..00000000 --- a/src/test/java/com/seaofnodes/simple/progs/fcn.smp +++ /dev/null @@ -1,15 +0,0 @@ - -struct Person { - int age; -}; - -val fcn = { !Person?[] ps, int x -> - val tmp = ps[x]; - if( ps[x] ) - ps[x].age++; -}; - -!Person?[] !ps = new !Person?[2]; -ps[0] = new Person; -ps[1] = new Person; -fcn(ps,1); diff --git a/src/test/java/com/seaofnodes/simple/progs/fcn2.smp b/src/test/java/com/seaofnodes/simple/progs/fcn2.smp deleted file mode 100644 index 049132d9..00000000 --- a/src/test/java/com/seaofnodes/simple/progs/fcn2.smp +++ /dev/null @@ -1,5 +0,0 @@ - -val fcn = (arg&1) - ? { int x -> x*x; } - : { int x -> x+x; }; -fcn(arg); diff --git a/src/test/java/com/seaofnodes/simple/progs/fcn3.smp b/src/test/java/com/seaofnodes/simple/progs/fcn3.smp deleted file mode 100644 index 55b74455..00000000 --- a/src/test/java/com/seaofnodes/simple/progs/fcn3.smp +++ /dev/null @@ -1,42 +0,0 @@ - -//val fact = { int n -> n <= 1 ? 1 : fact(n-1)*n; } -struct LL { !LL? !next; int payload; }; - - -struct String { - - val _hashCodeString = { -> - int hash=0; - for( int i=0; i< cs#; i++ ) - hash = hash*31 + cs[i]; - if( !hash ) hash = 123456789; - return hash; - }; - - val equals = { !String s -> - if( self == s ) return true; - if( cs# != s.cs# ) return false; - for( int i=0; i< #; i++ ) - if( [i] != s.cs[i] ) - return false; - return true; - }; - - int _hashCode = hash(); - u8[] cs; -}; - - - -val hashCode= { !String self -> - int hash = self._hashCode - ? self._hashCode - : self._hashCode = _hashCodeString(self); - return hash; -}; - - -String s = new String { cs = new u8[17]; }; -s.cs[0] = 67; // C -s.cs[1] = 108; // l -hashCode(s); diff --git a/src/test/java/com/seaofnodes/simple/progs/fib.c b/src/test/java/com/seaofnodes/simple/progs/fib.c new file mode 100644 index 00000000..263d2f13 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/fib.c @@ -0,0 +1,16 @@ +#include <stdio.h> +#include <stdbool.h> + + +__attribute__ ((CALL_CONV)) +extern int fib(int x); + +int main() { + printf("["); + for (int i = 0; i < 10; i++) { + printf("%d", fib(i)); + if (i < 9) printf(", "); + } + printf("]"); + return 0; +}
\ No newline at end of file diff --git a/src/test/java/com/seaofnodes/simple/progs/fib.smp b/src/test/java/com/seaofnodes/simple/progs/fib.smp new file mode 100644 index 00000000..1512541b --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/fib.smp @@ -0,0 +1,11 @@ +// -*- mode: java; -*- +val fib = {int n -> + int f1=1; + int f2=1; + while( n-- > 1 ){ + int temp = f1+f2; + f1=f2; + f2=temp; + } + return f2; +};
\ No newline at end of file diff --git a/src/test/java/com/seaofnodes/simple/progs/jig.smp b/src/test/java/com/seaofnodes/simple/progs/jig.smp new file mode 100644 index 00000000..1daeee09 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/jig.smp @@ -0,0 +1,2 @@ +// -*- mode: java; -*- +return 0; diff --git a/src/test/java/com/seaofnodes/simple/progs/loop2.c b/src/test/java/com/seaofnodes/simple/progs/loop2.c new file mode 100644 index 00000000..934f2d89 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/loop2.c @@ -0,0 +1,7 @@ + +__attribute__ ((CALL_CONV)) +extern int loop2(); + +int main() { + return loop2(); +} diff --git a/src/test/java/com/seaofnodes/simple/progs/loop2.smp b/src/test/java/com/seaofnodes/simple/progs/loop2.smp new file mode 100644 index 00000000..01ec827e --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/loop2.smp @@ -0,0 +1,10 @@ +// -*- mode: java; -*- +int i = 0; +while(true) { + i += 1; + if( i==0 ) continue; + if( i==1 ) continue; + if( i==2 ) break; + continue; +} +return i; diff --git a/src/test/java/com/seaofnodes/simple/progs/newtonFloat.c b/src/test/java/com/seaofnodes/simple/progs/newtonFloat.c new file mode 100644 index 00000000..7e7e796d --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/newtonFloat.c @@ -0,0 +1,16 @@ +#include <stdio.h> +#include <math.h> +#include <stdlib.h> + +extern double test_sqrt(double); + +int main( int argc, char** argv ) { + for( int i=0; i<10; i++ ) { + double d = test_sqrt((double)i), expect = sqrt((double)i); + double delta = fabs(d-expect); + printf("%d %f (%g)\n",i,d,delta); + if( delta > 1e-15 ) + return 1; + } + return 0; +} diff --git a/src/test/java/com/seaofnodes/simple/progs/newtonFloat.smp b/src/test/java/com/seaofnodes/simple/progs/newtonFloat.smp new file mode 100644 index 00000000..be2dffec --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/newtonFloat.smp @@ -0,0 +1,12 @@ +// -*- mode: java; -*- +// Newtons approximation to the square root +val test_sqrt = { flt x -> + flt epsilon = 1e-15; + flt guess = x; + while( 1 ) { + flt next = (x/guess + guess)/2; + if( guess-epsilon <= next & next <= guess+epsilon ) return guess; + //if( guess==next ) return guess; + guess = next; + } +}; diff --git a/src/test/java/com/seaofnodes/simple/progs/no_stack_arg_count.smp b/src/test/java/com/seaofnodes/simple/progs/no_stack_arg_count.smp new file mode 100644 index 00000000..20a05445 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/no_stack_arg_count.smp @@ -0,0 +1,6 @@ +// -*- mode: java; -*- +val addAll = { int i0, flt f1, int i2, flt f3, int i4, flt f5, int i6, flt f7, int x8, flt f9, int i10, flt f11, int i12, flt f13, int i14, flt f15 -> + return + i0 + f1+ i2+ f3+ i4+ f5+ i6+ f7+ x8 +f9 + + i10+f11+i12+f13+i14+f15; +}; diff --git a/src/test/java/com/seaofnodes/simple/progs/person.c b/src/test/java/com/seaofnodes/simple/progs/person.c new file mode 100644 index 00000000..df95e1ca --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/person.c @@ -0,0 +1,34 @@ +#include <stdio.h> +#include <stdbool.h> +#include <stdlib.h> + +typedef struct Person { + int age; +} Person; + +typedef struct PersonArray { + unsigned int length; + int pad; + Person* data[]; +} PersonArray; + +__attribute__ ((CALL_CONV)) +extern void fcn(PersonArray* people, int index); + +int main() { + Person base[5] = {{25},{1},{18},{45},{60}}; + unsigned char buf[8+8*5]; + PersonArray* people_array = (PersonArray*)buf; + people_array->length=5; + for (int i=0; i<people_array->length; i++) people_array->data[i] = &base[i]; + + fcn(people_array, 1); + fcn(people_array, 1); + fcn(people_array, 1); + fcn(people_array, 1); + fcn(people_array, 1); + + printf("%d\n", people_array->data[1]->age); // expected: 6 + + return 0; +}
\ No newline at end of file diff --git a/src/test/java/com/seaofnodes/simple/progs/person.smp b/src/test/java/com/seaofnodes/simple/progs/person.smp new file mode 100644 index 00000000..b5e6ee90 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/person.smp @@ -0,0 +1,9 @@ +// -*-java-*- +struct Person { + int age; +}; + +val fcn = { !Person?[] ps, int x -> + if( ps[x] ) + ps[x].age++; +};
\ No newline at end of file diff --git a/src/test/java/com/seaofnodes/simple/progs/sieve.c b/src/test/java/com/seaofnodes/simple/progs/sieve.c new file mode 100644 index 00000000..cac4cfa3 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/sieve.c @@ -0,0 +1,19 @@ +#include <stdio.h> +#include <stdbool.h> + +typedef struct AryInt { + unsigned int len; + unsigned int data[]; +} AryInt; + +__attribute__ ((CALL_CONV)) +extern AryInt *sieve(int N); + +int main() { + AryInt* ps = sieve(100); + printf("%d[",ps->len); + for( int i=0; i<ps->len; i++ ) + printf("%d, ",ps->data[i]); + printf("]"); + return 0; +} diff --git a/src/test/java/com/seaofnodes/simple/progs/sieve.smp b/src/test/java/com/seaofnodes/simple/progs/sieve.smp index 0788e6c5..569dbb96 100644 --- a/src/test/java/com/seaofnodes/simple/progs/sieve.smp +++ b/src/test/java/com/seaofnodes/simple/progs/sieve.smp @@ -1,32 +1,31 @@ - -int[] !ary = new int[arg], !primes = new int[arg]; -int nprimes = 0, p=2; -// Find primes while p^2 < arg -while( p*p < arg ) { - // skip marked non-primes - while( ary[p]==1 ) p = p + 1; - // p is now a prime - primes[nprimes] = p; nprimes = nprimes + 1; - // Mark out the rest non-primes - int i = p + p; - while( i < ary# ) { - ary[i] = 1; - i = i + p; - } - p = p + 1; -} -// Now just collect the remaining primes, no more marking -while( p < arg ) { - if( ary[p] == 0 ) { - primes[nprimes] = p; nprimes = nprimes + 1; +// -*- mode: java; -*- +val sieve = { int N -> + // The main Sieve array + bool[] !ary = new bool[N]; + // The primes less than N + u32[] !primes = new u32[N>>1]; + // Number of primes so far, searching at index p + int nprimes = 0, p=2; + // Find primes while p^2 < N + while( p*p < N ) { + // skip marked non-primes + while( ary[p] ) p++; + // p is now a prime + primes[nprimes++] = p; + // Mark out the rest non-primes + for( int i = p + p; i < ary#; i+= p ) + ary[i] = true; + p++; } - p = p + 1; -} -// Copy/shrink the result array -int[] !rez = new int[nprimes]; -int j = 0; -while( j < nprimes ) { - rez[j] = primes[j]; - j = j + 1; -} -return rez; + + // Now just collect the remaining primes, no more marking + for( ; p < N; p++ ) + if( !ary[p] ) + primes[nprimes++] = p; + + // Copy/shrink the result array + u32[] !rez = new u32[nprimes]; + for( int j=0; j < nprimes; j++ ) + rez[j] = primes[j]; + return rez; +}; diff --git a/src/test/java/com/seaofnodes/simple/progs/sort.c b/src/test/java/com/seaofnodes/simple/progs/sort.c new file mode 100644 index 00000000..ad76cc7f --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/sort.c @@ -0,0 +1,28 @@ +#include <stdio.h> +#include <stdlib.h> + +// Test wrapper to call `sort.smp` + +typedef struct AryInt { + unsigned int len; + long long data[]; +} AryInt; + +__attribute__ ((CALL_CONV)) +extern AryInt* merge_sort(AryInt*); + + +long long primes[] = { 25, 97, 89, 83, 79, 73, 71, 67, 61, 59, 53, 47, 43, 41, 37, 31, 29, 23, 19, 17, 13, 11, 7, 5, 3, 2 }; + +int main() { + AryInt *as = (AryInt*)primes; + + merge_sort(as); + + printf("%d[",as->len); + for( int i=0; i<as->len; i++ ) + printf("%d, ",as->data[i]); + printf("]"); + + return 0; +}
\ No newline at end of file diff --git a/src/test/java/com/seaofnodes/simple/progs/sort.smp b/src/test/java/com/seaofnodes/simple/progs/sort.smp new file mode 100644 index 00000000..ae3f4f23 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/sort.smp @@ -0,0 +1,38 @@ +// -*- mode: java; -*- +// based on the top-down version from https://en.wikipedia.org/wiki/Merge_sort + +val merge_sort = { int[] a -> + int[] !b = new int[a#]; + copy_array (a, 0, a#, b); + split_merge(a, 0, a#, b); +}; + +val split_merge = { int[] b, int begin, int end, int[] a -> + if (end - begin <= 1) + return 0; + int middle = (end + begin) / 2; + split_merge(a, begin, middle, b); + split_merge(a, middle, end, b); + merge(b, begin, middle, end, a); + return 0; +}; + +val merge = { int[] b, int begin, int middle, int end, int[] a -> + int i = begin, j = middle; + + for (int k = begin; k < end; k++) { + // && and || + bool cond = false; + if (i < middle) { + if (j >= end) cond = true; + else if (a[i] <= a[j]) cond = true; + } + if (cond) b[k] = a[i++]; + else b[k] = a[j++]; + } +}; + +val copy_array = { int[] a, int begin, int end, int[] b -> + for (int k = begin; k < end; k++) + b[k] = a[k]; +}; diff --git a/src/test/java/com/seaofnodes/simple/progs/stringHash.c b/src/test/java/com/seaofnodes/simple/progs/stringHash.c new file mode 100644 index 00000000..2c6d0fa9 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/stringHash.c @@ -0,0 +1,30 @@ +#include <stdio.h> +#include <stdbool.h> + +typedef struct ArrayU8 { + unsigned int length; + unsigned char data[]; +} ArrayU8; +typedef struct String { + ArrayU8 * cs; + long long _hashCode; +} String; + + +__attribute__ ((CALL_CONV)) +extern bool equals(String*st1, String*st2); + +int main() { + String test1 = {.cs=(ArrayU8*)"\x04\x00\x00\x00test"}; + String test2 = {.cs=(ArrayU8*)"\x05\x00\x00\x00test1"}; + + if( equals(&test1, &test2) ) { + printf("Expected test1 and test2 unequal"); + return 1; + } + if( !equals(&test2, &test2) ) { + printf("Expected test2 and test2 equal"); + return 1; + } + return 0; +} diff --git a/src/test/java/com/seaofnodes/simple/progs/stringHash.smp b/src/test/java/com/seaofnodes/simple/progs/stringHash.smp new file mode 100644 index 00000000..257f79f9 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/stringHash.smp @@ -0,0 +1,31 @@ +// -*- mode: java; -*- +struct String { + u8[] cs; + int _hashCode; +}; + +// Compare two Strings +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; +}; + +// Return the String hashCode (cached, and never 0) +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; +}; |
