111 Views
January 03, 21
スライド概要
自作言語OpeLaでSDLを使い絵を描く方法を説明します。SDLはC言語で作られており、OpeLaから簡単に使えます。後半は可変長引数の対応方針を紹介します。
サイボウズ・ラボ株式会社で教育向けのOSやCPU、コンパイラなどの研究開発をしています。
自作言語でお絵描き 2021年1月3日 第24回自作OSもくもく会 @uchan_nos
自己紹介 名前:内田公太 Twitter:@uchan_nos 東京工業大学 情報工学系 特任助教(週2) サイボウズ・ラボ株式会社(週3) 活動 osdev-jpの運営 OS、言語処理系の開発 『30日でできる! OS自作入門』 の校正を担当(2006年) 『自作エミュレータで学ぶ x86アーキテクチャ』著(2015年)
OpeLaプロジェクトとは OpeLa: Operating and Language processing system OSと言語処理系を全部自作するプロジェクト OS アセンブラ コンパイラ リンカ ライブラリ 特徴:完全なセルフホスト
OpeLaで書き初め
OpeLaで書き初めの仕組み OpeLaでSDLを使う SDLでウィンドウ、背景、線を表示 SDL: Simple DirectMedia Layer マルチメディアを扱うライブラリ シンプルなライブラリ故に対応機種 が幅広い C言語用インタフェースなのでいろ んな言語から簡単に使える! ロゴが明滅する様子
SDLの関数を呼び出す
func main() int {
if SDL_Init(0x20) < 0 { // SDL_INIT_VIDEO
printf("Failed to initialize SDL: %s\n", SDL_GetError());
return 1;
}
printf("Creating a window\n");
window := SDL_CreateWindow("SDL by OpeLa", 0x1fff0000, 0x1fff0000, 300, 200, 0);
extern "C" SDL_Init func(flag uint) int;
extern "C" SDL_GetError func() *byte;
extern "C" SDL_CreateWindow func(title *byte, x, y, w, h int, flags uint) *int;
SDLの関数をextern宣言し、呼び出す
ヘッダファイルは読み込めないのでマクロは使えない
SDL_Window*の代わりにint*
イベントポーリング var event [16]uint32; for { for SDL_PollEvent(&event[0]) != 0 { if event[0] == uint32(256) { // SDL_QUIT SDL_DestroyWindow(window); SDL_Quit(); return 0; } } SDL_Eventの代わりに[16]uint32
グラデーション背景 color += dir; if color == 255 { dir = -1; } else if color == 0 { dir = 1; } SDL_SetRenderDrawColor(renderer, 0, color, color, 255); SDL_RenderClear(renderer); Colorを1ずつ変化させ、黒→青緑に徐々に変化させる SDL_SetRenderDrawColorで描画色を設定し SDL_RenderClearで背景を塗りつぶす
ロゴ SDL_SetRenderDrawColor(renderer, 0, // O DrawRect(renderer, 30, 60, logo_x + // p DrawRect(renderer, 30, 30, logo_x SDL_RenderDrawLine(renderer, logo_x logo_x 0, 0, 255); 40*0, logo_y); + 40*1, logo_y + 30); + 40*1, logo_y + 60, + 40*1, logo_y + 90); func DrawRect(ren *int, w, h, x, y int) { var r [4]int32; r[0] = x; r[1] = y; r[2] = w; r[3] = h; SDL_RenderDrawRect(ren, &r[0]); } 長方形と直線の描画を駆使してロゴを描く
SDLの関数を呼び出すときの注意
SDLに限らないが、関数を呼び出す際はスタックのアライメント
に注意が必要
参考「x86-64 モードのプログラミングではスタックのアライメントに気を付けよう」
https://uchan.hateblo.jp/entry/2018/02/16/232029
関数を呼び出す直前でRSPが16の倍数でなければならない
void Call(std::ostream& os, Register addr) override {
os << "
push rbx\n";
os << "
mov rbx, rsp\n";
and rsp, -16
os << "
and rsp, -16\n";
=
os << "
call " << RegName(addr) << "\n";
and rsp, 0xf…f0
os << "
mov rsp, rbx\n";
os << "
pop rbx\n";
}
RBXは関数呼び出し前後で保存されている必要があるから、スタックに保存してから使う
SDLを使うサンプルのビルド cat sdl.opl | ../../opelac > sdl.s cc sdl.s -lSDL2 sdl.oplをコンパイルしてsdl.sを得て libSDL2.soとリンクする
可変長引数の対応
OpeLaで可変長引数 可変長引数:int printf(const char* format, ...);の... C言語で可変個の引数を渡す仕組み 呼び出し側 x86-64(SystemV AMD64 ABI) 普通の引数と同様に、レジスタ渡し RDI, RSI, RDX, RCX, R8, R9 RDI RSI RDX RCX format 1 2 3 X0 AArch64(EABI) 可変長引数だけスタック渡し format 1 2 3 SP
OpeLaの仕組みと可変長引数
OpeLaはスタックマシン
SP
式はスタックに値を積む
「1 + 2」は右図
SP
1
2
1
SP
3
SP
printf("%d:%d", 1, 2)
x86-64:いったんスタック
に引数を積み、最後にレジス
タへ転写すれば良い
普通の関数呼び出しと同じ!
文字列へのアドレス
1
2
for (int i = 0; i < num_args; ++i) {
asmgen->Pop64(os, kArgRegs[i]);
}
AArch64で可変長引数 printf("%d:%d", 1, 2) AArch64では、途中までレジ スタへ転写すればいいので は? SP 1 スタックに残す 2 →ダメだった SP AArch64ではスタックに式の 文字列へのアドレス 値を8バイト飛ばしで積んで いるから 空き 1 空き AppleのABIではこうなっているが、Armの世界で一般 的なEABIでは可変長引数もレジスタ渡しらしい。 https://developer.apple.com/documentation/xcode/ writing_arm64_code_for_apple_platforms レジスタへ 文字列へのアドレス 2 空き AArch64だと 実はこうなってる
なぜAArch64だと8バイト飛ばしなの? 16の倍数でないSPがベースアドレスとして使われると例外発生 str x0, [sp, #-16]! Spから16を引き、そのアドレスにx0の値を書く だから、opelacは8バイト飛ばしでスタックに値を積む →そのままでは可変長引数として使えない!
私の今日の目標 AArch64で可変長引数をサポートする 引数省略記号「...」にパーサを対応させ、 可変長引数の部分はスタックに残し、 スタックの8バイトの隙間を詰め、 最後に関数を呼び出す とうまく行くかなあ…? SP 可変長引数1 コピー 空き 可変長引数2 コピー コピー 空き 可変長引数3 空き 可変長引数4
OpeLaに興味ある人を募集してます 開発の様子は Twitterの@uchan_nos osdev-jp Slackの#opelaチャンネル に書いてます 今は言語設計とコンパイラ実装を並走中 「配列リテラルを実装するかどうか」 「sizeof(uint2)は何を返すべきか」 などなど… Wanted ・言語設計の話し相手 ・OpeLaロゴ制作 ・アプリ製作 ・実装のお手伝い 設計すべき部分がたくさんある 一緒にお話ししながらアイデアを練っていきたいです