link もっと前
   2020年 7月 11日 -
      2020年 7月 2日  
link もっと後

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

日々

link permalink

link 編集する

ノート PC の発熱を抑える方法 - その 2

前回(2020年 6月 29日の日記参照)、ノート PC の発熱を抑えるために CPU のクロック上限を抑える設定をしました。

前回は TurboBoost だけ無効化する方法がわかりませんでしたが、ググっていたら割と簡単に TurboBoost を無効化できることがわかりました。

レジストリエディタで、
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\PowerSettings\54533251-82be-4824-96c1-47b60b740d00\be337238-0d82-4146-a960-4f3749d470c7
の Attributes という DWORD 値を 1 から 0 or 2 に変更します(※)。

すると下記の「プロセッサ パフォーマンスの向上モード」オプションが出現しますので、無効を選択します。


TurboBoost を無効化すると、最大のプロセッサの状態を 100% にしても、ベース周波数の 1.6GHz までしか周波数が上がらなくなります。これは良い感じだ。

  • 〜60%: 0.99GHz
  • 〜65%: 1.10GHz
  • 〜71%: 1.19GHz
  • 〜76%: 1.30GHz
  • 〜82%: 1.39GHz
  • 〜87%: 1.49GHz
  • 〜100%: 3.36GHz → 1.59GHz

(※)私は Surface Pro で CPU クロックが最大に張り付くという問題(フォーラムへのリンク)の解決策に載っていたものを見つけたのですが、どうも昔の Windows 8 で消えた設定らしい(ASCII.jp : Windows 8.1で消えた詳細な電源管理項目を表示する!より)です。

[編集者: すずき]
[更新: 2020年 7月 9日 23:48]

コメント一覧

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



link permalink

link 編集する

GCC を調べる - その 15-3 - ベクトルレジスタを使用する machine mode

目次: GCC を調べる - まとめリンク

前回(2020年 7月 7日の日記参照)はベクトルレジスタが選択されてしまう仕組みが何となくわかりました。

今回やりたかったことを復習しておくと「ベクトルの演算以外でベクトルレジスタを使わないでほしい」でした。つまり ira_prohibited_class_mode_regs[cl][j] のうちベクトル以外の machine mode かつベクトルレジスタに相当するビットを「セット」つまり割り当て禁止状態にすれば良いはずです。

配列の次元のうち cl はレジスタのクラス(enum reg_class)で、j は machine mode です。レジスタのクラスは以前(2020年 3月 28日の日記参照)ちょっとだけ使いました。幸いなことに、今回はレジスタのクラスは気にしなくて良いです、というかコードの if 文の条件 hard_regno_mode_ok(hard_regno, (machine_mode) j) を見るとわかるように、そもそもレジスタのクラスが渡されないので、ターゲット側(=RISC-V 依存の実装部分)で何もできないです。

重要なのは machine mode で、ベクトル以外のモード(浮動小数点など)だったらベクトルレジスタを割り当て禁止状態にすれば良いです。

targetm.hard_regno_mode_ok() の RISC-V 向け実装

// gcc/config/riscv/riscv.c

/* Implement TARGET_HARD_REGNO_MODE_OK.  */

static bool
riscv_hard_regno_mode_ok (unsigned int regno, machine_mode mode)
{
  unsigned int nregs = riscv_hard_regno_nregs (regno, mode);

  if (GP_REG_P (regno))
    {
      if (!GP_REG_P (regno + nregs - 1))
	return false;
    }
  else if (FP_REG_P (regno))
    {
      if (!FP_REG_P (regno + nregs - 1))
	return false;

      if (GET_MODE_CLASS (mode) != MODE_FLOAT
	  && GET_MODE_CLASS (mode) != MODE_COMPLEX_FLOAT)
	return false;

      /* Only use callee-saved registers if a potential callee is guaranteed
	 to spill the requisite width.  */
      if (GET_MODE_UNIT_SIZE (mode) > UNITS_PER_FP_REG
	  || (!call_used_or_fixed_reg_p (regno)
	      && GET_MODE_UNIT_SIZE (mode) > UNITS_PER_FP_ARG))
	return false;
    }
  else if (VP_REG_P (regno))    //★★前回足した実装
    {
      return true;
    }
  else
    return false;

...

ここでベクトル系の machine mode を直接記述(mode == V64SImode など)しても間違いではないと思うのですが、単純にモードがたくさんあると鬱陶しいですし、該当するモードがあとで増えたときの修正が大変です。こういうときは machine mode のクラスが便利です。クラスってなんだったかというと、machmode.def や riscv-modes.def に書いたあれです。

machine mode クラスの定義

// gcc/machmode.def

...

/* Basic integer modes.  We go up to TI in generic code (128 bits).
   TImode is needed here because the some front ends now genericly
   support __int128.  If the front ends decide to generically support
   larger types, then corresponding modes must be added here.  The
   name OI is reserved for a 256-bit type (needed by some back ends).
    */

//★★MODE_INT クラスになる

INT_MODE (QI, 1);
INT_MODE (HI, 2);
INT_MODE (SI, 4);
INT_MODE (DI, 8);
INT_MODE (TI, 16);

...


// gcc/config/riscv/riscv-modes.def

//★★MODE_FLOAT クラスになる

FLOAT_MODE (TF, 16, ieee_quad_format);

//★★以前、追加した実装
//★★VECTOR_MODE の場合は少し特殊で、MODE_VECTOR + 最初の引数 クラスになる
//★★この例だと MODE_VECTOR_INT になる

VECTOR_MODE (INT, SI, 32);
VECTOR_MODE (INT, SI, 64);

クラスは上記の通り各所の *.def にて定義されますが、正直言ってどこにあるかわかりにくいし、クラスの名前も見えません。machine mode とクラスの対応を確認するだけなら、ビルド時に生成される insn-modes.c を見たほうが早いです。

machine mode とクラスの対応、早見方法

// build_gcc/insn-modes.c

const unsigned char mode_class[NUM_MACHINE_MODES] =
{
  MODE_RANDOM,             /* VOID */
  MODE_RANDOM,             /* BLK */
  MODE_CC,                 /* CC */
  MODE_INT,                /* BI */
  MODE_INT,                /* QI */
  MODE_INT,                /* HI */
  MODE_INT,                /* SI */
  MODE_INT,                /* DI */
  MODE_INT,                /* TI */
  MODE_FRACT,              /* QQ */
  MODE_FRACT,              /* HQ */
  MODE_FRACT,              /* SQ */
  MODE_FRACT,              /* DQ */
  MODE_FRACT,              /* TQ */
  MODE_UFRACT,             /* UQQ */
  MODE_UFRACT,             /* UHQ */
  MODE_UFRACT,             /* USQ */
  MODE_UFRACT,             /* UDQ */
  MODE_UFRACT,             /* UTQ */
  MODE_ACCUM,              /* HA */
  MODE_ACCUM,              /* SA */
  MODE_ACCUM,              /* DA */
  MODE_ACCUM,              /* TA */
  MODE_UACCUM,             /* UHA */
  MODE_UACCUM,             /* USA */
  MODE_UACCUM,             /* UDA */
  MODE_UACCUM,             /* UTA */
  MODE_FLOAT,              /* SF */
  MODE_FLOAT,              /* DF */
  MODE_FLOAT,              /* TF */

...

  MODE_COMPLEX_FLOAT,      /* SC */
  MODE_COMPLEX_FLOAT,      /* DC */
  MODE_COMPLEX_FLOAT,      /* TC */
  MODE_VECTOR_INT,         /* V32SI */
  MODE_VECTOR_INT,         /* V64SI */
};

ベクトル系の machine mode を引っ掛けるには MODE_VECTOR_INT を使えば良さそうです。今の実装では使っていませんが RISC-V のベクトルは浮動小数点のベクトル(MODE_VECTOR_FLOAT)も扱えるはずなので、これも条件に追加しておきましょう。

targetm.hard_regno_mode_ok() の RISC-V 向け実装、修正版

// gcc/config/riscv/riscv.c

/* Implement TARGET_HARD_REGNO_MODE_OK.  */

static bool
riscv_hard_regno_mode_ok (unsigned int regno, machine_mode mode)
{
  unsigned int nregs = riscv_hard_regno_nregs (regno, mode);

  if (GP_REG_P (regno))
    {
      if (!GP_REG_P (regno + nregs - 1))
	return false;
    }
  else if (FP_REG_P (regno))
    {
      if (!FP_REG_P (regno + nregs - 1))
	return false;

      if (GET_MODE_CLASS (mode) != MODE_FLOAT
	  && GET_MODE_CLASS (mode) != MODE_COMPLEX_FLOAT)
	return false;

      /* Only use callee-saved registers if a potential callee is guaranteed
	 to spill the requisite width.  */
      if (GET_MODE_UNIT_SIZE (mode) > UNITS_PER_FP_REG
	  || (!call_used_or_fixed_reg_p (regno)
	      && GET_MODE_UNIT_SIZE (mode) > UNITS_PER_FP_ARG))
	return false;
    }
  else if (VP_REG_P (regno))    //★★前回足した実装
    {
      if (GET_MODE_CLASS (mode) != MODE_VECTOR_INT
	  && GET_MODE_CLASS (mode) != MODE_VECTOR_FLOAT)    //★★今回足した実装
	return false;

      return true;
    }
  else
    return false;

...

修正後のコンパイラは浮動小数点数を 60個使うコードを正常にコンパイルできます。たったこの 3行を説明するだけで、えらい時間を費やしました。GCC は魔界ですね。

お気づきの方もいるかと思いますが、実は 1つ上の else if 節にほぼ全く同じ判定文が既にあります。何も考えずに上からパクって MODE の名前を書き換えれば、今回の問題は直るんですけど、それだとどうして直るのか全くわからないんですよ……。

[編集者: すずき]
[更新: 2020年 7月 11日 04:55]

コメント一覧

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



link permalink

link 編集する

GCC を調べる - その 15-2 - ベクトルレジスタが選ばれてしまう原因追跡

目次: GCC を調べる - まとめリンク

前回(2020年 7月 6日の日記参照)はエラー出力のコードを追いましたが、エラーの原因とは無関係でした。

問題解決のヒントは RTL のダンプファイルにあります。エラーを起こす 282r.reload の 1つ前のパス 281r.ira のダンプ(オプション --dump-rtl-all で出力できます)を見ると下記のような記述が見つかります。

エラーが起きたときの 281r.ira
...

      Popping a57(r107,l0)  -- assign reg 26
      Popping a58(r106,l0)  -- assign reg 27
      Popping a59(r105,l0)  -- assign reg 64    ★64は v0 レジスタの番号
      Popping a60(r104,l0)  -- assign reg 65    ★65は v1 レジスタの番号
      Popping a61(r165,l0)  -- assign reg 5

...

レジスタ番号にベクトルレジスタが出てきます。とても怪しいですね。このメッセージを出しているコードを追います。

281r.ira で Popping ... を出力しているコード

// gcc/ira-color.c

/* Pop the coloring stack and assign hard registers to the popped
   allocnos.  */
static void
pop_allocnos_from_stack (void)
{
  ira_allocno_t allocno;
  enum reg_class aclass;

  for (;allocno_stack_vec.length () != 0;)
    {
      allocno = allocno_stack_vec.pop ();
      aclass = ALLOCNO_CLASS (allocno);
      if (internal_flag_ira_verbose > 3 && ira_dump_file != NULL)
	{
	  fprintf (ira_dump_file, "      Popping");  //★★Popping はここで出力
	  ira_print_expanded_allocno (allocno);
	  fprintf (ira_dump_file, "  -- ");
	}
      if (aclass == NO_REGS)
	{
	  ALLOCNO_HARD_REGNO (allocno) = -1;
	  ALLOCNO_ASSIGNED_P (allocno) = true;
	  ira_assert (ALLOCNO_UPDATED_HARD_REG_COSTS (allocno) == NULL);
	  ira_assert
	    (ALLOCNO_UPDATED_CONFLICT_HARD_REG_COSTS (allocno) == NULL);
	  if (internal_flag_ira_verbose > 3 && ira_dump_file != NULL)
	    fprintf (ira_dump_file, "assign memory\n");
	}
      else if (assign_hard_reg (allocno, false))    //★★レジスタの割当をする
	{
	  if (internal_flag_ira_verbose > 3 && ira_dump_file != NULL)
	    fprintf (ira_dump_file, "assign reg %d\n",
		     ALLOCNO_HARD_REGNO (allocno));    //★★ assign reg 64 はここで出力
	}


// gcc/ira-build.c

/* This recursive function outputs allocno A and if it is a cap the
   function outputs its members.  */
void
ira_print_expanded_allocno (ira_allocno_t a)
{
  basic_block bb;

  //★★Popping の後ろの axx(rxx, lxx) を出力している

  fprintf (ira_dump_file, " a%d(r%d", ALLOCNO_NUM (a), ALLOCNO_REGNO (a));
  if ((bb = ALLOCNO_LOOP_TREE_NODE (a)->bb) != NULL)
    fprintf (ira_dump_file, ",b%d", bb->index);
  else
    fprintf (ira_dump_file, ",l%d", ALLOCNO_LOOP_TREE_NODE (a)->loop_num);
  if (ALLOCNO_CAP_MEMBER (a) != NULL)
    {
      fprintf (ira_dump_file, ":");
      ira_print_expanded_allocno (ALLOCNO_CAP_MEMBER (a));
    }
  fprintf (ira_dump_file, ")");
}


// gcc/ira-int.h

#define ALLOCNO_NUM(A) ((A)->num)
#define ALLOCNO_REGNO(A) ((A)->regno)
...
#define ALLOCNO_HARD_REGNO(A) ((A)->hard_regno)
...

281r.ira の Popping の値
いずれも ira_allocno_t のメンバを参照する。

(例)
Popping a59(r105,l0)  -- assign reg 64

  - a59   : num
  - r105  : regno
  - l0    : loop_num
  - reg 64: hard_regno

割り当てたレジスタ番号は allocno->hard_regno に格納されているようです。割り当てる関数はメッセージ出力のすぐ上にある assign_hard_reg() 関数が行います。

281r.ira でレジスタ割当を決めるコード

// gcc/ira-color.c

static bool
assign_hard_reg (ira_allocno_t a, bool retry_p)
{
  HARD_REG_SET conflicting_regs[2], profitable_hard_regs;
  int i, j, hard_regno, best_hard_regno, class_size;

...

  best_hard_regno = -1;

...

  /* We don't care about giving callee saved registers to allocnos no
     living through calls because call clobbered registers are
     allocated first (it is usual practice to put them first in
     REG_ALLOC_ORDER).  */
  mode = ALLOCNO_MODE (a);
  for (i = 0; i < class_size; i++)
    {
      hard_regno = ira_class_hard_regs[aclass][i];
#ifdef STACK_REGS
      if (no_stack_reg_p
	  && FIRST_STACK_REG <= hard_regno && hard_regno <= LAST_STACK_REG)
	continue;
#endif
      if (! check_hard_reg_p (a, hard_regno,
			      conflicting_regs, profitable_hard_regs))    //★★このチェックを通過すると、割り当てる候補になる
	continue;
      cost = costs[i];
      full_cost = full_costs[i];
      if (!HONOR_REG_ALLOC_ORDER)
	{

...

	}
      if (min_cost > cost)
	min_cost = cost;
      if (min_full_cost > full_cost
	  || (!HONOR_REG_ALLOC_ORDER && min_full_cost == full_cost
	      && best_hard_regno > hard_regno))
	{
	  min_full_cost = full_cost;
	  best_hard_regno = hard_regno;    //★★どのハードウェアレジスタに割り当てるか決まる
	  ira_assert (hard_regno >= 0);
	}
      if (internal_flag_ira_verbose > 5 && ira_dump_file != NULL)
	fprintf (ira_dump_file, "(%d=%d,%d) ", hard_regno, cost, full_cost);
    }

...

  if (! retry_p)
    restore_costs_from_copies (a);
  ALLOCNO_HARD_REGNO (a) = best_hard_regno;    //★★allocno->hard_regno に格納
  ALLOCNO_ASSIGNED_P (a) = true;
  if (best_hard_regno >= 0)
    update_costs_from_copies (a, true, ! retry_p);
  ira_assert (ALLOCNO_CLASS (a) == aclass);
  /* We don't need updated costs anymore.  */
  ira_free_allocno_updated_costs (a);
  return best_hard_regno >= 0;    //★★best_hard_regno に何か値が入っていれば成功
}

関数 assign_hard_reg() は色んな複雑なことをやっていますが、レジスタ割当の決め手は最後のループです。ループ内では基本的に先頭のレジスタから使います(※)。ループの先頭に check_hard_reg_p() というチェックがあって、このチェックをパスしたレジスタが割当の候補になります。

(※)厳密にはどのレジスタから割当を試みるかは、ira_class_hard_regs で決まります。この配列を初期化するのは setup_class_hard_regs() で、コードを見た感じだと順序を変更することもできるようです。今回、詳細は調べていません。

281r.ira でレジスタ割当候補を判断するコード

// gcc/ira-color.c

/* Return true if HARD_REGNO is ok for assigning to allocno A with
   PROFITABLE_REGS and whose objects have CONFLICT_REGS.  */
static inline bool
check_hard_reg_p (ira_allocno_t a, int hard_regno,
		  HARD_REG_SET *conflict_regs, HARD_REG_SET profitable_regs)
{
  int j, nwords, nregs;
  enum reg_class aclass;
  machine_mode mode;

  aclass = ALLOCNO_CLASS (a);
  mode = ALLOCNO_MODE (a);
  if (TEST_HARD_REG_BIT (ira_prohibited_class_mode_regs[aclass][mode],
			 hard_regno))    //★★このチェックを通過したら割当候補
    return false;
  /* Checking only profitable hard regs.  */
  if (! TEST_HARD_REG_BIT (profitable_regs, hard_regno))    //★★このチェックは基本的に通過するはず
    return false;

...


// gcc/ira-int.h

#define ALLOCNO_MODE(A) ((A)->mode)
...
#define ALLOCNO_CLASS(A) ((A)->aclass)
...

ベクトルレジスタ(番号 64 以降)が check_hard_reg_p() に渡されるときの aclass, mode の値を見ておきます。今はどうでも良いんですが、次の章で使います。

aclass, mode の値

ブレークに設定する条件は if hard_regno == 64

(gdb) p aclass
$4 = ALL_REGS

(gdb) p mode
$5 = E_SFmode

チェックのキモは ira_prohibited_class_mode_regs で、レジスタ番号が示す位置のビットがセットされていると、チェックに引っかかって、割り当て候補から外れます。この配列を初期化している箇所は setup_prohibited_class_mode_regs() です。

281r.ira でレジスタ割当候補を判断に使う ira_prohibited_class_mode_regs を初期化するコード

// gcc/ira.c

static void
setup_prohibited_class_mode_regs (void)
ira_prohibited_class_mode_regs
{
  int j, k, hard_regno, cl, last_hard_regno, count;

  for (cl = (int) N_REG_CLASSES - 1; cl >= 0; cl--)
    {
      temp_hard_regset = reg_class_contents[cl] & ~no_unit_alloc_regs;
      for (j = 0; j < NUM_MACHINE_MODES; j++)
	{
	  count = 0;
	  last_hard_regno = -1;
	  CLEAR_HARD_REG_SET (ira_prohibited_class_mode_regs[cl][j]);
	  for (k = ira_class_hard_regs_num[cl] - 1; k >= 0; k--)
	    {
	      hard_regno = ira_class_hard_regs[cl][k];
	      if (!targetm.hard_regno_mode_ok (hard_regno, (machine_mode) j))    //★★hard_regno_mode_ok() が true であれば割当候補になる
		SET_HARD_REG_BIT (ira_prohibited_class_mode_regs[cl][j],
				  hard_regno);
	      else if (in_hard_reg_set_p (temp_hard_regset,
					  (machine_mode) j, hard_regno))
		{
		  last_hard_regno = hard_regno;
		  count++;
		}
	    }
	  ira_class_singleton[cl][j] = (count == 1 ? last_hard_regno : -1);
	}
    }
}


// gcc/config/riscv/riscv.h

enum reg_class
{
  NO_REGS,			/* no registers in set */
  SIBCALL_REGS,			/* registers used by indirect sibcalls */
  JALR_REGS,			/* registers used by indirect calls */
  GR_REGS,			/* integer registers */
  FP_REGS,			/* floating-point registers */
  VP_REGS,			/* vector registers */
  FRAME_REGS,			/* arg pointer and frame pointer */
  ALL_REGS,			/* all registers */
  LIM_REG_CLASSES		/* max value + 1 */
};

#define N_REG_CLASSES (int) LIM_REG_CLASSES

配列の名前 prohibited の通り、この配列のビットがセットされているレジスタは割り当て「禁止」です。最初に配列のビットをクリアし(許可状態)、条件 targetm.hard_regno_mode_ok() が true だったら変更せず(許可状態のまま)、false だったらビットをセット(禁止状態)します。

次回は原因を修正します。

[編集者: すずき]
[更新: 2020年 7月 9日 22:34]

コメント一覧

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



link permalink

link 編集する

GCC を調べる - その 15-1 - 浮動小数点のロードストアになぜかベクトルレジスタが出現する

目次: GCC を調べる - まとめリンク

以前(2020年 3月 27日の日記参照)ベクトルレジスタの定義を追加したとき、riscv_hard_regno_mode_ok() を変更しました。machine mode は無視して、ベクトルレジスタを指すレジスタ番号だったらとにかく true を返すようにしましたが、実はこの実装は正しくありません。

例えば以下のようなプログラムを書くと、おかしなことが起こります。

浮動小数点を 60個使うプログラム

void _start(void)
{
	static volatile float gf[60];
	float f00, f01, f02, f03, f04, f05, f06, f07, f08, f09;
	float f10, f11, f12, f13, f14, f15, f16, f17, f18, f19;
	float f20, f21, f22, f23, f24, f25, f26, f27, f28, f29;
	float f30, f31, f32, f33, f34, f35, f36, f37, f38, f39;
	float f40, f41, f42, f43, f44, f45, f46, f47, f48, f49;
	float f50, f51, f52, f53, f54, f55, f56, f57, f58, f59;

	__asm__ volatile("nop;\n");

	f00 = gf[ 0]; f01 = gf[ 1]; f02 = gf[ 2]; f03 = gf[ 3]; f04 = gf[ 4];
	f05 = gf[ 5]; f06 = gf[ 6]; f07 = gf[ 7]; f08 = gf[ 8]; f09 = gf[ 9];
	f10 = gf[10]; f11 = gf[11]; f12 = gf[12]; f13 = gf[13]; f14 = gf[14];
	f15 = gf[15]; f16 = gf[16]; f17 = gf[17]; f18 = gf[18]; f19 = gf[19];
	f20 = gf[20]; f21 = gf[21]; f22 = gf[22]; f23 = gf[23]; f24 = gf[24];
	f25 = gf[25]; f26 = gf[26]; f27 = gf[27]; f28 = gf[28]; f29 = gf[29];
	f30 = gf[30]; f31 = gf[31]; f32 = gf[32]; f33 = gf[33]; f34 = gf[34];
	f35 = gf[35]; f36 = gf[36]; f37 = gf[37]; f38 = gf[38]; f39 = gf[39];
	f40 = gf[40]; f41 = gf[41]; f42 = gf[42]; f43 = gf[43]; f44 = gf[44];
	f45 = gf[45]; f46 = gf[46]; f47 = gf[47]; f48 = gf[48]; f49 = gf[49];
	f50 = gf[59]; f51 = gf[51]; f52 = gf[52]; f53 = gf[53]; f54 = gf[54];
	f55 = gf[55]; f56 = gf[56]; f57 = gf[57]; f58 = gf[58]; f59 = gf[59];

	__asm__ volatile("nop;\n");

	gf[ 0] = f00; gf[ 1] = f01; gf[ 2] = f02; gf[ 3] = f03; gf[ 4] = f04;
	gf[ 5] = f05; gf[ 6] = f06; gf[ 7] = f07; gf[ 8] = f08; gf[ 9] = f09;
	gf[10] = f10; gf[11] = f11; gf[12] = f12; gf[13] = f13; gf[14] = f14;
	gf[15] = f15; gf[16] = f16; gf[17] = f17; gf[18] = f18; gf[19] = f19;
	gf[20] = f20; gf[21] = f21; gf[22] = f22; gf[23] = f23; gf[24] = f24;
	gf[25] = f25; gf[26] = f26; gf[27] = f27; gf[28] = f28; gf[29] = f29;
	gf[30] = f30; gf[31] = f31; gf[32] = f32; gf[33] = f33; gf[34] = f34;
	gf[35] = f35; gf[36] = f36; gf[37] = f37; gf[38] = f38; gf[39] = f39;
	gf[40] = f40; gf[41] = f41; gf[42] = f42; gf[43] = f43; gf[44] = f44;
	gf[45] = f45; gf[46] = f46; gf[47] = f47; gf[48] = f48; gf[49] = f49;
	gf[50] = f50; gf[51] = f51; gf[52] = f52; gf[53] = f53; gf[54] = f54;
	gf[55] = f55; gf[56] = f56; gf[57] = f57; gf[58] = f58; gf[59] = f59;

	__asm__ volatile("nop;\n");
}

最適化 O1 以上でビルドすると internal compile error が発生します。

reload でコンパイルエラー
$ riscv32-unknown-elf-gcc b.c -march=rv32imafcv -mabi=ilp32f -O3 -Wall -fno-builtin -fno-inline -nostdlib -g

b.c: In function '_start':
b.c:52:1: error: insn does not satisfy its constraints:
   52 | }
      | ^
(insn 562 17 18 2 (set (reg/v:SF 65 v1 [orig:104 f00 ] [104])
        (reg/v:SF 47 fa5 [orig:104 f00 ] [104])) "b.c":23:6 153 {*movsf_hardfloat}
     (nil))

during RTL pass: reload
dump file: b.c.282r.reload
b.c:52:1: internal compiler error: in extract_constrain_insn, at recog.c:2195
0x11b9e4d _fatal_insn(char const*, rtx_def const*, char const*, int, char const*)
        gcc/gcc/rtl-error.c:108
0x11b9ed1 _fatal_insn_not_found(rtx_def const*, char const*, int, char const*)
        gcc/gcc/rtl-error.c:118
0x1130298 extract_constrain_insn(rtx_insn*)
        gcc/gcc/recog.c:2195
0xeabdd8 check_rtl
        gcc/gcc/lra.c:2167
0xead5c0 lra(_IO_FILE*)
        gcc/gcc/lra.c:2604
0xe1a63b do_reload
        gcc/gcc/ira.c:5523
0xe1b078 execute
        gcc/gcc/ira.c:5709

エラーが発生している箇所は reload で、エラーの原因となった RTL も出力されています。RTL の set を見ると、宛先が reg/v:SF 65 v1 になっています。浮動小数点の処理のはずなのに、どうしてベクトルレジスタが選択されているのでしょうか?

エラーを出力している箇所

エラーを出力するのは関数 check_rtl() です。lra() の最初あたり check_rtl(false) と、最後あたり check_rtl(true) の 2回呼ばれます。エラーが発生しているのは、2回目の呼び出しです。

エラーの出力箇所を追う
// gcc/lra.c

/* Function checks RTL for correctness.	 If FINAL_P is true, it is
   done at the end of LRA and the check is more rigorous.  */
static void
check_rtl (bool final_p)
{
  basic_block bb;
  rtx_insn *insn;

  lra_assert (! final_p || reload_completed);
  FOR_EACH_BB_FN (bb, cfun)
    FOR_BB_INSNS (bb, insn)
    if (NONDEBUG_INSN_P (insn)
	&& GET_CODE (PATTERN (insn)) != USE
	&& GET_CODE (PATTERN (insn)) != CLOBBER
	&& GET_CODE (PATTERN (insn)) != ASM_INPUT)
      {
	if (final_p)
	  {
	    extract_constrain_insn (insn);    //★★これ
	    continue;
	  }


// gcc/recog.c

/* Do uncached extract_insn, constrain_operands and complain about failures.
   This should be used when extracting a pre-existing constrained instruction
   if the caller wants to know which alternative was chosen.  */
void
extract_constrain_insn (rtx_insn *insn)
{
  extract_insn (insn);    //★★ INSN RTL から recog_data を作成する
  if (!constrain_operands (reload_completed, get_enabled_alternatives (insn)))    //★★ここで false が返る
    fatal_insn_not_found (insn);    //★★internal compile error が出力される
}

おさらいですが、エラーが発生するときの insn の先頭は下記のような RTL でした。

エラーが発生する RTL
(insn 562 17 18 2 (set (reg/v:SF 65 v1 [orig:104 f00 ] [104])
        (reg/v:SF 47 fa5 [orig:104 f00 ] [104])) "b.c":23:6 145 {*movsf_hardfloat}
     (nil))

関数 constrain_operands() は何度も呼ばれるので、エラーが起きる RTL が来たとき UID 562 で止めます。INSN_UID(insn) == 562 が来たときに止められるようにコードを少し改変し、ブレークポイントを仕掛けます。

もしくは INSN_UID(insn) はデバッガから参照するときは insn->u2.insn_uid で見えますから、条件ブレークでも止められると思います。

ブレークするために少し改造

// gcc/lra.c

/* Function checks RTL for correctness.	 If FINAL_P is true, it is
   done at the end of LRA and the check is more rigorous.  */
static void
check_rtl (bool final_p)
{
  basic_block bb;
  rtx_insn *insn;

  lra_assert (! final_p || reload_completed);
  FOR_EACH_BB_FN (bb, cfun)
    FOR_BB_INSNS (bb, insn)
    if (NONDEBUG_INSN_P (insn)
	&& GET_CODE (PATTERN (insn)) != USE
	&& GET_CODE (PATTERN (insn)) != CLOBBER
	&& GET_CODE (PATTERN (insn)) != ASM_INPUT)
      {
        if (INSN_UID(insn) == 562)      //★★ブレークポイントを仕掛けるために追加した部分
          printf("check point!!\n");    //★★ブレークポイントを仕掛けるために追加した部分

	if (final_p)
	  {
	    extract_constrain_insn (insn);
	    continue;
	  }

関数 constrain_operands() を追ってみると、RTL が取るオペランド(今回は 2つ取っている)に対応する constraints をチェックしています。recog_data.constraints を見ると constraints がわかります。

コードは以前(2020年 3月 29日の日記参照)見た process_alt_operands() の前半部分に似た作りとなっています。同じようなコードを色々なところに書くのはやめてほしいですね……。

エラーが発生する constraints
(gdb) p recog_data.constraints
$6 = {
  0x2d717f9 "=f,f,f,m,m,*f,*r,*r,*r,*m",
  0x2d71813 "f,G,m,f,G,*r,*f,*G*r,*m,*r",
...

この constraints は降って湧いたものではなくて、下記の *movsf_hardfloat の定義が由来です。

エラーが起きているときの constraints

// gcc/config/riscv/riscv.md

(define_insn "*movsf_hardfloat"
  [(set (match_operand:SF 0 "nonimmediate_operand" "=f,f,f,m,m,*f,*r,  *r,*r,*m")
	(match_operand:SF 1 "move_operand"         " f,G,m,f,G,*r,*f,*G*r,*m,*r"))]

...

浮動小数点の移動の定義ですから当たり前なんですけど、ベクトルレジスタを受け付ける constraints 'v' は含まれません。なのにベクトルレジスタを渡そうとしているので constrain_operands() が怒っているわけです。

と、ここまでエラー出力に至るまでを追いましたが、残念なことにエラーの原因は reload パスにはありません。エラーが出ている箇所と、エラーの原因が全く関係ない現象は GCC では珍しくなくて、いつも解析が難しくて困っています。

振り出しに戻ってしまいました。続きは次回。

[編集者: すずき]
[更新: 2020年 7月 9日 22:32]

コメント一覧

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



link permalink

link 編集する

ELF バイナリで SEGV その 2 - 64 バイト

昨日(2020年 7月 4日の日記参照)は SEGV する ELF バイナリサイズを 92 バイトまで削ることができました。

その後、色々弄っていて見つけたのですが、プログラムヘッダの type を NULL にしておけば、ファイルサイズやオフセットがめちゃくちゃでも execve は文句を言わないっぽいみたいです。

ELF ヘッダの e_ident の後半 8バイトは 0(前半を書き換えると ELF と認識されなくなります)なので、この部分をプログラムヘッダだよ、と指定すれば type NULL に解釈されます。

これで 64 バイト、つまり ELF ヘッダしかない実行ファイルができました。何の役にも立たないですけどね……。

SEGV するバイナリ、64バイト版
$ ls -la a.out

-rwxrwxr-x+ 1 katsuhiro katsuhiro 64 Jul  3 06:39 a.out


SEGV する 64 バイトバイナリ、ELF ヘッダ


SEGV する 64 バイトバイナリ、プログラムヘッダ

これ以上 1 バイトでも削ると ELF ヘッダの長さを下回るため、ELF バイナリとして成立しません。よって 64 バイトが最短だと思いますが、私が気づいていない裏技があるかもしれません。

SEGV する 64 バイトバイナリの検証

このファイルを実行してみると、システムコール execve がエラーを返さないで進むので、ELF ファイルとして認識してもらえているようです。

SEGV する 64 バイトバイナリ、実行
$ strace ./a.out

execve("./a.out", ["./a.out"], 0x7ffedf7dfa90 /* 47 vars */) = 0
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x400000001} ---
+++ killed by SIGSEGV +++
Segmentation fault
SEGV する 64 バイトバイナリ、readelf
$ readelf -a a.out
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x400000001
  Start of program headers:          8 (bytes into file)
  Start of section headers:          0 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         1
  Size of section headers:           0 (bytes)
  Number of section headers:         0
  Section header string table index: 0
There are no sections in this file.
There are no sections to group in this file.
Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  NULL           0x00000001003e0002 0x0000000400000001 0x0000000000000008
                 0x0000000000000000 0x0038004000000000         0x1
There is no dynamic section in this file.
There are no relocations in this file.
The decoding of unwind sections for machine type Advanced Micro Devices X86-64 is not curr
ently supported.
Dynamic symbol information is not available for displaying symbols.
No version information found in this file.

一応 readelf でも読めますが、プログラムヘッダのオフセットやアドレスはめちゃくちゃです。

[編集者: すずき]
[更新: 2020年 7月 5日 21:07]

コメント一覧

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



link permalink

link 編集する

ELF バイナリで SEGV その 1 - 92 バイト

昨日(2020年 7月 3日の日記参照)試したところによると、C 言語は 0 文字で SEGV するプログラムを書けました。

では、出力されたバイナリだと最短はいくつでしょうか?とりあえずベースとして C 言語の 0 文字 SEGV プログラムの出力を見ます。

0 文字で SEGV のバイナリサイズ
$ gcc -nostdlib a.c

/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000001000

$ ls -la a.out

-rwxr-xr-x 1 katsuhiro katsuhiro 9528 Jul  3 04:11 a.out

なんと 9KB もあります。readelf で見るとダイナミックリンカー関連のシンボルが含まれているようなので、static オプションを付けてダイナミックリンカー関連のシンボルを消します。

0 文字で SEGV のバイナリサイズ、static 版
$ echo -n  > a.c && gcc -nostdlib -static a.c

/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000

$ ls -la a.out

-rwxr-xr-x 1 katsuhiro katsuhiro 968 Jul  3 04:12 a.out

$ strace ./a.out

execve("./a.out", ["./a.out"], 0x7ffc92c2b390 /* 46 vars */) = 0
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x401000} ---
+++ killed by SIGSEGV +++
Segmentation fault

ログが大量に出るはずの strace も、わずか 1行しかログが出ません。何もしないバイナリにも関わらず a.out のサイズは 1KB 近くあります。

ツールでバイナリを削る

お手軽なバイナリのダイエットとして、実行に不要なセクションを strip します。

SEGV するバイナリ、strip する
$ strip -R ".note.gnu.build-id" -R ".comment" a.out

$ ls -la a.out
-rwxr-xr-x 1 katsuhiro katsuhiro 376 Jul  3 04:18 a.out

それでも 376 バイト。まだでかいですね。

手でバイナリを削る

この 376 バイトのファイルを元にして、バイナリを手で削ります。加工の方針としては、

  • ELF ヘッダ(64バイト)とプログラムヘッダ(56バイト)を一部重ねる(-28バイト)
  • 不要なプログラムヘッダ 2つを消す(-56 x 2バイト)
  • セクションヘッダを消す(-64 x 2バイト)
  • .shstrtab セクションを消す(-16バイト)
  • ELF ヘッダの辻褄を合わせる

結果 92 バイトになりました。

SEGV するバイナリ、手で削る
$ ls -la a.out

-rwxrwxr-x+ 1 katsuhiro katsuhiro 92 Jul  3 05:43 a.out


SEGV する 92 バイトバイナリ、ELF ヘッダ


SEGV する 92 バイトバイナリ、プログラムヘッダ

もっとアグレッシブに ELF ヘッダとプログラムヘッダを重ねれば、64 バイトにできるかもしれません。

SEGV する 92 バイトバイナリの検証

このファイルを実行してみると、システムコール execve がエラーを返さないで進むので、ELF ファイルとして認識してもらえているようです。

SEGV する 92 バイトバイナリ、実行
$ strace ./a.out

execve("./a.out", ["./a.out"], 0x7fff2b4f74a0 /* 47 vars */) = 0
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x400000001} ---
+++ killed by SIGSEGV +++
Segmentation fault
SEGV する 92 バイトバイナリ、readelf
$ readelf -a a.out

ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x400000001
  Start of program headers:          36 (bytes into file)
  Start of section headers:          0 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         1
  Size of section headers:           0 (bytes)
  Number of section headers:         0
  Section header string table index: 0
There are no sections in this file.
There are no sections to group in this file.
Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  NULL           0x0000000000000000 0x0000000100380040 0x0000000000000000
                 0x0000000000000000 0x0000000000000000         0x1000
There is no dynamic section in this file.
There are no relocations in this file.
The decoding of unwind sections for machine type Advanced Micro Devices X86-64 is not currently supported.
Dynamic symbol information is not available for displaying symbols.
No version information found in this file.

一応 readelf でも読めますし、そこそこ真っ当なファイルをキープできていると思います。

[編集者: すずき]
[更新: 2020年 7月 5日 21:06]

コメント一覧

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



link permalink

link 編集する

C 言語で SEGV

SEGV を出すのが Twitter で流行しているみたいなので、少し考えてみました。Twitter で見かけたのは、*a;main(){*a=0;}(16文字)です。最適化オプションにもよりますが、異常なアドレスへの mov か、未定義命令 ud2 が出力されて SEGV します。

16文字で SEGV
$ echo -n '*a;main(){*a=0;}' > a.c && gcc a.c

a.c:1:1: warning: data definition has no type or storage class
    1 | *a;main(){*a=0;}
      | ^
a.c:1:2: warning: type defaults to 'int' in declaration of 'a' [-Wimplicit-int]
    1 | *a;main(){*a=0;}
      |  ^
a.c:1:4: warning: return type defaults to 'int' [-Wimplicit-int]
    1 | *a;main(){*a=0;}
      |    ^~~~

$ ./a.out

Segmentation fault

ただ SEGV するだけで良ければ、main のアドレスを .text ではないアドレスにすれば良いので、main;(5 文字)でも、達成できます。

5文字で SEGV
$ echo -n 'main;' > a.c && gcc a.c

a.c:1:1: warning: data definition has no type or storage class
    1 | main;
      | ^~~~
a.c:1:1: warning: type defaults to 'int' in declaration of 'main' [-Wimplicit-int]

$ ./a.out

Segmentation fault

この例は main を関数ではなく変数として定義し、main を bss セクションに配置します。最近の Linux ならばデータが置かれているセグメントは実行禁止にするはずなので、main が指すデータが何であろうと、ジャンプした瞬間に SEGV します。

趣旨とは外れますが SEGV を避けて正常終了させたければ、

SEGV したくない場合
$ echo -n 'main=0xc3;' > a.c && gcc -z execstack a.c

a.c:1:1: warning: data definition has no type or storage class
    1 | main=0xc3;
      | ^~~~
a.c:1:1: warning: type defaults to 'int' in declaration of 'main' [-Wimplicit-int]

$ ./main3.out

(SEGV しない)

変数 main が指す位置に retq 命令(0xc3)を置いて、-z execstack オプションでデータ領域を実行可能にしています。attribute で .text 領域に置いても実行できますが、そんなことするくらいなら main() 関数を書いた方が短いです。

これが最短か?

変化球を使ってよければ 0 文字で SEGV できます。nostdlib オプションを使います。

0 文字で SEGV
$ echo -n > a.c && gcc -nostdlib a.c

/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000001000

$ ./a.out

Segmentation fault

GNU ld(他のリンカーでも同じだと思いますけど)は ELF のエントリアドレスに _start というシンボルのアドレスを使います。通常は crt.o など、リンク時に自動的に追加されるオブジェクトが _start を定義しますが、nostdlib オプションにより crt.o がリンクされなくなって、_start は未定義になります。

すると ld は _start を適当なアドレスに設定(上記の例では 0x1000 に)します。当然 _start が指すアドレスにコードはありませんから、実行するとクラッシュします。

[編集者: すずき]
[更新: 2020年 7月 5日 18:58]

コメント一覧

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



link permalink

link 編集する

Steam の秘密の実績を暴く方法

Steam の買い物カートの中身一覧を見る方法を探していたんですが、全くわかりませんでした。だいぶ彷徨いましたが、未だトップ画面からカートを見る方法はわからないままです。Steam は本当に UI がひどい。Amazon を見習ってくれ……。

その際に副産物として、秘密の実績を確認する方法を見つけたのでメモしておきます。

  • Steam のウインドウ
  • ユーザー名のタブ
  • プロフィール
  • 最近プレイした全てのゲーム
  • すべてのゲーム

と辿ると自分が所持しているゲームの一覧が出ますから、実績を見たいゲームの

  • データを表示
  • グローバル実績

と辿ると全ての実績が表示されます。


個人のプレイデータでは秘密の実績と表示される


グローバルプレイデータでは秘密の実績も全て表示される

秘密の実績の意味とは一体……??

[編集者: すずき]
[更新: 2020年 7月 2日 03:27]

コメント一覧

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



link もっと前
   2020年 7月 11日 -
      2020年 7月 2日  
link もっと後

管理用メニュー

link 記事を新規作成

合計:  counter total
本日:  counter today

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

最終更新: 7/11 18:26

カレンダー

<2020>
<<<07>>>
---1234
567891011
12131415161718
19202122232425
262728293031-

最近のコメント 5件

  • link 20年06月28日
    匿名 「「階段抜き」「ノンエスカレーター」「効率...」
    (更新:07/11 18:26)
  • link 20年06月29日
    すずき 「うちのマシンは基本速度が取得できてないん...」
    (更新:07/01 11:18)
  • link 20年06月29日
    hdk 「Athlon 5350 (2.05GHz...」
    (更新:06/30 23:55)
  • link 20年05月02日
    すずき 「ちょっと調べたところ、コアが焼けると騒ぎ...」
    (更新:05/08 15:43)
  • link 20年05月02日
    すずき 「結構、怖い制御に見えますね&hellip...」
    (更新:05/08 15:23)

最近の記事 3件

link もっとみる
  • link 20年07月08日
    すずき 「[GCC を調べる - その 15-3 - ベクトルレジスタを] ...」
    (更新:07/11 04:55)
  • link 08年01月31日
    すずき 「[long long の罠] 突然 printf の動きがおかしく...」
    (更新:07/11 04:53)
  • link 20年07月09日
    すずき 「[ノート PC の発熱を抑える方法 - その 2] 前回(2020...」
    (更新:07/09 23:48)

こんてんつ

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 サイトの情報