// 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(f16); 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; uint16_t v_f16; 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_f16) { return &v->value.v_f16; } 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; bool *out_unique; struct type **in_types; bool *in_unique; }; 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); // +1 to avoid zero-size arrays, which is UB. void* outs[num_outs+1]; void* ins[num_ins+1]; 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_rename(struct server_state *s, const char *args[]) { const char *oldname = get_arg(args, 0); const char *newname = get_arg(args, 1); struct variable *old = get_variable(s, oldname); struct variable *new = get_variable(s, newname); if (old == NULL) { failure(); printf("Unknown variable: %s\n", oldname); return; } if (new != NULL) { failure(); printf("Variable already exists: %s\n", newname); return; } free(old->name); old->name = strdup(newname); } 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++) { if (e->in_unique[i]) { putchar('*'); } 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++) { if (e->out_unique[i]) { putchar('*'); } 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); } char *next_word(char **line) { char *p = *line; while (isspace(*p)) { p++; } if (*p == 0) { return NULL; } if (*p == '"') { char *save = p+1; // Skip ahead till closing quote. p++; while (*p && *p != '"') { p++; } if (*p == '"') { *p = 0; *line = p+1; return save; } else { return NULL; } } else { char *save = p; // Skip ahead till next whitespace. while (*p && !isspace(*p)) { p++; } if (*p) { *p = 0; *line = p+1; } else { *line = p; } return save; } } void process_line(struct server_state *s, char *line) { int max_num_tokens = 1000; const char* tokens[max_num_tokens]; int num_tokens = 0; while ((tokens[num_tokens] = next_word(&line)) != NULL) { num_tokens++; if (num_tokens == max_num_tokens) { futhark_panic(1, "Line too long.\n"); } } 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, "rename") == 0) { cmd_rename(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.