OBFI

Brainfuck Interpreter
git clone http://git.omkov.net/OBFI
Log | Tree | Refs | README | LICENCE | Download

AuthorJakob Wakeling <[email protected]>
Date2024-01-27 01:18:37
Commit6080736397fadd2365a16bf7d9979040c867ab36
Parent78da73c4c5daa6f6b9828bca71816985dc8c7162

Remedy Mandelbrot long tape segfault issue

Diffstat

M CMakeLists.txt | 6 ++++--
M README.md | 14 +++++++++++++-
M src/obfi.c | 128 +++++++++++++++++++++++++++++++++++++++++++------------------------------------
M src/util/optget.c | 20 ++++++++++----------

4 files changed, 97 insertions, 71 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;
 }