Continuation based Cの GCC 4.6 上の実装について

  • 大城 信康
  • 琉球大学 並列信頼研究室

    目的と背景(1)

  • 当研究室ではコードセグメント単位で記述するプログラミング言語Continuation based C (以下CbC)を開発している。
  • コードセグメントは並列実行の単位として使うことができ、プログラムの正しさを示す単位としても使用することができる。
  • Many Core での並列実行を高い性能と高い信頼性で実現することができると考えている。
  • 目的と背景(2)

  • CbCのコンパイラは2001年に Micro-C版、2008年にはGCC-4.2をベースとしたコンパイラが開発された。
  • GCC上のCbCコンパイラは、GCCで修正・追加されていく最適化の機能を使用する為に、アップデートに合わせ変更する必要がある。
  • 本研究ではCbCコンパイラをGCC-4.6へとアップデートを行った。
  • 発表内容

    1. CbC の紹介
    2. GCC でのコンパイルの流れ
    3. CbC の実装
    4. Micro-C との性能比較
    5. まとめ

    Continuation based C

    コードセグメント単位での記述と継続を基本としたプログラミング言語。

    Continuation based C

    継続:現在の処理を実行していく為の情報

    Cの継続 CbCの継続(軽量継続)
    • 続く命令のアドレス
    • 命令に必要なデータ
    • スタックに積まれている値(環境)
    • Cの継続から環境を除外
    • 続く命令とその命令に必要なデータのみ
  • コードセグメントへの継続はcallではなくjmp命令で行われる
  • Continuation based C

    階乗を求めるCbCのプログラム
    
    __code print_factorial(int prod) {
      printf("factorial = %d\n",prod);
      exit(0); 
    }
    
    __code factorial0(int prod, int x) {
      if ( x >= 1) {
        goto factorial0(prod*x, x-1);
      }else{
        goto print_factorial(prod);
      } 
    }
    	  
    
    __code factorial(int x) {
      goto factorial0(1, x); 
    }
    
    int main(int argc, char **argv) {
      int i;
      i = atoi(argv[1]);
      goto factorial(i); 
      return 0;
    }
    
  • 以上がCbCについての紹介となる。
  • GCC

  • GCC:Gnu Compiler Collection
  • GCC:Generic Tree

  • CALL_EXPRE、MODIFY_EXPR、RETURN_EXPR等といった表現で扱われる。
  • ソースコード Generic Treeでの表現
    int main() {
      int a, b;
      a = 3;
      b = func(a, 10);
      return b;
    }
    	
  • CbCの実装においてこのGeneric Treeの生成を意識していくことになる。
  • CbCの実装

    CbCの実装:__codeシンタックスの追加

    tree
    build_code_segment_type (tree value_type, tree arg_types)
    {
      tree t;
      hashval_t hashcode = 0;
    
      gcc_assert (TREE_CODE (value_type) == VOID_TYPE);
    
      /* Make a node of the sort we want.  */
      t = make_node (FUNCTION_TYPE);
      TREE_TYPE (t) = value_type;
      TYPE_ARG_TYPES (t) = arg_types;
    
      CbC_IS_CODE_SEGMENT (t) = 1;
    
      if (!COMPLETE_TYPE_P (t))
        layout_type (t);
      return t;
    }
    	      
  • コードセグメントはGCC内部では関数として扱われる。
  • CbCの実装:gotoシンタックスの追加

  • goto によるコードセグメントへの継続
  •  case RID_GOTO:
       c_parser_consume_token (parser);
       if ( c_parser_next_token_is (parser, CPP_NAME)
    	&& c_parser_peek_2nd_token (parser)->type == CPP_SEMICOLON )
         {
              :
         }
    #ifndef noCbC
       else
         {
           if (c_parser_next_token_is (parser, CPP_NAME))
    	 {
    	   tree id = c_parser_peek_token (parser)->value;
    	   location_t loc = c_parser_peek_token (parser)->location;
    	   /** build_external_ref (id,RID_CbC_CODE , loc); **/
    	   build_external_ref (loc, id, RID_CbC_CODE, &expr.original_type);
    	 }
           expr = c_parser_expr_no_commas (parser, NULL);
           if (TREE_CODE(expr.value) == CALL_EXPR )
    	 {
    	   location_t loc = c_parser_peek_token (parser)->location;
    	   cbc_replace_arguments (loc, expr.value);
    	   TREE_TYPE(expr.value) = void_type_node;
    	   CbC_IS_CbC_GOTO (expr.value) = 1;
    	   CALL_EXPR_TAILCALL (expr.value) = 1;
    	   add_stmt(expr.value);
    	   stmt = c_finish_return(loc, NULL_TREE, NULL_TREE);
    	 }
           else
    	 c_parser_error (parser, "expected code segment jump or %<*%>");
         }
    #else
    	

    CbCの実装:gotoシンタックスの追加

    実際のコード GCC内で処理されるコード
    
    __code test() {
       :
      goto factorial0(1, x); 
    }
    	    
    
    void test() {
       :
      factorial0(1, x); 
      return;
    }
    	    

    CbCの実装:軽量継続(末尾除去)

    軽量継続は末尾除去(Tail Call elimination)によって実装される。

  • 以下のソースの場合 関数g から関数f へjmp命令で処理が移る。

  • 
    void f(int a, int b) {
      printf("f: a=%d b=%d\n",a,b);
      return ;
    }
    void g(int a, int b){
      printf("g: a=%d b=%d\n",a,b);
      f(a,b);
      return;
    }
    
    int main() {
      g(3,4);
      return 0;
    }
    	    
    	  

    CbCの実装:軽量継続(末尾除去)

    CbCの実装:軽量継続(末尾除去)

  • 末尾除去の条件はexpand_call関数で調べられる。
  • CbCの実装:軽量継続(末尾除去)

    CbCの実装:軽量継続(末尾除去)の実装について

    CbCの実装:引数渡し

  • GCC版コンパイラー開発当初、コンパイルしたCbCのプログラムはMicro-C版に速度面で勝てなかった。
  • そこで、GCC版CbCコンパイラの引数渡しもできるだけレジスタで行うことに。
  • CbCの実装:引数渡し(fastcall)

    fastcall

  • __codeで宣言された関数は自動でfastcall属性が付与されるように以下のコードを追加。
  • if(!TARGET_64BIT) {
      attrs = build_tree_list (get_identifier("fastcall"), NULL_TREE); 
      declspecs_add_attrs(specs, attrs);
     }	  
    	

    Intel64 ではレジスタが増えていてfastcallの機能は標準でつくようになっている。

    CbCの実装:引数渡し



    引数渡しに使われるレジスタの数(gcc)
    arch int(整数型) float(浮動小数点型) double(浮動小数点型)
    i386 2 0
    (stackを使用)
    0
    (stackを使用)
    x64 6 8 8

    CbCの実装:環境付き継続

  • 以下の使い方の場合は1を返す。
  • __code c1(__code ret(int,void *),void *env) {
     goto ret(1,env);
    }
    int main() {
     goto c1(__return, __environment);
    }
    	

    __environmentキーワードは関数の環境を保持する(Micro-Cの場合)。

    CbCの実装:環境付き継続

    生成しているコード 生成するコード(GCC内部)
    
    //goto c1(__return, __environment);
    goto c1(({
    	  __label__ _cbc_exit0;
    	  static int retval;
    	  void _cbc_internal_return(int retval_, void *_envp) {
    	    retval = retval_;
    	    goto _cbc_exit0; 
              }
    	  if (0) { 
                _cbc_exit0:
    	    return retval; 
              }
    	  _cbc_internal_return;
    	}), __environment);
    	  
    
        case RID_CbC_RET:
    {
      tree value, stmt, label, tlab, decl;
      c_parser_consume_token (parser);
    
      stmt = c_begin_stmt_expr ();
      cbc_return_f = c_parser_peek_token (parser)->value;
      location_t location = c_parser_peek_token (parser)->location;
    
      /* create label. (__label__ _cbc_exit0;) */
      label = get_identifier ("_cbc_exit0");
      tlab = declare_label (label);
      C_DECLARED_LABEL_FLAG (tlab) = 1;
      add_stmt (build_stmt (location, DECL_EXPR, tlab));
    
      /* declare retval.  (int retval;) */
      tree decl_cond =
        build_decl (location, VAR_DECL, get_identifier ("retval"),
    		TREE_TYPE (TREE_TYPE (current_function_decl)));
      TREE_STATIC (decl_cond) = 1;
      TREE_USED (decl_cond) = 1;
    
      /* Use thread-local */
      DECL_TLS_MODEL (decl_cond) = decl_default_tls_model (decl_cond);
      DECL_NONLOCAL (decl_cond) = 1;
      add_stmt (build_stmt(location, DECL_EXPR,  pushdecl (decl_cond)));
    
      /* define nested function.  */
      decl =
        cbc_finish_nested_function (location, label, decl_cond);
      TREE_USED(decl) = 1;
    
      /* define if-ed goto label and return statement. */
      cbc_finish_labeled_goto (location, label, decl_cond);
    
      /* get pointer to nested function.  */
      value = build_addr (decl , current_function_decl);
      TREE_USED (current_function_decl) = 1;
      SET_EXPR_LOCATION (value, location);
      add_stmt (value);
    
      TREE_SIDE_EFFECTS (stmt) = 1;
      expr.value = c_finish_stmt_expr (location, stmt);
      expr.original_code = ERROR_MARK;
    }
    			 
  • retval変数の型は継続を行った関数と同じ戻値の型となる。
  • 環境付き継続:実装の問題

  • 重要な部分
  • 次の方法が考えられる。
  • 環境付き継続:実装の問題

    クロージャの動作について

  • 予稿における訂正
  • 『GCC 4.6 と Lion の組合せでは Closure は正しく動作していないことが分かった.』
  • Micro-Cとの比較

    Micro-C,GCC-4.4とGCC-4.6のCbCコンパイラでコンパイルしたプログラムの実行の速度
  • 最適化無しだと、引数を全て一時変数に代入するGCCは遅い。 だが、最適化にかければ不要な代入は減りMicro-C版より早くなる。
  • まとめ

    今後の課題


    ご清聴ありがとうございました。

    CbCの引数渡し

    fastcall属性有・無の実行速度
    fastcall有り fastcall無し

    軽量継続(末尾除去)の動作

  • スタック:呼び出し元関数と同じ範囲を使うことになる。
    • func_bの引数はfunc_aのスタックに上書する
    • func_bの為にスタックポインタは伸ばされない

    call_insn

  • expand_callより作成されたRTLのTreeは以下のS式となる。
  • 
    (call_insn/j 18 17 19 3 (call (mem:QI (symbol_ref:DI ("factorial") [flags 0x403]  <function_decl 0x1443b4400 factorial>) [0 S1 A8])
            (const_int 1024 [0x400])) factorial.cbc:30 -1
         (expr_list:REG_EH_REGION (const_int 0 [0])
            (nil))
        (expr_list:REG_DEP_TRUE (use (reg:SI 5 di))
            (nil)))
    	
  • __rectype の実装

    selftype

    selftypeの実装

  • 以下の宣言が行えるようにしたい。
  • typedef sturct node {
     selftype *left;
     selftype *right;
     int num;
    }*NODE
    	

    selftype は struct node を指す。

    環境付き継続

    生成する為のコード 生成されるTree
    
        case RID_CbC_RET:
    {
      tree value, stmt, label, tlab, decl;
      c_parser_consume_token (parser);
    
      stmt = c_begin_stmt_expr ();
      cbc_return_f = c_parser_peek_token (parser)->value;
      location_t location = c_parser_peek_token (parser)->location;
    
      /* create label. (__label__ _cbc_exit0;) */
      label = get_identifier ("_cbc_exit0");
      tlab = declare_label (label);
      C_DECLARED_LABEL_FLAG (tlab) = 1;
      add_stmt (build_stmt (location, DECL_EXPR, tlab));
    
      /* declare retval.  (int retval;) */
      tree decl_cond =
        build_decl (location, VAR_DECL, get_identifier ("retval"),
    		TREE_TYPE (TREE_TYPE (current_function_decl)));
      TREE_STATIC (decl_cond) = 1;
      TREE_USED (decl_cond) = 1;
    
      /* Use thread-local */
      DECL_TLS_MODEL (decl_cond) = decl_default_tls_model (decl_cond);
      DECL_NONLOCAL (decl_cond) = 1;
      add_stmt (build_stmt(location, DECL_EXPR,  pushdecl (decl_cond)));
    
      /* define nested function.  */
      decl =
        cbc_finish_nested_function (location, label, decl_cond);
      TREE_USED(decl) = 1;
    
      /* define if-ed goto label and return statement. */
      cbc_finish_labeled_goto (location, label, decl_cond);
    
      /* get pointer to nested function.  */
      value = build_addr (decl , current_function_decl);
      TREE_USED (current_function_decl) = 1;
      SET_EXPR_LOCATION (value, location);
      add_stmt (value);
    
      TREE_SIDE_EFFECTS (stmt) = 1;
      expr.value = c_finish_stmt_expr (location, stmt);
      expr.original_code = ERROR_MARK;
    }
    			 

    環境付き継続

    生成しているコード 生成されるTree
    
    //goto c1(__return, __environment);
    goto c1(({
    	  __label__ _cbc_exit0;
    	  static int retval;
    	  void _cbc_internal_return(int retval_, void *_envp) {
    	    retval = retval_;
    	    goto _cbc_exit0; 
              }
    	  if (0) { 
                _cbc_exit0:
    	    return retval; 
              }
    	  _cbc_internal_return;
    	}), __environment);
    	  

  • 引数の並びの上書きにコピーが無い。
  • __code cs_a(int a, int b) {
      goto cs_b(b,a);
    }