LLVM, Clang 上の Continuation based C コンパイラの改良

Kaito TOKUMORI

code segment を用いるプログラミング言語 CbC

関数やクラス、オブジェクト等は分割、結合を行うことは困難である。

アセンブリは分割、結合が可能だが易しくない。

当研究室ではプログラムを code segment、data segmentという単位を用いて書くという手法を提案しており、Cerium, Continuation based C(CbC)等がそれらを利用する。

それぞれの単位は分割と結合を行うことが可能で、プログラムの分割、結合を容易にする。

それぞれの単位はメタレベルのものが存在し, メタ計算の記述も可能である。

Continuation based C(CbC)

コード例

__code f() {
    goto g();
}

__code g() {
    goto h();
}
              
  • __code は code segment であることを示す
  • code segment は返り値を持たない
  • 軽量継続は goto のとなりに code segment 名と引数を書く

CbC コンパイラ

LLVM Clang

LLVM Clang を用いる利点

LLVM と Clang の 基本構造

clang はソースコードを読み込むと Parser を用いて clangAST を生成する。CodeGenがASTを元にLLVM IRを生成し, それが LLVM の入力に対応する。

LLVM は中間表現の形を何度も変化させる。最適化をかけながら徐々にターゲットへの依存度が高くし、最終的にアセンブリコードや実行可能ファイルを出力する。最適化を含む全ての処理がパスによって行われる。

LLVM と Clang のもつ 中間表現

LLVM IR

define fastcc void @factorial(i32 %x) #0 {
  entry:
  tail call fastcc void @factorial0(i32 1, i32 %x)
  ret void
}
              

CbC 実装概要

__code 型

goto code_segment();

元のコード 生成される AST に対応するコード
__code code1() {
     :
  goto code2();
}
              
void code1() {
     :
  code2();
  return;
}
              

プロトタイプ宣言の自動生成

元のコード 生成される AST に対応するコード
__code code1(int a, int b) {
     :
  goto code2(a,b);
}

__code code2(int a, int b){
     :
}
              
__code code2(int a, int b);
__code code1(int a, int b) {
     :
  goto code2(a,b);
}

__code code2(int a, int b){
     :
}
              

tail call elimination の強制

tail call elimination

tail call elimination の強制

CbC から生成される LLVM IR

元のコード LLVM IR
__code code1() {
     :
  goto code2();
}

__code code2(){
     :
}
              
define fastcc void @code1() #0 {
entry:
     :
tail call fastcc void @code2()
ret void
}

define fastcc void @code2() #0 {
     :
}
              

環境付き継続

環境付き継続の例

  • __return と __environment を用いる
  • __return は funcB へ戻るための code segment
  • __environment は funcB の環境
  • この例では funcB が -1 でなく 1 を返す
__code cs(__code(*ret)(int,void *),void *env){ goto ret(1,env); } int funcB(){ goto cs(__ret, __env); /* never reached */ return -1; } int funcA(){ int retval; retval = funcB(); printf("return = %d\n",retval); return 0; }

環境付き継続実装方法

環境付き継続実装の問題

code segment 名の問題の解決法

戻り値の型の問題の解決法

LLVM での環境付き継続の実装

  • setjmp.h を自動で include
  • 環境を保存するための構造体の生成
  • C の関数で __builtin_setjmp を生成して環境を保存
  • __builtin_longjmp を用いて元の環境に戻る code segment を生成
#include struct CbC_env { void *ret_p,*env; }; __code cs(int retval,__code(*ret)(int,void *),void *env){ goto ret(n, env); } __code func..ret (int retval, void* env){ *(int*)((struct CbC_env *)(env))->ret_p = retcal; __builtin_longjmp((int*)(((struct CbC_env *)env)->env),1); } int func (){ __code (*__return)(); struct CbC_env __environment; jmp_buf env; int retval; __environment.ret_p = &retval; __environment.env = &env; __return = func..ret; if (__builtin_setjmp(__environment.env)){ return retval; } goto code1(30, __return, &__environment); return 0; }

Gears OS サポート

Gears OS コード例

  • code segment 間の遷移に meta code segment の処理が入る
  • data segment へは context からアクセスできる
  • 必要な data segment の取得は stub で行われる
__code meta(struct Context* context, enum Code next) { goto (context->code[next])(context); } __code code1_stub(struct Context* context) { goto code1(context, &context->data[Allocate]->allocate); } __code code1(struct Context* context, struct Allocate* allocate) { allocate->size = sizeof(long); allocator(context); goto meta(context, Code2); } __code code2(struct Context* context, long* count) { *count = 0; goto meta(context, Code3); }

Gears OS サポート

サポートする機能

Gears OS サポート

従来のコード 記述を簡易化したコード
__code meta(struct Context* context, enum Code next) {
  goto (context->code[next])(context);
}

__code code1_stub(struct Context* context) {
  goto code1(context, &context->data[Allocate]->allocate);
}

__code code1(struct Context* context, struct Allocate* allocate) {
  allocate->size = sizeof(long);
  allocator(context);
  goto meta(context, Code2);
}


__code code2(struct Context* context, long* count) {
  *count = 0;
  goto meta(context, Code3);
}

__code code2_stub(struct Context* context) {
  goto code2(context, &context->data[Count]->count);
}
              
__code meta(struct Context* context, enum Code next) {
  goto (context->code[next])(context);
}

__code code1(struct Allocate* allocate) {
  allocate->size = sizeof(long);
  allocator();
  goto code2();
}

__code code2(long* count) {
  *count = 0;
  goto code3();
}
              

評価

アセンブリコード

CbCのコード 出力されたアセンブリ
__code f(int i,stack sp) {
  int k,j;
  k = 3+i;
  goto f_g0(i,k,sp);
}
              
_f:                                     ## @f
	.cfi_startproc
## BB#0:                                ## %entry
	subq	$24, %rsp
Ltmp9:
	.cfi_def_cfa_offset 32
	movl	%edi, %eax
	addl	$3, %eax
	movq	%rsi, 16(%rsp)          ## 8-byte Spill
	movl	%eax, %esi
	movq	16(%rsp), %rdx          ## 8-byte Reload
	addq	$24, %rsp
	jmp	_f_g0                   ## TAILCALL
	.cfi_endproc
              

環境付き継続の速度比較

C, Scheme との速度比較

まとめ

今後の課題