From c119552c060b13611647b671039c0e0cccc219a1 Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Thu, 9 Feb 2017 16:35:26 -0800 Subject: [PATCH 01/29] A few minor edits/corrections --- apps/pdp11/boot/monitor/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/pdp11/boot/monitor/README.md b/apps/pdp11/boot/monitor/README.md index 505355250..5b868b1a2 100644 --- a/apps/pdp11/boot/monitor/README.md +++ b/apps/pdp11/boot/monitor/README.md @@ -8,12 +8,12 @@ PDP-11 Boot Monitor ------------------- [BOOTMON.mac](BOOTMON.mac) is a custom boot monitor/loader based on [boot.mac](http://skn.noip.me/pdp11/boot.mac) -written by Paul Nankervis. It is preloaded in a number of our PDP-11 test machines. +written by Paul Nankervis. [BOOTMON.mac](BOOTMON.mac) was cross-assembled with [MACRO11](https://github.com/j-hoppe/MACRO11) to produce -[BOOTMON.txt](BOOTMON.txt), which was then processed by [FileDump](/modules/filedump/) to produce [BOOTMON.json](BOOTMON.json). - -To see the Boot Monitor in action, try the [PDP-11/70 Boot Monitor with Debugger](/devices/pdp11/machine/1170/monitor/debugger/). +[BOOTMON.txt](BOOTMON.txt), which was then processed by [FileDump](/modules/filedump/) to produce [BOOTMON.json](BOOTMON.json), +which is preloaded in a number of our PDP-11 test machines. To see it in action, try the +[PDP-11/70 Boot Monitor with Debugger](/devices/pdp11/machine/1170/monitor/debugger/). The **BOOTMON.mac** source code is shown below. From bfab39cf5a4993c070ea5681e20f105b65dec8b1 Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Sun, 12 Feb 2017 11:21:49 -0800 Subject: [PATCH 02/29] Skeleton of an Int36 class that may be used to support 36-bit integers in a PDP-10 simulation --- modules/shared/bin/int36 | 129 +++++++++++++++++ modules/shared/lib/int36.js | 278 ++++++++++++++++++++++++++++++++++++ 2 files changed, 407 insertions(+) create mode 100644 modules/shared/bin/int36 create mode 100644 modules/shared/lib/int36.js diff --git a/modules/shared/bin/int36 b/modules/shared/bin/int36 new file mode 100644 index 000000000..13ef785cf --- /dev/null +++ b/modules/shared/bin/int36 @@ -0,0 +1,129 @@ +#!/usr/bin/env node +/** + * @fileoverview Test the Int36 class + * @author Jeff Parsons + * @copyright © Jeff Parsons 2012-2017 + * @suppress {missingProperties} + * + * This file is part of PCjs, a computer emulation software project at . + * + * PCjs is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCjs. If not, + * see . + * + * You are required to include the above copyright notice in every modified copy of this work + * and to display that copyright notice when the software starts running; see COPYRIGHT in + * . + * + * Some PCjs files also attempt to load external resource files, such as character-image files, + * ROM files, and disk image files. Those external resource files are not considered part of PCjs + * for purposes of the GNU General Public License, and the author does not claim any copyright + * as to their contents. + */ + +"use strict"; + +var repl = require("repl"); +var Defines = require("../../shared/lib/defines"); +var Int36 = require("../../shared/lib/int36"); + +var i36Reg = new Int36(); + +/** + * dumpInt36(i36) + * + * @param {Int36} i36 + * @return {string} + */ +function dumpInt36(i36) +{ + return i36.toString() + " (" + i36.toString(8, true) + ")"; +} + +/** + * test(sCmd, fREPL) + * + * @param {string} sCmd + * @param {boolean} [fREPL] + * @return {*} + */ +function test(sCmd, fREPL) +{ + var aTokens = sCmd.split(' '); + + var sOp = aTokens[0]; + var sNum1 = aTokens[1] && aTokens[1].replace(/,/g, ''); + var sNum2 = aTokens[2]; + + var i36Op = new Int36(+sNum1); + + switch(sOp) { + case "set": + i36Reg = new Int36(+sNum1, +sNum2); + break; + + case "add": + i36Reg.add(i36Op); + break; + + case "sub": + i36Reg.sub(i36Op); + break; + + case "mul": + i36Reg.mul(i36Op); + break; + + case "div": + i36Reg.div(i36Op); + break; + + case "print": + break; + + default: + console.log("unrecognized command: " + sCmd); + return false; + } + + console.log(sOp + (sNum1? (" " + dumpInt36(i36Op)) : "") + ": " + dumpInt36(i36Reg)); + + return true; +} + +/** + * onCommand(cmd, context, filename, callback) + * + * @param {string} cmd + * @param {Object} context + * @param {string} filename + * @param {function(Object|null, Object)} callback + */ +var onCommand = function (cmd, context, filename, callback) +{ + var result = false; + var match = cmd.match(/^\(?\s*([\S\s]*?)\s*\)?$/); + if (match && match[1]) result = test(match[1], true); + callback(null, result); +}; + +test("set -34,359,738,368"); +test("add 1"); +test("sub 1"); +test("sub 1"); +test("add 1"); +test("add 0"); + +repl.start({ + prompt: "int36> ", + input: process.stdin, + output: process.stdout, + eval: onCommand +}); diff --git a/modules/shared/lib/int36.js b/modules/shared/lib/int36.js new file mode 100644 index 000000000..1518fe414 --- /dev/null +++ b/modules/shared/lib/int36.js @@ -0,0 +1,278 @@ +/** + * @fileoverview Support for 36-bit integers + * @author Jeff Parsons (@jeffpar) + * @copyright © Jeff Parsons 2012-2017 + * + * This file is part of PCjs, a computer emulation software project at . + * + * PCjs is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCjs. If not, + * see . + * + * You are required to include the above copyright notice in every modified copy of this work + * and to display that copyright notice when the software starts running; see COPYRIGHT in + * . + * + * Some PCjs files also attempt to load external resource files, such as character-image files, + * ROM files, and disk image files. Those external resource files are not considered part of PCjs + * for purposes of the GNU General Public License, and the author does not claim any copyright + * as to their contents. + */ + +"use strict"; + +var DEBUG = true; + +class Int36 { + /** + * Int36(hi, lo) + * + * The constructor creates an Int36 from either: + * + * 1) another Int36 + * 2) a single (signed) 36-bit value + * 3) a pair of 18-bit values (the signs are irrelevant) + * 4) nothing (initial value will be zero) + * + * We guarantee that an Int36 value will be (and will always remain) a signed value within this range: + * + * -Math.pow(2, 35) <= i <= Math.pow(2, 35) - 1 + * + * Those lower and upper bounds are defined as Int36.MINVAL and Int36.MAXVAL. The sign of an Int36 + * value is determined by (and should always match) its highest bit (bit 35). + * + * NOTE: We use modern bit numbering, where bit 0 is the right-most (least-significant) bit and + * bit 35 is the left-most bit. This is opposite of the PDP-10 convention, which defined bit 0 as the + * left-most bit and bit 35 as the right-most bit. + * + * Although the integer precision of JavaScript floating-point (IEEE 754 double-precision) numbers is: + * + * -Math.pow(2, 53) <= i <= Math.pow(2, 53) + * + * it seems unwise to ever permit the internal value to creep outside the signed 36-bit range, because + * floating-point operations will drop least-significant bits in favor of most-significant bits when a + * result becomes too large, which is the opposite of what integer operations traditionally do. There + * might be some optimization benefits to performing our internal 36-bit truncation "lazily", but at + * least initially, I prefer to truncate the results of all operations immediately. + * + * Most of the Int36 operations come in two flavors: those that accept numbers, and those that accept + * another Int36. The latter are more efficient, because (as just explained) an Int36's internal value + * should always be in range, whereas external numbers could be out of range OR have a fractional value + * OR be something else entirely (NaN, Infinity, -Infinity, undefined, etc), so numeric inputs are + * always passed through the static validate() function. + * + * We could eliminate the two flavors and check each parameter's type, like we do in the constructor, + * but constructor calls are infrequent (if they're not, you're doing something wrong), whereas Int36-only + * operations should be as fast and unchecked as possible. + * + * @param {Int36|number} [hi] (if omitted, the default is zero) + * @param {number} [lo] (if present, both lo and hi must be 18-bit numbers) + */ + constructor(hi = 0, lo) + { + if (hi instanceof Int36) { + this.value = hi.value; + } + else if (isNaN(lo)) { + /* + * Checking isNaN(lo) includes checking for undefined. And since there's no guarantee + * that hi is within the 36-bit range, we call validate() to make sure. + */ + this.value = Int36.validate(hi); + } else { + /* + * We're masking both inputs to 18 bits, making them positive, so the result will + * be positive and no more than 36 bits; however, the value could be greater than MAXVAL, + * meaning the sign bit (bit 35) is set, in which case we must perform a two's complement + * conversion, by subtracting 2^36. + */ + this.value = (hi & Int36.MASKLO) * Int36.BIT18 + (lo & Int36.MASKLO); + if (this.value > Int36.MAXVAL) { + this.value -= Int36.BIT36; + } + } + this.error = Int36.ERROR.NONE; + } + + /** + * validate(num) + * + * @param {number} num + * @return {number} + */ + static validate(num) + { + var result = Math.trunc(num || 0) % Int36.BIT36; + if (result > Int36.MAXVAL) { + result -= Int36.BIT36; + } else if (result < Int36.MINVAL) { + result += Int36.BIT36; + } + if (DEBUG && num !== result) console.log("Int36.validate(" + num + " out of range, truncated to " + result + ")"); + return result; + } + + /** + * toString(radix, fUnsigned) + * + * @param {number} [radix] (default is 10) + * @param {boolean} [fUnsigned] (default is signed) + */ + toString(radix = 10, fUnsigned) + { + var s; + var value = this.value; + if (fUnsigned) { + if (value < 0) { + value += Int36.BIT36; + } + if (radix == 8) { + s = "0o" + ("00000000000" + value.toString(8)).slice(-12); + if (DEBUG && this.error) s += " error 0x" + this.error.toString(16); + } + } + if (!s) s = value.toString(radix); + return s; + } + + /** + * truncate(result) + * + * NOTE: This function's job is to truncate the result of an operation to 36-bit accuracy, + * not to remove any fractional portion that might also exist. If an operation could have produced + * a non-integer result (eg, div()), it's the caller's responsibility to deal with that first. + * + * @param {number} result + * @return {number} + */ + truncate(result) + { + if (DEBUG && result !== Math.trunc(result)) console.log("Int36.truncate(" + result + " is not an integer)"); + this.error = Int36.ERROR.NONE; + if (result > Int36.MAXVAL) { + result %= Int36.BIT36; + if (result > Int36.MAXVAL) result -= Int36.BIT36; + this.error |= Int36.ERROR.OVERFLOW; + } else if (result < Int36.MINVAL) { + result %= Int36.BIT36; + if (result < Int36.MINVAL) result += Int36.BIT36; + this.error |= Int36.ERROR.UNDERFLOW; + } + return result; + } + + /** + * add(i36) + * + * @param {Int36} i36 + */ + add(i36) { + this.value = this.truncate(this.value + i36.value); + } + + /** + * addNum(num) + * + * @param {number} num + */ + addNum(num) { + this.value = this.truncate(this.value + Int36.validate(num)); + } + + /** + * sub(i36) + * + * @param {Int36} i36 + */ + sub(i36) { + this.value = this.truncate(this.value - i36.value); + } + + /** + * subNum(num) + * + * @param {number} num + */ + subNum(num) { + this.value = this.truncate(this.value - Int36.validate(num)); + } + + /** + * mul(i36) + * + * TODO: Support multiplication results > 36 bits (ie, up to 72 bits) if an additional Int36 + * parameter is provided. + * + * @param {Int36} i36 + */ + mul(i36) { + this.value = this.truncate(this.value * i36.value); + } + + /** + * mulNum(num) + * + * @param {number} num + */ + mulNum(num) { + this.value = this.truncate(this.value * Int36.validate(num)); + } + + /** + * div(i36) + * + * TODO: Support division of dividends > 36 bits (ie, up to 72 bits) if an additional Int36 + * parameter is provided. + * + * WARNING: JavaScript division by zero returns Infinity (or -Infinity). For now, we simply record an error. + * + * @param {Int36} i36 + */ + div(i36) { + if (!i36.value) { + this.error |= Int36.ERROR.DIVZERO; + } else { + this.value = this.truncate(Math.trunc(this.value / i36.value)); + } + } + + /** + * divNum(num) + * + * WARNING: JavaScript division by zero returns Infinity (or -Infinity). For now, we simply record an error. + * + * @param {number} num + */ + divNum(num) { + var divisor = Int36.validate(num); + if (!divisor) { + this.error |= Int36.ERROR.DIVZERO; + } else { + this.value = this.truncate(Math.trunc(this.value / divisor)); + } + } +} + +Int36.ERROR = { + NONE: 0x0, + OVERFLOW: 0x1, + UNDERFLOW: 0x2, + DIVZERO: 0x4 +}; + +Int36.MASKLO = 0o777777; // 262,143 + +Int36.BIT18 = Math.pow(2, 18); // 262,144 +Int36.BIT36 = Math.pow(2, 36); // 68,719,476,736 + +Int36.MAXVAL = Math.pow(2, 35) - 1; // 34,359,738,367 +Int36.MINVAL = -Math.pow(2, 35); // -34,359,738,368 + +if (NODE) module.exports = Int36; From 92a919c927580f546a83c118d114de64c8f27622 Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Sun, 12 Feb 2017 22:24:06 -0800 Subject: [PATCH 03/29] Avoid reallocating 64-bit register arrays on every 64-bit division --- docs/pcx86/examples/pcx86-dbg.js | 160 ++++++++++++++--------------- docs/pcx86/examples/pcx86.js | 132 ++++++++++++------------ modules/pcx86/lib/x86cpu.js | 2 + modules/pcx86/lib/x86help.js | 103 ++++++++++--------- versions/pcx86/1.34.0/pcx86-dbg.js | 160 ++++++++++++++--------------- versions/pcx86/1.34.0/pcx86.js | 132 ++++++++++++------------ 6 files changed, 348 insertions(+), 341 deletions(-) diff --git a/docs/pcx86/examples/pcx86-dbg.js b/docs/pcx86/examples/pcx86-dbg.js index 079a824a0..8ed72740c 100644 --- a/docs/pcx86/examples/pcx86-dbg.js +++ b/docs/pcx86/examples/pcx86-dbg.js @@ -44,9 +44,9 @@ http://pcjs.org/modules/shared/lib/save.js (C) Jeff Parsons 2012-2017 */ var l,aa;function ba(a,b){function c(){}c.prototype=b.prototype;a.prototype=new c;a.prototype.constructor=a;for(var d in b)if(Object.defineProperties){var e=Object.getOwnPropertyDescriptor(b,d);e&&Object.defineProperty(a,d,e)}else a[d]=b[d]} -var da={163840:[40,1,8,,254],184320:[40,1,9,,252],327680:[40,2,8,,255],368640:[40,2,9,,253],737280:[80,2,9,,249],1228800:[80,2,15,,249],1474560:[80,2,18,,240],2949120:[80,2,36,,240],21368320:[615,4,17],256256:[77,1,26,128],2494464:[203,2,12,512],5242880:[256,2,40,256],10485760:[512,2,40,256]},n={jp:0,lp:1,mp:2,hl:3,np:4,op:5,pp:6,qp:7,rp:8,sp:9,tp:10,up:11,vp:12,wp:13,xp:14,yp:15,zp:16,Ap:17,Bp:18,Cp:19,Dp:20,Ep:21,Fp:22,Gp:23,Hp:24,Ip:25,Jp:26," ":32,"!":33,'"':34,"#":35,$:36,"%":37,"&":38,"'":39, -"(":40,")":41,"*":42,"+":43,",":44,"-":45,".":46,"/":47,0:48,1:49,2:50,3:51,4:52,5:53,6:54,7:55,8:56,9:57,":":58,";":59,"<":60,"=":61,">":62,"?":63,"@":64,ce:65,ti:66,ui:67,vi:68,E:69,wi:70,xi:71,yi:72,zi:73,Ai:74,Bi:75,Ci:76,Di:77,Ei:78,Fi:79,Gi:80,Q:81,Hi:82,Ii:83,Ji:84,Ki:85,Li:86,Mi:87,Ni:88,Oi:89,eg:90,"[":91,"\\":92,"]":93,"^":94,_:95,"`":96,de:97,vl:98,xl:99,d:100,e:101,Hl:102,Il:103,Jl:104,Kl:105,an:106,k:107,bn:108,fn:109,n:110,on:111,p:112,q:113,r:114,Lo:115,t:116,Oo:117,Po:118,Qo:119,x:120, -y:121,z:122,"{":123,"|":124,"}":125,"~":126,Kp:127},ea={};ea[173]=n["-"];ea[186]=n[";"];ea[187]=n["="];ea[189]=n["-"];ea[188]=n[","];ea[190]=n["."];ea[191]=n["/"];ea[192]=n["`"];ea[219]=n["["];ea[220]=n["\\"];ea[221]=n["]"];ea[222]=n["'"];var fa={};fa[n["1"]]=n["!"];fa[n["2"]]=n["@"];fa[n["3"]]=n["#"];fa[n["4"]]=n.$;fa[n["5"]]=n["%"];fa[n["6"]]=n["^"];fa[n["7"]]=n["&"];fa[n["8"]]=n["*"];fa[n["9"]]=n["("];fa[n["0"]]=n[")"];fa[186]=n[":"];fa[187]=n["+"];fa[188]=n["<"];fa[189]=n._;fa[190]=n[">"]; +var da={163840:[40,1,8,,254],184320:[40,1,9,,252],327680:[40,2,8,,255],368640:[40,2,9,,253],737280:[80,2,9,,249],1228800:[80,2,15,,249],1474560:[80,2,18,,240],2949120:[80,2,36,,240],21368320:[615,4,17],256256:[77,1,26,128],2494464:[203,2,12,512],5242880:[256,2,40,256],10485760:[512,2,40,256]},n={lp:0,np:1,op:2,hl:3,pp:4,qp:5,rp:6,sp:7,tp:8,up:9,vp:10,wp:11,xp:12,yp:13,zp:14,Ap:15,Bp:16,Cp:17,Dp:18,Ep:19,Fp:20,Gp:21,Hp:22,Ip:23,Jp:24,Kp:25,Lp:26," ":32,"!":33,'"':34,"#":35,$:36,"%":37,"&":38,"'":39, +"(":40,")":41,"*":42,"+":43,",":44,"-":45,".":46,"/":47,0:48,1:49,2:50,3:51,4:52,5:53,6:54,7:55,8:56,9:57,":":58,";":59,"<":60,"=":61,">":62,"?":63,"@":64,ce:65,ti:66,ui:67,vi:68,E:69,wi:70,xi:71,yi:72,zi:73,Ai:74,Bi:75,Ci:76,Di:77,Ei:78,Fi:79,Gi:80,Q:81,Hi:82,Ii:83,Ji:84,Ki:85,Li:86,Mi:87,Ni:88,Oi:89,eg:90,"[":91,"\\":92,"]":93,"^":94,_:95,"`":96,de:97,vl:98,xl:99,d:100,e:101,Hl:102,Il:103,Jl:104,Kl:105,an:106,k:107,dn:108,hn:109,n:110,qn:111,p:112,q:113,r:114,No:115,t:116,Qo:117,Ro:118,So:119,x:120, +y:121,z:122,"{":123,"|":124,"}":125,"~":126,Mp:127},ea={};ea[173]=n["-"];ea[186]=n[";"];ea[187]=n["="];ea[189]=n["-"];ea[188]=n[","];ea[190]=n["."];ea[191]=n["/"];ea[192]=n["`"];ea[219]=n["["];ea[220]=n["\\"];ea[221]=n["]"];ea[222]=n["'"];var fa={};fa[n["1"]]=n["!"];fa[n["2"]]=n["@"];fa[n["3"]]=n["#"];fa[n["4"]]=n.$;fa[n["5"]]=n["%"];fa[n["6"]]=n["^"];fa[n["7"]]=n["&"];fa[n["8"]]=n["*"];fa[n["9"]]=n["("];fa[n["0"]]=n[")"];fa[186]=n[":"];fa[187]=n["+"];fa[188]=n["<"];fa[189]=n._;fa[190]=n[">"]; fa[191]=n["?"];fa[192]=n["~"];fa[219]=n["{"];fa[220]=n["|"];fa[221]=n["}"];fa[222]=n['"'];fa[173]=n._;fa[61]=n["+"];fa[59]=n[":"]; function ga(a,b){var c;if(a){b||(b=10);var d=a.charAt(0),e=0>=1,f--;return d}function ia(a,b,c){var d="";if(!b||4>=8;return(c?"0b":"")+d} @@ -106,39 +106,39 @@ l.fc=function(a,b){this.aa[(a&this.C)>>>this.A].Dc(a&this.D,b&255,a)};function B function Cc(a,b){var c=0,d=[],e=!a.J&&a.N==a.C;e||fc(a,!0);for(var f=0;f>>=f)&k;if(void 0!==g){if(g[0])g[0](b,k,e);a.ba&&a.L!=g[1]&&Kc(a.ba,b,k)}else a.ba&&(Db(a.ba,a,b,k,e),a.L&&Kc(a.ba,b,k));f+=h<<3;b+=h;c-=h}}function hc(a,b,c,d,e){b="Memory block error ("+b+": "+q(c)+","+q(d)+")";e?a.ba?a.ba.message(b):a.log(b):Wa(b);return!1}var Ub,Lc={Hk:20,count:8,Zp:1,type:3},Nc=0,Oc;for(Oc in Lc){var Pc=Lc[Oc];Lc[Oc]={nh:(1<>>=f)&k;if(void 0!==g){if(g[0])g[0](b,k,e);a.ba&&a.L!=g[1]&&Kc(a.ba,b,k)}else a.ba&&(Db(a.ba,a,b,k,e),a.L&&Kc(a.ba,b,k));f+=h<<3;b+=h;c-=h}}function hc(a,b,c,d,e){b="Memory block error ("+b+": "+q(c)+","+q(d)+")";e?a.ba?a.ba.message(b):a.log(b):Wa(b);return!1}var Ub,Lc={Hk:20,count:8,aq:1,type:3},Nc=0,Oc;for(Oc in Lc){var Pc=Lc[Oc];Lc[Oc]={nh:(1<>1),this.X=new Int32Array(this.I,0,c>>2),pc(this,wc?xc:yc);else{this.X=Array(c>>2);for(e=0;e>2),b=0;b>8,c)};l.oi=function(a,b,c){this.Dc(a++,b&255,c++);this.Dc(a++,b>>8&255,c++);this.Dc(a++,b>>16&255,c++);this.Dc(a,b>>>24,c)};l.vo=function(a){return this.X[a>>2]>>>((a&3)<<3)&255}; -l.Ho=function(a){var b=a>>2;a=(a&3)<<3;var c=this.X[b]>>a;return 24>a?c&65535:c&255|(this.X[b+1]&255)<<8};l.Bo=function(a){var b=a>>2;a=(a&3)<<3;var c=this.X[b];a&&(c=c>>>a|this.X[b+1]<<32-a);return c};l.To=function(a,b){var c=a>>2;a=(a&3)<<3;this.X[c]=this.X[c]&~(255<>2;a=(a&3)<<3;24>a?this.X[c]=this.X[c]&~(65535<>8);this.Oa=!0}; -l.Zo=function(a,b){var c=a>>2;if(a=(a&3)<<3){var d=-1<>>32-a}else this.X[c]=b;this.Oa=!0};l.uo=function(a,b){this.ba&&null!=this.xa&&dd(this.ba,this.xa+a)||this.C&&ed(this.C,b,1,!1);return this.Zd(a,b)};l.Go=function(a,b){this.ba&&null!=this.xa&&dd(this.ba,this.xa+a,2)||this.C&&ed(this.C,b,2,!1);return this.Nf(a,b)};l.Ao=function(a,b){this.ba&&null!=this.xa&&dd(this.ba,this.xa+a,4)||this.C&&ed(this.C,b,4,!1);return this.ji(a,b)}; -l.So=function(a,b,c){this.ba&&null!=this.xa&&fd(this.ba,this.xa+a)||this.C&&ed(this.C,c,1,!0);this.D?this.cf(0,b,c):this.bf(a,b,c)};l.ep=function(a,b,c){this.ba&&null!=this.xa&&fd(this.ba,this.xa+a,2)||this.C&&ed(this.C,c,2,!0);this.D?this.cf(0,b,c):this.ri(a,b,c)};l.Yo=function(a,b,c){this.ba&&null!=this.xa&&fd(this.ba,this.xa+a,4)||this.C&&ed(this.C,c,4,!0);this.D?this.cf(0,b,c):this.K(a,b,c)};l.xo=function(a,b){this.lb.X[this.A]|=this.F;this.mb.X[this.B]|=this.F;return this.pd.Bc(a,b)}; -l.Jo=function(a,b){this.lb.X[this.A]|=this.F;this.mb.X[this.B]|=this.F;return this.pd.Mf(a,b)};l.Do=function(a,b){this.lb.X[this.A]|=this.F;this.mb.X[this.B]|=this.F;return this.pd.Nd(a,b)};l.Vo=function(a,b,c){this.lb.X[this.A]|=this.F;this.mb.X[this.B]|=this.J;this.pd.Dc(a,b,c)};l.hp=function(a,b,c){this.lb.X[this.A]|=this.F;this.mb.X[this.B]|=this.J;this.pd.Tf(a,b,c)};l.ap=function(a,b,c){this.lb.X[this.A]|=this.F;this.mb.X[this.B]|=this.J;this.pd.Sf(a,b,c)}; -l.yo=function(a,b){return gd(this.C,b,!1).Bc(a,b)};l.Ko=function(a,b){return gd(this.C,b,!1).Mf(a,b)};l.Eo=function(a,b){return gd(this.C,b,!1).Nd(a,b)};l.Wo=function(a,b,c){gd(this.C,c,!0).Dc(a,b,c)};l.ip=function(a,b,c){gd(this.C,c,!0).Tf(a,b,c)};l.bp=function(a,b,c){gd(this.C,c,!0).Sf(a,b,c)};l.to=function(a){return this.Pa[a]};l.Qk=function(a){return this.Pa[a]};l.wo=function(a){this.lb.X[this.A]|=32;this.mb.X[this.B]|=32;this.Bc=this.Qk;return this.Pa[a]}; -l.Fo=function(a){return this.G.getUint16(a,!0)};l.Vk=function(a){return a&1?this.Pa[a]|this.Pa[a+1]<<8:this.Rd[a>>1]};l.Io=function(a){this.lb.X[this.A]|=32;this.mb.X[this.B]|=32;this.Mf=this.Vk;return a&1?this.Pa[a]|this.Pa[a+1]<<8:this.Rd[a>>1]};l.zo=function(a){return this.G.getInt32(a,!0)};l.Sk=function(a){return a&3?this.Pa[a]|this.Pa[a+1]<<8|this.Pa[a+2]<<16|this.Pa[a+3]<<24:this.X[a>>2]}; -l.Co=function(a){this.lb.X[this.A]|=32;this.mb.X[this.B]|=32;this.Nd=this.Sk;return a&3?this.Pa[a]|this.Pa[a+1]<<8|this.Pa[a+2]<<16|this.Pa[a+3]<<24:this.X[a>>2]};l.Ro=function(a,b){this.Pa[a]=b;this.Oa=!0};l.el=function(a,b){this.Pa[a]=b;this.Oa=!0};l.Uo=function(a,b){this.Pa[a]=b;this.lb.X[this.A]|=32;this.mb.X[this.B]|=96;this.Dc=this.el;this.pd.Oa=!0};l.cp=function(a,b){this.G.setUint16(a,b,!0);this.Oa=!0};l.gl=function(a,b){a&1?(this.Pa[a]=b,this.Pa[a+1]=b>>8):this.Rd[a>>1]=b;this.Oa=!0}; -l.gp=function(a,b){a&1?(this.Pa[a]=b,this.Pa[a+1]=b>>8):this.Rd[a>>1]=b;this.lb.X[this.A]|=32;this.mb.X[this.B]|=96;this.Tf=this.gl;this.pd.Oa=!0};l.Xo=function(a,b){this.G.setInt32(a,b,!0);this.Oa=!0};l.fl=function(a,b){a&3?(this.Pa[a]=b,this.Pa[a+1]=b>>8,this.Pa[a+2]=b>>16,this.Pa[a+3]=b>>24):this.X[a>>2]=b;this.Oa=!0}; -l.$o=function(a,b){a&3?(this.Pa[a]=b,this.Pa[a+1]=b>>8,this.Pa[a+2]=b>>16,this.Pa[a+3]=b>>24):this.X[a>>2]=b;this.lb.X[this.A]|=32;this.mb.X[this.B]|=96;this.Sf=this.fl;this.pd.Oa=!0};function hd(a){Eb&&!wc&&(a=a<<24|a<<8&16711680|a>>8&65280|a>>>24);return a} -var Tc=0,vc=2,Uc=5,Wc=6,id=["black","blue","green","cyan"],kc="NONE RAM ROM VIDEO H/W UNPAGED PAGED".split(" "),Sc=0,Yc=[],zc=[x.prototype.vo,x.prototype.To,x.prototype.Ho,x.prototype.fp,x.prototype.Bo,x.prototype.Zo],cd=[x.prototype.uo,x.prototype.So,x.prototype.Go,x.prototype.ep,x.prototype.Ao,x.prototype.Yo],Xc=[x.prototype.xo,x.prototype.Vo,x.prototype.Jo,x.prototype.hp,x.prototype.Do,x.prototype.ap],Vc=[x.prototype.yo,x.prototype.Wo,x.prototype.Ko,x.prototype.ip,x.prototype.Eo,x.prototype.bp]; -if(Eb)var yc=[x.prototype.to,x.prototype.Ro,x.prototype.Fo,x.prototype.cp,x.prototype.zo,x.prototype.Xo],xc=[x.prototype.Qk,x.prototype.el,x.prototype.Vk,x.prototype.gl,x.prototype.Sk,x.prototype.fl],nd=[x.prototype.wo,x.prototype.Uo,x.prototype.Io,x.prototype.gp,x.prototype.Co,x.prototype.$o]; +l.Uk=function(a,b){return this.Bc(a++,b++)|this.Bc(a,b)<<8};l.Rk=function(a,b){return this.Bc(a++,b++)|this.Bc(a++,b++)<<8|this.Bc(a++,b++)<<16|this.Bc(a,b)<<24};l.pi=function(a,b,c){this.Dc(a++,b&255,c++);this.Dc(a,b>>8,c)};l.oi=function(a,b,c){this.Dc(a++,b&255,c++);this.Dc(a++,b>>8&255,c++);this.Dc(a++,b>>16&255,c++);this.Dc(a,b>>>24,c)};l.xo=function(a){return this.X[a>>2]>>>((a&3)<<3)&255}; +l.Jo=function(a){var b=a>>2;a=(a&3)<<3;var c=this.X[b]>>a;return 24>a?c&65535:c&255|(this.X[b+1]&255)<<8};l.Do=function(a){var b=a>>2;a=(a&3)<<3;var c=this.X[b];a&&(c=c>>>a|this.X[b+1]<<32-a);return c};l.Vo=function(a,b){var c=a>>2;a=(a&3)<<3;this.X[c]=this.X[c]&~(255<>2;a=(a&3)<<3;24>a?this.X[c]=this.X[c]&~(65535<>8);this.Oa=!0}; +l.ap=function(a,b){var c=a>>2;if(a=(a&3)<<3){var d=-1<>>32-a}else this.X[c]=b;this.Oa=!0};l.wo=function(a,b){this.ba&&null!=this.xa&&dd(this.ba,this.xa+a)||this.C&&ed(this.C,b,1,!1);return this.Zd(a,b)};l.Io=function(a,b){this.ba&&null!=this.xa&&dd(this.ba,this.xa+a,2)||this.C&&ed(this.C,b,2,!1);return this.Nf(a,b)};l.Co=function(a,b){this.ba&&null!=this.xa&&dd(this.ba,this.xa+a,4)||this.C&&ed(this.C,b,4,!1);return this.ji(a,b)}; +l.Uo=function(a,b,c){this.ba&&null!=this.xa&&fd(this.ba,this.xa+a)||this.C&&ed(this.C,c,1,!0);this.D?this.cf(0,b,c):this.bf(a,b,c)};l.gp=function(a,b,c){this.ba&&null!=this.xa&&fd(this.ba,this.xa+a,2)||this.C&&ed(this.C,c,2,!0);this.D?this.cf(0,b,c):this.ri(a,b,c)};l.$o=function(a,b,c){this.ba&&null!=this.xa&&fd(this.ba,this.xa+a,4)||this.C&&ed(this.C,c,4,!0);this.D?this.cf(0,b,c):this.K(a,b,c)};l.zo=function(a,b){this.lb.X[this.A]|=this.F;this.mb.X[this.B]|=this.F;return this.pd.Bc(a,b)}; +l.Lo=function(a,b){this.lb.X[this.A]|=this.F;this.mb.X[this.B]|=this.F;return this.pd.Mf(a,b)};l.Fo=function(a,b){this.lb.X[this.A]|=this.F;this.mb.X[this.B]|=this.F;return this.pd.Nd(a,b)};l.Xo=function(a,b,c){this.lb.X[this.A]|=this.F;this.mb.X[this.B]|=this.J;this.pd.Dc(a,b,c)};l.jp=function(a,b,c){this.lb.X[this.A]|=this.F;this.mb.X[this.B]|=this.J;this.pd.Tf(a,b,c)};l.cp=function(a,b,c){this.lb.X[this.A]|=this.F;this.mb.X[this.B]|=this.J;this.pd.Sf(a,b,c)}; +l.Ao=function(a,b){return gd(this.C,b,!1).Bc(a,b)};l.Mo=function(a,b){return gd(this.C,b,!1).Mf(a,b)};l.Go=function(a,b){return gd(this.C,b,!1).Nd(a,b)};l.Yo=function(a,b,c){gd(this.C,c,!0).Dc(a,b,c)};l.kp=function(a,b,c){gd(this.C,c,!0).Tf(a,b,c)};l.ep=function(a,b,c){gd(this.C,c,!0).Sf(a,b,c)};l.vo=function(a){return this.Pa[a]};l.Qk=function(a){return this.Pa[a]};l.yo=function(a){this.lb.X[this.A]|=32;this.mb.X[this.B]|=32;this.Bc=this.Qk;return this.Pa[a]}; +l.Ho=function(a){return this.G.getUint16(a,!0)};l.Vk=function(a){return a&1?this.Pa[a]|this.Pa[a+1]<<8:this.Rd[a>>1]};l.Ko=function(a){this.lb.X[this.A]|=32;this.mb.X[this.B]|=32;this.Mf=this.Vk;return a&1?this.Pa[a]|this.Pa[a+1]<<8:this.Rd[a>>1]};l.Bo=function(a){return this.G.getInt32(a,!0)};l.Sk=function(a){return a&3?this.Pa[a]|this.Pa[a+1]<<8|this.Pa[a+2]<<16|this.Pa[a+3]<<24:this.X[a>>2]}; +l.Eo=function(a){this.lb.X[this.A]|=32;this.mb.X[this.B]|=32;this.Nd=this.Sk;return a&3?this.Pa[a]|this.Pa[a+1]<<8|this.Pa[a+2]<<16|this.Pa[a+3]<<24:this.X[a>>2]};l.To=function(a,b){this.Pa[a]=b;this.Oa=!0};l.el=function(a,b){this.Pa[a]=b;this.Oa=!0};l.Wo=function(a,b){this.Pa[a]=b;this.lb.X[this.A]|=32;this.mb.X[this.B]|=96;this.Dc=this.el;this.pd.Oa=!0};l.fp=function(a,b){this.G.setUint16(a,b,!0);this.Oa=!0};l.gl=function(a,b){a&1?(this.Pa[a]=b,this.Pa[a+1]=b>>8):this.Rd[a>>1]=b;this.Oa=!0}; +l.ip=function(a,b){a&1?(this.Pa[a]=b,this.Pa[a+1]=b>>8):this.Rd[a>>1]=b;this.lb.X[this.A]|=32;this.mb.X[this.B]|=96;this.Tf=this.gl;this.pd.Oa=!0};l.Zo=function(a,b){this.G.setInt32(a,b,!0);this.Oa=!0};l.fl=function(a,b){a&3?(this.Pa[a]=b,this.Pa[a+1]=b>>8,this.Pa[a+2]=b>>16,this.Pa[a+3]=b>>24):this.X[a>>2]=b;this.Oa=!0}; +l.bp=function(a,b){a&3?(this.Pa[a]=b,this.Pa[a+1]=b>>8,this.Pa[a+2]=b>>16,this.Pa[a+3]=b>>24):this.X[a>>2]=b;this.lb.X[this.A]|=32;this.mb.X[this.B]|=96;this.Sf=this.fl;this.pd.Oa=!0};function hd(a){Eb&&!wc&&(a=a<<24|a<<8&16711680|a>>8&65280|a>>>24);return a} +var Tc=0,vc=2,Uc=5,Wc=6,id=["black","blue","green","cyan"],kc="NONE RAM ROM VIDEO H/W UNPAGED PAGED".split(" "),Sc=0,Yc=[],zc=[x.prototype.xo,x.prototype.Vo,x.prototype.Jo,x.prototype.hp,x.prototype.Do,x.prototype.ap],cd=[x.prototype.wo,x.prototype.Uo,x.prototype.Io,x.prototype.gp,x.prototype.Co,x.prototype.$o],Xc=[x.prototype.zo,x.prototype.Xo,x.prototype.Lo,x.prototype.jp,x.prototype.Fo,x.prototype.cp],Vc=[x.prototype.Ao,x.prototype.Yo,x.prototype.Mo,x.prototype.kp,x.prototype.Go,x.prototype.ep]; +if(Eb)var yc=[x.prototype.vo,x.prototype.To,x.prototype.Ho,x.prototype.fp,x.prototype.Bo,x.prototype.Zo],xc=[x.prototype.Qk,x.prototype.el,x.prototype.Vk,x.prototype.gl,x.prototype.Sk,x.prototype.fl],nd=[x.prototype.yo,x.prototype.Wo,x.prototype.Ko,x.prototype.ip,x.prototype.Eo,x.prototype.bp]; function od(a,b){ab.call(this,"CPU",a,1);b=a.cycles||b;var c=a.multiplier||1;this.T={};this.T.sd=b;this.T.Kd=c;this.T.rg=Math.round(this.T.sd/1E4)/100;this.T.oe=this.T.rg*this.T.Kd;this.ea.vb=!1;this.ea.ni=!1;this.ea.lf=a.autoStart;this.ea.kj=!1;this.ea.He=!1;this.T.uf=this.T.Te=0;this.T.wf=a.csStart;this.T.Se=a.csInterval;this.T.Ue=a.csStop;this.Ql=this.Uf.bind(this);zb(this)}ba(od,ab);l=od.prototype; l.uc=function(a,b,c,d){this.pa=a;this.ka=b;this.ba=d;for(b=0;b=a.T.Te&&(a.T.Te+=a.T.Se,c=!0);0<=a.T.Ue&&a.T.Ue<=vd(a)&&(a.T.Se=a.T.Ue=-1,sd(a),a.Jb(),c=!0);c&&a.P(vd(a)+" cycles: checksum="+q(a.T.uf))}} l.Cb=function(a,b,c){var d=this;a=!1;switch(b){case "power":case "reset":this.na[b]=c;a=!0;break;case "run":this.na[b]=c;c.onclick=function(){var a;if(a=d.pa)if(a=d.pa,a.ea.Yb)a=!0;else{var b=null,c,h=jb(a.id);for(c=0;ca.T.rg&&(c=Math.round(c/a.T.Kd));return c}function rd(a){a.T.Wd=0;a.Dd=a.md=a.zc=a.A=0;sd(a);wd(a,1)} +function zd(a,b){var c=Ad;ca.T.rg&&(c=Math.round(c/a.T.Kd));return c}function rd(a){a.T.Wd=0;a.Dd=a.md=a.zc=a.A=0;sd(a);wd(a,1)} function wd(a,b,c){var d=!1;if(void 0!==b){.8>a.T.Wd/a.T.oe?b=1:d=!0;a.T.Kd=b;b=a.T.rg*a.T.Kd;if(a.T.oe!=b){a.T.oe=b;b=a.T.oe.toFixed(2)+"Mhz";var e=a.na.setSpeed;e&&(e.textContent=b);a.P("target speed: "+b)}c&&a.pa&&a.pa.ld()}yd(a,a.md);a.md=0;a.T.Xd=Ba();a.T.pe=0;zd(a);return d} -l.Uf=function(a){if(Bb(this,!0)){if(!this.ea.vb){wd(this);this.pa&&this.pa.start(this.T.Xd,vd(this));this.ea.vb=!0;this.ea.ni=!0;this.W&&this.W.start();var b=this.na.run;b&&(b.textContent="Halt");this.pa&&(Dd(this.pa,!0),a&&this.pa.ld(!0))}this.T.th>=this.T.sd&&zd(this,!0);this.T.zf=0;this.T.sg=Ba();this.T.pe&&(a=this.T.sg-this.T.pe,a>this.T.Dj&&(this.T.Xd+=a,this.T.Xd>this.T.sg&&(this.T.Xd=this.T.sg)));try{do{var c=this.ea.He?1:this.T.kn;if(this.W){Ed(this.W);var d=this.W;a=c;var e=d.G[0];if(e.le){var f= +l.Uf=function(a){if(Bb(this,!0)){if(!this.ea.vb){wd(this);this.pa&&this.pa.start(this.T.Xd,vd(this));this.ea.vb=!0;this.ea.ni=!0;this.W&&this.W.start();var b=this.na.run;b&&(b.textContent="Halt");this.pa&&(Dd(this.pa,!0),a&&this.pa.ld(!0))}this.T.th>=this.T.sd&&zd(this,!0);this.T.zf=0;this.T.sg=Ba();this.T.pe&&(a=this.T.sg-this.T.pe,a>this.T.Dj&&(this.T.Xd+=a,this.T.Xd>this.T.sg&&(this.T.Xd=this.T.sg)));try{do{var c=this.ea.He?1:this.T.nn;if(this.W){Ed(this.W);var d=this.W;a=c;var e=d.G[0];if(e.le){var f= (vd(d.H,d.O)-e.jd)/d.Ba|0,g=Fd(d,0)-f;e.mode==Gd&&(g-=f);var h=g*d.Ba|0;e.mode==Gd&&(h>>=1);a>h&&(a=h)}var c=a,k=this.W;a=c;if(k.A&&k.A[Hd]&Id){var m=k.Z-vd(k.H,k.O);0m&&(a=m)}c=a}try{this.Lg(c)}catch(w){if("number"!=typeof w)throw w;}var p=this.zc-this.A;this.md+=p;this.T.zf+=p;yd(this,0,!0);ud(this,p);this.T.yf-=p;0>=this.T.yf&&(this.T.yf+=this.T.Fj,this.pa&&Jd(this.pa));this.T.xf-=p;0>=this.T.xf&&(this.T.xf+=this.T.Ej,this.pa&&Dd(this.pa));this.T.Ve-=p;if(0>=this.T.Ve){this.T.Ve+=this.T.sh; break}}while(this.ea.vb)}catch(w){this.Jb();td(this);this.pa&&this.pa.stop(Ba(),vd(this));Bb(this,!1);wb(this,w.stack||w.message);return}c=setTimeout;d=this.Ql;this.T.pe=Ba();e=this.T.Dj;this.T.zf&&(e=Math.round(e*this.T.zf/this.T.sh));e-=this.T.pe-this.T.sg;if(f=this.T.pe-this.T.Xd)this.T.Wd=Math.round(this.md/(10*f))/100,864E5<=f&&(this.Dd=0,this.W&&Ed(this.W,!0),wd(this));if(0>e||this.T.Wde&&(this.T.Xd-=e),e=0;this.T.th+=this.T.zf;this.T.pe+=e;c(d,e)}else td(this),this.pa&&this.pa.stop(Ba(), vd(this))};l.Lg=function(){return 0};l.Jb=function(a){Ab(this,!0);this.zc-=this.A;this.A=0;yd(this,this.md);this.md=0;if(this.ea.vb){this.ea.vb=!1;this.W&&this.W.stop();var b=this.na.run;b&&(b.textContent="Run")}this.ea.complete=a};function td(a,b){a.pa&&(Jd(a.pa,b),Dd(a.pa,b))}var Ad=30,Bd=60,Cd=2,pd=["power","reset"]; function Kd(a,b,c,d){this.gc=a;this.ba=a.ba;this.id=b;this.$b=c||"";this.U=0;this.Ka=65535;this.Nb=this.Ka+1;this.Ab=this.qc=this.ext=this.jb=this.type=this.ua=0;this.Lb=-1;this.V=this.Jc=2;this.R=this.wa=65535;this.G=this.mh;this.D=this.dj;this.F=this.fj;this.A={U:-1,ua:0,Ka:0,jb:0,type:0,ext:0,Lb:-1};1==this.id&&(this.Jf=0,this.C=null,this.Pe=!1,this.H=Array(32),this.B=[]);Md(this,!0,d)}function Nd(a,b){a.B.push(b);return[a.B.length,Od]}l=Kd.prototype; -l.mh=function(a){this.U=a&65535;return this.ua=this.U<<4};l.qg=function(a,b){var c,d,e=this.gc;a&=65535;a&4?(c=e.Eb.ua,d=c+e.Eb.Ka|0):(c=e.Kb,d=e.Pc);if(c){c=c+(a&65528)|0;if(d-c|0)return e.A-=15,Pd(this,c,a,b);this.id>>0)+b<=this.Nb?this.ua+a|0:this.kg()};l.zl=function(a,b){return(a>>>0)+b>this.Nb?this.ua+a|0:this.kg()};l.kg=function(){y.call(this.gc,13,0);return-1};l.ej=function(a,b){return(a>>>0)+b<=this.Nb?this.ua+a|0:this.lg()}; +l.mh=function(a){this.U=a&65535;return this.ua=this.U<<4};l.qg=function(a,b){var c,d,e=this.gc;a&=65535;a&4?(c=e.Eb.ua,d=c+e.Eb.Ka|0):(c=e.Kb,d=e.Pc);if(c){c=c+(a&65528)|0;if(d-c|0)return e.A-=15,Pd(this,c,a,b);this.id>>0)+b<=this.Nb?this.ua+a|0:this.kg()};l.zl=function(a,b){return(a>>>0)+b>this.Nb?this.ua+a|0:this.kg()};l.kg=function(){y.call(this.gc,13,0);return-1};l.ej=function(a,b){return(a>>>0)+b<=this.Nb?this.ua+a|0:this.lg()}; l.Al=function(a,b){return(a>>>0)+b>this.Nb?this.ua+a|0:this.lg()};l.lg=function(){y.call(this.gc,13,0);return-1};function Td(a,b,c,d,e){a.U=b;a.ua=d;a.Ka=e;a.Nb=(e>>>0)+1;a.jb=c;a.type=c&7936;a.ext=c>>16&192;a.Lb=(b&4?a.gc.Eb.ua:a.gc.Kb)+(b&65528)|0;a.id>>0)+1;a.jb=e;a.type=e&7936;a.ext=0;a.Lb=b;a.id>>0)+1,a.jb=a.A.jb,a.type=a.A.type,a.ext=a.A.ext,a.Lb=a.A.Lb,a.A.U=-1,Md(a,!0,!0,!1),a.ua;a.A.U=-1;var f=e.ja(b+0),g=e.ja(b+4),h=g&7936,k=e.ja(b+2)|(g&255)<<16,m=e.ja(b+6),p=c&65528;if(80386<=e.ca){var w=f,k=k|(m&65280)<<16,f=f|(m&15)<<16;m&128&&(f=f<<12|4095)}switch(a.id){case Wd:var u=a.C;a.Pe=!1;if(u&&c==Od&&a.B.length){var F=a.B[a.Jf-1];if(F&&!F())return-1}var K=c&3,H=(g&24576)>>13,F=-1,I,T;p||b>= e.Kb&&b=a.Ab&&(K>a.Ab&&(F=Xd(e),Yd(e,Xd(e),!0),Zd(e,F),a.Pe=!0),F=0);else{if(256==h||2304==h)return $d(a,c,u)?a.ua:-1;if(1024==h)F=2,T=0,K>>0)+1)}; -function Md(a,b,c,d){void 0===c&&(c=!!(a.gc.qa&1));a.Oc=!1;if(c)if(a.load=a.qg,a.Cj=a.dn,a.bc=a.cj,a.cc=a.ej,void 0===d&&(d=!!(a.gc.O&131072)),d)a.load=a.G,a.bc=a.D,a.cc=a.F,a.Ab=a.qc=3,a.V=2,a.R=a.wa=65535,a.Ka=65535,a.Nb=a.Ka+1,a.Jc=a.V,a.Lb=-1,a.Pe=!1;else{if(!(a.U&-4))a.bc=a.kg,a.cc=a.lg;else if(a.type&4096){6144==(a.type&6656)&&(a.bc=a.kg);if(a.type&2048||!(a.type&512))a.cc=a.lg;1024==(a.type&3072)&&(a.bc==a.cj&&(a.bc=a.zl),a.cc==a.ej&&(a.cc=a.Al),a.Oc=!0);b&&a.id>13,80386>a.gc.ca||!(a.ext&64)?(a.V=2,a.R=65535):(a.V=4,a.R=-1),a.Jc=a.V,a.wa=a.R)}else a.load=a.mh,a.Cj=a.en,a.bc=a.dj,a.cc=a.fj,a.Ab=a.qc=0,a.Lb=-1,a.Pe=!1}var Wd=1,he=2,Rd=3,Ud=4,Qd=6,Od=1; +function Md(a,b,c,d){void 0===c&&(c=!!(a.gc.qa&1));a.Oc=!1;if(c)if(a.load=a.qg,a.Cj=a.fn,a.bc=a.cj,a.cc=a.ej,void 0===d&&(d=!!(a.gc.O&131072)),d)a.load=a.G,a.bc=a.D,a.cc=a.F,a.Ab=a.qc=3,a.V=2,a.R=a.wa=65535,a.Ka=65535,a.Nb=a.Ka+1,a.Jc=a.V,a.Lb=-1,a.Pe=!1;else{if(!(a.U&-4))a.bc=a.kg,a.cc=a.lg;else if(a.type&4096){6144==(a.type&6656)&&(a.bc=a.kg);if(a.type&2048||!(a.type&512))a.cc=a.lg;1024==(a.type&3072)&&(a.bc==a.cj&&(a.bc=a.zl),a.cc==a.ej&&(a.cc=a.Al),a.Oc=!0);b&&a.id>13,80386>a.gc.ca||!(a.ext&64)?(a.V=2,a.R=65535):(a.V=4,a.R=-1),a.Jc=a.V,a.wa=a.R)}else a.load=a.mh,a.Cj=a.gn,a.bc=a.dj,a.cc=a.fj,a.Ab=a.qc=0,a.Lb=-1,a.Pe=!1}var Wd=1,he=2,Rd=3,Ud=4,Qd=6,Od=1; function me(a){var b=+a.model||8088,c;switch(b){default:c=4772727;break;case 80286:c=6E6;break;case 80386:c=16E6}od.call(this,a,c);this.ca=b;a=a.stepping;this.we=b+(a?ga(a,16):0);this.Ri=61442;this.Qd=1792;this.si=28672;this.Vf=4;this.Ta=255;this.B=80286<=this.ca?Gb:Fb;this.va=ne;this.bj=oe;this.ij=pe;this.nj=qe;if(80186<=this.ca&&(this.va=ne.slice(),this.bj=oe.slice(),this.ij=pe.slice(),this.Ta=31,this.va[15]=re,this.va[96]=se,this.va[97]=te,this.va[98]=ue,this.va[99]=re,this.va[100]=re,this.va[101]= re,this.va[102]=re,this.va[103]=re,this.va[104]=ze,this.va[105]=Ae,this.va[106]=Be,this.va[107]=Ce,this.va[108]=De,this.va[109]=Ee,this.va[110]=Fe,this.va[111]=Ge,this.va[192]=He,this.va[193]=Ie,this.va[200]=Je,this.va[201]=Ke,this.va[241]=Le,this.bj[7]=Me,this.ij[7]=Me,80286<=this.ca)){this.Ri=2;this.Qd|=28672;this.Vf=0;this.va[15]=Ne;this.Pd=Oe.slice();for(b=0;b=this.we&&(this.Pd[166]=We,this.Pd[167]=Xe)}}this.Yf=[];this.Zf=[];this.ag=0;rd(this);this.ea.complete=this.ea.jj=!1;this.tj=0;this.sc=this.aa=[];this.qb=this.Ng=this.Db=this.Wf=this.df=this.ef=this.ed=0;Ye(this)}ba(me,od);function Ze(a,b,c,d){b=(d?a.sc:a.aa)[b>>>a.qb];c?--b.ie||bd(b):--b.he||ad(b);d&&jc(a)} @@ -158,8 +158,8 @@ function $e(a){var b;if(a.aa===a.sc){a.aa=Array(a.Wf);a.$f=new x(null,0,0,Uc,nul function gd(a,b,c,d){var e=(b&-4194304)>>>20,f=a.sc[(a.Wc+e&a.ef)>>>a.qb],g=f.Nd(e);if(!(g&1))return d||ef.call(a,b,!1,c),a.ff;if(!(g&4)&&3==a.Ma)return d||ef.call(a,b,!0,c),a.ff;var h=(b&4190208)>>>10,g=a.sc[((g&-4096)+h&a.ef)>>>a.qb],k=g.Nd(h);if(!(k&1))return d||ef.call(a,b,!1,c),a.ff;if(!(k&4)&&3==a.Ma)return d||ef.call(a,b,!0,c),a.ff;c=a.sc[((k&-4096)+(b&4095)&a.ef)>>>a.qb];if(d)return c;d=b>>>a.qb;k=a.aa[d];b&=-4096;var m;0>2;b.mb=g;b.B=h>>2;Eb&&wc&&c.X&&!c.controller&&!c.he&&!c.ie?(b.Pa=c.Pa,b.Rd=c.Rd,b.X=c.X,pc(b,nd)):(b.F=c?hd(32):0,b.J=c?hd(96):0,pc(b,Xc));ec(b,a.ba,k);a.aa[d]=b;a.Xf.push(d);return b}function ff(a){a.aa!==a.sc&&(a.aa=a.sc,a.$f=null,a.Xf=null,a.ff=null)}l=me.prototype;l.reset=function(){this.ea.vb&&this.Jb();Ye(this);rd(this);this.ea.error=!1}; function gf(a,b){var c;switch(b){case 0:c=a.D;break;case 1:c=a.I;break;case 2:c=a.L;break;case 3:c=a.G;break;case 4:c=z(a);break;case 5:c=a.M;break;case 6:c=a.K;break;case 7:c=a.J}return c}function hf(a,b,c){switch(b){case 0:a.D=c;break;case 1:a.I=c;break;case 2:a.L=c;break;case 3:a.G=c;break;case 4:Zd(a,c);break;case 5:a.M=c;break;case 6:a.K=c;break;case 7:a.J=c}} -function Ye(a){a.D=0;a.G=0;a.I=0;a.L=0;a.Hc=0;a.M=0;a.K=0;a.J=0;a.vc=!1;a.Ba=a.$a=0;a.ya=0;a.sj=0;a.ga=0;a.qa=65520;a.Pb=0;a.cd=1023;a.O=a.lc=0;a.ze=a.hf=a.ye=a.Ae=0;a.Uc=-1;a.Ad=a.Vc=-1;a.Bd=a.ta=-1;a.Z=new Kd(a,Wd,"CS");a.Ca=new Kd(a,he,"DS");a.oa=new Kd(a,he,"ES");a.Y=new Kd(a,Rd,"SS");Zd(a,0);Yd(a,0);if(80386<=a.ca){switch(a.we){case 80562:case 80563:a.L=771;break;case 80578:a.L=772;break;case 80594:a.L=773;break;case 80595:case 80596:a.L=776}a.qa=16;a.Og=0;a.Cd=0;a.Wc=0;a.Qb=[0,0,0,0,null,null, -0,0];a.dg=[null,null,null,null,null,null,0,0];a.Ga=new Kd(a,he,"FS");a.Ha=new Kd(a,he,"GS");ff(a)}a.Qg=new Kd(a,0,"NULL");a.Da=a.Ca;a.Rb=a.Y;a.N=a.ha=0;a.C=a.F=-1;a.eb=a.Qg;a.Za=0;if(80286>a.ca)ke(a,0,65535);else{a.Kb=0;a.Pc=65535;a.Eb=new Kd(a,5,"LDT",!0);a.la=new Kd(a,Ud,"TSS",!0);a.xb=new Kd(a,Qd,"VER",!0);ke(a,65520,61440);var b,c=A(a);b=a.Z;var d=-65536;80386>b.gc.ca&&(d&=16777215);b=b.ua=d;a.da=b+c|0;a.Pg=(b>>>0)+(a.Z.Ka>>>0)+1}je(a,0);ae(a)} +function Ye(a){a.D=0;a.G=0;a.I=0;a.L=0;a.Hc=0;a.M=0;a.K=0;a.J=0;a.vc=!1;a.Ba=a.$a=0;a.bn=[0,0];a.cn=[0,0];a.ya=0;a.sj=0;a.ga=0;a.qa=65520;a.Pb=0;a.cd=1023;a.O=a.lc=0;a.ze=a.hf=a.ye=a.Ae=0;a.Uc=-1;a.Ad=a.Vc=-1;a.Bd=a.ta=-1;a.Z=new Kd(a,Wd,"CS");a.Ca=new Kd(a,he,"DS");a.oa=new Kd(a,he,"ES");a.Y=new Kd(a,Rd,"SS");Zd(a,0);Yd(a,0);if(80386<=a.ca){switch(a.we){case 80562:case 80563:a.L=771;break;case 80578:a.L=772;break;case 80594:a.L=773;break;case 80595:case 80596:a.L=776}a.qa=16;a.Og=0;a.Cd=0;a.Wc=0; +a.Qb=[0,0,0,0,null,null,0,0];a.dg=[null,null,null,null,null,null,0,0];a.Ga=new Kd(a,he,"FS");a.Ha=new Kd(a,he,"GS");ff(a)}a.Qg=new Kd(a,0,"NULL");a.Da=a.Ca;a.Rb=a.Y;a.N=a.ha=0;a.C=a.F=-1;a.eb=a.Qg;a.Za=0;if(80286>a.ca)ke(a,0,65535);else{a.Kb=0;a.Pc=65535;a.Eb=new Kd(a,5,"LDT",!0);a.la=new Kd(a,Ud,"TSS",!0);a.xb=new Kd(a,Qd,"VER",!0);ke(a,65520,61440);var b,c=A(a);b=a.Z;var d=-65536;80386>b.gc.ca&&(d&=16777215);b=b.ua=d;a.da=b+c|0;a.Pg=(b>>>0)+(a.Z.Ka>>>0)+1}je(a,0);ae(a)} function jf(a){2==a.Jc?(a.Sb=a.ja,a.kc=kf,a.tc=lf,a.kd=rf,2==a.V?(a.ma=sf,a.za=tf,a.rb=uf):(a.ma=vf,a.za=wf,a.rb=xf)):(a.Sb=a.ia,a.kc=yf,a.tc=zf,a.kd=Af,2==a.V?(a.ma=Bf,a.za=Cf,a.rb=Df):(a.ma=Ef,a.za=Ff,a.rb=Gf))}function be(a,b){a.V!=b&&(a.ha|=1024,a.V=b,a.R=2==b?65535:-1,Hf(a))}function Hf(a){2==a.V?(a.Fb=32768,a.hb=a.ja,a.yb=a.fb,2==a.Jc?(a.ma=sf,a.za=tf,a.rb=uf):(a.ma=Bf,a.za=Cf,a.rb=Df)):(a.Fb=-2147483648,a.hb=a.ia,a.yb=a.ab,2==a.Jc?(a.ma=vf,a.za=wf,a.rb=xf):(a.ma=Ef,a.za=Ff,a.rb=Gf))} function If(a){a.Jc=a.Z.Jc;a.wa=a.Z.wa;jf(a);a.V=a.Z.V;a.R=a.Z.R;Hf(a);a.ha&=-3073}l.uj=function(){var a=this.D+this.G+this.I+this.L+z(this)+this.M+this.K+this.J|0;return a=a+A(this)+this.Z.U+this.Ca.U+this.Y.U+this.oa.U+ie(this)|0};function Jf(a,b,c){void 0===a.Yf[b]&&(a.Yf[b]=[]);a.Yf[b].push(c)}function Kf(a,b,c){c&&(null==a.Zf[b]&&a.ag++,a.Zf[b]=c)}function Lf(a,b){var c=a.Zf[b];null!=c&&(c(--a.ag),delete a.Zf[b])} function Mf(a,b){for(var c=a.Qb[7],d=c>>16,e=0;4>e;e++){if(c&3){var f=!!(d&1),g=a.Qb[e],g=g&~(d>>2&3);b?a.aa[g>>>a.qb].Gd(g&a.Db,f,a):(g=a.aa[g>>>a.qb],f?--g.ie||bd(g):--g.he||ad(g))}c>>=2;d>>=4}}function ed(a,b,c,d){if(!(a.N&8192)&&a.Qb[7]&255){c--;var e=a.Qb[7],f=e>>16;d=d?1:0==d?3:0;for(var g=0;4>g;g++){if(e&3&&(f&3)==d){var h=f>>2;if(b+c>=a.Qb[g]&&b<=a.Qb[g]+h){a.Qb[6]|=1<>=2;f>>=4}}} @@ -237,7 +237,7 @@ function Fj(a,b){var c=a-b|0;Uf(this,a,b,c,this.Fb|63,!0);this.A-=-1===this.F?-1 function Jj(a,b){return b>>(this.D&this.R)&(1<<(this.I&31))-1&this.R}function Kj(a,b){if(-1===this.C){switch(this.ga&7){case 0:this.D=this.D&-256|a;break;case 1:this.I=this.I&-256|a;break;case 2:this.L=this.L&-256|a;break;case 3:this.G=this.G&-256|a;break;case 4:this.D=this.D&-65281|a<<8;break;case 5:this.I=this.I&-65281|a<<8;break;case 6:this.L=this.L&-65281|a<<8;break;case 7:this.G=this.G&-65281|a<<8}this.A-=this.B.di}else this.F=this.C,vg(this,a),this.A-=this.B.ci;return b} function Lj(a,b){if(-1===this.C){switch(this.ga&7){case 0:this.D=this.D&~this.R|a;break;case 1:this.I=this.I&~this.R|a;break;case 2:this.L=this.L&~this.R|a;break;case 3:this.G=this.G&~this.R|a;break;case 4:Zd(this,z(this)&~this.R|a);break;case 5:this.M=this.G&~this.R|a;break;case 6:this.K=this.K&~this.R|a;break;case 7:this.J=this.J&~this.R|a}this.A-=this.B.di}else this.F=this.C,this.N&2||this.yb(this.eb.cc(this.wb,this.V),a),this.A-=this.B.ci;return b} function Mj(a,b){a^=b;ag(this,a,128);this.A-=-1===this.F?-1===this.C?this.B.mc:this.B.Ib:this.B.Ac;return a}function Nj(a,b){this.A-=-1===this.F?-1===this.C?this.B.mc:this.B.Ib:this.B.Ac;return ag(this,a^b,this.Fb)&this.R}function Oj(a,b){var c=a[1]-b[1];c||(c=a[0]-b[0]);return c}function Pj(a){var b=a-1|0;Uf(this,a,1,b,this.Fb|62,!0);this.A-=2;return a&~this.R|b&this.R} -function Qj(a,b,c){c>>>=0;if(!c||c<=b>>>0)return!1;var d=0,e=1;c=[c>>>0,0];for(a=[a>>>0,b>>>0];0>>=0,b[1]++);e+=e}do 0<=Oj(a,c)&&(b=a,f=c,b[0]-=f[0],b[1]-=f[1],0>b[0]&&(b[0]>>>=0,b[1]--),d+=e),b=c,b[0]>>>=1,b[1]&1&&(b[0]=(b[0]|2147483648)>>>0),b[1]>>>=1,e/=2;while(1<=e);this.Ba=d;this.$a=a[0];return!0}function Rj(a){var b=a+1|0;Uf(this,a,1,b,this.Fb|62);this.A-=2;return a&~this.R|b&this.R} +function Qj(a,b,c){c>>>=0;if(!c||c<=b>>>0)return!1;var d=0,e=1,f=this.bn;f[0]=c>>>0;f[1]=0;c=this.cn;c[0]=a>>>0;for(c[1]=b>>>0;0>>=0,a[1]++),e+=e;do 0<=Oj(c,f)&&(a=c,b=f,a[0]-=b[0],a[1]-=b[1],0>a[0]&&(a[0]>>>=0,a[1]--),d+=e),a=f,a[0]>>>=1,a[1]&1&&(a[0]=(a[0]|2147483648)>>>0),a[1]>>>=1,e/=2;while(1<=e);this.Ba=d;this.$a=c[0];return!0}function Rj(a){var b=a+1|0;Uf(this,a,1,b,this.Fb|62);this.A-=2;return a&~this.R|b&this.R} function Sj(a){this.qa=a;ae(this);this.qa&-2147483648?$e(this):ff(this)}function le(a){this.Wc=a;jc(this)}function Tj(a){this.N|=1;this.tc.call(this,a);this.A-=-1===this.C?4:5}function tj(a,b,c){if(c){16>>16-c)&65535;ag(this,a,32768,d&32768)}return a}function vj(a,b,c){if(c){var d=a<>>32-c;ag(this,a,-2147483648,d&-2147483648)}return a} function zj(a,b,c){if(c){16>>c-1;a=(d>>>1|b<<16-c)&65535;ag(this,a,32768,d&1)}return a}function Bj(a,b,c){if(c){var d=a>>>c-1;a=d>>>1|b<<32-c;ag(this,a,-2147483648,d&1)}return a}function Uj(){this.A-=-1===this.C?2:this.B.Fk;return 1}function Vj(){var a=this.I&255;this.A-=(-1===this.C?this.B.Vh:this.B.Uh)+(a<c?c=c?c:12:c=(c-=12)?c+128:140,d=!0);a.A[Hd]&Im||(d&&128>8} -l.save=function(){var a=new Of(this);a.set(0,[this.C]);for(var b=[],c=0;c=Kg&&(a.set(5,[this.D,this.L,this.K,this.oa,this.M,this.qa]),a.set(6,[this.Y[7],this.Y,this.W,this.A,this.ma,this.Z]));return a.data()}; l.restore=function(a){var b,c;b=a[0];Array.isArray(b[0])?this.C=b[0]:(this.C[0][0]=b[0],this.C[1][0]=b[1]&15,this.C[0][1]=b[2],this.C[1][1]=b[3]&15);wl(this);b=a[1];for(c=0;c=f;f++){var g="pcjs-bitCell";f||(g+=" pcjs-bitCellLeft");d+='
'+f+"
\n"}e.innerHTML=d;Tm(a,b,c,!0)}function Um(a,b,c){if(b=(a=Vm[a.ca|0])&&a[b])for(var d in b)if(a=b[d],a.yc&1<g.nb[0]&&(g.nb[0]=255,g.nb[1]--,0>g.nb[1]&&(g.nb[1]=255)));return h}function jn(a,b,c,d,e,f){var g=a.F[b];t(a,768)&&v(a,d,e,f,"DMA"+b+".CHANNEL"+c+".COUNT["+g.zb+"]",null,!0);a=g.oc[c];a.nb[g.zb]=a.pc[g.zb]=e;g.zb^=1}function kn(a,b,c,d){var e=a.F[b],f=e.Mb|ln;e.Mb&=~mn;t(a,768)&&v(a,c,null,d,"DMA"+b+".STATUS",f,!0);return f} -function nn(a,b,c,d,e){var f=a.F[b];t(a,768)&&v(a,c,d,e,"DMA"+b+".REQ",null,!0);a=d&3;f.Mb=f.Mb&~(16<>2].oc[b&3],c,d,e)}function rn(a,b,c){b=a.F[b>>2].oc[b&3];b.mg&&b.eh&&b.Ag?(c&&(b.done=c),b.ne||Bn(a,b,!0)):c&&c(!0)} function Bn(a,b,c){c&&(b.count=b.nb[1]<<8|b.nb[0],b.type=b.mode&Cn,b.rj=b.ng=!1);for(var d=!1;0<=b.count&&(c=b.hg<<16|b.sb[1]<<8|b.sb[0],b.type==Dn?(d=!0,function(c){b.eh.call(b.mg,b.Ag,-1,function(e,g){0>e&&(b.rj||(b.rj=!0),e=255);b.ne||a.ka.fc(c,e);(d=g)&&setTimeout(function(){En(b)||Bn(a,b)},0)})}(c)):b.type==Fn?(c=a.ka.Ia(c),0>b.eh.call(b.mg,b.Ag,c)&&(b.ng=!0)):b.type!=Gn&&(b.ng=!0)),!d&&!En(b););} @@ -454,20 +454,20 @@ function po(a,b,c,d,e){v(a,c,d,e,"PIT"+b+".CTRL",null,2048);e=0;c=d&qo;b?(e=3,a. a.J==(Do|Eo|Fo|Go)&&(b=a.G[0],b.Mc[0]=b.pc[0],b.Mc[1]=b.pc[1],b.jd=vd(a.H,a.O))}}}function mo(a,b){a=a.G[b];(b=a.pc[1]<<8|a.pc[0])||(b=1==a.ad?256:65536);return b}function Fd(a,b){a=a.G[b];(b=a.Mc[1]<<8|a.Mc[0])||(b=1==a.ad?256:65536);return b}function xo(a,b){sl(a,b);var c=a.G[b];c.je[0]=c.nb[0];c.je[1]=c.nb[1];c.Me=!0;fo(a,b)}function fo(a,b){a=a.G[b];a.rd=a.Qf==Ho?1:0;a.ad=a.Qf==Io?2:1} function sl(a,b,c){var d=a.G[b];if(d.le&&(b!=no||a.J&Do)){var e=vd(a.H,a.O),f=(e-d.jd)/a.Ba|0;0>f&&(d.jd=e,f=0);var g=mo(a,b),h=Fd(a,b)-f;d.mode==ho?(0>=h&&(h=0),h||(d.gd=!0,d.le=!1,b||Zg(a,lo))):d.mode==Jo?(d.gd=1!=h,0>=h&&(h=g+h,0>=h&&(h=g),d.Mc[0]=h&255,d.Mc[1]=h>>8&255,d.jd=e,!b&&d.gd&&Zg(a,lo))):d.mode==Gd&&(h-=f,0>=h&&(d.gd=!d.gd,h=g+h,0>=h&&(h=g),d.Mc[0]=h&255,d.Mc[1]=h>>8&255,d.jd=e,!b&&d.gd&&Zg(a,lo)));d.nb[0]=h&255;d.nb[1]=h>>8&255;c&&(a.jd=0)}return d} function Ed(a,b){for(var c=0;c=Kg){b=a.H.T.sd;c=vd(a.H,a.O);null==a.Aa&&(a.ma=vd(a.H,a.O),a.Ha=1024,a.Aa=Math.floor(a.H.T.sd/a.Ha),Km(a));c>=a.Z&&(a.A[Gm]|=Ko,a.A[Hd]&Id&&(a.A[Gm]|=Lo,Zg(a,Mo)),a.Z=c+a.Aa);a.A[cm]==a.A[dm]&&a.A[em]==a.A[fm]&&a.A[gm]==a.A[hm]&&(a.A[Gm]|=No,a.A[Hd]&Oo&&(a.A[Gm]|=Lo,Zg(a,Mo)));var d=c-a.ma,e=Math.floor(d/b);if(e&&!(a.A[Hd]&Po)){for(;e--;)if(60<=++a.A[cm]&&(a.A[cm]=0,60<=++a.A[em]&&(a.A[em]=0,24<=++a.A[gm]))){a.A[gm]=0;a.A[im]=a.A[im]% -7+1;var f;f=a.A[Cm];var g=Aa[a.A[Bm]-1];28==g&&(f%4||!(f%100)&&f%400||g++);f=g;++a.A[Am]>f&&(a.A[Am]=1,12<++a.A[Bm]&&(a.A[Bm]=1,a.A[Cm]=(a.A[Cm]+1)%100))}a.A[Gm]|=Qo;a.A[Hd]&Ro&&(a.A[Gm]|=Lo,Zg(a,Mo))}a.ma=c-d%b}}l.Gm=function(a,b){var c=this.ya;this.ga&So&&(this.J&To?c=this.C[0][1]:this.B&&(c=Uo(this.B)));v(this,a,null,b,"PPI_A",c);return c};l.ho=function(a,b,c){v(this,a,b,c,"PPI_A");this.ya=b};l.Hm=function(a,b){var c=this.J;v(this,a,null,b,"PPI_B",c);return c}; -l.io=function(a,b,c){v(this,a,b,c,"PPI_B");Vo(this,b)};function Vo(a,b){var c=!!(b&Wo),d=!!(a.J&Wo);a.J=b;a.B&&Xo(a.B,!(b&To),!!(b&Go));c!=d&&Mm(a,c)}l.Im=function(a,b){var c=0,c=(this.ca|0)==bl?this.J&Eo?c|this.C[1][1]&Yo:c|this.C[1][1]>>4&1:this.J&Zo?c|this.C[0][1]>>4:c|this.C[0][1]&15;this.J&Do&&sl(this,no).gd&&(c=this.J&Wo?c|$o:c|ap);v(this,a,null,b,"PPI_C",c,32896);return c};l.jo=function(a,b,c){v(this,a,b,c,"PPI_C");this.Ca=b};l.Jm=function(a,b){var c=this.ga;v(this,a,null,b,"PPI_CTRL",c);return c}; -l.ko=function(a,b,c){v(this,a,b,c,"PPI_CTRL");this.ga=b};l.Sl=function(a,b){var c=this.B?Uo(this.B):0;v(this,a,null,b,"8041_KBD",c);this.aa&=~bp;return c};l.rn=function(a,b,c){v(this,a,b,c,"8041_KBD")};l.Rl=function(a,b){var c=this.J;v(this,a,null,b,"8041_CTRL",c);return c};l.qn=function(a,b,c){v(this,a,b,c,"8041_CTRL");Vo(this,b)};l.Tl=function(a,b){var c=this.aa;v(this,a,null,b,"8041_STATUS",c);return c}; +7+1;var f;f=a.A[Cm];var g=Aa[a.A[Bm]-1];28==g&&(f%4||!(f%100)&&f%400||g++);f=g;++a.A[Am]>f&&(a.A[Am]=1,12<++a.A[Bm]&&(a.A[Bm]=1,a.A[Cm]=(a.A[Cm]+1)%100))}a.A[Gm]|=Qo;a.A[Hd]&Ro&&(a.A[Gm]|=Lo,Zg(a,Mo))}a.ma=c-d%b}}l.Gm=function(a,b){var c=this.ya;this.ga&So&&(this.J&To?c=this.C[0][1]:this.B&&(c=Uo(this.B)));v(this,a,null,b,"PPI_A",c);return c};l.jo=function(a,b,c){v(this,a,b,c,"PPI_A");this.ya=b};l.Hm=function(a,b){var c=this.J;v(this,a,null,b,"PPI_B",c);return c}; +l.ko=function(a,b,c){v(this,a,b,c,"PPI_B");Vo(this,b)};function Vo(a,b){var c=!!(b&Wo),d=!!(a.J&Wo);a.J=b;a.B&&Xo(a.B,!(b&To),!!(b&Go));c!=d&&Mm(a,c)}l.Im=function(a,b){var c=0,c=(this.ca|0)==bl?this.J&Eo?c|this.C[1][1]&Yo:c|this.C[1][1]>>4&1:this.J&Zo?c|this.C[0][1]>>4:c|this.C[0][1]&15;this.J&Do&&sl(this,no).gd&&(c=this.J&Wo?c|$o:c|ap);v(this,a,null,b,"PPI_C",c,32896);return c};l.lo=function(a,b,c){v(this,a,b,c,"PPI_C");this.Ca=b};l.Jm=function(a,b){var c=this.ga;v(this,a,null,b,"PPI_CTRL",c);return c}; +l.mo=function(a,b,c){v(this,a,b,c,"PPI_CTRL");this.ga=b};l.Sl=function(a,b){var c=this.B?Uo(this.B):0;v(this,a,null,b,"8041_KBD",c);this.aa&=~bp;return c};l.tn=function(a,b,c){v(this,a,b,c,"8041_KBD")};l.Rl=function(a,b){var c=this.J;v(this,a,null,b,"8041_CTRL",c);return c};l.sn=function(a,b,c){v(this,a,b,c,"8041_CTRL");Vo(this,b)};l.Tl=function(a,b){var c=this.aa;v(this,a,null,b,"8041_STATUS",c);return c}; l.Ul=function(a,b){var c=this.oa;v(this,a,null,b,"8042_OUTBUFF",c,16384);this.D&=~(bp|cp);this.B&&dp(this.B);return c}; -l.tn=function(a,b,c){v(this,a,b,c,"8042_INBUF.DATA",null,16384);if(this.D&ep)switch(this.L){case fp:gp(this,b);break;case hp:ip(this,b);break;default:if(gp(this,this.K&~Fl),this.B){a=this.B;c=b;var d=-1;t(a)&&Cb(a,"sendCmd("+r(c)+")");switch(a.F||c){case jp:d=kp;lp(a);break;case mp:a.F&&(c=0);np(a,kp);a.F=c;break;case op:a.F&&(c=0);np(a,kp);a.F=c;break;default:Cb(a,"sendCmd(): unrecognized command")}pp(this,d)}}this.L=b;this.D&=~ep}; -l.Vl=function(a,b){var c=this.J&~(qp|rp)|(vd(this.H)&64?rp:0);v(this,a,null,b,"8042_RWREG",c,16384);return c};l.un=function(a,b,c){v(this,a,b,c,"8042_RWREG",null,16384);Vo(this,b)};l.Wl=function(a,b){v(this,a,null,b,"8042_STATUS",this.D,16384);a=this.D&255;this.D&cp&&(this.D|=bp,this.D&=~cp);return a}; -l.sn=function(a,b,c){v(this,a,b,c,"8042_INBUFF.CMD",null,16384);this.L=b;this.D|=ep;a=0;this.L>=sp&&(a=this.L^15,this.L=sp);switch(this.L){case tp:pp(this,this.K);break;case up:gp(this,this.K|Fl);break;case vp:gp(this,this.K&~Fl);this.B&&dp(this.B);break;case wp:this.B&&(a=this.B,a.A=[],t(a)&&Cb(a,"scan codes flushed"));gp(this,this.K|Fl);pp(this,xp);ip(this,Pl|Ql);break;case yp:pp(this,zp);break;case Ap:pp(this,this.M);break;case Bp:pp(this,this.qa);break;case Cp:pp(this,this.K&Fl?0:Dp);break;case sp:a& +l.vn=function(a,b,c){v(this,a,b,c,"8042_INBUF.DATA",null,16384);if(this.D&ep)switch(this.L){case fp:gp(this,b);break;case hp:ip(this,b);break;default:if(gp(this,this.K&~Fl),this.B){a=this.B;c=b;var d=-1;t(a)&&Cb(a,"sendCmd("+r(c)+")");switch(a.F||c){case jp:d=kp;lp(a);break;case mp:a.F&&(c=0);np(a,kp);a.F=c;break;case op:a.F&&(c=0);np(a,kp);a.F=c;break;default:Cb(a,"sendCmd(): unrecognized command")}pp(this,d)}}this.L=b;this.D&=~ep}; +l.Vl=function(a,b){var c=this.J&~(qp|rp)|(vd(this.H)&64?rp:0);v(this,a,null,b,"8042_RWREG",c,16384);return c};l.wn=function(a,b,c){v(this,a,b,c,"8042_RWREG",null,16384);Vo(this,b)};l.Wl=function(a,b){v(this,a,null,b,"8042_STATUS",this.D,16384);a=this.D&255;this.D&cp&&(this.D|=bp,this.D&=~cp);return a}; +l.un=function(a,b,c){v(this,a,b,c,"8042_INBUFF.CMD",null,16384);this.L=b;this.D|=ep;a=0;this.L>=sp&&(a=this.L^15,this.L=sp);switch(this.L){case tp:pp(this,this.K);break;case up:gp(this,this.K|Fl);break;case vp:gp(this,this.K&~Fl);this.B&&dp(this.B);break;case wp:this.B&&(a=this.B,a.A=[],t(a)&&Cb(a,"scan codes flushed"));gp(this,this.K|Fl);pp(this,xp);ip(this,Pl|Ql);break;case yp:pp(this,zp);break;case Ap:pp(this,this.M);break;case Bp:pp(this,this.qa);break;case Cp:pp(this,this.K&Fl?0:Dp);break;case sp:a& 1&&Ye(this.H)}};function gp(a,b){a.K=b;a.D=a.D&~Ep|b&Fp;a.B&&Xo(a.B,!!(b&Gp),!(b&Fl))}function pp(a,b,c){0<=b&&(a.oa=b,c?a.D|=bp:(a.D&=~bp,a.D|=cp))}function ip(a,b){a.qa=b;fc(a.ka,!!(b&Ql));b&Pl||Ye(a.H)}function Hp(a,b){a.ca>4)+(c&15),e=!0);if(d==gm||d==hm)e&&23=c?c=12==c?0:c:(c-=116,c=24==c?12:c))}}else c=b;this.A[d]=c;d==Hd&&a&Id&&b&Id&&Km(this)};l.Nk=function(a,b,c){v(this,a,b,c,"NMI");this.da=b};l.Tn=function(a,b,c){v(this,a,b,c,"FPU.CLEAR")};l.Un=function(a,b,c){v(this,a,b,c,"FPU.RESET");this.Tc&&Fg(this.Tc)}; +l.Kn=function(a,b,c){v(this,a,b,c,"CMOS.ADDR",null,4096);this.W=b;this.da=b&Lp?$g:Mp};l.km=function(a,b){var c=this.W&Np,d=c<=ul?vl(this,c):this.A[c];t(this,4352)&&v(this,a,null,b,"CMOS.DATA["+r(c)+"]",d,!0);null!=b&&c==Gm&&(this.A[c]&=Op,d&Lo&&Lg(this,Mo),d&Ko&&this.A[Hd]&Id&&Km(this));return d}; +l.Ln=function(a,b,c){var d=this.W&Np;t(this,4352)&&v(this,a,b,c,"CMOS.DATA["+r(d)+"]",null,!0);a=b^this.A[d];if(d<=ul){if(c=b,d>4)+(c&15),e=!0);if(d==gm||d==hm)e&&23=c?c=12==c?0:c:(c-=116,c=24==c?12:c))}}else c=b;this.A[d]=c;d==Hd&&a&Id&&b&Id&&Km(this)};l.Nk=function(a,b,c){v(this,a,b,c,"NMI");this.da=b};l.Vn=function(a,b,c){v(this,a,b,c,"FPU.CLEAR")};l.Wn=function(a,b,c){v(this,a,b,c,"FPU.RESET");this.Tc&&Fg(this.Tc)}; l.Xm=function(a){if(t(this,16)&&Nk(this.ba,26,a)){var b=this.H.D>>8;Kf(this.H,a,function(a,d){return function(c){d=vd(a.H)-d;var e,g=a.H.L&255,h=a.H.L>>8,k=a.H.L&255,m=a.H.L>>8;if(2==b||3==b)e=" CH(hour)="+ka(h)+" CL(min)="+r(g)+" DH(sec)="+r(m);else if(4==b||5==b)e=" CX(year)="+ka(a.H.I)+" DH(month)="+r(m)+" DL(day)="+r(k);g=a.ba;h=d;g.message("INT "+r(26)+": C="+(Vf(g.H)?1:0)+(e||"")+" (cycles="+h+(c?",level="+(c+1):"")+")")}}(this,vd(this.H)))}return!0}; function Mm(a,b){if(a.la)try{void 0!==b?a.Ga=b:b=!!(a.Ga&&a.H&&a.H.ea.vb);var c=Math.round(fl/mo(a,no));if(20>c||2E4>>4,0,this.F,this.C,this.Fd),delete this.Fd);return!0};Pp.prototype.Wb=function(){return!0}; @@ -511,14 +511,14 @@ e.A.push(f),1==e.A.length&&e.W&&Hp(e.W,f)):(e.A.length==Zq&&e.A.push($q),Cb(e,"s var qq={TAB:1009,ESC:1027,F1:1112,F2:1113,F3:1114,F4:1115,F5:1116,F6:1117,F7:1118,F8:1119,F9:1120,F10:1121,LEFT:1037,UP:1038,RIGHT:1039,DOWN:1040,SYSREQ:4027,CTRL_C:ar,CTRL_BREAK:Mq,CTRL_ALT_DEL:4046,CTRL_ALT_INS:4045,CTRL_ALT_ENTER:4013},sq={esc:1027,1:n["1"],2:n["2"],3:n["3"],4:n["4"],5:n["5"],6:n["6"],7:n["7"],8:n["8"],9:n["9"],0:n["0"],"-":n["-"],"=":n["="],bs:1008,tab:1009,q:n.Q,w:n.Mi,e:n.E,r:n.Hi,t:n.Ji,y:n.Oi,u:n.Ki,i:n.zi,o:n.Fi,p:n.Gi,"[":n["["],"]":n["]"],enter:13,ctrl:1017,a:n.ce,s:n.Ii, d:n.vi,f:n.wi,g:n.xi,h:n.yi,j:n.Ai,k:n.Bi,l:n.Ci,";":n[";"],quote:n["'"],"`":n["`"],shift:1016,"\\":n["\\"],z:n.eg,x:n.Ni,c:n.ui,v:n.Li,b:n.ti,n:n.Ei,m:n.Di,",":n[","],".":n["."],"/":n["/"],"right-shift":3016,prtsc:1044,alt:1018,space:1032,"caps-lock":nq,f1:1112,f2:1113,f3:1114,f4:1115,f5:1116,f6:1117,f7:1118,f8:1119,f9:1120,f10:1121,"num-lock":oq,"scroll-lock":pq,"num-home":1036,"num-up":1038,"num-pgup":1033,"num-sub":1109,"num-left":1037,"num-center":1101,"num-right":1039,"num-add":1107,"num-end":1035, "num-down":1040,"num-pgdn":1034,"num-ins":1045,"num-del":1046,sysreq:84},Dq={"caps-lock":Jq,"num-lock":1024,"scroll-lock":2048},O={1027:1};O[n["1"]]=2;O[n["!"]]=2|P<<8;O[n["2"]]=3;O[n["@"]]=3|P<<8;O[n["3"]]=4;O[n["#"]]=4|P<<8;O[n["4"]]=5;O[n.$]=5|P<<8;O[n["5"]]=6;O[n["%"]]=6|P<<8;O[n["6"]]=7;O[n["^"]]=7|P<<8;O[n["7"]]=8;O[n["&"]]=8|P<<8;O[n["8"]]=9;O[n["*"]]=9|P<<8;O[n["9"]]=10;O[n["("]]=10|P<<8;O[n["0"]]=11;O[n[")"]]=11|P<<8;O[n["-"]]=12;O[n._]=12|P<<8;O[n["="]]=13;O[n["+"]]=13|P<<8;O[1008]=Qq; -O[1009]=15;O[n.q]=16;O[n.Q]=16|P<<8;O[n.Qo]=17;O[n.Mi]=17|P<<8;O[n.e]=18;O[n.E]=18|P<<8;O[n.r]=19;O[n.Hi]=19|P<<8;O[n.t]=20;O[n.Ji]=20|P<<8;O[n.y]=21;O[n.Oi]=21|P<<8;O[n.Oo]=22;O[n.Ki]=22|P<<8;O[n.Kl]=23;O[n.zi]=23|P<<8;O[n.on]=24;O[n.Fi]=24|P<<8;O[n.p]=25;O[n.Gi]=25|P<<8;O[n["["]]=26;O[n["{"]]=26|P<<8;O[n["]"]]=27;O[n["}"]]=27|P<<8;O[13]=28;O[1017]=Vq;O[n.de]=30;O[n.ce]=30|P<<8;O[n.Lo]=31;O[n.Ii]=31|P<<8;O[n.d]=32;O[n.vi]=32|P<<8;O[n.Hl]=33;O[n.wi]=33|P<<8;O[n.Il]=34;O[n.xi]=34|P<<8;O[n.Jl]=35; -O[n.yi]=35|P<<8;O[n.an]=36;O[n.Ai]=36|P<<8;O[n.k]=37;O[n.Bi]=37|P<<8;O[n.bn]=38;O[n.Ci]=38|P<<8;O[n[";"]]=39;O[n[":"]]=39|P<<8;O[n["'"]]=40;O[n['"']]=40|P<<8;O[n["`"]]=41;O[n["~"]]=41|P<<8;O[1016]=P;O[n["\\"]]=43;O[n["|"]]=43|P<<8;O[n.z]=44;O[n.eg]=44|P<<8;O[n.x]=45;O[n.Ni]=45|P<<8;O[n.xl]=46;O[n.ui]=46|P<<8;O[n.Po]=47;O[n.Li]=47|P<<8;O[n.vl]=48;O[n.ti]=48|P<<8;O[n.n]=49;O[n.Ei]=49|P<<8;O[n.fn]=50;O[n.Di]=50|P<<8;O[n[","]]=51;O[n["<"]]=51|P<<8;O[n["."]]=52;O[n[">"]]=52|P<<8;O[n["/"]]=53; +O[1009]=15;O[n.q]=16;O[n.Q]=16|P<<8;O[n.So]=17;O[n.Mi]=17|P<<8;O[n.e]=18;O[n.E]=18|P<<8;O[n.r]=19;O[n.Hi]=19|P<<8;O[n.t]=20;O[n.Ji]=20|P<<8;O[n.y]=21;O[n.Oi]=21|P<<8;O[n.Qo]=22;O[n.Ki]=22|P<<8;O[n.Kl]=23;O[n.zi]=23|P<<8;O[n.qn]=24;O[n.Fi]=24|P<<8;O[n.p]=25;O[n.Gi]=25|P<<8;O[n["["]]=26;O[n["{"]]=26|P<<8;O[n["]"]]=27;O[n["}"]]=27|P<<8;O[13]=28;O[1017]=Vq;O[n.de]=30;O[n.ce]=30|P<<8;O[n.No]=31;O[n.Ii]=31|P<<8;O[n.d]=32;O[n.vi]=32|P<<8;O[n.Hl]=33;O[n.wi]=33|P<<8;O[n.Il]=34;O[n.xi]=34|P<<8;O[n.Jl]=35; +O[n.yi]=35|P<<8;O[n.an]=36;O[n.Ai]=36|P<<8;O[n.k]=37;O[n.Bi]=37|P<<8;O[n.dn]=38;O[n.Ci]=38|P<<8;O[n[";"]]=39;O[n[":"]]=39|P<<8;O[n["'"]]=40;O[n['"']]=40|P<<8;O[n["`"]]=41;O[n["~"]]=41|P<<8;O[1016]=P;O[n["\\"]]=43;O[n["|"]]=43|P<<8;O[n.z]=44;O[n.eg]=44|P<<8;O[n.x]=45;O[n.Ni]=45|P<<8;O[n.xl]=46;O[n.ui]=46|P<<8;O[n.Ro]=47;O[n.Li]=47|P<<8;O[n.vl]=48;O[n.ti]=48|P<<8;O[n.n]=49;O[n.Ei]=49|P<<8;O[n.hn]=50;O[n.Di]=50|P<<8;O[n[","]]=51;O[n["<"]]=51|P<<8;O[n["."]]=52;O[n[">"]]=52|P<<8;O[n["/"]]=53; O[n["?"]]=53|P<<8;O[3016]=54;O[1044]=55;O[1018]=Xq;O[1032]=57;O[nq]=58;O[1112]=59;O[1113]=60;O[1114]=61;O[1115]=62;O[1116]=63;O[1117]=64;O[1118]=65;O[1119]=66;O[1120]=67;O[1121]=68;O[oq]=69;O[pq]=70;O[1036]=71;O[1038]=72;O[1033]=73;O[1109]=74;O[1037]=75;O[1101]=76;O[1039]=77;O[1107]=78;O[1035]=79;O[1040]=80;O[1034]=81;O[1045]=82;O[1046]=Rq;O[4027]=84;O[1122]=87;O[1123]=88;O[1091]=91;O[1093]=93;O[1224]=91;O[ar]=46|Vq<<8;O[Mq]=70|Vq<<8;O[4046]=Rq|Vq<<8|Xq<<16;O[4045]=82|Vq<<8|Xq<<16; O[4013]=28|Vq<<8|Xq<<16;var jp=255,mp=243,op=237,xq=170,kp=250,$q=255,Zq=20;Ra(function(){for(var a=pb(document,"pcx86","keyboard"),b=0;bc.length)c=[!1,0,null,null,0,Array(b>2,32768));this.rc=c[0];this.Xc=c[1];this.Ze=c[2];this.fa=c[3];this.ec=c[4]&255;this.Fg=c[4]>>8&255;this.Wa=c[5];this.oh=dr;this.gg=fr;if(b>=Tp){this.oh=er;this.gg=gr;(b=c[6])||(b=[!1,0,Array(hr),0,f== Ll?0:ir,0,0,Array(jr),0,0,0,Array(kr),0,[this.bb,this.Ob,this.qd],Array(this.qd>>2),lr|mr|nr|or|pr,0,-1,0,-1,0,-1,0,0,0,0,qr,rr,0,0,sr,Array(tr)]);this.Ke=b[0];this.wd=b[1];this.Cc=b[2];this.Tg=ur;this.Ig=b[3];this.af=b[4];this.Pf=b[5];this.zd=b[6];this.$d=b[7];this.Vg=vr;this.Wk=b[8];this.Xk=b[9];this.yd=b[10];this.xd=b[11];this.Ug=wr;this.tb=b[12];d=b[13];"number"==typeof d&&(d=[this.bb,this.Ob,d]);this.bb=d[0];this.Ob=d[1];d=this.qd>>2;if((this.fd=b[14])&&this.fd.length=Tp){var c=[];c[0]=a.Ke;c[1]=a.wd;c[2]=a.Cc;c[3]=a.Ig;c[4]=a.af;c[5]=a.Pf;c[6]=a.zd;c[7]=a.$d;c[8]=a.Wk;c[9]=a.Xk;c[10]=a.yd;c[11]=a.xd;c[12]=a.tb;c[13]=[a.bb,a.Ob,a.qd];var d;if(d=a.fd){var e=0,f=[];if(void 0!==d[0])for(var g=0;2>g;g++)for(var h=g;h>1;f[e++]=k;h=m}f.length>8|(u&255)<<8;var I=e,T=16;m>=h))>>(T-=h);Ss(a.Ha,m++,p,b[ua])}m>F&&(F=m);p=H&&(H=p+1)}k+=2;d++;if(m>=a.F){m=0;p+=2;if(p>a.J)break;p==a.J&&(p=1,k=c+a.Pb)}}a.ma=!0; wa.F?a.Ma-a.F-u>>3:0;c>=8;b>w&&(w=b);m=K&&(K=m+1)}c+=H;if(b>=a.F){b=0;if(++m>a.J)break;c+=I}}u||(a.ma=!0);pa.F?a.Ma-a.F-K>>3:0;cI&&(T=I)):(u<<=K,T-=K,a.ma=!1):(a.ma&&u===a.M[d]?(h+=T,T=0):a.M[d]=u,d++);if(T){hp&&(p=h);b=F&&(F=b+1)}if(h>=a.F){h=0;if(++b>a.J)break;c+=H}}K||(a.ma=!0);ma&&(b.wh=a,a=-a|0);a%b.rh>b.jn&&(c|=1);a%b.uh>b.mn&&(c|=9);b.fi=a/b.uh|0;return c}l.Cm=function(a,b){return Qt(this,this.Y,a,b)};l.co=function(a,b,c){var d=this.Y;d.Fg=d.ec;d.ec=b&31;v(this,a,b,c,"CRTC.INDX")};l.Bm=function(a,b){return Rt(this,this.Y,a,b)};l.bo=function(a,b,c){St(this,this.Y,a,b,c)};l.Dm=function(a,b){return Tt(this,this.Y,b)};l.eo=function(a,b,c){a=this.Y;v(this,a.port+4,b,c,"MODE");a.Xc=b;Fs(this,!1)}; +a.la))}}}}function Ot(a,b){var c=0;a=vd(a.H)-b.wh;0>a&&(b.wh=a,a=-a|0);a%b.rh>b.mn&&(c|=1);a%b.uh>b.on&&(c|=9);b.fi=a/b.uh|0;return c}l.Cm=function(a,b){return Qt(this,this.Y,a,b)};l.fo=function(a,b,c){var d=this.Y;d.Fg=d.ec;d.ec=b&31;v(this,a,b,c,"CRTC.INDX")};l.Bm=function(a,b){return Rt(this,this.Y,a,b)};l.eo=function(a,b,c){St(this,this.Y,a,b,c)};l.Dm=function(a,b){return Tt(this,this.Y,b)};l.ho=function(a,b,c){a=this.Y;v(this,a.port+4,b,c,"MODE");a.Xc=b;Fs(this,!1)}; l.Em=function(a,b){return Ut(this,this.Y,b)};l.Mk=function(a,b,c){this.A.Pf=this.A.Pf&-4|b&3;v(this,a,b,c,"FEAT")};l.am=function(a,b){a=this.A.wd;b&&!t(this)||v(this,960,null,b,"ATC.INDX",a);return a};l.rl=function(a,b){a=this.A.Cc[this.A.wd&31];b&&!t(this)||v(this,960,null,b,"ATC."+this.A.Tg[this.A.wd&31],a);return a}; l.Lk=function(a,b,c){var d=this.A,e=d.wd&32;if(d.Ke){d.Ke=!1;var f=d.wd&31;if(16<=f||!e)if(Vt||d.Cc[f]!==b)c&&!t(this)||v(this,a,b,c,"ATC."+d.Tg[f]),d.Cc[f]=b,Lt(this,!1)}else d.wd=b,v(this,a,b,c,"ATC.INDX"),d.Ke=!0,b&32&&!e&&xs(this,!0)&&qs(this,!0),a=(d.Wa[12]<<8)+d.Wa[13]|0,d.vd!=a&&(d.vd=a,Lt(this)),d.Ye=0}; -l.Om=function(a,b){a=0;if(this.La==Tp)a=3-((this.A.af&12)>>2),a=(this.xb&1<>this.A.xc&63;b&&!t(this)||v(this,969,null,b,"DAC.DATA["+r(this.A.dd)+"]["+r(this.A.xc)+"]",a);this.A.xc+=6;12>2),a=(this.xb&1<>this.A.xc&63;b&&!t(this)||v(this,969,null,b,"DAC.DATA["+r(this.A.dd)+"]["+r(this.A.xc)+"]",a);this.A.xc+=6;12Missing <canvas> support. Please try a newer web browser.";break}e.setAttribute("class","pcjs-canvas");e.setAttribute("width",d.screenWidth);e.setAttribute("height",d.screenHeight);e.style.backgroundColor=d.screenColor;e.style.height="auto";0<=Ea().indexOf("MSIE")&&(c.onresize=function(a,b,c,d){return function(){b.style.height= (a.clientWidth*d/c|0)+"px"}}(c,e,d.screenWidth,d.screenHeight),c.onresize());var f=+(d.aspect||Ka("aspect"));f&&.3<=f&&3.33>=f&&(Pa("onresize",function(a,b,c){return function(){b.style.height=(a.clientWidth/c|0)+"px"}}(c,e,f)),window.onresize());c.appendChild(e);f=document.createElement("textarea");Ja("iOS")&&(f.setAttribute("autocapitalize","off"),f.setAttribute("autocorrect","off"),f.style.fontSize="16px");c.appendChild(f);var g=e.getContext("2d"),d=new Q(d,e,g,f,c);ob(d,c)}}); function Wt(a){ab.call(this,"ParallelPort",a,4194304);this.G=a.adapter;switch(this.G){case 1:this.D=956;this.C=7;break;case 2:this.D=888;this.C=7;break;case 3:this.D=632;this.C=5;break;default:Wa("Unrecognized parallel adapter #"+this.G);return}this.A=this.B=null;a=a.binding;"console"==a?this.B="":nb(this,a,Xt)}ba(Wt,ab);l=Wt.prototype;l.Cb=function(a,b,c){switch(b){case Xt:return this.na[b]=this.A=c,!0}return!1}; l.uc=function(a,b,c,d){this.ka=b;this.H=c;this.ba=d;this.W=Ob(a,"ChipSet");Ec(b,this,Yt,this.D);Ic(b,this,Zt,this.D);zb(this)};l.Xb=function(a,b){if(!b)if(!a||!this.restore)this.reset();else if(!this.restore(a))return!1;return!0};l.Wb=function(a){return a?this.save():!0};l.reset=function(){$t(this)};l.save=function(){var a=new Of(this),b=0,c=[];c[b++]=this.F;c[b++]=this.Mb;c[b]=this.mf;a.set(0,c);return a.data()};l.restore=function(a){return $t(this,a[0])}; function $t(a,b){var c=0;b||(b=[0,0,0]);a.F=b[c++];a.Mb=b[c++];a.mf=b[c];return!0}l.pm=function(a,b){var c=this.F;v(this,a,null,b,"DATA",c);return c};l.Nm=function(a,b){var c=this.Mb;v(this,a,null,b,"STAT",c);return c};l.lm=function(a,b){var c=this.mf;v(this,a,null,b,"CTRL",c);return c}; -l.Pn=function(a,b,c){v(this,a,b,c,"DATA");this.F=b;this.Mb|=au;a=!1;Cb(this,"transmitByte("+r(b)+")");this.A&&(8==b?this.A.value=this.A.value.slice(0,-1):(this.A.value+=String.fromCharCode(b),this.A.scrollTop=this.A.scrollHeight),a=!0);if(null!=this.B){if(10==b||1024<=this.B.length)this.P(this.B),this.B="";10!=b&&(this.B+=String.fromCharCode(b));a=!0}a&&(this.Mb&=~au);bu(this)};l.Kn=function(a,b,c){v(this,a,b,c,"CTRL");this.mf=b;bu(this)}; -function bu(a){a.W&&a.C&&(a.mf&cu&&!(a.Mb&au)?Zg(a.W,a.C):Lg(a.W,a.C))}var Xt="buffer",au=64,cu=16,Yt={0:Wt.prototype.pm,1:Wt.prototype.Nm,2:Wt.prototype.lm},Zt={0:Wt.prototype.Pn,2:Wt.prototype.Kn};Ra(function(){for(var a=pb(document,"pcx86","parallel"),b=0;b=b)a.preventDefault&&a.preventDefault(),64>8:this.N;v(this,a,null,b,this.C&tu?"DLM":"IER",c);return c};l.xm=function(a,b){var c=this.G;v(this,a,null,b,"IIR",c);return c};l.ym=function(a,b){var c=this.C;v(this,a,null,b,"LCR",c);return c};l.Am=function(a,b){var c=this.Z;v(this,a,null,b,"MCR",c);return c}; l.zm=function(a,b){var c=this.B;v(this,a,null,b,"LSR",c);return c};l.Fm=function(a,b){var c=this.A;this.A&=~(pu|qu);v(this,a,null,b,"MSR",c);return c}; -l.no=function(a,b,c){v(this,a,b,c,this.C&tu?"DLL":"THR");if(this.C&tu)this.L=this.L&-256|b;else{this.la=b;this.B&=~(mu|nu);a=!1;Cb(this,"transmitByte("+r(b)+")");this.aa&&this.aa.call(this.D,b)&&(a=!0);if(this.F){if(13==b)this.K=0;else if(8==b)this.F.value=this.F.value.slice(0,-1),0":String.fromCharCode(b);a=d.length;32>b&&1==a&&(a=0);9==b&&(b=this.oa||8,a=b-this.K%b,this.oa&&(d=qa("",a)));this.ma&&!this.K&&a&&(d=String.fromCharCode(this.ma)+ -d);this.F.value+=d;this.F.scrollTop=this.F.scrollHeight;this.K+=a}a=!0}else if(null!=this.I){if(10==b||1024<=this.I.length)this.P(this.I),this.I="";10!=b&&(this.I+=String.fromCharCode(b));a=!0}a&&(this.B=this.B|mu|nu)}};l.Zn=function(a,b,c){v(this,a,b,c,this.C&tu?"DLM":"IER");this.C&tu?this.L=this.L&255|b<<8:this.N=b};l.$n=function(a,b,c){v(this,a,b,c,"LCR");this.C=b}; -l.ao=function(a,b,c){var d=b^this.Z;v(this,a,b,c,"MCR");this.Z=b;d&(uu|vu)&&this.Y&&(a=0,this.O?(a|=b&vu?32:0,a|=b&uu?320:0):(a|=b&vu?16:0,a|=b&uu?1048576:0),this.Y.call(this.D,a))};function ru(a){var b=-1;a.B&su&&a.N&wu?b=xu:a.A&(pu|qu)&&a.N&yu&&(b=zu);0<=b?(a.G&=~(lu|Au),a.G|=b,a.W&&a.M&&Zg(a.W,a.M,100)):(a.G|=lu,a.W&&a.M&&Lg(a.W,a.M))} -var gu="buffer",ku=384,wu=1,yu=8,lu=1,xu=4,zu=0,Au=6,tu=128,uu=1,vu=2,su=1,mu=32,nu=64,pu=1,qu=2,eu=16,fu=32,hu={0:du.prototype.Km,1:du.prototype.wm,2:du.prototype.xm,3:du.prototype.ym,4:du.prototype.Am,5:du.prototype.zm,6:du.prototype.Fm},iu={0:du.prototype.no,1:du.prototype.Zn,3:du.prototype.$n,4:du.prototype.ao};Ra(function(){for(var a=pb(document,"pcx86","serial"),b=0;b":String.fromCharCode(b);a=d.length;32>b&&1==a&&(a=0);9==b&&(b=this.oa||8,a=b-this.K%b,this.oa&&(d=qa("",a)));this.ma&&!this.K&&a&&(d=String.fromCharCode(this.ma)+ +d);this.F.value+=d;this.F.scrollTop=this.F.scrollHeight;this.K+=a}a=!0}else if(null!=this.I){if(10==b||1024<=this.I.length)this.P(this.I),this.I="";10!=b&&(this.I+=String.fromCharCode(b));a=!0}a&&(this.B=this.B|mu|nu)}};l.ao=function(a,b,c){v(this,a,b,c,this.C&tu?"DLM":"IER");this.C&tu?this.L=this.L&255|b<<8:this.N=b};l.bo=function(a,b,c){v(this,a,b,c,"LCR");this.C=b}; +l.co=function(a,b,c){var d=b^this.Z;v(this,a,b,c,"MCR");this.Z=b;d&(uu|vu)&&this.Y&&(a=0,this.O?(a|=b&vu?32:0,a|=b&uu?320:0):(a|=b&vu?16:0,a|=b&uu?1048576:0),this.Y.call(this.D,a))};function ru(a){var b=-1;a.B&su&&a.N&wu?b=xu:a.A&(pu|qu)&&a.N&yu&&(b=zu);0<=b?(a.G&=~(lu|Au),a.G|=b,a.W&&a.M&&Zg(a.W,a.M,100)):(a.G|=lu,a.W&&a.M&&Lg(a.W,a.M))} +var gu="buffer",ku=384,wu=1,yu=8,lu=1,xu=4,zu=0,Au=6,tu=128,uu=1,vu=2,su=1,mu=32,nu=64,pu=1,qu=2,eu=16,fu=32,hu={0:du.prototype.Km,1:du.prototype.wm,2:du.prototype.xm,3:du.prototype.ym,4:du.prototype.Am,5:du.prototype.zm,6:du.prototype.Fm},iu={0:du.prototype.po,1:du.prototype.ao,3:du.prototype.bo,4:du.prototype.co};Ra(function(){for(var a=pb(document,"pcx86","serial"),b=0;bd&&a.pa)||a.pa.ea.Yb);if(a.og)d?a.controller.Fa('Unable to connect to disk "'+a.I+'" (error '+d+": "+c+")",f):(a.D=!0,Tu(a),e=a);else if(d)a.controller.Fa('Unable to load disk "'+a.F+'" (error '+d+": "+b+")",f);else{eb(a.controller.xe,b,c);try{if(0g&&0c.indexOf("0x")&&'["'!=c.substr(0,2)?JSON.parse(c.replace(/([a-z]+):/gm,'"$1":').replace(/\/\/[^\n]*/gm,"")):eval("("+c+")");if(h.length)if(1==h.length)Wa(h[0]);else{a.ob=h.length;a.ib=h[0].length;a.Ya=h[0][0].length;var k=h[0][0][0];a.Na=k&&k.length||512;for(d=c=0;d>2,p=k.pattern;void 0===p&&(p=k.pattern=0);var w=k.data;if(void 0===w){var u=k.bytes;if(void 0!==u&&u.length){for(var F= m<<2,K=u.length;Kb;b++){if(128==Wu(a,e,c+0,1)){d.Kf=Wu(a,e,c+8,4);(e=Vu(a,d.Kf))&&(f=!0);break}c+=16}if(!f)return}d.sf||(d.sf=Wu(a,e,19,2)||Wu(a,e,32,4),d.rf=Wu(a,e,14,2),d.lh=d.rf+Wu(a,e,22,2)*Wu(a,e,16,1),d.vh=Wu(a,e,17,2),d.vf=Wu(a,e,13,1));d.jh=d.lh+((32*d.vh+(d.Na-1))/d.Na|0);d.hn=(d.sf-d.jh)/d.vf|0;d.vg=4084>=d.hn?12:16;d.Nl=12==d.vg?4086:65526;b=[];for(e=d.lh;eb;b++){if(128==Wu(a,e,c+0,1)){d.Kf=Wu(a,e,c+8,4);(e=Vu(a,d.Kf))&&(f=!0);break}c+=16}if(!f)return}d.sf||(d.sf=Wu(a,e,19,2)||Wu(a,e,32,4),d.rf=Wu(a,e,14,2),d.lh=d.rf+Wu(a,e,22,2)*Wu(a,e,16,1),d.vh=Wu(a,e,17,2),d.vf=Wu(a,e,13,1));d.jh=d.lh+((32*d.vh+(d.Na-1))/d.Na|0);d.kn=(d.sf-d.jh)/d.vf|0;d.vg=4084>=d.kn?12:16;d.Nl=12==d.vg?4086:65526;b=[];for(e=d.lh;e>=8;f+=2;if(k)for(;m--;)jv(d,f,1),254>=k?(p=k,w=jv(d,f+1),f+=3):(p=jv(d,f+3,1),w=jv(d,f+4),f+=6),d.nd[p]&&(d.nd[p].ee[h]=[w]),d.A[h]=[p,w],h++;else h+=m}(g=Zu(e,mv,c))&&nv(e,g+c);g=Zu(e,ov,c);h=Zu(e,pv,c);g&&h&&nv(e,g,g+h)}}}} -function Yu(a,b,c,d,e){var f,g=a.C.length,h=b.Na/32|0;b.aq=d+"\\";for(var k=0;kK)break;for(var H=u.jh+(K-2)*u.vf,I=0;IK)break;for(var H=u.jh+(K-2)*u.vf,I=0;I>3,1),d?e=16==b.vg?e<<8:c&7?e<<4:(e&15)<<8:c&7&&(e>>=4));return e} function Vu(a,b){var c=a.ib*a.Ya,d=b/c|0;return dg)break;e|=g<=f)break;e+=String.fromCharCode(f)}return e}function Qu(a,b,c,d,e,f){a||(a={sector:d,length:e,data:[],pattern:f});a.Ol=b;a.Pl=c;a.hd=a.Lc=0;a.Oa=!1;return a} function Ru(a,b){b="action=open&volume="+b+("&mode="+a.mode);b+="&chs="+a.ob+":"+a.ib+":"+a.Ya+":"+a.Na;b+="&machine="+Ou(a.controller);b+="&user="+Pu(a.controller);return Da()+"/api/v1/disk?"+b} @@ -670,16 +670,16 @@ function Bv(a,b,c,d){var e,f=a.na.listDrives;if(f&&!isNaN(e=ga(f.value,10))&&0<= l.lj=function(a,b,c,d,e){var f;a.Le=!1;b&&(f=b.info(),b&&f[0]>a.ob||f[1]>a.ib)&&(this.Fa('Diskette "'+c+'" too large for drive '+String.fromCharCode(65+a.Ua)),b=null);b?(a.sa=b,a.Zk=c,a.te=d,Nv(this,c,d,b),f=b.info(),this.I|=Rv,this.Fa('Mounted diskette "'+c+'" in drive '+String.fromCharCode(65+a.Ua),a.ke||e),a.ug=f[0],a.Af=f[1],a.Bf=f[2],this.pa&&this.pa.ld()):a.Ne=!1;a.ke&&(a.ke=!1,--this.K||zb(this));Av(this,a.Ua)}; function Fv(a,b,c,d){if((a=a.na.listDisks)&&a.options){for(var e=0;e=this.C&&(this.fa&=~(Uv|Vv),this.D=this.C=0);return c}; -l.Rn=function(a,b,c){t(this)&&v(this,a,b,c,"DATA["+this.C+"]");this.C=Xv[a].Id){b=!1;this.D=0;a=Yv(this);var d,e,f,g,h=a&Wv;switch(h){case Zv:Yv(this);Yv(this);$v(this);break;case aw:c=Yv(this);this.Ua=c&3;d=this.A[this.Ua];$v(this);bw(this,(d.pb&cw)>>>24);break;case dw:case ew:c=Yv(this);b=c>>2&1;this.Ua=c&3;d=this.A[this.Ua];d.Xa=b;c=d.Gb=Yv(this);e=Yv(this);f=d.kb=Yv(this);g=Yv(this);d.ub=128<=Xv[a].Id){b=!1;this.D=0;a=Yv(this);var d,e,f,g,h=a&Wv;switch(h){case Zv:Yv(this);Yv(this);$v(this);break;case aw:c=Yv(this);this.Ua=c&3;d=this.A[this.Ua];$v(this);bw(this,(d.pb&cw)>>>24);break;case dw:case ew:c=Yv(this);b=c>>2&1;this.Ua=c&3;d=this.A[this.Ua];d.Xa=b;c=d.Gb=Yv(this);e=Yv(this);f=d.kb=Yv(this);g=Yv(this);d.ub=128<>2&1;this.Ua= c&3;d=this.A[this.Ua];c=d.Gb;e=d.Xa=b;f=d.kb=1;g=0;d.pb=Qv;d.sa&&(d.cb=d.sa.seek(d.Gb,d.Xa,d.kb))?g=d.cb.length>>8:d.pb=fw|gw;iw(this,d,a,b,c,e,f,g);b=!0;break;case pw:c=Yv(this);b=c>>2&1;this.Ua=c&3;d=this.A[this.Ua];c=d.Gb;e=d.Xa=b;f=1;g=Yv(this);d.ub=128<>2&1,c=Yv(this),d.Gb+=c-d.Sd,0>d.Gb&&(d.Gb=0),d.Gb>=d.ob&&(d.Gb=d.ob-1),d.Sd=c,d.pb=kw,d.Gb||(d.pb|=lw),$v(this),b=!0}0>2&1,c=Yv(this),d.Gb+=c-d.Sd,0>d.Gb&&(d.Gb=0),d.Gb>=d.ob&&(d.Gb=d.ob-1),d.Sd=c,d.pb=kw,d.Gb||(d.pb|=lw),$v(this),b=!0}0>>8);bw(a,(b.pb&sw)>>>16);var k=0;if(e!=b.Gb||f!=b.Xa)k=g=1;c&tw&&(f^=k,d||(k=0));bw(a,e+k);bw(a,f);bw(a,g);bw(a,h)}function Yv(a){var b=a.F[a.D];a.D++;return b}function $v(a){a.D=a.C=0}function bw(a,b){a.F[a.C++]=b}l.jl=function(a,b,c){void 0===b||0>b?this.se(a,c):c(-1,!1)};l.kl=function(a,b){return void 0!==b&&0<=b?uw(a,b):-1}; l.Cl=function(a,b){if(void 0!==b&&0<=b)a:if(a.pb)a=-1;else{a.$c[a.Ge++]=b;if(a.Ge==a.$c.length){a.Gb=a.$c[0];a.Xa=a.$c[1];a.kb=a.$c[2];a.ub=128<uw(a,a.Wi)){a=-1;break a}a.jg++}a.jg>=a.Td&&(b=-1);a=b}else a=-1;return a};l.se=function(a,b){var c=-1,d=null,e=0;if(!a.pb&&a.sa){do{if(a.cb&&(e=a.Sa,0<=(c=a.sa.read(a.cb,a.Sa++)))){d=a.cb;break}a.cb=a.sa.seek(a.Gb,a.Xa,a.kb);if(!a.cb){a.pb=vw|gw;break}a.Sa=0;ww(a)}while(1)}b(c,!1,d,e)}; function uw(a,b){if(a.pb||!a.sa)return-1;do{if(a.cb&&a.sa.write(a.cb,a.Sa++,b))break;a.cb=a.sa.seek(a.Gb,a.Xa,a.kb);if(!a.cb){a.pb=xw|gw;b=-1;break}a.Sa=0;ww(a)}while(1);return b}function ww(a){a.kb++;a.kb>=a.Bf+1&&(a.kb=1,a.Xa++,a.Xa>=a.Af&&(a.Xa=0,a.Gb++))}var Lv="Floppy Drive",Sv=4,Tv=8,Vv=16,Uv=64,Jv=128,Zv=3,aw=4,dw=5,ew=6,jw=7,mw=8,ow=10,pw=13,qw=15,Wv=31,tw=128,Qv=0,fw=8,kw=32,gw=64,Kv=192,nw=255,hw=512,vw=1024,xw=8192,rw=65280,sw=16711680,lw=268435456,cw=-16777216,Rv=128,Ov=0;aa={}; -var Xv={3:{Id:3,Ud:0,name:aa.Up},4:{Id:2,Ud:1,name:aa.Sp},5:{Id:9,Ud:7,name:aa.Yp},6:{Id:9,Ud:7,name:aa.Op},7:{Id:2,Ud:0,name:aa.Qp},8:{Id:1,Ud:2,name:aa.Tp},10:{Id:2,Ud:7,name:aa.Pp},13:{Id:6,Ud:7,name:aa.Lp},15:{Id:3,Ud:0,name:aa.Rp}},Dv={1009:zv.prototype.rm,1012:zv.prototype.tm,1013:zv.prototype.qm,1015:zv.prototype.sm},Ev={1010:zv.prototype.Sn,1013:zv.prototype.Rn,1015:zv.prototype.Qn}; +var Xv={3:{Id:3,Ud:0,name:aa.Wp},4:{Id:2,Ud:1,name:aa.Up},5:{Id:9,Ud:7,name:aa.$p},6:{Id:9,Ud:7,name:aa.Qp},7:{Id:2,Ud:0,name:aa.Sp},8:{Id:1,Ud:2,name:aa.Vp},10:{Id:2,Ud:7,name:aa.Rp},13:{Id:6,Ud:7,name:aa.Np},15:{Id:3,Ud:0,name:aa.Tp}},Dv={1009:zv.prototype.rm,1012:zv.prototype.tm,1013:zv.prototype.qm,1015:zv.prototype.sm},Ev={1010:zv.prototype.Un,1013:zv.prototype.Tn,1015:zv.prototype.Sn}; Ra(function(){for(var a=pb(document,"pcx86","fdc"),b=0;b=this.C&&(this.D=this.C=0,this.fa&=~(Pw|Qw|Rw));return c};l.po=function(a,b,c){v(this,a,b,c,"DATA["+this.C+"]");this.C=a&&(this.fa|=Pw,this.fa&=~Tw,Uw(this))};l.Um=function(a,b){var c=this.fa;v(this,a,null,b,"STATUS",c);this.D=this.C&&(this.D=this.C=0,this.fa&=~(Pw|Qw|Rw));return c};l.ro=function(a,b,c){v(this,a,b,c,"DATA["+this.C+"]");this.C=a&&(this.fa|=Pw,this.fa&=~Tw,Uw(this))};l.Um=function(a,b){var c=this.fa;v(this,a,null,b,"STATUS",c);this.D=a.B.Na?(a.fa=Ww,a.se(a.B,function(b){0<=b?(Xw(a),a.W&&a.W.ca==ol&&(a.fa=0),a.fa=a.fa|Hw|Yw|Zw):(a.fa=$w,a.I=ax)},!1)):a.fa=Hw|Yw));return d}l.nl=function(a,b){return Vw(this,a,b)|Vw(this,a,b)<<8}; -function bx(a,b,c,d){if(a.B&&a.B.ub>=a.B.Na)if(0>cx(a.B,c))a.fa=$w,a.I=ax;else if(1==a.B.Sa||a.B.Sa==a.B.Na)t(a,1048832)&&v(a,b,c,d,"DATA["+a.B.Sa+"]"),1=a.B.Na&&(a.fa|=Zw))}l.yn=function(a,b,c){bx(this,a,b&255,c);bx(this,a,b>>8&255,c)};l.$l=function(a,b){var c=this.I;v(this,a,null,b,"ERROR",c);return c};l.Dn=function(a,b,c){v(this,a,b,c,"WPREC");this.va=b};l.bm=function(a,b){var c=this.J;v(this,a,null,b,"SECCNT",c);return c}; -l.Bn=function(a,b,c){v(this,a,b,c,"SECCNT");this.J=b};l.cm=function(a,b){var c=this.ga;v(this,a,null,b,"SECNUM",c);return c};l.Cn=function(a,b,c){v(this,a,b,c,"SECNUM");this.ga=b};l.Yl=function(a,b){var c=this.da;v(this,a,null,b,"CYLLO",c);return c};l.xn=function(a,b,c){v(this,a,b,c,"CYLLO");this.da=b};l.Xl=function(a,b){var c=this.aa;v(this,a,null,b,"CYLHI",c);return c};l.wn=function(a,b,c){v(this,a,b,c,"CYLHI");this.aa=b};l.Zl=function(a,b){var c=this.Y;v(this,a,null,b,"DRVHD",c);return c}; -l.zn=function(a,b,c){v(this,a,b,c,"DRVHD");this.Y=b;this.fa=this.A[this.Y&dx?1:0]?this.fa|Hw|Yw:this.fa&~Hw};l.dm=function(a,b){var c=this.fa;v(this,a,null,b,"STATUS",c);this.fa&Hw&&(this.fa&=~Ww);return c};l.vn=function(a,b,c){v(this,a,b,c,"COMMAND");this.ha=b;this.W&&Lg(this.W,14);ex(this)};l.An=function(a,b,c){v(this,a,b,c,"FDR");this.L&fx&&!(b&fx)&&(this.I=gx);this.L=b}; +function bx(a,b,c,d){if(a.B&&a.B.ub>=a.B.Na)if(0>cx(a.B,c))a.fa=$w,a.I=ax;else if(1==a.B.Sa||a.B.Sa==a.B.Na)t(a,1048832)&&v(a,b,c,d,"DATA["+a.B.Sa+"]"),1=a.B.Na&&(a.fa|=Zw))}l.An=function(a,b,c){bx(this,a,b&255,c);bx(this,a,b>>8&255,c)};l.$l=function(a,b){var c=this.I;v(this,a,null,b,"ERROR",c);return c};l.Fn=function(a,b,c){v(this,a,b,c,"WPREC");this.va=b};l.bm=function(a,b){var c=this.J;v(this,a,null,b,"SECCNT",c);return c}; +l.Dn=function(a,b,c){v(this,a,b,c,"SECCNT");this.J=b};l.cm=function(a,b){var c=this.ga;v(this,a,null,b,"SECNUM",c);return c};l.En=function(a,b,c){v(this,a,b,c,"SECNUM");this.ga=b};l.Yl=function(a,b){var c=this.da;v(this,a,null,b,"CYLLO",c);return c};l.zn=function(a,b,c){v(this,a,b,c,"CYLLO");this.da=b};l.Xl=function(a,b){var c=this.aa;v(this,a,null,b,"CYLHI",c);return c};l.yn=function(a,b,c){v(this,a,b,c,"CYLHI");this.aa=b};l.Zl=function(a,b){var c=this.Y;v(this,a,null,b,"DRVHD",c);return c}; +l.Bn=function(a,b,c){v(this,a,b,c,"DRVHD");this.Y=b;this.fa=this.A[this.Y&dx?1:0]?this.fa|Hw|Yw:this.fa&~Hw};l.dm=function(a,b){var c=this.fa;v(this,a,null,b,"STATUS",c);this.fa&Hw&&(this.fa&=~Ww);return c};l.xn=function(a,b,c){v(this,a,b,c,"COMMAND");this.ha=b;this.W&&Lg(this.W,14);ex(this)};l.Cn=function(a,b,c){v(this,a,b,c,"FDR");this.L&fx&&!(b&fx)&&(this.I=gx);this.L=b}; function ex(a){var b=!1,c=a.ha,d=a.Y&dx?1:0,e=a.Y&hx,f=a.da|(a.aa&ix)<<8,g=a.ga,h=a.J||256;a.Ua=-1;a.B=null;a.I=jx;a.fa=Hw|Yw;var k=a.A[d];k?(k.Od=f,k.Xa=e,k.kb=g,k.ub=h*k.Na,c=c>=kx?c:c&lx,k.cb=null,k.Sa=0,k.errorCode=0,a.Ua=d,a.B=k):c=-1;switch(c&lx){case mx:b=!0;break;case nx:a.fa=Ww;a.se(k,function(b){0<=b&&a.W?(Xw(a),a.fa=Hw|Yw|Zw):(a.fa=$w,a.I=ax)},!1);break;case ox:a.fa=Zw;break;case px:b=!0;break;case qx:b=!0;break;case kx:a.I=gx;b=!0;break;case rx:k.ib=e+1,k.Ya=h,b=!0}b&&Xw(a)} function Xw(a){!a.W||a.L&sx||Zg(a.W,14,120)} function Uw(a){a.D=0;var b=tx(a),c=tx(a),d=c&32,e=d>>5,f=c&31,g=tx(a),h=tx(a),k=g<<2&768|h,m=g&63,p=tx(a),w=tx(a),u=a.A[e];u&&(u.Od=k,u.Xa=f,u.kb=m,u.ub=p*u.Na);switch(b){case ux:vx(a,u?u.errorCode:wx);xx(a,c);xx(a,g);xx(a,h);xx(a,yx|d);b=-1;break;case Sw:for(c=0;0<=(b=tx(a));)u&&c=a.Ya+b&&(a.kb=b,a.Xa++,a.Xa>=a.ib&&(a.Xa=0,a.Od++))}l.Vm=function(){var a=this.H.L&255;!(this.H.D>>8)&&128>8||!this.W)||(a=!(this.W.hc[0].od&64));return a?!0:!1}; var Kw="Hard Drive",Nw=["XTC","ATC","COMPAQ"],Lw=[{0:[306,2],1:[375,8],2:[306,6],3:[306,4]},{1:[306,4],2:[615,4],3:[615,6],4:[940,8],5:[940,6],6:[615,4],7:[462,8],8:[733,5],9:[900,15],10:[820,3],11:[855,5],12:[855,7],13:[306,8],14:[733,7],16:[612,4],17:[977,5],18:[977,7],19:[1024,7],20:[733,5],21:[733,7],22:[733,5],23:[306,4]},{1:[306,4],2:[615,4],3:[615,6],4:[1023,8],5:[940,6],6:[697,5],7:[462,8],8:[925,5],9:[900,15],10:[980,5],11:[925,7],12:[925,9],13:[612,8],14:[980,4],16:[612,4],17:[980,5],18:[966, 6],19:[1023,8],20:[733,5],21:[733,7],22:[524,4,40],23:[924,8],24:[966,14],25:[966,16],26:[1023,14],27:[832,6,33],28:[1222,15,34],29:[1240,7,34],30:[615,4,25],31:[615,8,25],32:[905,9,25],33:[832,8,33],34:[966,7,34],35:[966,8,34],36:[966,9,34],37:[966,5,34],38:[612,16,63],39:[1023,11,33],40:[1023,15,34],41:[1630,15,52],42:[1023,16,63],43:[805,4,26],44:[805,2,26],45:[748,8,33],46:[748,6,33],47:[966,5,25]}],Dw=496,gx=1,jx=0,ax=16,ix=3,hx=15,dx=16,$w=1,Zw=8,Yw=16,Hw=64,Ww=128,mx=16,nx=32,ox=48,px=64,qx= -112,kx=144,rx=145,lx=240,sx=2,fx=4,yx=0,zx=2,Cx=0,Dx=1,ux=3,Ex=5,Fx=8,Hx=10,Sw=12,Jx=15,Ax=224,Bx=228,Jw=0,wx=4,Lx=20,Iw=0,Tw=1,Pw=2,Qw=4,Rw=8,Ow=32,Aw={800:yw.prototype.Tm,801:yw.prototype.Um,802:yw.prototype.Sm},zw={496:yw.prototype.nl,497:yw.prototype.$l,498:yw.prototype.bm,499:yw.prototype.cm,500:yw.prototype.Yl,501:yw.prototype.Xl,502:yw.prototype.Zl,503:yw.prototype.dm},Cw={800:yw.prototype.po,801:yw.prototype.so,802:yw.prototype.ro,803:yw.prototype.qo,807:yw.prototype.hi,811:yw.prototype.hi, -815:yw.prototype.hi},Bw={496:yw.prototype.yn,497:yw.prototype.Dn,498:yw.prototype.Bn,499:yw.prototype.Cn,500:yw.prototype.xn,501:yw.prototype.wn,502:yw.prototype.zn,503:yw.prototype.vn,1014:yw.prototype.An};Ra(function(){for(var a=pb(document,"pcx86","hdc"),b=0;bthis.A&&this.C.length&&(this.A=0);if(0>this.A||a!=this.C[this.A])this.C.splice(0,0,a),this.A=0;this.A--}else this.aa?a="end":a=this.C[this.A+1];b=[];if(a){a=a.replace(/""/g,"'");var d=0,e=null;c=c||";";for(var f=0;f<=a.length;f++){var g=a.charAt(f);if('"'==g||"'"==g)e?g==e&&(e=null):e=g;else if(g==c&&!e||!g)b.push(ra(a.substring(d,f))),d=f+1}}return b}; function Ox(a,b,c){for(c=c||-1;c--&&b.length;){var d=b.pop();if(2>a.length)return!1;var e=a.pop(),f=a.pop();switch(d){case "*":d=f*e;break;case "/":if(!e)return!1;d=f/e;break;case "%":if(!e)return!1;d=f%e;break;case "+":d=f+e;break;case "-":d=f-e;break;case "<<":d=f<>":d=f>>e;break;case ">>>":d=f>>>e;break;case "<":d=f":d=f>e?1:0;break;case ">=":d=f>=e?1:0;break;case "==":d=f==e?1:0;break;case "!=":d=f!=e?1:0;break;case "&":d=f&e;break; case "^":d=f^e;break;case "|":d=f|e;break;case "&&":d=f&&e?1:0;break;case "||":d=f||e?1:0;break;default:return!1}a.push(d|0)}return!0} @@ -774,8 +774,8 @@ function JA(a,b){switch(b){case "V":a=$f(a.H);break;case "D":a=a.H.O&1024;break; function LA(a,b,c){return b.$b+"="+q(b.U,4)+(c?"["+q(b.ua,a.ha)+","+iy(b.Ka)+"]":"")}function MA(a,b,c,d,e){return b+"="+(null!=c?q(c,4):"")+"["+q(d,a.ha)+","+q(e-d,4)+"]"} function NA(a,b){var c;void 0===b&&(b=wy(a));c=KA(a,Uy)+KA(a,Xy)+KA(a,Vy)+KA(a,Wy)+(4a.H.ca&&(d="\n"+d,c+=e,e="");c+="\n"+LA(a,a.H.Z,b)+" ";80386<=a.H.ca&&(e+="\n",c+=LA(a,a.H.Ga,b)+" "+LA(a,a.H.Ha,b)+"\n");c+=MA(a,"LD",a.H.Eb.U,a.H.Eb.ua,a.H.Eb.ua+a.H.Eb.Ka)+" "+MA(a,"GD",null,a.H.Kb,a.H.Pc)+" "+MA(a,"ID", null,a.H.Pb,a.H.cd)+" ";c=c+(d+" "+e)+KA(a,rz);80386<=a.H.ca&&(c+=KA(a,tz)+KA(a,uz))}else 80386<=a.H.ca&&(c+=LA(a,a.H.Ga,b)+" "+LA(a,a.H.Ha,b)+" ");return c+=KA(a,wz)+JA(a,"V")+JA(a,"D")+JA(a,"I")+JA(a,"T")+JA(a,"S")+JA(a,"Z")+JA(a,"A")+JA(a,"P")+JA(a,"C")}l.gj=function(a,b){return a[0]>b[0]?1:a[0]>>0,p],H=ta(F,u,a.gj);0>H&&F.splice(-(H+1),0,u)}K&&(w.a=K.replace(/''/g,'"'))}a.D.push({Rf:b,nn:c,U:d,Ja:e,xa:f,cn:g,Fd:h,Pi:m})} -function sy(a,b,c){for(var d=0;d>>0,f=a.Sb(b)>>>0,g=0;g>>0,p=h.xa;null!=p&&(p>>>=0);var w=h.cn;48==k&&(k=40);if(k==b.U&&e>=m&&e=p&&f>>0,p],H=ta(F,u,a.gj);0>H&&F.splice(-(H+1),0,u)}K&&(w.a=K.replace(/''/g,'"'))}a.D.push({Rf:b,pn:c,U:d,Ja:e,xa:f,en:g,Fd:h,Pi:m})} +function sy(a,b,c){for(var d=0;d>>0,f=a.Sb(b)>>>0,g=0;g>>0,p=h.xa;null!=p&&(p>>>=0);var w=h.en;48==k&&(k=40);if(k==b.U&&e>=m&&e=p&&f":62,"?":63,"@":64,Bd:65,wh:66,xh:67,zh:68,E:69,Ah:70,Bh:71,Ch:72,Dh:73,Eh:74,Fh:75,Gh:76,Hh:77,Ih:78,Jh:79,Kh:80,Q:81,Lh:82,Mh:83,Nh:84,Oh:85,Ph:86,Qh:87,Rh:88,Sh:89,sf:90,"[":91,"\\":92,"]":93,"^":94,_:95,"`":96,Cd:97,jk:98,kk:99,d:100,e:101,vk:102,wk:103,xk:104,yk:105,Jl:106,k:107,Ll:108,Ol:109,n:110,Tl:111,p:112,q:113,r:114,nn:115,t:116,qn:117,rn:118,sn:119,x:120, -y:121,z:122,"{":123,"|":124,"}":125,"~":126,no:127},ea={};ea[173]=n["-"];ea[186]=n[";"];ea[187]=n["="];ea[189]=n["-"];ea[188]=n[","];ea[190]=n["."];ea[191]=n["/"];ea[192]=n["`"];ea[219]=n["["];ea[220]=n["\\"];ea[221]=n["]"];ea[222]=n["'"];var p={};p[n["1"]]=n["!"];p[n["2"]]=n["@"];p[n["3"]]=n["#"];p[n["4"]]=n.$;p[n["5"]]=n["%"];p[n["6"]]=n["^"];p[n["7"]]=n["&"];p[n["8"]]=n["*"];p[n["9"]]=n["("];p[n["0"]]=n[")"];p[186]=n[":"];p[187]=n["+"];p[188]=n["<"];p[189]=n._;p[190]=n[">"];p[191]=n["?"]; +var da={163840:[40,1,8,,254],184320:[40,1,9,,252],327680:[40,2,8,,255],368640:[40,2,9,,253],737280:[80,2,9,,249],1228800:[80,2,15,,249],1474560:[80,2,18,,240],2949120:[80,2,36,,240],21368320:[615,4,17],256256:[77,1,26,128],2494464:[203,2,12,512],5242880:[256,2,40,256],10485760:[512,2,40,256]},n={Nn:0,Pn:1,Qn:2,Zj:3,Rn:4,Sn:5,Tn:6,Un:7,Vn:8,Wn:9,Xn:10,Yn:11,Zn:12,$n:13,ao:14,bo:15,co:16,eo:17,fo:18,ho:19,io:20,jo:21,ko:22,lo:23,mo:24,no:25,oo:26," ":32,"!":33,'"':34,"#":35,$:36,"%":37,"&":38,"'":39, +"(":40,")":41,"*":42,"+":43,",":44,"-":45,".":46,"/":47,0:48,1:49,2:50,3:51,4:52,5:53,6:54,7:55,8:56,9:57,":":58,";":59,"<":60,"=":61,">":62,"?":63,"@":64,Bd:65,wh:66,xh:67,zh:68,E:69,Ah:70,Bh:71,Ch:72,Dh:73,Eh:74,Fh:75,Gh:76,Hh:77,Ih:78,Jh:79,Kh:80,Q:81,Lh:82,Mh:83,Nh:84,Oh:85,Ph:86,Qh:87,Rh:88,Sh:89,sf:90,"[":91,"\\":92,"]":93,"^":94,_:95,"`":96,Cd:97,jk:98,kk:99,d:100,e:101,vk:102,wk:103,xk:104,yk:105,Jl:106,k:107,Ll:108,Pl:109,n:110,Vl:111,p:112,q:113,r:114,pn:115,t:116,sn:117,tn:118,un:119,x:120, +y:121,z:122,"{":123,"|":124,"}":125,"~":126,po:127},ea={};ea[173]=n["-"];ea[186]=n[";"];ea[187]=n["="];ea[189]=n["-"];ea[188]=n[","];ea[190]=n["."];ea[191]=n["/"];ea[192]=n["`"];ea[219]=n["["];ea[220]=n["\\"];ea[221]=n["]"];ea[222]=n["'"];var p={};p[n["1"]]=n["!"];p[n["2"]]=n["@"];p[n["3"]]=n["#"];p[n["4"]]=n.$;p[n["5"]]=n["%"];p[n["6"]]=n["^"];p[n["7"]]=n["&"];p[n["8"]]=n["*"];p[n["9"]]=n["("];p[n["0"]]=n[")"];p[186]=n[":"];p[187]=n["+"];p[188]=n["<"];p[189]=n._;p[190]=n[">"];p[191]=n["?"]; p[192]=n["~"];p[219]=n["{"];p[220]=n["|"];p[221]=n["}"];p[222]=n['"'];p[173]=n._;p[61]=n["+"];p[59]=n[":"]; function fa(a,b){var c;if(a){b||(b=10);var d=a.charAt(0),e=0=e?48:55),d=String.fromCharCode(e)+d;a>>=4}return(c?"0x":"")+d}function ha(a,b){var c=a,d=a.lastIndexOf("/");0<=d&&(c=a.substr(d+1));d=c.indexOf("&");0>>this.B].nc(a&this.C,b&255,a)};function ac function bc(a,b){var c=0,d=[],e=!a.G&&a.W==a.D;e||Gb(a,!0);for(var f=0;f>>=f)&k;if(void 0!==g&&g[0])g[0](b,k,e);f+=h<<3;b+=h;c-=h}}function Ib(a,b,c,d,e){b="Memory block error ("+b+": "+ga(c)+","+ga(d)+")";e?a.Ea?a.Ea.message(b):a.log(b):r(b);return!1} -var tb,gc={Dj:20,count:8,Co:1,type:3},hc=0,ic;for(ic in gc){var jc=gc[ic];gc[ic]={qg:(1<>1),this.T=new Int32Array(this.L,0,c>>2),Rb(this,Wb?Xb:Yb);else{this.T=Array(c>>2);for(e=0;e>2),b=0;b>8,c)};l.rh=function(a,b,c){this.nc(a++,b&255,c++);this.nc(a++,b>>8&255,c++);this.nc(a++,b>>16&255,c++);this.nc(a,b>>>24,c)};l.Xm=function(a){return this.T[a>>2]>>>((a&3)<<3)&255}; -l.jn=function(a){var b=a>>2;a=(a&3)<<3;var c=this.T[b]>>a;return 24>a?c&65535:c&255|(this.T[b+1]&255)<<8};l.cn=function(a){var b=a>>2;a=(a&3)<<3;var c=this.T[b];a&&(c=c>>>a|this.T[b+1]<<32-a);return c};l.vn=function(a,b){var c=a>>2;a=(a&3)<<3;this.T[c]=this.T[c]&~(255<>2;a=(a&3)<<3;24>a?this.T[c]=this.T[c]&~(65535<>8);this.Da=!0}; -l.Bn=function(a,b){var c=a>>2;if(a=(a&3)<<3){var d=-1<>>32-a}else this.T[c]=b;this.Da=!0};l.Wm=function(a,b){this.F&&xc(this.F,b,1,!1);return this.zd(a,b)};l.hn=function(a,b){this.F&&xc(this.F,b,2,!1);return this.bf(a,b)};l.bn=function(a,b){this.F&&xc(this.F,b,4,!1);return this.mh(a,b)};l.un=function(a,b,c){this.F&&xc(this.F,c,1,!0);this.H||this.gf(a,b,c)};l.Gn=function(a,b,c){this.F&&xc(this.F,c,2,!0);this.H||this.uh(a,b,c)}; -l.An=function(a,b,c){this.F&&xc(this.F,c,4,!0);this.H||this.U(a,b,c)};l.Zm=function(a,b){this.A.T[this.C]|=this.I;this.B.T[this.D]|=this.I;return this.J.mc(a,b)};l.ln=function(a,b){this.A.T[this.C]|=this.I;this.B.T[this.D]|=this.I;return this.J.af(a,b)};l.en=function(a,b){this.A.T[this.C]|=this.I;this.B.T[this.D]|=this.I;return this.J.Md(a,b)};l.xn=function(a,b,c){this.A.T[this.C]|=this.I;this.B.T[this.D]|=this.M;this.J.nc(a,b,c)}; -l.Jn=function(a,b,c){this.A.T[this.C]|=this.I;this.B.T[this.D]|=this.M;this.J.jf(a,b,c)};l.Dn=function(a,b,c){this.A.T[this.C]|=this.I;this.B.T[this.D]|=this.M;this.J.hf(a,b,c)};l.$m=function(a,b){return yc(this.F,b,!1).mc(a,b)};l.mn=function(a,b){return yc(this.F,b,!1).af(a,b)};l.fn=function(a,b){return yc(this.F,b,!1).Md(a,b)};l.yn=function(a,b,c){yc(this.F,c,!0).nc(a,b,c)};l.Kn=function(a,b,c){yc(this.F,c,!0).jf(a,b,c)};l.En=function(a,b,c){yc(this.F,c,!0).hf(a,b,c)};l.Vm=function(a){return this.Fa[a]}; -l.Ij=function(a){return this.Fa[a]};l.Ym=function(a){this.A.T[this.C]|=32;this.B.T[this.D]|=32;this.mc=this.Ij;return this.Fa[a]};l.gn=function(a){return this.K.getUint16(a,!0)};l.Nj=function(a){return a&1?this.Fa[a]|this.Fa[a+1]<<8:this.qd[a>>1]};l.kn=function(a){this.A.T[this.C]|=32;this.B.T[this.D]|=32;this.af=this.Nj;return a&1?this.Fa[a]|this.Fa[a+1]<<8:this.qd[a>>1]};l.an=function(a){return this.K.getInt32(a,!0)}; -l.Kj=function(a){return a&3?this.Fa[a]|this.Fa[a+1]<<8|this.Fa[a+2]<<16|this.Fa[a+3]<<24:this.T[a>>2]};l.dn=function(a){this.A.T[this.C]|=32;this.B.T[this.D]|=32;this.Md=this.Kj;return a&3?this.Fa[a]|this.Fa[a+1]<<8|this.Fa[a+2]<<16|this.Fa[a+3]<<24:this.T[a>>2]};l.tn=function(a,b){this.Fa[a]=b;this.Da=!0};l.Wj=function(a,b){this.Fa[a]=b;this.Da=!0};l.wn=function(a,b){this.Fa[a]=b;this.A.T[this.C]|=32;this.B.T[this.D]|=96;this.nc=this.Wj;this.J.Da=!0}; -l.Fn=function(a,b){this.K.setUint16(a,b,!0);this.Da=!0};l.Yj=function(a,b){a&1?(this.Fa[a]=b,this.Fa[a+1]=b>>8):this.qd[a>>1]=b;this.Da=!0};l.In=function(a,b){a&1?(this.Fa[a]=b,this.Fa[a+1]=b>>8):this.qd[a>>1]=b;this.A.T[this.C]|=32;this.B.T[this.D]|=96;this.jf=this.Yj;this.J.Da=!0};l.zn=function(a,b){this.K.setInt32(a,b,!0);this.Da=!0};l.Xj=function(a,b){a&3?(this.Fa[a]=b,this.Fa[a+1]=b>>8,this.Fa[a+2]=b>>16,this.Fa[a+3]=b>>24):this.T[a>>2]=b;this.Da=!0}; -l.Cn=function(a,b){a&3?(this.Fa[a]=b,this.Fa[a+1]=b>>8,this.Fa[a+2]=b>>16,this.Fa[a+3]=b>>24):this.T[a>>2]=b;this.A.T[this.C]|=32;this.B.T[this.D]|=96;this.hf=this.Xj;this.J.Da=!0};function zc(a){gb&&!Wb&&(a=a<<24|a<<8&16711680|a>>8&65280|a>>>24);return a} -var nc=0,Vb=2,pc=5,rc=6,Ac=["black","blue","green","cyan"],Mb="NONE RAM ROM VIDEO H/W UNPAGED PAGED".split(" "),mc=0,tc=[],Zb=[u.prototype.Xm,u.prototype.vn,u.prototype.jn,u.prototype.Hn,u.prototype.cn,u.prototype.Bn],wc=[u.prototype.Wm,u.prototype.un,u.prototype.hn,u.prototype.Gn,u.prototype.bn,u.prototype.An],sc=[u.prototype.Zm,u.prototype.xn,u.prototype.ln,u.prototype.Jn,u.prototype.en,u.prototype.Dn],qc=[u.prototype.$m,u.prototype.yn,u.prototype.mn,u.prototype.Kn,u.prototype.fn,u.prototype.En]; -if(gb)var Yb=[u.prototype.Vm,u.prototype.tn,u.prototype.gn,u.prototype.Fn,u.prototype.an,u.prototype.zn],Xb=[u.prototype.Ij,u.prototype.Wj,u.prototype.Nj,u.prototype.Yj,u.prototype.Kj,u.prototype.Xj],Bc=[u.prototype.Ym,u.prototype.wn,u.prototype.kn,u.prototype.In,u.prototype.dn,u.prototype.Cn]; +l.Mj=function(a,b){return this.mc(a++,b++)|this.mc(a,b)<<8};l.Jj=function(a,b){return this.mc(a++,b++)|this.mc(a++,b++)<<8|this.mc(a++,b++)<<16|this.mc(a,b)<<24};l.th=function(a,b,c){this.nc(a++,b&255,c++);this.nc(a,b>>8,c)};l.rh=function(a,b,c){this.nc(a++,b&255,c++);this.nc(a++,b>>8&255,c++);this.nc(a++,b>>16&255,c++);this.nc(a,b>>>24,c)};l.Zm=function(a){return this.T[a>>2]>>>((a&3)<<3)&255}; +l.ln=function(a){var b=a>>2;a=(a&3)<<3;var c=this.T[b]>>a;return 24>a?c&65535:c&255|(this.T[b+1]&255)<<8};l.en=function(a){var b=a>>2;a=(a&3)<<3;var c=this.T[b];a&&(c=c>>>a|this.T[b+1]<<32-a);return c};l.xn=function(a,b){var c=a>>2;a=(a&3)<<3;this.T[c]=this.T[c]&~(255<>2;a=(a&3)<<3;24>a?this.T[c]=this.T[c]&~(65535<>8);this.Da=!0}; +l.Dn=function(a,b){var c=a>>2;if(a=(a&3)<<3){var d=-1<>>32-a}else this.T[c]=b;this.Da=!0};l.Ym=function(a,b){this.F&&xc(this.F,b,1,!1);return this.zd(a,b)};l.kn=function(a,b){this.F&&xc(this.F,b,2,!1);return this.bf(a,b)};l.dn=function(a,b){this.F&&xc(this.F,b,4,!1);return this.mh(a,b)};l.wn=function(a,b,c){this.F&&xc(this.F,c,1,!0);this.H||this.gf(a,b,c)};l.In=function(a,b,c){this.F&&xc(this.F,c,2,!0);this.H||this.uh(a,b,c)}; +l.Cn=function(a,b,c){this.F&&xc(this.F,c,4,!0);this.H||this.U(a,b,c)};l.an=function(a,b){this.A.T[this.C]|=this.I;this.B.T[this.D]|=this.I;return this.J.mc(a,b)};l.nn=function(a,b){this.A.T[this.C]|=this.I;this.B.T[this.D]|=this.I;return this.J.af(a,b)};l.gn=function(a,b){this.A.T[this.C]|=this.I;this.B.T[this.D]|=this.I;return this.J.Md(a,b)};l.zn=function(a,b,c){this.A.T[this.C]|=this.I;this.B.T[this.D]|=this.M;this.J.nc(a,b,c)}; +l.Ln=function(a,b,c){this.A.T[this.C]|=this.I;this.B.T[this.D]|=this.M;this.J.jf(a,b,c)};l.Fn=function(a,b,c){this.A.T[this.C]|=this.I;this.B.T[this.D]|=this.M;this.J.hf(a,b,c)};l.bn=function(a,b){return yc(this.F,b,!1).mc(a,b)};l.on=function(a,b){return yc(this.F,b,!1).af(a,b)};l.hn=function(a,b){return yc(this.F,b,!1).Md(a,b)};l.An=function(a,b,c){yc(this.F,c,!0).nc(a,b,c)};l.Mn=function(a,b,c){yc(this.F,c,!0).jf(a,b,c)};l.Gn=function(a,b,c){yc(this.F,c,!0).hf(a,b,c)};l.Xm=function(a){return this.Fa[a]}; +l.Ij=function(a){return this.Fa[a]};l.$m=function(a){this.A.T[this.C]|=32;this.B.T[this.D]|=32;this.mc=this.Ij;return this.Fa[a]};l.jn=function(a){return this.K.getUint16(a,!0)};l.Nj=function(a){return a&1?this.Fa[a]|this.Fa[a+1]<<8:this.qd[a>>1]};l.mn=function(a){this.A.T[this.C]|=32;this.B.T[this.D]|=32;this.af=this.Nj;return a&1?this.Fa[a]|this.Fa[a+1]<<8:this.qd[a>>1]};l.cn=function(a){return this.K.getInt32(a,!0)}; +l.Kj=function(a){return a&3?this.Fa[a]|this.Fa[a+1]<<8|this.Fa[a+2]<<16|this.Fa[a+3]<<24:this.T[a>>2]};l.fn=function(a){this.A.T[this.C]|=32;this.B.T[this.D]|=32;this.Md=this.Kj;return a&3?this.Fa[a]|this.Fa[a+1]<<8|this.Fa[a+2]<<16|this.Fa[a+3]<<24:this.T[a>>2]};l.vn=function(a,b){this.Fa[a]=b;this.Da=!0};l.Wj=function(a,b){this.Fa[a]=b;this.Da=!0};l.yn=function(a,b){this.Fa[a]=b;this.A.T[this.C]|=32;this.B.T[this.D]|=96;this.nc=this.Wj;this.J.Da=!0}; +l.Hn=function(a,b){this.K.setUint16(a,b,!0);this.Da=!0};l.Yj=function(a,b){a&1?(this.Fa[a]=b,this.Fa[a+1]=b>>8):this.qd[a>>1]=b;this.Da=!0};l.Kn=function(a,b){a&1?(this.Fa[a]=b,this.Fa[a+1]=b>>8):this.qd[a>>1]=b;this.A.T[this.C]|=32;this.B.T[this.D]|=96;this.jf=this.Yj;this.J.Da=!0};l.Bn=function(a,b){this.K.setInt32(a,b,!0);this.Da=!0};l.Xj=function(a,b){a&3?(this.Fa[a]=b,this.Fa[a+1]=b>>8,this.Fa[a+2]=b>>16,this.Fa[a+3]=b>>24):this.T[a>>2]=b;this.Da=!0}; +l.En=function(a,b){a&3?(this.Fa[a]=b,this.Fa[a+1]=b>>8,this.Fa[a+2]=b>>16,this.Fa[a+3]=b>>24):this.T[a>>2]=b;this.A.T[this.C]|=32;this.B.T[this.D]|=96;this.hf=this.Xj;this.J.Da=!0};function zc(a){gb&&!Wb&&(a=a<<24|a<<8&16711680|a>>8&65280|a>>>24);return a} +var nc=0,Vb=2,pc=5,rc=6,Ac=["black","blue","green","cyan"],Mb="NONE RAM ROM VIDEO H/W UNPAGED PAGED".split(" "),mc=0,tc=[],Zb=[u.prototype.Zm,u.prototype.xn,u.prototype.ln,u.prototype.Jn,u.prototype.en,u.prototype.Dn],wc=[u.prototype.Ym,u.prototype.wn,u.prototype.kn,u.prototype.In,u.prototype.dn,u.prototype.Cn],sc=[u.prototype.an,u.prototype.zn,u.prototype.nn,u.prototype.Ln,u.prototype.gn,u.prototype.Fn],qc=[u.prototype.bn,u.prototype.An,u.prototype.on,u.prototype.Mn,u.prototype.hn,u.prototype.Gn]; +if(gb)var Yb=[u.prototype.Xm,u.prototype.vn,u.prototype.jn,u.prototype.Hn,u.prototype.cn,u.prototype.Bn],Xb=[u.prototype.Ij,u.prototype.Wj,u.prototype.Nj,u.prototype.Yj,u.prototype.Kj,u.prototype.Xj],Bc=[u.prototype.$m,u.prototype.yn,u.prototype.mn,u.prototype.Kn,u.prototype.fn,u.prototype.En]; function Cc(a,b){t.call(this,"CPU",a);b=a.cycles||b;var c=a.multiplier||1;this.R={};this.R.Wc=b;this.R.xd=c;this.R.Hf=Math.round(this.R.Wc/1E4)/100;this.R.qe=this.R.Hf*this.R.xd;this.ca.Ib=!1;this.ca.Tj=!1;this.ca.Ee=a.autoStart;this.ca.ii=!1;this.ca.Af=!1;this.R.Jf=this.R.re=0;this.R.Kf=a.csStart;this.R.Je=a.csInterval;this.R.Ke=a.csStop;this.Kl=this.$f.bind(this);eb(this)}ba(Cc,t);l=Cc.prototype; l.hc=function(a,b,c,d){this.oa=a;this.ma=b;this.Ea=d;for(b=0;ba.R.Hf&&(c=Math.round(c/a.R.xd));return c}function Fc(a){a.R.vd=0;a.ld=a.Rc=a.Hc=a.A=0;Gc(a);Jc(a,1)} +function Mc(a,b){var c=Nc;ca.R.Hf&&(c=Math.round(c/a.R.xd));return c}function Fc(a){a.R.vd=0;a.ld=a.Rc=a.Hc=a.A=0;Gc(a);Jc(a,1)} function Jc(a,b,c){if(void 0!==b){.8>a.R.vd/a.R.qe&&(b=1);a.R.xd=b;b=a.R.Hf*a.R.xd;if(a.R.qe!=b){a.R.qe=b;b=a.R.qe.toFixed(2)+"Mhz";var d=a.ia.setSpeed;d&&(d.textContent=b);a.lc("target speed: "+b)}c&&a.oa&&Rc(a.oa)}Lc(a,a.Rc);a.Rc=0;a.R.wd=sa();a.R.Jd=0;Mc(a)} -l.$f=function(a){if(fb(this,!0)){if(!this.ca.Ib){Jc(this);this.oa&&this.oa.start(this.R.wd,Qc(this));this.ca.Ib=!0;this.ca.Tj=!0;this.V&&this.V.start();var b=this.ia.run;b&&(b.textContent="Halt");this.oa&&(Sc(this.oa,!0),a&&Rc(this.oa,!0))}this.R.yg>=this.R.Wc&&Mc(this,!0);this.R.Oe=0;this.R.If=sa();this.R.Jd&&(a=this.R.If-this.R.Jd,a>this.R.Ai&&(this.R.wd+=a,this.R.wd>this.R.If&&(this.R.wd=this.R.If)));try{do{var c=this.ca.Af?1:this.R.Rl;if(this.V){Tc(this.V);var d=this.V;a=c;var e=d.H[0];if(e.Gd){var f= +l.$f=function(a){if(fb(this,!0)){if(!this.ca.Ib){Jc(this);this.oa&&this.oa.start(this.R.wd,Qc(this));this.ca.Ib=!0;this.ca.Tj=!0;this.V&&this.V.start();var b=this.ia.run;b&&(b.textContent="Halt");this.oa&&(Sc(this.oa,!0),a&&Rc(this.oa,!0))}this.R.yg>=this.R.Wc&&Mc(this,!0);this.R.Oe=0;this.R.If=sa();this.R.Jd&&(a=this.R.If-this.R.Jd,a>this.R.Ai&&(this.R.wd+=a,this.R.wd>this.R.If&&(this.R.wd=this.R.If)));try{do{var c=this.ca.Af?1:this.R.Tl;if(this.V){Tc(this.V);var d=this.V;a=c;var e=d.H[0];if(e.Gd){var f= (Qc(d.U,d.V)-e.fd)/d.za|0,g=Uc(d,0)-f;e.mode==Vc&&(g-=f);var h=g*d.za|0;e.mode==Vc&&(h>>=1);a>h&&(a=h)}var c=a,k=this.V;a=c;if(k.A&&k.A[Wc]&Xc){var m=k.X-Qc(k.U,k.V);0m&&(a=m)}c=a}try{this.yh(c)}catch(y){if("number"!=typeof y)throw y;}var q=this.Hc-this.A;this.Rc+=q;this.R.Oe+=q;Lc(this,0,!0);a=q;this.ca.Af&&(b=!1,this.R.Jf=this.R.Jf+this.ri()|0,this.R.re-=a,0>=this.R.re&&(this.R.re+=this.R.Je,b=!0),0<=this.R.Ke&&this.R.Ke<=Qc(this)&&(this.R.Je=this.R.Ke=-1,Gc(this),Ic(this),b=!0),b&&this.lc(Qc(this)+ " cycles: checksum="+ga(this.R.Jf)));this.R.Me-=q;0>=this.R.Me&&(this.R.Me+=this.R.Ci,this.oa&&Yc(this.oa));this.R.Le-=q;0>=this.R.Le&&(this.R.Le+=this.R.Bi,this.oa&&Sc(this.oa));this.R.Ne-=q;if(0>=this.R.Ne){this.R.Ne+=this.R.xg;break}}while(this.ca.Ib)}catch(y){Ic(this);Hc(this);this.oa&&this.oa.stop(sa(),Qc(this));fb(this,!1);cb(this,y.stack||y.message);return}c=setTimeout;d=this.Kl;this.R.Jd=sa();e=this.R.Ai;this.R.Oe&&(e=Math.round(e*this.R.Oe/this.R.xg));e-=this.R.Jd-this.R.If;if(f=this.R.Jd- this.R.wd)this.R.vd=Math.round(this.Rc/(10*f))/100,864E5<=f&&(this.ld=0,this.V&&Tc(this.V,!0),Jc(this));if(0>e||this.R.vde&&(this.R.wd-=e),e=0;this.R.yg+=this.R.Oe;this.R.Jd+=e;c(d,e)}else Hc(this),this.oa&&this.oa.stop(sa(),Qc(this))};l.yh=function(){return 0};function Ic(a,b){a.ca.wf&&(a.ca.lg=!0);a.Hc-=a.A;a.A=0;Lc(a,a.Rc);a.Rc=0;if(a.ca.Ib){a.ca.Ib=!1;a.V&&a.V.stop();var c=a.ia.run;c&&(c.textContent="Run")}a.ca.complete=b} function Hc(a){a.oa&&(Yc(a.oa,void 0),Sc(a.oa,void 0))}var Nc=30,Oc=60,Pc=2,Dc=["power","reset"];function Zc(a,b,c,d){this.B=a;this.Ea=a.Ea;this.id=b;this.Yf=c||"";this.ha=0;this.Ta=65535;this.C=this.Ta+1;this.ob=this.gc=this.ext=this.jb=this.type=this.va=0;this.$b=-1;this.S=this.Jc=2;this.O=this.ra=65535;this.J=this.zi;this.H=this.ei;this.I=this.gi;this.A={ha:-1,va:0,Ta:0,jb:0,type:0,ext:0,$b:-1};1==this.id&&(this.Ze=0,this.D=null,this.ne=!1,this.G=Array(32),this.F=[]);$c(this,!0,d)}l=Zc.prototype; -l.zi=function(a){this.ha=a&65535;return this.va=this.ha<<4};l.Gf=function(a,b){var c,d,e=this.B;a&=65535;a&4?(c=e.jc.va,d=c+e.jc.Ta|0):(c=e.Kb,d=e.Oc);if(c){c=c+(a&65528)|0;if(d-c|0)return e.A-=15,ad(this,c,a,b);this.id>>0)+b<=this.C?this.va+a|0:this.yf()};l.lk=function(a,b){return(a>>>0)+b>this.C?this.va+a|0:this.yf()};l.yf=function(){v.call(this.B,13,0);return-1};l.fi=function(a,b){return(a>>>0)+b<=this.C?this.va+a|0:this.zf()}; +l.zi=function(a){this.ha=a&65535;return this.va=this.ha<<4};l.Gf=function(a,b){var c,d,e=this.B;a&=65535;a&4?(c=e.jc.va,d=c+e.jc.Ta|0):(c=e.Kb,d=e.Oc);if(c){c=c+(a&65528)|0;if(d-c|0)return e.A-=15,ad(this,c,a,b);this.id>>0)+b<=this.C?this.va+a|0:this.yf()};l.lk=function(a,b){return(a>>>0)+b>this.C?this.va+a|0:this.yf()};l.yf=function(){v.call(this.B,13,0);return-1};l.fi=function(a,b){return(a>>>0)+b<=this.C?this.va+a|0:this.zf()}; l.mk=function(a,b){return(a>>>0)+b>this.C?this.va+a|0:this.zf()};l.zf=function(){v.call(this.B,13,0);return-1};function dd(a,b,c,d,e){a.ha=b;a.va=d;a.Ta=e;a.C=(e>>>0)+1;a.jb=c;a.type=c&7936;a.ext=c>>16&192;a.$b=(b&4?a.B.jc.va:a.B.Kb)+(b&65528)|0;a.id>>0)+1;a.jb=e;a.type=e&7936;a.ext=0;a.$b=b;a.id>>0)+1,a.jb=a.A.jb,a.type=a.A.type,a.ext=a.A.ext,a.$b=a.A.$b,a.A.ha=-1,$c(a,!0,!0,!1),a.va;a.A.ha=-1;var f=e.fa(b+0),g=e.fa(b+4),h=g&7936,k=e.fa(b+2)|(g&255)<<16,m=e.fa(b+6),q=c&65528;if(80386<=e.aa){var y=f,k=k|(m&65280)<<16,f=f|(m&15)<<16;m&128&&(f=f<<12|4095)}switch(a.id){case gd:var w=a.D;a.ne=!1;if(w&&c==hd&&a.F.length){var z=a.F[a.Ze-1];if(z&&!z())return-1}var B=c&3,S=(g&24576)>>13,z=-1,X,ca;q|| b>=e.Kb&&b=a.ob&&(B>a.ob&&(z=x(e),id(e,x(e),!0),A(e,z),a.ne=!0),z=0);else{if(256==h||2304==h)return jd(a,c,w)?a.va:-1;if(1024==h)z=2,ca=0,B>>0)+1)}; -function $c(a,b,c,d){void 0===c&&(c=!!(a.B.pa&1));a.ud=!1;if(c)if(a.load=a.Gf,a.yi=a.Ml,a.Lb=a.di,a.Mb=a.fi,void 0===d&&(d=!!(a.B.N&131072)),d)a.load=a.J,a.Lb=a.H,a.Mb=a.I,a.ob=a.gc=3,a.S=2,a.O=a.ra=65535,a.Ta=65535,a.C=a.Ta+1,a.Jc=a.S,a.$b=-1,a.ne=!1;else{if(!(a.ha&-4))a.Lb=a.yf,a.Mb=a.zf;else if(a.type&4096){6144==(a.type&6656)&&(a.Lb=a.yf);if(a.type&2048||!(a.type&512))a.Mb=a.zf;1024==(a.type&3072)&&(a.Lb==a.di&&(a.Lb=a.lk),a.Mb==a.fi&&(a.Mb=a.mk),a.ud=!0);b&&a.id>13,80386>a.B.aa||!(a.ext&64)?(a.S=2,a.O=65535):(a.S=4,a.O=-1),a.Jc=a.S,a.ra=a.O)}else a.load=a.zi,a.yi=a.Nl,a.Lb=a.ei,a.Mb=a.gi,a.ob=a.gc=0,a.$b=-1,a.ne=!1}var gd=1,rd=2,cd=3,ed=4,bd=6,hd=1; +function $c(a,b,c,d){void 0===c&&(c=!!(a.B.pa&1));a.ud=!1;if(c)if(a.load=a.Gf,a.yi=a.Nl,a.Lb=a.di,a.Mb=a.fi,void 0===d&&(d=!!(a.B.N&131072)),d)a.load=a.J,a.Lb=a.H,a.Mb=a.I,a.ob=a.gc=3,a.S=2,a.O=a.ra=65535,a.Ta=65535,a.C=a.Ta+1,a.Jc=a.S,a.$b=-1,a.ne=!1;else{if(!(a.ha&-4))a.Lb=a.yf,a.Mb=a.zf;else if(a.type&4096){6144==(a.type&6656)&&(a.Lb=a.yf);if(a.type&2048||!(a.type&512))a.Mb=a.zf;1024==(a.type&3072)&&(a.Lb==a.di&&(a.Lb=a.lk),a.Mb==a.fi&&(a.Mb=a.mk),a.ud=!0);b&&a.id>13,80386>a.B.aa||!(a.ext&64)?(a.S=2,a.O=65535):(a.S=4,a.O=-1),a.Jc=a.S,a.ra=a.O)}else a.load=a.zi,a.yi=a.Ol,a.Lb=a.ei,a.Mb=a.gi,a.ob=a.gc=0,a.$b=-1,a.ne=!1}var gd=1,rd=2,cd=3,ed=4,bd=6,hd=1; function wd(a){var b=+a.model||8088,c;switch(b){default:c=4772727;break;case 80286:c=6E6;break;case 80386:c=16E6}Cc.call(this,a,c);this.aa=b;a=a.stepping;this.od=b+(a?fa(a,16):0);this.Uh=61442;this.nd=1792;this.Th=28672;this.kf=4;this.Ha=255;this.B=80286<=this.aa?ib:hb;this.qa=xd;this.ci=yd;this.hi=zd;this.li=Ad;if(80186<=this.aa&&(this.qa=xd.slice(),this.ci=yd.slice(),this.hi=zd.slice(),this.Ha=31,this.qa[15]=Bd,this.qa[96]=Cd,this.qa[97]=Dd,this.qa[98]=Ed,this.qa[99]=Bd,this.qa[100]=Bd,this.qa[101]= Bd,this.qa[102]=Bd,this.qa[103]=Bd,this.qa[104]=Fd,this.qa[105]=Gd,this.qa[106]=Hd,this.qa[107]=Id,this.qa[108]=Jd,this.qa[109]=Kd,this.qa[110]=Ld,this.qa[111]=Md,this.qa[192]=Nd,this.qa[193]=Od,this.qa[200]=Pd,this.qa[201]=Qd,this.qa[241]=Rd,this.ci[7]=Sd,this.hi[7]=Sd,80286<=this.aa)){this.Uh=2;this.nd|=28672;this.kf=0;this.qa[15]=Td;this.kd=Ud.slice();for(b=0;b=this.od&&(this.kd[166]=be,this.kd[167]=ce)}}this.nf=[];this.ai=[];this.bg=0;Fc(this);this.ca.complete=this.ca.pk=!1;this.pi=0;this.Nc=this.X=[];this.yb=this.vh=this.xb=this.lf=this.xe=this.ye=this.Ic=0;de(this)}ba(wd,Cc); @@ -141,8 +141,8 @@ function Lb(a){var b;if(a.X===a.Nc){a.X=Array(a.lf);a.pf=new u(null,0,0,pc,null, function yc(a,b,c,d){var e=(b&-4194304)>>>20,f=a.Nc[(a.md+e&a.ye)>>>a.yb],g=f.Md(e);if(!(g&1))return d||fe.call(a,b,!1,c),a.ze;if(!(g&4)&&3==a.Ga)return d||fe.call(a,b,!0,c),a.ze;var h=(b&4190208)>>>10,g=a.Nc[((g&-4096)+h&a.ye)>>>a.yb],k=g.Md(h);if(!(k&1))return d||fe.call(a,b,!1,c),a.ze;if(!(k&4)&&3==a.Ga)return d||fe.call(a,b,!0,c),a.ze;c=a.Nc[((k&-4096)+(b&4095)&a.ye)>>>a.yb];if(d)return c;d=b>>>a.yb;k=a.X[d];b&=-4096;var m;0>2;b.B=g;b.D=h>>2;gb&&Wb&&c.T&&!c.controller&&!c.ee&&!c.fe?(b.Fa=c.Fa,b.qd=c.qd,b.T=c.T,Rb(b,Bc)):(b.I=c?zc(32):0,b.M=c?zc(96):0,Rb(b,sc));Fb(b,a.Ea,k);a.X[d]=b;a.mf.push(d);return b}function ge(a){a.X!==a.Nc&&(a.X=a.Nc,a.pf=null,a.mf=null,a.ze=null)}l=wd.prototype;l.reset=function(){this.ca.Ib&&Ic(this);de(this);Fc(this);this.ca.error=!1}; function he(a,b){var c;switch(b){case 0:c=a.F;break;case 1:c=a.H;break;case 2:c=a.K;break;case 3:c=a.G;break;case 4:c=C(a);break;case 5:c=a.L;break;case 6:c=a.J;break;case 7:c=a.I}return c}function ie(a,b,c){switch(b){case 0:a.F=c;break;case 1:a.H=c;break;case 2:a.K=c;break;case 3:a.G=c;break;case 4:A(a,c);break;case 5:a.L=c;break;case 6:a.J=c;break;case 7:a.I=c}} -function de(a){a.F=0;a.G=0;a.H=0;a.K=0;a.ic=0;a.L=0;a.J=0;a.I=0;a.cc=!1;a.za=a.Va=0;a.ta=0;a.oi=0;a.Z=0;a.pa=65520;a.Tb=0;a.Pc=1023;a.N=a.Wb=0;a.Yd=a.Be=a.Xd=a.Zd=0;a.xc=-1;a.Sc=a.yc=-1;a.ad=a.na=-1;a.ga=new Zc(a,gd,"CS");a.Ca=new Zc(a,rd,"DS");a.ua=new Zc(a,rd,"ES");a.W=new Zc(a,cd,"SS");A(a,0);id(a,0);if(80386<=a.aa){switch(a.od){case 80562:case 80563:a.K=771;break;case 80578:a.K=772;break;case 80594:a.K=773;break;case 80595:case 80596:a.K=776}a.pa=16;a.xi=0;a.Wd=0;a.md=0;a.Bb=[0,0,0,0,null,null, -0,0];a.rf=[null,null,null,null,null,null,0,0];a.qb=new Zc(a,rd,"FS");a.rb=new Zc(a,rd,"GS");ge(a)}a.eg=new Zc(a,0,"NULL");a.Ba=a.Ca;a.Db=a.W;a.M=a.ba=0;a.C=a.D=-1;a.Xa=a.eg;a.Ka=0;if(80286>a.aa)ud(a,0,65535);else{a.Kb=0;a.Oc=65535;a.jc=new Zc(a,5,"LDT",!0);a.la=new Zc(a,ed,"TSS",!0);a.bb=new Zc(a,bd,"VER",!0);ud(a,65520,61440);var b,c=E(a);b=a.ga;var d=-65536;80386>b.B.aa&&(d&=16777215);b=b.va=d;a.ea=b+c|0;a.cg=(b>>>0)+(a.ga.Ta>>>0)+1}td(a,0);kd(a)} +function de(a){a.F=0;a.G=0;a.H=0;a.K=0;a.ic=0;a.L=0;a.J=0;a.I=0;a.cc=!1;a.za=a.Va=0;a.Ml=[0,0];a.Ql=[0,0];a.ta=0;a.oi=0;a.Z=0;a.pa=65520;a.Tb=0;a.Pc=1023;a.N=a.Wb=0;a.Yd=a.Be=a.Xd=a.Zd=0;a.xc=-1;a.Sc=a.yc=-1;a.ad=a.na=-1;a.ga=new Zc(a,gd,"CS");a.Ca=new Zc(a,rd,"DS");a.ua=new Zc(a,rd,"ES");a.W=new Zc(a,cd,"SS");A(a,0);id(a,0);if(80386<=a.aa){switch(a.od){case 80562:case 80563:a.K=771;break;case 80578:a.K=772;break;case 80594:a.K=773;break;case 80595:case 80596:a.K=776}a.pa=16;a.xi=0;a.Wd=0;a.md=0; +a.Bb=[0,0,0,0,null,null,0,0];a.rf=[null,null,null,null,null,null,0,0];a.qb=new Zc(a,rd,"FS");a.rb=new Zc(a,rd,"GS");ge(a)}a.eg=new Zc(a,0,"NULL");a.Ba=a.Ca;a.Db=a.W;a.M=a.ba=0;a.C=a.D=-1;a.Xa=a.eg;a.Ka=0;if(80286>a.aa)ud(a,0,65535);else{a.Kb=0;a.Oc=65535;a.jc=new Zc(a,5,"LDT",!0);a.la=new Zc(a,ed,"TSS",!0);a.bb=new Zc(a,bd,"VER",!0);ud(a,65520,61440);var b,c=E(a);b=a.ga;var d=-65536;80386>b.B.aa&&(d&=16777215);b=b.va=d;a.ea=b+c|0;a.cg=(b>>>0)+(a.ga.Ta>>>0)+1}td(a,0);kd(a)} function je(a){2==a.Jc?(a.vi=a.fa,a.Vb=ke,a.bc=le,a.Qc=me,2==a.S?(a.ja=ne,a.wa=oe,a.$a=pe):(a.ja=qe,a.wa=re,a.$a=se)):(a.vi=a.da,a.Vb=te,a.bc=ue,a.Qc=ve,2==a.S?(a.ja=we,a.wa=xe,a.$a=ye):(a.ja=ze,a.wa=Ae,a.$a=Be))}function ld(a,b){a.S!=b&&(a.ba|=1024,a.S=b,a.O=2==b?65535:-1,Ce(a))}function Ce(a){2==a.S?(a.sb=32768,a.ib=a.fa,a.nb=a.Wa,2==a.Jc?(a.ja=ne,a.wa=oe,a.$a=pe):(a.ja=we,a.wa=xe,a.$a=ye)):(a.sb=-2147483648,a.ib=a.da,a.nb=a.Pa,2==a.Jc?(a.ja=qe,a.wa=re,a.$a=se):(a.ja=ze,a.wa=Ae,a.$a=Be))} function De(a){a.Jc=a.ga.Jc;a.ra=a.ga.ra;je(a);a.S=a.ga.S;a.O=a.ga.O;Ce(a);a.ba&=-3073}l.ri=function(){var a=this.F+this.G+this.H+this.K+C(this)+this.L+this.J+this.I|0;return a=a+E(this)+this.ga.ha+this.Ca.ha+this.W.ha+this.ua.ha+sd(this)|0};function Ee(a,b,c){void 0===a.nf[b]&&(a.nf[b]=[]);a.nf[b].push(c)}function Fe(a,b){var c=a.ai[b];null!=c&&(c(--a.bg),delete a.ai[b])} function Ge(a,b){for(var c=a.Bb[7],d=c>>16,e=0;4>e;e++){if(c&3){var f=!!(d&1),g=a.Bb[e],g=g&~(d>>2&3);if(b){var g=a.X[g>>>a.yb],h=a;f?g.fe++||(h&&(g.F=h),vc(g,wc,!1)):g.ee++||(h&&(g.F=h),uc(g,wc,!1))}else g=a.X[g>>>a.yb],f?--g.fe||(f=g,f.nc=f.H?f.sh:f.gf,f.jf=f.H?f.th:f.uh,f.hf=f.H?f.rh:f.U):--g.ee||(f=g,f.mc=f.zd,f.af=f.bf,f.Md=f.mh)}c>>=2;d>>=4}} @@ -219,7 +219,7 @@ function Zh(a,b){var c=a-b|0;Oe(this,a,b,c,this.sb|63,!0);this.A-=-1===this.D?-1 function ci(a,b){return b>>(this.F&this.O)&(1<<(this.H&31))-1&this.O}function di(a,b){if(-1===this.C){switch(this.Z&7){case 0:this.F=this.F&-256|a;break;case 1:this.H=this.H&-256|a;break;case 2:this.K=this.K&-256|a;break;case 3:this.G=this.G&-256|a;break;case 4:this.F=this.F&-65281|a<<8;break;case 5:this.H=this.H&-65281|a<<8;break;case 6:this.K=this.K&-65281|a<<8;break;case 7:this.G=this.G&-65281|a<<8}this.A-=this.B.ih}else this.D=this.C,kf(this,a),this.A-=this.B.hh;return b} function ei(a,b){if(-1===this.C){switch(this.Z&7){case 0:this.F=this.F&~this.O|a;break;case 1:this.H=this.H&~this.O|a;break;case 2:this.K=this.K&~this.O|a;break;case 3:this.G=this.G&~this.O|a;break;case 4:A(this,C(this)&~this.O|a);break;case 5:this.L=this.G&~this.O|a;break;case 6:this.J=this.J&~this.O|a;break;case 7:this.I=this.I&~this.O|a}this.A-=this.B.ih}else this.D=this.C,this.M&2||this.nb(this.Xa.Mb(this.ab,this.S),a),this.A-=this.B.hh;return b} function fi(a,b){a^=b;H(this,a,128);this.A-=-1===this.D?-1===this.C?this.B.Xb:this.B.vb:this.B.kc;return a}function gi(a,b){this.A-=-1===this.D?-1===this.C?this.B.Xb:this.B.vb:this.B.kc;return H(this,a^b,this.sb)&this.O}function hi(a,b){var c=a[1]-b[1];c||(c=a[0]-b[0]);return c}function ii(a){var b=a-1|0;Oe(this,a,1,b,this.sb|62,!0);this.A-=2;return a&~this.O|b&this.O} -function ji(a,b,c){c>>>=0;if(!c||c<=b>>>0)return!1;var d=0,e=1;c=[c>>>0,0];for(a=[a>>>0,b>>>0];0>>=0,b[1]++);e+=e}do 0<=hi(a,c)&&(b=a,f=c,b[0]-=f[0],b[1]-=f[1],0>b[0]&&(b[0]>>>=0,b[1]--),d+=e),b=c,b[0]>>>=1,b[1]&1&&(b[0]=(b[0]|2147483648)>>>0),b[1]>>>=1,e/=2;while(1<=e);this.za=d;this.Va=a[0];return!0}function ki(a){var b=a+1|0;Oe(this,a,1,b,this.sb|62);this.A-=2;return a&~this.O|b&this.O} +function ji(a,b,c){c>>>=0;if(!c||c<=b>>>0)return!1;var d=0,e=1,f=this.Ml;f[0]=c>>>0;f[1]=0;c=this.Ql;c[0]=a>>>0;for(c[1]=b>>>0;0>>=0,a[1]++),e+=e;do 0<=hi(c,f)&&(a=c,b=f,a[0]-=b[0],a[1]-=b[1],0>a[0]&&(a[0]>>>=0,a[1]--),d+=e),a=f,a[0]>>>=1,a[1]&1&&(a[0]=(a[0]|2147483648)>>>0),a[1]>>>=1,e/=2;while(1<=e);this.za=d;this.Va=c[0];return!0}function ki(a){var b=a+1|0;Oe(this,a,1,b,this.sb|62);this.A-=2;return a&~this.O|b&this.O} function vd(a){this.md=a;this.pa&-2147483648&&Lb(this)}function li(a){this.M|=1;this.bc.call(this,a);this.A-=-1===this.C?4:5}function Kh(a,b,c){if(c){16>>16-c)&65535;H(this,a,32768,d&32768)}return a}function Ph(a,b,c){if(c){var d=a<>>32-c;H(this,a,-2147483648,d&-2147483648)}return a}function Th(a,b,c){if(c){16>>c-1;a=(d>>>1|b<<16-c)&65535;H(this,a,32768,d&1)}return a} function Vh(a,b,c){if(c){var d=a>>>c-1;a=d>>>1|b<<32-c;H(this,a,-2147483648,d&1)}return a}function mi(){this.A-=-1===this.C?2:this.B.Bj;return 1}function ni(){var a=this.H&255;this.A-=(-1===this.C?this.B.Zg:this.B.Yg)+(a<>8} -l.save=function(){var a=new Ie(this);a.set(0,[this.C]);for(var b=[],c=0;c=wf&&(a.set(5,[this.F,this.L,this.K,this.pa,this.M,this.qa]),a.set(6,[this.G[7],this.G,this.W,this.A,this.na,this.X]));return a.data()}; l.restore=function(a){var b,c;b=a[0];Array.isArray(b[0])?this.C=b[0]:(this.C[0][0]=b[0],this.C[1][0]=b[1]&15,this.C[0][1]=b[2],this.C[1][1]=b[3]&15);Hj(this);b=a[1];for(c=0;c=f;f++){var g="pcjs-bitCell";f||(g+=" pcjs-bitCellLeft");d+='
'+f+"
\n"}e.innerHTML=d;Ok(a,b,c,!0)}function Pk(a,b,c){if(b=(a=U[a.aa|0])&&a[b])for(var d in b)if(a=b[d],a.fc&1<d.eb[0]&&(d.eb[0]=255,d.eb[1]--,0>d.eb[1]&&(d.eb[1]=255)));return e}function cl(a,b,c,d){a=a.D[b];c=a.Qb[c];c.eb[a.Cb]=c.ac[a.Cb]=d;a.Cb^=1} -function dl(a,b){a=a.D[b];b=a.zb|el;a.zb&=~fl;return b}function gl(a,b,c){a=a.D[b];b=c&3;a.zb=a.zb&~(16<>2].Qb[b&3],c,d,e)}function kl(a,b,c){b=a.D[b>>2].Qb[b&3];b.Bf&&b.og&&b.Qf?(c&&(b.done=c),b.Id||pl(a,b,!0)):c&&c(!0)} +function dl(a,b){a=a.D[b];b=a.zb|el;a.zb&=~fl;return b}function gl(a,b,c){a=a.D[b];b=c&3;a.zb=a.zb&~(16<>2].Qb[b&3],c,d,e)}function kl(a,b,c){b=a.D[b>>2].Qb[b&3];b.Bf&&b.og&&b.Qf?(c&&(b.done=c),b.Id||pl(a,b,!0)):c&&c(!0)} function pl(a,b,c){c&&(b.count=b.eb[1]<<8|b.eb[0],b.type=b.mode&ql,b.ni=b.Df=!1);for(var d=!1;0<=b.count&&(c=b.uf<<16|b.cb[1]<<8|b.cb[0],b.type==rl?(d=!0,function(c){b.og.call(b.Bf,b.Qf,-1,function(e,g){0>e&&(b.ni||(b.ni=!0),e=255);b.Id||a.ma.Dc(c,e);(d=g)&&setTimeout(function(){sl(b)||pl(a,b)},0)})}(c)):b.type==tl?(c=a.ma.Eb(c),0>b.og.call(b.Bf,b.Qf,c)&&(b.Df=!0)):b.type!=ul&&(b.Df=!0)),!d&&!sl(b););} function sl(a){if(!a.Df&&0<=--a.count&&(a.mode&vl?(a.cb[0]--,0>a.cb[0]&&(a.cb[0]=255,a.cb[1]--,0>a.cb[1]&&(a.cb[1]=255))):(a.cb[0]++,255f&&(d.fd=e,f=0);var g=Xl(a,b),h=Uc(a,b)-f;d.mode==Sl?(0>=h&&(h=0),h||(d.Lc=!0,d.Gd=!1,b||zf(a,Wl))):d.mode==sm?(d.Lc=1!=h,0>=h&&(h=g+h,0>=h&&(h=g),d.vc[0]=h&255,d.vc[1]=h>>8&255,d.fd=e,!b&&d.Lc&&zf(a,Wl))):d.mode==Vc&&(h-=f,0>=h&&(d.Lc=!d.Lc,h=g+h,0>=h&&(h=g),d.vc[0]=h&255,d.vc[1]=h>>8&255,d.fd=e,!b&&d.Lc&&zf(a,Wl)));d.eb[0]=h&255;d.eb[1]=h>>8&255;c&&(a.fd=0)}return d} function Tc(a,b){for(var c=0;c=wf){b=a.U.R.Wc;c=Qc(a.U,a.V);null==a.xa&&(a.na=Qc(a.U,a.V),a.Ha=1024,a.xa=Math.floor(a.U.R.Wc/a.Ha),Fk(a));c>=a.X&&(a.A[Ck]|=tm,a.A[Wc]&Xc&&(a.A[Ck]|=um,zf(a,vm)),a.X=c+a.xa);a.A[pk]==a.A[qk]&&a.A[rk]==a.A[sk]&&a.A[tk]==a.A[uk]&&(a.A[Ck]|=wm,a.A[Wc]&xm&&(a.A[Ck]|=um,zf(a,vm)));var d=c-a.na,e=Math.floor(d/b);if(e&&!(a.A[Wc]&ym)){for(;e--;)if(60<=++a.A[pk]&&(a.A[pk]=0,60<=++a.A[rk]&&(a.A[rk]=0,24<=++a.A[tk]))){a.A[tk]=0;a.A[vk]=a.A[vk]% -7+1;var f;f=a.A[yk];var g=ra[a.A[xk]-1];28==g&&(f%4||!(f%100)&&f%400||g++);f=g;++a.A[wk]>f&&(a.A[wk]=1,12<++a.A[xk]&&(a.A[xk]=1,a.A[yk]=(a.A[yk]+1)%100))}a.A[Ck]|=zm;a.A[Wc]&Am&&(a.A[Ck]|=um,zf(a,vm))}a.na=c-d%b}}l.rl=function(){var a=this.ua;this.ga&Fm&&(this.J&Gm?a=this.C[0][1]:this.B&&(a=Hm(this.B)));return a};l.Jm=function(a,b){this.ua=b};l.sl=function(){return this.J};l.Km=function(a,b){Im(this,b)}; -function Im(a,b){var c=!!(b&Jm),d=!!(a.J&Jm);a.J=b;a.B&&Km(a.B,!(b&Gm),!!(b&pm));c!=d&&Hk(a,c)}l.tl=function(){var a=0,a=(this.aa|0)==rj?this.J&nm?a|this.C[1][1]&Lm:a|this.C[1][1]>>4&1:this.J&Mm?a|this.C[0][1]>>4:a|this.C[0][1]&15;this.J&mm&&Ql(this,Yl).Lc&&(a=this.J&Jm?a|Nm:a|Om);return a};l.Lm=function(a,b){this.Ba=b};l.ul=function(){return this.ga};l.Mm=function(a,b){this.ga=b};l.Dk=function(){var a=this.B?Hm(this.B):0;this.Z&=~Pm;return a};l.Vl=function(){};l.Ck=function(){return this.J}; -l.Ul=function(a,b){Im(this,b)};l.Ek=function(){return this.Z};l.Fk=function(){var a=this.pa;this.F&=~(Pm|Qm);this.B&&Rm(this.B);return a};l.Xl=function(a,b){if(this.F&Sm)switch(this.L){case Tm:Um(this,b);break;case Vm:Wm(this,b);break;default:if(Um(this,this.K&~Qj),this.B){a=this.B;var c=b,d=-1;switch(a.D||c){case Xm:d=Ym;a.Jb=[];Zm(a,$m);break;case an:a.D&&(c=0);Zm(a,Ym);a.D=c;break;case bn:a.D&&(c=0),Zm(a,Ym),a.D=c}cn(this,d)}}this.L=b;this.F&=~Sm}; -l.Gk=function(){return this.J&~(dn|en)|(Qc(this.U)&64?en:0)};l.Yl=function(a,b){Im(this,b)};l.Hk=function(){var a=this.F&255;this.F&Qm&&(this.F|=Pm,this.F&=~Qm);return a}; -l.Wl=function(a,b){this.L=b;this.F|=Sm;a=0;this.L>=fn&&(a=this.L^15,this.L=fn);switch(this.L){case gn:cn(this,this.K);break;case hn:Um(this,this.K|Qj);break;case jn:Um(this,this.K&~Qj);this.B&&Rm(this.B);break;case kn:this.B&&(this.B.Jb=[]);Um(this,this.K|Qj);cn(this,ln);Wm(this,ak|bk);break;case mn:cn(this,nn);break;case on:cn(this,this.M);break;case pn:cn(this,this.qa);break;case qn:cn(this,this.K&Qj?0:rn);break;case fn:a&1&&de(this.U)}}; -function Um(a,b){a.K=b;a.F=a.F&~sn|b&tn;a.B&&Km(a.B,!!(b&un),!(b&Qj))}function cn(a,b,c){0<=b&&(a.pa=b,c?a.F|=Pm:(a.F&=~Pm,a.F|=Qm))}function Wm(a,b){a.qa=b;Gb(a.ma,!!(b&bk));b&ak||de(a.U)}function vn(a,b){a.aaf&&(a.A[wk]=1,12<++a.A[xk]&&(a.A[xk]=1,a.A[yk]=(a.A[yk]+1)%100))}a.A[Ck]|=zm;a.A[Wc]&Am&&(a.A[Ck]|=um,zf(a,vm))}a.na=c-d%b}}l.rl=function(){var a=this.ua;this.ga&Fm&&(this.J&Gm?a=this.C[0][1]:this.B&&(a=Hm(this.B)));return a};l.Lm=function(a,b){this.ua=b};l.sl=function(){return this.J};l.Mm=function(a,b){Im(this,b)}; +function Im(a,b){var c=!!(b&Jm),d=!!(a.J&Jm);a.J=b;a.B&&Km(a.B,!(b&Gm),!!(b&pm));c!=d&&Hk(a,c)}l.tl=function(){var a=0,a=(this.aa|0)==rj?this.J&nm?a|this.C[1][1]&Lm:a|this.C[1][1]>>4&1:this.J&Mm?a|this.C[0][1]>>4:a|this.C[0][1]&15;this.J&mm&&Ql(this,Yl).Lc&&(a=this.J&Jm?a|Nm:a|Om);return a};l.Nm=function(a,b){this.Ba=b};l.ul=function(){return this.ga};l.Om=function(a,b){this.ga=b};l.Dk=function(){var a=this.B?Hm(this.B):0;this.Z&=~Pm;return a};l.Xl=function(){};l.Ck=function(){return this.J}; +l.Wl=function(a,b){Im(this,b)};l.Ek=function(){return this.Z};l.Fk=function(){var a=this.pa;this.F&=~(Pm|Qm);this.B&&Rm(this.B);return a};l.Zl=function(a,b){if(this.F&Sm)switch(this.L){case Tm:Um(this,b);break;case Vm:Wm(this,b);break;default:if(Um(this,this.K&~Qj),this.B){a=this.B;var c=b,d=-1;switch(a.D||c){case Xm:d=Ym;a.Jb=[];Zm(a,$m);break;case an:a.D&&(c=0);Zm(a,Ym);a.D=c;break;case bn:a.D&&(c=0),Zm(a,Ym),a.D=c}cn(this,d)}}this.L=b;this.F&=~Sm}; +l.Gk=function(){return this.J&~(dn|en)|(Qc(this.U)&64?en:0)};l.$l=function(a,b){Im(this,b)};l.Hk=function(){var a=this.F&255;this.F&Qm&&(this.F|=Pm,this.F&=~Qm);return a}; +l.Yl=function(a,b){this.L=b;this.F|=Sm;a=0;this.L>=fn&&(a=this.L^15,this.L=fn);switch(this.L){case gn:cn(this,this.K);break;case hn:Um(this,this.K|Qj);break;case jn:Um(this,this.K&~Qj);this.B&&Rm(this.B);break;case kn:this.B&&(this.B.Jb=[]);Um(this,this.K|Qj);cn(this,ln);Wm(this,ak|bk);break;case mn:cn(this,nn);break;case on:cn(this,this.M);break;case pn:cn(this,this.qa);break;case qn:cn(this,this.K&Qj?0:rn);break;case fn:a&1&&de(this.U)}}; +function Um(a,b){a.K=b;a.F=a.F&~sn|b&tn;a.B&&Km(a.B,!!(b&un),!(b&Qj))}function cn(a,b,c){0<=b&&(a.pa=b,c?a.F|=Pm:(a.F&=~Pm,a.F|=Qm))}function Wm(a,b){a.qa=b;Gb(a.ma,!!(b&bk));b&ak||de(a.U)}function vn(a,b){a.aac?c=c?c:12:c=(c-=12)?c+128:140,d=!0);this.A[Wc]&Bn||(d&&128>4)+(d&15),e=!0);if(a==tk||a==uk)e&&23=d?d=12==d?0:d:(d-=116,d=24==d?12:d))}}else d=b;this.A[a]=d;a==Wc&&c&Xc&&b&Xc&&Fk(this)};l.Gj=function(a,b){this.ba=b};l.wm=function(){};l.xm=function(){this.Ud&&sf(this.Ud)}; +l.om=function(a,b){a=this.W&An;var c=b^this.A[a],d;if(a<=Dk){if(d=b,a>4)+(d&15),e=!0);if(a==tk||a==uk)e&&23=d?d=12==d?0:d:(d-=116,d=24==d?12:d))}}else d=b;this.A[a]=d;a==Wc&&c&Xc&&b&Xc&&Fk(this)};l.Gj=function(a,b){this.ba=b};l.ym=function(){};l.zm=function(){this.Ud&&sf(this.Ud)}; function Hk(a,b){if(a.la)try{void 0!==b?a.Ga=b:b=!!(a.Ga&&a.U&&a.U.ca.Ib);var c=Math.round(vj/Xl(a,Yl));if(20>c||2E4>>4,0,this.F,this.C,this.H),delete this.H);return!0};En.prototype.Nb=function(){return!0}; @@ -480,13 +480,13 @@ vn(e.V,f)):e.Jb.length==No&&e.Jb.push(Oo));d=!0}return d}var lo=["US83","US84"," var fo={TAB:1009,ESC:1027,F1:1112,F2:1113,F3:1114,F4:1115,F5:1116,F6:1117,F7:1118,F8:1119,F9:1120,F10:1121,LEFT:1037,UP:1038,RIGHT:1039,DOWN:1040,SYSREQ:4027,CTRL_C:Po,CTRL_BREAK:Ao,CTRL_ALT_DEL:4046,CTRL_ALT_INS:4045,CTRL_ALT_ENTER:4013},ho={esc:1027,1:n["1"],2:n["2"],3:n["3"],4:n["4"],5:n["5"],6:n["6"],7:n["7"],8:n["8"],9:n["9"],0:n["0"],"-":n["-"],"=":n["="],bs:1008,tab:1009,q:n.Q,w:n.Qh,e:n.E,r:n.Lh,t:n.Nh,y:n.Sh,u:n.Oh,i:n.Dh,o:n.Jh,p:n.Kh,"[":n["["],"]":n["]"],enter:13,ctrl:1017,a:n.Bd,s:n.Mh, d:n.zh,f:n.Ah,g:n.Bh,h:n.Ch,j:n.Eh,k:n.Fh,l:n.Gh,";":n[";"],quote:n["'"],"`":n["`"],shift:1016,"\\":n["\\"],z:n.sf,x:n.Rh,c:n.xh,v:n.Ph,b:n.wh,n:n.Ih,m:n.Hh,",":n[","],".":n["."],"/":n["/"],"right-shift":3016,prtsc:1044,alt:1018,space:1032,"caps-lock":bo,f1:1112,f2:1113,f3:1114,f4:1115,f5:1116,f6:1117,f7:1118,f8:1119,f9:1120,f10:1121,"num-lock":co,"scroll-lock":eo,"num-home":1036,"num-up":1038,"num-pgup":1033,"num-sub":1109,"num-left":1037,"num-center":1101,"num-right":1039,"num-add":1107,"num-end":1035, "num-down":1040,"num-pgdn":1034,"num-ins":1045,"num-del":1046,sysreq:84},ro={"caps-lock":xo,"num-lock":1024,"scroll-lock":2048},V={1027:1};V[n["1"]]=2;V[n["!"]]=2|W<<8;V[n["2"]]=3;V[n["@"]]=3|W<<8;V[n["3"]]=4;V[n["#"]]=4|W<<8;V[n["4"]]=5;V[n.$]=5|W<<8;V[n["5"]]=6;V[n["%"]]=6|W<<8;V[n["6"]]=7;V[n["^"]]=7|W<<8;V[n["7"]]=8;V[n["&"]]=8|W<<8;V[n["8"]]=9;V[n["*"]]=9|W<<8;V[n["9"]]=10;V[n["("]]=10|W<<8;V[n["0"]]=11;V[n[")"]]=11|W<<8;V[n["-"]]=12;V[n._]=12|W<<8;V[n["="]]=13;V[n["+"]]=13|W<<8;V[1008]=Eo; -V[1009]=15;V[n.q]=16;V[n.Q]=16|W<<8;V[n.sn]=17;V[n.Qh]=17|W<<8;V[n.e]=18;V[n.E]=18|W<<8;V[n.r]=19;V[n.Lh]=19|W<<8;V[n.t]=20;V[n.Nh]=20|W<<8;V[n.y]=21;V[n.Sh]=21|W<<8;V[n.qn]=22;V[n.Oh]=22|W<<8;V[n.yk]=23;V[n.Dh]=23|W<<8;V[n.Tl]=24;V[n.Jh]=24|W<<8;V[n.p]=25;V[n.Kh]=25|W<<8;V[n["["]]=26;V[n["{"]]=26|W<<8;V[n["]"]]=27;V[n["}"]]=27|W<<8;V[13]=28;V[1017]=Jo;V[n.Cd]=30;V[n.Bd]=30|W<<8;V[n.nn]=31;V[n.Mh]=31|W<<8;V[n.d]=32;V[n.zh]=32|W<<8;V[n.vk]=33;V[n.Ah]=33|W<<8;V[n.wk]=34;V[n.Bh]=34|W<<8;V[n.xk]=35; -V[n.Ch]=35|W<<8;V[n.Jl]=36;V[n.Eh]=36|W<<8;V[n.k]=37;V[n.Fh]=37|W<<8;V[n.Ll]=38;V[n.Gh]=38|W<<8;V[n[";"]]=39;V[n[":"]]=39|W<<8;V[n["'"]]=40;V[n['"']]=40|W<<8;V[n["`"]]=41;V[n["~"]]=41|W<<8;V[1016]=W;V[n["\\"]]=43;V[n["|"]]=43|W<<8;V[n.z]=44;V[n.sf]=44|W<<8;V[n.x]=45;V[n.Rh]=45|W<<8;V[n.kk]=46;V[n.xh]=46|W<<8;V[n.rn]=47;V[n.Ph]=47|W<<8;V[n.jk]=48;V[n.wh]=48|W<<8;V[n.n]=49;V[n.Ih]=49|W<<8;V[n.Ol]=50;V[n.Hh]=50|W<<8;V[n[","]]=51;V[n["<"]]=51|W<<8;V[n["."]]=52;V[n[">"]]=52|W<<8;V[n["/"]]=53; +V[1009]=15;V[n.q]=16;V[n.Q]=16|W<<8;V[n.un]=17;V[n.Qh]=17|W<<8;V[n.e]=18;V[n.E]=18|W<<8;V[n.r]=19;V[n.Lh]=19|W<<8;V[n.t]=20;V[n.Nh]=20|W<<8;V[n.y]=21;V[n.Sh]=21|W<<8;V[n.sn]=22;V[n.Oh]=22|W<<8;V[n.yk]=23;V[n.Dh]=23|W<<8;V[n.Vl]=24;V[n.Jh]=24|W<<8;V[n.p]=25;V[n.Kh]=25|W<<8;V[n["["]]=26;V[n["{"]]=26|W<<8;V[n["]"]]=27;V[n["}"]]=27|W<<8;V[13]=28;V[1017]=Jo;V[n.Cd]=30;V[n.Bd]=30|W<<8;V[n.pn]=31;V[n.Mh]=31|W<<8;V[n.d]=32;V[n.zh]=32|W<<8;V[n.vk]=33;V[n.Ah]=33|W<<8;V[n.wk]=34;V[n.Bh]=34|W<<8;V[n.xk]=35; +V[n.Ch]=35|W<<8;V[n.Jl]=36;V[n.Eh]=36|W<<8;V[n.k]=37;V[n.Fh]=37|W<<8;V[n.Ll]=38;V[n.Gh]=38|W<<8;V[n[";"]]=39;V[n[":"]]=39|W<<8;V[n["'"]]=40;V[n['"']]=40|W<<8;V[n["`"]]=41;V[n["~"]]=41|W<<8;V[1016]=W;V[n["\\"]]=43;V[n["|"]]=43|W<<8;V[n.z]=44;V[n.sf]=44|W<<8;V[n.x]=45;V[n.Rh]=45|W<<8;V[n.kk]=46;V[n.xh]=46|W<<8;V[n.tn]=47;V[n.Ph]=47|W<<8;V[n.jk]=48;V[n.wh]=48|W<<8;V[n.n]=49;V[n.Ih]=49|W<<8;V[n.Pl]=50;V[n.Hh]=50|W<<8;V[n[","]]=51;V[n["<"]]=51|W<<8;V[n["."]]=52;V[n[">"]]=52|W<<8;V[n["/"]]=53; V[n["?"]]=53|W<<8;V[3016]=54;V[1044]=55;V[1018]=Lo;V[1032]=57;V[bo]=58;V[1112]=59;V[1113]=60;V[1114]=61;V[1115]=62;V[1116]=63;V[1117]=64;V[1118]=65;V[1119]=66;V[1120]=67;V[1121]=68;V[co]=69;V[eo]=70;V[1036]=71;V[1038]=72;V[1033]=73;V[1109]=74;V[1037]=75;V[1101]=76;V[1039]=77;V[1107]=78;V[1035]=79;V[1040]=80;V[1034]=81;V[1045]=82;V[1046]=Fo;V[4027]=84;V[1122]=87;V[1123]=88;V[1091]=91;V[1093]=93;V[1224]=91;V[Po]=46|Jo<<8;V[Ao]=70|Jo<<8;V[4046]=Fo|Jo<<8|Lo<<16;V[4045]=82|Jo<<8|Lo<<16; V[4013]=28|Jo<<8|Lo<<16;var Xm=255,an=243,bn=237,$m=170,Ym=250,Oo=255,No=20;Ea(function(){for(var a=Wa(document,"pcx86","keyboard"),b=0;bc.length)c=[!1,0,null,null,0,Array(b>2,32768));this.Ub=c[0];this.Cc=c[1];this.df=c[2];this.Y=c[3];this.ec=c[4]&255;this.Uf=c[4]>>8&255;this.Qa=c[5];this.tg=So;if(b>=Hn){this.tg=To;(b=c[6])||(b=[!1,0,Array(Uo),0,f==Wj?0:Vo,0,0,Array(Wo),0,0,0,Array(Xo),0,[this.Ya,this.Ab,this.Uc], Array(this.Uc>>2),Yo|Zo|$o|ap|bp,0,-1,0,-1,0,-1,0,0,0,0,cp,dp,0,0,ep,Array(fp)]);this.He=b[0];this.Nd=b[1];this.rc=b[2];this.nh=b[3];this.ef=b[4];this.Xf=b[5];this.Qd=b[6];this.Pd=b[7];this.Oj=b[8];this.Pj=b[9];this.Od=b[10];this.jd=b[11];this.mb=b[12];d=b[13];"number"==typeof d&&(d=[this.Ya,this.Ab,d]);this.Ya=d[0];this.Ab=d[1];d=this.Uc>>2;if((this.pd=b[14])&&this.pd.length=Hn){var c=[];c[0]=a.He;c[1]=a.Nd;c[2]=a.rc;c[3]=a.nh;c[4]=a.ef;c[5]=a.Xf;c[6]=a.Qd;c[7]=a.Pd;c[8]=a.Oj;c[9]=a.Pj;c[10]=a.Od;c[11]=a.jd;c[12]=a.mb;c[13]=[a.Ya,a.Ab,a.Uc];var d;if(d=a.pd){var e=0,f=[];if(void 0!==d[0])for(var g=0;2>g;g++)for(var h=g;h>1;f[e++]=k;h=m}f.length=Hn){var d=0,e=0,f=0;switch(b){case np:d=op;a.Ma==Jn&&(e=pp);break;case qp:a.Ma==Hn&&(d=rp);break;case sp:d=tp;a.Ma==Jn&&(e=up);break;case vp:d=wp;a.Ma==Jn&&(e=xp);break;case yp:d=zp;a.Ma==Jn&&(f=Ap);break;case Bp:d=Cp,a.Ma==Jn&&(f=Dp)}d&&(c|=a.Qa[Ep]&d?256:0,c|=a.Qa[Ep]&e?512:0,c|=a.Qa[Fp]&f?512:0)}return c} @@ -537,28 +537,28 @@ function Zp(a,b){if(a.ca.Pb){var c=!1,d=a.C;d&&(d!==a.A?d.Cc&8&&(c=!0):d.Nd&32&& q,f++),c+=2,d++;a.la=!0;f&&a.Ba&&a.J.drawImage(a.za,0,0,a.$a,a.ab,a.ic,a.jc,a.Kb,a.Tb);Bq(a)}}else if(a.Db){var g=k,w,k=c,d=a.ua=0,f=a.xb,e=16==f?65536:196608,h=16==f?1:2;b=pq(a,h);for(var q=m=0,y=a.F,z=0,B=a.H,S=0;k>8|(w&255)<<8;var X=e,ca=16;m>=h))>>(ca-=h);Aq(a.Ha,m++,q,b[zb])}m>z&&(z=m);q=S&&(S=q+1)}k+=2;d++;if(m>=a.F){m=0;q+=2;if(q>a.H)break;q==a.H&&(q=1,k=c+a.Db)}}a.la=!0; ya.F?a.Ka-a.F-w>>3:0;c>=8;b>y&&(y=b);m=B&&(B=m+1)}c+=S;if(b>=a.F){b=0;if(++m>a.H)break;c+=X}}w||(a.la=!0);qa.F?a.Ka-a.F-B>>3:0;cX&&(ca=X)):(w<<=B,ca-=B,a.la=!1):(a.la&&w===a.L[d]?(h+=ca,ca=0):a.L[d]=w,d++);if(ca){hq&&(q=h);b=z&&(z=b+1)}if(h>=a.F){h=0;if(++b>a.H)break;c+=S}}B||(a.la=!0);ma&&(b.Ag=a,a=-a|0);a%b.wg>b.Ql&&(c|=1);a%b.zg>b.Sl&&(c|=9);b.kh=a/b.zg|0;return c}l.nl=function(){var a=this.W,b;a.Ub&&(b=a.ec);return b};l.Gm=function(a,b){a=this.W;a.Uf=a.ec;a.ec=b&31};l.ml=function(){return $q(this.W)};l.Fm=function(a,b){ar(this,this.W,b)};l.ol=function(){return this.W.Cc};l.Hm=function(a,b){this.W.Cc=b;nq(this,!1)};l.pl=function(){return br(this,this.W)};l.Fj=function(a,b){this.A.Xf=this.A.Xf&-4|b&3}; +0,0,a.F,a.H,0,0,a.X,a.ja))}}}}function Yq(a,b){var c=0;a=Qc(a.U)-b.Ag;0>a&&(b.Ag=a,a=-a|0);a%b.wg>b.Sl&&(c|=1);a%b.zg>b.Ul&&(c|=9);b.kh=a/b.zg|0;return c}l.nl=function(){var a=this.W,b;a.Ub&&(b=a.ec);return b};l.Im=function(a,b){a=this.W;a.Uf=a.ec;a.ec=b&31};l.ml=function(){return $q(this.W)};l.Hm=function(a,b){ar(this,this.W,b)};l.ol=function(){return this.W.Cc};l.Jm=function(a,b){this.W.Cc=b;nq(this,!1)};l.pl=function(){return br(this,this.W)};l.Fj=function(a,b){this.A.Xf=this.A.Xf&-4|b&3}; l.Mk=function(){return this.A.Nd};l.hk=function(){return this.A.rc[this.A.Nd&31]};l.Ej=function(a,b){a=this.A;var c=a.Nd&32;if(a.He){a.He=!1;var d=a.Nd&31;if(16<=d||!c)if(cr||a.rc[d]!==b)a.rc[d]=b,Vq(this,!1)}else a.Nd=b,a.He=!0,b&32&&!c&&fq(this,!0)&&Zp(this,!0),b=(a.Qa[12]<<8)+a.Qa[13]|0,a.Zc!=b&&(a.Zc=b,Vq(this)),a.te=0}; -l.zl=function(){var a=0;if(this.Ma==Hn)a=3-((this.A.ef&12)>>2),a=(this.qb&1<>this.A.sc&63;this.A.sc+=6;12>2),a=(this.qb&1<>this.A.sc&63;this.A.sc+=6;12Missing <canvas> support. Please try a newer web browser.";break}e.setAttribute("class","pcjs-canvas");e.setAttribute("width",d.screenWidth);e.setAttribute("height",d.screenHeight);e.style.backgroundColor=d.screenColor;e.style.height="auto";0<=(window?window.navigator.userAgent:"").indexOf("MSIE")&&(c.onresize=function(a,b,c,d){return function(){b.style.height= (a.clientWidth*d/c|0)+"px"}}(c,e,d.screenWidth,d.screenHeight),c.onresize());var f=+(d.aspect||Aa("aspect"));f&&.3<=f&&3.33>=f&&(Da("onresize",function(a,b,c){return function(){b.style.height=(a.clientWidth/c|0)+"px"}}(c,e,f)),window.onresize());c.appendChild(e);f=document.createElement("textarea");za("iOS")&&(f.setAttribute("autocapitalize","off"),f.setAttribute("autocorrect","off"),f.style.fontSize="16px");c.appendChild(f);var g=e.getContext("2d"),d=new Y(d,e,g,f,c);Va(d,c)}}); function dr(a){t.call(this,"ParallelPort",a);this.G=a.adapter;switch(this.G){case 1:this.D=956;this.C=7;break;case 2:this.D=888;this.C=7;break;case 3:this.D=632;this.C=5;break;default:r("Unrecognized parallel adapter #"+this.G);return}this.A=this.B=null;a=a.binding;"console"==a?this.B="":Ua(this,a,er)}ba(dr,t);l=dr.prototype;l.wb=function(a,b,c){switch(b){case er:return this.ia[b]=this.A=c,!0}return!1}; l.hc=function(a,b,c,d){this.ma=b;this.U=c;this.Ea=d;this.V=nb(a,"ChipSet");cc(b,this,fr,this.D);ec(b,this,gr,this.D);eb(this)};l.Ob=function(a,b){if(!b)if(!a||!this.restore)this.reset();else if(!this.restore(a))return!1;return!0};l.Nb=function(a){return a?this.save():!0};l.reset=function(){hr(this)};l.save=function(){var a=new Ie(this),b=0,c=[];c[b++]=this.F;c[b++]=this.zb;c[b]=this.Fe;a.set(0,c);return a.data()};l.restore=function(a){return hr(this,a[0])}; -function hr(a,b){var c=0;b||(b=[0,0,0]);a.F=b[c++];a.zb=b[c++];a.Fe=b[c];return!0}l.al=function(){return this.F};l.yl=function(){return this.zb};l.Xk=function(){return this.Fe};l.sm=function(a,b){this.F=b;this.zb|=ir;a=!1;this.A&&(8==b?this.A.value=this.A.value.slice(0,-1):(this.A.value+=String.fromCharCode(b),this.A.scrollTop=this.A.scrollHeight),a=!0);if(null!=this.B){if(10==b||1024<=this.B.length)this.lc(this.B),this.B="";10!=b&&(this.B+=String.fromCharCode(b));a=!0}a&&(this.zb&=~ir);jr(this)}; -l.nm=function(a,b){this.Fe=b;jr(this)};function jr(a){a.V&&a.C&&(a.Fe&kr&&!(a.zb&ir)?zf(a.V,a.C):xf(a.V,a.C))}var er="buffer",ir=64,kr=16,fr={0:dr.prototype.al,1:dr.prototype.yl,2:dr.prototype.Xk},gr={0:dr.prototype.sm,2:dr.prototype.nm};Ea(function(){for(var a=Wa(document,"pcx86","parallel"),b=0;b=b)a.preventDefault&&a.preventDefault(),64>8:this.M};l.il=function(){return this.F};l.jl=function(){return this.I};l.ll=function(){return this.X};l.kl=function(){return this.B};l.ql=function(){var a=this.A;this.A&=~(xr|yr);return a}; -l.Pm=function(a,b){if(this.I&Br)this.K=this.K&-256|b;else{this.ja=b;this.B&=~(ur|vr);a=!1;this.Z&&this.Z.call(this.C,b)&&(a=!0);if(this.D){if(13==b)this.J=0;else if(8==b)this.D.value=this.D.value.slice(0,-1),0":String.fromCharCode(b);a=c.length;32>b&&1==a&&(a=0);9==b&&(b=this.na||8,a=b-this.J%b,this.na&&(c=" ".slice(0,a)));this.la&&!this.J&&a&&(c=String.fromCharCode(this.la)+c);this.D.value+=c; -this.D.scrollTop=this.D.scrollHeight;this.J+=a}a=!0}else if(null!=this.G){if(10==b||1024<=this.G.length)this.lc(this.G),this.G="";10!=b&&(this.G+=String.fromCharCode(b));a=!0}a&&(this.B=this.B|ur|vr)}};l.Cm=function(a,b){this.I&Br?this.K=this.K&255|b<<8:this.M=b};l.Dm=function(a,b){this.I=b};l.Em=function(a,b){a=b^this.X;this.X=b;a&(Cr|Dr)&&this.W&&(a=0,this.N?(a|=b&Dr?32:0,a|=b&Cr?320:0):(a|=b&Dr?16:0,a|=b&Cr?1048576:0),this.W.call(this.C,a))}; -function zr(a){var b=-1;a.B&Ar&&a.M&Er?b=Fr:a.A&(xr|yr)&&a.M&Gr&&(b=Hr);0<=b?(a.F&=~(tr|Ir),a.F|=b,a.V&&a.L&&zf(a.V,a.L,100)):(a.F|=tr,a.V&&a.L&&xf(a.V,a.L))}var or="buffer",sr=384,Er=1,Gr=8,tr=1,Fr=4,Hr=0,Ir=6,Br=128,Cr=1,Dr=2,Ar=1,ur=32,vr=64,xr=1,yr=2,mr=16,nr=32,pr={0:lr.prototype.vl,1:lr.prototype.hl,2:lr.prototype.il,3:lr.prototype.jl,4:lr.prototype.ll,5:lr.prototype.kl,6:lr.prototype.ql},qr={0:lr.prototype.Pm,1:lr.prototype.Cm,3:lr.prototype.Dm,4:lr.prototype.Em}; +l.Rm=function(a,b){if(this.I&Br)this.K=this.K&-256|b;else{this.ja=b;this.B&=~(ur|vr);a=!1;this.Z&&this.Z.call(this.C,b)&&(a=!0);if(this.D){if(13==b)this.J=0;else if(8==b)this.D.value=this.D.value.slice(0,-1),0":String.fromCharCode(b);a=c.length;32>b&&1==a&&(a=0);9==b&&(b=this.na||8,a=b-this.J%b,this.na&&(c=" ".slice(0,a)));this.la&&!this.J&&a&&(c=String.fromCharCode(this.la)+c);this.D.value+=c; +this.D.scrollTop=this.D.scrollHeight;this.J+=a}a=!0}else if(null!=this.G){if(10==b||1024<=this.G.length)this.lc(this.G),this.G="";10!=b&&(this.G+=String.fromCharCode(b));a=!0}a&&(this.B=this.B|ur|vr)}};l.Em=function(a,b){this.I&Br?this.K=this.K&255|b<<8:this.M=b};l.Fm=function(a,b){this.I=b};l.Gm=function(a,b){a=b^this.X;this.X=b;a&(Cr|Dr)&&this.W&&(a=0,this.N?(a|=b&Dr?32:0,a|=b&Cr?320:0):(a|=b&Dr?16:0,a|=b&Cr?1048576:0),this.W.call(this.C,a))}; +function zr(a){var b=-1;a.B&Ar&&a.M&Er?b=Fr:a.A&(xr|yr)&&a.M&Gr&&(b=Hr);0<=b?(a.F&=~(tr|Ir),a.F|=b,a.V&&a.L&&zf(a.V,a.L,100)):(a.F|=tr,a.V&&a.L&&xf(a.V,a.L))}var or="buffer",sr=384,Er=1,Gr=8,tr=1,Fr=4,Hr=0,Ir=6,Br=128,Cr=1,Dr=2,Ar=1,ur=32,vr=64,xr=1,yr=2,mr=16,nr=32,pr={0:lr.prototype.vl,1:lr.prototype.hl,2:lr.prototype.il,3:lr.prototype.jl,4:lr.prototype.ll,5:lr.prototype.kl,6:lr.prototype.ql},qr={0:lr.prototype.Rm,1:lr.prototype.Em,3:lr.prototype.Fm,4:lr.prototype.Gm}; Ea(function(){for(var a=Wa(document,"pcx86","serial"),b=0;ba.fb||f[1]>a.gb)&&(this.Aa('Diskette "'+c+'" too large for drive '+String.fromCharCode(65+a.La)),b=null);b?(a.sa=b,a.Qj=c,a.Rd=d,ys(this,c,d,b),f=b.info(),this.I|=Bs,this.Aa('Mounted diskette "'+c+'" in drive '+String.fromCharCode(65+a.La),a.Fd||e),a.Lf=f[0],a.Qe=f[1],a.Re=f[2],this.oa&&Rc(this.oa)):a.le=!1;a.Fd&&(a.Fd=!1,--this.J||eb(this));ls(this,a.La)}; function qs(a,b,c,d){if((a=a.ia.listDisks)&&a.options){for(var e=0;e=this.C&&(this.Y&=~(Es|Fs),this.D=this.C=0);return a}; -l.um=function(a,b){this.C=Hs[a].dd){b=!1;this.D=0;a=Is(this);var c,d,e,f,g,h=a&Gs;switch(h){case Js:Is(this);Is(this);Ks(this);break;case Ls:d=Is(this);this.La=d&3;c=this.A[this.La];Ks(this);Ms(this,(c.hb&Ns)>>>24);break;case Os:case Ps:d=Is(this);b=d>>2&1;this.La=d&3;c=this.A[this.La];c.Sa=b;d=c.ub=Is(this);e=Is(this);f=c.kb=Is(this);g=Is(this);c.tb=128<=this.C&&(this.Y&=~(Es|Fs),this.D=this.C=0);return a}; +l.wm=function(a,b){this.C=Hs[a].dd){b=!1;this.D=0;a=Is(this);var c,d,e,f,g,h=a&Gs;switch(h){case Js:Is(this);Is(this);Ks(this);break;case Ls:d=Is(this);this.La=d&3;c=this.A[this.La];Ks(this);Ms(this,(c.hb&Ns)>>>24);break;case Os:case Ps:d=Is(this);b=d>>2&1;this.La=d&3;c=this.A[this.La];c.Sa=b;d=c.ub=Is(this);e=Is(this);f=c.kb=Is(this);g=Is(this);c.tb=128<>2&1;this.La=d&3;c=this.A[this.La];d=c.ub;e=c.Sa=b;f=c.kb= 1;g=0;c.hb=Ss;c.sa&&(c.Ra=c.sa.seek(c.ub,c.Sa,c.kb))?g=c.Ra.length>>8:c.hb=Qs|Rs;Us(this,c,a,b,d,e,f,g);b=!0;break;case at:d=Is(this);b=d>>2&1;this.La=d&3;c=this.A[this.La];d=c.ub;e=c.Sa=b;f=1;g=Is(this);c.tb=128<>2&1,d=Is(this),c.ub+= -d-c.rd,0>c.ub&&(c.ub=0),c.ub>=c.fb&&(c.ub=c.fb-1),c.rd=d,c.hb=Ws,c.ub||(c.hb|=Xs),Ks(this),b=!0}0>>8);Ms(a,(b.hb&dt)>>>16);var k=0;if(e!=b.ub||f!=b.Sa)k=g=1;c&et&&(f^=k,d||(k=0));Ms(a,e+k);Ms(a,f);Ms(a,g);Ms(a,h)}function Is(a){var b=a.G[a.D];a.D++;return b} +d-c.rd,0>c.ub&&(c.ub=0),c.ub>=c.fb&&(c.ub=c.fb-1),c.rd=d,c.hb=Ws,c.ub||(c.hb|=Xs),Ks(this),b=!0}0>>8);Ms(a,(b.hb&dt)>>>16);var k=0;if(e!=b.ub||f!=b.Sa)k=g=1;c&et&&(f^=k,d||(k=0));Ms(a,e+k);Ms(a,f);Ms(a,g);Ms(a,h)}function Is(a){var b=a.G[a.D];a.D++;return b} function Ks(a){a.D=a.C=0}function Ms(a,b){a.G[a.C++]=b}l.$j=function(a,b,c){void 0===b||0>b?this.ue(a,c):c(-1,!1)};l.ak=function(a,b){return void 0!==b&&0<=b?ft(a,b):-1};l.qk=function(a,b){if(void 0!==b&&0<=b)a:if(a.hb)a=-1;else{a.Fc[a.ge++]=b;if(a.ge==a.Fc.length){a.ub=a.Fc[0];a.Sa=a.Fc[1];a.kb=a.Fc[2];a.tb=128<ft(a,a.Zh)){a=-1;break a}a.xf++}a.xf>=a.sd&&(b=-1);a=b}else a=-1;return a}; l.ue=function(a,b){var c=-1,d=null,e=0;if(!a.hb&&a.sa){do{if(a.Ra&&(e=a.Oa,0<=(c=a.sa.read(a.Ra,a.Oa++)))){d=a.Ra;break}a.Ra=a.sa.seek(a.ub,a.Sa,a.kb);if(!a.Ra){a.hb=gt|Rs;break}a.Oa=0;ht(a)}while(1)}b(c,!1,d,e)};function ft(a,b){if(a.hb||!a.sa)return-1;do{if(a.Ra&&a.sa.write(a.Ra,a.Oa++,b))break;a.Ra=a.sa.seek(a.ub,a.Sa,a.kb);if(!a.Ra){a.hb=it|Rs;b=-1;break}a.Oa=0;ht(a)}while(1);return b}function ht(a){a.kb++;a.kb>=a.Re+1&&(a.kb=1,a.Sa++,a.Sa>=a.Qe&&(a.Sa=0,a.ub++))} var ws="Floppy Drive",Cs=4,Ds=8,Fs=16,Es=64,us=128,Js=3,Ls=4,Os=5,Ps=6,Vs=7,Ys=8,$s=10,at=13,bt=15,Gs=31,et=128,Ss=0,Qs=8,Ws=32,Rs=64,vs=192,Zs=255,Ts=512,gt=1024,it=8192,ct=65280,dt=16711680,Xs=268435456,Ns=-16777216,Bs=128,zs=0;aa={}; -var Hs={3:{dd:3,td:0,name:aa.xo},4:{dd:2,td:1,name:aa.vo},5:{dd:9,td:7,name:aa.Bo},6:{dd:9,td:7,name:aa.ro},7:{dd:2,td:0,name:aa.to},8:{dd:1,td:2,name:aa.wo},10:{dd:2,td:7,name:aa.so},13:{dd:6,td:7,name:aa.oo},15:{dd:3,td:0,name:aa.uo}},os={1009:ks.prototype.cl,1012:ks.prototype.el,1013:ks.prototype.bl,1015:ks.prototype.dl},ps={1010:ks.prototype.vm,1013:ks.prototype.um,1015:ks.prototype.tm}; +var Hs={3:{dd:3,td:0,name:aa.zo},4:{dd:2,td:1,name:aa.xo},5:{dd:9,td:7,name:aa.Do},6:{dd:9,td:7,name:aa.to},7:{dd:2,td:0,name:aa.vo},8:{dd:1,td:2,name:aa.yo},10:{dd:2,td:7,name:aa.uo},13:{dd:6,td:7,name:aa.qo},15:{dd:3,td:0,name:aa.wo}},os={1009:ks.prototype.cl,1012:ks.prototype.el,1013:ks.prototype.bl,1015:ks.prototype.dl},ps={1010:ks.prototype.xm,1013:ks.prototype.wm,1015:ks.prototype.vm}; Ea(function(){for(var a=Wa(document,"pcx86","fdc"),b=0;b=this.C&&(this.D=this.C=0,this.Y&=~(zt|Dt|Et));return a};l.Rm=function(a,b){this.C=a&&(this.Y|=zt,this.Y&=~Gt,Ht(this))};l.Fl=function(){var a=this.Y;this.D=a.B.lb?(a.Y=Jt,a.ue(a.B,function(b){0<=b?(Kt(a),a.V&&a.V.aa==Ej&&(a.Y=0),a.Y=a.Y|rt|Lt|Mt):(a.Y=Nt,a.H=Ot)},!1)):a.Y=rt|Lt));return b}l.dk=function(){return It(this)|It(this)<<8}; -function Pt(a,b){a.B&&a.B.tb>=a.B.lb&&(0>Qt(a.B,b)?(a.Y=Nt,a.H=Ot):(1==a.B.Oa||a.B.Oa==a.B.lb)&&1=a.B.lb&&(a.Y|=Mt)))}l.bm=function(a,b){Pt(this,b&255);Pt(this,b>>8&255)};l.Lk=function(){return this.H};l.gm=function(a,b){this.ta=b};l.Nk=function(){return this.I};l.em=function(a,b){this.I=b};l.Ok=function(){return this.ea};l.fm=function(a,b){this.ea=b};l.Jk=function(){return this.ba};l.am=function(a,b){this.ba=b};l.Ik=function(){return this.Z}; -l.$l=function(a,b){this.Z=b};l.Kk=function(){return this.N};l.cm=function(a,b){this.N=b;this.Y=this.A[this.N&Rt?1:0]?this.Y|rt|Lt:this.Y&~rt};l.Pk=function(){var a=this.Y;this.Y&rt&&(this.Y&=~Jt);return a};l.Zl=function(a,b){this.ga=b;this.V&&xf(this.V,14);St(this)};l.dm=function(a,b){this.K&Tt&&!(b&Tt)&&(this.H=Ut);this.K=b}; +l.El=function(){var a=0;this.D=this.C&&(this.D=this.C=0,this.Y&=~(zt|Dt|Et));return a};l.Tm=function(a,b){this.C=a&&(this.Y|=zt,this.Y&=~Gt,Ht(this))};l.Fl=function(){var a=this.Y;this.D=a.B.lb?(a.Y=Jt,a.ue(a.B,function(b){0<=b?(Kt(a),a.V&&a.V.aa==Ej&&(a.Y=0),a.Y=a.Y|rt|Lt|Mt):(a.Y=Nt,a.H=Ot)},!1)):a.Y=rt|Lt));return b}l.dk=function(){return It(this)|It(this)<<8}; +function Pt(a,b){a.B&&a.B.tb>=a.B.lb&&(0>Qt(a.B,b)?(a.Y=Nt,a.H=Ot):(1==a.B.Oa||a.B.Oa==a.B.lb)&&1=a.B.lb&&(a.Y|=Mt)))}l.dm=function(a,b){Pt(this,b&255);Pt(this,b>>8&255)};l.Lk=function(){return this.H};l.im=function(a,b){this.ta=b};l.Nk=function(){return this.I};l.gm=function(a,b){this.I=b};l.Ok=function(){return this.ea};l.hm=function(a,b){this.ea=b};l.Jk=function(){return this.ba};l.cm=function(a,b){this.ba=b};l.Ik=function(){return this.Z}; +l.bm=function(a,b){this.Z=b};l.Kk=function(){return this.N};l.em=function(a,b){this.N=b;this.Y=this.A[this.N&Rt?1:0]?this.Y|rt|Lt:this.Y&~rt};l.Pk=function(){var a=this.Y;this.Y&rt&&(this.Y&=~Jt);return a};l.am=function(a,b){this.ga=b;this.V&&xf(this.V,14);St(this)};l.fm=function(a,b){this.K&Tt&&!(b&Tt)&&(this.H=Ut);this.K=b}; function St(a){var b=!1,c=a.ga,d=a.N&Rt?1:0,e=a.N&Vt,f=a.ba|(a.Z&Wt)<<8,g=a.ea,h=a.I||256;a.La=-1;a.B=null;a.H=Xt;a.Y=rt|Lt;var k=a.A[d];k?(k.Ad=f,k.Sa=e,k.kb=g,k.tb=h*k.lb,c=c>=Yt?c:c&Zt,k.Ra=null,k.Oa=0,k.errorCode=0,a.La=d,a.B=k):c=-1;switch(c&Zt){case $t:b=!0;break;case au:a.Y=Jt;a.ue(k,function(b){0<=b&&a.V?(Kt(a),a.Y=rt|Lt|Mt):(a.Y=Nt,a.H=Ot)},!1);break;case bu:a.Y=Mt;break;case cu:b=!0;break;case du:b=!0;break;case Yt:a.H=Ut;b=!0;break;case eu:k.gb=e+1,k.Za=h,b=!0}b&&Kt(a)} function Kt(a){!a.V||a.K&fu||zf(a.V,14,120)} function Ht(a){a.D=0;var b=gu(a),c=gu(a),d=c&32,e=d>>5,f=c&31,g=gu(a),h=gu(a),k=g<<2&768|h,m=g&63,q=gu(a),y=gu(a),w=a.A[e];w&&(w.Ad=k,w.Sa=f,w.kb=m,w.tb=q*w.lb);switch(b){case hu:iu(a,w?w.errorCode:ju);ku(a,c);ku(a,g);ku(a,h);ku(a,lu|d);b=-1;break;case Ft:for(c=0;0<=(b=gu(a));)w&&c=a.Za+b&&(a.kb=b,a.Sa++,a.Sa>=a.gb&&(a.Sa=0,a.Ad++))}l.Hl=function(){var a=this.U.K&255;!(this.U.F>>8)&&128>8||!this.V)||(a=!(this.V.Zb[0].Tc&64));return a?!0:!1}; var ut="Hard Drive",xt=["XTC","ATC","COMPAQ"],vt=[{0:[306,2],1:[375,8],2:[306,6],3:[306,4]},{1:[306,4],2:[615,4],3:[615,6],4:[940,8],5:[940,6],6:[615,4],7:[462,8],8:[733,5],9:[900,15],10:[820,3],11:[855,5],12:[855,7],13:[306,8],14:[733,7],16:[612,4],17:[977,5],18:[977,7],19:[1024,7],20:[733,5],21:[733,7],22:[733,5],23:[306,4]},{1:[306,4],2:[615,4],3:[615,6],4:[1023,8],5:[940,6],6:[697,5],7:[462,8],8:[925,5],9:[900,15],10:[980,5],11:[925,7],12:[925,9],13:[612,8],14:[980,4],16:[612,4],17:[980,5],18:[966, 6],19:[1023,8],20:[733,5],21:[733,7],22:[524,4,40],23:[924,8],24:[966,14],25:[966,16],26:[1023,14],27:[832,6,33],28:[1222,15,34],29:[1240,7,34],30:[615,4,25],31:[615,8,25],32:[905,9,25],33:[832,8,33],34:[966,7,34],35:[966,8,34],36:[966,9,34],37:[966,5,34],38:[612,16,63],39:[1023,11,33],40:[1023,15,34],41:[1630,15,52],42:[1023,16,63],43:[805,4,26],44:[805,2,26],45:[748,8,33],46:[748,6,33],47:[966,5,25]}],nt=496,Ut=1,Xt=0,Ot=16,Wt=3,Vt=15,Rt=16,Nt=1,Mt=8,Lt=16,rt=64,Jt=128,$t=16,au=32,bu=48,cu=64,du= -112,Yt=144,eu=145,Zt=240,fu=2,Tt=4,lu=0,mu=2,pu=0,qu=1,hu=3,ru=5,su=8,uu=10,Ft=12,wu=15,nu=224,ou=228,tt=0,ju=4,yu=20,st=0,Gt=1,zt=2,Dt=4,Et=8,yt=32,kt={800:Z.prototype.El,801:Z.prototype.Fl,802:Z.prototype.Dl},jt={496:Z.prototype.dk,497:Z.prototype.Lk,498:Z.prototype.Nk,499:Z.prototype.Ok,500:Z.prototype.Jk,501:Z.prototype.Ik,502:Z.prototype.Kk,503:Z.prototype.Pk},mt={800:Z.prototype.Rm,801:Z.prototype.Um,802:Z.prototype.Tm,803:Z.prototype.Sm,807:Z.prototype.lh,811:Z.prototype.lh,815:Z.prototype.lh}, -lt={496:Z.prototype.bm,497:Z.prototype.gm,498:Z.prototype.em,499:Z.prototype.fm,500:Z.prototype.am,501:Z.prototype.$l,502:Z.prototype.cm,503:Z.prototype.Zl,1014:Z.prototype.dm};Ea(function(){for(var a=Wa(document,"pcx86","hdc"),b=0;b\nLicense: GPL version 3 or later ");for(b=0;b} r64Dst is a 64-bit value + * @param {Array.} r64Src is a 64-bit value */ -X86.helpAdd64 = function(dst, src) +X86.helpAdd64 = function(r64Dst, r64Src) { - dst[0] += src[0]; - dst[1] += src[1]; - if (dst[0] > 0xffffffff) { - dst[0] >>>= 0; // truncate dst[0] to 32 bits AND keep it unsigned - dst[1]++; + r64Dst[0] += r64Src[0]; + r64Dst[1] += r64Src[1]; + if (r64Dst[0] > 0xffffffff) { + r64Dst[0] >>>= 0; // truncate r64Dst[0] to 32 bits AND keep it unsigned + r64Dst[1]++; } }; /** - * helpCmp64(dst, src) + * helpCmp64(r64Dst, r64Src) * - * Compares dst to src, by computing dst - src. + * Compares r64Dst to r64Src, by computing r64Dst - r64Src. * - * @param {Array} dst is a 64-bit value - * @param {Array} src is a 64-bit value - * @return {number} > 0 if dst > src, == 0 if dst == src, < 0 if dst < src + * @param {Array.} r64Dst is a 64-bit value + * @param {Array.} r64Src is a 64-bit value + * @return {number} > 0 if r64Dst > r64Src, == 0 if r64Dst == r64Src, < 0 if r64Dst < r64Src */ -X86.helpCmp64 = function(dst, src) +X86.helpCmp64 = function(r64Dst, r64Src) { - var result = dst[1] - src[1]; - if (!result) result = dst[0] - src[0]; + var result = r64Dst[1] - r64Src[1]; + if (!result) result = r64Dst[0] - r64Src[0]; return result; }; /** - * helpSet64(lo, hi) + * helpSet64(r64Dst, lo, hi) * + * @param {Array.} r64Dst * @param {number} lo * @param {number} hi + * @return {Array.} */ -X86.helpSet64 = function(lo, hi) +X86.helpSet64 = function(r64Dst, lo, hi) { - return [lo >>> 0, hi >>> 0]; + r64Dst[0] = lo >>> 0; + r64Dst[1] = hi >>> 0; + return r64Dst; }; /** - * helpShr64(dst) + * helpShr64(r64Dst) * - * Shifts dst right one bit. + * Shifts r64Dst right one bit. * - * @param {Array} dst is a 64-bit value + * @param {Array.} r64Dst is a 64-bit value */ -X86.helpShr64 = function(dst) +X86.helpShr64 = function(r64Dst) { - dst[0] >>>= 1; - if (dst[1] & 0x1) { - dst[0] = (dst[0] | 0x80000000) >>> 0; + r64Dst[0] >>>= 1; + if (r64Dst[1] & 0x1) { + r64Dst[0] = (r64Dst[0] | 0x80000000) >>> 0; } - dst[1] >>>= 1; + r64Dst[1] >>>= 1; }; /** - * helpSub64(dst, src) + * helpSub64(r64Dst, r64Src) * - * Subtracts src from dst. + * Subtracts r64Src from r64Dst. * - * @param {Array} dst is a 64-bit value - * @param {Array} src is a 64-bit value + * @param {Array.} r64Dst is a 64-bit value + * @param {Array.} r64Src is a 64-bit value */ -X86.helpSub64 = function(dst, src) +X86.helpSub64 = function(r64Dst, r64Src) { - dst[0] -= src[0]; - dst[1] -= src[1]; - if (dst[0] < 0) { - dst[0] >>>= 0; // truncate dst[0] to 32 bits AND keep it unsigned - dst[1]--; + r64Dst[0] -= r64Src[0]; + r64Dst[1] -= r64Src[1]; + if (r64Dst[0] < 0) { + r64Dst[0] >>>= 0; // truncate r64Dst[0] to 32 bits AND keep it unsigned + r64Dst[1]--; } }; @@ -144,32 +148,33 @@ X86.helpDECreg = function(w) X86.helpDIV32 = function(dstLo, dstHi, src) { src >>>= 0; + if (!src || src <= (dstHi >>> 0)) { return false; } var result = 0, bit = 1; - var div = X86.helpSet64(src, 0); - var rem = X86.helpSet64(dstLo, dstHi); + var r64Div = X86.helpSet64(this.r64Div, src, 0); + var r64Rem = X86.helpSet64(this.r64Rem, dstLo, dstHi); - while (X86.helpCmp64(rem, div) > 0) { - X86.helpAdd64(div, div); + while (X86.helpCmp64(r64Rem, r64Div) > 0) { + X86.helpAdd64(r64Div, r64Div); bit += bit; } do { - if (X86.helpCmp64(rem, div) >= 0) { - X86.helpSub64(rem, div); + if (X86.helpCmp64(r64Rem, r64Div) >= 0) { + X86.helpSub64(r64Rem, r64Div); result += bit; } - X86.helpShr64(div); + X86.helpShr64(r64Div); bit /= 2; } while (bit >= 1); - this.assert(result <= 0xffffffff && !rem[1]); + this.assert(result <= 0xffffffff && !r64Rem[1]); - this.regMDLo = result; // result is the quotient, which callers expect in the low MD register - this.regMDHi = rem[0]; // rem[0] is the remainder, which callers expect in the high MD register + this.regMDLo = result; // result is the quotient, which callers expect in the low MD register + this.regMDHi = r64Rem[0]; // r64Rem[0] is the remainder, which callers expect in the high MD register return true; }; diff --git a/versions/pcx86/1.34.0/pcx86-dbg.js b/versions/pcx86/1.34.0/pcx86-dbg.js index 079a824a0..8ed72740c 100644 --- a/versions/pcx86/1.34.0/pcx86-dbg.js +++ b/versions/pcx86/1.34.0/pcx86-dbg.js @@ -44,9 +44,9 @@ http://pcjs.org/modules/shared/lib/save.js (C) Jeff Parsons 2012-2017 */ var l,aa;function ba(a,b){function c(){}c.prototype=b.prototype;a.prototype=new c;a.prototype.constructor=a;for(var d in b)if(Object.defineProperties){var e=Object.getOwnPropertyDescriptor(b,d);e&&Object.defineProperty(a,d,e)}else a[d]=b[d]} -var da={163840:[40,1,8,,254],184320:[40,1,9,,252],327680:[40,2,8,,255],368640:[40,2,9,,253],737280:[80,2,9,,249],1228800:[80,2,15,,249],1474560:[80,2,18,,240],2949120:[80,2,36,,240],21368320:[615,4,17],256256:[77,1,26,128],2494464:[203,2,12,512],5242880:[256,2,40,256],10485760:[512,2,40,256]},n={jp:0,lp:1,mp:2,hl:3,np:4,op:5,pp:6,qp:7,rp:8,sp:9,tp:10,up:11,vp:12,wp:13,xp:14,yp:15,zp:16,Ap:17,Bp:18,Cp:19,Dp:20,Ep:21,Fp:22,Gp:23,Hp:24,Ip:25,Jp:26," ":32,"!":33,'"':34,"#":35,$:36,"%":37,"&":38,"'":39, -"(":40,")":41,"*":42,"+":43,",":44,"-":45,".":46,"/":47,0:48,1:49,2:50,3:51,4:52,5:53,6:54,7:55,8:56,9:57,":":58,";":59,"<":60,"=":61,">":62,"?":63,"@":64,ce:65,ti:66,ui:67,vi:68,E:69,wi:70,xi:71,yi:72,zi:73,Ai:74,Bi:75,Ci:76,Di:77,Ei:78,Fi:79,Gi:80,Q:81,Hi:82,Ii:83,Ji:84,Ki:85,Li:86,Mi:87,Ni:88,Oi:89,eg:90,"[":91,"\\":92,"]":93,"^":94,_:95,"`":96,de:97,vl:98,xl:99,d:100,e:101,Hl:102,Il:103,Jl:104,Kl:105,an:106,k:107,bn:108,fn:109,n:110,on:111,p:112,q:113,r:114,Lo:115,t:116,Oo:117,Po:118,Qo:119,x:120, -y:121,z:122,"{":123,"|":124,"}":125,"~":126,Kp:127},ea={};ea[173]=n["-"];ea[186]=n[";"];ea[187]=n["="];ea[189]=n["-"];ea[188]=n[","];ea[190]=n["."];ea[191]=n["/"];ea[192]=n["`"];ea[219]=n["["];ea[220]=n["\\"];ea[221]=n["]"];ea[222]=n["'"];var fa={};fa[n["1"]]=n["!"];fa[n["2"]]=n["@"];fa[n["3"]]=n["#"];fa[n["4"]]=n.$;fa[n["5"]]=n["%"];fa[n["6"]]=n["^"];fa[n["7"]]=n["&"];fa[n["8"]]=n["*"];fa[n["9"]]=n["("];fa[n["0"]]=n[")"];fa[186]=n[":"];fa[187]=n["+"];fa[188]=n["<"];fa[189]=n._;fa[190]=n[">"]; +var da={163840:[40,1,8,,254],184320:[40,1,9,,252],327680:[40,2,8,,255],368640:[40,2,9,,253],737280:[80,2,9,,249],1228800:[80,2,15,,249],1474560:[80,2,18,,240],2949120:[80,2,36,,240],21368320:[615,4,17],256256:[77,1,26,128],2494464:[203,2,12,512],5242880:[256,2,40,256],10485760:[512,2,40,256]},n={lp:0,np:1,op:2,hl:3,pp:4,qp:5,rp:6,sp:7,tp:8,up:9,vp:10,wp:11,xp:12,yp:13,zp:14,Ap:15,Bp:16,Cp:17,Dp:18,Ep:19,Fp:20,Gp:21,Hp:22,Ip:23,Jp:24,Kp:25,Lp:26," ":32,"!":33,'"':34,"#":35,$:36,"%":37,"&":38,"'":39, +"(":40,")":41,"*":42,"+":43,",":44,"-":45,".":46,"/":47,0:48,1:49,2:50,3:51,4:52,5:53,6:54,7:55,8:56,9:57,":":58,";":59,"<":60,"=":61,">":62,"?":63,"@":64,ce:65,ti:66,ui:67,vi:68,E:69,wi:70,xi:71,yi:72,zi:73,Ai:74,Bi:75,Ci:76,Di:77,Ei:78,Fi:79,Gi:80,Q:81,Hi:82,Ii:83,Ji:84,Ki:85,Li:86,Mi:87,Ni:88,Oi:89,eg:90,"[":91,"\\":92,"]":93,"^":94,_:95,"`":96,de:97,vl:98,xl:99,d:100,e:101,Hl:102,Il:103,Jl:104,Kl:105,an:106,k:107,dn:108,hn:109,n:110,qn:111,p:112,q:113,r:114,No:115,t:116,Qo:117,Ro:118,So:119,x:120, +y:121,z:122,"{":123,"|":124,"}":125,"~":126,Mp:127},ea={};ea[173]=n["-"];ea[186]=n[";"];ea[187]=n["="];ea[189]=n["-"];ea[188]=n[","];ea[190]=n["."];ea[191]=n["/"];ea[192]=n["`"];ea[219]=n["["];ea[220]=n["\\"];ea[221]=n["]"];ea[222]=n["'"];var fa={};fa[n["1"]]=n["!"];fa[n["2"]]=n["@"];fa[n["3"]]=n["#"];fa[n["4"]]=n.$;fa[n["5"]]=n["%"];fa[n["6"]]=n["^"];fa[n["7"]]=n["&"];fa[n["8"]]=n["*"];fa[n["9"]]=n["("];fa[n["0"]]=n[")"];fa[186]=n[":"];fa[187]=n["+"];fa[188]=n["<"];fa[189]=n._;fa[190]=n[">"]; fa[191]=n["?"];fa[192]=n["~"];fa[219]=n["{"];fa[220]=n["|"];fa[221]=n["}"];fa[222]=n['"'];fa[173]=n._;fa[61]=n["+"];fa[59]=n[":"]; function ga(a,b){var c;if(a){b||(b=10);var d=a.charAt(0),e=0>=1,f--;return d}function ia(a,b,c){var d="";if(!b||4>=8;return(c?"0b":"")+d} @@ -106,39 +106,39 @@ l.fc=function(a,b){this.aa[(a&this.C)>>>this.A].Dc(a&this.D,b&255,a)};function B function Cc(a,b){var c=0,d=[],e=!a.J&&a.N==a.C;e||fc(a,!0);for(var f=0;f>>=f)&k;if(void 0!==g){if(g[0])g[0](b,k,e);a.ba&&a.L!=g[1]&&Kc(a.ba,b,k)}else a.ba&&(Db(a.ba,a,b,k,e),a.L&&Kc(a.ba,b,k));f+=h<<3;b+=h;c-=h}}function hc(a,b,c,d,e){b="Memory block error ("+b+": "+q(c)+","+q(d)+")";e?a.ba?a.ba.message(b):a.log(b):Wa(b);return!1}var Ub,Lc={Hk:20,count:8,Zp:1,type:3},Nc=0,Oc;for(Oc in Lc){var Pc=Lc[Oc];Lc[Oc]={nh:(1<>>=f)&k;if(void 0!==g){if(g[0])g[0](b,k,e);a.ba&&a.L!=g[1]&&Kc(a.ba,b,k)}else a.ba&&(Db(a.ba,a,b,k,e),a.L&&Kc(a.ba,b,k));f+=h<<3;b+=h;c-=h}}function hc(a,b,c,d,e){b="Memory block error ("+b+": "+q(c)+","+q(d)+")";e?a.ba?a.ba.message(b):a.log(b):Wa(b);return!1}var Ub,Lc={Hk:20,count:8,aq:1,type:3},Nc=0,Oc;for(Oc in Lc){var Pc=Lc[Oc];Lc[Oc]={nh:(1<>1),this.X=new Int32Array(this.I,0,c>>2),pc(this,wc?xc:yc);else{this.X=Array(c>>2);for(e=0;e>2),b=0;b>8,c)};l.oi=function(a,b,c){this.Dc(a++,b&255,c++);this.Dc(a++,b>>8&255,c++);this.Dc(a++,b>>16&255,c++);this.Dc(a,b>>>24,c)};l.vo=function(a){return this.X[a>>2]>>>((a&3)<<3)&255}; -l.Ho=function(a){var b=a>>2;a=(a&3)<<3;var c=this.X[b]>>a;return 24>a?c&65535:c&255|(this.X[b+1]&255)<<8};l.Bo=function(a){var b=a>>2;a=(a&3)<<3;var c=this.X[b];a&&(c=c>>>a|this.X[b+1]<<32-a);return c};l.To=function(a,b){var c=a>>2;a=(a&3)<<3;this.X[c]=this.X[c]&~(255<>2;a=(a&3)<<3;24>a?this.X[c]=this.X[c]&~(65535<>8);this.Oa=!0}; -l.Zo=function(a,b){var c=a>>2;if(a=(a&3)<<3){var d=-1<>>32-a}else this.X[c]=b;this.Oa=!0};l.uo=function(a,b){this.ba&&null!=this.xa&&dd(this.ba,this.xa+a)||this.C&&ed(this.C,b,1,!1);return this.Zd(a,b)};l.Go=function(a,b){this.ba&&null!=this.xa&&dd(this.ba,this.xa+a,2)||this.C&&ed(this.C,b,2,!1);return this.Nf(a,b)};l.Ao=function(a,b){this.ba&&null!=this.xa&&dd(this.ba,this.xa+a,4)||this.C&&ed(this.C,b,4,!1);return this.ji(a,b)}; -l.So=function(a,b,c){this.ba&&null!=this.xa&&fd(this.ba,this.xa+a)||this.C&&ed(this.C,c,1,!0);this.D?this.cf(0,b,c):this.bf(a,b,c)};l.ep=function(a,b,c){this.ba&&null!=this.xa&&fd(this.ba,this.xa+a,2)||this.C&&ed(this.C,c,2,!0);this.D?this.cf(0,b,c):this.ri(a,b,c)};l.Yo=function(a,b,c){this.ba&&null!=this.xa&&fd(this.ba,this.xa+a,4)||this.C&&ed(this.C,c,4,!0);this.D?this.cf(0,b,c):this.K(a,b,c)};l.xo=function(a,b){this.lb.X[this.A]|=this.F;this.mb.X[this.B]|=this.F;return this.pd.Bc(a,b)}; -l.Jo=function(a,b){this.lb.X[this.A]|=this.F;this.mb.X[this.B]|=this.F;return this.pd.Mf(a,b)};l.Do=function(a,b){this.lb.X[this.A]|=this.F;this.mb.X[this.B]|=this.F;return this.pd.Nd(a,b)};l.Vo=function(a,b,c){this.lb.X[this.A]|=this.F;this.mb.X[this.B]|=this.J;this.pd.Dc(a,b,c)};l.hp=function(a,b,c){this.lb.X[this.A]|=this.F;this.mb.X[this.B]|=this.J;this.pd.Tf(a,b,c)};l.ap=function(a,b,c){this.lb.X[this.A]|=this.F;this.mb.X[this.B]|=this.J;this.pd.Sf(a,b,c)}; -l.yo=function(a,b){return gd(this.C,b,!1).Bc(a,b)};l.Ko=function(a,b){return gd(this.C,b,!1).Mf(a,b)};l.Eo=function(a,b){return gd(this.C,b,!1).Nd(a,b)};l.Wo=function(a,b,c){gd(this.C,c,!0).Dc(a,b,c)};l.ip=function(a,b,c){gd(this.C,c,!0).Tf(a,b,c)};l.bp=function(a,b,c){gd(this.C,c,!0).Sf(a,b,c)};l.to=function(a){return this.Pa[a]};l.Qk=function(a){return this.Pa[a]};l.wo=function(a){this.lb.X[this.A]|=32;this.mb.X[this.B]|=32;this.Bc=this.Qk;return this.Pa[a]}; -l.Fo=function(a){return this.G.getUint16(a,!0)};l.Vk=function(a){return a&1?this.Pa[a]|this.Pa[a+1]<<8:this.Rd[a>>1]};l.Io=function(a){this.lb.X[this.A]|=32;this.mb.X[this.B]|=32;this.Mf=this.Vk;return a&1?this.Pa[a]|this.Pa[a+1]<<8:this.Rd[a>>1]};l.zo=function(a){return this.G.getInt32(a,!0)};l.Sk=function(a){return a&3?this.Pa[a]|this.Pa[a+1]<<8|this.Pa[a+2]<<16|this.Pa[a+3]<<24:this.X[a>>2]}; -l.Co=function(a){this.lb.X[this.A]|=32;this.mb.X[this.B]|=32;this.Nd=this.Sk;return a&3?this.Pa[a]|this.Pa[a+1]<<8|this.Pa[a+2]<<16|this.Pa[a+3]<<24:this.X[a>>2]};l.Ro=function(a,b){this.Pa[a]=b;this.Oa=!0};l.el=function(a,b){this.Pa[a]=b;this.Oa=!0};l.Uo=function(a,b){this.Pa[a]=b;this.lb.X[this.A]|=32;this.mb.X[this.B]|=96;this.Dc=this.el;this.pd.Oa=!0};l.cp=function(a,b){this.G.setUint16(a,b,!0);this.Oa=!0};l.gl=function(a,b){a&1?(this.Pa[a]=b,this.Pa[a+1]=b>>8):this.Rd[a>>1]=b;this.Oa=!0}; -l.gp=function(a,b){a&1?(this.Pa[a]=b,this.Pa[a+1]=b>>8):this.Rd[a>>1]=b;this.lb.X[this.A]|=32;this.mb.X[this.B]|=96;this.Tf=this.gl;this.pd.Oa=!0};l.Xo=function(a,b){this.G.setInt32(a,b,!0);this.Oa=!0};l.fl=function(a,b){a&3?(this.Pa[a]=b,this.Pa[a+1]=b>>8,this.Pa[a+2]=b>>16,this.Pa[a+3]=b>>24):this.X[a>>2]=b;this.Oa=!0}; -l.$o=function(a,b){a&3?(this.Pa[a]=b,this.Pa[a+1]=b>>8,this.Pa[a+2]=b>>16,this.Pa[a+3]=b>>24):this.X[a>>2]=b;this.lb.X[this.A]|=32;this.mb.X[this.B]|=96;this.Sf=this.fl;this.pd.Oa=!0};function hd(a){Eb&&!wc&&(a=a<<24|a<<8&16711680|a>>8&65280|a>>>24);return a} -var Tc=0,vc=2,Uc=5,Wc=6,id=["black","blue","green","cyan"],kc="NONE RAM ROM VIDEO H/W UNPAGED PAGED".split(" "),Sc=0,Yc=[],zc=[x.prototype.vo,x.prototype.To,x.prototype.Ho,x.prototype.fp,x.prototype.Bo,x.prototype.Zo],cd=[x.prototype.uo,x.prototype.So,x.prototype.Go,x.prototype.ep,x.prototype.Ao,x.prototype.Yo],Xc=[x.prototype.xo,x.prototype.Vo,x.prototype.Jo,x.prototype.hp,x.prototype.Do,x.prototype.ap],Vc=[x.prototype.yo,x.prototype.Wo,x.prototype.Ko,x.prototype.ip,x.prototype.Eo,x.prototype.bp]; -if(Eb)var yc=[x.prototype.to,x.prototype.Ro,x.prototype.Fo,x.prototype.cp,x.prototype.zo,x.prototype.Xo],xc=[x.prototype.Qk,x.prototype.el,x.prototype.Vk,x.prototype.gl,x.prototype.Sk,x.prototype.fl],nd=[x.prototype.wo,x.prototype.Uo,x.prototype.Io,x.prototype.gp,x.prototype.Co,x.prototype.$o]; +l.Uk=function(a,b){return this.Bc(a++,b++)|this.Bc(a,b)<<8};l.Rk=function(a,b){return this.Bc(a++,b++)|this.Bc(a++,b++)<<8|this.Bc(a++,b++)<<16|this.Bc(a,b)<<24};l.pi=function(a,b,c){this.Dc(a++,b&255,c++);this.Dc(a,b>>8,c)};l.oi=function(a,b,c){this.Dc(a++,b&255,c++);this.Dc(a++,b>>8&255,c++);this.Dc(a++,b>>16&255,c++);this.Dc(a,b>>>24,c)};l.xo=function(a){return this.X[a>>2]>>>((a&3)<<3)&255}; +l.Jo=function(a){var b=a>>2;a=(a&3)<<3;var c=this.X[b]>>a;return 24>a?c&65535:c&255|(this.X[b+1]&255)<<8};l.Do=function(a){var b=a>>2;a=(a&3)<<3;var c=this.X[b];a&&(c=c>>>a|this.X[b+1]<<32-a);return c};l.Vo=function(a,b){var c=a>>2;a=(a&3)<<3;this.X[c]=this.X[c]&~(255<>2;a=(a&3)<<3;24>a?this.X[c]=this.X[c]&~(65535<>8);this.Oa=!0}; +l.ap=function(a,b){var c=a>>2;if(a=(a&3)<<3){var d=-1<>>32-a}else this.X[c]=b;this.Oa=!0};l.wo=function(a,b){this.ba&&null!=this.xa&&dd(this.ba,this.xa+a)||this.C&&ed(this.C,b,1,!1);return this.Zd(a,b)};l.Io=function(a,b){this.ba&&null!=this.xa&&dd(this.ba,this.xa+a,2)||this.C&&ed(this.C,b,2,!1);return this.Nf(a,b)};l.Co=function(a,b){this.ba&&null!=this.xa&&dd(this.ba,this.xa+a,4)||this.C&&ed(this.C,b,4,!1);return this.ji(a,b)}; +l.Uo=function(a,b,c){this.ba&&null!=this.xa&&fd(this.ba,this.xa+a)||this.C&&ed(this.C,c,1,!0);this.D?this.cf(0,b,c):this.bf(a,b,c)};l.gp=function(a,b,c){this.ba&&null!=this.xa&&fd(this.ba,this.xa+a,2)||this.C&&ed(this.C,c,2,!0);this.D?this.cf(0,b,c):this.ri(a,b,c)};l.$o=function(a,b,c){this.ba&&null!=this.xa&&fd(this.ba,this.xa+a,4)||this.C&&ed(this.C,c,4,!0);this.D?this.cf(0,b,c):this.K(a,b,c)};l.zo=function(a,b){this.lb.X[this.A]|=this.F;this.mb.X[this.B]|=this.F;return this.pd.Bc(a,b)}; +l.Lo=function(a,b){this.lb.X[this.A]|=this.F;this.mb.X[this.B]|=this.F;return this.pd.Mf(a,b)};l.Fo=function(a,b){this.lb.X[this.A]|=this.F;this.mb.X[this.B]|=this.F;return this.pd.Nd(a,b)};l.Xo=function(a,b,c){this.lb.X[this.A]|=this.F;this.mb.X[this.B]|=this.J;this.pd.Dc(a,b,c)};l.jp=function(a,b,c){this.lb.X[this.A]|=this.F;this.mb.X[this.B]|=this.J;this.pd.Tf(a,b,c)};l.cp=function(a,b,c){this.lb.X[this.A]|=this.F;this.mb.X[this.B]|=this.J;this.pd.Sf(a,b,c)}; +l.Ao=function(a,b){return gd(this.C,b,!1).Bc(a,b)};l.Mo=function(a,b){return gd(this.C,b,!1).Mf(a,b)};l.Go=function(a,b){return gd(this.C,b,!1).Nd(a,b)};l.Yo=function(a,b,c){gd(this.C,c,!0).Dc(a,b,c)};l.kp=function(a,b,c){gd(this.C,c,!0).Tf(a,b,c)};l.ep=function(a,b,c){gd(this.C,c,!0).Sf(a,b,c)};l.vo=function(a){return this.Pa[a]};l.Qk=function(a){return this.Pa[a]};l.yo=function(a){this.lb.X[this.A]|=32;this.mb.X[this.B]|=32;this.Bc=this.Qk;return this.Pa[a]}; +l.Ho=function(a){return this.G.getUint16(a,!0)};l.Vk=function(a){return a&1?this.Pa[a]|this.Pa[a+1]<<8:this.Rd[a>>1]};l.Ko=function(a){this.lb.X[this.A]|=32;this.mb.X[this.B]|=32;this.Mf=this.Vk;return a&1?this.Pa[a]|this.Pa[a+1]<<8:this.Rd[a>>1]};l.Bo=function(a){return this.G.getInt32(a,!0)};l.Sk=function(a){return a&3?this.Pa[a]|this.Pa[a+1]<<8|this.Pa[a+2]<<16|this.Pa[a+3]<<24:this.X[a>>2]}; +l.Eo=function(a){this.lb.X[this.A]|=32;this.mb.X[this.B]|=32;this.Nd=this.Sk;return a&3?this.Pa[a]|this.Pa[a+1]<<8|this.Pa[a+2]<<16|this.Pa[a+3]<<24:this.X[a>>2]};l.To=function(a,b){this.Pa[a]=b;this.Oa=!0};l.el=function(a,b){this.Pa[a]=b;this.Oa=!0};l.Wo=function(a,b){this.Pa[a]=b;this.lb.X[this.A]|=32;this.mb.X[this.B]|=96;this.Dc=this.el;this.pd.Oa=!0};l.fp=function(a,b){this.G.setUint16(a,b,!0);this.Oa=!0};l.gl=function(a,b){a&1?(this.Pa[a]=b,this.Pa[a+1]=b>>8):this.Rd[a>>1]=b;this.Oa=!0}; +l.ip=function(a,b){a&1?(this.Pa[a]=b,this.Pa[a+1]=b>>8):this.Rd[a>>1]=b;this.lb.X[this.A]|=32;this.mb.X[this.B]|=96;this.Tf=this.gl;this.pd.Oa=!0};l.Zo=function(a,b){this.G.setInt32(a,b,!0);this.Oa=!0};l.fl=function(a,b){a&3?(this.Pa[a]=b,this.Pa[a+1]=b>>8,this.Pa[a+2]=b>>16,this.Pa[a+3]=b>>24):this.X[a>>2]=b;this.Oa=!0}; +l.bp=function(a,b){a&3?(this.Pa[a]=b,this.Pa[a+1]=b>>8,this.Pa[a+2]=b>>16,this.Pa[a+3]=b>>24):this.X[a>>2]=b;this.lb.X[this.A]|=32;this.mb.X[this.B]|=96;this.Sf=this.fl;this.pd.Oa=!0};function hd(a){Eb&&!wc&&(a=a<<24|a<<8&16711680|a>>8&65280|a>>>24);return a} +var Tc=0,vc=2,Uc=5,Wc=6,id=["black","blue","green","cyan"],kc="NONE RAM ROM VIDEO H/W UNPAGED PAGED".split(" "),Sc=0,Yc=[],zc=[x.prototype.xo,x.prototype.Vo,x.prototype.Jo,x.prototype.hp,x.prototype.Do,x.prototype.ap],cd=[x.prototype.wo,x.prototype.Uo,x.prototype.Io,x.prototype.gp,x.prototype.Co,x.prototype.$o],Xc=[x.prototype.zo,x.prototype.Xo,x.prototype.Lo,x.prototype.jp,x.prototype.Fo,x.prototype.cp],Vc=[x.prototype.Ao,x.prototype.Yo,x.prototype.Mo,x.prototype.kp,x.prototype.Go,x.prototype.ep]; +if(Eb)var yc=[x.prototype.vo,x.prototype.To,x.prototype.Ho,x.prototype.fp,x.prototype.Bo,x.prototype.Zo],xc=[x.prototype.Qk,x.prototype.el,x.prototype.Vk,x.prototype.gl,x.prototype.Sk,x.prototype.fl],nd=[x.prototype.yo,x.prototype.Wo,x.prototype.Ko,x.prototype.ip,x.prototype.Eo,x.prototype.bp]; function od(a,b){ab.call(this,"CPU",a,1);b=a.cycles||b;var c=a.multiplier||1;this.T={};this.T.sd=b;this.T.Kd=c;this.T.rg=Math.round(this.T.sd/1E4)/100;this.T.oe=this.T.rg*this.T.Kd;this.ea.vb=!1;this.ea.ni=!1;this.ea.lf=a.autoStart;this.ea.kj=!1;this.ea.He=!1;this.T.uf=this.T.Te=0;this.T.wf=a.csStart;this.T.Se=a.csInterval;this.T.Ue=a.csStop;this.Ql=this.Uf.bind(this);zb(this)}ba(od,ab);l=od.prototype; l.uc=function(a,b,c,d){this.pa=a;this.ka=b;this.ba=d;for(b=0;b=a.T.Te&&(a.T.Te+=a.T.Se,c=!0);0<=a.T.Ue&&a.T.Ue<=vd(a)&&(a.T.Se=a.T.Ue=-1,sd(a),a.Jb(),c=!0);c&&a.P(vd(a)+" cycles: checksum="+q(a.T.uf))}} l.Cb=function(a,b,c){var d=this;a=!1;switch(b){case "power":case "reset":this.na[b]=c;a=!0;break;case "run":this.na[b]=c;c.onclick=function(){var a;if(a=d.pa)if(a=d.pa,a.ea.Yb)a=!0;else{var b=null,c,h=jb(a.id);for(c=0;ca.T.rg&&(c=Math.round(c/a.T.Kd));return c}function rd(a){a.T.Wd=0;a.Dd=a.md=a.zc=a.A=0;sd(a);wd(a,1)} +function zd(a,b){var c=Ad;ca.T.rg&&(c=Math.round(c/a.T.Kd));return c}function rd(a){a.T.Wd=0;a.Dd=a.md=a.zc=a.A=0;sd(a);wd(a,1)} function wd(a,b,c){var d=!1;if(void 0!==b){.8>a.T.Wd/a.T.oe?b=1:d=!0;a.T.Kd=b;b=a.T.rg*a.T.Kd;if(a.T.oe!=b){a.T.oe=b;b=a.T.oe.toFixed(2)+"Mhz";var e=a.na.setSpeed;e&&(e.textContent=b);a.P("target speed: "+b)}c&&a.pa&&a.pa.ld()}yd(a,a.md);a.md=0;a.T.Xd=Ba();a.T.pe=0;zd(a);return d} -l.Uf=function(a){if(Bb(this,!0)){if(!this.ea.vb){wd(this);this.pa&&this.pa.start(this.T.Xd,vd(this));this.ea.vb=!0;this.ea.ni=!0;this.W&&this.W.start();var b=this.na.run;b&&(b.textContent="Halt");this.pa&&(Dd(this.pa,!0),a&&this.pa.ld(!0))}this.T.th>=this.T.sd&&zd(this,!0);this.T.zf=0;this.T.sg=Ba();this.T.pe&&(a=this.T.sg-this.T.pe,a>this.T.Dj&&(this.T.Xd+=a,this.T.Xd>this.T.sg&&(this.T.Xd=this.T.sg)));try{do{var c=this.ea.He?1:this.T.kn;if(this.W){Ed(this.W);var d=this.W;a=c;var e=d.G[0];if(e.le){var f= +l.Uf=function(a){if(Bb(this,!0)){if(!this.ea.vb){wd(this);this.pa&&this.pa.start(this.T.Xd,vd(this));this.ea.vb=!0;this.ea.ni=!0;this.W&&this.W.start();var b=this.na.run;b&&(b.textContent="Halt");this.pa&&(Dd(this.pa,!0),a&&this.pa.ld(!0))}this.T.th>=this.T.sd&&zd(this,!0);this.T.zf=0;this.T.sg=Ba();this.T.pe&&(a=this.T.sg-this.T.pe,a>this.T.Dj&&(this.T.Xd+=a,this.T.Xd>this.T.sg&&(this.T.Xd=this.T.sg)));try{do{var c=this.ea.He?1:this.T.nn;if(this.W){Ed(this.W);var d=this.W;a=c;var e=d.G[0];if(e.le){var f= (vd(d.H,d.O)-e.jd)/d.Ba|0,g=Fd(d,0)-f;e.mode==Gd&&(g-=f);var h=g*d.Ba|0;e.mode==Gd&&(h>>=1);a>h&&(a=h)}var c=a,k=this.W;a=c;if(k.A&&k.A[Hd]&Id){var m=k.Z-vd(k.H,k.O);0m&&(a=m)}c=a}try{this.Lg(c)}catch(w){if("number"!=typeof w)throw w;}var p=this.zc-this.A;this.md+=p;this.T.zf+=p;yd(this,0,!0);ud(this,p);this.T.yf-=p;0>=this.T.yf&&(this.T.yf+=this.T.Fj,this.pa&&Jd(this.pa));this.T.xf-=p;0>=this.T.xf&&(this.T.xf+=this.T.Ej,this.pa&&Dd(this.pa));this.T.Ve-=p;if(0>=this.T.Ve){this.T.Ve+=this.T.sh; break}}while(this.ea.vb)}catch(w){this.Jb();td(this);this.pa&&this.pa.stop(Ba(),vd(this));Bb(this,!1);wb(this,w.stack||w.message);return}c=setTimeout;d=this.Ql;this.T.pe=Ba();e=this.T.Dj;this.T.zf&&(e=Math.round(e*this.T.zf/this.T.sh));e-=this.T.pe-this.T.sg;if(f=this.T.pe-this.T.Xd)this.T.Wd=Math.round(this.md/(10*f))/100,864E5<=f&&(this.Dd=0,this.W&&Ed(this.W,!0),wd(this));if(0>e||this.T.Wde&&(this.T.Xd-=e),e=0;this.T.th+=this.T.zf;this.T.pe+=e;c(d,e)}else td(this),this.pa&&this.pa.stop(Ba(), vd(this))};l.Lg=function(){return 0};l.Jb=function(a){Ab(this,!0);this.zc-=this.A;this.A=0;yd(this,this.md);this.md=0;if(this.ea.vb){this.ea.vb=!1;this.W&&this.W.stop();var b=this.na.run;b&&(b.textContent="Run")}this.ea.complete=a};function td(a,b){a.pa&&(Jd(a.pa,b),Dd(a.pa,b))}var Ad=30,Bd=60,Cd=2,pd=["power","reset"]; function Kd(a,b,c,d){this.gc=a;this.ba=a.ba;this.id=b;this.$b=c||"";this.U=0;this.Ka=65535;this.Nb=this.Ka+1;this.Ab=this.qc=this.ext=this.jb=this.type=this.ua=0;this.Lb=-1;this.V=this.Jc=2;this.R=this.wa=65535;this.G=this.mh;this.D=this.dj;this.F=this.fj;this.A={U:-1,ua:0,Ka:0,jb:0,type:0,ext:0,Lb:-1};1==this.id&&(this.Jf=0,this.C=null,this.Pe=!1,this.H=Array(32),this.B=[]);Md(this,!0,d)}function Nd(a,b){a.B.push(b);return[a.B.length,Od]}l=Kd.prototype; -l.mh=function(a){this.U=a&65535;return this.ua=this.U<<4};l.qg=function(a,b){var c,d,e=this.gc;a&=65535;a&4?(c=e.Eb.ua,d=c+e.Eb.Ka|0):(c=e.Kb,d=e.Pc);if(c){c=c+(a&65528)|0;if(d-c|0)return e.A-=15,Pd(this,c,a,b);this.id>>0)+b<=this.Nb?this.ua+a|0:this.kg()};l.zl=function(a,b){return(a>>>0)+b>this.Nb?this.ua+a|0:this.kg()};l.kg=function(){y.call(this.gc,13,0);return-1};l.ej=function(a,b){return(a>>>0)+b<=this.Nb?this.ua+a|0:this.lg()}; +l.mh=function(a){this.U=a&65535;return this.ua=this.U<<4};l.qg=function(a,b){var c,d,e=this.gc;a&=65535;a&4?(c=e.Eb.ua,d=c+e.Eb.Ka|0):(c=e.Kb,d=e.Pc);if(c){c=c+(a&65528)|0;if(d-c|0)return e.A-=15,Pd(this,c,a,b);this.id>>0)+b<=this.Nb?this.ua+a|0:this.kg()};l.zl=function(a,b){return(a>>>0)+b>this.Nb?this.ua+a|0:this.kg()};l.kg=function(){y.call(this.gc,13,0);return-1};l.ej=function(a,b){return(a>>>0)+b<=this.Nb?this.ua+a|0:this.lg()}; l.Al=function(a,b){return(a>>>0)+b>this.Nb?this.ua+a|0:this.lg()};l.lg=function(){y.call(this.gc,13,0);return-1};function Td(a,b,c,d,e){a.U=b;a.ua=d;a.Ka=e;a.Nb=(e>>>0)+1;a.jb=c;a.type=c&7936;a.ext=c>>16&192;a.Lb=(b&4?a.gc.Eb.ua:a.gc.Kb)+(b&65528)|0;a.id>>0)+1;a.jb=e;a.type=e&7936;a.ext=0;a.Lb=b;a.id>>0)+1,a.jb=a.A.jb,a.type=a.A.type,a.ext=a.A.ext,a.Lb=a.A.Lb,a.A.U=-1,Md(a,!0,!0,!1),a.ua;a.A.U=-1;var f=e.ja(b+0),g=e.ja(b+4),h=g&7936,k=e.ja(b+2)|(g&255)<<16,m=e.ja(b+6),p=c&65528;if(80386<=e.ca){var w=f,k=k|(m&65280)<<16,f=f|(m&15)<<16;m&128&&(f=f<<12|4095)}switch(a.id){case Wd:var u=a.C;a.Pe=!1;if(u&&c==Od&&a.B.length){var F=a.B[a.Jf-1];if(F&&!F())return-1}var K=c&3,H=(g&24576)>>13,F=-1,I,T;p||b>= e.Kb&&b=a.Ab&&(K>a.Ab&&(F=Xd(e),Yd(e,Xd(e),!0),Zd(e,F),a.Pe=!0),F=0);else{if(256==h||2304==h)return $d(a,c,u)?a.ua:-1;if(1024==h)F=2,T=0,K>>0)+1)}; -function Md(a,b,c,d){void 0===c&&(c=!!(a.gc.qa&1));a.Oc=!1;if(c)if(a.load=a.qg,a.Cj=a.dn,a.bc=a.cj,a.cc=a.ej,void 0===d&&(d=!!(a.gc.O&131072)),d)a.load=a.G,a.bc=a.D,a.cc=a.F,a.Ab=a.qc=3,a.V=2,a.R=a.wa=65535,a.Ka=65535,a.Nb=a.Ka+1,a.Jc=a.V,a.Lb=-1,a.Pe=!1;else{if(!(a.U&-4))a.bc=a.kg,a.cc=a.lg;else if(a.type&4096){6144==(a.type&6656)&&(a.bc=a.kg);if(a.type&2048||!(a.type&512))a.cc=a.lg;1024==(a.type&3072)&&(a.bc==a.cj&&(a.bc=a.zl),a.cc==a.ej&&(a.cc=a.Al),a.Oc=!0);b&&a.id>13,80386>a.gc.ca||!(a.ext&64)?(a.V=2,a.R=65535):(a.V=4,a.R=-1),a.Jc=a.V,a.wa=a.R)}else a.load=a.mh,a.Cj=a.en,a.bc=a.dj,a.cc=a.fj,a.Ab=a.qc=0,a.Lb=-1,a.Pe=!1}var Wd=1,he=2,Rd=3,Ud=4,Qd=6,Od=1; +function Md(a,b,c,d){void 0===c&&(c=!!(a.gc.qa&1));a.Oc=!1;if(c)if(a.load=a.qg,a.Cj=a.fn,a.bc=a.cj,a.cc=a.ej,void 0===d&&(d=!!(a.gc.O&131072)),d)a.load=a.G,a.bc=a.D,a.cc=a.F,a.Ab=a.qc=3,a.V=2,a.R=a.wa=65535,a.Ka=65535,a.Nb=a.Ka+1,a.Jc=a.V,a.Lb=-1,a.Pe=!1;else{if(!(a.U&-4))a.bc=a.kg,a.cc=a.lg;else if(a.type&4096){6144==(a.type&6656)&&(a.bc=a.kg);if(a.type&2048||!(a.type&512))a.cc=a.lg;1024==(a.type&3072)&&(a.bc==a.cj&&(a.bc=a.zl),a.cc==a.ej&&(a.cc=a.Al),a.Oc=!0);b&&a.id>13,80386>a.gc.ca||!(a.ext&64)?(a.V=2,a.R=65535):(a.V=4,a.R=-1),a.Jc=a.V,a.wa=a.R)}else a.load=a.mh,a.Cj=a.gn,a.bc=a.dj,a.cc=a.fj,a.Ab=a.qc=0,a.Lb=-1,a.Pe=!1}var Wd=1,he=2,Rd=3,Ud=4,Qd=6,Od=1; function me(a){var b=+a.model||8088,c;switch(b){default:c=4772727;break;case 80286:c=6E6;break;case 80386:c=16E6}od.call(this,a,c);this.ca=b;a=a.stepping;this.we=b+(a?ga(a,16):0);this.Ri=61442;this.Qd=1792;this.si=28672;this.Vf=4;this.Ta=255;this.B=80286<=this.ca?Gb:Fb;this.va=ne;this.bj=oe;this.ij=pe;this.nj=qe;if(80186<=this.ca&&(this.va=ne.slice(),this.bj=oe.slice(),this.ij=pe.slice(),this.Ta=31,this.va[15]=re,this.va[96]=se,this.va[97]=te,this.va[98]=ue,this.va[99]=re,this.va[100]=re,this.va[101]= re,this.va[102]=re,this.va[103]=re,this.va[104]=ze,this.va[105]=Ae,this.va[106]=Be,this.va[107]=Ce,this.va[108]=De,this.va[109]=Ee,this.va[110]=Fe,this.va[111]=Ge,this.va[192]=He,this.va[193]=Ie,this.va[200]=Je,this.va[201]=Ke,this.va[241]=Le,this.bj[7]=Me,this.ij[7]=Me,80286<=this.ca)){this.Ri=2;this.Qd|=28672;this.Vf=0;this.va[15]=Ne;this.Pd=Oe.slice();for(b=0;b=this.we&&(this.Pd[166]=We,this.Pd[167]=Xe)}}this.Yf=[];this.Zf=[];this.ag=0;rd(this);this.ea.complete=this.ea.jj=!1;this.tj=0;this.sc=this.aa=[];this.qb=this.Ng=this.Db=this.Wf=this.df=this.ef=this.ed=0;Ye(this)}ba(me,od);function Ze(a,b,c,d){b=(d?a.sc:a.aa)[b>>>a.qb];c?--b.ie||bd(b):--b.he||ad(b);d&&jc(a)} @@ -158,8 +158,8 @@ function $e(a){var b;if(a.aa===a.sc){a.aa=Array(a.Wf);a.$f=new x(null,0,0,Uc,nul function gd(a,b,c,d){var e=(b&-4194304)>>>20,f=a.sc[(a.Wc+e&a.ef)>>>a.qb],g=f.Nd(e);if(!(g&1))return d||ef.call(a,b,!1,c),a.ff;if(!(g&4)&&3==a.Ma)return d||ef.call(a,b,!0,c),a.ff;var h=(b&4190208)>>>10,g=a.sc[((g&-4096)+h&a.ef)>>>a.qb],k=g.Nd(h);if(!(k&1))return d||ef.call(a,b,!1,c),a.ff;if(!(k&4)&&3==a.Ma)return d||ef.call(a,b,!0,c),a.ff;c=a.sc[((k&-4096)+(b&4095)&a.ef)>>>a.qb];if(d)return c;d=b>>>a.qb;k=a.aa[d];b&=-4096;var m;0>2;b.mb=g;b.B=h>>2;Eb&&wc&&c.X&&!c.controller&&!c.he&&!c.ie?(b.Pa=c.Pa,b.Rd=c.Rd,b.X=c.X,pc(b,nd)):(b.F=c?hd(32):0,b.J=c?hd(96):0,pc(b,Xc));ec(b,a.ba,k);a.aa[d]=b;a.Xf.push(d);return b}function ff(a){a.aa!==a.sc&&(a.aa=a.sc,a.$f=null,a.Xf=null,a.ff=null)}l=me.prototype;l.reset=function(){this.ea.vb&&this.Jb();Ye(this);rd(this);this.ea.error=!1}; function gf(a,b){var c;switch(b){case 0:c=a.D;break;case 1:c=a.I;break;case 2:c=a.L;break;case 3:c=a.G;break;case 4:c=z(a);break;case 5:c=a.M;break;case 6:c=a.K;break;case 7:c=a.J}return c}function hf(a,b,c){switch(b){case 0:a.D=c;break;case 1:a.I=c;break;case 2:a.L=c;break;case 3:a.G=c;break;case 4:Zd(a,c);break;case 5:a.M=c;break;case 6:a.K=c;break;case 7:a.J=c}} -function Ye(a){a.D=0;a.G=0;a.I=0;a.L=0;a.Hc=0;a.M=0;a.K=0;a.J=0;a.vc=!1;a.Ba=a.$a=0;a.ya=0;a.sj=0;a.ga=0;a.qa=65520;a.Pb=0;a.cd=1023;a.O=a.lc=0;a.ze=a.hf=a.ye=a.Ae=0;a.Uc=-1;a.Ad=a.Vc=-1;a.Bd=a.ta=-1;a.Z=new Kd(a,Wd,"CS");a.Ca=new Kd(a,he,"DS");a.oa=new Kd(a,he,"ES");a.Y=new Kd(a,Rd,"SS");Zd(a,0);Yd(a,0);if(80386<=a.ca){switch(a.we){case 80562:case 80563:a.L=771;break;case 80578:a.L=772;break;case 80594:a.L=773;break;case 80595:case 80596:a.L=776}a.qa=16;a.Og=0;a.Cd=0;a.Wc=0;a.Qb=[0,0,0,0,null,null, -0,0];a.dg=[null,null,null,null,null,null,0,0];a.Ga=new Kd(a,he,"FS");a.Ha=new Kd(a,he,"GS");ff(a)}a.Qg=new Kd(a,0,"NULL");a.Da=a.Ca;a.Rb=a.Y;a.N=a.ha=0;a.C=a.F=-1;a.eb=a.Qg;a.Za=0;if(80286>a.ca)ke(a,0,65535);else{a.Kb=0;a.Pc=65535;a.Eb=new Kd(a,5,"LDT",!0);a.la=new Kd(a,Ud,"TSS",!0);a.xb=new Kd(a,Qd,"VER",!0);ke(a,65520,61440);var b,c=A(a);b=a.Z;var d=-65536;80386>b.gc.ca&&(d&=16777215);b=b.ua=d;a.da=b+c|0;a.Pg=(b>>>0)+(a.Z.Ka>>>0)+1}je(a,0);ae(a)} +function Ye(a){a.D=0;a.G=0;a.I=0;a.L=0;a.Hc=0;a.M=0;a.K=0;a.J=0;a.vc=!1;a.Ba=a.$a=0;a.bn=[0,0];a.cn=[0,0];a.ya=0;a.sj=0;a.ga=0;a.qa=65520;a.Pb=0;a.cd=1023;a.O=a.lc=0;a.ze=a.hf=a.ye=a.Ae=0;a.Uc=-1;a.Ad=a.Vc=-1;a.Bd=a.ta=-1;a.Z=new Kd(a,Wd,"CS");a.Ca=new Kd(a,he,"DS");a.oa=new Kd(a,he,"ES");a.Y=new Kd(a,Rd,"SS");Zd(a,0);Yd(a,0);if(80386<=a.ca){switch(a.we){case 80562:case 80563:a.L=771;break;case 80578:a.L=772;break;case 80594:a.L=773;break;case 80595:case 80596:a.L=776}a.qa=16;a.Og=0;a.Cd=0;a.Wc=0; +a.Qb=[0,0,0,0,null,null,0,0];a.dg=[null,null,null,null,null,null,0,0];a.Ga=new Kd(a,he,"FS");a.Ha=new Kd(a,he,"GS");ff(a)}a.Qg=new Kd(a,0,"NULL");a.Da=a.Ca;a.Rb=a.Y;a.N=a.ha=0;a.C=a.F=-1;a.eb=a.Qg;a.Za=0;if(80286>a.ca)ke(a,0,65535);else{a.Kb=0;a.Pc=65535;a.Eb=new Kd(a,5,"LDT",!0);a.la=new Kd(a,Ud,"TSS",!0);a.xb=new Kd(a,Qd,"VER",!0);ke(a,65520,61440);var b,c=A(a);b=a.Z;var d=-65536;80386>b.gc.ca&&(d&=16777215);b=b.ua=d;a.da=b+c|0;a.Pg=(b>>>0)+(a.Z.Ka>>>0)+1}je(a,0);ae(a)} function jf(a){2==a.Jc?(a.Sb=a.ja,a.kc=kf,a.tc=lf,a.kd=rf,2==a.V?(a.ma=sf,a.za=tf,a.rb=uf):(a.ma=vf,a.za=wf,a.rb=xf)):(a.Sb=a.ia,a.kc=yf,a.tc=zf,a.kd=Af,2==a.V?(a.ma=Bf,a.za=Cf,a.rb=Df):(a.ma=Ef,a.za=Ff,a.rb=Gf))}function be(a,b){a.V!=b&&(a.ha|=1024,a.V=b,a.R=2==b?65535:-1,Hf(a))}function Hf(a){2==a.V?(a.Fb=32768,a.hb=a.ja,a.yb=a.fb,2==a.Jc?(a.ma=sf,a.za=tf,a.rb=uf):(a.ma=Bf,a.za=Cf,a.rb=Df)):(a.Fb=-2147483648,a.hb=a.ia,a.yb=a.ab,2==a.Jc?(a.ma=vf,a.za=wf,a.rb=xf):(a.ma=Ef,a.za=Ff,a.rb=Gf))} function If(a){a.Jc=a.Z.Jc;a.wa=a.Z.wa;jf(a);a.V=a.Z.V;a.R=a.Z.R;Hf(a);a.ha&=-3073}l.uj=function(){var a=this.D+this.G+this.I+this.L+z(this)+this.M+this.K+this.J|0;return a=a+A(this)+this.Z.U+this.Ca.U+this.Y.U+this.oa.U+ie(this)|0};function Jf(a,b,c){void 0===a.Yf[b]&&(a.Yf[b]=[]);a.Yf[b].push(c)}function Kf(a,b,c){c&&(null==a.Zf[b]&&a.ag++,a.Zf[b]=c)}function Lf(a,b){var c=a.Zf[b];null!=c&&(c(--a.ag),delete a.Zf[b])} function Mf(a,b){for(var c=a.Qb[7],d=c>>16,e=0;4>e;e++){if(c&3){var f=!!(d&1),g=a.Qb[e],g=g&~(d>>2&3);b?a.aa[g>>>a.qb].Gd(g&a.Db,f,a):(g=a.aa[g>>>a.qb],f?--g.ie||bd(g):--g.he||ad(g))}c>>=2;d>>=4}}function ed(a,b,c,d){if(!(a.N&8192)&&a.Qb[7]&255){c--;var e=a.Qb[7],f=e>>16;d=d?1:0==d?3:0;for(var g=0;4>g;g++){if(e&3&&(f&3)==d){var h=f>>2;if(b+c>=a.Qb[g]&&b<=a.Qb[g]+h){a.Qb[6]|=1<>=2;f>>=4}}} @@ -237,7 +237,7 @@ function Fj(a,b){var c=a-b|0;Uf(this,a,b,c,this.Fb|63,!0);this.A-=-1===this.F?-1 function Jj(a,b){return b>>(this.D&this.R)&(1<<(this.I&31))-1&this.R}function Kj(a,b){if(-1===this.C){switch(this.ga&7){case 0:this.D=this.D&-256|a;break;case 1:this.I=this.I&-256|a;break;case 2:this.L=this.L&-256|a;break;case 3:this.G=this.G&-256|a;break;case 4:this.D=this.D&-65281|a<<8;break;case 5:this.I=this.I&-65281|a<<8;break;case 6:this.L=this.L&-65281|a<<8;break;case 7:this.G=this.G&-65281|a<<8}this.A-=this.B.di}else this.F=this.C,vg(this,a),this.A-=this.B.ci;return b} function Lj(a,b){if(-1===this.C){switch(this.ga&7){case 0:this.D=this.D&~this.R|a;break;case 1:this.I=this.I&~this.R|a;break;case 2:this.L=this.L&~this.R|a;break;case 3:this.G=this.G&~this.R|a;break;case 4:Zd(this,z(this)&~this.R|a);break;case 5:this.M=this.G&~this.R|a;break;case 6:this.K=this.K&~this.R|a;break;case 7:this.J=this.J&~this.R|a}this.A-=this.B.di}else this.F=this.C,this.N&2||this.yb(this.eb.cc(this.wb,this.V),a),this.A-=this.B.ci;return b} function Mj(a,b){a^=b;ag(this,a,128);this.A-=-1===this.F?-1===this.C?this.B.mc:this.B.Ib:this.B.Ac;return a}function Nj(a,b){this.A-=-1===this.F?-1===this.C?this.B.mc:this.B.Ib:this.B.Ac;return ag(this,a^b,this.Fb)&this.R}function Oj(a,b){var c=a[1]-b[1];c||(c=a[0]-b[0]);return c}function Pj(a){var b=a-1|0;Uf(this,a,1,b,this.Fb|62,!0);this.A-=2;return a&~this.R|b&this.R} -function Qj(a,b,c){c>>>=0;if(!c||c<=b>>>0)return!1;var d=0,e=1;c=[c>>>0,0];for(a=[a>>>0,b>>>0];0>>=0,b[1]++);e+=e}do 0<=Oj(a,c)&&(b=a,f=c,b[0]-=f[0],b[1]-=f[1],0>b[0]&&(b[0]>>>=0,b[1]--),d+=e),b=c,b[0]>>>=1,b[1]&1&&(b[0]=(b[0]|2147483648)>>>0),b[1]>>>=1,e/=2;while(1<=e);this.Ba=d;this.$a=a[0];return!0}function Rj(a){var b=a+1|0;Uf(this,a,1,b,this.Fb|62);this.A-=2;return a&~this.R|b&this.R} +function Qj(a,b,c){c>>>=0;if(!c||c<=b>>>0)return!1;var d=0,e=1,f=this.bn;f[0]=c>>>0;f[1]=0;c=this.cn;c[0]=a>>>0;for(c[1]=b>>>0;0>>=0,a[1]++),e+=e;do 0<=Oj(c,f)&&(a=c,b=f,a[0]-=b[0],a[1]-=b[1],0>a[0]&&(a[0]>>>=0,a[1]--),d+=e),a=f,a[0]>>>=1,a[1]&1&&(a[0]=(a[0]|2147483648)>>>0),a[1]>>>=1,e/=2;while(1<=e);this.Ba=d;this.$a=c[0];return!0}function Rj(a){var b=a+1|0;Uf(this,a,1,b,this.Fb|62);this.A-=2;return a&~this.R|b&this.R} function Sj(a){this.qa=a;ae(this);this.qa&-2147483648?$e(this):ff(this)}function le(a){this.Wc=a;jc(this)}function Tj(a){this.N|=1;this.tc.call(this,a);this.A-=-1===this.C?4:5}function tj(a,b,c){if(c){16>>16-c)&65535;ag(this,a,32768,d&32768)}return a}function vj(a,b,c){if(c){var d=a<>>32-c;ag(this,a,-2147483648,d&-2147483648)}return a} function zj(a,b,c){if(c){16>>c-1;a=(d>>>1|b<<16-c)&65535;ag(this,a,32768,d&1)}return a}function Bj(a,b,c){if(c){var d=a>>>c-1;a=d>>>1|b<<32-c;ag(this,a,-2147483648,d&1)}return a}function Uj(){this.A-=-1===this.C?2:this.B.Fk;return 1}function Vj(){var a=this.I&255;this.A-=(-1===this.C?this.B.Vh:this.B.Uh)+(a<c?c=c?c:12:c=(c-=12)?c+128:140,d=!0);a.A[Hd]&Im||(d&&128>8} -l.save=function(){var a=new Of(this);a.set(0,[this.C]);for(var b=[],c=0;c=Kg&&(a.set(5,[this.D,this.L,this.K,this.oa,this.M,this.qa]),a.set(6,[this.Y[7],this.Y,this.W,this.A,this.ma,this.Z]));return a.data()}; l.restore=function(a){var b,c;b=a[0];Array.isArray(b[0])?this.C=b[0]:(this.C[0][0]=b[0],this.C[1][0]=b[1]&15,this.C[0][1]=b[2],this.C[1][1]=b[3]&15);wl(this);b=a[1];for(c=0;c=f;f++){var g="pcjs-bitCell";f||(g+=" pcjs-bitCellLeft");d+='
'+f+"
\n"}e.innerHTML=d;Tm(a,b,c,!0)}function Um(a,b,c){if(b=(a=Vm[a.ca|0])&&a[b])for(var d in b)if(a=b[d],a.yc&1<g.nb[0]&&(g.nb[0]=255,g.nb[1]--,0>g.nb[1]&&(g.nb[1]=255)));return h}function jn(a,b,c,d,e,f){var g=a.F[b];t(a,768)&&v(a,d,e,f,"DMA"+b+".CHANNEL"+c+".COUNT["+g.zb+"]",null,!0);a=g.oc[c];a.nb[g.zb]=a.pc[g.zb]=e;g.zb^=1}function kn(a,b,c,d){var e=a.F[b],f=e.Mb|ln;e.Mb&=~mn;t(a,768)&&v(a,c,null,d,"DMA"+b+".STATUS",f,!0);return f} -function nn(a,b,c,d,e){var f=a.F[b];t(a,768)&&v(a,c,d,e,"DMA"+b+".REQ",null,!0);a=d&3;f.Mb=f.Mb&~(16<>2].oc[b&3],c,d,e)}function rn(a,b,c){b=a.F[b>>2].oc[b&3];b.mg&&b.eh&&b.Ag?(c&&(b.done=c),b.ne||Bn(a,b,!0)):c&&c(!0)} function Bn(a,b,c){c&&(b.count=b.nb[1]<<8|b.nb[0],b.type=b.mode&Cn,b.rj=b.ng=!1);for(var d=!1;0<=b.count&&(c=b.hg<<16|b.sb[1]<<8|b.sb[0],b.type==Dn?(d=!0,function(c){b.eh.call(b.mg,b.Ag,-1,function(e,g){0>e&&(b.rj||(b.rj=!0),e=255);b.ne||a.ka.fc(c,e);(d=g)&&setTimeout(function(){En(b)||Bn(a,b)},0)})}(c)):b.type==Fn?(c=a.ka.Ia(c),0>b.eh.call(b.mg,b.Ag,c)&&(b.ng=!0)):b.type!=Gn&&(b.ng=!0)),!d&&!En(b););} @@ -454,20 +454,20 @@ function po(a,b,c,d,e){v(a,c,d,e,"PIT"+b+".CTRL",null,2048);e=0;c=d&qo;b?(e=3,a. a.J==(Do|Eo|Fo|Go)&&(b=a.G[0],b.Mc[0]=b.pc[0],b.Mc[1]=b.pc[1],b.jd=vd(a.H,a.O))}}}function mo(a,b){a=a.G[b];(b=a.pc[1]<<8|a.pc[0])||(b=1==a.ad?256:65536);return b}function Fd(a,b){a=a.G[b];(b=a.Mc[1]<<8|a.Mc[0])||(b=1==a.ad?256:65536);return b}function xo(a,b){sl(a,b);var c=a.G[b];c.je[0]=c.nb[0];c.je[1]=c.nb[1];c.Me=!0;fo(a,b)}function fo(a,b){a=a.G[b];a.rd=a.Qf==Ho?1:0;a.ad=a.Qf==Io?2:1} function sl(a,b,c){var d=a.G[b];if(d.le&&(b!=no||a.J&Do)){var e=vd(a.H,a.O),f=(e-d.jd)/a.Ba|0;0>f&&(d.jd=e,f=0);var g=mo(a,b),h=Fd(a,b)-f;d.mode==ho?(0>=h&&(h=0),h||(d.gd=!0,d.le=!1,b||Zg(a,lo))):d.mode==Jo?(d.gd=1!=h,0>=h&&(h=g+h,0>=h&&(h=g),d.Mc[0]=h&255,d.Mc[1]=h>>8&255,d.jd=e,!b&&d.gd&&Zg(a,lo))):d.mode==Gd&&(h-=f,0>=h&&(d.gd=!d.gd,h=g+h,0>=h&&(h=g),d.Mc[0]=h&255,d.Mc[1]=h>>8&255,d.jd=e,!b&&d.gd&&Zg(a,lo)));d.nb[0]=h&255;d.nb[1]=h>>8&255;c&&(a.jd=0)}return d} function Ed(a,b){for(var c=0;c=Kg){b=a.H.T.sd;c=vd(a.H,a.O);null==a.Aa&&(a.ma=vd(a.H,a.O),a.Ha=1024,a.Aa=Math.floor(a.H.T.sd/a.Ha),Km(a));c>=a.Z&&(a.A[Gm]|=Ko,a.A[Hd]&Id&&(a.A[Gm]|=Lo,Zg(a,Mo)),a.Z=c+a.Aa);a.A[cm]==a.A[dm]&&a.A[em]==a.A[fm]&&a.A[gm]==a.A[hm]&&(a.A[Gm]|=No,a.A[Hd]&Oo&&(a.A[Gm]|=Lo,Zg(a,Mo)));var d=c-a.ma,e=Math.floor(d/b);if(e&&!(a.A[Hd]&Po)){for(;e--;)if(60<=++a.A[cm]&&(a.A[cm]=0,60<=++a.A[em]&&(a.A[em]=0,24<=++a.A[gm]))){a.A[gm]=0;a.A[im]=a.A[im]% -7+1;var f;f=a.A[Cm];var g=Aa[a.A[Bm]-1];28==g&&(f%4||!(f%100)&&f%400||g++);f=g;++a.A[Am]>f&&(a.A[Am]=1,12<++a.A[Bm]&&(a.A[Bm]=1,a.A[Cm]=(a.A[Cm]+1)%100))}a.A[Gm]|=Qo;a.A[Hd]&Ro&&(a.A[Gm]|=Lo,Zg(a,Mo))}a.ma=c-d%b}}l.Gm=function(a,b){var c=this.ya;this.ga&So&&(this.J&To?c=this.C[0][1]:this.B&&(c=Uo(this.B)));v(this,a,null,b,"PPI_A",c);return c};l.ho=function(a,b,c){v(this,a,b,c,"PPI_A");this.ya=b};l.Hm=function(a,b){var c=this.J;v(this,a,null,b,"PPI_B",c);return c}; -l.io=function(a,b,c){v(this,a,b,c,"PPI_B");Vo(this,b)};function Vo(a,b){var c=!!(b&Wo),d=!!(a.J&Wo);a.J=b;a.B&&Xo(a.B,!(b&To),!!(b&Go));c!=d&&Mm(a,c)}l.Im=function(a,b){var c=0,c=(this.ca|0)==bl?this.J&Eo?c|this.C[1][1]&Yo:c|this.C[1][1]>>4&1:this.J&Zo?c|this.C[0][1]>>4:c|this.C[0][1]&15;this.J&Do&&sl(this,no).gd&&(c=this.J&Wo?c|$o:c|ap);v(this,a,null,b,"PPI_C",c,32896);return c};l.jo=function(a,b,c){v(this,a,b,c,"PPI_C");this.Ca=b};l.Jm=function(a,b){var c=this.ga;v(this,a,null,b,"PPI_CTRL",c);return c}; -l.ko=function(a,b,c){v(this,a,b,c,"PPI_CTRL");this.ga=b};l.Sl=function(a,b){var c=this.B?Uo(this.B):0;v(this,a,null,b,"8041_KBD",c);this.aa&=~bp;return c};l.rn=function(a,b,c){v(this,a,b,c,"8041_KBD")};l.Rl=function(a,b){var c=this.J;v(this,a,null,b,"8041_CTRL",c);return c};l.qn=function(a,b,c){v(this,a,b,c,"8041_CTRL");Vo(this,b)};l.Tl=function(a,b){var c=this.aa;v(this,a,null,b,"8041_STATUS",c);return c}; +7+1;var f;f=a.A[Cm];var g=Aa[a.A[Bm]-1];28==g&&(f%4||!(f%100)&&f%400||g++);f=g;++a.A[Am]>f&&(a.A[Am]=1,12<++a.A[Bm]&&(a.A[Bm]=1,a.A[Cm]=(a.A[Cm]+1)%100))}a.A[Gm]|=Qo;a.A[Hd]&Ro&&(a.A[Gm]|=Lo,Zg(a,Mo))}a.ma=c-d%b}}l.Gm=function(a,b){var c=this.ya;this.ga&So&&(this.J&To?c=this.C[0][1]:this.B&&(c=Uo(this.B)));v(this,a,null,b,"PPI_A",c);return c};l.jo=function(a,b,c){v(this,a,b,c,"PPI_A");this.ya=b};l.Hm=function(a,b){var c=this.J;v(this,a,null,b,"PPI_B",c);return c}; +l.ko=function(a,b,c){v(this,a,b,c,"PPI_B");Vo(this,b)};function Vo(a,b){var c=!!(b&Wo),d=!!(a.J&Wo);a.J=b;a.B&&Xo(a.B,!(b&To),!!(b&Go));c!=d&&Mm(a,c)}l.Im=function(a,b){var c=0,c=(this.ca|0)==bl?this.J&Eo?c|this.C[1][1]&Yo:c|this.C[1][1]>>4&1:this.J&Zo?c|this.C[0][1]>>4:c|this.C[0][1]&15;this.J&Do&&sl(this,no).gd&&(c=this.J&Wo?c|$o:c|ap);v(this,a,null,b,"PPI_C",c,32896);return c};l.lo=function(a,b,c){v(this,a,b,c,"PPI_C");this.Ca=b};l.Jm=function(a,b){var c=this.ga;v(this,a,null,b,"PPI_CTRL",c);return c}; +l.mo=function(a,b,c){v(this,a,b,c,"PPI_CTRL");this.ga=b};l.Sl=function(a,b){var c=this.B?Uo(this.B):0;v(this,a,null,b,"8041_KBD",c);this.aa&=~bp;return c};l.tn=function(a,b,c){v(this,a,b,c,"8041_KBD")};l.Rl=function(a,b){var c=this.J;v(this,a,null,b,"8041_CTRL",c);return c};l.sn=function(a,b,c){v(this,a,b,c,"8041_CTRL");Vo(this,b)};l.Tl=function(a,b){var c=this.aa;v(this,a,null,b,"8041_STATUS",c);return c}; l.Ul=function(a,b){var c=this.oa;v(this,a,null,b,"8042_OUTBUFF",c,16384);this.D&=~(bp|cp);this.B&&dp(this.B);return c}; -l.tn=function(a,b,c){v(this,a,b,c,"8042_INBUF.DATA",null,16384);if(this.D&ep)switch(this.L){case fp:gp(this,b);break;case hp:ip(this,b);break;default:if(gp(this,this.K&~Fl),this.B){a=this.B;c=b;var d=-1;t(a)&&Cb(a,"sendCmd("+r(c)+")");switch(a.F||c){case jp:d=kp;lp(a);break;case mp:a.F&&(c=0);np(a,kp);a.F=c;break;case op:a.F&&(c=0);np(a,kp);a.F=c;break;default:Cb(a,"sendCmd(): unrecognized command")}pp(this,d)}}this.L=b;this.D&=~ep}; -l.Vl=function(a,b){var c=this.J&~(qp|rp)|(vd(this.H)&64?rp:0);v(this,a,null,b,"8042_RWREG",c,16384);return c};l.un=function(a,b,c){v(this,a,b,c,"8042_RWREG",null,16384);Vo(this,b)};l.Wl=function(a,b){v(this,a,null,b,"8042_STATUS",this.D,16384);a=this.D&255;this.D&cp&&(this.D|=bp,this.D&=~cp);return a}; -l.sn=function(a,b,c){v(this,a,b,c,"8042_INBUFF.CMD",null,16384);this.L=b;this.D|=ep;a=0;this.L>=sp&&(a=this.L^15,this.L=sp);switch(this.L){case tp:pp(this,this.K);break;case up:gp(this,this.K|Fl);break;case vp:gp(this,this.K&~Fl);this.B&&dp(this.B);break;case wp:this.B&&(a=this.B,a.A=[],t(a)&&Cb(a,"scan codes flushed"));gp(this,this.K|Fl);pp(this,xp);ip(this,Pl|Ql);break;case yp:pp(this,zp);break;case Ap:pp(this,this.M);break;case Bp:pp(this,this.qa);break;case Cp:pp(this,this.K&Fl?0:Dp);break;case sp:a& +l.vn=function(a,b,c){v(this,a,b,c,"8042_INBUF.DATA",null,16384);if(this.D&ep)switch(this.L){case fp:gp(this,b);break;case hp:ip(this,b);break;default:if(gp(this,this.K&~Fl),this.B){a=this.B;c=b;var d=-1;t(a)&&Cb(a,"sendCmd("+r(c)+")");switch(a.F||c){case jp:d=kp;lp(a);break;case mp:a.F&&(c=0);np(a,kp);a.F=c;break;case op:a.F&&(c=0);np(a,kp);a.F=c;break;default:Cb(a,"sendCmd(): unrecognized command")}pp(this,d)}}this.L=b;this.D&=~ep}; +l.Vl=function(a,b){var c=this.J&~(qp|rp)|(vd(this.H)&64?rp:0);v(this,a,null,b,"8042_RWREG",c,16384);return c};l.wn=function(a,b,c){v(this,a,b,c,"8042_RWREG",null,16384);Vo(this,b)};l.Wl=function(a,b){v(this,a,null,b,"8042_STATUS",this.D,16384);a=this.D&255;this.D&cp&&(this.D|=bp,this.D&=~cp);return a}; +l.un=function(a,b,c){v(this,a,b,c,"8042_INBUFF.CMD",null,16384);this.L=b;this.D|=ep;a=0;this.L>=sp&&(a=this.L^15,this.L=sp);switch(this.L){case tp:pp(this,this.K);break;case up:gp(this,this.K|Fl);break;case vp:gp(this,this.K&~Fl);this.B&&dp(this.B);break;case wp:this.B&&(a=this.B,a.A=[],t(a)&&Cb(a,"scan codes flushed"));gp(this,this.K|Fl);pp(this,xp);ip(this,Pl|Ql);break;case yp:pp(this,zp);break;case Ap:pp(this,this.M);break;case Bp:pp(this,this.qa);break;case Cp:pp(this,this.K&Fl?0:Dp);break;case sp:a& 1&&Ye(this.H)}};function gp(a,b){a.K=b;a.D=a.D&~Ep|b&Fp;a.B&&Xo(a.B,!!(b&Gp),!(b&Fl))}function pp(a,b,c){0<=b&&(a.oa=b,c?a.D|=bp:(a.D&=~bp,a.D|=cp))}function ip(a,b){a.qa=b;fc(a.ka,!!(b&Ql));b&Pl||Ye(a.H)}function Hp(a,b){a.ca>4)+(c&15),e=!0);if(d==gm||d==hm)e&&23=c?c=12==c?0:c:(c-=116,c=24==c?12:c))}}else c=b;this.A[d]=c;d==Hd&&a&Id&&b&Id&&Km(this)};l.Nk=function(a,b,c){v(this,a,b,c,"NMI");this.da=b};l.Tn=function(a,b,c){v(this,a,b,c,"FPU.CLEAR")};l.Un=function(a,b,c){v(this,a,b,c,"FPU.RESET");this.Tc&&Fg(this.Tc)}; +l.Kn=function(a,b,c){v(this,a,b,c,"CMOS.ADDR",null,4096);this.W=b;this.da=b&Lp?$g:Mp};l.km=function(a,b){var c=this.W&Np,d=c<=ul?vl(this,c):this.A[c];t(this,4352)&&v(this,a,null,b,"CMOS.DATA["+r(c)+"]",d,!0);null!=b&&c==Gm&&(this.A[c]&=Op,d&Lo&&Lg(this,Mo),d&Ko&&this.A[Hd]&Id&&Km(this));return d}; +l.Ln=function(a,b,c){var d=this.W&Np;t(this,4352)&&v(this,a,b,c,"CMOS.DATA["+r(d)+"]",null,!0);a=b^this.A[d];if(d<=ul){if(c=b,d>4)+(c&15),e=!0);if(d==gm||d==hm)e&&23=c?c=12==c?0:c:(c-=116,c=24==c?12:c))}}else c=b;this.A[d]=c;d==Hd&&a&Id&&b&Id&&Km(this)};l.Nk=function(a,b,c){v(this,a,b,c,"NMI");this.da=b};l.Vn=function(a,b,c){v(this,a,b,c,"FPU.CLEAR")};l.Wn=function(a,b,c){v(this,a,b,c,"FPU.RESET");this.Tc&&Fg(this.Tc)}; l.Xm=function(a){if(t(this,16)&&Nk(this.ba,26,a)){var b=this.H.D>>8;Kf(this.H,a,function(a,d){return function(c){d=vd(a.H)-d;var e,g=a.H.L&255,h=a.H.L>>8,k=a.H.L&255,m=a.H.L>>8;if(2==b||3==b)e=" CH(hour)="+ka(h)+" CL(min)="+r(g)+" DH(sec)="+r(m);else if(4==b||5==b)e=" CX(year)="+ka(a.H.I)+" DH(month)="+r(m)+" DL(day)="+r(k);g=a.ba;h=d;g.message("INT "+r(26)+": C="+(Vf(g.H)?1:0)+(e||"")+" (cycles="+h+(c?",level="+(c+1):"")+")")}}(this,vd(this.H)))}return!0}; function Mm(a,b){if(a.la)try{void 0!==b?a.Ga=b:b=!!(a.Ga&&a.H&&a.H.ea.vb);var c=Math.round(fl/mo(a,no));if(20>c||2E4>>4,0,this.F,this.C,this.Fd),delete this.Fd);return!0};Pp.prototype.Wb=function(){return!0}; @@ -511,14 +511,14 @@ e.A.push(f),1==e.A.length&&e.W&&Hp(e.W,f)):(e.A.length==Zq&&e.A.push($q),Cb(e,"s var qq={TAB:1009,ESC:1027,F1:1112,F2:1113,F3:1114,F4:1115,F5:1116,F6:1117,F7:1118,F8:1119,F9:1120,F10:1121,LEFT:1037,UP:1038,RIGHT:1039,DOWN:1040,SYSREQ:4027,CTRL_C:ar,CTRL_BREAK:Mq,CTRL_ALT_DEL:4046,CTRL_ALT_INS:4045,CTRL_ALT_ENTER:4013},sq={esc:1027,1:n["1"],2:n["2"],3:n["3"],4:n["4"],5:n["5"],6:n["6"],7:n["7"],8:n["8"],9:n["9"],0:n["0"],"-":n["-"],"=":n["="],bs:1008,tab:1009,q:n.Q,w:n.Mi,e:n.E,r:n.Hi,t:n.Ji,y:n.Oi,u:n.Ki,i:n.zi,o:n.Fi,p:n.Gi,"[":n["["],"]":n["]"],enter:13,ctrl:1017,a:n.ce,s:n.Ii, d:n.vi,f:n.wi,g:n.xi,h:n.yi,j:n.Ai,k:n.Bi,l:n.Ci,";":n[";"],quote:n["'"],"`":n["`"],shift:1016,"\\":n["\\"],z:n.eg,x:n.Ni,c:n.ui,v:n.Li,b:n.ti,n:n.Ei,m:n.Di,",":n[","],".":n["."],"/":n["/"],"right-shift":3016,prtsc:1044,alt:1018,space:1032,"caps-lock":nq,f1:1112,f2:1113,f3:1114,f4:1115,f5:1116,f6:1117,f7:1118,f8:1119,f9:1120,f10:1121,"num-lock":oq,"scroll-lock":pq,"num-home":1036,"num-up":1038,"num-pgup":1033,"num-sub":1109,"num-left":1037,"num-center":1101,"num-right":1039,"num-add":1107,"num-end":1035, "num-down":1040,"num-pgdn":1034,"num-ins":1045,"num-del":1046,sysreq:84},Dq={"caps-lock":Jq,"num-lock":1024,"scroll-lock":2048},O={1027:1};O[n["1"]]=2;O[n["!"]]=2|P<<8;O[n["2"]]=3;O[n["@"]]=3|P<<8;O[n["3"]]=4;O[n["#"]]=4|P<<8;O[n["4"]]=5;O[n.$]=5|P<<8;O[n["5"]]=6;O[n["%"]]=6|P<<8;O[n["6"]]=7;O[n["^"]]=7|P<<8;O[n["7"]]=8;O[n["&"]]=8|P<<8;O[n["8"]]=9;O[n["*"]]=9|P<<8;O[n["9"]]=10;O[n["("]]=10|P<<8;O[n["0"]]=11;O[n[")"]]=11|P<<8;O[n["-"]]=12;O[n._]=12|P<<8;O[n["="]]=13;O[n["+"]]=13|P<<8;O[1008]=Qq; -O[1009]=15;O[n.q]=16;O[n.Q]=16|P<<8;O[n.Qo]=17;O[n.Mi]=17|P<<8;O[n.e]=18;O[n.E]=18|P<<8;O[n.r]=19;O[n.Hi]=19|P<<8;O[n.t]=20;O[n.Ji]=20|P<<8;O[n.y]=21;O[n.Oi]=21|P<<8;O[n.Oo]=22;O[n.Ki]=22|P<<8;O[n.Kl]=23;O[n.zi]=23|P<<8;O[n.on]=24;O[n.Fi]=24|P<<8;O[n.p]=25;O[n.Gi]=25|P<<8;O[n["["]]=26;O[n["{"]]=26|P<<8;O[n["]"]]=27;O[n["}"]]=27|P<<8;O[13]=28;O[1017]=Vq;O[n.de]=30;O[n.ce]=30|P<<8;O[n.Lo]=31;O[n.Ii]=31|P<<8;O[n.d]=32;O[n.vi]=32|P<<8;O[n.Hl]=33;O[n.wi]=33|P<<8;O[n.Il]=34;O[n.xi]=34|P<<8;O[n.Jl]=35; -O[n.yi]=35|P<<8;O[n.an]=36;O[n.Ai]=36|P<<8;O[n.k]=37;O[n.Bi]=37|P<<8;O[n.bn]=38;O[n.Ci]=38|P<<8;O[n[";"]]=39;O[n[":"]]=39|P<<8;O[n["'"]]=40;O[n['"']]=40|P<<8;O[n["`"]]=41;O[n["~"]]=41|P<<8;O[1016]=P;O[n["\\"]]=43;O[n["|"]]=43|P<<8;O[n.z]=44;O[n.eg]=44|P<<8;O[n.x]=45;O[n.Ni]=45|P<<8;O[n.xl]=46;O[n.ui]=46|P<<8;O[n.Po]=47;O[n.Li]=47|P<<8;O[n.vl]=48;O[n.ti]=48|P<<8;O[n.n]=49;O[n.Ei]=49|P<<8;O[n.fn]=50;O[n.Di]=50|P<<8;O[n[","]]=51;O[n["<"]]=51|P<<8;O[n["."]]=52;O[n[">"]]=52|P<<8;O[n["/"]]=53; +O[1009]=15;O[n.q]=16;O[n.Q]=16|P<<8;O[n.So]=17;O[n.Mi]=17|P<<8;O[n.e]=18;O[n.E]=18|P<<8;O[n.r]=19;O[n.Hi]=19|P<<8;O[n.t]=20;O[n.Ji]=20|P<<8;O[n.y]=21;O[n.Oi]=21|P<<8;O[n.Qo]=22;O[n.Ki]=22|P<<8;O[n.Kl]=23;O[n.zi]=23|P<<8;O[n.qn]=24;O[n.Fi]=24|P<<8;O[n.p]=25;O[n.Gi]=25|P<<8;O[n["["]]=26;O[n["{"]]=26|P<<8;O[n["]"]]=27;O[n["}"]]=27|P<<8;O[13]=28;O[1017]=Vq;O[n.de]=30;O[n.ce]=30|P<<8;O[n.No]=31;O[n.Ii]=31|P<<8;O[n.d]=32;O[n.vi]=32|P<<8;O[n.Hl]=33;O[n.wi]=33|P<<8;O[n.Il]=34;O[n.xi]=34|P<<8;O[n.Jl]=35; +O[n.yi]=35|P<<8;O[n.an]=36;O[n.Ai]=36|P<<8;O[n.k]=37;O[n.Bi]=37|P<<8;O[n.dn]=38;O[n.Ci]=38|P<<8;O[n[";"]]=39;O[n[":"]]=39|P<<8;O[n["'"]]=40;O[n['"']]=40|P<<8;O[n["`"]]=41;O[n["~"]]=41|P<<8;O[1016]=P;O[n["\\"]]=43;O[n["|"]]=43|P<<8;O[n.z]=44;O[n.eg]=44|P<<8;O[n.x]=45;O[n.Ni]=45|P<<8;O[n.xl]=46;O[n.ui]=46|P<<8;O[n.Ro]=47;O[n.Li]=47|P<<8;O[n.vl]=48;O[n.ti]=48|P<<8;O[n.n]=49;O[n.Ei]=49|P<<8;O[n.hn]=50;O[n.Di]=50|P<<8;O[n[","]]=51;O[n["<"]]=51|P<<8;O[n["."]]=52;O[n[">"]]=52|P<<8;O[n["/"]]=53; O[n["?"]]=53|P<<8;O[3016]=54;O[1044]=55;O[1018]=Xq;O[1032]=57;O[nq]=58;O[1112]=59;O[1113]=60;O[1114]=61;O[1115]=62;O[1116]=63;O[1117]=64;O[1118]=65;O[1119]=66;O[1120]=67;O[1121]=68;O[oq]=69;O[pq]=70;O[1036]=71;O[1038]=72;O[1033]=73;O[1109]=74;O[1037]=75;O[1101]=76;O[1039]=77;O[1107]=78;O[1035]=79;O[1040]=80;O[1034]=81;O[1045]=82;O[1046]=Rq;O[4027]=84;O[1122]=87;O[1123]=88;O[1091]=91;O[1093]=93;O[1224]=91;O[ar]=46|Vq<<8;O[Mq]=70|Vq<<8;O[4046]=Rq|Vq<<8|Xq<<16;O[4045]=82|Vq<<8|Xq<<16; O[4013]=28|Vq<<8|Xq<<16;var jp=255,mp=243,op=237,xq=170,kp=250,$q=255,Zq=20;Ra(function(){for(var a=pb(document,"pcx86","keyboard"),b=0;bc.length)c=[!1,0,null,null,0,Array(b>2,32768));this.rc=c[0];this.Xc=c[1];this.Ze=c[2];this.fa=c[3];this.ec=c[4]&255;this.Fg=c[4]>>8&255;this.Wa=c[5];this.oh=dr;this.gg=fr;if(b>=Tp){this.oh=er;this.gg=gr;(b=c[6])||(b=[!1,0,Array(hr),0,f== Ll?0:ir,0,0,Array(jr),0,0,0,Array(kr),0,[this.bb,this.Ob,this.qd],Array(this.qd>>2),lr|mr|nr|or|pr,0,-1,0,-1,0,-1,0,0,0,0,qr,rr,0,0,sr,Array(tr)]);this.Ke=b[0];this.wd=b[1];this.Cc=b[2];this.Tg=ur;this.Ig=b[3];this.af=b[4];this.Pf=b[5];this.zd=b[6];this.$d=b[7];this.Vg=vr;this.Wk=b[8];this.Xk=b[9];this.yd=b[10];this.xd=b[11];this.Ug=wr;this.tb=b[12];d=b[13];"number"==typeof d&&(d=[this.bb,this.Ob,d]);this.bb=d[0];this.Ob=d[1];d=this.qd>>2;if((this.fd=b[14])&&this.fd.length=Tp){var c=[];c[0]=a.Ke;c[1]=a.wd;c[2]=a.Cc;c[3]=a.Ig;c[4]=a.af;c[5]=a.Pf;c[6]=a.zd;c[7]=a.$d;c[8]=a.Wk;c[9]=a.Xk;c[10]=a.yd;c[11]=a.xd;c[12]=a.tb;c[13]=[a.bb,a.Ob,a.qd];var d;if(d=a.fd){var e=0,f=[];if(void 0!==d[0])for(var g=0;2>g;g++)for(var h=g;h>1;f[e++]=k;h=m}f.length>8|(u&255)<<8;var I=e,T=16;m>=h))>>(T-=h);Ss(a.Ha,m++,p,b[ua])}m>F&&(F=m);p=H&&(H=p+1)}k+=2;d++;if(m>=a.F){m=0;p+=2;if(p>a.J)break;p==a.J&&(p=1,k=c+a.Pb)}}a.ma=!0; wa.F?a.Ma-a.F-u>>3:0;c>=8;b>w&&(w=b);m=K&&(K=m+1)}c+=H;if(b>=a.F){b=0;if(++m>a.J)break;c+=I}}u||(a.ma=!0);pa.F?a.Ma-a.F-K>>3:0;cI&&(T=I)):(u<<=K,T-=K,a.ma=!1):(a.ma&&u===a.M[d]?(h+=T,T=0):a.M[d]=u,d++);if(T){hp&&(p=h);b=F&&(F=b+1)}if(h>=a.F){h=0;if(++b>a.J)break;c+=H}}K||(a.ma=!0);ma&&(b.wh=a,a=-a|0);a%b.rh>b.jn&&(c|=1);a%b.uh>b.mn&&(c|=9);b.fi=a/b.uh|0;return c}l.Cm=function(a,b){return Qt(this,this.Y,a,b)};l.co=function(a,b,c){var d=this.Y;d.Fg=d.ec;d.ec=b&31;v(this,a,b,c,"CRTC.INDX")};l.Bm=function(a,b){return Rt(this,this.Y,a,b)};l.bo=function(a,b,c){St(this,this.Y,a,b,c)};l.Dm=function(a,b){return Tt(this,this.Y,b)};l.eo=function(a,b,c){a=this.Y;v(this,a.port+4,b,c,"MODE");a.Xc=b;Fs(this,!1)}; +a.la))}}}}function Ot(a,b){var c=0;a=vd(a.H)-b.wh;0>a&&(b.wh=a,a=-a|0);a%b.rh>b.mn&&(c|=1);a%b.uh>b.on&&(c|=9);b.fi=a/b.uh|0;return c}l.Cm=function(a,b){return Qt(this,this.Y,a,b)};l.fo=function(a,b,c){var d=this.Y;d.Fg=d.ec;d.ec=b&31;v(this,a,b,c,"CRTC.INDX")};l.Bm=function(a,b){return Rt(this,this.Y,a,b)};l.eo=function(a,b,c){St(this,this.Y,a,b,c)};l.Dm=function(a,b){return Tt(this,this.Y,b)};l.ho=function(a,b,c){a=this.Y;v(this,a.port+4,b,c,"MODE");a.Xc=b;Fs(this,!1)}; l.Em=function(a,b){return Ut(this,this.Y,b)};l.Mk=function(a,b,c){this.A.Pf=this.A.Pf&-4|b&3;v(this,a,b,c,"FEAT")};l.am=function(a,b){a=this.A.wd;b&&!t(this)||v(this,960,null,b,"ATC.INDX",a);return a};l.rl=function(a,b){a=this.A.Cc[this.A.wd&31];b&&!t(this)||v(this,960,null,b,"ATC."+this.A.Tg[this.A.wd&31],a);return a}; l.Lk=function(a,b,c){var d=this.A,e=d.wd&32;if(d.Ke){d.Ke=!1;var f=d.wd&31;if(16<=f||!e)if(Vt||d.Cc[f]!==b)c&&!t(this)||v(this,a,b,c,"ATC."+d.Tg[f]),d.Cc[f]=b,Lt(this,!1)}else d.wd=b,v(this,a,b,c,"ATC.INDX"),d.Ke=!0,b&32&&!e&&xs(this,!0)&&qs(this,!0),a=(d.Wa[12]<<8)+d.Wa[13]|0,d.vd!=a&&(d.vd=a,Lt(this)),d.Ye=0}; -l.Om=function(a,b){a=0;if(this.La==Tp)a=3-((this.A.af&12)>>2),a=(this.xb&1<>this.A.xc&63;b&&!t(this)||v(this,969,null,b,"DAC.DATA["+r(this.A.dd)+"]["+r(this.A.xc)+"]",a);this.A.xc+=6;12>2),a=(this.xb&1<>this.A.xc&63;b&&!t(this)||v(this,969,null,b,"DAC.DATA["+r(this.A.dd)+"]["+r(this.A.xc)+"]",a);this.A.xc+=6;12Missing <canvas> support. Please try a newer web browser.";break}e.setAttribute("class","pcjs-canvas");e.setAttribute("width",d.screenWidth);e.setAttribute("height",d.screenHeight);e.style.backgroundColor=d.screenColor;e.style.height="auto";0<=Ea().indexOf("MSIE")&&(c.onresize=function(a,b,c,d){return function(){b.style.height= (a.clientWidth*d/c|0)+"px"}}(c,e,d.screenWidth,d.screenHeight),c.onresize());var f=+(d.aspect||Ka("aspect"));f&&.3<=f&&3.33>=f&&(Pa("onresize",function(a,b,c){return function(){b.style.height=(a.clientWidth/c|0)+"px"}}(c,e,f)),window.onresize());c.appendChild(e);f=document.createElement("textarea");Ja("iOS")&&(f.setAttribute("autocapitalize","off"),f.setAttribute("autocorrect","off"),f.style.fontSize="16px");c.appendChild(f);var g=e.getContext("2d"),d=new Q(d,e,g,f,c);ob(d,c)}}); function Wt(a){ab.call(this,"ParallelPort",a,4194304);this.G=a.adapter;switch(this.G){case 1:this.D=956;this.C=7;break;case 2:this.D=888;this.C=7;break;case 3:this.D=632;this.C=5;break;default:Wa("Unrecognized parallel adapter #"+this.G);return}this.A=this.B=null;a=a.binding;"console"==a?this.B="":nb(this,a,Xt)}ba(Wt,ab);l=Wt.prototype;l.Cb=function(a,b,c){switch(b){case Xt:return this.na[b]=this.A=c,!0}return!1}; l.uc=function(a,b,c,d){this.ka=b;this.H=c;this.ba=d;this.W=Ob(a,"ChipSet");Ec(b,this,Yt,this.D);Ic(b,this,Zt,this.D);zb(this)};l.Xb=function(a,b){if(!b)if(!a||!this.restore)this.reset();else if(!this.restore(a))return!1;return!0};l.Wb=function(a){return a?this.save():!0};l.reset=function(){$t(this)};l.save=function(){var a=new Of(this),b=0,c=[];c[b++]=this.F;c[b++]=this.Mb;c[b]=this.mf;a.set(0,c);return a.data()};l.restore=function(a){return $t(this,a[0])}; function $t(a,b){var c=0;b||(b=[0,0,0]);a.F=b[c++];a.Mb=b[c++];a.mf=b[c];return!0}l.pm=function(a,b){var c=this.F;v(this,a,null,b,"DATA",c);return c};l.Nm=function(a,b){var c=this.Mb;v(this,a,null,b,"STAT",c);return c};l.lm=function(a,b){var c=this.mf;v(this,a,null,b,"CTRL",c);return c}; -l.Pn=function(a,b,c){v(this,a,b,c,"DATA");this.F=b;this.Mb|=au;a=!1;Cb(this,"transmitByte("+r(b)+")");this.A&&(8==b?this.A.value=this.A.value.slice(0,-1):(this.A.value+=String.fromCharCode(b),this.A.scrollTop=this.A.scrollHeight),a=!0);if(null!=this.B){if(10==b||1024<=this.B.length)this.P(this.B),this.B="";10!=b&&(this.B+=String.fromCharCode(b));a=!0}a&&(this.Mb&=~au);bu(this)};l.Kn=function(a,b,c){v(this,a,b,c,"CTRL");this.mf=b;bu(this)}; -function bu(a){a.W&&a.C&&(a.mf&cu&&!(a.Mb&au)?Zg(a.W,a.C):Lg(a.W,a.C))}var Xt="buffer",au=64,cu=16,Yt={0:Wt.prototype.pm,1:Wt.prototype.Nm,2:Wt.prototype.lm},Zt={0:Wt.prototype.Pn,2:Wt.prototype.Kn};Ra(function(){for(var a=pb(document,"pcx86","parallel"),b=0;b=b)a.preventDefault&&a.preventDefault(),64>8:this.N;v(this,a,null,b,this.C&tu?"DLM":"IER",c);return c};l.xm=function(a,b){var c=this.G;v(this,a,null,b,"IIR",c);return c};l.ym=function(a,b){var c=this.C;v(this,a,null,b,"LCR",c);return c};l.Am=function(a,b){var c=this.Z;v(this,a,null,b,"MCR",c);return c}; l.zm=function(a,b){var c=this.B;v(this,a,null,b,"LSR",c);return c};l.Fm=function(a,b){var c=this.A;this.A&=~(pu|qu);v(this,a,null,b,"MSR",c);return c}; -l.no=function(a,b,c){v(this,a,b,c,this.C&tu?"DLL":"THR");if(this.C&tu)this.L=this.L&-256|b;else{this.la=b;this.B&=~(mu|nu);a=!1;Cb(this,"transmitByte("+r(b)+")");this.aa&&this.aa.call(this.D,b)&&(a=!0);if(this.F){if(13==b)this.K=0;else if(8==b)this.F.value=this.F.value.slice(0,-1),0":String.fromCharCode(b);a=d.length;32>b&&1==a&&(a=0);9==b&&(b=this.oa||8,a=b-this.K%b,this.oa&&(d=qa("",a)));this.ma&&!this.K&&a&&(d=String.fromCharCode(this.ma)+ -d);this.F.value+=d;this.F.scrollTop=this.F.scrollHeight;this.K+=a}a=!0}else if(null!=this.I){if(10==b||1024<=this.I.length)this.P(this.I),this.I="";10!=b&&(this.I+=String.fromCharCode(b));a=!0}a&&(this.B=this.B|mu|nu)}};l.Zn=function(a,b,c){v(this,a,b,c,this.C&tu?"DLM":"IER");this.C&tu?this.L=this.L&255|b<<8:this.N=b};l.$n=function(a,b,c){v(this,a,b,c,"LCR");this.C=b}; -l.ao=function(a,b,c){var d=b^this.Z;v(this,a,b,c,"MCR");this.Z=b;d&(uu|vu)&&this.Y&&(a=0,this.O?(a|=b&vu?32:0,a|=b&uu?320:0):(a|=b&vu?16:0,a|=b&uu?1048576:0),this.Y.call(this.D,a))};function ru(a){var b=-1;a.B&su&&a.N&wu?b=xu:a.A&(pu|qu)&&a.N&yu&&(b=zu);0<=b?(a.G&=~(lu|Au),a.G|=b,a.W&&a.M&&Zg(a.W,a.M,100)):(a.G|=lu,a.W&&a.M&&Lg(a.W,a.M))} -var gu="buffer",ku=384,wu=1,yu=8,lu=1,xu=4,zu=0,Au=6,tu=128,uu=1,vu=2,su=1,mu=32,nu=64,pu=1,qu=2,eu=16,fu=32,hu={0:du.prototype.Km,1:du.prototype.wm,2:du.prototype.xm,3:du.prototype.ym,4:du.prototype.Am,5:du.prototype.zm,6:du.prototype.Fm},iu={0:du.prototype.no,1:du.prototype.Zn,3:du.prototype.$n,4:du.prototype.ao};Ra(function(){for(var a=pb(document,"pcx86","serial"),b=0;b":String.fromCharCode(b);a=d.length;32>b&&1==a&&(a=0);9==b&&(b=this.oa||8,a=b-this.K%b,this.oa&&(d=qa("",a)));this.ma&&!this.K&&a&&(d=String.fromCharCode(this.ma)+ +d);this.F.value+=d;this.F.scrollTop=this.F.scrollHeight;this.K+=a}a=!0}else if(null!=this.I){if(10==b||1024<=this.I.length)this.P(this.I),this.I="";10!=b&&(this.I+=String.fromCharCode(b));a=!0}a&&(this.B=this.B|mu|nu)}};l.ao=function(a,b,c){v(this,a,b,c,this.C&tu?"DLM":"IER");this.C&tu?this.L=this.L&255|b<<8:this.N=b};l.bo=function(a,b,c){v(this,a,b,c,"LCR");this.C=b}; +l.co=function(a,b,c){var d=b^this.Z;v(this,a,b,c,"MCR");this.Z=b;d&(uu|vu)&&this.Y&&(a=0,this.O?(a|=b&vu?32:0,a|=b&uu?320:0):(a|=b&vu?16:0,a|=b&uu?1048576:0),this.Y.call(this.D,a))};function ru(a){var b=-1;a.B&su&&a.N&wu?b=xu:a.A&(pu|qu)&&a.N&yu&&(b=zu);0<=b?(a.G&=~(lu|Au),a.G|=b,a.W&&a.M&&Zg(a.W,a.M,100)):(a.G|=lu,a.W&&a.M&&Lg(a.W,a.M))} +var gu="buffer",ku=384,wu=1,yu=8,lu=1,xu=4,zu=0,Au=6,tu=128,uu=1,vu=2,su=1,mu=32,nu=64,pu=1,qu=2,eu=16,fu=32,hu={0:du.prototype.Km,1:du.prototype.wm,2:du.prototype.xm,3:du.prototype.ym,4:du.prototype.Am,5:du.prototype.zm,6:du.prototype.Fm},iu={0:du.prototype.po,1:du.prototype.ao,3:du.prototype.bo,4:du.prototype.co};Ra(function(){for(var a=pb(document,"pcx86","serial"),b=0;bd&&a.pa)||a.pa.ea.Yb);if(a.og)d?a.controller.Fa('Unable to connect to disk "'+a.I+'" (error '+d+": "+c+")",f):(a.D=!0,Tu(a),e=a);else if(d)a.controller.Fa('Unable to load disk "'+a.F+'" (error '+d+": "+b+")",f);else{eb(a.controller.xe,b,c);try{if(0g&&0c.indexOf("0x")&&'["'!=c.substr(0,2)?JSON.parse(c.replace(/([a-z]+):/gm,'"$1":').replace(/\/\/[^\n]*/gm,"")):eval("("+c+")");if(h.length)if(1==h.length)Wa(h[0]);else{a.ob=h.length;a.ib=h[0].length;a.Ya=h[0][0].length;var k=h[0][0][0];a.Na=k&&k.length||512;for(d=c=0;d>2,p=k.pattern;void 0===p&&(p=k.pattern=0);var w=k.data;if(void 0===w){var u=k.bytes;if(void 0!==u&&u.length){for(var F= m<<2,K=u.length;Kb;b++){if(128==Wu(a,e,c+0,1)){d.Kf=Wu(a,e,c+8,4);(e=Vu(a,d.Kf))&&(f=!0);break}c+=16}if(!f)return}d.sf||(d.sf=Wu(a,e,19,2)||Wu(a,e,32,4),d.rf=Wu(a,e,14,2),d.lh=d.rf+Wu(a,e,22,2)*Wu(a,e,16,1),d.vh=Wu(a,e,17,2),d.vf=Wu(a,e,13,1));d.jh=d.lh+((32*d.vh+(d.Na-1))/d.Na|0);d.hn=(d.sf-d.jh)/d.vf|0;d.vg=4084>=d.hn?12:16;d.Nl=12==d.vg?4086:65526;b=[];for(e=d.lh;eb;b++){if(128==Wu(a,e,c+0,1)){d.Kf=Wu(a,e,c+8,4);(e=Vu(a,d.Kf))&&(f=!0);break}c+=16}if(!f)return}d.sf||(d.sf=Wu(a,e,19,2)||Wu(a,e,32,4),d.rf=Wu(a,e,14,2),d.lh=d.rf+Wu(a,e,22,2)*Wu(a,e,16,1),d.vh=Wu(a,e,17,2),d.vf=Wu(a,e,13,1));d.jh=d.lh+((32*d.vh+(d.Na-1))/d.Na|0);d.kn=(d.sf-d.jh)/d.vf|0;d.vg=4084>=d.kn?12:16;d.Nl=12==d.vg?4086:65526;b=[];for(e=d.lh;e>=8;f+=2;if(k)for(;m--;)jv(d,f,1),254>=k?(p=k,w=jv(d,f+1),f+=3):(p=jv(d,f+3,1),w=jv(d,f+4),f+=6),d.nd[p]&&(d.nd[p].ee[h]=[w]),d.A[h]=[p,w],h++;else h+=m}(g=Zu(e,mv,c))&&nv(e,g+c);g=Zu(e,ov,c);h=Zu(e,pv,c);g&&h&&nv(e,g,g+h)}}}} -function Yu(a,b,c,d,e){var f,g=a.C.length,h=b.Na/32|0;b.aq=d+"\\";for(var k=0;kK)break;for(var H=u.jh+(K-2)*u.vf,I=0;IK)break;for(var H=u.jh+(K-2)*u.vf,I=0;I>3,1),d?e=16==b.vg?e<<8:c&7?e<<4:(e&15)<<8:c&7&&(e>>=4));return e} function Vu(a,b){var c=a.ib*a.Ya,d=b/c|0;return dg)break;e|=g<=f)break;e+=String.fromCharCode(f)}return e}function Qu(a,b,c,d,e,f){a||(a={sector:d,length:e,data:[],pattern:f});a.Ol=b;a.Pl=c;a.hd=a.Lc=0;a.Oa=!1;return a} function Ru(a,b){b="action=open&volume="+b+("&mode="+a.mode);b+="&chs="+a.ob+":"+a.ib+":"+a.Ya+":"+a.Na;b+="&machine="+Ou(a.controller);b+="&user="+Pu(a.controller);return Da()+"/api/v1/disk?"+b} @@ -670,16 +670,16 @@ function Bv(a,b,c,d){var e,f=a.na.listDrives;if(f&&!isNaN(e=ga(f.value,10))&&0<= l.lj=function(a,b,c,d,e){var f;a.Le=!1;b&&(f=b.info(),b&&f[0]>a.ob||f[1]>a.ib)&&(this.Fa('Diskette "'+c+'" too large for drive '+String.fromCharCode(65+a.Ua)),b=null);b?(a.sa=b,a.Zk=c,a.te=d,Nv(this,c,d,b),f=b.info(),this.I|=Rv,this.Fa('Mounted diskette "'+c+'" in drive '+String.fromCharCode(65+a.Ua),a.ke||e),a.ug=f[0],a.Af=f[1],a.Bf=f[2],this.pa&&this.pa.ld()):a.Ne=!1;a.ke&&(a.ke=!1,--this.K||zb(this));Av(this,a.Ua)}; function Fv(a,b,c,d){if((a=a.na.listDisks)&&a.options){for(var e=0;e=this.C&&(this.fa&=~(Uv|Vv),this.D=this.C=0);return c}; -l.Rn=function(a,b,c){t(this)&&v(this,a,b,c,"DATA["+this.C+"]");this.C=Xv[a].Id){b=!1;this.D=0;a=Yv(this);var d,e,f,g,h=a&Wv;switch(h){case Zv:Yv(this);Yv(this);$v(this);break;case aw:c=Yv(this);this.Ua=c&3;d=this.A[this.Ua];$v(this);bw(this,(d.pb&cw)>>>24);break;case dw:case ew:c=Yv(this);b=c>>2&1;this.Ua=c&3;d=this.A[this.Ua];d.Xa=b;c=d.Gb=Yv(this);e=Yv(this);f=d.kb=Yv(this);g=Yv(this);d.ub=128<=Xv[a].Id){b=!1;this.D=0;a=Yv(this);var d,e,f,g,h=a&Wv;switch(h){case Zv:Yv(this);Yv(this);$v(this);break;case aw:c=Yv(this);this.Ua=c&3;d=this.A[this.Ua];$v(this);bw(this,(d.pb&cw)>>>24);break;case dw:case ew:c=Yv(this);b=c>>2&1;this.Ua=c&3;d=this.A[this.Ua];d.Xa=b;c=d.Gb=Yv(this);e=Yv(this);f=d.kb=Yv(this);g=Yv(this);d.ub=128<>2&1;this.Ua= c&3;d=this.A[this.Ua];c=d.Gb;e=d.Xa=b;f=d.kb=1;g=0;d.pb=Qv;d.sa&&(d.cb=d.sa.seek(d.Gb,d.Xa,d.kb))?g=d.cb.length>>8:d.pb=fw|gw;iw(this,d,a,b,c,e,f,g);b=!0;break;case pw:c=Yv(this);b=c>>2&1;this.Ua=c&3;d=this.A[this.Ua];c=d.Gb;e=d.Xa=b;f=1;g=Yv(this);d.ub=128<>2&1,c=Yv(this),d.Gb+=c-d.Sd,0>d.Gb&&(d.Gb=0),d.Gb>=d.ob&&(d.Gb=d.ob-1),d.Sd=c,d.pb=kw,d.Gb||(d.pb|=lw),$v(this),b=!0}0>2&1,c=Yv(this),d.Gb+=c-d.Sd,0>d.Gb&&(d.Gb=0),d.Gb>=d.ob&&(d.Gb=d.ob-1),d.Sd=c,d.pb=kw,d.Gb||(d.pb|=lw),$v(this),b=!0}0>>8);bw(a,(b.pb&sw)>>>16);var k=0;if(e!=b.Gb||f!=b.Xa)k=g=1;c&tw&&(f^=k,d||(k=0));bw(a,e+k);bw(a,f);bw(a,g);bw(a,h)}function Yv(a){var b=a.F[a.D];a.D++;return b}function $v(a){a.D=a.C=0}function bw(a,b){a.F[a.C++]=b}l.jl=function(a,b,c){void 0===b||0>b?this.se(a,c):c(-1,!1)};l.kl=function(a,b){return void 0!==b&&0<=b?uw(a,b):-1}; l.Cl=function(a,b){if(void 0!==b&&0<=b)a:if(a.pb)a=-1;else{a.$c[a.Ge++]=b;if(a.Ge==a.$c.length){a.Gb=a.$c[0];a.Xa=a.$c[1];a.kb=a.$c[2];a.ub=128<uw(a,a.Wi)){a=-1;break a}a.jg++}a.jg>=a.Td&&(b=-1);a=b}else a=-1;return a};l.se=function(a,b){var c=-1,d=null,e=0;if(!a.pb&&a.sa){do{if(a.cb&&(e=a.Sa,0<=(c=a.sa.read(a.cb,a.Sa++)))){d=a.cb;break}a.cb=a.sa.seek(a.Gb,a.Xa,a.kb);if(!a.cb){a.pb=vw|gw;break}a.Sa=0;ww(a)}while(1)}b(c,!1,d,e)}; function uw(a,b){if(a.pb||!a.sa)return-1;do{if(a.cb&&a.sa.write(a.cb,a.Sa++,b))break;a.cb=a.sa.seek(a.Gb,a.Xa,a.kb);if(!a.cb){a.pb=xw|gw;b=-1;break}a.Sa=0;ww(a)}while(1);return b}function ww(a){a.kb++;a.kb>=a.Bf+1&&(a.kb=1,a.Xa++,a.Xa>=a.Af&&(a.Xa=0,a.Gb++))}var Lv="Floppy Drive",Sv=4,Tv=8,Vv=16,Uv=64,Jv=128,Zv=3,aw=4,dw=5,ew=6,jw=7,mw=8,ow=10,pw=13,qw=15,Wv=31,tw=128,Qv=0,fw=8,kw=32,gw=64,Kv=192,nw=255,hw=512,vw=1024,xw=8192,rw=65280,sw=16711680,lw=268435456,cw=-16777216,Rv=128,Ov=0;aa={}; -var Xv={3:{Id:3,Ud:0,name:aa.Up},4:{Id:2,Ud:1,name:aa.Sp},5:{Id:9,Ud:7,name:aa.Yp},6:{Id:9,Ud:7,name:aa.Op},7:{Id:2,Ud:0,name:aa.Qp},8:{Id:1,Ud:2,name:aa.Tp},10:{Id:2,Ud:7,name:aa.Pp},13:{Id:6,Ud:7,name:aa.Lp},15:{Id:3,Ud:0,name:aa.Rp}},Dv={1009:zv.prototype.rm,1012:zv.prototype.tm,1013:zv.prototype.qm,1015:zv.prototype.sm},Ev={1010:zv.prototype.Sn,1013:zv.prototype.Rn,1015:zv.prototype.Qn}; +var Xv={3:{Id:3,Ud:0,name:aa.Wp},4:{Id:2,Ud:1,name:aa.Up},5:{Id:9,Ud:7,name:aa.$p},6:{Id:9,Ud:7,name:aa.Qp},7:{Id:2,Ud:0,name:aa.Sp},8:{Id:1,Ud:2,name:aa.Vp},10:{Id:2,Ud:7,name:aa.Rp},13:{Id:6,Ud:7,name:aa.Np},15:{Id:3,Ud:0,name:aa.Tp}},Dv={1009:zv.prototype.rm,1012:zv.prototype.tm,1013:zv.prototype.qm,1015:zv.prototype.sm},Ev={1010:zv.prototype.Un,1013:zv.prototype.Tn,1015:zv.prototype.Sn}; Ra(function(){for(var a=pb(document,"pcx86","fdc"),b=0;b=this.C&&(this.D=this.C=0,this.fa&=~(Pw|Qw|Rw));return c};l.po=function(a,b,c){v(this,a,b,c,"DATA["+this.C+"]");this.C=a&&(this.fa|=Pw,this.fa&=~Tw,Uw(this))};l.Um=function(a,b){var c=this.fa;v(this,a,null,b,"STATUS",c);this.D=this.C&&(this.D=this.C=0,this.fa&=~(Pw|Qw|Rw));return c};l.ro=function(a,b,c){v(this,a,b,c,"DATA["+this.C+"]");this.C=a&&(this.fa|=Pw,this.fa&=~Tw,Uw(this))};l.Um=function(a,b){var c=this.fa;v(this,a,null,b,"STATUS",c);this.D=a.B.Na?(a.fa=Ww,a.se(a.B,function(b){0<=b?(Xw(a),a.W&&a.W.ca==ol&&(a.fa=0),a.fa=a.fa|Hw|Yw|Zw):(a.fa=$w,a.I=ax)},!1)):a.fa=Hw|Yw));return d}l.nl=function(a,b){return Vw(this,a,b)|Vw(this,a,b)<<8}; -function bx(a,b,c,d){if(a.B&&a.B.ub>=a.B.Na)if(0>cx(a.B,c))a.fa=$w,a.I=ax;else if(1==a.B.Sa||a.B.Sa==a.B.Na)t(a,1048832)&&v(a,b,c,d,"DATA["+a.B.Sa+"]"),1=a.B.Na&&(a.fa|=Zw))}l.yn=function(a,b,c){bx(this,a,b&255,c);bx(this,a,b>>8&255,c)};l.$l=function(a,b){var c=this.I;v(this,a,null,b,"ERROR",c);return c};l.Dn=function(a,b,c){v(this,a,b,c,"WPREC");this.va=b};l.bm=function(a,b){var c=this.J;v(this,a,null,b,"SECCNT",c);return c}; -l.Bn=function(a,b,c){v(this,a,b,c,"SECCNT");this.J=b};l.cm=function(a,b){var c=this.ga;v(this,a,null,b,"SECNUM",c);return c};l.Cn=function(a,b,c){v(this,a,b,c,"SECNUM");this.ga=b};l.Yl=function(a,b){var c=this.da;v(this,a,null,b,"CYLLO",c);return c};l.xn=function(a,b,c){v(this,a,b,c,"CYLLO");this.da=b};l.Xl=function(a,b){var c=this.aa;v(this,a,null,b,"CYLHI",c);return c};l.wn=function(a,b,c){v(this,a,b,c,"CYLHI");this.aa=b};l.Zl=function(a,b){var c=this.Y;v(this,a,null,b,"DRVHD",c);return c}; -l.zn=function(a,b,c){v(this,a,b,c,"DRVHD");this.Y=b;this.fa=this.A[this.Y&dx?1:0]?this.fa|Hw|Yw:this.fa&~Hw};l.dm=function(a,b){var c=this.fa;v(this,a,null,b,"STATUS",c);this.fa&Hw&&(this.fa&=~Ww);return c};l.vn=function(a,b,c){v(this,a,b,c,"COMMAND");this.ha=b;this.W&&Lg(this.W,14);ex(this)};l.An=function(a,b,c){v(this,a,b,c,"FDR");this.L&fx&&!(b&fx)&&(this.I=gx);this.L=b}; +function bx(a,b,c,d){if(a.B&&a.B.ub>=a.B.Na)if(0>cx(a.B,c))a.fa=$w,a.I=ax;else if(1==a.B.Sa||a.B.Sa==a.B.Na)t(a,1048832)&&v(a,b,c,d,"DATA["+a.B.Sa+"]"),1=a.B.Na&&(a.fa|=Zw))}l.An=function(a,b,c){bx(this,a,b&255,c);bx(this,a,b>>8&255,c)};l.$l=function(a,b){var c=this.I;v(this,a,null,b,"ERROR",c);return c};l.Fn=function(a,b,c){v(this,a,b,c,"WPREC");this.va=b};l.bm=function(a,b){var c=this.J;v(this,a,null,b,"SECCNT",c);return c}; +l.Dn=function(a,b,c){v(this,a,b,c,"SECCNT");this.J=b};l.cm=function(a,b){var c=this.ga;v(this,a,null,b,"SECNUM",c);return c};l.En=function(a,b,c){v(this,a,b,c,"SECNUM");this.ga=b};l.Yl=function(a,b){var c=this.da;v(this,a,null,b,"CYLLO",c);return c};l.zn=function(a,b,c){v(this,a,b,c,"CYLLO");this.da=b};l.Xl=function(a,b){var c=this.aa;v(this,a,null,b,"CYLHI",c);return c};l.yn=function(a,b,c){v(this,a,b,c,"CYLHI");this.aa=b};l.Zl=function(a,b){var c=this.Y;v(this,a,null,b,"DRVHD",c);return c}; +l.Bn=function(a,b,c){v(this,a,b,c,"DRVHD");this.Y=b;this.fa=this.A[this.Y&dx?1:0]?this.fa|Hw|Yw:this.fa&~Hw};l.dm=function(a,b){var c=this.fa;v(this,a,null,b,"STATUS",c);this.fa&Hw&&(this.fa&=~Ww);return c};l.xn=function(a,b,c){v(this,a,b,c,"COMMAND");this.ha=b;this.W&&Lg(this.W,14);ex(this)};l.Cn=function(a,b,c){v(this,a,b,c,"FDR");this.L&fx&&!(b&fx)&&(this.I=gx);this.L=b}; function ex(a){var b=!1,c=a.ha,d=a.Y&dx?1:0,e=a.Y&hx,f=a.da|(a.aa&ix)<<8,g=a.ga,h=a.J||256;a.Ua=-1;a.B=null;a.I=jx;a.fa=Hw|Yw;var k=a.A[d];k?(k.Od=f,k.Xa=e,k.kb=g,k.ub=h*k.Na,c=c>=kx?c:c&lx,k.cb=null,k.Sa=0,k.errorCode=0,a.Ua=d,a.B=k):c=-1;switch(c&lx){case mx:b=!0;break;case nx:a.fa=Ww;a.se(k,function(b){0<=b&&a.W?(Xw(a),a.fa=Hw|Yw|Zw):(a.fa=$w,a.I=ax)},!1);break;case ox:a.fa=Zw;break;case px:b=!0;break;case qx:b=!0;break;case kx:a.I=gx;b=!0;break;case rx:k.ib=e+1,k.Ya=h,b=!0}b&&Xw(a)} function Xw(a){!a.W||a.L&sx||Zg(a.W,14,120)} function Uw(a){a.D=0;var b=tx(a),c=tx(a),d=c&32,e=d>>5,f=c&31,g=tx(a),h=tx(a),k=g<<2&768|h,m=g&63,p=tx(a),w=tx(a),u=a.A[e];u&&(u.Od=k,u.Xa=f,u.kb=m,u.ub=p*u.Na);switch(b){case ux:vx(a,u?u.errorCode:wx);xx(a,c);xx(a,g);xx(a,h);xx(a,yx|d);b=-1;break;case Sw:for(c=0;0<=(b=tx(a));)u&&c=a.Ya+b&&(a.kb=b,a.Xa++,a.Xa>=a.ib&&(a.Xa=0,a.Od++))}l.Vm=function(){var a=this.H.L&255;!(this.H.D>>8)&&128>8||!this.W)||(a=!(this.W.hc[0].od&64));return a?!0:!1}; var Kw="Hard Drive",Nw=["XTC","ATC","COMPAQ"],Lw=[{0:[306,2],1:[375,8],2:[306,6],3:[306,4]},{1:[306,4],2:[615,4],3:[615,6],4:[940,8],5:[940,6],6:[615,4],7:[462,8],8:[733,5],9:[900,15],10:[820,3],11:[855,5],12:[855,7],13:[306,8],14:[733,7],16:[612,4],17:[977,5],18:[977,7],19:[1024,7],20:[733,5],21:[733,7],22:[733,5],23:[306,4]},{1:[306,4],2:[615,4],3:[615,6],4:[1023,8],5:[940,6],6:[697,5],7:[462,8],8:[925,5],9:[900,15],10:[980,5],11:[925,7],12:[925,9],13:[612,8],14:[980,4],16:[612,4],17:[980,5],18:[966, 6],19:[1023,8],20:[733,5],21:[733,7],22:[524,4,40],23:[924,8],24:[966,14],25:[966,16],26:[1023,14],27:[832,6,33],28:[1222,15,34],29:[1240,7,34],30:[615,4,25],31:[615,8,25],32:[905,9,25],33:[832,8,33],34:[966,7,34],35:[966,8,34],36:[966,9,34],37:[966,5,34],38:[612,16,63],39:[1023,11,33],40:[1023,15,34],41:[1630,15,52],42:[1023,16,63],43:[805,4,26],44:[805,2,26],45:[748,8,33],46:[748,6,33],47:[966,5,25]}],Dw=496,gx=1,jx=0,ax=16,ix=3,hx=15,dx=16,$w=1,Zw=8,Yw=16,Hw=64,Ww=128,mx=16,nx=32,ox=48,px=64,qx= -112,kx=144,rx=145,lx=240,sx=2,fx=4,yx=0,zx=2,Cx=0,Dx=1,ux=3,Ex=5,Fx=8,Hx=10,Sw=12,Jx=15,Ax=224,Bx=228,Jw=0,wx=4,Lx=20,Iw=0,Tw=1,Pw=2,Qw=4,Rw=8,Ow=32,Aw={800:yw.prototype.Tm,801:yw.prototype.Um,802:yw.prototype.Sm},zw={496:yw.prototype.nl,497:yw.prototype.$l,498:yw.prototype.bm,499:yw.prototype.cm,500:yw.prototype.Yl,501:yw.prototype.Xl,502:yw.prototype.Zl,503:yw.prototype.dm},Cw={800:yw.prototype.po,801:yw.prototype.so,802:yw.prototype.ro,803:yw.prototype.qo,807:yw.prototype.hi,811:yw.prototype.hi, -815:yw.prototype.hi},Bw={496:yw.prototype.yn,497:yw.prototype.Dn,498:yw.prototype.Bn,499:yw.prototype.Cn,500:yw.prototype.xn,501:yw.prototype.wn,502:yw.prototype.zn,503:yw.prototype.vn,1014:yw.prototype.An};Ra(function(){for(var a=pb(document,"pcx86","hdc"),b=0;bthis.A&&this.C.length&&(this.A=0);if(0>this.A||a!=this.C[this.A])this.C.splice(0,0,a),this.A=0;this.A--}else this.aa?a="end":a=this.C[this.A+1];b=[];if(a){a=a.replace(/""/g,"'");var d=0,e=null;c=c||";";for(var f=0;f<=a.length;f++){var g=a.charAt(f);if('"'==g||"'"==g)e?g==e&&(e=null):e=g;else if(g==c&&!e||!g)b.push(ra(a.substring(d,f))),d=f+1}}return b}; function Ox(a,b,c){for(c=c||-1;c--&&b.length;){var d=b.pop();if(2>a.length)return!1;var e=a.pop(),f=a.pop();switch(d){case "*":d=f*e;break;case "/":if(!e)return!1;d=f/e;break;case "%":if(!e)return!1;d=f%e;break;case "+":d=f+e;break;case "-":d=f-e;break;case "<<":d=f<>":d=f>>e;break;case ">>>":d=f>>>e;break;case "<":d=f":d=f>e?1:0;break;case ">=":d=f>=e?1:0;break;case "==":d=f==e?1:0;break;case "!=":d=f!=e?1:0;break;case "&":d=f&e;break; case "^":d=f^e;break;case "|":d=f|e;break;case "&&":d=f&&e?1:0;break;case "||":d=f||e?1:0;break;default:return!1}a.push(d|0)}return!0} @@ -774,8 +774,8 @@ function JA(a,b){switch(b){case "V":a=$f(a.H);break;case "D":a=a.H.O&1024;break; function LA(a,b,c){return b.$b+"="+q(b.U,4)+(c?"["+q(b.ua,a.ha)+","+iy(b.Ka)+"]":"")}function MA(a,b,c,d,e){return b+"="+(null!=c?q(c,4):"")+"["+q(d,a.ha)+","+q(e-d,4)+"]"} function NA(a,b){var c;void 0===b&&(b=wy(a));c=KA(a,Uy)+KA(a,Xy)+KA(a,Vy)+KA(a,Wy)+(4a.H.ca&&(d="\n"+d,c+=e,e="");c+="\n"+LA(a,a.H.Z,b)+" ";80386<=a.H.ca&&(e+="\n",c+=LA(a,a.H.Ga,b)+" "+LA(a,a.H.Ha,b)+"\n");c+=MA(a,"LD",a.H.Eb.U,a.H.Eb.ua,a.H.Eb.ua+a.H.Eb.Ka)+" "+MA(a,"GD",null,a.H.Kb,a.H.Pc)+" "+MA(a,"ID", null,a.H.Pb,a.H.cd)+" ";c=c+(d+" "+e)+KA(a,rz);80386<=a.H.ca&&(c+=KA(a,tz)+KA(a,uz))}else 80386<=a.H.ca&&(c+=LA(a,a.H.Ga,b)+" "+LA(a,a.H.Ha,b)+" ");return c+=KA(a,wz)+JA(a,"V")+JA(a,"D")+JA(a,"I")+JA(a,"T")+JA(a,"S")+JA(a,"Z")+JA(a,"A")+JA(a,"P")+JA(a,"C")}l.gj=function(a,b){return a[0]>b[0]?1:a[0]>>0,p],H=ta(F,u,a.gj);0>H&&F.splice(-(H+1),0,u)}K&&(w.a=K.replace(/''/g,'"'))}a.D.push({Rf:b,nn:c,U:d,Ja:e,xa:f,cn:g,Fd:h,Pi:m})} -function sy(a,b,c){for(var d=0;d>>0,f=a.Sb(b)>>>0,g=0;g>>0,p=h.xa;null!=p&&(p>>>=0);var w=h.cn;48==k&&(k=40);if(k==b.U&&e>=m&&e=p&&f>>0,p],H=ta(F,u,a.gj);0>H&&F.splice(-(H+1),0,u)}K&&(w.a=K.replace(/''/g,'"'))}a.D.push({Rf:b,pn:c,U:d,Ja:e,xa:f,en:g,Fd:h,Pi:m})} +function sy(a,b,c){for(var d=0;d>>0,f=a.Sb(b)>>>0,g=0;g>>0,p=h.xa;null!=p&&(p>>>=0);var w=h.en;48==k&&(k=40);if(k==b.U&&e>=m&&e=p&&f":62,"?":63,"@":64,Bd:65,wh:66,xh:67,zh:68,E:69,Ah:70,Bh:71,Ch:72,Dh:73,Eh:74,Fh:75,Gh:76,Hh:77,Ih:78,Jh:79,Kh:80,Q:81,Lh:82,Mh:83,Nh:84,Oh:85,Ph:86,Qh:87,Rh:88,Sh:89,sf:90,"[":91,"\\":92,"]":93,"^":94,_:95,"`":96,Cd:97,jk:98,kk:99,d:100,e:101,vk:102,wk:103,xk:104,yk:105,Jl:106,k:107,Ll:108,Ol:109,n:110,Tl:111,p:112,q:113,r:114,nn:115,t:116,qn:117,rn:118,sn:119,x:120, -y:121,z:122,"{":123,"|":124,"}":125,"~":126,no:127},ea={};ea[173]=n["-"];ea[186]=n[";"];ea[187]=n["="];ea[189]=n["-"];ea[188]=n[","];ea[190]=n["."];ea[191]=n["/"];ea[192]=n["`"];ea[219]=n["["];ea[220]=n["\\"];ea[221]=n["]"];ea[222]=n["'"];var p={};p[n["1"]]=n["!"];p[n["2"]]=n["@"];p[n["3"]]=n["#"];p[n["4"]]=n.$;p[n["5"]]=n["%"];p[n["6"]]=n["^"];p[n["7"]]=n["&"];p[n["8"]]=n["*"];p[n["9"]]=n["("];p[n["0"]]=n[")"];p[186]=n[":"];p[187]=n["+"];p[188]=n["<"];p[189]=n._;p[190]=n[">"];p[191]=n["?"]; +var da={163840:[40,1,8,,254],184320:[40,1,9,,252],327680:[40,2,8,,255],368640:[40,2,9,,253],737280:[80,2,9,,249],1228800:[80,2,15,,249],1474560:[80,2,18,,240],2949120:[80,2,36,,240],21368320:[615,4,17],256256:[77,1,26,128],2494464:[203,2,12,512],5242880:[256,2,40,256],10485760:[512,2,40,256]},n={Nn:0,Pn:1,Qn:2,Zj:3,Rn:4,Sn:5,Tn:6,Un:7,Vn:8,Wn:9,Xn:10,Yn:11,Zn:12,$n:13,ao:14,bo:15,co:16,eo:17,fo:18,ho:19,io:20,jo:21,ko:22,lo:23,mo:24,no:25,oo:26," ":32,"!":33,'"':34,"#":35,$:36,"%":37,"&":38,"'":39, +"(":40,")":41,"*":42,"+":43,",":44,"-":45,".":46,"/":47,0:48,1:49,2:50,3:51,4:52,5:53,6:54,7:55,8:56,9:57,":":58,";":59,"<":60,"=":61,">":62,"?":63,"@":64,Bd:65,wh:66,xh:67,zh:68,E:69,Ah:70,Bh:71,Ch:72,Dh:73,Eh:74,Fh:75,Gh:76,Hh:77,Ih:78,Jh:79,Kh:80,Q:81,Lh:82,Mh:83,Nh:84,Oh:85,Ph:86,Qh:87,Rh:88,Sh:89,sf:90,"[":91,"\\":92,"]":93,"^":94,_:95,"`":96,Cd:97,jk:98,kk:99,d:100,e:101,vk:102,wk:103,xk:104,yk:105,Jl:106,k:107,Ll:108,Pl:109,n:110,Vl:111,p:112,q:113,r:114,pn:115,t:116,sn:117,tn:118,un:119,x:120, +y:121,z:122,"{":123,"|":124,"}":125,"~":126,po:127},ea={};ea[173]=n["-"];ea[186]=n[";"];ea[187]=n["="];ea[189]=n["-"];ea[188]=n[","];ea[190]=n["."];ea[191]=n["/"];ea[192]=n["`"];ea[219]=n["["];ea[220]=n["\\"];ea[221]=n["]"];ea[222]=n["'"];var p={};p[n["1"]]=n["!"];p[n["2"]]=n["@"];p[n["3"]]=n["#"];p[n["4"]]=n.$;p[n["5"]]=n["%"];p[n["6"]]=n["^"];p[n["7"]]=n["&"];p[n["8"]]=n["*"];p[n["9"]]=n["("];p[n["0"]]=n[")"];p[186]=n[":"];p[187]=n["+"];p[188]=n["<"];p[189]=n._;p[190]=n[">"];p[191]=n["?"]; p[192]=n["~"];p[219]=n["{"];p[220]=n["|"];p[221]=n["}"];p[222]=n['"'];p[173]=n._;p[61]=n["+"];p[59]=n[":"]; function fa(a,b){var c;if(a){b||(b=10);var d=a.charAt(0),e=0=e?48:55),d=String.fromCharCode(e)+d;a>>=4}return(c?"0x":"")+d}function ha(a,b){var c=a,d=a.lastIndexOf("/");0<=d&&(c=a.substr(d+1));d=c.indexOf("&");0>>this.B].nc(a&this.C,b&255,a)};function ac function bc(a,b){var c=0,d=[],e=!a.G&&a.W==a.D;e||Gb(a,!0);for(var f=0;f>>=f)&k;if(void 0!==g&&g[0])g[0](b,k,e);f+=h<<3;b+=h;c-=h}}function Ib(a,b,c,d,e){b="Memory block error ("+b+": "+ga(c)+","+ga(d)+")";e?a.Ea?a.Ea.message(b):a.log(b):r(b);return!1} -var tb,gc={Dj:20,count:8,Co:1,type:3},hc=0,ic;for(ic in gc){var jc=gc[ic];gc[ic]={qg:(1<>1),this.T=new Int32Array(this.L,0,c>>2),Rb(this,Wb?Xb:Yb);else{this.T=Array(c>>2);for(e=0;e>2),b=0;b>8,c)};l.rh=function(a,b,c){this.nc(a++,b&255,c++);this.nc(a++,b>>8&255,c++);this.nc(a++,b>>16&255,c++);this.nc(a,b>>>24,c)};l.Xm=function(a){return this.T[a>>2]>>>((a&3)<<3)&255}; -l.jn=function(a){var b=a>>2;a=(a&3)<<3;var c=this.T[b]>>a;return 24>a?c&65535:c&255|(this.T[b+1]&255)<<8};l.cn=function(a){var b=a>>2;a=(a&3)<<3;var c=this.T[b];a&&(c=c>>>a|this.T[b+1]<<32-a);return c};l.vn=function(a,b){var c=a>>2;a=(a&3)<<3;this.T[c]=this.T[c]&~(255<>2;a=(a&3)<<3;24>a?this.T[c]=this.T[c]&~(65535<>8);this.Da=!0}; -l.Bn=function(a,b){var c=a>>2;if(a=(a&3)<<3){var d=-1<>>32-a}else this.T[c]=b;this.Da=!0};l.Wm=function(a,b){this.F&&xc(this.F,b,1,!1);return this.zd(a,b)};l.hn=function(a,b){this.F&&xc(this.F,b,2,!1);return this.bf(a,b)};l.bn=function(a,b){this.F&&xc(this.F,b,4,!1);return this.mh(a,b)};l.un=function(a,b,c){this.F&&xc(this.F,c,1,!0);this.H||this.gf(a,b,c)};l.Gn=function(a,b,c){this.F&&xc(this.F,c,2,!0);this.H||this.uh(a,b,c)}; -l.An=function(a,b,c){this.F&&xc(this.F,c,4,!0);this.H||this.U(a,b,c)};l.Zm=function(a,b){this.A.T[this.C]|=this.I;this.B.T[this.D]|=this.I;return this.J.mc(a,b)};l.ln=function(a,b){this.A.T[this.C]|=this.I;this.B.T[this.D]|=this.I;return this.J.af(a,b)};l.en=function(a,b){this.A.T[this.C]|=this.I;this.B.T[this.D]|=this.I;return this.J.Md(a,b)};l.xn=function(a,b,c){this.A.T[this.C]|=this.I;this.B.T[this.D]|=this.M;this.J.nc(a,b,c)}; -l.Jn=function(a,b,c){this.A.T[this.C]|=this.I;this.B.T[this.D]|=this.M;this.J.jf(a,b,c)};l.Dn=function(a,b,c){this.A.T[this.C]|=this.I;this.B.T[this.D]|=this.M;this.J.hf(a,b,c)};l.$m=function(a,b){return yc(this.F,b,!1).mc(a,b)};l.mn=function(a,b){return yc(this.F,b,!1).af(a,b)};l.fn=function(a,b){return yc(this.F,b,!1).Md(a,b)};l.yn=function(a,b,c){yc(this.F,c,!0).nc(a,b,c)};l.Kn=function(a,b,c){yc(this.F,c,!0).jf(a,b,c)};l.En=function(a,b,c){yc(this.F,c,!0).hf(a,b,c)};l.Vm=function(a){return this.Fa[a]}; -l.Ij=function(a){return this.Fa[a]};l.Ym=function(a){this.A.T[this.C]|=32;this.B.T[this.D]|=32;this.mc=this.Ij;return this.Fa[a]};l.gn=function(a){return this.K.getUint16(a,!0)};l.Nj=function(a){return a&1?this.Fa[a]|this.Fa[a+1]<<8:this.qd[a>>1]};l.kn=function(a){this.A.T[this.C]|=32;this.B.T[this.D]|=32;this.af=this.Nj;return a&1?this.Fa[a]|this.Fa[a+1]<<8:this.qd[a>>1]};l.an=function(a){return this.K.getInt32(a,!0)}; -l.Kj=function(a){return a&3?this.Fa[a]|this.Fa[a+1]<<8|this.Fa[a+2]<<16|this.Fa[a+3]<<24:this.T[a>>2]};l.dn=function(a){this.A.T[this.C]|=32;this.B.T[this.D]|=32;this.Md=this.Kj;return a&3?this.Fa[a]|this.Fa[a+1]<<8|this.Fa[a+2]<<16|this.Fa[a+3]<<24:this.T[a>>2]};l.tn=function(a,b){this.Fa[a]=b;this.Da=!0};l.Wj=function(a,b){this.Fa[a]=b;this.Da=!0};l.wn=function(a,b){this.Fa[a]=b;this.A.T[this.C]|=32;this.B.T[this.D]|=96;this.nc=this.Wj;this.J.Da=!0}; -l.Fn=function(a,b){this.K.setUint16(a,b,!0);this.Da=!0};l.Yj=function(a,b){a&1?(this.Fa[a]=b,this.Fa[a+1]=b>>8):this.qd[a>>1]=b;this.Da=!0};l.In=function(a,b){a&1?(this.Fa[a]=b,this.Fa[a+1]=b>>8):this.qd[a>>1]=b;this.A.T[this.C]|=32;this.B.T[this.D]|=96;this.jf=this.Yj;this.J.Da=!0};l.zn=function(a,b){this.K.setInt32(a,b,!0);this.Da=!0};l.Xj=function(a,b){a&3?(this.Fa[a]=b,this.Fa[a+1]=b>>8,this.Fa[a+2]=b>>16,this.Fa[a+3]=b>>24):this.T[a>>2]=b;this.Da=!0}; -l.Cn=function(a,b){a&3?(this.Fa[a]=b,this.Fa[a+1]=b>>8,this.Fa[a+2]=b>>16,this.Fa[a+3]=b>>24):this.T[a>>2]=b;this.A.T[this.C]|=32;this.B.T[this.D]|=96;this.hf=this.Xj;this.J.Da=!0};function zc(a){gb&&!Wb&&(a=a<<24|a<<8&16711680|a>>8&65280|a>>>24);return a} -var nc=0,Vb=2,pc=5,rc=6,Ac=["black","blue","green","cyan"],Mb="NONE RAM ROM VIDEO H/W UNPAGED PAGED".split(" "),mc=0,tc=[],Zb=[u.prototype.Xm,u.prototype.vn,u.prototype.jn,u.prototype.Hn,u.prototype.cn,u.prototype.Bn],wc=[u.prototype.Wm,u.prototype.un,u.prototype.hn,u.prototype.Gn,u.prototype.bn,u.prototype.An],sc=[u.prototype.Zm,u.prototype.xn,u.prototype.ln,u.prototype.Jn,u.prototype.en,u.prototype.Dn],qc=[u.prototype.$m,u.prototype.yn,u.prototype.mn,u.prototype.Kn,u.prototype.fn,u.prototype.En]; -if(gb)var Yb=[u.prototype.Vm,u.prototype.tn,u.prototype.gn,u.prototype.Fn,u.prototype.an,u.prototype.zn],Xb=[u.prototype.Ij,u.prototype.Wj,u.prototype.Nj,u.prototype.Yj,u.prototype.Kj,u.prototype.Xj],Bc=[u.prototype.Ym,u.prototype.wn,u.prototype.kn,u.prototype.In,u.prototype.dn,u.prototype.Cn]; +l.Mj=function(a,b){return this.mc(a++,b++)|this.mc(a,b)<<8};l.Jj=function(a,b){return this.mc(a++,b++)|this.mc(a++,b++)<<8|this.mc(a++,b++)<<16|this.mc(a,b)<<24};l.th=function(a,b,c){this.nc(a++,b&255,c++);this.nc(a,b>>8,c)};l.rh=function(a,b,c){this.nc(a++,b&255,c++);this.nc(a++,b>>8&255,c++);this.nc(a++,b>>16&255,c++);this.nc(a,b>>>24,c)};l.Zm=function(a){return this.T[a>>2]>>>((a&3)<<3)&255}; +l.ln=function(a){var b=a>>2;a=(a&3)<<3;var c=this.T[b]>>a;return 24>a?c&65535:c&255|(this.T[b+1]&255)<<8};l.en=function(a){var b=a>>2;a=(a&3)<<3;var c=this.T[b];a&&(c=c>>>a|this.T[b+1]<<32-a);return c};l.xn=function(a,b){var c=a>>2;a=(a&3)<<3;this.T[c]=this.T[c]&~(255<>2;a=(a&3)<<3;24>a?this.T[c]=this.T[c]&~(65535<>8);this.Da=!0}; +l.Dn=function(a,b){var c=a>>2;if(a=(a&3)<<3){var d=-1<>>32-a}else this.T[c]=b;this.Da=!0};l.Ym=function(a,b){this.F&&xc(this.F,b,1,!1);return this.zd(a,b)};l.kn=function(a,b){this.F&&xc(this.F,b,2,!1);return this.bf(a,b)};l.dn=function(a,b){this.F&&xc(this.F,b,4,!1);return this.mh(a,b)};l.wn=function(a,b,c){this.F&&xc(this.F,c,1,!0);this.H||this.gf(a,b,c)};l.In=function(a,b,c){this.F&&xc(this.F,c,2,!0);this.H||this.uh(a,b,c)}; +l.Cn=function(a,b,c){this.F&&xc(this.F,c,4,!0);this.H||this.U(a,b,c)};l.an=function(a,b){this.A.T[this.C]|=this.I;this.B.T[this.D]|=this.I;return this.J.mc(a,b)};l.nn=function(a,b){this.A.T[this.C]|=this.I;this.B.T[this.D]|=this.I;return this.J.af(a,b)};l.gn=function(a,b){this.A.T[this.C]|=this.I;this.B.T[this.D]|=this.I;return this.J.Md(a,b)};l.zn=function(a,b,c){this.A.T[this.C]|=this.I;this.B.T[this.D]|=this.M;this.J.nc(a,b,c)}; +l.Ln=function(a,b,c){this.A.T[this.C]|=this.I;this.B.T[this.D]|=this.M;this.J.jf(a,b,c)};l.Fn=function(a,b,c){this.A.T[this.C]|=this.I;this.B.T[this.D]|=this.M;this.J.hf(a,b,c)};l.bn=function(a,b){return yc(this.F,b,!1).mc(a,b)};l.on=function(a,b){return yc(this.F,b,!1).af(a,b)};l.hn=function(a,b){return yc(this.F,b,!1).Md(a,b)};l.An=function(a,b,c){yc(this.F,c,!0).nc(a,b,c)};l.Mn=function(a,b,c){yc(this.F,c,!0).jf(a,b,c)};l.Gn=function(a,b,c){yc(this.F,c,!0).hf(a,b,c)};l.Xm=function(a){return this.Fa[a]}; +l.Ij=function(a){return this.Fa[a]};l.$m=function(a){this.A.T[this.C]|=32;this.B.T[this.D]|=32;this.mc=this.Ij;return this.Fa[a]};l.jn=function(a){return this.K.getUint16(a,!0)};l.Nj=function(a){return a&1?this.Fa[a]|this.Fa[a+1]<<8:this.qd[a>>1]};l.mn=function(a){this.A.T[this.C]|=32;this.B.T[this.D]|=32;this.af=this.Nj;return a&1?this.Fa[a]|this.Fa[a+1]<<8:this.qd[a>>1]};l.cn=function(a){return this.K.getInt32(a,!0)}; +l.Kj=function(a){return a&3?this.Fa[a]|this.Fa[a+1]<<8|this.Fa[a+2]<<16|this.Fa[a+3]<<24:this.T[a>>2]};l.fn=function(a){this.A.T[this.C]|=32;this.B.T[this.D]|=32;this.Md=this.Kj;return a&3?this.Fa[a]|this.Fa[a+1]<<8|this.Fa[a+2]<<16|this.Fa[a+3]<<24:this.T[a>>2]};l.vn=function(a,b){this.Fa[a]=b;this.Da=!0};l.Wj=function(a,b){this.Fa[a]=b;this.Da=!0};l.yn=function(a,b){this.Fa[a]=b;this.A.T[this.C]|=32;this.B.T[this.D]|=96;this.nc=this.Wj;this.J.Da=!0}; +l.Hn=function(a,b){this.K.setUint16(a,b,!0);this.Da=!0};l.Yj=function(a,b){a&1?(this.Fa[a]=b,this.Fa[a+1]=b>>8):this.qd[a>>1]=b;this.Da=!0};l.Kn=function(a,b){a&1?(this.Fa[a]=b,this.Fa[a+1]=b>>8):this.qd[a>>1]=b;this.A.T[this.C]|=32;this.B.T[this.D]|=96;this.jf=this.Yj;this.J.Da=!0};l.Bn=function(a,b){this.K.setInt32(a,b,!0);this.Da=!0};l.Xj=function(a,b){a&3?(this.Fa[a]=b,this.Fa[a+1]=b>>8,this.Fa[a+2]=b>>16,this.Fa[a+3]=b>>24):this.T[a>>2]=b;this.Da=!0}; +l.En=function(a,b){a&3?(this.Fa[a]=b,this.Fa[a+1]=b>>8,this.Fa[a+2]=b>>16,this.Fa[a+3]=b>>24):this.T[a>>2]=b;this.A.T[this.C]|=32;this.B.T[this.D]|=96;this.hf=this.Xj;this.J.Da=!0};function zc(a){gb&&!Wb&&(a=a<<24|a<<8&16711680|a>>8&65280|a>>>24);return a} +var nc=0,Vb=2,pc=5,rc=6,Ac=["black","blue","green","cyan"],Mb="NONE RAM ROM VIDEO H/W UNPAGED PAGED".split(" "),mc=0,tc=[],Zb=[u.prototype.Zm,u.prototype.xn,u.prototype.ln,u.prototype.Jn,u.prototype.en,u.prototype.Dn],wc=[u.prototype.Ym,u.prototype.wn,u.prototype.kn,u.prototype.In,u.prototype.dn,u.prototype.Cn],sc=[u.prototype.an,u.prototype.zn,u.prototype.nn,u.prototype.Ln,u.prototype.gn,u.prototype.Fn],qc=[u.prototype.bn,u.prototype.An,u.prototype.on,u.prototype.Mn,u.prototype.hn,u.prototype.Gn]; +if(gb)var Yb=[u.prototype.Xm,u.prototype.vn,u.prototype.jn,u.prototype.Hn,u.prototype.cn,u.prototype.Bn],Xb=[u.prototype.Ij,u.prototype.Wj,u.prototype.Nj,u.prototype.Yj,u.prototype.Kj,u.prototype.Xj],Bc=[u.prototype.$m,u.prototype.yn,u.prototype.mn,u.prototype.Kn,u.prototype.fn,u.prototype.En]; function Cc(a,b){t.call(this,"CPU",a);b=a.cycles||b;var c=a.multiplier||1;this.R={};this.R.Wc=b;this.R.xd=c;this.R.Hf=Math.round(this.R.Wc/1E4)/100;this.R.qe=this.R.Hf*this.R.xd;this.ca.Ib=!1;this.ca.Tj=!1;this.ca.Ee=a.autoStart;this.ca.ii=!1;this.ca.Af=!1;this.R.Jf=this.R.re=0;this.R.Kf=a.csStart;this.R.Je=a.csInterval;this.R.Ke=a.csStop;this.Kl=this.$f.bind(this);eb(this)}ba(Cc,t);l=Cc.prototype; l.hc=function(a,b,c,d){this.oa=a;this.ma=b;this.Ea=d;for(b=0;ba.R.Hf&&(c=Math.round(c/a.R.xd));return c}function Fc(a){a.R.vd=0;a.ld=a.Rc=a.Hc=a.A=0;Gc(a);Jc(a,1)} +function Mc(a,b){var c=Nc;ca.R.Hf&&(c=Math.round(c/a.R.xd));return c}function Fc(a){a.R.vd=0;a.ld=a.Rc=a.Hc=a.A=0;Gc(a);Jc(a,1)} function Jc(a,b,c){if(void 0!==b){.8>a.R.vd/a.R.qe&&(b=1);a.R.xd=b;b=a.R.Hf*a.R.xd;if(a.R.qe!=b){a.R.qe=b;b=a.R.qe.toFixed(2)+"Mhz";var d=a.ia.setSpeed;d&&(d.textContent=b);a.lc("target speed: "+b)}c&&a.oa&&Rc(a.oa)}Lc(a,a.Rc);a.Rc=0;a.R.wd=sa();a.R.Jd=0;Mc(a)} -l.$f=function(a){if(fb(this,!0)){if(!this.ca.Ib){Jc(this);this.oa&&this.oa.start(this.R.wd,Qc(this));this.ca.Ib=!0;this.ca.Tj=!0;this.V&&this.V.start();var b=this.ia.run;b&&(b.textContent="Halt");this.oa&&(Sc(this.oa,!0),a&&Rc(this.oa,!0))}this.R.yg>=this.R.Wc&&Mc(this,!0);this.R.Oe=0;this.R.If=sa();this.R.Jd&&(a=this.R.If-this.R.Jd,a>this.R.Ai&&(this.R.wd+=a,this.R.wd>this.R.If&&(this.R.wd=this.R.If)));try{do{var c=this.ca.Af?1:this.R.Rl;if(this.V){Tc(this.V);var d=this.V;a=c;var e=d.H[0];if(e.Gd){var f= +l.$f=function(a){if(fb(this,!0)){if(!this.ca.Ib){Jc(this);this.oa&&this.oa.start(this.R.wd,Qc(this));this.ca.Ib=!0;this.ca.Tj=!0;this.V&&this.V.start();var b=this.ia.run;b&&(b.textContent="Halt");this.oa&&(Sc(this.oa,!0),a&&Rc(this.oa,!0))}this.R.yg>=this.R.Wc&&Mc(this,!0);this.R.Oe=0;this.R.If=sa();this.R.Jd&&(a=this.R.If-this.R.Jd,a>this.R.Ai&&(this.R.wd+=a,this.R.wd>this.R.If&&(this.R.wd=this.R.If)));try{do{var c=this.ca.Af?1:this.R.Tl;if(this.V){Tc(this.V);var d=this.V;a=c;var e=d.H[0];if(e.Gd){var f= (Qc(d.U,d.V)-e.fd)/d.za|0,g=Uc(d,0)-f;e.mode==Vc&&(g-=f);var h=g*d.za|0;e.mode==Vc&&(h>>=1);a>h&&(a=h)}var c=a,k=this.V;a=c;if(k.A&&k.A[Wc]&Xc){var m=k.X-Qc(k.U,k.V);0m&&(a=m)}c=a}try{this.yh(c)}catch(y){if("number"!=typeof y)throw y;}var q=this.Hc-this.A;this.Rc+=q;this.R.Oe+=q;Lc(this,0,!0);a=q;this.ca.Af&&(b=!1,this.R.Jf=this.R.Jf+this.ri()|0,this.R.re-=a,0>=this.R.re&&(this.R.re+=this.R.Je,b=!0),0<=this.R.Ke&&this.R.Ke<=Qc(this)&&(this.R.Je=this.R.Ke=-1,Gc(this),Ic(this),b=!0),b&&this.lc(Qc(this)+ " cycles: checksum="+ga(this.R.Jf)));this.R.Me-=q;0>=this.R.Me&&(this.R.Me+=this.R.Ci,this.oa&&Yc(this.oa));this.R.Le-=q;0>=this.R.Le&&(this.R.Le+=this.R.Bi,this.oa&&Sc(this.oa));this.R.Ne-=q;if(0>=this.R.Ne){this.R.Ne+=this.R.xg;break}}while(this.ca.Ib)}catch(y){Ic(this);Hc(this);this.oa&&this.oa.stop(sa(),Qc(this));fb(this,!1);cb(this,y.stack||y.message);return}c=setTimeout;d=this.Kl;this.R.Jd=sa();e=this.R.Ai;this.R.Oe&&(e=Math.round(e*this.R.Oe/this.R.xg));e-=this.R.Jd-this.R.If;if(f=this.R.Jd- this.R.wd)this.R.vd=Math.round(this.Rc/(10*f))/100,864E5<=f&&(this.ld=0,this.V&&Tc(this.V,!0),Jc(this));if(0>e||this.R.vde&&(this.R.wd-=e),e=0;this.R.yg+=this.R.Oe;this.R.Jd+=e;c(d,e)}else Hc(this),this.oa&&this.oa.stop(sa(),Qc(this))};l.yh=function(){return 0};function Ic(a,b){a.ca.wf&&(a.ca.lg=!0);a.Hc-=a.A;a.A=0;Lc(a,a.Rc);a.Rc=0;if(a.ca.Ib){a.ca.Ib=!1;a.V&&a.V.stop();var c=a.ia.run;c&&(c.textContent="Run")}a.ca.complete=b} function Hc(a){a.oa&&(Yc(a.oa,void 0),Sc(a.oa,void 0))}var Nc=30,Oc=60,Pc=2,Dc=["power","reset"];function Zc(a,b,c,d){this.B=a;this.Ea=a.Ea;this.id=b;this.Yf=c||"";this.ha=0;this.Ta=65535;this.C=this.Ta+1;this.ob=this.gc=this.ext=this.jb=this.type=this.va=0;this.$b=-1;this.S=this.Jc=2;this.O=this.ra=65535;this.J=this.zi;this.H=this.ei;this.I=this.gi;this.A={ha:-1,va:0,Ta:0,jb:0,type:0,ext:0,$b:-1};1==this.id&&(this.Ze=0,this.D=null,this.ne=!1,this.G=Array(32),this.F=[]);$c(this,!0,d)}l=Zc.prototype; -l.zi=function(a){this.ha=a&65535;return this.va=this.ha<<4};l.Gf=function(a,b){var c,d,e=this.B;a&=65535;a&4?(c=e.jc.va,d=c+e.jc.Ta|0):(c=e.Kb,d=e.Oc);if(c){c=c+(a&65528)|0;if(d-c|0)return e.A-=15,ad(this,c,a,b);this.id>>0)+b<=this.C?this.va+a|0:this.yf()};l.lk=function(a,b){return(a>>>0)+b>this.C?this.va+a|0:this.yf()};l.yf=function(){v.call(this.B,13,0);return-1};l.fi=function(a,b){return(a>>>0)+b<=this.C?this.va+a|0:this.zf()}; +l.zi=function(a){this.ha=a&65535;return this.va=this.ha<<4};l.Gf=function(a,b){var c,d,e=this.B;a&=65535;a&4?(c=e.jc.va,d=c+e.jc.Ta|0):(c=e.Kb,d=e.Oc);if(c){c=c+(a&65528)|0;if(d-c|0)return e.A-=15,ad(this,c,a,b);this.id>>0)+b<=this.C?this.va+a|0:this.yf()};l.lk=function(a,b){return(a>>>0)+b>this.C?this.va+a|0:this.yf()};l.yf=function(){v.call(this.B,13,0);return-1};l.fi=function(a,b){return(a>>>0)+b<=this.C?this.va+a|0:this.zf()}; l.mk=function(a,b){return(a>>>0)+b>this.C?this.va+a|0:this.zf()};l.zf=function(){v.call(this.B,13,0);return-1};function dd(a,b,c,d,e){a.ha=b;a.va=d;a.Ta=e;a.C=(e>>>0)+1;a.jb=c;a.type=c&7936;a.ext=c>>16&192;a.$b=(b&4?a.B.jc.va:a.B.Kb)+(b&65528)|0;a.id>>0)+1;a.jb=e;a.type=e&7936;a.ext=0;a.$b=b;a.id>>0)+1,a.jb=a.A.jb,a.type=a.A.type,a.ext=a.A.ext,a.$b=a.A.$b,a.A.ha=-1,$c(a,!0,!0,!1),a.va;a.A.ha=-1;var f=e.fa(b+0),g=e.fa(b+4),h=g&7936,k=e.fa(b+2)|(g&255)<<16,m=e.fa(b+6),q=c&65528;if(80386<=e.aa){var y=f,k=k|(m&65280)<<16,f=f|(m&15)<<16;m&128&&(f=f<<12|4095)}switch(a.id){case gd:var w=a.D;a.ne=!1;if(w&&c==hd&&a.F.length){var z=a.F[a.Ze-1];if(z&&!z())return-1}var B=c&3,S=(g&24576)>>13,z=-1,X,ca;q|| b>=e.Kb&&b=a.ob&&(B>a.ob&&(z=x(e),id(e,x(e),!0),A(e,z),a.ne=!0),z=0);else{if(256==h||2304==h)return jd(a,c,w)?a.va:-1;if(1024==h)z=2,ca=0,B>>0)+1)}; -function $c(a,b,c,d){void 0===c&&(c=!!(a.B.pa&1));a.ud=!1;if(c)if(a.load=a.Gf,a.yi=a.Ml,a.Lb=a.di,a.Mb=a.fi,void 0===d&&(d=!!(a.B.N&131072)),d)a.load=a.J,a.Lb=a.H,a.Mb=a.I,a.ob=a.gc=3,a.S=2,a.O=a.ra=65535,a.Ta=65535,a.C=a.Ta+1,a.Jc=a.S,a.$b=-1,a.ne=!1;else{if(!(a.ha&-4))a.Lb=a.yf,a.Mb=a.zf;else if(a.type&4096){6144==(a.type&6656)&&(a.Lb=a.yf);if(a.type&2048||!(a.type&512))a.Mb=a.zf;1024==(a.type&3072)&&(a.Lb==a.di&&(a.Lb=a.lk),a.Mb==a.fi&&(a.Mb=a.mk),a.ud=!0);b&&a.id>13,80386>a.B.aa||!(a.ext&64)?(a.S=2,a.O=65535):(a.S=4,a.O=-1),a.Jc=a.S,a.ra=a.O)}else a.load=a.zi,a.yi=a.Nl,a.Lb=a.ei,a.Mb=a.gi,a.ob=a.gc=0,a.$b=-1,a.ne=!1}var gd=1,rd=2,cd=3,ed=4,bd=6,hd=1; +function $c(a,b,c,d){void 0===c&&(c=!!(a.B.pa&1));a.ud=!1;if(c)if(a.load=a.Gf,a.yi=a.Nl,a.Lb=a.di,a.Mb=a.fi,void 0===d&&(d=!!(a.B.N&131072)),d)a.load=a.J,a.Lb=a.H,a.Mb=a.I,a.ob=a.gc=3,a.S=2,a.O=a.ra=65535,a.Ta=65535,a.C=a.Ta+1,a.Jc=a.S,a.$b=-1,a.ne=!1;else{if(!(a.ha&-4))a.Lb=a.yf,a.Mb=a.zf;else if(a.type&4096){6144==(a.type&6656)&&(a.Lb=a.yf);if(a.type&2048||!(a.type&512))a.Mb=a.zf;1024==(a.type&3072)&&(a.Lb==a.di&&(a.Lb=a.lk),a.Mb==a.fi&&(a.Mb=a.mk),a.ud=!0);b&&a.id>13,80386>a.B.aa||!(a.ext&64)?(a.S=2,a.O=65535):(a.S=4,a.O=-1),a.Jc=a.S,a.ra=a.O)}else a.load=a.zi,a.yi=a.Ol,a.Lb=a.ei,a.Mb=a.gi,a.ob=a.gc=0,a.$b=-1,a.ne=!1}var gd=1,rd=2,cd=3,ed=4,bd=6,hd=1; function wd(a){var b=+a.model||8088,c;switch(b){default:c=4772727;break;case 80286:c=6E6;break;case 80386:c=16E6}Cc.call(this,a,c);this.aa=b;a=a.stepping;this.od=b+(a?fa(a,16):0);this.Uh=61442;this.nd=1792;this.Th=28672;this.kf=4;this.Ha=255;this.B=80286<=this.aa?ib:hb;this.qa=xd;this.ci=yd;this.hi=zd;this.li=Ad;if(80186<=this.aa&&(this.qa=xd.slice(),this.ci=yd.slice(),this.hi=zd.slice(),this.Ha=31,this.qa[15]=Bd,this.qa[96]=Cd,this.qa[97]=Dd,this.qa[98]=Ed,this.qa[99]=Bd,this.qa[100]=Bd,this.qa[101]= Bd,this.qa[102]=Bd,this.qa[103]=Bd,this.qa[104]=Fd,this.qa[105]=Gd,this.qa[106]=Hd,this.qa[107]=Id,this.qa[108]=Jd,this.qa[109]=Kd,this.qa[110]=Ld,this.qa[111]=Md,this.qa[192]=Nd,this.qa[193]=Od,this.qa[200]=Pd,this.qa[201]=Qd,this.qa[241]=Rd,this.ci[7]=Sd,this.hi[7]=Sd,80286<=this.aa)){this.Uh=2;this.nd|=28672;this.kf=0;this.qa[15]=Td;this.kd=Ud.slice();for(b=0;b=this.od&&(this.kd[166]=be,this.kd[167]=ce)}}this.nf=[];this.ai=[];this.bg=0;Fc(this);this.ca.complete=this.ca.pk=!1;this.pi=0;this.Nc=this.X=[];this.yb=this.vh=this.xb=this.lf=this.xe=this.ye=this.Ic=0;de(this)}ba(wd,Cc); @@ -141,8 +141,8 @@ function Lb(a){var b;if(a.X===a.Nc){a.X=Array(a.lf);a.pf=new u(null,0,0,pc,null, function yc(a,b,c,d){var e=(b&-4194304)>>>20,f=a.Nc[(a.md+e&a.ye)>>>a.yb],g=f.Md(e);if(!(g&1))return d||fe.call(a,b,!1,c),a.ze;if(!(g&4)&&3==a.Ga)return d||fe.call(a,b,!0,c),a.ze;var h=(b&4190208)>>>10,g=a.Nc[((g&-4096)+h&a.ye)>>>a.yb],k=g.Md(h);if(!(k&1))return d||fe.call(a,b,!1,c),a.ze;if(!(k&4)&&3==a.Ga)return d||fe.call(a,b,!0,c),a.ze;c=a.Nc[((k&-4096)+(b&4095)&a.ye)>>>a.yb];if(d)return c;d=b>>>a.yb;k=a.X[d];b&=-4096;var m;0>2;b.B=g;b.D=h>>2;gb&&Wb&&c.T&&!c.controller&&!c.ee&&!c.fe?(b.Fa=c.Fa,b.qd=c.qd,b.T=c.T,Rb(b,Bc)):(b.I=c?zc(32):0,b.M=c?zc(96):0,Rb(b,sc));Fb(b,a.Ea,k);a.X[d]=b;a.mf.push(d);return b}function ge(a){a.X!==a.Nc&&(a.X=a.Nc,a.pf=null,a.mf=null,a.ze=null)}l=wd.prototype;l.reset=function(){this.ca.Ib&&Ic(this);de(this);Fc(this);this.ca.error=!1}; function he(a,b){var c;switch(b){case 0:c=a.F;break;case 1:c=a.H;break;case 2:c=a.K;break;case 3:c=a.G;break;case 4:c=C(a);break;case 5:c=a.L;break;case 6:c=a.J;break;case 7:c=a.I}return c}function ie(a,b,c){switch(b){case 0:a.F=c;break;case 1:a.H=c;break;case 2:a.K=c;break;case 3:a.G=c;break;case 4:A(a,c);break;case 5:a.L=c;break;case 6:a.J=c;break;case 7:a.I=c}} -function de(a){a.F=0;a.G=0;a.H=0;a.K=0;a.ic=0;a.L=0;a.J=0;a.I=0;a.cc=!1;a.za=a.Va=0;a.ta=0;a.oi=0;a.Z=0;a.pa=65520;a.Tb=0;a.Pc=1023;a.N=a.Wb=0;a.Yd=a.Be=a.Xd=a.Zd=0;a.xc=-1;a.Sc=a.yc=-1;a.ad=a.na=-1;a.ga=new Zc(a,gd,"CS");a.Ca=new Zc(a,rd,"DS");a.ua=new Zc(a,rd,"ES");a.W=new Zc(a,cd,"SS");A(a,0);id(a,0);if(80386<=a.aa){switch(a.od){case 80562:case 80563:a.K=771;break;case 80578:a.K=772;break;case 80594:a.K=773;break;case 80595:case 80596:a.K=776}a.pa=16;a.xi=0;a.Wd=0;a.md=0;a.Bb=[0,0,0,0,null,null, -0,0];a.rf=[null,null,null,null,null,null,0,0];a.qb=new Zc(a,rd,"FS");a.rb=new Zc(a,rd,"GS");ge(a)}a.eg=new Zc(a,0,"NULL");a.Ba=a.Ca;a.Db=a.W;a.M=a.ba=0;a.C=a.D=-1;a.Xa=a.eg;a.Ka=0;if(80286>a.aa)ud(a,0,65535);else{a.Kb=0;a.Oc=65535;a.jc=new Zc(a,5,"LDT",!0);a.la=new Zc(a,ed,"TSS",!0);a.bb=new Zc(a,bd,"VER",!0);ud(a,65520,61440);var b,c=E(a);b=a.ga;var d=-65536;80386>b.B.aa&&(d&=16777215);b=b.va=d;a.ea=b+c|0;a.cg=(b>>>0)+(a.ga.Ta>>>0)+1}td(a,0);kd(a)} +function de(a){a.F=0;a.G=0;a.H=0;a.K=0;a.ic=0;a.L=0;a.J=0;a.I=0;a.cc=!1;a.za=a.Va=0;a.Ml=[0,0];a.Ql=[0,0];a.ta=0;a.oi=0;a.Z=0;a.pa=65520;a.Tb=0;a.Pc=1023;a.N=a.Wb=0;a.Yd=a.Be=a.Xd=a.Zd=0;a.xc=-1;a.Sc=a.yc=-1;a.ad=a.na=-1;a.ga=new Zc(a,gd,"CS");a.Ca=new Zc(a,rd,"DS");a.ua=new Zc(a,rd,"ES");a.W=new Zc(a,cd,"SS");A(a,0);id(a,0);if(80386<=a.aa){switch(a.od){case 80562:case 80563:a.K=771;break;case 80578:a.K=772;break;case 80594:a.K=773;break;case 80595:case 80596:a.K=776}a.pa=16;a.xi=0;a.Wd=0;a.md=0; +a.Bb=[0,0,0,0,null,null,0,0];a.rf=[null,null,null,null,null,null,0,0];a.qb=new Zc(a,rd,"FS");a.rb=new Zc(a,rd,"GS");ge(a)}a.eg=new Zc(a,0,"NULL");a.Ba=a.Ca;a.Db=a.W;a.M=a.ba=0;a.C=a.D=-1;a.Xa=a.eg;a.Ka=0;if(80286>a.aa)ud(a,0,65535);else{a.Kb=0;a.Oc=65535;a.jc=new Zc(a,5,"LDT",!0);a.la=new Zc(a,ed,"TSS",!0);a.bb=new Zc(a,bd,"VER",!0);ud(a,65520,61440);var b,c=E(a);b=a.ga;var d=-65536;80386>b.B.aa&&(d&=16777215);b=b.va=d;a.ea=b+c|0;a.cg=(b>>>0)+(a.ga.Ta>>>0)+1}td(a,0);kd(a)} function je(a){2==a.Jc?(a.vi=a.fa,a.Vb=ke,a.bc=le,a.Qc=me,2==a.S?(a.ja=ne,a.wa=oe,a.$a=pe):(a.ja=qe,a.wa=re,a.$a=se)):(a.vi=a.da,a.Vb=te,a.bc=ue,a.Qc=ve,2==a.S?(a.ja=we,a.wa=xe,a.$a=ye):(a.ja=ze,a.wa=Ae,a.$a=Be))}function ld(a,b){a.S!=b&&(a.ba|=1024,a.S=b,a.O=2==b?65535:-1,Ce(a))}function Ce(a){2==a.S?(a.sb=32768,a.ib=a.fa,a.nb=a.Wa,2==a.Jc?(a.ja=ne,a.wa=oe,a.$a=pe):(a.ja=we,a.wa=xe,a.$a=ye)):(a.sb=-2147483648,a.ib=a.da,a.nb=a.Pa,2==a.Jc?(a.ja=qe,a.wa=re,a.$a=se):(a.ja=ze,a.wa=Ae,a.$a=Be))} function De(a){a.Jc=a.ga.Jc;a.ra=a.ga.ra;je(a);a.S=a.ga.S;a.O=a.ga.O;Ce(a);a.ba&=-3073}l.ri=function(){var a=this.F+this.G+this.H+this.K+C(this)+this.L+this.J+this.I|0;return a=a+E(this)+this.ga.ha+this.Ca.ha+this.W.ha+this.ua.ha+sd(this)|0};function Ee(a,b,c){void 0===a.nf[b]&&(a.nf[b]=[]);a.nf[b].push(c)}function Fe(a,b){var c=a.ai[b];null!=c&&(c(--a.bg),delete a.ai[b])} function Ge(a,b){for(var c=a.Bb[7],d=c>>16,e=0;4>e;e++){if(c&3){var f=!!(d&1),g=a.Bb[e],g=g&~(d>>2&3);if(b){var g=a.X[g>>>a.yb],h=a;f?g.fe++||(h&&(g.F=h),vc(g,wc,!1)):g.ee++||(h&&(g.F=h),uc(g,wc,!1))}else g=a.X[g>>>a.yb],f?--g.fe||(f=g,f.nc=f.H?f.sh:f.gf,f.jf=f.H?f.th:f.uh,f.hf=f.H?f.rh:f.U):--g.ee||(f=g,f.mc=f.zd,f.af=f.bf,f.Md=f.mh)}c>>=2;d>>=4}} @@ -219,7 +219,7 @@ function Zh(a,b){var c=a-b|0;Oe(this,a,b,c,this.sb|63,!0);this.A-=-1===this.D?-1 function ci(a,b){return b>>(this.F&this.O)&(1<<(this.H&31))-1&this.O}function di(a,b){if(-1===this.C){switch(this.Z&7){case 0:this.F=this.F&-256|a;break;case 1:this.H=this.H&-256|a;break;case 2:this.K=this.K&-256|a;break;case 3:this.G=this.G&-256|a;break;case 4:this.F=this.F&-65281|a<<8;break;case 5:this.H=this.H&-65281|a<<8;break;case 6:this.K=this.K&-65281|a<<8;break;case 7:this.G=this.G&-65281|a<<8}this.A-=this.B.ih}else this.D=this.C,kf(this,a),this.A-=this.B.hh;return b} function ei(a,b){if(-1===this.C){switch(this.Z&7){case 0:this.F=this.F&~this.O|a;break;case 1:this.H=this.H&~this.O|a;break;case 2:this.K=this.K&~this.O|a;break;case 3:this.G=this.G&~this.O|a;break;case 4:A(this,C(this)&~this.O|a);break;case 5:this.L=this.G&~this.O|a;break;case 6:this.J=this.J&~this.O|a;break;case 7:this.I=this.I&~this.O|a}this.A-=this.B.ih}else this.D=this.C,this.M&2||this.nb(this.Xa.Mb(this.ab,this.S),a),this.A-=this.B.hh;return b} function fi(a,b){a^=b;H(this,a,128);this.A-=-1===this.D?-1===this.C?this.B.Xb:this.B.vb:this.B.kc;return a}function gi(a,b){this.A-=-1===this.D?-1===this.C?this.B.Xb:this.B.vb:this.B.kc;return H(this,a^b,this.sb)&this.O}function hi(a,b){var c=a[1]-b[1];c||(c=a[0]-b[0]);return c}function ii(a){var b=a-1|0;Oe(this,a,1,b,this.sb|62,!0);this.A-=2;return a&~this.O|b&this.O} -function ji(a,b,c){c>>>=0;if(!c||c<=b>>>0)return!1;var d=0,e=1;c=[c>>>0,0];for(a=[a>>>0,b>>>0];0>>=0,b[1]++);e+=e}do 0<=hi(a,c)&&(b=a,f=c,b[0]-=f[0],b[1]-=f[1],0>b[0]&&(b[0]>>>=0,b[1]--),d+=e),b=c,b[0]>>>=1,b[1]&1&&(b[0]=(b[0]|2147483648)>>>0),b[1]>>>=1,e/=2;while(1<=e);this.za=d;this.Va=a[0];return!0}function ki(a){var b=a+1|0;Oe(this,a,1,b,this.sb|62);this.A-=2;return a&~this.O|b&this.O} +function ji(a,b,c){c>>>=0;if(!c||c<=b>>>0)return!1;var d=0,e=1,f=this.Ml;f[0]=c>>>0;f[1]=0;c=this.Ql;c[0]=a>>>0;for(c[1]=b>>>0;0>>=0,a[1]++),e+=e;do 0<=hi(c,f)&&(a=c,b=f,a[0]-=b[0],a[1]-=b[1],0>a[0]&&(a[0]>>>=0,a[1]--),d+=e),a=f,a[0]>>>=1,a[1]&1&&(a[0]=(a[0]|2147483648)>>>0),a[1]>>>=1,e/=2;while(1<=e);this.za=d;this.Va=c[0];return!0}function ki(a){var b=a+1|0;Oe(this,a,1,b,this.sb|62);this.A-=2;return a&~this.O|b&this.O} function vd(a){this.md=a;this.pa&-2147483648&&Lb(this)}function li(a){this.M|=1;this.bc.call(this,a);this.A-=-1===this.C?4:5}function Kh(a,b,c){if(c){16>>16-c)&65535;H(this,a,32768,d&32768)}return a}function Ph(a,b,c){if(c){var d=a<>>32-c;H(this,a,-2147483648,d&-2147483648)}return a}function Th(a,b,c){if(c){16>>c-1;a=(d>>>1|b<<16-c)&65535;H(this,a,32768,d&1)}return a} function Vh(a,b,c){if(c){var d=a>>>c-1;a=d>>>1|b<<32-c;H(this,a,-2147483648,d&1)}return a}function mi(){this.A-=-1===this.C?2:this.B.Bj;return 1}function ni(){var a=this.H&255;this.A-=(-1===this.C?this.B.Zg:this.B.Yg)+(a<>8} -l.save=function(){var a=new Ie(this);a.set(0,[this.C]);for(var b=[],c=0;c=wf&&(a.set(5,[this.F,this.L,this.K,this.pa,this.M,this.qa]),a.set(6,[this.G[7],this.G,this.W,this.A,this.na,this.X]));return a.data()}; l.restore=function(a){var b,c;b=a[0];Array.isArray(b[0])?this.C=b[0]:(this.C[0][0]=b[0],this.C[1][0]=b[1]&15,this.C[0][1]=b[2],this.C[1][1]=b[3]&15);Hj(this);b=a[1];for(c=0;c=f;f++){var g="pcjs-bitCell";f||(g+=" pcjs-bitCellLeft");d+='
'+f+"
\n"}e.innerHTML=d;Ok(a,b,c,!0)}function Pk(a,b,c){if(b=(a=U[a.aa|0])&&a[b])for(var d in b)if(a=b[d],a.fc&1<d.eb[0]&&(d.eb[0]=255,d.eb[1]--,0>d.eb[1]&&(d.eb[1]=255)));return e}function cl(a,b,c,d){a=a.D[b];c=a.Qb[c];c.eb[a.Cb]=c.ac[a.Cb]=d;a.Cb^=1} -function dl(a,b){a=a.D[b];b=a.zb|el;a.zb&=~fl;return b}function gl(a,b,c){a=a.D[b];b=c&3;a.zb=a.zb&~(16<>2].Qb[b&3],c,d,e)}function kl(a,b,c){b=a.D[b>>2].Qb[b&3];b.Bf&&b.og&&b.Qf?(c&&(b.done=c),b.Id||pl(a,b,!0)):c&&c(!0)} +function dl(a,b){a=a.D[b];b=a.zb|el;a.zb&=~fl;return b}function gl(a,b,c){a=a.D[b];b=c&3;a.zb=a.zb&~(16<>2].Qb[b&3],c,d,e)}function kl(a,b,c){b=a.D[b>>2].Qb[b&3];b.Bf&&b.og&&b.Qf?(c&&(b.done=c),b.Id||pl(a,b,!0)):c&&c(!0)} function pl(a,b,c){c&&(b.count=b.eb[1]<<8|b.eb[0],b.type=b.mode&ql,b.ni=b.Df=!1);for(var d=!1;0<=b.count&&(c=b.uf<<16|b.cb[1]<<8|b.cb[0],b.type==rl?(d=!0,function(c){b.og.call(b.Bf,b.Qf,-1,function(e,g){0>e&&(b.ni||(b.ni=!0),e=255);b.Id||a.ma.Dc(c,e);(d=g)&&setTimeout(function(){sl(b)||pl(a,b)},0)})}(c)):b.type==tl?(c=a.ma.Eb(c),0>b.og.call(b.Bf,b.Qf,c)&&(b.Df=!0)):b.type!=ul&&(b.Df=!0)),!d&&!sl(b););} function sl(a){if(!a.Df&&0<=--a.count&&(a.mode&vl?(a.cb[0]--,0>a.cb[0]&&(a.cb[0]=255,a.cb[1]--,0>a.cb[1]&&(a.cb[1]=255))):(a.cb[0]++,255f&&(d.fd=e,f=0);var g=Xl(a,b),h=Uc(a,b)-f;d.mode==Sl?(0>=h&&(h=0),h||(d.Lc=!0,d.Gd=!1,b||zf(a,Wl))):d.mode==sm?(d.Lc=1!=h,0>=h&&(h=g+h,0>=h&&(h=g),d.vc[0]=h&255,d.vc[1]=h>>8&255,d.fd=e,!b&&d.Lc&&zf(a,Wl))):d.mode==Vc&&(h-=f,0>=h&&(d.Lc=!d.Lc,h=g+h,0>=h&&(h=g),d.vc[0]=h&255,d.vc[1]=h>>8&255,d.fd=e,!b&&d.Lc&&zf(a,Wl)));d.eb[0]=h&255;d.eb[1]=h>>8&255;c&&(a.fd=0)}return d} function Tc(a,b){for(var c=0;c=wf){b=a.U.R.Wc;c=Qc(a.U,a.V);null==a.xa&&(a.na=Qc(a.U,a.V),a.Ha=1024,a.xa=Math.floor(a.U.R.Wc/a.Ha),Fk(a));c>=a.X&&(a.A[Ck]|=tm,a.A[Wc]&Xc&&(a.A[Ck]|=um,zf(a,vm)),a.X=c+a.xa);a.A[pk]==a.A[qk]&&a.A[rk]==a.A[sk]&&a.A[tk]==a.A[uk]&&(a.A[Ck]|=wm,a.A[Wc]&xm&&(a.A[Ck]|=um,zf(a,vm)));var d=c-a.na,e=Math.floor(d/b);if(e&&!(a.A[Wc]&ym)){for(;e--;)if(60<=++a.A[pk]&&(a.A[pk]=0,60<=++a.A[rk]&&(a.A[rk]=0,24<=++a.A[tk]))){a.A[tk]=0;a.A[vk]=a.A[vk]% -7+1;var f;f=a.A[yk];var g=ra[a.A[xk]-1];28==g&&(f%4||!(f%100)&&f%400||g++);f=g;++a.A[wk]>f&&(a.A[wk]=1,12<++a.A[xk]&&(a.A[xk]=1,a.A[yk]=(a.A[yk]+1)%100))}a.A[Ck]|=zm;a.A[Wc]&Am&&(a.A[Ck]|=um,zf(a,vm))}a.na=c-d%b}}l.rl=function(){var a=this.ua;this.ga&Fm&&(this.J&Gm?a=this.C[0][1]:this.B&&(a=Hm(this.B)));return a};l.Jm=function(a,b){this.ua=b};l.sl=function(){return this.J};l.Km=function(a,b){Im(this,b)}; -function Im(a,b){var c=!!(b&Jm),d=!!(a.J&Jm);a.J=b;a.B&&Km(a.B,!(b&Gm),!!(b&pm));c!=d&&Hk(a,c)}l.tl=function(){var a=0,a=(this.aa|0)==rj?this.J&nm?a|this.C[1][1]&Lm:a|this.C[1][1]>>4&1:this.J&Mm?a|this.C[0][1]>>4:a|this.C[0][1]&15;this.J&mm&&Ql(this,Yl).Lc&&(a=this.J&Jm?a|Nm:a|Om);return a};l.Lm=function(a,b){this.Ba=b};l.ul=function(){return this.ga};l.Mm=function(a,b){this.ga=b};l.Dk=function(){var a=this.B?Hm(this.B):0;this.Z&=~Pm;return a};l.Vl=function(){};l.Ck=function(){return this.J}; -l.Ul=function(a,b){Im(this,b)};l.Ek=function(){return this.Z};l.Fk=function(){var a=this.pa;this.F&=~(Pm|Qm);this.B&&Rm(this.B);return a};l.Xl=function(a,b){if(this.F&Sm)switch(this.L){case Tm:Um(this,b);break;case Vm:Wm(this,b);break;default:if(Um(this,this.K&~Qj),this.B){a=this.B;var c=b,d=-1;switch(a.D||c){case Xm:d=Ym;a.Jb=[];Zm(a,$m);break;case an:a.D&&(c=0);Zm(a,Ym);a.D=c;break;case bn:a.D&&(c=0),Zm(a,Ym),a.D=c}cn(this,d)}}this.L=b;this.F&=~Sm}; -l.Gk=function(){return this.J&~(dn|en)|(Qc(this.U)&64?en:0)};l.Yl=function(a,b){Im(this,b)};l.Hk=function(){var a=this.F&255;this.F&Qm&&(this.F|=Pm,this.F&=~Qm);return a}; -l.Wl=function(a,b){this.L=b;this.F|=Sm;a=0;this.L>=fn&&(a=this.L^15,this.L=fn);switch(this.L){case gn:cn(this,this.K);break;case hn:Um(this,this.K|Qj);break;case jn:Um(this,this.K&~Qj);this.B&&Rm(this.B);break;case kn:this.B&&(this.B.Jb=[]);Um(this,this.K|Qj);cn(this,ln);Wm(this,ak|bk);break;case mn:cn(this,nn);break;case on:cn(this,this.M);break;case pn:cn(this,this.qa);break;case qn:cn(this,this.K&Qj?0:rn);break;case fn:a&1&&de(this.U)}}; -function Um(a,b){a.K=b;a.F=a.F&~sn|b&tn;a.B&&Km(a.B,!!(b&un),!(b&Qj))}function cn(a,b,c){0<=b&&(a.pa=b,c?a.F|=Pm:(a.F&=~Pm,a.F|=Qm))}function Wm(a,b){a.qa=b;Gb(a.ma,!!(b&bk));b&ak||de(a.U)}function vn(a,b){a.aaf&&(a.A[wk]=1,12<++a.A[xk]&&(a.A[xk]=1,a.A[yk]=(a.A[yk]+1)%100))}a.A[Ck]|=zm;a.A[Wc]&Am&&(a.A[Ck]|=um,zf(a,vm))}a.na=c-d%b}}l.rl=function(){var a=this.ua;this.ga&Fm&&(this.J&Gm?a=this.C[0][1]:this.B&&(a=Hm(this.B)));return a};l.Lm=function(a,b){this.ua=b};l.sl=function(){return this.J};l.Mm=function(a,b){Im(this,b)}; +function Im(a,b){var c=!!(b&Jm),d=!!(a.J&Jm);a.J=b;a.B&&Km(a.B,!(b&Gm),!!(b&pm));c!=d&&Hk(a,c)}l.tl=function(){var a=0,a=(this.aa|0)==rj?this.J&nm?a|this.C[1][1]&Lm:a|this.C[1][1]>>4&1:this.J&Mm?a|this.C[0][1]>>4:a|this.C[0][1]&15;this.J&mm&&Ql(this,Yl).Lc&&(a=this.J&Jm?a|Nm:a|Om);return a};l.Nm=function(a,b){this.Ba=b};l.ul=function(){return this.ga};l.Om=function(a,b){this.ga=b};l.Dk=function(){var a=this.B?Hm(this.B):0;this.Z&=~Pm;return a};l.Xl=function(){};l.Ck=function(){return this.J}; +l.Wl=function(a,b){Im(this,b)};l.Ek=function(){return this.Z};l.Fk=function(){var a=this.pa;this.F&=~(Pm|Qm);this.B&&Rm(this.B);return a};l.Zl=function(a,b){if(this.F&Sm)switch(this.L){case Tm:Um(this,b);break;case Vm:Wm(this,b);break;default:if(Um(this,this.K&~Qj),this.B){a=this.B;var c=b,d=-1;switch(a.D||c){case Xm:d=Ym;a.Jb=[];Zm(a,$m);break;case an:a.D&&(c=0);Zm(a,Ym);a.D=c;break;case bn:a.D&&(c=0),Zm(a,Ym),a.D=c}cn(this,d)}}this.L=b;this.F&=~Sm}; +l.Gk=function(){return this.J&~(dn|en)|(Qc(this.U)&64?en:0)};l.$l=function(a,b){Im(this,b)};l.Hk=function(){var a=this.F&255;this.F&Qm&&(this.F|=Pm,this.F&=~Qm);return a}; +l.Yl=function(a,b){this.L=b;this.F|=Sm;a=0;this.L>=fn&&(a=this.L^15,this.L=fn);switch(this.L){case gn:cn(this,this.K);break;case hn:Um(this,this.K|Qj);break;case jn:Um(this,this.K&~Qj);this.B&&Rm(this.B);break;case kn:this.B&&(this.B.Jb=[]);Um(this,this.K|Qj);cn(this,ln);Wm(this,ak|bk);break;case mn:cn(this,nn);break;case on:cn(this,this.M);break;case pn:cn(this,this.qa);break;case qn:cn(this,this.K&Qj?0:rn);break;case fn:a&1&&de(this.U)}}; +function Um(a,b){a.K=b;a.F=a.F&~sn|b&tn;a.B&&Km(a.B,!!(b&un),!(b&Qj))}function cn(a,b,c){0<=b&&(a.pa=b,c?a.F|=Pm:(a.F&=~Pm,a.F|=Qm))}function Wm(a,b){a.qa=b;Gb(a.ma,!!(b&bk));b&ak||de(a.U)}function vn(a,b){a.aac?c=c?c:12:c=(c-=12)?c+128:140,d=!0);this.A[Wc]&Bn||(d&&128>4)+(d&15),e=!0);if(a==tk||a==uk)e&&23=d?d=12==d?0:d:(d-=116,d=24==d?12:d))}}else d=b;this.A[a]=d;a==Wc&&c&Xc&&b&Xc&&Fk(this)};l.Gj=function(a,b){this.ba=b};l.wm=function(){};l.xm=function(){this.Ud&&sf(this.Ud)}; +l.om=function(a,b){a=this.W&An;var c=b^this.A[a],d;if(a<=Dk){if(d=b,a>4)+(d&15),e=!0);if(a==tk||a==uk)e&&23=d?d=12==d?0:d:(d-=116,d=24==d?12:d))}}else d=b;this.A[a]=d;a==Wc&&c&Xc&&b&Xc&&Fk(this)};l.Gj=function(a,b){this.ba=b};l.ym=function(){};l.zm=function(){this.Ud&&sf(this.Ud)}; function Hk(a,b){if(a.la)try{void 0!==b?a.Ga=b:b=!!(a.Ga&&a.U&&a.U.ca.Ib);var c=Math.round(vj/Xl(a,Yl));if(20>c||2E4>>4,0,this.F,this.C,this.H),delete this.H);return!0};En.prototype.Nb=function(){return!0}; @@ -480,13 +480,13 @@ vn(e.V,f)):e.Jb.length==No&&e.Jb.push(Oo));d=!0}return d}var lo=["US83","US84"," var fo={TAB:1009,ESC:1027,F1:1112,F2:1113,F3:1114,F4:1115,F5:1116,F6:1117,F7:1118,F8:1119,F9:1120,F10:1121,LEFT:1037,UP:1038,RIGHT:1039,DOWN:1040,SYSREQ:4027,CTRL_C:Po,CTRL_BREAK:Ao,CTRL_ALT_DEL:4046,CTRL_ALT_INS:4045,CTRL_ALT_ENTER:4013},ho={esc:1027,1:n["1"],2:n["2"],3:n["3"],4:n["4"],5:n["5"],6:n["6"],7:n["7"],8:n["8"],9:n["9"],0:n["0"],"-":n["-"],"=":n["="],bs:1008,tab:1009,q:n.Q,w:n.Qh,e:n.E,r:n.Lh,t:n.Nh,y:n.Sh,u:n.Oh,i:n.Dh,o:n.Jh,p:n.Kh,"[":n["["],"]":n["]"],enter:13,ctrl:1017,a:n.Bd,s:n.Mh, d:n.zh,f:n.Ah,g:n.Bh,h:n.Ch,j:n.Eh,k:n.Fh,l:n.Gh,";":n[";"],quote:n["'"],"`":n["`"],shift:1016,"\\":n["\\"],z:n.sf,x:n.Rh,c:n.xh,v:n.Ph,b:n.wh,n:n.Ih,m:n.Hh,",":n[","],".":n["."],"/":n["/"],"right-shift":3016,prtsc:1044,alt:1018,space:1032,"caps-lock":bo,f1:1112,f2:1113,f3:1114,f4:1115,f5:1116,f6:1117,f7:1118,f8:1119,f9:1120,f10:1121,"num-lock":co,"scroll-lock":eo,"num-home":1036,"num-up":1038,"num-pgup":1033,"num-sub":1109,"num-left":1037,"num-center":1101,"num-right":1039,"num-add":1107,"num-end":1035, "num-down":1040,"num-pgdn":1034,"num-ins":1045,"num-del":1046,sysreq:84},ro={"caps-lock":xo,"num-lock":1024,"scroll-lock":2048},V={1027:1};V[n["1"]]=2;V[n["!"]]=2|W<<8;V[n["2"]]=3;V[n["@"]]=3|W<<8;V[n["3"]]=4;V[n["#"]]=4|W<<8;V[n["4"]]=5;V[n.$]=5|W<<8;V[n["5"]]=6;V[n["%"]]=6|W<<8;V[n["6"]]=7;V[n["^"]]=7|W<<8;V[n["7"]]=8;V[n["&"]]=8|W<<8;V[n["8"]]=9;V[n["*"]]=9|W<<8;V[n["9"]]=10;V[n["("]]=10|W<<8;V[n["0"]]=11;V[n[")"]]=11|W<<8;V[n["-"]]=12;V[n._]=12|W<<8;V[n["="]]=13;V[n["+"]]=13|W<<8;V[1008]=Eo; -V[1009]=15;V[n.q]=16;V[n.Q]=16|W<<8;V[n.sn]=17;V[n.Qh]=17|W<<8;V[n.e]=18;V[n.E]=18|W<<8;V[n.r]=19;V[n.Lh]=19|W<<8;V[n.t]=20;V[n.Nh]=20|W<<8;V[n.y]=21;V[n.Sh]=21|W<<8;V[n.qn]=22;V[n.Oh]=22|W<<8;V[n.yk]=23;V[n.Dh]=23|W<<8;V[n.Tl]=24;V[n.Jh]=24|W<<8;V[n.p]=25;V[n.Kh]=25|W<<8;V[n["["]]=26;V[n["{"]]=26|W<<8;V[n["]"]]=27;V[n["}"]]=27|W<<8;V[13]=28;V[1017]=Jo;V[n.Cd]=30;V[n.Bd]=30|W<<8;V[n.nn]=31;V[n.Mh]=31|W<<8;V[n.d]=32;V[n.zh]=32|W<<8;V[n.vk]=33;V[n.Ah]=33|W<<8;V[n.wk]=34;V[n.Bh]=34|W<<8;V[n.xk]=35; -V[n.Ch]=35|W<<8;V[n.Jl]=36;V[n.Eh]=36|W<<8;V[n.k]=37;V[n.Fh]=37|W<<8;V[n.Ll]=38;V[n.Gh]=38|W<<8;V[n[";"]]=39;V[n[":"]]=39|W<<8;V[n["'"]]=40;V[n['"']]=40|W<<8;V[n["`"]]=41;V[n["~"]]=41|W<<8;V[1016]=W;V[n["\\"]]=43;V[n["|"]]=43|W<<8;V[n.z]=44;V[n.sf]=44|W<<8;V[n.x]=45;V[n.Rh]=45|W<<8;V[n.kk]=46;V[n.xh]=46|W<<8;V[n.rn]=47;V[n.Ph]=47|W<<8;V[n.jk]=48;V[n.wh]=48|W<<8;V[n.n]=49;V[n.Ih]=49|W<<8;V[n.Ol]=50;V[n.Hh]=50|W<<8;V[n[","]]=51;V[n["<"]]=51|W<<8;V[n["."]]=52;V[n[">"]]=52|W<<8;V[n["/"]]=53; +V[1009]=15;V[n.q]=16;V[n.Q]=16|W<<8;V[n.un]=17;V[n.Qh]=17|W<<8;V[n.e]=18;V[n.E]=18|W<<8;V[n.r]=19;V[n.Lh]=19|W<<8;V[n.t]=20;V[n.Nh]=20|W<<8;V[n.y]=21;V[n.Sh]=21|W<<8;V[n.sn]=22;V[n.Oh]=22|W<<8;V[n.yk]=23;V[n.Dh]=23|W<<8;V[n.Vl]=24;V[n.Jh]=24|W<<8;V[n.p]=25;V[n.Kh]=25|W<<8;V[n["["]]=26;V[n["{"]]=26|W<<8;V[n["]"]]=27;V[n["}"]]=27|W<<8;V[13]=28;V[1017]=Jo;V[n.Cd]=30;V[n.Bd]=30|W<<8;V[n.pn]=31;V[n.Mh]=31|W<<8;V[n.d]=32;V[n.zh]=32|W<<8;V[n.vk]=33;V[n.Ah]=33|W<<8;V[n.wk]=34;V[n.Bh]=34|W<<8;V[n.xk]=35; +V[n.Ch]=35|W<<8;V[n.Jl]=36;V[n.Eh]=36|W<<8;V[n.k]=37;V[n.Fh]=37|W<<8;V[n.Ll]=38;V[n.Gh]=38|W<<8;V[n[";"]]=39;V[n[":"]]=39|W<<8;V[n["'"]]=40;V[n['"']]=40|W<<8;V[n["`"]]=41;V[n["~"]]=41|W<<8;V[1016]=W;V[n["\\"]]=43;V[n["|"]]=43|W<<8;V[n.z]=44;V[n.sf]=44|W<<8;V[n.x]=45;V[n.Rh]=45|W<<8;V[n.kk]=46;V[n.xh]=46|W<<8;V[n.tn]=47;V[n.Ph]=47|W<<8;V[n.jk]=48;V[n.wh]=48|W<<8;V[n.n]=49;V[n.Ih]=49|W<<8;V[n.Pl]=50;V[n.Hh]=50|W<<8;V[n[","]]=51;V[n["<"]]=51|W<<8;V[n["."]]=52;V[n[">"]]=52|W<<8;V[n["/"]]=53; V[n["?"]]=53|W<<8;V[3016]=54;V[1044]=55;V[1018]=Lo;V[1032]=57;V[bo]=58;V[1112]=59;V[1113]=60;V[1114]=61;V[1115]=62;V[1116]=63;V[1117]=64;V[1118]=65;V[1119]=66;V[1120]=67;V[1121]=68;V[co]=69;V[eo]=70;V[1036]=71;V[1038]=72;V[1033]=73;V[1109]=74;V[1037]=75;V[1101]=76;V[1039]=77;V[1107]=78;V[1035]=79;V[1040]=80;V[1034]=81;V[1045]=82;V[1046]=Fo;V[4027]=84;V[1122]=87;V[1123]=88;V[1091]=91;V[1093]=93;V[1224]=91;V[Po]=46|Jo<<8;V[Ao]=70|Jo<<8;V[4046]=Fo|Jo<<8|Lo<<16;V[4045]=82|Jo<<8|Lo<<16; V[4013]=28|Jo<<8|Lo<<16;var Xm=255,an=243,bn=237,$m=170,Ym=250,Oo=255,No=20;Ea(function(){for(var a=Wa(document,"pcx86","keyboard"),b=0;bc.length)c=[!1,0,null,null,0,Array(b>2,32768));this.Ub=c[0];this.Cc=c[1];this.df=c[2];this.Y=c[3];this.ec=c[4]&255;this.Uf=c[4]>>8&255;this.Qa=c[5];this.tg=So;if(b>=Hn){this.tg=To;(b=c[6])||(b=[!1,0,Array(Uo),0,f==Wj?0:Vo,0,0,Array(Wo),0,0,0,Array(Xo),0,[this.Ya,this.Ab,this.Uc], Array(this.Uc>>2),Yo|Zo|$o|ap|bp,0,-1,0,-1,0,-1,0,0,0,0,cp,dp,0,0,ep,Array(fp)]);this.He=b[0];this.Nd=b[1];this.rc=b[2];this.nh=b[3];this.ef=b[4];this.Xf=b[5];this.Qd=b[6];this.Pd=b[7];this.Oj=b[8];this.Pj=b[9];this.Od=b[10];this.jd=b[11];this.mb=b[12];d=b[13];"number"==typeof d&&(d=[this.Ya,this.Ab,d]);this.Ya=d[0];this.Ab=d[1];d=this.Uc>>2;if((this.pd=b[14])&&this.pd.length=Hn){var c=[];c[0]=a.He;c[1]=a.Nd;c[2]=a.rc;c[3]=a.nh;c[4]=a.ef;c[5]=a.Xf;c[6]=a.Qd;c[7]=a.Pd;c[8]=a.Oj;c[9]=a.Pj;c[10]=a.Od;c[11]=a.jd;c[12]=a.mb;c[13]=[a.Ya,a.Ab,a.Uc];var d;if(d=a.pd){var e=0,f=[];if(void 0!==d[0])for(var g=0;2>g;g++)for(var h=g;h>1;f[e++]=k;h=m}f.length=Hn){var d=0,e=0,f=0;switch(b){case np:d=op;a.Ma==Jn&&(e=pp);break;case qp:a.Ma==Hn&&(d=rp);break;case sp:d=tp;a.Ma==Jn&&(e=up);break;case vp:d=wp;a.Ma==Jn&&(e=xp);break;case yp:d=zp;a.Ma==Jn&&(f=Ap);break;case Bp:d=Cp,a.Ma==Jn&&(f=Dp)}d&&(c|=a.Qa[Ep]&d?256:0,c|=a.Qa[Ep]&e?512:0,c|=a.Qa[Fp]&f?512:0)}return c} @@ -537,28 +537,28 @@ function Zp(a,b){if(a.ca.Pb){var c=!1,d=a.C;d&&(d!==a.A?d.Cc&8&&(c=!0):d.Nd&32&& q,f++),c+=2,d++;a.la=!0;f&&a.Ba&&a.J.drawImage(a.za,0,0,a.$a,a.ab,a.ic,a.jc,a.Kb,a.Tb);Bq(a)}}else if(a.Db){var g=k,w,k=c,d=a.ua=0,f=a.xb,e=16==f?65536:196608,h=16==f?1:2;b=pq(a,h);for(var q=m=0,y=a.F,z=0,B=a.H,S=0;k>8|(w&255)<<8;var X=e,ca=16;m>=h))>>(ca-=h);Aq(a.Ha,m++,q,b[zb])}m>z&&(z=m);q=S&&(S=q+1)}k+=2;d++;if(m>=a.F){m=0;q+=2;if(q>a.H)break;q==a.H&&(q=1,k=c+a.Db)}}a.la=!0; ya.F?a.Ka-a.F-w>>3:0;c>=8;b>y&&(y=b);m=B&&(B=m+1)}c+=S;if(b>=a.F){b=0;if(++m>a.H)break;c+=X}}w||(a.la=!0);qa.F?a.Ka-a.F-B>>3:0;cX&&(ca=X)):(w<<=B,ca-=B,a.la=!1):(a.la&&w===a.L[d]?(h+=ca,ca=0):a.L[d]=w,d++);if(ca){hq&&(q=h);b=z&&(z=b+1)}if(h>=a.F){h=0;if(++b>a.H)break;c+=S}}B||(a.la=!0);ma&&(b.Ag=a,a=-a|0);a%b.wg>b.Ql&&(c|=1);a%b.zg>b.Sl&&(c|=9);b.kh=a/b.zg|0;return c}l.nl=function(){var a=this.W,b;a.Ub&&(b=a.ec);return b};l.Gm=function(a,b){a=this.W;a.Uf=a.ec;a.ec=b&31};l.ml=function(){return $q(this.W)};l.Fm=function(a,b){ar(this,this.W,b)};l.ol=function(){return this.W.Cc};l.Hm=function(a,b){this.W.Cc=b;nq(this,!1)};l.pl=function(){return br(this,this.W)};l.Fj=function(a,b){this.A.Xf=this.A.Xf&-4|b&3}; +0,0,a.F,a.H,0,0,a.X,a.ja))}}}}function Yq(a,b){var c=0;a=Qc(a.U)-b.Ag;0>a&&(b.Ag=a,a=-a|0);a%b.wg>b.Sl&&(c|=1);a%b.zg>b.Ul&&(c|=9);b.kh=a/b.zg|0;return c}l.nl=function(){var a=this.W,b;a.Ub&&(b=a.ec);return b};l.Im=function(a,b){a=this.W;a.Uf=a.ec;a.ec=b&31};l.ml=function(){return $q(this.W)};l.Hm=function(a,b){ar(this,this.W,b)};l.ol=function(){return this.W.Cc};l.Jm=function(a,b){this.W.Cc=b;nq(this,!1)};l.pl=function(){return br(this,this.W)};l.Fj=function(a,b){this.A.Xf=this.A.Xf&-4|b&3}; l.Mk=function(){return this.A.Nd};l.hk=function(){return this.A.rc[this.A.Nd&31]};l.Ej=function(a,b){a=this.A;var c=a.Nd&32;if(a.He){a.He=!1;var d=a.Nd&31;if(16<=d||!c)if(cr||a.rc[d]!==b)a.rc[d]=b,Vq(this,!1)}else a.Nd=b,a.He=!0,b&32&&!c&&fq(this,!0)&&Zp(this,!0),b=(a.Qa[12]<<8)+a.Qa[13]|0,a.Zc!=b&&(a.Zc=b,Vq(this)),a.te=0}; -l.zl=function(){var a=0;if(this.Ma==Hn)a=3-((this.A.ef&12)>>2),a=(this.qb&1<>this.A.sc&63;this.A.sc+=6;12>2),a=(this.qb&1<>this.A.sc&63;this.A.sc+=6;12Missing <canvas> support. Please try a newer web browser.";break}e.setAttribute("class","pcjs-canvas");e.setAttribute("width",d.screenWidth);e.setAttribute("height",d.screenHeight);e.style.backgroundColor=d.screenColor;e.style.height="auto";0<=(window?window.navigator.userAgent:"").indexOf("MSIE")&&(c.onresize=function(a,b,c,d){return function(){b.style.height= (a.clientWidth*d/c|0)+"px"}}(c,e,d.screenWidth,d.screenHeight),c.onresize());var f=+(d.aspect||Aa("aspect"));f&&.3<=f&&3.33>=f&&(Da("onresize",function(a,b,c){return function(){b.style.height=(a.clientWidth/c|0)+"px"}}(c,e,f)),window.onresize());c.appendChild(e);f=document.createElement("textarea");za("iOS")&&(f.setAttribute("autocapitalize","off"),f.setAttribute("autocorrect","off"),f.style.fontSize="16px");c.appendChild(f);var g=e.getContext("2d"),d=new Y(d,e,g,f,c);Va(d,c)}}); function dr(a){t.call(this,"ParallelPort",a);this.G=a.adapter;switch(this.G){case 1:this.D=956;this.C=7;break;case 2:this.D=888;this.C=7;break;case 3:this.D=632;this.C=5;break;default:r("Unrecognized parallel adapter #"+this.G);return}this.A=this.B=null;a=a.binding;"console"==a?this.B="":Ua(this,a,er)}ba(dr,t);l=dr.prototype;l.wb=function(a,b,c){switch(b){case er:return this.ia[b]=this.A=c,!0}return!1}; l.hc=function(a,b,c,d){this.ma=b;this.U=c;this.Ea=d;this.V=nb(a,"ChipSet");cc(b,this,fr,this.D);ec(b,this,gr,this.D);eb(this)};l.Ob=function(a,b){if(!b)if(!a||!this.restore)this.reset();else if(!this.restore(a))return!1;return!0};l.Nb=function(a){return a?this.save():!0};l.reset=function(){hr(this)};l.save=function(){var a=new Ie(this),b=0,c=[];c[b++]=this.F;c[b++]=this.zb;c[b]=this.Fe;a.set(0,c);return a.data()};l.restore=function(a){return hr(this,a[0])}; -function hr(a,b){var c=0;b||(b=[0,0,0]);a.F=b[c++];a.zb=b[c++];a.Fe=b[c];return!0}l.al=function(){return this.F};l.yl=function(){return this.zb};l.Xk=function(){return this.Fe};l.sm=function(a,b){this.F=b;this.zb|=ir;a=!1;this.A&&(8==b?this.A.value=this.A.value.slice(0,-1):(this.A.value+=String.fromCharCode(b),this.A.scrollTop=this.A.scrollHeight),a=!0);if(null!=this.B){if(10==b||1024<=this.B.length)this.lc(this.B),this.B="";10!=b&&(this.B+=String.fromCharCode(b));a=!0}a&&(this.zb&=~ir);jr(this)}; -l.nm=function(a,b){this.Fe=b;jr(this)};function jr(a){a.V&&a.C&&(a.Fe&kr&&!(a.zb&ir)?zf(a.V,a.C):xf(a.V,a.C))}var er="buffer",ir=64,kr=16,fr={0:dr.prototype.al,1:dr.prototype.yl,2:dr.prototype.Xk},gr={0:dr.prototype.sm,2:dr.prototype.nm};Ea(function(){for(var a=Wa(document,"pcx86","parallel"),b=0;b=b)a.preventDefault&&a.preventDefault(),64>8:this.M};l.il=function(){return this.F};l.jl=function(){return this.I};l.ll=function(){return this.X};l.kl=function(){return this.B};l.ql=function(){var a=this.A;this.A&=~(xr|yr);return a}; -l.Pm=function(a,b){if(this.I&Br)this.K=this.K&-256|b;else{this.ja=b;this.B&=~(ur|vr);a=!1;this.Z&&this.Z.call(this.C,b)&&(a=!0);if(this.D){if(13==b)this.J=0;else if(8==b)this.D.value=this.D.value.slice(0,-1),0":String.fromCharCode(b);a=c.length;32>b&&1==a&&(a=0);9==b&&(b=this.na||8,a=b-this.J%b,this.na&&(c=" ".slice(0,a)));this.la&&!this.J&&a&&(c=String.fromCharCode(this.la)+c);this.D.value+=c; -this.D.scrollTop=this.D.scrollHeight;this.J+=a}a=!0}else if(null!=this.G){if(10==b||1024<=this.G.length)this.lc(this.G),this.G="";10!=b&&(this.G+=String.fromCharCode(b));a=!0}a&&(this.B=this.B|ur|vr)}};l.Cm=function(a,b){this.I&Br?this.K=this.K&255|b<<8:this.M=b};l.Dm=function(a,b){this.I=b};l.Em=function(a,b){a=b^this.X;this.X=b;a&(Cr|Dr)&&this.W&&(a=0,this.N?(a|=b&Dr?32:0,a|=b&Cr?320:0):(a|=b&Dr?16:0,a|=b&Cr?1048576:0),this.W.call(this.C,a))}; -function zr(a){var b=-1;a.B&Ar&&a.M&Er?b=Fr:a.A&(xr|yr)&&a.M&Gr&&(b=Hr);0<=b?(a.F&=~(tr|Ir),a.F|=b,a.V&&a.L&&zf(a.V,a.L,100)):(a.F|=tr,a.V&&a.L&&xf(a.V,a.L))}var or="buffer",sr=384,Er=1,Gr=8,tr=1,Fr=4,Hr=0,Ir=6,Br=128,Cr=1,Dr=2,Ar=1,ur=32,vr=64,xr=1,yr=2,mr=16,nr=32,pr={0:lr.prototype.vl,1:lr.prototype.hl,2:lr.prototype.il,3:lr.prototype.jl,4:lr.prototype.ll,5:lr.prototype.kl,6:lr.prototype.ql},qr={0:lr.prototype.Pm,1:lr.prototype.Cm,3:lr.prototype.Dm,4:lr.prototype.Em}; +l.Rm=function(a,b){if(this.I&Br)this.K=this.K&-256|b;else{this.ja=b;this.B&=~(ur|vr);a=!1;this.Z&&this.Z.call(this.C,b)&&(a=!0);if(this.D){if(13==b)this.J=0;else if(8==b)this.D.value=this.D.value.slice(0,-1),0":String.fromCharCode(b);a=c.length;32>b&&1==a&&(a=0);9==b&&(b=this.na||8,a=b-this.J%b,this.na&&(c=" ".slice(0,a)));this.la&&!this.J&&a&&(c=String.fromCharCode(this.la)+c);this.D.value+=c; +this.D.scrollTop=this.D.scrollHeight;this.J+=a}a=!0}else if(null!=this.G){if(10==b||1024<=this.G.length)this.lc(this.G),this.G="";10!=b&&(this.G+=String.fromCharCode(b));a=!0}a&&(this.B=this.B|ur|vr)}};l.Em=function(a,b){this.I&Br?this.K=this.K&255|b<<8:this.M=b};l.Fm=function(a,b){this.I=b};l.Gm=function(a,b){a=b^this.X;this.X=b;a&(Cr|Dr)&&this.W&&(a=0,this.N?(a|=b&Dr?32:0,a|=b&Cr?320:0):(a|=b&Dr?16:0,a|=b&Cr?1048576:0),this.W.call(this.C,a))}; +function zr(a){var b=-1;a.B&Ar&&a.M&Er?b=Fr:a.A&(xr|yr)&&a.M&Gr&&(b=Hr);0<=b?(a.F&=~(tr|Ir),a.F|=b,a.V&&a.L&&zf(a.V,a.L,100)):(a.F|=tr,a.V&&a.L&&xf(a.V,a.L))}var or="buffer",sr=384,Er=1,Gr=8,tr=1,Fr=4,Hr=0,Ir=6,Br=128,Cr=1,Dr=2,Ar=1,ur=32,vr=64,xr=1,yr=2,mr=16,nr=32,pr={0:lr.prototype.vl,1:lr.prototype.hl,2:lr.prototype.il,3:lr.prototype.jl,4:lr.prototype.ll,5:lr.prototype.kl,6:lr.prototype.ql},qr={0:lr.prototype.Rm,1:lr.prototype.Em,3:lr.prototype.Fm,4:lr.prototype.Gm}; Ea(function(){for(var a=Wa(document,"pcx86","serial"),b=0;ba.fb||f[1]>a.gb)&&(this.Aa('Diskette "'+c+'" too large for drive '+String.fromCharCode(65+a.La)),b=null);b?(a.sa=b,a.Qj=c,a.Rd=d,ys(this,c,d,b),f=b.info(),this.I|=Bs,this.Aa('Mounted diskette "'+c+'" in drive '+String.fromCharCode(65+a.La),a.Fd||e),a.Lf=f[0],a.Qe=f[1],a.Re=f[2],this.oa&&Rc(this.oa)):a.le=!1;a.Fd&&(a.Fd=!1,--this.J||eb(this));ls(this,a.La)}; function qs(a,b,c,d){if((a=a.ia.listDisks)&&a.options){for(var e=0;e=this.C&&(this.Y&=~(Es|Fs),this.D=this.C=0);return a}; -l.um=function(a,b){this.C=Hs[a].dd){b=!1;this.D=0;a=Is(this);var c,d,e,f,g,h=a&Gs;switch(h){case Js:Is(this);Is(this);Ks(this);break;case Ls:d=Is(this);this.La=d&3;c=this.A[this.La];Ks(this);Ms(this,(c.hb&Ns)>>>24);break;case Os:case Ps:d=Is(this);b=d>>2&1;this.La=d&3;c=this.A[this.La];c.Sa=b;d=c.ub=Is(this);e=Is(this);f=c.kb=Is(this);g=Is(this);c.tb=128<=this.C&&(this.Y&=~(Es|Fs),this.D=this.C=0);return a}; +l.wm=function(a,b){this.C=Hs[a].dd){b=!1;this.D=0;a=Is(this);var c,d,e,f,g,h=a&Gs;switch(h){case Js:Is(this);Is(this);Ks(this);break;case Ls:d=Is(this);this.La=d&3;c=this.A[this.La];Ks(this);Ms(this,(c.hb&Ns)>>>24);break;case Os:case Ps:d=Is(this);b=d>>2&1;this.La=d&3;c=this.A[this.La];c.Sa=b;d=c.ub=Is(this);e=Is(this);f=c.kb=Is(this);g=Is(this);c.tb=128<>2&1;this.La=d&3;c=this.A[this.La];d=c.ub;e=c.Sa=b;f=c.kb= 1;g=0;c.hb=Ss;c.sa&&(c.Ra=c.sa.seek(c.ub,c.Sa,c.kb))?g=c.Ra.length>>8:c.hb=Qs|Rs;Us(this,c,a,b,d,e,f,g);b=!0;break;case at:d=Is(this);b=d>>2&1;this.La=d&3;c=this.A[this.La];d=c.ub;e=c.Sa=b;f=1;g=Is(this);c.tb=128<>2&1,d=Is(this),c.ub+= -d-c.rd,0>c.ub&&(c.ub=0),c.ub>=c.fb&&(c.ub=c.fb-1),c.rd=d,c.hb=Ws,c.ub||(c.hb|=Xs),Ks(this),b=!0}0>>8);Ms(a,(b.hb&dt)>>>16);var k=0;if(e!=b.ub||f!=b.Sa)k=g=1;c&et&&(f^=k,d||(k=0));Ms(a,e+k);Ms(a,f);Ms(a,g);Ms(a,h)}function Is(a){var b=a.G[a.D];a.D++;return b} +d-c.rd,0>c.ub&&(c.ub=0),c.ub>=c.fb&&(c.ub=c.fb-1),c.rd=d,c.hb=Ws,c.ub||(c.hb|=Xs),Ks(this),b=!0}0>>8);Ms(a,(b.hb&dt)>>>16);var k=0;if(e!=b.ub||f!=b.Sa)k=g=1;c&et&&(f^=k,d||(k=0));Ms(a,e+k);Ms(a,f);Ms(a,g);Ms(a,h)}function Is(a){var b=a.G[a.D];a.D++;return b} function Ks(a){a.D=a.C=0}function Ms(a,b){a.G[a.C++]=b}l.$j=function(a,b,c){void 0===b||0>b?this.ue(a,c):c(-1,!1)};l.ak=function(a,b){return void 0!==b&&0<=b?ft(a,b):-1};l.qk=function(a,b){if(void 0!==b&&0<=b)a:if(a.hb)a=-1;else{a.Fc[a.ge++]=b;if(a.ge==a.Fc.length){a.ub=a.Fc[0];a.Sa=a.Fc[1];a.kb=a.Fc[2];a.tb=128<ft(a,a.Zh)){a=-1;break a}a.xf++}a.xf>=a.sd&&(b=-1);a=b}else a=-1;return a}; l.ue=function(a,b){var c=-1,d=null,e=0;if(!a.hb&&a.sa){do{if(a.Ra&&(e=a.Oa,0<=(c=a.sa.read(a.Ra,a.Oa++)))){d=a.Ra;break}a.Ra=a.sa.seek(a.ub,a.Sa,a.kb);if(!a.Ra){a.hb=gt|Rs;break}a.Oa=0;ht(a)}while(1)}b(c,!1,d,e)};function ft(a,b){if(a.hb||!a.sa)return-1;do{if(a.Ra&&a.sa.write(a.Ra,a.Oa++,b))break;a.Ra=a.sa.seek(a.ub,a.Sa,a.kb);if(!a.Ra){a.hb=it|Rs;b=-1;break}a.Oa=0;ht(a)}while(1);return b}function ht(a){a.kb++;a.kb>=a.Re+1&&(a.kb=1,a.Sa++,a.Sa>=a.Qe&&(a.Sa=0,a.ub++))} var ws="Floppy Drive",Cs=4,Ds=8,Fs=16,Es=64,us=128,Js=3,Ls=4,Os=5,Ps=6,Vs=7,Ys=8,$s=10,at=13,bt=15,Gs=31,et=128,Ss=0,Qs=8,Ws=32,Rs=64,vs=192,Zs=255,Ts=512,gt=1024,it=8192,ct=65280,dt=16711680,Xs=268435456,Ns=-16777216,Bs=128,zs=0;aa={}; -var Hs={3:{dd:3,td:0,name:aa.xo},4:{dd:2,td:1,name:aa.vo},5:{dd:9,td:7,name:aa.Bo},6:{dd:9,td:7,name:aa.ro},7:{dd:2,td:0,name:aa.to},8:{dd:1,td:2,name:aa.wo},10:{dd:2,td:7,name:aa.so},13:{dd:6,td:7,name:aa.oo},15:{dd:3,td:0,name:aa.uo}},os={1009:ks.prototype.cl,1012:ks.prototype.el,1013:ks.prototype.bl,1015:ks.prototype.dl},ps={1010:ks.prototype.vm,1013:ks.prototype.um,1015:ks.prototype.tm}; +var Hs={3:{dd:3,td:0,name:aa.zo},4:{dd:2,td:1,name:aa.xo},5:{dd:9,td:7,name:aa.Do},6:{dd:9,td:7,name:aa.to},7:{dd:2,td:0,name:aa.vo},8:{dd:1,td:2,name:aa.yo},10:{dd:2,td:7,name:aa.uo},13:{dd:6,td:7,name:aa.qo},15:{dd:3,td:0,name:aa.wo}},os={1009:ks.prototype.cl,1012:ks.prototype.el,1013:ks.prototype.bl,1015:ks.prototype.dl},ps={1010:ks.prototype.xm,1013:ks.prototype.wm,1015:ks.prototype.vm}; Ea(function(){for(var a=Wa(document,"pcx86","fdc"),b=0;b=this.C&&(this.D=this.C=0,this.Y&=~(zt|Dt|Et));return a};l.Rm=function(a,b){this.C=a&&(this.Y|=zt,this.Y&=~Gt,Ht(this))};l.Fl=function(){var a=this.Y;this.D=a.B.lb?(a.Y=Jt,a.ue(a.B,function(b){0<=b?(Kt(a),a.V&&a.V.aa==Ej&&(a.Y=0),a.Y=a.Y|rt|Lt|Mt):(a.Y=Nt,a.H=Ot)},!1)):a.Y=rt|Lt));return b}l.dk=function(){return It(this)|It(this)<<8}; -function Pt(a,b){a.B&&a.B.tb>=a.B.lb&&(0>Qt(a.B,b)?(a.Y=Nt,a.H=Ot):(1==a.B.Oa||a.B.Oa==a.B.lb)&&1=a.B.lb&&(a.Y|=Mt)))}l.bm=function(a,b){Pt(this,b&255);Pt(this,b>>8&255)};l.Lk=function(){return this.H};l.gm=function(a,b){this.ta=b};l.Nk=function(){return this.I};l.em=function(a,b){this.I=b};l.Ok=function(){return this.ea};l.fm=function(a,b){this.ea=b};l.Jk=function(){return this.ba};l.am=function(a,b){this.ba=b};l.Ik=function(){return this.Z}; -l.$l=function(a,b){this.Z=b};l.Kk=function(){return this.N};l.cm=function(a,b){this.N=b;this.Y=this.A[this.N&Rt?1:0]?this.Y|rt|Lt:this.Y&~rt};l.Pk=function(){var a=this.Y;this.Y&rt&&(this.Y&=~Jt);return a};l.Zl=function(a,b){this.ga=b;this.V&&xf(this.V,14);St(this)};l.dm=function(a,b){this.K&Tt&&!(b&Tt)&&(this.H=Ut);this.K=b}; +l.El=function(){var a=0;this.D=this.C&&(this.D=this.C=0,this.Y&=~(zt|Dt|Et));return a};l.Tm=function(a,b){this.C=a&&(this.Y|=zt,this.Y&=~Gt,Ht(this))};l.Fl=function(){var a=this.Y;this.D=a.B.lb?(a.Y=Jt,a.ue(a.B,function(b){0<=b?(Kt(a),a.V&&a.V.aa==Ej&&(a.Y=0),a.Y=a.Y|rt|Lt|Mt):(a.Y=Nt,a.H=Ot)},!1)):a.Y=rt|Lt));return b}l.dk=function(){return It(this)|It(this)<<8}; +function Pt(a,b){a.B&&a.B.tb>=a.B.lb&&(0>Qt(a.B,b)?(a.Y=Nt,a.H=Ot):(1==a.B.Oa||a.B.Oa==a.B.lb)&&1=a.B.lb&&(a.Y|=Mt)))}l.dm=function(a,b){Pt(this,b&255);Pt(this,b>>8&255)};l.Lk=function(){return this.H};l.im=function(a,b){this.ta=b};l.Nk=function(){return this.I};l.gm=function(a,b){this.I=b};l.Ok=function(){return this.ea};l.hm=function(a,b){this.ea=b};l.Jk=function(){return this.ba};l.cm=function(a,b){this.ba=b};l.Ik=function(){return this.Z}; +l.bm=function(a,b){this.Z=b};l.Kk=function(){return this.N};l.em=function(a,b){this.N=b;this.Y=this.A[this.N&Rt?1:0]?this.Y|rt|Lt:this.Y&~rt};l.Pk=function(){var a=this.Y;this.Y&rt&&(this.Y&=~Jt);return a};l.am=function(a,b){this.ga=b;this.V&&xf(this.V,14);St(this)};l.fm=function(a,b){this.K&Tt&&!(b&Tt)&&(this.H=Ut);this.K=b}; function St(a){var b=!1,c=a.ga,d=a.N&Rt?1:0,e=a.N&Vt,f=a.ba|(a.Z&Wt)<<8,g=a.ea,h=a.I||256;a.La=-1;a.B=null;a.H=Xt;a.Y=rt|Lt;var k=a.A[d];k?(k.Ad=f,k.Sa=e,k.kb=g,k.tb=h*k.lb,c=c>=Yt?c:c&Zt,k.Ra=null,k.Oa=0,k.errorCode=0,a.La=d,a.B=k):c=-1;switch(c&Zt){case $t:b=!0;break;case au:a.Y=Jt;a.ue(k,function(b){0<=b&&a.V?(Kt(a),a.Y=rt|Lt|Mt):(a.Y=Nt,a.H=Ot)},!1);break;case bu:a.Y=Mt;break;case cu:b=!0;break;case du:b=!0;break;case Yt:a.H=Ut;b=!0;break;case eu:k.gb=e+1,k.Za=h,b=!0}b&&Kt(a)} function Kt(a){!a.V||a.K&fu||zf(a.V,14,120)} function Ht(a){a.D=0;var b=gu(a),c=gu(a),d=c&32,e=d>>5,f=c&31,g=gu(a),h=gu(a),k=g<<2&768|h,m=g&63,q=gu(a),y=gu(a),w=a.A[e];w&&(w.Ad=k,w.Sa=f,w.kb=m,w.tb=q*w.lb);switch(b){case hu:iu(a,w?w.errorCode:ju);ku(a,c);ku(a,g);ku(a,h);ku(a,lu|d);b=-1;break;case Ft:for(c=0;0<=(b=gu(a));)w&&c=a.Za+b&&(a.kb=b,a.Sa++,a.Sa>=a.gb&&(a.Sa=0,a.Ad++))}l.Hl=function(){var a=this.U.K&255;!(this.U.F>>8)&&128>8||!this.V)||(a=!(this.V.Zb[0].Tc&64));return a?!0:!1}; var ut="Hard Drive",xt=["XTC","ATC","COMPAQ"],vt=[{0:[306,2],1:[375,8],2:[306,6],3:[306,4]},{1:[306,4],2:[615,4],3:[615,6],4:[940,8],5:[940,6],6:[615,4],7:[462,8],8:[733,5],9:[900,15],10:[820,3],11:[855,5],12:[855,7],13:[306,8],14:[733,7],16:[612,4],17:[977,5],18:[977,7],19:[1024,7],20:[733,5],21:[733,7],22:[733,5],23:[306,4]},{1:[306,4],2:[615,4],3:[615,6],4:[1023,8],5:[940,6],6:[697,5],7:[462,8],8:[925,5],9:[900,15],10:[980,5],11:[925,7],12:[925,9],13:[612,8],14:[980,4],16:[612,4],17:[980,5],18:[966, 6],19:[1023,8],20:[733,5],21:[733,7],22:[524,4,40],23:[924,8],24:[966,14],25:[966,16],26:[1023,14],27:[832,6,33],28:[1222,15,34],29:[1240,7,34],30:[615,4,25],31:[615,8,25],32:[905,9,25],33:[832,8,33],34:[966,7,34],35:[966,8,34],36:[966,9,34],37:[966,5,34],38:[612,16,63],39:[1023,11,33],40:[1023,15,34],41:[1630,15,52],42:[1023,16,63],43:[805,4,26],44:[805,2,26],45:[748,8,33],46:[748,6,33],47:[966,5,25]}],nt=496,Ut=1,Xt=0,Ot=16,Wt=3,Vt=15,Rt=16,Nt=1,Mt=8,Lt=16,rt=64,Jt=128,$t=16,au=32,bu=48,cu=64,du= -112,Yt=144,eu=145,Zt=240,fu=2,Tt=4,lu=0,mu=2,pu=0,qu=1,hu=3,ru=5,su=8,uu=10,Ft=12,wu=15,nu=224,ou=228,tt=0,ju=4,yu=20,st=0,Gt=1,zt=2,Dt=4,Et=8,yt=32,kt={800:Z.prototype.El,801:Z.prototype.Fl,802:Z.prototype.Dl},jt={496:Z.prototype.dk,497:Z.prototype.Lk,498:Z.prototype.Nk,499:Z.prototype.Ok,500:Z.prototype.Jk,501:Z.prototype.Ik,502:Z.prototype.Kk,503:Z.prototype.Pk},mt={800:Z.prototype.Rm,801:Z.prototype.Um,802:Z.prototype.Tm,803:Z.prototype.Sm,807:Z.prototype.lh,811:Z.prototype.lh,815:Z.prototype.lh}, -lt={496:Z.prototype.bm,497:Z.prototype.gm,498:Z.prototype.em,499:Z.prototype.fm,500:Z.prototype.am,501:Z.prototype.$l,502:Z.prototype.cm,503:Z.prototype.Zl,1014:Z.prototype.dm};Ea(function(){for(var a=Wa(document,"pcx86","hdc"),b=0;b\nLicense: GPL version 3 or later ");for(b=0;b Date: Mon, 13 Feb 2017 11:46:58 -0800 Subject: [PATCH 04/29] Implemented 36-bit multiplication with support for (up to) 72-bit results --- modules/pcx86/lib/x86func.js | 3 + modules/shared/bin/int36 | 17 ++++-- modules/shared/lib/int36.js | 103 ++++++++++++++++++++++++++++++----- 3 files changed, 106 insertions(+), 17 deletions(-) diff --git a/modules/pcx86/lib/x86func.js b/modules/pcx86/lib/x86func.js index e91dcaeba..7345361b0 100644 --- a/modules/pcx86/lib/x86func.js +++ b/modules/pcx86/lib/x86func.js @@ -1773,6 +1773,9 @@ X86.fnMULb = function(dst, src) * * This sets regMDHi:regMDLo to the 64-bit result of dst * src, both of which are treated as unsigned. * + * The algorithm is based on the traditional "by hand" multiplication method, by treating the two inputs + * (dst and src) as two 2-digit numbers, where each digit is a base-65536 digit. + * * @this {X86CPU} * @param {number} dst (any 32-bit number, treated as unsigned) * @param {number} src (any 32-bit number, treated as unsigned) diff --git a/modules/shared/bin/int36 b/modules/shared/bin/int36 index 13ef785cf..412098589 100644 --- a/modules/shared/bin/int36 +++ b/modules/shared/bin/int36 @@ -44,7 +44,7 @@ var i36Reg = new Int36(); */ function dumpInt36(i36) { - return i36.toString() + " (" + i36.toString(8, true) + ")"; + return i36.toString() + " [" + i36.toString(8, true) + "]"; } /** @@ -93,7 +93,8 @@ function test(sCmd, fREPL) return false; } - console.log(sOp + (sNum1? (" " + dumpInt36(i36Op)) : "") + ": " + dumpInt36(i36Reg)); + console.log(sOp + " " + dumpInt36(i36Op)); + if (sOp != "set") console.log(" = " + dumpInt36(i36Reg)); return true; } @@ -115,12 +116,20 @@ var onCommand = function (cmd, context, filename, callback) }; test("set -34,359,738,368"); -test("add 1"); -test("sub 1"); test("sub 1"); test("add 1"); test("add 0"); +for (let i = 0; i <= 12; i++) { + test("set 34,000,000,000"); + test("mul " + Math.pow(8, i)); +} + +for (let i = 0; i <= 12; i++) { + test("set -34,000,000,000"); + test("mul " + Math.pow(8, i)); +} + repl.start({ prompt: "int36> ", input: process.stdin, diff --git a/modules/shared/lib/int36.js b/modules/shared/lib/int36.js index 1518fe414..8935fb199 100644 --- a/modules/shared/lib/int36.js +++ b/modules/shared/lib/int36.js @@ -98,6 +98,12 @@ class Int36 { this.value -= Int36.BIT36; } } + /* + * The 'extended' property stores an additional 36 bits of data after a multiplication, and any + * remainder after a division. It's strictly an output-only property for those operations, and + * its value does not affect the result of ANY operation. + */ + this.extended = 0; this.error = Int36.ERROR.NONE; } @@ -119,26 +125,49 @@ class Int36 { return result; } + /** + * octal(value) + * + * @param {number} value + * @return {string} + */ + static octal(value) + { + if (value < 0) value += Int36.BIT36; + return ("00000000000" + value.toString(8)).slice(-12); + } + /** * toString(radix, fUnsigned) * * @param {number} [radix] (default is 10) - * @param {boolean} [fUnsigned] (default is signed) + * @param {boolean} [fUnsigned] (default is signed for radix 10, unsigned for any other radix) */ toString(radix = 10, fUnsigned) { var s; var value = this.value; - if (fUnsigned) { - if (value < 0) { - value += Int36.BIT36; + var extended = this.extended; + if (radix == 8) { + s = Int36.octal(value); + if (extended) { + s = Int36.octal(extended) + ',' + s; } - if (radix == 8) { - s = "0o" + ("00000000000" + value.toString(8)).slice(-12); - if (DEBUG && this.error) s += " error 0x" + this.error.toString(16); + if (DEBUG && this.error) s += " error 0x" + this.error.toString(16); + return s; + } + if (radix != 10) fUnsigned = true; + if (fUnsigned || extended) { + if (value < 0) value += Int36.BIT36; + if (extended) { + if (fUnsigned) extended += Int36.BIT36; + /* + * TODO: Come up with a solution that won't overflow JavaScript's more limited precision. + */ + value = extended * Int36.BIT36 + value; } } - if (!s) s = value.toString(radix); + s = value.toString(radix); return s; } @@ -207,13 +236,10 @@ class Int36 { /** * mul(i36) * - * TODO: Support multiplication results > 36 bits (ie, up to 72 bits) if an additional Int36 - * parameter is provided. - * * @param {Int36} i36 */ mul(i36) { - this.value = this.truncate(this.value * i36.value); + this.mulExtended(i36.value); } /** @@ -222,7 +248,58 @@ class Int36 { * @param {number} num */ mulNum(num) { - this.value = this.truncate(this.value * Int36.validate(num)); + this.mulExtended(Int36.validate(num)); + } + + /** + * mulExtended(value) + * + * To support 72-bit results, we perform the multiplication process as you would "by hand", + * treating each of the operands to be multiplied as two 2-digit numbers, where each digit is + * an 18-bit number (base 2^18). Each individual multiplication of these 18-bit "digits" + * will produce a result within 2^36, well within JavaScript integer accuracy. + * + * @param {number} value + */ + mulExtended(value) { + var fNeg = false, extended; + var n1 = this.value, n2 = value; + + if (n1 < 0) { + n1 = -n1; + fNeg = !fNeg; + } + + if (n2 < 0) { + n2 = -n2; + fNeg = !fNeg; + } + + if (n1 < Int36.BIT18 && n2 < Int36.BIT18) { + value = n1 * n2; + extended = 0; + } + else { + var n1d1 = (n1 % Int36.BIT18); + var n1d2 = Math.trunc(n1 / Int36.BIT18); + var n2d1 = (n2 % Int36.BIT18); + var n2d2 = Math.trunc(n2 / Int36.BIT18); + + var m1d1 = n1d1 * n2d1; + var m1d2 = (n1d2 * n2d1) + Math.trunc(m1d1 / Int36.BIT18); + extended = Math.trunc(m1d2 / Int36.BIT18); + m1d2 = (m1d2 % Int36.BIT18) + (n1d1 * n2d2); + value = (m1d2 * Int36.BIT18) + (m1d1 % Int36.BIT18); + extended += Math.trunc(m1d2 / Int36.BIT18) + (n1d2 * n2d2); + } + + if (fNeg) { + value = -value; + extended = -extended - (value? 1 : 0); + } + + this.value = this.truncate(value); + this.extended = this.truncate(extended); } /** From a0ddaa22395275267b9cd29241b4661272dbba11 Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Mon, 13 Feb 2017 11:55:29 -0800 Subject: [PATCH 05/29] Implemented 36-bit multiplication with support for (up to) 72-bit results --- modules/shared/lib/int36.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/shared/lib/int36.js b/modules/shared/lib/int36.js index 8935fb199..15e4c0f9b 100644 --- a/modules/shared/lib/int36.js +++ b/modules/shared/lib/int36.js @@ -162,7 +162,9 @@ class Int36 { if (extended) { if (fUnsigned) extended += Int36.BIT36; /* - * TODO: Come up with a solution that won't overflow JavaScript's more limited precision. + * TODO: Need a radix-independent solution for these extended (up to 72-bit) values, + * because after 52 bits, JavaScript will start dropping least-significant bits. Until + * then, you're better off sticking with octal (see above). */ value = extended * Int36.BIT36 + value; } From 7131e471ac29a26448ebae84b9e4d2b0634db0c9 Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Mon, 13 Feb 2017 14:04:00 -0800 Subject: [PATCH 06/29] Implemented 36-bit division with support for (up to) 72-bit dividends --- modules/shared/bin/int36 | 3 + modules/shared/lib/int36.js | 334 ++++++++++++++++++++++++++---------- 2 files changed, 248 insertions(+), 89 deletions(-) diff --git a/modules/shared/bin/int36 b/modules/shared/bin/int36 index 412098589..c61583558 100644 --- a/modules/shared/bin/int36 +++ b/modules/shared/bin/int36 @@ -130,6 +130,9 @@ for (let i = 0; i <= 12; i++) { test("mul " + Math.pow(8, i)); } +test("set 100"); +test("div 3"); + repl.start({ prompt: "int36> ", input: process.stdin, diff --git a/modules/shared/lib/int36.js b/modules/shared/lib/int36.js index 15e4c0f9b..2e19f7b24 100644 --- a/modules/shared/lib/int36.js +++ b/modules/shared/lib/int36.js @@ -30,16 +30,32 @@ var DEBUG = true; +/** + * @class Int36 + * @property {number} value + * @property {number} extended + * @property {number} remainder + * @property {number} error + * + * The 'value' property stores the 36-bit value as a two's complement integer. + * + * The 'extended' property stores an additional 36 bits of data from a multiplication; + * it must also be set prior to a division. + * + * The 'remainder' property stores the remainder from a division. + * + * The 'error' property records any error(s) from the last operation. + */ + class Int36 { /** - * Int36(hi, lo) + * Int36(obj, extended) * - * The constructor creates an Int36 from either: + * The constructor, which simply calls set(), creates an Int36 from either: * * 1) another Int36 - * 2) a single (signed) 36-bit value - * 3) a pair of 18-bit values (the signs are irrelevant) - * 4) nothing (initial value will be zero) + * 2) a single (signed) 36-bit value, with an optional 36-bit extended value + * 3) nothing (initial value will be zero) * * We guarantee that an Int36 value will be (and will always remain) a signed value within this range: * @@ -72,71 +88,37 @@ class Int36 { * but constructor calls are infrequent (if they're not, you're doing something wrong), whereas Int36-only * operations should be as fast and unchecked as possible. * - * @param {Int36|number} [hi] (if omitted, the default is zero) - * @param {number} [lo] (if present, both lo and hi must be 18-bit numbers) + * @param {Int36|number} [obj] (if omitted, the default is zero) + * @param {number} [extended] */ - constructor(hi = 0, lo) + constructor(obj, extended) { - if (hi instanceof Int36) { - this.value = hi.value; + this.set(obj, extended); + this.bitsDiv = [0, 0]; + this.bitsRem = [0, 0]; + } + + /** + * set(obj, extended) + * + * @param {Int36|number} [obj] (if omitted, the default is zero) + * @param {number} [extended] + */ + set(obj = 0, extended) + { + if (obj instanceof Int36) { + this.value = obj.value; + this.extended = obj.extended; + this.remainder = obj.remainder; } - else if (isNaN(lo)) { - /* - * Checking isNaN(lo) includes checking for undefined. And since there's no guarantee - * that hi is within the 36-bit range, we call validate() to make sure. - */ - this.value = Int36.validate(hi); - } else { - /* - * We're masking both inputs to 18 bits, making them positive, so the result will - * be positive and no more than 36 bits; however, the value could be greater than MAXVAL, - * meaning the sign bit (bit 35) is set, in which case we must perform a two's complement - * conversion, by subtracting 2^36. - */ - this.value = (hi & Int36.MASKLO) * Int36.BIT18 + (lo & Int36.MASKLO); - if (this.value > Int36.MAXVAL) { - this.value -= Int36.BIT36; - } + else { + this.value = Int36.validate(obj || 0); + this.extended = Int36.validate(extended || 0); + this.remainder = 0; } - /* - * The 'extended' property stores an additional 36 bits of data after a multiplication, and any - * remainder after a division. It's strictly an output-only property for those operations, and - * its value does not affect the result of ANY operation. - */ - this.extended = 0; this.error = Int36.ERROR.NONE; } - /** - * validate(num) - * - * @param {number} num - * @return {number} - */ - static validate(num) - { - var result = Math.trunc(num || 0) % Int36.BIT36; - if (result > Int36.MAXVAL) { - result -= Int36.BIT36; - } else if (result < Int36.MINVAL) { - result += Int36.BIT36; - } - if (DEBUG && num !== result) console.log("Int36.validate(" + num + " out of range, truncated to " + result + ")"); - return result; - } - - /** - * octal(value) - * - * @param {number} value - * @return {string} - */ - static octal(value) - { - if (value < 0) value += Int36.BIT36; - return ("00000000000" + value.toString(8)).slice(-12); - } - /** * toString(radix, fUnsigned) * @@ -153,6 +135,9 @@ class Int36 { if (extended) { s = Int36.octal(extended) + ',' + s; } + if (this.remainder) { + s += ':' + Int36.octal(this.remainder); + } if (DEBUG && this.error) s += " error 0x" + this.error.toString(16); return s; } @@ -204,7 +189,8 @@ class Int36 { * * @param {Int36} i36 */ - add(i36) { + add(i36) + { this.value = this.truncate(this.value + i36.value); } @@ -213,7 +199,8 @@ class Int36 { * * @param {number} num */ - addNum(num) { + addNum(num) + { this.value = this.truncate(this.value + Int36.validate(num)); } @@ -222,7 +209,8 @@ class Int36 { * * @param {Int36} i36 */ - sub(i36) { + sub(i36) + { this.value = this.truncate(this.value - i36.value); } @@ -231,7 +219,8 @@ class Int36 { * * @param {number} num */ - subNum(num) { + subNum(num) + { this.value = this.truncate(this.value - Int36.validate(num)); } @@ -240,7 +229,8 @@ class Int36 { * * @param {Int36} i36 */ - mul(i36) { + mul(i36) + { this.mulExtended(i36.value); } @@ -249,7 +239,8 @@ class Int36 { * * @param {number} num */ - mulNum(num) { + mulNum(num) + { this.mulExtended(Int36.validate(num)); } @@ -263,7 +254,8 @@ class Int36 { * * @param {number} value */ - mulExtended(value) { + mulExtended(value) + { var fNeg = false, extended; var n1 = this.value, n2 = value; @@ -307,35 +299,201 @@ class Int36 { /** * div(i36) * - * TODO: Support division of dividends > 36 bits (ie, up to 72 bits) if an additional Int36 - * parameter is provided. - * - * WARNING: JavaScript division by zero returns Infinity (or -Infinity). For now, we simply record an error. - * * @param {Int36} i36 */ - div(i36) { - if (!i36.value) { - this.error |= Int36.ERROR.DIVZERO; - } else { - this.value = this.truncate(Math.trunc(this.value / i36.value)); - } + div(i36) + { + this.divExtended(i36.value); } /** * divNum(num) * - * WARNING: JavaScript division by zero returns Infinity (or -Infinity). For now, we simply record an error. - * * @param {number} num */ - divNum(num) { - var divisor = Int36.validate(num); + divNum(num) + { + this.divExtended(Int36.validate(num)); + } + + /** + * divExtended(divisor) + * + * @param {number} divisor + */ + divExtended(divisor) + { + var value = this.value; + var extended = this.extended; + + var bNegLo = 0, bNegHi = 0; + /* + * dividend divisor quotient remainder + * -------- ------- -------- --------- + * + + -> + + + * + - -> - + + * - + -> - - + * - - -> + - + */ + if (divisor < 0) { + divisor = -divisor; + bNegLo = 1 - bNegLo; + } + + if (extended < 0) { + value = -value; + extended = -extended - (value? 1 : 0); + bNegHi = 1; + bNegLo = 1 - bNegLo; + } + if (!divisor) { this.error |= Int36.ERROR.DIVZERO; - } else { - this.value = this.truncate(Math.trunc(this.value / divisor)); } + else if (divisor <= extended) { + this.error |= Int36.ERROR.OVERFLOW; + } + else { + var result = 0, bit = 1; + var bitsDiv = Int36.setBits(this.bitsDiv, divisor, 0); + var bitsRem = Int36.setBits(this.bitsRem, value, extended); + + while (Int36.cmpBits(bitsRem, bitsDiv) > 0) { + Int36.addBits(bitsDiv, bitsDiv); + bit += bit; + } + + do { + if (Int36.cmpBits(bitsRem, bitsDiv) >= 0) { + Int36.subBits(bitsRem, bitsDiv); + result += bit; + } + Int36.shrBits(bitsDiv); + bit /= 2; + } while (bit >= 1); + + if (DEBUG) console.assert(result < Int36.BIT36 && !bitsRem[1], "divExtended() assertion failure"); + + this.value = result; + this.extended = 0; + this.remainder = bitsRem[0]; + + if (bNegLo) this.value = -this.value; + if (bNegHi) this.remainder = -this.remainder; + } + } + + /** + * addBits(bitsDst, bitsSrc) + * + * Adds bitsSrc to bitsDst. + * + * @param {Array.} bitsDst + * @param {Array.} bitsSrc + */ + static addBits(bitsDst, bitsSrc) + { + bitsDst[0] += bitsSrc[0]; + bitsDst[1] += bitsSrc[1]; + if (bitsDst[0] >= Int36.BIT36) { + bitsDst[0] %= Int36.BIT36; + bitsDst[1]++; + } + } + + /** + * cmpBits(bitsDst, bitsSrc) + * + * Compares bitsDst to bitsSrc, by computing bitsDst - bitsSrc. + * + * @param {Array.} bitsDst + * @param {Array.} bitsSrc + * @return {number} > 0 if bitsDst > bitsSrc, == 0 if bitsDst == bitsSrc, < 0 if bitsDst < bitsSrc + */ + static cmpBits(bitsDst, bitsSrc) + { + var result = bitsDst[1] - bitsSrc[1]; + if (!result) result = bitsDst[0] - bitsSrc[0]; + return result; + } + + /** + * setBits(bits, lo, hi) + * + * @param {Array.} bits + * @param {number} lo + * @param {number} hi + * @return {Array.} + */ + static setBits(bits, lo, hi) + { + bits[0] = lo; + bits[1] = hi; + return bits; + } + + /** + * shrBits(bitsDst) + * + * Shifts bitsDst right one bit. + * + * @param {Array.} bitsDst + */ + static shrBits(bitsDst) + { + if (bitsDst[1] % 2) { + bitsDst[0] += Int36.BIT36; + } + bitsDst[0] = Math.trunc(bitsDst[0] / 2); + bitsDst[1] = Math.trunc(bitsDst[1] / 2); + } + + /** + * subBits(bitsDst, bitsSrc) + * + * Subtracts bitsSrc from bitsDst. + * + * @param {Array.} bitsDst + * @param {Array.} bitsSrc + */ + static subBits(bitsDst, bitsSrc) + { + bitsDst[0] -= bitsSrc[0]; + bitsDst[1] -= bitsSrc[1]; + if (bitsDst[0] < 0) { + bitsDst[0] += Int36.BIT36; + bitsDst[1]--; + } + } + + /** + * octal(value) + * + * @param {number} value + * @return {string} + */ + static octal(value) + { + if (value < 0) value += Int36.BIT36; + return ("00000000000" + value.toString(8)).slice(-12); + } + + /** + * validate(num) + * + * @param {number} num + * @return {number} + */ + static validate(num) + { + var value = Math.trunc(num) % Int36.BIT36; + if (value > Int36.MAXVAL) { + value -= Int36.BIT36; + } else if (value < Int36.MINVAL) { + value += Int36.BIT36; + } + if (DEBUG && num !== value) console.log("Int36.validate(" + num + " out of range, truncated to " + value + ")"); + return value; } } @@ -346,8 +504,6 @@ Int36.ERROR = { DIVZERO: 0x4 }; -Int36.MASKLO = 0o777777; // 262,143 - Int36.BIT18 = Math.pow(2, 18); // 262,144 Int36.BIT36 = Math.pow(2, 36); // 68,719,476,736 From 656f7e6796cb115eabe3524e424b860606215e8e Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Tue, 14 Feb 2017 13:12:27 -0800 Subject: [PATCH 07/29] Numerous fixes to Int36 class, with improved support for 72-bit mul(), div(), and toString()/toDecimal() --- modules/shared/bin/int36 | 9 +- modules/shared/lib/int36.js | 191 +++++++++++++++++++++++++++++------- 2 files changed, 165 insertions(+), 35 deletions(-) diff --git a/modules/shared/bin/int36 b/modules/shared/bin/int36 index c61583558..c5a754de1 100644 --- a/modules/shared/bin/int36 +++ b/modules/shared/bin/int36 @@ -64,6 +64,8 @@ function test(sCmd, fREPL) var i36Op = new Int36(+sNum1); + if (sNum1 != null) console.log(sOp + " " + dumpInt36(i36Op)); + switch(sOp) { case "set": i36Reg = new Int36(+sNum1, +sNum2); @@ -85,6 +87,10 @@ function test(sCmd, fREPL) i36Reg.div(i36Op); break; + case "dec": + console.log("dec " + i36Reg.toDecimal()); + return true; + case "print": break; @@ -93,7 +99,6 @@ function test(sCmd, fREPL) return false; } - console.log(sOp + " " + dumpInt36(i36Op)); if (sOp != "set") console.log(" = " + dumpInt36(i36Reg)); return true; @@ -123,11 +128,13 @@ test("add 0"); for (let i = 0; i <= 12; i++) { test("set 34,000,000,000"); test("mul " + Math.pow(8, i)); + test("dec"); } for (let i = 0; i <= 12; i++) { test("set -34,000,000,000"); test("mul " + Math.pow(8, i)); + test("dec"); } test("set 100"); diff --git a/modules/shared/lib/int36.js b/modules/shared/lib/int36.js index 2e19f7b24..170b0854c 100644 --- a/modules/shared/lib/int36.js +++ b/modules/shared/lib/int36.js @@ -33,7 +33,7 @@ var DEBUG = true; /** * @class Int36 * @property {number} value - * @property {number} extended + * @property {number|null} extended * @property {number} remainder * @property {number} error * @@ -88,6 +88,7 @@ class Int36 { * but constructor calls are infrequent (if they're not, you're doing something wrong), whereas Int36-only * operations should be as fast and unchecked as possible. * + * @this {Int36} * @param {Int36|number} [obj] (if omitted, the default is zero) * @param {number} [extended] */ @@ -101,6 +102,7 @@ class Int36 { /** * set(obj, extended) * + * @this {Int36} * @param {Int36|number} [obj] (if omitted, the default is zero) * @param {number} [extended] */ @@ -113,25 +115,63 @@ class Int36 { } else { this.value = Int36.validate(obj || 0); - this.extended = Int36.validate(extended || 0); + this.extended = null; + if (extended != null && !isNaN(extended)) { + this.extended = Int36.validate(extended); + } this.remainder = 0; } this.error = Int36.ERROR.NONE; } + /** + * toDecimal() + * + * @this {Int36} + * @return {string} + */ + toDecimal() + { + var s = "", fNeg = false; + var i36Div = new Int36(10000000000); + var i36Tmp = new Int36(this.value, this.extended); + if (i36Tmp.extended < 0 || i36Tmp.extended == null && i36Tmp.value < 0) { + i36Tmp.negExtended(); + fNeg = true; + } + var quotient = i36Tmp.div(i36Div); + i36Tmp.value = i36Tmp.remainder; + if (quotient) { + var nDigits = 10; + do { + i36Tmp.divNum(10); + s = String.fromCharCode(0x30 + i36Tmp.remainder) + s; + } while (--nDigits); + i36Tmp.value = quotient; + } + do { + i36Tmp.divNum(10); + s = String.fromCharCode(0x30 + i36Tmp.remainder) + s; + } while (i36Tmp.value); + if (fNeg) s = '-' + s; + return s; + } + /** * toString(radix, fUnsigned) * + * @this {Int36} * @param {number} [radix] (default is 10) * @param {boolean} [fUnsigned] (default is signed for radix 10, unsigned for any other radix) + * @return {string} */ toString(radix = 10, fUnsigned) { - var s; var value = this.value; var extended = this.extended; + if (radix == 8) { - s = Int36.octal(value); + var s = Int36.octal(value); if (extended) { s = Int36.octal(extended) + ',' + s; } @@ -141,7 +181,13 @@ class Int36 { if (DEBUG && this.error) s += " error 0x" + this.error.toString(16); return s; } - if (radix != 10) fUnsigned = true; + + if (radix != 10) { + fUnsigned = true; + } else { + return this.toDecimal(); + } + if (fUnsigned || extended) { if (value < 0) value += Int36.BIT36; if (extended) { @@ -154,8 +200,7 @@ class Int36 { value = extended * Int36.BIT36 + value; } } - s = value.toString(radix); - return s; + return value.toString(radix); } /** @@ -165,12 +210,15 @@ class Int36 { * not to remove any fractional portion that might also exist. If an operation could have produced * a non-integer result (eg, div()), it's the caller's responsibility to deal with that first. * + * @this {Int36} * @param {number} result * @return {number} */ truncate(result) { - if (DEBUG && result !== Math.trunc(result)) console.log("Int36.truncate(" + result + " is not an integer)"); + if (DEBUG && result !== Math.trunc(result)) { + console.log("Int36.truncate(" + result + " is not an integer)"); + } this.error = Int36.ERROR.NONE; if (result > Int36.MAXVAL) { result %= Int36.BIT36; @@ -187,6 +235,7 @@ class Int36 { /** * add(i36) * + * @this {Int36} * @param {Int36} i36 */ add(i36) @@ -197,6 +246,7 @@ class Int36 { /** * addNum(num) * + * @this {Int36} * @param {number} num */ addNum(num) @@ -207,6 +257,7 @@ class Int36 { /** * sub(i36) * + * @this {Int36} * @param {Int36} i36 */ sub(i36) @@ -217,6 +268,7 @@ class Int36 { /** * subNum(num) * + * @this {Int36} * @param {number} num */ subNum(num) @@ -227,6 +279,7 @@ class Int36 { /** * mul(i36) * + * @this {Int36} * @param {Int36} i36 */ mul(i36) @@ -237,6 +290,7 @@ class Int36 { /** * mulNum(num) * + * @this {Int36} * @param {number} num */ mulNum(num) @@ -252,6 +306,7 @@ class Int36 { * an 18-bit number (base 2^18). Each individual multiplication of these 18-bit "digits" * will produce a result within 2^36, well within JavaScript integer accuracy. * + * @this {Int36} * @param {number} value */ mulExtended(value) @@ -260,12 +315,12 @@ class Int36 { var n1 = this.value, n2 = value; if (n1 < 0) { - n1 = -n1; + if (n1) n1 = -n1; fNeg = !fNeg; } if (n2 < 0) { - n2 = -n2; + if (n2) n2 = -n2; fNeg = !fNeg; } @@ -287,46 +342,45 @@ class Int36 { extended += Math.trunc(m1d2 / Int36.BIT18) + (n1d2 * n2d2); } - if (fNeg) { - value = -value; - extended = -extended - (value? 1 : 0); - } - this.value = this.truncate(value); this.extended = this.truncate(extended); + + if (fNeg) this.negExtended(); } /** * div(i36) * + * @this {Int36} * @param {Int36} i36 + * @return {number} (quotient) */ div(i36) { - this.divExtended(i36.value); + return this.divExtended(i36.value); } /** * divNum(num) * + * @this {Int36} * @param {number} num + * @return {number} (quotient) */ divNum(num) { - this.divExtended(Int36.validate(num)); + return this.divExtended(Int36.validate(num)); } /** * divExtended(divisor) * + * @this {Int36} * @param {number} divisor + * @return {number} (quotient) */ divExtended(divisor) { - var value = this.value; - var extended = this.extended; - - var bNegLo = 0, bNegHi = 0; /* * dividend divisor quotient remainder * -------- ------- -------- --------- @@ -335,16 +389,23 @@ class Int36 { * - + -> - - * - - -> + - */ - if (divisor < 0) { + var bNegLo = 0, bNegHi = 0; + + if (divisor < 0 && divisor > Int36.MINVAL) { divisor = -divisor; bNegLo = 1 - bNegLo; } - if (extended < 0) { - value = -value; - extended = -extended - (value? 1 : 0); - bNegHi = 1; - bNegLo = 1 - bNegLo; + if (this.extended < 0 || this.extended == null && this.value < 0) { + this.negExtended(); + bNegHi = 1; bNegLo = 1 - bNegLo; + } + + var value = this.value; + var extended = this.extended || 0; + + if (value < 0) { + value += Int36.BIT36; } if (!divisor) { @@ -372,14 +433,74 @@ class Int36 { bit /= 2; } while (bit >= 1); - if (DEBUG) console.assert(result < Int36.BIT36 && !bitsRem[1], "divExtended() assertion failure"); + if (DEBUG && !(result < Int36.BIT36 && !bitsRem[1])) { + console.log("divExtended() assertion failure"); + } this.value = result; this.extended = 0; this.remainder = bitsRem[0]; - if (bNegLo) this.value = -this.value; - if (bNegHi) this.remainder = -this.remainder; + if (bNegLo && this.value && this.value > Int36.MINVAL) { + this.value = -this.value; + } + if (bNegHi && this.remainder && this.remainder > Int36.MINVAL) { + this.remainder = -this.remainder; + } + } + return this.value; + } + + /** + * negExtended() + * + * Converts the current value to its two's complement. If we were dealing with 8-bit values: + * + * Original Two's One's + * ------- ----- ----- + * -128 -128 127 + * -127 127 126 + * ... ... ... + * -1 1 0 + * 0 0 -1 + * 1 -1 -2 + * ... ... ... + * 126 -126 -127 + * 127 -127 -128 + * + * So the one wrinkle is that, when performing two's complement, MINVAL and ZERO are not modified. + * + * However, in our world, since JavaScript numbers CAN represent both positive and negative MINVAL + * values, we don't need to exclude MINVAL from the process. + */ + negExtended() + { + this.error = Int36.ERROR.NONE; + /* + * Perform two's complement on the value. + */ + if (this.value /* && this.value > Int36.MINVAL */) { + this.value = -this.value; + } + if (this.extended == null) { + /* + * Set extended to match the sign of the value. + */ + this.extended = (this.value < 0? -1 : 0); + } + else if (this.value) { + /* + * Perform one's complement on the extended value. + */ + this.extended = -this.extended - 1; + } + else { + /* + * Perform two's complement on the extended value. + */ + if (this.extended /* && this.extended > Int36.MINVAL */) { + this.extended = -this.extended; + } } } @@ -492,7 +613,9 @@ class Int36 { } else if (value < Int36.MINVAL) { value += Int36.BIT36; } - if (DEBUG && num !== value) console.log("Int36.validate(" + num + " out of range, truncated to " + value + ")"); + if (DEBUG && num !== value) { + console.log("Int36.validate(" + num + " out of range, truncated to " + value + ")"); + } return value; } } @@ -504,10 +627,10 @@ Int36.ERROR = { DIVZERO: 0x4 }; -Int36.BIT18 = Math.pow(2, 18); // 262,144 -Int36.BIT36 = Math.pow(2, 36); // 68,719,476,736 +Int36.BIT18 = Math.pow(2, 18); // 262,144 +Int36.BIT36 = Math.pow(2, 36); // 68,719,476,736 -Int36.MAXVAL = Math.pow(2, 35) - 1; // 34,359,738,367 +Int36.MAXVAL = Math.pow(2, 35) - 1; // 34,359,738,367 Int36.MINVAL = -Math.pow(2, 35); // -34,359,738,368 if (NODE) module.exports = Int36; From 3a33ffdd5092083ea817b871df3ce28130836d13 Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Tue, 14 Feb 2017 17:55:41 -0800 Subject: [PATCH 08/29] A few more toDecimal() fixes --- modules/shared/bin/int36 | 3 ++ modules/shared/lib/int36.js | 69 ++++++++++++++++++++++--------------- 2 files changed, 44 insertions(+), 28 deletions(-) diff --git a/modules/shared/bin/int36 b/modules/shared/bin/int36 index c5a754de1..103f3e0c8 100644 --- a/modules/shared/bin/int36 +++ b/modules/shared/bin/int36 @@ -140,6 +140,9 @@ for (let i = 0; i <= 12; i++) { test("set 100"); test("div 3"); +test("set 4001"); +test("div -5"); + repl.start({ prompt: "int36> ", input: process.stdin, diff --git a/modules/shared/lib/int36.js b/modules/shared/lib/int36.js index 170b0854c..05bc5d67d 100644 --- a/modules/shared/lib/int36.js +++ b/modules/shared/lib/int36.js @@ -34,15 +34,17 @@ var DEBUG = true; * @class Int36 * @property {number} value * @property {number|null} extended - * @property {number} remainder + * @property {number|null} remainder * @property {number} error * * The 'value' property stores the 36-bit value as a two's complement integer. * * The 'extended' property stores an additional 36 bits of data from a multiplication; - * it must also be set prior to a division. + * it must also be set prior to a division. Internally, it will be set to null whenever + * the current value is not extended. * - * The 'remainder' property stores the remainder from a division. + * The 'remainder' property stores the remainder from the last division. You should + * assume that it will be set to null by any other operation. * * The 'error' property records any error(s) from the last operation. */ @@ -54,7 +56,7 @@ class Int36 { * The constructor, which simply calls set(), creates an Int36 from either: * * 1) another Int36 - * 2) a single (signed) 36-bit value, with an optional 36-bit extended value + * 2) a single (signed) 36-bit value, with an optional 36-bit extension * 3) nothing (initial value will be zero) * * We guarantee that an Int36 value will be (and will always remain) a signed value within this range: @@ -90,7 +92,7 @@ class Int36 { * * @this {Int36} * @param {Int36|number} [obj] (if omitted, the default is zero) - * @param {number} [extended] + * @param {number|null} [extended] */ constructor(obj, extended) { @@ -104,7 +106,7 @@ class Int36 { * * @this {Int36} * @param {Int36|number} [obj] (if omitted, the default is zero) - * @param {number} [extended] + * @param {number|null} [extended] */ set(obj = 0, extended) { @@ -116,10 +118,13 @@ class Int36 { else { this.value = Int36.validate(obj || 0); this.extended = null; + /* + * NOTE: Surprisingly, isNaN(null) is false, whereas isNaN(undefined) is true. Go figure. + */ if (extended != null && !isNaN(extended)) { this.extended = Int36.validate(extended); } - this.remainder = 0; + this.remainder = null; } this.error = Int36.ERROR.NONE; } @@ -135,23 +140,19 @@ class Int36 { var s = "", fNeg = false; var i36Div = new Int36(10000000000); var i36Tmp = new Int36(this.value, this.extended); - if (i36Tmp.extended < 0 || i36Tmp.extended == null && i36Tmp.value < 0) { - i36Tmp.negExtended(); + if (i36Tmp.isNegative()) { + i36Tmp.negate(); fNeg = true; } - var quotient = i36Tmp.div(i36Div); - i36Tmp.value = i36Tmp.remainder; - if (quotient) { - var nDigits = 10; + do { + var quotient = i36Tmp.div(i36Div); + var nMinDigits = (quotient? 10 : 1); + i36Tmp.value = i36Tmp.remainder; do { i36Tmp.divNum(10); s = String.fromCharCode(0x30 + i36Tmp.remainder) + s; - } while (--nDigits); + } while (--nMinDigits > 0 || i36Tmp.value); i36Tmp.value = quotient; - } - do { - i36Tmp.divNum(10); - s = String.fromCharCode(0x30 + i36Tmp.remainder) + s; } while (i36Tmp.value); if (fNeg) s = '-' + s; return s; @@ -190,7 +191,7 @@ class Int36 { if (fUnsigned || extended) { if (value < 0) value += Int36.BIT36; - if (extended) { + if (extended != null) { if (fUnsigned) extended += Int36.BIT36; /* * TODO: Need a radix-independent solution for these extended (up to 72-bit) values, @@ -219,6 +220,8 @@ class Int36 { if (DEBUG && result !== Math.trunc(result)) { console.log("Int36.truncate(" + result + " is not an integer)"); } + this.extended = null; + this.remainder = null; this.error = Int36.ERROR.NONE; if (result > Int36.MAXVAL) { result %= Int36.BIT36; @@ -345,7 +348,7 @@ class Int36 { this.value = this.truncate(value); this.extended = this.truncate(extended); - if (fNeg) this.negExtended(); + if (fNeg) this.negate(); } /** @@ -396,8 +399,8 @@ class Int36 { bNegLo = 1 - bNegLo; } - if (this.extended < 0 || this.extended == null && this.value < 0) { - this.negExtended(); + if (this.isNegative()) { + this.negate(); bNegHi = 1; bNegLo = 1 - bNegLo; } @@ -438,7 +441,7 @@ class Int36 { } this.value = result; - this.extended = 0; + this.extended = null; this.remainder = bitsRem[0]; if (bNegLo && this.value && this.value > Int36.MINVAL) { @@ -452,7 +455,17 @@ class Int36 { } /** - * negExtended() + * isNegative() + * + * @return {boolean} + */ + isNegative() + { + return (this.extended < 0 || this.extended == null && this.value < 0); + } + + /** + * negate() * * Converts the current value to its two's complement. If we were dealing with 8-bit values: * @@ -468,12 +481,12 @@ class Int36 { * 126 -126 -127 * 127 -127 -128 * - * So the one wrinkle is that, when performing two's complement, MINVAL and ZERO are not modified. + * so you can see that, when performing two's complement, MINVAL and ZERO are not modified. * - * However, in our world, since JavaScript numbers CAN represent both positive and negative MINVAL - * values, we don't need to exclude MINVAL from the process. + * However, in our happy little world, since JavaScript numbers CAN represent both positive and negative + * MINVAL values, we don't need to exclude MINVAL from the conversion. */ - negExtended() + negate() { this.error = Int36.ERROR.NONE; /* From 259dd7d66fff9e7fc2ebac1c766b91565cab4298 Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Wed, 15 Feb 2017 11:54:16 -0800 Subject: [PATCH 09/29] Updated comments --- modules/shared/lib/int36.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/modules/shared/lib/int36.js b/modules/shared/lib/int36.js index 05bc5d67d..59b0796d3 100644 --- a/modules/shared/lib/int36.js +++ b/modules/shared/lib/int36.js @@ -37,14 +37,18 @@ var DEBUG = true; * @property {number|null} remainder * @property {number} error * - * The 'value' property stores the 36-bit value as a two's complement integer. + * The 'value' property stores the 36-bit value as a signed integer, meaning if the sign bit + * (bit 35) is set, we store the corresponding negative (two's complement) value. While there + * might be some slight benefits to always storing the 36-bit value as an unsigned quantity + * and negating it "on demand", I like having JavaScript's native representation match the + * emulated value. * * The 'extended' property stores an additional 36 bits of data from a multiplication; - * it must also be set prior to a division. Internally, it will be set to null whenever - * the current value is not extended. + * it must also be set prior to a division. Internally, it will be set to null whenever the + * current value is not extended. * - * The 'remainder' property stores the remainder from the last division. You should - * assume that it will be set to null by any other operation. + * The 'remainder' property stores the remainder from the last division. You should assume it + * will be set to null by any other operation. * * The 'error' property records any error(s) from the last operation. */ @@ -481,7 +485,7 @@ class Int36 { * 126 -126 -127 * 127 -127 -128 * - * so you can see that, when performing two's complement, MINVAL and ZERO are not modified. + * you can see that, when performing two's complement, MINVAL and ZERO are not modified. * * However, in our happy little world, since JavaScript numbers CAN represent both positive and negative * MINVAL values, we don't need to exclude MINVAL from the conversion. From 6bc0d8ac49797eee9e3ad24b00258f8ff6fddb8c Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Wed, 15 Feb 2017 16:16:32 -0800 Subject: [PATCH 10/29] Trying a second approach to the Int36 class that sticks to unsigned numbers internally (only downside so far is that the detecting arithmetic overflow/underflow is more work and hasn't been implemented yet) --- modules/shared/bin/int36 | 4 +- modules/shared/lib/int36.js | 154 ++++----- modules/shared/lib/int36s.js | 652 +++++++++++++++++++++++++++++++++++ 3 files changed, 713 insertions(+), 97 deletions(-) create mode 100644 modules/shared/lib/int36s.js diff --git a/modules/shared/bin/int36 b/modules/shared/bin/int36 index 103f3e0c8..2d2045612 100644 --- a/modules/shared/bin/int36 +++ b/modules/shared/bin/int36 @@ -31,8 +31,8 @@ "use strict"; var repl = require("repl"); -var Defines = require("../../shared/lib/defines"); -var Int36 = require("../../shared/lib/int36"); +var Defines = require("../lib/defines"); +var Int36 = require("../lib/int36"); var i36Reg = new Int36(); diff --git a/modules/shared/lib/int36.js b/modules/shared/lib/int36.js index 59b0796d3..04bc25499 100644 --- a/modules/shared/lib/int36.js +++ b/modules/shared/lib/int36.js @@ -1,5 +1,5 @@ /** - * @fileoverview Support for 36-bit integers + * @fileoverview Support for 36-bit integers (using unsigned JavaScript numbers) * @author Jeff Parsons (@jeffpar) * @copyright © Jeff Parsons 2012-2017 * @@ -37,11 +37,8 @@ var DEBUG = true; * @property {number|null} remainder * @property {number} error * - * The 'value' property stores the 36-bit value as a signed integer, meaning if the sign bit - * (bit 35) is set, we store the corresponding negative (two's complement) value. While there - * might be some slight benefits to always storing the 36-bit value as an unsigned quantity - * and negating it "on demand", I like having JavaScript's native representation match the - * emulated value. + * The 'value' property stores the 36-bit value as an unsigned integer. When the value + * should be interpreted as a signed quantity, subtract BIT36 whenever value > MAXSIGNED. * * The 'extended' property stores an additional 36 bits of data from a multiplication; * it must also be set prior to a division. Internally, it will be set to null whenever the @@ -60,15 +57,16 @@ class Int36 { * The constructor, which simply calls set(), creates an Int36 from either: * * 1) another Int36 - * 2) a single (signed) 36-bit value, with an optional 36-bit extension + * 2) a single 36-bit value, with an optional 36-bit extension * 3) nothing (initial value will be zero) * - * We guarantee that an Int36 value will be (and will always remain) a signed value within this range: + * We guarantee that an Int36 value will be (and always remain) an unsigned value within this range: * - * -Math.pow(2, 35) <= i <= Math.pow(2, 35) - 1 + * 0 <= i <= Math.pow(2, 36) - 1 * - * Those lower and upper bounds are defined as Int36.MINVAL and Int36.MAXVAL. The sign of an Int36 - * value is determined by (and should always match) its highest bit (bit 35). + * The lower bound is ZERO and the upper bound is Int36.MAXVAL. Whenever an Int36 value should be + * interpreted as a signed value, call isNegative() to check the sign bit (bit 35) and call negate() + * as appropriate. * * NOTE: We use modern bit numbering, where bit 0 is the right-most (least-significant) bit and * bit 35 is the left-most bit. This is opposite of the PDP-10 convention, which defined bit 0 as the @@ -78,7 +76,7 @@ class Int36 { * * -Math.pow(2, 53) <= i <= Math.pow(2, 53) * - * it seems unwise to ever permit the internal value to creep outside the signed 36-bit range, because + * it seems unwise to ever permit the internal value to creep outside the 36-bit range, because * floating-point operations will drop least-significant bits in favor of most-significant bits when a * result becomes too large, which is the opposite of what integer operations traditionally do. There * might be some optimization benefits to performing our internal 36-bit truncation "lazily", but at @@ -134,17 +132,18 @@ class Int36 { } /** - * toDecimal() + * toDecimal(fUnsigned) * * @this {Int36} + * @param {boolean} fUnsigned * @return {string} */ - toDecimal() + toDecimal(fUnsigned) { var s = "", fNeg = false; var i36Div = new Int36(10000000000); var i36Tmp = new Int36(this.value, this.extended); - if (i36Tmp.isNegative()) { + if (!fUnsigned && i36Tmp.isNegative()) { i36Tmp.negate(); fNeg = true; } @@ -172,6 +171,10 @@ class Int36 { */ toString(radix = 10, fUnsigned) { + if (radix == 10) { + return this.toDecimal(fUnsigned); + } + var value = this.value; var extended = this.extended; @@ -187,23 +190,13 @@ class Int36 { return s; } - if (radix != 10) { - fUnsigned = true; - } else { - return this.toDecimal(); - } - - if (fUnsigned || extended) { - if (value < 0) value += Int36.BIT36; - if (extended != null) { - if (fUnsigned) extended += Int36.BIT36; - /* - * TODO: Need a radix-independent solution for these extended (up to 72-bit) values, - * because after 52 bits, JavaScript will start dropping least-significant bits. Until - * then, you're better off sticking with octal (see above). - */ - value = extended * Int36.BIT36 + value; - } + if (extended != null) { + /* + * TODO: Need a radix-independent solution for these extended (up to 72-bit) values, + * because after 52 bits, JavaScript will start dropping least-significant bits. Until + * then, you're better off sticking with octal (see above). + */ + value = extended * Int36.BIT36 + value; } return value.toString(radix); } @@ -227,15 +220,11 @@ class Int36 { this.extended = null; this.remainder = null; this.error = Int36.ERROR.NONE; - if (result > Int36.MAXVAL) { - result %= Int36.BIT36; - if (result > Int36.MAXVAL) result -= Int36.BIT36; - this.error |= Int36.ERROR.OVERFLOW; - } else if (result < Int36.MINVAL) { - result %= Int36.BIT36; - if (result < Int36.MINVAL) result += Int36.BIT36; - this.error |= Int36.ERROR.UNDERFLOW; + + if (result < 0) { + result += Int36.BIT36; } + result %= Int36.BIT36; return result; } @@ -321,13 +310,13 @@ class Int36 { var fNeg = false, extended; var n1 = this.value, n2 = value; - if (n1 < 0) { - if (n1) n1 = -n1; + if (n1 > Int36.MAXSIGNED) { + n1 = Int36.BIT36 - n1; fNeg = !fNeg; } - if (n2 < 0) { - if (n2) n2 = -n2; + if (n2 > Int36.MAXSIGNED) { + n2 = Int36.BIT36 - n2; fNeg = !fNeg; } @@ -352,7 +341,9 @@ class Int36 { this.value = this.truncate(value); this.extended = this.truncate(extended); - if (fNeg) this.negate(); + if (fNeg) { + this.negate(); + } } /** @@ -398,8 +389,8 @@ class Int36 { */ var bNegLo = 0, bNegHi = 0; - if (divisor < 0 && divisor > Int36.MINVAL) { - divisor = -divisor; + if (divisor > Int36.MAXSIGNED) { + divisor = Int36.BIT36 - divisor; bNegLo = 1 - bNegLo; } @@ -411,10 +402,6 @@ class Int36 { var value = this.value; var extended = this.extended || 0; - if (value < 0) { - value += Int36.BIT36; - } - if (!divisor) { this.error |= Int36.ERROR.DIVZERO; } @@ -448,11 +435,11 @@ class Int36 { this.extended = null; this.remainder = bitsRem[0]; - if (bNegLo && this.value && this.value > Int36.MINVAL) { - this.value = -this.value; + if (bNegLo && this.value && this.value > Int36.MINSIGNED) { + this.value = Int36.BIT36 - this.value; } - if (bNegHi && this.remainder && this.remainder > Int36.MINVAL) { - this.remainder = -this.remainder; + if (bNegHi && this.remainder && this.remainder > Int36.MINSIGNED) { + this.remainder = Int36.BIT36 - this.remainder; } } return this.value; @@ -465,60 +452,37 @@ class Int36 { */ isNegative() { - return (this.extended < 0 || this.extended == null && this.value < 0); + return (this.extended > Int36.MAXSIGNED || this.extended == null && this.value > Int36.MAXSIGNED); } /** * negate() - * - * Converts the current value to its two's complement. If we were dealing with 8-bit values: - * - * Original Two's One's - * ------- ----- ----- - * -128 -128 127 - * -127 127 126 - * ... ... ... - * -1 1 0 - * 0 0 -1 - * 1 -1 -2 - * ... ... ... - * 126 -126 -127 - * 127 -127 -128 - * - * you can see that, when performing two's complement, MINVAL and ZERO are not modified. - * - * However, in our happy little world, since JavaScript numbers CAN represent both positive and negative - * MINVAL values, we don't need to exclude MINVAL from the conversion. */ negate() { - this.error = Int36.ERROR.NONE; - /* - * Perform two's complement on the value. - */ - if (this.value /* && this.value > Int36.MINVAL */) { - this.value = -this.value; - } if (this.extended == null) { /* - * Set extended to match the sign of the value. + * Set extended to match the sign of the (negated) value. */ - this.extended = (this.value < 0? -1 : 0); + this.extended = (this.value > Int36.MAXSIGNED? 0 : Int36.MAXVAL); } else if (this.value) { /* * Perform one's complement on the extended value. */ - this.extended = -this.extended - 1; + this.extended = Int36.MAXVAL - this.extended; } else { /* * Perform two's complement on the extended value. */ - if (this.extended /* && this.extended > Int36.MINVAL */) { - this.extended = -this.extended; - } + if (this.extended) this.extended = Int36.BIT36 - this.extended; } + /* + * Perform two's complement on the value. + */ + if (this.value) this.value = Int36.BIT36 - this.value; + this.error = Int36.ERROR.NONE; } /** @@ -624,12 +588,10 @@ class Int36 { */ static validate(num) { - var value = Math.trunc(num) % Int36.BIT36; - if (value > Int36.MAXVAL) { - value -= Int36.BIT36; - } else if (value < Int36.MINVAL) { - value += Int36.BIT36; + if (num < 0 && num >= Int36.MINSIGNED) { + num += Int36.BIT36; } + var value = Math.trunc(Math.abs(num)) % Int36.BIT36; if (DEBUG && num !== value) { console.log("Int36.validate(" + num + " out of range, truncated to " + value + ")"); } @@ -647,7 +609,9 @@ Int36.ERROR = { Int36.BIT18 = Math.pow(2, 18); // 262,144 Int36.BIT36 = Math.pow(2, 36); // 68,719,476,736 -Int36.MAXVAL = Math.pow(2, 35) - 1; // 34,359,738,367 -Int36.MINVAL = -Math.pow(2, 35); // -34,359,738,368 +Int36.MAXSIGNED = Math.pow(2, 35) - 1; // 34,359,738,367 +Int36.MINSIGNED = -Math.pow(2, 35); // -34,359,738,368 + +Int36.MAXVAL = Math.pow(2, 36) - 1; // 68,719,476,735 if (NODE) module.exports = Int36; diff --git a/modules/shared/lib/int36s.js b/modules/shared/lib/int36s.js new file mode 100644 index 000000000..a8b119d54 --- /dev/null +++ b/modules/shared/lib/int36s.js @@ -0,0 +1,652 @@ +/** + * @fileoverview Support for 36-bit integers (using signed JavaScript numbers) + * @author Jeff Parsons (@jeffpar) + * @copyright © Jeff Parsons 2012-2017 + * + * This file is part of PCjs, a computer emulation software project at . + * + * PCjs is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCjs. If not, + * see . + * + * You are required to include the above copyright notice in every modified copy of this work + * and to display that copyright notice when the software starts running; see COPYRIGHT in + * . + * + * Some PCjs files also attempt to load external resource files, such as character-image files, + * ROM files, and disk image files. Those external resource files are not considered part of PCjs + * for purposes of the GNU General Public License, and the author does not claim any copyright + * as to their contents. + */ + +"use strict"; + +var DEBUG = true; + +/** + * @class Int36 + * @property {number} value + * @property {number|null} extended + * @property {number|null} remainder + * @property {number} error + * + * The 'value' property stores the 36-bit value as a signed integer, meaning if the sign bit + * (bit 35) is set, we store the corresponding negative (two's complement) value. While there + * might be some slight benefits to always storing the 36-bit value as an unsigned quantity + * and negating it "on demand", I like having JavaScript's native representation match the + * emulated value. + * + * The 'extended' property stores an additional 36 bits of data from a multiplication; + * it must also be set prior to a division. Internally, it will be set to null whenever the + * current value is not extended. + * + * The 'remainder' property stores the remainder from the last division. You should assume it + * will be set to null by any other operation. + * + * The 'error' property records any error(s) from the last operation. + */ + +class Int36 { + /** + * Int36(obj, extended) + * + * The constructor, which simply calls set(), creates an Int36 from either: + * + * 1) another Int36 + * 2) a single (signed) 36-bit value, with an optional 36-bit extension + * 3) nothing (initial value will be zero) + * + * We guarantee that an Int36 value will be (and will always remain) a signed value within this range: + * + * -Math.pow(2, 35) <= i <= Math.pow(2, 35) - 1 + * + * Those lower and upper bounds are defined as Int36.MINVAL and Int36.MAXVAL. The sign of an Int36 + * value is determined by (and should always match) its highest bit (bit 35). + * + * NOTE: We use modern bit numbering, where bit 0 is the right-most (least-significant) bit and + * bit 35 is the left-most bit. This is opposite of the PDP-10 convention, which defined bit 0 as the + * left-most bit and bit 35 as the right-most bit. + * + * Although the integer precision of JavaScript floating-point (IEEE 754 double-precision) numbers is: + * + * -Math.pow(2, 53) <= i <= Math.pow(2, 53) + * + * it seems unwise to ever permit the internal value to creep outside the signed 36-bit range, because + * floating-point operations will drop least-significant bits in favor of most-significant bits when a + * result becomes too large, which is the opposite of what integer operations traditionally do. There + * might be some optimization benefits to performing our internal 36-bit truncation "lazily", but at + * least initially, I prefer to truncate the results of all operations immediately. + * + * Most of the Int36 operations come in two flavors: those that accept numbers, and those that accept + * another Int36. The latter are more efficient, because (as just explained) an Int36's internal value + * should always be in range, whereas external numbers could be out of range OR have a fractional value + * OR be something else entirely (NaN, Infinity, -Infinity, undefined, etc), so numeric inputs are + * always passed through the static validate() function. + * + * We could eliminate the two flavors and check each parameter's type, like we do in the constructor, + * but constructor calls are infrequent (if they're not, you're doing something wrong), whereas Int36-only + * operations should be as fast and unchecked as possible. + * + * @this {Int36} + * @param {Int36|number} [obj] (if omitted, the default is zero) + * @param {number|null} [extended] + */ + constructor(obj, extended) + { + this.set(obj, extended); + this.bitsDiv = [0, 0]; + this.bitsRem = [0, 0]; + } + + /** + * set(obj, extended) + * + * @this {Int36} + * @param {Int36|number} [obj] (if omitted, the default is zero) + * @param {number|null} [extended] + */ + set(obj = 0, extended) + { + if (obj instanceof Int36) { + this.value = obj.value; + this.extended = obj.extended; + this.remainder = obj.remainder; + } + else { + this.value = Int36.validate(obj || 0); + this.extended = null; + /* + * NOTE: Surprisingly, isNaN(null) is false, whereas isNaN(undefined) is true. Go figure. + */ + if (extended != null && !isNaN(extended)) { + this.extended = Int36.validate(extended); + } + this.remainder = null; + } + this.error = Int36.ERROR.NONE; + } + + /** + * toDecimal(fUnsigned) + * + * @this {Int36} + * @param {boolean} fUnsigned + * @return {string} + */ + toDecimal(fUnsigned) + { + var s = "", fNeg = false; + var i36Div = new Int36(10000000000); + var i36Tmp = new Int36(this.value, this.extended); + if (!fUnsigned && i36Tmp.isNegative()) { + i36Tmp.negate(); + fNeg = true; + } + do { + var quotient = i36Tmp.div(i36Div); + var nMinDigits = (quotient? 10 : 1); + i36Tmp.value = i36Tmp.remainder; + do { + i36Tmp.divNum(10); + s = String.fromCharCode(0x30 + i36Tmp.remainder) + s; + } while (--nMinDigits > 0 || i36Tmp.value); + i36Tmp.value = quotient; + } while (i36Tmp.value); + if (fNeg) s = '-' + s; + return s; + } + + /** + * toString(radix, fUnsigned) + * + * @this {Int36} + * @param {number} [radix] (default is 10) + * @param {boolean} [fUnsigned] (default is signed for radix 10, unsigned for any other radix) + * @return {string} + */ + toString(radix = 10, fUnsigned) + { + if (radix == 10) { + return this.toDecimal(fUnsigned); + } + + var value = this.value; + var extended = this.extended; + + if (radix == 8) { + var s = Int36.octal(value); + if (extended) { + s = Int36.octal(extended) + ',' + s; + } + if (this.remainder) { + s += ':' + Int36.octal(this.remainder); + } + if (DEBUG && this.error) s += " error 0x" + this.error.toString(16); + return s; + } + + if (value < 0) { + value += Int36.BIT36; + } + if (extended != null) { + if (extended < 0) extended += Int36.BIT36; + /* + * TODO: Need a radix-independent solution for these extended (up to 72-bit) values, + * because after 52 bits, JavaScript will start dropping least-significant bits. Until + * then, you're better off sticking with octal (see above). + */ + value = extended * Int36.BIT36 + value; + } + return value.toString(radix); + } + + /** + * truncate(result) + * + * NOTE: This function's job is to truncate the result of an operation to 36-bit accuracy, + * not to remove any fractional portion that might also exist. If an operation could have produced + * a non-integer result (eg, div()), it's the caller's responsibility to deal with that first. + * + * @this {Int36} + * @param {number} result + * @return {number} + */ + truncate(result) + { + if (DEBUG && result !== Math.trunc(result)) { + console.log("Int36.truncate(" + result + " is not an integer)"); + } + this.extended = null; + this.remainder = null; + this.error = Int36.ERROR.NONE; + if (result > Int36.MAXVAL) { + result %= Int36.BIT36; + if (result > Int36.MAXVAL) result -= Int36.BIT36; + this.error |= Int36.ERROR.OVERFLOW; + } else if (result < Int36.MINVAL) { + result %= Int36.BIT36; + if (result < Int36.MINVAL) result += Int36.BIT36; + this.error |= Int36.ERROR.UNDERFLOW; + } + return result; + } + + /** + * add(i36) + * + * @this {Int36} + * @param {Int36} i36 + */ + add(i36) + { + this.value = this.truncate(this.value + i36.value); + } + + /** + * addNum(num) + * + * @this {Int36} + * @param {number} num + */ + addNum(num) + { + this.value = this.truncate(this.value + Int36.validate(num)); + } + + /** + * sub(i36) + * + * @this {Int36} + * @param {Int36} i36 + */ + sub(i36) + { + this.value = this.truncate(this.value - i36.value); + } + + /** + * subNum(num) + * + * @this {Int36} + * @param {number} num + */ + subNum(num) + { + this.value = this.truncate(this.value - Int36.validate(num)); + } + + /** + * mul(i36) + * + * @this {Int36} + * @param {Int36} i36 + */ + mul(i36) + { + this.mulExtended(i36.value); + } + + /** + * mulNum(num) + * + * @this {Int36} + * @param {number} num + */ + mulNum(num) + { + this.mulExtended(Int36.validate(num)); + } + + /** + * mulExtended(value) + * + * To support 72-bit results, we perform the multiplication process as you would "by hand", + * treating each of the operands to be multiplied as two 2-digit numbers, where each digit is + * an 18-bit number (base 2^18). Each individual multiplication of these 18-bit "digits" + * will produce a result within 2^36, well within JavaScript integer accuracy. + * + * @this {Int36} + * @param {number} value + */ + mulExtended(value) + { + var fNeg = false, extended; + var n1 = this.value, n2 = value; + + if (n1 < 0) { + if (n1) n1 = -n1; + fNeg = !fNeg; + } + + if (n2 < 0) { + if (n2) n2 = -n2; + fNeg = !fNeg; + } + + if (n1 < Int36.BIT18 && n2 < Int36.BIT18) { + value = n1 * n2; + extended = 0; + } + else { + var n1d1 = (n1 % Int36.BIT18); + var n1d2 = Math.trunc(n1 / Int36.BIT18); + var n2d1 = (n2 % Int36.BIT18); + var n2d2 = Math.trunc(n2 / Int36.BIT18); + + var m1d1 = n1d1 * n2d1; + var m1d2 = (n1d2 * n2d1) + Math.trunc(m1d1 / Int36.BIT18); + extended = Math.trunc(m1d2 / Int36.BIT18); + m1d2 = (m1d2 % Int36.BIT18) + (n1d1 * n2d2); + value = (m1d2 * Int36.BIT18) + (m1d1 % Int36.BIT18); + extended += Math.trunc(m1d2 / Int36.BIT18) + (n1d2 * n2d2); + } + + this.value = this.truncate(value); + this.extended = this.truncate(extended); + + if (fNeg) this.negate(); + } + + /** + * div(i36) + * + * @this {Int36} + * @param {Int36} i36 + * @return {number} (quotient) + */ + div(i36) + { + return this.divExtended(i36.value); + } + + /** + * divNum(num) + * + * @this {Int36} + * @param {number} num + * @return {number} (quotient) + */ + divNum(num) + { + return this.divExtended(Int36.validate(num)); + } + + /** + * divExtended(divisor) + * + * @this {Int36} + * @param {number} divisor + * @return {number} (quotient) + */ + divExtended(divisor) + { + /* + * dividend divisor quotient remainder + * -------- ------- -------- --------- + * + + -> + + + * + - -> - + + * - + -> - - + * - - -> + - + */ + var bNegLo = 0, bNegHi = 0; + + if (divisor < 0 && divisor > Int36.MINVAL) { + divisor = -divisor; + bNegLo = 1 - bNegLo; + } + + if (this.isNegative()) { + this.negate(); + bNegHi = 1; bNegLo = 1 - bNegLo; + } + + var value = this.value; + var extended = this.extended || 0; + + if (value < 0) { + value += Int36.BIT36; + } + + if (!divisor) { + this.error |= Int36.ERROR.DIVZERO; + } + else if (divisor <= extended) { + this.error |= Int36.ERROR.OVERFLOW; + } + else { + var result = 0, bit = 1; + var bitsDiv = Int36.setBits(this.bitsDiv, divisor, 0); + var bitsRem = Int36.setBits(this.bitsRem, value, extended); + + while (Int36.cmpBits(bitsRem, bitsDiv) > 0) { + Int36.addBits(bitsDiv, bitsDiv); + bit += bit; + } + + do { + if (Int36.cmpBits(bitsRem, bitsDiv) >= 0) { + Int36.subBits(bitsRem, bitsDiv); + result += bit; + } + Int36.shrBits(bitsDiv); + bit /= 2; + } while (bit >= 1); + + if (DEBUG && !(result < Int36.BIT36 && !bitsRem[1])) { + console.log("divExtended() assertion failure"); + } + + this.value = result; + this.extended = null; + this.remainder = bitsRem[0]; + + if (bNegLo && this.value && this.value > Int36.MINVAL) { + this.value = -this.value; + } + if (bNegHi && this.remainder && this.remainder > Int36.MINVAL) { + this.remainder = -this.remainder; + } + } + return this.value; + } + + /** + * isNegative() + * + * @return {boolean} + */ + isNegative() + { + return (this.extended < 0 || this.extended == null && this.value < 0); + } + + /** + * negate() + * + * Converts the current value to its two's complement. If we were dealing with 8-bit values: + * + * Original Two's One's + * ------- ----- ----- + * -128 -128 127 + * -127 127 126 + * ... ... ... + * -1 1 0 + * 0 0 -1 + * 1 -1 -2 + * ... ... ... + * 126 -126 -127 + * 127 -127 -128 + * + * you can see that, when performing two's complement, MINVAL and ZERO are not modified. + * + * However, in our happy little world, since JavaScript numbers CAN represent both positive and negative + * MINVAL values, we don't need to exclude MINVAL from the conversion. + */ + negate() + { + this.error = Int36.ERROR.NONE; + /* + * Perform two's complement on the value. + */ + if (this.value /* && this.value > Int36.MINVAL */) { + this.value = -this.value; + } + if (this.extended == null) { + /* + * Set extended to match the sign of the value. + */ + this.extended = (this.value < 0? -1 : 0); + } + else if (this.value) { + /* + * Perform one's complement on the extended value. + */ + this.extended = -this.extended - 1; + } + else { + /* + * Perform two's complement on the extended value. + */ + if (this.extended /* && this.extended > Int36.MINVAL */) { + this.extended = -this.extended; + } + } + } + + /** + * addBits(bitsDst, bitsSrc) + * + * Adds bitsSrc to bitsDst. + * + * @param {Array.} bitsDst + * @param {Array.} bitsSrc + */ + static addBits(bitsDst, bitsSrc) + { + bitsDst[0] += bitsSrc[0]; + bitsDst[1] += bitsSrc[1]; + if (bitsDst[0] >= Int36.BIT36) { + bitsDst[0] %= Int36.BIT36; + bitsDst[1]++; + } + } + + /** + * cmpBits(bitsDst, bitsSrc) + * + * Compares bitsDst to bitsSrc, by computing bitsDst - bitsSrc. + * + * @param {Array.} bitsDst + * @param {Array.} bitsSrc + * @return {number} > 0 if bitsDst > bitsSrc, == 0 if bitsDst == bitsSrc, < 0 if bitsDst < bitsSrc + */ + static cmpBits(bitsDst, bitsSrc) + { + var result = bitsDst[1] - bitsSrc[1]; + if (!result) result = bitsDst[0] - bitsSrc[0]; + return result; + } + + /** + * setBits(bits, lo, hi) + * + * @param {Array.} bits + * @param {number} lo + * @param {number} hi + * @return {Array.} + */ + static setBits(bits, lo, hi) + { + bits[0] = lo; + bits[1] = hi; + return bits; + } + + /** + * shrBits(bitsDst) + * + * Shifts bitsDst right one bit. + * + * @param {Array.} bitsDst + */ + static shrBits(bitsDst) + { + if (bitsDst[1] % 2) { + bitsDst[0] += Int36.BIT36; + } + bitsDst[0] = Math.trunc(bitsDst[0] / 2); + bitsDst[1] = Math.trunc(bitsDst[1] / 2); + } + + /** + * subBits(bitsDst, bitsSrc) + * + * Subtracts bitsSrc from bitsDst. + * + * @param {Array.} bitsDst + * @param {Array.} bitsSrc + */ + static subBits(bitsDst, bitsSrc) + { + bitsDst[0] -= bitsSrc[0]; + bitsDst[1] -= bitsSrc[1]; + if (bitsDst[0] < 0) { + bitsDst[0] += Int36.BIT36; + bitsDst[1]--; + } + } + + /** + * octal(value) + * + * @param {number} value + * @return {string} + */ + static octal(value) + { + if (value < 0) value += Int36.BIT36; + return ("00000000000" + value.toString(8)).slice(-12); + } + + /** + * validate(num) + * + * @param {number} num + * @return {number} + */ + static validate(num) + { + var value = Math.trunc(num) % Int36.BIT36; + if (value > Int36.MAXVAL) { + value -= Int36.BIT36; + } else if (value < Int36.MINVAL) { + value += Int36.BIT36; + } + if (DEBUG && num !== value) { + console.log("Int36.validate(" + num + " out of range, truncated to " + value + ")"); + } + return value; + } +} + +Int36.ERROR = { + NONE: 0x0, + OVERFLOW: 0x1, + UNDERFLOW: 0x2, + DIVZERO: 0x4 +}; + +Int36.BIT18 = Math.pow(2, 18); // 262,144 +Int36.BIT36 = Math.pow(2, 36); // 68,719,476,736 + +Int36.MAXVAL = Math.pow(2, 35) - 1; // 34,359,738,367 +Int36.MINVAL = -Math.pow(2, 35); // -34,359,738,368 + +if (NODE) module.exports = Int36; From 798fae189861cb66b19945e7d42ff46064758381 Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Wed, 15 Feb 2017 16:35:15 -0800 Subject: [PATCH 11/29] Updated Int36 comments --- modules/shared/lib/int36.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/shared/lib/int36.js b/modules/shared/lib/int36.js index 04bc25499..debcab99a 100644 --- a/modules/shared/lib/int36.js +++ b/modules/shared/lib/int36.js @@ -60,13 +60,15 @@ class Int36 { * 2) a single 36-bit value, with an optional 36-bit extension * 3) nothing (initial value will be zero) * - * We guarantee that an Int36 value will be (and always remain) an unsigned value within this range: + * All internal Int36 values (ie, the value and any extension) are unsigned values in the range: * * 0 <= i <= Math.pow(2, 36) - 1 * * The lower bound is ZERO and the upper bound is Int36.MAXVAL. Whenever an Int36 value should be - * interpreted as a signed value, call isNegative() to check the sign bit (bit 35) and call negate() - * as appropriate. + * interpreted as a signed value, values above Int36.MAXSIGNED (ie, values with bit 35 set) should be + * converted to their signed counterpart by subtracting the value from Int36.BIT36 (aka MAXVAL + 1); + * the easiest way to do that is call isNegative() to check the sign bit and then call negate() as + * appropriate. * * NOTE: We use modern bit numbering, where bit 0 is the right-most (least-significant) bit and * bit 35 is the left-most bit. This is opposite of the PDP-10 convention, which defined bit 0 as the From 40311e82b21070f53c8b684cd80f4b8f9c3e15ea Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Wed, 15 Feb 2017 22:20:07 -0800 Subject: [PATCH 12/29] Added Int36 overflow/underflow detection --- modules/shared/lib/int36.js | 53 ++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/modules/shared/lib/int36.js b/modules/shared/lib/int36.js index debcab99a..dcac1a7c4 100644 --- a/modules/shared/lib/int36.js +++ b/modules/shared/lib/int36.js @@ -38,7 +38,7 @@ var DEBUG = true; * @property {number} error * * The 'value' property stores the 36-bit value as an unsigned integer. When the value - * should be interpreted as a signed quantity, subtract BIT36 whenever value > MAXSIGNED. + * should be interpreted as a signed quantity, subtract BIT36 whenever value > MAXPOS. * * The 'extended' property stores an additional 36 bits of data from a multiplication; * it must also be set prior to a division. Internally, it will be set to null whenever the @@ -65,7 +65,7 @@ class Int36 { * 0 <= i <= Math.pow(2, 36) - 1 * * The lower bound is ZERO and the upper bound is Int36.MAXVAL. Whenever an Int36 value should be - * interpreted as a signed value, values above Int36.MAXSIGNED (ie, values with bit 35 set) should be + * interpreted as a signed value, values above Int36.MAXPOS (ie, values with bit 35 set) should be * converted to their signed counterpart by subtracting the value from Int36.BIT36 (aka MAXVAL + 1); * the easiest way to do that is call isNegative() to check the sign bit and then call negate() as * appropriate. @@ -204,7 +204,14 @@ class Int36 { } /** - * truncate(result) + * truncate(result, original) + * + * The range of valid results (0 - MAXVAL) is divided into two equal sub-ranges: 0 to MAXPOS, + * where the sign bit is zero (the bottom range), and MAXPOS+1 to MAXVAL (the top range), where + * the sign bit is one. During a single arithmetic operation, the result can "wrap around" from + * the bottom range to the top, or from the top range to the bottom, but it's an overflow/underflow + * condition ONLY if the result "wraps across" the midpoint between the two ranges, producing an + * unnaturally small delta (<= MAXPOS). * * NOTE: This function's job is to truncate the result of an operation to 36-bit accuracy, * not to remove any fractional portion that might also exist. If an operation could have produced @@ -212,13 +219,15 @@ class Int36 { * * @this {Int36} * @param {number} result + * @param {number} [original] * @return {number} */ - truncate(result) + truncate(result, original) { if (DEBUG && result !== Math.trunc(result)) { console.log("Int36.truncate(" + result + " is not an integer)"); } + this.extended = null; this.remainder = null; this.error = Int36.ERROR.NONE; @@ -226,7 +235,15 @@ class Int36 { if (result < 0) { result += Int36.BIT36; } + result %= Int36.BIT36; + + if (original !== undefined && (result > Int36.MAXPOS) != (original > Int36.MAXPOS)) { + var delta = result - original; + if (Math.abs(delta) <= Int36.MAXPOS) { + this.error |= (delta > 0? Int36.ERROR.OVERFLOW : Int36.ERROR.UNDERFLOW); + } + } return result; } @@ -238,7 +255,7 @@ class Int36 { */ add(i36) { - this.value = this.truncate(this.value + i36.value); + this.value = this.truncate(this.value + i36.value, this.value); } /** @@ -249,7 +266,7 @@ class Int36 { */ addNum(num) { - this.value = this.truncate(this.value + Int36.validate(num)); + this.value = this.truncate(this.value + Int36.validate(num), this.value); } /** @@ -260,7 +277,7 @@ class Int36 { */ sub(i36) { - this.value = this.truncate(this.value - i36.value); + this.value = this.truncate(this.value - i36.value, this.value); } /** @@ -271,7 +288,7 @@ class Int36 { */ subNum(num) { - this.value = this.truncate(this.value - Int36.validate(num)); + this.value = this.truncate(this.value - Int36.validate(num), this.value); } /** @@ -312,12 +329,12 @@ class Int36 { var fNeg = false, extended; var n1 = this.value, n2 = value; - if (n1 > Int36.MAXSIGNED) { + if (n1 > Int36.MAXPOS) { n1 = Int36.BIT36 - n1; fNeg = !fNeg; } - if (n2 > Int36.MAXSIGNED) { + if (n2 > Int36.MAXPOS) { n2 = Int36.BIT36 - n2; fNeg = !fNeg; } @@ -391,7 +408,7 @@ class Int36 { */ var bNegLo = 0, bNegHi = 0; - if (divisor > Int36.MAXSIGNED) { + if (divisor > Int36.MAXPOS) { divisor = Int36.BIT36 - divisor; bNegLo = 1 - bNegLo; } @@ -437,10 +454,10 @@ class Int36 { this.extended = null; this.remainder = bitsRem[0]; - if (bNegLo && this.value && this.value > Int36.MINSIGNED) { + if (bNegLo && this.value) { this.value = Int36.BIT36 - this.value; } - if (bNegHi && this.remainder && this.remainder > Int36.MINSIGNED) { + if (bNegHi && this.remainder) { this.remainder = Int36.BIT36 - this.remainder; } } @@ -454,7 +471,7 @@ class Int36 { */ isNegative() { - return (this.extended > Int36.MAXSIGNED || this.extended == null && this.value > Int36.MAXSIGNED); + return (this.extended > Int36.MAXPOS || this.extended == null && this.value > Int36.MAXPOS); } /** @@ -466,7 +483,7 @@ class Int36 { /* * Set extended to match the sign of the (negated) value. */ - this.extended = (this.value > Int36.MAXSIGNED? 0 : Int36.MAXVAL); + this.extended = (this.value > Int36.MAXPOS? 0 : Int36.MAXVAL); } else if (this.value) { /* @@ -590,7 +607,7 @@ class Int36 { */ static validate(num) { - if (num < 0 && num >= Int36.MINSIGNED) { + if (num < 0 && num >= Int36.MINNEG) { num += Int36.BIT36; } var value = Math.trunc(Math.abs(num)) % Int36.BIT36; @@ -611,8 +628,8 @@ Int36.ERROR = { Int36.BIT18 = Math.pow(2, 18); // 262,144 Int36.BIT36 = Math.pow(2, 36); // 68,719,476,736 -Int36.MAXSIGNED = Math.pow(2, 35) - 1; // 34,359,738,367 -Int36.MINSIGNED = -Math.pow(2, 35); // -34,359,738,368 +Int36.MAXPOS = Math.pow(2, 35) - 1; // 34,359,738,367 +Int36.MINNEG = -Math.pow(2, 35); // -34,359,738,368 Int36.MAXVAL = Math.pow(2, 36) - 1; // 68,719,476,735 From 4d94b5812b02f95d53be43a933b3c0dedecb9445 Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Wed, 15 Feb 2017 22:27:03 -0800 Subject: [PATCH 13/29] Cleaned up divExtended() --- modules/shared/lib/int36.js | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/modules/shared/lib/int36.js b/modules/shared/lib/int36.js index dcac1a7c4..65085f9ba 100644 --- a/modules/shared/lib/int36.js +++ b/modules/shared/lib/int36.js @@ -398,24 +398,16 @@ class Int36 { */ divExtended(divisor) { - /* - * dividend divisor quotient remainder - * -------- ------- -------- --------- - * + + -> + + - * + - -> - + - * - + -> - - - * - - -> + - - */ - var bNegLo = 0, bNegHi = 0; + var fNegQ = false, fNegR = false; if (divisor > Int36.MAXPOS) { divisor = Int36.BIT36 - divisor; - bNegLo = 1 - bNegLo; + fNegQ = !fNegQ; } if (this.isNegative()) { this.negate(); - bNegHi = 1; bNegLo = 1 - bNegLo; + fNegR = true; fNegQ = !fNegQ; } var value = this.value; @@ -454,10 +446,10 @@ class Int36 { this.extended = null; this.remainder = bitsRem[0]; - if (bNegLo && this.value) { + if (fNegQ && this.value) { this.value = Int36.BIT36 - this.value; } - if (bNegHi && this.remainder) { + if (fNegR && this.remainder) { this.remainder = Int36.BIT36 - this.remainder; } } From 048a10aac681bc62677861b88ec53736f2ec624e Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Wed, 15 Feb 2017 23:43:23 -0800 Subject: [PATCH 14/29] Fixed toDecimal() for large extended Int36 values (although there are still some very large values that trigger an error) --- modules/shared/bin/int36 | 35 ++++++++++++++++++++++------------- modules/shared/lib/int36.js | 5 +++++ 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/modules/shared/bin/int36 b/modules/shared/bin/int36 index 2d2045612..075e7834d 100644 --- a/modules/shared/bin/int36 +++ b/modules/shared/bin/int36 @@ -61,14 +61,18 @@ function test(sCmd, fREPL) var sOp = aTokens[0]; var sNum1 = aTokens[1] && aTokens[1].replace(/,/g, ''); var sNum2 = aTokens[2]; + if (!sNum2) { + sNum2 = sNum1; + sNum1 = undefined; + } - var i36Op = new Int36(+sNum1); + var i36Op = new Int36(+sNum2, +sNum1); - if (sNum1 != null) console.log(sOp + " " + dumpInt36(i36Op)); + if (sNum2 != null) console.log(sOp + " " + dumpInt36(i36Op)); switch(sOp) { case "set": - i36Reg = new Int36(+sNum1, +sNum2); + i36Reg = new Int36(i36Op); break; case "add": @@ -87,19 +91,12 @@ function test(sCmd, fREPL) i36Reg.div(i36Op); break; - case "dec": - console.log("dec " + i36Reg.toDecimal()); - return true; - - case "print": - break; - default: console.log("unrecognized command: " + sCmd); return false; } - if (sOp != "set") console.log(" = " + dumpInt36(i36Reg)); + console.log(" = " + dumpInt36(i36Reg)); return true; } @@ -128,13 +125,11 @@ test("add 0"); for (let i = 0; i <= 12; i++) { test("set 34,000,000,000"); test("mul " + Math.pow(8, i)); - test("dec"); } for (let i = 0; i <= 12; i++) { test("set -34,000,000,000"); test("mul " + Math.pow(8, i)); - test("dec"); } test("set 100"); @@ -143,6 +138,20 @@ test("div 3"); test("set 4001"); test("div -5"); +test("set 0o037777777777 0o777777777777"); + +// test("set 0o047777777777 0o777777777777"); +// test("set 0o057777777777 0o777777777777"); +// test("set 0o067777777777 0o777777777777"); +// test("set 0o077777777777 0o777777777777"); +// test("set 0o177777777777 0o777777777777"); +// test("set 0o277777777777 0o777777777777"); +// test("set 0o377777777777 0o777777777777"); +// test("set 0o477777777777 0o777777777777"); +// test("set 0o577777777777 0o777777777777"); +// test("set 0o677777777777 0o777777777777"); +// test("set 0o777777777777 0o777777777777"); + repl.start({ prompt: "int36> ", input: process.stdin, diff --git a/modules/shared/lib/int36.js b/modules/shared/lib/int36.js index 65085f9ba..2b3f02fd1 100644 --- a/modules/shared/lib/int36.js +++ b/modules/shared/lib/int36.js @@ -151,6 +151,10 @@ class Int36 { } do { var quotient = i36Tmp.div(i36Div); + if (i36Tmp.error) { + s = "error"; + break; + } var nMinDigits = (quotient? 10 : 1); i36Tmp.value = i36Tmp.remainder; do { @@ -158,6 +162,7 @@ class Int36 { s = String.fromCharCode(0x30 + i36Tmp.remainder) + s; } while (--nMinDigits > 0 || i36Tmp.value); i36Tmp.value = quotient; + i36Tmp.extended = 0; } while (i36Tmp.value); if (fNeg) s = '-' + s; return s; From 4de50acdf18819464bf682de8be1ae3df8f9e42a Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Thu, 16 Feb 2017 14:05:05 -0800 Subject: [PATCH 15/29] Added alternate Int36 addition/subtraction overflow/underflow detection logic as a verification check --- modules/shared/bin/int36 | 23 +++++----- modules/shared/lib/int36.js | 88 +++++++++++++++++++++++++++++++------ 2 files changed, 85 insertions(+), 26 deletions(-) diff --git a/modules/shared/bin/int36 b/modules/shared/bin/int36 index 075e7834d..739b76123 100644 --- a/modules/shared/bin/int36 +++ b/modules/shared/bin/int36 @@ -139,18 +139,17 @@ test("set 4001"); test("div -5"); test("set 0o037777777777 0o777777777777"); - -// test("set 0o047777777777 0o777777777777"); -// test("set 0o057777777777 0o777777777777"); -// test("set 0o067777777777 0o777777777777"); -// test("set 0o077777777777 0o777777777777"); -// test("set 0o177777777777 0o777777777777"); -// test("set 0o277777777777 0o777777777777"); -// test("set 0o377777777777 0o777777777777"); -// test("set 0o477777777777 0o777777777777"); -// test("set 0o577777777777 0o777777777777"); -// test("set 0o677777777777 0o777777777777"); -// test("set 0o777777777777 0o777777777777"); +test("set 0o047777777777 0o777777777777"); +test("set 0o057777777777 0o777777777777"); +test("set 0o067777777777 0o777777777777"); +test("set 0o077777777777 0o777777777777"); +test("set 0o177777777777 0o777777777777"); +test("set 0o277777777777 0o777777777777"); +test("set 0o377777777777 0o777777777777"); +test("set 0o477777777777 0o777777777777"); +test("set 0o577777777777 0o777777777777"); +test("set 0o677777777777 0o777777777777"); +test("set 0o777777777777 0o777777777777"); repl.start({ prompt: "int36> ", diff --git a/modules/shared/lib/int36.js b/modules/shared/lib/int36.js index 2b3f02fd1..e0088509a 100644 --- a/modules/shared/lib/int36.js +++ b/modules/shared/lib/int36.js @@ -78,11 +78,11 @@ class Int36 { * * -Math.pow(2, 53) <= i <= Math.pow(2, 53) * - * it seems unwise to ever permit the internal value to creep outside the 36-bit range, because + * it seems unwise to ever permit our internal values to creep outside the 36-bit range, because * floating-point operations will drop least-significant bits in favor of most-significant bits when a * result becomes too large, which is the opposite of what integer operations traditionally do. There * might be some optimization benefits to performing our internal 36-bit truncation "lazily", but at - * least initially, I prefer to truncate the results of all operations immediately. + * least initially, I prefer to truncate() the results of all 36-bit arithmetic operations immediately. * * Most of the Int36 operations come in two flavors: those that accept numbers, and those that accept * another Int36. The latter are more efficient, because (as just explained) an Int36's internal value @@ -209,25 +209,53 @@ class Int36 { } /** - * truncate(result, original) + * truncate(result, operand, fSub) * * The range of valid results (0 - MAXVAL) is divided into two equal sub-ranges: 0 to MAXPOS, * where the sign bit is zero (the bottom range), and MAXPOS+1 to MAXVAL (the top range), where * the sign bit is one. During a single arithmetic operation, the result can "wrap around" from * the bottom range to the top, or from the top range to the bottom, but it's an overflow/underflow - * condition ONLY if the result "wraps across" the midpoint between the two ranges, producing an + * error ONLY if the result "wraps across" the midpoint between the two ranges, producing an * unnaturally small delta (<= MAXPOS). * + * This can be confirmed independently by examining the sign bits (BIT35) of the original value + * (V), the operand (O), the result (R), as well as two intermediate calculations, VR = (V ^ R) + * and OR = (O ^ R), and a final calculation: E = (VR & OR). In the case of subtraction (fSub), + * OV replaces OR. + * + * V O R VR OR E + * - - - -- -- - + * 0 0 0 0 0 0 + * 0 0 1 1 1 1 (adding positive to positive yielded negative: overflow) + * 0 1 0 0 1 0 + * 0 1 1 1 0 0 + * 1 0 0 1 0 0 + * 1 0 1 0 1 0 + * 1 1 0 1 1 1 (adding negative to negative yielded positive: underflow) + * 1 1 1 0 0 0 + * + * V O R VR OV E + * - - - -- -- - + * 0 0 0 0 0 0 + * 0 0 1 1 0 0 + * 0 1 0 0 1 0 + * 0 1 1 1 1 1 (subtracting negative from positive yielded negative: overflow) + * 1 0 0 1 1 1 (subtracting positive from negative yielded positive: underflow) + * 1 0 1 0 1 0 + * 1 1 0 1 0 0 + * 1 1 1 0 0 0 + * * NOTE: This function's job is to truncate the result of an operation to 36-bit accuracy, * not to remove any fractional portion that might also exist. If an operation could have produced * a non-integer result (eg, div()), it's the caller's responsibility to deal with that first. * * @this {Int36} * @param {number} result - * @param {number} [original] + * @param {number} [operand] + * @param {boolean} [fSub] (true if operand was subtracted) * @return {number} */ - truncate(result, original) + truncate(result, operand, fSub) { if (DEBUG && result !== Math.trunc(result)) { console.log("Int36.truncate(" + result + " is not an integer)"); @@ -243,11 +271,33 @@ class Int36 { result %= Int36.BIT36; - if (original !== undefined && (result > Int36.MAXPOS) != (original > Int36.MAXPOS)) { - var delta = result - original; - if (Math.abs(delta) <= Int36.MAXPOS) { - this.error |= (delta > 0? Int36.ERROR.OVERFLOW : Int36.ERROR.UNDERFLOW); + /* + * We don't actually need to know what the operand was to determine overflow or underflow + * for addition or subtraction, just the original value (this.value) the new value (result). + * + * We do, however, need to know the operand if we want to confirm our error calculation using + * the truth table above, which requires examining the sign bits of all the inputs and outputs. + */ + if (operand !== undefined) { + /* + * Calculating V, R, O, and E as described above is somewhat tedious, because bits + * above bit 31 cannot be accessed directly; we shift all the sign bits down to bit 0 + * using division first. We don't need to truncate the results, because the subsequent + * bit-wise operations perform truncation automatically. + */ + var e = 0; + if (DEBUG) { + var v = this.value / Int36.BIT35, r = result / Int36.BIT35, o = operand / Int36.BIT35; + e = ((v ^ r) & (o ^ (fSub? v : r))) & 1; } + if ((result > Int36.MAXPOS) != (this.value > Int36.MAXPOS)) { + var delta = result - this.value; + if (Math.abs(delta) <= Int36.MAXPOS) { + this.error |= (delta > 0 ? Int36.ERROR.OVERFLOW : Int36.ERROR.UNDERFLOW); + if (DEBUG && (delta > 0) != !(e & v)) e = 0; + } + } + if (DEBUG && (!this.error) != (!e)) console.log("overflow mismatch"); } return result; } @@ -260,7 +310,7 @@ class Int36 { */ add(i36) { - this.value = this.truncate(this.value + i36.value, this.value); + this.value = this.truncate(this.value + i36.value, i36.value); } /** @@ -271,7 +321,8 @@ class Int36 { */ addNum(num) { - this.value = this.truncate(this.value + Int36.validate(num), this.value); + num = Int36.validate(num); + this.value = this.truncate(this.value + num, num); } /** @@ -282,7 +333,7 @@ class Int36 { */ sub(i36) { - this.value = this.truncate(this.value - i36.value, this.value); + this.value = this.truncate(this.value - i36.value, i36.value, true); } /** @@ -293,7 +344,8 @@ class Int36 { */ subNum(num) { - this.value = this.truncate(this.value - Int36.validate(num), this.value); + num = Int36.validate(num); + this.value = this.truncate(this.value - num, num, true); } /** @@ -599,11 +651,18 @@ class Int36 { /** * validate(num) * + * This ensures that any incoming (external) 36-bit values conform to our internal requirements. + * * @param {number} num * @return {number} */ static validate(num) { + /* + * Although it's expected that most callers will supply unsigned 36-bit values, we're nice about + * converting any signed values to their unsigned (two's complement) counterpart, provided they are + * within the acceptable range. Any signed values outside that range will be dealt with afterward. + */ if (num < 0 && num >= Int36.MINNEG) { num += Int36.BIT36; } @@ -623,6 +682,7 @@ Int36.ERROR = { }; Int36.BIT18 = Math.pow(2, 18); // 262,144 +Int36.BIT35 = Math.pow(2, 35); // 34,359,738,368 (aka the sign bit) Int36.BIT36 = Math.pow(2, 36); // 68,719,476,736 Int36.MAXPOS = Math.pow(2, 35) - 1; // 34,359,738,367 From 80756041f359154c80f0b81c8cd040da76d6861a Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Thu, 16 Feb 2017 15:58:50 -0800 Subject: [PATCH 16/29] Int36.toDecimal() now supports any value up to 72 bits --- modules/shared/lib/int36.js | 112 ++++++++++++++++++++---------------- 1 file changed, 61 insertions(+), 51 deletions(-) diff --git a/modules/shared/lib/int36.js b/modules/shared/lib/int36.js index e0088509a..720478c43 100644 --- a/modules/shared/lib/int36.js +++ b/modules/shared/lib/int36.js @@ -101,6 +101,8 @@ class Int36 { constructor(obj, extended) { this.set(obj, extended); + this.bitsRes = [0, 0]; + this.bitsPow = [0, 0]; this.bitsDiv = [0, 0]; this.bitsRem = [0, 0]; } @@ -144,26 +146,26 @@ class Int36 { { var s = "", fNeg = false; var i36Div = new Int36(10000000000); + var i36Rem = new Int36(); var i36Tmp = new Int36(this.value, this.extended); if (!fUnsigned && i36Tmp.isNegative()) { i36Tmp.negate(); fNeg = true; } do { - var quotient = i36Tmp.div(i36Div); + i36Tmp.div(i36Div); if (i36Tmp.error) { s = "error"; break; } + var quotient = i36Tmp.value || i36Tmp.extended; var nMinDigits = (quotient? 10 : 1); - i36Tmp.value = i36Tmp.remainder; + i36Rem.set(i36Tmp.remainder); do { - i36Tmp.divNum(10); - s = String.fromCharCode(0x30 + i36Tmp.remainder) + s; - } while (--nMinDigits > 0 || i36Tmp.value); - i36Tmp.value = quotient; - i36Tmp.extended = 0; - } while (i36Tmp.value); + i36Rem.divNum(10); + s = String.fromCharCode(0x30 + i36Rem.remainder) + s; + } while (--nMinDigits > 0 || i36Rem.value); + } while (quotient); if (fNeg) s = '-' + s; return s; } @@ -427,11 +429,10 @@ class Int36 { * * @this {Int36} * @param {Int36} i36 - * @return {number} (quotient) */ div(i36) { - return this.divExtended(i36.value); + this.divExtended(i36.value); } /** @@ -439,11 +440,10 @@ class Int36 { * * @this {Int36} * @param {number} num - * @return {number} (quotient) */ divNum(num) { - return this.divExtended(Int36.validate(num)); + this.divExtended(Int36.validate(num)); } /** @@ -451,10 +451,23 @@ class Int36 { * * @this {Int36} * @param {number} divisor - * @return {number} (quotient) */ divExtended(divisor) { + /* + * NOTE: A divisor of zero is always a bad idea; however, we no longer require the divisor to be + * GREATER than the extended portion of the dividend, because toDecimal() needs to be able to divide + * large 72-bit values by 10,000,000,000 without worrying about the size of the resulting quotient. + * + * For callers that can only support 36-bit results, they can either perform their own preliminary + * check of the divisor against any dividend extension, or they can simply allow all divisions to + * proceed, check for an extended quotient afterward, and record the appropriate error. + */ + if (!divisor) { + this.error |= Int36.ERROR.DIVZERO; + return; + } + var fNegQ = false, fNegR = false; if (divisor > Int36.MAXPOS) { @@ -467,50 +480,47 @@ class Int36 { fNegR = true; fNegQ = !fNegQ; } - var value = this.value; - var extended = this.extended || 0; + var bitsRes = Int36.setBits(this.bitsRes, 0, 0); + var bitsPow = Int36.setBits(this.bitsPow, 1, 0); + var bitsDiv = Int36.setBits(this.bitsDiv, divisor, 0); + var bitsRem = Int36.setBits(this.bitsRem, this.value, this.extended || 0); - if (!divisor) { - this.error |= Int36.ERROR.DIVZERO; + while (Int36.cmpBits(bitsRem, bitsDiv) > 0) { + Int36.addBits(bitsDiv, bitsDiv); + Int36.addBits(bitsPow, bitsPow); } - else if (divisor <= extended) { - this.error |= Int36.ERROR.OVERFLOW; + do { + if (Int36.cmpBits(bitsRem, bitsDiv) >= 0) { + Int36.subBits(bitsRem, bitsDiv); + Int36.addBits(bitsRes, bitsPow); + } + Int36.shrBits(bitsDiv); + Int36.shrBits(bitsPow); + } while (bitsPow[0] || bitsPow[1]); + + /* + * NOTE: We no longer require bitsRes[1] to be zero (that is, we no longer require the quotient + * to fit within the 36 bits of bitsRes[0]) because toDecimal() needs to be able to divide large + * 72-bit values by 10,000,000,000 without worrying about the size of the resulting quotient. + * + * We do, however, still expect remainders to fit within the 36 bits of bitsRem[0], because our + * divisors are limited to 36 bits as well. + */ + if (DEBUG && bitsRem[1]) { + console.log("divExtended() assertion failure"); } - else { - var result = 0, bit = 1; - var bitsDiv = Int36.setBits(this.bitsDiv, divisor, 0); - var bitsRem = Int36.setBits(this.bitsRem, value, extended); - while (Int36.cmpBits(bitsRem, bitsDiv) > 0) { - Int36.addBits(bitsDiv, bitsDiv); - bit += bit; - } + this.value = bitsRes[0]; + this.extended = bitsRes[1]; + this.remainder = bitsRem[0]; - do { - if (Int36.cmpBits(bitsRem, bitsDiv) >= 0) { - Int36.subBits(bitsRem, bitsDiv); - result += bit; - } - Int36.shrBits(bitsDiv); - bit /= 2; - } while (bit >= 1); - - if (DEBUG && !(result < Int36.BIT36 && !bitsRem[1])) { - console.log("divExtended() assertion failure"); - } - - this.value = result; - this.extended = null; - this.remainder = bitsRem[0]; - - if (fNegQ && this.value) { - this.value = Int36.BIT36 - this.value; - } - if (fNegR && this.remainder) { - this.remainder = Int36.BIT36 - this.remainder; - } + if (fNegQ) { + this.negate(); + } + + if (fNegR && this.remainder) { + this.remainder = Int36.BIT36 - this.remainder; } - return this.value; } /** From 688fa635c805bc750ef0155caef92a9f3423ae93 Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Fri, 17 Feb 2017 11:52:36 -0800 Subject: [PATCH 17/29] Clarified the operation of negate(), added an explicit extend(), and made toDecimal() more robust (in case we end up with goofy values) --- modules/shared/lib/int36.js | 75 ++++++++++++++++++++++++------------- 1 file changed, 50 insertions(+), 25 deletions(-) diff --git a/modules/shared/lib/int36.js b/modules/shared/lib/int36.js index 720478c43..1e2908453 100644 --- a/modules/shared/lib/int36.js +++ b/modules/shared/lib/int36.js @@ -148,13 +148,22 @@ class Int36 { var i36Div = new Int36(10000000000); var i36Rem = new Int36(); var i36Tmp = new Int36(this.value, this.extended); + if (!fUnsigned && i36Tmp.isNegative()) { i36Tmp.negate(); fNeg = true; } + + var nMaxDivs = 3; do { i36Tmp.div(i36Div); - if (i36Tmp.error) { + /* + * In a perfect world, there would be no errors, because all Int36 calculations would + * involve positive values within their respective ranges, any remainder would always be less + * than the divisor, and the entire process would complete within 3 divisions. But until + * then, let's make sure we don't produce garbage or spin our wheels. + */ + if (i36Tmp.error || i36Tmp.remainder >= 10000000000 || !nMaxDivs--) { s = "error"; break; } @@ -166,6 +175,7 @@ class Int36 { s = String.fromCharCode(0x30 + i36Rem.remainder) + s; } while (--nMinDigits > 0 || i36Rem.value); } while (quotient); + if (fNeg) s = '-' + s; return s; } @@ -419,9 +429,7 @@ class Int36 { this.value = this.truncate(value); this.extended = this.truncate(extended); - if (fNeg) { - this.negate(); - } + if (fNeg) this.negate(); } /** @@ -449,20 +457,20 @@ class Int36 { /** * divExtended(divisor) * + * We disallow a divisor of zero; however, we no longer disallow a divisor smaller than the than + * the extended portion of the dividend, even though such a divisor would produce a quotient larger + * than 36 bits. Instead, we support extended quotients, because some of our internal functions + * (eg, toDecimal()) require it. + * + * For callers that can only handle 36-bit quotients, they can either perform their own preliminary + * check of the divisor against any dividend extension, or they can simply allow all divisions to + * proceed, check for an extended quotient afterward, and report the appropriate error. + * * @this {Int36} * @param {number} divisor */ divExtended(divisor) { - /* - * NOTE: A divisor of zero is always a bad idea; however, we no longer require the divisor to be - * GREATER than the extended portion of the dividend, because toDecimal() needs to be able to divide - * large 72-bit values by 10,000,000,000 without worrying about the size of the resulting quotient. - * - * For callers that can only support 36-bit results, they can either perform their own preliminary - * check of the divisor against any dividend extension, or they can simply allow all divisions to - * proceed, check for an extended quotient afterward, and record the appropriate error. - */ if (!divisor) { this.error |= Int36.ERROR.DIVZERO; return; @@ -480,10 +488,12 @@ class Int36 { fNegR = true; fNegQ = !fNegQ; } + this.extend(); + var bitsRes = Int36.setBits(this.bitsRes, 0, 0); var bitsPow = Int36.setBits(this.bitsPow, 1, 0); var bitsDiv = Int36.setBits(this.bitsDiv, divisor, 0); - var bitsRem = Int36.setBits(this.bitsRem, this.value, this.extended || 0); + var bitsRem = Int36.setBits(this.bitsRem, this.value, this.extended); while (Int36.cmpBits(bitsRem, bitsDiv) > 0) { Int36.addBits(bitsDiv, bitsDiv); @@ -499,12 +509,7 @@ class Int36 { } while (bitsPow[0] || bitsPow[1]); /* - * NOTE: We no longer require bitsRes[1] to be zero (that is, we no longer require the quotient - * to fit within the 36 bits of bitsRes[0]) because toDecimal() needs to be able to divide large - * 72-bit values by 10,000,000,000 without worrying about the size of the resulting quotient. - * - * We do, however, still expect remainders to fit within the 36 bits of bitsRem[0], because our - * divisors are limited to 36 bits as well. + * Since divisors are limited to 36-bit values, something's wrong if we have an extended remainder. */ if (DEBUG && bitsRem[1]) { console.log("divExtended() assertion failure"); @@ -514,15 +519,26 @@ class Int36 { this.extended = bitsRes[1]; this.remainder = bitsRem[0]; - if (fNegQ) { - this.negate(); - } + if (fNegQ) this.negate(); if (fNegR && this.remainder) { this.remainder = Int36.BIT36 - this.remainder; } } + /** + * extend() + */ + extend() + { + /* + * Set extended to match the sign of value (if not already set). + */ + if (this.extended == null) { + this.extended = (this.value > Int36.MAXPOS? Int36.MAXVAL : 0); + } + } + /** * isNegative() * @@ -535,12 +551,19 @@ class Int36 { /** * negate() + * + * negate() MUST automatically extend the value, because the two's complement of the most negative + * number (MINNEG) still has its sign bit set, so we must rely on the sign of the extended value to + * compensate. + * + * This is handled below by setting extended first, based on the opposite of the value's current sign; + * we could negate extended AFTER negating value, but then we'd need a special test for the MINNEG value. */ negate() { if (this.extended == null) { /* - * Set extended to match the sign of the (negated) value. + * Set extended to the OPPOSITE of the current value. */ this.extended = (this.value > Int36.MAXPOS? 0 : Int36.MAXVAL); } @@ -559,7 +582,9 @@ class Int36 { /* * Perform two's complement on the value. */ - if (this.value) this.value = Int36.BIT36 - this.value; + if (this.value) { + this.value = Int36.BIT36 - this.value; + } this.error = Int36.ERROR.NONE; } From 689adca2a77b42731829b37b9664dd21a40435c4 Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Fri, 17 Feb 2017 12:15:48 -0800 Subject: [PATCH 18/29] Comment updates --- modules/shared/lib/int36.js | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/modules/shared/lib/int36.js b/modules/shared/lib/int36.js index 1e2908453..286970646 100644 --- a/modules/shared/lib/int36.js +++ b/modules/shared/lib/int36.js @@ -232,8 +232,11 @@ class Int36 { * * This can be confirmed independently by examining the sign bits (BIT35) of the original value * (V), the operand (O), the result (R), as well as two intermediate calculations, VR = (V ^ R) - * and OR = (O ^ R), and a final calculation: E = (VR & OR). In the case of subtraction (fSub), - * OV replaces OR. + * and OR = (O ^ R), and a final calculation, E = (VR & OR). If E is set, then overflow (V == 0) + * or underflow (V == 1) occurred. + * + * In the case of subtraction (when fSub is true), consult the second table, which replaces OR + * with OV (O ^ V). * * V O R VR OR E * - - - -- -- - @@ -309,7 +312,7 @@ class Int36 { if (DEBUG && (delta > 0) != !(e & v)) e = 0; } } - if (DEBUG && (!this.error) != (!e)) console.log("overflow mismatch"); + if (DEBUG && (!this.error) != (!e)) console.log("overflow inconsistency"); } return result; } @@ -386,9 +389,9 @@ class Int36 { * mulExtended(value) * * To support 72-bit results, we perform the multiplication process as you would "by hand", - * treating each of the operands to be multiplied as two 2-digit numbers, where each digit is - * an 18-bit number (base 2^18). Each individual multiplication of these 18-bit "digits" - * will produce a result within 2^36, well within JavaScript integer accuracy. + * treating the operands to be multiplied as two 2-digit numbers, where each digit is an 18-bit + * number (base 2^18). Each individual multiplication of these 18-bit "digits" will produce + * a result within 2^36, well within JavaScript integer accuracy. * * @this {Int36} * @param {number} value @@ -457,13 +460,13 @@ class Int36 { /** * divExtended(divisor) * - * We disallow a divisor of zero; however, we no longer disallow a divisor smaller than the than - * the extended portion of the dividend, even though such a divisor would produce a quotient larger + * We disallow a divisor of zero; however, we no longer disallow a divisor smaller than the + * extended portion of the dividend, even though such a divisor would produce a quotient larger * than 36 bits. Instead, we support extended quotients, because some of our internal functions * (eg, toDecimal()) require it. * * For callers that can only handle 36-bit quotients, they can either perform their own preliminary - * check of the divisor against any dividend extension, or they can simply allow all divisions to + * check of the divisor against any extended dividend, or they can simply allow all divisions to * proceed, check for an extended quotient afterward, and report the appropriate error. * * @this {Int36} @@ -528,12 +531,11 @@ class Int36 { /** * extend() + * + * Set the extended field to match the sign of the value (if not already set). */ extend() { - /* - * Set extended to match the sign of value (if not already set). - */ if (this.extended == null) { this.extended = (this.value > Int36.MAXPOS? Int36.MAXVAL : 0); } @@ -696,7 +698,7 @@ class Int36 { /* * Although it's expected that most callers will supply unsigned 36-bit values, we're nice about * converting any signed values to their unsigned (two's complement) counterpart, provided they are - * within the acceptable range. Any signed values outside that range will be dealt with afterward. + * within the acceptable range. Any values outside that range will be dealt with afterward. */ if (num < 0 && num >= Int36.MINNEG) { num += Int36.BIT36; From cff039f6a5c139aa436bda84f62d4219f7432ced Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Fri, 17 Feb 2017 13:31:17 -0800 Subject: [PATCH 19/29] Added the reduce() function, to reduce 72-bit extended values to 36-bit values, magnitude permitting --- modules/shared/lib/int36.js | 138 +++++++++++++++++++++++++++++++++--- 1 file changed, 129 insertions(+), 9 deletions(-) diff --git a/modules/shared/lib/int36.js b/modules/shared/lib/int36.js index 286970646..7351c45f8 100644 --- a/modules/shared/lib/int36.js +++ b/modules/shared/lib/int36.js @@ -30,6 +30,93 @@ var DEBUG = true; +/* + From the "PDP-10 System Reference Manual", May 1968, p. 1-4: + + 1.1 NUMBER SYSTEM + + The program can interpret a data word as a 36-digit, unsigned binary number, or the left and right + halves of a word can be taken as separate 18-bit numbers. The PDP-10 repertoire includes instructions + that effectively add or subtract one from both halves of a word, so the right half can be used for + address modification when the word is addressed as an index register, while the left half is used to + keep a control count. + + The standard arithmetic instructions in the PDP-10 use twos complement, fixed point conventions to do + binary arithmetic. In a word used as a number, bit 0 (the leftmost bit) represents the sign, 0 for positive, + 1 for negative. In a positive number the remaining 35 bits are the magnitude in ordinary binary notation. + The negative of a number is obtained by taking its twos complement. If x is an n-digit binary number, its + twos complement is 2^n - x, and its ones complement is (2^n - 1) - x, or equivalently (2^n - x) - 1. + + Subtracting a number from 2^n - 1 (ie, from all 1s) is equivalent to performing the logical complement, + ie changing all 0s to 1s and all 1s to 0s. Therefore, to form the twos complement one takes the logical + complement (usually referred to merely as the complement) of the entire word including the sign, and adds + 1 to the result. In a negative number the sign bit is 1, and the remaining bits are the twos complement + of the magnitude. + + Zero is represented by a word containing all 0s. Complementing this number produces all 1s, and adding + 1 to that produces all 0s again. Hence there is only one zero representation and its sign is positive. + Since the numbers are symmetrical in magnitude about a single zero representation, all even numbers both + positive and negative end in 0, all odd numbers in 1 (a number all 1s represents -1). But since there are + the same number of positive and negative numbers and zero is positive, there is one more negative number + than there are nonzero positive numbers. This is the most negative number and it cannot be produced by + negating any positive number (its octal representation is 400000 000000 and its magnitude is one greater + than the largest positive number). + + + If ones complements were used for negatives one could read a negative number by attaching significance + to the as instead of the 1s. In twos complement notation each negative number is one greater than the + complement of the positive number of the same magnitude, so one can read a negative number by attaching + significance to the rightmost 1 and attaching significance to the 0s at the left of it (the negative number + of largest magnitude has a 1 in only the sign position). In a negative integer, 1s may be discarded at the + left, just as leading 0s may be dropped in a positive integer. In a negative fraction, 0s may be discarded + at the right. So long as only 0s are discarded, the number remains in twos complement form because it + still has a 1 that possesses significance; but if a portion including the rightmost 1 is discarded, the + remaining part of the fraction is now a ones complement. + + The computer does not keep track of a binary point - the programmer must adopt a point convention and shift + the magnitude of the result to conform to the convention used. Two common conventions are to regard a number + as an integer (binary point at the right) or as a proper fraction (binary point at the left); in these two + cases the range of numbers represented by a single word is -2^35 to 2^35 - 1, or -1 to 1 - 2^35. Since + multiplication and division make use of double length numbers, there are special instructions for performing + these operations with integral operands. + + SIDEBAR: Multiplication produces a double length product, and the programmer must remember that discarding + the low order part of a double length negative leaves the high order part in correct twos complement form + only if the low order part is null. + + ... + + 2.5 FIXED POINT ARITHMETIC + + For fixed point arithmetic the PDP-10 has instructions for arithmetic shifting (which is essentially + multiplication by a power of 2) as well as for performing addition, subtraction, multiplication and division + of numbers in fixed point format [§ 1.1]. In such numbers the position of the binary point is arbitrary + (the programmer may adopt any point convention). The add and subtract instructions involve only single length + numbers, whereas multiply supplies a double length product, and divide uses a double length dividend. The high + and low order words respectively of a double length fixed point number are in accumulators A and A+1 (mod 20), + where the magnitude is the 70-bit string in bits 1-35 of the two words and the signs of the two are identical. + There are also integer multiply and divide instructions that involve only single length numbers and are + especially suited for handling smaller integers, particularly those of eighteen bits or less such as addresses + (of course they can be used for small fractions as well provided the programmer keeps track of the binary point). + For convenience in the following, all operands are assumed to be integers (binary point at the right). + + The processor has four flags, Overflow, Carry 0, Carry 1 and No Divide, that indicate when the magnitude of a + number is or would be larger than can be accommodated. Carry 0 and Carry 1 actually detect carries out of bits + 0 and 1 in certain instructions that employ fixed point arithmetic operations: the add and subtract instructions + treated here, the move instructions that produce the negative or magnitude of the word moved [§ 2.2], and the + arithmetic test instructions that increment or decrement the test word [§ 2.7]. In these instructions an + incorrect result is indicated - and the Overflow flag set - if the carries are different, ie if there is a carry + into the sign but not out of it, or vice versa. The Overflow flag is also set by No Divide being set, which + means the processor has failed to perform a division because the magnitude of the dividend is greater than or + equal to that of the divisor, or in integer divide, simply that the divisor is zero. In other overflow cases + only Overflow itself is set: these include too large a product in multiplication, and loss of significant bits + in left arithmetic shifting. + + SIDEBAR: Overflow is determined directly from the carries, not from the carry flags, as their states may reflect + events in previous instructions. + */ + + /** * @class Int36 * @property {number} value @@ -37,17 +124,21 @@ var DEBUG = true; * @property {number|null} remainder * @property {number} error * - * The 'value' property stores the 36-bit value as an unsigned integer. When the value - * should be interpreted as a signed quantity, subtract BIT36 whenever value > MAXPOS. + * The 'value' property stores the 36-bit value as an unsigned integer. When the value should be + * interpreted as a signed quantity, subtract BIT36 whenever value > MAXPOS. * - * The 'extended' property stores an additional 36 bits of data from a multiplication; - * it must also be set prior to a division. Internally, it will be set to null whenever the - * current value is not extended. + * The 'extended' property stores an additional 36 bits of data from a multiplication, and can also + * provide an additional 36 bits of data to a division. Internally, it should be null whenever the + * value is not extended. * - * The 'remainder' property stores the remainder from the last division. You should assume it - * will be set to null by any other operation. + * The 'remainder' property stores the remainder from the last division. You should assume it will + * be set to null by any other operation. * * The 'error' property records any error(s) from the last operation. + * + * NOTE: What we call extended Int36 values DEC refers to as "double length numbers", and they refer + * to the 'extended' portion as the "low order part" and the 'value' portion as the "high order part", + * presumably because they number the left-most significant bit 0. */ class Int36 { @@ -389,7 +480,7 @@ class Int36 { * mulExtended(value) * * To support 72-bit results, we perform the multiplication process as you would "by hand", - * treating the operands to be multiplied as two 2-digit numbers, where each digit is an 18-bit + * treating the operands to be multiplied as two 2-digit numbers, where each "digit" is an 18-bit * number (base 2^18). Each individual multiplication of these 18-bit "digits" will produce * a result within 2^36, well within JavaScript integer accuracy. * @@ -524,6 +615,8 @@ class Int36 { if (fNegQ) this.negate(); + this.reduce(); + if (fNegR && this.remainder) { this.remainder = Int36.BIT36 - this.remainder; } @@ -532,7 +625,7 @@ class Int36 { /** * extend() * - * Set the extended field to match the sign of the value (if not already set). + * Sets extended to match the sign of value (if not already set). */ extend() { @@ -541,6 +634,33 @@ class Int36 { } } + /** + * reduce() + * + * Unsets extended if it's superfluous; opposite of extend(). + * + * It's worth noting DEC's SIDEBAR comment (from above): + * + * Multiplication produces a double length product, and the programmer must remember that discarding + * the low order part ['extended'] of a double length negative leaves the high order part ['value'] in + * correct twos complement form only if the low order part ['extended'] is null. + * + * is not entirely applicable to us. For one thing, when value is MINNEG and extended is ZERO, we interpret + * that extended value as 34,359,738,368; we cannot simply eliminate the extended portion, otherwise value + * would be interpreted as -34,359,738,368. + * + * DEC can say that because each of the words in a PDP-10 double-length product contains its own sign bit, + * resulting in only 70 bits of magnitude. However, we don't store our extended (double-length) results that + * way, so be aware of these mismatches in both terminology and format when converting an Int36 to/from PDP-10 + * registers/memory. + */ + reduce() + { + if (this.extended == 0 && this.value <= Int36.MAXPOS || this.extended == Int36.MAXVAL && this.value > Int36.MAXPOS) { + this.extended = null; + } + } + /** * isNegative() * From 55bb0252b79e8dc3f932677fd28351a206c583bf Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Fri, 17 Feb 2017 13:38:34 -0800 Subject: [PATCH 20/29] Be more specific about detected errors --- modules/shared/lib/int36.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/modules/shared/lib/int36.js b/modules/shared/lib/int36.js index 7351c45f8..e475ddefd 100644 --- a/modules/shared/lib/int36.js +++ b/modules/shared/lib/int36.js @@ -296,7 +296,17 @@ class Int36 { if (this.remainder) { s += ':' + Int36.octal(this.remainder); } - if (DEBUG && this.error) s += " error 0x" + this.error.toString(16); + if (DEBUG && this.error) { + if (this.error & Int36.ERROR.OVERFLOW) { + s += " overflow"; + } + if (this.error & Int36.ERROR.UNDERFLOW) { + s += " underflow"; + } + if (this.error & Int36.ERROR.DIVZERO) { + s += " divide-by-zero"; + } + } return s; } From 242fa1545ce81257a55da3dbada910e50be7f172 Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Fri, 17 Feb 2017 15:53:46 -0800 Subject: [PATCH 21/29] Put back some redirects that external sites are using --- devices/pcx86/machine/5150/cga/64kb/donkey/README.md | 1 + devices/pcx86/machine/5160/cga/256kb/win101/README.md | 3 +++ disks/pcx86/games/infocom/zork1/machine.md | 2 ++ disks/pcx86/tools/microsoft/masm/1.00/README.md | 2 ++ disks/pcx86/windows/1.01/README.md | 1 + disks/pcx86/windows/3.10/README.md | 2 ++ 6 files changed, 11 insertions(+) diff --git a/devices/pcx86/machine/5150/cga/64kb/donkey/README.md b/devices/pcx86/machine/5150/cga/64kb/donkey/README.md index c77528d8e..59c6a0531 100644 --- a/devices/pcx86/machine/5150/cga/64kb/donkey/README.md +++ b/devices/pcx86/machine/5150/cga/64kb/donkey/README.md @@ -6,6 +6,7 @@ redirect_from: - /configs/pc/machines/5150/cga/64kb/donkey/ - /configs/pc/machines/5150/cga/64kb/donkey/machine.xml/ - /demos/pc/donkey/ + - /devices/pc/machine/5150/cga/64kb/donkey/ machines: - type: pcx86 id: ibm5150 diff --git a/devices/pcx86/machine/5160/cga/256kb/win101/README.md b/devices/pcx86/machine/5160/cga/256kb/win101/README.md index d49578a44..cd534da0b 100644 --- a/devices/pcx86/machine/5160/cga/256kb/win101/README.md +++ b/devices/pcx86/machine/5160/cga/256kb/win101/README.md @@ -3,10 +3,13 @@ layout: page title: IBM PC XT (Model 5160, 256Kb, 10Mb Drive, Color Display) running Windows 1.01 permalink: /devices/pcx86/machine/5160/cga/256kb/win101/ redirect_from: + - /configs/pc/machines/5160/cga/win101/ - /configs/pc/machines/5160/cga/256kb/win101/ - /configs/pc/machines/5160/cga/256kb/win101/machine.xml/ - /demos/pc/cga-win101/xt-cga-win101.xml/ - /demos/pc/cga-win101/ + - /devices/pc/machine/5160/cga/256kb/win101/ + - /devices/pc/machine/5160/cga/256kb/win101/machine.xml/ machines: - type: pcx86 id: ibm5160 diff --git a/disks/pcx86/games/infocom/zork1/machine.md b/disks/pcx86/games/infocom/zork1/machine.md index 1c76a96ca..bfa8443f1 100644 --- a/disks/pcx86/games/infocom/zork1/machine.md +++ b/disks/pcx86/games/infocom/zork1/machine.md @@ -2,6 +2,8 @@ layout: page title: "IBM PC (Model 5150) running Zork I" permalink: /disks/pcx86/games/infocom/zork1/ +redirect_from: + - /disks/pc/games/infocom/zork1/ machines: - type: pcx86 id: ibm5150-zork1 diff --git a/disks/pcx86/tools/microsoft/masm/1.00/README.md b/disks/pcx86/tools/microsoft/masm/1.00/README.md index b9ca0554b..8d72e957f 100644 --- a/disks/pcx86/tools/microsoft/masm/1.00/README.md +++ b/disks/pcx86/tools/microsoft/masm/1.00/README.md @@ -2,6 +2,8 @@ layout: page title: Microsoft Macro Assembler 1.00 permalink: /disks/pcx86/tools/microsoft/masm/1.00/ +redirect_from: + - /disks/pc/tools/microsoft/masm/1.00/ --- Microsoft Macro Assembler 1.00 diff --git a/disks/pcx86/windows/1.01/README.md b/disks/pcx86/windows/1.01/README.md index 30b2b1649..eb1637a7c 100644 --- a/disks/pcx86/windows/1.01/README.md +++ b/disks/pcx86/windows/1.01/README.md @@ -6,6 +6,7 @@ redirect_from: - /configs/pc/machines/5160/ega/640kb/win101/ - /devices/pc/machine/5160/ega/640kb/win101/ - /devices/pc/machine/5160/ega/640kb/win101/debugger/ + - /disks/pc/windows/1.01/ machines: - type: pcx86 id: ibm5160 diff --git a/disks/pcx86/windows/3.10/README.md b/disks/pcx86/windows/3.10/README.md index 79d055221..61436062b 100644 --- a/disks/pcx86/windows/3.10/README.md +++ b/disks/pcx86/windows/3.10/README.md @@ -2,6 +2,8 @@ layout: page title: Microsoft Windows 3.10 permalink: /disks/pcx86/windows/3.10/ +redirect_from: + - /disks/pc/windows/3.10/ machines: - type: pcx86 id: ibm5170-win310 From 3f65a1b770d290f400f68b1a3eedb39028c3d8a2 Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Sat, 18 Feb 2017 08:50:24 -0800 Subject: [PATCH 22/29] Simplify Int36 bit handling (only affects division) --- modules/shared/lib/int36.js | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/modules/shared/lib/int36.js b/modules/shared/lib/int36.js index e475ddefd..7529005d7 100644 --- a/modules/shared/lib/int36.js +++ b/modules/shared/lib/int36.js @@ -192,10 +192,6 @@ class Int36 { constructor(obj, extended) { this.set(obj, extended); - this.bitsRes = [0, 0]; - this.bitsPow = [0, 0]; - this.bitsDiv = [0, 0]; - this.bitsRem = [0, 0]; } /** @@ -594,10 +590,10 @@ class Int36 { this.extend(); - var bitsRes = Int36.setBits(this.bitsRes, 0, 0); - var bitsPow = Int36.setBits(this.bitsPow, 1, 0); - var bitsDiv = Int36.setBits(this.bitsDiv, divisor, 0); - var bitsRem = Int36.setBits(this.bitsRem, this.value, this.extended); + var bitsRes = [0, 0]; + var bitsPow = [1, 0]; + var bitsDiv = [divisor, 0]; + var bitsRem = [this.value, this.extended]; while (Int36.cmpBits(bitsRem, bitsDiv) > 0) { Int36.addBits(bitsDiv, bitsDiv); @@ -754,21 +750,6 @@ class Int36 { return result; } - /** - * setBits(bits, lo, hi) - * - * @param {Array.} bits - * @param {number} lo - * @param {number} hi - * @return {Array.} - */ - static setBits(bits, lo, hi) - { - bits[0] = lo; - bits[1] = hi; - return bits; - } - /** * shrBits(bitsDst) * From 7770a6bffefa38d79d10f0e46d776f7d129a6b19 Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Sat, 18 Feb 2017 09:17:08 -0800 Subject: [PATCH 23/29] Added an "early out" for Int36 division --- modules/shared/lib/int36.js | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/modules/shared/lib/int36.js b/modules/shared/lib/int36.js index 7529005d7..7ce14574d 100644 --- a/modules/shared/lib/int36.js +++ b/modules/shared/lib/int36.js @@ -590,6 +590,24 @@ class Int36 { this.extend(); + /* + * Initialize the four double-length 72-bit "bits" values we need for the division process. + * + * The process involves shifting the divisor left 1 bit (ie, doubling it) until it equals + * or exceeds the dividend, and then repeatedly subtracting the divisor from the dividend and + * shifting the divisor right 1 bit until the divisor is "exhausted" (no bits left), with an + * "early out" if the dividend gets "exhausted" first. + * + * Note that each element of these "bits" arrays is a 36-bit value, so it's rarely a good idea + * to use bit-wise operators on them, because those would operate on only the low 32 bits. + * Stick with the "bits" worker functions I've created, and trust your JavaScript engine to + * inline/optimize the code. + * + * TODO: Profile this code to determine if individual variables (eg, bitsResLo and bitsResHi) + * instead of 2-element arrays is faster and/or less impactful on garbage collection. I prefer + * both the simplified syntax of arrays as well as their extensibility if we ever want/need + * to go beyond 72 bits. + */ var bitsRes = [0, 0]; var bitsPow = [1, 0]; var bitsDiv = [divisor, 0]; @@ -603,10 +621,11 @@ class Int36 { if (Int36.cmpBits(bitsRem, bitsDiv) >= 0) { Int36.subBits(bitsRem, bitsDiv); Int36.addBits(bitsRes, bitsPow); + if (Int36.zeroBits(bitsRem)) break; } Int36.shrBits(bitsDiv); Int36.shrBits(bitsPow); - } while (bitsPow[0] || bitsPow[1]); + } while (!Int36.zeroBits(bitsPow)); /* * Since divisors are limited to 36-bit values, something's wrong if we have an extended remainder. @@ -784,6 +803,18 @@ class Int36 { } } + /** + * zeroBits(bits) + * + * True if bits are all zero, false otherwise. + * + * @param {Array.} bits + */ + static zeroBits(bits) + { + return !bits[0] && !bits[1]; + } + /** * octal(value) * From c1ab8918bd8861e5acc3489ee8db4e784195b1f1 Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Sat, 18 Feb 2017 09:23:45 -0800 Subject: [PATCH 24/29] Make Int36 toDecimal() more paranoid (we want perfect output, or no output at all) --- modules/shared/lib/int36.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/modules/shared/lib/int36.js b/modules/shared/lib/int36.js index 7ce14574d..c531a93fa 100644 --- a/modules/shared/lib/int36.js +++ b/modules/shared/lib/int36.js @@ -245,10 +245,10 @@ class Int36 { do { i36Tmp.div(i36Div); /* - * In a perfect world, there would be no errors, because all Int36 calculations would - * involve positive values within their respective ranges, any remainder would always be less - * than the divisor, and the entire process would complete within 3 divisions. But until - * then, let's make sure we don't produce garbage or spin our wheels. + * In a perfect world, there would be no errors, because all Int36 calculations at + * this point should be positive values, the remainder should always be less than the + * divisor, and the entire process should complete within 3 divisions. But until then, + * let's make sure we don't produce garbage or spin our wheels. */ if (i36Tmp.error || i36Tmp.remainder >= 10000000000 || !nMaxDivs--) { s = "error"; @@ -259,6 +259,11 @@ class Int36 { i36Rem.set(i36Tmp.remainder); do { i36Rem.divNum(10); + if (i36Rem.remainder < 0 || i36Rem.remainder > 9) { + quotient = 0; + s = "error"; + break; + } s = String.fromCharCode(0x30 + i36Rem.remainder) + s; } while (--nMinDigits > 0 || i36Rem.value); } while (quotient); From acdf89ec5458b94d5c575e569f7fda9b48ab53bb Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Sat, 18 Feb 2017 09:28:43 -0800 Subject: [PATCH 25/29] Make Int36 toDecimal() more paranoid (we want perfect output, or no output at all) --- modules/shared/lib/int36.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/shared/lib/int36.js b/modules/shared/lib/int36.js index c531a93fa..119441c1d 100644 --- a/modules/shared/lib/int36.js +++ b/modules/shared/lib/int36.js @@ -240,15 +240,15 @@ class Int36 { i36Tmp.negate(); fNeg = true; } - + /* + * Conversion of any 72-bit value should take no more than 3 divisions by 10,000,000,000. + */ var nMaxDivs = 3; do { i36Tmp.div(i36Div); /* - * In a perfect world, there would be no errors, because all Int36 calculations at - * this point should be positive values, the remainder should always be less than the - * divisor, and the entire process should complete within 3 divisions. But until then, - * let's make sure we don't produce garbage or spin our wheels. + * In a perfect world, there would be no errors, because all calculations at this point + * are within known bounds. But let's make sure we don't produce garbage or spin our wheels. */ if (i36Tmp.error || i36Tmp.remainder >= 10000000000 || !nMaxDivs--) { s = "error"; From b1d16c979a21f6f9f64f8aeef30904d3bf4e6fb9 Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Sat, 18 Feb 2017 09:36:47 -0800 Subject: [PATCH 26/29] Removed obsolete version of Int36 (using signed integers internally); sticking with unsigned integers internally not only simplifies some of the existing logic, but will make it easier to add logical operations later (eg, AND, OR, XOR, etc) --- modules/shared/lib/int36s.js | 652 ----------------------------------- 1 file changed, 652 deletions(-) delete mode 100644 modules/shared/lib/int36s.js diff --git a/modules/shared/lib/int36s.js b/modules/shared/lib/int36s.js deleted file mode 100644 index a8b119d54..000000000 --- a/modules/shared/lib/int36s.js +++ /dev/null @@ -1,652 +0,0 @@ -/** - * @fileoverview Support for 36-bit integers (using signed JavaScript numbers) - * @author Jeff Parsons (@jeffpar) - * @copyright © Jeff Parsons 2012-2017 - * - * This file is part of PCjs, a computer emulation software project at . - * - * PCjs is free software: you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation, either version 3 - * of the License, or (at your option) any later version. - * - * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without - * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with PCjs. If not, - * see . - * - * You are required to include the above copyright notice in every modified copy of this work - * and to display that copyright notice when the software starts running; see COPYRIGHT in - * . - * - * Some PCjs files also attempt to load external resource files, such as character-image files, - * ROM files, and disk image files. Those external resource files are not considered part of PCjs - * for purposes of the GNU General Public License, and the author does not claim any copyright - * as to their contents. - */ - -"use strict"; - -var DEBUG = true; - -/** - * @class Int36 - * @property {number} value - * @property {number|null} extended - * @property {number|null} remainder - * @property {number} error - * - * The 'value' property stores the 36-bit value as a signed integer, meaning if the sign bit - * (bit 35) is set, we store the corresponding negative (two's complement) value. While there - * might be some slight benefits to always storing the 36-bit value as an unsigned quantity - * and negating it "on demand", I like having JavaScript's native representation match the - * emulated value. - * - * The 'extended' property stores an additional 36 bits of data from a multiplication; - * it must also be set prior to a division. Internally, it will be set to null whenever the - * current value is not extended. - * - * The 'remainder' property stores the remainder from the last division. You should assume it - * will be set to null by any other operation. - * - * The 'error' property records any error(s) from the last operation. - */ - -class Int36 { - /** - * Int36(obj, extended) - * - * The constructor, which simply calls set(), creates an Int36 from either: - * - * 1) another Int36 - * 2) a single (signed) 36-bit value, with an optional 36-bit extension - * 3) nothing (initial value will be zero) - * - * We guarantee that an Int36 value will be (and will always remain) a signed value within this range: - * - * -Math.pow(2, 35) <= i <= Math.pow(2, 35) - 1 - * - * Those lower and upper bounds are defined as Int36.MINVAL and Int36.MAXVAL. The sign of an Int36 - * value is determined by (and should always match) its highest bit (bit 35). - * - * NOTE: We use modern bit numbering, where bit 0 is the right-most (least-significant) bit and - * bit 35 is the left-most bit. This is opposite of the PDP-10 convention, which defined bit 0 as the - * left-most bit and bit 35 as the right-most bit. - * - * Although the integer precision of JavaScript floating-point (IEEE 754 double-precision) numbers is: - * - * -Math.pow(2, 53) <= i <= Math.pow(2, 53) - * - * it seems unwise to ever permit the internal value to creep outside the signed 36-bit range, because - * floating-point operations will drop least-significant bits in favor of most-significant bits when a - * result becomes too large, which is the opposite of what integer operations traditionally do. There - * might be some optimization benefits to performing our internal 36-bit truncation "lazily", but at - * least initially, I prefer to truncate the results of all operations immediately. - * - * Most of the Int36 operations come in two flavors: those that accept numbers, and those that accept - * another Int36. The latter are more efficient, because (as just explained) an Int36's internal value - * should always be in range, whereas external numbers could be out of range OR have a fractional value - * OR be something else entirely (NaN, Infinity, -Infinity, undefined, etc), so numeric inputs are - * always passed through the static validate() function. - * - * We could eliminate the two flavors and check each parameter's type, like we do in the constructor, - * but constructor calls are infrequent (if they're not, you're doing something wrong), whereas Int36-only - * operations should be as fast and unchecked as possible. - * - * @this {Int36} - * @param {Int36|number} [obj] (if omitted, the default is zero) - * @param {number|null} [extended] - */ - constructor(obj, extended) - { - this.set(obj, extended); - this.bitsDiv = [0, 0]; - this.bitsRem = [0, 0]; - } - - /** - * set(obj, extended) - * - * @this {Int36} - * @param {Int36|number} [obj] (if omitted, the default is zero) - * @param {number|null} [extended] - */ - set(obj = 0, extended) - { - if (obj instanceof Int36) { - this.value = obj.value; - this.extended = obj.extended; - this.remainder = obj.remainder; - } - else { - this.value = Int36.validate(obj || 0); - this.extended = null; - /* - * NOTE: Surprisingly, isNaN(null) is false, whereas isNaN(undefined) is true. Go figure. - */ - if (extended != null && !isNaN(extended)) { - this.extended = Int36.validate(extended); - } - this.remainder = null; - } - this.error = Int36.ERROR.NONE; - } - - /** - * toDecimal(fUnsigned) - * - * @this {Int36} - * @param {boolean} fUnsigned - * @return {string} - */ - toDecimal(fUnsigned) - { - var s = "", fNeg = false; - var i36Div = new Int36(10000000000); - var i36Tmp = new Int36(this.value, this.extended); - if (!fUnsigned && i36Tmp.isNegative()) { - i36Tmp.negate(); - fNeg = true; - } - do { - var quotient = i36Tmp.div(i36Div); - var nMinDigits = (quotient? 10 : 1); - i36Tmp.value = i36Tmp.remainder; - do { - i36Tmp.divNum(10); - s = String.fromCharCode(0x30 + i36Tmp.remainder) + s; - } while (--nMinDigits > 0 || i36Tmp.value); - i36Tmp.value = quotient; - } while (i36Tmp.value); - if (fNeg) s = '-' + s; - return s; - } - - /** - * toString(radix, fUnsigned) - * - * @this {Int36} - * @param {number} [radix] (default is 10) - * @param {boolean} [fUnsigned] (default is signed for radix 10, unsigned for any other radix) - * @return {string} - */ - toString(radix = 10, fUnsigned) - { - if (radix == 10) { - return this.toDecimal(fUnsigned); - } - - var value = this.value; - var extended = this.extended; - - if (radix == 8) { - var s = Int36.octal(value); - if (extended) { - s = Int36.octal(extended) + ',' + s; - } - if (this.remainder) { - s += ':' + Int36.octal(this.remainder); - } - if (DEBUG && this.error) s += " error 0x" + this.error.toString(16); - return s; - } - - if (value < 0) { - value += Int36.BIT36; - } - if (extended != null) { - if (extended < 0) extended += Int36.BIT36; - /* - * TODO: Need a radix-independent solution for these extended (up to 72-bit) values, - * because after 52 bits, JavaScript will start dropping least-significant bits. Until - * then, you're better off sticking with octal (see above). - */ - value = extended * Int36.BIT36 + value; - } - return value.toString(radix); - } - - /** - * truncate(result) - * - * NOTE: This function's job is to truncate the result of an operation to 36-bit accuracy, - * not to remove any fractional portion that might also exist. If an operation could have produced - * a non-integer result (eg, div()), it's the caller's responsibility to deal with that first. - * - * @this {Int36} - * @param {number} result - * @return {number} - */ - truncate(result) - { - if (DEBUG && result !== Math.trunc(result)) { - console.log("Int36.truncate(" + result + " is not an integer)"); - } - this.extended = null; - this.remainder = null; - this.error = Int36.ERROR.NONE; - if (result > Int36.MAXVAL) { - result %= Int36.BIT36; - if (result > Int36.MAXVAL) result -= Int36.BIT36; - this.error |= Int36.ERROR.OVERFLOW; - } else if (result < Int36.MINVAL) { - result %= Int36.BIT36; - if (result < Int36.MINVAL) result += Int36.BIT36; - this.error |= Int36.ERROR.UNDERFLOW; - } - return result; - } - - /** - * add(i36) - * - * @this {Int36} - * @param {Int36} i36 - */ - add(i36) - { - this.value = this.truncate(this.value + i36.value); - } - - /** - * addNum(num) - * - * @this {Int36} - * @param {number} num - */ - addNum(num) - { - this.value = this.truncate(this.value + Int36.validate(num)); - } - - /** - * sub(i36) - * - * @this {Int36} - * @param {Int36} i36 - */ - sub(i36) - { - this.value = this.truncate(this.value - i36.value); - } - - /** - * subNum(num) - * - * @this {Int36} - * @param {number} num - */ - subNum(num) - { - this.value = this.truncate(this.value - Int36.validate(num)); - } - - /** - * mul(i36) - * - * @this {Int36} - * @param {Int36} i36 - */ - mul(i36) - { - this.mulExtended(i36.value); - } - - /** - * mulNum(num) - * - * @this {Int36} - * @param {number} num - */ - mulNum(num) - { - this.mulExtended(Int36.validate(num)); - } - - /** - * mulExtended(value) - * - * To support 72-bit results, we perform the multiplication process as you would "by hand", - * treating each of the operands to be multiplied as two 2-digit numbers, where each digit is - * an 18-bit number (base 2^18). Each individual multiplication of these 18-bit "digits" - * will produce a result within 2^36, well within JavaScript integer accuracy. - * - * @this {Int36} - * @param {number} value - */ - mulExtended(value) - { - var fNeg = false, extended; - var n1 = this.value, n2 = value; - - if (n1 < 0) { - if (n1) n1 = -n1; - fNeg = !fNeg; - } - - if (n2 < 0) { - if (n2) n2 = -n2; - fNeg = !fNeg; - } - - if (n1 < Int36.BIT18 && n2 < Int36.BIT18) { - value = n1 * n2; - extended = 0; - } - else { - var n1d1 = (n1 % Int36.BIT18); - var n1d2 = Math.trunc(n1 / Int36.BIT18); - var n2d1 = (n2 % Int36.BIT18); - var n2d2 = Math.trunc(n2 / Int36.BIT18); - - var m1d1 = n1d1 * n2d1; - var m1d2 = (n1d2 * n2d1) + Math.trunc(m1d1 / Int36.BIT18); - extended = Math.trunc(m1d2 / Int36.BIT18); - m1d2 = (m1d2 % Int36.BIT18) + (n1d1 * n2d2); - value = (m1d2 * Int36.BIT18) + (m1d1 % Int36.BIT18); - extended += Math.trunc(m1d2 / Int36.BIT18) + (n1d2 * n2d2); - } - - this.value = this.truncate(value); - this.extended = this.truncate(extended); - - if (fNeg) this.negate(); - } - - /** - * div(i36) - * - * @this {Int36} - * @param {Int36} i36 - * @return {number} (quotient) - */ - div(i36) - { - return this.divExtended(i36.value); - } - - /** - * divNum(num) - * - * @this {Int36} - * @param {number} num - * @return {number} (quotient) - */ - divNum(num) - { - return this.divExtended(Int36.validate(num)); - } - - /** - * divExtended(divisor) - * - * @this {Int36} - * @param {number} divisor - * @return {number} (quotient) - */ - divExtended(divisor) - { - /* - * dividend divisor quotient remainder - * -------- ------- -------- --------- - * + + -> + + - * + - -> - + - * - + -> - - - * - - -> + - - */ - var bNegLo = 0, bNegHi = 0; - - if (divisor < 0 && divisor > Int36.MINVAL) { - divisor = -divisor; - bNegLo = 1 - bNegLo; - } - - if (this.isNegative()) { - this.negate(); - bNegHi = 1; bNegLo = 1 - bNegLo; - } - - var value = this.value; - var extended = this.extended || 0; - - if (value < 0) { - value += Int36.BIT36; - } - - if (!divisor) { - this.error |= Int36.ERROR.DIVZERO; - } - else if (divisor <= extended) { - this.error |= Int36.ERROR.OVERFLOW; - } - else { - var result = 0, bit = 1; - var bitsDiv = Int36.setBits(this.bitsDiv, divisor, 0); - var bitsRem = Int36.setBits(this.bitsRem, value, extended); - - while (Int36.cmpBits(bitsRem, bitsDiv) > 0) { - Int36.addBits(bitsDiv, bitsDiv); - bit += bit; - } - - do { - if (Int36.cmpBits(bitsRem, bitsDiv) >= 0) { - Int36.subBits(bitsRem, bitsDiv); - result += bit; - } - Int36.shrBits(bitsDiv); - bit /= 2; - } while (bit >= 1); - - if (DEBUG && !(result < Int36.BIT36 && !bitsRem[1])) { - console.log("divExtended() assertion failure"); - } - - this.value = result; - this.extended = null; - this.remainder = bitsRem[0]; - - if (bNegLo && this.value && this.value > Int36.MINVAL) { - this.value = -this.value; - } - if (bNegHi && this.remainder && this.remainder > Int36.MINVAL) { - this.remainder = -this.remainder; - } - } - return this.value; - } - - /** - * isNegative() - * - * @return {boolean} - */ - isNegative() - { - return (this.extended < 0 || this.extended == null && this.value < 0); - } - - /** - * negate() - * - * Converts the current value to its two's complement. If we were dealing with 8-bit values: - * - * Original Two's One's - * ------- ----- ----- - * -128 -128 127 - * -127 127 126 - * ... ... ... - * -1 1 0 - * 0 0 -1 - * 1 -1 -2 - * ... ... ... - * 126 -126 -127 - * 127 -127 -128 - * - * you can see that, when performing two's complement, MINVAL and ZERO are not modified. - * - * However, in our happy little world, since JavaScript numbers CAN represent both positive and negative - * MINVAL values, we don't need to exclude MINVAL from the conversion. - */ - negate() - { - this.error = Int36.ERROR.NONE; - /* - * Perform two's complement on the value. - */ - if (this.value /* && this.value > Int36.MINVAL */) { - this.value = -this.value; - } - if (this.extended == null) { - /* - * Set extended to match the sign of the value. - */ - this.extended = (this.value < 0? -1 : 0); - } - else if (this.value) { - /* - * Perform one's complement on the extended value. - */ - this.extended = -this.extended - 1; - } - else { - /* - * Perform two's complement on the extended value. - */ - if (this.extended /* && this.extended > Int36.MINVAL */) { - this.extended = -this.extended; - } - } - } - - /** - * addBits(bitsDst, bitsSrc) - * - * Adds bitsSrc to bitsDst. - * - * @param {Array.} bitsDst - * @param {Array.} bitsSrc - */ - static addBits(bitsDst, bitsSrc) - { - bitsDst[0] += bitsSrc[0]; - bitsDst[1] += bitsSrc[1]; - if (bitsDst[0] >= Int36.BIT36) { - bitsDst[0] %= Int36.BIT36; - bitsDst[1]++; - } - } - - /** - * cmpBits(bitsDst, bitsSrc) - * - * Compares bitsDst to bitsSrc, by computing bitsDst - bitsSrc. - * - * @param {Array.} bitsDst - * @param {Array.} bitsSrc - * @return {number} > 0 if bitsDst > bitsSrc, == 0 if bitsDst == bitsSrc, < 0 if bitsDst < bitsSrc - */ - static cmpBits(bitsDst, bitsSrc) - { - var result = bitsDst[1] - bitsSrc[1]; - if (!result) result = bitsDst[0] - bitsSrc[0]; - return result; - } - - /** - * setBits(bits, lo, hi) - * - * @param {Array.} bits - * @param {number} lo - * @param {number} hi - * @return {Array.} - */ - static setBits(bits, lo, hi) - { - bits[0] = lo; - bits[1] = hi; - return bits; - } - - /** - * shrBits(bitsDst) - * - * Shifts bitsDst right one bit. - * - * @param {Array.} bitsDst - */ - static shrBits(bitsDst) - { - if (bitsDst[1] % 2) { - bitsDst[0] += Int36.BIT36; - } - bitsDst[0] = Math.trunc(bitsDst[0] / 2); - bitsDst[1] = Math.trunc(bitsDst[1] / 2); - } - - /** - * subBits(bitsDst, bitsSrc) - * - * Subtracts bitsSrc from bitsDst. - * - * @param {Array.} bitsDst - * @param {Array.} bitsSrc - */ - static subBits(bitsDst, bitsSrc) - { - bitsDst[0] -= bitsSrc[0]; - bitsDst[1] -= bitsSrc[1]; - if (bitsDst[0] < 0) { - bitsDst[0] += Int36.BIT36; - bitsDst[1]--; - } - } - - /** - * octal(value) - * - * @param {number} value - * @return {string} - */ - static octal(value) - { - if (value < 0) value += Int36.BIT36; - return ("00000000000" + value.toString(8)).slice(-12); - } - - /** - * validate(num) - * - * @param {number} num - * @return {number} - */ - static validate(num) - { - var value = Math.trunc(num) % Int36.BIT36; - if (value > Int36.MAXVAL) { - value -= Int36.BIT36; - } else if (value < Int36.MINVAL) { - value += Int36.BIT36; - } - if (DEBUG && num !== value) { - console.log("Int36.validate(" + num + " out of range, truncated to " + value + ")"); - } - return value; - } -} - -Int36.ERROR = { - NONE: 0x0, - OVERFLOW: 0x1, - UNDERFLOW: 0x2, - DIVZERO: 0x4 -}; - -Int36.BIT18 = Math.pow(2, 18); // 262,144 -Int36.BIT36 = Math.pow(2, 36); // 68,719,476,736 - -Int36.MAXVAL = Math.pow(2, 35) - 1; // 34,359,738,367 -Int36.MINVAL = -Math.pow(2, 35); // -34,359,738,368 - -if (NODE) module.exports = Int36; From ba7de38fbf2824a389ada42aa43b2e62c6e12010 Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Sun, 19 Feb 2017 15:22:58 -0800 Subject: [PATCH 27/29] Created PDP-10 skeleton --- Gruntfile.js | 89 +- docs/pcx86/examples/pcx86-dbg.js | 2 +- modules/pdp10/lib/bus.js | 717 ++++++ modules/pdp10/lib/computer.js | 1640 ++++++++++++ modules/pdp10/lib/cpu.js | 1274 +++++++++ modules/pdp10/lib/cpustate.js | 802 ++++++ modules/pdp10/lib/debugger.js | 3558 ++++++++++++++++++++++++++ modules/pdp10/lib/defines.js | 148 ++ modules/pdp10/lib/device.js | 172 ++ modules/pdp10/lib/memory.js | 645 +++++ modules/pdp10/lib/messages.js | 104 + modules/pdp10/lib/nodebugger.js | 43 + modules/pdp10/lib/panel.js | 1266 +++++++++ modules/pdp10/lib/ram.js | 413 +++ modules/pdp10/lib/rom.js | 339 +++ modules/pdp10/lib/serial.js | 670 +++++ modules/pdp11/lib/bus.js | 2 +- modules/pdp11/lib/computer.js | 2 +- modules/pdp11/lib/cpu.js | 2 +- modules/pdp11/lib/cpuops.js | 2 +- modules/pdp11/lib/cpustate.js | 2 +- modules/pdp11/lib/debugger.js | 2 +- modules/pdp11/lib/defines.js | 2 +- modules/pdp11/lib/device.js | 2 +- modules/pdp11/lib/drive.js | 2 +- modules/pdp11/lib/keyboard.js | 2 +- modules/pdp11/lib/memory.js | 12 +- modules/pdp11/lib/messages.js | 2 +- modules/pdp11/lib/panel.js | 2 +- modules/pdp11/lib/ram.js | 2 +- modules/pdp11/lib/rom.js | 2 +- modules/shared/lib/debugger.js | 4 +- modules/shared/lib/int36.js | 7 +- package.json | 29 + versions/pc8080/1.34.0/pc8080-dbg.js | 2 +- versions/pcx86/1.34.0/pcx86-dbg.js | 2 +- versions/pdpjs/1.34.0/components.xsl | 2 +- versions/pdpjs/1.34.0/pdp10-dbg.js | 204 ++ versions/pdpjs/1.34.0/pdp10.js | 143 ++ versions/pdpjs/1.34.0/pdp11-dbg.js | 2 +- 40 files changed, 12282 insertions(+), 35 deletions(-) create mode 100644 modules/pdp10/lib/bus.js create mode 100644 modules/pdp10/lib/computer.js create mode 100644 modules/pdp10/lib/cpu.js create mode 100644 modules/pdp10/lib/cpustate.js create mode 100644 modules/pdp10/lib/debugger.js create mode 100644 modules/pdp10/lib/defines.js create mode 100644 modules/pdp10/lib/device.js create mode 100644 modules/pdp10/lib/memory.js create mode 100644 modules/pdp10/lib/messages.js create mode 100644 modules/pdp10/lib/nodebugger.js create mode 100644 modules/pdp10/lib/panel.js create mode 100644 modules/pdp10/lib/ram.js create mode 100644 modules/pdp10/lib/rom.js create mode 100644 modules/pdp10/lib/serial.js create mode 100644 versions/pdpjs/1.34.0/pdp10-dbg.js create mode 100644 versions/pdpjs/1.34.0/pdp10.js diff --git a/Gruntfile.js b/Gruntfile.js index 744effce6..b9619dfc0 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -114,6 +114,7 @@ module.exports = function(grunt) { * @property {Array.} c1pJSFiles * @property {Array.} pcX86Files * @property {Array.} pc8080Files + * @property {Array.} pdp10Files * @property {Array.} pdp11Files * @property {Array.} closureCompilerExterns */ @@ -122,6 +123,7 @@ module.exports = function(grunt) { var tmpC1Pjs = "./tmp/c1pjs/" + pkg.version + "/c1p.js"; var tmpPCx86 = "./tmp/pcx86/" + pkg.version + "/pcx86.js"; var tmpPC8080 = "./tmp/pc8080/" + pkg.version + "/pc8080.js"; + var tmpPDP10 = "./tmp/pdpjs/" + pkg.version + "/pdp10.js"; var tmpPDP11 = "./tmp/pdpjs/" + pkg.version + "/pdp11.js"; grunt.initConfig({ @@ -176,6 +178,19 @@ module.exports = function(grunt) { src: pkg.pc8080Files, dest: "./versions/pc8080/" + pkg.version + "/pc8080-dbg.js" }, + "pdp10.js": { + src: pkg.pdp10Files, + dest: "./versions/pdpjs/" + pkg.version + "/pdp10.js", + options: { + process: function(src, filepath) { + return (path.basename(filepath) == "debugger.js"? "" : src); + } + } + }, + "pdp10-dbg.js": { + src: pkg.pdp10Files, + dest: "./versions/pdpjs/" + pkg.version + "/pdp10-dbg.js" + }, "pdp11.js": { src: pkg.pdp11Files, dest: "./versions/pdpjs/" + pkg.version + "/pdp11.js", @@ -243,6 +258,24 @@ module.exports = function(grunt) { } } }, + "tmp-pdp10": { + src: pkg.pdp10Files, + dest: tmpPDP10, + options: { + banner: '"use strict";\n\n', + process: function(src, filepath) { + return "/**\n * @copyright " + filepath.replace(/^\./, "http://pcjs.org") + " (C) Jeff Parsons 2012-2017\n */\n\n" + + src.replace(/(^|\n)[ \t]*(['"])use strict\2;?/g, '') + .replace(/^(import|export)[ \t]+[^\n]*\n/gm, '') + .replace(/^[ \t]*var\s+\S+\s*=\s*require\((['"]).*?\1\);/gm, '') + .replace(/^[ \t]*(if\s+\(NODE\)\s*|)module\.exports\s*=\s*\S+;/gm, '') + .replace(/\/\*\*\s*\*\s*@fileoverview[\s\S]*?\*\/\s*/g, '') + .replace(/[ \t]*if\s*\(NODE\)\s*(\{[^}]*}|[^\n]*)(\n|$)/gm, '') + .replace(/[ \t]*if\s*\(typeof\s+module\s*!==\s*(['"])undefined\1\)\s*(\{[^}]*}|[^\n]*)(\n|$)/gm, '') + .replace(/[ \t]*[A-Za-z_][A-Za-z0-9_.]*\.assert\([^\n]*\);[^\n]*/g, ''); + } + } + }, "tmp-pdp11": { src: pkg.pdp11Files, dest: tmpPDP11, @@ -281,11 +314,14 @@ module.exports = function(grunt) { src: pkg.pc8080Files, dest: tmpPC8080 }, + "pdp10.js": { + src: pkg.pdp10Files, + dest: tmpPDP10 + }, "pdp11.js": { src: pkg.pdp11Files, dest: tmpPDP11 } - }, closureCompiler: { options: { @@ -293,7 +329,7 @@ module.exports = function(grunt) { compilerFile: "./bin/compiler.jar", // [OPTIONAL] set to true if you want to check if files were modified before starting compilation - checkModified: grunt.option("rebuild")? false : true, + checkModified: !grunt.option("rebuild"), // [OPTIONAL] Set Closure Compiler Directives here compilerOpts: { @@ -420,6 +456,33 @@ module.exports = function(grunt) { src: tmpPC8080, dest: "./versions/pc8080/" + pkg.version + "/pc8080-dbg.js" }, + "pdp10.js": { + TEMPcompilerOpts: { + create_source_map: "./tmp/pdpjs/" + pkg.version + "/pdp10.map", + define: ["\"APPVERSION='" + pkg.version + "'\"", + "\"SITEHOST='www.pcjs.org'\"", "COMPILED=true", "DEBUG=false", "DEBUGGER=false"], + output_wrapper: "\"(function(){%output%})();//# sourceMappingURL=/tmp/pdpjs/" + pkg.version + "/pdp10.map\"" + // output_wrapper: "\"(function(){%output%})();\"" + }, + // src: pkg.pdp10Files, + src: tmpPDP10, + dest: "./versions/pdpjs/" + pkg.version + "/pdp10.js" + }, + "pdp10-dbg.js": { + /* + * Technically, this is the one case we don't need to override the default 'define' settings, but maybe it's best to be explicit. + */ + TEMPcompilerOpts: { + create_source_map: "./tmp/pdpjs/" + pkg.version + "/pdp10-dbg.map", + define: ["\"APPVERSION='" + pkg.version + "'\"", + "\"SITEHOST='www.pcjs.org'\"", "COMPILED=true", "DEBUG=false", "DEBUGGER=true"], + output_wrapper: "\"(function(){%output%})();//# sourceMappingURL=/tmp/pdpjs/" + pkg.version + "/pdp10-dbg.map\"" + // output_wrapper: "\"(function(){%output%})();\"" + }, + // src: pkg.pdp10Files, + src: tmpPDP10, + dest: "./versions/pdpjs/" + pkg.version + "/pdp10-dbg.js" + }, "pdp11.js": { TEMPcompilerOpts: { create_source_map: "./tmp/pdpjs/" + pkg.version + "/pdp11.map", @@ -522,6 +585,26 @@ module.exports = function(grunt) { } } }, + "pdp10": { + files: [ + { + cwd: "modules/shared/templates/", + src: ["common.css", "common.xsl", "components.*", "document.css", "document.xsl", "machine.xsl", "manifest.xsl", "outline.xsl"], + dest: "versions/pdpjs/<%= pkg.version %>/", + expand: true + } + ], + options: { + process: function(content, srcPath) { + var s = content.replace(/()[^<]*(<\/xsl:variable>)/g, "$1pdp10$2"); + s = s.replace(/()[^<]*(<\/xsl:variable>)/g, "$1PDPjs$2"); + s = s.replace(/()[^<]*(<\/xsl:variable>)/g, "$1" + pkg.version + "$2"); + s = s.replace(/"[^"]*\/?(common.css|common.xsl|components.css|components.xsl|document.css|document.xsl)"/g, '"/versions/pdpjs/' + pkg.version + '/$1"'); + s = s.replace(/[ \t]*\/\*[^*][\s\S]*?\*\//g, "").replace(/[ \t]*[ \t]*\n?/g, ""); + return s; + } + } + }, "pdp11": { files: [ { @@ -667,7 +750,7 @@ module.exports = function(grunt) { grunt.loadTasks("modules/grunts/prepjs/tasks"); - grunt.registerTask("preCompiler", grunt.option("rebuild")? ["concat:tmp-c1pjs", "concat:tmp-pcx86", "concat:tmp-pc8080", "concat:tmp-pdp11"] : ["newer:concat:tmp-c1pjs", "newer:concat:tmp-pcx86", "newer:concat:tmp-pc8080", "newer:concat:tmp-pdp11"]); + grunt.registerTask("preCompiler", grunt.option("rebuild")? ["concat:tmp-c1pjs", "concat:tmp-pcx86", "concat:tmp-pc8080", "concat:tmp-pdp10", "concat:tmp-pdp11"] : ["newer:concat:tmp-c1pjs", "newer:concat:tmp-pcx86", "newer:concat:tmp-pc8080", "newer:concat:tmp-pdp10", "newer:concat:tmp-pdp11"]); grunt.registerTask("compile", ["preCompiler", "closureCompiler", "replace:fix-source-maps"]); diff --git a/docs/pcx86/examples/pcx86-dbg.js b/docs/pcx86/examples/pcx86-dbg.js index 8ed72740c..5aa39dcbc 100644 --- a/docs/pcx86/examples/pcx86-dbg.js +++ b/docs/pcx86/examples/pcx86-dbg.js @@ -718,7 +718,7 @@ case "^":d=f^e;break;case "|":d=f|e;break;case "&&":d=f&&e?1:0;break;case "||":d function Px(a,b,c){var d;if(b){b=Qx(a,b);for(var e=0,f=!1,g=b,h=[],k=[],m=b.split(/(\|\||&&|\||^|&|!=|==|>=|>>>|>>|>|<=|<<|<|-|\+|%|\/|\*)/);ec&&(d+=" '"+String.fromCharCode(c)+"'"));a.P((null!=b?b+": ":"")+d);return e}function Vx(a,b){if(b)return Tx(a,b,a.da[b]);var c=0;for(b in a.da)Tx(a,b,a.da[b]),c++;return 0=":6,">":6,"<=":6,"<":6,">>>":7,">>":7,"<<":7,"-":8,"+":8,"%":9,"/":9,"*":9}; +function Ux(a,b){var c;c=void 0===c?0:c;switch(a.ya){case 8:a=ja(b,3*c);break;case 10:a=b.toString();break;default:a=q(b,2*c)}return a}var Sx={"||":0,"&&":1,"|":2,"^":3,"&":4,"!=":5,"==":5,">=":6,">":6,"<=":6,"<":6,">>>":7,">>":7,"<<":7,"-":8,"+":8,"%":9,"/":9,"*":9}; function Wx(a){Nx.call(this,a);this.oa=4;this.ha=5;this.wa=1048575;this.K=Xx(this);this.Ga=Xx(this);this.Z=Xx(this);this.D=[];this.B=this.Y=this.M=[];Yx(this);this.ta=0;Zx(this);this.za={};$x(this,a.messages);this.Ca=a.commands;var b=this;window?void 0===window.pcx86&&(window.pcx86=function(a){return ay(b,a)}):void 0===global.pcx86&&(global.pcx86=function(a){return ay(b,a)})}ba(Wx,Nx);l=Wx.prototype; l.uc=function(a,b,c,d){this.ka=b;this.H=c;this.pa=a;this.Za=Ob(a,"FDC");this.Ha=Ob(a,"HDC");this.Tc=Ob(a,"FPU");this.G=Ob(a,"Mouse");(a=qd(a,"messages"))&&$x(this,a);this.ha=b.I>>2;this.wa=b.N;this.Ta=new Kd(this.H,7,"DBG");this.ma=by;80186<=this.H.ca&&(this.ma=by.slice(),this.ma[15]=cy,80286<=this.H.ca&&(this.ma[15]=dy,80386<=this.H.ca&&(this.oa=8)));rl(this,64,function(a){ey(d,d.H.sc,a[0])});rl(this,4,function(a){if(a=a[0]){var b=Rx(d,a);if(void 0===b)d.P("invalid selector: "+a);else if(a=fy(d, b,gy),d.P("dumpSel("+ka(a?a.U:b)+"): %"+q(a?a.Lb:null,d.ha)),a){var c,b=!1;if(a.type&4096)a.type&2048?(c="code"+(a.type&512?",readable":",execonly"),a.type&1024&&(c+=",conforming")):(c="data"+(a.type&512?",writable":",readonly"),a.type&1024&&(c+=",expdown")),a.type&256&&(c+=",accessed");else{var e=hy[a.type];e&&(c=e[0],b=e[1])}!c||a.jb&32768||(c+=",not present");d.P((b?"seg="+ka(a.ua&65535)+" off="+ka(a.Ka):"base="+q(a.ua,d.ha)+" limit="+iy(a.Ka))+" type="+r(a.type>>8)+" ("+c+") ext="+ka(a.ext&-65296)+ diff --git a/modules/pdp10/lib/bus.js b/modules/pdp10/lib/bus.js new file mode 100644 index 000000000..b27033749 --- /dev/null +++ b/modules/pdp10/lib/bus.js @@ -0,0 +1,717 @@ +/** + * @fileoverview Implements the PDP-10 Bus component. + * @author Jeff Parsons + * @copyright © Jeff Parsons 2012-2017 + * + * This file is part of PCjs, a computer emulation software project at . + * + * PCjs is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCjs. If not, + * see . + * + * You are required to include the above copyright notice in every modified copy of this work + * and to display that copyright notice when the software starts running; see COPYRIGHT in + * . + * + * Some PCjs files also attempt to load external resource files, such as character-image files, + * ROM files, and disk image files. Those external resource files are not considered part of PCjs + * for purposes of the GNU General Public License, and the author does not claim any copyright + * as to their contents. + */ + +"use strict"; + +if (NODE) { + var Str = require("../../shared/lib/strlib"); + var Usr = require("../../shared/lib/usrlib"); + var Component = require("../../shared/lib/component"); + var State = require("../../shared/lib/state"); + var PDP10 = require("./defines"); + var MemoryPDP10 = require("./memory"); + var MessagesPDP10 = require("./messages"); +} + +/* + * Data types used by scanMemory() + */ + +/** + * This defines the BlockInfo bit fields used by scanMemory() when it creates the aBlocks array. + * + * @typedef {{ + * num: BitField, + * count: BitField, + * btmod: BitField, + * type: BitField + * }} + */ +var BlockInfoPDP10 = Usr.defineBitFields({num:20, count:8, btmod:1, type:3}); + +/** + * BusInfoPDP10 object definition (returned by scanMemory()) + * + * cbTotal: total bytes allocated + * cBlocks: total Memory blocks allocated + * aBlocks: array of allocated Memory block numbers + * + * @typedef {{ + * cbTotal: number, + * cBlocks: number, + * aBlocks: Array. + * }} + */ +var BusInfoPDP10; + +class BusPDP10 extends Component { + /** + * BusPDP10(parmsBus, cpu, dbg) + * + * The BusPDP10 component manages physical memory and I/O address spaces. + * + * The BusPDP10 component has no UI elements, so it does not require an init() handler, + * but it still inherits from the Component class and must be allocated like any + * other device component. It's currently allocated by the Computer's init() handler, + * which then calls the initBus() method of all the other components. + * + * For memory beyond the simple needs of the ROM and RAM components (ie, memory-mapped + * devices), the address space must still be allocated through the BusPDP10 component via + * addMemory(). If the component needs something more than simple read/write storage, + * it must provide a custom controller. + * + * @param {Object} parmsBus + * @param {CPUStatePDP10} cpu + * @param {DebuggerPDP10} dbg + */ + constructor(parmsBus, cpu, dbg) + { + super("Bus", parmsBus, MessagesPDP10.BUS); + + this.cpu = cpu; + this.dbg = dbg; + + /* + * Supported values for nBusWidth: 18 (default). This represents the maximum size of the bus for the + * life of the machine, regardless what memory management mode the CPU has enabled. + */ + this.nBusWidth = +parmsBus['busWidth'] || 18; + + /* + * Compute all BusPDP10 memory block parameters now, based on the width of the bus. + * + * Note that all PCjs machines divide their address space into blocks, using a block size appropriate for + * the machine's bus width. This allows us to efficiently allocate the entire address space, by reusing blocks + * as appropriate, and to define to different address behaviors on a block-granular level. + */ + this.addrTotal = 1 << this.nBusWidth; + this.nBusMask = (this.addrTotal - 1); + this.nBlockSize = 16384; + this.nBlockShift = Math.log2(this.nBlockSize); // ES6 ALERT (alternatively: Math.log(this.nBlockSize) / Math.LN2) + this.nBlockLen = this.nBlockSize >> 2; + this.nBlockLimit = this.nBlockSize - 1; + this.nBlockTotal = (this.addrTotal / this.nBlockSize) | 0; + this.nBlockMask = this.nBlockTotal - 1; + this.assert(this.nBlockMask <= BlockInfoPDP10.num.mask); + + /* + * Define all the properties to be initialized by initMemory() + */ + this.aBusBlocks = []; + + /* + * We're ready to allocate empty Memory blocks to span the entire physical address space. + */ + this.initMemory(); + + this.setReady(); + } + + /** + * initMemory() + * + * Allocate enough (empty) Memory blocks to span the entire physical address space. + * + * @this {BusPDP10} + */ + initMemory() + { + var block = new MemoryPDP10(this); + block.copyBreakpoints(this.dbg); + + this.aBusBlocks = new Array(this.nBlockTotal); + for (var iBlock = 0; iBlock < this.nBlockTotal; iBlock++) { + this.aBusBlocks[iBlock] = block; + } + } + + /** + * reset() + * + * @this {BusPDP10} + */ + reset() + { + } + + /** + * getWidth() + * + * @this {BusPDP10} + * @return {number} + */ + getWidth() + { + return this.nBusWidth; + } + + /** + * powerUp(data, fRepower) + * + * @this {BusPDP10} + * @param {Object|null} data (always null because we supply no powerDown() handler) + * @param {boolean} [fRepower] + * @return {boolean} true if successful, false if failure + */ + powerUp(data, fRepower) + { + if (!fRepower) { + if (!data) { + this.reset(); + } else { + if (!this.restore(data)) return false; + } + } + return true; + } + + /** + * powerDown(fSave, fShutdown) + * + * @this {BusPDP10} + * @param {boolean} [fSave] + * @param {boolean} [fShutdown] + * @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure + */ + powerDown(fSave, fShutdown) + { + return fSave? this.save() : true; + } + + /** + * save() + * + * @this {BusPDP10} + * @return {Object|null} + */ + save() + { + var state = new State(this); + state.set(0, this.saveMemory()); + return state.data(); + } + + /** + * restore(data) + * + * @this {BusPDP10} + * @param {Object} data + * @return {boolean} true if restore successful, false if not + */ + restore(data) + { + return this.restoreMemory(data[0]); + } + + /** + * addMemory(addr, size, type) + * + * Adds new Memory blocks to the specified address range. Any Memory blocks previously + * added to that range must first be removed via removeMemory(); otherwise, you'll get + * an allocation conflict error. This helps prevent address calculation errors, redundant + * allocations, etc. + * + * We've relaxed some of the original requirements (ie, that addresses must start at a + * block-granular address, or that sizes must be equal to exactly one or more blocks), + * because machines with large block sizes can make it impossible to load certain ROMs at + * their required addresses. Every allocation still allocates a whole number of blocks. + * + * Even so, BusPDP10 memory management does NOT provide a general-purpose heap. Most memory + * allocations occur during machine initialization and never change. In particular, there + * is NO support for removing partial-block allocations. + * + * Each Memory block keeps track of a start address (addr) and length (used), indicating + * the used space within the block; any free space that precedes or follows that used space + * can be allocated later, by simply extending the beginning or ending of the previously used + * space. However, any holes that might have existed between the original allocation and an + * extension are subsumed by the extension. + * + * @this {BusPDP10} + * @param {number} addr is the starting physical address of the request + * @param {number} size of the request, in bytes + * @param {number} type is one of the MemoryPDP10.TYPE constants + * @return {boolean} true if successful, false if not + */ + addMemory(addr, size, type) + { + var addrNext = addr; + var sizeLeft = size; + var iBlock = addrNext >>> this.nBlockShift; + + while (sizeLeft > 0 && iBlock < this.aBusBlocks.length) { + + var block = this.aBusBlocks[iBlock]; + var addrBlock = iBlock * this.nBlockSize; + var sizeBlock = this.nBlockSize - (addrNext - addrBlock); + if (sizeBlock > sizeLeft) sizeBlock = sizeLeft; + + if (block && block.size) { + if (block.type == type) { + /* + * Where there is already a similar block with a non-zero size, we allow the allocation only if: + * + * 1) addrNext + sizeLeft <= block.addr (the request precedes the used portion of the current block), or + * 2) addrNext >= block.addr + block.used (the request follows the used portion of the current block) + */ + if (addrNext + sizeLeft <= block.addr) { + block.used += (block.addr - addrNext); + block.addr = addrNext; + return true; + } + if (addrNext >= block.addr + block.used) { + var sizeAvail = block.size - (addrNext - addrBlock); + if (sizeAvail > sizeLeft) sizeAvail = sizeLeft; + block.used = addrNext - block.addr + sizeAvail; + addrNext = addrBlock + this.nBlockSize; + sizeLeft -= sizeAvail; + iBlock++; + continue; + } + } + return this.reportError(BusPDP10.ERROR.RANGE_INUSE, addrNext, sizeLeft); + } + + var blockNew = new MemoryPDP10(this, addrNext, sizeBlock, this.nBlockSize, type); + blockNew.copyBreakpoints(this.dbg, block); + this.aBusBlocks[iBlock++] = blockNew; + + addrNext = addrBlock + this.nBlockSize; + sizeLeft -= sizeBlock; + } + + if (sizeLeft <= 0) { + this.status("Added " + (size >> 10) + "Kb " + MemoryPDP10.TYPE_NAMES[type] + " at " + Str.toOct(addr)); + return true; + } + + return this.reportError(BusPDP10.ERROR.RANGE_INVALID, addr, size); + } + + /** + * cleanMemory(addr, size) + * + * @this {BusPDP10} + * @param {number} addr + * @param {number} size + * @return {boolean} true if all blocks were clean, false if dirty; all blocks are cleaned in the process + */ + cleanMemory(addr, size) + { + var fClean = true; + var iBlock = addr >>> this.nBlockShift; + while (size > 0 && iBlock < this.aBusBlocks.length) { + if (this.aBusBlocks[iBlock].fDirty) { + this.aBusBlocks[iBlock].fDirty = fClean = false; + this.aBusBlocks[iBlock].fDirtyEver = true; + } + size -= this.nBlockSize; + iBlock++; + } + return fClean; + } + + /** + * zeroMemory(addr, size, pattern) + * + * @this {BusPDP10} + * @param {number} addr + * @param {number} size + * @param {number} [pattern] + */ + zeroMemory(addr, size, pattern) + { + var off = addr & this.nBlockLimit; + var iBlock = addr >>> this.nBlockShift; + while (size > 0 && iBlock < this.aBusBlocks.length) { + this.aBusBlocks[iBlock].zero(off, size, pattern); + size -= this.nBlockSize; + iBlock++; + off = 0; + } + } + + /** + * scanMemory(info, addr, size) + * + * Returns a BusInfoPDP10 object for the specified address range. + * + * @this {BusPDP10} + * @param {BusInfoPDP10} [info] previous BusInfoPDP10, if any + * @param {number} [addr] starting address of range (0 if none provided) + * @param {number} [size] size of range, in bytes (up to end of address space if none provided) + * @return {BusInfoPDP10} updated info (or new info if no previous info provided) + */ + scanMemory(info, addr, size) + { + if (addr == null) addr = 0; + if (size == null) size = (this.addrTotal - addr) | 0; + if (info == null) info = {cbTotal: 0, cBlocks: 0, aBlocks: []}; + + var iBlock = addr >>> this.nBlockShift; + var iBlockMax = ((addr + size - 1) >>> this.nBlockShift); + + info.cbTotal = 0; + info.cBlocks = 0; + while (iBlock <= iBlockMax) { + var block = this.aBusBlocks[iBlock]; + info.cbTotal += block.size; + if (block.size) { + info.aBlocks.push(/** @type {BlockInfoPDP10} */ (Usr.initBitFields(BlockInfoPDP10, iBlock, 0, 0, block.type))); + info.cBlocks++ + } + iBlock++; + } + return info; + } + + /** + * removeMemory(addr, size) + * + * Replaces every block in the specified address range with empty Memory blocks that ignore all reads/writes. + * + * TODO: Update the removeMemory() interface to reflect the relaxed requirements of the addMemory() interface. + * + * @this {BusPDP10} + * @param {number} addr + * @param {number} size + * @return {boolean} true if successful, false if not + */ + removeMemory(addr, size) + { + if (!(addr & this.nBlockLimit) && size && !(size & this.nBlockLimit)) { + var iBlock = addr >>> this.nBlockShift; + while (size > 0) { + var blockOld = this.aBusBlocks[iBlock]; + var blockNew = new MemoryPDP10(this, addr); + blockNew.copyBreakpoints(this.dbg, blockOld); + this.aBusBlocks[iBlock++] = blockNew; + addr = iBlock * this.nBlockSize; + size -= this.nBlockSize; + } + return true; + } + return this.reportError(BusPDP10.ERROR.RANGE_INVALID, addr, size); + } + + /** + * getMemoryBlocks(addr, size) + * + * @this {BusPDP10} + * @param {number} addr is the starting physical address + * @param {number} size of the request, in bytes + * @return {Array} of Memory blocks + */ + getMemoryBlocks(addr, size) + { + var aBlocks = []; + var iBlock = addr >>> this.nBlockShift; + while (size > 0 && iBlock < this.aBusBlocks.length) { + aBlocks.push(this.aBusBlocks[iBlock++]); + size -= this.nBlockSize; + } + return aBlocks; + } + + /** + * setMemoryBlocks(addr, size, aBlocks, type) + * + * If no type is specified, then specified address range uses all the provided blocks as-is; + * this form of setMemoryBlocks() is used for complete physical aliases. + * + * Otherwise, new blocks are allocated with the specified type; the underlying memory from the + * provided blocks is still used, but the new blocks may have different access to that memory. + * + * @this {BusPDP10} + * @param {number} addr is the starting physical address + * @param {number} size of the request, in bytes + * @param {Array} aBlocks as returned by getMemoryBlocks() + * @param {number} [type] is one of the MemoryPDP10.TYPE constants + */ + setMemoryBlocks(addr, size, aBlocks, type) + { + var i = 0; + var iBlock = addr >>> this.nBlockShift; + while (size > 0 && iBlock < this.aBusBlocks.length) { + var block = aBlocks[i++]; + this.assert(block); + if (!block) break; + if (type !== undefined) { + var blockNew = new MemoryPDP10(this, addr); + blockNew.clone(block, type, this.dbg); + block = blockNew; + } + this.aBusBlocks[iBlock++] = block; + size -= this.nBlockSize; + } + } + + /** + * getWord(addr) + * + * @this {BusPDP10} + * @param {number} addr is a physical address + * @return {number} word (16-bit) value at that address + */ + getWord(addr) + { + var off = addr & this.nBlockLimit; + var iBlock = (addr & this.nBusMask) >>> this.nBlockShift; + return this.aBusBlocks[iBlock].readWord(off, addr); + } + + /** + * setWord(addr, w) + * + * @this {BusPDP10} + * @param {number} addr is a physical address + * @param {number} w is the word (16-bit) value to write + */ + setWord(addr, w) + { + var off = addr & this.nBlockLimit; + var iBlock = (addr & this.nBusMask) >>> this.nBlockShift; + this.aBusBlocks[iBlock].writeWord(off, w, addr); + } + + /** + * getBlockDirect(addr) + * + * @this {BusPDP10} + * @param {number} addr is a physical address + * @return {MemoryPDP10} + */ + getBlockDirect(addr) + { + return this.aBusBlocks[(addr & this.nBusMask) >>> this.nBlockShift]; + } + + /** + * getWordDirect(addr) + * + * This is used for device I/O and Debugger physical memory requests, not the CPU. + * + * @this {BusPDP10} + * @param {number} addr is a physical address + * @return {number} word (16-bit) value at that address + */ + getWordDirect(addr) + { + var w; + var off = addr & this.nBlockLimit; + var block = this.getBlockDirect(addr); + w = block.readWordDirect(off, addr); + return w; + } + + /** + * setWordDirect(addr, w) + * + * This is used for device I/O and Debugger physical memory requests, not the CPU. + * + * @this {BusPDP10} + * @param {number} addr is a physical address + * @param {number} w is the word (16-bit) value to write (we truncate it to 16 bits to be safe) + */ + setWordDirect(addr, w) + { + var off = addr & this.nBlockLimit; + var block = this.getBlockDirect(addr); + block.writeWordDirect(off, w & 0xffff, addr); + } + + /** + * addMemBreak(addr, fWrite) + * + * @this {BusPDP10} + * @param {number} addr + * @param {boolean} fWrite is true for a memory write breakpoint, false for a memory read breakpoint + */ + addMemBreak(addr, fWrite) + { + if (DEBUGGER) { + var iBlock = addr >>> this.nBlockShift; + this.aBusBlocks[iBlock].addBreakpoint(addr & this.nBlockLimit, fWrite); + } + } + + /** + * removeMemBreak(addr, fWrite) + * + * @this {BusPDP10} + * @param {number} addr + * @param {boolean} fWrite is true for a memory write breakpoint, false for a memory read breakpoint + */ + removeMemBreak(addr, fWrite) + { + if (DEBUGGER) { + var iBlock = addr >>> this.nBlockShift; + this.aBusBlocks[iBlock].removeBreakpoint(addr & this.nBlockLimit, fWrite); + } + } + + /** + * saveMemory(fAll) + * + * The only memory blocks we save are those marked as dirty, but most likely all of RAM will have been marked dirty, + * and even if our dirty-memory flags were as smart as our dirty-sector flags (ie, were set only when a write changed + * what was already there), it's unlikely that would reduce the number of RAM blocks we must save/restore. At least + * all the ROM blocks should be clean (except in the unlikely event that the Debugger was used to modify them). + * + * All dirty blocks will be stored in a single array, as pairs of block numbers and data arrays, like so: + * + * [iBlock0, [dw0, dw1, ...], iBlock1, [dw0, dw1, ...], ...] + * + * In a normal 4Kb block, there will be 1K DWORD values in the data array. Remember that each DWORD is a signed 32-bit + * integer (because they are formed using bit-wise operator rather than floating-point math operators), so don't be + * surprised to see negative numbers in the data. + * + * The above example assumes "uncompressed" data arrays. If we choose to use "compressed" data arrays, the data arrays + * will look like: + * + * [count0, dw0, count1, dw1, ...] + * + * where each count indicates how many times the following DWORD value occurs. A data array length less than 1K indicates + * that it's compressed, since we'll only store them in compressed form if they actually shrank, and we'll use State + * helper methods compress() and decompress() to create and expand the compressed data arrays. + * + * @this {BusPDP10} + * @param {boolean} [fAll] (true to save all non-ROM memory blocks, regardless of their dirty flags) + * @return {Array} a + */ + saveMemory(fAll) + { + var i = 0; + var a = []; + + for (var iBlock = 0; iBlock < this.nBlockTotal; iBlock++) { + var block = this.aBusBlocks[iBlock]; + /* + * We have to check both fDirty and fDirtyEver, because we may have called cleanMemory() on some of + * the memory blocks (eg, video memory), and while cleanMemory() will clear a dirty block's fDirty flag, + * it also sets the dirty block's fDirtyEver flag, which is left set for the lifetime of the machine. + */ + if (fAll && block.type != MemoryPDP10.TYPE.ROM || block.fDirty || block.fDirtyEver) { + a[i++] = iBlock; + a[i++] = State.compress(block.save()); + } + } + + return a; + } + + /** + * restoreMemory(a) + * + * This restores the contents of all Memory blocks; called by CPUState.restore(). + * + * In theory, we ONLY have to save/restore block contents. Other block attributes, + * like the type, the memory controller (if any), and the active memory access functions, + * should already be restored, since every component (re)allocates all the memory blocks + * it was using when it's restored. And since the CPU is guaranteed to be the last + * component to be restored, all those blocks (and their attributes) should be in place now. + * + * See saveMemory() for more information on how the memory block contents are saved. + * + * @this {BusPDP10} + * @param {Array} a + * @return {boolean} true if successful, false if not + */ + restoreMemory(a) + { + var i; + for (i = 0; i < a.length - 1; i += 2) { + var iBlock = a[i]; + var adw = a[i+1]; + if (adw && adw.length < this.nBlockLen) { + adw = State.decompress(adw, this.nBlockLen); + } + var block = this.aBusBlocks[iBlock]; + if (!block || !block.restore(adw)) { + /* + * Either the block to restore hasn't been allocated, indicating a change in the machine + * configuration since it was last saved (the most likely explanation) or there's some internal + * inconsistency (eg, the block size is wrong). + */ + Component.error("Unable to restore memory block " + iBlock); + return false; + } + } + return true; + } + + /** + * getMemoryLimit(type) + * + * @this {BusPDP10} + * @param {number} type is one of the MemoryPDP10.TYPE constants + * @return {number} (the limiting address of the specified memory type, zero if none) + */ + getMemoryLimit(type) + { + var addr = 0; + for (var iBlock = 0; iBlock < this.aBusBlocks.length; iBlock++) { + var block = this.aBusBlocks[iBlock]; + if (block.type == type) { + addr = block.addr + block.used; + } + } + return addr; + } + + /** + * reportError(errNum, addr, size, fQuiet) + * + * @this {BusPDP10} + * @param {number} errNum + * @param {number} addr + * @param {number} size + * @param {boolean} [fQuiet] (true if any error should be quietly logged) + * @return {boolean} false + */ + reportError(errNum, addr, size, fQuiet) + { + var sError = "Memory block error (" + errNum + ": " + Str.toHex(addr) + "," + Str.toHex(size) + ")"; + if (fQuiet) { + if (this.dbg) { + this.dbg.message(sError); + } else { + this.log(sError); + } + } else { + Component.error(sError); + } + return false; + } +} + +BusPDP10.ERROR = { + RANGE_INUSE: 1, + RANGE_INVALID: 2 +}; + +if (NODE) module.exports = BusPDP10; diff --git a/modules/pdp10/lib/computer.js b/modules/pdp10/lib/computer.js new file mode 100644 index 000000000..b2e0072f3 --- /dev/null +++ b/modules/pdp10/lib/computer.js @@ -0,0 +1,1640 @@ +/** + * @fileoverview Implements the PDP-10 Computer component. + * @author Jeff Parsons + * @copyright © Jeff Parsons 2012-2017 + * + * This file is part of PCjs, a computer emulation software project at . + * + * PCjs is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCjs. If not, + * see . + * + * You are required to include the above copyright notice in every modified copy of this work + * and to display that copyright notice when the software starts running; see COPYRIGHT in + * . + * + * Some PCjs files also attempt to load external resource files, such as character-image files, + * ROM files, and disk image files. Those external resource files are not considered part of PCjs + * for purposes of the GNU General Public License, and the author does not claim any copyright + * as to their contents. + */ + +"use strict"; + +if (NODE) { + var Str = require("../../shared/lib/strlib"); + var Usr = require("../../shared/lib/usrlib"); + var Web = require("../../shared/lib/weblib"); + var UserAPI = require("../../shared/lib/userapi"); + var ReportAPI = require("../../shared/lib/reportapi"); + var Component = require("../../shared/lib/component"); + var State = require("../../shared/lib/state"); + var PDP10 = require("./defines"); + var BusPDP10 = require("./bus"); + var MessagesPDP10 = require("./messages"); +} + +class ComputerPDP10 extends Component { + /** + * ComputerPDP10(parmsComputer, parmsMachine, fSuspended) + * + * The ComputerPDP10 component has no required (parmsComputer) properties, but it does + * support the following: + * + * autoPower: true to automatically power the computer (default), false to wait; + * false is honored only if a "power" button binding exists. + * + * busWidth: number of memory address lines (address bits) on the computer's "bus"; + * 20 is the minimum (and the default), which implies 8086/8088 real-mode addressing, + * while 24 is required for 80286 protected-mode addressing. This value is passed + * directly through to the Bus component; see that component for more details. + * + * resume: one of the ComputerPDP10.RESUME constants, which are as follows: + * '0' if resume disabled (default) + * '1' if enabled without prompting + * '2' if enabled with prompting + * '3' if enabled with prompting and auto-delete + * or a string containing the path of a predefined JSON-encoded state + * + * state: the path to JSON-encoded state file (see details regarding 'state' below) + * + * The parmsMachine object, if provided, may contain any of: + * + * autoMount: if set, this should override any 'autoMount' property in the FDC's + * parmsFDC object. + * + * autoPower: if set, this should override any 'autoPower' property in the ComputerPDP10's + * parmsComputer object. + * + * messages: if set, this should override any 'messages' property in the Debugger's + * parmsDbg object. + * + * state: if set, this should override any 'state' property in the ComputerPDP10's + * parmsComputer object. + * + * url: the location of the machine XML file + * + * If a predefined state is supplied AND it's successfully loaded, then resume behavior + * defaults to '1' (ie, resume enabled without prompting). + * + * This component insures that all components are ready before "powering" them. + * + * Different components become ready at different times, and initialization order (ie, + * the order the scripts are combined on the page) only partially determines readiness. + * This is because components like ROM and Video must finish loading their resource files + * before they are ready. Other components become ready after we call their initBus() + * function, because they have a Bus or CPU dependency, such as access to memory management + * functions. And other components, like CPU and Panel, are ready as soon as their + * constructor finishes. + * + * Once a component has indicated it's ready, we call its powerUp() notification + * function (if it has one--it's optional). We call the CPU's powerUp() function last, + * so that the CPU is assured that all other components are ready and "powered". + * + * @param {Object} parmsComputer + * @param {Object} [parmsMachine] + * @param {boolean} [fSuspended] + */ + constructor(parmsComputer, parmsMachine, fSuspended) + { + super("Computer", parmsComputer, MessagesPDP10.COMPUTER); + + this.flags.powered = false; + + this.parmsMachine = null; + this.setMachineParms(parmsMachine); + + this.fAutoPower = this.getMachineParm('autoPower', parmsComputer, Str.TYPES.BOOLEAN); + + /* + * nPowerChange is 0 while the power state is stable, 1 while power is transitioning + * to "on", and -1 while power is transitioning to "off". + */ + this.nPowerChange = 0; + + /* + * TODO: Deprecate 'buswidth' (it should have always used camelCase) + */ + this.nBusWidth = +parmsComputer['busWidth'] || +parmsComputer['buswidth']; + + this.sResumePath = this.sStatePath = null; + this.sStateData = null; + this.fStateData = false; // remembers if sStateData was loaded + this.fServerState = false; + this.stateComputer = this.stateFailSafe = null; + this.fInitialized = this.fReload = this.fRestoreError = false; + + this.url = /** @type {string} */ (this.getMachineParm('url') || ""); + + /* + * Generate a random number x (where 0 <= x < 1), add 0.1 so that it's guaranteed to be + * non-zero, convert to base 36, and chop off the leading digit and "decimal" point. + */ + this.sMachineID = (Math.random() + 0.1).toString(36).substr(2,12); + this.sUserID = this.queryUserID(); + + /* + * Find the appropriate CPU (and Debugger and Control Panel, if any). + * + * CLOSURE COMPILER TIP: To override the type of a right-hand expression (as we need to do here, + * where we know getComponentByType() will only return an CPUState object or null), wrap the expression + * in parentheses. I never knew this until I stumbled across it in "Closure: The Definitive Guide". + */ + this.cpu = /** @type {CPUStatePDP10} */ (Component.getComponentByType("CPU", this.id)); + if (!this.cpu) { + Component.error("Unable to find CPU component"); + return; + } + this.dbg = /** @type {DebuggerPDP10} */ (Component.getComponentByType("Debugger", this.id)); + + /* + * Initialize the Bus component + */ + this.bus = new BusPDP10({'id': this.idMachine + '.bus', 'busWidth': this.nBusWidth}, this.cpu, this.dbg); + + /* + * Iterate through all the components and connect them to the Control Panel, if any + */ + var iComponent, component; + var aComponents = Component.getComponents(this.id); + this.panel = /** @type {PanelPDP10} */ (Component.getComponentByType("Panel", this.id)); + + if (this.panel && this.panel.controlPrint) { + for (iComponent = 0; iComponent < aComponents.length; iComponent++) { + component = aComponents[iComponent]; + /* + * I can think of many "cleaner" ways for the Control Panel component to pass its + * notice(), println(), etc, overrides on to all the other components, but it's just + * too darn convenient to slam those overrides into the components directly. + */ + component.notice = this.panel.notice; + component.println = this.panel.println; + component.controlPrint = this.panel.controlPrint; + } + } + + this.println(PDP10.APPNAME + " v" + PDP10.APPVERSION + "\n" + COPYRIGHT + "\n" + LICENSE); + + /* + * Iterate through all the components again and call their initBus() handler, if any + */ + for (iComponent = 0; iComponent < aComponents.length; iComponent++) { + component = aComponents[iComponent]; + if (component.initBus) component.initBus(this, this.bus, this.cpu, this.dbg); + } + + var sStatePath = null; + var sResume = /** @type {string} */ (this.getMachineParm('resume', parmsComputer)); + if (sResume !== undefined) { + /* + * Decide whether the 'resume' property is a number or the path of a state file to resume. + */ + if (sResume.length > 1) { + sStatePath = this.sResumePath = sResume; + } else { + this.resume = parseInt(sResume, 10); + } + } + + /* + * The Computer 'state' property allows a state file to be specified independent of the 'resume' feature; + * previously, you could only use 'resume' to load a state file -- which we still support, but loading a state + * file that way prevents the machine's state from being saved, since we always resume from the 'resume' file. + * + * The other wrinkle is on the restore side: we need to IGNORE the 'state' property if a saved state now exists. + * So we have to peek at localStorage, and unfortunately, the only way to "peek" is to actually load the data, + * but we're not ready to use it yet, so powerUp() has been changed to use any existing stateComputer that we've + * already loaded. + * + * However, there's now a wrinkle to the wrinkle: if a 'state' parameter has been passed via the URL, then that + * OVERRIDES everything; it overrides any 'state' Computer parameter AND it disables resume of any saved state in + * localStorage (in other words, it prevents fAllowResume from being true, and forcing resume off). + */ + var fAllowResume; + var sState = this.getMachineParm('state') || (fAllowResume = true) && parmsComputer['state']; + + if (sState) { + this.sStatePath = sStatePath = sState; + if (!fAllowResume) { + this.fServerState = true; + this.resume = ComputerPDP10.RESUME_NONE; + } + if (this.resume) { + this.stateComputer = new State(this, PDP10.APPVERSION); + if (this.stateComputer.load()) { + sStatePath = null; + } else { + delete this.stateComputer; + } + } + } + + /* + * If sStatePath is set, we must use it. But if there's no sStatePath AND resume is set, + * then we have the option of resuming from a server-side state, assuming a valid USERID. + */ + if (!sStatePath && this.resume) { + sStatePath = this.getServerStatePath(); + if (sStatePath) this.fServerState = true; + } + + if (!sStatePath) { + this.setReady(); + } else { + var cmp = this; + Web.getResource(sStatePath, null, true, function doneStateLoad(sURL, sResource, nErrorCode) { + cmp.finishStateLoad(sURL, sResource, nErrorCode); + }); + } + + if (!this.bindings["power"]) this.fAutoPower = true; + + /* + * Power on the computer, giving every component the opportunity to reset or restore itself. + */ + if (!fSuspended && this.fAutoPower) this.wait(this.powerOn); + } + + /** + * getMachineID() + * + * @return {string} + */ + getMachineID() + { + return this.sMachineID; + } + + /** + * setMachineParms(parmsMachine) + * + * If no explicit machine parms were provided, then we check for 'parms' in the bundled resources (if any). + * + * @param {Object} [parmsMachine] + */ + setMachineParms(parmsMachine) + { + if (!parmsMachine) { + var sParms; + if (typeof resources == 'object' && (sParms = resources['parms'])) { + try { + parmsMachine = /** @type {Object} */ (eval("(" + sParms + ")")); + } catch(e) { + Component.error(e.message + " (" + sParms + ")"); + } + } + } + this.parmsMachine = parmsMachine; + } + + /** + * getMachineParm(sParm, parmsComponent, type, defaultValue) + * + * If the machine parameter doesn't exist, we check for a matching component parameter + * (if parmsComponent is provided), and failing that, we check the bundled resources (if any). + * + * At the moment, the only bundled resource request we expect to encounter is 'state'; if it exists, + * then we return 'state' back to the caller (ie, the name of the resource), so that the caller will + * then attempt to load the 'state' resource to obtain the actual state. + * + * TODO: It would be nice if we could tell the Closure Compiler that when a specific type parameter + * (eg, Str.TYPES.NUMBER) is used, the return value will be that type; unfortunately, every caller + * must coerce their own return value. + * + * @param {string} sParm + * @param {Object|null} [parmsComponent] + * @param {number} [type] (from Str.TYPES) + * @param {*} [defaultValue] + * @return {*} + */ + getMachineParm(sParm, parmsComponent, type, defaultValue) + { + /* + * When checking parmsURL, the check is allowed be a bit looser, because URL parameters are + * user-supplied, whereas most other parameters are developer-supplied. Granted, a developer + * may also be sloppy and neglect to use correct case (eg, 'automount' instead of 'autoMount'), + * but there are limits to my paranoia. + */ + var sParmLC = sParm.toLowerCase(); + var value = Web.getURLParm(sParm) || Web.getURLParm(sParmLC); + if (value === undefined && this.parmsMachine) value = this.parmsMachine[sParm]; + if (value === undefined && parmsComponent) value = parmsComponent[sParm]; + if (value === undefined && typeof resources == 'object' && resources[sParm]) value = sParm; + if (value === undefined) value = defaultValue; + if (typeof value == "string" && type) { + switch(type) { + case Str.TYPES.NUMBER: + value = +value; + if (isNaN(/** @type {number} */(value))) value = defaultValue || 0; + break; + case Str.TYPES.BOOLEAN: + value = (value == "true"); + break; + } + } + return value; + } + + /** + * saveMachineParms() + * + * @return {string|null} + */ + saveMachineParms() + { + return this.parmsMachine? JSON.stringify(this.parmsMachine) : null; + } + + /** + * getUserID() + * + * @return {string} + */ + getUserID() + { + return this.sUserID || ""; + } + + /** + * finishStateLoad(sURL, sStateData, nErrorCode) + * + * @this {ComputerPDP10} + * @param {string} sURL + * @param {string} sStateData + * @param {number} nErrorCode + */ + finishStateLoad(sURL, sStateData, nErrorCode) + { + if (!nErrorCode) { + this.sStateData = sStateData; + this.fStateData = true; + if (DEBUG && this.messageEnabled()) { + this.printMessage("loaded state file " + sURL.replace(this.sUserID || "xxx", "xxx")); + } + } else { + this.sResumePath = null; + this.fServerState = false; + this.notice('Unable to load machine state from server (error ' + nErrorCode + (sStateData? ': ' + Str.trim(sStateData) : '') + ')'); + } + this.setReady(); + } + + /** + * wait(fn, parms) + * + * wait() waits until every component is ready (including ourselves, the last component we check), then calls the + * specified Computer method. + * + * TODO: The Closure Compiler makes it difficult for us to define a function type for "fn" that works in all cases; + * sometimes we want to pass a function that takes only a "number", and other times we want to pass a function that + * takes only an "Array" (the type will mirror that of the "parms" parameter). However, the Closure Compiler insists + * that both functions must be declared as accepting both types of parameters. So once again, we must use an untyped + * function declaration, instead of something stricter like: + * + * param {function(this:Computer, (number|Array|undefined)): undefined} fn + * + * @this {ComputerPDP10} + * @param {function(...)} fn + * @param {number|Array} [parms] optional parameters + */ + wait(fn, parms) + { + var computer = this; + var aComponents = Component.getComponents(this.id); + for (var iComponent = 0; iComponent <= aComponents.length; iComponent++) { + var component = (iComponent < aComponents.length ? aComponents[iComponent] : this); + if (!component.isReady()) { + component.isReady(function onComponentReady() { + computer.wait(fn, parms); + }); + return; + } + } + if (DEBUG && this.messageEnabled()) this.printMessage("ComputerPDP10.wait(ready)"); + //noinspection JSUnresolvedFunction + fn.call(this, parms); + } + + /** + * validateState(stateComputer) + * + * NOTE: We clear() stateValidate only when there's no stateComputer. + * + * @this {ComputerPDP10} + * @param {State|null} [stateComputer] + * @return {boolean} true if state passes validation, false if not + */ + validateState(stateComputer) + { + var fValid = true; + var stateValidate = new State(this, PDP10.APPVERSION, ComputerPDP10.STATE_VALIDATE); + if (stateValidate.load() && stateValidate.parse()) { + var sTimestampValidate = stateValidate.get(ComputerPDP10.STATE_TIMESTAMP); + var sTimestampComputer = stateComputer? stateComputer.get(ComputerPDP10.STATE_TIMESTAMP) : "unknown"; + if (sTimestampValidate != sTimestampComputer) { + this.notice("Machine state may be out-of-date\n(" + sTimestampValidate + " vs. " + sTimestampComputer + ")\nCheck your browser's local storage limits"); + fValid = false; + if (!stateComputer) stateValidate.clear(); + } else { + if (DEBUG && this.messageEnabled()) { + this.printMessage("Last state: " + sTimestampComputer + " (validate: " + sTimestampValidate + ")"); + } + } + } + return fValid; + } + + /** + * powerOn(resume) + * + * Power every component "up", applying any previously available state information. + * + * @this {ComputerPDP10} + * @param {number} [resume] is a valid RESUME value; default is this.resume + */ + powerOn(resume) + { + if (resume === undefined) { + resume = this.resume || (this.sStateData? ComputerPDP10.RESUME_AUTO : ComputerPDP10.RESUME_NONE); + } + + if (DEBUG && this.messageEnabled()) { + this.printMessage("ComputerPDP10.powerOn(" + (resume == ComputerPDP10.RESUME_REPOWER ? "repower" : (resume ? "resume" : "")) + ")"); + } + + if (this.nPowerChange) { + return; + } + this.nPowerChange++; + + var fRepower = false; + var fRestore = false; + this.fRestoreError = false; + var stateComputer = this.stateComputer || new State(this, PDP10.APPVERSION); + + if (resume == ComputerPDP10.RESUME_REPOWER) { + fRepower = true; + } + else if (resume > ComputerPDP10.RESUME_NONE) { + if (stateComputer.load(this.sStateData)) { + /* + * Since we're resuming something (either a predefined state or a state from localStorage), let's + * create a "failsafe" checkpoint in localStorage, and destroy it at the end of a successful powerOn(). + * Which means, of course, that if a previous "failsafe" checkpoint already exists, something bad + * may have happened the last time around. + */ + this.stateFailSafe = new State(this, PDP10.APPVERSION, ComputerPDP10.STATE_FAILSAFE); + if (this.stateFailSafe.load()) { + this.powerReport(stateComputer); + /* + * We already know resume is something other than RESUME_NONE, so we'll go ahead and bump it + * all the way to RESUME_PROMPT, so that the user will be prompted, and if the user declines to + * restore, the state will be removed. + */ + resume = ComputerPDP10.RESUME_PROMPT; + /* + * To ensure that the set() below succeeds, we need to call unload(), otherwise it may fail + * with a "read only" error (eg, "TypeError: Cannot assign to read only property 'timestamp'"). + */ + this.stateFailSafe.unload(); + } + + this.stateFailSafe.set(ComputerPDP10.STATE_TIMESTAMP, Usr.getTimestamp()); + this.stateFailSafe.store(); + + var fValidate = this.resume && !this.fServerState; + if (resume == ComputerPDP10.RESUME_AUTO || Component.confirmUser("Click OK to restore the previous " + PDP10.APPNAME + " machine state, or CANCEL to reset the machine.")) { + fRestore = stateComputer.parse(); + if (fRestore) { + var sCode = /** @type {string} */ (stateComputer.get(UserAPI.RES.CODE)); + var sData = /** @type {string} */ (stateComputer.get(UserAPI.RES.DATA)); + if (sCode) { + if (sCode == UserAPI.CODE.OK) { + stateComputer.load(sData); + } else { + /* + * A missing (or not yet created) state file is no cause for alarm, but other errors might be + */ + if (sCode == UserAPI.CODE.FAIL && sData != UserAPI.FAIL.NOSTATE) { + this.notice("Error: " + sData); + if (sData == UserAPI.FAIL.VERIFY) this.resetUserID(); + } else { + this.println(sCode + ": " + sData); + } + /* + * Try falling back to the state that we should have saved in localStorage, as a backup to the + * server-side state. + */ + stateComputer.unload(); // discard the invalid server-side state first + if (stateComputer.load()) { + fRestore = stateComputer.parse(); + fValidate = true; + } else { + fRestore = false; // hmmm, there was nothing in localStorage either + } + } + } + } + /* + * If the load/parse was successful, and it was from localStorage (not sStateData), + * then we should to try verify that localStorage snapshot is current. One reason it may + * NOT be current is if localStorage was full and we got a quota error during the last + * powerOff(). + */ + if (fValidate) this.validateState(fRestore? stateComputer : null); + } else { + /* + * RESUME_PROMPT indicates we should delete the state if they clicked Cancel to confirm() above. + */ + if (resume == ComputerPDP10.RESUME_PROMPT) stateComputer.clear(); + } + } else { + /* + * If there's no state, then there should also be no validation timestamp; if there is, then once again, + * we're probably dealing with a quota error. + */ + this.validateState(); + } + delete this.sStateData; + delete this.stateComputer; + } + + /* + * Start powering all components, including any data they may need to restore their state; + * we restore power to the CPU last. + */ + var aComponents = Component.getComponents(this.id); + for (var iComponent = 0; iComponent < aComponents.length; iComponent++) { + var component = aComponents[iComponent]; + if (component !== this && component != this.cpu) { + fRestore = this.powerRestore(component, stateComputer, fRepower, fRestore); + } + } + + /* + * Assuming this is not a repower, we must perform another wait, because some components may + * have marked themselves as "not ready" again (eg, the FDC component, if the restore forced it + * to mount one or more additional disk images). + */ + var aParms = [stateComputer, resume, fRestore]; + + if (resume != ComputerPDP10.RESUME_REPOWER) { + this.wait(this.donePowerOn, aParms); + return; + } + this.donePowerOn(aParms); + } + + /** + * powerRestore(component, stateComputer, fRepower, fRestore) + * + * @this {ComputerPDP10} + * @param {Component} component + * @param {State} stateComputer + * @param {boolean} fRepower + * @param {boolean} fRestore + * @return {boolean} true if restore should continue, false if not + */ + powerRestore(component, stateComputer, fRepower, fRestore) + { + if (!component.flags.powered) { + + /* + * TODO: If all components called super.powerUp(), the powered flag would be set automatically. + */ + this.assert(component.powerUp); + component.flags.powered = true; + + var data = null; + + try { + if (fRestore) { + data = stateComputer.get(component.id); + if (!data) { + /* + * This is a hack that makes it possible for a machine whose ID has been + * supplemented with a suffix (a single letter or digit) to find object IDs + * in states created from a machine without the suffix. + * + * For example, if a state file was created from a machine with ID "ibm5160" + * but the current machine is "ibm5160a", this attempts a second lookup with + * "ibm5160", enabling us to find objects that match the original machine ID + * (eg, "ibm5160.romEGA"). + */ + data = stateComputer.get(component.id.replace(/[a-z0-9]\./i, '.')); + } + } + + /* + * State.get() will return whatever was originally passed to State.set() (eg, an + * Object or a string), but components are supposed to store only Objects, so if a + * string comes back, something went wrong. By explicitly eliminating "string" data, + * the Closure Compiler stops complaining that we might be passing strings to our + * powerUp() functions (even though we know we're not). + * + * TODO: Determine if there's some way to coerce the Closure Compiler into treating + * data as Object or null, without having to include this runtime check. An assert + * would be a good idea, but this is overkill. + */ + if (typeof data === "string") data = null; + + /* + * If computer is null, this is simply a repower notification, which most components + * don't do anything with. Exceptions include: CPU (since it may be halted) and Video + * (since its screen may be "turned off"). + */ + if (!component.powerUp(data, fRepower) && data) { + + Component.error("Unable to restore state for " + component.type); + /* + * If this is a resume error for a machine that also has a predefined state + * AND we're not restoring from that state, then throw away the current state, + * prevent any new state from being created, and then force a reload, which will + * hopefully restore us to the functioning predefined state. + * + * TODO: Considering doing this in ALL cases, not just in situations where a + * 'state' exists but we're not actually resuming from it. + */ + if (this.sStatePath && !this.fStateData) { + stateComputer.clear(); + this.resume = ComputerPDP10.RESUME_NONE; + Web.reloadPage(); + } else { + /* + * In all other cases, we set fRestoreError, which should trigger a call to + * powerReport() and then delete the offending state. + */ + this.fRestoreError = true; + } + /* + * Any failure triggers an automatic to call powerUp() again, without any state, + * in the hopes that the component can recover by performing a reset. + */ + component.powerUp(null); + /* + * We also disable the rest of the restore operation, because it's not clear + * the remaining state information can be trusted; the machine is already in an + * inconsistent state, so we're not likely to make things worse, and the only + * alternative (starting over and performing a state-less reset) isn't likely to make + * the user any happier. But, we'll see... we need some experience with the code. + */ + fRestore = false; + } + + if (!fRepower && component.comment) { + var asComments = component.comment.split("|"); + for (var i = 0; i < asComments.length; i++) { + component.status(asComments[i]); + } + } + } + catch (err) { + Component.error("Error restoring state for " + component.type + " (" + err.message + ")"); + } + } + return fRestore; + } + + /** + * donePowerOn(aParms) + * + * This is nothing more than a continuation of powerOn(), giving us the option of calling wait() one more time. + * + * @this {ComputerPDP10} + * @param {Array} aParms containing [stateComputer, resume, fRestore] + */ + donePowerOn(aParms) + { + var stateComputer = aParms[0]; + var fRepower = (aParms[1] < 0); + var fRestore = aParms[2]; + + if (DEBUG && this.flags.powered && this.messageEnabled()) { + this.printMessage("ComputerPDP10.donePowerOn(): redundant"); + } + + this.fInitialized = true; + this.flags.powered = true; + var controlPower = this.bindings["power"]; + if (controlPower) controlPower.textContent = "Shutdown"; + + /* + * Once we get to this point, we're guaranteed that all components are ready, so it's safe to power the CPU; + * the CPU should begin executing immediately, unless a debugger is attached. + */ + if (this.cpu) { + /* + * TODO: Do we not care about the return value here? (ie, is checking fRestoreError sufficient)? + */ + this.powerRestore(this.cpu, stateComputer, fRepower, fRestore); + this.updateDisplays(-2); + this.cpu.autoStart(); + } + + /* + * If the state was bad, offer to report it and then delete it. Deleting may be moot, since invariably a new + * state will be created on powerOff() before the next powerOn(), but it seems like good paranoia all the same. + */ + if (this.fRestoreError) { + this.powerReport(stateComputer); + stateComputer.clear(); + } + + if (!fRepower && this.stateFailSafe) { + this.stateFailSafe.clear(); + delete this.stateFailSafe; + } + + this.nPowerChange = 0; + } + + /** + * checkPower() + * + * @this {ComputerPDP10} + * @return {boolean} true if the computer is fully powered, false otherwise + */ + checkPower() + { + if (this.flags.powered) return true; + + var component = null, iComponent; + var aComponents = Component.getComponents(this.id); + for (iComponent = 0; iComponent < aComponents.length; iComponent++) { + component = aComponents[iComponent]; + if (component !== this && !component.flags.ready) break; + } + if (iComponent == aComponents.length) { + for (iComponent = 0; iComponent < aComponents.length; iComponent++) { + component = aComponents[iComponent]; + if (component !== this && !component.flags.powered) break; + } + } + if (iComponent == aComponents.length) component = this; + var s = "The " + component.type + " component (" + component.id + ") is not " + (!component.flags.ready? "ready yet" + (component.fnReady? " (waiting for notification)" : "") : "powered yet") + "."; + Component.alertUser(s); + return false; + } + + /** + * powerReport(stateComputer) + * + * @this {ComputerPDP10} + * @param {State} stateComputer + */ + powerReport(stateComputer) + { + if (Component.confirmUser("There may be a problem with your " + PDP10.APPNAME + " machine.\n\nTo help us diagnose it, click OK to send this " + PDP10.APPNAME + " machine state to http://" + SITEHOST + ".")) { + Web.sendReport(PDP10.APPNAME, PDP10.APPVERSION, this.url, this.getUserID(), ReportAPI.TYPE.BUG, stateComputer.toString()); + } + } + + /** + * powerOff(fSave, fShutdown) + * + * Power every component "down" and optionally save the machine state. + * + * There's one scenario that powerOff() isn't currently able to deal with very effectively: what to do when + * the user switches away while it's still being restored, causing Disk getResource() calls to fail. The + * Disk component calls notify() when that happens -- see Disk.mount() -- but the FDC and HDC controllers don't + * notify *us* of those problems, so Computer assumes that the restore was completely successful, when in fact + * it was only partially successful. + * + * Then we immediately arrive here to perform a save, following that incomplete restore. It would be wrong to + * deal with that incomplete restore by setting fRestoreError, because we don't want to trigger a powerReport() + * and the deletion of the previous state, because the state itself was presumably OK. Unfortunately, the new + * state we now save will no longer include manually mounted disk images whose remounts were interrupted, so future + * restores won't remount them either. + * + * We could perhaps solve this by having the Disk component notify us in those situations, set a new flag + * (fRestoreIncomplete?), and set fSave to false if that's ever set. Be careful though: when fSave is false, + * that means MORE than not saving; it also means deleting any previous state, which is NOT what you'd want to + * do in a "fRestoreIncomplete" situation. Also, we have to worry about Disk operations that fail for other reasons, + * making sure those failures don't interfere with the save process in the same way. + * + * As it stands, the worst that happens is any manually mounted disk images might have to be manually remounted, + * which doesn't seem like a huge problem. + * + * @this {ComputerPDP10} + * @param {boolean} [fSave] is true to request a saved state + * @param {boolean} [fShutdown] is true if the machine is being shut down + * @return {string|null} string representing the saved state (or null if error) + */ + powerOff(fSave, fShutdown) + { + var data; + var sState = "none"; + + if (DEBUG && this.messageEnabled()) { + this.printMessage("ComputerPDP10.powerOff(" + (fSave ? "save" : "nosave") + (fShutdown ? ",shutdown" : "") + ")"); + } + + if (this.nPowerChange) { + return null; + } + this.nPowerChange--; + + var stateComputer = new State(this, PDP10.APPVERSION); + var stateValidate = new State(this, PDP10.APPVERSION, ComputerPDP10.STATE_VALIDATE); + + var sTimestamp = Usr.getTimestamp(); + stateValidate.set(ComputerPDP10.STATE_TIMESTAMP, sTimestamp); + stateComputer.set(ComputerPDP10.STATE_TIMESTAMP, sTimestamp); + stateComputer.set(ComputerPDP10.STATE_VERSION, APPVERSION); + stateComputer.set(ComputerPDP10.STATE_HOSTURL, Web.getHostURL()); + stateComputer.set(ComputerPDP10.STATE_BROWSER, Web.getUserAgent()); + + /* + * Always power the CPU "down" first, just to help insure it doesn't ask other components to do anything + * after they're no longer ready. + */ + if (this.cpu && this.cpu.powerDown) { + if (fShutdown) { + if (fSave) this.cpu.flags.autoStart = this.cpu.flags.running; + this.cpu.stopCPU(); + } + data = this.cpu.powerDown(fSave, fShutdown); + if (typeof data === "object") stateComputer.set(this.cpu.id, data); + if (fShutdown) { + this.cpu.flags.powered = false; + if (data === false) sState = null; + } + } + + var aComponents = Component.getComponents(this.id); + for (var iComponent = 0; iComponent < aComponents.length; iComponent++) { + var component = aComponents[iComponent]; + if (component.flags.powered) { + if (component.powerDown) { + data = component.powerDown(fSave, fShutdown); + if (typeof data === "object") stateComputer.set(component.id, data); + } + if (fShutdown) { + component.flags.powered = false; + if (data === false) sState = null; + } + } + } + + if (sState) { + if (fShutdown) { + var fClear = false; + var fClearAll = false; + if (fSave) { + if (this.sUserID) { + this.saveServerState(this.sUserID, stateComputer.toString()); + } + if (!stateValidate.store() || !stateComputer.store()) { + sState = null; + /* + * New behavior as of v1.13.2: if it appears that localStorage is full, we blow it ALL away. + * Dedicated server-side storage is the only way we'll ever be able to reliably preserve a + * particular machine's state. Historically, attempting to limp along with whatever localStorage + * is left just generates the same useless and annoying warnings over and over. + */ + fClear = fClearAll = true; + } + } + else { + /* + * I used to ALWAYS clear (ie, delete) any associated computer state, but now I do this only if the + * current machine is "resumable", because there are situations where I have two configurations + * for the same machine -- one resumable and one not -- and I don't want the latter throwing away the + * state of the former. + * + * So this code is here now strictly for callers to delete the state of a "resumable" machine, not as + * some paranoid clean-up operation. + * + * An undocumented feature of this operation is that if your configuration uses the special 'resume="3"' + * value, and you click the "Reset" button, and then you click OK to reset the everything, this will + * actually reset EVERYTHING (ie, all localStorage for ALL configs will be reclaimed). + */ + if (this.resume) { + fClear = true; + fClearAll = (this.resume == ComputerPDP10.RESUME_DELETE); + } + } + if (fClear) { + stateComputer.clear(fClearAll); + } + } else { + sState = stateComputer.toString(); + } + } + + if (fShutdown) { + this.flags.powered = false; + var controlPower = this.bindings["power"]; + if (controlPower) controlPower.textContent = "Power"; + } + + this.nPowerChange = 0; + + return sState; + } + + /** + * reset() + * + * Notify all (other) components with a reset() method that the Computer is being reset. + * + * NOTE: We'd like to reset the Bus first (due to the importance of the A20 line), but since we + * allocated the Bus object ourselves, after all the other components were allocated, it ends + * up near the end of Component's list of components. Hence the special case for this.bus below. + * + * Ditto for the CPU, in part because if the Front Panel resets before the CPU, it will end up + * snapping/displaying the PC as of the last instruction executed, before the CPU resets the PC, + * causing the Front Panel to display a stale address when we call updateDisplays() at the end. + * + * @this {ComputerPDP10} + */ + reset() + { + this.flags.reset = true; + if (this.bus && this.bus.reset) { + this.printMessage("Resetting " + this.bus.type); + this.bus.reset(); + } + if (this.cpu && this.cpu.reset) { + this.printMessage("Resetting " + this.cpu.type); + this.cpu.reset(); + } + var aComponents = Component.getComponents(this.id); + for (var iComponent = 0; iComponent < aComponents.length; iComponent++) { + var component = aComponents[iComponent]; + if (component !== this && component !== this.bus && component !== this.cpu && component.reset) { + this.printMessage("Resetting " + component.type); + component.reset(); + } + } + this.flags.reset = false; + this.updateDisplays(-1); + } + + /** + * start(ms, nCycles) + * + * Notify all (other) components with a start() method that the CPU has started. + * + * Note that we're called by startCPU(), which is why we exclude the CPU component, + * as well as ourselves. + * + * @this {ComputerPDP10} + * @param {number} ms + * @param {number} nCycles + */ + start(ms, nCycles) + { + var aComponents = Component.getComponents(this.id); + for (var iComponent = 0; iComponent < aComponents.length; iComponent++) { + var component = aComponents[iComponent]; + if (component.type == "CPU" || component === this) continue; + if (component.start) { + component.start(ms, nCycles); + } + } + this.updateDisplays(-1); + } + + /** + * stop(ms, nCycles) + * + * Notify all (other) components with a stop() method that the CPU has stopped. + * + * Note that we're called by stopCPU(), which is why we exclude the CPU component, + * as well as ourselves. + * + * @this {ComputerPDP10} + * @param {number} ms + * @param {number} nCycles + */ + stop(ms, nCycles) + { + var aComponents = Component.getComponents(this.id); + for (var iComponent = 0; iComponent < aComponents.length; iComponent++) { + var component = aComponents[iComponent]; + if (component.type == "CPU" || component === this) continue; + if (component.stop) { + component.stop(ms, nCycles); + } + } + this.updateDisplays(-1); + } + + /** + * updateDisplays(nUpdate) + * + * TODO: Notify all components with an updateDisplay() method that the computer's state has changed (not + * just the hard-coded ones below). + * + * If any DOM controls were bound to the CPU, then we need to call its updateDisplay() handler; if there are no + * such bindings, then cpu.updateDisplay() does nothing. + * + * Similarly, if there's a Panel, then we need to call its updateDisplay() handler, in case it created its own canvas + * and implemented its own register display (eg, dumpRegisters()); if not, then panel.updateDisplay() also does nothing. + * + * In practice, there will *either* be a Panel with a custom canvas *or* a set of DOM controls bound to the CPU *or* + * neither. In theory, there could be BOTH, but that would be unusual. + * + * TODO: Consider alternate approaches to these largely register-oriented display updates. Ordinarily, we like to + * separate logic from presentation, and currently the CPUState contains both, since it's the component that intimately + * knows the names, number, sizes, etc, of all the active registers. The Panel component is the logical candidate, + * but Panel is an optional component; it's often the case that only machines that include the Debugger also include + * Panel. + * + * @this {ComputerPDP10} + * @param {number} [nUpdate] (1 for periodic, -1 for forced, 0 or undefined otherwise) + */ + updateDisplays(nUpdate) + { + /* + * nUpdate is generally set to -1 whenever the CPU is transitioning to/from a running state, in which case + * cpu.updateDisplay() will definitely want to hide/show register contents; however, at other times, when the + * CPU is running, constantly updating the DOM controls too frequently can adversely impact overall performance. + * + * nUpdate will also be -1 whenever the Debugger has modified the state of the machine, implying that we're + * not sure what, if anything, actually changed. + */ + if (this.cpu) this.cpu.updateDisplay(nUpdate || 0); + if (this.panel) this.panel.updateDisplay(nUpdate || 0); + } + + /** + * setBinding(sType, sBinding, control, sValue) + * + * @this {ComputerPDP10} + * @param {string|null} sType is the type of the HTML control (eg, "button", "textarea", "register", "flag", "rled", etc) + * @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "reset") + * @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement) + * @param {string} [sValue] optional data value + * @return {boolean} true if binding was successful, false if unrecognized binding request + */ + setBinding(sType, sBinding, control, sValue) + { + var computer = this; + + switch (sBinding) { + case "power": + this.bindings[sBinding] = control; + control.onclick = function onClickPower() { + computer.onPower(); + }; + return true; + + case "reset": + this.bindings[sBinding] = control; + control.onclick = function onClickReset() { + computer.onReset(); + }; + return true; + + /* + * Technically, this binding should now be called "saveState", to clearly distinguish it from + * the "Save Machine" control that's normally bound to the savePC() function in save.js. Saving + * an entire machine includes everything needed to start/restore the machine; eg, the machine + * XML configuration file(s) *and* the JSON-encoded machine state. + */ + case "save": + /* + * Since this feature depends on the server supporting the PCjs User API (see userapi.js), + * and since pcjs.org is no longer running a Node web server, we disable the feature for that + * particular host. + */ + if (Str.endsWith(Web.getHost(), "pcjs.org")) { + if (DEBUG) this.log("Remote user API not available"); + /* + * We could also simply hide the control; eg: + * + * control.style.display = "none"; + * + * but removing the control altogether seems better. + */ + control.parentNode.removeChild(/** @type {Node} */ (control)); + return false; + } + this.bindings[sBinding] = control; + control.onclick = function onClickSave() { + var sUserID = computer.queryUserID(true); + if (sUserID) { + /* + * I modified the test to include a check for sStatePath so that I could save new states + * for machines with existing states; otherwise, I'd have no (easy) way of capturing and + * updating their state. Making the machine (even temporarily) resumable would have been + * one work-around, but it's not appropriate for some machines, as their state is simply + * too large (for localStorage anyway, which is the default storage solution). + */ + var fSave = !!(computer.resume && !computer.sResumePath || computer.sStatePath); + var sState = computer.powerOff(fSave); + if (fSave) { + computer.saveServerState(sUserID, sState); + } else { + computer.notice("Resume disabled, machine state not saved"); + } + } + /* + * This seemed like a handy alternative, but it turned out to be a no-go, at least for large states: + * + * var sState = computer.powerOff(true); + * if (sState) { + * sState = "data:text/json;charset=utf-8," + encodeURIComponent(sState); + * window.open(sState); + * } + * + * Perhaps if I embedded the data in a link on the current page instead; eg: + * + * $('Download').appendTo('#container'); + */ + }; + return true; + + default: + break; + } + return false; + } + + /** + * resetUserID() + */ + resetUserID() + { + Web.setLocalStorageItem(ComputerPDP10.STATE_USERID, ""); + this.sUserID = null; + } + + /** + * queryUserID(fPrompt) + * + * @param {boolean} [fPrompt] + * @returns {string|null|undefined} + */ + queryUserID(fPrompt) + { + var sUserID = this.sUserID; + if (!sUserID) { + sUserID = Web.getLocalStorageItem(ComputerPDP10.STATE_USERID); + if (sUserID !== undefined) { + if (!sUserID && fPrompt) { + /* + * NOTE: Warning the user here that "Save" operations are not currently supported by pcjs.org is + * merely a precaution, because ordinarily, setBinding() should have already determined if we are + * running from pcjs.org and disabled any "Save" button. + */ + sUserID = Component.promptUser("Saving machine states on the pcjs.org server is currently unsupported.\n\nIf you're running your own server, enter your user ID below."); + if (sUserID) { + sUserID = this.verifyUserID(sUserID); + if (!sUserID) this.notice("The user ID is invalid."); + } + } + } else if (fPrompt) { + this.notice("Browser local storage is not available"); + } + } + return sUserID; + } + + /** + * verifyUserID(sUserID) + * + * @this {ComputerPDP10} + * @param {string} sUserID + * @return {string} validated user ID, or null if error + */ + verifyUserID(sUserID) + { + this.sUserID = null; + var fMessages = DEBUG && this.messageEnabled(); + if (fMessages) this.printMessage("verifyUserID(" + sUserID + ")"); + var sRequest = Web.getHost() + UserAPI.ENDPOINT + '?' + UserAPI.QUERY.REQ + '=' + UserAPI.REQ.VERIFY + '&' + UserAPI.QUERY.USER + '=' + sUserID; + var response = Web.getResource(sRequest); + var nErrorCode = response[0]; + var sResponse = response[1]; + if (!nErrorCode && sResponse) { + try { + response = eval("(" + sResponse + ")"); + if (response.code && response.code == UserAPI.CODE.OK) { + Web.setLocalStorageItem(ComputerPDP10.STATE_USERID, response.data); + if (fMessages) this.printMessage(ComputerPDP10.STATE_USERID + " updated: " + response.data); + this.sUserID = response.data; + } else { + if (fMessages) this.printMessage(response.code + ": " + response.data); + } + } catch (e) { + Component.error(e.message + " (" + sResponse + ")"); + } + } else { + if (fMessages) this.printMessage("invalid response (error " + nErrorCode + ")"); + } + return this.sUserID; + } + + /** + * getServerStatePath() + * + * @this {ComputerPDP10} + * @return {string|null} sStatePath (null if no localStorage or no USERID stored in localStorage) + */ + getServerStatePath() + { + var sStatePath = null; + if (this.sUserID) { + if (DEBUG && this.messageEnabled()) { + this.printMessage(ComputerPDP10.STATE_USERID + " for load: " + this.sUserID); + } + sStatePath = Web.getHost() + UserAPI.ENDPOINT + '?' + UserAPI.QUERY.REQ + '=' + UserAPI.REQ.LOAD + '&' + UserAPI.QUERY.USER + '=' + this.sUserID + '&' + UserAPI.QUERY.STATE + '=' + State.key(this, PDP10.APPVERSION); + } else { + if (DEBUG && this.messageEnabled()) { + this.printMessage(ComputerPDP10.STATE_USERID + " unavailable"); + } + } + return sStatePath; + } + + /** + * saveServerState(sUserID, sState) + * + * @param {string} sUserID + * @param {string|null} sState + */ + saveServerState(sUserID, sState) + { + /* + * We must pass fSync == true, because (as I understand it) browsers will blow off any async + * requests when a page is being closed. Since our request is synchronous, storeServerState() + * should also return a result, but there's not much we can do with it, since browsers ALSO + * tend to blow off alerts() and the like when closing down. + */ + if (sState) { + if (DEBUG && this.messageEnabled()) { + this.printMessage("size of server state: " + sState.length + " bytes"); + } + var response = this.storeServerState(sUserID, sState, true); + if (response && response[UserAPI.RES.CODE] == UserAPI.CODE.OK) { + this.notice("Machine state saved to server"); + } else if (sState) { + var sError = (response && response[UserAPI.RES.DATA]) || UserAPI.FAIL.BADSTORE; + if (response[UserAPI.RES.CODE] == UserAPI.CODE.FAIL) { + sError = "Error: " + sError; + } else { + sError = "Error " + response[UserAPI.RES.CODE] + ": " + sError; + } + this.notice(sError); + this.resetUserID(); + } + } else { + if (DEBUG && this.messageEnabled()) { + this.printMessage("no state to store"); + } + } + } + + /** + * storeServerState(sUserID, sState, fSync) + * + * @this {ComputerPDP10} + * @param {string} sUserID + * @param {string} sState + * @param {boolean} [fSync] is true if we're powering down and should perform a synchronous request (default is async) + * @return {*} server response if fSync is true and a response was received; otherwise null + */ + storeServerState(sUserID, sState, fSync) + { + if (DEBUG && this.messageEnabled()) { + this.printMessage(ComputerPDP10.STATE_USERID + " for store: " + sUserID); + } + /* + * TODO: Determine whether or not any browsers cancel our request if we're called during a browser "shutdown" event, + * and whether or not it matters if we do an async request (currently, we're not, to try to ensure the request goes through). + */ + var dataPost = {}; + dataPost[UserAPI.QUERY.REQ] = UserAPI.REQ.STORE; + dataPost[UserAPI.QUERY.USER] = sUserID; + dataPost[UserAPI.QUERY.STATE] = State.key(this, PDP10.APPVERSION); + dataPost[UserAPI.QUERY.DATA] = sState; + var sRequest = Web.getHost() + UserAPI.ENDPOINT; + if (!fSync) { + Web.getResource(sRequest, dataPost, true); + } else { + var response = Web.getResource(sRequest, dataPost); + var sResponse = response[0]; + if (response[1]) { + if (sResponse) { + var i = sResponse.indexOf('\n'); + if (i > 0) sResponse = sResponse.substr(0, i); + if (!sResponse.indexOf("Error: ")) sResponse = sResponse.substr(7); + } + sResponse = '{"' + UserAPI.RES.CODE + '":' + response[1] + ',"' + UserAPI.RES.DATA + '":"' + sResponse + '"}'; + } + if (DEBUG && this.messageEnabled()) this.printMessage(sResponse); + return JSON.parse(sResponse); + } + return null; + } + + /** + * onPower() + * + * This handles UI requests to toggle the computer's power (eg, see the "power" button binding). + * + * @this {ComputerPDP10} + */ + onPower() + { + if (!this.nPowerChange) { + if (!this.flags.powered) { + this.wait(this.powerOn); + } else { + this.powerOff(false, true); + } + } + } + + /** + * onReset() + * + * This handles UI requests to reset the computer's state (eg, see the "reset" button binding). + * + * @this {ComputerPDP10} + */ + onReset() + { + /* + * I'm going to start with the presumption that it makes little sense for an "unpowered" computer to be "reset"; + * ditto if the power state is currently being changed. + */ + if (!this.flags.powered || this.nPowerChange) return; + + /* + * If this is a "resumable" machine (and it's not using a predefined state), then we overload the reset + * operation to offer an explicit "save or discard" option first. This is currently the only UI we offer to + * discard a machine's state, including any disk changes. The traditional "reset" operation is still available + * for non-resumable machines. + * + * TODO: Break this behavior out into a separate "discard" operation, in case the designer of the machine really + * wants to clutter the UI with confusing options. ;-) + */ + if (this.resume && !this.sResumePath) { + /* + * I used to bypass the prompt if this.resume == ComputerPDP10.RESUME_AUTO, setting fSave to true automatically, + * but that gives the user no means of resetting a resumable machine that contains errors in its resume state. + */ + var fSave = (/* this.resume == ComputerPDP10.RESUME_AUTO || */ Component.confirmUser("Click OK to save changes to this " + PDP10.APPNAME + " machine.\n\nWARNING: If you CANCEL, all disk changes will be discarded.")); + this.powerOff(fSave, true); + /* + * Forcing the page to reload is an expedient option, but ugly. It's preferable to call powerOn() + * and rely on all the components to reset themselves to their default state. The components with + * the greatest burden here are FDC and HDC, which must rely on the fReload flag to determine whether + * or not to unload/reload all their original auto-mounted disk images. + * + * However, if we started with a predefined state (ie, sStatePath is set), we take this shortcut, because + * we don't (yet) have code in place to gracefully reload the initial state (requires calling getResource() + * again); alternatively, we could avoid throwing that state away, but it seems better to save the memory. + * + * TODO: Make this more graceful, so that we can stop using the reloadPage() sledgehammer. + */ + if (!fSave && this.sStatePath) { + Web.reloadPage(); + return; + } + if (!fSave) this.fReload = true; + this.powerOn(ComputerPDP10.RESUME_NONE); + this.fReload = false; + } else { + this.reset(); + if (this.cpu && !this.dbg) this.cpu.autoStart(); + } + } + + /** + * getMachineComponent(sType, componentPrev) + * + * @this {ComputerPDP10} + * @param {string} sType + * @param {Component|null} [componentPrev] of previously returned component, if any + * @return {Component|null} + */ + getMachineComponent(sType, componentPrev) + { + var componentLast = componentPrev; + var aComponents = Component.getComponents(this.id); + for (var iComponent = 0; iComponent < aComponents.length; iComponent++) { + var component = aComponents[iComponent]; + if (componentPrev) { + if (componentPrev == component) componentPrev = null; + continue; + } + if (component.type == sType) return component; + } + if (!componentLast) Component.log("Machine component type '" + sType + "' not found", "warning"); + return null; + } + + /** + * setFocus(fScroll) + * + * NOTE: When soft keyboard buttons call us to return focus to the machine (and away from the button), + * the browser's default behavior is to scroll the element into view, which can be annoying, especially on iOS, + * where the display is more constrained, so we no longer do it by default (fScroll must be true). + * + * @this {ComputerPDP10} + * @param {boolean} [fScroll] (true if you really want the control scrolled into view) + */ + setFocus(fScroll) + { + if (this.controlPrint) { + /* + * This seems to be recommended work-around to prevent the browser from scrolling the focused element + * into view. The CPU is not a visual component, so when the CPU wants to set focus, the primary intent + * is to ensure that keyboard input is fielded properly. + */ + var x = 0, y = 0; + if (!fScroll && window) { + x = window.scrollX; + y = window.scrollY; + } + + this.controlPrint.focus(); + + if (!fScroll && window) { + window.scrollTo(x, y); + } + } + } + + /** + * ComputerPDP10.init() + * + * For every machine represented by an HTML element of class "PDP10-machine", this function + * locates the HTML element of class "computer", extracting the JSON-encoded parameters for the + * Computer constructor from the element's "data-value" attribute, invoking the constructor to + * create a Computer component, and then binding any associated HTML controls to the new component. + */ + static init() + { + /* + * In non-COMPILED builds, embedMachine() may have set XMLVERSION. + */ + if (!COMPILED && XMLVERSION) PDP10.APPVERSION = XMLVERSION; + + var aeMachines = Component.getElementsByClass(document, PDP10.APPCLASS + "-machine"); + + for (var iMachine = 0; iMachine < aeMachines.length; iMachine++) { + + var eMachine = aeMachines[iMachine]; + var parmsMachine = Component.getComponentParms(eMachine); + + var aeComputers = Component.getElementsByClass(eMachine, PDP10.APPCLASS, "computer"); + + for (var iComputer = 0; iComputer < aeComputers.length; iComputer++) { + + var eComputer = aeComputers[iComputer]; + var parmsComputer = Component.getComponentParms(eComputer); + + /* + * We set fSuspended in the Computer constructor because we want to "power up" the + * computer ourselves, after any/all bindings are in place. + */ + var computer = new ComputerPDP10(parmsComputer, parmsMachine, true); + + if (DEBUG && computer.messageEnabled()) { + computer.printMessage("onInit(" + computer.flags.powered + ")"); + } + + /* + * Bind any "power", "reset" and "save" buttons. An "erase" button was also considered, + * but "reset" now provides a way to force the machine to start from scratch again, so "erase" + * may be redundant now. + */ + Component.bindComponentControls(computer, eComputer, PDP10.APPCLASS); + + /* + * Power on the computer, giving every component the opportunity to reset or restore itself. + */ + if (computer.fAutoPower) computer.wait(computer.powerOn); + } + } + } + + /** + * ComputerPDP10.show() + * + * When exit() is using an "onbeforeunload" handler, this "onpageshow" handler allows us to repower everything, + * without either resetting or restoring. We call powerOn() with a special resume value (RESUME_REPOWER) if the + * computer is already marked as "ready", meaning the browser didn't change anything. This "repower" process + * should be very quick, essentially just marking all components as powered again (so that, for example, the Video + * component will start drawing again) and firing the CPU up again. + */ + static show() + { + var aeComputers = Component.getElementsByClass(document, PDP10.APPCLASS, "computer"); + for (var iComputer = 0; iComputer < aeComputers.length; iComputer++) { + var eComputer = aeComputers[iComputer]; + var parmsComputer = Component.getComponentParms(eComputer); + var computer = /** @type {ComputerPDP10} */ (Component.getComponentByType("Computer", parmsComputer['id'])); + if (computer) { + + if (DEBUG && computer.messageEnabled()) { + computer.printMessage("onShow(" + computer.fInitialized + "," + computer.flags.powered + ")"); + } + + /* + * Note that the FIRST 'onpageshow' event, and therefore the first show() callback, occurs + * AFTER the the initial 'onload' event, and at that point in time, fInitialized will not be set yet. + * So, practically speaking, the first show() callback isn't all that useful. + */ + if (computer.fInitialized && !computer.flags.powered) { + /** + * Repower the computer, notifying every component to continue running as-is. + */ + computer.powerOn(ComputerPDP10.RESUME_REPOWER); + } + } + } + } + + /** + * ComputerPDP10.exit() + * + * The Computer is currently the only component that uses an "exit" handler, which Web.onExit() defines as + * either an "unload" or "onbeforeunload" handler. This gives us the opportunity to save the machine state, + * using our powerOff() function, before the page goes away. + * + * It's worth noting that "onbeforeunload" offers one nice feature when used instead of "onload": the entire + * page (and therefore this entire application) is retained in its current state by the browser (well, some + * browsers), so that if you go to a new URL, either by entering a new URL in the same window/tab, or by pressing + * the FORWARD button, and then you press the BACK button, the page is immediately restored to its previous state. + * + * In fact, that's how some browsers operate whether you have an "onbeforeunload" handler or not; in other words, + * an "onbeforeunload" handler doesn't change the page retention behavior of the browser. By contrast, the mere + * presence of an "onunload" handler generally causes a browser to throw the page away once the handler returns. + * + * However, in order to safely use "onbeforeunload", we must add yet another handler ("onpageshow") to repower + * everything, without either resetting or restoring. Hence, the ComputerPDP10.show() function, which calls powerOn() + * with a special resume value (RESUME_REPOWER) if the computer is already marked as "ready", meaning the browser + * didn't change anything. This "repower" process should be very quick, essentially just marking all components as + * powered again (so that, for example, the Video component will start drawing again) and firing the CPU up again. + * + * Reportedly, some browsers (eg, Opera) don't support "onbeforeunload", in which case Component will have to use + * "unload" instead. But even when the page must be rebuilt from scratch, the combination of browser cache and + * localStorage means the simulation should be restored and become operational almost immediately. + */ + static exit() + { + var aeComputers = Component.getElementsByClass(document, PDP10.APPCLASS, "computer"); + for (var iComputer = 0; iComputer < aeComputers.length; iComputer++) { + var eComputer = aeComputers[iComputer]; + var parmsComputer = Component.getComponentParms(eComputer); + var computer = /** @type {ComputerPDP10} */ (Component.getComponentByType("Computer", parmsComputer['id'])); + if (computer) { + + if (DEBUG && computer.messageEnabled()) { + computer.printMessage("onExit(" + computer.flags.powered + ")"); + } + + if (computer.flags.powered) { + /** + * Power off the computer, giving every component an opportunity to save its state, + * but only if 'resume' has been set AND there is no valid resume path (because if a valid resume + * path exists, we'll always load our state from there, and not from whatever we save here). + */ + computer.powerOff(!!(computer.resume && !computer.sResumePath), true); + } + } + } + } +} + +ComputerPDP10.STATE_FAILSAFE = "failsafe"; +ComputerPDP10.STATE_VALIDATE = "validate"; +ComputerPDP10.STATE_TIMESTAMP = "timestamp"; +ComputerPDP10.STATE_VERSION = "version"; +ComputerPDP10.STATE_HOSTURL = "url"; +ComputerPDP10.STATE_BROWSER = "browser"; +ComputerPDP10.STATE_USERID = "user"; + +/* + * The following constants define all the resume options. Negative values (eg, RESUME_REPOWER) are for + * internal use only, and RESUME_DELETE is not documented (it provides a way of deleting ALL saved states + * whenever a resume is declined). As a result, the only "end-user" values are 0, 1 and 2. + */ +ComputerPDP10.RESUME_REPOWER = -1; // resume without changing any state (for internal use only) +ComputerPDP10.RESUME_NONE = 0; // default (no resume) +ComputerPDP10.RESUME_AUTO = 1; // automatically save/restore state +ComputerPDP10.RESUME_PROMPT = 2; // automatically save but conditionally restore (WARNING: if restore is declined, any state is discarded) +ComputerPDP10.RESUME_DELETE = 3; // same as RESUME_PROMPT but discards ALL machines states whenever ANY machine restore is declined (undocumented) + +/* + * Initialize every Computer on the page. + */ +Web.onInit(ComputerPDP10.init); +Web.onShow(ComputerPDP10.show); +Web.onExit(ComputerPDP10.exit); + +if (NODE) module.exports = ComputerPDP10; diff --git a/modules/pdp10/lib/cpu.js b/modules/pdp10/lib/cpu.js new file mode 100644 index 000000000..407662401 --- /dev/null +++ b/modules/pdp10/lib/cpu.js @@ -0,0 +1,1274 @@ +/** + * @fileoverview Controls the PDP-10 CPU component. + * @author Jeff Parsons + * @copyright © Jeff Parsons 2012-2017 + * + * This file is part of PCjs, a computer emulation software project at . + * + * PCjs is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCjs. If not, + * see . + * + * You are required to include the above copyright notice in every modified copy of this work + * and to display that copyright notice when the software starts running; see COPYRIGHT in + * . + * + * Some PCjs files also attempt to load external resource files, such as character-image files, + * ROM files, and disk image files. Those external resource files are not considered part of PCjs + * for purposes of the GNU General Public License, and the author does not claim any copyright + * as to their contents. + */ + +"use strict"; + +if (NODE) { + var Str = require("../../shared/lib/strlib"); + var Component = require("../../shared/lib/component"); + var MessagesPDP10 = require("./messages"); +} + +/* + * A word (or more) about PDP-11 speeds: + * + * After looking over the timings of PDP-11/70 instructions, nearly all of them appear + * to be multiples of 150ns. So that's what we'll consider a cycle. How many 150ns are + * in one second? Approximately 6666667. So by way of comparison to other PCjs machines, + * that makes the PDP-11 (or at least the PDP-11/70) look like a 6.67Mhz machine. + * + * I've started with the PDP-11/70, since that's what Paul Nankervis started with. When + * I go back and add support for earlier PDP-11 models (primarily by neutering functions + * that didn't exist), I will no doubt have to tweak some instruction cycle counts, too. + * + * Examples of operations that take 1 extra cycle (150ns): single and double operand byte + * instructions with an odd address (except MOV/MTPI/MTPD/JMP/JRS), ADD/SUB/BIC/BIS/MOVB/CMP/BIT + * instructions with src of R1-R7 and dst of R6-R7, RORB/ASRB with an odd address, and each + * shift of ASH/ASHC. As you can see, the rules are not simple. + * + * We're not simulating cache hardware, but our timings should be optimistic and assume 100% + * cache hits; for cache hits, each read cycle is 300ns. As for write cycles, they are always + * 750ns. My initial take on DEC's timings is that they are including the write time as part + * of the total EF (execute/fetch) time. So, for instructions that write to memory, it looks + * like we'll normally need to add 5 cycles (750/150) to the instruction's base time, but + * we'll need to keep an eye out for exceptions. + */ + +/** + * @class CPUPDP10 + * @unrestricted + */ +class CPUPDP10 extends Component { + /** + * CPUPDP10(parmsCPU, nCyclesDefault) + * + * The CPUPDP10 class supports the following (parmsCPU) properties: + * + * cycles: the machine's base cycles per second; the CPUStatePDP10 constructor + * will provide us with a default (based on the CPU model) to use as a fallback. + * + * multiplier: base cycle multiplier; default is 1. + * + * autoStart: true to automatically start, false to not, or null if "it depends"; + * null is the default, which means do not autostart UNLESS there is no Debugger + * and no "Run" button (ie, no way to manually start the machine). + * + * csStart: the number of cycles that runCPU() must wait before generating + * checksum records; -1 if disabled. checksum records are a diagnostic aid + * used to help compare one CPU run to another. + * + * csInterval: the number of cycles that runCPU() must execute before generating + * a checksum record; -1 if disabled. + * + * csStop: the number of cycles to stop generating checksum records. + * + * This component is primarily responsible for interfacing the CPU with the outside + * world (eg, Panel and Debugger components), and managing overall CPU operation. + * + * It is extended by the CPUStatePDP10 component, where the simulation control logic resides. + * + * @param {Object} parmsCPU + * @param {number} nCyclesDefault + */ + constructor(parmsCPU, nCyclesDefault) + { + super("CPU", parmsCPU, MessagesPDP10.CPU); + + var nCycles = +parmsCPU['cycles'] || nCyclesDefault; + + var nMultiplier = +parmsCPU['multiplier'] || 1; + + this.nDisplayCount = 0; + this.nDisplayLimit = 30; + this.nCyclesPerSecond = nCycles; + + /* + * nCyclesMultiplier replaces the old "speed" variable (0, 1, 2) and eliminates the need for + * the constants (SPEED_SLOW, SPEED_FAST and SPEED_MAX). The UI simply doubles the multiplier + * until we've exceeded the host's speed limit and then starts the multiplier over at 1. + */ + this.nCyclesMultiplier = nMultiplier; + this.mhzDefault = Math.round(this.nCyclesPerSecond / 10000) / 100; + this.mhzTarget = this.mhzDefault * this.nCyclesMultiplier; + this.msPerYield = this.nCyclesPerYield = this.nCyclesNextYield = this.nCyclesRecalc = 0; + + /* + * We add a number of flags to the set initialized by Component + */ + this.flags.running = this.flags.starting = false; + this.flags.autoStart = parmsCPU['autoStart']; + if (typeof this.flags.autoStart == "string") this.flags.autoStart = (this.flags.autoStart == "true"); + + /* + * Get checksum parameters, if any. runCPU() behavior is not affected until fChecksum + * is true, which won't happen until resetChecksum() is called with nCyclesChecksumInterval + * ("csInterval") set to a positive value. + * + * As above, any of these parameters can also be set with the Debugger's execution options + * command ("x"); for example, "x cs int 5000" will set nCyclesChecksumInterval to 5000 + * and call resetChecksum(). + */ + this.flags.checksum = false; + this.nChecksum = this.nCyclesChecksumNext = 0; + this.nCyclesChecksumStart = +parmsCPU["csStart"]; + this.nCyclesChecksumInterval = +parmsCPU["csInterval"]; + this.nCyclesChecksumStop = +parmsCPU["csStop"]; + + /* + * Array of countdown timers managed by addTimer() and setTimer(). + */ + this.aTimers = []; + + this.onRunTimeout = this.runCPU.bind(this); // function onRunTimeout() { cpu.runCPU(); }; + + /* + * Define the rest of the properties used by the class + */ + this.mhz = 0; + this.nYieldsSinceStatusUpdate = 0; + this.msStartRun = this.msStartThisRun = this.msEndThisRun = this.nCyclesThisRun = 0; + this.nTotalCycles = this.nRunCycles = this.nBurstCycles = this.nStepCycles = this.nSnapCycles = 0; + this.panel = null; + + this.setReady(); + } + + /** + * initBus(cmp, bus, cpu, dbg) + * + * @this {CPUPDP10} + * @param {ComputerPDP10} cmp + * @param {BusPDP10} bus + * @param {CPUPDP10} cpu + * @param {DebuggerPDP10} dbg + */ + initBus(cmp, bus, cpu, dbg) + { + this.cmp = cmp; + this.bus = bus; + this.dbg = dbg; + this.panel = cmp.panel; + for (var i = 0; i < CPUPDP10.BUTTONS.length; i++) { + var control = this.bindings[CPUPDP10.BUTTONS[i]]; + if (control) this.cmp.setBinding(null, CPUPDP10.BUTTONS[i], control); + } + this.setReady(); + } + + /** + * reset() + * + * Stub for reset notification (overridden by the CPUStatePDP10 component). + * + * @this {CPUPDP10} + */ + reset() + { + } + + /** + * save() + * + * Stub for save support (overridden by the CPUStatePDP10 component). + * + * @this {CPUPDP10} + * @return {Object|null} + */ + save() + { + return null; + } + + /** + * restore(data) + * + * Stub for restore support (overridden by the CPUStatePDP10 component). + * + * @this {CPUPDP10} + * @param {Object} data + * @return {boolean} true if restore successful, false if not + */ + restore(data) + { + return false; + } + + /** + * powerUp(data, fRepower) + * + * @this {CPUPDP10} + * @param {Object|null} data + * @param {boolean} [fRepower] + * @return {boolean} true if successful, false if failure + */ + powerUp(data, fRepower) + { + /* + * We've already saved the parmsCPU 'autoStart' setting, but there may be a machine (or URL) override. + */ + var sAutoStart = this.cmp.getMachineParm('autoStart'); + if (sAutoStart != null) { + this.flags.autoStart = (sAutoStart == "true"? true : (sAutoStart == "false"? false : !!sAutoStart)); + } + else if (this.flags.autoStart == null) { + /* + * If there's no explicit parmsCPU setting either, then we will autoStart if there's no Debugger and + * no "Run" button. + */ + this.flags.autoStart = ((!DEBUGGER || !this.dbg) && this.bindings["run"] === undefined); + } + + if (!fRepower) { + if (!data) { + this.reset(); + } else { + this.resetCycles(); + if (!this.restore(data)) return false; + this.resetChecksum(); + } + /* + * Give the Debugger a chance to do/print something once we've powered up. + */ + if (DEBUGGER && this.dbg) { + this.dbg.init(this.flags.autoStart); + } else { + /* + * The Computer (this.cmp) knows if there's a Control Panel (this.panel), and the Control Panel + * knows if there's a "print" control (this.panel.controlPrint), and if there IS a "print" control + * but no debugger, the machine is probably misconfigured (most likely, the page simply neglected to + * load the Debugger component). + * + * However, we don't actually need to check all that; it's always safe use println(), regardless whether + * a Control Panel with a "print" control is present or not. + */ + this.status("No debugger detected"); + } + if (!this.flags.autoStart) { + this.println("CPU will not be auto-started " + (this.panel? "(click Run to start)" : "(type 'go' to start)")); + } + } + /* + * The Computer component (which is responsible for all powerDown and powerUp notifications) + * is now responsible for managing a component's fPowered flag, not us. + * + * this.flags.powered = true; + */ + return true; + } + + /** + * powerDown(fSave, fShutdown) + * + * @this {CPUPDP10} + * @param {boolean} [fSave] + * @param {boolean} [fShutdown] + * @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure + */ + powerDown(fSave, fShutdown) + { + return fSave? this.save() : true; + } + + /** + * autoStart() + * + * @this {CPUPDP10} + * @return {boolean} true if started, false if not + */ + autoStart() + { + if (this.flags.running) { + return true; + } + if (this.flags.autoStart) { + /* + * We used to also set fUpdateFocus when calling startCPU(), on the assumption that in the "auto-starting" + * context, a machine without focus is like a day without sunshine, but in reality, focus should only be + * forced when the user takes some other machine-related action. + */ + this.startCPU(); + return true; + } + return false; + } + + /** + * isPowered() + * + * @this {CPUPDP10} + * @return {boolean} + */ + isPowered() + { + if (!this.flags.powered) { + this.println(this.toString() + " not powered"); + return false; + } + return true; + } + + /** + * isRunning() + * + * @this {CPUPDP10} + * @return {boolean} + */ + isRunning() + { + return this.flags.running; + } + + /** + * getChecksum() + * + * This will be implemented by the CPUStatePDP10 component. + * + * @this {CPUPDP10} + * @return {number} a 32-bit summation of key elements of the current CPU state (used by the CPU checksum code) + */ + getChecksum() + { + return 0; + } + + /** + * resetChecksum() + * + * If checksum generation is enabled (fChecksum is true), this resets the running 32-bit checksum and the + * cycle counter that will trigger the next displayChecksum(); called by resetCycles(), which is called whenever + * the CPU is reset or restored. + * + * @this {CPUPDP10} + * @return {boolean} true if checksum generation enabled, false if not + */ + resetChecksum() + { + if (this.nCyclesChecksumStart === undefined) this.nCyclesChecksumStart = 0; + if (this.nCyclesChecksumInterval === undefined) this.nCyclesChecksumInterval = -1; + if (this.nCyclesChecksumStop === undefined) this.nCyclesChecksumStop = -1; + this.flags.checksum = (this.nCyclesChecksumStart >= 0 && this.nCyclesChecksumInterval > 0); + if (this.flags.checksum) { + this.nChecksum = 0; + this.nCyclesChecksumNext = this.nCyclesChecksumStart - this.nTotalCycles; + /* + * this.nCyclesChecksumNext = this.nCyclesChecksumStart + this.nCyclesChecksumInterval - + * (this.nTotalCycles % this.nCyclesChecksumInterval); + */ + return true; + } + return false; + } + + /** + * updateChecksum(nCycles) + * + * When checksum generation is enabled (fChecksum is true), runCPU() asks stepCPU() to execute a minimum + * number of cycles (1), effectively limiting execution to a single instruction, and then we're called with + * the exact number cycles that were actually executed. This should give us instruction-granular checksums + * at precise intervals that are 100% repeatable. + * + * @this {CPUPDP10} + * @param {number} nCycles + */ + updateChecksum(nCycles) + { + if (this.flags.checksum) { + /* + * Get a 32-bit summation of the current CPU state and add it to our running 32-bit checksum + */ + var fDisplay = false; + this.nChecksum = (this.nChecksum + this.getChecksum())|0; + this.nCyclesChecksumNext -= nCycles; + if (this.nCyclesChecksumNext <= 0) { + this.nCyclesChecksumNext += this.nCyclesChecksumInterval; + fDisplay = true; + } + if (this.nCyclesChecksumStop >= 0) { + if (this.nCyclesChecksumStop <= this.getCycles()) { + this.nCyclesChecksumInterval = this.nCyclesChecksumStop = -1; + this.resetChecksum(); + this.stopCPU(); + fDisplay = true; + } + } + if (fDisplay) this.displayChecksum(); + } + } + + /** + * displayChecksum() + * + * When checksum generation is enabled (fChecksum is true), this is called to provide a crude log of all + * checksums generated at the specified cycle intervals, as specified by the "csStart" and "csInterval" parmsCPU + * properties). + * + * @this {CPUPDP10} + */ + displayChecksum() + { + this.println(this.getCycles() + " cycles: " + "checksum=" + Str.toHex(this.nChecksum)); + } + + /** + * setBinding(sType, sBinding, control, sValue) + * + * @this {CPUPDP10} + * @param {string|null} sType is the type of the HTML control (eg, "button", "textarea", "register", "flag", "rled", etc) + * @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "run") + * @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement) + * @param {string} [sValue] optional data value + * @return {boolean} true if binding was successful, false if unrecognized binding request + */ + setBinding(sType, sBinding, control, sValue) + { + var cpu = this; + + switch (sBinding) { + case "power": + case "reset": + /* + * The "power" and "reset" buttons are functions of the entire computer, not just the CPU, + * but it's not always convenient to stick a power button in the Computer component definition, + * so we record those bindings here and pass them on to the Computer component in initBus(). + */ + this.bindings[sBinding] = control; + return true; + + case "run": + this.bindings[sBinding] = control; + control.onclick = function onClickRun() { + if (!cpu.cmp || !cpu.cmp.checkPower()) return; + /* + * We no longer pass true to these startCPU()/stopCPU() calls, on the theory that if the "run" + * control is visible, then the computer is probably sufficiently visible as well; the problem + * with setting fUpdateFocus to true is that it can jerk the web page around in annoying ways. + */ + if (!cpu.flags.running) + cpu.startCPU(); + else + cpu.stopCPU(); + }; + return true; + + case "speed": + this.bindings[sBinding] = control; + return true; + + case "setSpeed": + this.bindings[sBinding] = control; + control.onclick = function onClickSetSpeed() { + cpu.setSpeed(cpu.nCyclesMultiplier << 1, true); + }; + control.textContent = this.getSpeedTarget(); + return true; + + default: + break; + } + return false; + } + + /** + * updateDisplays(nUpdate) + * + * Simpler wrapper around the Computer's updateDisplays() method. + * + * @this {CPUPDP10} + * @param {number} [nUpdate] (1 for periodic, -1 for forced, 0 or undefined otherwise) + */ + updateDisplays(nUpdate) + { + if (this.cmp) this.cmp.updateDisplays(nUpdate); + } + + /** + * updateDisplay(nUpdate) + * + * Some of the CPU bindings provide feedback and therefore need to be updated periodically. + * However, this should be called via the Computer's updateDisplays() interface, not directly. + * + * @this {CPUPDP10} + * @param {number} [nUpdate] (1 for periodic, -1 for forced, 0 otherwise) + */ + updateDisplay(nUpdate) + { + var controlSpeed = this.bindings["speed"]; + if (controlSpeed) { + if (nUpdate <= 0 || (this.nDisplayCount += nUpdate) >= this.nDisplayLimit) { + controlSpeed.textContent = this.getSpeedCurrent(); + this.nDisplayCount = 0; + } + } + } + + /** + * addCycles(nCycles, fEndStep) + * + * @this {CPUPDP10} + * @param {number} nCycles + * @param {boolean} [fEndStep] + */ + addCycles(nCycles, fEndStep) + { + this.nTotalCycles += nCycles; + if (fEndStep) { + this.nBurstCycles = this.nStepCycles = this.nSnapCycles = 0; + } + } + + /** + * calcCycles(fRecalc) + * + * Calculate the number of cycles to process for each "burst" of CPU activity. The size of a burst + * is driven by YIELDS_PER_SECOND (eg, 30). + * + * At the end of each burst, we subtract burst cycles from the yield cycle "threshold" counter. + * Whenever the "next yield" cycle counter goes to (or below) zero, we compare elapsed time to the time + * we expected the virtual hardware to take (eg, 1000ms/50 or 20ms), and if we still have time remaining, + * we sleep the remaining time (or 0ms if there's no remaining time), and then restart runCPU(). + * + * @this {CPUPDP10} + * @param {boolean} [fRecalc] is true if the caller wants to recalculate thresholds based on the most recent + * speed calculation (see calcSpeed). + */ + calcCycles(fRecalc) + { + /* + * Calculate "per" yield values. + */ + var vMultiplier = 1; + if (fRecalc) { + if (this.nCyclesMultiplier > 1 && this.mhz) { + vMultiplier = (this.mhz / this.mhzDefault); + } + } + + this.msPerYield = Math.round(1000 / CPUPDP10.YIELDS_PER_SECOND); + this.nCyclesPerYield = Math.floor(this.nCyclesPerSecond / CPUPDP10.YIELDS_PER_SECOND * vMultiplier); + + /* + * And initialize "next" yield values to the "per" values. + */ + if (!fRecalc) this.nCyclesNextYield = this.nCyclesPerYield; + this.nCyclesRecalc = 0; + } + + /** + * getCycles(fScaled) + * + * getCycles() returns the number of cycles executed so far. Note that we can be called after + * runCPU() OR during runCPU(), perhaps from a handler triggered during the current run's stepCPU(), + * so nRunCycles must always be adjusted by number of cycles stepCPU() was asked to run (nBurstCycles), + * less the number of cycles it has yet to run (nStepCycles). + * + * nRunCycles is zeroed whenever the CPU is halted or the CPU speed is changed, which is why we also + * have nTotalCycles, which accumulates all nRunCycles before we zero it. However, nRunCycles and + * nTotalCycles eventually get reset by calcSpeed(), to avoid overflow, so components that rely on + * getCycles() returning steadily increasing values should also be prepared for a reset at any time. + * + * @this {CPUPDP10} + * @param {boolean} [fScaled] is true if the caller wants a cycle count relative to a multiplier of 1 + * @return {number} + */ + getCycles(fScaled) + { + var nCycles = this.nTotalCycles + this.nRunCycles + this.nBurstCycles - this.nStepCycles; + if (fScaled && this.nCyclesMultiplier > 1 && this.mhz > this.mhzDefault) { + /* + * We could scale the current cycle count by the current effective speed (this.mhz); eg: + * + * nCycles = Math.round(nCycles / (this.mhz / this.mhzDefault)); + * + * but that speed will fluctuate somewhat: large fluctuations at first, but increasingly smaller + * fluctuations after each burst of instructions that runCPU() executes. + * + * Alternatively, we can scale the cycle count by the multiplier, which is good in that the + * multiplier doesn't vary once the user changes it, but a potential downside is that the + * multiplier might be set too high, resulting in a target speed that's higher than the effective + * speed is able to reach. + * + * Also, if multipliers were always limited to a power-of-two, then this could be calculated + * with a simple shift. However, only the "setSpeed" UI binding limits it that way; the Debugger + * interface allows any value, as does the CPU "multiplier" parmsCPU property (from the machine's + * XML file). + */ + nCycles = Math.round(nCycles / this.nCyclesMultiplier); + } + return nCycles; + } + + /** + * getCyclesPerSecond() + * + * This returns the CPU's "base" speed (ie, the original cycles per second defined for the machine) + * + * @this {CPUPDP10} + * @return {number} + */ + getCyclesPerSecond() + { + return this.nCyclesPerSecond; + } + + /** + * resetCycles() + * + * Resets speed and cycle information as part of any reset() or restore(); this typically occurs during powerUp(). + * It's important that this be called BEFORE the actual restore() call, because restore() may want to call setSpeed(), + * which in turn assumes that all the cycle counts have been initialized to sensible values. + * + * @this {CPUPDP10} + */ + resetCycles() + { + this.mhz = 0; + this.nYieldsSinceStatusUpdate = 0; + this.nTotalCycles = this.nRunCycles = this.nBurstCycles = this.nStepCycles = this.nSnapCycles = 0; + this.resetChecksum(); + this.setSpeed(1); + } + + /** + * getSpeed() + * + * @this {CPUPDP10} + * @return {number} the current speed multiplier + */ + getSpeed() + { + return this.nCyclesMultiplier; + } + + /** + * getSpeedCurrent() + * + * @this {CPUPDP10} + * @return {string} the current speed, in mhz, as a string formatted to two decimal places + */ + getSpeedCurrent() + { + /* + * TODO: Has toFixed() been "fixed" in all browsers (eg, IE) to return a rounded value now? + */ + return ((this.flags.running)? (this.mhz.toFixed(2) + "Mhz") : "Stopped"); + } + + /** + * getSpeedTarget() + * + * @this {CPUPDP10} + * @return {string} the target speed, in mhz, as a string formatted to two decimal places + */ + getSpeedTarget() + { + /* + * TODO: Has toFixed() been "fixed" in all browsers (eg, IE) to return a rounded value now? + */ + return this.mhzTarget.toFixed(2) + "Mhz"; + } + + /** + * setSpeed(nMultiplier, fUpdateFocus) + * + * NOTE: This used to return the target speed, in mhz, but no callers appear to care at this point. + * + * @desc Whenever the speed is changed, the running cycle count and corresponding start time must be reset, + * so that the next effective speed calculation obtains sensible results. In fact, when runCPU() initially calls + * setSpeed() with no parameters, that's all this function does (it doesn't change the current speed setting). + * + * @this {CPUPDP10} + * @param {number} [nMultiplier] is the new proposed multiplier (reverts to 1 if the target was too high) + * @param {boolean} [fUpdateFocus] is true to update Computer focus + * @return {boolean} true if successful, false if not + */ + setSpeed(nMultiplier, fUpdateFocus) + { + var fSuccess = false; + if (nMultiplier !== undefined) { + /* + * If we haven't reached 80% (0.8) of the current target speed, revert to a multiplier of one (1). + */ + if (this.mhz / this.mhzTarget < 0.8) { + nMultiplier = 1; + } else { + fSuccess = true; + } + this.nCyclesMultiplier = nMultiplier; + var mhz = this.mhzDefault * this.nCyclesMultiplier; + if (this.mhzTarget != mhz) { + this.mhzTarget = mhz; + var sSpeed = this.getSpeedTarget(); + var controlSpeed = this.bindings["setSpeed"]; + if (controlSpeed) controlSpeed.textContent = sSpeed; + this.println("target speed: " + sSpeed); + } + if (fUpdateFocus && this.cmp) this.cmp.setFocus(); + } + this.addCycles(this.nRunCycles); + this.nRunCycles = 0; + this.msStartRun = Component.getTime(); + this.msEndThisRun = 0; + this.calcCycles(); + return fSuccess; + } + + /** + * calcSpeed(nCycles, msElapsed) + * + * @this {CPUPDP10} + * @param {number} nCycles + * @param {number} msElapsed + */ + calcSpeed(nCycles, msElapsed) + { + if (msElapsed) { + this.mhz = Math.round(nCycles / (msElapsed * 10)) / 100; + if (msElapsed >= 86400000) { + this.nTotalCycles = 0; + this.setSpeed(); // reset all counters once per day so that we never have to worry about overflow + } + } + } + + /** + * calcStartTime() + * + * @this {CPUPDP10} + */ + calcStartTime() + { + if (this.nCyclesRecalc >= this.nCyclesPerSecond) { + this.calcCycles(true); + } + this.nCyclesThisRun = 0; + this.msStartThisRun = Component.getTime(); + + /* + * Try to detect situations where the browser may have throttled us, such as when the user switches + * to a different tab; in those situations, Chrome and Safari may restrict setTimeout() callbacks + * to roughly one per second. + * + * Another scenario: the user resizes the browser window. setTimeout() callbacks are not throttled, + * but there can still be enough of a lag between the callbacks that CPU speed will be noticeably + * erratic if we don't compensate for it here. + * + * We can detect throttling/lagging by verifying that msEndThisRun (which was set at the end of the + * previous run and includes any requested sleep time) is comparable to the current msStartThisRun; + * if the delta is significant, we compensate by bumping msStartRun forward by that delta. + * + * This shouldn't be triggered when the Debugger halts the CPU, because setSpeed() -- which is called + * whenever the CPU starts running again -- zeroes msEndThisRun. + * + * This also won't do anything about other internal delays; for example, Debugger message() calls. + * By the time the message() function has called yieldCPU(), the cost of the message has already been + * incurred, so it will be end up being charged against the instruction(s) that triggered it. + * + * TODO: Consider calling yieldCPU() sooner from message(), so that it can arrange for the msEndThisRun + * "snapshot" to occur sooner; it's unclear, however, whether that will really improve the CPU's ability + * to hit its target speed, since you would expect any instruction that displays a message to be an + * EXTREMELY slow instruction. + */ + if (this.msEndThisRun) { + var msDelta = this.msStartThisRun - this.msEndThisRun; + if (msDelta > this.msPerYield) { + if (MAXDEBUG) this.println("large time delay: " + msDelta + "ms"); + this.msStartRun += msDelta; + /* + * Bumping msStartRun forward should NEVER cause it to exceed msStartThisRun; however, just + * in case, I make absolutely sure it cannot happen, since doing so could result in negative + * speed calculations. + */ + this.assert(this.msStartRun <= this.msStartThisRun); + if (this.msStartRun > this.msStartThisRun) { + this.msStartRun = this.msStartThisRun; + } + } + } + } + + /** + * calcRemainingTime() + * + * @this {CPUPDP10} + * @return {number} + */ + calcRemainingTime() + { + this.msEndThisRun = Component.getTime(); + + var msYield = this.msPerYield; + if (this.nCyclesThisRun) { + /* + * Normally, we would assume we executed a full quota of work over msPerYield, but since the CPU + * now has the option of calling yieldCPU(), that might not be true. If nCyclesThisRun is correct, then + * the ratio of nCyclesThisRun/nCyclesPerYield should represent the percentage of work we performed, + * and so applying that percentage to msPerYield should give us a better estimate of work vs. time. + */ + msYield = Math.round(msYield * this.nCyclesThisRun / this.nCyclesPerYield); + } + + var msElapsedThisRun = this.msEndThisRun - this.msStartThisRun; + var msRemainsThisRun = msYield - msElapsedThisRun; + + /* + * We could pass only "this run" results to calcSpeed(): + * + * nCycles = this.nCyclesThisRun; + * msElapsed = msElapsedThisRun; + * + * but it seems preferable to use longer time periods and hopefully get a more accurate speed. + * + * Also, if msRemainsThisRun >= 0 && this.nCyclesMultiplier == 1, we could pass these results instead: + * + * nCycles = this.nCyclesThisRun; + * msElapsed = this.msPerYield; + * + * to insure that we display a smooth, constant N Mhz. But for now, I prefer seeing any fluctuations. + */ + var nCycles = this.nRunCycles; + var msElapsed = this.msEndThisRun - this.msStartRun; + + if (MAXDEBUG && msRemainsThisRun < 0 && this.nCyclesMultiplier > 1) { + this.println("warning: updates @" + msElapsedThisRun + "ms (prefer " + Math.round(msYield) + "ms)"); + } + + this.calcSpeed(nCycles, msElapsed); + + if (msRemainsThisRun < 0 || this.mhz < this.mhzTarget) { + /* + * Try "throwing out" the effects of large anomalies, by moving the overall run start time up; + * ordinarily, this should only happen when the someone is using an external Debugger or some other + * tool or feature that is interfering with our overall execution. + */ + if (msRemainsThisRun < -1000) { + this.msStartRun -= msRemainsThisRun; + } + /* + * If the last burst took MORE time than we allotted (ie, it's taking more than 1 second to simulate + * nCyclesPerSecond), all we can do is yield for as little time as possible (ie, 0ms) and hope that the + * simulation is at least usable. + */ + msRemainsThisRun = 0; + } + + /* + * Last but not least, update nCyclesRecalc, so that when runCPU() starts up again and calls calcStartTime(), + * it'll be ready to decide if calcCycles() should be called again. + */ + this.nCyclesRecalc += this.nCyclesThisRun; + + if (DEBUG && this.messageEnabled(MessagesPDP10.LOG) && msRemainsThisRun) { + this.log("calcRemainingTime: " + msRemainsThisRun + "ms to sleep after " + this.msEndThisRun + "ms"); + } + + this.msEndThisRun += msRemainsThisRun; + return msRemainsThisRun; + } + + /** + * addTimer(callBack) + * + * Components that want to have timers that periodically fire after some number of milliseconds call + * addTimer() to create the timer, and then setTimer() every time they want to arm it. There is currently + * no removeTimer() because these are generally used for the entire lifetime of a component. + * + * Internally, each timer entry is a preallocated Array with two entries: a cycle countdown in element [0] + * and a callback function in element [1]. A timer is initially dormant; dormant timers have a countdown + * value of -1 (although any negative number will suffice) and active timers have a non-negative value. + * + * Why not use JavaScript's setTimeout() instead? Good question. For a good answer, see setTimer() below. + * + * TODO: Consider making the addTimer() and setTimer() interfaces more like the addIRQ() and setIRQ() + * interfaces (which return the underlying object instead of an array index) and maintaining a separate list + * of active timers, in order of highest to lowest cycle countdown values, as this could speed up + * getBurstCycles() and updateTimers() functions ever so slightly. + * + * @this {CPUPDP10} + * @param {function()} callBack + * @return {number} timer index + */ + addTimer(callBack) + { + var iTimer = this.aTimers.length; + this.aTimers.push([-1, callBack]); + return iTimer; + } + + /** + * setTimer(iTimer, ms, fReset) + * + * Using the timer index from a previous addTimer() call, this sets that timer to fire after the + * specified number of milliseconds. + * + * This is preferred over JavaScript's setTimeout(), because all our timers are effectively paused when + * the CPU is paused (eg, when the Debugger halts execution). Moreover, setTimeout() handlers only run after + * runCPU() yields, which is far too granular for some components (eg, when the SerialPort tries to simulate + * interrupts at 9600 baud). + * + * Ideally, the only function that would use setTimeout() is runCPU(), while the rest of the components + * use setTimer(); however, due to legacy code (ie, code that predates these functions) and/or laziness, + * that may not be the case. + * + * @this {CPUPDP10} + * @param {number} iTimer + * @param {number} ms (converted into a cycle countdown internally) + * @param {boolean} [fReset] (true if the timer should be reset even if already armed) + * @return {number} (number of cycles used to arm timer, or -1 if error) + */ + setTimer(iTimer, ms, fReset) + { + var nCycles = -1; + if (iTimer >= 0 && iTimer < this.aTimers.length) { + if (fReset || this.aTimers[iTimer][0] < 0) { + nCycles = this.getMSCycles(ms); + /* + * We must now confront the following problem: if the CPU is currently executing a burst of cycles, + * the number of cycles it has executed in that burst so far must NOT be charged against the cycle + * timeout we're about to set. The simplest way to resolve that is to immediately call endBurst() + * and bias the cycle timeout by the number of cycles that the burst executed. + */ + if (this.flags.running) { + nCycles += this.endBurst(); + } + this.aTimers[iTimer][0] = nCycles; + } + } + return nCycles; + } + + /** + * getMSCycles(ms) + * + * @this {CPUPDP10} + * @param {number} ms + * @return {number} number of corresponding cycles + */ + getMSCycles(ms) + { + return ((this.nCyclesPerSecond * this.nCyclesMultiplier) / 1000 * ms)|0; + } + + /** + * getBurstCycles(nCycles) + * + * Used by runCPU() to get min(nCycles,[timer cycle counts]) + * + * @this {CPUPDP10} + * @param {number} nCycles (number of cycles about to execute) + * @return {number} (either nCycles or less if a timer needs to fire) + */ + getBurstCycles(nCycles) + { + for (var i = this.aTimers.length - 1; i >= 0; i--) { + var timer = this.aTimers[i]; + this.assert(!isNaN(timer[0])); + if (timer[0] < 0) continue; + if (nCycles > timer[0]) { + nCycles = timer[0]; + } + } + return nCycles; + } + + /** + * saveTimers() + * + * @this {CPUPDP10} + * @return {Array.} + */ + saveTimers() + { + var aTimerCycles = []; + for (var i = 0; i < this.aTimers.length; i++) { + var timer = this.aTimers[i]; + aTimerCycles.push(timer[0]); + } + return aTimerCycles; + } + + /** + * restoreTimers(aTimerCycles) + * + * @this {CPUPDP10} + * @param {Array.} aTimerCycles + */ + restoreTimers(aTimerCycles) + { + this.assert(aTimerCycles.length === this.aTimers.length); + for (var i = 0; i < this.aTimers.length && i < aTimerCycles.length; i++) { + var timer = this.aTimers[i]; + timer[0] = aTimerCycles[i]; + } + } + + /** + * updateTimers(nCycles) + * + * Used by runCPU() to reduce all active timer countdown values by the number of cycles just executed; + * this is the function that actually "fires" any timer(s) whose countdown has reached (or dropped below) + * zero, invoking their callback function. + * + * @this {CPUPDP10} + * @param {number} nCycles (number of cycles actually executed) + */ + updateTimers(nCycles) + { + for (var i = this.aTimers.length - 1; i >= 0; i--) { + var timer = this.aTimers[i]; + this.assert(!isNaN(timer[0])); + if (timer[0] < 0) continue; + timer[0] -= nCycles; + if (timer[0] <= 0) { + timer[0] = -1; // zero is technically an "active" value, so ensure the timer is dormant now + timer[1](); // safe to invoke the callback function now + } + } + } + + /** + * endBurst(fReset) + * + * @this {CPUPDP10} + * @param {boolean} [fReset] + * @return {number} (number of cycles executed in the most recent burst) + */ + endBurst(fReset) + { + var nCycles = this.nBurstCycles -= this.nStepCycles; + /* + * In addition to zeroing nStepCycles, it's important that we also zero nSnapCycles, because if a CPU + * burst is being ended after nStepCycles has been "snapped" (because a certain opcode has an unusual timing + * calculation that must be based on a "snapped" cycle count rather the opcode's starting cycle count), we + * could inadvertently undo the endBurst() if the original "snapped" value was used to update nStepCycles. + */ + this.nStepCycles = this.nSnapCycles = 0; + if (fReset) this.nBurstCycles = 0; + return nCycles; + } + + /** + * runCPU() + * + * @this {CPUPDP10} + */ + runCPU() + { + if (!this.flags.running) return; + + /* + * calcStartTime() initializes the cycle counter and timestamp for this runCPU() invocation, and optionally + * recalculates the the maximum number of cycles for each burst if the nCyclesRecalc threshold has been reached. + */ + this.calcStartTime(); + + try { + do { + /* + * nCycles is how many cycles we WANT to run on each iteration of stepCPU(), and may be as + * HIGH as nCyclesPerYield, but it may be significantly less. getBurstCycles() will adjust + * nCycles downward if any CPU timers need to fire during the next burst. + */ + var nCycles = this.getBurstCycles(this.flags.checksum? 1 : this.nCyclesPerYield); + + /* + * Execute the burst. + */ + try { + this.stepCPU(nCycles); + } + catch(exception) { + /* + * We assume that any numeric exception was explicitly thrown by the CPU to interrupt the + * current instruction (and by extension, the current burst, but not the current run). All + * other exceptions are re-thrown to the catch below, which will attempt a stack dump. + */ + if (typeof exception != "number") throw exception; + } + + /* + * Terminate the burst, returning the number of cycles that stepCPU() actually ran. + */ + nCycles = this.endBurst(true); + + /* + * Add nCycles to nCyclesThisRun, as well as nRunCycles (the cycle count since the CPU started). + */ + this.nCyclesThisRun += nCycles; + this.nRunCycles += nCycles; + this.updateChecksum(nCycles); + + /* + * Update any/all timers, firing those whose cycle countdowns have reached (or dropped below) zero. + */ + this.updateTimers(nCycles); + + this.nCyclesNextYield -= nCycles; + if (this.nCyclesNextYield <= 0) { + this.nCyclesNextYield += this.nCyclesPerYield; + if (++this.nYieldsSinceStatusUpdate >= CPUPDP10.YIELDS_PER_STATUS) { + this.updateDisplays(); + this.nYieldsSinceStatusUpdate = 0; + } + break; + } + } while (this.flags.running); + } + catch (e) { + this.stopCPU(); + if (this.cmp) this.cmp.stop(Component.getTime(), this.getCycles()); + this.setError(e.stack || e.message); + return; + } + + if (this.flags.running) setTimeout(this.onRunTimeout, this.calcRemainingTime()); + } + + /** + * startCPU(fUpdateFocus) + * + * For use by any component that wants to start the CPU. + * + * @param {boolean} [fUpdateFocus] + * @return {boolean} + */ + startCPU(fUpdateFocus) + { + if (this.isError()) { + return false; + } + if (this.flags.running) { + this.println(this.toString() + " busy"); + return false; + } + /* + * setSpeed() without a speed parameter leaves the selected speed in place, but also resets the + * cycle counter and timestamp for the current series of runCPU() calls, calculates the maximum number + * of cycles for each burst based on the last known effective CPU speed, and resets the nCyclesRecalc + * threshold counter. + */ + this.setSpeed(); + this.flags.running = true; + this.flags.starting = true; + var controlRun = this.bindings["run"]; + if (controlRun) controlRun.textContent = "Halt"; + if (this.cmp) { + if (fUpdateFocus) this.cmp.setFocus(true); + this.cmp.start(this.msStartRun, this.getCycles()); + } + if (!this.dbg) this.status("Started"); + setTimeout(this.onRunTimeout, 0); + return true; + } + + /** + * stepCPU(nMinCycles) + * + * This will be implemented by the CPUStatePDP10 component. + * + * @this {CPUPDP10} + * @param {number} nMinCycles (0 implies a single-step, and therefore breakpoints should be ignored) + * @return {number} of cycles executed; 0 indicates that the last instruction was not executed + */ + stepCPU(nMinCycles) + { + return 0; + } + + /** + * stopCPU(fComplete) + * + * For use by any component that wants to stop the CPU. + * + * This similar to yieldCPU(), but it doesn't need to zero nCyclesNextYield to break out of runCPU(); + * it simply needs to clear fRunning (well, "simply" may be oversimplifying a bit....) + * + * @this {CPUPDP10} + * @param {boolean} [fComplete] + * @return {boolean} true if the CPU was stopped, false if it was already stopped + */ + stopCPU(fComplete) + { + var fStopped = false; + if (this.flags.running) { + this.endBurst(); + this.addCycles(this.nRunCycles); + this.nRunCycles = 0; + this.flags.running = false; + var controlRun = this.bindings["run"]; + if (controlRun) controlRun.textContent = "Run"; + if (this.cmp) { + this.cmp.stop(Component.getTime(), this.getCycles()); + } + fStopped = true; + if (!this.dbg) this.status("Stopped"); + } + this.flags.complete = fComplete; + return fStopped; + } + + /** + * yieldCPU() + * + * Similar to stopCPU() with regard to how it resets various cycle countdown values, but the CPU + * remains in a "running" state. + * + * @this {CPUPDP10} + */ + yieldCPU() + { + this.endBurst(); // this will break us out of stepCPU() + this.nCyclesNextYield = 0; // this will break us out of runCPU(), once we break out of stepCPU() + /* + * The Debugger calls yieldCPU() after every message() to ensure browser responsiveness, but it looks + * odd for those messages to show CPU state changes if the Control Panel, Video display, etc, does not, + * so I've added this call to try to keep things looking synchronized. + */ + this.updateDisplays(); + } +} + +/* + * Constants that control the frequency at which various updates should occur. + * + * These values do NOT control the simulation directly. Instead, they are used by + * calcCycles(), which uses the nCyclesPerSecond passed to the constructor as a starting + * point and computes the following variables: + * + * this.nCyclesPerYield: (this.nCyclesPerSecond / CPUPDP10.YIELDS_PER_SECOND) + * + * The above variables are also multiplied by any cycle multiplier in effect, via setSpeed(), + * and then they're used to initialize another set of variables for each runCPU() iteration: + * + * this.nCyclesNextYield: this.nCyclesPerYield + */ +CPUPDP10.YIELDS_PER_SECOND = 30; // just a gut feeling for the MINIMUM number of yields per second +CPUPDP10.YIELDS_PER_STATUS = 15; // every 15 yields (ie, twice per second), perform CPU status updates + +CPUPDP10.BUTTONS = ["power", "reset"]; + +if (NODE) module.exports = CPUPDP10; diff --git a/modules/pdp10/lib/cpustate.js b/modules/pdp10/lib/cpustate.js new file mode 100644 index 000000000..b5bf1e8e3 --- /dev/null +++ b/modules/pdp10/lib/cpustate.js @@ -0,0 +1,802 @@ +/** + * @fileoverview Implements the PDP-10 CPU component. + * @author Jeff Parsons + * @copyright © Jeff Parsons 2012-2017 + * + * This file is part of PCjs, a computer emulation software project at . + * + * PCjs is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCjs. If not, + * see . + * + * You are required to include the above copyright notice in every modified copy of this work + * and to display that copyright notice when the software starts running; see COPYRIGHT in + * . + * + * Some PCjs files also attempt to load external resource files, such as character-image files, + * ROM files, and disk image files. Those external resource files are not considered part of PCjs + * for purposes of the GNU General Public License, and the author does not claim any copyright + * as to their contents. + */ + +"use strict"; + +if (NODE) { + var Str = require("../../shared/lib/strlib"); + var Web = require("../../shared/lib/weblib"); + var Component = require("../../shared/lib/component"); + var State = require("../../shared/lib/state"); + var PDP10 = require("./defines"); + var BusPDP10 = require("./bus"); + var CPUPDP10 = require("./cpu"); + var MessagesPDP10 = require("./messages"); + var MemoryPDP10 = require("./memory"); +} + +/* + * Overview of Device Interrupt Support + * + * Originally, the CPU maintained a queue of requested interrupts. Entries in this queue recorded a device's + * priority, vector, and delay (ie, a number of instructions to execute before dispatching the interrupt). This + * queue would constantly grow and shrink as requests were issued and dispatched, and as long as there was something + * in the queue, the CPU was constantly examining it. + * + * Now we are trying something more efficient. First, for devices that require delays (like the SerialPort's receiver + * and transmitter buffer registers, which are supposed to "clock" the data in and out at a specific baud rate), the + * CPU offers timer services that will "fire" a callback after a specified delay, which are much more efficient than + * requiring the CPU to dive into an interrupt queue and decrement delay counts on every instruction. + * + * Second, devices that generate interrupts will allocate an IRQ object during initialization; we will no longer + * be creating and destroying interrupt event objects and inserting/deleting them in a constantly changing queue. + * Each IRQ contains properties that never change (eg, the vector and priority), along with a "next" pointer that's + * only used when the IRQ is active. + * + * When a device decides it's time to interrupt (either at the end of some I/O operation or when a timer has fired), + * it will simply set the IRQ, which basically means that the IRQ will be linked onto a list of active IRQs, in + * priority order, so that when the CPU is ready to acknowledge interrupts, it need only check the top of the active + * IRQ list. + */ + +/** + * @typedef {{ + * vector: number, + * priority: number, + * message: number, + * name: (string|null), + * next: (IRQ|null) + * }} + */ +var IRQ; + +/** + * @class CPUStatePDP10 + * @unrestricted + */ +class CPUStatePDP10 extends CPUPDP10 { + /** + * CPUStatePDP10(parmsCPU) + * + * The CPUStatePDP10 class uses the following (parmsCPU) properties: + * + * model: a number (eg, 1001) that should match one of the PDP10.MODEL_* values + * addrReset: reset address (default is 0) + * + * This extends the CPU class and passes any remaining parmsCPU properties to the CPU class + * constructor, along with a default speed (cycles per second) based on the specified (or default) + * CPU model number. + * + * @param {Object} parmsCPU + */ + constructor(parmsCPU) + { + var nCyclesDefault = 0; + var model = +parmsCPU['model'] || PDP10.MODEL_KA10; + + switch(model) { + case PDP10.MODEL_KA10: + default: + nCyclesDefault = 6666667; + break; + } + + /* + * ES6 ALERT: Classes cannot access "this" until all superclasses have been initialized as well. + */ + super(parmsCPU, nCyclesDefault); + + this.model = model; + this.addrReset = +parmsCPU['addrReset'] || 0; + + /** @type {IRQ|null} */ + this.irqNext = null; // the head of the active IRQ list, in priority order + + /** @type {Array.} */ + this.aIRQs = []; // list of all IRQs, active or not (to be used for auto-configuration) + + this.flags.complete = false; + } + + /** + * initBus(cmp, bus, cpu, dbg) + * + * Called once the Bus has been initialized. + * + * @this {CPUStatePDP10} + * @param {ComputerPDP10} cmp + * @param {BusPDP10} bus + * @param {CPUPDP10} cpu + * @param {DebuggerPDP10} dbg + */ + initBus(cmp, bus, cpu, dbg) + { + super.initBus(cmp, bus, cpu, dbg); + } + + /** + * reset() + * + * @this {CPUStatePDP10} + */ + reset() + { + this.status("Model " + this.model); + if (this.flags.running) this.stopCPU(); + this.initCPU(); + this.resetCycles(); + this.clearError(); // clear any fatal error/exception that setError() may have flagged + super.reset(); + } + + /** + * initCPU() + * + * @this {CPUStatePDP10} + */ + initCPU() + { + this.regPC = this.pcLast = this.addrReset; + + /* + * This is queried and displayed by the Panel when it's not displaying its own ADDRESS register + * (which takes precedence when, for example, you've manually halted the CPU and are independently + * examining the contents of other addresses). + * + * We initialize it to the current PC. + */ + this.addrLast = this.regPC; + + /* + * opFlags contains various conditions that stepCPU() needs to be aware of. + */ + this.opFlags = 0; + + this.setMemoryAccess(); + + this.resetIRQs(); + } + + /** + * setMemoryAccess() + * + * @this {CPUStatePDP10} + */ + setMemoryAccess() + { + this.readWord = this.readWordFromPhysical; + this.writeWord = this.writeWordToPhysical; + } + + /** + * setReset(addr, fStart, bUnit, addrStack) + * + * @this {CPUStatePDP10} + * @param {number} addr + * @param {boolean} [fStart] (true if a "startable" image was just loaded, false if not) + * @param {number} [bUnit] (boot unit #) + * @param {number} [addrStack] + */ + setReset(addr, fStart, bUnit, addrStack) + { + this.addrReset = addr; + + this.setPC(addr); + + if (fStart) { + if (!this.flags.powered) { + this.flags.autoStart = true; + } + else if (!this.flags.running) { + this.startCPU(); + } + } + else { + if (this.dbg && this.flags.powered) { + /* + * TODO: Review the decision to always stop the CPU if the Debugger is loaded. Note that + * when stopCPU() stops a running CPU, the Debugger gets notified, so no need to notify it again. + * + * TODO: There are more serious problems to deal with if another component is slamming a new PC down + * the CPU's throat (presumably while also dropping some new code into RAM) while the CPU is running; + * we should probably force a complete reset, but for now, it's up to the user to hit the reset button + * themselves. + */ + if (!this.stopCPU() && !this.cmp.flags.reset) { + this.dbg.updateStatus(); + this.cmp.updateDisplays(-1); + } + } + else if (fStart === false) { + this.stopCPU(); + } + } + if (!this.isRunning() && this.panel) this.panel.stop(); + } + + /** + * getChecksum() + * + * TODO: Implement + * + * @this {CPUStatePDP10} + * @return {number} a 32-bit summation of key elements of the current CPU state (used by the CPU checksum code) + */ + getChecksum() + { + return 0; + } + + /** + * save() + * + * @this {CPUStatePDP10} + * @return {Object|null} + */ + save() + { + var state = new State(this); + state.set(0, [ + this.regPC, + this.pcLast, + this.addrLast, + this.opFlags + ]); + state.set(1, []); + state.set(2, [this.nTotalCycles, this.getSpeed(), this.flags.autoStart]); + state.set(3, this.saveIRQs()); + state.set(4, this.saveTimers()); + return state.data(); + } + + /** + * restore(data) + * + * @this {CPUStatePDP10} + * @param {Object} data + * @return {boolean} true if restore successful, false if not + */ + restore(data) + { + /* + * ES6 ALERT: A handy destructuring assignment, which makes it easy to perform the inverse + * of what save() does when it collects a bunch of object properties into an array. + */ + [ + this.regPC, + this.pcLast, + this.addrLast, + this.opFlags + ] = data[0]; + + var a = data[2]; + this.nTotalCycles = a[0]; + this.setSpeed(a[1]); + this.flags.autoStart = a[2]; + + this.restoreIRQs(data[3]); + this.restoreTimers(data[4]); + return true; + } + + /** + * getOpcode() + * + * NOTE: This function is nothing more than a convenience, and we fully expect it to be inlined at runtime. + * + * @this {CPUStatePDP10} + * @return {number} + */ + getOpcode() + { + var pc = this.regPC; + var opCode = this.readWord(pc); + this.regPC = (pc + 1) % PDP10.ADDR_LIMIT; + return opCode; + } + + /** + * advancePC(off) + * + * NOTE: This function is nothing more than a convenience, and we fully expect it to be inlined at runtime. + * + * @this {CPUStatePDP10} + * @param {number} off + * @return {number} (original PC) + */ + advancePC(off) + { + var pc = this.regPC; + this.regPC = (pc + off) % PDP10.ADDR_LIMIT; + return pc; + } + + /** + * getPC() + * + * NOTE: This function is nothing more than a convenience, and we fully expect it to be inlined at runtime. + * + * @this {CPUStatePDP10} + * @return {number} + */ + getPC() + { + return this.regPC; + } + + /** + * getLastAddr() + * + * @this {CPUStatePDP10} + * @return {number} + */ + getLastAddr() + { + return this.addrLast; + } + + /** + * getLastPC() + * + * @this {CPUStatePDP10} + * @return {number} + */ + getLastPC() + { + return this.pcLast; + } + + /** + * setPC() + * + * NOTE: Unlike other PCjs emulators, such as PCx86, where all PC updates MUST go through the setPC() + * function, this function is nothing more than a convenience, because in the PDP-11, the PC can be loaded + * like any other general register. We fully expect this function to be inlined at runtime. + * + * @this {CPUStatePDP10} + * @param {number} addr + */ + setPC(addr) + { + this.regPC = addr % PDP10.ADDR_LIMIT; + } + + /** + * addIRQ(vector, priority, message) + * + * @this {CPUStatePDP10} + * @param {number} vector (-1 for floating vector) + * @param {number} priority + * @param {number} [message] + * @return {IRQ} + */ + addIRQ(vector, priority, message) + { + var irq = {vector: vector, priority: priority, message: message || 0, name: null, next: null}; + this.aIRQs.push(irq); + return irq; + } + + /** + * insertIRQ(irq) + * + * @this {CPUStatePDP10} + * @param {IRQ} irq + */ + insertIRQ(irq) + { + if (irq != this.irqNext) { + var irqPrev = this.irqNext; + if (!irqPrev || irqPrev.priority <= irq.priority) { + irq.next = irqPrev; + this.irqNext = irq; + } else { + do { + var irqNext = irqPrev.next; + if (!irqNext || irqNext.priority <= irq.priority) { + irq.next = irqNext; + irqPrev.next = irq; + break; + } + irqPrev = irqNext; + } while (irqPrev); + } + } + /* + * See the writeXCSR() function for an explanation of why signalling an IRQ hardware interrupt + * should be done using IRQ_DELAY rather than setting IRQ directly. + */ + this.opFlags |= PDP10.OPFLAG.IRQ_DELAY; + } + + /** + * removeIRQ(irq) + * + * @this {CPUStatePDP10} + * @param {IRQ} irq + */ + removeIRQ(irq) + { + var irqPrev = this.irqNext; + if (irqPrev == irq) { + this.irqNext = irq.next; + } else { + while (irqPrev) { + var irqNext = irqPrev.next; + if (irqNext == irq) { + irqPrev.next = irqNext.next; + break; + } + irqPrev = irqNext; + } + } + /* + * We could also set irq.next to null now, but strictly speaking, that shouldn't be necessary. + * + * Last but not least, if there's still an IRQ on the active IRQ list, we need to make sure IRQ_DELAY + * is still set. + */ + if (this.irqNext) { + this.opFlags |= PDP10.OPFLAG.IRQ_DELAY; + } + } + + /** + * setIRQ(irq) + * + * @this {CPUStatePDP10} + * @param {IRQ|null} irq + */ + setIRQ(irq) + { + if (irq) { + this.insertIRQ(irq); + if (irq.message && this.messageEnabled(irq.message | MessagesPDP10.INT)) { + this.printMessage("setIRQ(vector=" + Str.toOct(irq.vector) + ",priority=" + irq.priority + ")", true, true); + } + } + } + + /** + * clearIRQ(irq) + * + * @this {CPUStatePDP10} + * @param {IRQ|null} irq + */ + clearIRQ(irq) + { + if (irq) { + this.removeIRQ(irq); + if (irq.message && this.messageEnabled(irq.message | MessagesPDP10.INT)) { + this.printMessage("clearIRQ(vector=" + Str.toOct(irq.vector) + ",priority=" + irq.priority + ")", true, true); + } + } + } + + /** + * findIRQ(vector) + * + * @this {CPUStatePDP10} + * @param {number} vector + * @return {IRQ|null} + */ + findIRQ(vector) + { + for (var i = 0; i < this.aIRQs.length; i++) { + var irq = this.aIRQs[i]; + if (irq.vector === vector) return irq; + } + return null; + } + + /** + * checkIRQs(priority) + * + * @this {CPUStatePDP10} + * @param {number} priority + * @return {IRQ|null} + */ + checkIRQs(priority) + { + return (this.irqNext && this.irqNext.priority > priority)? this.irqNext : null; + } + + /** + * resetIRQs(priority) + * + * @this {CPUStatePDP10} + */ + resetIRQs() + { + this.irqNext = null; + } + + /** + * saveIRQs() + * + * @this {CPUStatePDP10} + * @return {Array.} + */ + saveIRQs() + { + var aIRQVectors = []; + var irq = this.irqNext; + while (irq) { + aIRQVectors.push(irq.vector); + irq = irq.next; + } + return aIRQVectors; + } + + /** + * restoreIRQs(aIRQVectors) + * + * @this {CPUStatePDP10} + * @param {Array.} aIRQVectors + */ + restoreIRQs(aIRQVectors) + { + for (var i = aIRQVectors.length - 1; i >= 0; i--) { + var irq = this.findIRQ(aIRQVectors[i]); + this.assert(irq != null); + if (irq) { + irq.next = this.irqNext; + this.irqNext = irq; + } + } + } + + /** + * checkInterrupts() + * + * @this {CPUStatePDP10} + * @return {boolean} true if an interrupt was dispatched, false if not + */ + checkInterrupts() + { + var fInterrupt = false; + + if (this.opFlags & PDP10.OPFLAG.IRQ) { + + // var vector = PDP10.TRAP.PIRQ; + // var priority = (this.regPIR & PDP10.PSW.PRI) >> PDP10.PSW.SHIFT.PRI; + // + // var irq = this.checkIRQs(priority); + // if (irq) { + // vector = irq.vector; + // priority = irq.priority; + // } + // + // if (this.dispatchInterrupt(vector, priority)) { + // if (irq) this.removeIRQ(irq); + // fInterrupt = true; + // } + + if (!this.irqNext) { + this.opFlags &= ~PDP10.OPFLAG.IRQ; + } + } + else if (this.opFlags & PDP10.OPFLAG.IRQ_DELAY) { + /* + * We know that IRQ (bit 2) is clear, so since IRQ_DELAY (bit 0) is set, incrementing opFlags + * will eventually transform IRQ_DELAY into IRQ, without affecting any other (higher) bits. + */ + this.opFlags++; + } + return fInterrupt; + } + + /** + * dispatchInterrupt(vector, priority) + * + * TODO: The process of dispatching an interrupt MUST cost some cycles; either trap() needs to assess + * that cost, or we do. + * + * @this {CPUStatePDP10} + * @param {number} vector + * @param {number} priority + * @return {boolean} (true if dispatched, false if not) + */ + dispatchInterrupt(vector, priority) + { + return false; + } + + /** + * isWaiting() + * + * @this {CPUStatePDP10} + * @return {boolean} (true if OPFLAG.WAIT is set, false otherwise) + */ + isWaiting() + { + return !!(this.opFlags & PDP10.OPFLAG.WAIT); + } + + /** + * readWordFromPhysical(addr) + * + * This is a handler set up by setMemoryAccess(). All calls should go through readWord(). + * + * @this {CPUStatePDP10} + * @param {number} addr + * @return {number} + */ + readWordFromPhysical(addr) + { + return this.bus.getWord(this.addrLast = addr); + } + + /** + * writeWordToPhysical(addr, data) + * + * This is a handler set up by setMemoryAccess(). All calls should go through writeWord(). + * + * @this {CPUStatePDP10} + * @param {number} addr + * @param {number} data + */ + writeWordToPhysical(addr, data) + { + this.bus.setWord(this.addrLast = addr, data); + } + + /** + * stepCPU(nMinCycles) + * + * NOTE: Single-stepping should not be confused with the Trap flag; single-stepping is a Debugger + * operation that's completely independent of Trap status. The CPU can go in and out of Trap mode, + * in and out of h/w interrupt service routines (ISRs), etc, but from the Debugger's perspective, + * they're all one continuous stream of instructions that can be stepped or run at will. Moreover, + * stepping vs. running should never change the behavior of the simulation. + * + * @this {CPUStatePDP10} + * @param {number} nMinCycles (0 implies a single-step, and therefore breakpoints should be ignored) + * @return {number} of cycles executed; 0 indicates a pre-execution condition (ie, an execution breakpoint + * was hit), -1 indicates a post-execution condition (eg, a read or write breakpoint was hit), and a positive + * number indicates successful completion of that many cycles (which should always be >= nMinCycles). + */ + stepCPU(nMinCycles) + { + /* + * The Debugger uses complete to determine if the instruction completed (true) or was interrupted + * by a breakpoint or some other exceptional condition (false). NOTE: this does NOT include JavaScript + * exceptions, which stepCPU() expects the caller to catch using its own exception handler. + * + * The CPU relies on the use of stopCPU() rather than complete, because the CPU never single-steps + * (ie, nMinCycles is always some large number), whereas the Debugger does. And conversely, when the + * Debugger is single-stepping (even when performing multiple single-steps), fRunning is never set, + * so stopCPU() would have no effect as far as the Debugger is concerned. + */ + this.flags.complete = true; + + /* + * nDebugCheck is 1 if we want the Debugger's checkInstruction() to check every instruction, + * -1 if we want it to check just the first instruction, and 0 if there's no need for any checks. + */ + var nDebugCheck = (DEBUGGER && this.dbg)? (this.dbg.checksEnabled()? 1 : (this.flags.starting? -1 : 0)) : 0; + + /* + * nDebugState is needed only when nDebugCheck is non-zero; it is -1 if this is a single-step, 0 if + * this is the start of a new run, and 1 if this is a continuation of a previous run. It is used by + * checkInstruction() to determine if it should skip breakpoint checks and/or HALT instructions (ie, + * if nDebugState is <= zero). + */ + var nDebugState = (!nMinCycles)? -1 : (this.flags.starting? 0 : 1); + this.flags.starting = false; // we've moved beyond "starting" and have officially "started" now + + /* + * We move the minimum cycle count to nStepCycles (the number of cycles left to step), so that other + * functions have the ability to force that number to zero (eg, stopCPU()), and thus we don't have to check + * any other criteria to determine whether we should continue stepping or not. + */ + this.nBurstCycles = this.nStepCycles = nMinCycles; + + /* + * And finally, move the nDebugCheck state to an OPFLAG bit, so that the loop need check only one variable. + */ + this.opFlags = (this.opFlags & ~PDP10.OPFLAG.DEBUGGER) | (nDebugCheck? PDP10.OPFLAG.DEBUGGER : 0); + + do { + if (this.opFlags) { + /* + * NOTE: We still check DEBUGGER to ensure that this code will be compiled out of existence in + * non-DEBUGGER builds. + */ + if (DEBUGGER && (this.opFlags & PDP10.OPFLAG.DEBUGGER)) { + if (this.dbg.checkInstruction(this.getPC(), nDebugState)) { + this.stopCPU(); + break; + } + if (!++nDebugCheck) this.opFlags &= ~PDP10.OPFLAG.DEBUGGER; + if (!nDebugState) nDebugState++; + } + /* + * If we're in the IRQ or WAIT state, check for any pending interrupts. + * + * NOTE: It's no coincidence that we're checking this BEFORE any pending traps, because in rare + * cases (including some presented by those pesky "TRAP TEST" diagnostics), the process of dispatching + * an interrupt can trigger a TRAP_SP stack overflow condition, which must be dealt with BEFORE we + * execute the first instruction of the interrupt handler. + */ + if ((this.opFlags & (PDP10.OPFLAG.IRQ_MASK | PDP10.OPFLAG.WAIT)) /* && nDebugState >= 0 */) { + if (this.checkInterrupts()) { + if ((this.opFlags & PDP10.OPFLAG.DEBUGGER) && this.dbg.checkInstruction(this.getPC(), nDebugState)) { + this.stopCPU(); + break; + } + /* + * Since an interrupt was just dispatched, altering the normal flow of time and changing + * the future as we knew it, let's break out immediately if we're single-stepping, so that + * the Debugger gets to see the first instruction of the interrupt handler. NOTE: This + * assumes that we've still commented out the nDebugState check above that used to bypass + * checkInterrupts() when single-stepping. + */ + if (nDebugState < 0) break; + } + } + } + + this.opFlags &= PDP10.OPFLAG.PRESERVE; + + var opCode = this.getOpcode(); + + // this.decode(opCode); + + } while (this.nStepCycles > 0); + + return (this.flags.complete? this.nBurstCycles - this.nStepCycles : (this.flags.complete === false? -1 : 0)); + } + + /** + * CPUStatePDP10.init() + * + * This function operates on every HTML element of class "cpu", extracting the + * JSON-encoded parameters for the CPUStatePDP10 constructor from the element's "data-value" + * attribute, invoking the constructor (which in turn invokes the CPU constructor) + * to create a CPUStatePDP10 component, and then binding any associated HTML controls to the + * new component. + */ + static init() + { + var aeCPUs = Component.getElementsByClass(document, PDP10.APPCLASS, "cpu"); + for (var iCPU = 0; iCPU < aeCPUs.length; iCPU++) { + var eCPU = aeCPUs[iCPU]; + var parmsCPU = Component.getComponentParms(eCPU); + var cpu = new CPUStatePDP10(parmsCPU); + Component.bindComponentControls(cpu, eCPU, PDP10.APPCLASS); + } + } +} + +/* + * Initialize every CPU module on the page + */ +Web.onInit(CPUStatePDP10.init); + +if (NODE) module.exports = CPUStatePDP10; diff --git a/modules/pdp10/lib/debugger.js b/modules/pdp10/lib/debugger.js new file mode 100644 index 000000000..7d04e1a7d --- /dev/null +++ b/modules/pdp10/lib/debugger.js @@ -0,0 +1,3558 @@ +/** + * @fileoverview Implements the PDP-10 Debugger component. + * @author Jeff Parsons + * @copyright © Jeff Parsons 2012-2017 + * + * This file is part of PCjs, a computer emulation software project at . + * + * PCjs is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCjs. If not, + * see . + * + * You are required to include the above copyright notice in every modified copy of this work + * and to display that copyright notice when the software starts running; see COPYRIGHT in + * . + * + * Some PCjs files also attempt to load external resource files, such as character-image files, + * ROM files, and disk image files. Those external resource files are not considered part of PCjs + * for purposes of the GNU General Public License, and the author does not claim any copyright + * as to their contents. + */ + +"use strict"; + +if (NODE) { + var Str = require("../../shared/lib/strlib"); + var Usr = require("../../shared/lib/usrlib"); + var Web = require("../../shared/lib/weblib"); + var Component = require("../../shared/lib/component"); + var Debugger = require("../../shared/lib/debugger"); + var Keys = require("../../shared/lib/keys"); + var State = require("../../shared/lib/state"); + var PDP10 = require("./defines"); + var BusPDP10 = require("./bus"); + var MemoryPDP10 = require("./memory"); + var MessagesPDP10 = require("./messages"); +} + +/** + * DebuggerPDP10 Address Object + * + * addr address + * fPhysical true if this is a physical address + * fTemporary true if this is a temporary breakpoint address + * nBase set if the address contained an explicit base (eg, 16, 10, 8, etc) + * sCmd set for breakpoint addresses if there's an associated command string + * aCmds preprocessed commands (from sCmd) + * + * @typedef {{ + * addr:(number|null), + * fPhysical:(boolean), + * fTemporary:(boolean), + * nBase:(number|undefined), + * sCmd:(string|undefined), + * aCmds:(Array.|undefined) + * }} + */ +var DbgAddrPDP10; + +class DebuggerPDP10 extends Debugger { + /** + * DebuggerPDP10(parmsDbg) + * + * The DebuggerPDP10 component supports the following optional (parmsDbg) properties: + * + * commands: string containing zero or more commands, separated by ';' + * + * messages: string containing zero or more message categories to enable; + * multiple categories must be separated by '|' or ';'. Parsed by messageInit(). + * + * The DebuggerPDP10 component is an optional component that implements a variety of user + * commands for controlling the CPU, dumping and editing memory, etc. + * + * @param {Object} parmsDbg + */ + constructor(parmsDbg) + { + if (DEBUGGER) { + + super(parmsDbg); + + /* + * Since this Debugger doesn't use replaceRegs(), we can use parentheses instead of braces. + */ + this.fInit = false; + this.fParens = true; + + /* + * Most commands that require an address call parseAddr(), which defaults to dbgAddrNextCode + * or dbgAddrNextData when no address has been given. doDump() and doUnassemble(), in turn, + * update dbgAddrNextData and dbgAddrNextCode, respectively, when they're done. + * + * For TEMPORARY breakpoint addresses, we set fTemporary to true, so that they can be automatically + * cleared when they're hit. + */ + this.dbgAddrNextCode = this.newAddr(); + this.dbgAddrNextData = this.newAddr(); + this.dbgAddrAssemble = this.newAddr(); + + /* + * aSymbolTable is an array of SymbolTable objects, one per ROM or other chunk of address space, + * where each object contains the following properties: + * + * sModule + * addr (physical address, if any; eg, symbols for a ROM) + * len + * aSymbols + * aOffsets + * + * See addSymbols() for more details, since that's how callers add sets of symbols to the table. + */ + this.aSymbolTable = []; + + /* + * clearBreakpoints() initializes the breakpoints lists: aBreakExec is a list of addresses + * to halt on whenever attempting to execute an instruction at the corresponding address, + * and aBreakRead and aBreakWrite are lists of addresses to halt on whenever a read or write, + * respectively, occurs at the corresponding address. + * + * NOTE: Curiously, after upgrading the Google Closure Compiler from v20141215 to v20150609, + * the resulting compiled code would crash in clearBreakpoints(), because the (renamed) aBreakRead + * property was already defined. To eliminate whatever was confusing the Closure Compiler, I've + * explicitly initialized all the properties that clearBreakpoints() (re)initializes. + */ + this.aBreakExec = this.aBreakRead = this.aBreakWrite = []; + this.clearBreakpoints(); + + /* + * The new "bn" command allows you to specify a number of instructions to execute and then stop; + * "bn 0" disables any outstanding count. + */ + this.nBreakInstructions = 0; + + /* + * Execution history is allocated by historyInit() whenever checksEnabled() conditions change. + * Execution history is updated whenever the CPU calls checkInstruction(), which will happen + * only when checksEnabled() returns true (eg, whenever one or more breakpoints have been set). + * This ensures that, by default, the CPU runs as fast as possible. + */ + this.iInstructionHistory = 0; + this.aInstructionHistory = []; + this.nextHistory = undefined; + this.historyInit(); + + /* + * Initialize DebuggerPDP10 message support. + */ + this.dbg = this; + this.afnDumpers = {}; + this.bitsMessage = this.bitsWarning = 0; + this.sMessagePrev = null; + this.aMessageBuffer = []; + this.messageInit(parmsDbg['messages']); + this.sInitCommands = parmsDbg['commands']; + + /* + * Define remaining miscellaneous DebuggerPDP10 properties. + */ + this.opTable = DebuggerPDP10.OPTABLE; + this.aOpReserved = []; + this.nStep = 0; + this.sCmdTracePrev = null; + this.sCmdDumpPrev = null; + this.fIgnoreNextCheckFault = false; // TODO: Does this serve any purpose on a PDP-11? + this.nSuppressBreaks = 0; + this.cInstructions = this.cInstructionsStart = 0; + this.nCycles = this.nCyclesStart = this.msStart = 0; + this.controlDebug = null; + this.panel = null; + + /* + * Make it easier to access DebuggerPDP10 commands from an external REPL (eg, the WebStorm + * "live" console window); eg: + * + * pdp10('r') + * pdp10('dw 0:0') + * pdp10('h') + * ... + */ + var dbg = this; + if (window) { + if (window[PDP10.APPCLASS] === undefined) { + window[PDP10.APPCLASS] = function(s) { return dbg.doCommands(s); }; + } + } else { + if (global[PDP10.APPCLASS] === undefined) { + global[PDP10.APPCLASS] = function(s) { return dbg.doCommands(s); }; + } + } + + } // endif DEBUGGER + } + + /** + * getAddr(dbgAddr, fWrite) + * + * @this {DebuggerPDP10} + * @param {DbgAddrPDP10|null} [dbgAddr] + * @param {boolean} [fWrite] + * @return {number} is the corresponding linear address, or PDP10.ADDR_INVALID + */ + getAddr(dbgAddr, fWrite) + { + var addr = dbgAddr && dbgAddr.addr; + if (addr == null) addr = PDP10.ADDR_INVALID; + return addr; + } + + /** + * newAddr(addr, fPhysical, nBase) + * + * Returns a NEW DbgAddrPDP10 object, initialized with specified values and/or defaults. + * + * @this {DebuggerPDP10} + * @param {number|null} [addr] + * @param {boolean} [fPhysical] + * @param {number} [nBase] + * @return {DbgAddrPDP10} + */ + newAddr(addr = null, fPhysical = false, nBase) + { + return {addr: addr, fPhysical: fPhysical, fTemporary: false, nBase: nBase}; + } + + /** + * setAddr(dbgAddr, addr) + * + * Updates an EXISTING DbgAddrPDP10 object, initialized with specified values and/or defaults. + * + * @this {DebuggerPDP10} + * @param {DbgAddrPDP10} dbgAddr + * @param {number} addr + * @return {DbgAddrPDP10} + */ + setAddr(dbgAddr, addr) + { + dbgAddr.addr = addr; + dbgAddr.fTemporary = false; + dbgAddr.nBase = undefined; + return dbgAddr; + } + + /** + * packAddr(dbgAddr) + * + * Packs a DbgAddrPDP10 object into an Array suitable for saving in a machine state object. + * + * @this {DebuggerPDP10} + * @param {DbgAddrPDP10} dbgAddr + * @return {Array} + */ + packAddr(dbgAddr) + { + return [dbgAddr.addr, dbgAddr.fPhysical, dbgAddr.nBase, dbgAddr.fTemporary, dbgAddr.sCmd]; + } + + /** + * unpackAddr(aAddr) + * + * Unpacks a DbgAddrPDP10 object from an Array created by packAddr() and restored from a saved machine state. + * + * @this {DebuggerPDP10} + * @param {Array} aAddr + * @return {DbgAddrPDP10} + */ + unpackAddr(aAddr) + { + var dbgAddr = this.newAddr(aAddr[0], aAddr[1], aAddr[2]); + dbgAddr.fTemporary = aAddr[3]; + if (aAddr[4]) { + dbgAddr.aCmds = this.parseCommand(dbgAddr.sCmd = aAddr[4]); + } + return dbgAddr; + } + + /** + * initBus(bus, cpu, dbg) + * + * @this {DebuggerPDP10} + * @param {ComputerPDP10} cmp + * @param {BusPDP10} bus + * @param {CPUStatePDP10} cpu + * @param {DebuggerPDP10} dbg + */ + initBus(cmp, bus, cpu, dbg) + { + this.bus = bus; + this.cmp = cmp; + this.cpu = cpu; + this.panel = cmp.panel; + + /* + * Re-initialize Debugger message support if necessary + */ + var sMessages = /** @type {string|undefined} */ (cmp.getMachineParm('messages')); + if (sMessages) this.messageInit(sMessages); + + /* + * Update aOpReserved as appropriate for the current model + */ + + this.messageDump(MessagesPDP10.BUS, function onDumpBus(asArgs) { dbg.dumpBus(asArgs); }); + + this.setReady(); + } + + /** + * setBinding(sType, sBinding, control, sValue) + * + * @this {DebuggerPDP10} + * @param {string|null} sType is the type of the HTML control (eg, "button", "textarea", "register", "flag", "rled", etc) + * @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "debugInput") + * @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement) + * @param {string} [sValue] optional data value + * @return {boolean} true if binding was successful, false if unrecognized binding request + */ + setBinding(sType, sBinding, control, sValue) + { + var dbg = this; + switch (sBinding) { + + case "debugInput": + this.bindings[sBinding] = control; + this.controlDebug = control; + /* + * For halted machines, this is fine, but for auto-start machines, it can be annoying. + * + * control.focus(); + */ + control.onkeydown = function onKeyDownDebugInput(event) { + var sCmd; + if (event.keyCode == Keys.KEYCODE.CR) { + sCmd = control.value; + control.value = ""; + dbg.doCommands(sCmd, true); + } + else if (event.keyCode == Keys.KEYCODE.ESC) { + control.value = sCmd = ""; + } + else { + if (event.keyCode == Keys.KEYCODE.UP) { + sCmd = dbg.getPrevCommand(); + } + else if (event.keyCode == Keys.KEYCODE.DOWN) { + sCmd = dbg.getNextCommand(); + } + if (sCmd != null) { + var cch = sCmd.length; + control.value = sCmd; + control.setSelectionRange(cch, cch); + } + } + if (sCmd != null && event.preventDefault) event.preventDefault(); + }; + return true; + + case "debugEnter": + this.bindings[sBinding] = control; + Web.onClickRepeat( + control, + 500, 100, + function onClickDebugEnter(fRepeat) { + if (dbg.controlDebug) { + var sCmd = dbg.controlDebug.value; + dbg.controlDebug.value = ""; + dbg.doCommands(sCmd, true); + return true; + } + if (DEBUG) dbg.log("no debugger input buffer"); + return false; + } + ); + return true; + + case "step": + this.bindings[sBinding] = control; + Web.onClickRepeat( + control, + 500, 100, + function onClickStep(fRepeat) { + var fCompleted = false; + if (!dbg.isBusy(true)) { + dbg.setBusy(true); + fCompleted = dbg.stepCPU(fRepeat? 1 : 0, null); + dbg.setBusy(false); + } + return fCompleted; + } + ); + return true; + + default: + break; + } + return false; + } + + /** + * setFocus(fScroll) + * + * @this {DebuggerPDP10} + * @param {boolean} [fScroll] (true if you really want the control scrolled into view) + */ + setFocus(fScroll) + { + if (this.controlDebug) { + /* + * This is the recommended work-around to prevent the browser from scrolling the focused element + * into view. The CPU is not a visual component, so when the CPU wants to set focus, the primary intent + * is to ensure that keyboard input is fielded properly. + */ + var x = 0, y = 0; + if (!fScroll && window) { + x = window.scrollX; + y = window.scrollY; + } + + this.controlDebug.focus(); + + if (!fScroll && window) { + window.scrollTo(x, y); + } + } + } + + /** + * getWord(dbgAddr, inc) + * + * @this {DebuggerPDP10} + * @param {DbgAddrPDP10} dbgAddr + * @param {number} [inc] + * @return {number} + */ + getWord(dbgAddr, inc) + { + var w = PDP10.DATA_INVALID; + var addr = this.getAddr(dbgAddr, false); + if (addr !== PDP10.ADDR_INVALID) { + w = this.bus.getWordDirect(addr); + if (inc) this.incAddr(dbgAddr, inc); + } + return w; + } + + /** + * setWord(dbgAddr, w, inc) + * + * @this {DebuggerPDP10} + * @param {DbgAddrPDP10} dbgAddr + * @param {number} w + * @param {number} [inc] + */ + setWord(dbgAddr, w, inc) + { + var addr = this.getAddr(dbgAddr, true); + if (addr !== PDP10.ADDR_INVALID) { + this.bus.setWordDirect(addr, w); + if (inc) this.incAddr(dbgAddr, inc); + this.cmp.updateDisplays(-1); + } + } + + /** + * parseAddr(sAddr, fCode, fNoChecks, fPrint) + * + * Address evaluation and validation (eg, range checks) are no longer performed at this stage. That's + * done later, by getAddr(), which returns PDP10.ADDR_INVALID for invalid segments, out-of-range offsets, + * etc. The Debugger's low-level get/set memory functions verify all getAddr() results, but even if an + * invalid address is passed through to the Bus memory interfaces, the address will simply be masked with + * bus.nBusMask; in the case of PDP10.ADDR_INVALID, that will generally refer to the top of the physical + * address space. + * + * @this {DebuggerPDP10} + * @param {string|undefined} sAddr + * @param {boolean} [fCode] (true if target is code, false if target is data) + * @param {boolean} [fNoChecks] (true when setting breakpoints that may not be valid now, but will be later) + * @param {boolean} [fPrint] + * @return {DbgAddrPDP10|null|undefined} + */ + parseAddr(sAddr, fCode, fNoChecks, fPrint) + { + var dbgAddr; + var dbgAddrNext = (fCode? this.dbgAddrNextCode : this.dbgAddrNextData); + var addr = dbgAddrNext.addr; + var fPhysical, nBase; + if (sAddr !== undefined) { + sAddr = this.parseReference(sAddr); + var ch = sAddr.charAt(0); + if (ch == '%') { + fPhysical = true; + sAddr = sAddr.substr(1); + } + dbgAddr = this.findSymbolAddr(sAddr); + if (dbgAddr) return dbgAddr; + if (sAddr.indexOf("0x") >= 0) { + nBase = 16 + } else if (sAddr.indexOf("0o") >= 0) { + nBase = 8; + } else if (sAddr.indexOf('.') >= 0) { + nBase = 10; + } + addr = this.parseExpression(sAddr, fPrint); + } + if (addr != null) { + dbgAddr = this.newAddr(addr, fPhysical, nBase); + } + return dbgAddr; + } + + /** + * parseAddrOptions(dbdAddr, sOptions) + * + * @this {DebuggerPDP10} + * @param {DbgAddrPDP10} dbgAddr + * @param {string} [sOptions] + */ + parseAddrOptions(dbgAddr, sOptions) + { + if (sOptions) { + var a = sOptions.match(/(['"])(.*?)\1/); + if (a) { + dbgAddr.aCmds = this.parseCommand(dbgAddr.sCmd = a[2]); + } + } + } + + /** + * incAddr(dbgAddr, inc) + * + * @this {DebuggerPDP10} + * @param {DbgAddrPDP10} dbgAddr + * @param {number} [inc] contains value to increment dbgAddr by (default is 1) + */ + incAddr(dbgAddr, inc) + { + if (dbgAddr.addr != null) { + dbgAddr.addr += (inc || 1); + } + } + + /** + * toStrOffset(off) + * + * @this {DebuggerPDP10} + * @param {number|null|undefined} [off] + * @return {string} the hex representation of off + */ + toStrOffset(off) + { + return this.toStrBase(off); + } + + /** + * toStrAddr(dbgAddr) + * + * @this {DebuggerPDP10} + * @param {DbgAddrPDP10} dbgAddr + * @return {string} the hex representation of the address + */ + toStrAddr(dbgAddr) + { + return this.toStrOffset(dbgAddr.addr); + } + + /** + * dumpBlocks(aBlocks, sAddr) + * + * @this {DebuggerPDP10} + * @param {Array} aBlocks + * @param {string} [sAddr] (optional block address) + */ + dumpBlocks(aBlocks, sAddr) + { + var addr = 0, i = 0, n = aBlocks.length; + + if (sAddr) { + addr = this.getAddr(this.parseAddr(sAddr)); + if (addr === PDP10.ADDR_INVALID) { + this.println("invalid address: " + sAddr); + return; + } + i = addr >>> this.bus.nBlockShift; + n = 1; + } + + this.println("blockid physical blockaddr used size type"); + this.println("-------- --------- --------- ------ ------ ----"); + + var typePrev = -1, cPrev = 0; + while (n--) { + var block = aBlocks[i]; + if (block.type == typePrev) { + if (!cPrev++) this.println("..."); + } else { + typePrev = block.type; + var sType = MemoryPDP10.TYPE_NAMES[typePrev]; + if (block) { + this.println(Str.toHex(block.id, 8) + " %" + Str.toHex(i << this.bus.nBlockShift, 8) + " %" + Str.toHex(block.addr, 8) + " " + Str.toHexWord(block.used) + " " + Str.toHexWord(block.size) + " " + sType); + } + if (typePrev != MemoryPDP10.TYPE.NONE) typePrev = -1; + cPrev = 0; + } + addr += this.bus.nBlockSize; + i++; + } + } + + /** + * dumpBus(asArgs) + * + * Dumps Bus allocations. + * + * @this {DebuggerPDP10} + * @param {Array.} asArgs (asArgs[0] is an optional block address) + */ + dumpBus(asArgs) + { + this.dumpBlocks(this.bus.aBusBlocks, asArgs[0]); + } + + /** + * dumpHistory(sPrev, sLines) + * + * If sLines is not a number, it can be a instruction filter. However, for the moment, the only + * supported filter is "call", which filters the history buffer for all CALL and RET instructions + * from the specified previous point forward. + * + * @this {DebuggerPDP10} + * @param {string} [sPrev] is a (decimal) number of instructions to rewind to (default is 10) + * @param {string} [sLines] is a (decimal) number of instructions to print (default is, again, 10) + */ + dumpHistory(sPrev, sLines) + { + var sMore = ""; + var cHistory = 0; + var iHistory = this.iInstructionHistory; + var aHistory = this.aInstructionHistory; + + if (aHistory.length) { + var nPrev = +sPrev || this.nextHistory; + var nLines = +sLines || 10; + + if (isNaN(nPrev)) { + nPrev = nLines; + } else { + sMore = "more "; + } + + if (nPrev > aHistory.length) { + this.println("note: only " + aHistory.length + " available"); + nPrev = aHistory.length; + } + + iHistory -= nPrev; + if (iHistory < 0) { + /* + * If the dbgAddr of the last aHistory element contains a valid selector, wrap around. + */ + if (aHistory[aHistory.length - 1].addr == null) { + nPrev = iHistory + nPrev; + iHistory = 0; + } else { + iHistory += aHistory.length; + } + } + + var aFilters = []; + if (sLines == "call") { + nLines = 100000; + aFilters = ["CALL"]; + } + + if (sPrev !== undefined) { + this.println(nPrev + " instructions earlier:"); + } + + /* + * TODO: The following is necessary to prevent dumpHistory() from causing additional (or worse, recursive) + * faults due to segmented addresses that are no longer valid, but the only alternative is to dramatically + * increase the amount of memory used to store instruction history (eg, storing copies of all the instruction + * bytes alongside the execution addresses). + * + * For now, we're living dangerously, so that our history dumps actually work. + * + * this.nSuppressBreaks++; + * + * If you re-enable this protection, be sure to re-enable the decrement below, too. + */ + while (nLines > 0 && iHistory != this.iInstructionHistory) { + + var dbgAddr = aHistory[iHistory++]; + if (dbgAddr.addr == null) break; + + /* + * We must create a new dbgAddr from the address in aHistory, because dbgAddr was + * a reference, not a copy, and we don't want getInstruction() modifying the original. + */ + var dbgAddrNew = this.newAddr(dbgAddr.addr); + + var sComment = "history"; + var nSequence = nPrev--; + + /* + * TODO: Need to some UI to control whether cycle counts are displayed as part of the history. + * It's currently disabled in checkInstruction(), so it's disable here, too. + * + if (DEBUG && dbgAddr.cycleCount != null) { + sComment = "cycles"; + nSequence = dbgAddr.cycleCount; + } + */ + + var sInstruction = this.getInstruction(dbgAddrNew, sComment, nSequence); + + if (!aFilters.length || sInstruction.indexOf(aFilters[0]) >= 0) { + this.println(sInstruction); + } + + /* + * If there were OPERAND or ADDRESS overrides on the previous instruction, getInstruction() + * will have automatically disassembled additional bytes, so skip additional history entries. + */ + if (dbgAddrNew.cOverrides) { + iHistory += dbgAddrNew.cOverrides; nLines -= dbgAddrNew.cOverrides; nPrev -= dbgAddrNew.cOverrides; + } + + if (iHistory >= aHistory.length) iHistory = 0; + this.nextHistory = nPrev; + cHistory++; + nLines--; + } + /* + * See comments above. + * + * this.nSuppressBreaks--; + */ + } + + if (!cHistory) { + this.println("no " + sMore + "history available"); + this.nextHistory = undefined; + } + } + + /** + * messageInit(sEnable) + * + * @this {DebuggerPDP10} + * @param {string|undefined} sEnable contains zero or more message categories to enable, separated by '|' + */ + messageInit(sEnable) + { + this.dbg = this; + this.bitsMessage = this.bitsWarning = MessagesPDP10.WARN; + this.sMessagePrev = null; + this.aMessageBuffer = []; + /* + * Internally, we use "key" instead of "keys", since the latter is a method on JavasScript objects, + * but externally, we allow the user to specify "keys"; "kbd" is also allowed as shorthand for "keyboard". + */ + var aEnable = this.parseCommand(sEnable.replace("keys","key").replace("kbd","keyboard"), false, '|'); + if (aEnable.length) { + for (var m in MessagesPDP10.CATEGORIES) { + if (Usr.indexOf(aEnable, m) >= 0) { + this.bitsMessage |= MessagesPDP10.CATEGORIES[m]; + this.println(m + " messages enabled"); + } + } + } + } + + /** + * messageDump(bitMessage, fnDumper) + * + * @this {DebuggerPDP10} + * @param {number} bitMessage is one Messages category flag + * @param {function(Array.)} fnDumper is a function the Debugger can use to dump data for that category + * @return {boolean} true if successfully registered, false if not + */ + messageDump(bitMessage, fnDumper) + { + for (var m in MessagesPDP10.CATEGORIES) { + if (bitMessage == MessagesPDP10.CATEGORIES[m]) { + this.afnDumpers[m] = fnDumper; + return true; + } + } + return false; + } + + /** + * getRegIndex(sReg, off) + * + * @this {DebuggerPDP10} + * @param {string} sReg + * @param {number} [off] optional offset into sReg + * @return {number} register index, or -1 if not found + */ + getRegIndex(sReg, off) + { + return DebuggerPDP10.REGNAMES.indexOf(sReg.toUpperCase()); + } + + /** + * getRegName(iReg) + * + * @this {DebuggerPDP10} + * @param {number} iReg (0-7; not used for other registers) + * @return {string|undefined} + */ + getRegName(iReg) + { + return DebuggerPDP10.REGNAMES[iReg]; + } + + /** + * getRegValue(iReg) + * + * @this {DebuggerPDP10} + * @param {number} iReg + * @return {number|undefined} + */ + getRegValue(iReg) + { + var value; + switch(iReg) { + case DebuggerPDP10.REGS.PC: + value = this.cpu.getPC(); + break; + } + return value; + } + + /** + * replaceRegs(s) + * + * TODO: Implement or eliminate. + * + * @this {DebuggerPDP10} + * @param {string} s + * @return {string} + */ + replaceRegs(s) + { + return s; + } + + /** + * message(sMessage, fAddress) + * + * @this {DebuggerPDP10} + * @param {string} sMessage is any caller-defined message string + * @param {boolean} [fAddress] is true to display the current address + */ + message(sMessage, fAddress) + { + if (fAddress) { + sMessage += " @" + this.toStrAddr(this.newAddr(this.cpu.getLastPC())); + } + + if (this.sMessagePrev && sMessage == this.sMessagePrev) return; + this.sMessagePrev = sMessage; + + if (this.bitsMessage & MessagesPDP10.BUFFER) { + this.aMessageBuffer.push(sMessage); + return; + } + + var fRunning; + if ((this.bitsMessage & MessagesPDP10.HALT) && this.cpu && (fRunning = this.cpu.isRunning()) || this.isBusy(true)) { + this.stopCPU(); + if (fRunning) sMessage += " (cpu halted)"; + } + + this.println(sMessage); // + " (" + this.cpu.getCycles() + " cycles)" + + /* + * We have no idea what the frequency of println() calls might be; all we know is that they easily + * screw up the CPU's careful assumptions about cycles per burst. So we call yieldCPU() after every + * message, to effectively end the current burst and start fresh. + * + * TODO: See CPUPDP10.calcStartTime() for a discussion of why we might want to call yieldCPU() *before* + * we display the message. + */ + if (this.cpu) this.cpu.yieldCPU(); + } + + /** + * init() + * + * @this {DebuggerPDP10} + * @param {boolean} [fAutoStart] + */ + init(fAutoStart) + { + this.fInit = true; + this.println("Type ? for help with PDPjs Debugger commands"); + this.updateStatus(); + if (!fAutoStart) this.setFocus(); + if (this.sInitCommands) { + var sCmds = this.sInitCommands; + this.sInitCommands = null; + this.doCommands(sCmds); + } + } + + /** + * historyInit(fQuiet) + * + * This function is intended to be called by the constructor, reset(), addBreakpoint(), findBreakpoint() + * and any other function that changes the checksEnabled() criteria used to decide whether checkInstruction() + * should be called. + * + * That is, if the history arrays need to be allocated and haven't already been allocated, then allocate them, + * and if the arrays are no longer needed, then deallocate them. + * + * @this {DebuggerPDP10} + * @param {boolean} [fQuiet] + */ + historyInit(fQuiet) + { + var i; + if (!this.checksEnabled()) { + if (this.aInstructionHistory && this.aInstructionHistory.length && !fQuiet) { + this.println("instruction history buffer freed"); + } + this.iInstructionHistory = 0; + this.aInstructionHistory = []; + return; + } + if (!this.aInstructionHistory || !this.aInstructionHistory.length) { + this.aInstructionHistory = new Array(DebuggerPDP10.HISTORY_LIMIT); + for (i = 0; i < this.aInstructionHistory.length; i++) { + /* + * Preallocate dummy Addr (Array) objects in every history slot, so that + * checkInstruction() doesn't need to call newAddr() on every slot update. + */ + this.aInstructionHistory[i] = this.newAddr(); + } + this.iInstructionHistory = 0; + if (!fQuiet) { + this.println("instruction history buffer allocated"); + } + } + } + + /** + * startCPU(fUpdateFocus, fQuiet) + * + * @this {DebuggerPDP10} + * @param {boolean} [fUpdateFocus] is true to update focus + * @param {boolean} [fQuiet] + * @return {boolean} true if run request successful, false if not + */ + startCPU(fUpdateFocus, fQuiet) + { + if (!this.checkCPU(fQuiet)) return false; + this.cpu.startCPU(fUpdateFocus); + return true; + } + + /** + * stepCPU(nCycles, fRegs, fUpdateDisplays) + * + * @this {DebuggerPDP10} + * @param {number} nCycles (0 for one instruction without checking breakpoints) + * @param {boolean|null} [fRegs] is true to display registers after step (default is false; use null for previous setting) + * @param {boolean} [fUpdateDisplays] is false to disable Computer display updates (default is true) + * @return {boolean} + */ + stepCPU(nCycles, fRegs, fUpdateDisplays) + { + if (!this.checkCPU()) return false; + + var sCmd = ""; + if (fRegs === null) { + fRegs = (!this.sCmdTracePrev || this.sCmdTracePrev == "tr"); + sCmd = fRegs? "tr" : "t"; + } + + this.nCycles = 0; + + if (!nCycles) { + /* + * When single-stepping, the CPU won't call checkInstruction(), which is good for + * avoiding breakpoints, but bad for instruction data collection if checks are enabled. + * So we call checkInstruction() ourselves. + */ + if (this.checksEnabled()) this.checkInstruction(this.cpu.getPC(), 0); + } + /* + * For our typically tiny bursts (usually single instructions), mimic what runCPU() does. + */ + try { + nCycles = this.cpu.getBurstCycles(nCycles); + var nCyclesStep = this.cpu.stepCPU(nCycles); + if (nCyclesStep > 0) { + this.cpu.updateTimers(nCyclesStep); + this.nCycles += nCyclesStep; + this.cpu.addCycles(nCyclesStep, true); + this.cpu.updateChecksum(nCyclesStep); + this.cInstructions++; + } + } + catch(exception) { + /* + * We assume that any numeric exception was explicitly thrown by the CPU to interrupt the + * current instruction. For all other exceptions, we attempt a stack dump. + */ + if (typeof exception != "number") { + var e = exception; + this.nCycles = 0; + this.cpu.setError(e.stack || e.message); + } + } + + /* + * Because we called cpu.stepCPU() and not cpu.startCPU(), we must nudge the Computer's update code, + * and then update our own state. Normally, the only time fUpdateDisplays will be false is when doTrace() + * is calling us in a loop, in which case it will perform its own updateDisplays() when it's done. + */ + if (fUpdateDisplays !== false) { + if (this.panel) this.panel.stop(); + this.cmp.updateDisplays(-1); + } + + this.updateStatus(fRegs || false, sCmd); + return (this.nCycles > 0); + } + + /** + * stopCPU() + * + * @this {DebuggerPDP10} + * @param {boolean} [fComplete] + */ + stopCPU(fComplete) + { + if (this.cpu) this.cpu.stopCPU(fComplete); + } + + /** + * updateStatus(fRegs, sCmd) + * + * @this {DebuggerPDP10} + * @param {boolean} [fRegs] (default is true) + * @param {string} [sCmd] + */ + updateStatus(fRegs, sCmd) + { + if (!this.fInit) return; + + if (fRegs === undefined) fRegs = true; + + if (sCmd) { + this.println(DebuggerPDP10.PROMPT + sCmd); + } + + this.dbgAddrNextCode = this.newAddr(this.cpu.getPC()); + + /* + * this.nStep used to be a simple boolean, but now it's 0 (or undefined) + * if inactive, 1 if stepping over an instruction without a register dump, or 2 + * if stepping over an instruction with a register dump. + */ + if (!fRegs || this.nStep == 1) { + this.doUnassemble(); + } else { + this.doRegisters(); + } + } + + /** + * checkCPU(fQuiet) + * + * Make sure the CPU is ready (finished initializing), powered, not already running, and not in an error state. + * + * @this {DebuggerPDP10} + * @param {boolean} [fQuiet] + * @return {boolean} + */ + checkCPU(fQuiet) + { + if (!this.cpu || !this.cpu.isReady() || !this.cpu.isPowered() || this.cpu.isRunning()) { + if (!fQuiet) this.println("cpu busy or unavailable, command ignored"); + return false; + } + return !this.cpu.isError(); + } + + /** + * powerUp(data, fRepower) + * + * @this {DebuggerPDP10} + * @param {Object|null} data + * @param {boolean} [fRepower] + * @return {boolean} true if successful, false if failure + */ + powerUp(data, fRepower) + { + if (!fRepower) { + /* + * Because Debugger save/restore support is somewhat limited (and didn't always exist), + * we deviate from the typical save/restore design pattern: instead of reset OR restore, + * we always reset and then perform a (potentially limited) restore. + */ + this.reset(true); + + // this.println(data? "resuming" : "powering up"); + + if (data) { + return this.restore(data); + } + } + return true; + } + + /** + * powerDown(fSave, fShutdown) + * + * @this {DebuggerPDP10} + * @param {boolean} [fSave] + * @param {boolean} [fShutdown] + * @return {Object|boolean} + */ + powerDown(fSave, fShutdown) + { + if (fShutdown) this.println(fSave? "suspending" : "shutting down"); + return fSave? this.save() : true; + } + + /** + * reset(fQuiet) + * + * This is a notification handler, called by the Computer, to inform us of a reset. + * + * @this {DebuggerPDP10} + * @param {boolean} fQuiet (true only when called from our own powerUp handler) + */ + reset(fQuiet) + { + this.historyInit(); + this.cInstructions = this.cInstructionsStart = 0; + this.sMessagePrev = null; + this.nCycles = 0; + this.dbgAddrNextCode = this.newAddr(this.cpu.getPC()); + /* + * fRunning is set by start() and cleared by stop(). In addition, we clear + * it here, so that if the CPU is reset while running, we can prevent stop() + * from unnecessarily dumping the CPU state. + */ + this.flags.running = false; + this.clearTempBreakpoint(); + if (!fQuiet) this.updateStatus(); + } + + /** + * save() + * + * This implements (very rudimentary) save support for the Debugger component. + * + * @this {DebuggerPDP10} + * @return {Object} + */ + save() + { + var state = new State(this); + state.set(0, this.packAddr(this.dbgAddrNextCode)); + state.set(1, this.packAddr(this.dbgAddrAssemble)); + state.set(2, [this.aPrevCmds, this.fAssemble, this.bitsMessage]); + state.set(3, this.aSymbolTable); + return state.data(); + } + + /** + * restore(data) + * + * This implements (very rudimentary) restore support for the Debugger component. + * + * @this {DebuggerPDP10} + * @param {Object} data + * @return {boolean} true if successful, false if failure + */ + restore(data) + { + var i = 0; + if (data[2] !== undefined) { + this.dbgAddrNextCode = this.unpackAddr(data[i++]); + this.dbgAddrAssemble = this.unpackAddr(data[i++]); + this.aPrevCmds = data[i][0]; + if (typeof this.aPrevCmds == "string") this.aPrevCmds = [this.aPrevCmds]; + this.fAssemble = data[i][1]; + this.bitsMessage |= data[i][2]; // keep our current message bits set, and simply "add" any extra bits defined by the saved state + } + if (data[3]) this.aSymbolTable = data[3]; + return true; + } + + /** + * start(ms, nCycles) + * + * This is a notification handler, called by the Computer, to inform us the CPU has started. + * + * @this {DebuggerPDP10} + * @param {number} ms + * @param {number} nCycles + */ + start(ms, nCycles) + { + if (!this.nStep) this.println("running"); + this.flags.running = true; + this.msStart = ms; + this.nCyclesStart = nCycles; + } + + /** + * stop(ms, nCycles) + * + * This is a notification handler, called by the Computer, to inform us the CPU has now stopped. + * + * @this {DebuggerPDP10} + * @param {number} ms + * @param {number} nCycles + */ + stop(ms, nCycles) + { + if (this.flags.running) { + this.flags.running = false; + this.nCycles = nCycles - this.nCyclesStart; + if (!this.nStep) { + var sStopped = "stopped"; + if (this.nCycles) { + var msTotal = ms - this.msStart; + var nCyclesPerSecond = (msTotal > 0? Math.round(this.nCycles * 1000 / msTotal) : 0); + sStopped += " ("; + if (this.checksEnabled()) { + sStopped += this.cInstructions + " instructions, "; + /* + * $ops displays progress by calculating cInstructions - cInstructionsStart, so before + * zeroing cInstructions, we should subtract cInstructions from cInstructionsStart (since + * we're effectively subtracting cInstructions from cInstructions as well). + */ + this.cInstructionsStart -= this.cInstructions; + this.cInstructions = 0; + } + sStopped += this.nCycles + " cycles, " + msTotal + " ms, " + nCyclesPerSecond + " hz)"; + } else { + if (this.messageEnabled(MessagesPDP10.HALT)) { + /* + * It's possible the user is trying to 'g' past a fault that was blocked by helpCheckFault() + * for the Debugger's benefit; if so, it will continue to be blocked, so try displaying a helpful + * message (another helpful tip would be to simply turn off the "halt" message category). + */ + sStopped += " (use the 't' command to execute blocked faults)"; + } + } + this.println(sStopped); + } + this.updateStatus(true); + this.setFocus(); + this.clearTempBreakpoint(this.cpu.getPC()); + this.sMessagePrev = null; + } + } + + /** + * checksEnabled(fRelease) + * + * This "check" function is called by the CPU; we indicate whether or not every instruction needs to be checked. + * + * Originally, this returned true even when there were only read and/or write breakpoints, but those breakpoints + * no longer require the intervention of checkInstruction(); the Bus component automatically swaps in/out appropriate + * "checked" Memory access functions to deal with those breakpoints in the corresponding Memory blocks. So I've + * simplified the test below. + * + * @this {DebuggerPDP10} + * @param {boolean} [fRelease] is true for release criteria only; default is false (any criteria) + * @return {boolean} true if every instruction needs to pass through checkInstruction(), false if not + */ + checksEnabled(fRelease) + { + return ((DEBUG && !fRelease)? true : (this.aBreakExec.length > 1 || !!this.nBreakInstructions)); + } + + /** + * checkInstruction(addr, nState) + * + * This "check" function is called by the CPU to inform us about the next instruction to be executed, + * giving us an opportunity to look for "exec" breakpoints and update opcode instruction history. + * + * @this {DebuggerPDP10} + * @param {number} addr + * @param {number} nState is < 0 if stepping, 0 if starting, or > 0 if running + * @return {boolean} true if breakpoint hit, false if not + */ + checkInstruction(addr, nState) + { + var opCode = -1; + var cpu = this.cpu; + + /* + * If opHalt() calls our stopInstruction() function, it will effectively rewind the PC back to the HALT, + * purely for our debugging benefit, so we must compensate for that here by advancing the PC past the HALT + * when the machine starts up again. + */ + if (!nState) { + opCode = this.cpu.readWord(addr); + if (opCode == PDP10.OPCODE.HALT && this.cpu.getLastPC() == addr) { + addr = this.cpu.advancePC(1); + } + } + + /* + * If the CPU stopped on a breakpoint, we're not interested in stopping again if the machine is starting. + */ + if (nState > 0) { + if (this.nBreakInstructions) { + if (!--this.nBreakInstructions) return true; + } + if (this.checkBreakpoint(addr, 1, this.aBreakExec)) { + return true; + } + } + + /* + * The rest of the instruction tracking logic can only be performed if historyInit() has allocated the + * necessary data structures. Note that there is no explicit UI for enabling/disabling history, other than + * adding/removing breakpoints, simply because it's breakpoints that trigger the call to checkInstruction(); + * well, OK, and a few other things now, like enabling MessagesPDP10.INT messages. + */ + if (nState >= 0 && this.aInstructionHistory.length) { + this.cInstructions++; + if (opCode < 0) { + opCode = this.cpu.readWord(addr); + } + if ((opCode & 0xffff) != PDP10.OPCODE.INVALID) { + var dbgAddr = this.aInstructionHistory[this.iInstructionHistory]; + this.setAddr(dbgAddr, addr); + // if (DEBUG) dbgAddr.cycleCount = cpu.getCycles(); + if (++this.iInstructionHistory == this.aInstructionHistory.length) this.iInstructionHistory = 0; + } + } + return false; + } + + /** + * stopInstruction(sMessage) + * + * TODO: Currently, the only way to prevent this call from stopping the CPU is when you're single-stepping. + * + * @this {DebuggerPDP10} + * @param {string} [sMessage] + * @return {boolean} true if stopping is enabled, false if not + */ + stopInstruction(sMessage) + { + var cpu = this.cpu; + if (cpu.isRunning()) { + cpu.setPC(this.cpu.getLastPC()); + if (sMessage) this.println(sMessage); + this.stopCPU(); + /* + * TODO: Review the appropriate-ness of throwing a bogus vector number in order to immediately stop + * the instruction. It's handy, but it also means that we no longer actually return true, so callers + * of either stopInstruction() or undefinedInstruction() may have unreachable code paths. + */ + throw -1; + } + return false; + } + + /** + * undefinedInstruction(opCode) + * + * @this {DebuggerPDP10} + * @param {number} opCode + * @return {boolean} true if stopping is enabled, false if not + */ + undefinedInstruction(opCode) + { + if (this.messageEnabled(MessagesPDP10.CPU)) { + this.printMessage("undefined opcode " + this.toStrBase(opCode), true, true); + return this.stopInstruction(); // allow the caller to step over it if they really want a trap generated + } + return false; + } + + /** + * checkMemoryRead(addr, nb) + * + * This "check" function is called by a Memory block to inform us that a memory read occurred, giving us an + * opportunity to track the read if we want, and look for a matching "read" breakpoint, if any. + * + * In the "old days", it would be an error for this call to fail to find a matching Debugger breakpoint, but now + * Memory blocks have no idea whether the Debugger or the machine's Debug register(s) triggered this "checked" read. + * + * If we return true, we "trump" the machine's Debug register(s); false allows normal Debug register processing. + * + * @this {DebuggerPDP10} + * @param {number} addr + * @param {number} [nb] (# of bytes; default is 1) + * @return {boolean} true if breakpoint hit, false if not + */ + checkMemoryRead(addr, nb) + { + if (this.checkBreakpoint(addr, nb || 1, this.aBreakRead)) { + this.stopCPU(false); + return true; + } + return false; + } + + /** + * checkMemoryWrite(addr, nb) + * + * This "check" function is called by a Memory block to inform us that a memory write occurred, giving us an + * opportunity to track the write if we want, and look for a matching "write" breakpoint, if any. + * + * In the "old days", it would be an error for this call to fail to find a matching Debugger breakpoint, but now + * Memory blocks have no idea whether the Debugger or the machine's Debug register(s) triggered this "checked" write. + * + * If we return true, we "trump" the machine's Debug register(s); false allows normal Debug register processing. + * + * @this {DebuggerPDP10} + * @param {number} addr + * @param {number} [nb] (# of bytes; default is 1) + * @return {boolean} true if breakpoint hit, false if not + */ + checkMemoryWrite(addr, nb) + { + if (this.checkBreakpoint(addr, nb || 1, this.aBreakWrite)) { + this.stopCPU(false); + return true; + } + return false; + } + + /** + * clearBreakpoints() + * + * @this {DebuggerPDP10} + */ + clearBreakpoints() + { + var i, dbgAddr, addr; + this.aBreakExec = ["bp"]; + if (this.aBreakRead !== undefined) { + for (i = 1; i < this.aBreakRead.length; i++) { + dbgAddr = this.aBreakRead[i]; + addr = this.getAddr(dbgAddr); + this.bus.removeMemBreak(addr, false); + } + } + this.aBreakRead = ["br"]; + if (this.aBreakWrite !== undefined) { + for (i = 1; i < this.aBreakWrite.length; i++) { + dbgAddr = this.aBreakWrite[i]; + addr = this.getAddr(dbgAddr); + this.bus.removeMemBreak(addr, true); + } + } + this.aBreakWrite = ["bw"]; + /* + * nSuppressBreaks ensures we can't get into an infinite loop where a breakpoint lookup + * requires reading memory that triggers more memory reads, which triggers more breakpoint checks. + */ + this.nSuppressBreaks = 0; + this.nBreakInstructions = 0; + } + + /** + * addBreakpoint(aBreak, dbgAddr, fTemporary) + * + * In case you haven't already figured this out, all our breakpoint commands use the address + * to identify a breakpoint, not an incrementally assigned breakpoint index like other debuggers; + * see doBreak() for details. + * + * This has a few implications, one being that you CANNOT set more than one kind of breakpoint + * on a single address. In practice, that's rarely a problem, because you can almost always set + * a different breakpoint on a neighboring address. + * + * Also, there is one exception to the "one address, one breakpoint" rule, and that involves + * temporary breakpoints (ie, one-time execution breakpoints that either a "p" or "g" command + * may create to step over a chunk of code). Those breakpoints automatically clear themselves, + * so there usually isn't any need to refer to them using breakpoint commands. + * + * TODO: Consider supporting the more "traditional" breakpoint index syntax; the current + * address-based syntax was implemented solely for expediency and consistency. At the same time, + * also consider a more WDEB386-like syntax, where "br" is used to set a variety of access-specific + * breakpoints, using modifiers like "r1", "r2", "w1", "w2, etc. + * + * @this {DebuggerPDP10} + * @param {Array} aBreak + * @param {DbgAddrPDP10} dbgAddr + * @param {boolean} [fTemporary] + * @return {boolean} true if breakpoint added, false if already exists + */ + addBreakpoint(aBreak, dbgAddr, fTemporary) + { + var fSuccess = true; + + // this.nSuppressBreaks++; + + /* + * Instead of complaining that a breakpoint already exists (as we used to do), we now + * allow breakpoints to be re-set; this makes it easier to update any commands that may + * be associated with the breakpoint. + * + * The only exception: we DO allow a temporary breakpoint at an address where there may + * already be a breakpoint, so that you can easily step ("p" or "g") over such addresses. + */ + if (!fTemporary) { + this.findBreakpoint(aBreak, dbgAddr, true, false, true); + } + + if (aBreak != this.aBreakExec) { + var addr = this.getAddr(dbgAddr); + if (addr === PDP10.ADDR_INVALID) { + this.println("invalid address: " + this.toStrAddr(dbgAddr)); + fSuccess = false; + } else { + var fWrite = (aBreak == this.aBreakWrite); + this.bus.addMemBreak(addr, fWrite); + } + } + + if (fSuccess) { + aBreak.push(dbgAddr); + if (fTemporary) { + dbgAddr.fTemporary = true; + } + else { + this.printBreakpoint(aBreak, aBreak.length-1, "set"); + this.historyInit(); + } + } + + // this.nSuppressBreaks--; + + return fSuccess; + } + + /** + * findBreakpoint(aBreak, dbgAddr, fRemove, fTemporary, fQuiet) + * + * @this {DebuggerPDP10} + * @param {Array} aBreak + * @param {DbgAddrPDP10} dbgAddr + * @param {boolean} [fRemove] + * @param {boolean} [fTemporary] + * @param {boolean} [fQuiet] + * @return {boolean} true if found, false if not + */ + findBreakpoint(aBreak, dbgAddr, fRemove, fTemporary, fQuiet) + { + var fFound = false; + var addr = this.getAddr(dbgAddr); + for (var i = 1; i < aBreak.length; i++) { + var dbgAddrBreak = aBreak[i]; + if (addr == this.getAddr(dbgAddrBreak)) { + if (!fTemporary || dbgAddrBreak.fTemporary) { + fFound = true; + if (fRemove) { + if (!dbgAddrBreak.fTemporary && !fQuiet) { + this.printBreakpoint(aBreak, i, "cleared"); + } + aBreak.splice(i, 1); + if (aBreak != this.aBreakExec) { + var fWrite = (aBreak == this.aBreakWrite); + this.bus.removeMemBreak(addr, fWrite); + } + /* + * We'll mirror the logic in addBreakpoint() and leave the history buffer alone if this + * was a temporary breakpoint. + */ + if (!dbgAddrBreak.fTemporary) { + this.historyInit(); + } + break; + } + if (!fQuiet) this.printBreakpoint(aBreak, i, "exists"); + break; + } + } + } + return fFound; + } + + /** + * listBreakpoints(aBreak) + * + * @this {DebuggerPDP10} + * @param {Array} aBreak + * @return {number} of breakpoints listed, 0 if none + */ + listBreakpoints(aBreak) + { + for (var i = 1; i < aBreak.length; i++) { + this.printBreakpoint(aBreak, i); + } + return aBreak.length - 1; + } + + /** + * printBreakpoint(aBreak, i, sAction) + * + * @this {DebuggerPDP10} + * @param {Array} aBreak + * @param {number} i + * @param {string} [sAction] + */ + printBreakpoint(aBreak, i, sAction) + { + var dbgAddr = aBreak[i]; + this.println(aBreak[0] + ' ' + this.toStrAddr(dbgAddr) + (sAction? (' ' + sAction) : (dbgAddr.sCmd? (' "' + dbgAddr.sCmd + '"') : ''))); + } + + /** + * setTempBreakpoint(dbgAddr) + * + * @this {DebuggerPDP10} + * @param {DbgAddrPDP10} dbgAddr of new temp breakpoint + */ + setTempBreakpoint(dbgAddr) + { + this.addBreakpoint(this.aBreakExec, dbgAddr, true); + } + + /** + * clearTempBreakpoint(addr) + * + * @this {DebuggerPDP10} + * @param {number|undefined} [addr] clear all temp breakpoints if no address specified + */ + clearTempBreakpoint(addr) + { + if (addr !== undefined) { + this.checkBreakpoint(addr, 1, this.aBreakExec, true); + this.nStep = 0; + } else { + for (var i = 1; i < this.aBreakExec.length; i++) { + var dbgAddrBreak = this.aBreakExec[i]; + if (dbgAddrBreak.fTemporary) { + if (!this.findBreakpoint(this.aBreakExec, dbgAddrBreak, true, true)) break; + i = 0; + } + } + } + } + + /** + * checkBreakpoint(addr, nb, aBreak, fTemporary) + * + * @this {DebuggerPDP10} + * @param {number} addr + * @param {number} nb (# of bytes) + * @param {Array} aBreak + * @param {boolean} [fTemporary] + * @return {boolean} true if breakpoint has been hit, false if not + */ + checkBreakpoint(addr, nb, aBreak, fTemporary) + { + /* + * Time to check for breakpoints; note that this should be done BEFORE updating history data + * (see checkInstruction), since we might not actually execute the current instruction. + */ + var fBreak = false; + + if (!this.nSuppressBreaks++) { + + for (var i = 1; !fBreak && i < aBreak.length; i++) { + + var dbgAddrBreak = aBreak[i]; + + if (fTemporary && !dbgAddrBreak.fTemporary) continue; + + /* + * If we're checking an execution address, which is always virtual, and virtual + * addresses are always restricted to 16 bits, let's mask the breakpoint address to match + * (the user should know better, but we'll be nice). + */ + var addrBreak = this.getAddr(dbgAddrBreak) & (aBreak == this.aBreakExec? 0xffff : -1); + for (var n = 0; n < nb; n++) { + + if ((addr + n) != addrBreak) continue; + + var a; + fBreak = true; + if (dbgAddrBreak.fTemporary) { + this.findBreakpoint(aBreak, dbgAddrBreak, true, true); + fTemporary = true; + } + if (a = dbgAddrBreak.aCmds) { + /* + * When one or more commands are attached to a breakpoint, we don't halt by default. + * Instead, we set fBreak to true only if, at the completion of all the commands, the + * CPU is halted; in other words, you should include "h" as one of the breakpoint commands + * if you want the breakpoint to stop execution. + * + * Another useful command is "if", which will return false if the expression is false, + * at which point we'll jump ahead to the next "else" command, and if there isn't an "else", + * we abort. + */ + fBreak = false; + for (var j = 0; j < a.length; j++) { + if (!this.doCommand(a[j], true)) { + if (a[j].indexOf("if")) { + fBreak = true; // the failed command wasn't "if", so abort + break; + } + var k = j + 1; + for (; k < a.length; k++) { + if (!a[k].indexOf("else")) break; + j++; + } + if (k == a.length) { // couldn't find an "else" after the "if", so abort + fBreak = true; + break; + } + /* + * If we're still here, we'll execute the "else" command (which is just a no-op), + * followed by any remaining commands. + */ + } + } + if (!this.cpu.isRunning()) fBreak = true; + } + if (fBreak) { + if (!fTemporary) this.printBreakpoint(aBreak, i, "hit"); + break; + } + } + } + } + + this.nSuppressBreaks--; + + return fBreak; + } + + /** + * getInstruction(dbgAddr, sComment, nSequence) + * + * Get the next instruction, by decoding the opcode and any operands. + * + * @this {DebuggerPDP10} + * @param {DbgAddrPDP10} dbgAddr + * @param {string} [sComment] is an associated comment + * @param {number|null} [nSequence] is an associated sequence number, undefined if none + * @return {string} (and dbgAddr is updated to the next instruction) + */ + getInstruction(dbgAddr, sComment, nSequence) + { + var opNames = DebuggerPDP10.OPNAMES; + var dbgAddrOp = this.newAddr(dbgAddr.addr); + var opCode = this.getWord(dbgAddr, 2); + + var opDesc; + for (var mask in this.opTable) { + var opMasks = this.opTable[mask]; + opDesc = opMasks[opCode & mask]; + if (opDesc) break; + } + + if (!opDesc) { + opDesc = DebuggerPDP10.OPNONE; + } + + var opNum = opDesc[0]; + if (this.aOpReserved.indexOf(opNum) >= 0) { + opDesc = DebuggerPDP10.OPNONE; + opNum = opDesc[0]; + } + + var sOperands = "", sTarget = ""; + var sOpName = opNames[opNum]; + var cOperands = opDesc.length - 1; + + if (!opNum && !cOperands) { + sOperands = this.toStrBase(opCode); + } + + for (var iOperand = 1; iOperand <= cOperands; iOperand++) { + + var opType = opDesc[iOperand]; + if (opType === undefined) continue; + + var sOperand = this.getOperand(opCode, opType, dbgAddr); + + if (!sOperand || !sOperand.length) { + sOperands = "INVALID"; + break; + } + + /* + * If getOperand() returns an Array rather than a string, then the first element is the original + * operand, and the second element contains additional information (eg, the target) of the operand. + */ + if (typeof sOperand != "string") { + sTarget = sOperand[1]; + sOperand = sOperand[0]; + } + + if (sOperands.length > 0) sOperands += ','; + sOperands += (sOperand || "???"); + } + + var sOpCodes = ""; + var sLine = this.toStrAddr(dbgAddrOp) + ":"; + if (dbgAddrOp.addr !== PDP10.ADDR_INVALID && dbgAddr.addr !== PDP10.ADDR_INVALID) { + do { + sOpCodes += ' ' + this.toStrBase(this.getWord(dbgAddrOp, 2)); + if (dbgAddrOp.addr == null) break; + } while (dbgAddrOp.addr != dbgAddr.addr); + } + + sLine += Str.pad(sOpCodes, 24); + sLine += Str.pad(sOpName, 5); + if (sOperands) sLine += ' ' + sOperands; + + if (sComment || sTarget) { + sLine = Str.pad(sLine, 60) + ';' + (sComment || ""); + if (!this.cpu.flags.checksum) { + sLine += (nSequence != null? '=' + nSequence.toString() : ""); + } else { + var nCycles = this.cpu.getCycles(); + sLine += "cycles=" + nCycles.toString() + " cs=" + Str.toHex(this.cpu.nChecksum); + } + if (sTarget) { + if (sLine.slice(-1) != ';') sLine += ' '; + sLine += sTarget; + } + } + return sLine; + } + + /** + * getOperand(opCode, opType, dbgAddr) + * + * If getOperand() returns an Array rather than a string, then the first element is the original + * operand, and the second element is a comment containing additional information (eg, the target) + * of the operand. + * + * @this {DebuggerPDP10} + * @param {number} opCode + * @param {number} opType + * @param {DbgAddrPDP10} dbgAddr + * @return {string|Array.} + */ + getOperand(opCode, opType, dbgAddr) + { + return ""; + } + + /** + * parseInstruction(sOp, sOperand, addr) + * + * TODO: Unimplemented. See parseInstruction() in modules/c1pjs/lib/debugger.js for a sample implementation. + * + * @this {DebuggerPDP10} + * @param {string} sOp + * @param {string|undefined} sOperand + * @param {DbgAddrPDP10} dbgAddr of memory where this instruction is being assembled + * @return {Array.} of opcode bytes; if the instruction can't be parsed, the array will be empty + */ + parseInstruction(sOp, sOperand, dbgAddr) + { + var aOpBytes = []; + this.println("not supported yet"); + return aOpBytes; + } + + /** + * getRegOutput(iReg) + * + * @this {DebuggerPDP10} + * @param {number} iReg + * @return {string} + */ + getRegOutput(iReg) + { + var sReg = this.getRegName(iReg) || "undefined"; + if (sReg) sReg += ' '; + return sReg; + } + + /** + * getMiscDump() + * + * @this {DebuggerPDP10} + * @return {string} + */ + getMiscDump() + { + return ""; + } + + /** + * getRegDump(fMisc) + * + * @this {DebuggerPDP10} + * @param {boolean} [fMisc] (true to include misc registers) + * @return {string} + */ + getRegDump(fMisc) + { + var i; + var sDump = ""; + sDump += this.getRegOutput(DebuggerPDP10.REGS.PC); + if (fMisc) sDump += '\n' + this.getMiscDump(); + return sDump; + } + + /** + * comparePairs(p1, p2) + * + * @this {DebuggerPDP10} + * @param {number|string|Array|Object} p1 + * @param {number|string|Array|Object} p2 + * @return {number} + */ + comparePairs(p1, p2) + { + return p1[0] > p2[0]? 1 : p1[0] < p2[0]? -1 : 0; + } + + /** + * addSymbols(sModule, addr, len, aSymbols) + * + * As filedump.js (formerly convrom.php) explains, aSymbols is a JSON-encoded object whose properties consist + * of all the symbols (in upper-case), and the values of those properties are objects containing any or all of + * the following properties: + * + * 'v': the value of an absolute (unsized) value + * 'b': either 1, 2, 4 or undefined if an unsized value + * 's': either a hard-coded segment or undefined + * 'o': the offset of the symbol within the associated address space + * 'l': the original-case version of the symbol, present only if it wasn't originally upper-case + * 'a': annotation for the specified offset; eg, the original assembly language, with optional comment + * + * To that list of properties, we also add: + * + * 'p': the physical address (calculated whenever both 's' and 'o' properties are defined) + * + * Note that values for any 'v', 'b', 's' and 'o' properties are unquoted decimal values, and the values + * for any 'l' or 'a' properties are quoted strings. Also, if double-quotes were used in any of the original + * annotation ('a') values, they will have been converted to two single-quotes, so we're responsible for + * converting them back to individual double-quotes. + * + * For example: + * { + * 'HF_PORT': { + * 'v':800 + * }, + * 'HDISK_INT': { + * 'b':4, 's':0, 'o':52 + * }, + * 'ORG_VECTOR': { + * 'b':4, 's':0, 'o':76 + * }, + * 'CMD_BLOCK': { + * 'b':1, 's':64, 'o':66 + * }, + * 'DISK_SETUP': { + * 'o':3 + * }, + * '.40': { + * 'o':40, 'a':"MOV AX,WORD PTR ORG_VECTOR ;GET DISKETTE VECTOR" + * } + * } + * + * If a symbol only has an offset, then that offset value can be assigned to the symbol property directly: + * + * 'DISK_SETUP': 3 + * + * The last property is an example of an "anonymous" entry, for offsets where there is no associated symbol. + * Such entries are identified by a period followed by a unique number (usually the offset of the entry), and + * they usually only contain offset ('o') and annotation ('a') properties. I could eliminate the leading + * period, but it offers a very convenient way of quickly discriminating among genuine vs. anonymous symbols. + * + * We add all these entries to our internal symbol table, which is an array of 4-element arrays, each of which + * look like: + * + * [addr, len, aSymbols, aOffsets] + * + * There are two basic symbol operations: findSymbol(), which takes an address and finds the symbol, if any, + * at that address, and findSymbolAddr(), which takes a string and attempts to match it to a non-anonymous + * symbol with a matching offset ('o') property. + * + * To implement findSymbol() efficiently, addSymbols() creates an array of [offset, sSymbol] pairs + * (aOffsets), one pair for each symbol that corresponds to an offset within the specified address space. + * + * We guarantee the elements of aOffsets are in offset order, because we build it using binaryInsert(); + * it's quite likely that the MAP file already ordered all its symbols in offset order, but since they're + * hand-edited files, we can't assume that, and we need to ensure that findSymbol()'s binarySearch() operates + * properly. + * + * @this {DebuggerPDP10} + * @param {string|null} sModule + * @param {number|null} addr (physical address where the symbols are located, if the memory is physical; eg, ROM) + * @param {number} len (the size of the region, in bytes) + * @param {Object} aSymbols (collection of symbols in this group; the format of this collection is described below) + */ + addSymbols(sModule, addr, len, aSymbols) + { + var dbgAddr = {}; + var aOffsets = []; + for (var sSymbol in aSymbols) { + var symbol = aSymbols[sSymbol]; + if (typeof symbol == "number") { + aSymbols[sSymbol] = symbol = {'o': symbol}; + } + var offSymbol = symbol['o']; + var sAnnotation = symbol['a']; + if (offSymbol !== undefined) { + Usr.binaryInsert(aOffsets, [offSymbol >>> 0, sSymbol], this.comparePairs); + } + if (sAnnotation) symbol['a'] = sAnnotation.replace(/''/g, "\""); + } + var symbolTable = { + sModule: sModule, + addr: addr, + len: len, + aSymbols: aSymbols, + aOffsets: aOffsets + }; + this.aSymbolTable.push(symbolTable); + } + + /** + * dumpSymbols() + * + * TODO: Add "numerical" and "alphabetical" dump options. This is simply dumping them in whatever + * order they appeared in the original MAP file. + * + * @this {DebuggerPDP10} + */ + dumpSymbols() + { + for (var iTable = 0; iTable < this.aSymbolTable.length; iTable++) { + var symbolTable = this.aSymbolTable[iTable]; + for (var sSymbol in symbolTable.aSymbols) { + if (sSymbol.charAt(0) == '.') continue; + var symbol = symbolTable.aSymbols[sSymbol]; + var offSymbol = symbol['o']; + if (offSymbol === undefined) continue; + var sSymbolOrig = symbolTable.aSymbols[sSymbol]['l']; + if (sSymbolOrig) sSymbol = sSymbolOrig; + this.println(this.toStrOffset(offSymbol) + ' ' + sSymbol); + } + } + } + + /** + * findSymbol(dbgAddr, fNearest) + * + * Search aSymbolTable for dbgAddr, and return an Array for the corresponding symbol (empty if not found). + * + * If fNearest is true, and no exact match was found, then the Array returned will contain TWO sets of + * entries: [0]-[3] will refer to closest preceding symbol, and [4]-[7] will refer to the closest subsequent symbol. + * + * @this {DebuggerPDP10} + * @param {DbgAddrPDP10} dbgAddr + * @param {boolean} [fNearest] + * @return {Array} where [0] == symbol name, [1] == symbol value, [2] == any annotation, and [3] == any associated comment + */ + findSymbol(dbgAddr, fNearest) + { + var aSymbol = []; + var addrSymbol = this.getAddr(dbgAddr) >>> 0; + for (var iTable = 0; iTable < this.aSymbolTable.length; iTable++) { + var symbolTable = this.aSymbolTable[iTable]; + var addr = symbolTable.addr >>> 0; + var len = symbolTable.len; + if (addrSymbol >= addr && addrSymbol < addr + len) { + var offSymbol = addrSymbol - addr; + var result = Usr.binarySearch(symbolTable.aOffsets, [offSymbol], this.comparePairs); + if (result >= 0) { + this.returnSymbol(iTable, result, aSymbol); + } + else if (fNearest) { + result = ~result; + this.returnSymbol(iTable, result-1, aSymbol); + this.returnSymbol(iTable, result, aSymbol); + } + break; + } + } + return aSymbol; + } + + /** + * findSymbolAddr(sSymbol) + * + * Search our symbol tables for sSymbol, and if found, return a dbgAddr (same as parseAddr()). + * + * @this {DebuggerPDP10} + * @param {string} sSymbol + * @return {DbgAddrPDP10|undefined} + */ + findSymbolAddr(sSymbol) + { + var dbgAddr, offSymbol; + + if (sSymbol.match(/^[a-z_][a-z0-9_]*$/i)) { + var sUpperCase = sSymbol.toUpperCase(); + for (var iTable = 0; iTable < this.aSymbolTable.length; iTable++) { + var symbolTable = this.aSymbolTable[iTable]; + var symbol = symbolTable.aSymbols[sUpperCase]; + if (symbol != null) { + offSymbol = symbol['o']; + /* + * If the symbol matched but there's no 'o' offset (ie, it wasn't for an address), there's + * no point looking any farther, since each symbol appears only once. + * + * NOTE: We assume that every ROM is ORG'ed at 0x0000, and therefore unless the symbol has an + * explicitly-defined segment, we return the segment associated with the entire group; for a ROM, + * that segment is normally "addrROM >>> 4". Down the road, we may want/need to support a special + * symbol entry (eg, ".ORG") that defines an alternate origin. + */ + break; + } + } + } + if (offSymbol != null) { + dbgAddr = this.newAddr(offSymbol); + } + return dbgAddr; + } + + /** + * returnSymbol(iTable, iOffset, aSymbol) + * + * Helper function for findSymbol(). + * + * @param {number} iTable + * @param {number} iOffset + * @param {Array} aSymbol is updated with the specified symbol, if it exists + */ + returnSymbol(iTable, iOffset, aSymbol) + { + var symbol = {}; + var aOffsets = this.aSymbolTable[iTable].aOffsets; + var offset = 0, sSymbol = null; + if (iOffset >= 0 && iOffset < aOffsets.length) { + offset = aOffsets[iOffset][0]; + sSymbol = aOffsets[iOffset][1]; + } + if (sSymbol) { + symbol = this.aSymbolTable[iTable].aSymbols[sSymbol]; + sSymbol = (sSymbol.charAt(0) == '.'? null : (symbol['l'] || sSymbol)); + } + aSymbol.push(sSymbol); + aSymbol.push(offset); + aSymbol.push(symbol['a']); + aSymbol.push(symbol['c']); + } + + /** + * doHelp() + * + * @this {DebuggerPDP10} + */ + doHelp() + { + var s = "commands:"; + for (var sCommand in DebuggerPDP10.COMMANDS) { + s += '\n' + Str.pad(sCommand, 9) + DebuggerPDP10.COMMANDS[sCommand]; + } + if (!this.checksEnabled()) s += "\nnote: history disabled if no exec breakpoints"; + this.println(s); + } + + /** + * doAssemble(asArgs) + * + * This always receives the complete argument array, where the order of the arguments is: + * + * [0]: the assemble command (assumed to be "a") + * [1]: the target address (eg, "200") + * [2]: the operation code, aka instruction name (eg, "adc") + * [3]: the operation mode operand, if any (eg, "14", "[1234]", etc) + * + * The Debugger enters "assemble mode" whenever only the first (or first and second) arguments are present. + * As long as "assemble mode is active, the user can omit the first two arguments on all later assemble commands + * until "assemble mode" is cancelled with an empty command line; the command processor automatically prepends "a" + * and the next available target address to the argument array. + * + * Entering "assemble mode" is optional; one could enter a series of fully-qualified assemble commands; eg: + * + * a ff00 cld + * a ff01 ldx 28 + * ... + * + * without ever entering "assemble mode", but of course, that requires more typing and doesn't take advantage + * of automatic target address advancement (see dbgAddrAssemble). + * + * NOTE: As the previous example implies, you can even assemble new instructions into ROM address space; + * as our setByte() function explains, the ROM write-notification handlers only refuse writes from the CPU. + * + * @this {DebuggerPDP10} + * @param {Array.} asArgs is the complete argument array, beginning with the "a" command in asArgs[0] + */ + doAssemble(asArgs) + { + var dbgAddr = this.parseAddr(asArgs[1], true); + if (!dbgAddr) return; + + this.dbgAddrAssemble = dbgAddr; + if (asArgs[2] === undefined) { + this.println("begin assemble at " + this.toStrAddr(dbgAddr)); + this.fAssemble = true; + this.cmp.updateDisplays(); + return; + } + + var aOps = this.parseInstruction(asArgs[2], asArgs[3], dbgAddr); + if (aOps.length) { + for (var i = 0; i < aOps.length; i++) { + this.setWord(dbgAddr, aOps[i], 1); + } + /* + * Since getInstruction() also updates the specified address, dbgAddrAssemble is automatically advanced. + */ + this.println(this.getInstruction(this.dbgAddrAssemble)); + } + } + + /** + * doBreak(sCmd, sAddr, sOptions) + * + * As the "help" output below indicates, the following breakpoint commands are supported: + * + * bp # set exec breakpoint + * br # set read breakpoint + * bw # set write breakpoint + * bc # clear breakpoint (* to clear all) + * bl list all breakpoints + * bn [#] break after # instruction(s) + * + * The "bn" command, like the "dh" command and all other commands that use an instruction count, + * assumes a decimal value, regardless of the current base. Use "bn" without an argument to display + * the break count, and use "bn 0" to clear the break count. + * + * @this {DebuggerPDP10} + * @param {string} sCmd + * @param {string|undefined} [sAddr] + * @param {string} [sOptions] (the rest of the breakpoint command-line) + */ + doBreak(sCmd, sAddr, sOptions) + { + if (sAddr == '?') { + this.println("breakpoint commands:"); + this.println("\tbp #\tset exec breakpoint"); + this.println("\tbr #\tset read breakpoint"); + this.println("\tbw #\tset write breakpoint"); + this.println("\tbc #\tclear breakpoint (* to clear all)"); + this.println("\tbl\tlist all breakpoints"); + this.println("\tbn [#]\tbreak after # instruction(s)"); + return; + } + + var sParm = sCmd.charAt(1); + if (sParm == 'l') { + var cBreaks = 0; + cBreaks += this.listBreakpoints(this.aBreakExec); + cBreaks += this.listBreakpoints(this.aBreakRead); + cBreaks += this.listBreakpoints(this.aBreakWrite); + if (!cBreaks) this.println("no breakpoints"); + return; + } + + if (sParm == 'n') { + var n = +sAddr || 0; + if (sAddr) this.nBreakInstructions = n; + this.println("break after " + n + " instruction(s)"); + return; + } + + if (sAddr === undefined) { + this.println("missing breakpoint address"); + return; + } + + var dbgAddr = this.newAddr(); + if (sAddr != '*') { + dbgAddr = this.parseAddr(sAddr, true, true); + if (!dbgAddr) return; + } + + if (sParm == 'c') { + if (dbgAddr.addr == null) { + this.clearBreakpoints(); + this.println("all breakpoints cleared"); + return; + } + if (this.findBreakpoint(this.aBreakExec, dbgAddr, true)) + return; + if (this.findBreakpoint(this.aBreakRead, dbgAddr, true)) + return; + if (this.findBreakpoint(this.aBreakWrite, dbgAddr, true)) + return; + this.println("breakpoint missing: " + this.toStrAddr(dbgAddr)); + return; + } + + if (dbgAddr.addr == null) return; + + this.parseAddrOptions(dbgAddr, sOptions); + + if (sParm == 'p') { + this.addBreakpoint(this.aBreakExec, dbgAddr); + return; + } + if (sParm == 'r') { + this.addBreakpoint(this.aBreakRead, dbgAddr); + return; + } + if (sParm == 'w') { + this.addBreakpoint(this.aBreakWrite, dbgAddr); + return; + } + this.println("unknown breakpoint command: " + sParm); + } + + /** + * doClear(sCmd) + * + * @this {DebuggerPDP10} + * @param {string} [sCmd] (eg, "cls" or "clear") + */ + doClear(sCmd) + { + /* + * TODO: There should be a clear() component method that the Control Panel overrides to perform this function. + */ + if (this.controlPrint) this.controlPrint.value = ""; + } + + /** + * doDump(asArgs) + * + * @this {DebuggerPDP10} + * @param {Array.} asArgs (formerly sCmd, [sAddr], [sLen] and [sBytes]) + */ + doDump(asArgs) + { + var m; + var sCmd = asArgs[0]; + var sAddr = asArgs[1]; + var sLen = asArgs[2]; + var sBytes = asArgs[3]; + + if (sAddr == '?') { + var sDumpers = ""; + for (m in MessagesPDP10.CATEGORIES) { + if (this.afnDumpers[m]) { + if (sDumpers) sDumpers += ','; + sDumpers = sDumpers + m; + } + } + sDumpers += ",state,symbols"; + this.println("dump memory commands:"); + this.println("\tdw [a] [n] dump n words at address a"); + this.println("\tds [a] [n] dump n words at address a as JSON"); + this.println("\tdh [p] [n] dump n instructions from history position p"); + if (sDumpers.length) this.println("dump extension commands:\n\t" + sDumpers); + return; + } + + if (sAddr == "state") { + var sState = this.cmp.powerOff(true); + if (sLen == "console") { + /* + * Console buffers are notoriously small, and even the following code, which breaks the + * data into parts (eg, "d state console 1", "d state console 2", etc) just isn't that helpful. + * + * var nPart = +sBytes; + * if (nPart) sState = sState.substr(1000000 * (nPart-1), 1000000); + * + * So, the best way to capture a large machine state is to use the new "Save Machine" link + * that downloads a machine's entire state. Alternatively, run your own local server and use + * server-side storage. Take a look at the "Save" binding in computer.js, which binds an HTML + * control to the computer.powerOff() and computer.saveServerState() functions. + */ + console.log(sState); + } else { + this.doClear(); + if (sState) this.println(sState); + } + return; + } + + if (sAddr == "symbols") { + this.dumpSymbols(); + return; + } + + if (sCmd == "d") { + for (m in MessagesPDP10.CATEGORIES) { + if (asArgs[1] == m) { + var fnDumper = this.afnDumpers[m]; + if (fnDumper) { + asArgs.shift(); + asArgs.shift(); + fnDumper(asArgs); + } else { + this.println("no dump registered for " + sAddr); + } + return; + } + } + if (!sAddr) sCmd = this.sCmdDumpPrev || "dw"; + } else { + this.sCmdDumpPrev = sCmd; + } + + if (sCmd == "dh") { + this.dumpHistory(sAddr, sLen); + return; + } + + var dbgAddr = this.parseAddr(sAddr); + if (!dbgAddr) return; + + var len = 0; + var fJSON = (sCmd == "ds"); + + if (sLen) { + if (sLen.charAt(0) == 'l') { + sLen = sLen.substr(1) || sBytes; + len = this.parseValue(sLen); + } + else { + var dbgAddrEnd = this.parseAddr(sLen); + if (dbgAddrEnd) len = dbgAddrEnd.addr - dbgAddr.addr; + } + if (len < 0) len = 0; + if (len > 0x10000) len = 0x10000; + } + + var nBase = this.nBase; + if (dbgAddr.nBase) this.nBase = dbgAddr.nBase; + + var nBitsPerWord = 36; + var nWords = len || 32; + var nWordsPerLine = 4; // fJSON? 16 : this.nBase; + var nLines = (((nWords + nWordsPerLine - 1) / nWordsPerLine)|0) || 1; + + var sDump = ""; + while (nLines-- && nWords > 0) { + var sData = "", sChars = ""; + sAddr = this.toStrAddr(dbgAddr); + var n = nWordsPerLine; + while (n-- > 0 && nWords-- > 0) { + var w = this.getWord(dbgAddr, 1); + if (fJSON) { + if (sData) sData += ","; + sData += "0x"+ Str.toHex(w, nBitsPerWord >> 2); + } else { + sData += this.toStrBase(w, nBitsPerWord >> 3); + sData += ' '; + } + var nBytesPerWord = nBitsPerWord >> 3; + while (nBytesPerWord--) { + var c = w % 256; + sChars += (c >= 32 && c < 128? String.fromCharCode(c) : '.'); + w /= 256; + } + } + if (sDump) sDump += "\n"; + if (fJSON) { + sDump += sData + ","; + } else { + sDump += sAddr + ": " + sData + ((n == 0)? (' ' + sChars) : ""); + } + } + + if (sDump) this.println(sDump); + + this.dbgAddrNextData = dbgAddr; + this.nBase = nBase; + } + + /** + * doEdit(asArgs) + * + * @this {DebuggerPDP10} + * @param {Array.} asArgs + */ + doEdit(asArgs) + { + var size, mask; + var fnGet, fnSet; + var sCmd = asArgs[0]; + var sAddr = asArgs[1]; + if (sCmd == "e" || sCmd == "ew") { + size = 2; + mask = 0xffff; + fnGet = this.getWord; + fnSet = this.setWord; + } else { + sAddr = null; + } + if (sAddr == null) { + this.println("edit memory commands:"); + this.println("\tew [a] [...] edit words at address a"); + return; + } + var dbgAddr = this.parseAddr(sAddr); + if (!dbgAddr) return; + for (var i = 2; i < asArgs.length; i++) { + var vNew = this.parseExpression(asArgs[i]); + if (vNew === undefined) { + this.println("unrecognized value: " + asArgs[i]); + break; + } + if (vNew & ~mask) { + this.println("warning: " + Str.toHex(vNew) + " exceeds " + size + "-byte value"); + } + this.println("changing " + this.toStrAddr(dbgAddr) + (this.messageEnabled(MessagesPDP10.BUS)? "" : (" from " + this.toStrBase(fnGet.call(this, dbgAddr), size))) + " to " + this.toStrBase(vNew, size)); + //noinspection JSUnresolvedFunction + fnSet.call(this, dbgAddr, vNew, size); + } + } + + /** + * doHalt(fQuiet) + * + * @this {DebuggerPDP10} + * @param {boolean} [fQuiet] + */ + doHalt(fQuiet) + { + var sMsg; + if (this.flags.running) { + if (!fQuiet) this.println("halting"); + this.stopCPU(); + } else { + if (this.isBusy(true)) return; + if (!fQuiet) this.println("already halted"); + } + } + + /** + * doIf(sCmd, fQuiet) + * + * NOTE: Don't forget that the default base for all numeric constants is 16 (hex), so when you evaluate + * an expression like "a==10", it will compare the value of the variable "a" to 0x10; use a trailing period + * (eg, "10.") if you really intend decimal. + * + * Also, if no variable named "a" exists, "a" will evaluate to 0x0A, so the expression "a==10" becomes + * "0x0A==0x10" (false), whereas the expression "a==10." becomes "0x0A==0x0A" (true). + * + * @this {DebuggerPDP10} + * @param {string} sCmd + * @param {boolean} [fQuiet] + * @return {boolean} true if expression is non-zero, false if zero (or undefined due to a parse error) + */ + doIf(sCmd, fQuiet) + { + sCmd = Str.trim(sCmd); + if (!this.parseExpression(sCmd)) { + if (!fQuiet) this.println("false: " + sCmd); + return false; + } + if (!fQuiet) this.println("true: " + sCmd); + return true; + } + + /** + * doInfo(asArgs) + * + * @this {DebuggerPDP10} + * @param {Array.} asArgs + * @return {boolean} true only if the instruction info command ("n") is supported + */ + doInfo(asArgs) + { + if (DEBUG) { + this.println("msPerYield: " + this.cpu.msPerYield); + this.println("nCyclesPerYield: " + this.cpu.nCyclesPerYield); + return true; + } + return false; + } + + /** + * doVar(sCmd) + * + * The command must be of the form "{variable} = [{expression}]", where expression may contain constants, + * operators, registers, symbols, other variables, or nothing at all; in the latter case, the variable, if + * any, is deleted. + * + * Other supported shorthand: "var" with no parameters prints the values of all variables, and "var {variable}" + * prints the value of the specified variable. + * + * @this {DebuggerPDP10} + * @param {string} sCmd + * @return {boolean} true if valid "var" assignment, false if not + */ + doVar(sCmd) + { + var a = sCmd.match(/^\s*([A-Z_]?[A-Z0-9_]*)\s*(=?)\s*(.*)$/i); + if (a) { + if (!a[1]) { + if (!this.printVariable()) this.println("no variables"); + return true; // it's not considered an error to print an empty list of variables + } + if (!a[2]) { + return this.printVariable(a[1]); + } + if (!a[3]) { + this.delVariable(a[1]); + return true; // it's not considered an error to delete a variable that didn't exist + } + var v = this.parseExpression(a[3]); + if (v !== undefined) { + this.setVariable(a[1], v); + return true; + } + return false; + } + this.println("invalid assignment:" + sCmd); + return false; + } + + /** + * doList(sAddr, fPrint) + * + * @this {DebuggerPDP10} + * @param {string} sAddr + * @param {boolean} [fPrint] + * @return {string|null} + */ + doList(sAddr, fPrint) + { + var sSymbol = null; + + var dbgAddr = this.parseAddr(sAddr, true); + if (dbgAddr) { + var addr = this.getAddr(dbgAddr); + var aSymbol = this.findSymbol(dbgAddr, true); + if (aSymbol.length) { + var nDelta, sDelta, s; + if (aSymbol[0]) { + sDelta = ""; + nDelta = dbgAddr.addr - aSymbol[1]; + if (nDelta) sDelta = " + " + Str.toHexWord(nDelta); + s = aSymbol[0] + " (" + this.toStrOffset(aSymbol[1]) + ')' + sDelta; + if (fPrint) this.println(s); + sSymbol = s; + } + if (aSymbol.length > 4 && aSymbol[4]) { + sDelta = ""; + nDelta = aSymbol[5] - dbgAddr.addr; + if (nDelta) sDelta = " - " + Str.toHexWord(nDelta); + s = aSymbol[4] + " (" + this.toStrOffset(aSymbol[5]) + ')' + sDelta; + if (fPrint) this.println(s); + if (!sSymbol) sSymbol = s; + } + } else { + if (fPrint) this.println("no symbols"); + } + } + return sSymbol; + } + + /** + * doMessages(asArgs) + * + * @this {DebuggerPDP10} + * @param {Array.} asArgs + */ + doMessages(asArgs) + { + var m; + var fCriteria = null; + var sCategory = asArgs[1]; + if (sCategory == '?') sCategory = undefined; + + if (sCategory !== undefined) { + var bitsMessage = 0; + if (sCategory == "all") { + bitsMessage = (0xffffffff|0) & ~(MessagesPDP10.HALT | MessagesPDP10.KEYS | MessagesPDP10.LOG); + sCategory = null; + } else if (sCategory == "on") { + fCriteria = true; + sCategory = null; + } else if (sCategory == "off") { + fCriteria = false; + sCategory = null; + } else { + /* + * Internally, we use "key" instead of "keys", since the latter is a method on JavasScript objects, + * but externally, we allow the user to specify "keys"; "kbd" is also allowed as shorthand for "keyboard". + */ + if (sCategory == "keys") sCategory = "key"; + if (sCategory == "kbd") sCategory = "keyboard"; + for (m in MessagesPDP10.CATEGORIES) { + if (sCategory == m) { + bitsMessage = MessagesPDP10.CATEGORIES[m]; + fCriteria = !!(this.bitsMessage & bitsMessage); + break; + } + } + if (!bitsMessage) { + this.println("unknown message category: " + sCategory); + return; + } + } + if (bitsMessage) { + if (asArgs[2] == "on") { + this.bitsMessage |= bitsMessage; + fCriteria = true; + } + else if (asArgs[2] == "off") { + this.bitsMessage &= ~bitsMessage; + fCriteria = false; + if (bitsMessage == MessagesPDP10.BUFFER) { + var i = this.aMessageBuffer.length >= 1000? this.aMessageBuffer.length - 1000 : 0; + while (i < this.aMessageBuffer.length) { + this.println(this.aMessageBuffer[i++]); + } + this.aMessageBuffer = []; + } + } + } + } + + /* + * Display those message categories that match the current criteria (on or off) + */ + var n = 0; + var sCategories = ""; + for (m in MessagesPDP10.CATEGORIES) { + if (!sCategory || sCategory == m) { + var bitMessage = MessagesPDP10.CATEGORIES[m]; + var fEnabled = !!(this.bitsMessage & bitMessage); + if (fCriteria !== null && fCriteria != fEnabled) continue; + if (sCategories) sCategories += ','; + if (!(++n % 10)) sCategories += "\n\t"; // jshint ignore:line + /* + * Internally, we use "key" instead of "keys", since the latter is a method on JavasScript objects, + * but externally, we allow the user to specify "keys". + */ + if (m == "key") m = "keys"; + sCategories += m; + } + } + + if (sCategory === undefined) { + this.println("message commands:\n\tm [category] [on|off]\tturn categories on/off"); + } + + this.println((fCriteria !== null? (fCriteria? "messages on: " : "messages off: ") : "message categories:\n\t") + (sCategories || "none")); + + this.historyInit(); // call this just in case MessagesPDP10.INT was turned on + } + + /** + * doOptions(asArgs) + * + * @this {DebuggerPDP10} + * @param {Array.} asArgs + */ + doOptions(asArgs) + { + switch (asArgs[1]) { + + case "base": + if (asArgs[2]) { + var nBase = +asArgs[2]; + if (nBase == 8 || nBase == 10 || nBase == 16) { + this.nBase = nBase; + } else { + this.println("invalid base: " + nBase); + break; + } + } + this.println("default base: " + this.nBase); + break; + + case "cs": + var nCycles; + if (asArgs[3] !== undefined) nCycles = +asArgs[3]; // warning: decimal instead of hex conversion + switch (asArgs[2]) { + case "int": + this.cpu.nCyclesChecksumInterval = nCycles; + break; + case "start": + this.cpu.nCyclesChecksumStart = nCycles; + break; + case "stop": + this.cpu.nCyclesChecksumStop = nCycles; + break; + default: + this.println("unknown cs option"); + return; + } + if (nCycles !== undefined) { + this.cpu.resetChecksum(); + } + this.println("checksums " + (this.cpu.flags.checksum? "enabled" : "disabled")); + return; + + case "sp": + if (asArgs[2] !== undefined) { + if (!this.cpu.setSpeed(+asArgs[2])) { + this.println("warning: using 1x multiplier, previous target not reached"); + } + } + this.println("target speed: " + this.cpu.getSpeedTarget() + " (" + this.cpu.getSpeed() + "x)"); + return; + + default: + if (asArgs[1]) { + this.println("unknown option: " + asArgs[1]); + return; + } + /* falls through */ + + case "?": + this.println("debugger options:"); + this.println("\tbase #\t\tset default base to #"); + this.println("\tcs int #\tset checksum cycle interval to #"); + this.println("\tcs start #\tset checksum cycle start count to #"); + this.println("\tcs stop #\tset checksum cycle stop count to #"); + this.println("\tsp #\t\tset speed multiplier to #"); + break; + } + } + + /** + * doRegisters(asArgs, fInstruction) + * + * @this {DebuggerPDP10} + * @param {Array.} [asArgs] + * @param {boolean} [fInstruction] (true to include the current instruction; default is true) + */ + doRegisters(asArgs, fInstruction) + { + if (asArgs && asArgs[1] == '?') { + this.println("register commands:"); + this.println("\tr\tdump registers"); + this.println("\trm\tdump misc registers"); + this.println("\trx [#]\tset flag or register x to [#]"); + return; + } + + var fMisc = false; + var cpu = this.cpu; + if (fInstruction == null) fInstruction = true; + + if (asArgs != null && asArgs.length > 1) { + var sReg = asArgs[1]; + + if (sReg == 'm') { + fMisc = true; + } + else { + var sValue = null; + var i = sReg.indexOf('='); + if (i > 0) { + sValue = sReg.substr(i + 1); + sReg = sReg.substr(0, i); + } + else if (asArgs.length > 2) { + sValue = asArgs[2]; + } + else { + this.println("missing value for " + asArgs[1]); + return; + } + + var w = this.parseExpression(sValue); + if (w === undefined) return; + + var sRegMatch = sReg.toUpperCase(); + switch (sRegMatch) { + case "PC": + cpu.setPC(w); + this.dbgAddrNextCode = this.newAddr(cpu.getPC()); + break; + default: + this.println("unknown register: " + sReg); + return; + } + this.cmp.updateDisplays(); + this.println("updated registers:"); + } + } + + this.println(this.getRegDump(fMisc)); + + if (fInstruction) { + this.dbgAddrNextCode = this.newAddr(cpu.getPC()); + this.doUnassemble(this.toStrAddr(this.dbgAddrNextCode)); + } + } + + /** + * doRun(sCmd, sAddr, sOptions, fQuiet) + * + * @this {DebuggerPDP10} + * @param {string} sCmd + * @param {string|undefined} [sAddr] + * @param {string} [sOptions] (the rest of the breakpoint command-line) + * @param {boolean} [fQuiet] + */ + doRun(sCmd, sAddr, sOptions, fQuiet) + { + if (sCmd == "gt") { + this.fIgnoreNextCheckFault = true; + } + if (sAddr !== undefined) { + var dbgAddr = this.parseAddr(sAddr, true); + if (!dbgAddr) return; + this.parseAddrOptions(dbgAddr, sOptions); + this.setTempBreakpoint(dbgAddr); + } + this.startCPU(true, fQuiet); + } + + /** + * doPrint(sCmd) + * + * NOTE: If the string to print is a quoted string, then we run it through replaceRegs(), so that + * you can take advantage of all the special replacement options used for software interrupt logging. + * + * @this {DebuggerPDP10} + * @param {string} sCmd + */ + doPrint(sCmd) + { + sCmd = Str.trim(sCmd); + var a = sCmd.match(/^(['"])(.*?)\1$/); + if (!a) { + this.parseExpression(sCmd, true); + } else { + if (a[2].length > 1) { + this.println(this.replaceRegs(a[2])); + } else { + this.printValue(null, a[2].charCodeAt(0)); + } + } + } + + /** + * doStep(sCmd, sOption) + * + * @this {DebuggerPDP10} + * @param {string} [sCmd] "p" or "pr" + * @param {string} [sOption] + */ + doStep(sCmd, sOption) + { + if (sOption == '?') { + this.println("step commands:"); + this.println("\tp\tstep over instruction"); + this.println("\tpr\tstep over instruction with register update"); + return; + } + + var fCallStep = true; + var nRegs = (sCmd == "pr"? 1 : 0); + /* + * Set up the value for this.nStep (ie, 1 or 2) depending on whether the user wants + * a subsequent register dump ("pr") or not ("p"). + */ + var nStep = 1 + nRegs; + + if (!this.nStep) { + var dbgAddr = this.newAddr(this.cpu.getPC()); + var opCode = this.getWord(dbgAddr); + + if (this.nStep) { + this.setTempBreakpoint(dbgAddr); + if (!this.startCPU()) { + if (this.cmp) this.cmp.setFocus(); + this.nStep = 0; + } + /* + * A successful run will ultimately call stop(), which will in turn call clearTempBreakpoint(), + * which will clear nStep, so there's your assurance that nStep will be reset. Now we may have + * stopped for reasons unrelated to the temporary breakpoint, but that's OK. + */ + } else { + this.doTrace(nRegs? "tr" : "t"); + } + } else { + this.println("step in progress"); + } + } + + /** + * getCall(dbgAddr) + * + * Given a possible return address (typically from the stack), look for a matching CALL (or INT) that + * immediately precedes that address. + * + * @this {DebuggerPDP10} + * @param {DbgAddrPDP10} dbgAddr + * @return {string|null} CALL instruction at or near dbgAddr, or null if none + */ + getCall(dbgAddr) + { + var sCall = null; + var addr = dbgAddr.addr; + var addrOrig = addr; + for (var n = 1; n <= 6 && !!addr; n++) { + if (n > 2) { + dbgAddr.addr = addr; + var s = this.getInstruction(dbgAddr); + if (s.indexOf("JSR") >= 0) { + /* + * Verify that the length of this call, when added to the address of the call, matches + * the original return address. We do this by getting the string index of the opcode bytes, + * subtracting that from the string index of the next space, and dividing that difference + * by two, to yield the length of the CALL (or INT) instruction, in bytes. + */ + var i = s.indexOf(' '); + var j = s.indexOf(' ', i+1); + if (addr + (j - i - 1)/2 == addrOrig) { + sCall = s; + break; + } + } + } + addr -= 2; + } + dbgAddr.addr = addrOrig; + return sCall; + } + + /** + * doStackTrace(sCmd, sAddr) + * + * Use "k" for a normal stack trace and "ks" for a stack trace with symbolic info. + * + * @this {DebuggerPDP10} + * @param {string} [sCmd] + * @param {string} [sAddr] (not used yet) + */ + doStackTrace(sCmd, sAddr) + { + if (sAddr == '?') { + this.println("stack trace commands:"); + this.println("\tk\tshow frame addresses"); + this.println("\tks\tshow symbol information"); + return; + } + + var nFrames = 10, cFrames = 0; + var dbgAddrCall = this.newAddr(); + var dbgAddrStack = this.newAddr(/*this.cpu.getSP()*/); + this.println("stack trace for " + this.toStrAddr(dbgAddrStack)); + + while (cFrames < nFrames) { + var sCall = null, sCallPrev = null, cTests = 256; + while ((dbgAddrStack.addr >>> 0) < 0x10000) { + dbgAddrCall.addr = this.getWord(dbgAddrStack, 2); + /* + * Because we're using the auto-increment feature of getWord(), and because that will automatically + * wrap the offset around the end of the segment, we must also check the addr property to detect the wrap. + */ + if (dbgAddrStack.addr == null || !cTests--) break; + if (dbgAddrCall.addr & 0x1) continue; // an odd address on the PDP-11 is not a valid instruction boundary + sCall = this.getCall(dbgAddrCall); + if (sCall) break; + } + /* + * The sCallPrev check eliminates duplicate sequential calls, which are usually (but not always) + * indicative of a false positive, in which case the previous call is probably bogus as well, but + * at least we won't duplicate that mistake. Of course, there are always exceptions, recursion + * being one of them, but it's rare that we're debugging recursive code. + */ + if (!sCall || sCall == sCallPrev) break; + var sSymbol = null; + if (sCmd == "ks") { + var a = sCall.match(/[0-9A-F]+$/); + if (a) sSymbol = this.doList(a[0]); + } + sCall = Str.pad(sCall, 50) + " ;" + (sSymbol || "stack=" + this.toStrAddr(dbgAddrStack)); // + " return=" + this.toStrAddr(dbgAddrCall)); + this.println(sCall); + sCallPrev = sCall; + cFrames++; + } + if (!cFrames) this.println("no return addresses found"); + } + + /** + * doTrace(sCmd, sCount) + * + * The "t" and "tr" commands interpret the count as a number of instructions, and since + * we call the Debugger's stepCPU() for each iteration, a single instruction includes + * any/all prefixes; the CPU's stepCPU() treats prefixes as discrete operations. The only + * difference between "t" and "tr": the former displays only the next instruction, while + * the latter also displays the (updated) registers. + * + * The "tc" command interprets the count as a number of cycles rather than instructions, + * allowing you to quickly execute large chunks of instructions with a single command; it + * doesn't display anything until the the chunk has finished. "tc 1" is also a useful + * command in that it doesn't inhibit interrupts like "t" or "tr" does. + * + * However, generally a more useful command is "bn", which allows you to break after some + * number of instructions have been executed (as opposed to some number of cycles). + * + * @this {DebuggerPDP10} + * @param {string} [sCmd] ("t", "tc", or "tr") + * @param {string} [sCount] # of instructions to step + */ + doTrace(sCmd, sCount) + { + if (sCount == '?') { + this.println("trace commands:"); + this.println("\tt [#]\ttrace # instructions"); + this.println("\ttr [#]\ttrace # instructions with register updates"); + this.println("\ttc [#]\ttrace # cycles"); + this.println("note: bn [#] breaks after # instructions without updates"); + return; + } + + var dbg = this; + var fRegs = (sCmd != "t"); + var nCount = this.parseValue(sCount, null, true) || 1; + + /* + * We used to set nCycles to 1 when a count > 1 was specified, because nCycles set + * to 0 used to mean "execute the next instruction without checking for interrupts". + * Well, this machine's stepCPU() doesn't do that; it ALWAYS checks for interrupts, + * so we should leave nCycles set to 0, so that if an interrupt is dispatched, we will + * get to see the first instruction of the interrupt handler. + */ + var nCycles = 0; // (nCount == 1? 0 : 1); + + if (sCmd == "tc") { + nCycles = nCount; + nCount = 1; + } + this.sCmdTracePrev = sCmd; + + Web.onCountRepeat( + nCount, + function onCountStep() { + return dbg.setBusy(true) && dbg.stepCPU(nCycles, fRegs, false); + }, + function onCountStepComplete() { + /* + * We explicitly called stepCPU() with fUpdateDisplays set to false, because repeatedly + * calling updateDisplays() can be very slow, especially if a Control Panel is present with + * displayLiveRegs enabled, so once the repeat count has been exhausted, we must perform + * a final updateDisplays(). + */ + if (dbg.panel) dbg.panel.stop(); + dbg.cmp.updateDisplays(-1); + dbg.setBusy(false); + } + ); + } + + /** + * doUnassemble(sAddr, sAddrEnd, nLines) + * + * @this {DebuggerPDP10} + * @param {string} [sAddr] + * @param {string} [sAddrEnd] + * @param {number} [nLines] + */ + doUnassemble(sAddr, sAddrEnd, nLines) + { + var dbgAddr = this.parseAddr(sAddr, true); + if (!dbgAddr) return; + + if (nLines === undefined) nLines = 1; + + var nBytes = 0x100; + if (sAddrEnd !== undefined) { + + if (sAddrEnd.charAt(0) == 'l') { + var n = this.parseValue(sAddrEnd.substr(1)); + if (n != null) nLines = n; + } + else { + var dbgAddrEnd = this.parseAddr(sAddrEnd, true); + if (!dbgAddrEnd || dbgAddrEnd.addr < dbgAddr.addr) return; + + nBytes = dbgAddrEnd.addr - dbgAddr.addr; + if (!DEBUG && nBytes > 0x100) { + /* + * Limiting the amount of disassembled code to 256 bytes in non-DEBUG builds is partly to + * prevent the user from wedging the browser by dumping too many lines, but also a recognition + * that, in non-DEBUG builds, this.println() keeps print output buffer truncated to 8Kb anyway. + */ + this.println("range too large"); + return; + } + nLines = -1; + } + } + + var nPrinted = 0; + var sInstruction; + + while (nBytes > 0 && nLines--) { + + var nSequence = (this.isBusy(false) || this.nStep)? this.nCycles : null; + var sComment = (nSequence != null? "cycles" : null); + var aSymbol = this.findSymbol(dbgAddr); + + var addr = dbgAddr.addr; // we snap dbgAddr.addr *after* calling findSymbol(), which re-evaluates it + + if (aSymbol[0] && nLines) { + if (!nPrinted && nLines || aSymbol[0].indexOf('+') < 0) { + var sLabel = aSymbol[0] + ':'; + if (aSymbol[2]) sLabel += ' ' + aSymbol[2]; + this.println(sLabel); + } + } + + if (aSymbol[3]) { + sComment = aSymbol[3]; + nSequence = null; + } + + sInstruction = this.getInstruction(dbgAddr, sComment, nSequence); + + this.println(sInstruction); + this.dbgAddrNextCode = dbgAddr; + nBytes -= dbgAddr.addr - addr; + nPrinted++; + } + } + + /** + * splitArgs(sCmd) + * + * @this {DebuggerPDP10} + * @param {string} sCmd + * @return {Array.} + */ + splitArgs(sCmd) + { + var asArgs = sCmd.replace(/ +/g, ' ').split(' '); + asArgs[0] = asArgs[0].toLowerCase(); + if (asArgs && asArgs.length) { + var s0 = asArgs[0]; + var ch0 = s0.charAt(0); + for (var i = 1; i < s0.length; i++) { + var ch = s0.charAt(i); + if (ch0 == '?' || ch0 == 'r' || ch < 'a' || ch > 'z') { + asArgs[0] = s0.substr(i); + asArgs.unshift(s0.substr(0, i)); + break; + } + } + } + return asArgs; + } + + /** + * doCommand(sCmd, fQuiet) + * + * @this {DebuggerPDP10} + * @param {string} sCmd + * @param {boolean} [fQuiet] + * @return {boolean} true if command processed, false if unrecognized + */ + doCommand(sCmd, fQuiet) + { + var result = true; + + try { + if (!sCmd.length || sCmd == "end") { + if (this.fAssemble) { + this.println("ended assemble at " + this.toStrAddr(this.dbgAddrAssemble)); + this.dbgAddrNextCode = this.dbgAddrAssemble; + this.fAssemble = false; + } + sCmd = ""; + } + else if (!fQuiet) { + this.println(DebuggerPDP10.PROMPT + sCmd); + } + + var ch = sCmd.charAt(0); + if (ch == '"' || ch == "'") return true; + + /* + * Zap the previous message buffer to ensure the new command's output is not tossed out as a repeat. + */ + this.sMessagePrev = null; + + /* + * I've relaxed the !isBusy() requirement, to maximize our ability to issue Debugger commands externally. + */ + if (this.isReady() /* && !this.isBusy(true) */ && sCmd.length > 0) { + + if (this.fAssemble) { + sCmd = "a " + this.toStrAddr(this.dbgAddrAssemble) + ' ' + sCmd; + } + + var fError = false; + var asArgs = this.splitArgs(sCmd); + + switch (asArgs[0].charAt(0)) { + case 'a': + this.doAssemble(asArgs); + break; + case 'b': + this.doBreak(asArgs[0], asArgs[1], sCmd); + break; + case 'c': + this.doClear(asArgs[0]); + break; + case 'd': + if (!COMPILED && sCmd == "debug") { + window.DEBUG = true; + this.println("DEBUG checks on"); + break; + } + this.doDump(asArgs); + break; + case 'e': + if (asArgs[0] == "else") break; + this.doEdit(asArgs); + break; + case 'g': + this.doRun(asArgs[0], asArgs[1], sCmd, fQuiet); + break; + case 'h': + this.doHalt(fQuiet); + break; + case 'i': + if (asArgs[0] == "if") { + if (!this.doIf(sCmd.substr(2), fQuiet)) { + result = false; + } + break; + } + fError = true; + break; + case 'k': + this.doStackTrace(asArgs[0], asArgs[1]); + break; + case 'l': + if (asArgs[0] == "ln") { + this.doList(asArgs[1], true); + break; + } + fError = true; + break; + case 'm': + this.doMessages(asArgs); + break; + case 'p': + if (asArgs[0] == "print") { + this.doPrint(sCmd.substr(5)); + break; + } + this.doStep(asArgs[0], asArgs[1]); + break; + case 'r': + if (sCmd == "reset") { + if (this.cmp) this.cmp.reset(); + break; + } + this.doRegisters(asArgs); + break; + case 's': + this.doOptions(asArgs); + break; + case 't': + this.doTrace(asArgs[0], asArgs[1]); + break; + case 'u': + this.doUnassemble(asArgs[1], asArgs[2], 8); + break; + case 'v': + if (asArgs[0] == "var") { + if (!this.doVar(sCmd.substr(3))) { + result = false; + } + break; + } + if (asArgs[0] == "ver") { + this.println((PDP10.APPNAME || "PDP10") + " version " + (XMLVERSION || PDP10.APPVERSION) + " (" + this.cpu.model + (PDP10.COMPILED? ",RELEASE" : (PDP10.DEBUG? ",DEBUG" : ",NODEBUG")) + ')'); + this.println(Web.getUserAgent()); + break; + } + fError = true; + break; + case '?': + if (asArgs[1]) { + this.doPrint(sCmd.substr(1)); + break; + } + this.doHelp(); + break; + case 'n': + if (!COMPILED && sCmd == "nodebug") { + window.DEBUG = false; + this.println("DEBUG checks off"); + break; + } + if (this.doInfo(asArgs)) break; + /* falls through */ + default: + fError = true; + break; + } + if (fError) { + this.println("unknown command: " + sCmd); + result = false; + } + } + } catch(e) { + this.println("debugger error: " + (e.stack || e.message)); + result = false; + } + return result; + } + + /** + * doCommands(sCmds, fSave) + * + * @this {DebuggerPDP10} + * @param {string} sCmds + * @param {boolean} [fSave] + * @return {boolean} true if all commands processed, false if not + */ + doCommands(sCmds, fSave) + { + var a = this.parseCommand(sCmds, fSave); + for (var s in a) { + if (!this.doCommand(a[+s])) return false; + } + return true; + } + + /** + * DebuggerPDP10.init() + * + * This function operates on every HTML element of class "debugger", extracting the + * JSON-encoded parameters for the Debugger constructor from the element's "data-value" + * attribute, invoking the constructor to create a Debugger component, and then binding + * any associated HTML controls to the new component. + */ + static init() + { + var aeDbg = Component.getElementsByClass(document, PDP10.APPCLASS, "debugger"); + for (var iDbg = 0; iDbg < aeDbg.length; iDbg++) { + var eDbg = aeDbg[iDbg]; + var parmsDbg = Component.getComponentParms(eDbg); + var dbg = new DebuggerPDP10(parmsDbg); + Component.bindComponentControls(dbg, eDbg, PDP10.APPCLASS); + } + } +} + +if (DEBUGGER) { + + /* + * NOTE: Every DebuggerPDP10 property from here to the first prototype function definition (initBus()) is + * considered a "class constant"; most of them use our "all-caps" convention (and all of them SHOULD, but + * that wouldn't help us catch any bugs). + * + * Technically, all of them should ALSO be preceded by a "@const" annotation, but that's a lot of work and it + * really clutters the code. I wish the Closure Compiler had a way to annotate every definition with a given + * section with a single annotation.... + */ + + DebuggerPDP10.COMMANDS = { + '?': "help/print", + 'a [#]': "assemble", // TODO: Implement this command someday + 'b [#]': "breakpoint", // multiple variations (use b? to list them) + 'c': "clear output", + 'd [#]': "dump memory", // additional syntax: d [#] [l#], where l# is a number of bytes to dump + 'e [#]': "edit memory", + 'g [#]': "go [to #]", + 'h': "halt", + 'if': "eval expression", + 'int [#]': "request interrupt", + 'k': "stack trace", + "ln": "list nearest symbol(s)", + 'm': "messages", + 'p': "step over", // other variations: pr (step and dump registers) + 'print': "print expression", + 'r': "dump/set registers", + 'reset': "reset machine", + 's': "set options", + 't [#]': "trace", // other variations: tr (trace and dump registers) + 'u [#]': "unassemble", + 'var': "assign variable", + 'ver': "print version" + }; + + /* + * CPU opcode IDs + */ + DebuggerPDP10.OPS = { + NONE: 0 + }; + + /* + * CPU opcode names, indexed by CPU opcode ordinal (above) + */ + DebuggerPDP10.OPNAMES = [ + ".WORD", + ]; + + DebuggerPDP10.REGS = { + PC: 0 + }; + + DebuggerPDP10.REGNAMES = [ + "PC" + ]; + + /* + * The OPTABLE contains opcode masks, and each mask refers to table of possible values, and each + * value refers to an array that contains: + * + * [0]: {number} of the opcode name (see OP.*) + * [1]: {number} containing the first operand type bit(s), if any + * [2]: {number} containing the second operand type bit(s), if any + * + * Note that, by convention, opcodes that require two operands list the SRC operand first and DST operand + * second (ie, the OPPOSITE of the Intel convention). + * + * Also note that, for some of the newer PDP-11 opcodes (eg, MUL, DIV, ASH, ASHC), the location of the + * opcode's SRC and DST bits are reversed. This is why, for example, you'll see the MUL instruction defined + * below as having OP_DST for the first operand and OP_SRCREG for the second operand. This does NOT mean + * that the opcode's destination operand is being listed first, but rather that the bits describing the source + * operand are in the opcode's OP_DST field. + */ + DebuggerPDP10.OPTABLE = {}; + + DebuggerPDP10.OPNONE = [DebuggerPDP10.OPS.NONE]; + + DebuggerPDP10.HISTORY_LIMIT = DEBUG? 100000 : 1000; + + DebuggerPDP10.PROMPT = ">> "; + + /* + * Initialize every Debugger module on the page (as IF there's ever going to be more than one ;-)) + */ + Web.onInit(DebuggerPDP10.init); + +} // endif DEBUGGER + +if (NODE) module.exports = DebuggerPDP10; diff --git a/modules/pdp10/lib/defines.js b/modules/pdp10/lib/defines.js new file mode 100644 index 000000000..2560419e3 --- /dev/null +++ b/modules/pdp10/lib/defines.js @@ -0,0 +1,148 @@ +/** + * @fileoverview PDP10-specific compile-time definitions. + * @author Jeff Parsons + * @copyright © Jeff Parsons 2012-2017 + * + * This file is part of PCjs, a computer emulation software project at . + * + * PCjs is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCjs. If not, + * see . + * + * You are required to include the above copyright notice in every modified copy of this work + * and to display that copyright notice when the software starts running; see COPYRIGHT in + * . + * + * Some PCjs files also attempt to load external resource files, such as character-image files, + * ROM files, and disk image files. Those external resource files are not considered part of PCjs + * for purposes of the GNU General Public License, and the author does not claim any copyright + * as to their contents. + */ + +"use strict"; + +/** + * @define {string} + */ +var APPCLASS = "pdp10"; // this @define is the default application class (eg, "pcx86", "c1pjs") + +/** + * APPNAME is used more for display purposes than anything else now. APPCLASS is what matters in terms + * of folder and file names, CSS styles, etc. + * + * @define {string} + */ +var APPNAME = "PDPjs"; // this @define is the default application name (eg, "PCx86", "C1Pjs") + +/** + * WARNING: DEBUGGER needs to accurately reflect whether or not the Debugger component is (or will be) loaded. + * In the compiled case, we rely on the Closure Compiler to override DEBUGGER as appropriate. When it's *false*, + * nearly all of debugger.js will be conditionally removed by the compiler, reducing it to little more than a + * "type skeleton", which also solves some type-related warnings we would otherwise have if we tried to remove + * debugger.js from the compilation process altogether. + * + * However, when we're in "development mode" and running uncompiled code in debugger-less configurations, + * I would like to skip loading debugger.js altogether. When doing that, we must ALSO arrange for an additional file + * (nodebugger.js) to be loaded immediately after this file, which *explicitly* overrides DEBUGGER with *false*. + * + * @define {boolean} + */ +var DEBUGGER = true; // this @define is overridden by the Closure Compiler to remove Debugger-related support + +/* + * Combine all the shared globals and machine-specific globals into one machine-specific global object, + * which all machine components should start using; eg: "if (PDP10.DEBUG) ..." instead of "if (DEBUG) ...". + */ +var PDP10 = { + APPCLASS: APPCLASS, + APPNAME: APPNAME, + APPVERSION: APPVERSION, // shared + COMPILED: COMPILED, // shared + CSSCLASS: CSSCLASS, // shared + DEBUG: DEBUG, // shared + DEBUGGER: DEBUGGER, + MAXDEBUG: MAXDEBUG, // shared + PRIVATE: PRIVATE, // shared + SITEHOST: SITEHOST, // shared + XMLVERSION: XMLVERSION, // shared + + /* + * CPU model numbers (supported) + * + * The 11/20 includes the 11/10, which is not identified separately because there was + * nothing functionally different about it. + * + * The 11/40 added the MODE bits to the PSW (but only KERNEL=00 and USER=11) and 18-bit + * addressing via an MMU; there was still only one register set. + * + * The 11/45 added REGSET bit to the PSW (to support a second register set), SUPER=01 + * mode to the existing KERNEL=00 and USER=11 modes, separate I/D spaces, and other MMU + * extensions (eg, MMR1 and MMR3). + * + * The 11/70 added 22-bit addressing and corresponding extensions to the MMU. + */ + MODEL_KA10: 1001, + + /* + * This constant is used to mark points in the code where the physical address being returned + * is invalid and should not be used. + * + * In a 32-bit CPU, -1 (ie, 0xffffffff) could actually be a valid address, so consider changing + * ADDR_INVALID to NaN or null (which is also why all ADDR_INVALID tests should use strict equality + * operators). + * + * The main reason I'm NOT using NaN or null now is my concern that, by mixing non-numbers + * (specifically, values outside the range of signed 32-bit integers), performance may suffer. + * + * WARNING: Like many of the properties defined here, ADDR_INVALID is a common constant, which the + * Closure Compiler will happily inline (with or without @const annotations; in fact, I've yet to + * see a @const annotation EVER improve automatic inlining). However, if you don't make ABSOLUTELY + * certain that this file is included BEFORE the first reference to any of these properties, that + * automatic inlining will no longer occur. + */ + ADDR_INVALID: -1, + ADDR_LIMIT: Math.pow(2, 18), + DATA_INVALID: 0, + DATA_LIMIT: Math.pow(2, 36), + /* + * Assorted common opcodes + */ + OPCODE: { + HALT: 0o000000000000, // TODO: Resolve + INVALID: 0o777777777777 // TODO: Resolve + }, + /* + * Internal operation state flags + */ + OPFLAG: { + IRQ_DELAY: 0x0001, // incremented until it becomes IRQ + IRQ: 0x0002, // time to call checkInterrupts() + IRQ_MASK: 0x0003, + DEBUGGER: 0x0004, // set if the Debugger wants to perform checks + WAIT: 0x0008, // WAIT operation in progress + PRESERVE: 0x000F, // OPFLAG bits to preserve prior to the next instruction + } +}; + +/* + * Combine all the shared globals and machine-specific globals into one machine-specific global object, + * which all machine components should start using; eg: "if (PDP10.DEBUGGER)" instead of "if (DEBUGGER)". + */ +PDP10.APPCLASS = APPCLASS; +PDP10.APPNAME = APPNAME; +PDP10.DEBUGGER = DEBUGGER; + +if (NODE) { + global.APPCLASS = APPCLASS; + global.APPNAME = APPNAME; + global.DEBUGGER = DEBUGGER; + global.PDP10 = PDP10; + module.exports = PDP10; +} diff --git a/modules/pdp10/lib/device.js b/modules/pdp10/lib/device.js new file mode 100644 index 000000000..b9d918740 --- /dev/null +++ b/modules/pdp10/lib/device.js @@ -0,0 +1,172 @@ +/** + * @fileoverview Implements PDP-10 device support. + * @author Jeff Parsons + * @copyright © Jeff Parsons 2012-2017 + * + * This file is part of PCjs, a computer emulation software project at . + * + * PCjs is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCjs. If not, + * see . + * + * You are required to include the above copyright notice in every modified copy of this work + * and to display that copyright notice when the software starts running; see COPYRIGHT in + * . + * + * Some PCjs files also attempt to load external resource files, such as character-image files, + * ROM files, and disk image files. Those external resource files are not considered part of PCjs + * for purposes of the GNU General Public License, and the author does not claim any copyright + * as to their contents. + */ + +"use strict"; + +if (NODE) { + var Str = require("../../shared/lib/strlib"); + var Web = require("../../shared/lib/weblib"); + var Component = require("../../shared/lib/component"); + var State = require("../../shared/lib/state"); + var PDP10 = require("./defines"); + var BusPDP10 = require("./bus"); + var MemoryPDP10 = require("./memory"); + var MessagesPDP10 = require("./messages"); +} + +class DevicePDP10 extends Component { + /** + * DevicePDP10(parmsDevice) + * + * @param {Object} parmsDevice + */ + constructor(parmsDevice) + { + super("Device", parmsDevice, MessagesPDP10.DEVICE); + } + + /** + * initBus(cmp, bus, cpu, dbg) + * + * @this {DevicePDP10} + * @param {ComputerPDP10} cmp + * @param {BusPDP10} bus + * @param {CPUStatePDP10} cpu + * @param {DebuggerPDP10} dbg + */ + initBus(cmp, bus, cpu, dbg) + { + this.bus = bus; + this.cmp = cmp; + this.cpu = cpu; + this.dbg = dbg; + + this.setReady(); + } + + /** + * powerUp(data, fRepower) + * + * @this {DevicePDP10} + * @param {Object|null} data + * @param {boolean} [fRepower] + * @return {boolean} true if successful, false if failure + */ + powerUp(data, fRepower) + { + if (!fRepower) { + if (!data) { + this.reset(); + } else { + if (!this.restore(data)) return false; + } + } + return true; + } + + /** + * powerDown(fSave, fShutdown) + * + * @this {DevicePDP10} + * @param {boolean} [fSave] + * @param {boolean} [fShutdown] + * @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure + */ + powerDown(fSave, fShutdown) + { + return fSave? this.save() : true; + } + + /** + * reset() + * + * @this {DevicePDP10} + */ + reset() + { + } + + /** + * save() + * + * This implements save support for the DevicePDP10 component. + * + * @this {DevicePDP10} + * @return {Object} + */ + save() + { + var state = new State(this); + return state.data(); + } + + /** + * restore(data) + * + * This implements restore support for the DevicePDP10 component. + * + * @this {DevicePDP10} + * @param {Object} data + * @return {boolean} true if successful, false if failure + */ + restore(data) + { + return true; + } + + /** + * DevicePDP10.init() + * + * This function operates on every HTML element of class "device", extracting the + * JSON-encoded parameters for the DevicePDP10 constructor from the element's "data-value" + * attribute, invoking the constructor to create a DevicePDP10 component, and then binding + * any associated HTML controls to the new component. + */ + static init() + { + var aeDevice = Component.getElementsByClass(document, PDP10.APPCLASS, "device"); + for (var iDevice = 0; iDevice < aeDevice.length; iDevice++) { + var device; + var eDevice = aeDevice[iDevice]; + var parmsDevice = Component.getComponentParms(eDevice); + switch(parmsDevice['type']) { + case 'default': + device = new DevicePDP10(parmsDevice); + Component.bindComponentControls(device, eDevice, PDP10.APPCLASS); + break; + } + } + } +} + +/* + * Initialize all the DevicePDP10 modules on the page. + */ +Web.onInit(DevicePDP10.init); + +if (NODE) module.exports = DevicePDP10; diff --git a/modules/pdp10/lib/memory.js b/modules/pdp10/lib/memory.js new file mode 100644 index 000000000..7381b68f5 --- /dev/null +++ b/modules/pdp10/lib/memory.js @@ -0,0 +1,645 @@ +/** + * @fileoverview Implements the PDP-10 Memory component. + * @author Jeff Parsons + * @copyright © Jeff Parsons 2012-2017 + * + * This file is part of PCjs, a computer emulation software project at . + * + * PCjs is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCjs. If not, + * see . + * + * You are required to include the above copyright notice in every modified copy of this work + * and to display that copyright notice when the software starts running; see COPYRIGHT in + * . + * + * Some PCjs files also attempt to load external resource files, such as character-image files, + * ROM files, and disk image files. Those external resource files are not considered part of PCjs + * for purposes of the GNU General Public License, and the author does not claim any copyright + * as to their contents. + */ + +"use strict"; + +if (NODE) { + var Component = require("../../shared/lib/component"); + var Int36 = require("../../shared/lib/int36"); + var PDP10 = require("./defines"); + var MessagesPDP10 = require("./messages"); +} + +/** + * @class DataView + * @property {function(number,boolean):number} getUint8 + * @property {function(number,number,boolean)} setUint8 + * @property {function(number,boolean):number} getUint16 + * @property {function(number,number,boolean)} setUint16 + * @property {function(number,boolean):number} getInt32 + * @property {function(number,number,boolean)} setInt32 + */ +class MemoryPDP10 { + /** + * MemoryPDP10(bus, addr, used, size, type) + * + * The Bus component allocates Memory objects so that each has a memory buffer with a + * block-granular starting address and an address range equal to bus.nBlockSize; however, + * the size of any given Memory object's underlying buffer can be either zero or bus.nBlockSize; + * memory read/write functions for empty (buffer-less) blocks are mapped to readNone/writeNone. + * + * The Bus allocates empty blocks for the entire address space during initialization, so that + * any reads/writes to undefined addresses will have no effect. Later, the ROM and RAM + * components will ask the Bus to allocate memory for specific ranges, and the Bus will allocate + * as many new blockSize Memory objects as the ranges require. Partial Memory blocks could + * also be supported in theory, but in practice, they're not. + * + * WARNING: Since Memory blocks are low-level objects that have no UI requirements, they + * do not inherit from the Component class, so if you want to use any Component class methods, + * such as Component.assert(), use the corresponding Debugger methods instead (assuming a debugger + * is available). + * + * @param {BusPDP10} bus + * @param {number|null} [addr] of lowest used address in block + * @param {number} [used] portion of block in words (0 for none) + * @param {number} [size] of block's buffer in words (0 for none) + * @param {number} [type] is one of the MemoryPDP10.TYPE constants (default is MemoryPDP10.TYPE.NONE) + */ + constructor(bus, addr, used, size, type) + { + var a, i; + this.bus = bus; + this.id = (MemoryPDP10.idBlock += 2); + this.adw = null; + this.offset = 0; + this.addr = addr; + this.used = used; + this.size = size || 0; + this.type = type || MemoryPDP10.TYPE.NONE; + this.fReadOnly = (type == MemoryPDP10.TYPE.ROM); + this.dbg = null; + this.readBits = this.readBitsDirect = this.readNone; + this.readWord = this.readWordDirect = this.readWordDefault; + this.writeBits = this.writeBitsDirect = this.writeNone; + this.writeWord = this.writeWordDirect = this.writeWordDefault; + this.cReadBreakpoints = this.cWriteBreakpoints = 0; + this.copyBreakpoints(); // initialize the block's Debugger info; the caller will reinitialize + + /* + * TODO: Study the impact of dirty block tracking. The original purposes were to allow saveMemory() + * to save only dirty blocks, and to enable the Video component to quickly detect changes to the video buffer. + * + * However, a quick test with dirty block tracking disabled didn't yield a noticeable improvement in performance, + * so I think the overhead of our block-based architecture is swamping the impact of these micro-updates. + */ + this.fDirty = this.fDirtyEver = false; + + /* + * For empty memory blocks, all we need to do is ensure all access functions are mapped to "none" handlers. + */ + if (!this.size) { + this.setAccess(); + return; + } + + /* + * This is the normal case: allocate a buffer that provides a word of data per address; + * no controller is required because our default memory access functions (see afnMemory) + * know how to deal with this simple 1-1 mapping of addresses to words. + * + * TODO: Consider initializing the memory array to random (or pseudo-random) values in DEBUG + * mode; pseudo-random might be best, to help make any bugs reproducible. + */ + a = this.aw = new Array(this.size); + for (i = 0; i < a.length; i++) a[i] = 0; + this.setAccess(MemoryPDP10.afnMemory); + } + + /** + * init(addr) + * + * Quick reinitializer when reusing a Memory block. + * + * @this {MemoryPDP10} + * @param {number} addr + */ + init(addr) + { + this.addr = addr; + } + + /** + * clone(mem, type, dbg) + * + * Converts the current Memory block (this) into a clone of the given Memory block (mem), + * and optionally overrides the current block's type with the specified type. + * + * @this {MemoryPDP10} + * @param {MemoryPDP10} mem + * @param {number} [type] + * @param {DebuggerPDP10} [dbg] + */ + clone(mem, type, dbg) + { + /* + * Original memory block IDs are even; cloned memory block IDs are odd; + * the original ID of the current block is lost, but that's OK, since it was presumably + * produced merely to become a clone. + */ + this.id = mem.id | 0x1; + this.used = mem.used; + this.size = mem.size; + if (type) { + this.type = type; + this.fReadOnly = (type == MemoryPDP10.TYPE.ROM); + } + this.aw = mem.aw; + this.setAccess(MemoryPDP10.afnMemory); + this.copyBreakpoints(dbg, mem); + } + + /** + * save() + * + * This gets the contents of a Memory block as an array of numeric values; used by Bus.saveMemory(), + * which in turn is called by CPUState.save(). + * + * @this {MemoryPDP10} + * @return {Array.|null} + */ + save() + { + return this.aw; + } + + /** + * restore(aw) + * + * This restores the contents of a Memory block from an array of numeric values; used by Bus.restoreMemory(), + * which is called by CPUState.restore(), after all other components have been restored and thus all Memory + * blocks have been allocated by their respective components. + * + * @this {MemoryPDP10} + * @param {Array.|null} aw + * @return {boolean} true if successful, false if block size mismatch + */ + restore(aw) + { + if (aw && this.size == aw.length) { + this.aw = aw; + this.fDirty = true; + return true; + } + return false; + } + + /** + * zero(off, len, pattern) + * + * @this {MemoryPDP10} + * @param {number} [off] (optional starting word offset within block) + * @param {number} [len] (optional maximum number of words; default is the entire block) + * @param {number} [pattern] + */ + zero(off, len, pattern) + { + var i; + off = off || 0; + pattern = Int36.validate(pattern || 0); + /* + * NOTE: If len happens to be larger than the block, that's OK, because we also bounds-check the index. + */ + if (len === undefined) len = this.size; + Component.assert(off >= 0 && off < this.size); + for (i = off; len-- && i < this.size; i++) this.writeWordDirect(off, pattern, this.addr + off); + } + + /** + * setAccess(afn, fDirect) + * + * The afn parameter should be a 4-entry function table containing two bits handlers and + * two word handlers. See the static afnMemory table for an example. + * + * If no function table is specified, a default is selected based on the Memory type; + * similarly, any undefined entries in the table are filled with default handlers that fall + * back to the bits handlers, and if one or both bits handlers are undefined, they default + * to handlers that simply ignore the access. + * + * fDirect indicates that both the default AND the direct handlers should be updated. Direct + * handlers normally match the default handlers, except when "checked" handlers are installed; + * this allows "checked" handlers to know where to dispatch the call after performing checks. + * Examples of checks are read/write breakpoints, but it's really up to the Debugger to decide + * what the check consists of. + * + * @this {MemoryPDP10} + * @param {Array.} [afn] function table + * @param {boolean} [fDirect] (true to update direct access functions as well; default is true) + */ + setAccess(afn, fDirect) + { + if (!afn) { + Component.assert(this.type == MemoryPDP10.TYPE.NONE); + afn = MemoryPDP10.afnNone; + } + this.setReadAccess(afn, fDirect); + this.setWriteAccess(afn, fDirect); + } + + /** + * setReadAccess(afn, fDirect) + * + * @this {MemoryPDP10} + * @param {Array.} afn + * @param {boolean} [fDirect] + */ + setReadAccess(afn, fDirect) + { + if (!fDirect || !this.cReadBreakpoints) { + this.readBits = afn[0] || this.readNone; + this.readWord = afn[2] || this.readWordDefault; + } + if (fDirect || fDirect === undefined) { + this.readBitsDirect = afn[0] || this.readNone; + this.readWordDirect = afn[2] || this.readWordDefault; + } + } + + /** + * setWriteAccess(afn, fDirect) + * + * @this {MemoryPDP10} + * @param {Array.} afn + * @param {boolean} [fDirect] + */ + setWriteAccess(afn, fDirect) + { + if (!fDirect || !this.cWriteBreakpoints) { + this.writeBits = !this.fReadOnly && afn[1] || this.writeNone; + this.writeWord = !this.fReadOnly && afn[3] || this.writeWordDefault; + } + if (fDirect || fDirect === undefined) { + this.writeBitsDirect = afn[1] || this.writeNone; + this.writeWordDirect = afn[3] || this.writeWordDefault; + } + } + + /** + * resetReadAccess() + * + * @this {MemoryPDP10} + */ + resetReadAccess() + { + this.readBits = this.readBitsDirect; + this.readWord = this.readWordDirect; + } + + /** + * resetWriteAccess() + * + * @this {MemoryPDP10} + */ + resetWriteAccess() + { + this.writeBits = this.fReadOnly? this.writeNone : this.writeBitsDirect; + this.writeWord = this.fReadOnly? this.writeWordDefault : this.writeWordDirect; + } + + /** + * printAddr(sMessage) + * + * @this {MemoryPDP10} + * @param {string} sMessage + */ + printAddr(sMessage) + { + if (DEBUG && this.dbg && this.dbg.messageEnabled(MessagesPDP10.MEMORY)) { + this.dbg.printMessage(sMessage + ' ' + (this.addr != null? ('@' + this.dbg.toStrBase(this.addr)) : '#' + this.id), true); + } + } + + /** + * addBreakpoint(off, fWrite) + * + * @this {MemoryPDP10} + * @param {number} off + * @param {boolean} fWrite + */ + addBreakpoint(off, fWrite) + { + if (!fWrite) { + if (this.cReadBreakpoints++ === 0) { + this.setReadAccess(MemoryPDP10.afnChecked, false); + } + if (DEBUG) this.printAddr("read breakpoint added to memory block"); + } + else { + if (this.cWriteBreakpoints++ === 0) { + this.setWriteAccess(MemoryPDP10.afnChecked, false); + } + if (DEBUG) this.printAddr("write breakpoint added to memory block"); + } + } + + /** + * removeBreakpoint(off, fWrite) + * + * @this {MemoryPDP10} + * @param {number} off + * @param {boolean} fWrite + */ + removeBreakpoint(off, fWrite) + { + if (!fWrite) { + if (--this.cReadBreakpoints === 0) { + this.resetReadAccess(); + if (DEBUG) this.printAddr("all read breakpoints removed from memory block"); + } + Component.assert(this.cReadBreakpoints >= 0); + } + else { + if (--this.cWriteBreakpoints === 0) { + this.resetWriteAccess(); + if (DEBUG) this.printAddr("all write breakpoints removed from memory block"); + } + Component.assert(this.cWriteBreakpoints >= 0); + } + } + + /** + * copyBreakpoints(dbg, mem) + * + * @this {MemoryPDP10} + * @param {DebuggerPDP10} [dbg] + * @param {MemoryPDP10} [mem] (outgoing MemoryPDP10 block to copy breakpoints from, if any) + */ + copyBreakpoints(dbg, mem) + { + this.dbg = dbg; + this.cReadBreakpoints = this.cWriteBreakpoints = 0; + if (mem) { + if ((this.cReadBreakpoints = mem.cReadBreakpoints)) { + this.setReadAccess(MemoryPDP10.afnChecked, false); + } + if ((this.cWriteBreakpoints = mem.cWriteBreakpoints)) { + this.setWriteAccess(MemoryPDP10.afnChecked, false); + } + } + } + + /** + * readNone(off) + * + * @this {MemoryPDP10} + * @param {number} off + * @param {number} addr + * @return {number} + */ + readNone(off, addr) + { + if (DEBUGGER && this.dbg && this.dbg.messageEnabled(MessagesPDP10.MEMORY) /* && !off */) { + this.dbg.printMessage("attempt to read invalid address " + this.dbg.toStrBase(addr), true); + } + return 0; + } + + /** + * writeNone(v, off, addr) + * + * @this {MemoryPDP10} + * @param {number} v + * @param {number} off + * @param {number} addr + */ + writeNone(v, off, addr) + { + if (DEBUGGER && this.dbg && this.dbg.messageEnabled(MessagesPDP10.MEMORY) /* && !off */) { + this.dbg.printMessage("attempt to write " + this.dbg.toStrBase(v) + " to invalid addresses " + this.dbg.toStrBase(addr), true); + } + } + + /** + * readWordDefault(off, addr) + * + * @this {MemoryPDP10} + * @param {number} off + * @param {number} addr + * @return {number} + */ + readWordDefault(off, addr) + { + return this.readWord(off, addr); + } + + /** + * writeWordDefault(w, off, addr) + * + * @this {MemoryPDP10} + * @param {number} w + * @param {number} off + * @param {number} addr + */ + writeWordDefault(w, off, addr) + { + this.writeWord(w, off, addr); + } + + /** + * readBitsMemory(offBits, lenBits, off, addr) + * + * @this {MemoryPDP10} + * @param {number} offBits (the bit position of the right-most bit of the result, using modern bit numbering) + * @param {number} lenBits + * @param {number} off + * @param {number} addr + * @return {number} + */ + readBitsMemory(offBits, lenBits, off, addr) + { + var w = this.aw[off]; + if (offBits + lenBits <= 32) { + w = (w >> offBits) & ((1 << lenBits) - 1); + } else { + w = Math.trunc(w / Math.pow(2, offBits)) % Math.pow(2, lenBits); + } + return w; + } + + /** + * readWordMemory(off, addr) + * + * @this {MemoryPDP10} + * @param {number} off + * @param {number} addr + * @return {number} + */ + readWordMemory(off, addr) + { + return this.aw[off]; + } + + /** + * writeBitsMemory(offBits, lenBits, bits, off, addr) + * + * @this {MemoryPDP10} + * @param {number} offBits (the bit position of the right-most bit of the result, using modern bit numbering) + * @param {number} lenBits + * @param {number} bits (only the right-most lenBits of bits are used, so this value doesn't need to be pre-masked) + * @param {number} off + * @param {number} addr + */ + writeBitsMemory(offBits, lenBits, bits, off, addr) + { + var w = this.aw[off]; + if (offBits + lenBits <= 32) { + var bitsMask = ((1 << lenBits) - 1) << offBits; + w = (w & ~bitsMask) | ((bits << offBits) & bitsMask); + } else { + var shiftBits = Math.pow(2, offBits); + bits %= Math.pow(2, lenBits); + var v = (w % Math.pow(2, offBits + lenBits)); + w = (w - v) + (bits * shiftBits) + (v % shiftBits); + } + if (this.aw[off] != w) { + this.aw[off] = w; + this.fDirty = true; + } + } + + /** + * writeWordMemory(w, off, addr) + * + * @this {MemoryPDP10} + * @param {number} w + * @param {number} off + * @param {number} addr + */ + writeWordMemory(w, off, addr) + { + this.aw[off] = w; + this.fDirty = true; + } + + /** + * readBitsChecked(offBits, lenBits, off, addr) + * + * @this {MemoryPDP10} + * @param {number} offBits (the bit position of the right-most bit of the result, using modern bit numbering) + * @param {number} lenBits + * @param {number} off + * @param {number} addr + * @return {number} + */ + readBitsChecked(offBits, lenBits, off, addr) + { + if (DEBUGGER && this.dbg && this.addr != null) { + this.dbg.checkMemoryRead(this.addr + off); + } + return this.readBitsDirect(offBits, lenBits, off, addr); + } + + /** + * readWordChecked(off, addr) + * + * @this {MemoryPDP10} + * @param {number} off + * @param {number} addr + * @return {number} + */ + readWordChecked(off, addr) + { + if (DEBUGGER && this.dbg && this.addr != null) { + this.dbg.checkMemoryRead(this.addr + off, 2); + } + return this.readWordDirect(off, addr); + } + + /** + * writeBitsChecked(offBits, lenBits, bits, off, addr) + * + * @this {MemoryPDP10} + * @param {number} offBits (the bit position of the right-most bit of the result, using modern bit numbering) + * @param {number} lenBits + * @param {number} bits + * @param {number} off + * @param {number} addr + */ + writeBitsChecked(offBits, lenBits, bits, off, addr) + { + if (DEBUGGER && this.dbg && this.addr != null) { + this.dbg.checkMemoryWrite(this.addr + off); + } + if (this.fReadOnly) this.writeNone(bits, off, addr); else this.writeBitsDirect(offBits, lenBits, bits, off, addr); + } + + /** + * writeWordChecked(w, off, addr) + * + * @this {MemoryPDP10} + * @param {number} w + * @param {number} off + * @param {number} addr + */ + writeWordChecked(w, off, addr) + { + if (DEBUGGER && this.dbg && this.addr != null) { + this.dbg.checkMemoryWrite(this.addr + off, 2) + } + if (this.fReadOnly) this.writeNone(w, off, addr); else this.writeWordDirect(w, off, addr); + } +} + +/* + * Basic memory types + * + * RAM is the most conventional memory type, providing full read/write capability. ROM is equally + * conventional, except that the fReadOnly property is set. ROM can be written using the Bus setWordDirect() + * interface (which in turn uses the Memory writeWordDirect() interface), allowing the ROM component to + * initialize its own memory. + */ +MemoryPDP10.TYPE = { + NONE: 0, + RAM: 1, + ROM: 2 +}; +MemoryPDP10.TYPE_COLORS = ["black", "blue", "green"]; +MemoryPDP10.TYPE_NAMES = ["NONE", "RAM", "ROM"]; + +/* + * Last used block ID (used for debugging only) + */ +MemoryPDP10.idBlock = 0; + +/* + * This is the effective definition of afnNone, but we need not fully define it, because setAccess() + * uses these defaults when any of the 4 handlers (ie, 2 bits handlers and 2 word handlers) are undefined. + * +MemoryPDP10.afnNone = [ + MemoryPDP10.prototype.readNone, + MemoryPDP10.prototype.writeNone, + MemoryPDP10.prototype.readWordDefault, + MemoryPDP10.prototype.writeWordDefault +]; + */ +MemoryPDP10.afnNone = []; + +MemoryPDP10.afnMemory = [ + MemoryPDP10.prototype.readBitsMemory, + MemoryPDP10.prototype.writeBitsMemory, + MemoryPDP10.prototype.readWordMemory, + MemoryPDP10.prototype.writeWordMemory +]; + +MemoryPDP10.afnChecked = [ + MemoryPDP10.prototype.readBitsChecked, + MemoryPDP10.prototype.writeBitsChecked, + MemoryPDP10.prototype.readWordChecked, + MemoryPDP10.prototype.writeWordChecked +]; + +if (NODE) module.exports = MemoryPDP10; diff --git a/modules/pdp10/lib/messages.js b/modules/pdp10/lib/messages.js new file mode 100644 index 000000000..297d6d4e4 --- /dev/null +++ b/modules/pdp10/lib/messages.js @@ -0,0 +1,104 @@ +/** + * @fileoverview Defines PDP-10 message categories. + * @author Jeff Parsons + * @copyright © Jeff Parsons 2012-2017 + * + * This file is part of PCjs, a computer emulation software project at . + * + * PCjs is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCjs. If not, + * see . + * + * You are required to include the above copyright notice in every modified copy of this work + * and to display that copyright notice when the software starts running; see COPYRIGHT in + * . + * + * Some PCjs files also attempt to load external resource files, such as character-image files, + * ROM files, and disk image files. Those external resource files are not considered part of PCjs + * for purposes of the GNU General Public License, and the author does not claim any copyright + * as to their contents. + */ + +"use strict"; + +var MessagesPDP10 = { + CPU: 0x00000001, + TRAP: 0x00000002, + FAULT: 0x00000004, + INT: 0x00000008, + BUS: 0x00000010, + MEMORY: 0x00000020, + MMU: 0x00000040, + ROM: 0x00000080, + DEVICE: 0x00000100, + PANEL: 0x00000200, + KEYBOARD: 0x00000400, + KEYS: 0x00000800, + PAPER: 0x00001000, + READ: 0x00004000, + WRITE: 0x00008000, + SERIAL: 0x00100000, + TIMER: 0x00200000, + SPEAKER: 0x01000000, + COMPUTER: 0x02000000, + LOG: 0x10000000, + WARN: 0x20000000, + BUFFER: 0x40000000, + HALT: 0x80000000|0 +}; + +/* + * Message categories supported by the messageEnabled() function and other assorted message + * functions. Each category has a corresponding bit value that can be combined (ie, OR'ed) as + * needed. The Debugger's message command ("m") is used to turn message categories on and off, + * like so: + * + * m port on + * m port off + * ... + * + * NOTE: The order of these categories can be rearranged, alphabetized, etc, as desired; just be + * aware that changing the bit values could break saved Debugger states (not a huge concern, just + * something to be aware of). + */ +MessagesPDP10.CATEGORIES = { + "cpu": MessagesPDP10.CPU, + "trap": MessagesPDP10.TRAP, + "fault": MessagesPDP10.FAULT, + "int": MessagesPDP10.INT, + "bus": MessagesPDP10.BUS, + "memory": MessagesPDP10.MEMORY, + "mmu": MessagesPDP10.MMU, + "rom": MessagesPDP10.ROM, + "device": MessagesPDP10.DEVICE, + "panel": MessagesPDP10.PANEL, + "keyboard": MessagesPDP10.KEYBOARD, // "kbd" is also allowed as shorthand for "keyboard"; see doMessages() + "key": MessagesPDP10.KEYS, // using "key" instead of "keys", since the latter is a method on JavasScript objects + "paper": MessagesPDP10.PAPER, + "read": MessagesPDP10.READ, + "write": MessagesPDP10.WRITE, + "serial": MessagesPDP10.SERIAL, + "timer": MessagesPDP10.TIMER, + "speaker": MessagesPDP10.SPEAKER, + "computer": MessagesPDP10.COMPUTER, + "log": MessagesPDP10.LOG, + "warn": MessagesPDP10.WARN, + /* + * Now we turn to message actions rather than message types; for example, setting "halt" + * on or off doesn't enable "halt" messages, but rather halts the CPU on any message above. + * + * Similarly, "m buffer on" turns on message buffering, deferring the display of all messages + * until "m buffer off" is issued. + */ + "buffer": MessagesPDP10.BUFFER, + "halt": MessagesPDP10.HALT +}; + +if (NODE) module.exports = MessagesPDP10; diff --git a/modules/pdp10/lib/nodebugger.js b/modules/pdp10/lib/nodebugger.js new file mode 100644 index 000000000..e4c83aa08 --- /dev/null +++ b/modules/pdp10/lib/nodebugger.js @@ -0,0 +1,43 @@ +/** + * @fileoverview Compile-time definitions for Debugger-less configurations. + * @author Jeff Parsons + * @copyright © Jeff Parsons 2012-2017 + * + * This file is part of PCjs, a computer emulation software project at . + * + * PCjs is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCjs. If not, + * see . + * + * You are required to include the above copyright notice in every modified copy of this work + * and to display that copyright notice when the software starts running; see COPYRIGHT in + * . + * + * Some PCjs files also attempt to load external resource files, such as character-image files, + * ROM files, and disk image files. Those external resource files are not considered part of PCjs + * for purposes of the GNU General Public License, and the author does not claim any copyright + * as to their contents. + */ + +"use strict"; + +/* + * WARNING: DEBUGGER needs to accurately reflect whether or not the Debugger component is (or will be) loaded. + * In the compiled case, we rely on the Closure Compiler to override DEBUGGER as appropriate. When it's *false*, + * nearly all of debugger.js will be conditionally removed by the compiler, reducing it to little more than a + * "type skeleton", which also solves some type-related warnings we would otherwise have if we tried to remove + * debugger.js from the compilation process altogether. + * + * However, when we're in "development mode" and running uncompiled code in debugger-less configurations, + * I would still like to skip loading debugger.js altogether. To do that, we must arrange for this additional file, + * nodebugger.js, to be loaded immediately after defines.js, *explicitly* overriding the previously defined value + * of DEBUGGER with *false*. + */ +DEBUGGER = false; diff --git a/modules/pdp10/lib/panel.js b/modules/pdp10/lib/panel.js new file mode 100644 index 000000000..538c70c75 --- /dev/null +++ b/modules/pdp10/lib/panel.js @@ -0,0 +1,1266 @@ +/** + * @fileoverview Implements the PDP-10 Panel component. + * @author Jeff Parsons + * @copyright © Jeff Parsons 2012-2017 + * + * This file is part of PCjs, a computer emulation software project at . + * + * PCjs is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCjs. If not, + * see . + * + * You are required to include the above copyright notice in every modified copy of this work + * and to display that copyright notice when the software starts running; see COPYRIGHT in + * . + * + * Some PCjs files also attempt to load external resource files, such as character-image files, + * ROM files, and disk image files. Those external resource files are not considered part of PCjs + * for purposes of the GNU General Public License, and the author does not claim any copyright + * as to their contents. + */ + +"use strict"; + +if (NODE) { + var Str = require("../../shared/lib/strlib"); + var Web = require("../../shared/lib/weblib"); + var Component = require("../../shared/lib/component"); + var State = require("../../shared/lib/state"); + var PDP10 = require("./defines"); + var BusPDP10 = require("./bus"); + var MessagesPDP10 = require("./messages"); +} + +/** + * Since the Closure Compiler treats ES6 classes as @struct rather than @dict by default, + * it deters us from defining named properties on our components; eg: + * + * this['exports'] = {...} + * + * results in an error: + * + * Cannot do '[]' access on a struct + * + * So, in order to define 'exports', we must override the @struct assumption by annotating + * the class as @unrestricted (or @dict). Note that this must be done both here and in the + * Component class, because otherwise the Compiler won't allow us to *reference* the named + * property either. + * + * TODO: Consider marking ALL our classes unrestricted, because otherwise it forces us to + * define every single property the class uses in its constructor, which results in a fair + * bit of redundant initialization, since many properties aren't (and don't need to be) fully + * initialized until the appropriate init(), reset(), restore(), etc. function is called. + * + * The upside, however, may be that since the structure of the class is completely defined by + * the constructor, JavaScript engines may be able to optimize and run more efficiently. + * + * @unrestricted + */ +class PanelPDP10 extends Component { + /** + * PanelPDP10(parmsPanel) + * + * The PanelPDP10 component has no required (parmsPanel) properties. + * + * @param {Object} parmsPanel + * @param {boolean} fBindings (true if panel may have bindings, otherwise not) + */ + constructor(parmsPanel, fBindings) + { + super("Panel", parmsPanel, MessagesPDP10.PANEL); + + /* + * If there are any live registers, LEDs, etc, to display, this will provide a count. + * TODO: Add some UI for fDisplayLiveRegs (either an XML property, or a UI checkbox, or both). + */ + this.cLiveRegs = 0; + this.nDisplayCount = 0; + this.nDisplayLimit = 60; + this.fDisplayLiveRegs = true; + this.fBindings = fBindings; + + /* + * regSwitches contains the Front Panel (aka Console) SWITCH register, which is also available + * as a read-only register at 177570 (but only the low 16 bits). regDisplay contains the DISPLAY + * register, a write-only register at the same address. + * + * regAddr is an internal register containing the contents of the Front Panel's ADDRESS display, + * and regData corresponds to the DATA display. They are updated by updateAddr() and updateData(), + * which in turn take care of calling updateLEDArray(). + * + * The state of ALL switches is maintained in this.switches, and likewise all LED states are + * maintained in this.leds, but for convenience, we also mirror some of those states in dedicated + * variables (eg, regSwitches for the SWITCH register, fLEDTest for the 'TEST' switch, etc). + */ + this.regDisplay = 0; + this.regSwitches = 0; + this.regAddr = this.regData = 0; + this.ledAddr = this.ledData = -1; + + /* + * The panel hardware has the following additional (supported) state; note that there are several + * settings on a real Front Panel that we don't support (eg, stepping one cycle vs. one instruction). + * + * While my initial intent is to eventually support all the ADDRSEL switch settings, I probably + * won't bother with any DATASEL switch settings; instead, I will automatically display the DISPLAY + * register (regDisplay) [the equivalent of selecting 'DISPLAY REGISTER'] except when data is being + * examined or deposited [the equivalent of selecting 'DATA PATHS']. + */ + this.fLEDTest = false; // LED (lamp) test in progress + this.fExamine = false; // true if the previously pressed switch was the 'EXAM' switch + this.fDeposit = false; // true if the previously pressed switch was the 'DEP' switch + this.nAddrSel = PanelPDP10.ADDRSEL.CONS_PHY; + + /* + * Every LED has a simple numeric value, assigned when setBinding() is called: + * + * zero if "off", non-zero if "on" + * + * initBus() will call displayLEDs() to ensure that every LED is set to its initial value. + */ + this.leds = {}; + + /* + * Every switch has an array associated with it: + * + * [0]: initial value of switch (0 if "down", 1 if "up") + * [1]: current value of switch + * [2]: true if the switch is momentary, false if not + * [3]: true if the switch is currently pressed, false if released + * [4]: optional handler to call whenever the switch is pressed or released + * [5]: optional switch index (used with CNSW switches 'S0' through 'S21') + * + * initBus() will call displaySwitches() to ensure that every switch is the position represented below. + * + * NOTE: Not all switches have the same "process" criteria. For example, 'TEST' will perform a LED test + * when it is momentarily pressed "up", whereas 'LOAD [ADRS]' will load the ADDRESS register from the + * SWITCH register when it is momentarily pressed "down". + * + * This means that processLEDTest(value) must act when value == 1 ("up"), whereas processLoadAddr(value) + * must act when value == 0 ("down"). You can infer all this from the table below, because the initial value + * of any momentary switch is its "inactive" value, so the opposite is its "active" value. + */ + this.switches = { + 'START': [1, 1, true, false, this.processStart], + 'STEP': [1, 1, false, false, this.processStep], + 'ENABLE': [1, 1, false, false, this.processEnable], + 'CONT': [1, 1, true, false, this.processContinue], + 'DEP': [0, 0, true, false, this.processDeposit], + 'EXAM': [1, 1, true, false, this.processExamine], + 'LOAD': [1, 1, true, false, this.processLoadAddr], + 'TEST': [0, 0, true, false, this.processLEDTest] + }; + for (var i = 0; i < 22; i++) { + this.switches['S'+i] = [0, 0, false, false, this.processSRSwitch, i]; + } + + /** @type {ComputerPDP10} */ + this.cmp = null; + + /** @type {BusPDP10} */ + this.bus = null; + + /** @type {CPUStatePDP10} */ + this.cpu = null; + + /** @type {DebuggerPDP10} */ + this.dbg = null; + + /* + * The 'hold' and 'toggle' exports, which map to holdSwitch() and toggleSwitch(), both press and release + * the specified switch, but processCommands() considers a 'hold' function to be asynchronous, which means + * that holdSwitch() will be passed a callback function that can be used to implement a delay between the + * press and the release, whereas toggleSwitch() will not. + * + * holdSwitch() only makes sense for momentary switches (eg, 'TEST'), where a visual delay might be nice. + * If the switch isn't momentary, or no delay is desired, then use toggleSwitch(); it will be more efficient. + * + * Finally, for switches that are toggles (eg, 'ENABLE'), you can use setSwitch() to set it to a specific + * state: zero for "off" and non-zero for "on". setSwitch() also supports meta-switches like "SR", using + * the entire value to set a series of switches at once; the value is assumed to be octal unless overridden + * by a prefix (eg, "0x") or suffix (eg, "."). + */ + this['exports'] = { + 'hold': this.holdSwitch, + 'toggle': this.toggleSwitch, + 'reset': this.resetSwitches, + 'set': this.setSwitch + }; + + this.setReady(); + } + + /** + * getAR() + * + * @this {PanelPDP10} + * @return {number} (current ADDRESS register) + */ + getAR() + { + return this.regAddr; + } + + /** + * setAR(value) + * + * @this {PanelPDP10} + * @param {number} value (new ADDRESS register) + */ + setAR(value) + { + this.updateAddr(this.regAddr = value); + } + + /** + * getDR() + * + * @this {PanelPDP10} + * @return {number} (current DISPLAY register) + */ + getDR() + { + return this.regDisplay; + } + + /** + * setDR(value) + * + * @this {PanelPDP10} + * @param {number} value (new DISPLAY register) + * @return {number} + */ + setDR(value) + { + return this.updateData(this.regDisplay = value); + } + + /** + * getSR() + * + * @this {PanelPDP10} + * @return {number} (current SWITCH register) + */ + getSR() + { + return this.regSwitches; + } + + /** + * setSR(value) + * + * @this {PanelPDP10} + * @param {number} value (new SWITCH register) + */ + setSR(value) + { + this.setSRSwitches(value); + } + + /** + * getSwitch(name) + * + * @this {PanelPDP10} + * @param {string} name + * @return {number|undefined} 0 if switch is off ("down"), 1 if on ("up"), or undefined if unrecognized + */ + getSwitch(name) + { + return this.switches[name] && this.switches[name][1]; + } + + /** + * reset(fPowerUp) + * + * NOTE: Since we've registered our handler with the Bus component, we will be called twice whenever + * the entire machine is reset: once when the Computer's reset() handler calls the Bus's reset() handler, + * and again when the Computer's reset() handler calls us directly. Multiple resets should be harmless. + * + * @this {PanelPDP10} + * @param {boolean} [fPowerUp] + */ + reset(fPowerUp) + { + /* + * Simulate a call to our stop() handler, to update the panel's ADDRESS register with the current PC. + */ + this.stop(); + if (fPowerUp) this.setDR(0); + } + + /** + * setBinding(sType, sBinding, control, sValue) + * + * Some panel layouts don't have bindings of their own, and even when they do, there may still be some + * components (eg, the CPU) that prefer to update their own bindings, so we pass along all binding requests + * to the Computer, CPU, Keyboard and Debugger components first. The order shouldn't matter, since any + * component that doesn't recognize the specified binding should simply ignore it. + * + * @this {PanelPDP10} + * @param {string|null} sType is the type of the HTML control (eg, "button", "textarea", "register", "flag", "rled", etc) + * @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "reset") + * @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement) + * @param {string} [sValue] optional data value + * @return {boolean} true if binding was successful, false if unrecognized binding request + */ + setBinding(sType, sBinding, control, sValue) + { + if (this.cmp && this.cmp.setBinding(sType, sBinding, control, sValue)) { + return true; + } + if (this.cpu && this.cpu.setBinding(sType, sBinding, control, sValue)) { + return true; + } + if (DEBUGGER && this.dbg && this.dbg.setBinding(sType, sBinding, control, sValue)) { + return true; + } + + switch (sBinding) { + case 'R0': + case 'R1': + case 'R2': + case 'R3': + case 'R4': + case 'R5': + case 'R6': + case 'R7': + case 'NF': + case 'ZF': + case 'VF': + case 'CF': + case 'PS': + this.bindings[sBinding] = control; + this.cLiveRegs++; + return true; + + default: + /* + * Square ("led") or round ("rled") LEDs are defined in machine XML files like so: + * + * + * + * Only *type* and *binding* attributes are required; if *value* is omitted, the default value is 0 ("off"). + */ + if (sType == "led" || sType == "rled") { + this.bindings[sBinding] = control; + this.leds[sBinding] = sValue? 1 : 0; + this.cLiveRegs++; + return true; + } + /* + * Switches are defined in machine XML files like so: + * + * + * + * Only *type* and *binding* attributes are required; if *value* is omitted, the default value is 0 ("down"). + * + * Currently, there is no XML attribute to indicate whether a switch is "momentary"; only recognized switches + * in our internal table can have that attribute. + */ + if (sType == "switch") { + /* + * Like LEDs, we allow unrecognized switches to be defined as well, but they won't do anything useful, + * since only recognized switches will have handlers that perform the appropriate operations. + */ + if (this.switches[sBinding] === undefined) { + this.switches[sBinding] = [sValue? 1 : 0, sValue? 1 : 0]; + } + this.bindings[sBinding] = control; + var parent = control.parentElement || control; + parent = parent.parentElement || parent; + parent.onmousedown = function(panel, sBinding) { + return function onPressSwitch() { + panel.pressSwitch(sBinding); + }; + }(this, sBinding); + parent.onmouseup = parent.onmouseout = function(panel, sBinding) { + return function onReleaseSwitch() { + panel.releaseSwitch(sBinding); + }; + }(this, sBinding); + parent.ontouchstart = function(panel, sBinding) { + return function onPressSwitch(event) { + panel.pressSwitch(sBinding); + event.preventDefault(); + }; + }(this, sBinding); + parent.ontouchend = function(panel, sBinding) { + return function onReleaseSwitch() { + panel.releaseSwitch(sBinding); + }; + }(this, sBinding); + return true; + } + return super.setBinding(sType, sBinding, control, sValue); + } + } + + /** + * initBus(cmp, bus, cpu, dbg) + * + * @this {PanelPDP10} + * @param {ComputerPDP10} cmp + * @param {BusPDP10} bus + * @param {CPUStatePDP10} cpu + * @param {DebuggerPDP10} dbg + */ + initBus(cmp, bus, cpu, dbg) + { + this.cmp = cmp; + this.bus = bus; + this.cpu = cpu; + this.dbg = dbg; + + this.displayLEDs(); + this.displaySwitches(); + } + + /** + * powerUp(data, fRepower) + * + * @this {PanelPDP10} + * @param {Object|null} data + * @param {boolean} [fRepower] + * @return {boolean} true if successful, false if failure + */ + powerUp(data, fRepower) + { + if (!fRepower) { + /* + * As noted in init(), our powerUp() method gives us a second opportunity to notify any + * components that that might care (eg, CPU, Keyboard, and Debugger) that we have some controls + * (ie, bindings) they might want to use. + */ + if (this.fBindings) PanelPDP10.init(); + + if (!data) { + this.reset(true); + } else { + if (!this.restore(data)) return false; + } + } + return true; + } + + /** + * powerDown(fSave, fShutdown) + * + * @this {PanelPDP10} + * @param {boolean} [fSave] + * @param {boolean} [fShutdown] + * @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure + */ + powerDown(fSave, fShutdown) + { + return fSave? this.save() : true; + } + + /** + * save() + * + * This implements save support for the PanelPDP10 component. + * + * @this {PanelPDP10} + * @return {Object} + */ + save() + { + var state = new State(this); + state.set(0, [ + this.getAR(), + this.getDR(), + this.getSR() + ]); + return state.data(); + } + + /** + * restore(data) + * + * This implements restore support for the PanelPDP10 component. + * + * @this {PanelPDP10} + * @param {Object} data + * @return {boolean} true if successful, false if failure + */ + restore(data) + { + var a = data[0]; + if (a) { + this.setAR(a[0]); + this.setDR(a[1]); + this.setSR(a[2]); + } + return true; + } + + /** + * resetSwitches() + * + * @this {PanelPDP10} + * @return {boolean} + */ + resetSwitches() + { + for (var sBinding in this.switches) { + var sw = this.switches[sBinding]; + sw[1] = sw[0]; + } + this.displaySwitches(); + return true; + } + + /** + * displayLED(sBinding, value) + * + * @this {PanelPDP10} + * @param {string} sBinding + * @param {boolean|number} value (true or non-zero if the LED should be on, false or zero if off) + */ + displayLED(sBinding, value) + { + var control = this.bindings[sBinding]; + if (control) { + /* + * TODO: Add support for user-definable LED colors? + */ + control.style.backgroundColor = (value? "#ff0000" : "#000000"); + } + } + + /** + * displayLEDs(override) + * + * @this {PanelPDP10} + * @param {boolean|number|null} [override] (true turn on all LEDs, false to turn off all LEDs, null or undefined for normal LED activity) + */ + displayLEDs(override) + { + for (var sBinding in this.leds) { + this.displayLED(sBinding, override != null? override : this.leds[sBinding]); + } + } + + /** + * displaySwitch(sBinding, value) + * + * @this {PanelPDP10} + * @param {string} sBinding + * @param {boolean|number} value (true if the switch should be "up" (on), false if "down" (off)) + */ + displaySwitch(sBinding, value) + { + var control = this.bindings[sBinding]; + if (control) { + control.style.marginTop = (value? "0px" : "20px"); + control.style.backgroundColor = (value? "#00ff00" : "#228B22"); + } + } + + /** + * displaySwitches() + * + * @this {PanelPDP10} + */ + displaySwitches() + { + for (var sBinding in this.switches) { + this.displaySwitch(sBinding, this.switches[sBinding][1]); + } + } + + /** + * displayValue(sLabel, nValue, cch) + * + * This is principally for displaying register values, but in reality, it can be used to display any + * numeric value bound to the given label. + * + * @this {PanelPDP10} + * @param {string} sLabel + * @param {number} nValue + * @param {number} [cch] + */ + displayValue(sLabel, nValue, cch) + { + if (this.bindings[sLabel]) { + var sVal; + var nBase = this.dbg && this.dbg.nBase || 8; + nValue = nValue || 0; + if (!this.cpu.isRunning() || this.fDisplayLiveRegs) { + sVal = nBase == 8? Str.toOct(nValue, cch) : Str.toHex(nValue, cch); + } else { + sVal = "--------".substr(0, cch || 4); + } + /* + * TODO: Determine if this test actually avoids any redrawing when a register hasn't changed, and/or if + * we should maintain our own (numeric) cache of displayed register values (to avoid creating these temporary + * string values that will have to garbage-collected), and/or if this is actually slower, and/or if I'm being + * too obsessive. + */ + if (this.bindings[sLabel].textContent != sVal) this.bindings[sLabel].textContent = sVal; + } + } + + /** + * holdSwitch(fnCallback, sBinding, sDelay) + * + * @this {PanelPDP10} + * @param {function()|null} fnCallback + * @param {string} sBinding + * @param {string} [sDelay] + * @return {boolean} false if wait required, true otherwise + */ + holdSwitch(fnCallback, sBinding, sDelay) + { + if (this.pressSwitch(sBinding)) { + if (sDelay) { + var panel = this; + setTimeout(function() { + panel.releaseSwitch(sBinding); + if (fnCallback) fnCallback(); + }, +sDelay); + return false; + } else { + this.releaseSwitch(sBinding); + } + } + return true; + } + + /** + * setSwitch(sBinding, sValue) + * + * @this {PanelPDP10} + * @param {string} sBinding + * @param {string} sValue + * @return {boolean} + */ + setSwitch(sBinding, sValue) + { + if (sBinding == "SR") { + return this.setSRSwitches(Str.parseInt(sValue, 8)) + } + var sw = this.switches[sBinding]; + if (sw) { + sw[1] = +sValue? 1 : 0; + this.displaySwitch(sBinding, sw[1]); + return true; + } + return false; + } + + /** + * toggleSwitch(sBinding) + * + * @this {PanelPDP10} + * @param {string} sBinding + * @return {boolean} + */ + toggleSwitch(sBinding) + { + if (this.pressSwitch(sBinding)) { + this.releaseSwitch(sBinding); + return true; + } + return false; + } + + /** + * pressSwitch(sBinding) + * + * @this {PanelPDP10} + * @param {string} sBinding + * @return {boolean} + */ + pressSwitch(sBinding) + { + var sw = this.switches[sBinding]; + if (sw) { + /* + * Set the new switch value in sw[1] and then immediately display it + */ + this.displaySwitch(sBinding, (sw[1] = 1 - sw[1])); + + /* + * Mark the switch as "pressed" + */ + sw[3] = true; + + /* + * Call the appropriate process handler with the current switch value (sw[1]) + */ + if (sw[4]) sw[4].call(this, sw[1], sw[5]); + + /* + * This helps the next 'DEP' or 'EXAM' press determine if the previous press was the same, + * while also ignoring any intervening 'STEP' presses (see processStep() for why we do that). + */ + if (sBinding != PanelPDP10.SWITCH.STEP) { + this.fDeposit = (sBinding == PanelPDP10.SWITCH.DEP); + this.fExamine = (sBinding == PanelPDP10.SWITCH.EXAM); + } + return true; + } + return false; + } + + /** + * releaseSwitch(sBinding) + * + * @this {PanelPDP10} + * @param {string} sBinding + * @return {boolean} + */ + releaseSwitch(sBinding) + { + /* + * pressSwitch() is simple: flip the switch's current value in sw[1] and marked it "pressed" in sw[3]. + * + * releaseSwitch() is more complicated, because we must handle both mouseUp and mouseOut events. The first time + * we receive EITHER of those events AND the switch is marked momentary (sw[2]) AND the switch is pressed (sw[3]), + * then we must flip the switch back to its original value. + * + * Otherwise, the only thing we have to do is mark the switch as "released" (ie, set sw[3] to false). + */ + var sw = this.switches[sBinding]; + if (sw) { + if (sw[2] && sw[3]) { + /* + * Set the new switch value in sw[1] and then immediately display it + */ + this.displaySwitch(sBinding, (sw[1] = sw[0])); + + /* + * Call the appropriate process handler with the current switch value (sw[1]) + */ + if (sw[4]) sw[4].call(this, sw[1], sw[5]); + } + /* + * Mark the switch as "released" + */ + sw[3] = false; + return true; + } + return false; + } + + /** + * processStart(value, index) + * + * @this {PanelPDP10} + * @param {number} value + * @param {number} [index] + */ + processStart(value, index) + { + if (!value && !this.cpu.isRunning()) { + + this.cpu.setPC(this.regAddr); + + /* + * The PDP-11/70 Handbook goes on to say: "If the system needs to be initialized but execution + * is not wanted, the START switch should be depressed while the HALT/ENABLE switch is in the HALT + * position." + */ + if (this.getSwitch(PanelPDP10.SWITCH.ENABLE)) { + this.cpu.startCPU(); + } + } + } + + /** + * processStep(value, index) + * + * If value == 1 (our initial value), then the 'STEP' switch is set to "S INST" (step one instruction); + * otherwise, it's set to "S BUS CYCLE" (step one bus cycle). + * + * However, since we can't currently support cycle-stepping, I've decided to innovate a little and + * change the meaning of this switch: the normal ("up") position means that successive 'EXAM' and 'DEP' + * operations will first add 2 to the ADDRESS register, while the opposite ("down") position means + * they will first subtract 2. + * + * See processLEDTest() for more of these exciting "innovations". ;-) + * + * @this {PanelPDP10} + * @param {number} value + * @param {number} [index] + */ + processStep(value, index) + { + /* + * There's really nothing for us to do here, because the normal press and release handlers + * already record the state of this switch, so it can be queried as needed, using getSwitch(). + */ + } + + /** + * processEnable(value, index) + * + * If value == 1 (our initial value), then the 'ENABLE'/'HALT' switch is set to 'ENABLE', otherwise 'HALT'. + * + * @this {PanelPDP10} + * @param {number} value + * @param {number} [index] + */ + processEnable(value, index) + { + /* + * The "down" (0) position is 'HALT', which stops the CPU; however, the "up" (1) position ('ENABLE') + * does NOT start the CPU. You must press 'CONT' to continue execution, which will either continue for + * one instruction if this switch to set to 'HALT' or indefinitely if it is set to 'ENABLE'. + */ + if (!value) { + this.cpu.stopCPU(); + } + } + + /** + * processContinue(value, index) + * + * @this {PanelPDP10} + * @param {number} value + * @param {number} [index] + */ + processContinue(value, index) + { + if (!value && !this.cpu.isRunning()) { + /* + * TODO: Technically, we're also supposed to check the 'STEP' switch to determine if we should + * step one instruction or just one cycle, but we don't currently have the ability to do the latter. + */ + if (!this.getSwitch(PanelPDP10.SWITCH.ENABLE)) { + /* + * Using the Debugger's stepCPU() function is more convenient, and has the pleasant side-effect + * of updating the debugger's display; however, not all machines with a Front Panel will necessarily + * also have the Debugger loaded. + */ + var dbg = this.dbg; + if (dbg && !dbg.isBusy(true)) { + dbg.setBusy(true); + dbg.stepCPU(0, null); + dbg.setBusy(false); + } + else { + /* + * For this tiny single-instruction burst, mimic what runCPU() does. + */ + try { + var nCyclesStep = this.cpu.stepCPU(1); + if (nCyclesStep > 0) { + this.cpu.updateTimers(nCyclesStep); + this.cpu.addCycles(nCyclesStep, true); + this.cpu.updateChecksum(nCyclesStep); + } + } + catch(exception) { + /* + * We assume that any numeric exception was explicitly thrown by the CPU to interrupt the + * current instruction. For all other exceptions, we attempt a stack dump. + */ + if (typeof exception != "number") { + var e = exception; + this.cpu.setError(e.stack || e.message); + } + } + } + + /* + * Simulate a call to our stop() handler, to update the panel's ADDRESS register with the new PC. + */ + this.stop(); + + /* + * Going through the normal channels (ie, the Computer's updateDisplays() interface) ensures that + * ALL updateDisplay() handlers will be called, including ours. + * + * NOTE: If we used the Debugger's stepCPU() function, then that includes a call to updateDisplay(); + * unfortunately, it will have happened BEFORE we called stop() to update the ADDRESS register, so + * we still need to call it again. + */ + if (this.cmp) this.cmp.updateDisplays(); + } + else { + this.cpu.startCPU(); + } + } + } + + /** + * processDeposit(value, index) + * + * @this {PanelPDP10} + * @param {number} value + * @param {number} [index] + */ + processDeposit(value, index) + { + if (value && !this.cpu.isRunning()) { + if (this.fDeposit) this.advanceAddr(); + /* + * This used to be updateData(), but that only updates regData, whereas setDR() updates both regData and regDisplay, + * and for these kinds of explicit Front Panel operations, I'm assuming the values should be synced. + */ + var w = this.setDR(this.regSwitches); + + if (this.nAddrSel == PanelPDP10.ADDRSEL.CONS_PHY) { + /* + * TODO: Determine if this needs to take the UNIBUS map into consideration. + */ + this.bus.setWordDirect(this.regAddr, w); + } else { + /* + * TODO: This code is obviously incomplete, since it doesn't take into account the precise ADDRSEL mode. + */ + this.cpu.writeWord(this.regAddr, w); + } + } + } + + /** + * processExamine(value, index) + * + * @this {PanelPDP10} + * @param {number} value + * @param {number} [index] + */ + processExamine(value, index) + { + if (!value && !this.cpu.isRunning()) { + var w; + if (this.fExamine) this.advanceAddr(); + if (this.nAddrSel == PanelPDP10.ADDRSEL.CONS_PHY) { + /* + * TODO: Determine if this needs to take the UNIBUS map into consideration. + */ + w = this.bus.getWordDirect(this.regAddr); + } else { + /* + * TODO: This code is obviously incomplete, since it doesn't take into account the precise ADDRSEL mode. + */ + w = this.cpu.readWord(this.regAddr); + } + /* + * This used to be updateData(), but that only updates regData, whereas setDR() updates both regData and regDisplay, + * and for these kinds of explicit Front Panel operations, I'm assuming the values should be synced. + */ + this.setDR(w); + } + } + + /** + * processLoadAddr(value, index) + * + * @this {PanelPDP10} + * @param {number} value + * @param {number} [index] + */ + processLoadAddr(value, index) + { + if (!value && !this.cpu.isRunning()) { + this.updateAddr(this.regSwitches); + } + } + + /** + * processLEDTest(value, index) + * + * @this {PanelPDP10} + * @param {number} value + * @param {number} [index] + */ + processLEDTest(value, index) + { + if (value) { + this.fLEDTest = true; + this.displayLEDs(true); + } else { + this.fLEDTest = false; + this.displayLEDs(); + /* + * This is another one of my "innovations": when you're done testing the LEDs, all the switches reset as well. + */ + this.setSRSwitches(0); + } + } + + /** + * processSRSwitch(value, index) + * + * @this {PanelPDP10} + * @param {number} value (normally 0 or 1, but we only depend on it being zero or non-zero) + * @param {number} index + */ + processSRSwitch(value, index) + { + if (value) { + this.regSwitches |= 1 << index; + } else { + this.regSwitches &= ~(1 << index); + } + } + + /** + * advanceAddr() + * + * This should also take care of the following Front Panel behaviors when the accessing the general-purpose + * registers: + * + * 1) ADDRESS display incremented by 1 (instead of 2) + * 2) The STEP after the last register is 177700, such that the addresses are looped + * + * A third behavior is NOT emulated: preventing the ADDRESS from stepping to the first General Register (177700) + * from 177676. + * + * @this {PanelPDP10} + * @return {number} + */ + advanceAddr() + { + var inc = 1; + var mask = this.bus.nBusMask; + if (!this.getSwitch(PanelPDP10.SWITCH.STEP)) inc = -inc; + return this.updateAddr((this.regAddr & ~mask) | ((this.regAddr + inc) & mask)); + } + + /** + * updateAddr(value) + * + * @this {PanelPDP10} + * @param {number} value + * @return {number} + */ + updateAddr(value) + { + this.regAddr = value & this.bus.nBusMask; + if (this.ledAddr !== this.regAddr) { + this.ledAddr = this.regAddr; + this.updateLEDArray("A", this.ledAddr, 22); + } + return this.regAddr; + } + + /** + * updateData(value) + * + * @this {PanelPDP10} + * @param {number} value + * @return {number} + */ + updateData(value) + { + this.regData = value % PDP10.DATA_LIMIT; + if (this.ledData !== this.regData) { + this.ledData = this.regData; + this.updateLEDArray("D", this.ledData, 16); + } + return this.regData; + } + + /** + * updateLED(sBinding, value) + * + * @this {PanelPDP10} + * @param {string} sBinding + * @param {number} value + * @return {number} + */ + updateLED(sBinding, value) + { + this.leds[sBinding] = value; + if (!this.fLEDTest) this.displayLED(sBinding, value); + return value; + } + + /** + * updateLEDArray(sPrefix, value, nLEDs) + * + * @this {PanelPDP10} + * @param {string} sPrefix + * @param {number} value + * @param {number} nLEDs + */ + updateLEDArray(sPrefix, value, nLEDs) + { + for (var i = 0; i < nLEDs; i++) { + var sBinding = sPrefix + i; + this.updateLED(sBinding, value & (1 << i)); + } + } + + /** + * setSRSwitches(value) + * + * @this {PanelPDP10} + * @param {number|undefined} value + * @return {boolean} + */ + setSRSwitches(value) + { + this.regSwitches = value | 0; + for (var i = 0; i < 22; i++) { + this.switches['S'+i][1] = (this.regSwitches & (1 << i))? 1 : 0; + } + /* + * This (re)displays ALL switches, not merely the SR switches, but that's OK. + */ + this.displaySwitches(); + return true; + } + + /** + * stop(ms, nCycles) + * + * This is a notification handler, called by the Computer, to inform us the CPU has now stopped. + * + * @this {PanelPDP10} + * @param {number} [ms] + * @param {number} [nCycles] + */ + stop(ms, nCycles) + { + this.updateAddr(this.cpu.getPC()); + } + + /** + * setAddr(value, fActive) + * + * This interface is for passing new addresses to the Front Panel. However, whether or not this will become the + * ADDRESS actually displayed will depend on other settings (see updateStatus() for details). + * + * @this {PanelPDP10} + * @param {number} value + * @param {boolean} [fActive] (true if this should become the "active" ADDRESS regardless of other settings) + */ + setAddr(value, fActive) + { + this.regAddr = value; + } + + /** + * setData(value, fActive) + * + * This interface is for passing new data to the Front Panel. However, whether or not this will become the + * DATA actually displayed will depend on the Front Panel's DATASEL switch setting, as well as the fActive flag. + * + * @this {PanelPDP10} + * @param {number} value + * @param {boolean} [fActive] (true if this should become the "active" DATA regardless of the DATASEL switch setting) + */ + setData(value, fActive) + { + if (!fActive) { + this.regData = value; + } else { + this.regDisplay = value; + } + } + + /** + * updateDisplay(nUpdate) + * + * Called by the Computer component at intervals to update registers, LEDs, etc. + * + * @this {PanelPDP10} + * @param {number} [nUpdate] (-2 for power on, -1 for forced, > 0 for periodic, 0 or undefined otherwise) + */ + updateDisplay(nUpdate) + { + if (this.cLiveRegs) { + + var fRunning = this.cpu.isRunning(); + var fWaiting = this.cpu.isWaiting(); + + if (nUpdate < 0 || !fRunning || this.fDisplayLiveRegs) { + + /* + * We arbitrarily separate the display elements into two categories: cheap and expensive. + * + * LEDs are considered cheap, register displays are not. So we'll skip the latter if this + * is a periodic update AND our periodic update counter hasn't reached the periodic update limit. + */ + if (nUpdate <= 0 || (this.nDisplayCount += nUpdate) >= this.nDisplayLimit) { + this.displayValue("PC", this.cpu.getPC()); + this.nDisplayCount = 0; + } + + /* + * Update the ADDRESS and DATA LEDs by selecting the appropriate values. + * + * TODO: There is currently no mechanism for selecting regData over regDisplay; + * we are acting as if the DATASEL switch setting is locked to "DISPLAY REGISTER". + */ + if (nUpdate < -1) { + this.regAddr = this.cpu.getPC(); + } else if (nUpdate > 0 && fRunning && !fWaiting) { + this.regAddr = this.cpu.getLastAddr(); + } + + this.updateAddr(this.regAddr); + this.updateData(this.regDisplay); + } + } + } + + /** + * PanelPDP10.init() + * + * This function operates on every HTML element of class "panel", extracting the + * JSON-encoded parameters for the PanelPDP10 constructor from the element's "data-value" + * attribute, invoking the constructor to create a PanelPDP10 component, and then binding + * any associated HTML controls to the new component. + * + * NOTE: Unlike most other component init() functions, this one is designed to be + * called multiple times: once at load time, so that we can bind our print() + * function to the panel's output control ASAP, and again when the Computer component + * is verifying that all components are ready and invoking their powerUp() functions. + * + * Our powerUp() method gives us a second opportunity to notify any components that + * that might care (eg, CPU, Keyboard, and Debugger) that we have some controls they + * might want to use. + */ + static init() + { + var aePanels = Component.getElementsByClass(document, PDP10.APPCLASS, "panel"); + for (var iPanel=0; iPanel < aePanels.length; iPanel++) { + var ePanel = aePanels[iPanel]; + var parmsPanel = Component.getComponentParms(ePanel); + var panel = Component.getComponentByID(parmsPanel['id']); + if (!panel) panel = new PanelPDP10(parmsPanel, true); + Component.bindComponentControls(panel, ePanel, PDP10.APPCLASS); + } + } +} + +PanelPDP10.ADDRSEL = { + CONS_PHY: 7 // use a physical address to perform console operations (e.g., LOAD ADRS, EXAM, & DEP) +}; + +/* + * To get the current state of a switch; eg:: + * + * this.getSwitch(PanelPDP10.SWITCH.ENABLE) + * + * I haven't filled out this table, primarily it only needs to list switches we actually query + * (eg, non-momentary ones like 'ENABLE' and 'STEP', and 'EXAM' and 'DEP' since they have special + * "step" behavior when pressed more than once in a row). Ditto for the LED table. + */ +PanelPDP10.SWITCH = { + DEP: 'DEP', + ENABLE: 'ENABLE', + EXAM: 'EXAM', + STEP: 'STEP' +}; + +/* + * Initialize every Panel module on the page. + */ +Web.onInit(PanelPDP10.init); + +if (NODE) module.exports = PanelPDP10; diff --git a/modules/pdp10/lib/ram.js b/modules/pdp10/lib/ram.js new file mode 100644 index 000000000..226e9e0a3 --- /dev/null +++ b/modules/pdp10/lib/ram.js @@ -0,0 +1,413 @@ +/** + * @fileoverview Implements the PDP-10 RAM component. + * @author Jeff Parsons + * @copyright © Jeff Parsons 2012-2017 + * + * This file is part of PCjs, a computer emulation software project at . + * + * PCjs is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCjs. If not, + * see . + * + * You are required to include the above copyright notice in every modified copy of this work + * and to display that copyright notice when the software starts running; see COPYRIGHT in + * . + * + * Some PCjs files also attempt to load external resource files, such as character-image files, + * ROM files, and disk image files. Those external resource files are not considered part of PCjs + * for purposes of the GNU General Public License, and the author does not claim any copyright + * as to their contents. + */ + +"use strict"; + +if (NODE) { + var Str = require("../../shared/lib/strlib"); + var Web = require("../../shared/lib/weblib"); + var DumpAPI = require("../../shared/lib/dumpapi"); + var Component = require("../../shared/lib/component"); + var PDP10 = require("./defines"); + var MemoryPDP10 = require("./memory"); + var MessagesPDP10 = require("./messages"); +} + +class RAMPDP10 extends Component { + /** + * RAMPDP10(parmsRAM) + * + * The RAMPDP10 component expects the following (parmsRAM) properties: + * + * addr: starting physical address of RAM (default is 0) + * size: amount of RAM, in bytes (default is 0, which means defer to motherboard switch settings) + * file: name of optional data file to load into RAM (default is "") + * load: optional file load address (overrides any load address specified in the data file; default is null) + * exec: optional file exec address (overrides any exec address specified in the data file; default is null) + * + * NOTE: We make a note of the specified size, but no memory is initially allocated for the RAM until the + * Computer component calls powerUp(). + * + * @param {Object} parmsRAM + */ + constructor(parmsRAM) + { + super("RAM", parmsRAM); + + this.abInit = null; + this.aSymbols = null; + + this.addrRAM = +parmsRAM['addr']; + this.sizeRAM = +parmsRAM['size']; + + this.addrLoad = parmsRAM['load']; + this.addrExec = parmsRAM['exec']; + if (this.addrLoad != null) this.addrLoad = +this.addrLoad; + if (this.addrExec != null) this.addrExec = +this.addrExec; + + this.fInstalled = (!!this.sizeRAM); // 0 is the default value for 'size' when none is specified + this.fAllocated = false; + + this.sFilePath = parmsRAM['file']; + this.sFileName = Str.getBaseName(this.sFilePath); + + if (this.sFilePath) { + var sFileURL = this.sFilePath; + if (DEBUG) this.log('load("' + sFileURL + '")'); + /* + * If the selected data file has a ".json" extension, then we assume it's pre-converted + * JSON-encoded data, so we load it as-is; ditto for ROM files with a ".hex" extension. + * Otherwise, we ask our server-side converter to return the file in a JSON-compatible format. + */ + var sFileExt = Str.getExtension(this.sFileName); + if (sFileExt != DumpAPI.FORMAT.JSON && sFileExt != DumpAPI.FORMAT.HEX) { + sFileURL = Web.getHost() + DumpAPI.ENDPOINT + '?' + DumpAPI.QUERY.FILE + '=' + this.sFilePath + '&' + DumpAPI.QUERY.FORMAT + '=' + DumpAPI.FORMAT.BYTES + '&' + DumpAPI.QUERY.DECIMAL + '=true'; + } + var ram = this; + Web.getResource(sFileURL, null, true, function doneLoad(sURL, sResponse, nErrorCode) { + ram.finishLoad(sURL, sResponse, nErrorCode); + }); + } + } + + /** + * initBus(cmp, bus, cpu, dbg) + * + * @this {RAMPDP10} + * @param {ComputerPDP10} cmp + * @param {BusPDP10} bus + * @param {CPUStatePDP10} cpu + * @param {DebuggerPDP10} dbg + */ + initBus(cmp, bus, cpu, dbg) + { + this.bus = bus; + this.cpu = cpu; + this.dbg = dbg; + this.initRAM(); + } + + /** + * powerUp(data, fRepower) + * + * @this {RAMPDP10} + * @param {Object|null} data + * @param {boolean} [fRepower] + * @return {boolean} true if successful, false if failure + */ + powerUp(data, fRepower) + { + if (this.aSymbols) { + if (this.dbg) { + this.dbg.addSymbols(this.id, this.addrRAM, this.sizeRAM, this.aSymbols); + } + /* + * Our only role in the handling of symbols is to hand them off to the Debugger at our + * first opportunity. Now that we've done that, our copy of the symbols, if any, are toast. + */ + delete this.aSymbols; + } + if (!fRepower) { + /* + * Since we use the Bus to allocate all our memory, memory contents are already restored for us, + * so we don't save any state, and therefore no state should be restored. Just do a reset(). + */ + this.assert(!data); + this.reset(); + } + return true; + } + + /** + * powerDown(fSave, fShutdown) + * + * @this {RAMPDP10} + * @param {boolean} [fSave] + * @param {boolean} [fShutdown] + * @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure + */ + powerDown(fSave, fShutdown) + { + /* + * The Computer powers down the CPU first, at which point CPUState state is saved, + * which includes the Bus state, and since we use the Bus component to allocate all + * our memory, memory contents are already saved for us, so we don't need the usual + * save logic. + */ + return true; + } + + /** + * finishLoad(sURL, sData, nErrorCode) + * + * @this {RAMPDP10} + * @param {string} sURL + * @param {string} sData + * @param {number} nErrorCode (response from server if anything other than 200) + */ + finishLoad(sURL, sData, nErrorCode) + { + if (nErrorCode) { + this.notice("Unable to load RAM resource (error " + nErrorCode + ": " + sURL + ")"); + this.sFilePath = null; + } + else { + Component.addMachineResource(this.idMachine, sURL, sData); + var resource = Web.parseMemoryResource(sURL, sData); + if (resource) { + this.abInit = resource.aBytes; + this.aSymbols = resource.aSymbols; + if (this.addrLoad == null) this.addrLoad = resource.addrLoad; + if (this.addrExec == null) this.addrExec = resource.addrExec; + } else { + this.sFilePath = null; + } + } + this.initRAM(); + } + + /** + * initRAM() + * + * This function is called by both initBus() and finishLoad(), but it cannot copy the initial data into place + * until after initBus() has received the Bus component AND finishLoad() has received the data. When both those + * criteria are satisfied, the component becomes "ready". + * + * @this {RAMPDP10} + */ + initRAM() + { + if (!this.bus) return; + + if (!this.fAllocated && this.sizeRAM) { + if (this.bus.addMemory(this.addrRAM, this.sizeRAM, MemoryPDP10.TYPE.RAM)) { + this.fAllocated = true; + } else { + this.sizeRAM = 0; // don't bother trying again (it just results in redundant error messages) + } + } + if (!this.isReady()) { + if (!this.fAllocated) { + Component.error("No RAM allocated"); + } + else if (this.sFilePath) { + /* + * Too early... + */ + if (!this.abInit || !this.bus) return; + + if (this.loadImage(this.abInit, this.addrLoad, this.addrExec, this.addrRAM)) { + this.status('Loaded image "' + this.sFileName + '"'); + } else { + this.notice('Error loading image "' + this.sFileName + '"'); + } + + /* + * NOTE: We now retain this data, so that reset() can return the RAM to its predefined state. + * + * delete this.abInit; + */ + } + this.setReady(); + } + } + + /** + * reset() + * + * @this {RAMPDP10} + */ + reset() + { + if (this.fAllocated) { + /* + * TODO: Add a configuration parameter for selecting the byte pattern on reset? + * Note that when memory blocks are originally created, they are currently always + * zero-initialized, so this would only affect resets. + */ + this.bus.zeroMemory(this.addrRAM, this.sizeRAM, 0); + if (this.abInit) { + this.loadImage(this.abInit, this.addrLoad, this.addrExec, this.addrRAM, !this.dbg); + } + } + } + + /** + * loadImage(aBytes, addrLoad, addrExec, addrInit, fStart) + * + * If the array contains a PAPER tape image in the "Absolute Format," load it as specified + * by the format; otherwise, load it as-is using the address(es) supplied. + * + * @this {RAMPDP10} + * @param {Array|Uint8Array} aBytes + * @param {number|null} [addrLoad] + * @param {number|null} [addrExec] (this CAN override any starting address INSIDE the image) + * @param {number|null} [addrInit] + * @param {boolean} [fStart] + * @return {boolean} (true if loaded, false if not) + */ + loadImage(aBytes, addrLoad, addrExec, addrInit, fStart) + { + var fStop = false; + var fLoaded = false; + /* + * Data on tapes in the "Absolute Format" is organized into blocks; each block begins with + * a 6-byte header: + * + * 2-byte signature (0x0001) + * 2-byte block length (N + 6, because it includes the 6-byte header) + * 2-byte load address + * + * followed by N data bytes. If N is zero, then the 2-byte load address is the exec address, + * unless the address is odd (usually 1). DEC's Absolute Loader jumps to the exec address + * in former case, halts in the latter. + * + * All values are stored "little endian" (low byte followed by high byte), just like the + * PDP-11's memory architecture. + * + * After the data bytes, there is a single checksum byte. The 8-bit sum of all the bytes in + * the block (including the header bytes and checksum byte) should be zero. + * + * ANOMALIES: Tape files don't always begin with a signature word, so I allow any number of + * leading zeros before the first signature. Tape files don't always end cleanly either, so as + * soon as I see an invalid signature, I break out of the loop without signalling an error, as + * long as at least ONE block was successfully processed. In fact, it's possible that as + * soon as a block with ZERO data bytes is encountered, processing is supposed to stop, but + * I haven't examined enough tapes (or the Absolute Loader code) to know for sure. + */ + if (addrLoad == null) { + var off = 0, fError = false; + while (off < aBytes.length - 1) { + var w = (aBytes[off] & 0xff) | ((aBytes[off+1] & 0xff) << 8); + if (!w) { // ignore pairs of leading zeros + off += 2; + continue; + } + if (!(w & 0xff)) { // as well as single bytes of zero + off++; + continue; + } + var offBlock = off; + if (w != 0x0001) { + this.printMessage("invalid signature (" + Str.toHexWord(w) + ") at offset " + Str.toHexWord(offBlock), MessagesPDP10.PAPER); + break; + } + if (off + 6 >= aBytes.length) { + this.printMessage("invalid block at offset " + Str.toHexWord(offBlock), MessagesPDP10.PAPER); + break; + } + off += 2; + var checksum = w; + var len = (aBytes[off++] & 0xff) | ((aBytes[off++] & 0xff) << 8); + var addr = (aBytes[off++] & 0xff) | ((aBytes[off++] & 0xff) << 8); + checksum += (len & 0xff) + (len >> 8) + (addr & 0xff) + (addr >> 8); + var offData = off, cbData = len -= 6; + while (len > 0 && off < aBytes.length) { + checksum += aBytes[off++] & 0xff; + len--; + } + if (len != 0 || off >= aBytes.length) { + this.printMessage("insufficient data for block at offset " + Str.toHexWord(offBlock), MessagesPDP10.PAPER); + break; + } + checksum += aBytes[off++] & 0xff; + if (checksum & 0xff) { + this.printMessage("invalid checksum (" + Str.toHexByte(checksum) + ") for block at offset " + Str.toHexWord(offBlock), MessagesPDP10.PAPER); + break; + } + if (!cbData) { + if (addr & 0x1) { + fStop = true; + } else { + if (addrExec == null) addrExec = addr; + } + if (addrExec != null) this.printMessage("starting address: " + Str.toHexWord(addrExec), MessagesPDP10.PAPER); + } else { + this.printMessage("loading " + Str.toHexWord(cbData) + " bytes at " + Str.toHexWord(addr) + "-" + Str.toHexWord(addr + cbData), MessagesPDP10.PAPER); + while (cbData--) { + this.bus.setWordDirect(addr++, aBytes[offData++]); + } + } + fLoaded = true; + } + } + if (!fLoaded) { + if (addrLoad == null) addrLoad = addrInit; + if (addrLoad != null) { + for (var i = 0; i < aBytes.length; i++) { + this.bus.setWordDirect(addrLoad + i, aBytes[i]); + } + fLoaded = true; + } + } + if (fLoaded) { + /* + * Set the start address to whatever the caller provided, or failing that, whatever start + * address was specified inside the image. + * + * For example, the diagnostic "MAINDEC-11-D0AA-PB" doesn't include a start address inside the + * image, but we know that the directions for that diagnostic say to "Start and Restart at 200", + * so we have manually inserted an "exec":128 in the JSON containing the image. + */ + if (addrExec == null || fStop) { + this.cpu.stopCPU(); + fStart = false; + } + if (addrExec != null) { + this.cpu.setReset(addrExec, fStart); + } + } + return fLoaded; + } + + /** + * RAMPDP10.init() + * + * This function operates on every HTML element of class "ram", extracting the + * JSON-encoded parameters for the RAMPDP10 constructor from the element's "data-value" + * attribute, invoking the constructor to create a RAMPDP10 component, and then binding + * any associated HTML controls to the new component. + */ + static init() + { + var aeRAM = Component.getElementsByClass(document, PDP10.APPCLASS, "ram"); + for (var iRAM = 0; iRAM < aeRAM.length; iRAM++) { + var eRAM = aeRAM[iRAM]; + var parmsRAM = Component.getComponentParms(eRAM); + var ram = new RAMPDP10(parmsRAM); + Component.bindComponentControls(ram, eRAM, PDP10.APPCLASS); + } + } +} + +/* + * Initialize all the RAMPDP10 modules on the page. + */ +Web.onInit(RAMPDP10.init); + +if (NODE) module.exports = RAMPDP10; diff --git a/modules/pdp10/lib/rom.js b/modules/pdp10/lib/rom.js new file mode 100644 index 000000000..2bf7ae018 --- /dev/null +++ b/modules/pdp10/lib/rom.js @@ -0,0 +1,339 @@ +/** + * @fileoverview Implements the PDP-10 ROM component. + * @author Jeff Parsons + * @copyright © Jeff Parsons 2012-2017 + * + * This file is part of PCjs, a computer emulation software project at . + * + * PCjs is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCjs. If not, + * see . + * + * You are required to include the above copyright notice in every modified copy of this work + * and to display that copyright notice when the software starts running; see COPYRIGHT in + * . + * + * Some PCjs files also attempt to load external resource files, such as character-image files, + * ROM files, and disk image files. Those external resource files are not considered part of PCjs + * for purposes of the GNU General Public License, and the author does not claim any copyright + * as to their contents. + */ + +"use strict"; + +if (NODE) { + var Str = require("../../shared/lib/strlib"); + var Web = require("../../shared/lib/weblib"); + var DumpAPI = require("../../shared/lib/dumpapi"); + var Component = require("../../shared/lib/component"); + var PDP10 = require("./defines"); + var BusPDP10 = require("./bus"); + var MemoryPDP10 = require("./memory"); + var MessagesPDP10 = require("./messages"); +} + +class ROMPDP10 extends Component { + /** + * ROMPDP10(parmsROM) + * + * The ROMPDP10 component expects the following (parmsROM) properties: + * + * addr: physical address of ROM + * size: amount of ROM, in bytes + * alias: physical alias address (null if none) + * file: name of ROM data file + * + * NOTE: The ROM data will not be copied into place until the Bus is ready (see initBus()) AND + * the ROM data file has finished loading (see finishLoad()). + * + * Also, while the size parameter may seem redundant, I consider it useful to confirm that the ROM + * you received is the ROM you expected. + * + * @param {Object} parmsROM + */ + constructor(parmsROM) + { + super("ROM", parmsROM, MessagesPDP10.ROM); + + this.abInit = null; + this.aSymbols = null; + + this.addrROM = +parmsROM['addr']; + this.sizeROM = +parmsROM['size']; + this.fRetainROM = false; + + /* + * The new 'alias' property can now be EITHER a single physical address (like 'addr') OR an array of + * physical addresses; eg: + * + * [0xf0000,0xffff0000,0xffff8000] + * + * We could have overloaded 'addr' to accomplish the same thing, but I think it's better to have any + * aliased locations listed under a separate property. + * + * Most ROMs are not aliased, in which case the 'alias' property should have the default value of null. + */ + this.addrAlias = parmsROM['alias']; + if (typeof this.addrAlias == "string") { + this.addrAlias = eval(this.addrAlias); + } + + this.sFilePath = parmsROM['file']; + this.sFileName = Str.getBaseName(this.sFilePath); + + if (this.sFilePath) { + var sFileURL = this.sFilePath; + if (DEBUG) this.log('load("' + sFileURL + '")'); + /* + * If the selected ROM file has a ".json" extension, then we assume it's pre-converted + * JSON-encoded ROM data, so we load it as-is; ditto for ROM files with a ".hex" extension. + * Otherwise, we ask our server-side ROM converter to return the file in a JSON-compatible format. + */ + var sFileExt = Str.getExtension(this.sFileName); + if (sFileExt != DumpAPI.FORMAT.JSON && sFileExt != DumpAPI.FORMAT.HEX) { + sFileURL = Web.getHost() + DumpAPI.ENDPOINT + '?' + DumpAPI.QUERY.FILE + '=' + this.sFilePath + '&' + DumpAPI.QUERY.FORMAT + '=' + DumpAPI.FORMAT.BYTES + '&' + DumpAPI.QUERY.DECIMAL + '=true'; + } + var rom = this; + Web.getResource(sFileURL, null, true, function doneLoad(sURL, sResponse, nErrorCode) { + rom.finishLoad(sURL, sResponse, nErrorCode); + }); + } + } + + /** + * initBus(cmp, bus, cpu, dbg) + * + * @this {ROMPDP10} + * @param {ComputerPDP10} cmp + * @param {BusPDP10} bus + * @param {CPUStatePDP10} cpu + * @param {DebuggerPDP10} dbg + */ + initBus(cmp, bus, cpu, dbg) + { + this.bus = bus; + this.cpu = cpu; + this.dbg = dbg; + this.initROM(); + } + + /** + * powerUp(data, fRepower) + * + * @this {ROMPDP10} + * @param {Object|null} data + * @param {boolean} [fRepower] + * @return {boolean} true if successful, false if failure + */ + powerUp(data, fRepower) + { + if (this.aSymbols) { + if (this.dbg) { + this.dbg.addSymbols(this.id, this.addrROM, this.sizeROM, this.aSymbols); + } + /* + * Our only role in the handling of symbols is to hand them off to the Debugger at our + * first opportunity. Now that we've done that, our copy of the symbols, if any, are toast. + */ + delete this.aSymbols; + } + return true; + } + + /** + * powerDown(fSave, fShutdown) + * + * Since we have nothing to do on powerDown(), and no state to return, we could simply omit + * this function. But it doesn't hurt anything, and maybe we'll use our state to save something + * useful down the road, like user-defined symbols (ie, symbols that the Debugger may have + * created, above and beyond those symbols we automatically loaded, if any, along with the ROM). + * + * @this {ROMPDP10} + * @param {boolean} [fSave] + * @param {boolean} [fShutdown] + * @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure + */ + powerDown(fSave, fShutdown) + { + return true; + } + + /** + * finishLoad(sURL, sData, nErrorCode) + * + * @this {ROMPDP10} + * @param {string} sURL + * @param {string} sData + * @param {number} nErrorCode (response from server if anything other than 200) + */ + finishLoad(sURL, sData, nErrorCode) + { + if (nErrorCode) { + this.notice("Unable to load ROM resource (error " + nErrorCode + ": " + sURL + ")"); + this.sFilePath = null; + } + else { + Component.addMachineResource(this.idMachine, sURL, sData); + var resource = Web.parseMemoryResource(sURL, sData); + if (resource) { + this.abInit = resource.aBytes; + this.aSymbols = resource.aSymbols; + } else { + this.sFilePath = null; + } + } + this.initROM(); + } + + /** + * initROM() + * + * This function is called by both initBus() and finishLoad(), but it cannot copy the initial data into place + * until after initBus() has received the Bus component AND finishLoad() has received the data. When both those + * criteria are satisfied, the component becomes "ready". + * + * @this {ROMPDP10} + */ + initROM() + { + if (!this.isReady()) { + if (this.sFilePath) { + /* + * Too early... + */ + if (!this.abInit || !this.bus) return; + + /* + * If no explicit size was specified, then use whatever the actual size is. + */ + if (!this.sizeROM) { + this.sizeROM = this.abInit.length; + } + if (this.abInit.length != this.sizeROM) { + /* + * Note that setError() sets the component's fError flag, which in turn prevents setReady() from + * marking the component ready. TODO: Revisit this decision. On the one hand, it sounds like a + * good idea to stop the machine in its tracks whenever a setError() occurs, but there may also be + * times when we'd like to forge ahead anyway. + */ + this.setError("ROM size (" + Str.toHexLong(this.abInit.length) + ") does not match specified size (" + Str.toHexLong(this.sizeROM) + ")"); + } + else if (this.addROM(this.addrROM)) { + + var aliases = []; + if (typeof this.addrAlias == "number") { + aliases.push(this.addrAlias); + } else if (this.addrAlias != null && this.addrAlias.length) { + aliases = this.addrAlias; + } + for (var i = 0; i < aliases.length; i++) { + this.cloneROM(aliases[i]); + } + /* + * We used to hang onto the initial ROM data so that we could restore any bytes the CPU overwrote, + * using memory write-notification handlers, but with the introduction of read-only memory blocks, that's + * no longer necessary. + * + * TODO: Consider an option to retain the ROM data, and give the user some way of restoring ROMs. + * That may be useful for "resumable" machines that save/restore all dirty block of memory, regardless + * whether they're ROM or RAM. However, the only way to modify a machine's ROM is with the Debugger, + * and Debugger users should know better. + */ + if (!this.fRetainROM) { + delete this.abInit; + } + } + } + this.setReady(); + } + } + + /** + * addROM(addr) + * + * @this {ROMPDP10} + * @param {number} addr + * @return {boolean} + */ + addROM(addr) + { + if (this.bus.addMemory(addr, this.sizeROM, MemoryPDP10.TYPE.ROM)) { + if (DEBUG) this.log("addROM(): copying ROM to " + Str.toHexLong(addr) + " (" + Str.toHexLong(this.abInit.length) + " bytes)"); + var i; + for (i = 0; i < this.abInit.length; i++) { + this.bus.setWordDirect(addr + i, this.abInit[i]); + } + return true; + } + + /* + * We don't need to report an error here, because addMemory() already takes care of that. + */ + return false; + } + + /** + * cloneROM(addr) + * + * For ROMs with one or more alias addresses, we used to call addROM() for each address. However, + * that obviously wasted memory, since each alias was an independent copy, and if you used the + * Debugger to edit the ROM in one location, the changes would not appear in the other location(s). + * + * Now that the Bus component provides low-level getMemoryBlocks() and setMemoryBlocks() methods + * to manually get and set the blocks of any memory range, it is now possible to create true aliases. + * + * @this {ROMPDP10} + * @param {number} addr + */ + cloneROM(addr) + { + var aBlocks = this.bus.getMemoryBlocks(this.addrROM, this.sizeROM); + this.bus.setMemoryBlocks(addr, this.sizeROM, aBlocks); + } + + /** + * ROMPDP10.init() + * + * This function operates on every HTML element of class "rom", extracting the + * JSON-encoded parameters for the ROMPDP10 constructor from the element's "data-value" + * attribute, invoking the constructor to create a ROMPDP10 component, and then binding + * any associated HTML controls to the new component. + */ + static init() + { + var aeROM = Component.getElementsByClass(document, PDP10.APPCLASS, "rom"); + for (var iROM = 0; iROM < aeROM.length; iROM++) { + var eROM = aeROM[iROM]; + var parmsROM = Component.getComponentParms(eROM); + var rom = new ROMPDP10(parmsROM); + Component.bindComponentControls(rom, eROM, PDP10.APPCLASS); + } + } +} + +/* + * NOTE: There's currently no need for this component to have a reset() function, since + * once the ROM data is loaded, it can't be changed, so there's nothing to reinitialize. + * + * OK, well, I take that back, because the Debugger, if installed, has the ability to modify + * ROM contents, so in that case, having a reset() function that restores the original ROM data + * might be useful; then again, it might not, depending on what you're trying to debug. + * + * If we do add reset(), then we'll want to change initROM() to hang onto the original + * ROM data; currently, we release it after copying it into the read-only memory allocated + * via bus.addMemory(). + */ + +/* + * Initialize all the ROMPDP10 modules on the page. + */ +Web.onInit(ROMPDP10.init); + +if (NODE) module.exports = ROMPDP10; diff --git a/modules/pdp10/lib/serial.js b/modules/pdp10/lib/serial.js new file mode 100644 index 000000000..a0e31bd0b --- /dev/null +++ b/modules/pdp10/lib/serial.js @@ -0,0 +1,670 @@ +/** + * @fileoverview Implements the PDP-10 SerialPort component + * @author Jeff Parsons + * @copyright © Jeff Parsons 2012-2017 + * + * This file is part of PCjs, a computer emulation software project at . + * + * PCjs is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCjs. If not, + * see . + * + * You are required to include the above copyright notice in every modified copy of this work + * and to display that copyright notice when the software starts running; see COPYRIGHT in + * . + * + * Some PCjs files also attempt to load external resource files, such as character-image files, + * ROM files, and disk image files. Those external resource files are not considered part of PCjs + * for purposes of the GNU General Public License, and the author does not claim any copyright + * as to their contents. + */ + +"use strict"; + +if (NODE) { + var Str = require("../../shared/lib/strlib"); + var Web = require("../../shared/lib/weblib"); + var Component = require("../../shared/lib/component"); + var Keys = require("../../shared/lib/keys"); + var State = require("../../shared/lib/state"); + var PDP10 = require("./defines"); + var MessagesPDP10 = require("./messages"); +} + +/** + * Since the Closure Compiler treats ES6 classes as @struct rather than @dict by default, + * it deters us from defining named properties on our components; eg: + * + * this['exports'] = {...} + * + * results in an error: + * + * Cannot do '[]' access on a struct + * + * So, in order to define 'exports', we must override the @struct assumption by annotating + * the class as @unrestricted (or @dict). Note that this must be done both here and in the + * Component class, because otherwise the Compiler won't allow us to *reference* the named + * property either. + * + * TODO: Consider marking ALL our classes unrestricted, because otherwise it forces us to + * define every single property the class uses in its constructor, which results in a fair + * bit of redundant initialization, since many properties aren't (and don't need to be) fully + * initialized until the appropriate init(), reset(), restore(), etc. function is called. + * + * The upside, however, may be that since the structure of the class is completely defined by + * the constructor, JavaScript engines may be able to optimize and run more efficiently. + * + * @unrestricted + */ +class SerialPortPDP10 extends Component { + /** + * SerialPortPDP10(parmsSerial) + * + * The SerialPort component has the following component-specific (parmsSerial) properties: + * + * adapter: adapter number; 0 if not defined (the PCx86 SerialPort component uses this + * value to set the device's internal COM number, which in turn determines other properties, + * such as I/O ports and IRQ; for the PDP-10, this currently has no defined use) + * + * binding: name of a control (based on its "binding" attribute) to bind to this port's I/O + * + * tabSize: set to a non-zero number to convert tabs to spaces (applies only to output to + * the above binding); default is 0 (no conversion) + * + * upperCase: if true, all received input is upper-cased; it is normally the responsibility + * of the sending device to ensure this, but sometimes it's more convenient to enforce + * on the receiving end. + * + * @param {Object} parmsSerial + */ + constructor(parmsSerial) + { + super("SerialPort", parmsSerial, MessagesPDP10.SERIAL); + + this.iAdapter = +parmsSerial['adapter']; + this.fUpperCase = parmsSerial['upperCase']; + if (typeof this.fUpperCase == "string") this.fUpperCase = (this.fUpperCase == "true"); + /** + * consoleOutput becomes a string that records serial port output if the 'binding' property is set to the + * reserved name "console". Nothing is written to the console, however, until a linefeed (0x0A) is output + * or the string length reaches a threshold (currently, 1024 characters). + * + * @type {string|null} + */ + this.consoleOutput = null; + + /** + * controlIOBuffer is a DOM element bound to the port (currently used for output only; see transmitByte()). + * + * Example: CTTY COM2 + * + * The CTTY DOS command redirects all CON I/O to the specified serial port (eg, COM2), which it assumes is + * connected to a serial terminal, and therefore anything it *transmits* via COM2 will be displayed by the + * terminal. It further assumes that anything typed on such a terminal is NOT displayed, so as DOS *receives* + * serial input, DOS *transmits* the appropriate characters back to the terminal via COM2. + * + * As a result, controlIOBuffer only needs to be updated by the transmitByte() function. + * + * @type {Object} + */ + this.controlIOBuffer = null; + + /* + * If controlIOBuffer is being used AND 'tabSize' is set, then we make an attempt to monitor the characters + * being echoed via transmitByte(), maintain a logical column position, and convert any tabs into the appropriate + * number of spaces. + * + * charBOL, if nonzero, is a character to automatically output at the beginning of every line. This probably + * isn't generally useful; I use it internally to preformat serial output. + */ + this.tabSize = +parmsSerial['tabSize']; + this.charBOL = +parmsSerial['charBOL']; + this.iLogicalCol = 0; + this.fNullModem = true; + + this.abReceive = []; + + var sBinding = parmsSerial['binding']; + if (sBinding == "console") { + this.consoleOutput = ""; + } else { + /* + * NOTE: If sBinding is not the name of a valid Control Panel DOM element, this call does nothing. + */ + Component.bindExternalControl(this, sBinding, SerialPortPDP10.sIOBuffer); + } + + /* + * No connection until initConnection() is called. + */ + this.sDataReceived = ""; + this.connection = this.sendData = this.updateStatus = null; + + /* + * Export all functions required by initConnection(). + */ + this['exports'] = { + 'connect': this.initConnection, + 'receiveData': this.receiveData, + 'receiveStatus': this.receiveStatus, + 'setConnection': this.setConnection + }; + } + + /** + * setBinding(sType, sBinding, control, sValue) + * + * @this {SerialPortPDP10} + * @param {string|null} sType is the type of the HTML control (eg, "button", "textarea", "register", "flag", "rled", etc) + * @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "buffer") + * @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement) + * @param {string} [sValue] optional data value + * @return {boolean} true if binding was successful, false if unrecognized binding request + */ + setBinding(sType, sBinding, control, sValue) + { + var serial = this; + + switch (sBinding) { + case SerialPortPDP10.sIOBuffer: + this.bindings[sBinding] = this.controlIOBuffer = control; + + /* + * An onkeydown handler is required for certain keys that browsers tend to consume themselves; + * for example, BACKSPACE is often defined as going back to the previous web page, and certain + * CTRL keys are often used for browser shortcuts (usually on Windows-based browsers). + * + * NOTE: We don't bother with a keyUp handler, because for the most part, we're only intercepting + * keys that require special treatment; in general, we're content with keyPress events. + */ + control.onkeydown = function onKeyDown(event) { + event = event || window.event; + var bASCII = 0; + var keyCode = event.keyCode; + /* + * Perform the same remapping of BACKSPACE and DELETE that our VT100 emulation performs, + * for PCjs-wide consistency; see the KEYMAP table in /modules/pc8080/lib/keyboard.js for + * the rationale. Ditto for ALT-DELETE; see onKeyDown() in /modules/pc8080/lib/keyboard.js + * for details. + * + * NOTE: keyDown (and keyUp) events supply us with KEYCODE values, which are NOT the same as + * ASCII values, which is why we are comparing with KEYCODE values but assigning ASCII values, + * because receiveData() requires ASCII values. + */ + if (keyCode == Keys.KEYCODE.BS) { + bASCII = event.altKey? Keys.ASCII.CTRL_H : Keys.ASCII.DEL; + } + else if (keyCode == Keys.KEYCODE.DEL) { + bASCII = Keys.ASCII.CTRL_H; + } + else if (event.ctrlKey && keyCode >= Keys.ASCII.A && keyCode <= Keys.ASCII.Z) { + bASCII = keyCode - (Keys.ASCII.A - Keys.ASCII.CTRL_A); + } + if (bASCII) { + if (event.preventDefault) event.preventDefault(); + serial.receiveData(bASCII); + } + return true; + }; + + control.onkeypress = function onKeyPress(event) { + /* + * NOTE: Unlike keyDown events, keyPress events generally supply us with ASCII values, + * despite the fact that, as above, they come to us via the keyCode property. Yes, it's + * brilliant (or rather, the opposite of brilliant), but that's life. + */ + event = event || window.event; + /* + * Not sure why COMMAND-key combinations are coming through here (on Safari at least), + * but in any case, let's make sure we don't act on them. + */ + if (!event.metaKey) { + var bASCII = event.which || event.keyCode; + /* + * Perform the same remapping of ALT-ENTER (to LINE-FEED) that our VT100 emulation performs, + * for PCjs-wide consistency; see onKeyDown() in /modules/pc8080/lib/keyboard.js for details. + */ + if (event.altKey) { + if (bASCII == Keys.ASCII.CTRL_M) { + bASCII = Keys.ASCII.CTRL_J; + } + } + serial.receiveData(bASCII); + /* + * Since we're going to remove the "readonly" attribute from the