Best practices for a CPU emulation library?

My project has, among other things, emulation of a rather complex CPU. It would be useful to isolate it as a separate crate, so that it's easy to reuse in other projects.

In order to use the CPU, of course, you need to implement a number of functions so that it can communicate with the outside world. This often has to happen somewhere deep within the emulator, several call stacks deep.

One wrinkle is that for speed, I use static muts instead of the more dynamic self references. In the original C code, which this project is a port of, using statically allocated structures with a fixed compile-time address resulted in a 15-30% speedup over runtime allocated ones. Yes, I know this doesn't lead to very pretty code, but in this case, the performance improvements are large enough that I'm willing to stick to this.

In C, it was easy enough to pass around a few function pointers:

typedef void (*io_write_t)(void* opaque, int port, int value);

void cpu_register_io_write(int port_range_start, int port_range_end, void* opaque, io_write_t handler);

opaque, in this case, roughly corresponds to &mut self; you supply a reference to &mut self along with the handler so that the handler has some idea what object it's supposed to act on, if more than one instance of self exists.

In Java, it was possible to create an interface containing all the functions that I needed. this would be passed automatically, and personally I prefer this method over C's function pointers, since the interface lets you know beforehand what methods you need to implement.

The only way that I would be able to implement this with my current understanding of Rust is to create function pointers, passing *mut u8 around as a void* pointer and having the callee convert that back into a &mut self reference. But that would lead to really ugly code, and I'd like to use traits instead. So far, trying to mix traits with statics has been exceedingly frustrating, and nothing has worked.

Is there a better way?

1 Like

You can use trait in pretty much the exact same way as Java interfaces.

// Java

interface SomeInterface {
    int get();
    int set(value: u32);
}

// later

class SomeUser {
   void take_interface(iface: SomeInterface) {
        int value = iface.get();
        iface.set(value + 1);
    }
}
// Rust

trait SomeInterface {
    fn get(&self) -> u32;
    fn set(&mut self, u32);
}

// later

fn take_interface(iface: &mut dyn SomeInterface) {
    let value = iface.get();
    iface.set(value + 1);
}

You could also use generics

fn take_interface<I: SomeInterface + ?Sized>(iface: &mut I) {
    let value = iface.get();
    iface.set(value + 1);
}

If you have a list of methods, I can help you build the trait

1 Like

I understand how traits work (kinda...) but my main issue is how to store them, especially with statics. In Java, it was quite easy to do this:

interface TestInterface {
    public void test();
}
class Test {
    static TestInterface iface;
    public static something() {
        iface.test(); 
    } 
}

In Rust, though, this is impossible:

trait TestTrait {
    fn test(&mut self);
}
static mut IFACE: dyn TestTrait = ?;

fn something() {
    unsafe { IFACE.test(); };
}

I'm wondering if it's possible to do the same in Rust, having a static mut that holds a trait and is able to dispatch from that. I've tried all sorts of combinations of raw pointers, Box<>, Option<>, but none of that seems to work.

Sorry if I'm not being clear. This is a really esoteric usecase, so apologies for any confusion.

First I recommend against using static mut. It's really hard to use correctly because of Rust's aliasing rules (near impossible if you don't follow some very strict guidelines). I would recommend you use once_cell + Mutex instead. If you know that you are single-threaded and that test isn't reentrant, then you could potentially use static mut. But that's quite strict.

Here's what I recommend.

use once_cell::sync::Lazy;
use std::sync::Mutex;

static IFACE: Lazy<Mutex<Box<dyn TestInterface + Send>>> = Lazy::new(|| Mutex::new(Box::new(SomeImpl { ... })));

If you are single-threaded and can't guarantee that test won't be reentrant, then you should use thread_local+RefCell

thread_local! {
    static IFACE: RefCell<Box<dyn TestInterface>> = RefCell::new(Box::new(SomeImpl { ... }));
}

The best option is to not use a global! Instead just put it on the stack and pass around a &mut dyn TestInterface.

edit: whoops, forgot + Send in the first snippet

If after you measure the cost of these apporaches (which will likely give different results from Java), and you are still unsatisfied, you can use this method. Rust Playground. Which does some minimal checks, but is still just as unsafe as static mut.

I'll explain here why static mut is so dangerous in Rust.

static mut X: u32 = 0;

fn main() {
    unsafe {
        let x = &mut X;
        X = 10;
        let y = x; // this is UB, because `x` was invalidated by `X = 10`
    }
}

Code like this is very easy to write, and a nightmare to debug. Because Rust has much stricter aliasing rules compared to C, (&mut _ may never alias a different reference), it's much easier to cause UB.

Avoid globals where you can, because it's not as worthwhile in Rust. Especially static mut.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.