link もっと前
   2020年 3月 29日 -
      2020年 3月 20日  
link もっと後

link 未来から過去へ表示(*)
link 過去から未来へ表示

日々

link permalink

link 編集する

GCC を調べる - その 8-3 - レジスタ constraint 判定

インラインアセンブラで "v" constraints を指定すると、何も実装していない場合は impossible constraint in 'asm' と怒られました。レジスタの constraints だけ足すと inconsistent operand constraints in an asm と怒られるはずです。エラーをチェックしている箇所は、

inconsistent なんとかエラーを出している場所

static bool
curr_insn_transform (bool check_only_p)
{

...

  if (process_alt_operands (reused_alternative_num))  //★★これが成立して alt_p = true が期待値だが
    alt_p = true;

...

  if (! alt_p && ! sec_mem_p)
    {
      /* No alternative works with reloads??  */
      if (INSN_CODE (curr_insn) >= 0)
	fatal_insn ("unable to generate reloads for:", curr_insn);
      error_for_asm (curr_insn,
		     "inconsistent operand constraints in an %<asm%>");  //★★ここに到達しエラーが出る
      lra_asm_error_p = true;
      /* Avoid further trouble with this insn.  Don't generate use
	 pattern here as we could use the insn SP offset.  */
      lra_set_insn_deleted (curr_insn);
      return true;
    }

...

この curr_insn_transform() 関数はやたら長くて(700行)訳のわからない構造です。うまく行く場合(r などを渡したとき)を観察すると、alt_p が true になるのが期待値と思われます。幸いなことに alt_p の設定は一箇所だけ、条件も process_alt_operands() 関数だけです。

そう思って process_alt_operands() 関数を見ると、これがまたもの凄い実装で、目を覆いたくなります(1200行!!)。GCC 見ていると、クソコードには事欠かないです。これはひどい。

コードの一部を抜粋しても全く意味不明で、そもそもこの関数自体がかなりゴチャゴチャで意味不明です。全て追うのは不可能です。なので "r" がどの辺りを通るかをもって、当たりを付けました。下記のところが分岐点になっているようです。

エラーを判定してそうな場所

static bool
process_alt_operands (int only_alternative)
{

...

	  do
	    {
              //★★p は "=&v" が入っていて、c に先頭から一文字ずつ取って解析している
	      switch ((c = *p, len = CONSTRAINT_LEN (c, p)), c)
		{
		case '\0':
		  len = 0;
		  break;

...

		default:
		  cn = lookup_constraint (p);  //★★ 'v' に対しては、CONSTRAINT_v が返る
		  switch (get_constraint_type (cn))
		    {
		    case CT_REGISTER:
		      cl = reg_class_for_constraint (cn);  //★★CONSTRAINT_v に対しては VP_REGS が返る
		      if (cl != NO_REGS)
			goto reg;    //★★このジャンプで飛ぶ
		      break;

...

		reg:
		  if (mode == BLKmode)
		    break;
		  this_alternative = reg_class_subunion[this_alternative][cl];
		  this_alternative_set |= reg_class_contents[cl];  //★★どこかでみた reg_class_contents が登場
		  if (costly_p)
		    {
		      this_costly_alternative
			= reg_class_subunion[this_costly_alternative][cl];
		      this_costly_alternative_set |= reg_class_contents[cl];
		    }
		  winreg = true;
		  if (REG_P (op))
		    {
		      if (hard_regno[nop] >= 0
			  && in_hard_reg_set_p (this_alternative_set,
						mode, hard_regno[nop]))  //★★これが成立しない
			win = true;  //★★少なくとも win = true にならないと関数が失敗を返す(条件は他にもあるが)
		      else if (hard_regno[nop] < 0
			       && in_class_p (op, this_alternative, NULL))
			win = true;
		    }
		  break;
		}

...

	    }
	  while ((p += len), c);  //★★基本は次の文字に行くが、スキップすることもある模様

どこかでみたアイツです。このエラーは reg_class_contents を見に行った結末に起きているようです。

試してみたら、色々おかしい

REG_CLASS_CONTENTS を正しく設定すると、下記のコードがコンパイルできるはずです。雰囲気を出すため RISC-V のベクトル命令を書いていますが、ぶっちゃけコンパイラは命令を全く見ないので、実は abcd でも何でも通ります。コンパイルのみ(*.s を出力)であればアセンブラすら要りません(※)。

"v" constraint のテスト

// a.c

void _start()
{
	int b[100];
	int v;

	__asm__ volatile ("vlw.v %0, %1\n"
		: "=&v"(v) : "A"(b[10]));
}

ビルドして、逆アセンブルしてみます。

"v" constraint のテストをビルド、逆アセンブル
$ riscv32-unknown-elf-gcc -Wall -g -march=rv32gcv -mabi=ilp32f -nostdlib -O2 a.c

$ riscv32-unknown-elf-objdump -drS a.out

a.out:     file format elf32-littleriscv

Disassembly of section .text:

00010054 <_start>:
void _start()
{
   10054:       7165                    addi    sp,sp,-400
        int b[100];
        int v;

        __asm__ volatile ("vlw.v %0, %1\n"
   10056:       103c                    addi    a5,sp,40
   10058:       1207e007                vlw.v   v0,(a5)
                : "=&v"(v) : "A"(b[10]));
}
   1005c:       6159                    addi    sp,sp,400
   1005e:       8082                    ret

それらしきベクトルレジスタ(v0)が出力されているようです。めでたし、めでたし。と言いたいところですが、実は全然ダメです。

  • 変数が int なので sizeof(v) が 4 になる、ベクトルを扱いたい
  • 最適化オプションを O0 にするとコンパイラが internal error を出す

まだまだ改善の余地があります。これも今後、調べていこうと思います。

(※)もしアセンブルまで実行したければ、RISC-V の GitHub にある binutils を使ってください(GitHub へのリンク)。ビルド方法は Upstream のコードとほぼ同じ(2019年 4月 19日の日記参照)です。唯一の違いは configure 時に --with-system-readline を付けないと、readline がないと言われてエラーになる点です。

[編集者: すずき]
[更新: 2020年 4月 1日 21:18]

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link permalink

link 編集する

GCC を調べる - その 8-2 - レジスタとレジスタクラス

レジスタ追加の変更の要は REG_CLASS_CONTENTS です。このマクロは 32ビット整数の配列で、各レジスタ番号がどのレジスタの仲間(enum reg_class)に属するかを指定するテーブルです。こんな風に変更します。

REG_CLASS_CONTENTS の変更内容

 #define REG_CLASS_CONTENTS						\
 {									\
-  { 0x00000000, 0x00000000, 0x00000000 },	/* NO_REGS */		\
-  { 0xf003fcc0, 0x00000000, 0x00000000 },	/* SIBCALL_REGS */	\
-  { 0xffffffc0, 0x00000000, 0x00000000 },	/* JALR_REGS */		\
-  { 0xffffffff, 0x00000000, 0x00000000 },	/* GR_REGS */		\
-  { 0x00000000, 0xffffffff, 0x00000000 },	/* FP_REGS */		\
-  { 0x00000000, 0x00000000, 0x00000003 },	/* FRAME_REGS */	\
-  { 0xffffffff, 0xffffffff, 0x00000003 }	/* ALL_REGS */		\
+  { 0x00000000, 0x00000000, 0x00000000, 0x00000000 },	/* NO_REGS */		\
+  { 0xf003fcc0, 0x00000000, 0x00000000, 0x00000000 },	/* SIBCALL_REGS */	\
+  { 0xffffffc0, 0x00000000, 0x00000000, 0x00000000 },	/* JALR_REGS */		\
+  { 0xffffffff, 0x00000000, 0x00000000, 0x00000000 },	/* GR_REGS */		\
+  { 0x00000000, 0xffffffff, 0x00000000, 0x00000000 },	/* FP_REGS */		\
+  { 0x00000000, 0x00000000, 0xffffffff, 0x00000000 },	/* VP_REGS */		\
+  { 0x00000000, 0x00000000, 0x00000000, 0x00000003 },	/* FRAME_REGS */	\
+  { 0xffffffff, 0xffffffff, 0xffffffff, 0x00000003 }	/* ALL_REGS */		\
 }
                              ↑ここの 3列目を足した

行方向は、ビットフィールドになっており非常にわかりにくいです。0要素目の 0ビット目、0要素目の 1ビット目、…という順に見ます。整数内では右から左(右が上位ビット)、要素間では左から右(左が 0要素目)に見ます。

列方向は enum reg_class の整数値と一致しますのでさほど難しくはないでしょう。

REG_CLASS_CONTENTS の見方
行と列の意味

  →→ 行方向、レジスタ番号(0 〜 FIRST_PSEUDO_REGISTER - 1 まで)
↓
↓
列方向、enum reg_class を整数に直したもの


行方向の見方

例えば 3行目(GR_REGS)がこうなっていたとすると、
{ 0x0000000f, 0x0000000c, },

- 0要素目(レジスタ番号 0 〜 31 のクラス): 0x0000000f
  - 0, 1, 2, 3ビット目が 1 = レジスタ番号 0 〜 3 は GR_REGS
  - 他のレジスタについては言及しない
- 1要素目(レジスタ番号 32 〜 63 のクラス): 0x0000000c
  - 2, 3ビット目が 1 = レジスタ番号 34 〜 35 は GR_REGS
  - 他のレジスタについては言及しない

ALL_REGS は全レジスタに 1 をセットしますので、ビットフィールドのルールがわかりやすいと思います。今回はレジスタが 98本なので、3要素(32 * 3 = 96)+ 最後の要素は 2ビット分だけ 1 にセットしています。

今回は VR_REGS という新たなレジスタクラスを足したいので、行が一つ増えます。レジスタの総数も増えるので、列方向も増えます。ちょうど良いことに新規に追加するレジスタは 32本なので、整数 1要素分を増やすだけです。

コード上での扱い

このマクロは直接使用されるわけではなく、別の配列にコピーされます。

REG_CLASS_CONTENTS が使われているところ

// gcc/reginfo.c

static const unsigned int_reg_class_contents[N_REG_CLASSES][N_REG_INTS]
  = REG_CLASS_CONTENTS;

...

/* Function called only once per target_globals to initialize the
   target_hard_regs structure.  Once this is done, various switches
   may override.  */
void
init_reg_sets (void)
{
  int i, j;

  /* First copy the register information from the initial int form into
     the regsets.  */

  for (i = 0; i < N_REG_CLASSES; i++)
    {
      CLEAR_HARD_REG_SET (reg_class_contents[i]);

      /* Note that we hard-code 32 here, not HOST_BITS_PER_INT.  */
      for (j = 0; j < FIRST_PSEUDO_REGISTER; j++)
	if (int_reg_class_contents[i][j / 32]    //★★ここで参照している
	    & ((unsigned) 1 << (j % 32)))
	  SET_HARD_REG_BIT (reg_class_contents[i], j);
    }


// gcc/reginfo.c

struct target_hard_regs default_target_hard_regs;


// gcc/hard-reg-set.h

#if SWITCHABLE_TARGET  //★★x86, ARM, MIPS などは SWITCHABLE_TARGET = 1, RISC-V は 0 のようだ
extern struct target_hard_regs *this_target_hard_regs;
#else
#define this_target_hard_regs (&default_target_hard_regs)
#endif

#define reg_class_contents \r  (this_target_hard_regs->x_reg_class_contents)

難しそうに見えてやっていることは int_reg_class_contents から default_target_hard_regs->x_reg_class_contents へビットを移し替えているだけです。違いは int_reg_class_contents が必ず 32ビット幅であるのに対し、x_reg_class_contents はアーキテクチャ最速の整数幅(x86_64 なら 64bit になるでしょう)である点です。

個人的には可読性を殺してまでやる意味あるの……?と疑問ですが、きっと GCC 内で頻繁に呼ばれ速度的に重要なポイントだったのでしょう。

[編集者: すずき]
[更新: 2020年 4月 1日 21:16]

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link permalink

link 編集する

GCC を調べる - その 8-1 - レジスタ定義を足す

前回(2020年 3月 6日の日記参照)はレジスタ制約(register_constraints)を追加しました。これだけでは何もできませんので、今回はベクトルレジスタの定義を追加してみます。長そうなので分割して書きます。

RISC-V には汎用レジスタ(GP_REGS)と浮動小数点レジスタ(FP_REGS)が既に定義されているため、それらを参考にします。

変更するファイルは gcc/config/riscv/riscv.c, riscv.h です。FP_REG くらいで検索すると、下記の関数、マクロに名前が見当たりますので、真似して追加します。(詳細はlink パッチファイルもご覧ください、内容の正しさは全く保証できませんけど)

レジスタ定義を追加する際に変更する箇所

// gcc/config/riscv/riscv.c

riscv_regno_to_class[FIRST_PSEUDO_REGISTER]  //32個レジスタを足す
riscv_hard_regno_nregs    //どのマシンモードでもレジスタを 1つだけ使う、よくわからん、また今度調べる
riscv_hard_regno_mode_ok  //どのマシンモードでも許可する、よくわからん、また今度調べる
riscv_class_max_nregs     //どのクラスでもレジスタを 1つだけ使う、よくわからん、また今度調べる


// gcc/config/riscv/riscv.h

FIRST_PSEUDO_REGISTER  //32個分ずれてもらう
FIXED_REGISTERS        //32個足す、今回は 0 にした、固定された役目(スタックポインタなど)はない
CALL_USED_REGISTERS    //32個足す、今回は 0 にした(関数呼び出しにより内容を破壊されない、s0 - s11 と同じ扱い)

enum reg_class
#define REG_CLASS_NAMES     //新たなレジスタクラスを足す
#define REG_CLASS_CONTENTS  //後述する
#define REG_ALLOC_ORDER	    //レジスタの割当順、レジスタ番号で指定する
#define REGISTER_NAMES      //レジスタの名前
#define ADDITIONAL_REGISTER_NAMES

初歩の初歩的な変更の割に必要な変更点はかなり多いです。どの変更が何に効くか完全にわかっていないので、合っているかわかりませんし、説明し難い変更もあります。後日、要調査ですね。

GCC の 2つのレジスタ

変更した中の riscv_regno_to_class をみると FIRST_PSEUDO_REGISTER というマクロが出てきます。GCC はレジスタを 2種類使い分けていて、レジスタ番号で区別できます。

  • 物理レジスタ: 0 〜 (FIRST_PSEUDO_REGISTER - 1)
  • 疑似レジスタ(pseudo register): FIRST_PSEUDO_REGISTER より大きな値

正式な名前がわからない(※)ので、名付けは適当です。GCC は RTL のフェーズで命令の引数にレジスタを割り当てます。その際、いきなりメモリや物理レジスタを割り当てるのではなく、まず疑似レジスタを割り当てます。

疑似レジスタには数の制限がないので、最初の方の最適化パスで必要なだけ割り当てます。その後の最適化パスで物理レジスタや、メモリにうまく割り当てを考える二段構成になっています。

今回は 32個の物理レジスタを足そうとしているので、FIRST_PSEUDO_REGISTER にも 32個分だけズレてもらう必要があります。

今回の変更の要は REG_CLASS_CONTENTS です。このマクロの効き目についてはまた今度。

(※)GCC のヘンテコなマクロの意味を調べる際、GCC Internals(HTML 版へのリンク)が大変参考になるのですが、この文書は用語の説明がイマイチ甘くて、正式な用語がわかりません。いつも困ります……。

[編集者: すずき]
[更新: 2020年 4月 29日 16:10]

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link もっと前
   2020年 3月 29日 -
      2020年 3月 20日  
link もっと後

管理用メニュー

link 記事を新規作成

合計:  counter total
本日:  counter today

link About www.katsuster.net
RDF ファイル RSS 1.0
QR コード QR コード

最終更新: 5/31 01:18

カレンダー

<2020>
<<<03>>>
1234567
891011121314
15161718192021
22232425262728
293031----

最近のコメント 5件

  • link 20年05月02日
    すずき 「ちょっと調べたところ、コアが焼けると騒ぎ...」
    (更新:05/08 15:43)
  • link 20年05月02日
    すずき 「結構、怖い制御に見えますね&hellip...」
    (更新:05/08 15:23)
  • link 20年05月02日
    hdk 「デスクトップ用のNVIDIA Quadr...」
    (更新:05/07 20:30)
  • link 20年01月27日
    すずき 「詳細は調べていないので、コード中のコメン...」
    (更新:04/18 23:05)
  • link 20年01月27日
    superzeros 「少し気になったのでglibcの履歴を調べ...」
    (更新:04/16 21:07)

最近の記事 3件

link もっとみる
  • link 20年06月01日
    すずき 「[GCC を調べる - その 13-4 - ベクトル命令のオフ] ...」
    (更新:05/31 01:18)
  • link 20年05月31日
    すずき 「[GCC を調べる - その 13-3 - オフセット付きアド] ...」
    (更新:05/30 22:00)
  • link 20年05月30日
    すずき 「[GCC を調べる - その 13-2 - オフセット付きアド] ...」
    (更新:05/30 21:53)

こんてんつ

open/close wiki
open/close Java API

過去の日記

open/close 2002年
open/close 2003年
open/close 2004年
open/close 2005年
open/close 2006年
open/close 2007年
open/close 2008年
open/close 2009年
open/close 2010年
open/close 2011年
open/close 2012年
open/close 2013年
open/close 2014年
open/close 2015年
open/close 2016年
open/close 2017年
open/close 2018年
open/close 2019年
open/close 2020年
open/close 過去日記について

その他の情報

open/close アクセス統計
open/close サーバ一覧
open/close サイトの情報