|
|
nono 1.2.0
;
; nono
; Copyright (C) 2020 nono project
;
; Redistribution and use in source and binary forms, with or without
; modification, are permitted provided that the following conditions
; are met:
; 1. Redistributions of source code must retain the above copyright
; notice, this list of conditions and the following disclaimer.
; 2. Redistributions in binary form must reproduce the above copyright
; notice, this list of conditions and the following disclaimer in the
; documentation and/or other materials provided with the distribution.
;
; THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
; IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
; OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
; IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
; INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
; BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
; AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
; OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
; OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
; SUCH DAMAGE.
; How to compile:
; > has fmovemn.has
; > hlk fmovemn.o si_util.o
.xref hexstr_long
.include doscall.mac
.include iocscall.mac
.list
.cpu 68030
PRINT .macro msg
pea.l msg
DOS _PRINT
addq.l #4,sp
.endm
.text
.even
<?php
// レジスタ割り当て
// d0-d1 work
// d4 d8(An,IX) の IX で使う。形式だけでいいので値は 0。
// d5 0=easy, $ffffffff=hard
// d6 ダイナミックモードでのレジスタ
// d7 1=検査対象命令でFライン例外がおきた
// その後は ok を表示するかどうかのカウント
// a0-a2 work
// a3 検査対象命令の次の位置 (Fライン例外から戻るのに使用)
// a4 元のFライン例外ハンドラ置き場
?>
<?php
// 第2ワードの下位バイトが FP 指定、3通りある。
//
// b7 b6 b5 b4 b3 b2 b1 b0
// -------------------------------
// FP7 FP6 FP5 FP4 FP3 FP2 FP1 FP0 : static pred
// FP0 FP1 FP2 FP3 FP4 FP5 FP6 FP7 : static ctrl
// 0 r r r 0 0 0 0 : dynamic
//
// ここでは常に %0110'0000 ($60) を指定しておき、
// プリデクリメントなら FP6-FP5、
// それ以外なら FP1-FP2、
// 動的レジスタ指定なら d6 を指すようにしておく。
// d6 の中は $30 (FP5-FP4 か FP2-FP3 になる) にしておく。
$d6 = "$30";
$srctable = array(
// dir ,mode1 easy hard
0 /* toFP, pred */ => ".......... !!m+!rxwp!",
2 /* toFP, ctrl */ => "..m+.rxwp. !!m+!rxwp!",
4 /* fromFP, pred */ => "....-..... !!m!-rxw!!",
6 /* fromFP, ctrl */ => "..m..rxw.. !!m!-rxw!!",
);
$testlist = array();
$testnum = 0;
foreach ($srctable as $mode21 => $eastr) {
list ($easy_str, $hard_str) = preg_split('/\s+/', $eastr);
$easy_ea = preg_split("//", $easy_str, -1, PREG_SPLIT_NO_EMPTY);
$hard_ea = preg_split("//", $hard_str, -1, PREG_SPLIT_NO_EMPTY);
// mode0 ビットは dynamic or static。
for ($mode0 = 0; $mode0 <= 1; $mode0++) {
$mode = $mode21 + $mode0;
for ($i = 0; $i < 10; $i++) {
$eatype = substr("dam+-rxwpi", $i, 1);
$ee = $easy_ea[$i];
$eh = $hard_ea[$i];
// 難しいので保留。
if ($i >= 7) {
continue;
}
// 命令語。
$inst = 0xf200c060;
$inst |= eatype2word($eatype) << 16;
$inst |= $mode << 11;
$test = array(
"num" => $testnum++,
"mode" => $mode,
"mem_to_fp" => (($mode & 4) == 0),
"is_pred" => (($mode & 2) == 0),
"dynamic" => (($mode & 1) != 0),
"eatype" => $eatype,
"is_easy" => ($ee != '.'),
"is_trap" => ($eh == '!'),
"inst" => $inst,
);
$testlist[] = $test;
}
}
}
?>
start:
clr.l -(sp)
DOS _SUPER
movem.l d4-d7/a2-a4,-(sp)
moveq.l #<?php print $d6; ?>,d6 ; Init D6 for dynamic
moveq.l #0,d4 ; Init D4 for d8(An,IX)
fmove.l d4,fpcr ; Init FPCR, especially round mode and round prec.
; rough argument check
cmpi.b #4,(a2)+ ; strlen(arg)<4 ?
bcs easymode
cmpi.l #$68617264,(a2) ; "hard"
bne easymode
hardmode:
moveq.l #-1,d5
PRINT msg_hard(pc)
<?php
foreach ($testlist as $test) {
out(" bsr test_{$test["num"]}");
}
?>
bsr test_nofpn
bsr test_illdyn
bra done
easymode:
moveq.l #0,d5
PRINT msg_easy(pc)
<?php
foreach ($testlist as $test) {
if ($test["is_easy"]) {
out(" bsr test_{$test["num"]}");
}
}
?>
done:
movem.l (sp)+,d4-d7/a2-a4
DOS _EXIT
<?php
// テスト本体を出力。
foreach ($testlist as $test) {
$dir = $test["mem_to_fp"] ? "to" : "from";
$pred = $test["is_pred"] ? "pred" : "ctrl";
$dyn = $test["dynamic"] ? "dyn" : "sta";
$hard = $test["is_easy"] ? "" : " (hard)";
out("; {$dir},{$pred},{$dyn},EA={$test["eatype"]}{$hard}");
out("test_%d:", $test["num"]);
// msg_test_* は先頭にテスト番号 "nn:ctrl;" が8バイトある。
// hard モードでは表示したいが easy モードでは表示したくない。
out(" moveq.l #-8,d0");
out(" and.w d5,d0");
out(" PRINT msg_test_{$test["num"]}+8(pc,d0.w)");
if ($test["is_trap"]) {
test_trap($test);
} else {
if ($test["mem_to_fp"]) {
test_to($test);
} else {
test_from($test);
}
}
}
?>
<?php
function test_trap($test)
{
// 実行
out_run($test, "");
// 例外が起きるはずなので D7 が非ゼロなら成功(次へ)
out(" tst.l d7");
out(" bne @f");
out(" PRINT msg_fail_notrap(pc)");
out(" rts");
out("@@:");
out(" PRINT msg_oktrap(pc)");
out(" rts");
out("");
}
function test_to($test)
{
$eatype = $test["eatype"];
// FPn を初期化。(こっちでは使わない書き込みバッファも初期化されるけど)
out(" bsr init_from");
// 実行。
out_run($test, "srcdata");
// 例外は起きないはずなので D7 がゼロなら成功(次へ)
out(" tst.l d7");
out(" beq @f");
out(" PRINT msg_fail_trap(pc)");
out(" rts");
out("@@:");
// An を照合する。
// (An) なら変化なし。
// -(An) なら実行前に進めてあるので +0 に戻る。
// (An)+ なら進む。
out(" lea.l srcdata%s(pc),a1", ($eatype == "+" ? "+24" : ""));
out(" cmp.l a1,a0");
out(" beq @f");
out(" pea.l msg_fail_addr(pc)");
out(" move.l a1,-(sp)");
out(" move.l a0,-(sp)");
out(" bsr result");
out(" lea.l 12(sp),sp");
out("@@:");
// FPn を比較。
// 8本のうち、どこかの2本が (昇順か降順かで) 書き換わってる。
// $s1 が srcdata+0 が転送されたレジスタ番号、
// $s2 が srcdata+12 が転送されたレジスタ番号。
list ($s1, $s2) = get_fplist($test);
// ただし src が -(An) なら(undocumented)、レジスタは書き換わらないようだ。
if ($eatype == "-") {
$s1 = -1;
$s2 = -1;
}
out(" lea.l workarea(pc),a2");
for ($i = 0; $i < 8; $i++) {
out(" ; compare FP${i}");
out(" pea.l msg_fail_fp{$i}(pc)");
if ($i == $s1) {
out(" pea.l srcdata(pc)");
} else if ($i == $s2) {
out(" pea.l srcdata+12(pc)");
} else {
out(" pea.l (initdata+%d*12)(pc)", $i);
}
out(" fmove.x fp{$i},(a2)");
out(" pea.l (a2)");
out(" bsr check_ext");
out(" lea.l 12(sp),sp");
}
out("");
out(" tst.l d7");
out(" bne @f");
out(" PRINT msg_ok(pc)");
out("@@:");
out(" rts");
out("");
}
function test_from($test)
{
$eatype = $test["eatype"];
// FPn と書き込みバッファを初期化。
out(" bsr init_from");
// 実行
out_run($test, "workarea");
// 例外は起きないはずなので D7 がゼロなら成功(次へ)
out(" tst.l d7");
out(" beq @f");
out(" PRINT msg_fail_trap(pc)");
out(" rts");
out("@@:");
// An を照合する。
// (An) なら変化なし。
// -(An) なら実行前に進めてあるので +0 に戻る。
// (An)+ なら進む。
out(" lea.l workarea%s(pc),a1", ($eatype == "+" ? "+24" : ""));
out(" cmp.l a1,a0");
out(" beq @f");
out(" pea.l msg_fail_addr(pc)");
out(" move.l a1,-(sp)");
out(" move.l a0,-(sp)");
out(" bsr result");
out(" lea.l 12(sp),sp");
out("@@:");
// workarea に書き出されたのを initdata と比較。
if ($eatype == "+") {
// dst が (An)+ なら(undocumented)、謎な書き込みが行われるようだ。
// 法則はありそうだが分からないのでテストしないことにする。
out(" PRINT msg_skip_dst(pc)");
} else {
// workarea の若いアドレスから順に比較するが、
// FMOVEM fpnlist,-(An) の時 (predec の時ではなく eatype='-' の時) は
// 転送順にメモリを下っていくので、比較も下りながら行わないといけない。
list ($s1, $s2) = get_fplist($test);
$work = 0;
if ($eatype == "-") {
$work = 1 - $work;
}
out(" ; compare first one");
out(" pea.l msg_fail_1st(pc)");
out(" pea.l (initdata+%d*12)(pc)", $s1);
out(" pea.l (%d*12)(a1)", $work);
out(" bsr check_ext");
out(" lea.l 12(sp),sp");
$work = 1 - $work;
out(" ; compare second one");
out(" pea.l msg_fail_2nd(pc)");
out(" pea.l (initdata+%d*12)(pc)", $s2);
out(" pea.l (%d*12)(a1)", $work);
out(" bsr check_ext");
out(" lea.l 12(sp),sp");
}
out("");
out(" tst.l d7");
out(" bne @f");
out(" PRINT msg_ok(pc)");
out("@@:");
out(" rts");
out("");
}
// 1行を出力する。引数は printf のように fmt... で指定可能。
// 改行はこちらで付加する。
function out()
{
$args = func_get_args();
$fmt = array_shift($args);
print vsprintf($fmt, $args);
print "\n";
}
// ターゲット実行部分を出力する。
function out_run($test, $label)
{
// EA ごとに命令前後にいろいろ必要。
// to/from どちらも EA (の先頭) が $label を指すようにする。
$pre = "";
$post = "";
if ($test["is_trap"] == false) {
switch ($test["eatype"]) {
case 'm':
case '+':
$pre = "lea.l {$label}(pc),a0";
break;
case '-':
$pre = "lea.l ({$label}+24)(pc),a0";
break;
case 'r': // d18(An)
// 賢いアセンブラだと 0(An) は (An) にしてしまうが、ここは
// 生の命令列を書き込んでいるので 0(An) は d16(An) 扱いになる。
$pre = "lea.l {$label}(pc),a0";
$post = ".dc.w 0";
break;
case 'x': // d8(An,IX) = 0(An,d4<0>)
$pre = "lea.l {$label}(pc),a0";
$post = ".dc.w $4000";
break;
case 'w': // Abs、ここでは Abs.L のみ
$post = ".dc.l {$label}";
break;
case 'p': // d8(PC,IX)
// これは難しいので保留。
break;
default:
break;
}
}
if (strlen($pre) > 0) {
out(" {$pre}");
}
out(" bsr set_fline");
out(" moveq.l #0,d7");
out(" lea.l @f,a3");
out(" .dc.l $%08x", $test["inst"]);
if (strlen($post) > 0) {
out(" {$post}");
}
out("@@:");
out(" bsr restore_fline");
}
// ターゲットレジスタを返す。
// 制御モードとプリデクリメントとでビットの割り当てが違う。
// スタティックと動的ではあえて違うビットを割り当てている。
function get_fplist($test)
{
$fplist = array(
0 /*pred, static */ => array(6, 5),
1 /*pred, dynamic */ => array(5, 4),
2 /*ctrl, static */ => array(1, 2),
3 /*ctrl, dynamic */ => array(2, 3),
);
$mode = $test["mode"];
return $fplist[$mode & 3];
}
// EA 種別文字から命令ワードに。n はすべて0
function eatype2word($eatype)
{
switch ($eatype) {
case 'd': return 000; // D0
case 'a': return 010; // A0
case 'm': return 020; // (A0)
case '+': return 030; // (A0)+
case '-': return 040; // -(A0)
case 'r': return 050; // d16(An)
case 'x': return 060; // d8(An,IX)
case 'w': return 071; // Abs.L
case 'p': return 072; // (PC,IX)
case 'i': return 074; // #imm
}
err("unknown eatype '{$eatype}'");
}
// EA 種別文字からニーモニックに。
function eatype2mnem($eatype, $n = 0)
{
switch ($eatype) {
case 'd': return "d{$n}";
case 'a': return "a{$n}";
case 'm': return "(a{$n})";
case '+': return "(a{$n})+";
case '-': return "-(a{$n})";
case 'r': return "d16(a{$n})";
case 'x': return "d8(a{$n},ix)";
case 'w': return "abs";
case 'p': return "(pc,ix)";
case 'i': return "#imm";
}
return "";
}
?>
; 空リストは正しく0個転送という扱いになる。
; たぶん dynamic でも同様だろうけどもうテストしない。
test_nofpn:
PRINT msg_test_nofpn(pc)
bsr init_from
; 実行
lea.l srcdata(pc),a0
bsr set_fline
moveq.l #0,d7
lea.l @f,a3
.dc.l $f218d000 ; fmovem.x (a0)+,nofpn
@@:
bsr restore_fline
; 例外は起きないはずなので D7 がゼロなら成功(次へ)
tst.l d7
beq @f
PRINT msg_fail_trap(pc)
rts
@@:
; An を照合する。(An)+ だけど変わらないはず。
lea.l srcdata(pc),a1
cmp.l a1,a0
beq @f
pea.l msg_fail_addr(pc)
move.l a1,-(sp)
move.l a0,-(sp)
bsr result
lea.l 12(sp),sp
@@:
lea.l workarea(pc),a2
<?php
// FPn を比較。
for ($i = 0; $i < 8; $i++) {
out(" ; compare FP${i}");
out(" pea.l msg_fail_fp{$i}(pc)");
out(" pea.l (initdata+%d*12)(pc)", $i);
out(" fmove.x fp{$i},(a2)");
out(" pea.l (a2)");
out(" bsr check_ext");
out(" lea.l 12(sp),sp");
}
?>
tst.l d7
bne @f
PRINT msg_ok(pc)
@@:
rts
; dynamic レジスタ指定の 0rrr_0000 の %0 のところに何か書かれてても無視。
test_illdyn:
PRINT msg_test_illdyn(pc)
bsr init_from
; 実行
lea.l srcdata(pc),a0
moveq.l #$80,d6
moveq.l #0,d7
lea.l @f,a3
.dc.l $f210d86f ; fmovem.x (a0),d6
@@:
bsr restore_fline
; 例外は起きないはずなので D7 がゼロなら成功(次へ)
tst.l d7
beq @f
PRINT msg_fail_trap(pc)
rts
@@:
; An を照合する。
lea.l srcdata(pc),a1
cmp.l a1,a0
beq @f
pea.l msg_fail_addr(pc)
move.l a1,-(sp)
move.l a0,-(sp)
bsr result
lea.l 12(sp),sp
@@:
lea.l workarea(pc),a2
<?php
// FPn を比較。
for ($i = 0; $i < 8; $i++) {
out(" ; compare FP${i}");
out(" pea.l msg_fail_fp{$i}(pc)");
if ($i == 0) {
out(" pea.l srcdata(pc)");
} else {
out(" pea.l (initdata+%d*12)(pc)", $i);
}
out(" fmove.x fp{$i},(a2)");
out(" pea.l (a2)");
out(" bsr check_ext");
out(" lea.l 12(sp),sp");
}
?>
tst.l d7
bne @f
PRINT msg_ok(pc)
@@:
rts
; test_from 用の初期化。
; FP0..7 を初期値で埋めるのと、書き込み先バッファを用意。
; a0/a1 は破壊する。
init_from:
lea.l initdata(pc),a0
<?php
for ($i = 0; $i < 8; $i++) {
out(" fmove.x (a0)+,fp{$i}");
}
?>
; 書き込み先を分かりやすいデータで初期化。
lea.l initdata_from(pc),a0
lea.l workarea(pc),a1
move.l (a0)+,(a1)+
move.l (a0)+,(a1)+
move.l (a0),(a1)+
subq.l #8,a0
move.l (a0)+,(a1)+
move.l (a0)+,(a1)+
move.l (a0),(a1)+
rts
; 結果表示
; スタックには、メッセージ、期待値、実際の値の順で積む。
result:
link a6,#0
movem.l d0/a0,-(sp)
move.l 8+8(a6),-(sp)
DOS _PRINT
addq.l #4,sp
move.l 8+4(a6),d0
lea.l buf(pc),a0
bsr hexstr_long
PRINT buf(pc)
PRINT msg_but(pc)
move.l 8+0(a6),d0
lea.l buf(pc),a0
bsr hexstr_long
PRINT buf(pc)
PRINT msg_crlf(pc)
addq.l #1,d7 ; errcnt
movem.l (sp)+,d0/a0
unlk a6
rts
; 拡張精度の比較と結果表示。
; 一致したら何もせず帰る。一致しなければエラーメッセージを表示。
; スタックには、(エラー時の)メッセージ、期待値、実際の値の順で積む
check_ext:
link a6,#0
movem.l d0/a0-a2,-(sp)
move.l 8+4(a6),a1
move.l 8+0(a6),a2
cmpm.l (a1)+,(a2)+
bne @f
cmpm.l (a1)+,(a2)+
bne @f
cmpm.l (a1)+,(a2)+
bne @f
; equal
bra check_ext_done
@@: ; not equal
; テスト名を表示した行に続けてこれを表示すると折り返してしまうので、
; これが1件目のエラーなら先に一つ改行する。
tst.l d7
bne @f
PRINT msg_crlf(pc)
@@:
move.l 8+8(a6),-(sp)
DOS _PRINT
addq.l #4,sp
lea.l buf(pc),a0
move.l 8+4(a6),a1
move.l (a1)+,d0
bsr hexstr_long
move.b #'_',(a0)+
move.l (a1)+,d0
bsr hexstr_long
move.b #'_',(a0)+
move.l (a1),d0
bsr hexstr_long
PRINT buf(pc)
PRINT msg_but(pc)
lea.l buf(pc),a0
move.l 8+0(a6),a2
move.l (a2)+,d0
bsr hexstr_long
move.b #'_',(a0)+
move.l (a2)+,d0
bsr hexstr_long
move.b #'_',(a0)+
move.l (a2),d0
bsr hexstr_long
PRINT buf(pc)
PRINT msg_crlf(pc)
addq.l #1,d7 ; errcnt
check_ext_done:
movem.l (sp)+,d0/a0-a2
unlk a6
rts
; Fライン例外をテスト用のものに差し替える。
; 変更前のアドレスを a4 に格納する。
; a1 は破壊する。
set_fline:
movem.l d0-d1,-(sp)
moveq.l #$b,d1
lea.l fline_handler(pc),a1
IOCS _B_INTVCS
movea.l d0,a4
movem.l (sp)+,d0-d1
rts
; Fライン例外を元に戻す。
; a1 は破壊する。
restore_fline:
movem.l d0-d1,-(sp)
moveq.l #$b,d1
movea.l a4,a1
IOCS _B_INTVCS
movem.l (sp)+,d0-d1
rts
; Fライン例外ハンドラ
; a3 に戻りアドレスをセットしてあること
; d7 を 1 にして帰る
fline_handler:
moveq.l #1,d7
move.l a3,2(sp)
rte
; FPn を埋める初期値
initdata:
.dc.l $00010000, $80888888, $88888888
.dc.l $00010000, $80111111, $11111111
.dc.l $00010000, $80222222, $22222222
.dc.l $00010000, $80333333, $33333333
.dc.l $00010000, $80444444, $44444444
.dc.l $00010000, $80555555, $55555555
.dc.l $00010000, $80666666, $66666666
.dc.l $00010000, $80777777, $77777777
; ターゲットの FPn に転送するデータ
srcdata:
.dc.l $3fff0000, $80aaaaaa, $aaaaaaaa
.dc.l $3fff0000, $80bbbbbb, $bbbbbbbb
; 書き込み先の初期化用データ
initdata_from:
.dc.l $90919293, $94959697, $98999a9b
msg_hard: .dc.b "hard mode",$d,$a,0
msg_easy: .dc.b "easy mode",$d,$a,0
msg_oktrap: .dc.b $9,"ok(trap)",$d,$a,0, 0,0
msg_fail_notrap: .dc.b $9,"FAIL: trap expected but not occured",$d,$a,0
msg_fail_trap: .dc.b $9,"FAIL: unexpected trap occured",$d,$a,0
msg_skip_dst: .dc.b $9,"SKIP(dstmem)",0
msg_ok: .dc.b $9,"ok",$d,$a,0
msg_fail_fp0: .dc.b $9,"FAIL: fp0 expects ",0
msg_fail_fp1: .dc.b $9,"FAIL: fp1 expects ",0
msg_fail_fp2: .dc.b $9,"FAIL: fp2 expects ",0
msg_fail_fp3: .dc.b $9,"FAIL: fp3 expects ",0
msg_fail_fp4: .dc.b $9,"FAIL: fp4 expects ",0
msg_fail_fp5: .dc.b $9,"FAIL: fp5 expects ",0
msg_fail_fp6: .dc.b $9,"FAIL: fp6 expects ",0
msg_fail_fp7: .dc.b $9,"FAIL: fp7 expects ",0
msg_fail_1st: .dc.b $9,"FAIL: 1st expects ",0
msg_fail_2nd: .dc.b $9,"FAIL: 2nd expects ",0
msg_fail_addr: .dc.b $9,"FAIL: an expects ",0
msg_but: .dc.b " but ",0
msg_crlf: .dc.b $d,$a,0
<?php
foreach ($testlist as $test) {
$name = sprintf("%02d:", $test["num"]);
if ($test["is_pred"]) {
$name .= "pred;";
} else {
$name .= "ctrl;";
}
$name .= "fmovem.x ";
$m = eatype2mnem($test["eatype"]);
$f = get_fplist($test);
sort($f);
if ($test["dynamic"]) {
$r = "d6<fp{$f[0]}-fp{$f[1]}>";
} else {
$r = "fp{$f[0]}-fp{$f[1]}";
}
if ($test["mem_to_fp"]) {
$name .= "{$m},{$r}";
} else {
$name .= "{$r},{$m}";
}
out("msg_test_{$test["num"]}: .dc.b \"{$name}\",0");
}
?>
msg_test_nofpn: .dc.b "fmovem.x (a0)+,nofpn",0
msg_test_illdyn:.dc.b "fmovem.x (a0),d6<FP0>_bit",0
.data
.even
workarea: .ds.b 24
buf: .ds.b 64
.end start
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.