/* A quick implementation of the CPU of the Chifir virtual machine
described in “The Cuneiform Tablets of 2015”, VPRI TR-2015-004,
<http://www.vpri.org/pdf/tr2015004_cuneiform.pdf>
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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#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
}