Created
January 28, 2023 11:21
-
-
Save gabryon99/b7d87a39352692647fd274c84fc35904 to your computer and use it in GitHub Desktop.
Implement simple JavaScript's generator in C
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdlib.h> | |
#include <setjmp.h> | |
#define STACK_SIZE (4 * 1024) // 4KB | |
#define GEN_YIELD (1) | |
#define GEN_EXIT (-1) | |
#define yield(x) \ | |
do { \ | |
current_generator->yielded = x; \ | |
current_generator->status = G_YIELD; \ | |
if (setjmp(current_generator->env)) { ; } else { longjmp(gen_ctx.buf, 1); } \ | |
} while (0) | |
typedef struct generator { | |
int id; | |
jmp_buf env; | |
char stack[STACK_SIZE]; | |
char* stack_top; | |
// Current generator status | |
enum { | |
G_CREATED, | |
G_RUN, | |
G_YIELD, | |
G_EXIT | |
} status; | |
void (*fun)(int); | |
int arg; | |
int yielded; | |
} generator_t; | |
struct { | |
int id; | |
jmp_buf buf; | |
} gen_ctx; | |
generator_t* current_generator = NULL; | |
generator_t* gen_spawn(void (*fun)(int), int arg) { | |
generator_t* gen = malloc(sizeof(generator_t)); | |
if (gen == NULL) { | |
perror("Failed memory allocation"); | |
exit(EXIT_FAILURE); | |
} | |
gen->stack_top = gen->stack + STACK_SIZE; | |
gen->fun = fun; | |
gen->yielded = 0; | |
gen->arg = arg; | |
gen->status = G_CREATED; | |
gen->id = gen_ctx.id++; | |
printf("[info] :: stack top: %p, stack bottom: %p, stack size: %d\n", gen->stack_top, gen->stack, gen->stack_top - gen->stack); | |
return gen; | |
} | |
int gen_id() { | |
return (current_generator == NULL) ? -1 : current_generator->id; | |
} | |
void gen_yield(int val) { | |
if (current_generator == NULL) return; | |
current_generator->yielded = val; | |
current_generator->status = G_YIELD; | |
if (!setjmp(current_generator->env)) { | |
longjmp(gen_ctx.buf, 1); | |
} | |
} | |
int gen_resume(generator_t* gen) { | |
if (gen == NULL) return GEN_EXIT; | |
setjmp(gen_ctx.buf); | |
if (gen->status == G_CREATED) { | |
// The generator has been created and not executed yet. | |
gen->status = G_RUN; | |
// Let's run the generator in its own stack space. Set up | |
// the stack pointer. | |
register void *top = gen->stack_top; | |
asm volatile( | |
"mov %[rs], %%rsp \n" | |
: [ rs ] "+r" (top) :: | |
); | |
// We can run our function since our stack pointer has been changed. | |
// All variables inside `fun` will be allocated inside `gen->stack`. | |
current_generator = gen; | |
gen->fun(gen->arg); | |
// The stack pointer should be ripristinated. | |
gen->status = G_EXIT; | |
longjmp(gen_ctx.buf, 1); | |
// TODO | |
} | |
else if (gen->status == G_RUN) { | |
// Jump back to our function's execution | |
current_generator = gen; | |
longjmp(gen->env, 1); | |
// NO RETURN | |
} | |
else if (gen->status == G_YIELD) { | |
gen->status = G_RUN; | |
current_generator = NULL; | |
return gen->yielded; | |
} | |
return GEN_EXIT; | |
} | |
// ---- | |
void interval_gen(int max) { | |
int i = 0; | |
while (i < max) { | |
i = i + 1; | |
gen_yield(i); | |
printf("[info:gen#%d] :: resuming...\n", gen_id()); | |
} | |
} | |
int main(void) { | |
int val = 0; | |
generator_t* gen1 = gen_spawn(interval_gen, 42); | |
generator_t* gen2 = gen_spawn(interval_gen, 42); | |
for (int i = 0; i < (42 * 2); i++) { | |
if (i % 2 == 0) { | |
val = gen_resume(gen1); | |
} | |
else { | |
val = gen_resume(gen2); | |
} | |
printf("[info] :: value: %d\n", val); | |
} | |
printf("[info] :: end\n"); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is a toy implementation on how Generators could be implemented in C