#define Size 819000
int sieve ( int N ) {
int64_t i , k , prime , count , n ; char flags [ Size ];
for ( n = 0 ; n < N ; n ++ ) {
count = 0 ;
for ( i = 0 ; i < Size ; i ++ )
flags [ i ] = 1 ;
for ( i = 0 ; i < Size ; i ++ )
if ( flags [ i ]) {
prime = i + i + 3 ;
for ( k = i + prime ; k < Size ; k += prime )
flags [ k ] = 0 ;
count ++ ;
}
}
return count ;
}
void ex100 ( void ) {
printf ("sieve (100) = %d", sieve (100));
}
m_sieve : module
export sieve
sieve : func i32, i32:N
local i64:iter, i64:count, i64:i, i64:k, i64:prime, i64:temp, i64:flags
alloca flags, 819000
mov iter, 0
loop : bge fin, iter, N
mov count, 0; mov i, 0
loop2 : bge fin2, i, 819000
mov u8:(flags, i), 1; add i, i, 1
jmp loop2
fin2 : mov i, 0
loop3 : bge fin3, i, 819000
beq cont3, u8:(flags,i), 0
add temp, i, i; add prime, temp, 3; add k, i, prime
loop4 : bge fin4, k, 819000
mov u8:(flags, k), 0; add k, k, prime
jmp loop4
fin4 : add count, count, 1
cont3 : add i, i, 1
jmp loop3
fin3 : add iter, iter, 1
jmp loop
fin : ret count
endfunc
endmodule
m_ex100 : module
format : string "sieve (10) = %dn"
p_printf : proto p:fmt, i32:result
p_sieve : proto i32, i32:iter
export ex100
import sieve, printf
ex100 : func v, 0
local i64:r
call p_sieve, sieve, r, 100
call p_printf, printf, format, r
endfunc
endmodule
func
関数の署名 (32 ビット符号付き整数の引数を受け取り、32 ビット符号付き整数値を返す) と、64 ビット符号付き整数型のローカル変数となる関数引数N
記述します。;
string
C 文字列の形式でデータを記述しますexport
現在のモジュールの外部で表示されるモジュール関数またはデータを記述します。import
他の MIR モジュールで定義する必要があるモジュール関数またはデータを記述します。proto
関数のプロトタイプを記述します。構文はfunc
構文と同じですcall
関数を呼び出す MIR 命令ですMIR_load_external
を使用して外部 C 関数をロードできます。m1
とm2
はモジュールm_sieve
とm_e100
、 func
は関数ex100
、 sieve
は関数sieve
です)。 /* ctx is a context created by MIR_init / MIR_init2 */
MIR_load_module ( ctx , m1 ); MIR_load_module ( ctx , m2 );
MIR_load_external ( ctx , "printf" , printf );
MIR_link ( ctx , MIR_set_interp_interface , import_resolver );
/* or use MIR_set_gen_interface to generate and use the machine code */
/* or use MIR_set_lazy_gen_interface to generate function code on its 1st call */
/* use MIR_gen (ctx, func) to explicitly generate the function machine code */
MIR_interp ( ctx , func , & result , 0 ); /* zero here is arguments number */
/* or ((void (*) (void)) func->addr) (); to call interpr. or gen. code through the interface */
binfmt_misc
介してバイナリ MIR ファイルを実行するmir-bin-run
バイナリは、次の行 (例) でbinfmt_misc
から使用できるように準備されています。
line=:mir:M::MIR::/usr/local/bin/mir-bin-run:P
echo $line > /proc/sys/fs/binfmt_misc/register
mir-bin-run バイナリ パス(デフォルトのパス)をシステムに適合させてください。
そして一緒に走ります
c2m your-file.c -o your-file
chmod +x your-file
./your-file your args
実行可能ファイルは環境変数で「構成可能」です。
MIR_TYPE
コード実行のインターフェイスを設定します: interp
(解釈用)、 jit
(生成用)、 lazy
(遅延生成用、デフォルト)。MIR_LIBS
(コロン区切りリスト) は、ロードする追加ライブラリのリストを定義します。MIR_LIB_DIRS
またはLD_LIBRARY_PATH
(コロン区切りリスト) は、ライブラリを検索するディレクトリの追加リストを定義します。
mir-bin-run
はbinfmt_misc
と結びついているため、mir-bin-run
直接呼び出すのは少し奇妙かもしれません。 binfmt_misc のP
フラグは、MIR バイナリへのフルパスを含む追加の引数を渡します。
速度と軽量性を実現する非常に短い最適化パイプライン
最も価値のある最適化の使用法のみ:
コンパイル速度と生成されたコードのパフォーマンスを調整するためのさまざまな最適化レベル
MIR のSSA形式はレジスタ割り当ての前に使用されます
極端な生成コードのパフォーマンスに対する最適化実装の簡素化
完全な JIT コンパイラ パイプラインの詳細:
簡略化: MIR を下げる
インライン: MIR 呼び出しのインライン化
CFG の構築: 制御フロー グラフの構築 (基本ブロックと CFG エッジ)
SSA の構築: ファイ ノードと SSA エッジをオペランドに追加して単一の静的割り当てフォームを構築する
アドレス変換: MIR ADDR 命令の削除または変更
グローバル値番号付け: GVN を介して冗長な inns を削除します。これには、定常的な伝播と冗長負荷の除去が含まれます。
コピー伝播: SSA コピー伝播と冗長な拡張命令の削除
デッドストアの削除: 冗長なストアを削除します。
デッドコードの削除: 未使用の出力を持つ inns を削除します。
圧力リリーフ: レジスターの圧力を下げるためにインスを移動します。
SSA 結合: アドレスと比較および分岐命令のペアを結合します。
SSA の外: phi ノードと SSA エッジの削除
Jump opts : さまざまなジャンプの最適化
Machineize : ABI、2-op insns などの呼び出しに対して MIR を変換するマシン依存のコードを実行します。
ループの検索: 自然なループの検索とループ ツリーの構築
ビルドライブ情報: 基本ブロックのライブインとライブアウトの計算
Build Register Conflicts : 移動に関係するレジスタの競合マトリックスを構築します。レジスタの結合に使用されます
Coalesce : 積極的なレジスタ合体
レジスタ アロケータ (RA) : ライブ レンジ分割を備えた優先順位ベースのリニア スキャン RA
Build Live Ranges : レジスタのプログラム ポイント範囲の計算
割り当て: -O0
の場合は高速 RA、 -O1
以上の場合は優先順位ベースのリニア スキャン RA
書き換え: 予約されたハードレジスタを使用して割り当てに従って MIR を変換します
結合(コード選択): データに依存する inns を 1 つに結合します。
デッドコードの削除: 未使用の出力を持つ inns を削除します。
Generate Machine Insns : マシン依存のコードを実行してマシン Insns を作成します
c2m
に小規模な C11 (いくつかの GCC 拡張機能を備えた 2011 ANSI C 標準) を実装しました。 README.mdを参照してください。mir.h
およびmir.c
には、MIR バイナリおよび MIR テキスト表現の入出力を含む主要な API コードが含まれていますmir-dlist.h
、 mir-mp.h
、 mir-varr.h
、 mir-bitmap.h
、 mir-hash.h
、 mir-htab.h
、 mir-reduce.h
には、二重リンクに対応する汎用コードが含まれています。リスト、メモリ プール、可変長配列、ビットマップ、ハッシュ計算、ハッシュ テーブル、データの圧縮/解凍。ファイルmir-hash.h
、ハッシュテーブルで使用される一般的でシンプルな高品質のハッシュ関数です。mir-interp.c
は、MIR コードを解釈するためのコードが含まれています。 mir.c
に含まれており、個別にコンパイルされることはありませんmir-gen.h
、 mir-gen.c
、 mir-gen-x86_64.c
、 mir-gen-aarch64.c
、 mir-gen-ppc64.c
、 mir-gen-s390x.c
、およびmir-gen-riscv64.c
は MIR JIT コンパイラのコードが含まれていますmir-gen-x86_64.c
、 mir-gen-aarch64.c
、 mir-gen-ppc64.c
、 mir-gen-s390x.c
、およびmir-gen-riscv64.c
は、JIT コンパイラーのマシン依存コードです。mir-.c
には、インタプリタと JIT コンパイラに共通の単純なマシン依存コードが含まれていますmir-.h
には、インタープリターと JIT コンパイラーに共通の宣言が含まれていますmir2c/mir2c.h
およびmir2c/mir2c.c
には、MIR から C へのコンパイラ用のコードが含まれています。生成されたコードは移植できない可能性がありますc2mir/c2mir.h
、 c2mir/c2mir.c
、 c2mir/c2mir-driver.c
、およびc2mir/mirc.h
には、C to MIR コンパイラ用のコードが含まれています。ディレクトリc2mir/x86_64
およびc2mir/aarch64
、 c2mir/ppc64
、 c2mir/s390x
、およびc2mir/riscv64
内のファイルには、対応する C to MIR コンパイラ用の x86_64、aarch64、ppc64le、s390x、および riscv マシン依存コードが含まれています。mir-bin-run.c
には、上記のmir-bin-run
のコードが含まれていますb2ctab
ユーティリティを備えたファイルmir-bin-driver.c
は、MIR バイナリ ファイルからバイナリを生成するポータブルな方法として使用できます。mir-utils
には、MIR を操作するためのさまざまなユーティリティが含まれています。たとえば、バイナリ MIR からテキスト MIR への変換、またはその逆の変換などです。adt-tests
、 mir-tests
、 c-tests
、およびc-benchmarks
は、MIR およびc2m
テストおよびベンチマーク用のコードが含まれていますmake bench
とmake test
使用して、いくつかのベンチマークとテストを実行できます。 GCC-12.3.1 を搭載した FC37 で 64GB メモリを搭載した Intel i5-13600K
MIRジェネレーター | MIRインタープリター | gcc -O2 | gcc -O0 | |
---|---|---|---|---|
編集 [1] | 1.0 (249us) | 0.09 (22μs) | 109 (27.1ミリ秒) | 105 (26.1ミリ秒) |
処刑[2] | 1.0 (1.74秒) | 13.7 (23.8秒) | 0.92 (1.6秒) | 2.28 (3.97秒) |
コードサイズ [3] | 1.0 (557KB) | 0.43 (240KB) | 58 (32.2MB) | 58(32.2MB) |
ロック [4] | 1.0 (23.4K) | 0.48 (11.3K) | 103 (2420K) | 103 (2402K) |
[1] は、C sieve コード (インクルード ファイルなし、GCC のメモリ ファイル システムを使用) と、最適化レベル 2 の MIR インタプリタおよび MIR ジェネレータによる対応する MIR sieve コードのコンパイルにかかった時間に基づいています。
[2] は、MIR ジェネレーター最適化レベル 2 を使用した場合の 10 回の実行の最良の経過時間に基づいています。
[3] は、GCC および MIR コアおよび MIR のインタープリターまたはジェネレーターの cc1 のストリップ サイズに基づいています。
[4] x86-64 GNU C コンパイラに必要なファイルと、MIR コードを作成して実行するための最小限のプログラムの MIR ファイルのみに基づいた私の推定
GCC-12.3.1 を搭載した FC37 で 64GB メモリを搭載した Intel i5-13600K
c2m -O2 -eg (ジェネレーター) | c2m -ei (インタプリタ) | gcc -O2 | gcc -O0 | |
---|---|---|---|---|
編集 [1] | 1.0 (336us) | 1.0 (337us) | 80 (27.1ミリ秒) | 77 (26.1ミリ秒) |
処刑[2] | 1.0 (1.74秒) | 13.7 (23.8秒) | 0.92 (1.6秒) | 2.28 (3.97秒) |
コードサイズ [3] | 1.0 (961KB) | 1.0 (961KB) | 34 (32.2MB) | 34(32.2MB) |
ロック [4] | 1.0 (54.8K) | 1.0 (54.8K) | 44 (2420K) | 44 (2420K) |
[1] は、C sieve コードのコンパイルにかかった時間に基づいています (インクルード ファイルなし、GCC のメモリ ファイル システムを使用した場合)
[2] は、MIR ジェネレーター最適化レベル 2 を使用した場合の 10 回の実行の最良の経過時間に基づいています。
[3] は、GCC および C2MIR の cc1、MIR コア、インタープリター、および MIR のジェネレーターのストリップ サイズに基づいています。
[4] はテストを除くすべてのソース ファイルに基づいています
以下は、同じマシン上の 15 個の小さな C ベンチマーク (ディレクトリc-benchmarks
から) での、さまざまな C コンパイラの GCC -O2 に関連する生成されたコードのパフォーマンスです。
平均 | ジオメアン | |
---|---|---|
gcc -O2 | 1.00 | 1.00 |
gcc -O0 | 0.63 | 0.57 |
c2m -eg | 0.96 | 0.91 |
c2m-eb | 0.92 | 0.85 |
チビック | 0.38 | 0.30 |
カラン -O2 | 1.12 | 1.09 |
cparser -O3 | 1.02 | 0.98 |
cproc | 0.68 | 0.65 |
lacc -O3 | 0.47 | 0.39 |
pcc -O | 0.80 | 0.78 |
tcc | 0.54 | 0.50 |
emcc -O2/ワスマー | 0.60 | 0.55 |
wasi -O2/ワスマークレーンリフト | 0.60 | 0.54 |
wasi -O2/ワスマー LLVM | 0.78 | 0.72 |
wasi -O2/wasmer シングルパス | 0.45 | 0.36 |
wasi -O2/wasmtime | 0.92 | 0.87 |
c2m
を含む) を別のターゲットに移植できるのに 1 ~ 2 か月かかると考えられます。