Permalink
Browse files

eval: use direct threading

  • Loading branch information...
hellerve committed Nov 14, 2018
1 parent 61adedc commit 2954d32bc6aa952478c0640846aaf7214d0fd4a3
Showing with 63 additions and 56 deletions.
  1. +1 −1 Makefile
  2. +9 −9 README.md
  3. +52 −45 src/eval.c
  4. +1 −1 src/parser.h
@@ -3,7 +3,7 @@ BUILDDIR=bin/
PREFIX=/usr/local/bin/
SOURCES=$(wildcard src/*.c)
MAIN=main.c
override CFLAGS+=-Werror -Wall -g -fPIC -DNDEBUG -Wfloat-equal -Wundef -Wwrite-strings -Wuninitialized -pedantic -std=c11 -O2
override CFLAGS+=-Werror -Wall -g -fPIC -DNDEBUG -Wfloat-equal -Wundef -Wwrite-strings -Wuninitialized -pedantic -std=c11 -O2 -Wno-gnu

all: main.c
mkdir -p $(BUILDDIR)
@@ -18,8 +18,8 @@ tape of—typically—30,000 elements, initially all set to 0.

- `+`: Increment the value currently under the tape head.
- `-`: Decrement the value currently under the tape head.
- `>`: Advance the tape head.
- `<`: Retreat the tape head.
- `>`: Advance the tape head or wrap around.
- `<`: Retreat the tape head or wrap around.
- `.`: Print out the value currently under the tape head, interpreted as
ASCII code.
- `,`: Read a character into the cell currently under the tape head,
@@ -51,9 +51,9 @@ a newline to make it pretty.

## Implementation

Actors are implemented as pthreads. The virtual machine is a simple bytecode
VM that offers 30,000 elements to each Brainfuck program (it will actually
overflow if you go past that, oops).
Actors are implemented as pthreads. The virtual machine is a simple direct
threaded bytecode VM that offers 30,000 elements to each Brainfuck program (it
will wrap around if you go past the low or high threshold).

It should be reasonably performant, but who cares? I hope noone’s going to run
their MapReduce jobs on it. There are some low-hanging fruits for optimization,
@@ -63,15 +63,15 @@ Feel free to hack on it you want to! I’m happy to help you get started.
The VM does seem to execute [ridiculous programs](http://www.clifford.at/bfcpu/hanoi.html)
in standard Brainfuck pretty efficiently, which makes me unreasonably happy.

It’s only about 300 lines of C, so it should be reasonably consumable. The
It’s only about 350 lines of C, so it should be reasonably consumable. The
code isn’t necessarily pretty, but it seems to work well. It is not incredibly
battle-tested, though.

If you want to know more, read [my blog post](http://blog.veitheller.de/Brainfuck_and_Actors.html)!

**Disclaimer**: I know approximately as much about concurrent programming in C as
I know about writing production-grade Brainfuck. The system should be expected
to be brittle.
**Disclaimer**: I know approximately as much about concurrent programming in C
as I know about writing production-grade Brainfuck. The system should be
expected to be brittle.

<hr/>

@@ -16,63 +16,70 @@ typedef struct {
bytecode* code;
} actor_ctx;

/*
We’re using direct threadin here. Basically, we avoid looping and case
dispatch by using a computed GOTO instead. This comes at the cost of using a
GNU extension, but I love those anyway.
*/
void* eval(void* arg) {
#define DISPATCH() { c = ctx->code[i++]; goto *dispatch_table[c.code]; }
int i = 0;
int h = 0;
unsigned int t[TAPE_LEN];
actor_ctx* ctx = (actor_ctx*) arg;
bytecode c;
static void* dispatch_table[] = {
&&do_zero, &&do_inc, &&do_dec, &&do_fwd, &&do_bck, &&do_prn, &&do_read,
&&do_startl, &&do_endl, &&do_send, &&do_recv, &&do_halt
};

for (int idx = 0; idx < TAPE_LEN; idx++) t[idx] = 0;

while(1) {
c = ctx->code[i++];
switch (c.code) {
case ZERO: t[h] = 0; break;
case INC: t[h]++; break;
case DEC: t[h]--; break;
case FWD: h++;/*h = h < TAPE_LEN-1 ? h+1 : 0;*/ break;
case BCK: h--;/*h = h > -1 ? h-1 : TAPE_LEN-1;*/ break;
case PRN: printf("%c", t[h]); break;
case READ: scanf("%c", (char*)&t[h]); break;
case STARTL: if(!t[h]) i = c.arg; break;
case ENDL: if(t[h]) i = c.arg; break;
DISPATCH();
do_zero: t[h] = 0; DISPATCH();
do_inc: t[h]++; DISPATCH();
do_dec: t[h]--; DISPATCH();
do_fwd: h = h < TAPE_LEN-1 ? h+1 : 0; DISPATCH();
do_bck: h = h > -1 ? h-1 : TAPE_LEN-1; DISPATCH();
do_prn: printf("%c", t[h]); DISPATCH();
do_read: scanf("%c", (char*)&t[h]); DISPATCH();
do_startl: if(!t[h]) i = c.arg; DISPATCH();
do_endl: if(t[h]) i = c.arg; DISPATCH();

case SEND: {
if (c.arg) {
if (!ctx->up) {
fputs("Actor tried to write to non-existant up channel, ignoring.", stderr);
continue;
}
*ctx->up = t[h];
*ctx->up_written = 1;
break;
} else {
if (!ctx->down) {
fputs("Actor tried to write to non-existant down channel, ignoring.", stderr);
continue;
}
*ctx->down = t[h];
*ctx->down_written = 1;
break;
}
}
case RECV: {
while ((ctx->up_written && !(*ctx->up_written)) &&
(ctx->down_written && !(*ctx->down_written))) usleep(100);
if (ctx->up_written && *ctx->up_written) {
t[h] = *ctx->up;
*ctx->up_written = 0;
} else if (ctx->down_written && *ctx->down_written) {
t[h] = *ctx->down;
*ctx->down_written = 0;
}
break;
}

case HALT: return NULL;
do_send:
if (c.arg) {
if (!ctx->up) {
fputs("Actor tried to write to non-existant up channel, ignoring.", stderr);
DISPATCH();
}
*ctx->up = t[h];
*ctx->up_written = 1;
DISPATCH();
} else {
if (!ctx->down) {
fputs("Actor tried to write to non-existant down channel, ignoring.", stderr);
DISPATCH();
}
*ctx->down = t[h];
*ctx->down_written = 1;
DISPATCH();
}

do_recv:
while ((ctx->up_written && !(*ctx->up_written)) &&
(ctx->down_written && !(*ctx->down_written))) usleep(100);
if (ctx->up_written && *ctx->up_written) {
t[h] = *ctx->up;
*ctx->up_written = 0;
} else if (ctx->down_written && *ctx->down_written) {
t[h] = *ctx->down;
*ctx->down_written = 0;
}
DISPATCH();

do_halt:
return NULL;
#undef DISPATCH
}

void eval_actors(actors* ac) {
@@ -2,6 +2,7 @@
#define parser_h

enum BYTECODES {
ZERO = 0,
INC,
DEC,
FWD,
@@ -10,7 +11,6 @@ enum BYTECODES {
READ,
STARTL,
ENDL,
ZERO,

SEND,
RECV,

0 comments on commit 2954d32

Please sign in to comment.