Skip to content

Instantly share code, notes, and snippets.

@gabryon99
Created January 28, 2023 11:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gabryon99/b7d87a39352692647fd274c84fc35904 to your computer and use it in GitHub Desktop.
Save gabryon99/b7d87a39352692647fd274c84fc35904 to your computer and use it in GitHub Desktop.
Implement simple JavaScript's generator in C
#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;
}
@gabryon99
Copy link
Author

This is a toy implementation on how Generators could be implemented in C

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment