futhark-0.19.3: rts/c/server.h
// Start of server.h.
// Forward declarations of things that we technically don't know until
// the application header file is included, but which we need.
struct futhark_context;
char *futhark_context_get_error(struct futhark_context *ctx);
int futhark_context_sync(struct futhark_context *ctx);
int futhark_context_clear_caches(struct futhark_context *ctx);
typedef int (*restore_fn)(const void*, FILE *, struct futhark_context*, void*);
typedef void (*store_fn)(const void*, FILE *, struct futhark_context*, void*);
typedef int (*free_fn)(const void*, struct futhark_context*, void*);
struct type {
const char *name;
restore_fn restore;
store_fn store;
free_fn free;
const void *aux;
};
int free_scalar(const void *aux, struct futhark_context *ctx, void *p) {
(void)aux;
(void)ctx;
(void)p;
// Nothing to do.
return 0;
}
#define DEF_SCALAR_TYPE(T) \
int restore_##T(const void *aux, FILE *f, \
struct futhark_context *ctx, void *p) { \
(void)aux; \
(void)ctx; \
return read_scalar(f, &T##_info, p); \
} \
\
void store_##T(const void *aux, FILE *f, \
struct futhark_context *ctx, void *p) { \
(void)aux; \
(void)ctx; \
write_scalar(f, 1, &T##_info, p); \
} \
\
struct type type_##T = \
{ .name = #T, \
.restore = restore_##T, \
.store = store_##T, \
.free = free_scalar \
} \
DEF_SCALAR_TYPE(i8);
DEF_SCALAR_TYPE(i16);
DEF_SCALAR_TYPE(i32);
DEF_SCALAR_TYPE(i64);
DEF_SCALAR_TYPE(u8);
DEF_SCALAR_TYPE(u16);
DEF_SCALAR_TYPE(u32);
DEF_SCALAR_TYPE(u64);
DEF_SCALAR_TYPE(f32);
DEF_SCALAR_TYPE(f64);
DEF_SCALAR_TYPE(bool);
struct value {
struct type *type;
union {
void *v_ptr;
int8_t v_i8;
int16_t v_i16;
int32_t v_i32;
int64_t v_i64;
uint8_t v_u8;
uint16_t v_u16;
uint32_t v_u32;
uint64_t v_u64;
float v_f32;
double v_f64;
bool v_bool;
} value;
};
void* value_ptr(struct value *v) {
if (v->type == &type_i8) {
return &v->value.v_i8;
}
if (v->type == &type_i16) {
return &v->value.v_i16;
}
if (v->type == &type_i32) {
return &v->value.v_i32;
}
if (v->type == &type_i64) {
return &v->value.v_i64;
}
if (v->type == &type_u8) {
return &v->value.v_u8;
}
if (v->type == &type_u16) {
return &v->value.v_u16;
}
if (v->type == &type_u32) {
return &v->value.v_u32;
}
if (v->type == &type_u64) {
return &v->value.v_u64;
}
if (v->type == &type_f32) {
return &v->value.v_f32;
}
if (v->type == &type_f64) {
return &v->value.v_f64;
}
if (v->type == &type_bool) {
return &v->value.v_bool;
}
return &v->value.v_ptr;
}
struct variable {
// NULL name indicates free slot. Name is owned by this struct.
char *name;
struct value value;
};
typedef int (*entry_point_fn)(struct futhark_context*, void**, void**);
struct entry_point {
const char *name;
entry_point_fn f;
struct type **out_types;
struct type **in_types;
};
int entry_num_ins(struct entry_point *e) {
int count = 0;
while (e->in_types[count]) {
count++;
}
return count;
}
int entry_num_outs(struct entry_point *e) {
int count = 0;
while (e->out_types[count]) {
count++;
}
return count;
}
struct futhark_prog {
// Last entry point identified by NULL name.
struct entry_point *entry_points;
// Last type identified by NULL name.
struct type **types;
};
struct server_state {
struct futhark_prog prog;
struct futhark_context *ctx;
int variables_capacity;
struct variable *variables;
};
struct variable* get_variable(struct server_state *s,
const char *name) {
for (int i = 0; i < s->variables_capacity; i++) {
if (s->variables[i].name != NULL &&
strcmp(s->variables[i].name, name) == 0) {
return &s->variables[i];
}
}
return NULL;
}
struct variable* create_variable(struct server_state *s,
const char *name,
struct type *type) {
int found = -1;
for (int i = 0; i < s->variables_capacity; i++) {
if (found == -1 && s->variables[i].name == NULL) {
found = i;
} else if (s->variables[i].name != NULL &&
strcmp(s->variables[i].name, name) == 0) {
return NULL;
}
}
if (found != -1) {
// Found a free spot.
s->variables[found].name = strdup(name);
s->variables[found].value.type = type;
return &s->variables[found];
}
// Need to grow the buffer.
found = s->variables_capacity;
s->variables_capacity *= 2;
s->variables = realloc(s->variables,
s->variables_capacity * sizeof(struct variable));
s->variables[found].name = strdup(name);
s->variables[found].value.type = type;
for (int i = found+1; i < s->variables_capacity; i++) {
s->variables[i].name = NULL;
}
return &s->variables[found];
}
void drop_variable(struct variable *v) {
free(v->name);
v->name = NULL;
}
int arg_exists(const char *args[], int i) {
return args[i] != NULL;
}
const char* get_arg(const char *args[], int i) {
if (!arg_exists(args, i)) {
futhark_panic(1, "Insufficient command args.\n");
}
return args[i];
}
struct type* get_type(struct server_state *s, const char *name) {
for (int i = 0; s->prog.types[i]; i++) {
if (strcmp(s->prog.types[i]->name, name) == 0) {
return s->prog.types[i];
}
}
futhark_panic(1, "Unknown type %s\n", name);
return NULL;
}
struct entry_point* get_entry_point(struct server_state *s, const char *name) {
for (int i = 0; s->prog.entry_points[i].name; i++) {
if (strcmp(s->prog.entry_points[i].name, name) == 0) {
return &s->prog.entry_points[i];
}
}
return NULL;
}
// Print the command-done marker, indicating that we are ready for
// more input.
void ok() {
printf("%%%%%% OK\n");
fflush(stdout);
}
// Print the failure marker. Output is now an error message until the
// next ok().
void failure() {
printf("%%%%%% FAILURE\n");
}
void error_check(struct server_state *s, int err) {
if (err != 0) {
failure();
char *error = futhark_context_get_error(s->ctx);
puts(error);
free(error);
}
}
void cmd_call(struct server_state *s, const char *args[]) {
const char *name = get_arg(args, 0);
struct entry_point *e = get_entry_point(s, name);
if (e == NULL) {
failure();
printf("Unknown entry point: %s\n", name);
return;
}
int num_outs = entry_num_outs(e);
int num_ins = entry_num_ins(e);
void* outs[num_outs];
void* ins[num_ins];
for (int i = 0; i < num_ins; i++) {
const char *in_name = get_arg(args, 1+num_outs+i);
struct variable *v = get_variable(s, in_name);
if (v == NULL) {
failure();
printf("Unknown variable: %s\n", in_name);
return;
}
if (v->value.type != e->in_types[i]) {
failure();
printf("Wrong input type. Expected %s, got %s.\n",
e->in_types[i]->name, v->value.type->name);
return;
}
ins[i] = value_ptr(&v->value);
}
for (int i = 0; i < num_outs; i++) {
const char *out_name = get_arg(args, 1+i);
struct variable *v = create_variable(s, out_name, e->out_types[i]);
if (v == NULL) {
failure();
printf("Variable already exists: %s\n", out_name);
return;
}
outs[i] = value_ptr(&v->value);
}
int64_t t_start = get_wall_time();
int err = e->f(s->ctx, outs, ins);
err |= futhark_context_sync(s->ctx);
int64_t t_end = get_wall_time();
long long int elapsed_usec = t_end - t_start;
printf("runtime: %lld\n", elapsed_usec);
error_check(s, err);
if (err != 0) {
// Need to uncreate the output variables, which would otherwise be left
// in an uninitialised state.
for (int i = 0; i < num_outs; i++) {
const char *out_name = get_arg(args, 1+i);
struct variable *v = get_variable(s, out_name);
if (v) {
drop_variable(v);
}
}
}
}
void cmd_restore(struct server_state *s, const char *args[]) {
const char *fname = get_arg(args, 0);
FILE *f = fopen(fname, "rb");
if (f == NULL) {
failure();
printf("Failed to open %s: %s\n", fname, strerror(errno));
} else {
int values = 0;
for (int i = 1; arg_exists(args, i); i+=2, values++) {
const char *vname = get_arg(args, i);
const char *type = get_arg(args, i+1);
struct type *t = get_type(s, type);
struct variable *v = create_variable(s, vname, t);
if (v == NULL) {
failure();
printf("Variable already exists: %s\n", vname);
return;
}
if (t->restore(t->aux, f, s->ctx, value_ptr(&v->value)) != 0) {
failure();
printf("Failed to restore variable %s.\n"
"Possibly malformed data in %s (errno: %s)\n",
vname, fname, strerror(errno));
drop_variable(v);
break;
}
}
if (end_of_input(f) != 0) {
failure();
printf("Expected EOF after reading %d values from %s\n",
values, fname);
}
fclose(f);
}
int err = futhark_context_sync(s->ctx);
error_check(s, err);
}
void cmd_store(struct server_state *s, const char *args[]) {
const char *fname = get_arg(args, 0);
FILE *f = fopen(fname, "wb");
if (f == NULL) {
failure();
printf("Failed to open %s: %s\n", fname, strerror(errno));
} else {
for (int i = 1; arg_exists(args, i); i++) {
const char *vname = get_arg(args, i);
struct variable *v = get_variable(s, vname);
if (v == NULL) {
failure();
printf("Unknown variable: %s\n", vname);
return;
}
struct type *t = v->value.type;
t->store(t->aux, f, s->ctx, value_ptr(&v->value));
}
fclose(f);
}
}
void cmd_free(struct server_state *s, const char *args[]) {
for (int i = 0; arg_exists(args, i); i++) {
const char *name = get_arg(args, i);
struct variable *v = get_variable(s, name);
if (v == NULL) {
failure();
printf("Unknown variable: %s\n", name);
return;
}
struct type *t = v->value.type;
int err = t->free(t->aux, s->ctx, value_ptr(&v->value));
error_check(s, err);
drop_variable(v);
}
}
void cmd_inputs(struct server_state *s, const char *args[]) {
const char *name = get_arg(args, 0);
struct entry_point *e = get_entry_point(s, name);
if (e == NULL) {
failure();
printf("Unknown entry point: %s\n", name);
return;
}
int num_ins = entry_num_ins(e);
for (int i = 0; i < num_ins; i++) {
puts(e->in_types[i]->name);
}
}
void cmd_outputs(struct server_state *s, const char *args[]) {
const char *name = get_arg(args, 0);
struct entry_point *e = get_entry_point(s, name);
if (e == NULL) {
failure();
printf("Unknown entry point: %s\n", name);
return;
}
int num_outs = entry_num_outs(e);
for (int i = 0; i < num_outs; i++) {
puts(e->out_types[i]->name);
}
}
void cmd_clear(struct server_state *s, const char *args[]) {
(void)args;
int err = 0;
for (int i = 0; i < s->variables_capacity; i++) {
struct variable *v = &s->variables[i];
if (v->name != NULL) {
err |= v->value.type->free(v->value.type->aux, s->ctx, value_ptr(&v->value));
drop_variable(v);
}
}
err |= futhark_context_clear_caches(s->ctx);
error_check(s, err);
}
void cmd_pause_profiling(struct server_state *s, const char *args[]) {
(void)args;
futhark_context_pause_profiling(s->ctx);
}
void cmd_unpause_profiling(struct server_state *s, const char *args[]) {
(void)args;
futhark_context_unpause_profiling(s->ctx);
}
void cmd_report(struct server_state *s, const char *args[]) {
(void)args;
char *report = futhark_context_report(s->ctx);
puts(report);
free(report);
}
void process_line(struct server_state *s, char *line) {
int max_num_tokens = 100;
const char* tokens[max_num_tokens];
int num_tokens = 0;
char *saveptr;
char *tmp = line;
while ((tokens[num_tokens] = strtok_r(tmp, " \n", &saveptr)) != NULL) {
num_tokens++;
if (num_tokens == max_num_tokens) {
futhark_panic(1, "Line too long.\n");
}
tmp = NULL;
}
const char *command = tokens[0];
if (command == NULL) {
failure();
printf("Empty line\n");
} else if (strcmp(command, "call") == 0) {
cmd_call(s, tokens+1);
} else if (strcmp(command, "restore") == 0) {
cmd_restore(s, tokens+1);
} else if (strcmp(command, "store") == 0) {
cmd_store(s, tokens+1);
} else if (strcmp(command, "free") == 0) {
cmd_free(s, tokens+1);
} else if (strcmp(command, "inputs") == 0) {
cmd_inputs(s, tokens+1);
} else if (strcmp(command, "outputs") == 0) {
cmd_outputs(s, tokens+1);
} else if (strcmp(command, "clear") == 0) {
cmd_clear(s, tokens+1);
} else if (strcmp(command, "pause_profiling") == 0) {
cmd_pause_profiling(s, tokens+1);
} else if (strcmp(command, "unpause_profiling") == 0) {
cmd_unpause_profiling(s, tokens+1);
} else if (strcmp(command, "report") == 0) {
cmd_report(s, tokens+1);
} else {
futhark_panic(1, "Unknown command: %s\n", command);
}
}
void run_server(struct futhark_prog *prog, struct futhark_context *ctx) {
char *line = NULL;
size_t buflen = 0;
ssize_t linelen;
struct server_state s = {
.ctx = ctx,
.variables_capacity = 100,
.prog = *prog
};
s.variables = malloc(s.variables_capacity * sizeof(struct variable));
for (int i = 0; i < s.variables_capacity; i++) {
s.variables[i].name = NULL;
}
ok();
while ((linelen = getline(&line, &buflen, stdin)) > 0) {
process_line(&s, line);
ok();
}
free(line);
}
// The aux struct lets us write generic method implementations without
// code duplication.
typedef void* (*array_new_fn)(struct futhark_context *, const void*, const int64_t*);
typedef const int64_t* (*array_shape_fn)(struct futhark_context*, void*);
typedef int (*array_values_fn)(struct futhark_context*, void*, void*);
typedef int (*array_free_fn)(struct futhark_context*, void*);
struct array_aux {
int rank;
const struct primtype_info_t* info;
const char *name;
array_new_fn new;
array_shape_fn shape;
array_values_fn values;
array_free_fn free;
};
int restore_array(const struct array_aux *aux, FILE *f,
struct futhark_context *ctx, void *p) {
void *data = NULL;
int64_t shape[aux->rank];
if (read_array(f, aux->info, &data, shape, aux->rank) != 0) {
return 1;
}
void *arr = aux->new(ctx, data, shape);
if (arr == NULL) {
return 1;
}
*(void**)p = arr;
free(data);
return 0;
}
void store_array(const struct array_aux *aux, FILE *f,
struct futhark_context *ctx, void *p) {
void *arr = *(void**)p;
const int64_t *shape = aux->shape(ctx, arr);
int64_t size = sizeof(aux->info->size);
for (int i = 0; i < aux->rank; i++) {
size *= shape[i];
}
int32_t *data = malloc(size);
assert(aux->values(ctx, arr, data) == 0);
assert(futhark_context_sync(ctx) == 0);
assert(write_array(f, 1, aux->info, data, shape, aux->rank) == 0);
free(data);
}
int free_array(const struct array_aux *aux,
struct futhark_context *ctx, void *p) {
void *arr = *(void**)p;
return aux->free(ctx, arr);
}
typedef void* (*opaque_restore_fn)(struct futhark_context*, void*);
typedef int (*opaque_store_fn)(struct futhark_context*, const void*, void **, size_t *);
typedef int (*opaque_free_fn)(struct futhark_context*, void*);
struct opaque_aux {
opaque_restore_fn restore;
opaque_store_fn store;
opaque_free_fn free;
};
int restore_opaque(const struct opaque_aux *aux, FILE *f,
struct futhark_context *ctx, void *p) {
// We have a problem: we need to load data from 'f', since the
// restore function takes a pointer, but we don't know how much we
// need (and cannot possibly). So we do something hacky: we read
// *all* of the file, pass all of the data to the restore function
// (which doesn't care if there's extra at the end), then we compute
// how much space the the object actually takes in serialised form
// and rewind the file to that position. The only downside is more IO.
size_t start = ftell(f);
size_t size;
char *bytes = fslurp_file(f, &size);
void *obj = aux->restore(ctx, bytes);
free(bytes);
if (obj != NULL) {
*(void**)p = obj;
size_t obj_size;
(void)aux->store(ctx, obj, NULL, &obj_size);
fseek(f, start+obj_size, SEEK_SET);
return 0;
} else {
fseek(f, start, SEEK_SET);
return 1;
}
}
void store_opaque(const struct opaque_aux *aux, FILE *f,
struct futhark_context *ctx, void *p) {
void *obj = *(void**)p;
size_t obj_size;
void *data = NULL;
(void)aux->store(ctx, obj, &data, &obj_size);
fwrite(data, sizeof(char), obj_size, f);
free(data);
}
int free_opaque(const struct opaque_aux *aux,
struct futhark_context *ctx, void *p) {
void *obj = *(void**)p;
return aux->free(ctx, obj);
}
// End of server.h.