Created
August 3, 2025 21:31
-
-
Save aabiji/17ea168e51aba9eec1c2756b4e8fd9ec to your computer and use it in GitHub Desktop.
JIT compiled brainfuck
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <stdbool.h> | |
| #include <stdint.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <sys/mman.h> | |
| #include <unistd.h> | |
| typedef struct { int length; int capacity; uint8_t* buffer; } Vector; | |
| void vector_append(Vector *v, uint8_t *data, int length) { | |
| if (v->length + length >= v->capacity) { | |
| v->capacity *= 2; | |
| v->buffer = (uint8_t*)realloc(v->buffer, v->capacity); | |
| } | |
| memcpy(v->buffer + v->length, data, length); | |
| v->length += length; | |
| } | |
| int handle_loop(Vector *code, bool opening) { | |
| uint8_t jmp = opening ? 0x84 : 0x85; | |
| uint8_t instructions[9] = { | |
| 0x80, 0x3F, 0x00, // cmpb $0x0,(%rdi) | |
| 0x0F, jmp, // je rel32 or jne rel32 | |
| 0x00, 0x00, 0x00, 0x00, // signed 32 bit offset | |
| }; | |
| vector_append(code, instructions, 9); | |
| return code->length; | |
| } | |
| typedef void (*run_brainfuck)(char *memory); // %rdi -> data buffer (data ptr) | |
| uint8_t dataptr_add[3] = {0x48, 0xff, 0xc7}; // inc %rdi | |
| uint8_t dataptr_sub[3] = {0x48, 0xff, 0xcf}; // dec %rdi | |
| uint8_t data_add[2] = {0xfe, 0x07}; // incb (%rdi) | |
| uint8_t data_sub[2] = {0xfe, 0x0f}; // decb (%rdi) | |
| uint8_t printf_value[35] = { | |
| 0x57, // push rdi | |
| 0x8A, 0x07, // mov al, byte ptr [rdi] | |
| 0x48, 0x83, 0xEC, 0x08, // sub rsp, 8 | |
| 0x88, 0x04, 0x24, // mov [rsp], al | |
| 0xB8, 0x01, 0x00, 0x00, 0x00, // mov eax, 1 | |
| 0xBF, 0x01, 0x00, 0x00, 0x00, // mov edi, 1 | |
| 0x48, 0x89, 0xE6, // mov rsi, rsp | |
| 0xBA, 0x01, 0x00, 0x00, 0x00, // mov edx, 1 | |
| 0x0F, 0x05, // syscall | |
| 0x48, 0x83, 0xC4, 0x08, // add rsp, 8 | |
| 0x5F // pop rdi | |
| }; | |
| uint8_t read_value[22] = { | |
| 0x57, // push rdi | |
| 0x48, 0x89, 0xFE, // mov rsi, rdi | |
| 0xB8, 0x00, 0x00, 0x00, 0x00, // mov eax, 0 | |
| 0xBF, 0x00, 0x00, 0x00, 0x00, // mov edi, 0 | |
| 0xBA, 0x01, 0x00, 0x00, 0x00, // mov edx, 1 | |
| 0x0F, 0x05, // syscall | |
| 0x5F // pop rdi | |
| }; | |
| void jit_compile(Vector *code, char *bf_source, int length) { | |
| size_t stack[2048] = {0}; | |
| int stack_index = 0; | |
| char *current = bf_source; | |
| while (current - bf_source < length) { | |
| int offset = current - bf_source; | |
| if (*current == '>') vector_append(code, dataptr_add, 3); | |
| else if (*current == '<') vector_append(code, dataptr_sub, 3); | |
| else if (*current == '+') vector_append(code, data_add, 2); | |
| else if (*current == '-') vector_append(code, data_sub, 2); | |
| else if (*current == '.') vector_append(code, printf_value, 35); | |
| else if (*current == ',') vector_append(code, read_value, 22); | |
| // emit machine code for the loops, while updating the jump offsets | |
| else if (*current == '[') | |
| stack[stack_index++] = handle_loop(code, true); | |
| else if (*current == ']') { | |
| int opening_offset_pos = stack[--stack_index]; | |
| int closing_offset_pos = handle_loop(code, false); | |
| int32_t forward = (closing_offset_pos - 9) - (opening_offset_pos - 3); | |
| int32_t backward = (opening_offset_pos - 9) - (closing_offset_pos - 3); | |
| for (int i = 0; i < 4; i++) { | |
| code->buffer[opening_offset_pos - 4 + i] = (forward >> (8 * i)) & 0xFF; | |
| code->buffer[closing_offset_pos - 4 + i] = (backward >> (8 * i)) & 0xFF; | |
| } | |
| } | |
| current++; | |
| } | |
| uint8_t ret = 0xc3; | |
| vector_append(code, &ret, 1); | |
| } | |
| void run_jit(char *bf_src, char *memory, int src_len) { | |
| Vector code = { .length = 0, .capacity = 1024, .buffer = (uint8_t*)malloc(1024) }; | |
| jit_compile(&code, bf_src, src_len); | |
| // align to a page boundary | |
| size_t page_size = sysconf(_SC_PAGESIZE); | |
| uintptr_t page_start = (uintptr_t)code.buffer & ~(page_size - 1); | |
| uintptr_t page_end = | |
| ((uintptr_t)code.buffer + code.length + page_size - 1) & ~(page_size - 1); | |
| size_t total_len = page_end - page_start; | |
| // mark as executable | |
| if (mprotect((void *)page_start, total_len, PROT_READ | PROT_WRITE | PROT_EXEC)) { | |
| perror("mprotect"); | |
| exit(-1); | |
| } | |
| ((run_brainfuck)code.buffer)(memory); | |
| free(code.buffer); | |
| } | |
| int main(int argc, char **argv) { | |
| if (argc != 2) { | |
| printf("Usage: brainfuck PROGRAM_PATH\n"); | |
| exit(-1); | |
| } | |
| FILE *f = fopen(argv[1], "r"); | |
| fseek(f, 0, SEEK_END); | |
| long file_length = ftell(f); | |
| fseek(f, 0, SEEK_SET); | |
| char *instructions = (char *)calloc(file_length + 1, sizeof(char)); | |
| fread(instructions, file_length, 1, f); | |
| char *head = instructions; | |
| char *memory = (char *)calloc(1024 * 1024, sizeof(char)); | |
| run_jit(instructions, memory, file_length); | |
| free(memory); | |
| free(instructions); | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment