File:  [Isaki's NoNo m68k/m88k emulator] / nono / exp / optestm88k / optestm88k_gen.cpp
Revision 1.1.1.6 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:49 2026 UTC (3 months ago) by root
Branches: MAIN, Isaki
CVS tags: v027, v026, v025, HEAD
nono 1.5.0

//
// nono
// Copyright (C) 2021 nono project
// Licensed under nono-license.txt
//

// instruction test for m88100
//
// optestm88k_gen.cpp (this file)
//  |
//  | compile and link on the host
//  v
// optestm88k_gen
//  |
//  | (executing it will create following files)
//  v
//  +--> optestm88k_add.s      ---+
//  +--> optestm88k_bitfield.s ---+
//  +--> optestm88k_fcmp.s     ---+
//  +--> optestm88k_flt.s      ---+
//  +--> optestm88k_lda.s      ---+
//  +--> optestm88k_logical.s  ---+
//  +--> optestm88k_muldiv.s   ---+
//  +--> optestm88k_sub.s      ---+
//  +--> optestm88k_table.c    ---+
//  +--> optestm88k_table.h    ---+
//                                |
//       optestm88k_main.[ch]  ---+
//       optestm88k_code.s     ---+
//                                | compile and link on m88k
//                                v
//                             optestm88k

// XXX mul/div/divu 命令の FPU disabled のケースは未検査

#include "nono.h"
#include "mystring.h"
#include <assert.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <algorithm>
#include <cfenv>
#include <cmath>
#include <tuple>
#include <vector>

struct fpi
{
	union {
		double d {};
		uint64 q;
		// float は h の位置に置く
#if BYTE_ORDER == LITTLE_ENDIAN
		struct { uint32 l, h; };
		struct { float dummy_f, f; };
		struct { uint32 dummy_i, i; };
#else
		struct { uint32 h, l; };
		struct { float f, dummy_f; };
		struct { uint32 i, dummy_i; };
#endif
	};

	enum {
		NONE,
		FLOAT,
		DOUBLE,
		INT,
	} type {};
	bool IsFloat()  const { return (type == FLOAT); }
	bool IsDouble() const { return (type == DOUBLE); }
	bool IsInt()	const { return (type == INT); }

	// コンストラクタ
	fpi() {
	}
	fpi(float f_) {
		Set(f_);
	}
	fpi(double d_) {
		Set(d_);
	}
	fpi(uint32 i_) {
		SetInt(i_);
	}

	// どの値を保持しているか覚えておく
	void Set(float f_) {
		f = f_;
		type = FLOAT;
	}
	void Set(double d_) {
		d = d_;
		type = DOUBLE;
	}
	void Set(uint32 i_) {
		i = i_;
		type = FLOAT;
	}
	void Set(uint64 q_) {
		q = q_;
		type = DOUBLE;
	}
	void SetInt(uint32 i_) {
		i = i_;
		type = INT;
	}

	// どっちであっても double 値を返す (printf 用)
	double AsDouble() const {
		if (IsFloat()) {
			return (double)f;
		} else {
			return d;
		}
	}

	std::string ToString() const {
		switch (type) {
		 case FLOAT:
			return string_format("%9.8g", f);
		 case DOUBLE:
			return string_format("%19.18g", d);
		 case INT:
			return string_format("$%08x", i);
		 default:
			return "NONE";
		}
	}
};

// テスト情報 (グローバル)
static const char *testname;
static int testnum;
// 出力先 (グローバル)
static FILE *fp;

#define IMM	(0xff)

// 理解しやすさのため 1, 2, 3 で表現しているが実際は r8, r10, r12 を使う
static constexpr uint32 ntor(uint32 n) {
	assert(n < 4);
	if (n == 0) return 0;
	if (n == 1) return 8;
	if (n == 2) return 10;
	if (n == 3) return 12;
	__unreachable();
}
static constexpr uint32 R3(uint32 d, uint32 s1, uint32 s2) {
	d  = ntor(d);
	s1 = ntor(s1);
	s2 = ntor(s2);
	return (d << 16) | (s1 << 8) | s2;
}
// 3オペランドの場合のレジスタの組み合わせ
static std::vector<uint32> reglist3 = {
	R3(0, 0, 0),
	R3(0, 0, 1),
	R3(0, 1, 0),
	R3(0, 1, 1),
	R3(0, 1, 2),

	R3(1, 0, 0),
	R3(1, 0, 1),
	R3(1, 1, 0),
	R3(1, 1, 1),
	R3(1, 0, 2),
	R3(1, 2, 0),
	R3(1, 2, 2),
	R3(1, 1, 2),
	R3(1, 2, 1),
	R3(1, 2, 3),
};

// 2オペランド(+IMM)の場合のレジスタの組み合わせ
#define R2(d, s1)		R3(d, s1, 0)
static std::vector<uint32> reglist2 = {
	R2(0, 0),
	R2(0, 1),
	R2(1, 0),
	R2(1, 1),
	R2(1, 2),
};

// r0 用の値リスト
static std::vector<uint32> v0 = {
	0,
};

// 数値演算用の入力値リスト
static std::vector<uint32> vlist_arith = {
	0,
	1,
	2,
	0x7f,
	0x80,
	0xff,
	0x7fff,
	0x8000,
	0xffff,
	0x7fffffff,
	0x80000000,
	0xffffffff,
};
// ビットフィールド用の入力値リスト
// XXX どのパターンで試せばいいか
static std::vector<uint32> vlist_bf = {
	0x00000000,
	0x87654321,
	0xffffffff,
};
// ビットフィールド用の入力 W,O リスト
// XXX どのパターンで試せばいいか
#define WO(w, o)	(((w) << 5) | (o))
static std::vector<uint32> vlist_wo = {
	WO( 0,  0),
	WO( 0,  1),
	WO( 0,  2),
	WO( 0, 31),
	WO( 1,  0),
	WO( 1,  1),
	WO( 1, 31),
	WO(16,  0),
	WO(16, 15),
	WO(16, 16),
	WO(16, 17),
	WO(16, 31),
	WO(31,  0),
	WO(31,  1),
	WO(31, 31),
};
// ff0/ff1 用の入力値リスト
static std::vector<uint32> vlist_ff = {
	0x00000000,
	0x00000001,
	0x00000020,
	0x00000480,
	0x00009000,
	0x00010000,
	0x00200000,
	0x06000000,
	0xf0000000,
};
// r0 用
static std::vector<double> vf0 = {
	0.0,
};
// 浮動小数点数の入力値リスト
static std::vector<double> vlist_double = {
	0.0,
	1.0,
	2.0,
	16777215.0,		// float → int で正確に表現できる最大値?
	16777215.5,
	16777217.0,		// 16777216 は 1.0*2^24 で表現できるため省略
	1000000000.0,	// 2^29 以上 2^30 以下
	2147483646.0,	// INT32_MAX と区別可能な最大値
	2147483647.0,	// INT32_MAX
	2147483647.5,
	2147483649.0,
	4294967295.0,	// double → uint32 で正確に表現できる最大値?
	4294967295.5,
	4294967297.0,

	-0.0,
	-1.0,
	-2.0,
	-16777215.0,
	-16777215.5,
	-16777217.0,
	-1000000000.0,
	-2000000000.0,
	-2147483648.0,
	-2147483648.5,
	-2147483649.0,

	 std::numeric_limits<double>::infinity(),
	-std::numeric_limits<double>::infinity(),
	std::nan(""),
};

#define RM_NEAR		(0)
#define RM_ZERO		(1)
#define RM_MINUS	(2)
#define RM_PLUS		(3)

static const char *rmstr[] = {
	"RN",
	"RZ",
	"RM",
	"RP",
};

template <class T>
using Tuple2_1 = std::tuple<uint32, uint32, T>;
template <class T>
using Tuple2_2 = std::tuple<uint32, uint32, T, T>;
template <class T>
using Tuple3_2 = std::tuple<uint32, uint32, uint32, T, T>;

static std::vector<Tuple3_2<uint32>> param_arith_reg;
static std::vector<Tuple2_2<uint32>> param_arith_imm;
static std::vector<Tuple3_2<uint32>> param_bitfield_reg;
static std::vector<Tuple2_2<uint32>> param_bitfield_imm;
static std::vector<Tuple2_1<uint32>> param_ff01;
static std::vector<Tuple2_1<uint32>> param_flt;
static std::vector<Tuple2_1<double>> param_trnc;
static std::vector<Tuple3_2<double>> param_fcmp;

// 2オペランドテストの要素を変数に取り出す部分 (副作用のあるマクロ)
#define VAR_DIADIC(t, rd, rs2, vs2)	\
	uint32 rd  = std::get<0>(t);	\
	uint32 rs2 = std::get<1>(t);	\
	auto   vs2 = std::get<2>(t)

// 3オペランドテストの要素を変数に取り出す部分 (副作用のあるマクロ)
#define VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2) \
	uint32 rd  = std::get<0>(t); \
	uint32 rs1 = std::get<1>(t); \
	uint32 rs2 = std::get<2>(t); \
	auto   vs1 = std::get<3>(t); \
	auto   vs2 = std::get<4>(t)

// 3オペランド(IMM)テストの要素を変数に取り出す部分 (副作用のあるマクロ)
#define VAR_TRI_IMM(t, rd, rs1, vs1, imm) \
	uint32 rd  = std::get<0>(t); \
	uint32 rs1 = std::get<1>(t); \
	auto   vs1 = std::get<2>(t); \
	auto   imm = std::get<3>(t)


// 自前の Round Mode 定数でシステムの丸めモードを設定する。
static void
setround(uint32 rm)
{
	int mode;
	switch (rm) {
	 case RM_NEAR:
		mode = FE_TONEAREST;
		break;
	 case RM_ZERO:
		mode = FE_TOWARDZERO;
		break;
	 case RM_MINUS:
		mode = FE_DOWNWARD;
		break;
	 case RM_PLUS:
		mode = FE_UPWARD;
		break;
	 default:
		PANIC("corrupted rm=%d", rm);
	}
	std::fesetround(mode);
}

// d が float でも同じ値として表現可能なら true を返す
static bool
can_float(double d)
{
	float f = (float)d;

	// NAN 同士の == 比較は false になるため
	if (std::isnan(d) && std::isnan(f))
		return true;
	if (d == f)
		return true;
	return false;
}

// fp に出力
static void out(const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	vfprintf(fp, fmt, ap);
	va_end(ap);
}

// レジスタ regno に即値 imm をセットする
static void
MOV_imm(int regno, uint32 imm)
{
	if (regno == 0) {
		// nop
	} else if (imm == 0) {
		out("	xor	%%r%d, %%r%d, %%r%d\n", regno, regno, regno);
	} else if (imm < 0x10000) {
		out("	or 	%%r%d, %%r0, 0x%04x\n", regno, imm);
	} else if (imm == 0xffffffff) {
		out("	or.c	%%r%d, %%r0, %%r0	| MOV %%r%d, 0xffffffff\n",
			regno, regno);
	} else {
		out("	or.u	%%r%d, %%r0, 0x%04x	| MOV %%r%d, 0x%08x\n",
			regno, (imm >> 16), regno, imm);
		if ((imm & 0xffff) != 0) {
			out("	or  	%%r%d, %%r%d, 0x%04x\n",
				regno, regno, imm & 0xffff);
		}
	}
}


//
// ここからテストコード
//

// r2: 引数1: グローバル変数の先頭
//  r2[0] = &exception_occurred
//  r2[1] = &exception_expected
// r3: 引数2: 今回のテストのパラメータの先頭。内容は code ごとに異なる。
//
// r5: r1 のバックアップ
// r6(,r7):   期待 rD
// r8(,r9):   rD  (テストで使う)、結果の rD をここに入れて返す
// r10(,r11): rS1 (テストで使う)
// r12(,r13): rS2 (テストで使う)
//
// 規約では r10-13 がサブルーチンが破壊してよいレジスタで、
// r2-r9 は呼び出し側が引数を置くレジスタだがここもサブルーチンが破壊してよい
// ので使ってしまう。

#define Rexp (6)	// 期待値を格納するレジスタ
#define Rres (8)	// 結果を格納するレジスタ

// add でのオーバーフロー条件
static inline bool
isovf_add(uint32 s1, uint32 s2, uint32 res)
{
	return (int32)((s1 ^ res) & (s2 ^ res)) < 0;
}

// sub でのオーバーフロー条件
static inline bool
isovf_sub(uint32 s1, uint32 s2, uint32 res)
{
	return (int32)((s1 ^ s2) & (s1 ^ res)) < 0;
}

// 汎用演算命令共通のテストパターン出力部分
static void
outtest_generic(const char *mnemonic,
	uint32 rd, uint32 rs1, uint32 rs2, uint32 vs1, uint32 vs2, uint32 exp_rd)
{
	// rs2 が IMM なら #imm
	bool is_imm = (rs2 == IMM);
	uint32& imm = vs2;

	out("test_%s_%d:\n", testname, testnum);
	if (is_imm) {
		out("	/* %s r%d, r%d, #0x%04x (S1=$%08x) */\n",
			mnemonic, rd, rs1, imm, vs1);
	} else {
		out("	/* %s r%d, r%d, r%d (S1=$%08x, S2=$%08x) */\n",
			mnemonic, rd, rs1, rs2, vs1, vs2);
	}

	out("	.word	0x%08x	| exp_rd\n", exp_rd);
	// ここからコード
	MOV_imm(rs1, vs1);
	if (!is_imm && rs1 != rs2) {
		MOV_imm(rs2, vs2);
	}

	if (is_imm) {
		out("	%s	%%r%d, %%r%d, 0x%04x	| test\n", mnemonic, rd, rs1, imm);
	} else {
		out("	%s	%%r%d, %%r%d, %%r%d	| test\n", mnemonic, rd, rs1, rs2);
	}

	if (rd != Rres) {
		out("	or	%%r%d, %%r0, %%r%d\n", Rres, rd);
	}
	out("	jmp	%%r1\n");
	out("\n");

	testnum++;
}

// 2項ビットフィールド命令共通のテストパターン出力部分
// (outtest_generic と似てるけど微妙に違う)
static void
outtest_bitfield(const char *mnemonic,
	uint32 rd, uint32 rs1, uint32 vs1, uint32 wo, uint32 exp_rd)
{
	uint32 w = (wo >> 5) & 0x1f;	// XXX 表記上は 0 なのか 32 なのか
	uint32 o =  wo       & 0x1f;

	out("test_%s_%d:\n", testname, testnum);
	out("	/* %s r%d, r%d, %d<%d> (S1=$%08x) */\n",
		mnemonic, rd, rs1, w, o, vs1);

	out("	.word	0x%08x	| exp_rd\n", exp_rd);
	// ここからコード
	MOV_imm(rs1, vs1);

	out("	%s	%%r%d, %%r%d, %d<%d>	| test\n", mnemonic, rd, rs1, w, o);

	if (rd != Rres) {
		out("	or	%%r%d, %%r0, %%r%d\n", Rres, rd);
	}
	out("	jmp	%%r1\n");
	out("\n");

	testnum++;
}

// ff0/ff1 命令共通のテストパターン出力部分
// (outtest_generic と似てるけど微妙に違う)
static void
outtest_ff01(const char *mnemonic,
	uint32 rd, uint32 rs2, uint32 vs2, uint32 exp_rd)
{
	out("test_%s_%d:\n", testname, testnum);
	out("	/* %s r%d, r%d (S2=$%08x) */\n",
		mnemonic, rd, rs2, vs2);

	out("	.word	0x%08x	| exp_rd\n", exp_rd);
	// ここからコード
	MOV_imm(rs2, vs2);

	out("	%s	%%r%d, %%r%d	| test\n", mnemonic, rd, rs2);

	if (rd != Rres) {
		out("	or	%%r%d, %%r0, %%r%d\n", Rres, rd);
	}
	out("	jmp	%%r1\n");
	out("\n");

	testnum++;
}

// add/sub 命令共通のテストパターン出力部分
// rs2 == IMM なら即値指定。
// scale == true なら lda rD, rS1[rS2] 形式。
static void
outtest_addsub(const char *mnemonic,
	uint32 rd, uint32 rs1, uint32 rs2, uint32 vs1, uint32 vs2, int cin,
	uint32 exp_rd, uint32 exp_cy, bool exception = false, bool is_scale = false)
{
	// rs2 が IMM なら #imm
	bool is_imm = (rs2 == IMM);
	uint32& imm = vs2;

	out("test_%s_%d:\n", testname, testnum);
	if (is_imm) {
		out("	/* %s r%d, r%d, #0x%04x (S1=$%08x, Cin=%d) */\n",
			mnemonic, rd, rs1, imm, vs1, cin);
	} else if (is_scale) {
		out("	/* %s r%d, r%d[r%d] (S1=$%08x, S2=$%08x) */\n",
			mnemonic, rd, rs1, rs2, vs1, vs2);
	} else {
		out("	/* %s r%d, r%d, r%d (S1=$%08x, S2=$%08x, Cin=%d) */\n",
			mnemonic, rd, rs1, rs2, vs1, vs2, cin);
	}

	out("	.word	0x%08x	| exp_rd\n", exp_rd);
	out("	.word	0x%08x	| Cin|ExpCy|Exception|0\n",
		(cin << 24) | (exp_cy << 16) | (exception << 8));
	// ここからテストコード
	// ソースレジスタをセット、実行、結果を保存
	MOV_imm(rs1, vs1);
	if (!is_imm && rs1 != rs2) {
		MOV_imm(rs2, vs2);
	}
	if (exception) {
		// 例外期待なら、rD (と Cy)の期待値は命令実行前の値 (p.6-19, 6.6)
		out("	or	%%r%d, %%r0, %%r%d\n", Rexp, rd);
	} else {
		MOV_imm(Rexp, exp_rd);
	}

	if (exception) {
		out("	| exception is expected\n");
	}
	if (is_imm) {
		out("	%s	%%r%d, %%r%d, 0x%04x	| test\n", mnemonic, rd, rs1, imm);
	} else if (is_scale) {
		out("	%s	%%r%d, %%r%d[%%r%d]	| test\n", mnemonic, rd, rs1, rs2);
	} else {
		out("	%s	%%r%d, %%r%d, %%r%d	| test\n", mnemonic, rd, rs1, rs2);
	}

	if (rd != Rres) {
		out("	or	%%r%d, %%r0, %%r%d\n", Rres, rd);
	}
	out("	jmp	%%r1\n");
	out("\n");

	testnum++;
}

// div 命令共通のテストパターン出力部分
static void
outtest_div(const char *mnemonic,
	uint32 rd, uint32 rs1, uint32 rs2, uint32 vs1, uint32 vs2,
	uint32 exp_rd, bool exception = false)
{
	// rs2 が IMM なら #imm
	bool is_imm = (rs2 == IMM);
	uint32& imm = vs2;

	out("test_%s_%d:\n", testname, testnum);
	if (is_imm) {
		out("	/* %s r%d, r%d, #0x%04x (S1=$%08x) */\n",
			mnemonic, rd, rs1, imm, vs1);
	} else {
		out("	/* %s r%d, r%d, r%d (S1=$%08x, S2=$%08x) */\n",
			mnemonic, rd, rs1, rs2, vs1, vs2);
	}

	out("	.word	0x%08x	| exp_ex\n", exception ? 2 : 0);
	// ここからテストコード
	// ソースレジスタをセット、実行、結果を保存
	MOV_imm(rs1, vs1);
	if (!is_imm && rs1 != rs2) {
		MOV_imm(rs2, vs2);
	}
	if (exception) {
		// 例外期待なら、rD の期待値は命令実行前の値 (p.6-40, 6.8.3)
		out("	or	%%r%d, %%r0, %%r%d\n", Rexp, rd);
	} else {
		MOV_imm(Rexp, exp_rd);
	}

	if (exception) {
		out("	| exception is expected\n");
	}
	if (is_imm) {
		out("	%s	%%r%d, %%r%d, 0x%04x	| test\n", mnemonic, rd, rs1, imm);
	} else {
		out("	%s	%%r%d, %%r%d, %%r%d	| test\n", mnemonic, rd, rs1, rs2);
	}

	if (rd != Rres) {
		out("	or	%%r%d, %%r0, %%r%d\n", Rres, rd);
	}
	out("	jmp	%%r1\n");
	out("\n");

	testnum++;
}

// FP 命令共通のテストパターン出力部分。
// ソースレジスタが1つの場合 (rs1 を使わない場合) は fsz1 = '\0'、rs1 = -1 に
// すること(vs1 は不定でよい)。
static void
outtest_fop(const char *mnemonic, char fsz0, char fsz1, char fsz2,
	uint32 rd, uint32 rs1, uint32 rs2, fpi vs1, fpi vs2,
	uint32 rm, fpi expected, uint32 exception)
{
	bool r4_backup = false;

	out("test_%s_%d:\n", testname, testnum);
	if (fsz1 == '\0') {
		out("	/* %s.%c%c r%d, r%d (S2=%s; %s) */\n",
			mnemonic, fsz0, fsz2, rd, rs2,
			vs2.ToString().c_str(), rmstr[rm]);
	} else {
		out("	/* %s.%c%c%c r%d, r%d, r%d (S1=%s, S2=%s; %s) */\n",
			mnemonic, fsz0, fsz1, fsz2, rd, rs1, rs2,
			vs1.ToString().c_str(), vs2.ToString().c_str(), rmstr[rm]);
	}

	out("	.word	0x%08x	| RndMode | exp_ex\n",
		exception | (rm << (16 + 14)));
	// ここからテストコード
	if (fsz1 != '\0') {
		MOV_imm(rs1, vs1.h);
		if (fsz1 == 'd') {
			if (rs1 == 0) {
				// ソースが r0,r1 になるので r1 を(さらに)退避
				out("	or	%%r4, %%r0, %%r1\n");
				r4_backup = true;
			}
			MOV_imm(rs1 + 1, vs1.l);
		}
	}
	MOV_imm(rs2, vs2.h);
	if (fsz2 == 'd') {
		if (r4_backup == false && rs2 == 0) {
			// ソースが r0,r1 になるので r1 を(さらに)退避
			out("	or	%%r4, %%r0, %%r1\n");
			r4_backup = true;
		}
		MOV_imm(rs2 + 1, vs2.l);
	}

	if (exception != 0) {
		// 例外期待なら rD の期待値は命令実行前の値
		out("	or	%%r%d, %%r0, %%r%d\n", Rexp,     rd);
		if (fsz0 == 'd') {
			out("	or	%%r%d, %%r0, %%r%d\n", Rexp + 1, rd + 1);
		}
	} else {
		// 例外が起きないことを期待する
		MOV_imm(Rexp, expected.h);
		if (fsz0 == 'd') {
			MOV_imm(Rexp + 1, expected.l);
		}
	}

	if (fsz1 == '\0') {
		out("	%s.%c%c	%%r%d, %%r%d	| test\n",
			mnemonic, fsz0, fsz2, rd, rs2);
	} else {
		out("	%s.%c%c%c	%%r%d, %%r%d, %%r%d	| test\n",
			mnemonic, fsz0, fsz1, fsz2, rd, rs1, rs2);
	}

	if (r4_backup) {
		out("	or	%%r1, %%r0, %%r4\n");
	}
	if (rd != Rres) {
		out("	or	%%r%d, %%r0, %%r%d\n", Rres, rd);
		if (fsz0 == 'd') {
			out("	or	%%r%d, %%r0, %%r%d\n", Rres + 1, rd + 1);
		}
	}
	out("	jmp	%%r1\n");
	out("\n");

	testnum++;
}

// FP 命令共通 (ソースレジスタが1つの形式)
static void
outtest_fop(const char *mnemonic, char fsz0, char fsz2,
	uint32 rd, uint32 rs2, fpi vs2,
	uint32 rm, fpi expected, uint32 exception)
{
	fpi dummy_vs1;

	outtest_fop(mnemonic, fsz0, '\0', fsz2,
		rd, (uint32)-1, rs2, dummy_vs1, vs2,
		rm, expected, exception);
}

// int, nint, trnc 命令共通のテストパターン出力部分。
// rm はテスト環境内の FPCR に設定する丸めモード (入力パラメータ)。
static void
outtest_toint(const char *mnemonic, uint32 rd, uint32 rs2, fpi vs2,
	uint32 rm, uint64 exp)
{
	uint32 exception = exp >> 32;
	uint32 expected  = exp;

	outtest_fop(mnemonic, 's', (vs2.IsFloat() ? 's' : 'd'),
		rd, rs2, vs2, rm, expected, exception);
}


//
// ここからテストの出力
//

static void
gen_add()
{
	for (const auto& t : param_arith_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);

		for (int cin = 0; cin <= 1; cin++) {
			// 期待値
			uint64 result = (uint64)vs1 + vs2;
			uint32 exp_rd = (uint32)result;
			uint32 exp_cy = cin;
			if (rd == 0) {
				exp_rd = 0;
			}
			bool exp_ex = isovf_add(vs1, vs2, result);
			if (exp_ex) {
				exp_cy = cin;
			}

			outtest_addsub("add",
				rd, rs1, rs2, vs1, vs2, cin, exp_rd, exp_cy, exp_ex);
		}
	}
}

static void
gen_add_ci()
{
	for (const auto& t : param_arith_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);

		for (int cin = 0; cin <= 1; cin++) {
			// 期待値
			uint64 result = (uint64)vs1 + vs2 + cin;
			uint32 exp_rd = (uint32)result;
			uint32 exp_cy = cin;
			if (rd == 0) {
				exp_rd = 0;
			}
			bool exp_ex = isovf_add(vs1, vs2, result);
			if (exp_ex) {
				exp_cy = cin;
			}

			outtest_addsub("add.ci",
				rd, rs1, rs2, vs1, vs2, cin, exp_rd, exp_cy, exp_ex);
		}
	}
}

static void
gen_add_co()
{
	for (const auto& t : param_arith_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);

		for (int cin = 0; cin <= 1; cin++) {
			// 期待値
			uint64 result = (uint64)vs1 + vs2;
			uint32 exp_rd = (uint32)result;
			uint32 exp_cy = result >> 32;
			if (rd == 0) {
				exp_rd = 0;
			}
			bool exp_ex = isovf_add(vs1, vs2, result);
			if (exp_ex) {
				exp_cy = cin;
			}

			outtest_addsub("add.co",
				rd, rs1, rs2, vs1, vs2, cin, exp_rd, exp_cy, exp_ex);
		}
	}
}

static void
gen_add_cio()
{
	for (const auto& t : param_arith_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);

		for (int cin = 0; cin <= 1; cin++) {
			// 期待値
			uint64 result = (uint64)vs1 + vs2 + cin;
			uint32 exp_rd = (uint32)result;
			uint32 exp_cy = result >> 32;
			if (rd == 0) {
				exp_rd = 0;
			}
			bool exp_ex = isovf_add(vs1, vs2, result);
			if (exp_ex) {
				exp_cy = cin;
			}

			outtest_addsub("add.cio",
				rd, rs1, rs2, vs1, vs2, cin, exp_rd, exp_cy, exp_ex);
		}
	}
}

static void
gen_add_imm()
{
	for (const auto& t : param_arith_imm) {
		VAR_TRI_IMM(t, rd, rs1, vs1, imm);

		for (int cin = 0; cin <= 1; cin++) {
			// 期待値
			uint64 result = (uint64)vs1 + imm;
			uint32 exp_rd = (uint32)result;
			uint32 exp_cy = cin;
			if (rd == 0) {
				exp_rd = 0;
			}
			bool exp_ex = isovf_add(vs1, imm, result);
			if (exp_ex) {
				exp_cy = cin;
			}

			outtest_addsub("add",
				rd, rs1, IMM, vs1, imm, cin, exp_rd, exp_cy, exp_ex);
		}
	}
}

// addu rD, rS1, rS2 と lda rD, rS1, rS2 は名前が違うだけで同じ…
static void
gen_addu_lda(const char *mnemonic)
{
	for (const auto& t : param_arith_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);

		for (int cin = 0; cin <= 1; cin++) {
			// 期待値
			uint64 result = (uint64)vs1 + vs2;
			uint32 exp_rd = (uint32)result;
			uint32 exp_cy = cin;
			if (rd == 0) {
				exp_rd = 0;
			}

			outtest_addsub(mnemonic,
				rd, rs1, rs2, vs1, vs2, cin, exp_rd, exp_cy);
		}
	}
}

static void
gen_addu()
{
	gen_addu_lda("addu");
}

static void
gen_addu_ci()
{
	for (const auto& t : param_arith_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);

		for (int cin = 0; cin <= 1; cin++) {
			// 期待値
			uint64 result = (uint64)vs1 + vs2 + cin;
			uint32 exp_rd = (uint32)result;
			uint32 exp_cy = cin;
			if (rd == 0) {
				exp_rd = 0;
			}

			outtest_addsub("addu.ci",
				rd, rs1, rs2, vs1, vs2, cin, exp_rd, exp_cy);
		}
	}
}

static void
gen_addu_co()
{
	for (const auto& t : param_arith_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);

		for (int cin = 0; cin <= 1; cin++) {
			// 期待値
			uint64 result = (uint64)vs1 + vs2;
			uint32 exp_rd = (uint32)result;
			uint32 exp_cy = result >> 32;
			if (rd == 0) {
				exp_rd = 0;
			}

			outtest_addsub("addu.co",
				rd, rs1, rs2, vs1, vs2, cin, exp_rd, exp_cy);
		}
	}
}

static void
gen_addu_cio()
{
	for (const auto& t : param_arith_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);

		for (int cin = 0; cin <= 1; cin++) {
			// 期待値
			uint64 result = (uint64)vs1 + vs2 + cin;
			uint32 exp_rd = (uint32)result;
			uint32 exp_cy = result >> 32;
			if (rd == 0) {
				exp_rd = 0;
			}

			outtest_addsub("addu.cio",
				rd, rs1, rs2, vs1, vs2, cin, exp_rd, exp_cy);
		}
	}
}

// addu rD, rS1, #imm と lda rD, rS1, #imm は名前が違うだけで同じ…
static void
gen_addu_lda_imm(const char *mnemonic)
{
	for (const auto& t : param_arith_imm) {
		VAR_TRI_IMM(t, rd, rs1, vs1, imm);

		for (int cin = 0; cin <= 1; cin++) {
			// 期待値
			uint64 result = (uint64)vs1 + imm;
			uint32 exp_rd = (uint32)result;
			uint32 exp_cy = cin;
			if (rd == 0) {
				exp_rd = 0;
			}

			outtest_addsub(mnemonic,
				rd, rs1, IMM, vs1, imm, cin, exp_rd, exp_cy);
		}
	}
}

static void
gen_addu_imm()
{
	gen_addu_lda_imm("addu");
}

static void
gen_and()
{
	for (const auto& t : param_arith_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);
		uint32 exp_rd = vs1 & vs2;
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_generic("and", rd, rs1, rs2, vs1, vs2, exp_rd);
	}
}

static void
gen_and_c()
{
	for (const auto& t : param_arith_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);
		uint32 exp_rd = vs1 & ~vs2;
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_generic("and.c", rd, rs1, rs2, vs1, vs2, exp_rd);
	}
}

static void
gen_and_imm()
{
	for (const auto& t : param_arith_imm) {
		VAR_TRI_IMM(t, rd, rs1, vs1, imm);
		uint32 exp_rd = vs1 & (imm | 0xffff0000);
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_generic("and", rd, rs1, IMM, vs1, imm, exp_rd);
	}
}

static void
gen_and_u()
{
	for (const auto& t : param_arith_imm) {
		VAR_TRI_IMM(t, rd, rs1, vs1, imm);
		uint32 exp_rd = vs1 & ((imm << 16) | 0xffff);
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_generic("and.u", rd, rs1, IMM, vs1, imm, exp_rd);
	}
}

static uint32
calc_clr(uint32 src, uint32 wo)
{
	uint32 w = ((wo >> 5) & 0x1f) ?: 32;
	uint32 o =   wo       & 0x1f;
	uint32 mask = (uint32)(((1ULL << w) - 1) << o);
	return src & ~mask;
}

static void
gen_clr_imm()
{
	for (const auto& t : param_bitfield_imm) {
		VAR_TRI_IMM(t, rd, rs1, vs1, imm);
		uint32 exp_rd = calc_clr(vs1, imm);
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_bitfield("clr", rd, rs1, vs1, imm, exp_rd);
	}
}

static void
gen_clr_reg()
{
	for (const auto& t : param_bitfield_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);
		uint32 exp_rd = calc_clr(vs1, vs2);
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_generic("clr", rd, rs1, rs2, vs1, vs2, exp_rd);
	}
}

static uint32
calc_cmp(uint32 vs1, uint32 vs2)
{
	// 期待値
	uint32 exp_rd = 0;

	exp_rd |= (vs1 >= vs2) ? 0x0800 : 0;
	exp_rd |= (vs1 <  vs2) ? 0x0400 : 0;
	exp_rd |= (vs1 <= vs2) ? 0x0200 : 0;
	exp_rd |= (vs1 >  vs2) ? 0x0100 : 0;
	exp_rd |= ((int32)vs1 >= (int32)vs2) ? 0x0080 : 0;
	exp_rd |= ((int32)vs1 <  (int32)vs2) ? 0x0040 : 0;
	exp_rd |= ((int32)vs1 <= (int32)vs2) ? 0x0020 : 0;
	exp_rd |= ((int32)vs1 >  (int32)vs2) ? 0x0010 : 0;
	exp_rd |= (vs1 != vs2) ? 0x0008 : 0;
	exp_rd |= (vs1 == vs2) ? 0x0004 : 0;

	return exp_rd;
}

static void
gen_cmp()
{
	for (const auto& t : param_arith_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);
		uint32 exp_rd = calc_cmp(vs1, vs2);
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_generic("cmp", rd, rs1, rs2, vs1, vs2, exp_rd);
	}
}

static void
gen_cmp_imm()
{
	for (const auto& t : param_arith_imm) {
		VAR_TRI_IMM(t, rd, rs1, vs1, imm);
		uint32 exp_rd = calc_cmp(vs1, imm);
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_generic("cmp", rd, rs1, IMM, vs1, imm, exp_rd);
	}
}

// 符号付きで x / y の期待値を計算する。
// 計算可能な場合は *exp_rd に結果を書き戻し *exp_ex に false を書き戻す。
// 例外を期待する場合は *exp_ex に true を書き戻す。
static void
calc_div(uint32 *exp_rd, bool *exp_ex, int32 x, int32 y)
{
	// どちらかのオペランドが負でも例外らしい。符号付きとは一体…。
	if (x < 0 || y <= 0) {
		*exp_ex = true;
		*exp_rd = 0;	// なくていいはずだけどコンパイラに怒られるので
	} else {
		*exp_ex = false;
		*exp_rd = (uint32)(x / y);
	}
}

static void
gen_div()
{
	for (const auto& t : param_arith_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);
		uint32 exp_rd;
		bool exp_ex;
		calc_div(&exp_rd, &exp_ex, vs1, vs2);
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_div("div", rd, rs1, rs2, vs1, vs2, exp_rd, exp_ex);
	}
}

static void
gen_div_imm()
{
	for (const auto& t : param_arith_imm) {
		VAR_TRI_IMM(t, rd, rs1, vs1, imm);
		uint32 exp_rd;
		bool exp_ex;
		calc_div(&exp_rd, &exp_ex, vs1, imm);
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_div("div", rd, rs1, IMM, vs1, imm, exp_rd, exp_ex);
	}
}

// 符号無しで x / y の期待値を計算する。
// 計算可能な場合は *exp_rd に結果を書き戻し *exp_ex に false を書き戻す。
// DivByZero を期待する場合は *exp_ex に true を書き戻す。
static void
calc_divu(uint32 *exp_rd, bool *exp_ex, uint32 x, uint32 y)
{
	if (y == 0) {
		*exp_ex = true;
		*exp_rd = 0;	// なくていいはずだけどコンパイラに怒られるので
	} else {
		*exp_ex = false;
		*exp_rd = x / y;
	}
}

static void
gen_divu()
{
	for (const auto& t : param_arith_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);
		uint32 exp_rd;
		bool exp_ex;
		calc_divu(&exp_rd, &exp_ex, vs1, vs2);
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_div("divu", rd, rs1, rs2, vs1, vs2, exp_rd, exp_ex);
	}
}

static void
gen_divu_imm()
{
	for (const auto& t : param_arith_imm) {
		VAR_TRI_IMM(t, rd, rs1, vs1, imm);
		uint32 exp_rd;
		bool exp_ex;
		calc_divu(&exp_rd, &exp_ex, vs1, imm);
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_div("divu", rd, rs1, IMM, vs1, imm, exp_rd, exp_ex);
	}
}

static uint32
calc_ext(uint32 src, uint32 wo)
{
	uint32 w = ((wo >> 5) & 0x1f) ?: 32;
	uint32 o =   wo       & 0x1f;
	uint32 mask = (uint32)(((1ULL << w) - 1) << o);

	src &= mask;

	int shift = 0;
	if (o + w < 32) {
		shift = 32 - (o + w);
		src <<= shift;
	}
	src = (int32)src >> (o + shift);
	return src;
}

static void
gen_ext_imm()
{
	for (const auto& t : param_bitfield_imm) {
		VAR_TRI_IMM(t, rd, rs1, vs1, imm);
		uint32 exp_rd = calc_ext(vs1, imm);
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_bitfield("ext", rd, rs1, vs1, imm, exp_rd);
	}
}

static void
gen_ext_reg()
{
	for (const auto& t : param_bitfield_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);
		uint32 exp_rd = calc_ext(vs1, vs2);
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_generic("ext", rd, rs1, rs2, vs1, vs2, exp_rd);
	}
}

static uint32
calc_extu(uint32 src, uint32 wo)
{
	uint32 w = ((wo >> 5) & 0x1f) ?: 32;
	uint32 o =   wo       & 0x1f;
	uint32 mask = (uint32)(((1ULL << w) - 1) << o);

	return (src & mask) >> o;
}

static void
gen_extu_imm()
{
	for (const auto& t : param_bitfield_imm) {
		VAR_TRI_IMM(t, rd, rs1, vs1, imm);
		uint32 exp_rd = calc_extu(vs1, imm);
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_bitfield("extu", rd, rs1, vs1, imm, exp_rd);
	}
}

static void
gen_extu_reg()
{
	for (const auto& t : param_bitfield_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);
		uint32 exp_rd = calc_extu(vs1, vs2);
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_generic("extu", rd, rs1, rs2, vs1, vs2, exp_rd);
	}
}

// fcmp.sXX の計算部分のみ
static uint32
calc_fcmp(double vs1, double vs2)
{
	uint32 res = 0;

	if (vs2 >= 0) {
		res |= (vs1 <= 0 || vs1 >= vs2) ? 0x0800 : 0;	// ob
		res |= (0 <  vs1 && vs1 <  vs2) ? 0x0400 : 0;	// in
		res |= (0 <= vs1 && vs1 <= vs2) ? 0x0200 : 0;	// ib
		res |= (vs1 <  0 || vs1 >  vs2) ? 0x0100 : 0;	// ou
	}
	res |= (vs1 >= vs2) ? 0x0080 : 0;	// ge
	res |= (vs1 <  vs2) ? 0x0040 : 0;	// lt
	res |= (vs1 <= vs2) ? 0x0020 : 0;	// le
	res |= (vs1 >  vs2) ? 0x0010 : 0;	// gt
	res |= (vs1 != vs2) ? 0x0008 : 0;	// ne
	res |= (vs1 == vs2) ? 0x0004 : 0;	// eq
	// cp; if and only if the two operands are comparable
	if (!std::isnan(vs1) && !std::isnan(vs2)) {
		res |= 0x0002;
	}
	// nc; if, and only if, the two operands are not comparable
	if (std::isnan(vs1) || std::isnan(vs2)) {
		res |= 0x0001;
	}

	return res;
}

// fcmp.sXX の共通部分
static void
gen_fcmp(char fsz1, const char fsz2)
{
	for (uint32 rm = 0; rm < 4; rm++) {
		for (const auto& t : param_fcmp) {
			VAR_TRI_REG(t, rd, rs1, rs2, v1, v2);
			uint32 exception = 0;
			uint32 exp_rd = 0;
			fpi fpi1;
			fpi fpi2;

			// rS1, rS2 が同じで幅が違うのは(ゼロ以外は)意味がないので飛ばす
			if (rs1 == rs2 && fsz1 != fsz2 && (v1 != 0 && v2 != 0)) {
				continue;
			}
			// double で r0 を指すのは実質意味がないので飛ばす
			// (ゼロの場合は飛ばさなくてもいいけど面倒なので飛ばす)
			if (fsz1 == 'd' && rs1 == 0)
				continue;
			if (fsz2 == 'd' && rs2 == 0)
				continue;

			// ソースサイズが s なら float で表現できない値は飛ばす
			if (fsz1 == 's' && can_float(v1) == false)
				continue;
			if (fsz2 == 's' && can_float(v2) == false)
				continue;

			// r0 なら値も 0 としておかないといけない
			if (rs1 == 0) {
				v1 = 0;
			}
			if (rs2 == 0) {
				v2 = 0;
			}

			// rd ==0 の場合もソースに出力するコメントのために必要
			if (fsz1 == 's') {
				fpi1.Set((float)v1);
			} else {
				fpi1.Set(v1);
			}
			if (fsz2 == 's') {
				fpi2.Set((float)v2);
			} else {
				fpi2.Set(v2);
			}

			if (rd == 0) {
				exception = 0x1000;
			} else {
				exp_rd = calc_fcmp(fpi1.AsDouble(), fpi2.AsDouble());
			}

			outtest_fop("fcmp", 's', fsz1, fsz2,
				rd, rs1, rs2, fpi1, fpi2, rm, exp_rd, exception);
		}
	}
}
static void gen_fcmp_sss() { gen_fcmp('s', 's'); }
static void gen_fcmp_ssd() { gen_fcmp('s', 'd'); }
static void gen_fcmp_sds() { gen_fcmp('d', 's'); }
static void gen_fcmp_sdd() { gen_fcmp('d', 'd'); }

static void
gen_ff0()
{
	for (const auto& t : param_ff01) {
		VAR_DIADIC(t, rd, rs2, vs2);
		// 最も MSB 側にある %0 のビット位置(LSBを0とする)を返す
		// %0 がなければ 32 を返す
		uint32 exp_rd = 32;
		if (vs2 != 0xffffffff) {
			exp_rd = 31 - __builtin_clz(~vs2);
		}
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_ff01("ff0", rd, rs2, vs2, exp_rd);
	}
}

static void
gen_ff1()
{
	for (const auto& t : param_ff01) {
		VAR_DIADIC(t, rd, rs2, vs2);
		// 最も MSB 側にある %1 のビット位置(LSBを0とする)を返す
		// %0 がなければ 32 を返す
		uint32 exp_rd = 32;
		if (vs2 != 0x00000000) {
			exp_rd = 31 - __builtin_clz(vs2);
		}
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_ff01("ff1", rd, rs2, vs2, exp_rd);
	}
}

static void
gen_flt_ss()
{
	for (uint32 rm = 0; rm < 4; rm++) {
		setround(rm);
		for (const auto& t : param_flt) {
			VAR_DIADIC(t, rd, rs2, vs2);
			fpi fpi((float)(int32)vs2);

			outtest_fop("flt", 's', 's',
				rd, rs2, vs2, rm, fpi, (rd == 0 ? 0x1000 : 0));
		}
	}
}

static void
gen_flt_ds()
{
	for (uint32 rm = 0; rm < 4; rm++) {
		setround(rm);
		for (const auto& t : param_flt) {
			VAR_DIADIC(t, rd, rs2, vs2);
			fpi fpi((double)(int32)vs2);

			outtest_fop("flt", 'd', 's',
				rd, rs2, vs2, rm, fpi, (rd == 0 ? 0x1000 : 0));
		}
	}
}

// int, nint, trnc 命令共通の期待値計算。
// 期待値を計算するための丸めモードはあらかじめ setround() で設定しておくこと。
// 戻り値は上位 32bit が例外、下位 32bit が期待値。
// 例外が出ることを期待する時は、上位が例外コード(非0)、下位は無視。
// 例外が出ないことを期待する場合は、上位が 0、下位が期待値。
static uint64
calc_toint(uint32 rd, fpi vs2)
{
	uint32 expected = 0;
	uint32 exception = 0;

	if (rd == 0) {
		exception = 0x1000;
	} else {
		double src = vs2.AsDouble();
		double result = std::nearbyint(src);

		int exponent = 0;
		std::frexp(result, &exponent);

		// 実際には trnc 命令は 30ビット以上で例外を出すが、
		// そこから先は (OpenBSD の) ソフトウェアハンドラが処理して
		// 実際に収まらなかった時は最大値を返す。
		if (std::isinf(result) || std::isnan(result) || exponent >= 32) {
			if (std::signbit(result)) {
				expected = 0x80000000;
			} else {
				expected = 0x7fffffff;
			}
		} else {
			expected = (int32)result;
		}
	}

	return ((uint64)exception << 32) | expected;
}

static void
gen_int_ss()
{
	for (uint32 rm = 0; rm < 4; rm++) {
		setround(rm);
		for (const auto& t : param_trnc) {
			VAR_DIADIC(t, rd, rs2, vs2);
			// float で表現できない値は飛ばす
			if (can_float(vs2))
				continue;
			fpi fpi((float)vs2);
			uint64 exp = calc_toint(rd, fpi);
			outtest_toint("int", rd, rs2, fpi, rm, exp);
		}
	}
}

static void
gen_int_sd()
{
	for (uint32 rm = 0; rm < 4; rm++) {
		setround(rm);
		for (const auto& t : param_trnc) {
			VAR_DIADIC(t, rd, rs2, vs2);
			fpi fpi(vs2);
			uint64 exp = calc_toint(rd, fpi);
			outtest_toint("int", rd, rs2, fpi, rm, exp);
		}
	}
}

// lda.* rD, rS1, #imm は addu rD, rS1, #imm と同じ
static void
gen_lda_imm()
{
	gen_addu_lda_imm("lda");
}
static void
gen_lda_b_imm()
{
	gen_addu_lda_imm("lda.b");
}
static void
gen_lda_h_imm()
{
	gen_addu_lda_imm("lda.h");
}
static void
gen_lda_d_imm()
{
	gen_addu_lda_imm("lda.d");
}

// lda.* rD, rS1, rS2 は addu rD, rS1, rS2 と同じ
static void
gen_lda_reg()
{
	gen_addu_lda("lda");
}
static void
gen_lda_b_reg()
{
	gen_addu_lda("lda.b");
}
static void
gen_lda_h_reg()
{
	gen_addu_lda("lda.h");
}
static void
gen_lda_d_reg()
{
	gen_addu_lda("lda.d");
}

static void
gen_lda_scale(const char *mnemonic, uint32 scale)
{
	for (const auto& t : param_arith_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);

		for (int cin = 0; cin <= 1; cin++) {
			// 期待値
			uint64 result = (uint64)vs1 + vs2 * scale;
			uint32 exp_rd = (uint32)result;
			uint32 exp_cy = cin;
			if (rd == 0) {
				exp_rd = 0;
			}

			bool exception = false;
			bool is_scale = true;
			outtest_addsub(mnemonic,
				rd, rs1, rs2, vs1, vs2, cin, exp_rd, exp_cy,
				exception, is_scale);
		}
	}
}
static void
gen_lda_scale()
{
	gen_lda_scale("lda", 4);
}
static void
gen_lda_b_scale()
{
	gen_addu_lda("lda.b");
}
static void
gen_lda_h_scale()
{
	gen_lda_scale("lda.h", 2);
}
static void
gen_lda_d_scale()
{
	gen_lda_scale("lda.d", 8);
}

static uint32
calc_mak(uint32 src, uint32 wo)
{
	uint32 w = ((wo >> 5) & 0x1f) ?: 32;
	uint32 o =   wo       & 0x1f;
	uint32 mask = (uint32)((1ULL << w) - 1);

	return (src & mask) << o;
}

static void
gen_mak_imm()
{
	for (const auto& t : param_bitfield_imm) {
		VAR_TRI_IMM(t, rd, rs1, vs1, imm);
		uint32 exp_rd = calc_mak(vs1, imm);
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_bitfield("mak", rd, rs1, vs1, imm, exp_rd);
	}
}

static void
gen_mak_reg()
{
	for (const auto& t : param_bitfield_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);
		uint32 exp_rd = calc_mak(vs1, vs2);
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_generic("mak", rd, rs1, rs2, vs1, vs2, exp_rd);
	}
}

static void
gen_mask_imm()
{
	for (const auto& t : param_arith_imm) {
		VAR_TRI_IMM(t, rd, rs1, vs1, imm);
		uint32 exp_rd = vs1 & imm;
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_generic("mask", rd, rs1, IMM, vs1, imm, exp_rd);
	}
}

static void
gen_mask_u()
{
	for (const auto& t : param_arith_imm) {
		VAR_TRI_IMM(t, rd, rs1, vs1, imm);
		uint32 exp_rd = vs1 & (imm << 16);
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_generic("mask.u", rd, rs1, IMM, vs1, imm, exp_rd);
	}
}

static void
gen_mul()
{
	for (const auto& t : param_arith_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);
		uint32 exp_rd = (uint32)((uint64)vs1 * vs2);
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_generic("mul", rd, rs1, rs2, vs1, vs2, exp_rd);
	}
}

static void
gen_mul_imm()
{
	for (const auto& t : param_arith_imm) {
		VAR_TRI_IMM(t, rd, rs1, vs1, imm);
		uint32 exp_rd = (uint32)((uint64)vs1 * imm);
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_generic("mul", rd, rs1, IMM, vs1, imm, exp_rd);
	}
}

static void
gen_nint_ss()
{
	// 期待値は NEAR で計算
	setround(RM_NEAR);

	for (uint32 rm = 0; rm < 4; rm++) {
		for (const auto& t : param_trnc) {
			VAR_DIADIC(t, rd, rs2, vs2);
			// float で表現できない値は飛ばす
			if (can_float(vs2))
				continue;
			fpi fpi((float)vs2);
			uint64 exp = calc_toint(rd, fpi);
			outtest_toint("nint", rd, rs2, fpi, rm, exp);
		}
	}
}

static void
gen_nint_sd()
{
	// 期待値は NEAR で計算
	setround(RM_NEAR);

	for (uint32 rm = 0; rm < 4; rm++) {
		for (const auto& t : param_trnc) {
			VAR_DIADIC(t, rd, rs2, vs2);
			fpi fpi(vs2);
			uint64 exp = calc_toint(rd, fpi);
			outtest_toint("nint", rd, rs2, fpi, rm, exp);
		}
	}
}

static void
gen_or()
{
	for (const auto& t : param_arith_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);
		uint32 exp_rd = vs1 | vs2;
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_generic("or", rd, rs1, rs2, vs1, vs2, exp_rd);
	}
}

static void
gen_or_c()
{
	for (const auto& t : param_arith_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);
		uint32 exp_rd = vs1 | ~vs2;
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_generic("or.c", rd, rs1, rs2, vs1, vs2, exp_rd);
	}
}

static void
gen_or_imm()
{
	for (const auto& t : param_arith_imm) {
		VAR_TRI_IMM(t, rd, rs1, vs1, imm);
		uint32 exp_rd = vs1 | imm;
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_generic("or", rd, rs1, IMM, vs1, imm, exp_rd);
	}
}

static void
gen_or_u()
{
	for (const auto& t : param_arith_imm) {
		VAR_TRI_IMM(t, rd, rs1, vs1, imm);
		uint32 exp_rd = vs1 | (imm << 16);
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_generic("or.u", rd, rs1, IMM, vs1, imm, exp_rd);
	}
}

static uint32
calc_rot(uint32 src, uint32 wo)
{
	uint32 o = wo & 0x1f;
	return (src >> o) | (src << (32 - o));
}

static void
gen_rot_imm()
{
	// 手抜きで W は無視するのでパターンが重複する
	for (const auto& t : param_bitfield_imm) {
		VAR_TRI_IMM(t, rd, rs1, vs1, imm);
		uint32 exp_rd = calc_rot(vs1, imm);
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_bitfield("rot", rd, rs1, vs1, imm, exp_rd);
	}
}

static void
gen_rot_reg()
{
	// 手抜きで W は無視するのでパターンが重複する
	for (const auto& t : param_bitfield_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);
		uint32 exp_rd = calc_rot(vs1, vs2);
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_generic("rot", rd, rs1, rs2, vs1, vs2, exp_rd);
	}
}

static uint32
calc_set(uint32 src, uint32 wo)
{
	uint32 w = ((wo >> 5) & 0x1f) ?: 32;
	uint32 o =   wo       & 0x1f;
	uint32 mask = (uint32)(((1ULL << w) - 1) << o);
	return src | mask;
}

static void
gen_set_imm()
{
	for (const auto& t : param_bitfield_imm) {
		VAR_TRI_IMM(t, rd, rs1, vs1, imm);
		uint32 exp_rd = calc_set(vs1, imm);
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_bitfield("set", rd, rs1, vs1, imm, exp_rd);
	}
}

static void
gen_set_reg()
{
	for (const auto& t : param_bitfield_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);
		uint32 exp_rd = calc_set(vs1, vs2);
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_generic("set", rd, rs1, rs2, vs1, vs2, exp_rd);
	}
}

static void
gen_sub()
{
	for (const auto& t : param_arith_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);

		for (int cin = 0; cin <= 1; cin++) {
			// 期待値
			uint64 result = (uint64)vs1 + (~vs2) + 1;
			uint32 exp_rd = (uint32)result;
			uint32 exp_cy = cin;
			if (rd == 0) {
				exp_rd = 0;
			}
			bool exp_ex = isovf_sub(vs1, vs2, result);
			if (exp_ex) {
				exp_cy = cin;
			}

			outtest_addsub("sub",
				rd, rs1, rs2, vs1, vs2, cin, exp_rd, exp_cy, exp_ex);
		}
	}
}

static void
gen_sub_ci()
{
	for (const auto& t : param_arith_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);

		for (int cin = 0; cin <= 1; cin++) {
			// 期待値
			uint64 result = (uint64)vs1 + (~vs2) + cin;
			uint32 exp_rd = (uint32)result;
			uint32 exp_cy = cin;
			if (rd == 0) {
				exp_rd = 0;
			}
			bool exp_ex = isovf_sub(vs1, vs2, result);
			if (exp_ex) {
				exp_cy = cin;
			}

			outtest_addsub("sub.ci",
				rd, rs1, rs2, vs1, vs2, cin, exp_rd, exp_cy, exp_ex);
		}
	}
}

static void
gen_sub_co()
{
	for (const auto& t : param_arith_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);

		for (int cin = 0; cin <= 1; cin++) {
			// 期待値
			uint64 result = (uint64)vs1 + (~vs2) + 1;
			uint32 exp_rd = (uint32)result;
			uint32 exp_cy = result >> 32;
			if (rd == 0) {
				exp_rd = 0;
			}
			bool exp_ex = isovf_sub(vs1, vs2, result);
			if (exp_ex) {
				exp_cy = cin;
			}

			outtest_addsub("sub.co",
				rd, rs1, rs2, vs1, vs2, cin, exp_rd, exp_cy, exp_ex);
		}
	}
}

static void
gen_sub_cio()
{
	for (const auto& t : param_arith_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);

		for (int cin = 0; cin <= 1; cin++) {
			// 期待値
			uint64 result = (uint64)vs1 + (~vs2) + cin;
			uint32 exp_rd = (uint32)result;
			uint32 exp_cy = result >> 32;
			if (rd == 0) {
				exp_rd = 0;
			}
			bool exp_ex = isovf_sub(vs1, vs2, result);
			if (exp_ex) {
				exp_cy = cin;
			}

			outtest_addsub("sub.cio",
				rd, rs1, rs2, vs1, vs2, cin, exp_rd, exp_cy, exp_ex);
		}
	}
}

static void
gen_sub_imm()
{
	for (const auto& t : param_arith_imm) {
		VAR_TRI_IMM(t, rd, rs1, vs1, imm);

		for (int cin = 0; cin <= 1; cin++) {
			// 期待値
			uint64 result = (uint64)vs1 + (~imm) + 1;
			uint32 exp_rd = (uint32)result;
			uint32 exp_cy = cin;
			if (rd == 0) {
				exp_rd = 0;
			}
			bool exp_ex = isovf_sub(vs1, imm, result);
			if (exp_ex) {
				exp_cy = cin;
			}

			outtest_addsub("sub",
				rd, rs1, IMM, vs1, imm, cin, exp_rd, exp_cy, exp_ex);
		}
	}
}

static void
gen_subu()
{
	for (const auto& t : param_arith_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);

		for (int cin = 0; cin <= 1; cin++) {
			// 期待値
			uint64 result = (uint64)vs1 + (~vs2) + 1;
			uint32 exp_rd = (uint32)result;
			uint32 exp_cy = cin;
			if (rd == 0) {
				exp_rd = 0;
			}

			outtest_addsub("subu",
				rd, rs1, rs2, vs1, vs2, cin, exp_rd, exp_cy);
		}
	}
}

static void
gen_subu_ci()
{
	for (const auto& t : param_arith_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);

		for (int cin = 0; cin <= 1; cin++) {
			// 期待値
			uint64 result = (uint64)vs1 + (~vs2) + cin;
			uint32 exp_rd = (uint32)result;
			uint32 exp_cy = cin;
			if (rd == 0) {
				exp_rd = 0;
			}

			outtest_addsub("subu.ci",
				rd, rs1, rs2, vs1, vs2, cin, exp_rd, exp_cy);
		}
	}
}

static void
gen_subu_co()
{
	for (const auto& t : param_arith_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);

		for (int cin = 0; cin <= 1; cin++) {
			// 期待値
			uint64 result = (uint64)vs1 + (~vs2) + 1;
			uint32 exp_rd = (uint32)result;
			uint32 exp_cy = result >> 32;
			if (rd == 0) {
				exp_rd = 0;
			}

			outtest_addsub("subu.co",
				rd, rs1, rs2, vs1, vs2, cin, exp_rd, exp_cy);
		}
	}
}

static void
gen_subu_cio()
{
	for (const auto& t : param_arith_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);

		for (int cin = 0; cin <= 1; cin++) {
			// 期待値
			uint64 result = (uint64)vs1 + (~vs2) + cin;
			uint32 exp_rd = (uint32)result;
			uint32 exp_cy = result >> 32;
			if (rd == 0) {
				exp_rd = 0;
			}

			outtest_addsub("subu.cio",
				rd, rs1, rs2, vs1, vs2, cin, exp_rd, exp_cy);
		}
	}
}

static void
gen_subu_imm()
{
	for (const auto& t : param_arith_imm) {
		VAR_TRI_IMM(t, rd, rs1, vs1, imm);

		for (int cin = 0; cin <= 1; cin++) {
			// 期待値
			uint64 result = (uint64)vs1 + (~imm) + 1;
			uint32 exp_rd = (uint32)result;
			uint32 exp_cy = cin;
			if (rd == 0) {
				exp_rd = 0;
			}

			outtest_addsub("subu",
				rd, rs1, IMM, vs1, imm, cin, exp_rd, exp_cy);
		}
	}
}

static void
gen_trnc_ss()
{
	// 期待値は ZERO で計算
	setround(RM_ZERO);

	for (uint32 rm = 0; rm < 4; rm++) {
		for (const auto& t : param_trnc) {
			VAR_DIADIC(t, rd, rs2, vs2);
			// float で表現できない値は飛ばす
			if (can_float(vs2))
				continue;
			fpi fpi((float)vs2);
			uint64 exp = calc_toint(rd, fpi);
			outtest_toint("trnc", rd, rs2, fpi, rm, exp);
		}
	}
}

static void
gen_trnc_sd()
{
	// 期待値は ZERO で計算
	setround(RM_ZERO);

	for (uint32 rm = 0; rm < 4; rm++) {
		for (const auto& t : param_trnc) {
			VAR_DIADIC(t, rd, rs2, vs2);
			fpi fpi(vs2);
			uint64 exp = calc_toint(rd, fpi);
			outtest_toint("trnc", rd, rs2, fpi, rm, exp);
		}
	}
}

static void
gen_xor()
{
	for (const auto& t : param_arith_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);
		uint32 exp_rd = vs1 ^ vs2;
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_generic("xor", rd, rs1, rs2, vs1, vs2, exp_rd);
	}
}

static void
gen_xor_c()
{
	for (const auto& t : param_arith_reg) {
		VAR_TRI_REG(t, rd, rs1, rs2, vs1, vs2);
		uint32 exp_rd = vs1 ^ ~vs2;
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_generic("xor.c", rd, rs1, rs2, vs1, vs2, exp_rd);
	}
}

static void
gen_xor_imm()
{
	for (const auto& t : param_arith_imm) {
		VAR_TRI_IMM(t, rd, rs1, vs1, imm);
		uint32 exp_rd = vs1 ^ imm;
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_generic("xor", rd, rs1, IMM, vs1, imm, exp_rd);
	}
}

static void
gen_xor_u()
{
	for (const auto& t : param_arith_imm) {
		VAR_TRI_IMM(t, rd, rs1, vs1, imm);
		uint32 exp_rd = vs1 ^ (imm << 16);
		if (rd == 0) {
			exp_rd = 0;
		}
		outtest_generic("xor.u", rd, rs1, IMM, vs1, imm, exp_rd);
	}
}



//
// ここから出力用の処理
//

enum {
	G_ADD = 1,
	G_SUB,
	G_LOGICAL,
	G_BITFIELD,
	G_LDA,
	G_MULDIV,
	G_FLT,
	G_FCMP,
};

// テスト一覧 (編集しやすいように実体は一番下に置いてある)
struct testentry {
	int group;
	const char *code;
	void (*genfunc)();
	int quad;			// 結果が 64bit なら 1
	const char *name;
};
extern std::vector<testentry> test_table;

// list の中に val がなければ追加する
template <class T>
static void append(std::vector<T>& list, const T& val)
{
	if (std::find(list.begin(), list.end(), val) == list.end()) {
		list.emplace_back(val);
	}
}

// ファイルオープンとクローズ (副作用のあるマクロ)
#define FOPEN(filename_)	do { \
	filename = filename_;	\
	tmpname = filename + ".tmp";	\
	fp = fopen(tmpname.c_str(), "w");	\
	if (fp == NULL) {	\
		err(1, "%s", tmpname.c_str());	\
	}	\
	out("/* This file was created by %s */\n", getprogname());	\
	out("\n");	\
} while (0)
#define FCLOSE()	do {	\
	fclose(fp);	\
	compare_and_move(tmpname, filename);	\
} while (0)

// src, dst のファイルが異なっていれば src を dst に rename する
static void
compare_and_move(const std::string& src, const std::string& dst)
{
	struct stat st;
	int r;
	bool update;

	update = false;
	r = stat(dst.c_str(), &st);
	if (r < 0) {
		// dst not found
		update = true;
	} else {
		// dst exists

		std::string cmd = "cmp -s " + src + " " + dst;
		r = system(cmd.c_str());
		if (r < 0) {
			err(1, "system");
		}
		r = WEXITSTATUS(r);
		if (r > 1) {
			errx(1, "cmp failed?");
		}

		if (r == 0) {
			// src and dst are the same
		} else {
			update = true;
		}
	}

	if (update) {
		// 違っていれば、src を dst に移動
		r = rename(src.c_str(), dst.c_str());
		if (r < 0) {
			err(1, "rename: %s %s", src.c_str(), dst.c_str());
		}
		printf("Updated: %s\n", dst.c_str());
	} else {
		// 同じなら、今作った src のほうを消す
		r = remove(src.c_str());
		if (r < 0) {
			err(1, "remove: %s", src.c_str());
		}
	}
}
// テストアセンブリソースの出力共通部分
static void
print_test(const testentry& t)
{
	// グローバル変数にセット
	testname = t.name;
	testnum = 0;

	// テストの実体を出力
	t.genfunc();

	// テーブル
	out(".globl list_%s\n", testname);
	out("list_%s:\n", testname);
	for (int i = 0; i < testnum; i++) {
		out("\t.word	test_%s_%d\n", testname, i);
	}
	out("\t.word	0\n");
	out("\n");
}

// ソースファイルを作成
static void
create_file(const std::string& filename_, int group)
{
	std::string filename;
	std::string tmpname;

	FOPEN(filename_);
	for (const auto& t : test_table) {
		if (t.group == group) {
			print_test(t);
		}
	}
	FCLOSE();
}

static void compare_and_move(const std::string&, const std::string&);

int
main(int ac, char *av[])
{
	std::string filename;
	std::string tmpname;

	// テストパターンを用意
	for (const auto& rlist : reglist3) {
		auto rd  = (rlist >> 16) & 0xff;
		auto rs1 = (rlist >>  8) & 0xff;
		auto rs2 =  rlist        & 0xff;

		std::vector<uint32> *v_arith_s1 = (rs1 == 0) ? &v0 : &vlist_arith;
		std::vector<uint32> *v_arith_s2 = (rs2 == 0) ? &v0 : &vlist_arith;

		if (rs1 == rs2) {
			for (const auto& vs1 : *v_arith_s1) {
				Tuple3_2<uint32> val(rd, rs1, rs2, vs1, vs1);
				append(param_arith_reg, val);
			}
		} else {
			for (const auto& vs1 : *v_arith_s1) {
				for (const auto& vs2 : *v_arith_s2) {
					Tuple3_2<uint32> val(rd, rs1, rs2, vs1, vs2);
					append(param_arith_reg, val);
				}
			}
		}
	}
	for (const auto& rlist : reglist2) {
		auto rd  = (rlist >> 16) & 0xff;
		auto rs1 = (rlist >>  8) & 0xff;

		std::vector<uint32> *v_arith_s1 = (rs1 == 0) ? &v0 : &vlist_arith;

		for (const auto& vs1 : *v_arith_s1) {
			for (const auto& imm : vlist_arith) {
				if (imm <= 0xffff) {
					Tuple2_2<uint32> val(rd, rs1, vs1, imm);
					append(param_arith_imm, val);
				}
			}
		}
	}
	for (const auto& rlist : reglist3) {
		auto rd  = (rlist >> 16) & 0xff;
		auto rs1 = (rlist >>  8) & 0xff;
		auto rs2 =  rlist        & 0xff;

		std::vector<uint32> *v_bf = (rs1 == 0) ? &v0 : &vlist_bf;
		std::vector<uint32> *v_wo = (rs2 == 0) ? &v0 : &vlist_wo;

		if (rs1 == rs2) {
			for (const auto& vs1 : *v_bf) {
				Tuple3_2<uint32> val(rd, rs1, rs2, vs1, vs1);
				append(param_bitfield_reg, val);
			}
			for (const auto& vs2 : *v_wo) {
				Tuple3_2<uint32> val(rd, rs1, rs2, vs2, vs2);
				append(param_bitfield_reg, val);
			}
		} else {
			for (const auto& vs1 : *v_bf) {
				for (const auto& vs2 : *v_wo) {
					Tuple3_2<uint32> val(rd, rs1, rs2, vs1, vs2);
					append(param_bitfield_reg, val);
				}
			}
		}
	}
	for (const auto& rlist : reglist2) {
		auto rd  = (rlist >> 16) & 0xff;
		auto rs1 = (rlist >>  8) & 0xff;

		std::vector<uint32> *v_bf = (rs1 == 0) ? &v0 : &vlist_bf;

		for (const auto& vs1 : *v_bf) {
			for (const auto& wo : vlist_wo) {
				Tuple2_2<uint32> val(rd, rs1, vs1, wo);
				append(param_bitfield_imm, val);
			}
		}
	}
	for (const auto& rlist : reglist2) {
		auto rd  = (rlist >> 16) & 0xff;
		auto rs2 = (rlist >>  8) & 0xff;

		std::vector<uint32> v_ff;
		if (rs2 == 0) {
			v_ff = v0;
		} else {
			// vlist_arith とそれをビット反転したもの
			for (const auto v : vlist_arith) {
				v_ff.emplace_back(v);
				v_ff.emplace_back(~v);
			}
		}

		for (const auto& vs2 : v_ff) {
			Tuple2_1<uint32> val(rd, rs2, vs2);
			append(param_ff01, val);
		}
	}
	for (const auto& rlist : reglist2) {
		auto rd  = (rlist >> 16) & 0xff;
		auto rs2 = (rlist >>  8) & 0xff;

		std::vector<uint32> *v_arith_s2 = (rs2 == 0) ? &v0 : &vlist_arith;

		for (const auto& vs2 : *v_arith_s2) {
			Tuple2_1<uint32> val(rd, rs2, vs2);
			append(param_flt, val);
		}
	}
	for (const auto& rlist : reglist2) {
		auto rd  = (rlist >> 16) & 0xff;
		auto rs2 = (rlist >>  8) & 0xff;

		if (rs2 == 0) {
			Tuple2_1<double> val(rd, rs2, 0.0);
			append(param_trnc, val);
		} else {
			for (const auto& vs2 : vlist_double) {
				Tuple2_1<double> val(rd, rs2, vs2);
				append(param_trnc, val);
			}
		}
	}

	for (const auto& rlist : reglist3) {
		auto rd  = (rlist >> 16) & 0xff;
		auto rs1 = (rlist >>  8) & 0xff;
		auto rs2 =  rlist        & 0xff;

		std::vector<double> *vlist1 = (rs1 == 0) ? &vf0 : &vlist_double;
		std::vector<double> *vlist2 = (rs2 == 0) ? &vf0 : &vlist_double;

		if (rs1 == rs2) {
			for (const auto& vs1 : *vlist1) {
				Tuple3_2<double> val(rd, rs1, rs2, vs1, vs1);
				append(param_fcmp, val);
			}
		} else {
			for (const auto& vs1 : *vlist1) {
				for (const auto& vs2 : *vlist2) {
					Tuple3_2<double> val(rd, rs1, rs2, vs1, vs2);
					append(param_fcmp, val);
				}
			}
		}
	}

	// ソースファイルを出力
	create_file("optestm88k_add.s",		G_ADD);
	create_file("optestm88k_sub.s",		G_SUB);
	create_file("optestm88k_logical.s",	G_LOGICAL);
	create_file("optestm88k_bitfield.s",G_BITFIELD);
	create_file("optestm88k_lda.s",		G_LDA);
	create_file("optestm88k_muldiv.s",	G_MULDIV);
	create_file("optestm88k_flt.s",		G_FLT);
	create_file("optestm88k_fcmp.s",	G_FCMP);

	//
	// table.h を出力
	//
	FOPEN("optestm88k_table.h");
	out("#ifndef optestm88k_table_h\n");
	out("#define optestm88k_table_h\n");
	out("\n");
	// testcode は table_*.name を uniq したもの
	std::vector<std::string> test_code_list;
	for (const auto& t : test_table) {
		append(test_code_list, std::string(t.code));
	}
	for (const auto& name : test_code_list) {
		out("extern int test_%s(int *, int *);\n", name.c_str());
	}
	out("\n");

	for (const auto& t : test_table) {
		out("extern int *list_%s[];\n", t.name);
	}
	out("\n");
	out("#endif /* !optestm88k_table_h */\n");
	FCLOSE();

	//
	// table.c を出力
	//
	FOPEN("optestm88k_table.c");
	out("#include \"optestm88k_main.h\"\n");
	out("#include \"optestm88k_table.h\"\n");
	out("\n");
	out("struct testentry test_table[] = {\n");
	for (const auto& t : test_table) {
		out("	{ test_%s,\tlist_%s,\t%d, \"%s\" },\n",
			t.code, t.name, t.quad, t.name);
	}
	out("	{ NULL },\n");
	out("};\n");
	FCLOSE();

	return 0;
}

//
// テスト一覧
//
#define T(group, code, quad, name)	{ group, code, gen_##name, quad, #name }
std::vector<testentry> test_table = {
	T(G_ADD,		"addsub",	0,	add),
	T(G_ADD,		"addsub",	0,	add_ci),
	T(G_ADD,		"addsub",	0,	add_co),
	T(G_ADD,		"addsub",	0,	add_cio),
	T(G_ADD,		"addsub",	0,	add_imm),
	T(G_ADD,		"addsub",	0,	addu),
	T(G_ADD,		"addsub",	0,	addu_ci),
	T(G_ADD,		"addsub",	0,	addu_co),
	T(G_ADD,		"addsub",	0,	addu_cio),
	T(G_ADD,		"addsub",	0,	addu_imm),

	T(G_SUB,		"addsub",	0,	sub),
	T(G_SUB,		"addsub",	0,	sub_ci),
	T(G_SUB,		"addsub",	0,	sub_co),
	T(G_SUB,		"addsub",	0,	sub_cio),
	T(G_SUB,		"addsub",	0,	sub_imm),
	T(G_SUB,		"addsub",	0,	subu),
	T(G_SUB,		"addsub",	0,	subu_ci),
	T(G_SUB,		"addsub",	0,	subu_co),
	T(G_SUB,		"addsub",	0,	subu_cio),
	T(G_SUB,		"addsub",	0,	subu_imm),
	T(G_SUB,		"generic",	0,	cmp),
	T(G_SUB,		"generic",	0,	cmp_imm),

	T(G_LOGICAL,	"generic",	0,	and),
	T(G_LOGICAL,	"generic",	0,	and_c),
	T(G_LOGICAL,	"generic",	0,	and_imm),
	T(G_LOGICAL,	"generic",	0,	and_u),
	T(G_LOGICAL,	"generic",	0,	mask_imm),
	T(G_LOGICAL,	"generic",	0,	mask_u),
	T(G_LOGICAL,	"generic",	0,	or),
	T(G_LOGICAL,	"generic",	0,	or_c),
	T(G_LOGICAL,	"generic",	0,	or_imm),
	T(G_LOGICAL,	"generic",	0,	or_u),
	T(G_LOGICAL,	"generic",	0,	xor),
	T(G_LOGICAL,	"generic",	0,	xor_c),
	T(G_LOGICAL,	"generic",	0,	xor_imm),
	T(G_LOGICAL,	"generic",	0,	xor_u),

	T(G_BITFIELD,	"generic",	0,	clr_imm),
	T(G_BITFIELD,	"generic",	0,	clr_reg),
	T(G_BITFIELD,	"generic",	0,	ext_imm),
	T(G_BITFIELD,	"generic",	0,	ext_reg),
	T(G_BITFIELD,	"generic",	0,	extu_imm),
	T(G_BITFIELD,	"generic",	0,	extu_reg),
	T(G_BITFIELD,	"generic",	0,	ff0),
	T(G_BITFIELD,	"generic",	0,	ff1),
	T(G_BITFIELD,	"generic",	0,	mak_imm),
	T(G_BITFIELD,	"generic",	0,	mak_reg),
	T(G_BITFIELD,	"generic",	0,	rot_imm),
	T(G_BITFIELD,	"generic",	0,	rot_reg),
	T(G_BITFIELD,	"generic",	0,	set_imm),
	T(G_BITFIELD,	"generic",	0,	set_reg),

	T(G_LDA,		"addsub",	0,	lda_imm),
	T(G_LDA,		"addsub",	0,	lda_b_imm),
	T(G_LDA,		"addsub",	0,	lda_h_imm),
	T(G_LDA,		"addsub",	0,	lda_d_imm),
	T(G_LDA,		"addsub",	0,	lda_reg),
	T(G_LDA,		"addsub",	0,	lda_b_reg),
	T(G_LDA,		"addsub",	0,	lda_h_reg),
	T(G_LDA,		"addsub",	0,	lda_d_reg),
	T(G_LDA,		"addsub",	0,	lda_scale),
	T(G_LDA,		"addsub",	0,	lda_b_scale),
	T(G_LDA,		"addsub",	0,	lda_h_scale),
	T(G_LDA,		"addsub",	0,	lda_d_scale),

	T(G_MULDIV,		"generic",	0,	mul),
	T(G_MULDIV,		"generic",	0,	mul_imm),
	T(G_MULDIV,		"div",		0,	div),
	T(G_MULDIV,		"div",		0,	div_imm),
	T(G_MULDIV,		"div",		0,	divu),
	T(G_MULDIV,		"div",		0,	divu_imm),

	T(G_FLT,		"fop_s",	0,	flt_ss),
	T(G_FLT,		"fop_d",	1,	flt_ds),
	T(G_FLT,		"fop_s",	0,	trnc_ss),
	T(G_FLT,		"fop_s",	0,	trnc_sd),
	T(G_FLT,		"fop_s",	0,	nint_ss),
	T(G_FLT,		"fop_s",	0,	nint_sd),
	T(G_FLT,		"fop_s",	0,	int_ss),
	T(G_FLT,		"fop_s",	0,	int_sd),

	T(G_FCMP,		"fop_s",	0,	fcmp_sss),
	T(G_FCMP,		"fop_s",	0,	fcmp_ssd),
	T(G_FCMP,		"fop_s",	0,	fcmp_sds),
	T(G_FCMP,		"fop_s",	0,	fcmp_sdd),
};

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.