-
-
Save rhysd/0a4cc1c1f62ff03389a1c00081b29fbc to your computer and use it in GitHub Desktop.
elvm/target/viml.c
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <ir/ir.h> | |
#include <target/util.h> | |
// a〜d の4つの汎用レジスタとプログラムカウンタやスタックポインタなどのレジスタ定義 | |
static const char* REG_NAMES_VIML[] = { | |
"s:a", "s:b", "s:c", "s:d", "s:bp", "s:sp", "s:pc" | |
}; | |
// 初期化処理.コード生成時に最初に一度だけ呼ばれる | |
static void init_state_viml(Data* data) { | |
reg_names = REG_NAMES_VIML; | |
// すべてのレジスタを 0 で初期化 | |
for (int i = 0; i < 7; i++) { | |
emit_line("let %s = 0", reg_names[i]); | |
} | |
// メモリはリストを使って表す.2^24 = 16777216 分の領域を 0 で初期化 | |
emit_line("let s:mem = repeat([0], 16777216)"); | |
// C プログラムのデータ部分をメモリにロードする処理 | |
for (int mp = 0; data; data = data->next, mp++) { | |
if (data->v) { | |
emit_line("let s:mem[%d] = %d", mp, data->v); | |
} | |
} | |
} | |
static void viml_emit_inst(Inst* inst) { | |
switch (inst->op) { | |
// 値のコピー. | |
// | |
// e.g. let s:d = 4 | |
case MOV: | |
emit_line("let %s = %s", reg_names[inst->dst.reg], src_str(inst)); | |
break; | |
// 値の加算 | |
// 加算結果がオーバーフローしているかもしれない (ワード長は24bit) ので,and を取ります. | |
// e.g. let s:b = and((s:a + s:c), 16777216) | |
case ADD: | |
emit_line("let %s = and((%s + %s), " UINT_MAX_STR ")", | |
reg_names[inst->dst.reg], | |
reg_names[inst->dst.reg], src_str(inst)); | |
break; | |
// 値の減算 | |
case SUB: | |
emit_line("let %s = and((%s - %s), " UINT_MAX_STR ")", | |
reg_names[inst->dst.reg], | |
reg_names[inst->dst.reg], src_str(inst)); | |
break; | |
// 値のメモリからの読み出し | |
// e.g. let s:a = s:mem[s:b] | |
case LOAD: | |
emit_line("let %s = s:mem[%s]", reg_names[inst->dst.reg], src_str(inst)); | |
break; | |
// メモリへの書き込み | |
// e.g. let s:mem[s:a] = s:d | |
case STORE: | |
emit_line("let s:mem[%s] = %s", src_str(inst), reg_names[inst->dst.reg]); | |
break; | |
// 1文字書き出す. :echon を使ってメッセージとして出力 | |
// e.g. echon nr2char(64) | |
case PUTC: | |
emit_line("echon nr2char(%s)", src_str(inst)); | |
break; | |
// 1文字入力を受け取る | |
// getchar() を使ってユーザから入力を受け取る | |
// getchar() は基本的に入力された1バイトを数値として返すが,特別な場合に文字列を | |
// 返したりするので,数値でないときは 0 (読めなかった)としてレジスタに書き込む | |
// e.g. | |
// let l:c = getchar() | |
// let s:a = type(l:c) == type(0) ? l:c : 0 | |
case GETC: | |
emit_line("let l:c = getchar()"); | |
emit_line("let %s = type(l:c) == type(0) ? l:c : 0", reg_names[inst->dst.reg]); | |
break; | |
// プログラムを終了する | |
// Vim script にはその場でプログラムを終了する方法は無い(:finish は :source で読み込まれた | |
// 場合などに使えないので,1 を返したときはプログラム終了として呼び出し元でハンドルする | |
case EXIT: | |
emit_line("return 1"); | |
break; | |
case DUMP: | |
break; | |
// 各種比較命令. | |
// cmp_str() ヘルパ関数が比較関数を生成してくれるので,手で書く必要はない (引数の "1" は真値) | |
// e.g. let s:a = s:a >= s:b ? 1 : 0 | |
case EQ: | |
case NE: | |
case LT: | |
case GT: | |
case LE: | |
case GE: | |
emit_line("let %s = %s ? 1 : 0", reg_names[inst->dst.reg], cmp_str(inst, "1")); | |
break; | |
// ジャンプ命令. | |
// 条件を満たした時ジャンプする(プログラムカウンタに特定のアドレスを書き込む) | |
case JEQ: | |
case JNE: | |
case JLT: | |
case JGT: | |
case JLE: | |
case JGE: | |
case JMP: | |
emit_line("if %s", cmp_str(inst, "1")); | |
inc_indent(); | |
emit_line("let s:pc = %s - 1", value_str(&inst->jmp)); | |
dec_indent(); | |
emit_line("endif"); | |
break; | |
// 想定外の命令が来た時.unreachable なはず | |
default: | |
error("oops"); | |
} | |
} | |
// Func0 などの関数の開始部分のコードを生成する | |
// プログラムカウンタの値が一定のうちは実行し,自分が実行すべき範囲を抜けたときに | |
// while ループを抜けて関数を終了する. | |
// CHUNKED_FUNC_SIZE が実行するプログラムカウンタの幅 (512) | |
static void viml_emit_func_prologue(int func_id) { | |
emit_line(""); | |
emit_line("function! Func%d()", func_id); | |
inc_indent(); | |
emit_line("while %d <= s:pc && s:pc < %d", | |
func_id * CHUNKED_FUNC_SIZE, (func_id + 1) * CHUNKED_FUNC_SIZE); | |
inc_indent(); | |
// elseif 決め打ちでコード生成できるように if 0 で始める | |
emit_line("if 0"); | |
inc_indent(); | |
} | |
// Func0 などの関数の終了部分のコードを生成する | |
static void viml_emit_func_epilogue(void) { | |
dec_indent(); | |
emit_line("endif"); | |
emit_line("let s:pc += 1"); | |
dec_indent(); | |
emit_line("endwhile"); | |
dec_indent(); | |
emit_line("endfunction"); | |
} | |
// 各プログラムカウンタごとに分岐する処理 | |
// Vim script では elseif s:pc == {値} で分岐する | |
static void viml_emit_pc_change(int pc) { | |
emit_line(""); | |
dec_indent(); | |
emit_line("elseif s:pc == %d", pc); | |
inc_indent(); | |
} | |
// エントリポイント | |
void target_viml(Module* module) { | |
// 初期化処理のコードを生成.上記(1) | |
init_state_viml(module->data); | |
emit_line(""); | |
// emit_chunked_main_loop() は Func0, Func1, ... の定義を生成する関数.上記(2) | |
// 上で定義したいくつかの関数を渡すと各チャンクを実行する関数のコードを生成してくれる | |
int num_funcs = emit_chunked_main_loop(module->text, | |
viml_emit_func_prologue, | |
viml_emit_func_epilogue, | |
viml_emit_pc_change, | |
viml_emit_inst); | |
// プログラムの実行部分のコード生成.上記 (3) | |
emit_line(""); | |
emit_line("while 1"); | |
inc_indent(); | |
emit_line("if 0"); | |
for (int i = 0; i < num_funcs; i++) { | |
emit_line("elseif s:pc < %d", (i + 1) * CHUNKED_FUNC_SIZE); | |
inc_indent(); | |
// Func%d() returns 1 if the program exited or not (otherwise returns 0). | |
emit_line("if Func%d() | break | endif", i); | |
dec_indent(); | |
} | |
emit_line("endif"); | |
dec_indent(); | |
emit_line("endwhile"); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment