--- nono/m88xx0/m88100ops.cpp 2026/04/29 17:05:47 1.1.1.16 +++ nono/m88xx0/m88100ops.cpp 2026/04/29 17:05:56 1.1.1.17 @@ -4,6 +4,45 @@ // Licensed under nono-license.txt // +// 疑似 STOP について。 +// m88k には、m68k の STOP 状態に相当するものは存在せず、同じところを +// ぐるぐる回って時間を潰す方法が取られる。実機であれば (電力が無駄な +// こと以外は) これでも特段困らないが、エミュレータ、特に高速モードでは +// ホストの CPU がぶん回る状況になるため、これを検出して等速モードに +// 落としたい。 +// この無限ループの命令列は OS/コンパイラによってバリエーションがあるので +// 勝手に分類してそれぞれ対応する。 +// +// STOP#1) +// or r0,rS1,rS2 命令のみで構成されたループの場合は、or 命令を +// カウントして DoBranch() で検出している。 +// XXX どこで使われてたっけ +// +// STOP#2) +// OpenBSD 6.6(?) 〜 7.7 では以下の命令列が使用されている。 +// L1: tb1 #1, r0, #0xff +// ld r13, r21, #0x168 +// bcnd.n eq0, r13, L1 +// or r2, r25, #0x5ea0 +// tb1 は volatile 変数をアクセスするための同期命令として +// gcc 3.3.6 により出力されている(*1)。スーパバイザ状態において、 +// 同じ位置の tb1 命令に後方から直接 3 回ジャンプしてきたら、 +// それは volatile 変数を観測しながら変化を待っているループだと +// 思われるので、STOP と判断する。 +// これは tb1 命令内で検出している。 +// 3回という数字に確たる理由はない。また念の為、4命令後方以内 +// からのブランチに限定しておく。 +// *1: https://misskey.io/notes/aervliztx3l308r2 参照 +// +// STOP#3) +// OpenBSD 7.8 以降は tb1 命令の代わりに fldcr r0, fpcr 命令が +// 出力されるようになったが基本構造は STOP#2 と同じ。 +// fldcr r0, fpcr 命令内で対応。 +// L1: fldcr r0, fpcr +// ld r13, r24, #0x0204 +// bcnd.n eq0, r13, L1 +// or r2, r21, #0x7b18 + #include "m88100acc.h" #include "bitops.h" #include @@ -91,33 +130,20 @@ MPU88xx0Device::DoBranch(uint32 toaddr, auto e = brhist.AddEntry(reg.xip | (IsSuper() ? 1 : 0), toaddr, reg.opX); reg.fip = toaddr; - // STOP 検出。 - // m88k には STOP 状態 (何もせず割り込みだけ待つ状態。m68k の STOP 命令 - // による STOP 状態に相当するもの) は存在せず、割り込みが起きるまで NOP - // 相当の命令を無限ループで実行し続ける方法が一般的にとられる。 - // 実機であれば (電力が無駄なこと以外は) これで特段困らないかも知れないが - // エミュレータ (特に高速モード) ではこのような無限ループはホストの CPU - // がぶん回る状況になるため避けたい。そのためこの無限ループを検出したい。 - // ただし特定の(または定番の)命令列があるわけではなく、任意の無限ループが - // 用いられているため、汎用的に自動検出する必要がある。 - - // ループの 1 回目のブランチで初期化して、 - // 2 回目のブランチで STOP だったかどうかをチェックする。 - // 3 回目以降の場合は STOP でなかったので、もうチェックする必要はない。 - - // m68k の STOP 命令が特権命令なので、それに倣ってここでも特権状態のみを - // 対象とする。 - // 通常のブランチで来る回数がはるかに多いと考えられるので、 // 先にカウント数をチェック対象かどうか調べる。 + // また m68k の STOP 命令に倣ってここでも特権状態のみを対象とする。 if (e.count < 3 && IsSuper()) { if (e.count == 1) { // 新規ブランチ // 初期化 nop_counter = 0; } else if (pseudo_stop_enable) { - // 疑似 STOP 状態有効のときに、 - // e.count == 2 のときだけ STOP の検証をすれば良い。 + // STOP#1 の検出。 + // ループ内がすべて NOP (or r0, rS1, rS2) だったら STOP 状態。 + // そのためループ2周目に入ったら nop_counter を初期化し、 + // ループ3周目に入ったところで命令数を数える。 + // ブランチ間の命令がすべて NOP なら STOP ということにする。 // d はブランチ間の命令数 uint32 d = (reg.xip - toaddr) / sizeof(uint32); @@ -125,18 +151,9 @@ MPU88xx0Device::DoBranch(uint32 toaddr, if (next_exec) { d++; } - // ブランチ間の命令がすべて NOP なら STOP ということにする。 if (nop_counter == d) { ChangeState(CPU_STATE_STOP); } - - // OpenBSD 6.6 では - // L1: tb1 #1 r0, #0xff - // ld r13, r21, #0x168 - // bcnd.n eq0, r13, L1 - // or r2, r25, #0x5ea0 - // の命令列が使用されている。 - // これについては tb1 で実装してある。 } // STOP ではなかったら、あとは放置していい } @@ -811,8 +828,20 @@ OP_DEF(fldcr) return; } } - if (FLD_D == 0) + if (FLD_D == 0) { + // STOP#3。OpenBSD 7.8 以降の STOP エミュレーション。 + // 4命令以内の後方から fldcr r0, fpcr (何もしない命令) への分岐が + // 3回連続すれば STOP 状態とする。 + if (__predict_true(pseudo_stop_enable)) { + if (__predict_true(IsSuper())) { + const auto& e = brhist.entry[brhist.top]; + if (e.count == 3 && e.to == reg.xip && e.from - e.to < 16) { + ChangeState(CPU_STATE_STOP); + } + } + } return; + } // XXX 内部がバグってなければ読み出す時にマスクする必要はないが、一応 rD = reg.fcr[n] & reg.fcr_mask[n]; @@ -1313,22 +1342,9 @@ OP_DEF(tb1) if ((rS1 & (1U << B5)) != 0) { TrapException(VEC9); } else if (pseudo_stop_enable) { - - // OpenBSD の STOP エミュレーション。 - // OpenBSD 6.6 では - // L1: tb1 #1 r0, #0xff - // ld r13, r21, #0x168 - // bcnd.n eq0, r13, L1 - // or r2, r25, #0x5ea0 - // の命令列が使用されている。 - // tb1 は volatile 変数をアクセスするための同期命令として - // gcc により出力されている。スーパバイザが volatile を - // 観測するために同じアドレスの tb1 に直接ブランチで 3 回飛び込んで - // きたら、それは割り込みか別プロセッサか I/O による変更を待っている - // はずなので STOP と判断してみる。 - // o. 3回、という数字に確たる理由はない。 - // o. 念の為、16バイト(4命令)後方以内からのブランチに限定しておく。 - + // STOP#2。OpenBSD 6.6 〜 7.7 の STOP エミュレーション。 + // 4命令以内の後方から tb1 false, 0xff 命令への分岐が 3回連続すれば + // STOP 状態とする。 if (IsSuper() && VEC9 == 0xff) { const auto& e = brhist.entry[brhist.top]; if (e.count == 3