Browse Source

port pic to rust (XXX: missing state)

Fabian 6 months ago
parent
commit
24eb27bf61
13 changed files with 536 additions and 188 deletions
  1. 1 1
      Makefile
  2. 1 1
      debug.html
  3. 6 4
      src/browser/print_stats.js
  4. 6 5
      src/browser/starter.js
  5. 0 7
      src/config.js
  6. 44 131
      src/cpu.js
  7. 1 1
      src/main.js
  8. 52 27
      src/rust/cpu/cpu.rs
  9. 13 5
      src/rust/cpu/instructions.rs
  10. 1 0
      src/rust/cpu/mod.rs
  11. 395 0
      src/rust/cpu/pic.rs
  12. 14 6
      src/rust/jit.rs
  13. 2 0
      src/rust/profiler.rs

+ 1 - 1
Makefile

@@ -79,7 +79,7 @@ CARGO_FLAGS_SAFE=\
 CARGO_FLAGS=$(CARGO_FLAGS_SAFE) -C target-feature=+bulk-memory -C target-feature=+multivalue -C target-feature=+simd128
 
 CORE_FILES=const.js config.js io.js main.js lib.js buffer.js ide.js pci.js floppy.js \
-	   memory.js dma.js pit.js vga.js ps2.js pic.js rtc.js uart.js hpet.js \
+	   memory.js dma.js pit.js vga.js ps2.js rtc.js uart.js hpet.js \
 	   acpi.js apic.js ioapic.js \
 	   state.js ne2k.js sb16.js virtio.js bus.js log.js \
 	   cpu.js debug.js \

+ 1 - 1
debug.html

@@ -10,7 +10,7 @@
 var CORE_FILES =
     "const.js config.js log.js lib.js buffer.js cpu.js debug.js " +
     "io.js main.js ide.js pci.js floppy.js " +
-    "memory.js dma.js pit.js vga.js ps2.js pic.js rtc.js uart.js acpi.js apic.js ioapic.js hpet.js sb16.js " +
+    "memory.js dma.js pit.js vga.js ps2.js rtc.js uart.js acpi.js apic.js ioapic.js hpet.js sb16.js " +
     "ne2k.js state.js virtio.js bus.js elf.js kernel.js";
 
 var BROWSER_FILES = "main.js screen.js keyboard.js mouse.js speaker.js serial.js network.js starter.js worker_bus.js print_stats.js filestorage.js";

+ 6 - 4
src/browser/print_stats.js

@@ -89,6 +89,8 @@ const print_stats = {
             "SAFE_READ_WRITE_SLOW_HAS_CODE",
             "PAGE_FAULT",
             "TLB_MISS",
+            "MAIN_LOOP",
+            "MAIN_LOOP_IDLE",
             "DO_MANY_CYCLES",
             "CYCLE_INTERNAL",
             "INVALIDATE_ALL_MODULES_NO_FREE_WASM_INDICES",
@@ -148,13 +150,13 @@ const print_stats = {
         text += "JIT_CACHE_SIZE=" + cpu.wm.exports["jit_get_cache_size"]() + "\n";
         text += "FLAT_SEGMENTS=" + cpu.wm.exports["has_flat_segmentation"]() + "\n";
 
-        text += "do_many_cycles avg: " + (cpu.do_many_cycles_total / cpu.do_many_cycles_count || 0) + "\n";
         text += "wasm memory size: " + (cpu.wasm_memory.buffer.byteLength >> 20) + "m\n";
 
         text += "Config:\n";
-        text += "MAX_PAGES=" + cpu.wm.exports["get_jit_config"](0) + "\n";
-        text += "JIT_USE_LOOP_SAFETY=" + Boolean(cpu.wm.exports["get_jit_config"](1)) + "\n";
-        text += "MAX_EXTRA_BASIC_BLOCKS=" + cpu.wm.exports["get_jit_config"](2) + "\n";
+        text += "JIT_DISABLED=" + cpu.wm.exports["get_jit_config"](0) + "\n";
+        text += "MAX_PAGES=" + cpu.wm.exports["get_jit_config"](1) + "\n";
+        text += "JIT_USE_LOOP_SAFETY=" + Boolean(cpu.wm.exports["get_jit_config"](2)) + "\n";
+        text += "MAX_EXTRA_BASIC_BLOCKS=" + cpu.wm.exports["get_jit_config"](3) + "\n";
 
         return text;
     },

+ 6 - 5
src/browser/starter.js

@@ -109,12 +109,12 @@ function V86(options)
 
     const wasm_shared_funcs = {
         "cpu_exception_hook": n => this.cpu_exception_hook(n),
-        "hlt_op": function() { return cpu.hlt_op(); },
+        "run_hardware_timers": function(t) { return cpu.run_hardware_timers(t); },
+        "cpu_event_halt": () => { this.emulator_bus.send("cpu-event-halt"); },
         "abort": function() { dbg_assert(false); },
         "microtick": v86.microtick,
         "get_rand_int": function() { return v86util.get_rand_int(); },
-
-        "pic_acknowledge": function() { cpu.pic_acknowledge(); },
+        "apic_acknowledge_irq": function() { cpu.devices.apic.acknowledge_irq(); },
 
         "io_port_read8": function(addr) { return cpu.io.port_read8(addr); },
         "io_port_read16": function(addr) { return cpu.io.port_read16(addr); },
@@ -651,8 +651,9 @@ V86.prototype.zstd_decompress_worker = async function(decompressed_size, src)
                 if(!wasm)
                 {
                     const env = Object.fromEntries([
-                        "cpu_exception_hook", "hlt_op",
-                        "microtick", "get_rand_int", "pic_acknowledge",
+                        "cpu_exception_hook", "run_hardware_timers",
+                        "cpu_event_halt", "microtick", "get_rand_int",
+                        "apic_acknowledge_irq",
                         "io_port_read8", "io_port_read16", "io_port_read32",
                         "io_port_write8", "io_port_write16", "io_port_write32",
                         "mmap_read8", "mmap_read16", "mmap_read32",

+ 0 - 7
src/config.js

@@ -47,13 +47,6 @@ var DEBUG_SCREEN_LAYERS = DEBUG && false;
 /** @const */
 var ENABLE_HPET = DEBUG && false;
 
-/**
- * @const
- * How often, in milliseconds, to yield to the browser for rendering and
- * running events
- */
-var TIME_PER_FRAME = 1;
-
 /**
  * @const
  * How many ticks the TSC does per millisecond

+ 44 - 131
src/cpu.js

@@ -174,9 +174,6 @@ function CPU(bus, wm, next_tick_immediately)
 
     if(DEBUG)
     {
-        this.do_many_cycles_count = 0;
-        this.do_many_cycles_total = 0;
-
         this.seen_code = {};
         this.seen_code_uncompiled = {};
     }
@@ -213,11 +210,9 @@ CPU.prototype.create_jit_imports = function()
 
 CPU.prototype.wasm_patch = function()
 {
-    const get_optional_import = (name) => {
-        return this.wm.exports[name];
-    };
+    const get_optional_import = name => this.wm.exports[name];
 
-    const get_import = (name) =>
+    const get_import = name =>
     {
         const f = get_optional_import(name);
         console.assert(f, "Missing import: " + name);
@@ -228,12 +223,13 @@ CPU.prototype.wasm_patch = function()
 
     this.getiopl = get_import("getiopl");
     this.get_eflags = get_import("get_eflags");
-    this.get_eflags_no_arith = get_import("get_eflags_no_arith");
 
+    this.handle_irqs = get_import("handle_irqs");
     this.pic_call_irq = get_import("pic_call_irq");
 
-    this.do_many_cycles_native = get_import("do_many_cycles_native");
-    this.do_many_cycles_native_nojit = get_import("do_many_cycles_native_nojit");
+    this.main_loop = get_import("main_loop");
+
+    this.set_jit_config = get_import("set_jit_config");
 
     this.read8 = get_import("read8");
     this.read16 = get_import("read16");
@@ -262,6 +258,9 @@ CPU.prototype.wasm_patch = function()
 
     this.set_cpuid_level = get_import("set_cpuid_level");
 
+    this.pic_set_irq = get_import("pic_set_irq");
+    this.pic_clear_irq = get_import("pic_clear_irq");
+
     if(DEBUG)
     {
         this.jit_force_generate_unsafe = get_optional_import("jit_force_generate_unsafe");
@@ -284,6 +283,21 @@ CPU.prototype.wasm_patch = function()
     this.zstd_free_ctx = get_import("zstd_free_ctx");
     this.zstd_read = get_import("zstd_read");
     this.zstd_read_free = get_import("zstd_read_free");
+
+    this.port20_read = get_import("port20_read");
+    this.port21_read = get_import("port21_read");
+    this.portA0_read = get_import("portA0_read");
+    this.portA1_read = get_import("portA1_read");
+
+    this.port20_write = get_import("port20_write");
+    this.port21_write = get_import("port21_write");
+    this.portA0_write = get_import("portA0_write");
+    this.portA1_write = get_import("portA1_write");
+
+    this.port4D0_read = get_import("port4D0_read");
+    this.port4D1_read = get_import("port4D1_read");
+    this.port4D0_write = get_import("port4D0_write");
+    this.port4D1_write = get_import("port4D1_write");
 };
 
 CPU.prototype.jit_force_generate = function(addr)
@@ -372,7 +386,7 @@ CPU.prototype.get_state = function()
     state[57] = this.devices.hda;
     state[58] = this.devices.pit;
     state[59] = this.devices.net;
-    state[60] = this.devices.pic;
+    //state[60] = this.devices.pic;
     state[61] = this.devices.sb16;
 
     state[62] = this.fw_value;
@@ -468,7 +482,7 @@ CPU.prototype.set_state = function(state)
     this.devices.hda && this.devices.hda.set_state(state[57]);
     this.devices.pit && this.devices.pit.set_state(state[58]);
     this.devices.net && this.devices.net.set_state(state[59]);
-    this.devices.pic && this.devices.pic.set_state(state[60]);
+    //this.devices.pic && this.devices.pic.set_state(state[60]);
     this.devices.sb16 && this.devices.sb16.set_state(state[61]);
 
     this.devices.uart1 && this.devices.uart1.set_state(state[79]);
@@ -566,42 +580,6 @@ CPU.prototype.unpack_memory = function(bitmap, packed_memory)
     }
 };
 
-/**
- * @return {number} time in ms until this method should becalled again
- */
-CPU.prototype.main_run = function()
-{
-    if(this.in_hlt[0])
-    {
-        const t = this.hlt_loop();
-
-        if(this.in_hlt[0])
-        {
-            return t;
-        }
-    }
-
-    const start = v86.microtick();
-    let now = start;
-
-    for(; now - start < TIME_PER_FRAME;)
-    {
-        this.do_many_cycles();
-
-        now = v86.microtick();
-
-        const t = this.run_hardware_timers(now);
-        this.handle_irqs();
-
-        if(this.in_hlt[0])
-        {
-            return t;
-        }
-    }
-
-    return 0;
-};
-
 CPU.prototype.reboot_internal = function()
 {
     this.reset_cpu();
@@ -660,7 +638,7 @@ CPU.prototype.init = function(settings, device_bus)
 
     if(settings.disable_jit)
     {
-        this.do_many_cycles_native = this.do_many_cycles_native_nojit;
+        this.set_jit_config(0, 1);
     }
 
     settings.cpuid_level && this.set_cpuid_level(settings.cpuid_level);
@@ -819,12 +797,26 @@ CPU.prototype.init = function(settings, device_bus)
         io.register_write(0xE9, this, function(out_byte) {});
     }
 
+    io.register_read(0x20, this, this.port20_read);
+    io.register_read(0x21, this, this.port21_read);
+    io.register_read(0xA0, this, this.portA0_read);
+    io.register_read(0xA1, this, this.portA1_read);
+
+    io.register_write(0x20, this, this.port20_write);
+    io.register_write(0x21, this, this.port21_write);
+    io.register_write(0xA0, this, this.portA0_write);
+    io.register_write(0xA1, this, this.portA1_write);
+
+    io.register_read(0x4D0, this, this.port4D0_read);
+    io.register_read(0x4D1, this, this.port4D1_read);
+    io.register_write(0x4D0, this, this.port4D0_write);
+    io.register_write(0x4D1, this, this.port4D1_write);
+
     this.devices = {};
 
     // TODO: Make this more configurable
     if(settings.load_devices)
     {
-        this.devices.pic = new PIC(this);
         this.devices.pci = new PCI(this);
 
         if(this.acpi_enabled[0])
@@ -1224,22 +1216,6 @@ CPU.prototype.load_bios = function()
         }.bind(this));
 };
 
-CPU.prototype.do_many_cycles = function()
-{
-    if(DEBUG)
-    {
-        var start_time = v86.microtick();
-    }
-
-    this.do_many_cycles_native();
-
-    if(DEBUG)
-    {
-        this.do_many_cycles_total += v86.microtick() - start_time;
-        this.do_many_cycles_count++;
-    }
-};
-
 CPU.prototype.codegen_finalize = function(wasm_table_index, start, state_flags, ptr, len)
 {
     ptr >>>= 0;
@@ -1398,20 +1374,6 @@ CPU.prototype.dump_function_code = function(block_ptr, count)
     }
 };
 
-CPU.prototype.hlt_loop = function()
-{
-    if(this.get_eflags_no_arith() & FLAG_INTERRUPT)
-    {
-        const t = this.run_hardware_timers(v86.microtick());
-        this.handle_irqs();
-        return t;
-    }
-    else
-    {
-        return 100;
-    }
-};
-
 CPU.prototype.run_hardware_timers = function(now)
 {
     if(ENABLE_HPET)
@@ -1438,56 +1400,10 @@ CPU.prototype.run_hardware_timers = function(now)
     return Math.min(pit_time, rtc_time, hpet_time, acpi_time, apic_time);
 };
 
-CPU.prototype.hlt_op = function()
-{
-    if((this.get_eflags_no_arith() & FLAG_INTERRUPT) === 0)
-    {
-        // execution can never resume (until NMIs are supported)
-        this.bus.send("cpu-event-halt");
-    }
-
-    // get out of here and into hlt_loop
-    this.in_hlt[0] = +true;
-
-    // Try an hlt loop right now: This will run timer interrupts, and if one is
-    // due it will immediately call call_interrupt_vector and continue
-    // execution without an unnecessary cycle through do_run
-    this.hlt_loop();
-};
-
-CPU.prototype.handle_irqs = function()
-{
-    //dbg_assert(this.prefixes[0] === 0);
-
-    if(this.get_eflags_no_arith() & FLAG_INTERRUPT)
-    {
-        this.pic_acknowledge();
-        this.next_tick_immediately();
-    }
-};
-
-CPU.prototype.pic_acknowledge = function()
-{
-    dbg_assert(this.get_eflags_no_arith() & FLAG_INTERRUPT);
-
-    if(this.devices.pic)
-    {
-        this.devices.pic.acknowledge_irq();
-    }
-
-    if(this.devices.apic)
-    {
-        this.devices.apic.acknowledge_irq();
-    }
-};
-
 CPU.prototype.device_raise_irq = function(i)
 {
     dbg_assert(arguments.length === 1);
-    if(this.devices.pic)
-    {
-        this.devices.pic.set_irq(i);
-    }
+    this.pic_set_irq(i);
 
     if(this.devices.ioapic)
     {
@@ -1497,10 +1413,7 @@ CPU.prototype.device_raise_irq = function(i)
 
 CPU.prototype.device_lower_irq = function(i)
 {
-    if(this.devices.pic)
-    {
-        this.devices.pic.clear_irq(i);
-    }
+    this.pic_clear_irq(i);
 
     if(this.devices.ioapic)
     {

+ 1 - 1
src/main.js

@@ -50,7 +50,7 @@ v86.prototype.do_tick = function()
     }
 
     this.idle = false;
-    const t = this.cpu.main_run();
+    const t = this.cpu.main_loop();
 
     this.next_tick(t);
 };

+ 52 - 27
src/rust/cpu/cpu.rs

@@ -2,9 +2,11 @@
 
 extern "C" {
     fn cpu_exception_hook(interrupt: i32) -> bool;
-    fn microtick() -> f64;
     fn call_indirect1(f: i32, x: u16);
-    fn pic_acknowledge();
+    pub fn microtick() -> f64;
+    pub fn run_hardware_timers(t: f64) -> f64;
+    pub fn cpu_event_halt();
+    pub fn apic_acknowledge_irq();
 
     pub fn io_port_read8(port: i32) -> i32;
     pub fn io_port_read16(port: i32) -> i32;
@@ -26,6 +28,7 @@ use cpu::misc_instr::{
     push16, push32,
 };
 use cpu::modrm::{resolve_modrm16, resolve_modrm32};
+use cpu::pic;
 use jit;
 use jit::is_near_end_of_page;
 use page::Page;
@@ -63,6 +66,9 @@ pub const CHECK_MISSED_ENTRY_POINTS: bool = false;
 
 pub const INTERPRETER_ITERATION_LIMIT: u32 = 100_001;
 
+// How often, in milliseconds, to yield to the browser for rendering and running events
+pub const TIME_PER_FRAME: f64 = 1.0;
+
 pub const FLAG_SUB: i32 = -0x8000_0000;
 pub const FLAG_CARRY: i32 = 1;
 pub const FLAG_PARITY: i32 = 4;
@@ -727,10 +733,6 @@ pub unsafe fn call_interrupt_vector(
     is_software_int: bool,
     error_code: Option<i32>,
 ) {
-    // we have to leave hlt_loop at some point, this is a
-    // good place to do it
-    *in_hlt = false;
-
     if *protected_mode {
         if vm86_mode() && *cr.offset(4) & CR4_VME != 0 {
             panic!("Unimplemented: VME");
@@ -1756,9 +1758,6 @@ pub unsafe fn get_eflags() -> i32 {
         | (getof() as i32) << 11;
 }
 
-#[no_mangle]
-pub unsafe fn get_eflags_no_arith() -> i32 { return *flags; }
-
 pub unsafe fn readable_or_pagefault(addr: i32, size: i32) -> OrPageFault<()> {
     dbg_assert!(size < 0x1000);
     dbg_assert!(size > 0);
@@ -3024,29 +3023,51 @@ pub unsafe fn segment_prefix_op(seg: i32) {
 }
 
 #[no_mangle]
-pub unsafe fn do_many_cycles_native() {
-    profiler::stat_increment(DO_MANY_CYCLES);
-    let initial_instruction_counter = *instruction_counter;
-    while (*instruction_counter).wrapping_sub(initial_instruction_counter) < LOOP_COUNTER as u32
-        && !*in_hlt
-    {
-        cycle_internal();
+pub unsafe fn main_loop() -> f64 {
+    profiler::stat_increment(MAIN_LOOP);
+
+    let start = microtick();
+
+    if *in_hlt {
+        if *flags & FLAG_INTERRUPT != 0 {
+            let t = run_hardware_timers(start);
+            handle_irqs();
+            if *in_hlt {
+                profiler::stat_increment(MAIN_LOOP_IDLE);
+                return t;
+            }
+        }
+        else {
+            // dead
+            return 100.0;
+        }
     }
+
+    loop {
+        do_many_cycles_native();
+
+        let now = microtick();
+        let t = run_hardware_timers(now);
+        handle_irqs();
+        if *in_hlt {
+            return t;
+        }
+
+        if now - start > TIME_PER_FRAME {
+            break;
+        }
+    }
+
+    return 0.0;
 }
 
-#[no_mangle]
-pub unsafe fn do_many_cycles_native_nojit() {
+pub unsafe fn do_many_cycles_native() {
     profiler::stat_increment(DO_MANY_CYCLES);
     let initial_instruction_counter = *instruction_counter;
     while (*instruction_counter).wrapping_sub(initial_instruction_counter) < LOOP_COUNTER as u32
         && !*in_hlt
     {
-        *previous_ip = *instruction_pointer;
-        let opcode = return_on_pagefault!(read_imm8());
-        *instruction_counter += 1;
-        dbg_assert!(*prefixes == 0);
-        run_instruction(opcode | (*is_32 as i32) << 8);
-        dbg_assert!(*prefixes == 0);
+        cycle_internal();
     }
 }
 
@@ -4140,14 +4161,18 @@ pub unsafe fn store_current_tsc() { *current_tsc = read_tsc(); }
 #[no_mangle]
 pub unsafe fn handle_irqs() {
     if *flags & FLAG_INTERRUPT != 0 {
-        pic_acknowledge()
+        pic::pic_acknowledge_irq();
+        if *acpi_enabled {
+            apic_acknowledge_irq();
+        }
     }
 }
 
 #[no_mangle]
-pub unsafe fn pic_call_irq(interrupt_nr: i32) {
+pub unsafe fn pic_call_irq(interrupt_nr: u8) {
     *previous_ip = *instruction_pointer; // XXX: What if called after instruction (port IO)
-    call_interrupt_vector(interrupt_nr, false, None);
+    *in_hlt = false;
+    call_interrupt_vector(interrupt_nr as i32, false, None);
 }
 
 #[no_mangle]

+ 13 - 5
src/rust/cpu/instructions.rs

@@ -1,9 +1,5 @@
 #![allow(non_snake_case)]
 
-extern "C" {
-    fn hlt_op();
-}
-
 use cpu::arith::*;
 use cpu::cpu::*;
 use cpu::fpu::*;
@@ -2190,7 +2186,19 @@ pub unsafe fn instr_F4() {
         return;
     }
 
-    hlt_op();
+    *in_hlt = true;
+
+    // Try an hlt loop right now: This will run timer interrupts, and if one is
+    // due it will immediately call call_interrupt_vector and continue
+    // execution without an unnecessary cycle through do_run
+    if *flags & FLAG_INTERRUPT != 0 {
+        run_hardware_timers(microtick());
+        handle_irqs();
+    }
+    else {
+        // execution can never resume (until NMIs are supported)
+        cpu_event_halt();
+    }
 }
 #[no_mangle]
 pub unsafe fn instr_F5() {

+ 1 - 0
src/rust/cpu/mod.rs

@@ -8,6 +8,7 @@ pub mod instructions_0f;
 pub mod memory;
 pub mod misc_instr;
 pub mod modrm;
+pub mod pic;
 pub mod sse_instr;
 pub mod string;
 pub mod vga;

+ 395 - 0
src/rust/cpu/pic.rs

@@ -0,0 +1,395 @@
+#![allow(non_snake_case)]
+
+pub const PIC_LOG: bool = false;
+pub const PIC_LOG_VERBOSE: bool = false;
+use cpu::cpu;
+
+struct Pic {
+    irq_mask: u8,
+
+    irq_map: u8,
+
+    // in-service register
+    // Holds interrupts that are currently being serviced
+    isr: u8,
+
+    // interrupt request register
+    // Holds interrupts that have been requested
+    irr: u8,
+
+    irq_value: u8,
+
+    requested_irq: Option<u8>,
+
+    expect_icw4: bool,
+    state: u8,
+    read_isr: bool,
+    auto_eoi: bool,
+    special_mask_mode: bool,
+
+    elcr: u8,
+
+    master: bool,
+}
+
+#[allow(non_upper_case_globals)]
+static mut master: Pic = Pic {
+    // all irqs off
+    irq_mask: 0,
+    // Bogus default value (both master and slave mapped to 0).
+    // Will be initialized by the BIOS
+    irq_map: 0,
+    // in-service register
+    // Holds interrupts that are currently being serviced
+    isr: 0,
+    // interrupt request register
+    // Holds interrupts that have been requested
+    irr: 0,
+    irq_value: 0,
+    requested_irq: None,
+    expect_icw4: false,
+    state: 0,
+    read_isr: false,
+    auto_eoi: false,
+    special_mask_mode: false,
+    elcr: 0,
+    master: true,
+};
+
+#[allow(non_upper_case_globals)]
+static mut slave: Pic = Pic {
+    // all irqs off
+    irq_mask: 0,
+    // Bogus default value (both master and slave mapped to 0).
+    // Will be initialized by the BIOS
+    irq_map: 0,
+    // in-service register
+    // Holds interrupts that are currently being serviced
+    isr: 0,
+    // interrupt request register
+    // Holds interrupts that have been requested
+    irr: 0,
+    irq_value: 0,
+    requested_irq: None,
+    expect_icw4: false,
+    state: 0,
+    read_isr: false,
+    auto_eoi: false,
+    special_mask_mode: false,
+    elcr: 0,
+    master: false,
+};
+
+
+// Checking for callable interrupts:
+// (cpu changes interrupt flag) -> cpu.handle_irqs -> pic.check_irqs -> cpu.pic_call_irq
+// (pic changes isr/irr) -> cpu.handle_irqs -> ...
+
+// triggering irqs:
+// (io device has irq) -> cpu.device_raise_irq -> pic.set_irq -> cpu.handle_irqs -> (see above)
+
+// called by the cpu
+pub unsafe fn pic_acknowledge_irq() {
+    let irq = match master.requested_irq {
+        Some(i) => i,
+        None => return
+    };
+    master.requested_irq = None;
+
+    if master.irr == 0 {
+        //PIC_LOG_VERBOSE && dbg_log!("master> spurious requested=" + pic.requested_irq);
+        //pic.cpu.pic_call_irq(pic.irq_map | 7);
+        return;
+    }
+
+    let mask = 1 << irq;
+
+    if master.elcr & mask == 0  {
+        // not in level mode
+        master.irr &= !mask;
+    }
+
+    if !master.auto_eoi {
+        master.isr |= mask;
+    }
+
+    if PIC_LOG_VERBOSE {
+        dbg_log!("[PIC] master> acknowledge {}", irq);
+    }
+    if irq == 2 {
+        acknowledge_irq_slave();
+    }
+    else {
+        cpu::pic_call_irq(master.irq_map | irq);
+    }
+
+    check_irqs(&mut master);
+}
+
+unsafe fn acknowledge_irq_slave() {
+    let irq = match slave.requested_irq {
+        Some(i) => i,
+        None => return
+    };
+    slave.requested_irq = None;
+    master.irq_value &= !(1 << 2);
+
+    if slave.irr == 0 {
+        //PIC_LOG_VERBOSE && dbg_log!("slave> spurious requested=" + pic.requested_irq);
+        //pic.cpu.pic_call_irq(pic.irq_map | 7);
+        cpu::pic_call_irq(slave.irq_map | 7);
+        return;
+    }
+
+    let mask = 1 << irq;
+
+    if slave.elcr & mask == 0  {
+        // not in level mode
+        slave.irr &= !mask;
+    }
+
+    if !slave.auto_eoi {
+        slave.isr |= mask;
+    }
+
+    if PIC_LOG_VERBOSE {
+        dbg_log!("[PIC] slave> acknowledge {}", irq);
+    }
+    cpu::pic_call_irq(slave.irq_map | irq);
+
+    check_irqs(&mut slave);
+}
+
+unsafe fn check_irqs(pic: &mut Pic) {
+    if let Some(irq) = pic.requested_irq {
+        if PIC_LOG_VERBOSE {
+            dbg_log!("[PIC] Already requested irq: {}", irq);
+        }
+        cpu::handle_irqs();
+        return;
+    }
+
+    let enabled_irr = pic.irr & pic.irq_mask;
+
+    if enabled_irr == 0 {
+        if PIC_LOG_VERBOSE {
+            dbg_log!("master> no unmasked irrs. irr={:x} mask={:x} isr={:x}", pic.irr, pic.irq_mask, pic.isr);
+        }
+        return;
+    }
+
+    let irq_mask = enabled_irr & (!enabled_irr + 1);
+    let special_mask = if pic.special_mask_mode { pic.irq_mask } else { 0xFF };
+
+    if pic.isr != 0 && (pic.isr & (!pic.isr + 1) & special_mask) <= irq_mask {
+        // wait for eoi of higher or same priority interrupt
+        if PIC_LOG {
+            dbg_log!("[PIC] higher prio: isr={:x} mask={:x} irq={:x}", pic.isr, pic.irq_mask, irq_mask);
+        }
+        return;
+    }
+
+    dbg_assert!(irq_mask != 0);
+    let irq_number = irq_mask.ilog2() as u8;
+    dbg_assert!(irq_mask == (1 << irq_number));
+
+    if PIC_LOG_VERBOSE {
+        dbg_log!("[PIC] request irq {}", irq_number);
+    }
+
+    pic.requested_irq = Some(irq_number);
+    // XXX: lifetimes ...
+    if !pic.master {
+        pic_set_irq(2);
+    }
+    cpu::handle_irqs();
+}
+
+// called by javascript
+#[no_mangle]
+pub unsafe fn pic_set_irq(i: u8) {
+    dbg_assert!(i < 16);
+
+    if PIC_LOG_VERBOSE {
+        dbg_log!("[PIC] set irq {}, irq_value={:x}", i, master.irq_value);
+    }
+
+    if i < 8 {
+        let mask = 1 << i;
+        if master.irq_value & mask == 0 {
+            master.irr |= mask;
+            master.irq_value |= mask;
+            check_irqs(&mut master);
+        }
+    }
+    else {
+        let mask = 1 << (i - 8);
+        if slave.irq_value & mask == 0 {
+            slave.irr |= mask;
+            slave.irq_value |= mask;
+            check_irqs(&mut slave);
+        }
+    }
+}
+
+// called by javascript
+#[no_mangle]
+pub unsafe fn pic_clear_irq(i: u8) {
+    dbg_assert!(i < 16);
+
+    if PIC_LOG_VERBOSE {
+        dbg_log!("[PIC] clear irq {}", i);
+    }
+
+    if i < 8 {
+        let mask = 1 << i;
+        if master.irq_value & mask != 0 {
+            master.irq_value &= !mask;
+            master.irr &= !mask;
+            check_irqs(&mut master);
+        }
+    } else {
+        let mask = 1 << (i - 8);
+        if slave.irq_value & mask != 0 {
+            slave.irq_value &= !mask;
+            slave.irr &= !mask;
+            check_irqs(&mut slave);
+        }
+    }
+}
+
+unsafe fn port0_read(pic: &mut Pic) -> u32 {
+    (if pic.read_isr { pic.isr } else { pic.irr }) as u32
+}
+unsafe fn port1_read(pic: &mut Pic) -> u32 {
+    !pic.irq_mask as u32
+}
+
+unsafe fn port0_write(pic: &mut Pic, v: u8) {
+    if v & 0x10 != 0 { // xxxx1xxx
+        // icw1
+        dbg_log!("icw1 = {:x}", v);
+        pic.isr = 0;
+        pic.irr = 0;
+        pic.irq_mask = 0;
+        pic.irq_value = 0;
+        pic.auto_eoi = true;
+        pic.requested_irq = None;
+
+        pic.expect_icw4 = v & 1 != 0;
+        pic.state = 1;
+    }
+    else if v & 8 != 0 { // xxx01xxx
+        // ocw3
+        dbg_log!("ocw3: {:x}", v);
+        if v & 2 != 0 {
+            pic.read_isr = v & 1 != 0;
+        }
+        if v & 4 != 0 {
+            dbg_assert!(false, "unimplemented: polling");
+        }
+        if v & 0x40 != 0 {
+            pic.special_mask_mode = (v & 0x20) == 0x20;
+            dbg_log!("special mask mode: {}", pic.special_mask_mode);
+        }
+    }
+    else { // xxx00xxx
+        // ocw2
+        // end of interrupt
+        if PIC_LOG {
+            dbg_log!("eoi: {:x}", v);
+        }
+
+        let eoi_type = v >> 5;
+
+        if eoi_type == 1 {
+            // non-specific eoi
+            pic.isr &= pic.isr - 1;
+            if PIC_LOG {
+                dbg_log!("new isr: {:x}", pic.isr);
+            }
+        }
+        else if eoi_type == 3 {
+            // specific eoi
+            pic.isr &= !(1 << (v & 7));
+        }
+        else if (v & 0xC8) == 0xC0 {
+            // os2 v4
+            let priority = v & 7;
+            dbg_log!("lowest priority: {:x}", priority);
+        }
+        else {
+            dbg_log!("Unknown eoi: {:x}", v);
+            dbg_assert!(false);
+            pic.isr &= pic.isr - 1;
+        }
+
+        check_irqs(pic);
+    }
+}
+
+unsafe fn port1_write(pic: &mut Pic, v: u8) {
+    //dbg_log!("21 write: " + h(v));
+    if pic.state == 0 {
+        if pic.expect_icw4 {
+            // icw4
+            pic.expect_icw4 = false;
+            pic.auto_eoi = v & 2 != 0;
+            dbg_log!("icw4: {:x} autoeoi={}", v, pic.auto_eoi);
+
+            if v & 1 == 0 {
+                dbg_assert!(false, "unimplemented: not 8086 mode");
+            }
+        }
+        else {
+            // ocw1
+            pic.irq_mask = !v;
+
+            if PIC_LOG_VERBOSE {
+                dbg_log!("interrupt mask: {:x}", pic.irq_mask);
+            }
+
+            check_irqs(pic);
+        }
+    }
+    else if pic.state == 1 {
+        // icw2
+        pic.irq_map = v;
+        dbg_log!("interrupts are mapped to {}", pic.irq_map);
+        pic.state += 1;
+    }
+    else if pic.state == 2 {
+        // icw3
+        pic.state = 0;
+        dbg_log!("icw3: {:x}", v);
+    }
+}
+
+#[no_mangle]
+pub unsafe fn port20_read() -> u32 { port0_read(&mut master) }
+#[no_mangle]
+pub unsafe fn port21_read() -> u32 { port1_read(&mut master) }
+
+#[no_mangle]
+pub unsafe fn portA0_read() -> u32 { port0_read(&mut slave) }
+#[no_mangle]
+pub unsafe fn portA1_read() -> u32 { port1_read(&mut slave) }
+
+#[no_mangle]
+pub unsafe fn port20_write(v: u8) { port0_write(&mut master, v) }
+#[no_mangle]
+pub unsafe fn port21_write(v: u8) { port1_write(&mut master, v) }
+
+#[no_mangle]
+pub unsafe fn portA0_write(v: u8) { port0_write(&mut slave, v) }
+#[no_mangle]
+pub unsafe fn portA1_write(v: u8) { port1_write(&mut slave, v) }
+
+#[no_mangle]
+pub unsafe fn port4D0_read() -> u32 { master.elcr as u32 }
+#[no_mangle]
+pub unsafe fn port4D1_read() -> u32 { slave.elcr as u32 }
+#[no_mangle]
+pub unsafe fn port4D0_write(v: u8) { master.elcr = v }
+#[no_mangle]
+pub unsafe fn port4D1_write(v: u8) { slave.elcr = v }

+ 14 - 6
src/rust/jit.rs

@@ -56,6 +56,8 @@ pub fn jit_clear_func(wasm_table_index: WasmTableIndex) {
     unsafe { unsafe_jit::jit_clear_func(wasm_table_index) }
 }
 
+static mut JIT_DISABLED: bool = false;
+
 // Maximum number of pages per wasm module. Necessary for the following reasons:
 // - There is an upper limit on the size of a single function in wasm (currently ~7MB in all browsers)
 //   See https://github.com/WebAssembly/design/issues/1138
@@ -2075,6 +2077,10 @@ pub fn jit_increase_hotness_and_maybe_compile(
     state_flags: CachedStateFlags,
     heat: u32,
 ) {
+    if unsafe { JIT_DISABLED } {
+        return
+    }
+
     let ctx = get_jit_state();
     let page = Page::page_of(phys_address);
     let (hotness, entry_points) = ctx.entry_points.entry(page).or_insert_with(|| {
@@ -2389,9 +2395,10 @@ pub fn enter_basic_block(phys_eip: u32) {
 #[no_mangle]
 pub unsafe fn set_jit_config(index: u32, value: u32) {
     match index {
-        0 => MAX_PAGES = value,
-        1 => JIT_USE_LOOP_SAFETY = value != 0,
-        2 => MAX_EXTRA_BASIC_BLOCKS = value,
+        0 => JIT_DISABLED = value != 0,
+        1 => MAX_PAGES = value,
+        2 => JIT_USE_LOOP_SAFETY = value != 0,
+        3 => MAX_EXTRA_BASIC_BLOCKS = value,
         _ => dbg_assert!(false),
     }
 }
@@ -2399,9 +2406,10 @@ pub unsafe fn set_jit_config(index: u32, value: u32) {
 #[no_mangle]
 pub unsafe fn get_jit_config(index: u32) -> u32 {
     match index {
-        0 => MAX_PAGES as u32,
-        1 => JIT_USE_LOOP_SAFETY as u32,
-        2 => MAX_EXTRA_BASIC_BLOCKS as u32,
+        0 => JIT_DISABLED as u32,
+        1 => MAX_PAGES as u32,
+        2 => JIT_USE_LOOP_SAFETY as u32,
+        3 => MAX_EXTRA_BASIC_BLOCKS as u32,
         _ => 0,
     }
 }

+ 2 - 0
src/rust/profiler.rs

@@ -84,6 +84,8 @@ pub enum stat {
     PAGE_FAULT,
     TLB_MISS,
 
+    MAIN_LOOP,
+    MAIN_LOOP_IDLE,
     DO_MANY_CYCLES,
     CYCLE_INTERNAL,