Brainfuck compiler
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

328 lines
8.8KB

  1. // This is an exercise in futility more than anything else
  2. #define _GNU_SOURCE
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <string.h>
  6. #include <stdint.h>
  7. #include <stdbool.h>
  8. #include <assert.h>
  9. #include <errno.h>
  10. #if (defined __x86_64__ || defined __amd64__) && defined __unix__
  11. #include <sys/mman.h>
  12. #else
  13. #error Platform not supported
  14. #endif
  15. #define exit_fatal(...) \
  16. do { \
  17. fprintf (stderr, "fatal: " __VA_ARGS__); \
  18. exit (EXIT_FAILURE); \
  19. } while (0)
  20. // --- Safe memory management --------------------------------------------------
  21. static void *
  22. xcalloc (size_t m, size_t n)
  23. {
  24. void *p = calloc (m, n);
  25. if (!p)
  26. exit_fatal ("calloc: %s\n", strerror (errno));
  27. return p;
  28. }
  29. static void *
  30. xrealloc (void *o, size_t n)
  31. {
  32. void *p = realloc (o, n);
  33. if (!p && n)
  34. exit_fatal ("realloc: %s\n", strerror (errno));
  35. return p;
  36. }
  37. // --- Dynamically allocated strings -------------------------------------------
  38. struct str
  39. {
  40. char *str; ///< String data, null terminated
  41. size_t alloc; ///< How many bytes are allocated
  42. size_t len; ///< How long the string actually is
  43. };
  44. static void
  45. str_init (struct str *self)
  46. {
  47. self->len = 0;
  48. self->str = xcalloc (1, (self->alloc = 16));
  49. }
  50. static void
  51. str_ensure_space (struct str *self, size_t n)
  52. {
  53. // We allocate at least one more byte for the terminating null character
  54. size_t new_alloc = self->alloc;
  55. while (new_alloc <= self->len + n)
  56. new_alloc <<= 1;
  57. if (new_alloc != self->alloc)
  58. self->str = xrealloc (self->str, (self->alloc = new_alloc));
  59. }
  60. static void
  61. str_append_data (struct str *self, const void *data, size_t n)
  62. {
  63. str_ensure_space (self, n);
  64. memcpy (self->str + self->len, data, n);
  65. self->str[self->len += n] = '\0';
  66. }
  67. static void
  68. str_append_c (struct str *self, char c)
  69. {
  70. str_append_data (self, &c, 1);
  71. }
  72. // --- Application -------------------------------------------------------------
  73. struct str data; ///< Data tape
  74. volatile size_t dataptr; ///< Current location on the tape
  75. FILE *input; ///< User input
  76. enum command { RIGHT, LEFT, INC, DEC, SET, IN, OUT, BEGIN, END };
  77. bool grouped[] = { 1, 1, 1, 1, 1, 0, 0, 0, 0 };
  78. struct instruction { enum command cmd; size_t arg; };
  79. // - - Callbacks - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  80. // Some things I just really don't want to write in assembly even though it
  81. // is effectively a big performance hit, eliminating the advantage of JIT
  82. static void
  83. right (size_t arg)
  84. {
  85. assert (SIZE_MAX - dataptr > arg);
  86. dataptr += arg;
  87. while (dataptr >= data.len)
  88. str_append_c (&data, 0);
  89. }
  90. static void
  91. left (size_t arg)
  92. {
  93. assert (dataptr >= arg);
  94. dataptr -= arg;
  95. }
  96. static int
  97. cin (void)
  98. {
  99. int c = fgetc (input);
  100. assert (c != EOF);
  101. return c;
  102. }
  103. // - - Main - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  104. int
  105. main (int argc, char *argv[])
  106. {
  107. (void) argc;
  108. (void) argv;
  109. struct str program;
  110. str_init (&program);
  111. int c;
  112. while ((c = fgetc (stdin)) != EOF)
  113. str_append_c (&program, c);
  114. if (ferror (stdin))
  115. exit_fatal ("can't read program\n");
  116. if (!(input = fopen ("/dev/tty", "rb")))
  117. exit_fatal ("can't open terminal for reading\n");
  118. // - - Decode and group - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  119. struct instruction *parsed = xcalloc (sizeof *parsed, program.len);
  120. size_t parsed_len = 0;
  121. for (size_t i = 0; i < program.len; i++)
  122. {
  123. enum command cmd;
  124. switch (program.str[i])
  125. {
  126. case '>': cmd = RIGHT; break;
  127. case '<': cmd = LEFT; break;
  128. case '+': cmd = INC; break;
  129. case '-': cmd = DEC; break;
  130. case '.': cmd = OUT; break;
  131. case ',': cmd = IN; break;
  132. case '[': cmd = BEGIN; break;
  133. case ']': cmd = END; break;
  134. default: continue;
  135. }
  136. if (!parsed_len || !grouped[cmd] || parsed[parsed_len - 1].cmd != cmd)
  137. parsed_len++;
  138. parsed[parsed_len - 1].cmd = cmd;
  139. parsed[parsed_len - 1].arg++;
  140. }
  141. // - - Simple optimization pass - - - - - - - - - - - - - - - - - - - - - - - -
  142. size_t in = 0, out = 0;
  143. for (; in < parsed_len; in++, out++)
  144. {
  145. if (in + 2 < parsed_len
  146. && parsed[in ].cmd == BEGIN
  147. && parsed[in + 1].cmd == DEC && parsed[in + 1].arg == 1
  148. && parsed[in + 2].cmd == END)
  149. {
  150. parsed[out].cmd = SET;
  151. parsed[out].arg = 0;
  152. in += 2;
  153. }
  154. else if (out && parsed[out - 1].cmd == SET && parsed[in].cmd == INC)
  155. parsed[--out].arg += parsed[in].arg;
  156. else if (out != in)
  157. parsed[out] = parsed[in];
  158. }
  159. parsed_len = out;
  160. // - - Loop pairing - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  161. size_t nesting = 0;
  162. size_t *stack = xcalloc (sizeof *stack, parsed_len);
  163. for (size_t i = 0; i < parsed_len; i++)
  164. {
  165. switch (parsed[i].cmd)
  166. {
  167. case BEGIN:
  168. stack[nesting++] = i;
  169. break;
  170. case END:
  171. assert (nesting > 0);
  172. --nesting;
  173. parsed[stack[nesting]].arg = i + 1;
  174. parsed[i].arg = stack[nesting] + 1;
  175. default:
  176. break;
  177. }
  178. }
  179. free (stack);
  180. assert (nesting == 0);
  181. // - - JIT - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  182. // Functions preserve the registers rbx, rsp, rbp, r12, r13, r14, and r15;
  183. // while rax, rdi, rsi, rdx, rcx, r8, r9, r10, r11 are scratch registers.
  184. str_init (&program);
  185. size_t *offsets = xcalloc (sizeof *offsets, parsed_len + 1);
  186. #define CODE(x) { char t[] = x; str_append_data (&program, t, sizeof t - 1); }
  187. #define WORD(x) { size_t t = (size_t)(x); str_append_data (&program, &t, 8); }
  188. CODE ("\x49\xBD") WORD (&dataptr) // mov r13, qword "&dataptr"
  189. CODE ("\x49\xBF") WORD (&data.str) // mov r15, qword "&data.str"
  190. CODE ("\x4D\x8B\x37") // mov r14, qword [r15]
  191. CODE ("\x30\xDB") // xor bl, bl
  192. for (size_t i = 0; i < parsed_len; i++)
  193. {
  194. offsets[i] = program.len;
  195. size_t arg = parsed[i].arg;
  196. switch (parsed[i].cmd)
  197. {
  198. case RIGHT:
  199. CODE ("\x41\x88\x1E") // mov [r14], bl
  200. CODE ("\x48\xBF") WORD (arg) // mov rdi, "arg"
  201. CODE ("\x48\xB8") WORD (right) // mov rax, "right"
  202. CODE ("\xFF\xD0") // call rax
  203. // The data could get reallocated, so reload the address
  204. CODE ("\x4D\x8B\x37") // mov r14, qword [r15]
  205. CODE ("\x4D\x03\x75\x00") // add r14, [r13]
  206. CODE ("\x41\x8A\x1E") // mov bl, [r14]
  207. break;
  208. case LEFT:
  209. CODE ("\x41\x88\x1E") // mov [r14], bl
  210. CODE ("\x48\xBF") WORD (arg) // mov rdi, "arg"
  211. CODE ("\x49\x29\xFE") // sub r14, rdi -- optimistic
  212. CODE ("\x48\xB8") WORD (left) // mov rax, "left"
  213. CODE ("\xFF\xD0") // call rax
  214. CODE ("\x41\x8A\x1E") // mov bl, [r14]
  215. break;
  216. case INC:
  217. CODE ("\x80\xC3") // add bl, "arg"
  218. str_append_c (&program, arg);
  219. break;
  220. case DEC:
  221. CODE ("\x80\xEB") // sub bl, "arg"
  222. str_append_c (&program, arg);
  223. break;
  224. case SET:
  225. CODE ("\xB3") // mov bl, "arg"
  226. str_append_c (&program, arg);
  227. break;
  228. case OUT:
  229. CODE ("\x48\x0F\xB6\xFB") // movzx rdi, bl
  230. CODE ("\x48\xBE") WORD (stdout) // mov rsi, "stdout"
  231. CODE ("\x48\xB8") WORD (fputc) // mov rax, "fputc"
  232. CODE ("\xFF\xD0") // call rax
  233. break;
  234. case IN:
  235. CODE ("\x48\xB8") WORD (cin) // mov rax, "cin"
  236. CODE ("\xFF\xD0") // call rax
  237. CODE ("\x88\xC3") // mov bl, al
  238. break;
  239. case BEGIN:
  240. CODE ("\x84\xDB") // test bl, bl
  241. CODE ("\x0F\x84\x00\x00\x00\x00") // jz "offsets[i]"
  242. break;
  243. case END:
  244. CODE ("\x84\xDB") // test bl, bl
  245. CODE ("\x0F\x85\x00\x00\x00\x00") // jnz "offsets[i]"
  246. break;
  247. }
  248. }
  249. // When there is a loop at the end we need to be able to jump past it
  250. offsets[parsed_len] = program.len;
  251. str_append_c (&program, '\xC3'); // ret
  252. // Now that we know where each instruction is, fill in relative jumps
  253. for (size_t i = 0; i < parsed_len; i++)
  254. {
  255. if (parsed[i].cmd != BEGIN && parsed[i].cmd != END)
  256. continue;
  257. size_t fixup = offsets[i] + 4;
  258. *(int32_t *)(program.str + fixup) =
  259. ((intptr_t)(offsets[parsed[i].arg]) - (intptr_t)(fixup + 4));
  260. }
  261. free (offsets);
  262. // - - Runtime - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  263. // Some systems may have W^X
  264. void *executable = mmap (NULL, program.len, PROT_READ | PROT_WRITE,
  265. MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  266. if (!executable)
  267. exit_fatal ("mmap: %s\n", strerror (errno));
  268. memcpy (executable, program.str, program.len);
  269. if (mprotect (executable, program.len, PROT_READ | PROT_EXEC))
  270. exit_fatal ("mprotect: %s\n", strerror (errno));
  271. str_init (&data);
  272. str_append_c (&data, 0);
  273. ((void (*) (void)) executable)();
  274. return 0;
  275. }