diff options
Diffstat (limited to 'core/module.c')
| -rw-r--r-- | core/module.c | 194 |
1 files changed, 143 insertions, 51 deletions
diff --git a/core/module.c b/core/module.c index 2228725..c6eb9be 100644 --- a/core/module.c +++ b/core/module.c @@ -142,20 +142,11 @@ static const struct { unsigned char pops, in[3], out; unsigned short where; unsi static uint32_t read16(const unsigned char *p) { return (uint32_t)p[0] | (uint32_t)p[1] << 8; } -static uint32_t read32(const unsigned char *p) { - return (uint32_t)p[0] | (uint32_t)p[1] << 8 | (uint32_t)p[2] << 16 | (uint32_t)p[3] << 24; -} static void write32(unsigned char *p, uint32_t value) { for (unsigned i = 0; i < 4; ++i) p[i] = (unsigned char)(value >> (8 * i)); } -static double read_double(const unsigned char *p) { - uint64_t bits = read32(p) | (uint64_t)read32(p + 4) << 32; - double value; - memcpy(&value, &bits, sizeof(value)); - return value; -} static void write_double(unsigned char *p, double value) { uint64_t bits; @@ -695,11 +686,59 @@ PURE_COLD static bool block_has_lanes(const PureModule *m, PureBlock block, int return true; } +/* A count's loop whose block has batch forms throughout runs a batch of its + * rounds at a time (module-batch.c, docs/design/024): lane forms, a local + * set (a number, an index or a bool) and read only after every round sets + * it, a branch with nothing else on the stack to a place in the block, and + * a number or bool set in an item (not its key). PURE_BATCH=0 in the + * environment: none does (the executor alone, to measure it). */ +PURE_COLD static bool loop_batches(const PureModule *m, uint32_t pc, int input) { + static int off = -1; + if (off < 0) { + const char *batches = getenv("PURE_BATCH"); + off = batches && !strcmp(batches, "0"); + } + const PureOp *loop = &m->code[pc]; + uint32_t start = pc + 1, end = start + (loop->arg >> 16), arms = start; + if (off || loop->arg & PURE_LOOP_INTEGER || start >= end || m->height[start]) return false; + uint64_t set[4] = {0}, sure[4] = {0}; /* the locals the block sets; set before here in every round */ + for (uint32_t at = start; at < end; ++at) + if (m->code[at].op == PURE_OP_LOCAL_STORE) set[(m->code[at].arg & 0xFF) / 64] |= UINT64_C(1) << (m->code[at].arg & 63); + for (uint32_t at = start; at < end; ++at) { /* a local read before a round sets it: an earlier round's (no batch has it) */ + const PureOp *op = &m->code[at]; + unsigned local = op->arg & 0xFF; + if ((op->op == PURE_OP_JZ || op->op == PURE_OP_JMP) && op->arg > arms) arms = op->arg; /* code before arms: in a branch's way */ + if (op->op == PURE_OP_LOCAL_STORE && at >= arms) sure[local / 64] |= UINT64_C(1) << (local % 64); + if (op->op == PURE_OP_LOCAL_LOAD && (set[local / 64] & ~sure[local / 64]) >> (local % 64) & 1) return false; + } + for (uint32_t at = start; at < end; ++at) { + const PureOp *op = &m->code[at]; + switch (op->op) { + case PURE_OP_LOCAL_STORE: + if (local_type(op->arg) == T_I64) return false; + break; + case PURE_OP_JZ: case PURE_OP_JMP: + if (op->arg > end || m->height[at] != (op->op == PURE_OP_JZ)) return false; + break; + case PURE_OP_ITEM_SET: { + unsigned char type = field_type(m, list_of(op->arg), item_field(op->arg)); + if ((type != T_NUM && type != T_BOOL) || item_field(op->arg) == m->state_key[list_of(op->arg)] - 1u) return false; + break; + } + default: + if (!has_lanes(m, op, input)) return false; + } + } + return true; +} + /* A fold in a verified block whose own block has lane forms throughout runs - * a leaf of its list at a time. */ + * a leaf of its list at a time; a count's loop so planned, a batch of its + * rounds. */ PURE_COLD static void plan_folds(PureModule *m, PureBlock block, int input) { for (uint32_t pc = block.start; pc < block.start + block.length; ++pc) { PureOp *op = &m->code[pc]; + if (op->op == PURE_OP_LOOP && (op->plan = loop_batches(m, pc, input) ? PLAN_BATCH : 0)) m->batches = true; if (op->op != PURE_OP_FOLD) continue; op->plan = !(op->arg & PURE_FOLD_INTEGER) && block_has_lanes(m, (PureBlock){pc + 1, op->arg >> 16}, input); pc += op->arg >> 16; @@ -1018,6 +1057,7 @@ PURE_COLD PureResult pure_module_read(const char *path, PureModule *out, char *e void pure_module_release(PureModule *m) { pure_jit_free(m->jit); m->jit = NULL; + m->batches = false; free(m->height); m->height = NULL; free(m->storage); @@ -1546,8 +1586,6 @@ int pure_module_column(const PureModule *m, uint32_t table, const char *name) { /* ---- Execution ------------------------------------------------------ */ /* IEEE 754-2019 minimum/maximum on finite values: -0 orders below +0. */ -static double minimum(double a, double b) { return a < b || (a == b && signbit(a)) ? a : b; } -static double maximum(double a, double b) { return a > b || (a == b && !signbit(a)) ? a : b; } /* Whether two values that order as order (below zero: the first below) are related as a relation says (PureRelation). */ @@ -1813,37 +1851,6 @@ static const double *lanes_of(const Lanes *v, double *scratch, const PureRun *it return scratch; } -/* a op b as run computes it; false where run faults. */ -static bool number_op(uint8_t op, double a, double b, double *out) { - double value; - switch (op) { - case PURE_OP_ADD: value = a + b; break; - case PURE_OP_SUB: value = a - b; break; - case PURE_OP_MUL: value = a * b; break; - case PURE_OP_DIV: - if (b == 0) return false; - value = a / b; - break; - case PURE_OP_LT: value = a < b; break; - case PURE_OP_LE: value = a <= b; break; - case PURE_OP_GT: value = a > b; break; - case PURE_OP_GE: value = a >= b; break; - case PURE_OP_EQ: value = a == b; break; - case PURE_OP_NE: value = a != b; break; - case PURE_OP_AND: value = a != 0 && b != 0; break; - case PURE_OP_OR: value = a != 0 || b != 0; break; - case PURE_OP_MIN: value = minimum(a, b); break; - case PURE_OP_MOD: - if (b == 0) return false; - value = fmod(a, b); - if (value != 0 && (value < 0) != (b < 0)) value += b; - else if (value == 0) value = copysign(0.0, b); - break; - default: value = maximum(a, b); break; - } - *out = value; - return isfinite(value); -} /* a op b for every item into out: a loop per operation, reading a value for * all with stride 0. false where run faults for some item. */ @@ -2586,13 +2593,69 @@ static inline __attribute__((always_inline)) int iterate(Frame *frame, uint32_t return STEP_NEXT; } +/* A loop planned PLAN_BATCH from its round loop->k (set up and counted) + * on: batches while they run (module-batch.c), the rounds of one that failed + * one by one, then batches again, until the loop ends or batches stop + * (then *next its block: the rest of it as any loop's). STEP_NEXT with *next + * past its block when it ended here, or how a round stopped. */ +static __attribute__((noinline, cold)) int batches(Frame *frame, Loop *loop, uint32_t *next) { + unsigned depth = frame->depth; + for (;;) { + unsigned done = pure_batch_run(frame, loop); + if (done) { + if ((loop->k += done) >= loop->count) { /* it ended */ + frame->arena->used = loop->mark; + frame->end = loop->outer; + --frame->depth; + *next = loop->end; + return STEP_NEXT; + } + if (!charge(frame->app, (uint64_t)(loop->end - loop->start) + 1)) { + frame->kind = PURE_FAULT_WORK; + frame->fault_pc = loop->start - 1; + return STEP_FAULT; + } + loop->slot->num = loop->low.num + (double)loop->k; + continue; + } + if (loop->resume == UINT64_MAX) break; + while (loop->k < loop->resume) { /* a failed batch's rounds */ + int how = pure_run_rest(frame, loop->start); + if (how == STEP_SKIP) { /* it left the loop */ + *next = loop->end; + return STEP_NEXT; + } + if (how != STEP_NEXT) return how; + uint32_t pc = loop->end; + if ((how = iterate(frame, &pc)) != STEP_NEXT) return how; + if (frame->depth < depth) { + *next = loop->end; + return STEP_NEXT; + } + } + } + *next = loop->start; + return STEP_NEXT; +} + +/* A LOOP planned PLAN_BATCH the executor runs (step leaves it to its + * caller): opened, then in batches. */ +static __attribute__((noinline, cold)) int open_batches(Frame *frame, uint32_t pc, uint32_t *next) { + unsigned depth = frame->depth; + int how = enter_loop(frame, &frame->m->code[pc], pc, next); + if (how != STEP_NEXT || frame->depth == depth) return how; + Loop *loop = &frame->loops[depth]; + loop->resume = 0, loop->failures = 0, loop->reverse = false; + return batches(frame, loop, next); +} + #define NEXT do { frame->sp = sp; return STEP_NEXT; } while (0) #define FAILED do { frame->sp = sp; frame->kind = kind; frame->fault_pc = pc; return STEP_FAULT; } while (0) #define REFUSED do { frame->sp = sp; frame->status = status; frame->fault_pc = pc; return STEP_REFUSED; } while (0) #define STOPPED(why) do { frame->status = (why); return STEP_RETURN; } while (0) /* The instruction op at pc; *next the one to run after it. */ -static inline __attribute__((always_inline)) int step(Frame *frame, const PureOp *op, uint32_t pc, uint32_t *next) { +static inline __attribute__((always_inline)) int step(Frame *frame, const PureOp *op, uint32_t pc, uint32_t *next, bool batches) { const PureModule *m = frame->m; PureValue *values = frame->values; Event *ev = frame->ev; @@ -2612,7 +2675,7 @@ static inline __attribute__((always_inline)) int step(Frame *frame, const PureOp return STEP_NEXT; case PURE_OP_JMP: *next = arg; return STEP_NEXT; case PURE_OP_EACH: return enter_each(frame, op, pc, next); - case PURE_OP_LOOP: return enter_loop(frame, op, pc, next); + case PURE_OP_LOOP: return batches && op->plan & PLAN_BATCH ? STEP_BATCH : enter_loop(frame, op, pc, next); case PURE_OP_BREAK: *next = leave_loop(frame); return STEP_NEXT; case PURE_OP_CONST: stack[sp++].i64 = op->integer; NEXT; /* the bits: a number's, a bool's or an i64's */ case PURE_OP_LOAD: stack[sp++].i64 = values[arg].i64; NEXT; @@ -3073,7 +3136,7 @@ static inline __attribute__((always_inline)) PureResult finish(Frame *frame, int /* The steps compiled code calls (module-run.h). */ int pure_run_step(Frame *frame, uint32_t pc) { uint32_t next; - return step(frame, &frame->m->code[pc], pc, &next); + return step(frame, &frame->m->code[pc], pc, &next, false); } int pure_run_each(Frame *frame, uint32_t pc) { @@ -3105,7 +3168,7 @@ int pure_run_item(Frame *frame, uint32_t pc) { for (unsigned f = 0; f < 16; ++f) frame->run.columns[f] = run.column[f].numbers; } uint32_t next; - return step(frame, op, pc, &next); + return step(frame, op, pc, &next, false); } int pure_run_rest(Frame *frame, uint32_t pc) { @@ -3118,7 +3181,7 @@ int pure_run_rest(Frame *frame, uint32_t pc) { if (how != STEP_NEXT) return how; continue; } - int how = step(frame, &frame->m->code[pc], pc, &pc); + int how = step(frame, &frame->m->code[pc], pc, &pc, false); if (how != STEP_NEXT) return how; } } @@ -3135,7 +3198,7 @@ int pure_run_iterate(Frame *frame) { static inline __attribute__((always_inline)) void open_frame(Frame *frame, const PureModule *m, PureValue *values, PureBlock block, Event *ev, Slot *stack, Loop *loops) { frame->m = m, frame->values = values, frame->ev = ev, frame->app = ev->app, frame->arena = ev->arena; - frame->stack = stack, frame->sp = 0, frame->loops = loops, frame->depth = 0; + frame->stack = stack, frame->sp = 0, frame->loops = loops, frame->depth = 0, frame->kind = PURE_FAULT_NONE; frame->end = block.start + block.length; } @@ -3150,9 +3213,38 @@ static __attribute__((noinline)) PureResult run_compiled(PureCompiled code, cons return finish(&frame, code(&frame), result); } -static PureResult run(const PureModule *m, PureValue *values, PureBlock block, Event *ev, Slot *result) { +/* The executor's run of a block of a module with loops planned PLAN_BATCH: + * run's, those loops in batches (module-batch.c). Its own copy of the + * steps: in run's, a batch's way among them would cost every instruction + * registers (research 126). */ +static __attribute__((noinline, aligned(64))) PureResult run_batches(const PureModule *m, PureValue *values, PureBlock block, Event *ev, + Slot *result) { + Slot stack[PURE_MODULE_STACK]; + Loop loops[PURE_MODULE_NESTING]; + Frame frame; + open_frame(&frame, m, values, block, ev, stack, loops); + int how = STEP_NEXT; + for (uint32_t pc = block.start;;) { + if (pc >= frame.end) { + if (!frame.depth || (how = iterate(&frame, &pc)) != STEP_NEXT) break; + continue; + } + if ((how = step(&frame, &m->code[pc], pc, &pc, true)) == STEP_NEXT) continue; + if (how != STEP_BATCH) break; + Frame apart = frame; /* the frame's copy (no call sees the frame itself: its fields stay in registers) */ + uint32_t past = 0; + how = open_batches(&apart, pc - 1, &past); + frame = apart, pc = past; + if (how != STEP_NEXT) break; + } + return finish(&frame, how, result); +} + +/* At a cache line's start: the steps' branches land where they would (research 126: else 10% more cycles, as many instructions). */ +static __attribute__((aligned(64))) PureResult run(const PureModule *m, PureValue *values, PureBlock block, Event *ev, Slot *result) { PureCompiled code = m->jit ? pure_jit_code(m->jit, block) : NULL; if (code) return run_compiled(code, m, values, block, ev, result); + if (m->batches) return run_batches(m, values, block, ev, result); Slot stack[PURE_MODULE_STACK]; Loop loops[PURE_MODULE_NESTING]; Frame frame; @@ -3163,7 +3255,7 @@ static PureResult run(const PureModule *m, PureValue *values, PureBlock block, E if (!frame.depth || (how = iterate(&frame, &pc)) != STEP_NEXT) break; continue; } - if ((how = step(&frame, &m->code[pc], pc, &pc)) != STEP_NEXT) break; + if ((how = step(&frame, &m->code[pc], pc, &pc, false)) != STEP_NEXT) break; } return finish(&frame, how, result); } |
