diff options
Diffstat (limited to 'research/experiments/045-proof-compiler/stitch.c')
| -rw-r--r-- | research/experiments/045-proof-compiler/stitch.c | 108 |
1 files changed, 108 insertions, 0 deletions
diff --git a/research/experiments/045-proof-compiler/stitch.c b/research/experiments/045-proof-compiler/stitch.c new file mode 100644 index 0000000..8d690e0 --- /dev/null +++ b/research/experiments/045-proof-compiler/stitch.c @@ -0,0 +1,108 @@ +/* Research 045 (029's principle 1, experiment (b)): the executor's own instruction bodies stitched without dispatch. + * Reads a module as Pure loads it and writes C for Pure's core to include (native.inc): the module's code as a + * constant array; a function for each basic block calling the executor's step() on each of its constant instructions, + * so the C compiler specializes each body to its instruction alone (no fetch, no decode, no dispatch within a block); + * and run_native, run_calls's loop dispatching once a block. Calls, returns and the ends of loops go through + * run_calls's own code. The semantics are the executor's source, unchanged. + * + * cc -O2 -std=c17 -I../../../core stitch.c ../../../build/libpure.a -lm -pthread -o stitch + * ./stitch MODULE > native.inc (then core/module.c built with -DPURE_NATIVE_INC='"native.inc"') */ +#include "module.h" +#include <inttypes.h> +#include <stdio.h> + +int main(int argc, char **argv) { + if (argc != 2) { fprintf(stderr, "usage: %s MODULE\n", argv[0]); return 2; } + PureModule m = {0}; + char error[160]; + if (pure_module_read(argv[1], &m, error, sizeof(error)) != PURE_OK) { fprintf(stderr, "%s\n", error); return 1; } + uint32_t n = m.code_count; + printf("/* Stitched from %s: %u instructions (research 045). */\n", argv[1], n); + printf("enum { NATIVE_COUNT = %u, STEP_ENDED = 9 }; /* STEP_ENDED: a block reached its end (frame.end) */\n", n); + printf("static const PureOp native_code[NATIVE_COUNT] = {\n"); + for (uint32_t pc = 0; pc < n; ++pc) { + const PureOp *o = &m.code[pc]; + printf(" {.integer = INT64_C(%" PRId64 "), .arg = %" PRIu32 "u, .op = %u, .plan = %u, .shape = %u, .range = %u},\n", + o->integer, o->arg, o->op, o->plan, o->shape, o->range); + } + printf("};\n\n"); + printf("static bool native_matches(const PureModule *m) {\n" + " if (m->code_count != NATIVE_COUNT) return false;\n" + " for (uint32_t pc = 0; pc < NATIVE_COUNT; ++pc)\n" + " if (m->code[pc].op != native_code[pc].op || m->code[pc].arg != native_code[pc].arg ||\n" + " m->code[pc].integer != native_code[pc].integer || m->code[pc].plan != native_code[pc].plan)\n" + " return false;\n" + " return true;\n" + "}\n\n"); + /* Basic blocks: a leader at each block's and function's start, each jump's target, past each instruction that may + * go elsewhere (a branch, a loop's, a call, a return), and every 16 instructions. Each is a function of its own (one + * enormous function took the compiler past 3 GiB): stitched within, dispatched once a block. */ + static bool leader[1 << 16]; + for (uint32_t h = 0; h < m.handlers; ++h) leader[m.handler[h].code.start] = true; + for (uint32_t f = 0; f < m.functions; ++f) leader[m.function[f].code.start] = true; + for (uint32_t pc = 0; pc < n; ++pc) { + uint8_t op = m.code[pc].op; + bool control = op == PURE_OP_JZ || op == PURE_OP_JMP || op == PURE_OP_LOOP || op == PURE_OP_EACH || op == PURE_OP_BREAK || + op == PURE_OP_CALL || op == PURE_OP_RETURN || op == PURE_OP_FOLD; + if ((op == PURE_OP_JZ || op == PURE_OP_JMP) && m.code[pc].arg < n) leader[m.code[pc].arg] = true; + if (control && pc + 1 < n) leader[pc + 1] = true; + } + leader[0] = true; + for (uint32_t pc = 0, run = 0; pc < n; ++pc, ++run) { + if (leader[pc]) run = 0; + else if (run == 16) leader[pc] = true, run = 0; + } + for (uint32_t pc = 0; pc < n;) { + uint32_t end = pc + 1; + while (end < n && !leader[end]) ++end; + printf("static __attribute__((noinline)) int native_%u(Frame *frame, uint32_t *pc) {\n int how;\n", pc); + for (uint32_t k = pc; k < end; ++k) { + printf(" if (%uu >= frame->end) { *pc = %uu; return STEP_ENDED; }\n", k, k); + printf(" if ((how = step(frame, &native_code[%u], %uu, pc, true)) != STEP_NEXT || *pc != %uu) return how;\n", k, k, k + 1); + } + printf(" return STEP_NEXT;\n}\n"); + pc = end; + } + printf("typedef int (*NativeBlock)(Frame *, uint32_t *);\n"); + printf("static const NativeBlock native_blocks[NATIVE_COUNT] = {\n"); + for (uint32_t pc = 0; pc < n; ++pc) if (leader[pc]) printf(" [%u] = native_%u,\n", pc, pc); + printf("};\n\n"); + printf("static __attribute__((noinline)) PureResult run_native(const PureModule *m, PureValue *values, PureBlock block, Event *ev,\n" + " Slot *result) {\n" + " Slot stack[PURE_MODULE_STACK];\n" + " Loop few[PURE_MODULE_NESTING], *loops = m->nesting > PURE_MODULE_NESTING ? deep_loops(ev->app, m->nesting) : few;\n" + " if (!loops) return PURE_ALLOCATION;\n" + " Frame frame;\n" + " open_frame(&frame, m, values, block, ev, stack, loops);\n" + " Call *top = NULL;\n" + " int how = STEP_NEXT;\n" + " for (uint32_t pc = block.start;;) {\n" + " if (pc >= frame.end) {\n" + " if (!frame.depth || (how = iterate(&frame, &pc)) != STEP_NEXT) break;\n" + " continue;\n" + " }\n" + " NativeBlock b = pc < NATIVE_COUNT ? native_blocks[pc] : NULL;\n" + " how = b ? b(&frame, &pc) : step(&frame, &m->code[pc], pc, &pc, true);\n" + " if (how == STEP_NEXT || how == STEP_ENDED) continue;\n" + " if (how != STEP_CALL && how != STEP_BACK && how != STEP_BATCH) break;\n" + " Frame apart = frame;\n" + " if (how == STEP_CALL) how = enter_call(&apart, &top, &pc);\n" + " else if (how == STEP_BACK) how = leave_call(&apart, &top, &pc);\n" + " else {\n" + " uint32_t past = 0;\n" + " how = open_batches(&apart, pc - 1, &past);\n" + " pc = past;\n" + " }\n" + " frame = apart;\n" + " if (how != STEP_NEXT) break;\n" + " }\n" + " if (top) {\n" + " Frame apart = frame;\n" + " unwind_calls(&apart, top);\n" + " frame = apart;\n" + " }\n" + " return finish(&frame, how, result);\n" + "}\n"); + pure_module_release(&m); + return 0; +} |
