Author | Jakob Wakeling <[email protected]> |
Date | 2024-01-27 01:18:37 |
Commit | 6080736397fadd2365a16bf7d9979040c867ab36 |
Parent | 78da73c4c5daa6f6b9828bca71816985dc8c7162 |
Remedy Mandelbrot long tape segfault issue
Diffstat
M | CMakeLists.txt | | | 6 | ++++-- |
M | README.md | | | 14 | +++++++++++++- |
M | src/obfi.c | | | 132 | +++++++++++++++++++++++++++++++++++++++---------------------------------------- |
M | src/util/optget.c | | | 20 | ++++++++++---------- |
4 files changed, 92 insertions, 80 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 8fb9971..f3db411 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,13 @@ cmake_minimum_required(VERSION 3.21 FATAL_ERROR) -project(OBFI VERSION 1.0.4 LANGUAGES C) +project(OBFI LANGUAGES C) set(CMAKE_C_STANDARD 23) set(CMAKE_C_STANDARD_REQUIRED TRUE) +set(CMAKE_C_EXTENSIONS FALSE) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin) +add_compile_definitions(VERSION="$ENV{VERSION}") file(GLOB_RECURSE SRC CONFIGURE_DEPENDS ${PROJECT_SOURCE_DIR}/src/*.c) -add_compile_definitions(PROJECT_VERSION="${PROJECT_VERSION}") add_executable(obfi ${SRC}) diff --git a/README.md b/README.md index 5653c46..ad18fea 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ A minimal and performant Brainfuck interpreter. ## Implementation Details * OBFI uses single octet cells, and arithmetic wraps around. -* OBFI provides 30000 cells. +* OBFI provides 30000 cells (configurable via `-l`). * OBFI does not perform array bound checking. * OBFI will set the current cell to 0 when EOF is encountered. @@ -22,6 +22,18 @@ A minimal and performant Brainfuck interpreter. To build **OBFI**, from the project root, run `make build`. +See `./bin/obfi --help` for program options. + +## Performance + +On a machine with an Intel i7 12700K (20 cores @ 4.90 GHz) and 64 GB of +DDR4-4800 RAM, the `./examples/mandel.b` example was able to be run in an +average time of `1.945` seconds, compiled as specified above. + +For comparison, the [SBFI](https://github.com/rinoldm/sbfi) interpreter, on the +same machine, with the same example program, was able to run in an average time +of `1.245` seconds, compiled with its recommended compilation command. + ## Meta Copyright (C) 2020, Jakob Wakeling diff --git a/src/obfi.c b/src/obfi.c index ed6cd2e..2b8442e 100644 --- a/src/obfi.c +++ b/src/obfi.c @@ -1,10 +1,6 @@ // Copyright (C) 2020, Jakob Wakeling // MIT Licence -/* - FIXME Segfault with large tape lengths and certain programs. -*/ - #include "util/log.h" #include "util/optget.h" #include "util/util.h" @@ -15,7 +11,7 @@ #include <string.h> static inline int cmp(const char *s1, const char *s2); -static inline int run(const char *b, const long *c, u64 len); +static inline int run(const char *b, const u64 *c, uptr len); static const char *const help; static const char *const version; @@ -23,47 +19,51 @@ static const char *const version; int main(int ac, char *av[]) { struct opt opt = OPTGET_INIT; opt.str = "l:"; opt.lops = (struct lop[]){ { "help", ARG_NUL, 256 }, - { "version", ARG_NUL, 256 }, + { "version", ARG_NUL, 257 }, { NULL, 0, 0 } }; - - struct { u64 l; } args = { .l = 30000 }; - + + struct { uptr l; } args = { .l = 30000 }; + for (int o; (o = optget(&opt, av, 1)) != -1;) switch (o) { case 'l': { register char *s = opt.arg; register int c; - for (args.l = 0; *s >= '0' && *s <= '9'; ++s) { c = *s - '0'; - if (args.l > (SIZE_MAX - c) / 10) { break; } args.l = args.l * 10 + c; + for (args.l = 0; *s >= '0' && *s <= '9'; s += 1) { c = *s - '0'; + if (args.l > (UPTR_MAX - c) / 10) { break; } args.l = args.l * 10 + c; } - + if (*s) { log_fatal(-1, "%s: invalid tape length", opt.arg); } break; } break; case 256: { fputs(help, stdout); } return 0; case 257: { fputs(version, stdout); } return 0; default: {} return -1; } - + if (opt.ind == ac) { log_fatal(-1, "missing operand"); } - - FILE *fi = fopen(av[opt.ind], "r"); - if (!fi) { log_fatal(-1, "%s: %s", av[opt.ind], strerror(errno)); } - - fseek(fi, 0, SEEK_END); size_t fl = ftell(fi); rewind(fi); - + + /* Open and read source file */ + FILE *file = fopen(av[opt.ind], "r"); + if (!file) { log_fatal(-1, "%s: %s", av[opt.ind], strerror(errno)); } + + fseek(file, 0, SEEK_END); uptr fl = ftell(file); rewind(file); + + /* Instruction operators */ char *fb = malloc(fl + 1 * sizeof (*fb)); fb[fl] = 0; if (!fb) { log_fatal(-1, "%s", strerror(errno)); } - - long *fc = malloc(fl * sizeof (*fc)); + + /* Instruction operands */ + u64 *fc = malloc(fl * sizeof (*fc)); if (!fc) { log_fatal(-1, "%s", strerror(errno)); } - - fread(fb, 1, fl, fi); fclose(fi); - + + fread(fb, 1, fl, file); fclose(file); + /* Remove comments from instruction buffer */ - char *p = fb, *q = fb; - for (; *p; ++p) if (strchr("><+-.,[]", *p)) { *q++ = *p; } *q = 0; - - register size_t i, j; - for (i = 0; fb[i]; ++i) { + register char *p = fb, *q = fb; for (; *p; p += 1) { + if (strchr("><+-.,[]", *p)) { *q = *p; q += 1; } + } *q = 0; + + register uptr i, j; + for (i = 0; fb[i]; i += 1) { switch (fb[i]) { /* Generate initial operands */ case '<': case '-': { fc[i] = -1; break; } default: { fc[i] = 1; break; } @@ -73,79 +73,76 @@ int main(int ac, char *av[]) { case '+': case '-': { fb[i] = '+'; break; } } } - + /* Compress movement and additive instructions */ - for (i = 0, j = 0; fb[i]; ++j) { + for (i = 0, j = 0; fb[i]; j += 1) { fb[j] = fb[i]; fc[j] = fc[i]; - if (strchr(">+", fb[++i])) for (; fb[j] == fb[i]; fc[j] += fc[i++]); + if (strchr(">+", fb[i += 1])) for (; fb[j] == fb[i]; fc[j] += fc[i], i += 1); } fb[j] = 0; - - for (i = 0; fb[i]; ++i) { + + for (i = 0; fb[i]; i += 1) { /* Optimise set to zero loops */ if (cmp(fb + i, "[+]")) { fb[i] = 'Z'; fb[i + 1] = fb[i + 2] = ' '; } - + /* Optimise move to zero loops */ else if (cmp(fb + i, "[>]")) { fb[i] = 'T'; fb[i + 1] = fb[i + 2] = ' '; fc[i] = fc[i + 1]; } - + /* Optimise backward and forward move to loops */ - else if (cmp(fb + i, "[+>+>]") && fc[i + 1] == -1 && fc[i + 3] == 1 && - fc[i + 2] == -fc[i + 4]) { + else if (cmp(fb + i, "[+>+>]") && fc[i + 1] == -1 && fc[i + 3] == 1 && fc[i + 2] == -fc[i + 4]) { fb[i] = 'M'; fb[i + 1] = fb[i + 2] = fb[i + 3] = fb[i + 4] = fb[i + 5] = ' '; fc[i] = fc[i + 2]; } - else if (cmp(fb + i, "[>+>+]") && fc[i + 4] == -1 && fc[i + 2] == 1 && - fc[i + 1] == -fc[i + 3]) { + else if (cmp(fb + i, "[>+>+]") && fc[i + 4] == -1 && fc[i + 2] == 1 && fc[i + 1] == -fc[i + 3]) { fb[i] = 'M'; fb[i + 1] = fb[i + 2] = fb[i + 3] = fb[i + 4] = fb[i + 5] = ' '; fc[i] = fc[i + 1]; } } - + /* Remove resultant spaces of loop optimisations */ - for (i = 0, j = 0; fb[i]; ++i) if (fb[i] != ' ') { - fb[j] = fb[i]; fc[j] = fc[i]; ++j; + for (i = 0, j = 0; fb[i]; i += 1) if (fb[i] != ' ') { + fb[j] = fb[i]; fc[j] = fc[i]; j += 1; } fb[j] = 0; - - size_t l = 1024, t = 0, *S = malloc(l * sizeof (*S)); + + uptr l = 1024, t = 0, *S = malloc(l * sizeof (*S)); if (!S) { log_fatal(-1, "%s", strerror(errno)); } - + /* Find and store bracket pairs */ - for (size_t i = 0; fb[i]; ++i) switch (fb[i]) { - case '[': { S[t++] = i; break; } + for (uptr i = 0; fb[i]; i += 1) switch (fb[i]) { + case '[': { S[t] = i; t += 1; break; } case ']': { --t; fc[S[t]] = i - S[t]; fc[i] = S[t] - i; break; } } - + if (run(fb, fc, args.l)) { log_fatal(-1, "%s", strerror(errno)); } - + free(S); free(fb); free(fc); return 0; } /* Check if s2 is fully matched at the start of s1. */ static inline int cmp(const char *s1, const char *s2) { - size_t i = 0; for (; s2[i] && s1[i] == s2[i]; ++i) {} return !s2[i]; + uptr i = 0; for (; s2[i] && s1[i] == s2[i]; i += 1) {} return !s2[i]; } /* Execute an array of Brainfuck instructions. */ -static inline int run(const char *fb, const long *fc, u64 len) { - uint8_t *M = calloc(len, sizeof (*M)), *p = M; +static inline int run(const char *fb, const u64 *fc, uptr len) { + u8 *M = calloc(len, sizeof (*M)), *p = M + 256; /* Allow slight movement into "negative" memory */ if (!M) { log_fatal(-1, "%s", strerror(errno)); } - - // Execute each optimised Brainfuck instruction - for (size_t i = 0; fb[i]; ++i) switch (fb[i]) { - case '>': { p += fc[i]; break; } - case '+': { *p += fc[i]; break; } - case '.': { fputc(*p, stdout); break; } - case ',': { int c = fgetc(stdin); *p = c != EOF ? c : 0; break; } - case '[': { i += !*p ? fc[i] : 0; break; } - case ']': { i += *p ? fc[i] : 0; break; } - case 'Z': { *p = 0; break; } - case 'T': { for (; *p; p += fc[i]) {} break; } - case 'M': { *(p + fc[i]) += *p; *p = 0; break; } + + for (uptr i = 0; fb[i]; i += 1) switch (fb[i]) { + case '>': { p += fc[i]; } break; + case '+': { *p += fc[i]; } break; + case '.': { fputc(*p, stdout); } break; + case ',': { int c = fgetc(stdin); *p = c != EOF ? c : 0; } break; + case '[': { i += !*p ? fc[i] : 0; } break; + case ']': { i += *p ? fc[i] : 0; } break; + case 'Z': { *p = 0; } break; + case 'T': { for (; *p; p += fc[i]); } break; + case 'M': { *(p + fc[i]) += *p; *p = 0; } break; } - + free(M); return 0; } @@ -154,12 +151,13 @@ static const char *const help = "Usage:\n" " obfi file\n" "Options:\n" + " -l length Set tape length\n" " --help Display help information\n" " --version Display version information\n" ; static const char *const version = - "OBFI, version " PROJECT_VERSION "\n" + "OBFI, version " VERSION "\n" "Copyright (C) 2020, Jakob Wakeling\n" "MIT Licence (https://opensource.org/licenses/MIT)\n" ; diff --git a/src/util/optget.c b/src/util/optget.c index a21e736..893d01e 100644 --- a/src/util/optget.c +++ b/src/util/optget.c @@ -19,20 +19,20 @@ int optget(struct opt *opt, char *av[], int flags) { if (!cur) { opt->ind -= opt->nop; opt->nop = 0; return -1; } } else if (!cur || (cur[0] != '-' || cur[1] == 0)) { return -1; } - + int optind = opt->ind, optret; - + if (cur[1] == '-') { if (cur[2] == 0) { if (opt->nop) { permute(av, opt->ind++, opt->nop); opt->ind -= opt->nop; opt->nop = 0; } else { ++opt->ind; } return -1; } - + int optend, lop; optret = '?'; opt->opt = 0; opt->lop = cur; if (!opt->lops) { goto nol; } for (optend = 2; cur[optend] != '=' && cur[optend] != 0; ++optend); - + for (lop = 0; opt->lops[lop].str; ++lop) { if (strncmp(&cur[2], opt->lops[lop].str, (size_t)optend - 2) == 0) { if (!opt->lops[lop].str[optend - 2]) { @@ -40,7 +40,7 @@ int optget(struct opt *opt, char *av[], int flags) { } } } - + if (opt->lops[lop].arg > ARG_NUL) { if (cur[optend]) { opt->arg = &cur[optend + 1]; } else if (av[opt->ind + 1]) { opt->arg = av[++opt->ind]; } @@ -50,13 +50,13 @@ int optget(struct opt *opt, char *av[], int flags) { } } else { opt->arg = NULL; } - + nol: opt->pos = 0; } else { optret = opt->opt = cur[opt->pos++]; opt->lop = NULL; const char *optchr = strchr(opt->str, opt->opt); - + if (!optchr) { optret = '?'; } else if (optchr[1] == ':') { if (cur[opt->pos]) { opt->arg = &cur[opt->pos]; } @@ -66,14 +66,14 @@ nol: opt->pos = 0; } else { opt->arg = NULL; } } - + if (!opt->pos || !cur[opt->pos]) { ++opt->ind; opt->pos = 1; if (opt->nop) for (; optind < opt->ind; ++optind) { permute(av, optind, opt->nop); } } - + if (optret == '?' && opt->str[0] != ':') { if (opt->opt) { fprintf(stderr, "%c: invalid option", opt->opt); } else if (opt->lop) { fprintf(stderr, "%s: invalid option", opt->lop); } @@ -82,7 +82,7 @@ nol: opt->pos = 0; if (opt->opt) { fprintf(stderr, "%c: option requires argument", opt->opt); } else if (opt->lop) { fprintf(stderr, "%s: option requires argument", opt->lop); } } - + return optret; }