From 1034ff06b1a5742f332365d7069f41db78642c87 Mon Sep 17 00:00:00 2001 From: eunhhu Date: Mon, 19 Oct 2026 03:56:43 +0100 Subject: Bytes compared (140's decision point: DNS and HTTP both met it): TEXT_COMPARE's argument takes PURE_COMPARE_BYTES (bit 8) beside its relation, its two then bytes (UTF-8 asked of neither), as CONCAT's argument says bytes; verified as A_ORDER (a relation, and the bit), the executor relating by the argument's relation alone; the JIT, the tier and the batch executor leave TEXT_COMPARE to the executor as before; unit test bytes_compared: eight relations of a constant's bytes, none, 0xff and 'A' (0xff after 'A', a longer after its first byte), four refused (the bit on texts, no bit on bytes, a text with bytes, a relation past NE); counts: b46 against b45 the same on all five works, with and without batches; 008 --- core/module.c | 12 +++--- core/module.h | 4 +- core/test-module.c | 49 ++++++++++++++++++++++ docs/design/008-module-format.md | 2 +- .../041-batches/2026-10-19-counts.jsonl | 10 +++++ 5 files changed, 70 insertions(+), 7 deletions(-) diff --git a/core/module.c b/core/module.c index 3657843..64ec8f6 100644 --- a/core/module.c +++ b/core/module.c @@ -35,11 +35,12 @@ enum { READ = IN_VIEW | IN_HANDLER }; * a fold runs in, a number, bool or i64 state (A_VALUE), or the value's type * (A_TYPE: 0 a number, 1 a bool, 2 an i64), how I64_BITS computes (A_BITS), * what a position is (A_POSITION: 0 a number, 1 an i64), what CONCAT joins - * (A_JOINED: 0 texts, 1 bytes), how NUM_TEXT writes (A_FORMAT) or the value + * (A_JOINED: 0 texts, 1 bytes), what TEXT_COMPARE relates (A_ORDER: a relation, + * and PURE_COMPARE_BYTES for two bytes), how NUM_TEXT writes (A_FORMAT) or the value * held by name (A_HELD), or a function (A_FUNCTION: CALL's, and RETURN's own). */ enum { A_NONE, A_STATE, A_ITEM = A_STATE + PURE_STATE_I64 + 1, A_FIELD, A_CONSTANT, A_IN_TEXT, A_IN_LIST, A_ROW, A_TARGET, A_DEPTH, A_FOLD, A_RELATION, A_KEYED, A_OUTPUT, A_OUTER, A_EACH, A_LOCAL, A_VALUE, A_TYPE, - A_LOOP, A_BITS, A_POSITION, A_JOINED, A_FORMAT, A_HELD, A_FUNCTION }; + A_LOOP, A_BITS, A_POSITION, A_JOINED, A_FORMAT, A_HELD, A_FUNCTION, A_ORDER }; /* A fold's block is verified as a column that leaves one bool (count, first) * or a value and whether to include it (a bool): a number (FOLD_TWO) or an * i64 (FOLD_I64). A table's filter is verified as a column that leaves a @@ -119,7 +120,7 @@ static const struct { unsigned char pops, in[3], out; unsigned short where; unsi [PURE_OP_PICK] = {0, {0}, 0, READ, A_DEPTH, O_ANY}, [PURE_OP_SLIDE] = {0, {0}, 0, READ, A_DEPTH, O_ANY}, [PURE_OP_FOLD] = {0, {0}, T_NUM, READ, A_FOLD, O_ANY}, - [PURE_OP_TEXT_COMPARE] = {2, {T_TEXT, T_TEXT}, T_BOOL, READ, A_RELATION, O_ANY}, + [PURE_OP_TEXT_COMPARE] = {2, {T_TEXT, T_TEXT}, T_BOOL, READ, A_ORDER, O_ANY}, [PURE_OP_LIST_FIND] = {1, {T_KEY}, T_INDEX, READ, A_KEYED, O_ARG}, [PURE_OP_LIST_HAS] = {1, {T_KEY}, T_BOOL, READ, A_KEYED, O_ANY}, [PURE_OP_NOW] = {0, {0}, T_NUM, IN_HANDLER, A_NONE, O_ANY}, @@ -459,6 +460,7 @@ PURE_COLD static bool names_arg(const PureModule *m, unsigned kind, uint32_t arg (form != PURE_FORMAT_FIXED || type == 0) && (form == PURE_FORMAT_FIXED || form == PURE_FORMAT_DECIMAL || !digits); } case A_RELATION: return arg <= PURE_NOT_EQUAL; + case A_ORDER: return (arg & ~PURE_COMPARE_BYTES) <= PURE_NOT_EQUAL; /* a relation, of texts or of bytes */ case A_KEYED: return state_of(m, arg, PURE_STATE_LIST) && m->state_key[arg]; case A_OUTPUT: return arg < m->inputs && m->input[arg].kind == PURE_KIND_OUTPUT; case A_NONE: case A_TARGET: case A_DEPTH: return true; @@ -668,7 +670,7 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i need[0] = state_type(m, arg); } else if (op->op == PURE_OP_NUM_TEXT && arg & 3) { need[0] = (arg & 3) == 1 ? T_BOOL : T_I64; - } else if (op->op == PURE_OP_CONCAT && arg) { + } else if ((op->op == PURE_OP_CONCAT && arg) || (op->op == PURE_OP_TEXT_COMPARE && arg & PURE_COMPARE_BYTES)) { need[0] = need[1] = T_BYTES; } else if (op->op == PURE_OP_BYTES_SLICE && arg) { need[1] = need[2] = T_I64; /* the positions i64s */ @@ -4233,7 +4235,7 @@ static inline __attribute__((always_inline)) int step(Frame *frame, const PureOp int order = shorter ? memcmp(a.data, b.data, shorter) : 0; if (!order) order = (a.size > b.size) - (a.size < b.size); arena->used = mark; - stack[sp - 1].num = related(arg, order); + stack[sp - 1].num = related(arg & ~PURE_COMPARE_BYTES, order); NEXT; } case PURE_OP_LIST_FIND: case PURE_OP_LIST_HAS: { diff --git a/core/module.h b/core/module.h index 75e27ba..6fad0ec 100644 --- a/core/module.h +++ b/core/module.h @@ -209,8 +209,10 @@ enum { PURE_LOOP_INTEGER = 256, PURE_MODULE_NESTING = 8 }; #define PURE_WORK_DEFAULT (UINT64_C(1) << 28) #define PURE_GROWTH_DEFAULT ((size_t)1 << 28) /* TEXT_COMPARE's argument: the relation, in the order of the number - * comparisons (LT LE GT GE EQ NE); texts order by their bytes. */ + * comparisons (LT LE GT GE EQ NE), and PURE_COMPARE_BYTES with it when its + * two are bytes (texts and bytes alike order by their bytes). */ typedef enum { PURE_LESS, PURE_LESS_EQUAL, PURE_GREATER, PURE_GREATER_EQUAL, PURE_EQUAL, PURE_NOT_EQUAL } PureRelation; +#define PURE_COMPARE_BYTES 0x100u /* An input is a message (027's 4d: the only thing an app takes), an output * or a view's arguments. An output is a message the app sends (SEND, in a diff --git a/core/test-module.c b/core/test-module.c index f89e198..8795bf8 100644 --- a/core/test-module.c +++ b/core/test-module.c @@ -6164,6 +6164,54 @@ static void bits_and_bytes(void) { printf("{\"bits_and_bytes\":{\"bits\":%zu,\"reads\":%zu,\"refused\":%zu}}\n", LEN(cases), LEN(reads), LEN(refused)); } +/* ---- Bytes compared (after research 140's DNS and HTTP) -------------------- + * TEXT_COMPARE with PURE_COMPARE_BYTES relates two bytes by their bytes, as + * it relates texts (a shorter before a longer it begins), UTF-8 asked of + * neither; without the bit it takes texts alone, with it bytes alone. */ +static void bytes_compared(void) { + enum { CMP = PURE_OP_TEXT_COMPARE, BF = PURE_OP_BYTES_FROM, JZ_ = PURE_OP_JZ, BYTE_ = PURE_OP_BYTE, B = PURE_COMPARE_BYTES }; + const O word = {TK, 0, 0}, none = {TK, 1, 0}; /* "Az\xc3\xa9" and "" */ + const struct { O a[2], b[2]; uint32_t relation; double holds; } cases[] = { + {{word, {BF, 0, 0}}, {word, {BF, 0, 0}}, PURE_EQUAL, 1}, + {{word, {BF, 0, 0}}, {none, {BF, 0, 0}}, PURE_GREATER, 1}, + {{none, {BF, 0, 0}}, {word, {BF, 0, 0}}, PURE_LESS, 1}, + {{none, {BF, 0, 0}}, {none, {BF, 0, 0}}, PURE_LESS_EQUAL, 1}, + {{IC(0xFF), {BYTE_, 0, 0}}, {word, {BF, 0, 0}}, PURE_GREATER, 1}, /* no UTF-8: 0xff after 'A' */ + {{IC(0xFF), {BYTE_, 0, 0}}, {IC(0xFF), {BYTE_, 0, 0}}, PURE_NOT_EQUAL, 0}, + {{word, {BF, 0, 0}}, {IC('A'), {BYTE_, 0, 0}}, PURE_GREATER_EQUAL, 1}, /* longer, the same first byte */ + {{word, {BF, 0, 0}}, {IC('A'), {BYTE_, 0, 0}}, PURE_EQUAL, 0}, + }; + for (size_t k = 0; k < LEN(cases); ++k) { + O code[] = {cases[k].a[0], cases[k].a[1], cases[k].b[0], cases[k].b[1], {CMP, cases[k].relation | B, 0}, {JZ_, 8, 0}, + {C, 0, 1}, {ST, 0, 0}}; + Byted ran = run_bytes(code, LEN(code)); + if (ran.fault != PURE_FAULT_NONE || ran.n != cases[k].holds) { + fprintf(stderr, "bytes compared %zu: fault %d, %g\n", k, (int)ran.fault, ran.n); + abort(); + } + } + /* Refused: the bit on texts, no bit on bytes, a text with bytes, a relation past NE. */ + const O refused[][5] = { + {word, none, {CMP, PURE_EQUAL | B, 0}, {JZ_, 5, 0}, {C, 0, 1}}, + {word, {BF, 0, 0}, none, {BF, 0, 0}, {CMP, PURE_EQUAL, 0}}, + {word, {BF, 0, 0}, none, {CMP, PURE_EQUAL | B, 0}, {JZ_, 5, 0}}, + {word, {BF, 0, 0}, none, {BF, 0, 0}, {CMP, (PURE_NOT_EQUAL + 1) | B, 0}}, + }; + for (size_t k = 0; k < LEN(refused); ++k) { + static const S nums[] = {{"n", 0}}; + static const T others[] = {{"note", 1, 0}, {"bytes", 3, 0}, {"i", PURE_STATE_I64, 0}, {"said", 1, 1}}; + static const RF byte_fields[] = {{"v", 3, 0}}; + static const R records[] = {{"Byte", byte_fields, 1}}; + static const char *const constants[] = {"Az\xc3\xa9", ""}; + static const I inputs[] = {KEY("go", "KeyG")}; + H handler = {1, 0, 5}; + Spec spec = {nums, 1, inputs, 1, NULL, 0, &handler, 1, refused[k], 5, NULL, 0, others, LEN(others), constants, + LEN(constants), NULL, 0, records, 1, NULL, 0}; + if (load(spec, &module_b) == PURE_OK) { fprintf(stderr, "bytes compared: refused %zu loaded\n", k); abort(); } + } + printf("{\"bytes_compared\":{\"cases\":%zu,\"refused\":%zu}}\n", LEN(cases), LEN(refused)); +} + /* ---- Formats, slices and values held by name (docs/design/027, stage 2a) -- * NUM_TEXT writes a number, a bool or an i64 in a width, with zeros or * spaces, in a base or with fixed digits, or an integer in minor units @@ -7342,6 +7390,7 @@ int main(void) { bools(); integers(); bits_and_bytes(); + bytes_compared(); formats_and_held(); lists_in_records(); functions_and_characters(); diff --git a/docs/design/008-module-format.md b/docs/design/008-module-format.md index 4d950fd..41abb3b 100644 --- a/docs/design/008-module-format.md +++ b/docs/design/008-module-format.md @@ -145,7 +145,7 @@ v7은 027의 4단계(패턴 모으기)의 판이다. 4b로 **키별 문서**를 | 52 | PICK | … → … v | `인자`개 아래의 값을 복사해 올린다(0이면 맨 위). 타입과 대상도 그대로 | | 53 | SLIDE | … v → v | 맨 위 값 아래의 `인자`개(1 이상)를 버린다 | | 54 | FOLD | → n 또는 i | 집계. `인자`의 비트 0–7은 종류(0 count, 1 sum, 2 max, 3 min, 4 first, 5 distinct: 포함된 항목의 서로 다른 값의 수, −0은 0), 8–15는 리스트 상태, 16–31은 뒤따르는 블록의 길이 L. 그 L개 명령이 항목마다 그 리스트의 열처럼 돈다(`ROW`가 항목). count는 블록이 참을 남긴 항목을 세고, sum·max·min·distinct는 블록이 남긴 수와 포함 여부(bool)로 계산한다. first는 블록이 참을 남긴 첫 항목의 인덱스(그 리스트의 인덱스)이고 거기서 멈춘다. 포함된 항목이 없으면 count·sum은 0, max·min·first는 `not_found` | -| 55 | TEXT_COMPARE | t t → bool | 바이트 순서로 비교한다. `인자`는 관계(0 LT, 1 LE, 2 GT, 3 GE, 4 EQ, 5 NE, 수 비교 명령의 순서). 앞이 같으면 짧은 쪽이 앞이다 | +| 55 | TEXT_COMPARE | t t → bool, b b → bool | 바이트 순서로 비교한다. `인자`의 비트 0–7은 관계(0 LT, 1 LE, 2 GT, 3 GE, 4 EQ, 5 NE, 수 비교 명령의 순서), 비트 8(`PURE_COMPARE_BYTES`)이 서면 둘 다 바이트다(UTF-8을 묻지 않는다, 140의 DNS와 HTTP 뒤). 앞이 같으면 짧은 쪽이 앞이다 | | 56 | LIST_FIND | k → i | 키 있는 리스트 `인자`에서 키가 k인 항목의 인덱스. 반씩 나눠 찾는다. 없으면 `not_found` | | 57 | LIST_HAS | k → bool | 그 키의 항목이 있으면 참 | | 58 | NOW | → n | 이벤트가 보는 지금(1970년부터 ms, UTC). 호스트가 정하고 스크립트는 아무 때나 재현한다. 핸들러 전용: 보기는 상태의 순수 함수로 남는다 | diff --git a/research/experiments/041-batches/2026-10-19-counts.jsonl b/research/experiments/041-batches/2026-10-19-counts.jsonl index fbb9666..dbce756 100644 --- a/research/experiments/041-batches/2026-10-19-counts.jsonl +++ b/research/experiments/041-batches/2026-10-19-counts.jsonl @@ -48,3 +48,13 @@ {"version":"b45","way":"rounds","work":"matmul","instructions":21186169957,"cycles":6774482517} {"version":"b45","way":"batches","work":"bfs","instructions":3341456134,"cycles":1665227825} {"version":"b45","way":"rounds","work":"bfs","instructions":4707546895,"cycles":2096871761} +{"version":"b46","way":"batches","work":"roots","instructions":157245208,"cycles":109545990} +{"version":"b46","way":"rounds","work":"roots","instructions":2715503939,"cycles":1008197614} +{"version":"b46","way":"batches","work":"knapsack","instructions":348032006,"cycles":108449419} +{"version":"b46","way":"rounds","work":"knapsack","instructions":22162412506,"cycles":7780214307} +{"version":"b46","way":"batches","work":"join","instructions":1783720716,"cycles":1017685077} +{"version":"b46","way":"rounds","work":"join","instructions":7723077605,"cycles":3367159934} +{"version":"b46","way":"batches","work":"matmul","instructions":702232035,"cycles":229318958} +{"version":"b46","way":"rounds","work":"matmul","instructions":21186169899,"cycles":6880424808} +{"version":"b46","way":"batches","work":"bfs","instructions":3341454374,"cycles":1651047768} +{"version":"b46","way":"rounds","work":"bfs","instructions":4707545665,"cycles":2088014772} -- cgit v1.2.3