I'm working on an NES emulator in c and figured that the most efficient way to run opcodes would be to call a function pointer in an array of functions that do exactly what the opcode does.
The problem is that each opcode has a specific operation and memory address. While searching for a solution, I stumbled upon lambda expressions. This is definitely good enough for a NES emulator on modern hardware. However, I can't find a solution such that each function in the array contains the machine code for both the operation and the addressing without defining 256 separate functions.
This is along what I had in mind for a similar function that combines f and g:
int addone(int x) {
return x 1;
}
int multtwo(int x) {
return 2 * x;
}
something combine(function<int(int)> f, function <int(int)> g) {
/* code here */
}
/*
combine(addone, multtwo) creates a function h that has the same machine code as
int h(x) {
return 2 * x 1;
}
*/
Any ideas? If I had to take a guess, it would have something to do with templates. Thanks!
CodePudding user response:
You can do something like this, using a lambda to capture the two functions and to assign a function to a variable:
#include <functional>
#include <iostream>
int addone(int x){
return x 1;
}
int multtwo(int x){
return x * 2;
}
std::function<int(int)> combine(std::function<int(int)> f, std::function<int(int)> g){
auto tmp = [&](int x){ return f(g(x)); };
return std::function<int(int)>(tmp);
}
int main(){
auto h = combine(std::function<int(int)>(addone), std::function<int(int)>(multtwo)); // (2 * x) 1
std::cout << h(10); // Prints 21
}
If you want it to generally combine the functions, you can use a template:
#include <functional>
#include <iostream>
int addone(int x){
return x 1;
}
int multtwo(int x){
return x * 2;
}
template <typename Func>
std::function<Func> combine(std::function<Func> f, std::function<Func> g){
auto tmp = [&](int x){ return f(g(x)); };
return std::function<Func>(tmp);
}
int main(){
auto h = combine<int(int)>(std::function<int(int)>(addone), std::function<int(int)>(multtwo));
std::cout << h(10) << "\n"; // Prints 21
}
You also don't need to specify the type, since the compiler can figure it out:
#include <functional>
#include <iostream>
int addone(int x){
return x 1;
}
int multtwo(int x){
return x * 2;
}
template <typename func>
std::function<Func> combine(std::function<Func> f, std::function<Func> g){
auto tmp = [&](int x){ return f(g(x)); };
return std::function<Func>(tmp);
}
int main(){
auto h = combine(std::function<int(int)>(addone), std::function<int(int)>(multtwo));
std::cout << h(10) << "\n"; // Still prints 21
}
CodePudding user response:
If you want to create automatically the functions, use 2pichar's answer above with a for loop, but for an emulator you'd probably want something like opcode->int(*)(int)
. This could be done by some tree-like structure:
std::map<char, naive_opcode> opcodes;
struct naive_opcode {
std::map<char, naive_opcode> next;
int(* opcode_func)(int);
};
You'd work through this in some fashion like:
char data;
buf >> data;
naive_opcode opcode = opcodes[data];
while(!opcode.opcode_func){
buf >> data;
opcode = opcode.next[data];
}
opcode.opcode_func(param);
This of course ignores errors and does not include things like the instruction pointer and the .text
section memory, rather replacing it with the buf
buffer for illustrative purposes (In a real life example I'd expect this to be replaced by data=memory[ip]; ip;
). This could then be combined with an implementation like:
#include <iostream>
int addone(int x){
return x 1;
}
int multtwo(int x){
return x * 2;
}
template<int(* F)(int), int(* G)(int)>
int combined(int x){
return F(G(x));
}
int main(){
std::cout << combined<addone,multtwo>(10);
}
for which you could essentially just define the end node of naive_opcode
as {{}, combined<addone,multtwo>}
.
Unfortunately as I mentioned in my comment, this probably cannot be done automatically. The best you could do I recon is that you define something like:
std::vector<std::pair<const char*, int(*)(int)>> raw_opcodes = {{"\x10\x13", addone}, ...};
and then parse that into the tree like structure. As a brief side note: this might not be needed if all the opcodes are 1 byte (which I am unsure about since I am not familiar with NES). Then a simple std::map<char,int(*)(int)> opcodes
will suffice instead of the convoluted naive_opcode
(or better tree) implementation.
Looked it up and it seems that you wouldn't need the tree implementation, but a modification like this can be useful:
template<int(* F)(int)>
int combined(int x){
return F(x);
}
template<int(* F)(int), int(* A)(int), int(*... G)(int)>
int combined(int x){
return combined<A, G...>(F(x));
}
This breaks the order of operations, but allows for combining many effects into each other, rather than 2.
CodePudding user response:
We can use templates to create a generic compose
function that "combines" two unary functions using a lambda that captures the functions passed in, and returns it.
#include <functional>
#include <iostream>
template <typename Input, typename Output1, typename Output2>
std::function<Output2(Input)> compose(
std::function<Output2(Output1)> f,
std::function<Output1(Input)> g
) {
return [&f, &g](Input x) { return f(g(x)); };
}
int foo(int x) {
return x 1;
}
int bar(int x) {
return x * 2;
}
int main() {
auto baz = compose<int, int, int>(foo, bar);
std::cout << baz(5) << std::endl;
auto wooble = compose<int, int, float>(
[](int x) { return static_cast<float>(x) 1.5; },
[](int x) { return x * 3; }
);
std::cout << wooble(5) << std::endl;
return 0;
}
CodePudding user response:
Do you want this?
int f1(int x) { return x 1; }
int f2(int x) { return x * 2; }
int f3(int x) { return x * 3; }
int f4(int x) { return x - 5; }
int f5(int x) { return x 9; }
int main() {
auto cf = combine<int>(f1, f2, f3, f4, f5);
std::cout << cf(5) << std::endl;
return 0;
}
Output:
40
Full code:
#include <functional>
#include <concepts>
#include <iostream>
template<typename T, typename NUM = int>
concept num_processor = requires (T t, NUM x) {
{t(x)} -> std::same_as<NUM>;
};
template<typename NUM, num_processor p>
NUM process(NUM v, p proc) {
return proc(v);
}
template<typename NUM, num_processor p, num_processor... Funs>
NUM process(NUM v, p proc, Funs... funs) {
return process(proc(v), funs...);
}
template<typename NUM, num_processor... Funs>
std::function<NUM (NUM)> combine(Funs... funs) {
return [...funs = funs] (NUM v) { return process(v, funs...); };
}
int f1(int x) { return x 1; }
int f2(int x) { return x * 2; }
int f3(int x) { return x * 3; }
int f4(int x) { return x - 5; }
int f5(int x) { return x 9; }
int main() {
auto cf = combine<int>(f1, f2, f3, f4, f5);
std::cout << cf(5) << std::endl;
return 0;
}
Compile with
-std=c 20
for gcc and /std:c latest
for msvc