From 34addd01947ca80b735e06c54d6df30136860c1e Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Mon, 16 Oct 2017 00:03:35 +0200 Subject: [PATCH] feat: Start on x86-64 JIT backend This change adds the fundamentals for a x86-64 JIT backend which translates the Brainfuck instruction list onto x86-64 instructions. See #61 --- CMakeLists.txt | 2 +- include/jit.h | 31 +++++ src/jit/stub.c | 30 ++++ src/jit/x86_64.c | 347 +++++++++++++++++++++++++++++++++++++++++++++++ src/main.c | 3 +- 5 files changed, 411 insertions(+), 2 deletions(-) create mode 100644 include/jit.h create mode 100644 src/jit/stub.c create mode 100644 src/jit/x86_64.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 220d29f..2ce6bcc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,7 @@ option(ENABLE_EDITLINE "Enable GNU readline functionality provided by the editli include_directories(include/) add_definitions("-Wall -Wextra -O2 -std=c89") -add_executable(brainfuck src/main.c src/brainfuck.c) +add_executable(brainfuck include/brainfuck.h include/jit.h src/main.c src/brainfuck.c src/jit/x86_64.c) install(TARGETS brainfuck RUNTIME DESTINATION bin) install(FILES include/brainfuck.h DESTINATION "include") install(FILES man/brainfuck.1 DESTINATION "share/man/man1") diff --git a/include/jit.h b/include/jit.h new file mode 100644 index 0000000..dc8d64f --- /dev/null +++ b/include/jit.h @@ -0,0 +1,31 @@ +/* + * Copyright 2016 Fabian Mastenbroek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef JIT_H +#define JIT_H + +#define BRAINFUCK_EUNAVAILABLE 2 /**< Feature not implemented or unavailable */ + +/** + * Execute the given list of instructions using a Just-in-Time compiler. + * + * @param root The first in the list of instructions. + * @param context The context within the execution takes place. + * @return A negative non-zero integer to indicate a failure, zero on success. + */ +int brainfuck_execute_jit(struct BrainfuckInstruction *, + struct BrainfuckExecutionContext *); + +#endif /* JIT_H */ diff --git a/src/jit/stub.c b/src/jit/stub.c new file mode 100644 index 0000000..fab4789 --- /dev/null +++ b/src/jit/stub.c @@ -0,0 +1,30 @@ +/* + * Copyright 2016 Fabian Mastenbroek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +/** + * Execute the given list of instructions using a Just-in-Time compiler. + * + * @param root The first in the list of instructions. + * @param context The context within the execution takes place. + * @return A negative non-zero integer to indicate a failure, zero on success. + */ +int brainfuck_execute_jit(BrainfuckInstruction *root, + BrainfuckExecutionContext *context) +{ + return BRAINFUCK_EUNAVAILABLE; +} \ No newline at end of file diff --git a/src/jit/x86_64.c b/src/jit/x86_64.c new file mode 100644 index 0000000..e4a2145 --- /dev/null +++ b/src/jit/x86_64.c @@ -0,0 +1,347 @@ +/* + * Copyright 2016 Fabian Mastenbroek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include +#include + +/** + * This structure describes the state of the Just-in-Time compiler. + */ +struct BrainfuckJitState { + /** + * The memory containing the (partially) compiled program. + */ + unsigned char *memory; + + /** + * The size of the memory block. + */ + size_t size; + + /** + * The index in the memory. + */ + int index; +}; + +/** + * Emit the given program to memory. + * + * @param state The state of the JIT compiler. + * @param root The first in the list of instructions. + * @param context The context within the execution takes place. + */ +static void brainfuck_jit_emit_program(struct BrainfuckJitState *state, + BrainfuckInstruction *root, + BrainfuckExecutionContext *context); + +/** + * Allocate the given amount of bytes from the heap. This allocated memory can + * be used to write the emitted code to which can be marked executable. + * + * @param size The amount of bytes to allocate. + * @return The pointer to the memory block that has been allocated or + * null on failure. + */ +static void* brainfuck_jit_alloc(size_t size) +{ + void* mem = mmap(0, size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (mem == MAP_FAILED) { + perror("mmap"); + return NULL; + } + return mem; +} + +/** + * Mark the given block in memory as executable, but not writable. + * + * @param state The state of the compiler. + * @return 0 on success, -1 otherwise. + */ +static int brainfuck_jit_mark_executable(struct BrainfuckJitState *state) +{ + if (mprotect(state->memory, state->size, PROT_READ | PROT_EXEC) != 0) { + perror("mprotect"); + return -1; + } + return 0; +} + +/** + * Write the given bytes to memory. + * + * @param state The state of the compiler. + * @param memory The memory to write. + * @param size The size of the memory block to write. + */ +static void brainfuck_jit_emit(struct BrainfuckJitState *state, + const unsigned char *memory, + size_t size) { + memcpy(state->memory + state->index, memory, size); + state->index += size; +} + +/** + * Write the prologue of the program to the memory. + * + * @param state The state of the compiler. + */ +static void brainfuck_jit_emit_prologue(struct BrainfuckJitState *state) +{ + static const unsigned char prologue[] = { + 0x55, /* push %rbp */ + 0x48, 0x89, 0xE5, /* mov %rsp, %rbp */ + 0x48, 0x83, 0xEC, 0x20 /* sub $20, %rsp */ + }; + brainfuck_jit_emit(state, prologue, sizeof(prologue)); +} + +/** + * Write the epilogue of the program to the memory. + * + * @param state The state of the compiler. + */ +static void brainfuck_jit_emit_epilogue(struct BrainfuckJitState *state) +{ + static const unsigned char epilogue[] = { + 0x48, 0x89, 0xEC, /* mov %rbp, %rsp */ + 0x5D, /* pop %rbp */ + 0xC3 /* ret */ + }; + brainfuck_jit_emit(state, epilogue, sizeof(epilogue)); +} + +/** + * Create a dump of the memory at the given address with the given length. + * + * @param state The state of the compiler. + */ +static void brainfuck_jit_dump(const struct BrainfuckJitState *state) +{ + int i; + unsigned char buff[17]; + unsigned char *pc = state->memory; + size_t len = state->size; + + if (len == 0) { + printf(" ZERO LENGTH\n"); + return; + } + if (len < 0) { + printf(" NEGATIVE LENGTH: %lu\n", len); + return; + } + + + for (i = 0; i < len; i++) { + if ((i % 16) == 0) { + if (i != 0) + printf(" %s\n", buff); + printf(" %04x ", i); + } + printf(" %02x", pc[i]); + if ((pc[i] < 0x20) || (pc[i] > 0x7e)) + buff[i % 16] = '.'; + else + buff[i % 16] = pc[i]; + buff[(i % 16) + 1] = '\0'; + } + + while ((i % 16) != 0) { + printf(" "); + i++; + } + printf(" %s\n", buff); +} + +/** + * Emit an ADD instruction to memory. + * + * @param state The state of the JIT compiler. + * @param instruction The instruction to emit. + * @param context The context within the execution takes place. + */ +static void brainfuck_jit_emit_add(struct BrainfuckJitState *state, + BrainfuckInstruction *instruction, + BrainfuckExecutionContext *context) +{ + const unsigned char code[] = {}; + brainfuck_jit_emit(state, code, sizeof(code)); +} + +/** + * Emit a SUB instruction to memory. + * + * @param state The state of the JIT compiler. + * @param instruction The instruction to emit. + * @param context The context within the execution takes place. + */ +static void brainfuck_jit_emit_sub(struct BrainfuckJitState *state, + BrainfuckInstruction *instruction, + BrainfuckExecutionContext *context) +{ + const unsigned char code[] = {}; + brainfuck_jit_emit(state, code, sizeof(code)); +} + +/** + * Emit an MOVL instruction to memory. + * + * @param state The state of the JIT compiler. + * @param instruction The instruction to emit. + * @param context The context within the execution takes place. + */ +static void brainfuck_jit_emit_movl(struct BrainfuckJitState *state, + BrainfuckInstruction *instruction, + BrainfuckExecutionContext *context) +{ + const unsigned char code[] = {}; + brainfuck_jit_emit(state, code, sizeof(code)); +} + +/** + * Emit a MOVR instruction to memory. + * + * @param state The state of the JIT compiler. + * @param instruction The instruction to emit. + * @param context The context within the execution takes place. + */ +static void brainfuck_jit_emit_movr(struct BrainfuckJitState *state, + BrainfuckInstruction *instruction, + BrainfuckExecutionContext *context) +{ + const unsigned char code[] = {}; + brainfuck_jit_emit(state, code, sizeof(code)); +} + +/** + * Emit an INPUT instruction to memory. + * + * @param state The state of the JIT compiler. + * @param instruction The instruction to emit. + * @param context The context within the execution takes place. + */ +static void brainfuck_jit_emit_input(struct BrainfuckJitState *state, + BrainfuckInstruction *instruction, + BrainfuckExecutionContext *context) +{ + const unsigned char code[] = {}; + brainfuck_jit_emit(state, code, sizeof(code)); +} + +/** + * Emit an OUTPUT instruction to memory. + * + * @param state The state of the JIT compiler. + * @param instruction The instruction to emit. + * @param context The context within the execution takes place. + */ +static void brainfuck_jit_emit_output(struct BrainfuckJitState *state, + BrainfuckInstruction *instruction, + BrainfuckExecutionContext *context) +{ + const unsigned char code[] = {}; + brainfuck_jit_emit(state, code, sizeof(code)); +} + +/** + * Emit a single instruction to memory. + * + * @param state The state of the JIT compiler. + * @param instruction The instruction to emit. + * @param context The context within the execution takes place. + */ +static void brainfuck_jit_emit_instruction(struct BrainfuckJitState *state, + BrainfuckInstruction *instruction, + BrainfuckExecutionContext *context) +{ + switch (instruction->type) { + case BRAINFUCK_TOKEN_PLUS: + brainfuck_jit_emit_add(state, instruction, context); + break; + case BRAINFUCK_TOKEN_MINUS: + brainfuck_jit_emit_sub(state, instruction, context); + break; + case BRAINFUCK_TOKEN_PREVIOUS: + brainfuck_jit_emit_movl(state, instruction, context); + break; + case BRAINFUCK_TOKEN_NEXT: + brainfuck_jit_emit_movr(state, instruction, context); + break; + case BRAINFUCK_TOKEN_INPUT: + brainfuck_jit_emit_input(state, instruction, context); + break; + case BRAINFUCK_TOKEN_OUTPUT: + brainfuck_jit_emit_output(state, instruction, context); + break; + case BRAINFUCK_TOKEN_LOOP_START: + brainfuck_jit_emit_program(state, instruction, context); + break; + default: + break; + } +} + +/** + * Emit the given program to memory. + * + * @param state The state of the JIT compiler. + * @param root The first in the list of instructions. + * @param context The context within the execution takes place. + */ +static void brainfuck_jit_emit_program(struct BrainfuckJitState *state, + BrainfuckInstruction *root, + BrainfuckExecutionContext *context) +{ + while (root != NULL) { + brainfuck_jit_emit_instruction(state, root, context); + root = root->next; + } +} + +/** + * Execute the given list of instructions using a Just-in-Time compiler. + * + * @param root The first in the list of instructions. + * @param context The context within the execution takes place. + * @return A negative non-zero integer to indicate a failure, zero on success. + */ +int brainfuck_execute_jit(BrainfuckInstruction *root, + BrainfuckExecutionContext *context) +{ + struct BrainfuckJitState state; + size_t size = 1000; + + /* Allocate writable memory */ + state.memory = brainfuck_jit_alloc(size); + state.size = size; + state.index = 0; + + brainfuck_jit_emit_prologue(&state); + brainfuck_jit_emit_program(&state, root, context); + brainfuck_jit_emit_epilogue(&state); + brainfuck_jit_mark_executable(&state); + brainfuck_jit_dump(&state); + void (*program)() = (void *) state.memory; + program(); + return 0; +} diff --git a/src/main.c b/src/main.c index 649061a..900a08b 100644 --- a/src/main.c +++ b/src/main.c @@ -29,6 +29,7 @@ #endif #include +#include /** * Print the usage message of this program. @@ -78,7 +79,7 @@ int run_file(FILE *file) { return EXIT_FAILURE; } brainfuck_add(state, brainfuck_parse_stream(file)); - brainfuck_execute(state->root, context); + brainfuck_execute_jit(state->root, context); brainfuck_destroy_context(context); brainfuck_destroy_state(state); fclose(file);