blob: 08d2cd3be494e8d91553bad1180e6351198efdb9 [file] [log] [blame] [raw]
"use strict";
/** @define {boolean} */
var IN_NODE = false;
/** @define {boolean} */
var IN_WORKER = false;
/** @define {boolean} */
var IN_BROWSER = true;
if(IN_BROWSER + IN_NODE + IN_WORKER !== 1)
{
throw "Invalid environment";
}
/** @constructor */
function v86()
{
/** @type {boolean} */
this.first_init = true;
/** @type {boolean} */
this.running = false;
/** @type {boolean} */
this.stopped = false;
/** @type {CPU} */
this.cpu = new CPU();
this.next_tick = function() {};
this.microtick = function() {};
}
v86.prototype.run = function()
{
if(!this.running)
{
this.next_tick();
}
};
v86.prototype.do_tick = function()
{
if(this.stopped)
{
this.stopped = this.running = false;
return;
}
this.running = true;
this.cpu.main_run();
var me = this;
this.next_tick();
};
v86.prototype.stop = function()
{
if(this.running)
{
this.stopped = true;
}
};
v86.prototype.restart = function()
{
dbg_log("cpu restart", LOG_CPU);
var was_running = this.running;
var cpu = this;
this.stopped = true;
this.running = false;
setTimeout(function()
{
cpu.devices.ps2.destroy();
cpu.devices.vga.destroy();
cpu.init(cpu.current_settings);
if(was_running)
{
cpu.next_tick();
}
}, 10);
};
v86.prototype.init = function(settings)
{
if(this.first_init)
{
this.first_init = false;
this.lazy_init();
}
this.cpu.init(settings);
};
// initialization that only needs to be once
v86.prototype.lazy_init = function()
{
var emulator = this;
if(typeof setImmediate !== "undefined")
{
this.next_tick = function()
{
setImmediate(function() { emulator.do_tick(); });
};
}
else if(typeof window !== "undefined" && typeof postMessage !== "undefined")
{
// setImmediate shim for the browser.
// TODO: Make this deactivatable, for other applications
// using postMessage
/** @const */
var MAGIC_POST_MESSAGE = 0xAA55;
window.addEventListener("message", function(e)
{
if(e.source === window && e.data === MAGIC_POST_MESSAGE)
{
emulator.do_tick();
}
}, false);
this.next_tick = function()
{
window.postMessage(MAGIC_POST_MESSAGE, "*");
};
}
else
{
this.next_tick = function()
{
setTimeout(function() { emulator.do_tick(); }, 0);
};
}
};
if(typeof performance === "object" && performance.now)
{
v86.microtick = function()
{
return performance.now();
};
}
else
{
v86.microtick = Date.now;
}
Object.fromList = function(xs)
{
var result = {};
for(var i = 0; i < xs.length; i++)
{
result[xs[i][0]] = xs[i][1];
}
return result;
};
var dbg_names = Object.fromList([
[1, ""],
[LOG_CPU, "CPU"],
[LOG_DISK, "DISK"],
[LOG_FPU, "FPU"],
[LOG_MEM, "MEM"],
[LOG_DMA, "DMA"],
[LOG_IO, "IO"],
[LOG_PS2, "PS2"],
[LOG_PIC, "PIC"],
[LOG_VGA, "VGA"],
[LOG_PIT, "PIT"],
[LOG_MOUSE, "MOUS"],
[LOG_PCI, "PCI"],
[LOG_BIOS, "BIOS"],
[LOG_CD, "CD"],
[LOG_SERIAL, "SERI"],
[LOG_RTC, "RTC"],
[LOG_HPET, "HPET"],
[LOG_ACPI, "ACPI"],
[LOG_APIC, "APIC"],
[LOG_NET, "NET"],
[LOG_VIRTIO, "VIO"],
]);
var log_last_message = "",
log_message_repetitions = 0;
/**
* @param {number=} level
*/
function dbg_log(stuff, level)
{
if(!DEBUG) return;
level = level || 1;
if(level & LOG_LEVEL)
{
var level_name = dbg_names[level] || "",
message = "[" + String.pads(level_name, 4) + "] " + stuff;
if(message === log_last_message)
{
log_message_repetitions++;
if(log_message_repetitions < 2048)
{
return;
}
}
if(log_message_repetitions)
{
if(log_message_repetitions === 1)
{
console.log(log_last_message);
}
else
{
console.log("Previous message repeated " + log_message_repetitions + " times");;
}
log_message_repetitions = 0;
}
console.log(message);
log_last_message = message;
}
}
/**
* @param {number=} level
*/
function dbg_trace(level)
{
if(!DEBUG) return;
dbg_log(Error().stack, level);
}
/**
* console.assert is fucking slow
* @param {string=} msg
* @param {number=} level
*/
function dbg_assert(cond, msg, level)
{
if(!DEBUG) return;
if(!cond)
{
//dump_regs();
console.log(Error().stack);
console.trace();
if(msg)
{
throw "Assert failed: " + msg;
}
else
{
throw "Assert failed";
}
}
};
// pad string with spaces on the right
String.pads = function(str, len)
{
str = str ? str + "" : "";
while(str.length < len)
{
str = str + " ";
}
return str;
}
// pad string with zeros on the left
String.pad0 = function(str, len)
{
str = str ? str + "" : "";
while(str.length < len)
{
str = "0" + str;
}
return str;
}
/**
* number to hex
* @param {number=} len
*/
function h(n, len)
{
//dbg_assert(typeof n === "number");
if(!n) return String.pad0("", len || 1);
if(len)
{
return String.pad0(n.toString(16).toUpperCase(), len);
}
else
{
return n.toString(16).toUpperCase();
}
}
/**
* Synchronous access to ArrayBuffer
* @constructor
*/
function SyncBuffer(buffer)
{
this.byteLength = buffer.byteLength;
// warning: fn may be called synchronously or asynchronously
this.get = function(start, len, fn)
{
dbg_assert(start + len <= buffer.byteLength);
fn(new Uint8Array(buffer, start, len));
};
this.set = function(start, slice, fn)
{
dbg_assert(start + slice.length <= buffer.byteLength);
new Uint8Array(buffer, start, slice.byteLength).set(slice);
fn();
};
this.get_buffer = function(fn)
{
fn(buffer);
};
}
/**
* Simple circular queue for logs
*
* @param {number} size
* @constructor
*/
function CircularQueue(size)
{
var data,
index;
this.add = function(item)
{
data[index] = item;
index = (index + 1) % size;
};
this.toArray = function()
{
return [].slice.call(data, index).concat([].slice.call(data, 0, index));
};
this.clear = function()
{
data = [];
index = 0;
};
this.set = function(new_data)
{
data = new_data;
index = 0;
};
this.clear();
}
var int_log2_table = new Int8Array(256);
for(var i = 0, b = -2; i < 256; i++)
{
if(!(i & i - 1))
b++;
int_log2_table[i] = b;
}
Math.int_log2 = function(x)
{
dbg_assert(x > 0);
// http://jsperf.com/integer-log2/6
var tt = x >>> 16;
if(tt)
{
var t = tt >>> 8;
if(t)
{
return 24 + int_log2_table[t];
}
else
{
return 16 + int_log2_table[tt];
}
}
else
{
var t = x >>> 8;
if(t)
{
return 8 + int_log2_table[t];
}
else
{
return int_log2_table[x];
}
}
}
/**
* @constructor
*
* Queue wrapper around Uint8Array
* Used by devices such as the PS2 controller
*/
function ByteQueue(size)
{
var data = new Uint8Array(size),
start,
end;
dbg_assert((size & size - 1) === 0);
this.length = 0;
this.push = function(item)
{
if(this.length === size)
{
// intentional overwrite
}
else
{
this.length++;
}
data[end] = item;
end = end + 1 & size - 1;
};
this.shift = function()
{
if(!this.length)
{
return -1;
}
else
{
var item = data[start];
start = start + 1 & size - 1;
this.length--;
return item;
}
};
this.peek = function()
{
if(!this.length)
{
return -1;
}
else
{
return data[start];
}
};
this.clear = function()
{
start = 0;
end = 0;
this.length = 0;
};
this.clear();
}
Array.setify = function(array)
{
var set = {};
for(var i = 0; i < array.length; i++)
{
set[array[i]] = true;
}
return set;
};