diff --git a/src/cpu.macro.js b/src/cpu.macro.js new file mode 100644 index 00000000..5bc82c5e --- /dev/null +++ b/src/cpu.macro.js @@ -0,0 +1,2384 @@ +"use strict"; + +#define read_imm16s() (read_imm16() << 16 >> 16) +#define read_imm32() (read_imm32s() >>> 0) + +#define safe_read16s(addr) (safe_read16(addr) << 16 >> 16) +#define safe_read32(addr) (safe_read32s(addr) >>> 0) + +var debug = {}; + + +/** @constructor */ +function v86() +{ + +var cpu = this; + +this.run = function() +{ + if(!running) + { + cpu_run(); + } +} + +this.stop = cpu_stop; +this.init = cpu_init; +this.restart = cpu_restart; + +this.dev = {}; + +this.instr_counter = 0; + + +var + segment_is_null, + segment_offsets, + segment_limits, + segment_infos, + + /* + * Translation Lookaside Buffer + */ + tlb_user_read, + tlb_user_write, + tlb_system_read, + tlb_system_write, + + /* + * Information about which pages are cached in the tlb. + * By bit: + * 0 system, read + * 1 system, write + * 2 user, read + * 3 user, write + */ + tlb_info, + + /* + * Same as tlb_info, except it only contains global pages + */ + tlb_info_global, + + /** + * Wheter or not in protected mode + * @type {boolean} + */ + protected_mode, + + /** + * interrupt descriptor table + * @type {number} + */ + idtr_size, + /** @type {number} */ + idtr_offset, + + /** + * global descriptor table register + * @type {number} + */ + gdtr_size, + /** @type {number} */ + gdtr_offset, + + /** + * local desciptor table + * @type {number} + */ + ldtr_size, + /** @type {number} */ + ldtr_offset, + + /** + * task register + * @type {number} + */ + tsr_size, + /** @type {number} */ + tsr_offset, + + /* + * whether or not a page fault occured + */ + page_fault, + + /** @type {number} */ + cr0, + /** @type {number} */ + cr2, + /** @type {number} */ + cr3, + /** @type {number} */ + cr4, + + // current privilege level + /** @type {number} */ + cpl, + + // paging enabled + /** @type {boolean} */ + paging, + + // if false, pages are 4 KiB, else 4 Mib + /** @type {number} */ + page_size_extensions, + + // current operand/address/stack size + /** @type {boolean} */ + is_32, + /** @type {boolean} */ + operand_size_32, + /** @type {boolean} */ + stack_size_32, + + /** + * Cycles since last cpu reset, used by rdtsc instruction + * @type {number} + */ + cpu_timestamp_counter, + + /** @type {number} */ + previous_ip, + + /** + * wheter or not in step mode + * used for debugging + * @type {boolean} + */ + step_mode, + + /** + * was the last instruction a hlt + * @type {boolean} + */ + in_hlt, + + /** @type {VGAScreen} */ + vga, + + /** @type {PS2} */ + ps2, + + /** + * Programmable interval timer + * @type {PIT} + */ + timer, + + + /** + * Real Time Clock + * @type {RTC} + */ + rtc, + + /** + * Floppy Disk controller + * @type {FloppyController} + */ + fdc, + + /** + * Serial controller + * @type {UART} + */ + uart, + + /** @type {boolean} */ + running, + + /** @type {boolean} */ + stopped, + + /** @type {number} */ + loop_counter, + + /** @type {Memory} */ + memory, + + /** @type {(FPU|NoFPU)} */ + fpu, + + + /** + * Programmable interrupt controller + * @type {PIC} + */ + pic, + + /** + * @type {IO} + */ + io, + + /** + * @type {PCI} + */ + pci, + + /** + * @type {CDRom} + */ + cdrom, + + /** + * @type {HDD} + */ + hda, + + /** + * @type {HDD} + */ + hdb, + + /** + * Direct Memory Access Controller + * @type {DMA} + */ + dma, + + translate_address_read, + translate_address_write, + + ops, + + /** @type {boolean} */ + address_size_32, + + /** @type {number} */ + instruction_pointer, + + /** @type {number} */ + last_virt_eip, + + /** @type {number} */ + eip_phys, + + /** @type {number} */ + last_virt_esp, + + /** @type {number} */ + esp_phys, + + + // current state of prefixes + segment_prefix, + + + /** @type {boolean} */ + repeat_string_prefix, + + /** @type {boolean} */ + repeat_string_type, + + /** @type {number} */ + last_result, + + /** @type {number} */ + flags, + + /** + * bitmap of flags which are not updated in the flags variable + * changed by arithmetic instructions, so only relevant to arithmetic flags + * @type {number} + */ + flags_changed, + + /** + * the last 2 operators and the result and size of the last arithmetic operation + * @type {number} + */ + last_op1, + /** @type {number} */ + last_op2, + /** @type {number} */ + last_op_size, + + + // registers + reg32, + reg32s, + reg16, + reg16s, + reg8, + reg8s, + + sreg, + + + // sp or esp, depending on stack size attribute + stack_reg, + reg_vsp, + reg_vbp, + + // reg16 or reg32, depending on address size attribute + regv, + reg_vcx, + reg_vsi, + reg_vdi, + + // functions that are set depending on whether paging is enabled or not + read_imm8, + read_imm8s, + read_imm16, + read_imm32s, + + safe_read8, + safe_read8s, + safe_read16, + safe_read32s, + + get_esp_read, + get_esp_write, + + + table, + table0F, + + modrm_resolve, + + + current_settings +; + + +function cpu_run() +{ + if(stopped) + { + stopped = running = false; + return; + } + + running = true; + + try { + do_run(); + } + catch(e) + { + if(e === 0xDEADBEE) + { + // A legit CPU exception (for instance, a page fault happened) + // call_interrupt_vector has already been called at this point, + // so we just need to reset some state + + page_fault = false; + repeat_string_prefix = false; + segment_prefix = -1; + + address_size_32 = is_32; + update_address_size(); + operand_size_32 = is_32; + update_operand_size(); + + cpu_run(); + } + else + { + throw e; + } + } +}; + +function cpu_stop() +{ + if(running) + { + stopped = true; + } +} + +function cpu_restart() +{ + var was_running = running; + + stopped = true; + running = false; + + setTimeout(function() + { + ps2.destroy(); + vga.destroy(); + + cpu_init(current_settings); + + if(was_running) + { + cpu_run(); + } + }, 10); +} + +function cpu_reboot_internal() +{ + dbg_assert(running); + + ps2.destroy(); + vga.destroy(); + + cpu_init(current_settings); + + throw 0xDEADBEE; +} + +function cpu_init(settings) +{ + // see browser/main.js or node/main.js + if(typeof set_tick !== "undefined") + { + set_tick(cpu_run); + } + + current_settings = settings; + + cpu.memory = memory = new Memory(new ArrayBuffer(memory_size), memory_size); + + segment_is_null = new Uint8Array(8); + segment_limits = new Uint32Array(8); + segment_infos = new Uint32Array(8); + segment_offsets = new Int32Array(8); + + // 16 MB in total + tlb_user_read = new Int32Array(1 << 20); + tlb_user_write = new Int32Array(1 << 20); + tlb_system_read = new Int32Array(1 << 20); + tlb_system_write = new Int32Array(1 << 20); + + tlb_info = new Uint8Array(1 << 20); + tlb_info_global = new Uint8Array(1 << 20); + + + reg32 = new Uint32Array(8); + reg32s = new Int32Array(reg32.buffer); + reg16 = new Uint16Array(reg32.buffer); + reg16s = new Int16Array(reg32.buffer); + reg8 = new Uint8Array(reg32.buffer); + reg8s = new Int8Array(reg32.buffer); + sreg = new Uint16Array(8); + protected_mode = false; + + idtr_size = 0; + idtr_offset = 0; + + gdtr_size = 0; + gdtr_offset = 0; + + ldtr_size = 0; + ldtr_offset = 0; + + tsr_size = 0; + tsr_offset = 0; + + page_fault = false; + cr0 = 0; + cr2 = 0; + cr3 = 0; + cr4 = 0; + cpl = 0; + paging = false; + page_size_extensions = 0; + is_32 = false; + operand_size_32 = false; + stack_size_32 = false; + address_size_32 = false; + + paging_changed(); + + update_operand_size(); + update_address_size(); + + stack_reg = reg16; + reg_vsp = reg_sp; + reg_vbp = reg_bp; + + cpu_timestamp_counter = 0; + previous_ip = 0; + step_mode = false; + in_hlt = false; + + running = false; + stopped = false; + loop_counter = 20; + + translate_address_read = translate_address_disabled; + translate_address_write = translate_address_disabled; + + segment_prefix = -1; + repeat_string_prefix = false; + last_result = 0; + flags = flags_default; + flags_changed = 0; + last_op1 = 0; + last_op2 = 0; + last_op_size = 0; + + + + if(settings.bios) + { + // load bios + var data = new Uint8Array(settings.bios), + start = 0x100000 - settings.bios.byteLength; + + for(var i = 0; i < settings.bios.byteLength; i++) + { + memory.mem8[start + i] = data[i]; + } + + if(settings.vga_bios) + { + // load vga bios + data = new Uint8Array(settings.vga_bios); + + for(var i = 0; i < settings.vga_bios.byteLength; i++) + { + memory.mem8[0xC0000 + i] = data[i]; + } + } + + // ip initial value + instruction_pointer = 0xFFFF0; + + // ss and sp inital value + switch_seg(reg_ss, 0x30); + reg16[reg_sp] = 0x100; + } + else if(settings.linux) + { + instruction_pointer = 0x10000; + + memory.write_blob(new Uint8Array(settings.linux.vmlinux), 0x100000); + memory.write_blob(new Uint8Array(settings.linux.linuxstart), instruction_pointer); + + if(settings.linux.root) + { + memory.write_blob(new Uint8Array(settings.linux.root), 0x00400000); + reg32[reg_ebx] = settings.linux.root.byteLength; + } + + memory.write_string(settings.linux.cmdline, 0xF800); + + reg32[reg_eax] = memory_size; + reg32[reg_ecx] = 0xF800; + + switch_seg(reg_cs, 0); + switch_seg(reg_ss, 0); + switch_seg(reg_ds, 0); + switch_seg(reg_es, 0); + switch_seg(reg_gs, 0); + switch_seg(reg_fs, 0); + + is_32 = true; + address_size_32 = true; + operand_size_32 = true; + stack_size_32 = true; + protected_mode = true; + + update_operand_size(); + update_address_size(); + + regv = reg32; + reg_vsp = reg_esp; + reg_vbp = reg_ebp; + + cr0 = 1; + } + else + { + switch_seg(reg_ss, 0x30); + reg16[reg_sp] = 0x100; + + instruction_pointer = 0; + } + + + cpu.dev = {}; + + if(settings.load_devices) + { + var devapi = { + memory: memory, + reboot: cpu_reboot_internal, + }; + + devapi.io = cpu.dev.io = io = new IO(); + devapi.pic = pic = new PIC(devapi, call_interrupt_vector, handle_irqs); + devapi.pci = pci = new PCI(devapi); + devapi.dma = dma = new DMA(devapi); + + + cpu.dev.vga = vga = new VGAScreen(devapi, settings.screen_adapter) + cpu.dev.ps2 = ps2 = new PS2(devapi, settings.keyboard_adapter, settings.mouse_adapter); + + //fpu = new NoFPU(); + fpu = new FPU(devapi); + + uart = new UART(devapi); + + cpu.dev.fdc = fdc = new FloppyController(devapi, settings.floppy_disk); + + if(settings.cdrom_disk) + { + cpu.dev.cdrom = cdrom = new CDRom(devapi, settings.cdrom_disk); + } + + if(settings.hda_disk) + { + cpu.dev.hda = hda = new HDD(devapi, settings.hda_disk, 0); + } + if(settings.hdb_disk) + { + cpu.dev.hdb = hdb = new HDD(devapi, settings.hdb_disk, 1); + } + + timer = new PIT(devapi); + rtc = new RTC(devapi, fdc.type); + } + + if(DEBUG) + { + // used for debugging + ops = new CircularQueue(30000); + + if(typeof window !== "undefined") + { + window.memory = memory; + window.vga = vga; + } + + if(io) + { + // write seabios debug output to console + var seabios_debug = ""; + + io.register_write(0x402, function(out_byte) + { + // seabios debug + // + if(out_byte === 10) + { + dbg_log(seabios_debug, LOG_BIOS); + seabios_debug = ""; + } + else + { + seabios_debug += String.fromCharCode(out_byte); + } + }); + } + } +} + +function do_run() +{ + var + /** + * @type {number} + */ + now, + start = Date.now(); + + vga.timer(start); + + // outer loop: + // runs cycles + timers + for(var j = loop_counter; j--;) + { + // inner loop: + // runs only cycles + for(var k = LOOP_COUNTER; k--;) + { + previous_ip = instruction_pointer; + + cycle(); + cpu_timestamp_counter++; + } + + now = Date.now(); + timer.timer(now); + rtc.timer(now); + } + + cpu.instr_counter += loop_counter * LOOP_COUNTER; + + if(now - start > TIME_PER_FRAME) + { + loop_counter--; + } + else + { + loop_counter++; + } + + next_tick(); +} + +// do_run must not be inlined into cpu_run, because then more code +// is in the deoptimized try-catch. +// This trick is a bit ugly, but it works without further complication. +if(typeof window !== "undefined") +{ + window.__no_inline = do_run; +} + + +/** + * execute a single instruction cycle on the cpu + * this includes reading all prefixes and the whole instruction + */ +function cycle() +{ + var opcode = read_imm8(); + + logop(instruction_pointer - 1, opcode); + + // call the instruction + table[opcode](); + + // TODO + //if(flags & flag_trap) + //{ + // + //} +} + +cpu.cycle = function() +{ + table[read_imm8()](); +} + +function cr0_changed() +{ + //protected_mode = (cr0 & 1) === 1; + //dbg_log("cr0 = " + h(cr0)); + + var new_paging = (cr0 & 0x80000000) !== 0; + + if(fpu.is_fpu) + { + cr0 &= ~4; + } + else + { + cr0 |= 4; + } + + if(new_paging !== paging) + { + paging = new_paging; + + paging_changed(); + } +} + +function paging_changed() +{ + var table = paging ? pe_functions : npe_functions; + + read_imm8 = table.read_imm8; + read_imm8s = table.read_imm8s; + read_imm16 = table.read_imm16; + read_imm32s = table.read_imm32s; + + safe_read8 = table.safe_read8; + safe_read8s = table.safe_read8s; + safe_read16 = table.safe_read16; + safe_read32s = table.safe_read32s; + + get_esp_read = table.get_esp_read; + get_esp_write = table.get_esp_write; + + + // set translate_address_* depending on cpl and paging + cpl_changed(); +} + +function cpl_changed() +{ + last_virt_eip = -1; + last_virt_esp = -1; + + if(!paging) + { + translate_address_write = translate_address_disabled; + translate_address_read = translate_address_disabled; + } + else if(cpl) + { + translate_address_write = translate_address_user_write; + translate_address_read = translate_address_user_read; + } + else + { + translate_address_write = translate_address_system_write; + translate_address_read = translate_address_system_read; + } +} + + +// functions that are used when paging is disabled +var npe_functions = { + get_esp_read: get_esp_npe, + get_esp_write: get_esp_npe, + + read_imm8: function() + { + return memory.mem8[instruction_pointer++]; + }, + + read_imm8s: function() + { + return memory.mem8s[instruction_pointer++]; + }, + + read_imm16 : function() + { + var data16 = memory.read16(instruction_pointer); + instruction_pointer = instruction_pointer + 2 | 0; + return data16; + }, + + read_imm32s : function() + { + var data32 = memory.read32s(instruction_pointer); + instruction_pointer = instruction_pointer + 4 | 0; + return data32; + }, + + safe_read8 : function(addr) { return memory.read8(addr) }, + safe_read8s : function(addr) { return memory.read8s(addr); }, + safe_read16 : function(addr) { return memory.read16(addr); }, + safe_read32s : function(addr) { return memory.read32s(addr); }, +}; + +// functions that are used when paging is enabled +var pe_functions = +{ + get_esp_read: get_esp_pe_read, + get_esp_write: get_esp_pe_write, + + read_imm8 : function() + { + if((instruction_pointer & ~0xFFF) ^ last_virt_eip) + { + eip_phys = translate_address_read(instruction_pointer) ^ instruction_pointer; + last_virt_eip = instruction_pointer & ~0xFFF; + } + + // memory.read8 inlined under the assumption that code never runs in + // memory-mapped io + return memory.mem8[eip_phys ^ instruction_pointer++]; + }, + + read_imm8s : function() + { + if((instruction_pointer & ~0xFFF) ^ last_virt_eip) + { + eip_phys = translate_address_read(instruction_pointer) ^ instruction_pointer; + last_virt_eip = instruction_pointer & ~0xFFF; + } + + return memory.mem8s[eip_phys ^ instruction_pointer++]; + }, + + read_imm16 : function() + { + // Two checks in one comparison: + // 1. Did the high 20 bits of eip change + // or 2. Are the low 12 bits of eip 0xFFF (and this read crosses a page boundary) + if((instruction_pointer ^ last_virt_eip) > 0xFFE) + { + return read_imm8() | read_imm8() << 8; + } + + var data16 = memory.read16(eip_phys ^ instruction_pointer); + instruction_pointer = instruction_pointer + 2 | 0; + + return data16; + }, + + + read_imm32s : function() + { + // Analogue to the above comment + if((instruction_pointer ^ last_virt_eip) > 0xFFC) + { + return read_imm16() | read_imm16() << 16; + } + + var data32 = memory.read32s(eip_phys ^ instruction_pointer); + instruction_pointer = instruction_pointer + 4 | 0; + + return data32; + }, + + safe_read8 : do_safe_read8, + safe_read8s : do_safe_read8s, + safe_read16 : do_safe_read16, + safe_read32s : do_safe_read32s, +}; + + +// read word from a page boundary, given 2 physical addresses +function virt_boundary_read16(low, high) +{ + dbg_assert((low & 0xFFF) === 0xFFF); + dbg_assert((high & 0xFFF) === 0); + + return memory.read8(low) | memory.read8(high) << 8; +} + +// read doubleword from a page boundary, given 2 addresses +function virt_boundary_read32s(low, high) +{ + dbg_assert((low & 0xFFF) >= 0xFFD); + dbg_assert((high - 3 & 0xFFF) === (low & 0xFFF)); + + var result = memory.read8(low) | memory.read8(high) << 24; + + if(low & 1) + { + if(low & 2) + { + // 0xFFF + result |= memory.read8(high - 2) << 8 | + memory.read8(high - 1) << 16; + } + else + { + // 0xFFD + result |= memory.read8(low + 1) << 8 | + memory.read8(low + 2) << 16; + } + } + else + { + // 0xFFE + result |= memory.read8(low + 1) << 8 | + memory.read8(high - 1) << 16; + } + + return result; +} + +function virt_boundary_write16(low, high, value) +{ + dbg_assert((low & 0xFFF) === 0xFFF); + dbg_assert((high & 0xFFF) === 0); + + memory.write8(low, value); + memory.write8(high, value >> 8); +} + +function virt_boundary_write32(low, high, value) +{ + dbg_assert((low & 0xFFF) >= 0xFFD); + dbg_assert((high - 3 & 0xFFF) === (low & 0xFFF)); + + memory.write8(low, value); + memory.write8(high, value >> 24); + + if(low & 1) + { + if(low & 2) + { + // 0xFFF + memory.write8(high - 2, value >> 8); + memory.write8(high - 1, value >> 16); + } + else + { + // 0xFFD + memory.write8(low + 1, value >> 8); + memory.write8(low + 2, value >> 16); + } + } + else + { + // 0xFFE + memory.write8(low + 1, value >> 8); + memory.write8(high - 1, value >> 16); + } +} + +// safe_read, safe_write +// read or write byte, word or dword to the given *virtual* address, +// and be safe on page boundaries + +function do_safe_read8(addr) +{ + return memory.read8(translate_address_read(addr)); +} + +function do_safe_read8s(addr) +{ + return memory.read8s(translate_address_read(addr)); +} + +function do_safe_read16(addr) +{ + if((addr & 0xFFF) === 0xFFF) + { + return safe_read8(addr) | safe_read8(addr + 1) << 8; + } + else + { + return memory.read16(translate_address_read(addr)); + } +} + +function do_safe_read32s(addr) +{ + if((addr & 0xFFF) >= 0xFFD) + { + return safe_read16(addr) | safe_read16(addr + 2) << 16; + } + else + { + return memory.read32s(translate_address_read(addr)); + } +} + +function safe_write8(addr, value) +{ + memory.write8(translate_address_write(addr), value); +} + +function safe_write16(addr, value) +{ + var phys_low = translate_address_write(addr); + + if((addr & 0xFFF) === 0xFFF) + { + virt_boundary_write16(phys_low, translate_address_write(addr + 1), value); + } + else + { + memory.write16(phys_low, value); + } +} + +function safe_write32(addr, value) +{ + var phys_low = translate_address_write(addr); + + if((addr & 0xFFF) >= 0xFFD) + { + virt_boundary_write32(phys_low, translate_address_write(addr + 3), value); + } + else + { + memory.write32(phys_low, value); + } +} + +// read 2 or 4 byte from ip, depending on address size attribute +function read_moffs() +{ + if(address_size_32) + { + return get_seg_prefix(reg_ds) + read_imm32s(); + } + else + { + return get_seg_prefix(reg_ds) + read_imm16(); + } +} + +function get_flags() +{ + return (flags & ~flags_all) | getcf() | getpf() | getaf() | getzf() | getsf() | getof(); +} + +function load_flags() +{ + flags = get_flags(); + flags_changed = 0; +} + +// get esp with paging disabled +function get_esp_npe(mod) +{ + if(stack_size_32) + { + return get_seg(reg_ss) + stack_reg[reg_vsp] + mod; + } + else + { + return get_seg(reg_ss) + (stack_reg[reg_vsp] + mod & 0xFFFF); + } +} + +function get_esp_pe_read(mod) +{ + // UNSAFE: stack_reg[reg_vsp]+mod needs to be masked in 16 bit mode + // (only if paging is enabled and in 16 bit mode) + + return translate_address_read(get_seg(reg_ss) + stack_reg[reg_vsp] + mod); +} + +function get_esp_pe_write(mod) +{ + return translate_address_write(get_seg(reg_ss) + stack_reg[reg_vsp] + mod); +} + + +/* + * returns the "real" instruction pointer, + * without segment offset + */ +function get_real_ip() +{ + return instruction_pointer - get_seg(reg_cs); +} + +function call_interrupt_vector(interrupt_nr, is_software_int, error_code) +{ + if(DEBUG) + { + ops.add(instruction_pointer); + ops.add("-- INT " + h(interrupt_nr)); + ops.add(1); + } + + //if(interrupt_nr == 0x13) + //{ + // dbg_log("INT 13"); + // dbg_log(memory.read8(ch) + "/" + memory.read8(dh) + "/" + memory.read8(cl) + " |" + memory.read8(al)); + // dbg_log("=> ", h(memory.read16(es) * 16 + memory.read16(bx))); + //} + + + //if(interrupt_nr == 0x10) + //{ + // dbg_log("int10 ax=" + h(reg16[reg_ax], 4) + " '" + String.fromCharCode(reg8[reg_al]) + "'"); + // dump_regs_short(); + // if(reg8[reg_ah] == 0xe) vga.tt_write(reg8[reg_al]); + //} + + //dbg_log("int " + h(interrupt_nr)); + + //if(interrupt_nr === 0x13) + //{ + // dump_regs_short(); + //} + + //if(interrupt_nr === 0x80) + //{ + // dbg_log("linux syscall"); + // dump_regs_short(); + //} + + + if(interrupt_nr === 14) + { + dbg_log("int14 error_code=" + error_code + " cr2=" + h(cr2) + " prev=" + h(previous_ip) + " cpl=" + cpl, LOG_CPU); + } + + + if(in_hlt) + { + // return to the instruction following the hlt + instruction_pointer++; + in_hlt = false; + } + + if(protected_mode) + { + if((interrupt_nr << 3 | 7) > idtr_size) + { + dbg_log(interrupt_nr, LOG_CPU); + dbg_trace(); + throw unimpl("#GP handler"); + } + + + var addr = idtr_offset + (interrupt_nr << 3) | 0; + dbg_assert((addr & 0xFFF) < 0xFF8); + + if(paging) + { + addr = translate_address_system_read(addr); + } + + var base = memory.read16(addr) | memory.read16(addr + 6) << 16, + selector = memory.read16(addr + 2), + type = memory.read8(addr + 5), + dpl = type >> 5 & 3, + is_trap; + + if((type & 128) === 0) + { + // present bit not set + throw unimpl("#NP handler"); + } + + if(is_software_int && dpl < cpl) + { + trigger_gp(interrupt_nr << 3 | 2); + } + + type &= 31; + + if(type === 14) + { + is_trap = false; + } + else if(type === 15) + { + is_trap = true; + } + else if(type === 5) + { + throw unimpl("call int to task gate"); + } + else if(type === 6) + { + throw unimpl("16 bit interrupt gate"); + } + else if(type === 7) + { + throw unimpl("16 bit trap gate"); + } + else + { + // invalid type + dbg_trace(); + dbg_log("invalid type: " + h(type)); + dbg_log(h(addr) + " " + h(base) + " " + h(selector)); + throw unimpl("#GP handler"); + } + + var info = lookup_segment_selector(selector); + + if(info.is_null) + { + dbg_log("is null"); + throw unimpl("#GP handler"); + } + if(info === -1) + { + dbg_log("is -1"); + throw unimpl("#GP handler"); + } + if(!info.is_executable || info.dpl > cpl) + { + dbg_log("not exec"); + throw unimpl("#GP handler"); + } + if(!info.is_present) + { + dbg_log("not present"); + throw unimpl("#NP handler"); + } + + if(flags & flag_vm) + { + throw unimpl("VM flag"); + } + + + if(!info.dc_bit && info.dpl < cpl) + { + // inter privilege level interrupt + + var tss_stack_addr = (info.dpl << 3) + 4; + + if(tss_stack_addr + 5 > tsr_size) + { + throw unimpl("#TS handler"); + } + + tss_stack_addr += tsr_offset; + + if(paging) + { + tss_stack_addr = translate_address_system_read(tss_stack_addr); + } + + var new_esp = memory.read32s(tss_stack_addr), + new_ss = memory.read16(tss_stack_addr + 4), + ss_info = lookup_segment_selector(new_ss); + + if(ss_info.is_null) + { + throw unimpl("#TS handler"); + } + if(ss_info.rpl !== info.dpl) + { + throw unimpl("#TS handler"); + } + if(ss_info.dpl !== info.dpl || !ss_info.rw_bit) + { + throw unimpl("#TS handler"); + } + if(!ss_info.is_present) + { + throw unimpl("#TS handler"); + } + + var old_esp = reg32s[reg_esp], + old_ss = sreg[reg_ss]; + + reg32[reg_esp] = new_esp; + sreg[reg_ss] = new_ss; + + cpl = info.dpl; + //dbg_log("int" + h(interrupt_nr, 2) +" from=" + h(instruction_pointer, 8) + // + " cpl=" + cpl + " old ss:esp=" + h(old_ss,4) + ":" + h(old_esp,8), LOG_CPU); + + cpl_changed(); + + push32(old_ss); + push32(old_esp); + } + else if(info.dc_bit || info.dpl === cpl) + { + // intra privilege level interrupt + + //dbg_log("int" + h(interrupt_nr, 2) +" from=" + h(instruction_pointer, 8), LOG_CPU); + } + + + load_flags(); + push32(flags); + + push32(sreg[reg_cs]); + push32(get_real_ip()); + //dbg_log("pushed eip to " + h(reg32[reg_esp], 8), LOG_CPU); + + + if(error_code !== false) + { + dbg_assert(typeof error_code == "number"); + push32(error_code); + } + + + // TODO + sreg[reg_cs] = selector; + //switch_seg(reg_cs); + + //dbg_log("current esp: " + h(reg32[reg_esp]), LOG_CPU); + //dbg_log("call int " + h(interrupt_nr) + " from " + h(instruction_pointer) + " to " + h(base) + " with error_code=" + error_code, LOG_CPU); + + instruction_pointer = get_seg(reg_cs) + base | 0; + + //dbg_log("int" + h(interrupt_nr) + " trap=" + is_trap + " if=" + +!!(flags & flag_interrupt)); + + if(!is_trap) + { + // clear int flag for interrupt gates + flags &= ~flag_interrupt; + } + else + { + handle_irqs(); + } + } + else + { + // call 4 byte cs:ip interrupt vector from ivt at memory 0 + + //logop(instruction_pointer, "callu " + h(interrupt_nr) + "." + h(memory.read8(ah))); + //dbg_log("callu " + h(interrupt_nr) + "." + h(memory.read8(ah)) + " at " + h(instruction_pointer, 8), LOG_CPU, LOG_CPU); + + // push flags, cs:ip + load_flags(); + push16(flags); + push16(sreg[reg_cs]); + push16(get_real_ip()); + + flags = flags & ~flag_interrupt; + + switch_seg(reg_cs, memory.read16((interrupt_nr << 2) + 2)); + instruction_pointer = get_seg(reg_cs) + memory.read16(interrupt_nr << 2) | 0; + } +} + +// assumes ip to point to the byte before the next instruction +function raise_exception(interrupt_nr) +{ + if(DEBUG) + { + // warn about error + dbg_log("Exception " + h(interrupt_nr), LOG_CPU); + dbg_trace(); + //throw "exception: " + interrupt_nr; + } + + + // TODO + + call_interrupt_vector(interrupt_nr, false, false); + throw 0xDEADBEE; +} + +function raise_exception_with_code(interrupt_nr, error_code) +{ + if(DEBUG) + { + dbg_log("Exception " + h(interrupt_nr) + " err=" + h(error_code), LOG_CPU); + dbg_trace(); + //throw "exception: " + interrupt_nr; + } + + call_interrupt_vector(interrupt_nr, false, error_code); + throw 0xDEADBEE; +} + +function trigger_de() +{ + instruction_pointer = previous_ip; + raise_exception(0); +} + +function trigger_ud() +{ + instruction_pointer = previous_ip; + raise_exception(6); +} + +function trigger_gp(code) +{ + instruction_pointer = previous_ip; + raise_exception_with_code(13, code); +} + +function trigger_np(code) +{ + instruction_pointer = previous_ip; + raise_exception_with_code(11, code); +} + +function trigger_ss(code) +{ + instruction_pointer = previous_ip; + raise_exception_with_code(12, code); +} + + +/** + * @param {number} seg + */ +function seg_prefix(seg) +{ + dbg_assert(segment_prefix === -1); + dbg_assert(seg >= 0 && seg <= 5); + + segment_prefix = seg; + table[read_imm8()](); + segment_prefix = -1; +} + +/** + * Get segment base by prefix or default + * @param {number} default_segment + */ +function get_seg_prefix(default_segment /*, offset*/) +{ + if(segment_prefix === -1) + { + return get_seg(default_segment /*, offset*/); + } + else + { + return get_seg(segment_prefix /*, offset*/); + } +} + +/** + * Get segment base + * @param {number} segment + */ +function get_seg(segment /*, offset*/) +{ + dbg_assert(segment >= 0 && segment < 8); + dbg_assert(protected_mode || (sreg[segment] << 4) == segment_offsets[segment]); + + if(protected_mode) + { + if(segment_is_null[segment]) + { + // trying to access null segment + if(DEBUG) + { + dbg_log("Load null segment: " + h(segment), LOG_CPU); + throw unimpl("#GP handler"); + } + } + + // TODO: + // - validate segment limits + // - validate if segment is writable + // - set accessed bit + } + + return segment_offsets[segment]; +} + +function arpl(seg, r16) +{ + flags_changed &= ~flag_zero; + + if((seg & 3) < (reg16[r16] & 3)) + { + flags |= flag_zero; + return seg & ~3 | reg16[r16] & 3; + } + else + { + flags &= ~flag_zero; + return seg; + } +} + + +function handle_irqs() +{ + if(pic) + { + if((flags & flag_interrupt) && !page_fault) + { + pic.handle_irqs(); + } + } +} + +// any two consecutive 8-bit ports can be treated as a 16-bit port; +// and four consecutive 8-bit ports can be treated as a 32-bit port +// +// http://css.csail.mit.edu/6.858/2012/readings/i386/s08_01.htm + +function out8(port_addr, out_byte) +{ + if(privileges_for_io()) + { + io.port_write(port_addr, out_byte); + } + else + { + trigger_gp(0); + } +} + +function out16(port_addr, out_word) +{ + if(privileges_for_io()) + { + io.port_write(port_addr, out_word & 0xFF); + io.port_write(port_addr + 1, out_word >> 8 & 0xFF); + } + else + { + trigger_gp(0); + } +} + +function out32(port_addr, out_dword) +{ + if(privileges_for_io()) + { + io.port_write(port_addr, out_dword & 0xFF); + io.port_write(port_addr + 1, out_dword >> 8 & 0xFF); + io.port_write(port_addr + 2, out_dword >> 16 & 0xFF); + io.port_write(port_addr + 3, out_dword >> 24 & 0xFF); + } + else + { + trigger_gp(0); + } +} + +function in8(port_addr) +{ + if(privileges_for_io()) + { + return io.port_read(port_addr); + } + else + { + trigger_gp(0); + } +} + +function in16(port_addr) +{ + if(privileges_for_io()) + { + return io.port_read(port_addr) | + io.port_read(port_addr + 1) << 8; + } + else + { + trigger_gp(0); + } +} + +function in32(port_addr) +{ + if(privileges_for_io()) + { + return io.port_read(port_addr) | + io.port_read(port_addr + 1) << 8 | + io.port_read(port_addr + 2) << 16 | + io.port_read(port_addr + 3) << 24; + } + else + { + trigger_gp(0); + } +} + + +/** + * returns the current iopl from the eflags register + */ +function getiopl() +{ + return flags >> 12 & 3; +} + +function privileges_for_io() +{ + return !protected_mode || cpl <= getiopl(); +} + +function cpuid() +{ + // cpuid + // TODO: Fill in with less bogus values + + // http://lxr.linux.no/linux+%2a/arch/x86/include/asm/cpufeature.h + + var id = reg32s[reg_eax]; + + if((id & 0x7FFFFFFF) === 0) + { + reg32[reg_eax] = 2; + + if(id === 0) + { + reg32[reg_ebx] = 0x756E6547; // Genu + reg32[reg_edx] = 0x49656E69; // ineI + reg32[reg_ecx] = 0x6C65746E; // ntel + } + } + else if(id === 1) + { + // pentium + reg32[reg_eax] = 0x513; + reg32[reg_ebx] = 0; + reg32[reg_ecx] = 0; + reg32[reg_edx] = fpu.is_fpu | 1 << 3 | 1 << 4 | 1 << 8| 1 << 13 | 1 << 15; + } + else if(id === 2) + { + // Taken from http://siyobik.info.gf/main/reference/instruction/CPUID + reg32[reg_eax] = 0x665B5001; + reg32[reg_ebx] = 0; + reg32[reg_ecx] = 0; + reg32[reg_edx] = 0x007A7000; + } + else if(id === (0x80860000 | 0)) + { + reg32[reg_eax] = 0; + reg32[reg_ebx] = 0; + reg32[reg_ecx] = 0; + reg32[reg_edx] = 0; + } + else if((id & 0xF0000000) === ~~0x40000000) + { + // Invalid + } + else + { + if(DEBUG) throw "cpuid: unimplemented eax: " + h(id); + } +} + +/** + * Update the flags register depending on iopl and cpl + */ +function update_flags(new_flags) +{ + if(cpl === 0 || !protected_mode) + { + // can update all flags + flags = new_flags; + } + else if(cpl <= getiopl()) + { + // cpl != 0 and iopl <= cpl + // can update interrupt flag but not iopl + flags = (new_flags & ~flag_iopl) | (flags & flag_iopl); + } + else + { + // cannot update interrupt flag or iopl + flags = (new_flags & ~flag_iopl & ~flag_interrupt) | (flags & (flag_iopl | flag_interrupt)); + } + + flags_changed = 0; + //flags = (flags & flags_mask) | flags_default; +} + +function update_operand_size() +{ + if(operand_size_32) + { + table = table32; + table0F = table0F_32; + } + else + { + table = table16; + table0F = table0F_16; + } +} + +function update_address_size() +{ + if(address_size_32) + { + modrm_resolve = modrm_resolve32; + + regv = reg32; + reg_vcx = reg_ecx; + reg_vsi = reg_esi; + reg_vdi = reg_edi; + } + else + { + modrm_resolve = modrm_resolve16; + + regv = reg16; + reg_vcx = reg_cx; + reg_vsi = reg_si; + reg_vdi = reg_di; + } +} + +/** + * @param {number} selector + */ +function lookup_segment_selector(selector) +{ + var is_gdt = (selector & 4) === 0, + selector_offset = selector & ~7, + info, + table_offset, + table_limit; + + info = { + rpl: selector & 3, + from_gdt: is_gdt, + is_null: false, + is_valid: true, + }; + + if(is_gdt) + { + table_offset = gdtr_offset; + table_limit = gdtr_size; + } + else + { + table_offset = ldtr_offset + table_limit = ldtr_size; + } + + if(selector_offset === 0) + { + info.is_null = true; + return info; + } + + // limit is the number of entries in the table minus one + if((selector_offset >> 3) > table_limit) + { + info.is_valid = false; + return info; + } + + table_offset += selector_offset; + + if(paging) + { + table_offset = translate_address_system_read(table_offset); + } + + info.base = memory.read16(table_offset + 2) | memory.read8(table_offset + 4) << 16 | + memory.read8(table_offset + 7) << 24, + info.access = memory.read8(table_offset + 5), + info.flags = memory.read8(table_offset + 6) >> 4, + info.limit = memory.read16(table_offset) | (memory.read8(table_offset + 6) & 0xF) << 16, + + // used if system + info.type = info.access & 0xF; + + info.dpl = info.access >> 5 & 3; + + info.is_system = (info.access & 0x10) === 0; + info.is_present = (info.access & 0x80) === 0x80; + info.is_executable = (info.access & 8) === 8; + + info.rw_bit = (info.access & 2) === 2; + info.dc_bit = (info.access & 4) === 4; + + info.size = (info.flags & 4) === 4; + info.granularity = (info.flags & 8) === 8; + + + if(info.gr_bit) + { + info.real_limit = (info.limit << 12 | 0xFFF) >>> 0; + } + else + { + info.real_limit = info.limit; + } + + info.is_writable = info.rw_bit && !info.is_executable; + info.is_readable = info.rw_bit || !info.is_executable; + + return info; +} + +/** + * @param {number} reg + * @param {number} selector + */ +function switch_seg(reg, selector) +{ + dbg_assert(reg >= 0 && reg <= 5); + dbg_assert(typeof selector === "number" && selector < 0x10000 && selector >= 0); + + if(reg === reg_cs) + { + protected_mode = (cr0 & 1) === 1; + } + + if(!protected_mode) + { + sreg[reg] = selector; + segment_is_null[reg] = 0; + segment_limits[reg] = 0xFFFFF; + segment_offsets[reg] = selector << 4; + return; + } + + var info = lookup_segment_selector(selector); + + if(reg === reg_ss) + { + if(info.is_null) + { + trigger_gp(0); + return false; + } + if(!info.is_valid || + info.is_system || + info.rpl !== cpl || + !info.is_writable || + info.dpl !== cpl) + { + trigger_gp(selector & ~3); + return false; + } + if(!info.is_present) + { + trigger_ss(selector & ~3); + return false; + } + + stack_size_32 = info.size; + + if(info.size) + { + stack_reg = reg32s; + reg_vsp = reg_esp; + reg_vbp = reg_ebp; + } + else + { + stack_reg = reg16; + reg_vsp = reg_sp; + reg_vbp = reg_bp; + } + } + else if(reg === reg_cs) + { + if(!info.is_executable) + { + // cs not executable + dbg_log(info + " " + h(selector & ~3), LOG_CPU); + throw unimpl("#GP handler"); + } + + if(info.is_system) + { + dbg_log(info + " " + h(selector & ~3), LOG_CPU); + throw unimpl("load system segment descriptor, type = " + (info.access & 15)); + } + + if(info.dc_bit && (info.dpl !== info.rpl)) + { + dbg_log(info + " " + h(selector & ~3), LOG_CPU); + throw unimpl("#GP handler"); + } + + if(info.rpl !== cpl) + { + dbg_log(info + " " + h(selector & ~3), LOG_CPU); + throw unimpl("privilege change"); + } + + dbg_assert(cpl === info.dpl); + + if(!info.dc_bit && info.dpl < cpl) + { + throw unimpl("inter privilege interrupt"); + } + else + { + if(info.dc_bit || info.dpl === cpl) + { + // ok + } + else + { + // PE = 1, interrupt or trap gate, nonconforming code segment, DPL > CPL + dbg_log(info + " " + h(selector & ~3), LOG_CPU); + throw unimpl("#GP handler"); + } + } + + + operand_size_32 = address_size_32 = is_32 = info.size; + + update_operand_size(); + update_address_size(); + } + else + { + // es, ds, fs, gs + if(info.is_null) + { + sreg[reg] = selector; + segment_is_null[reg] = 1; + return true; + } + if(!info.is_valid || + info.is_system || + !info.is_readable || + ((!info.is_executable || !info.dc_bit) && + info.rpl > info.dpl && + cpl > info.dpl)) + { + trigger_gp(selector & ~3); + return false; + } + if(!info.is_present) + { + trigger_np(selector & ~3); + return false; + } + } + + //dbg_log("seg " + reg + " " + h(info.base)); + + segment_is_null[reg] = 0; + segment_limits[reg] = info.real_limit; + segment_infos[reg] = 0; // TODO + + segment_offsets[reg] = info.base; + + sreg[reg] = selector; + + return true; +} + +function load_tr(selector) +{ + var info = lookup_segment_selector(selector); + + //dbg_log("load tr"); + + if(!info.from_gdt) + { + throw unimpl("TR can only be loaded from GDT"); + } + + if(info.is_null) + { + dbg_log("#GP(0) | tried to load null selector (ltr)"); + throw unimpl("#GP handler"); + } + + if(!info.is_present) + { + dbg_log("#GP | present bit not set (ltr)"); + throw unimpl("#GP handler"); + } + + if(!info.is_system) + { + dbg_log("#GP | ltr: not a system entry"); + throw unimpl("#GP handler"); + } + + if(info.type !== 9) + { + dbg_log("#GP | ltr: invalid type (type = " + info.type + ")"); + throw unimpl("#GP handler"); + } + + tsr_size = info.limit; + tsr_offset = info.base; + + //dbg_log("tsr at " + h(tsr_offset) + "; (" + tsr_size + " bytes)"); +} + +function load_ldt(selector) +{ + var info = lookup_segment_selector(selector); + + if(info.is_null) + { + // invalid + ldtr_size = 0; + ldtr_offset = 0; + return; + } + + if(!info.from_gdt) + { + throw unimpl("LDTR can only be loaded from GDT"); + } + + if(!info.is_present) + { + dbg_log("lldt: present bit not set"); + throw unimpl("#GP handler"); + } + + if(!info.is_system) + { + dbg_log("lldt: not a system entry"); + throw unimpl("#GP handler"); + } + + if(info.type !== 2) + { + dbg_log("lldt: invalid type (" + info.type + ")"); + throw unimpl("#GP handler"); + } + + ldtr_size = info.limit; + ldtr_offset = info.base; + + //dbg_log("ldt at " + h(ldtr_offset) + "; (" + ldtr_size + " bytes)"); +} + + + +function clear_tlb() +{ + // clear tlb excluding global pages + last_virt_eip = -1; + last_virt_esp = -1; + + tlb_info.set(tlb_info_global); + + //dbg_log("page table loaded", LOG_CPU); +} + +function full_clear_tlb() +{ + // clear tlb including global pages + tlb_info_global = new Uint8Array(1 << 20); + + clear_tlb(); + +} + +function invlpg(addr) +{ + var page = addr >>> 12; + dbg_log("invlpg: " + h(page), LOG_CPU); + + tlb_info[page] = 0; + tlb_info_global[page] = 0; + + last_virt_eip = -1; + last_virt_esp = -1; +} + +/** + * @param {number} addr + */ +function translate_address_disabled(addr) +{ + return addr; +} + +function translate_address_user_write(addr) +{ + var base = addr >>> 12; + + if(tlb_info[base] & TLB_USER_WRITE) + { + return tlb_user_write[base] ^ addr; + } + else + { + return do_page_translation(addr, 1, 1) | addr & 0xFFF; + } +} + +function translate_address_user_read(addr) +{ + var base = addr >>> 12; + + if(tlb_info[base] & TLB_USER_READ) + { + return tlb_user_read[base] ^ addr; + } + else + { + return do_page_translation(addr, 0, 1) | addr & 0xFFF; + } +} + +function translate_address_system_write(addr) +{ + var base = addr >>> 12; + + if(tlb_info[base] & TLB_SYSTEM_WRITE) + { + return tlb_system_write[base] ^ addr; + } + else + { + return do_page_translation(addr, 1, 0) | addr & 0xFFF; + } +} + +function translate_address_system_read(addr) +{ + var base = addr >>> 12; + + if(tlb_info[base] & TLB_SYSTEM_READ) + { + return tlb_system_read[base] ^ addr; + } + else + { + return do_page_translation(addr, 0, 0) | addr & 0xFFF; + } +} + +/** + * @return {number} + */ +function do_page_translation(addr, for_writing, user) +{ + var page = addr >>> 12, + page_dir_addr = (cr3 >>> 2) + (page >> 10), + page_dir_entry = memory.mem32s[page_dir_addr], + high, + can_write = true, + global, + cachable = true, + allow_user = true; + + if(!(page_dir_entry & 1)) + { + // to do at this place: + // + // - set cr2 = addr (which caused the page fault) + // - call_interrupt_vector with id 14, error code 0-7 (requires information if read or write) + // - prevent execution of the function that triggered this call + dbg_log("#PF not present", LOG_CPU); + + cr2 = addr; + trigger_pagefault(for_writing, user, 0); + + // never reached as trigger_pagefault throws up + dbg_assert(false); + } + + if((page_dir_entry & 2) === 0) + { + can_write = false; + + if(for_writing) + { + cr2 = addr; + trigger_pagefault(for_writing, user, 1); + dbg_assert(false); + } + } + + if((page_dir_entry & 4) === 0) + { + allow_user = false; + + if(user) + { + // "Page Fault: page table accessed by non-supervisor"; + dbg_log("#PF supervisor", LOG_CPU); + cr2 = addr; + trigger_pagefault(for_writing, user, 1); + dbg_assert(false); + } + } + + if((page_dir_entry & 0x10) === 0) + { + cachable = false; + } + + if(page_dir_entry & page_size_extensions) + { + // size bit is set + + // set the accessed and dirty bits + memory.mem32s[page_dir_addr] = page_dir_entry | 0x20 | for_writing << 6; + + high = (page_dir_entry & 0xFFC00000) | (page << 12 & 0x3FF000); + + global = page_dir_entry & 0x100; + } + else + { + var page_table_addr = ((page_dir_entry & 0xFFFFF000) >>> 2) + (page & 0x3FF), + page_table_entry = memory.mem32s[page_table_addr]; + + if(!(page_table_entry & 1)) + { + dbg_log("#PF not present table", LOG_CPU); + cr2 = addr; + trigger_pagefault(for_writing, user, 0); + dbg_assert(false); + } + + if((page_table_entry & 2) === 0) + { + can_write = false; + + if(for_writing) + { + dbg_log("#PF not writable page", LOG_CPU); + cr2 = addr; + trigger_pagefault(for_writing, user, 1); + dbg_assert(false); + } + } + + if((page_table_entry & 4) === 0) + { + allow_user = false; + + if(user) + { + dbg_log("#PF not supervisor page", LOG_CPU); + cr2 = addr; + trigger_pagefault(for_writing, user, 1); + dbg_assert(false); + } + } + + if((page_table_entry & 0x10) === 0) + { + cachable = false; + } + + // set the accessed and dirty bits + memory.mem32s[page_dir_addr] = page_dir_entry | 0x20; + memory.mem32s[page_table_addr] = page_table_entry | 0x20 | for_writing << 6; + + high = page_table_entry & 0xFFFFF000; + + global = page_table_entry & 0x100; + } + + if(cachable) + { + var cache_entry = high ^ page << 12, + info = 0; + + if(allow_user) + { + tlb_user_read[page] = cache_entry; + info |= TLB_USER_READ; + + if(can_write) + { + tlb_user_write[page] = cache_entry; + info |= TLB_USER_WRITE; + } + } + + tlb_system_read[page] = cache_entry; + info |= TLB_SYSTEM_READ; + + if(can_write) + { + tlb_system_write[page] = cache_entry; + info |= TLB_SYSTEM_WRITE; + } + + tlb_info[page] |= info; + + if(global) + { + tlb_info_global[page] = info; + } + } + + return high ; +} + +function trigger_pagefault(write, user, present) +{ + if(LOG_LEVEL & LOG_CPU) + { + dbg_trace(); + } + + if(page_fault) + { + dbg_trace(); + throw unimpl("Double fault"); + } + + instruction_pointer = previous_ip; + page_fault = true; + call_interrupt_vector(14, false, user << 2 | write << 1 | present); + + throw 0xDEADBEE; +} + + +// it looks pointless to have these two here, but +// Closure Compiler is able to remove unused functions +//#include "test_helpers.js" +#include "debug.macro.js" + + +#include "modrm.macro.js" +#include "arith.macro.js" +#include "misc_instr.macro.js" +#include "string.macro.js" + +#include "fpu.macro.js" + +#include "instructions.macro.js" + + +}