aboutsummaryrefslogtreecommitdiffstats
path: root/research/experiments/045-proof-compiler/app.c
diff options
context:
space:
mode:
Diffstat (limited to 'research/experiments/045-proof-compiler/app.c')
-rw-r--r--research/experiments/045-proof-compiler/app.c417
1 files changed, 417 insertions, 0 deletions
diff --git a/research/experiments/045-proof-compiler/app.c b/research/experiments/045-proof-compiler/app.c
new file mode 100644
index 0000000..2c54a8e
--- /dev/null
+++ b/research/experiments/045-proof-compiler/app.c
@@ -0,0 +1,417 @@
+/* Research 045: UAX #14's whole app emitted by hand (app-way.h), a rung at a time, each dropping one more thing the
+ * runtime holds that a fact of the proof could let go. Cumulative:
+ *
+ * exact Pure's meaning and the runtime's forms: numbers doubles, each sum and difference checked finite, //
+ * floor_divide and % Pure's (fmod); every index checked whole and in bounds (INDEX_AT) and again
+ * (ITEM_GET); the event's lists PureLists in FLAT arrays as the runtime keeps them (021: columns of
+ * the narrowest width that holds what they hold; cleared each line, appended with pure_list_insert and
+ * the memory a change holds counted as list_held and grows count it), the points the message's list,
+ * ranges the stored tree (pure_list_get); a Text's lists copied (pure_list_copied, LIST_VALUE) and let
+ * go at the line's end; the work budget charged each round and call. Locals in registers, plain calls.
+ * integer + whole numbers as integers (positions, counts, ranges, start, nonspace, ri, at): one bounds check.
+ * stored_flat + ranges' columns flat (unchanged in the event).
+ * local_flat + the event's lists, the points and out as flat buffers (a memory charge an append kept).
+ * shared + a Text's lists the buffers themselves (no copy).
+ * unbounded - the bounds checks (the budget and memory still charged).
+ * plain - every charge too: the same algorithm in C.
+ * vega Vega's C (text/linebreak.c; its classes from a two-stage table, no halving).
+ *
+ * Every rung's boundaries are compared first with the test's for all lines (and exactly with exact's); built with
+ * -DBREAK_ONE_RULE (LB18 made wrong) every rung must be refused. Then machine instructions and cycles a code point
+ * (perf_event_open, user mode), ROUNDS passes a rung, the rungs interleaved.
+ *
+ * cc -O2 -std=gnu17 -I../../../core -I../../../../vega/text app.c ../../../../vega/text/linebreak.c \
+ * ../../../../vega/text/unicode-data.c ../../../build/libpure.a -lm -pthread -o app
+ * ./app linebreak.pmod RANGES LINES ROUNDS one JSON line a rung */
+#define _GNU_SOURCE
+#include "module.h"
+#include "module-list.h"
+#include "text.h"
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <linux/perf_event.h>
+#include <sys/ioctl.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+enum {
+ BK = 0, CR = 1, LF = 2, NL = 3, SP = 4, ZW = 5, ZWJ = 6, CM = 7, WJ = 8, GL = 9, BA = 10, BB = 11, B2 = 12, HY = 13, CB = 14,
+ CL = 15, CP = 16, EX = 17, IN = 18, NS = 19, OP = 20, QU = 21, IS = 22, NU = 23, PO = 24, PR = 25, SY = 26, AK = 27, AL = 28,
+ AP = 29, AS = 30, EB = 31, EM = 32, H2 = 33, H3 = 34, HL = 35, ID = 36, JL = 37, JV = 38, JT = 39, RI = 40, VF = 41, VI = 42
+};
+enum { NONE = 0, ALLOWED = 1, MANDATORY = 2 };
+enum { ROUND_COST = 24, CALL_COST = 8 }; /* what a round and a call charge (constants: what is charged, not how much) */
+enum { START = 0, CLASS = 1, PI_FIELD = 2, PF_FIELD = 3, EAST_FIELD = 4, PICT_FIELD = 5 }; /* Range's fields */
+
+typedef struct { double *v; size_t n, cap; } BufN;
+typedef struct { int64_t *v; size_t n, cap; } BufI;
+typedef struct { unsigned char *v; size_t n, cap; } BufB;
+typedef struct { size_t stored, kept; } Held;
+typedef PureList *ListP; /* a list's handle as one name, so a declaration of several declares each a handle */
+typedef BufN *BufNP;
+typedef BufI *BufIP;
+typedef BufB *BufBP;
+
+typedef struct {
+ bool fault;
+ int fault_line; /* where the last fault was (a macro's use) */
+ uint64_t work, budget;
+ int64_t grown, spent, room; /* the memory the event's changes hold (module.c's grows) */
+ PureUndo undo; /* empty: the event's lists keep none (undo_for), list_held still asks its size */
+ const PureList *ranges;
+ size_t ranges_count;
+ const double *r_start, *r_class;
+ const unsigned char *r_pi, *r_pf, *r_east, *r_pict;
+ PureList *codes, *cls, *eff, *start, *nonspace, *ri, *at, *joined, *numeric, *out; /* the event's lists */
+ PureList *copies[9];
+ unsigned copied;
+ BufN b_codes, b_cls, b_eff, k_codes, k_cls, k_eff; /* the event's lists as buffers, and a Text's copies of them */
+ BufI b_start, b_nonspace, b_ri, b_at, b_out, k_start, k_nonspace, k_ri, k_at;
+ BufB b_joined, b_numeric, k_joined, k_numeric;
+} Context;
+typedef Context CONTEXT;
+
+typedef struct { const PureList *list; const double *codes; size_t count; } Points;
+typedef Points POINTS;
+
+/* core/module.c's, as they are. */
+static double floor_divide(double a, double b) {
+ double mod = fmod(a, b), quotient = (a - mod) / b;
+ if (mod != 0 && (b < 0) != (mod < 0)) quotient -= 1.0;
+ if (quotient == 0) return copysign(0.0, a / b);
+ double whole = floor(quotient);
+ return quotient - whole > 0.5 ? whole + 1.0 : whole;
+}
+static double pure_mod(double a, double b) {
+ double value = fmod(a, b);
+ if (value != 0 && (value < 0) != (b < 0)) value += b;
+ else if (value == 0) value = copysign(0.0, b);
+ return value;
+}
+static inline Held held(const Context *x, const PureList *l) {
+ return (Held){pure_list_size(l), pure_list_changes(l).size + pure_undo_size(&x->undo) + pure_list_extra(l)};
+}
+static inline bool grows(Context *x, Held before, Held after) {
+ x->grown += (int64_t)after.stored - (int64_t)before.stored;
+ x->spent += (int64_t)(after.stored + after.kept) - (int64_t)(before.stored + before.kept);
+ return x->spent <= x->room;
+}
+static __attribute__((noinline)) bool grow(void **v, size_t *cap, size_t size) {
+ size_t more = *cap ? *cap * 2 : 64;
+ void *p = realloc(*v, more * size);
+ if (!p) return false;
+ *v = p, *cap = more;
+ return true;
+}
+
+#define CHARGES_ON(n) do { if ((uint64_t)(n) > x->budget - x->work) FAIL; x->work += (uint64_t)(n); } while (0)
+
+/* ======== exact ======== */
+#define WAY exact
+#define IDX double
+#define ADD(a, b) ({ double v_ = (double)(a) + (double)(b); if (!isfinite(v_)) FAIL; v_; })
+#define SUB(a, b) ({ double v_ = (double)(a) - (double)(b); if (!isfinite(v_)) FAIL; v_; })
+#define GT(a, b) ((a) > (b))
+#define DIV2(a) ({ double q_ = floor_divide((a), 2); if (!isfinite(q_)) FAIL; q_; })
+#define MOD2(a) ({ double m_ = pure_mod((a), 2); if (!isfinite(m_)) FAIL; m_; })
+#define CHECK_AT(count, i) do { size_t c_ = (count); double i_ = (i); /* INDEX_AT, then ITEM_GET */ \
+ if (i_ != floor(i_) || i_ < 0 || i_ >= (double)c_) FAIL; if ((size_t)i_ >= c_) FAIL; } while (0)
+#define CHARGE(n) CHARGES_ON(n)
+#define LIST_N ListP
+#define LIST_I ListP
+#define LIST_B ListP
+#define L(name) (x->name)
+#define NCOUNT(l) ((IDX)pure_list_count(l))
+#define GET_CELL(l, i) ({ const PureList *l_ = (l); IDX at_ = (i); CHECK_AT(pure_list_count(l_), at_); pure_list_get(l_, (size_t)at_, 0); })
+#define GETN(l, i) (GET_CELL(l, i).num)
+#define GETI(l, i) ((IDX)GET_CELL(l, i).num)
+#define GETB(l, i) (GET_CELL(l, i).num != 0)
+#define APP_LIST(l, cell) do { PureList *l_ = (l); Held b_ = held(x, l_); PureCell c_ = cell; \
+ if (pure_list_insert(l_, pure_list_count(l_), &c_, NULL) != PURE_OK) FAIL; if (!grows(x, b_, held(x, l_))) FAIL; } while (0)
+#define APPN(l, v) APP_LIST(l, ((PureCell){.num = (v)}))
+#define APPI(l, v) APP_LIST(l, ((PureCell){.num = (double)(v)}))
+#define APPB(l, v) APP_LIST(l, ((PureCell){.num = (v) ? 1.0 : 0.0}))
+#define R_COUNT ((IDX)pure_list_count(x->ranges))
+#define R_CELL(f, k) ({ IDX k_ = (k); CHECK_AT(pure_list_count(x->ranges), k_); pure_list_get(x->ranges, (size_t)k_, f); })
+#define R_START(k) (R_CELL(START, k).num)
+#define R_CLASS(k) (R_CELL(CLASS, k).num)
+#define R_PI(k) (R_CELL(PI_FIELD, k).num != 0)
+#define R_PF(k) (R_CELL(PF_FIELD, k).num != 0)
+#define R_EAST(k) (R_CELL(EAST_FIELD, k).num != 0)
+#define R_PICT(k) (R_CELL(PICT_FIELD, k).num != 0)
+#define P_COUNT(p) ((IDX)pure_list_count((p)->list))
+#define P_CODE(p, k) ({ IDX k_ = (k); CHECK_AT(pure_list_count((p)->list), k_); pure_list_get((p)->list, (size_t)k_, 0).num; })
+#define CAPTURE(dst, name) do { PureList *s_ = L(name); /* LIST_VALUE: nest's copy, an empty list none */ \
+ if (pure_list_count(s_)) { PureList *c_ = pure_list_copied(s_); if (!c_) FAIL; \
+ if (!grows(x, (Held){0, 0}, (Held){0, pure_list_size(c_)})) FAIL; x->copies[x->copied++] = c_; s_ = c_; } \
+ dst = s_; } while (0)
+#define OUT_APPEND(v) APP_LIST(x->out, ((PureCell){.i64 = (int64_t)(v)}))
+#define OUT_EMIT(got) do { size_t n_ = pure_list_count(x->out); for (size_t k_ = 0; k_ < n_; ++k_) { \
+ int64_t v_ = pure_list_get(x->out, k_, 0).i64; if ((uint64_t)v_ > 255) FAIL; (got)[k_] = (uint8_t)v_; } } while (0)
+#define LINE_BEGIN do { PureList *all_[] = {x->codes, x->cls, x->eff, x->start, x->nonspace, x->ri, x->at, x->joined, \
+ x->numeric, x->out}; for (unsigned l_ = 0; l_ < 10; ++l_) pure_list_clear(all_[l_]); x->copied = 0; } while (0)
+#define LINE_END do { for (unsigned c_ = 0; c_ < x->copied; ++c_) pure_list_free(x->copies[c_]); x->copied = 0; } while (0)
+#include "app-way.h"
+#undef WAY
+#undef IDX
+#undef ADD
+#undef SUB
+#undef DIV2
+#undef MOD2
+#undef CHECK_AT
+#undef GETI
+
+/* ======== integer: whole numbers as integers ======== */
+#define WAY integer
+#define IDX int64_t
+#define ADD(a, b) ((a) + (b))
+#define SUB(a, b) ((a) - (b))
+#define DIV2(a) ((a) / 2) /* whole and at or above 0 */
+#define MOD2(a) ((a) % 2) /* ri: whole and at or above 0 */
+#define CHECK_AT(count, i) do { if ((uint64_t)(i) >= (uint64_t)(count)) FAIL; } while (0)
+#define GETI(l, i) ((IDX)GET_CELL(l, i).num)
+#include "app-way.h"
+#undef WAY
+
+/* ======== stored_flat: ranges' columns flat ======== */
+#define WAY stored_flat
+#undef R_COUNT
+#undef R_START
+#undef R_CLASS
+#undef R_PI
+#undef R_PF
+#undef R_EAST
+#undef R_PICT
+#define R_COUNT ((IDX)x->ranges_count)
+#define R_AT(column, k) ({ IDX k_ = (k); CHECK_AT(x->ranges_count, k_); (column)[k_]; })
+#define R_START(k) R_AT(x->r_start, k)
+#define R_CLASS(k) R_AT(x->r_class, k)
+#define R_PI(k) (R_AT(x->r_pi, k) != 0)
+#define R_PF(k) (R_AT(x->r_pf, k) != 0)
+#define R_EAST(k) (R_AT(x->r_east, k) != 0)
+#define R_PICT(k) (R_AT(x->r_pict, k) != 0)
+#include "app-way.h"
+#undef WAY
+
+/* ======== local_flat: the event's lists, the points and out as buffers ======== */
+#define WAY local_flat
+#undef LIST_N
+#undef LIST_I
+#undef LIST_B
+#undef L
+#undef NCOUNT
+#undef GETN
+#undef GETI
+#undef GETB
+#undef APPN
+#undef APPI
+#undef APPB
+#undef P_COUNT
+#undef P_CODE
+#undef CAPTURE
+#undef OUT_APPEND
+#undef OUT_EMIT
+#undef LINE_BEGIN
+#undef LINE_END
+#define LIST_N BufNP
+#define LIST_I BufIP
+#define LIST_B BufBP
+#define L(name) (&x->b_##name)
+#define NCOUNT(l) ((IDX)(l)->n)
+#define GET_BUF(l, i) ({ __typeof__(l) l_ = (l); IDX at_ = (i); CHECK_AT(l_->n, at_); l_->v[at_]; })
+#define GETN(l, i) GET_BUF(l, i)
+#define GETI(l, i) GET_BUF(l, i)
+#define GETB(l, i) (GET_BUF(l, i) != 0)
+#define MEMORY(bytes) do { if ((x->spent += (bytes)) > x->room) FAIL; } while (0)
+#define APP_BUF(l, value) do { __typeof__(l) l_ = (l); \
+ if (l_->n == l_->cap && !grow((void **)&l_->v, &l_->cap, sizeof(*l_->v))) FAIL; \
+ MEMORY(sizeof(*l_->v)); l_->v[l_->n++] = (value); } while (0)
+#define APPN(l, v) APP_BUF(l, (v))
+#define APPI(l, v) APP_BUF(l, (v))
+#define APPB(l, v) APP_BUF(l, (unsigned char)((v) ? 1 : 0))
+#define P_COUNT(p) ((IDX)(p)->count)
+#define P_CODE(p, k) ({ IDX k_ = (k); CHECK_AT((p)->count, k_); (p)->codes[k_]; })
+#define CAPTURE(dst, name) do { __typeof__(L(name)) s_ = L(name), c_ = &x->k_##name; /* LIST_VALUE: the items copied */ \
+ while (c_->cap < s_->n) if (!grow((void **)&c_->v, &c_->cap, sizeof(*c_->v))) FAIL; \
+ MEMORY(s_->n * sizeof(*s_->v)); memcpy(c_->v, s_->v, s_->n * sizeof(*s_->v)); c_->n = s_->n; dst = c_; } while (0)
+#define OUT_APPEND(v) APP_BUF(&x->b_out, (int64_t)(v))
+#define OUT_EMIT(got) do { for (size_t k_ = 0; k_ < x->b_out.n; ++k_) { int64_t v_ = x->b_out.v[k_]; \
+ if ((uint64_t)v_ > 255) FAIL; (got)[k_] = (uint8_t)v_; } } while (0)
+#define LINE_BEGIN do { x->b_codes.n = x->b_cls.n = x->b_eff.n = x->b_start.n = x->b_nonspace.n = x->b_ri.n = x->b_at.n = \
+ x->b_joined.n = x->b_numeric.n = x->b_out.n = 0; } while (0)
+#define LINE_END do { } while (0)
+#include "app-way.h"
+#undef WAY
+
+/* ======== shared: a Text's lists the buffers ======== */
+#define WAY shared
+#undef CAPTURE
+#define CAPTURE(dst, name) ((dst) = L(name))
+#include "app-way.h"
+#undef WAY
+
+/* ======== unbounded: shared, no bounds checks (the budget and memory charged) ======== */
+#define WAY unbounded
+#undef CHECK_AT
+#define CHECK_AT(count, i) do { (void)(count); } while (0)
+#include "app-way.h"
+#undef WAY
+
+/* ======== plain: no check, no charge ======== */
+#define WAY plain
+#undef CHARGE
+#undef MEMORY
+#define CHARGE(n) do { (void)x; } while (0)
+#define MEMORY(bytes) do { } while (0)
+#undef OUT_EMIT
+#define OUT_EMIT(got) do { for (size_t k_ = 0; k_ < x->b_out.n; ++k_) (got)[k_] = (uint8_t)x->b_out.v[k_]; } while (0)
+#include "app-way.h"
+#undef WAY
+
+static bool on_line_vega(Context *x, const Points *p, const uint32_t *codes, uint8_t *got) {
+ (void)x, (void)p;
+ return vega_line_breaks(codes, p->count, got);
+}
+
+/* ---- the harness ---- */
+static unsigned char *whole(const char *path, size_t *size) {
+ FILE *f = fopen(path, "rb");
+ if (!f) return NULL;
+ fseek(f, 0, SEEK_END);
+ *size = (size_t)ftell(f);
+ rewind(f);
+ unsigned char *bytes = malloc(*size ? *size : 1);
+ if (fread(bytes, 1, *size, f) != *size) { free(bytes); bytes = NULL; }
+ fclose(f);
+ return bytes;
+}
+
+static int counter(uint64_t config) {
+ struct perf_event_attr a;
+ memset(&a, 0, sizeof(a));
+ a.size = sizeof(a), a.type = PERF_TYPE_HARDWARE, a.config = config, a.disabled = 1, a.exclude_kernel = 1, a.exclude_hv = 1;
+ return (int)syscall(SYS_perf_event_open, &a, 0, -1, -1, 0);
+}
+
+static uint64_t read_counter(int fd) {
+ uint64_t v = 0;
+ if (read(fd, &v, sizeof(v)) != sizeof(v)) return 0;
+ return v;
+}
+
+typedef struct { Points points; const uint32_t *codes; const unsigned char *want; } Line;
+enum { RUNGS = 8 };
+static const char *const names[RUNGS] = {"exact", "integer", "stored_flat", "local_flat", "shared", "unbounded", "plain", "vega"};
+
+static bool run(int rung, Context *x, const Line *l, uint8_t *got) {
+ switch (rung) {
+ case 0: return on_line_exact(x, &l->points, got);
+ case 1: return on_line_integer(x, &l->points, got);
+ case 2: return on_line_stored_flat(x, &l->points, got);
+ case 3: return on_line_local_flat(x, &l->points, got);
+ case 4: return on_line_shared(x, &l->points, got);
+ case 5: return on_line_unbounded(x, &l->points, got);
+ case 6: return on_line_plain(x, &l->points, got);
+ default: return on_line_vega(x, &l->points, l->codes, got);
+ }
+}
+
+int main(int argc, char **argv) {
+ if (argc != 5) { fprintf(stderr, "usage: %s linebreak.pmod RANGES LINES ROUNDS\n", argv[0]); return 2; }
+ size_t ranges_size, lines_size;
+ unsigned char *ranges = whole(argv[2], &ranges_size), *bytes = whole(argv[3], &lines_size);
+ int rounds = atoi(argv[4]);
+ if (!ranges || !bytes) return 1;
+ PureModule module = {0};
+ char error[160];
+ if (pure_module_read(argv[1], &module, error, sizeof(error)) != PURE_OK) { fprintf(stderr, "%s\n", error); return 1; }
+ PureApp app;
+ if (pure_app_start(&app, &module, (PureData){0}) != PURE_OK) return 1;
+ int load = pure_module_input(&module, "load"), state = pure_module_state(&module, "ranges");
+ if (load < 0 || state < 0 || pure_app_message(&app, (uint32_t)load, (PureSpan){ranges, ranges_size}) != PURE_OK) return 1;
+ static Context x;
+ x.budget = UINT64_MAX, x.room = INT64_MAX;
+ x.ranges = app.lists[state], x.ranges_count = pure_list_count(x.ranges);
+ double *r_start = malloc(x.ranges_count * sizeof(double)), *r_class = malloc(x.ranges_count * sizeof(double));
+ unsigned char *flags = malloc(4 * x.ranges_count);
+ for (size_t i = 0; i < x.ranges_count; ++i) {
+ r_start[i] = pure_list_get(x.ranges, i, START).num, r_class[i] = pure_list_get(x.ranges, i, CLASS).num;
+ for (unsigned f = 0; f < 4; ++f) flags[f * x.ranges_count + i] = pure_list_get(x.ranges, i, PI_FIELD + f).num != 0;
+ }
+ x.r_start = r_start, x.r_class = r_class;
+ x.r_pi = flags, x.r_pf = flags + x.ranges_count, x.r_east = flags + 2 * x.ranges_count, x.r_pict = flags + 3 * x.ranges_count;
+ const uint8_t number[] = {PURE_CELL_NUMBER}, boolean[] = {PURE_CELL_BOOL}, integer[] = {PURE_CELL_INTEGER};
+ PureList **numbers[] = {&x.codes, &x.cls, &x.eff, &x.start, &x.nonspace, &x.ri, &x.at};
+ for (unsigned k = 0; k < 7; ++k) *numbers[k] = pure_list_new(1, number);
+ x.joined = pure_list_new(1, boolean), x.numeric = pure_list_new(1, boolean), x.out = pure_list_new(1, integer);
+ PureList *events[] = {x.codes, x.cls, x.eff, x.start, x.nonspace, x.ri, x.at, x.joined, x.numeric, x.out};
+ for (unsigned k = 0; k < 10; ++k) /* an event's list in FLAT arrays, as the runtime starts one (021) */
+ if (!pure_list_arrays(events[k])) return 1;
+ /* The lines: each its points as the message's list (the event's form) and as the message's numbers. */
+ static Line lines[1 << 16];
+ size_t count = 0, points = 0;
+ for (size_t at = 0; at + 4 <= lines_size && count < sizeof(lines) / sizeof(*lines); ++count) {
+ Line *l = &lines[count];
+ uint32_t n;
+ memcpy(&n, bytes + at, 4);
+ l->codes = (const uint32_t *)(bytes + at + 4);
+ l->want = bytes + at + 4 + 4 * (size_t)n;
+ at += 4 + 4 * (size_t)n + n + 1;
+ double *codes = malloc(((size_t)n + 1) * sizeof(double));
+ PureList *list = pure_list_new(1, number);
+ if (!pure_list_arrays(list)) return 1;
+ for (uint32_t i = 0; i < n; ++i) {
+ codes[i] = l->codes[i];
+ PureCell cell = {.num = codes[i]};
+ if (pure_list_insert(list, i, &cell, NULL) != PURE_OK) return 1;
+ }
+ l->points = (Points){list, codes, n};
+ points += n;
+ }
+ /* Checked first: each rung's boundaries are the test's, and exactly exact's. */
+ static uint8_t got[4096], first[4096];
+ size_t wrong[RUNGS] = {0};
+ for (size_t k = 0; k < count; ++k) {
+ const Line *l = &lines[k];
+ if (l->points.count + 1 > sizeof(got)) return 1;
+ for (int r = 0; r < RUNGS; ++r) {
+ x.fault = false;
+ bool same = run(r, &x, l, got) && !x.fault;
+ for (size_t i = 0; same && i <= l->points.count; ++i) same = (got[i] != NONE) == l->want[i];
+ if (r == 0) memcpy(first, got, l->points.count + 1);
+ else if (r < RUNGS - 1) same = same && !memcmp(first, got, l->points.count + 1);
+ if (!same && getenv("APP_DEBUG") && !wrong[r]) {
+ fprintf(stderr, "%s line %zu fault %d at %d:", names[r], k, x.fault, x.fault_line);
+ for (size_t i = 0; i <= l->points.count; ++i) fprintf(stderr, " %u/%u", got[i], l->want[i]);
+ fprintf(stderr, "\n");
+ }
+ wrong[r] += !same;
+ }
+ }
+ bool refused = false;
+ for (int r = 0; r < RUNGS; ++r)
+ if (wrong[r]) fprintf(stderr, "%s: %zu of %zu lines wrong\n", names[r], wrong[r], count), refused = true;
+ if (refused) return 1;
+ int ins = counter(PERF_COUNT_HW_INSTRUCTIONS), cyc = counter(PERF_COUNT_HW_CPU_CYCLES);
+ if (ins < 0 || cyc < 0) { perror("perf_event_open"); return 1; }
+ uint64_t total_ins[RUNGS] = {0}, total_cyc[RUNGS] = {0};
+ for (int round = 0; round < rounds; ++round)
+ for (int r = 0; r < RUNGS; ++r) {
+ x.work = 0, x.spent = 0, x.grown = 0;
+ ioctl(ins, PERF_EVENT_IOC_RESET, 0), ioctl(cyc, PERF_EVENT_IOC_RESET, 0);
+ ioctl(ins, PERF_EVENT_IOC_ENABLE, 0), ioctl(cyc, PERF_EVENT_IOC_ENABLE, 0);
+ for (size_t k = 0; k < count; ++k) run(r, &x, &lines[k], got);
+ ioctl(ins, PERF_EVENT_IOC_DISABLE, 0), ioctl(cyc, PERF_EVENT_IOC_DISABLE, 0);
+ total_ins[r] += read_counter(ins), total_cyc[r] += read_counter(cyc);
+ x.spent = 0;
+ }
+ double n = (double)points * rounds;
+ for (int r = 0; r < RUNGS; ++r)
+ printf("{\"rung\":\"%s\",\"lines\":%zu,\"code_points\":%zu,\"rounds\":%d,\"instructions_per_point\":%.1f,"
+ "\"cycles_per_point\":%.1f,\"answers\":\"the test's, and exact's\"}\n",
+ names[r], count, points, rounds, total_ins[r] / n, total_cyc[r] / n);
+ pure_app_stop(&app);
+ return 0;
+}