|
|
1.1 ! root 1: ; ! 2: ; nono ! 3: ; Copyright (C) 2019 [email protected] ! 4: ; ! 5: ! 6: ; How to compile: ! 7: ; > has fmovem.has ! 8: ; > hlk fmovem.o si_util.o ! 9: ! 10: .xref hexstr_long ! 11: .include doscall.mac ! 12: .list ! 13: .cpu 68030 ! 14: ! 15: PRINT .macro msg ! 16: pea.l msg(pc) ! 17: DOS _PRINT ! 18: addq.l #4,sp ! 19: .endm ! 20: ! 21: SET_FLINE_TRAP .macro ! 22: move.l (a5),a4 ! 23: lea.l fline_handler(pc),a1 ! 24: move.l a1,(a5) ! 25: .endm ! 26: ! 27: RESTORE_FLINE_TRAP .macro ! 28: move.l a4,(a5) ! 29: .endm ! 30: ! 31: .text ! 32: .even ! 33: <?php ! 34: // レジスタ割り当て ! 35: // d0/d1 work ! 36: // d7 1=検査対象命令でFライン例外がおきた ! 37: // その後は ok を表示するかどうかのカウント ! 38: // a0/a1 work ! 39: // a3 検査対象命令の次の位置 (Fライン例外から戻るのに使用) ! 40: // a4 元のFライン例外ハンドラ置き場 ! 41: // a5 Fライン例外ベクタの位置 $002c ! 42: ?> ! 43: <?php ! 44: // regs がターゲットのFPcr。 ! 45: // C..FPCR ! 46: // S..FPSR ! 47: // I..FPIAR の3ビット。 ! 48: // 右辺がそのターゲットの時に使える EA。レジスタ相対は省略でいい? ! 49: // d..Dn ! 50: // a..An ! 51: // m..(An) ! 52: // +..(An)+ ! 53: // -..-(An) ! 54: // i..#imm ! 55: ! 56: // FMOVEM EA,regs ! 57: $table = array( ! 58: //regs EA ! 59: "___" => "dam+-i", ! 60: "__I" => "dam+-i", ! 61: "_S_" => "d.m+-i", ! 62: "_SI" => "..m+-i", ! 63: "C__" => "d.m+-i", ! 64: "C_I" => "..m+-i", ! 65: "CS_" => "..m+-i", ! 66: "CSI" => "..m+-i", ! 67: ); ! 68: foreach ($table as $regs => $eastr) { ! 69: $ealist = preg_split("//", $eastr, -1, PREG_SPLIT_NO_EMPTY); ! 70: for ($id = 0; $id < 6; $id++) { ! 71: $ea = $eastr[$id]; ! 72: if (0) printf("regs='{$regs}' id=${id} ea={$ea}\n"); ! 73: ! 74: if ($ea == ".") { ! 75: // 許されない組み合わせ。例外が出ることを確認 ! 76: subr_fail("to", $regs, $id); ! 77: } else { ! 78: // アクセス可能な場合のテスト ! 79: subr_success_to($regs, $id); ! 80: } ! 81: } ! 82: } ! 83: ! 84: // FMOVEM regs,EA ! 85: foreach ($table as $regs => $eastr_src) { ! 86: // EA が dst になる時は #imm は無効 ! 87: $eastr = preg_replace("/i/", ".", $eastr_src); ! 88: ! 89: $ealist = preg_split("//", $eastr, -1, PREG_SPLIT_NO_EMPTY); ! 90: for ($id = 0; $id < 6; $id++) { ! 91: $ea = $eastr[$id]; ! 92: if (0) printf("regs='{$regs}' id=${id} ea={$ea}\n"); ! 93: ! 94: if ($ea == ".") { ! 95: // 許されない組み合わせ。例外が出ることを確認 ! 96: subr_fail("from", $regs, $id); ! 97: } else { ! 98: // アクセス可能な場合のテスト ! 99: subr_success_from($regs, $id); ! 100: } ! 101: } ! 102: } ! 103: ?> ! 104: <?php ! 105: // 1行を出力する。引数は printf のように fmt... で指定可能。 ! 106: // 改行はこちらで付加する。 ! 107: function out() ! 108: { ! 109: $args = func_get_args(); ! 110: $fmt = array_shift($args); ! 111: print vsprintf($fmt, $args); ! 112: print "\n"; ! 113: } ! 114: ! 115: // regs の省略記号を数値形式に ! 116: function reg2num($str) ! 117: { ! 118: $n = 0; ! 119: if (preg_match("/I/", $str)) ! 120: $n += 1; ! 121: if (preg_match("/S/", $str)) ! 122: $n += 2; ! 123: if (preg_match("/C/", $str)) ! 124: $n += 4; ! 125: return $n; ! 126: } ! 127: ! 128: // regs 数値形式をニーモニックに ! 129: function regnum2mnemonic($regnum) ! 130: { ! 131: if ($regnum == 0) ! 132: return "nofpc"; ! 133: ! 134: $list = array(); ! 135: if ($regnum & 4) ! 136: $list[] = "fpcr"; ! 137: if ($regnum & 2) ! 138: $list[] = "fpsr"; ! 139: if ($regnum & 1) ! 140: $list[] = "fpiar"; ! 141: return join($list, "/"); ! 142: } ! 143: ! 144: // regs 数値形式から転送レジスタ数に ! 145: function regnum2count($regnum) ! 146: { ! 147: $counts = array(0, 1, 1, 2, 1, 2, 2, 3); ! 148: return $counts[$regnum]; ! 149: } ! 150: ! 151: // regs 数値形式からマスクを返す。 ! 152: // regs は1ビットだけ立っていること。 ! 153: // %100=FPCR, %010=FPSR, %001=FPIAR ! 154: function regmask($regnum) ! 155: { ! 156: $masks = array( ! 157: /*0*/ 0, ! 158: /*1*/ 0xffffffff, // FPIAR ! 159: /*2*/ 0x0ffffff8, // FPSR ! 160: /*3*/ 0, ! 161: /*4*/ 0x0000fff0, // FPCR ! 162: ); ! 163: return $masks[$regnum]; ! 164: } ! 165: ! 166: // EA 番号から命令ワードに。n はすべて0 ! 167: function eanum2word($id) ! 168: { ! 169: switch ($id) { ! 170: case 0: // D0 ! 171: return 0 << 3; ! 172: case 1: // A0 ! 173: return 1 << 3; ! 174: case 2: // (A0) ! 175: return 2 << 3; ! 176: case 3: // (A0)+ ! 177: return 3 << 3; ! 178: case 4: // -(An) ! 179: return 4 << 3; ! 180: case 5: // #imm ! 181: return 0x3c; ! 182: } ! 183: } ! 184: ! 185: // EA 番号をラベル文字列に。 ! 186: function eanum2label($id) ! 187: { ! 188: $idstr = array( ! 189: 0 => "dn", ! 190: 1 => "an", ! 191: 2 => "anin", ! 192: 3 => "anpi", ! 193: 4 => "anpd", ! 194: 5 => "imm", ! 195: ); ! 196: return $idstr[$id]; ! 197: } ! 198: ! 199: // EA 番号をニーモニックに。 ! 200: function eanum2mnem($id, $n = 0) ! 201: { ! 202: switch ($id) { ! 203: case 0: return "d{$n}"; ! 204: case 1: return "a{$n}"; ! 205: case 2: return "(a{$n})"; ! 206: case 3: return "(a{$n})+"; ! 207: case 4: return "-(a{$n})"; ! 208: } ! 209: } ! 210: ! 211: function addfunc($name) ! 212: { ! 213: global $funclist; ! 214: ! 215: $funclist[] = $name; ! 216: out("test_{$name}:"); ! 217: out(" PRINT msg_{$name}"); ! 218: } ! 219: ! 220: // 結果表示のサブルーチンコールを出力 ! 221: function out_result($msg, $exp, $act) ! 222: { ! 223: out(" pea.l {$msg}(pc)"); ! 224: out(" move.l {$exp},-(sp)"); ! 225: out(" move.l {$act},-(sp)"); ! 226: out(" bsr result"); ! 227: out(" lea.l 12(sp),sp"); ! 228: } ! 229: ! 230: // 許されない組み合わせのテスト。 ! 231: // Fライン例外が起きることをチェックする。 ! 232: function subr_fail($dir, $regs, $id) ! 233: { ! 234: $regnum = reg2num($regs); ! 235: $mnem = regnum2mnemonic($regnum); ! 236: $label = eanum2label($id); ! 237: ! 238: $funcname = ($dir == "to") ! 239: ? "fmovem_{$label}_to_{$regs}" ! 240: : "fmovem_{$regs}_to_{$label}"; ! 241: addfunc($funcname); ! 242: ! 243: $w1 = 0xf200 | eanum2word($id); ! 244: $w2 = 0x8000 | ($regnum << 10); ! 245: if ($dir == "from") ! 246: $w2 |= 0x2000; ! 247: ! 248: out(" SET_FLINE_TRAP"); ! 249: out(" moveq.l #0,d7"); ! 250: out(" lea.l @f,a3"); ! 251: // アセンブルできない可能性があるので命令はバイナリを出力。 ! 252: out(" .dc.w $%04x, $%04x", $w1, $w2); ! 253: out("@@:"); ! 254: ! 255: out(" RESTORE_FLINE_TRAP"); ! 256: // 例外が起きるはずなので D7 が非ゼロなら成功(次へ) ! 257: out(" tst.l d7"); ! 258: out(" bne @f"); ! 259: out(" PRINT msg_fail_notrap"); ! 260: out(" rts"); ! 261: out("@@:"); ! 262: out(" PRINT msg_oktrap"); ! 263: out(" rts"); ! 264: out(""); ! 265: } ! 266: ! 267: // アクセス可能な場合のテスト。FMOVEM <ea>,<FPcr> ! 268: function subr_success_to($regs, $id) ! 269: { ! 270: $regnum = reg2num($regs); ! 271: $mnem = regnum2mnemonic($regnum); ! 272: $label = eanum2label($id); ! 273: ! 274: $funcname = "fmovem_{$label}_to_{$regs}"; ! 275: addfunc($funcname); ! 276: ! 277: $w1 = 0xf200 | eanum2word($id); ! 278: $w2 = 0x8000 | ($regnum << 10); ! 279: $cpureg = eanum2mnem($id); ! 280: // count は指定されたレジスタ数 (0..3) ! 281: // txcount は転送するレジスタ数 (1..3) ! 282: $count = regnum2count($regnum); ! 283: $txcount = ($count == 0) ? 1 : $count; ! 284: ! 285: // FPcr を空にしておく ! 286: out(" fmove.l #0,fpcr"); ! 287: out(" fmove.l #0,fpsr"); ! 288: out(" fmove.l #0,fpiar"); ! 289: // 準備 ! 290: switch ($id) { ! 291: case 0: // Dn (singleのみ) ! 292: out(" move.l #\$55555555,d0"); ! 293: break; ! 294: case 1: // An (singleのみ) ! 295: out(" move.l #\$55555555,a0"); ! 296: break; ! 297: case 2: // (An) ! 298: case 3: // (An)+ ! 299: case 4: // -(An) ! 300: out(" move.l #\$55555555,workarea"); ! 301: if ($count >= 2) ! 302: out(" move.l #\$aaaaaaaa,workarea+4"); ! 303: if ($count == 3) ! 304: out(" move.l #\$33333333,workarea+8"); ! 305: ! 306: if ($id == 4) { ! 307: // 後ろ基準 ! 308: // nofpc でも転送は発生する ! 309: out(" lea.l workarea+%d(pc),a0", $txcount * 4); ! 310: } else { ! 311: // 前から ! 312: out(" lea.l workarea(pc),a0"); ! 313: } ! 314: break; ! 315: case 5: // #imm ! 316: $cpureg = "#\$55555555"; ! 317: if ($count >= 2) ! 318: $cpureg .= ",#\$aaaaaaaa"; ! 319: if ($count == 3) ! 320: $cpureg .= ",#\$33333333"; ! 321: break; ! 322: } ! 323: ! 324: out(" SET_FLINE_TRAP"); ! 325: out(" moveq.l #0,d7"); ! 326: out(" lea.l @f,a3"); ! 327: // 実行 ! 328: if ($mnem == "nofpc") { ! 329: out(" .dc.w $%04x, $%04x", $w1, $w2); ! 330: // #imm,nofpc はアセンブル出来ないのでここで即値をおく ! 331: if ($id == 5/*imm*/ && $count == 0) { ! 332: out(" .dc.l \$55555555"); ! 333: } ! 334: } else { ! 335: out(" fmovem.l {$cpureg},{$mnem}"); ! 336: } ! 337: out("@@:"); ! 338: ! 339: out(" RESTORE_FLINE_TRAP"); ! 340: // 例外は起きないはずなので D7 はゼロなら成功(次へ) ! 341: out(" tst.l d7"); ! 342: out(" beq @f"); ! 343: out(" PRINT msg_fail_trap"); ! 344: out(" rts"); ! 345: out("@@:"); ! 346: ! 347: // 検証 ! 348: $expected = array( ! 349: 0x55555555, ! 350: 0xaaaaaaaa, ! 351: 0x33333333, ! 352: ); ! 353: $i = 0; ! 354: ! 355: // An の変化量が正しいか。 ! 356: // この後のレジスタの検証で A0 を使うのでその前に行う。 ! 357: // (An) なら workarea から変化しない ! 358: // (An)+ なら workarea+count*4 ! 359: // -(An) なら workarea に戻ってくる (最初にオフセットつけてるので) ! 360: switch ($id) { ! 361: case 2: // (An) ! 362: case 4: // -(An) ! 363: out(" lea.l workarea(pc),a1"); ! 364: out(" cmpa.l a1,a0"); ! 365: out(" beq @f"); ! 366: out_result("msg_fail_addr", "a1", "a0"); ! 367: out("@@:"); ! 368: break; ! 369: case 3: // (An)+ ! 370: out(" lea.l workarea+%d(pc),a1", $txcount * 4); ! 371: out(" cmpa.l a1,a0"); ! 372: out(" beq @f"); ! 373: out_result("msg_fail_addr", "a1", "a0"); ! 374: out("@@:"); ! 375: break; ! 376: } ! 377: ! 378: // FPCR ! 379: out(" fmove.l fpcr,d0"); ! 380: if (($regnum & 4)) { ! 381: out(" move.l #$%08x,d1", $expected[$i] & regmask(4)); ! 382: $i++; ! 383: } else { ! 384: out(" moveq.l #0,d1"); ! 385: } ! 386: out(" cmp.l d1,d0"); ! 387: out(" beq @f"); ! 388: out_result("msg_fail_cr", "d1", "d0"); ! 389: out("@@:"); ! 390: ! 391: // FPSR ! 392: out(" fmove.l fpsr,d0"); ! 393: if (($regnum & 2)) { ! 394: out(" move.l #$%08x,d1", $expected[$i] & regmask(2)); ! 395: $i++; ! 396: } else { ! 397: out(" moveq.l #0,d1"); ! 398: } ! 399: out(" cmp.l d1,d0"); ! 400: out(" beq @f"); ! 401: out_result("msg_fail_sr", "d1", "d0"); ! 402: out("@@:"); ! 403: ! 404: // FPIAR ! 405: out(" fmove.l fpiar,a0"); ! 406: // fmovem *,nofpc は FPIAR が受け取ってしまうようだ ! 407: if (($regnum & 1) || $regnum == 0) { ! 408: out(" move.l #$%08x,a1", $expected[$i] & regmask(1)); ! 409: $i++; ! 410: } else { ! 411: out(" move.l d7,a1 ; d7=0"); ! 412: } ! 413: out(" cmpa.l a1,a0"); ! 414: out(" beq @f"); ! 415: out_result("msg_fail_ir", "a1", "a0"); ! 416: out("@@:"); ! 417: ! 418: out(" tst.l d7"); ! 419: out(" bne @f"); ! 420: out(" PRINT msg_ok"); ! 421: out("@@:"); ! 422: out(" rts"); ! 423: out(""); ! 424: } ! 425: ! 426: // アクセス可能な場合のテスト。FMOVEM <FPcr>,<ea> ! 427: function subr_success_from($regs, $id) ! 428: { ! 429: $regnum = reg2num($regs); ! 430: $mnem = regnum2mnemonic($regnum); ! 431: $label = eanum2label($id); ! 432: ! 433: $funcname = "fmovem_{$regs}_to_{$label}"; ! 434: addfunc($funcname); ! 435: ! 436: $w1 = 0xf200 | eanum2word($id); ! 437: $w2 = 0xa000 | ($regnum << 10); ! 438: $cpureg = eanum2mnem($id); ! 439: // count は指定されたレジスタ数 (0..3) ! 440: // txcount は転送するレジスタ数 (1..3) ! 441: $count = regnum2count($regnum); ! 442: $txcount = ($count == 0) ? 1 : $count; ! 443: ! 444: // FPcr に初期値をセット ! 445: // nofpc の時は FPIAR ! 446: $expected = array( ! 447: 0x55555555, ! 448: 0xaaaaaaaa, ! 449: 0x33333333, ! 450: ); ! 451: $i = 0; ! 452: out(" fmove.l #$%08x,fpcr", ($regnum & 4) ? $expected[$i++] : 0); ! 453: out(" fmove.l #$%08x,fpsr", ($regnum & 2) ? $expected[$i++] : 0); ! 454: out(" fmove.l #$%08x,fpiar", ! 455: (($regnum & 1) || $count == 0) ? $expected[$i++] : 0); ! 456: ! 457: // 準備 ! 458: switch ($id) { ! 459: case 2: // (An) ! 460: case 3: // (An)+ ! 461: out(" lea.l workarea(pc),a0"); ! 462: break; ! 463: case 4: // -(An) ! 464: // nofpc でも転送は発生する ! 465: out(" lea.l workarea+%d(pc),a0", $txcount * 4); ! 466: break; ! 467: case 5: // #imm は dst にならない ! 468: break; ! 469: } ! 470: ! 471: out(" SET_FLINE_TRAP"); ! 472: out(" moveq.l #0,d7"); ! 473: out(" lea.l @f,a3"); ! 474: // 実行 ! 475: if ($mnem == "nofpc") ! 476: out(" .dc.w $%04x, $%04x", $w1, $w2); ! 477: else ! 478: out(" fmovem.l {$mnem},{$cpureg}"); ! 479: out("@@:"); ! 480: ! 481: out(" RESTORE_FLINE_TRAP"); ! 482: // 例外は起きないはずなので D7 はゼロなら成功(次へ) ! 483: out(" tst.l d7"); ! 484: out(" beq @f"); ! 485: out(" PRINT msg_fail_trap"); ! 486: out(" rts"); ! 487: out("@@:"); ! 488: ! 489: // 検証 ! 490: ! 491: // An の変化量が正しいか ! 492: // (An) なら workarea から変化しない ! 493: // (An)+ なら workarea+count*4 ! 494: // -(An) なら workarea に戻ってくる (最初にオフセットつけてるので) ! 495: switch ($id) { ! 496: case 2: // (An) ! 497: case 4: // -(An) ! 498: out(" lea.l workarea(pc),a1"); ! 499: out(" cmpa.l a1,a0"); ! 500: out(" beq @f"); ! 501: out_result("msg_fail_addr", "a1", "a0"); ! 502: out("@@:"); ! 503: break; ! 504: case 3: // (An)+ ! 505: out(" lea.l workarea+%d(pc),a1", $txcount * 4); ! 506: out(" cmpa.l a1,a0"); ! 507: out(" beq @f"); ! 508: out_result("msg_fail_addr", "a1", "a0"); ! 509: out("@@:"); ! 510: break; ! 511: } ! 512: ! 513: $i = 0; ! 514: switch ($id) { ! 515: case 0: // Dn ! 516: for ($r = 4; $r >= 1; $r >>= 1) { ! 517: // 各 fpn の該当する時か、nofpc なら FPIAR ! 518: if (($regnum & $r) || ($r == 1 && $count == 0)) { ! 519: out(" move.l #$%08x,d1", $expected[$i] & regmask($r)); ! 520: $i++; ! 521: out(" cmp.l d1,d0"); ! 522: out(" beq @f"); ! 523: out_result("msg_fail", "d1", "d0"); ! 524: out("@@:"); ! 525: } ! 526: } ! 527: break; ! 528: case 1: // An ! 529: for ($r = 4; $r >= 1; $r >>= 1) { ! 530: // 各 fpn の該当する時か、nofpc なら FPIAR ! 531: if (($regnum & $r) || ($r == 1 && $count == 0)) { ! 532: out(" movea.l #$%08x,a1", $expected[$i] & regmask($r)); ! 533: $i++; ! 534: out(" cmp.l a1,a0"); ! 535: out(" beq @f"); ! 536: out_result("msg_fail", "a1", "a0"); ! 537: out("@@:"); ! 538: } ! 539: } ! 540: break; ! 541: case 2: // (An) ! 542: case 3: // (An)+ ! 543: case 4: // -(An) ! 544: for ($r = 4; $r >= 1; $r >>= 1) { ! 545: // 各 fpn の該当する時か、nofpc なら FPIAR ! 546: if (($regnum & $r) || ($r == 1 && $count == 0)) { ! 547: out(" move.l #$%08x,d1", $expected[$i] & regmask($r)); ! 548: out(" cmp.l workarea+%d,d1", $i * 4); ! 549: $i++; ! 550: out(" beq @f"); ! 551: out_result("msg_fail", "d1", sprintf("workarea+%d", $i * 4)); ! 552: out("@@:"); ! 553: } ! 554: } ! 555: break; ! 556: } ! 557: ! 558: out(" tst.l d7"); ! 559: out(" bne @f"); ! 560: out(" PRINT msg_ok"); ! 561: out("@@:"); ! 562: out(" rts"); ! 563: out(""); ! 564: } ! 565: ?> ! 566: <?php ! 567: // lea.l $0000002c.l,a5 だと has.x に絶対アドレッシングだと ! 568: // 怒られてしまうため仕方なく ! 569: ?> ! 570: start: ! 571: clr.l -(sp) ! 572: DOS _SUPER ! 573: movem.l d7/a3-a5,-(sp) ! 574: moveq.l #$2c,d0 ; alternate of "lea.l $2c,a5" ! 575: move.l d0,a5 ; ! 576: <?php ! 577: foreach ($funclist as $name) { ! 578: out(" bsr test_{$name}"); ! 579: } ! 580: ?> ! 581: movem.l (sp)+,d7/a3-a5 ! 582: DOS _EXIT ! 583: ! 584: ; 結果表示 ! 585: ; スタックには、メッセージ、期待値、実際の値の順で積む。 ! 586: result: ! 587: link a6,#0 ! 588: movem.l d0/a0,-(sp) ! 589: ! 590: move.l 8+8(a6),-(sp) ! 591: DOS _PRINT ! 592: addq.l #4,sp ! 593: ! 594: move.l 8+4(a6),d0 ! 595: lea.l buf(pc),a0 ! 596: bsr hexstr_long ! 597: PRINT buf ! 598: PRINT msg_but ! 599: ! 600: move.l 8+0(a6),a0 ! 601: lea.l buf(pc),a0 ! 602: bsr hexstr_long ! 603: PRINT buf ! 604: PRINT msg_crlf ! 605: ! 606: addq.l #1,d7 ; errcnt ! 607: movem.l (sp)+,d0/a0 ! 608: unlk a6 ! 609: rts ! 610: ! 611: ; Fライン例外ハンドラ ! 612: ; a3 に戻りアドレスをセットしてあること ! 613: ; d7 を 1 にして帰る ! 614: fline_handler: ! 615: moveq.l #1,d7 ! 616: move.l a3,2(sp) ! 617: rte ! 618: ! 619: msg_oktrap: .dc.b $9,"ok(trap)",$d,$a,0, 0,0 ! 620: msg_fail_notrap: .dc.b $9,"FAIL: trap expected but not occured",$d,$a,0 ! 621: msg_fail_trap: .dc.b $9,"FAIL: unexpected trap occured",$d,$a,0 ! 622: ! 623: msg_ok: .dc.b $9,"ok",$d,$a,0 ! 624: msg_fail: .dc.b $9,"fail",$d,$a,0 ! 625: msg_fail_cr: .dc.b $9,"FAIL: fpcr expects ",0 ! 626: msg_fail_sr: .dc.b $9,"FAIL: fpsr expects ",0 ! 627: msg_fail_ir: .dc.b $9,"FAIL: fpiar expects ",0 ! 628: msg_fail_addr: .dc.b $9,"FAIL: an expects ",0 ! 629: msg_but: .dc.b " but ",0 ! 630: msg_crlf: .dc.b $d,$a,0 ! 631: ! 632: ! 633: <?php ! 634: foreach ($funclist as $name) { ! 635: out("msg_{$name}: .dc.b \"{$name}\",0"); ! 636: } ! 637: ?> ! 638: ! 639: .data ! 640: .even ! 641: workarea: .ds.b 24 ! 642: buf: .dc.b 20 ! 643: ! 644: .end start
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.