コグノスケ


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

link もっと前
2020年7月8日 >>> 2020年6月25日
link もっと後

2020年7月8日

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の名前を書き換えれば、今回の問題は直るんですけど、それだとどうして直るのか全くわからないんですよ……。

編集者:すずき(2023/09/24 11:52)

コメント一覧

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



2020年7月7日

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だったらビットをセット(禁止状態)します。

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

編集者:すずき(2023/09/24 11:52)

コメント一覧

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



2020年7月6日

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では珍しくなくて、いつも解析が難しくて困っています。

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

編集者:すずき(2023/09/24 11:51)

コメント一覧

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



2020年7月5日

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でも読めますが、プログラムヘッダのオフセットやアドレスはめちゃくちゃです。

編集者:すずき(2023/09/24 12:59)

コメント一覧

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



2020年7月4日

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でも読めますし、そこそこ真っ当なファイルをキープできていると思います。

編集者:すずき(2023/09/24 12:59)

コメント一覧

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



2020年7月3日

C言語でSEGV

目次: C言語とlibc
目次: ベンチマーク

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]

$ ./a.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が指すアドレスにコードはありませんから、実行するとクラッシュします。

編集者:すずき(2023/09/24 13:01)

コメント一覧

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



2020年7月2日

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

目次: ゲーム

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

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

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

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

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

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


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


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

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

編集者:すずき(2023/09/24 13:10)

コメント一覧

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



2020年7月1日

STATIONflowランク100

目次: ゲーム

STATIONflowのランクが100になりました。何か実績と紐づいているかなと思いましたが、特に何も起きませんでした。しょんぼり。


ランク100

普通のマップだと重いし、時間も掛かりすぎてやってられないので、専用の軽量マップを作ってひたすら待ちました。それでも相当時間が掛かります。普通に遊ぶ分にはランク50で十分ですね。

わかったこと

STATIONflowでランク100にする途中でいくつかわかったことがありました。

集客レベル
途中で出入口と乗り場の集客レベルがカンストし、変化もやることもなくなります。実績解除に必要なランク50以降はほぼ無意味です。
集客レベルの上がる余地は最大132(※)なので、ランク100まで上がり続けるかと思いきや、ランク1上がるごとに集客レベルが2〜4くらい上がるので、だいたいランク60くらいでカンストします。
評価収入
ランクとは無関係で、出入口と乗り場の集客レベルの合計に依存するようです。出入口と乗り場を最大数作り、集客レベルを全て最大にして、ランクA+ を取ったときが540,000 でした。おそらくこれが最大値です。

ランク100まで行ってもまだ取れない実績が残っていて(ラッキーセブン、トウキョウ)、なかなか気が遠くなるゲームです。

(※)出入口が6 x 9 = 54、乗り場が6 x 2 = 12、集客レベルは2段階上がるため (54 + 12) x 2 = 132です。

実績のtypo

STATIONflowの実績にtypoがありました。


8000?九千?

漢字だけ間違ってます。惜しい……!

編集者:すずき(2024/06/03 23:59)

コメント一覧

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



2020年6月30日

STATIONflowの駅の評価

目次: ゲーム

以前(2020年5月28日の日記参照)STATIONflowで速度3にすると駅の評価が下がることをお伝えしましたが、若干間違っていて「画面の処理落ちで評価が下がる」方が実態に近そうです。

速度2でA+ 評価の駅を使って実験したところ、ノートPCのクロック周波数を低くするだけで、駅の評価がAに下がりました。

駅の評価と見せかけて、実はマシンの評価も入ってるんでしょうか?余計なお世話なので、勘弁してくれよ。

編集者:すずき(2024/06/03 23:59)

コメント一覧

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



2020年6月29日

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

目次: Windows

その2はこちら。

ノートPCでゲームをしていると、筐体が焼けそうなほど熱くなり、キーボードまで熱くなってキーを打つのが辛いです。

発熱の原因はグラフィックスチップですが、どうもCPUも無罪ではないらしく、TurboBoostを無効にするとややマシになることがわかりました。

私のマシンでは「電源オプション」 - 「プロセッサの電源管理」 - 「最大のプロセッサの状態」にて、87%以下にするとTurboBoostがOFFになりました。


最大のプロセッサの状態を87%に設定

下記は87%設定(1.49GHz)と88%設定(3.38GHz)にしたときのCPU動作周波数の変化です。


87%に設定したときのCPU動作周波数は1.49GHz


88%に設定したときのCPU動作周波数は3.38GHz

当然ながら1.49GHzと3.38GHzでは性能に天と地ほどの差があって、1.49GHzだとSTATIONflowの画面はめちゃくちゃカクつきます。

しかしシミュレーションゲームでは、待っているだけの時もありますし、常に爆熱で動いてくれる必要はありません。TurboBoostを任意にOFFにできるのは非常に便利です。

設定と動作周波数の関係一覧

CPUのクロック周波数の上限は何段階かあるようなので、変化点を調べました。

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

こんな感じでした。Core i5-8250Uはベース周波数1.6GHz、ブースト周波数3.4GHzなのにベース周波数である1.6GHzに張り付く設定は存在しません。謎です。

タスクマネージャーに「基本速度1.8GHz」と表示されているのも謎です。どこから1.8GHz出てきた……??

編集者:すずき(2023/09/24 12:41)

コメント一覧

  • hdkさん(2020/06/30 23:55)
    Athlon 5350 (2.05GHz) をUEFI Setupで上限1.1GHzに設定して使っているのですが、基本速度のところにはちゃんと1.10 GHzと出ています。1.80 GHzとは...
  • すずきさん(2020/07/01 11:18)
    うちのマシンは基本速度が取得できてないんですかねえ?BIOS は 1.6GHz とおっしゃっているんですが。
    仮に Windows が言ってる 1.8GHz が正解だとしても、1.8GHz に張り付く設定もないので、やっぱり謎は残ります。
  • s16223さん(2023/08/08 23:06)
    うちの8250Uもベースクロックが1.8GHzになってますね、、、 いい情報ありがとうございます。
  • すずきさん(2023/08/10 00:51)
    何の数字なのか結局よくわからなかったですが、ご参考になれば幸いです。
open/close この記事にコメントする



2020年6月28日

STATIONflowまさかの実績解除方法

目次: ゲーム

STATIONflowのしょうもない小技 その2です。

実績の解除が非常に難しい「エレベスト」や「効率の鬼」のような難しい実績が、超簡単に取れる方法です。

例えば、エレベストの条件は「駅ランク20以上で階段とエスカレーターを設置せずにA+ 評価」です。前回(2020年6月27日の日記参照)お伝えした通り、エレベーターと通勤客の変な挙動が合わさって、まともにやるとかなり難しいです。

しかしSTATIONflowの実績判定は甘々で「00:00になった瞬間」しか見ません。従って、

  • ランク20、A+ 評価の駅を作る(階段、エスカレーター使用可)
  • 23:50になったら時間を止める
  • 階段、エスカレーターなど、実績取得の妨げとなるものを全て破壊する
  • 時間を再始動させ00:00を迎える

これだけで達成できます。正直、こんな低レベルな小細工が通じると思わなかったので、逆にびっくりしました……。

類似した実績「階段抜き」「ノンエスカレーター」「エレベスト」「効率の鬼」ならば同じ手が通用するはずです。

作成者の想定とは違うだろうという意味で「邪道」な感じはしますが、バグを突いた挙動でもなさそうだし、早解きしたい方は利用してみても良いでしょう。

編集者:すずき(2024/06/03 23:59)

コメント一覧

  • 匿名さん(2020/07/11 18:26)
    「階段抜き」「ノンエスカレーター」「効率の鬼」を除いて通常プレイで実績解除したのですが、こんな方法があったとは・・・検索からたどり着いたのですが眼から鱗でした。ありがとうございました。
  • すずきさん(2020/07/12 00:53)
    コメントありがとうございます。私もやってみてびっくりしました。何でも試してみるものですね……。
open/close この記事にコメントする



2020年6月27日

STATIONflowプレイ日記

目次: ゲーム

STATIONflowの基本は理解したつもりなので、実績解除に挑んでますが、難しくて取れないものがいくつかあります。

今のところ一番難しいと思うのは「エレベスト」です。条件は「駅ランク20以上で階段とエスカレーターを設置せずにA+ 評価」です。

ありがちな条件ですが、STATIONflowのエレベーターはとてもヘボいため達成は困難です。

輸送力が低い
特に電車乗り場が顕著で8基設置しても客を捌ききれません。
輸送距離に応じて待ち時間が長くなる
長距離(例えばB1〜B5など)輸送すると、やたら待たされます。
客が待ち行列から離脱する
階段ユーザー(通勤客、学生)は非常に短気で、すぐに離脱します。

一番問題なのは3つ目の条件です。通常、待ち行列から離脱した客は階段やエスカレーターを利用しますが、移動手段がエレベーターしかない場合、不可解な動きをします。

  • エレベーターに並ぶ
  • エレベーターがこない
  • 待ち行列から外れる
  • 他の移動手段を探すが見つからない(※)
  • またエレベーターに並ぶ
  • エレベーターがこない
  • (以降、繰り返し)

(※)彷徨っている間はエレベーターが到着しても乗りません。一番訳が分からない動きです。しかもこの彷徨う時間が長い。

このようなおかしな行動をし続けた挙句、勝手に怒り始めて、駅の評価を下げてきます。理不尽極まりないですね。


エレベーターが来てるのに乗らない&怒ってる様子

ランク20到達後も駅と人が増え続けるため、どんどん客が捌ききれなくなり条件が悪くなる一方ですから、「エレベスト」実績を達成する最大のチャンスは「ランク20になる瞬間」でしょう。

が、私の腕では評価Aが限界でした。

もっと頑張ればできるのかもしれないですが、何回も同じマップをランク20までやり直すのは時間掛かって辛いし面白くないのでもうやりません……。

編集者:すずき(2024/06/03 23:59)

コメント一覧

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



2020年6月26日

STATIONflow小技

目次: ゲーム

STATIONflowのしょうもない小技。

将来的に、どこに駅の入り口と電車の乗り場が出てくるか?を見たい場合は「マップ作成」で見たいマップを「複製」「マップ編集」すれば、全ての入り口、乗り場を見ることができます。


マップ作成メニュー画面

STATIONflowの面白さは、どこに増設されるかわからない入り口や乗り場に対応することではあるのですが、記録を狙ったりするときや、事前に完璧な駅建築計画を立てたい人は、覗いてみると役立つかもしれません。

編集者:すずき(2024/06/03 23:58)

コメント一覧

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



link もっと前
2020年7月8日 >>> 2020年6月25日
link もっと後

管理用メニュー

link 記事を新規作成

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

最近のコメント20件

  • link 24年10月1日
    すずきさん (10/06 03:41)
    「xrdpで十分動作しているので、Wayl...」
  • link 24年10月1日
    hdkさん (10/03 19:05)
    「GNOMEをお使いでしたら今はWayla...」
  • link 24年10月1日
    すずきさん (10/03 10:12)
    「私は逆にVNCサーバーに繋ぐ使い方をした...」
  • link 24年10月1日
    hdkさん (10/03 08:30)
    「おー、面白いですね。xrdpはすでに立ち...」
  • link 14年6月13日
    2048player...さん (09/26 01:04)
    「最後に、この式を出すのに紙4枚(A4)も...」
  • link 14年6月13日
    2048playerさん (09/26 01:00)
    「今のところ最も簡略化した式です。\n--...」
  • link 14年6月13日
    2048playerさん (09/16 01:00)
    「返信ありがとうございます。\nコメントが...」
  • link 14年6月13日
    すずきさん (09/12 21:19)
    「コメントありがとうございます。同じ結果に...」
  • link 14年6月13日
    2048playerさん (09/08 17:30)
    「私も2048の最高スコアを求めたのですが...」
  • link 14年6月13日
    2048さん (09/08 17:16)
    「私も2048の最高スコアを求めたのですが...」
  • link 14年6月13日
    2048playerさん (09/08 16:10)
    「私も2048の最高スコアを求めたのですが...」
  • link 02年8月4日
    lxbfYeaaさん (07/12 10:11)
    「555」
  • link 24年6月17日
    すずきさん (06/23 00:12)
    「ありがとうございます。バルコニーではない...」
  • link 24年6月17日
    hdkさん (06/22 22:08)
    「GPSの最初の同期を取る時は見晴らしのい...」
  • link 24年5月16日
    すずきさん (05/21 11:41)
    「あー、確かにdpkg-reconfigu...」
  • link 24年5月16日
    hdkさん (05/21 08:55)
    「システム全体のlocale設定はDebi...」
  • link 24年5月17日
    すずきさん (05/20 13:16)
    「そうですねえ、普通はStandardなの...」
  • link 24年5月17日
    hdkさん (05/19 07:45)
    「なるほど、そういうことなんですね。Exc...」
  • link 24年5月17日
    すずきさん (05/19 03:41)
    「Standardだと下記の設定になってい...」
  • link 24年5月17日
    hdkさん (05/18 22:16)
    「ドメインを変えたせいで別サイト扱いになっ...」

最近の記事3件

  • link 24年10月31日
    すずき (11/04 15:17)
    「[DENSOの最終勤務日] 最終勤務日でした、入門カードや会社のPCを返却してきました。在籍期間はNSITEXE(品川のオフィ...」
  • link 22年7月8日
    すずき (11/02 20:34)
    「[マンガ紹介 - まとめリンク] 目次: マンガ紹介一覧が欲しくなったので作りました。5作品乙女ゲームの破滅フラグしかない悪役...」
  • link 24年10月30日
    すずき (11/02 20:33)
    「[マンガ紹介] 目次: マンガ紹介お気に入りのマンガ紹介シリーズ。最近完結した短めの作品を紹介します。マイナススキル持ち四人が...」
link もっとみる

こんてんつ

open/close wiki
open/close Linux JM
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 2021年
open/close 2022年
open/close 2023年
open/close 2024年
open/close 過去日記について

その他の情報

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

合計:  counter total
本日:  counter today

link About www2.katsuster.net
RDFファイル RSS 1.0

最終更新: 11/04 15:17