Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmake/llvm.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 18 additions & 0 deletions src/interp.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "interp.h"

#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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));
Expand Down
255 changes: 255 additions & 0 deletions src/ir.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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) {
Expand Down
24 changes: 24 additions & 0 deletions src/ir.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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;
};

Expand Down Expand Up @@ -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
Loading
Loading