In my Motorola 6809 CPU emulator (written in C++14) I'm working on an abstraction to represent the wiring between emulated devices, such as the ports on a 6522 VIA, or the interrupt lines connecting a peripheral to the CPU.
The abstraction uses functional composition. Each OutputPin
encapsulates a developer-supplied function that returns the current state of that output. InputPin
objects belonging to a different device have a copy of the OutputPin
to which they are attached. Each InputPin
may only be attached to a single OutputPin
, but an OutputPin
may be attached to multiple InputPin
s.
This abstraction is working, and allows for expressive and powerful constructions like this, which does a "wired-and" of three IRQ lines (where one of them is active-high) and attaches it to the CPU's IRQ input and each time the state of that line is tested the composed functions generate the correct result based on the current state of the three inputs:
cpu.IRQ << (!fdc.DIRQ & via.IRQ & acia.IRQ);
and this, which generates an output signal that is the parity of 8 other outputs.
OutputPin parity = out[0] ^ out[1] ^ out[2] ^ out[3] ^ out[4] ^ out[5] ^ out[6] ^ out[7];
My concern, though, is performance. I had hoped that compilers might be able to elide many of the function calls and collapse them into simpler expressions but initial tests with clang 12.0 on macOS don't appear to show any sign of this.
Is there anything I could be doing to improve the run-time efficiency, or am I hoping for too much from the compiler's optimiser?
Here's the complete implementation as a header with inline functions:
static inline bool default_true() {
return true;
}
class OutputPin {
public:
using Function = std::function<bool()>;
protected:
Function f = default_true;
public:
OutputPin() { }
OutputPin(const Function& f) : f(f) { }
void bind(const Function& _f) {
f = _f;
}
operator bool() const {
return f();
}
OutputPin operator !() const {
return OutputPin([&]() {
return !f();
});
}
};
class InputPin {
protected:
OutputPin input;
public:
void attach(const OutputPin& _input) {
input = _input;
}
operator bool() const {
return input;
}
};
inline void operator<<(InputPin& in, const OutputPin& out)
{
in.attach(out);
}
inline OutputPin operator&(const OutputPin& a, const OutputPin& b)
{
return OutputPin([=]() {
return (bool)a && (bool)b;
});
}
inline OutputPin operator|(const OutputPin& a, const OutputPin& b)
{
return OutputPin([=]() {
return (bool)a || (bool)b;
});
}
inline OutputPin operator^(const OutputPin& a, const OutputPin& b)
{
return OutputPin([=]() {
return (bool)a ^ (bool)b;
});
}
and here's a trivial test case for the above header (but which generates 60kB of assembler output!):
#include "device.h"
int main()
{
OutputPin a, b, c;
InputPin i;
i << (!a & b & c);
return i;
}