|
|
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 fmovemc.has
; > hlk fmovemc.o si_util.o
.xref hexstr_long
.include doscall.mac
.include iocscall.mac
.list
.cpu 68030
PRINT .macro msg
pea.l msg(pc)
DOS _PRINT
addq.l #4,sp
.endm
.text
.even
start:
bra start2
<?php
// レジスタ割り当て
// d0/d1 work
// d7 1=検査対象命令でFライン例外がおきた
// その後は ok を表示するかどうかのカウント
// a0/a1 work
// a3 検査対象命令の次の位置 (Fライン例外から戻るのに使用)
// a4 元のFライン例外ハンドラ置き場
?>
<?php
// regs がターゲットのFPcr。
// C..FPCR
// S..FPSR
// I..FPIAR の3ビット。
// 右辺がそのターゲットの時に使える EA。レジスタ相対は省略でいい?
// d..Dn
// a..An
// m..(An)
// +..(An)+
// -..-(An)
// i..#imm
// FMOVEM EA,regs
$table = array(
//regs EA
"___" => "dam+-i",
"__I" => "dam+-i",
"_S_" => "d.m+-i",
"_SI" => "..m+-i",
"C__" => "d.m+-i",
"C_I" => "..m+-i",
"CS_" => "..m+-i",
"CSI" => "..m+-i",
);
foreach ($table as $regs => $eastr) {
$ealist = preg_split("//", $eastr, -1, PREG_SPLIT_NO_EMPTY);
for ($id = 0; $id < 6; $id++) {
$ea = $eastr[$id];
if (0) printf("regs='{$regs}' id=${id} ea={$ea}\n");
if ($ea == ".") {
// 許されない組み合わせ。例外が出ることを確認
subr_fail("to", $regs, $id);
} else {
// アクセス可能な場合のテスト
subr_success_to($regs, $id);
}
}
}
// FMOVEM regs,EA
foreach ($table as $regs => $eastr_src) {
// EA が dst になる時は #imm は無効
$eastr = preg_replace("/i/", ".", $eastr_src);
$ealist = preg_split("//", $eastr, -1, PREG_SPLIT_NO_EMPTY);
for ($id = 0; $id < 6; $id++) {
$ea = $eastr[$id];
if (0) printf("regs='{$regs}' id=${id} ea={$ea}\n");
if ($ea == ".") {
// 許されない組み合わせ。例外が出ることを確認
subr_fail("from", $regs, $id);
} else {
// アクセス可能な場合のテスト
subr_success_from($regs, $id);
}
}
}
?>
<?php
// 1行を出力する。引数は printf のように fmt... で指定可能。
// 改行はこちらで付加する。
function out()
{
$args = func_get_args();
$fmt = array_shift($args);
print vsprintf($fmt, $args);
print "\n";
}
// regs の省略記号を数値形式に
function reg2num($str)
{
$n = 0;
if (preg_match("/I/", $str))
$n += 1;
if (preg_match("/S/", $str))
$n += 2;
if (preg_match("/C/", $str))
$n += 4;
return $n;
}
// regs 数値形式をニーモニックに
function regnum2mnemonic($regnum)
{
if ($regnum == 0)
return "nofpc";
$list = array();
if ($regnum & 4)
$list[] = "fpcr";
if ($regnum & 2)
$list[] = "fpsr";
if ($regnum & 1)
$list[] = "fpiar";
return implode("/", $list);
}
// regs 数値形式から転送レジスタ数に
function regnum2count($regnum)
{
$counts = array(0, 1, 1, 2, 1, 2, 2, 3);
return $counts[$regnum];
}
// regs 数値形式からマスクを返す。
// regs は1ビットだけ立っていること。
// %100=FPCR, %010=FPSR, %001=FPIAR
function regmask($regnum)
{
$masks = array(
/*0*/ 0,
/*1*/ 0xffffffff, // FPIAR
/*2*/ 0x0ffffff8, // FPSR
/*3*/ 0,
/*4*/ 0x0000fff0, // FPCR
);
return $masks[$regnum];
}
// EA 番号から命令ワードに。n はすべて0
function eanum2word($id)
{
switch ($id) {
case 0: // D0
return 0 << 3;
case 1: // A0
return 1 << 3;
case 2: // (A0)
return 2 << 3;
case 3: // (A0)+
return 3 << 3;
case 4: // -(An)
return 4 << 3;
case 5: // #imm
return 0x3c;
}
}
// EA 番号をラベル文字列に。
function eanum2label($id)
{
$idstr = array(
0 => "dn",
1 => "an",
2 => "anin",
3 => "anpi",
4 => "anpd",
5 => "imm",
);
return $idstr[$id];
}
// EA 番号をニーモニックに。
function eanum2mnem($id, $n = 0)
{
switch ($id) {
case 0: return "d{$n}";
case 1: return "a{$n}";
case 2: return "(a{$n})";
case 3: return "(a{$n})+";
case 4: return "-(a{$n})";
case 5: return "#imm";
}
return "";
}
function addfunc($name, $dir, $mnem, $cpureg)
{
global $funclist;
if ($cpureg == "#imm") {
// $cpureg の #imm は常に1つだが
// ニーモニック表記では対向と数を合わせるか。
$cpureg = preg_replace(',([^/]+),', "#imm", $mnem);
}
$text = "fmovem.l ";
if ($dir == "to") {
$text .= "{$cpureg},{$mnem}";
} else {
$text .= "{$mnem},{$cpureg}";
}
$funclist[$name] = $text;
out("test_{$name}:");
}
// 結果表示のサブルーチンコールを出力
function out_result($msg, $exp, $act)
{
out(" pea.l {$msg}(pc)");
out(" move.l {$exp},-(sp)");
out(" move.l {$act},-(sp)");
out(" bsr result");
out(" lea.l 12(sp),sp");
}
// 許されない組み合わせのテスト。hard の時だけ検査する。
// Fライン例外が起きることをチェックする。
function subr_fail($dir, $regs, $id)
{
$cpureg = eanum2mnem($id);
$regnum = reg2num($regs);
$mnem = regnum2mnemonic($regnum);
$label = eanum2label($id);
$funcname = ($dir == "to")
? "fmovem_{$label}_to_{$regs}"
: "fmovem_{$regs}_to_{$label}";
addfunc($funcname, $dir, $mnem, $cpureg);
$w1 = 0xf200 | eanum2word($id);
$w2 = 0x8000 | ($regnum << 10);
if ($dir == "from")
$w2 |= 0x2000;
out(" tst.b is_hard(pc)");
out(" bne @f");
out(" rts");
out("@@:");
out(" PRINT msg_{$funcname}");
out(" bsr set_fline");
out(" moveq.l #0,d7");
out(" lea.l @f,a3");
// アセンブルできない可能性があるので命令はバイナリを出力。
out(" .dc.w $%04x, $%04x", $w1, $w2);
out("@@:");
out(" bsr restore_fline");
// 例外が起きるはずなので D7 が非ゼロなら成功(次へ)
out(" tst.l d7");
out(" bne @f");
out(" PRINT msg_fail_notrap");
out(" rts");
out("@@:");
out(" PRINT msg_oktrap");
out(" rts");
out("");
}
// アクセス可能な場合のテスト。FMOVEM <ea>,<FPcr>
function subr_success_to($regs, $id)
{
$cpureg = eanum2mnem($id);
$regnum = reg2num($regs);
$mnem = regnum2mnemonic($regnum);
$label = eanum2label($id);
$funcname = "fmovem_{$label}_to_{$regs}";
addfunc($funcname, "to", $mnem, $cpureg);
out(" PRINT msg_{$funcname}");
$w1 = 0xf200 | eanum2word($id);
$w2 = 0x8000 | ($regnum << 10);
// count は指定されたレジスタ数 (0..3)
// txcount は転送するレジスタ数 (1..3)
$count = regnum2count($regnum);
$txcount = ($count == 0) ? 1 : $count;
// FPcr を空にしておく
out(" fmove.l #0,fpcr");
out(" fmove.l #0,fpsr");
out(" fmove.l #0,fpiar");
// 準備
switch ($id) {
case 0: // Dn (singleのみ)
out(" move.l #\$55555555,d0");
break;
case 1: // An (singleのみ)
out(" move.l #\$55555555,a0");
break;
case 2: // (An)
case 3: // (An)+
case 4: // -(An)
out(" move.l #\$55555555,workarea");
if ($count >= 2)
out(" move.l #\$aaaaaaaa,workarea+4");
if ($count == 3)
out(" move.l #\$33333333,workarea+8");
if ($id == 4) {
// 後ろ基準
// nofpc でも転送は発生する
out(" lea.l workarea+%d(pc),a0", $txcount * 4);
} else {
// 前から
out(" lea.l workarea(pc),a0");
}
break;
case 5: // #imm
$cpureg = "#\$55555555";
if ($count >= 2)
$cpureg .= ",#\$aaaaaaaa";
if ($count == 3)
$cpureg .= ",#\$33333333";
break;
}
out(" bsr set_fline");
out(" moveq.l #0,d7");
out(" lea.l @f,a3");
// 実行
if ($mnem == "nofpc") {
out(" .dc.w $%04x, $%04x", $w1, $w2);
// #imm,nofpc はアセンブル出来ないのでここで即値をおく
if ($id == 5/*imm*/ && $count == 0) {
out(" .dc.l \$55555555");
}
} else {
out(" fmovem.l {$cpureg},{$mnem}");
}
out("@@:");
out(" bsr restore_fline");
// 例外は起きないはずなので D7 はゼロなら成功(次へ)
out(" tst.l d7");
out(" beq @f");
out(" PRINT msg_fail_trap");
out(" rts");
out("@@:");
// 検証
$expected = array(
0x55555555,
0xaaaaaaaa,
0x33333333,
);
$i = 0;
// An の変化量が正しいか。
// この後のレジスタの検証で A0 を使うのでその前に行う。
// (An) なら workarea から変化しない
// (An)+ なら workarea+count*4
// -(An) なら workarea に戻ってくる (最初にオフセットつけてるので)
switch ($id) {
case 2: // (An)
case 4: // -(An)
out(" lea.l workarea(pc),a1");
out(" cmpa.l a1,a0");
out(" beq @f");
out_result("msg_fail_addr", "a1", "a0");
out("@@:");
break;
case 3: // (An)+
out(" lea.l workarea+%d(pc),a1", $txcount * 4);
out(" cmpa.l a1,a0");
out(" beq @f");
out_result("msg_fail_addr", "a1", "a0");
out("@@:");
break;
}
// FPCR
out(" fmove.l fpcr,d0");
if (($regnum & 4)) {
out(" move.l #$%08x,d1", $expected[$i] & regmask(4));
$i++;
} else {
out(" moveq.l #0,d1");
}
out(" cmp.l d1,d0");
out(" beq @f");
out_result("msg_fail_cr", "d1", "d0");
out("@@:");
// FPSR
out(" fmove.l fpsr,d0");
if (($regnum & 2)) {
out(" move.l #$%08x,d1", $expected[$i] & regmask(2));
$i++;
} else {
out(" moveq.l #0,d1");
}
out(" cmp.l d1,d0");
out(" beq @f");
out_result("msg_fail_sr", "d1", "d0");
out("@@:");
// FPIAR
out(" fmove.l fpiar,a0");
// fmovem *,nofpc は FPIAR が受け取ってしまうようだ
if (($regnum & 1) || $regnum == 0) {
out(" move.l #$%08x,a1", $expected[$i] & regmask(1));
$i++;
} else {
out(" suba.l a1,a1");
}
out(" cmpa.l a1,a0");
out(" beq @f");
out_result("msg_fail_ir", "a1", "a0");
out("@@:");
out(" tst.l d7");
out(" bne @f");
out(" PRINT msg_ok");
out("@@:");
out(" rts");
out("");
}
// アクセス可能な場合のテスト。FMOVEM <FPcr>,<ea>
function subr_success_from($regs, $id)
{
$cpureg = eanum2mnem($id);
$regnum = reg2num($regs);
$mnem = regnum2mnemonic($regnum);
$label = eanum2label($id);
$funcname = "fmovem_{$regs}_to_{$label}";
addfunc($funcname, "from", $mnem, $cpureg);
out(" PRINT msg_{$funcname}");
$w1 = 0xf200 | eanum2word($id);
$w2 = 0xa000 | ($regnum << 10);
// count は指定されたレジスタ数 (0..3)
// txcount は転送するレジスタ数 (1..3)
$count = regnum2count($regnum);
$txcount = ($count == 0) ? 1 : $count;
// FPcr に初期値をセット
// nofpc の時は FPIAR
$expected = array(
0x55555555,
0xaaaaaaaa,
0x33333333,
);
$i = 0;
out(" fmove.l #$%08x,fpcr", ($regnum & 4) ? $expected[$i++] : 0);
out(" fmove.l #$%08x,fpsr", ($regnum & 2) ? $expected[$i++] : 0);
out(" fmove.l #$%08x,fpiar",
(($regnum & 1) || $count == 0) ? $expected[$i++] : 0);
// 準備
switch ($id) {
case 2: // (An)
case 3: // (An)+
out(" lea.l workarea(pc),a0");
break;
case 4: // -(An)
// nofpc でも転送は発生する
out(" lea.l workarea+%d(pc),a0", $txcount * 4);
break;
case 5: // #imm は dst にならない
break;
}
out(" bsr set_fline");
out(" moveq.l #0,d7");
out(" lea.l @f,a3");
// 実行
if ($mnem == "nofpc")
out(" .dc.w $%04x, $%04x", $w1, $w2);
else
out(" fmovem.l {$mnem},{$cpureg}");
out("@@:");
out(" bsr restore_fline");
// 例外は起きないはずなので D7 はゼロなら成功(次へ)
out(" tst.l d7");
out(" beq @f");
out(" PRINT msg_fail_trap");
out(" rts");
out("@@:");
// 検証
// An の変化量が正しいか
// (An) なら workarea から変化しない
// (An)+ なら workarea+count*4
// -(An) なら workarea に戻ってくる (最初にオフセットつけてるので)
switch ($id) {
case 2: // (An)
case 4: // -(An)
out(" lea.l workarea(pc),a1");
out(" cmpa.l a1,a0");
out(" beq @f");
out_result("msg_fail_addr", "a1", "a0");
out("@@:");
break;
case 3: // (An)+
out(" lea.l workarea+%d(pc),a1", $txcount * 4);
out(" cmpa.l a1,a0");
out(" beq @f");
out_result("msg_fail_addr", "a1", "a0");
out("@@:");
break;
}
$i = 0;
switch ($id) {
case 0: // Dn
for ($r = 4; $r >= 1; $r >>= 1) {
// 各 fpn の該当する時か、nofpc なら FPIAR
if (($regnum & $r) || ($r == 1 && $count == 0)) {
out(" move.l #$%08x,d1", $expected[$i] & regmask($r));
$i++;
out(" cmp.l d1,d0");
out(" beq @f");
out_result("msg_fail_dst", "d1", "d0");
out("@@:");
}
}
break;
case 1: // An
for ($r = 4; $r >= 1; $r >>= 1) {
// 各 fpn の該当する時か、nofpc なら FPIAR
if (($regnum & $r) || ($r == 1 && $count == 0)) {
out(" movea.l #$%08x,a1", $expected[$i] & regmask($r));
$i++;
out(" cmp.l a1,a0");
out(" beq @f");
out_result("msg_fail_dst", "a1", "a0");
out("@@:");
}
}
break;
case 2: // (An)
case 3: // (An)+
case 4: // -(An)
for ($r = 4; $r >= 1; $r >>= 1) {
// 各 fpn の該当する時か、nofpc なら FPIAR
if (($regnum & $r) || ($r == 1 && $count == 0)) {
out(" move.l #$%08x,d1", $expected[$i] & regmask($r));
out(" cmp.l workarea+%d,d1", $i * 4);
$i++;
out(" beq @f");
out_result("msg_fail_dst",
"d1", sprintf("workarea+%d", $i * 4));
out("@@:");
}
}
break;
}
out(" tst.l d7");
out(" bne @f");
out(" PRINT msg_ok");
out("@@:");
out(" rts");
out("");
}
?>
start2:
clr.l -(sp)
DOS _SUPER
movem.l d7/a3-a4,-(sp)
; rough argument check
cmpi.b #4,(a2)+ ; strlen(arg)>=4 ?
bcs @f
cmpi.l #$68617264,(a2) ; "hard"
seq.b is_hard
@@:
tst.b is_hard(pc)
beq disp_easy
disp_hard:
PRINT msg_hard
bra @f
disp_easy:
PRINT msg_easy
@@:
<?php
foreach ($funclist as $name => $text) {
// FPcr が1つもないケースは hard の時だけ実行。
$undef_case = preg_match("/____/", $name);
if ($undef_case) {
out(" tst.b is_hard(pc)");
out(" beq @f");
}
out(" bsr test_{$name}");
if ($undef_case) {
out("@@:");
}
}
?>
movem.l (sp)+,d7/a3-a4
DOS _EXIT
; 結果表示
; スタックには、メッセージ、期待値、実際の値の順で積む。
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
PRINT msg_but
move.l 8+0(a6),d0
lea.l buf(pc),a0
bsr hexstr_long
PRINT buf
PRINT msg_crlf
addq.l #1,d7 ; errcnt
movem.l (sp)+,d0/a0
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
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_ok: .dc.b $9,"ok",$d,$a,0
msg_fail_cr: .dc.b $9,"FAIL: fpcr expects ",0
msg_fail_sr: .dc.b $9,"FAIL: fpsr expects ",0
msg_fail_ir: .dc.b $9,"FAIL: fpiar expects ",0
msg_fail_addr: .dc.b $9,"FAIL: an expects ",0
msg_fail_dst: .dc.b $9,"FAIL: dst expects ",0
msg_but: .dc.b " but ",0
msg_crlf: .dc.b $d,$a,0
<?php
foreach ($funclist as $name => $text) {
out("msg_{$name}: .dc.b \"{$text}\",0");
}
?>
.data
.even
workarea: .ds.b 24
buf: .ds.b 20
is_hard: .ds.b 1
.end start
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.