# chifir.c -rw-r--r-- 3.8 KiB View raw
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/* 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
}