diff --git a/cmake/llvm.cmake b/cmake/llvm.cmake index 3ddb958..8253637 100644 --- a/cmake/llvm.cmake +++ b/cmake/llvm.cmake @@ -68,7 +68,7 @@ else() link_directories(${LLVM_LIBRARY_DIRS}) endif() - llvm_map_components_to_libnames(llvm_libs support core irreader) + llvm_map_components_to_libnames(llvm_libs support core irreader passes) # Use an OBJECT library for shared sources to avoid flag leakage set(LIB_SOURCES diff --git a/src/interp.c b/src/interp.c index 0dc58ef..7dc2543 100644 --- a/src/interp.c +++ b/src/interp.c @@ -1,6 +1,7 @@ #include "interp.h" #include +#include #include #include #include @@ -32,6 +33,9 @@ size_t abstract_to_concrete_pc(size_t abstract_pc, struct program *program) { case CMD_JUMP_BACK: concrete_pc++; break; + case CMD_CLEAR: + case CMD_MULTIPLY: + break; default: fprintf(stderr, "Unrecognised cmd_type '%c'\n", program->cmds[cmd_index].type); @@ -171,6 +175,20 @@ int interp(struct context_t *ctx, int out_fd, int in_fd, bool byte_output) { ctx->pc = current_cmd.value.jump_index; } break; + case CMD_CLEAR: + ctx->data[ctx->dp] = 0; + break; + case CMD_MULTIPLY: + for (size_t i = 0; i < current_cmd.value.multiply.n_moves; + i++) { + int target = (int)ctx->dp + + current_cmd.value.multiply.moves[i].offset; + ctx->data[target] += + ctx->data[ctx->dp] * + (uint8_t)current_cmd.value.multiply.moves[i].factor; + } + ctx->data[ctx->dp] = 0; + break; default: fprintf(stderr, "Invalid character '%c'\n", cmd_type_to_char(current_cmd.type)); diff --git a/src/ir.c b/src/ir.c index e50f828..baafdbf 100644 --- a/src/ir.c +++ b/src/ir.c @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -56,6 +57,9 @@ size_t program_str_length(struct program *program) { case CMD_JUMP_BACK: length++; break; + case CMD_CLEAR: + case CMD_MULTIPLY: + break; default: fprintf(stderr, "Unrecognised cmd_type '%c'\n", program->cmds[cmd_index].type); @@ -203,6 +207,9 @@ char *program_to_string(struct program *program) { out[str_index++] = cmd_type_to_char(program->cmds[cmd_index].type); break; + case CMD_CLEAR: + case CMD_MULTIPLY: + break; default: fprintf(stderr, "Unrecognised cmd_type '%c'\n", program->cmds[cmd_index].type); @@ -222,6 +229,254 @@ char program_contains_output(struct program *program) { return 0; } +static int are_opposing(enum cmd_type a, enum cmd_type b) { + return (a == CMD_SIMPLE_INC && b == CMD_SIMPLE_DEC) || + (a == CMD_SIMPLE_DEC && b == CMD_SIMPLE_INC) || + (a == CMD_SIMPLE_RIGHT && b == CMD_SIMPLE_LEFT) || + (a == CMD_SIMPLE_LEFT && b == CMD_SIMPLE_RIGHT); +} + +static enum cmd_type opposite_type(enum cmd_type t) { + switch (t) { + case CMD_SIMPLE_INC: + return CMD_SIMPLE_DEC; + case CMD_SIMPLE_DEC: + return CMD_SIMPLE_INC; + case CMD_SIMPLE_RIGHT: + return CMD_SIMPLE_LEFT; + case CMD_SIMPLE_LEFT: + return CMD_SIMPLE_RIGHT; + default: + return t; + } +} + +static void cancel_opposing(struct program *program) { + struct cmd *new_cmds = malloc(program->length * sizeof(struct cmd)); + size_t *old_to_new = malloc(program->length * sizeof(size_t)); + size_t *new_to_old = malloc(program->length * sizeof(size_t)); + if (!new_cmds || !old_to_new || !new_to_old) { + fprintf(stderr, "Memory allocation failed\n"); + exit(1); + } + size_t new_len = 0; + + for (size_t old = 0; old < program->length; old++) { + struct cmd curr = program->cmds[old]; + if (new_len > 0) { + struct cmd *prev = &new_cmds[new_len - 1]; + if (are_opposing(prev->type, curr.type)) { + size_t pc = prev->value.simple_count; + size_t cc = curr.value.simple_count; + if (cc > pc) { + prev->type = opposite_type(prev->type); + prev->value.simple_count = cc - pc; + } else if (cc < pc) { + prev->value.simple_count = pc - cc; + } else { + old_to_new[new_to_old[new_len - 1]] = + SIZE_MAX; + new_len--; + } + old_to_new[old] = SIZE_MAX; + continue; + } + } + old_to_new[old] = new_len; + new_to_old[new_len] = old; + new_cmds[new_len++] = curr; + } + + for (size_t i = 0; i < new_len; i++) { + if (new_cmds[i].type == CMD_JUMP_FORWARD || + new_cmds[i].type == CMD_JUMP_BACK) { + size_t old_target = + program->cmds[new_to_old[i]].value.jump_index; + assert(old_to_new[old_target] != SIZE_MAX); + new_cmds[i].value.jump_index = old_to_new[old_target]; + } + } + + free(program->cmds); + free(old_to_new); + free(new_to_old); + program->cmds = new_cmds; + program->length = new_len; +} + +static void detect_clear_loops(struct program *program) { + struct cmd *new_cmds = malloc(program->length * sizeof(struct cmd)); + size_t *old_to_new = malloc(program->length * sizeof(size_t)); + size_t *new_to_old = malloc(program->length * sizeof(size_t)); + if (!new_cmds || !old_to_new || !new_to_old) { + fprintf(stderr, "Memory allocation failed\n"); + exit(1); + } + size_t new_len = 0; + + for (size_t old = 0; old < program->length;) { + struct cmd c = program->cmds[old]; + if (c.type == CMD_JUMP_FORWARD && old + 2 < program->length) { + struct cmd body = program->cmds[old + 1]; + struct cmd close = program->cmds[old + 2]; + if ((body.type == CMD_SIMPLE_INC || + body.type == CMD_SIMPLE_DEC) && + close.type == CMD_JUMP_BACK && + c.value.jump_index == old + 2) { + old_to_new[old] = new_len; + old_to_new[old + 1] = SIZE_MAX; + old_to_new[old + 2] = SIZE_MAX; + new_to_old[new_len] = old; + new_cmds[new_len++] = + (struct cmd){.type = CMD_CLEAR}; + old += 3; + continue; + } + } + old_to_new[old] = new_len; + new_to_old[new_len] = old; + new_cmds[new_len++] = c; + old++; + } + + for (size_t i = 0; i < new_len; i++) { + if (new_cmds[i].type == CMD_JUMP_FORWARD || + new_cmds[i].type == CMD_JUMP_BACK) { + size_t old_target = + program->cmds[new_to_old[i]].value.jump_index; + assert(old_to_new[old_target] != SIZE_MAX); + new_cmds[i].value.jump_index = old_to_new[old_target]; + } + } + + free(program->cmds); + free(old_to_new); + free(new_to_old); + program->cmds = new_cmds; + program->length = new_len; +} + +#define DELTA_RANGE 64 + +static void detect_multiply_loops(struct program *program) { + struct cmd *new_cmds = malloc(program->length * sizeof(struct cmd)); + size_t *old_to_new = malloc(program->length * sizeof(size_t)); + size_t *new_to_old = malloc(program->length * sizeof(size_t)); + if (!new_cmds || !old_to_new || !new_to_old) { + fprintf(stderr, "Memory allocation failed\n"); + exit(1); + } + size_t new_len = 0; + + for (size_t old = 0; old < program->length;) { + struct cmd c = program->cmds[old]; + if (c.type == CMD_JUMP_FORWARD) { + size_t close_idx = c.value.jump_index; + int valid = 1; + int dp_delta = 0; + int deltas[2 * DELTA_RANGE + 1]; + memset(deltas, 0, sizeof(deltas)); + + for (size_t k = old + 1; k < close_idx && valid; k++) { + struct cmd bk = program->cmds[k]; + switch (bk.type) { + case CMD_SIMPLE_RIGHT: + dp_delta += (int)bk.value.simple_count; + if (dp_delta > DELTA_RANGE || + dp_delta < -DELTA_RANGE) + valid = 0; + break; + case CMD_SIMPLE_LEFT: + dp_delta -= (int)bk.value.simple_count; + if (dp_delta > DELTA_RANGE || + dp_delta < -DELTA_RANGE) + valid = 0; + break; + case CMD_SIMPLE_INC: + deltas[dp_delta + DELTA_RANGE] += + (int)bk.value.simple_count; + break; + case CMD_SIMPLE_DEC: + deltas[dp_delta + DELTA_RANGE] -= + (int)bk.value.simple_count; + break; + default: + valid = 0; + break; + } + } + + if (valid && dp_delta == 0 && + deltas[DELTA_RANGE] == -1) { + struct multiply_move moves[MULTIPLY_MOVES_MAX]; + size_t n_moves = 0; + int overflow = 0; + for (int d = -DELTA_RANGE; + d <= DELTA_RANGE && !overflow; d++) { + if (d == 0 || + deltas[d + DELTA_RANGE] == 0) + continue; + if (n_moves >= MULTIPLY_MOVES_MAX) { + overflow = 1; + break; + } + moves[n_moves++] = + (struct multiply_move){ + .offset = d, + .factor = + deltas[d + DELTA_RANGE]}; + } + if (!overflow) { + for (size_t k = old; k <= close_idx; + k++) { + old_to_new[k] = (k == old) + ? new_len + : SIZE_MAX; + } + new_to_old[new_len] = old; + struct cmd mc = { + .type = CMD_MULTIPLY, + .value.multiply.n_moves = n_moves}; + for (size_t i = 0; i < n_moves; i++) + mc.value.multiply.moves[i] = + moves[i]; + new_cmds[new_len++] = mc; + old = close_idx + 1; + continue; + } + } + } + old_to_new[old] = new_len; + new_to_old[new_len] = old; + new_cmds[new_len++] = c; + old++; + } + + for (size_t i = 0; i < new_len; i++) { + if (new_cmds[i].type == CMD_JUMP_FORWARD || + new_cmds[i].type == CMD_JUMP_BACK) { + size_t old_target = + program->cmds[new_to_old[i]].value.jump_index; + assert(old_to_new[old_target] != SIZE_MAX); + new_cmds[i].value.jump_index = old_to_new[old_target]; + } + } + + free(program->cmds); + free(old_to_new); + free(new_to_old); + program->cmds = new_cmds; + program->length = new_len; +} + +#undef DELTA_RANGE + +void optimise_program(struct program *program) { + cancel_opposing(program); + detect_clear_loops(program); + detect_multiply_loops(program); +} + char program_contains_input(struct program *program) { for (size_t cmd_index = 0; cmd_index < program->length; cmd_index++) { if (program->cmds[cmd_index].type == CMD_SIMPLE_INPUT) { diff --git a/src/ir.h b/src/ir.h index cfc502a..7d02668 100644 --- a/src/ir.h +++ b/src/ir.h @@ -21,6 +21,21 @@ enum cmd_type { CMD_JUMP_FORWARD, /// `']'`: jump back if current cell is non-zero. CMD_JUMP_BACK, + /// Synthetic: set current cell to zero (replaces `[-]`/`[+]`). + CMD_CLEAR, + /// Synthetic: multiply-add loop (replaces `[-offset1*factor1...]`). + CMD_MULTIPLY, +}; + +/// Maximum number of target cells in a CMD_MULTIPLY instruction. +#define MULTIPLY_MOVES_MAX 8 + +/// One (offset, factor) pair in a CMD_MULTIPLY instruction. +struct multiply_move { + /// Cell offset from the current data pointer. + int offset; + /// Multiplier applied to the loop counter cell. + int factor; }; /// One compressed instruction in the internal Brainfuck IR. @@ -33,6 +48,11 @@ struct cmd { size_t simple_count; /// Matching bracket command index. size_t jump_index; + /// Moves for CMD_MULTIPLY. + struct { + struct multiply_move moves[MULTIPLY_MOVES_MAX]; + size_t n_moves; + } multiply; } value; }; @@ -83,4 +103,8 @@ char program_contains_input(struct program *program); /// @return 1 if valid; otherwise 0. char program_is_valid(char *source_str); +/// Apply IR-level optimisations to a parsed program in-place. +/// @param program Program to optimise. +void optimise_program(struct program *program); + #endif diff --git a/src/llvm.c b/src/llvm.c index d8e3f84..22da5ba 100644 --- a/src/llvm.c +++ b/src/llvm.c @@ -1,9 +1,12 @@ #include "llvm.h" #include +#include #include #include +#include + #include "common.h" #include "ir.h" @@ -95,8 +98,6 @@ struct llvm_context create_module_preamble(struct program *program, if (program_contains_input(program)) { create_getchar_declaration(&ctx); } - ctx.dp = LLVMAddGlobal(ctx.module, int32_type(&ctx), "dp"); - LLVMSetInitializer(ctx.dp, LLVMConstNull(int32_type(&ctx))); ctx.data = LLVMAddGlobal(ctx.module, data_array_type(&ctx), "data"); LLVMSetInitializer(ctx.data, LLVMConstNull(data_array_type(&ctx))); ctx.js = jump_stack_new(); @@ -115,6 +116,9 @@ void create_main_function(struct llvm_context *ctx) { LLVMBasicBlockRef entry_block = LLVMAppendBasicBlockInContext(ctx->context, ctx->main, "entry"); LLVMPositionBuilderAtEnd(ctx->builder, entry_block); + ctx->dp = LLVMBuildAlloca(ctx->builder, int32_type(ctx), "dp"); + LLVMBuildStore(ctx->builder, LLVMConstInt(int32_type(ctx), 0, 0), + ctx->dp); } LLVMValueRef get_dataptr(struct llvm_context *ctx) { @@ -184,6 +188,43 @@ void comma(struct llvm_context *ctx) { LLVMBuildStore(ctx->builder, char_value, data_ptr); } +void multiply(struct llvm_context *ctx, struct multiply_move *moves, + size_t n_moves) { + LLVMValueRef counter_ptr = get_dataptr(ctx); + LLVMValueRef counter = + LLVMBuildLoad2(ctx->builder, int8_type(ctx), counter_ptr, ""); + for (size_t i = 0; i < n_moves; i++) { + LLVMValueRef dp_value = + LLVMBuildLoad2(ctx->builder, int32_type(ctx), ctx->dp, ""); + LLVMValueRef offset = + LLVMConstInt(int32_type(ctx), (uint64_t)moves[i].offset, 1); + LLVMValueRef target_idx = + LLVMBuildAdd(ctx->builder, dp_value, offset, ""); + LLVMValueRef indices[] = {LLVMConstInt(int32_type(ctx), 0, 0), + target_idx}; + LLVMValueRef target_ptr = + LLVMBuildGEP2(ctx->builder, data_array_type(ctx), ctx->data, + indices, 2, ""); + LLVMValueRef target = LLVMBuildLoad2( + ctx->builder, int8_type(ctx), target_ptr, ""); + LLVMValueRef factor = + LLVMConstInt(int8_type(ctx), (uint64_t)moves[i].factor, 1); + LLVMValueRef product = + LLVMBuildMul(ctx->builder, counter, factor, ""); + LLVMValueRef new_val = + LLVMBuildAdd(ctx->builder, target, product, ""); + LLVMBuildStore(ctx->builder, new_val, target_ptr); + } + LLVMBuildStore(ctx->builder, LLVMConstInt(int8_type(ctx), 0, 0), + counter_ptr); +} + +void clear(struct llvm_context *ctx) { + LLVMValueRef data_ptr = get_dataptr(ctx); + LLVMBuildStore(ctx->builder, LLVMConstInt(int8_type(ctx), 0, 0), + data_ptr); +} + void left_bracket(struct llvm_context *ctx) { LLVMValueRef data_ptr = get_dataptr(ctx); LLVMValueRef current_value = @@ -212,7 +253,7 @@ void right_bracket(struct llvm_context *ctx) { LLVMPositionBuilderAtEnd(ctx->builder, pair.exit); } -LLVMModuleRef generate(struct program *program) { +LLVMModuleRef generate(struct program *program, bool optimise) { struct llvm_context ctx = create_module_preamble(program, "main"); create_main_function(&ctx); for (size_t cmd_index = 0; cmd_index < program->length; cmd_index++) { @@ -250,6 +291,13 @@ LLVMModuleRef generate(struct program *program) { case CMD_JUMP_BACK: right_bracket(&ctx); break; + case CMD_CLEAR: + clear(&ctx); + break; + case CMD_MULTIPLY: + multiply(&ctx, command.value.multiply.moves, + command.value.multiply.n_moves); + break; default: fprintf(stderr, "Unsupported cmd_type '%c'\n", command.type); @@ -258,5 +306,17 @@ LLVMModuleRef generate(struct program *program) { } LLVMBuildRet(ctx.builder, LLVMConstInt(int32_type(&ctx), 0, 0)); LLVMDisposeBuilder(ctx.builder); + if (optimise) { + LLVMPassBuilderOptionsRef opts = LLVMCreatePassBuilderOptions(); + LLVMErrorRef err = LLVMRunPasses( + ctx.module, "mem2reg,instcombine,simplifycfg,gvn", NULL, + opts); + if (err) { + char *msg = LLVMGetErrorMessage(err); + fprintf(stderr, "Pass error: %s\n", msg); + LLVMDisposeErrorMessage(msg); + } + LLVMDisposePassBuilderOptions(opts); + } return ctx.module; } diff --git a/src/llvm.h b/src/llvm.h index 426b767..21adf80 100644 --- a/src/llvm.h +++ b/src/llvm.h @@ -1,14 +1,17 @@ #ifndef LLVM_H #define LLVM_H +#include + #include #include "ir.h" /// Generate LLVM IR for a parsed Brainfuck program. /// @param program Parsed Brainfuck program. +/// @param optimise Run LLVM optimisation passes (mem2reg, instcombine, etc.). /// @return Generated LLVM module. -LLVMModuleRef generate(struct program *program); +LLVMModuleRef generate(struct program *program, bool optimise); /// Release an LLVM module created by generate(). /// @param module LLVM module created by `generate`. diff --git a/src/main_bfc.c b/src/main_bfc.c index d382b26..1b5f4f4 100644 --- a/src/main_bfc.c +++ b/src/main_bfc.c @@ -72,7 +72,8 @@ int main(int argc, char **argv) { } struct program parsed_program = string_to_program(program_str); free(program_str); - LLVMModuleRef module = generate(&parsed_program); + optimise_program(&parsed_program); + LLVMModuleRef module = generate(&parsed_program, optimise); char *err = NULL; LLVMPrintModuleToFile(module, "/dev/stdout", &err); if (err) diff --git a/src/main_bfi.c b/src/main_bfi.c index 640b0a7..99a13f8 100644 --- a/src/main_bfi.c +++ b/src/main_bfi.c @@ -67,6 +67,7 @@ int main(int argc, char **argv) { } struct program parsed_program = string_to_program(program_str); free(program_str); + optimise_program(&parsed_program); struct context_t ctx = init_context(parsed_program); while (!interp(&ctx, STDOUT_FILENO, STDIN_FILENO, byte_output)) { }; diff --git a/test/main_fuzz.c b/test/main_fuzz.c index 9b8fd04..670bb05 100644 --- a/test/main_fuzz.c +++ b/test/main_fuzz.c @@ -31,7 +31,8 @@ int main(int argc, char **argv) { input[input_len] = '\0'; clean_whitespace(input); struct program p = string_to_program(input); - LLVMModuleRef module = generate(&p); + optimise_program(&p); + LLVMModuleRef module = generate(&p, false); char *module_str = LLVMPrintModuleToString(module); // Optionally, do something with module_str (e.g., hash, check, // etc.) diff --git a/test/res/multiply.b b/test/res/multiply.b new file mode 100644 index 0000000..bfcc969 --- /dev/null +++ b/test/res/multiply.b @@ -0,0 +1 @@ +>+++++[<+>-] \ No newline at end of file diff --git a/test/test_hi.filecheck b/test/test_hi.filecheck index ad965bc..b8f060c 100644 --- a/test/test_hi.filecheck +++ b/test/test_hi.filecheck @@ -1,42 +1,44 @@ ; RUN: %bf %s.b --emit-llvm | FileCheck %s ; Test brainfuck program that outputs "Hi" -; This should generate IR for setting up 'H' (72) and 'i' (105) and calling putchar ; CHECK: ; ModuleID = 'main' ; CHECK: source_filename = "main" -; CHECK: @dp = global i32 0 +; @dp is now an alloca inside main, not a global. +; CHECK-NOT: @dp = global ; CHECK: @data = global [65536 x i8] zeroinitializer ; CHECK: declare i32 @putchar(i32) ; CHECK: define i32 @main() { ; CHECK: entry: +; CHECK: %dp = alloca i32, align 4 +; CHECK: store i32 0, ptr %dp, align 4 ; First, build up to 'H' (ASCII 72) -; CHECK: %[[DP1:.*]] = load i32, ptr @dp, align 4 +; CHECK: %[[DP1:.*]] = load i32, ptr %dp, align 4 ; CHECK: %[[PTR1:.*]] = getelementptr [65536 x i8], ptr @data, i32 0, i32 %[[DP1]] ; CHECK: %[[VAL1:.*]] = load i8, ptr %[[PTR1]], align 1 ; CHECK: %[[ADD1:.*]] = add i8 %[[VAL1]], 72 ; CHECK: store i8 %[[ADD1]], ptr %[[PTR1]], align 1 ; Output 'H' with putchar -; CHECK: %[[DP2:.*]] = load i32, ptr @dp, align 4 +; CHECK: %[[DP2:.*]] = load i32, ptr %dp, align 4 ; CHECK: %[[PTR2:.*]] = getelementptr [65536 x i8], ptr @data, i32 0, i32 %[[DP2]] ; CHECK: %[[VAL2:.*]] = load i8, ptr %[[PTR2]], align 1 ; CHECK: %[[EXT1:.*]] = zext i8 %[[VAL2]] to i32 ; CHECK: %[[CALL1:.*]] = call i32 @putchar(i32 %[[EXT1]]) ; Add 33 more to get to 'i' (72 + 33 = 105) -; CHECK: %[[DP3:.*]] = load i32, ptr @dp, align 4 +; CHECK: %[[DP3:.*]] = load i32, ptr %dp, align 4 ; CHECK: %[[PTR3:.*]] = getelementptr [65536 x i8], ptr @data, i32 0, i32 %[[DP3]] ; CHECK: %[[VAL3:.*]] = load i8, ptr %[[PTR3]], align 1 ; CHECK: %[[ADD2:.*]] = add i8 %[[VAL3]], 33 ; CHECK: store i8 %[[ADD2]], ptr %[[PTR3]], align 1 ; Output 'i' with putchar -; CHECK: %[[DP4:.*]] = load i32, ptr @dp, align 4 +; CHECK: %[[DP4:.*]] = load i32, ptr %dp, align 4 ; CHECK: %[[PTR4:.*]] = getelementptr [65536 x i8], ptr @data, i32 0, i32 %[[DP4]] ; CHECK: %[[VAL4:.*]] = load i8, ptr %[[PTR4]], align 1 ; CHECK: %[[EXT2:.*]] = zext i8 %[[VAL4]] to i32 diff --git a/test/test_multiply.filecheck b/test/test_multiply.filecheck new file mode 100644 index 0000000..1e70ca0 --- /dev/null +++ b/test/test_multiply.filecheck @@ -0,0 +1,46 @@ +; RUN: %bf %s.b --emit-llvm | FileCheck %s + +; Test multiply-loop optimisation: >+++++[<+>-] +; Moves dp right, sets cell[1]=5, then [<+>-] adds cell[1] into cell[0] +; and zeros cell[1]. Optimised to CMD_MULTIPLY {offset=-1, factor=1}. + +; CHECK: ; ModuleID = 'main' +; CHECK: source_filename = "main" + +; @dp is now an alloca inside main, not a global. +; CHECK-NOT: @dp = global +; CHECK: @data = global [65536 x i8] zeroinitializer + +; CHECK: define i32 @main() { +; CHECK: entry: +; CHECK: %dp = alloca i32, align 4 +; CHECK: store i32 0, ptr %dp, align 4 + +; > (move right) +; CHECK: %[[DP1:.*]] = load i32, ptr %dp, align 4 +; CHECK: %[[RIGHT:.*]] = add i32 %[[DP1]], 1 +; CHECK: store i32 %[[RIGHT]], ptr %dp, align 4 + +; +++++ (set cell[1] = 5) +; CHECK: %[[DP2:.*]] = load i32, ptr %dp, align 4 +; CHECK: %[[PTR1:.*]] = getelementptr [65536 x i8], ptr @data, i32 0, i32 %[[DP2]] +; CHECK: %[[VAL1:.*]] = load i8, ptr %[[PTR1]], align 1 +; CHECK: %[[ADD:.*]] = add i8 %[[VAL1]], 5 +; CHECK: store i8 %[[ADD]], ptr %[[PTR1]], align 1 + +; CMD_MULTIPLY {offset=-1, factor=1}: load counter (cell[1]), +; compute cell[0] += counter * 1, zero cell[1]. +; CHECK: %[[DP3:.*]] = load i32, ptr %dp, align 4 +; CHECK: %[[CPTR:.*]] = getelementptr [65536 x i8], ptr @data, i32 0, i32 %[[DP3]] +; CHECK: %[[COUNTER:.*]] = load i8, ptr %[[CPTR]], align 1 +; CHECK: %[[DP4:.*]] = load i32, ptr %dp, align 4 +; CHECK: %[[TIDX:.*]] = add i32 %[[DP4]], -1 +; CHECK: %[[TPTR:.*]] = getelementptr [65536 x i8], ptr @data, i32 0, i32 %[[TIDX]] +; CHECK: %[[TVAL:.*]] = load i8, ptr %[[TPTR]], align 1 +; CHECK: %[[PROD:.*]] = mul i8 %[[COUNTER]], 1 +; CHECK: %[[NEW:.*]] = add i8 %[[TVAL]], %[[PROD]] +; CHECK: store i8 %[[NEW]], ptr %[[TPTR]], align 1 +; CHECK: store i8 0, ptr %[[CPTR]], align 1 + +; CHECK: ret i32 0 +; CHECK: } diff --git a/test/test_simple_echo.filecheck b/test/test_simple_echo.filecheck index 40fb089..fde5073 100644 --- a/test/test_simple_echo.filecheck +++ b/test/test_simple_echo.filecheck @@ -1,12 +1,12 @@ ; RUN: %bf %s.b --emit-llvm | FileCheck %s ; Test brainfuck program that echoes a character: ,. -; This should generate IR for getchar() followed by putchar() ; CHECK: ; ModuleID = 'main' ; CHECK: source_filename = "main" -; CHECK: @dp = global i32 0 +; @dp is now an alloca inside main, not a global. +; CHECK-NOT: @dp = global ; CHECK: @data = global [65536 x i8] zeroinitializer ; CHECK: declare i32 @putchar(i32) @@ -15,16 +15,18 @@ ; CHECK: define i32 @main() { ; CHECK: entry: +; CHECK: %dp = alloca i32, align 4 +; CHECK: store i32 0, ptr %dp, align 4 ; Input operation (,) - read character with getchar -; CHECK: %[[DP1:.*]] = load i32, ptr @dp, align 4 +; CHECK: %[[DP1:.*]] = load i32, ptr %dp, align 4 ; CHECK: %[[PTR1:.*]] = getelementptr [65536 x i8], ptr @data, i32 0, i32 %[[DP1]] ; CHECK: %[[GETCHAR:.*]] = call i32 @getchar() ; CHECK: %[[TRUNC:.*]] = trunc i32 %[[GETCHAR]] to i8 ; CHECK: store i8 %[[TRUNC]], ptr %[[PTR1]], align 1 ; Output operation (.) - write character with putchar -; CHECK: %[[DP2:.*]] = load i32, ptr @dp, align 4 +; CHECK: %[[DP2:.*]] = load i32, ptr %dp, align 4 ; CHECK: %[[PTR2:.*]] = getelementptr [65536 x i8], ptr @data, i32 0, i32 %[[DP2]] ; CHECK: %[[VAL:.*]] = load i8, ptr %[[PTR2]], align 1 ; CHECK: %[[EXT:.*]] = zext i8 %[[VAL]] to i32 diff --git a/test/test_simple_loop.filecheck b/test/test_simple_loop.filecheck index cd01342..34479d5 100644 --- a/test/test_simple_loop.filecheck +++ b/test/test_simple_loop.filecheck @@ -1,46 +1,32 @@ ; RUN: %bf %s.b --emit-llvm | FileCheck %s -; Test brainfuck program with a simple loop -; This should generate IR for adding 5 to a cell, then looping to decrement until zero +; Test brainfuck program with a simple loop: +++++[-] +; The [-] pattern is optimised to a single store of zero (CMD_CLEAR). ; CHECK: ; ModuleID = 'main' ; CHECK: source_filename = "main" -; CHECK: @dp = global i32 0 +; @dp is now an alloca inside main, not a global. +; CHECK-NOT: @dp = global ; CHECK: @data = global [65536 x i8] zeroinitializer ; CHECK: define i32 @main() { ; CHECK: entry: +; CHECK: %dp = alloca i32, align 4 +; CHECK: store i32 0, ptr %dp, align 4 -; Initial addition of 5 to current cell -; CHECK: %[[DP1:.*]] = load i32, ptr @dp, align 4 +; Initial addition of 5 to current cell (+++++). +; CHECK: %[[DP1:.*]] = load i32, ptr %dp, align 4 ; CHECK: %[[PTR1:.*]] = getelementptr [65536 x i8], ptr @data, i32 0, i32 %[[DP1]] ; CHECK: %[[VAL1:.*]] = load i8, ptr %[[PTR1]], align 1 ; CHECK: %[[ADD:.*]] = add i8 %[[VAL1]], 5 ; CHECK: store i8 %[[ADD]], ptr %[[PTR1]], align 1 -; Loop condition check - load current value and compare to zero -; CHECK: %[[DP2:.*]] = load i32, ptr @dp, align 4 +; CMD_CLEAR: [-] collapses to a single store of zero. +; CHECK: %[[DP2:.*]] = load i32, ptr %dp, align 4 ; CHECK: %[[PTR2:.*]] = getelementptr [65536 x i8], ptr @data, i32 0, i32 %[[DP2]] -; CHECK: %[[VAL2:.*]] = load i8, ptr %[[PTR2]], align 1 -; CHECK: %[[LOOPCOND:.*]] = icmp ne i8 %[[VAL2]], 0 -; CHECK: br i1 %[[LOOPCOND]], label %{{.*}}, label %exit +; CHECK: store i8 0, ptr %[[PTR2]], align 1 -; Loop body - decrement current cell -; CHECK: %[[DP3:.*]] = load i32, ptr @dp, align 4 -; CHECK: %[[PTR3:.*]] = getelementptr [65536 x i8], ptr @data, i32 0, i32 %[[DP3]] -; CHECK: %[[VAL3:.*]] = load i8, ptr %[[PTR3]], align 1 -; CHECK: %[[SUB:.*]] = sub i8 %[[VAL3]], 1 -; CHECK: store i8 %[[SUB]], ptr %[[PTR3]], align 1 - -; Loop condition check again (end of loop body) -; CHECK: %[[DP4:.*]] = load i32, ptr @dp, align 4 -; CHECK: %[[PTR4:.*]] = getelementptr [65536 x i8], ptr @data, i32 0, i32 %[[DP4]] -; CHECK: %[[VAL4:.*]] = load i8, ptr %[[PTR4]], align 1 -; CHECK: %[[LOOPCOND2:.*]] = icmp ne i8 %[[VAL4]], 0 -; CHECK: br i1 %[[LOOPCOND2]], label %{{.*}}, label %exit - -; Exit block -; CHECK: exit: +; Return statement ; CHECK: ret i32 0 ; CHECK: } diff --git a/test/test_simple_no_io.filecheck b/test/test_simple_no_io.filecheck index 28b7d14..b9cbb1f 100644 --- a/test/test_simple_no_io.filecheck +++ b/test/test_simple_no_io.filecheck @@ -3,38 +3,41 @@ ; CHECK: ; ModuleID = 'main' ; CHECK: source_filename = "main" -; CHECK: @dp = global i32 0 +; @dp is now an alloca inside main, not a global. +; CHECK-NOT: @dp = global ; CHECK: @data = global [65536 x i8] zeroinitializer ; CHECK: define i32 @main() { ; CHECK: entry: +; CHECK: %dp = alloca i32, align 4 +; CHECK: store i32 0, ptr %dp, align 4 ; First increment operation (++) -; CHECK: %[[DP1:.*]] = load i32, ptr @dp, align 4 +; CHECK: %[[DP1:.*]] = load i32, ptr %dp, align 4 ; CHECK: %[[PTR1:.*]] = getelementptr [65536 x i8], ptr @data, i32 0, i32 %[[DP1]] ; CHECK: %[[VAL1:.*]] = load i8, ptr %[[PTR1]], align 1 ; CHECK: %[[ADD1:.*]] = add i8 %[[VAL1]], 2 ; CHECK: store i8 %[[ADD1]], ptr %[[PTR1]], align 1 ; Move right operation (>) -; CHECK: %[[DP2:.*]] = load i32, ptr @dp, align 4 +; CHECK: %[[DP2:.*]] = load i32, ptr %dp, align 4 ; CHECK: %[[RIGHT:.*]] = add i32 %[[DP2]], 1 -; CHECK: store i32 %[[RIGHT]], ptr @dp, align 4 +; CHECK: store i32 %[[RIGHT]], ptr %dp, align 4 ; Decrement operation (--) -; CHECK: %[[DP3:.*]] = load i32, ptr @dp, align 4 +; CHECK: %[[DP3:.*]] = load i32, ptr %dp, align 4 ; CHECK: %[[PTR2:.*]] = getelementptr [65536 x i8], ptr @data, i32 0, i32 %[[DP3]] ; CHECK: %[[VAL2:.*]] = load i8, ptr %[[PTR2]], align 1 ; CHECK: %[[SUB:.*]] = sub i8 %[[VAL2]], 2 ; CHECK: store i8 %[[SUB]], ptr %[[PTR2]], align 1 ; Move left operation (<) -; CHECK: %[[DP4:.*]] = load i32, ptr @dp, align 4 +; CHECK: %[[DP4:.*]] = load i32, ptr %dp, align 4 ; CHECK: %[[LEFT:.*]] = sub i32 %[[DP4]], 1 -; CHECK: store i32 %[[LEFT]], ptr @dp, align 4 +; CHECK: store i32 %[[LEFT]], ptr %dp, align 4 ; Final increment operation (++) -; CHECK: %[[DP5:.*]] = load i32, ptr @dp, align 4 +; CHECK: %[[DP5:.*]] = load i32, ptr %dp, align 4 ; CHECK: %[[PTR3:.*]] = getelementptr [65536 x i8], ptr @data, i32 0, i32 %[[DP5]] ; CHECK: %[[VAL3:.*]] = load i8, ptr %[[PTR3]], align 1 ; CHECK: %[[ADD2:.*]] = add i8 %[[VAL3]], 2