/* A quick implementation of the CPU of the Chifir virtual machine described in “The Cuneiform Tablets of 2015”, VPRI TR-2015-004, I wanted to see how hard it would be to implement and how much code it would be. This version took me 32 minutes and 70 lines of C, having previously read the spec; but it doesn’t actually display the screen, and it may be buggy, because I haven’t tested it. Compiled for i386 with -Os, the executable is 4 kibibytes, of which about 2.3 kibibytes are actual code and data. Statically linked with dietlibc -Os and stripped, the executable is 6.7 kibibytes. Probably a version that opened an X-Windows window, flushed a pixmap to it in refresh_screen(), and listened for keyboard events would be a bit more code, maybe another 1.7 kilobytes of executable code and 70 lines more. Note that this is about the same amount of code as this entire CPU emulator. I expect that debugging would probably not increase or decrease the amount of code significantly; how long it takes would probably depend on what “unit testing” code is available. (The Cuneiform paper provides no test code, although it says they would like to write some.) This suggests to me that, although its spec has some holes in it, Chifir is indeed implementable as a “fun afternoon hack”, as intended; indeed, it’s probably only slightly harder to get running than BF, and it should be dramatically easier to program for and dramatically more efficient. Also, it’s comparable to the “6 kilobytes of 8086 machine code playing the role of the “microcode”” mentioned in the paper. I think it’s possible to do significantly better still. */ #include #include #include #include #include #include #include #include "chifir.h" #ifndef __GCC__ #define attribute(x) #endif word m[mem_size]; word pc = 0; void read_image(int fd, word *m) { unsigned char input[4]; for (size_t i = 0; i < mem_size; i++) { int result = read(fd, input, 4); if (result != 4) return; m[i] = input[0] << 24 | input[1] << 16 | input[2] << 8 | input[3]; } } void die(const char *context) { perror(context); abort(); } static inline word load(word addr) { if (addr > mem_size) die("invalid load address"); // this behavior is unspecified return m[addr]; } static inline void store(word addr, word data) { if (addr > mem_size) die("invalid store address"); // this behavior is unspecified m[addr] = data; } __attribute__((weak)) void refresh_screen() { } static inline word read_keyboard(int fd) { unsigned char buf; if (1 != read(fd, &buf, 1)) return -1; return (word)buf; } #define IF break; case #define ELSE break; default void run(int fd) { for (;;) { word a = load(pc+1), b = load(pc+2), c = load(pc+3); switch(load(pc)) { IF 1: pc = load(a)-4; IF 2: if (load(b) == 0) pc = load(a)-4; IF 3: store(a, pc); IF 4: store(a, load(b)); IF 5: store(a, load(load(b))); IF 6: store(load(b), load(a)); IF 7: store(a, load(b) + load(c)); IF 8: store(a, load(b) - load(c)); IF 9: store(a, load(b) * load(c)); IF 10: store(a, load(b) / load(c)); // division by zero behavior is unspecified IF 11: store(a, load(b) % load(c)); IF 12: store(a, load(b) < load(c) ? 1 : 0); IF 13: store(a, ~(load(b) & load(c))); IF 14: refresh_screen(); // intentionally unimplemented in this version IF 15: store(a, read_keyboard(fd)); ELSE: die("unknown instruction"); } pc += 4; } } __attribute__((weak)) int main(int argc, char **argv) { if (argc != 2) die("usage: chifir input_file"); int fd = open(argv[1], O_RDONLY); if (fd < 0) die("open"); read_image(fd, m); run(0); return 0; // can’t happen }