aboutsummaryrefslogtreecommitdiffstats
path: root/tests/check.py
Commit message (Collapse)AuthorAgeFilesLines
* 140: aup's frictions written away and the apps measured again (no core ↵eunhhu38 hours1-1/+1
| | | | change, no new opcode): lists declared together, targets together (straight when no value can read what a target changes), a list given a list value whole, abs, tick.period, hex where numbers go; lines to Python 1.47-2.04x (were 1.47-2.15x): UAX #14 176 to 169, layout 116 to 110, HTTP 148 to 146 and its last workaround gone; instructions of the four whose modules changed within 0.2%, inflate's and UAX #14's modules the same; the six specs' checks pass on the three paths, module_specs nine; RTO's checks no longer rewrite G (tick.period); the suite table's rows 5, 8 and 9
* 140's 8, box layout, written in aup with no new opcode (aup's ↵eunhhu39 hours1-1/+24
| | | | examples/layout.aup: a subset of CSS Flexbox 1 written down first, one line, row or column, padding, gap, six justify-content, four align-items, grow and shrink with 9.7's loop, cross sizes stretched, nothing sized by content): 2,000 random trees (29,388 nodes; 7,659 items frozen at their padding by the loop) are Taffy 0.15's within 0.01 (layout_taffy, built by cargo; the worst 8.3e-5) and layout.py's bit for bit on the three paths, the paths the same; the single-pass subset first tried refused 66% of trees, so the loop came in; Taffy given min sizes equal to the padding (given 0 it floors at 0 while resolving lengths, where 9.7 floors the content box), the reference adding from the left (sum compensates since Python 3.12); module_specs in make check: 100 trees (tests/specs/layout); bars: 116 lines against Python's 54, 4,754 ns a node, Taffy's 644 (7.4x), pure Python 5,396 (Pure 1.14x faster), 1,494 instructions a node (9.7's loop 44%); four frictions; aup's rows read by position no longer copy (99,067 ns a node before); the suite table's row 8; CHECKPOINT's direction row
* 140's 10, RFC 6298's retransmission timer, written in aup with no new opcode ↵eunhhu39 hours1-1/+27
| | | | (aup's examples/rto.aup, the timer a deadline in the state seen at each periodic tick): 20 scenarios (10 seeds, ticks every 10 and 100 ms; a closed loop: first sendings lost one in five, the receiver acknowledging cumulatively) and their 241,612 events right against rto.py, the model of the same rules (no known implementation takes given events), on the three paths, the paths the same; module_specs in make check: one scenario at both periods (tests/specs/rto, 2,297 events); bars: one workaround (the periodic tick: 2,795.8 ticks a timeout at 10 ms, 281.6 at 100, seen 4.27 and 49.23 ms late on average; ticks 93% and 57% of the counted work), 56 lines against Python's 30, three frictions (an input's period not readable, no abs, send a keyword); the one-shot timer's shape as a decision point, one spec needing it: noted, not made; the suite table's row 10
* 140's 5, line breaking (UAX #14, Unicode 16.0), written in aup with no new ↵eunhhu39 hours1-1/+30
| | | | opcode (aup's examples/linebreak.aup, the properties as data: 2,787 ranges from the UCD 16.0 Vega's text is frozen at, built by Vega's text/unicode.py, loaded once): every line of LineBreakTest.txt (16,672) and 2,000 random lines (their boundaries as linebreak.py, the Python reference, which passes the test whole, works them out) right on the three paths, the paths' outputs the same; module_specs in make check: the ranges and the lines kept in tests/specs/linebreak (derived from the UCD, the Unicode License v3's notice), run by pure-run --views; bars: written, no workaround (three frictions: hex literals always i64, number no let's name, a let's list one a line), 176 lines against Python's 99, 6,920 ns a code point against Vega's C 34.7 (199x), pure Python 2,523 (Pure 2.7x slower), 1,774 instructions a code point (setup 260, the ranges' binary search 360, decide about 1,150 a boundary); the decisions before 8 (box layout: Taffy can be fetched by cargo, a subset to choose first; as a handler) and 10 (a one-shot timer is a core primitive one spec needs: noted, not made); the suite table's row 5; CHECKPOINT's direction row
* 140 measured again without the workarounds: the three specs' checks pass on ↵eunhhu40 hours1-3/+3
| | | | the three paths as before (zlib's 640 streams and 240 damaged, dnspython's 400 messages and 300 damaged, h11's 200 streams on every cut and 200 damaged); lines inflate 132 to 121, DNS 115 to 106, HTTP 159 to 148 (Python's 1.5-1.9x, was 1.6-2.0x); time inflate 1,152 to 1,139 ns a byte (102x C), DNS 244 to 221 (61x, instructions 63.6 to 49.9 a byte: == in its compression), HTTP 278 to 270 (83x, with the free empty join); left: DNS's view calls no block fn, HTTP's let list takes no list value; module_specs, the checks and the timing tools read the bytes states (inflated, pending); the suite table's rows for 1, 3 and 9
* 140's 9, HTTP/1.1 requests a packet at a time (RFC 9112), written in aup ↵eunhhu42 hours1-1/+38
| | | | with no new opcode (aup's examples/http.aup: each packet an event, its bytes added to what the last left, every request they complete read, h11's grammar): 200 streams (132,623 bytes, 578 requests: methods, targets, HTTP/1.1 and a last 1.0, mixed-case names, spaces around values, Content-Length and chunked bodies with extensions and trailers, some ending in part of a request) fed whole, at random boundaries and a byte at a time (the 50 under 400 bytes), the requests read h11's on every cut and the bytes left those after them, the three paths' outputs the same bytes; 200 damaged streams fed whole as http_requests.py (the Python reference) reads them, 35 refused by both and 165 the same; module_specs in make check: 80 streams and their reading (tests/specs/http, no h11 at check time) whole on the three paths and cut 88 ways; bars: written, three workarounds (the bytes left in a list of one: bytes are no state; names and codings compared as text: bytes have no ==; field lines returned in a record: a fn returns no bare list), 159 lines against Python's 79, 278 ns a byte against the same algorithm in C 3.27 (85x), pure Python 67 (its bytes methods are C: Pure 4.1x slower), h11 284; 140's decision point after 027: bytes alone as a state (inflate, HTTP) and bytes' == (DNS, HTTP) now each met by two written specs, so the two-spec rule lets them into the core (their design not begun); a view calling a block fn met by one (DNS); aup's three its compiler's; CHECKPOINT's direction row
* 140's 1, DNS messages (RFC 1035), written in aup with no new opcode (aup's ↵eunhhu42 hours1-1/+33
| | | | examples/dns.aup: a message read by a handler into its header and records, names uncompressed in their wire form, those in NS, CNAME, PTR, MX, SOA and SRV data too, each pointer pointing before the last; written back by the view wire, the owners compressed): 400 messages dnspython made (148,582 bytes, 3,466 records: A, AAAA, NS, CNAME, PTR, MX, SOA, TXT, SRV, EDNS, shared suffixes, 63-byte labels, bytes above 0x7f, 245-byte names) read on the three paths as dnspython reads them (each section's records as a set) and written back to bytes dnspython reads as the same message, the paths' outputs the same bytes; 300 damaged messages as dns_message.py (the Python reference) reads them, 185 refused by both and 115 the same records and bytes; module_specs in make check: 200 of those messages (tests/specs/dns, no dnspython at check time) read on the three paths, written back and read again to the same state and bytes, 23 cut ones failing; dnspython shuffles a set's records unless want_shuffle is off, and merges a set's differing TTLs to the least; bars: written, two workarounds both the core's (bytes have no ==: TEXT_COMPARE takes text alone; a view calls no block fn: CALL is in handlers and functions alone), 115 lines against Python's 72, 244 ns a message byte read and written against the same algorithm in C 3.65 (67x), glibc's resolver reading alone 4.4, pure Python 131 (Pure 1.9x slower), 218 compiled; 63.6 instructions a byte, reading 42%, writing 58%; CHECKPOINT's direction row
* 140's 3, inflate (RFC 1951), written in aup with no new opcode (aup's ↵eunhhu43 hours1-1/+36
| | | | examples/inflate.aup, codes read a bit at a time from canonical counts as zlib's puff.c): 640 streams zlib made (6,488,480 bytes: random bytes, words, runs and repeats 32,768 back; levels 0, 1, 6, 9 and zlib's five strategies, so stored, fixed and dynamic blocks) inflated to the original's bytes on the three paths, the paths' outputs the same bytes; 240 damaged streams (cut, a bit flipped) read as inflate.py (the Python reference) reads them, 165 refused by both and 75 the same bytes; module_specs in make check: 144 streams on the three paths, 21 cut ones failing and leaving the last bytes; bars: written (no new opcode), three workarounds (lists passed in records: a fn takes no list; the output a list of one: a bytes value is no state; a block's tables built in a fn: a let's list lives in the handler's own block), 132 lines against Python's 81, 1,152 ns an output byte against the same algorithm in C 10.7 (108x) and zlib 3.5 (329x), pure Python 1,214; 191 instructions a byte (a block fn called for each bit was 246 and 1,553 ns: small block fns' inlining is 2c's leftover); the four timing tools' message calls fixed for 4b's API (no keys); CHECKPOINT's direction row
* Module format v7, 027's 4e: a view of any value: a view's and a column's ↵eunhhu47 hours1-3/+35
| | | | type a record field's (number, text, bool, i64, list, bytes; PURE_VIEW_TABLE now PURE_VIEW_LIST), a view entry 20 bytes (its table + 1, its items' record + 1, its list state + 1); a list view's rows a list state's items as they are or filtered (its record's fields its columns, made at load: each reads its item's field as it is), a list state's map (its columns following it, as tables were), or the items of a list its block leaves (copied into a kept list once a change, its rows and cells kept until the next); a list cell lent in its items' stored form (pure_app_cell_items, pure_items_next reading it), bytes as a text is; a pointing only at a list view over a state; pure-run writes bytes as hex and a list cell as an array of objects, pure-serve a bytes view as application/octet-stream and, in JSON, bytes as hex strings and list cells as arrays of objects; a hole closed: a view without a body never began its growth anew, so in a snapshot's reader (pure-serve's views) reading a kept list's list failed as memory (unreachable before 4e: no such view could be written), now marked a body at load; unit test list_views (as it is, filtered, computed, a map's list and bytes columns, a bytes view, six refused); check server_trees keeps its forests in a stored list shown by list views over HTTP against the model, a restart too; counts: the first build's bank tables rose (field +5.9%: view_of left inline at its seventh caller, serve's put_bytes too), so view_of is inlined, the made columns' and computed lists' paths and serve's new writers kept apart, a computed list's rows held as a filter's; b37 against b35 within 0.001% with batches and without; the bank's queries (s35 on the 4d aup's module, s37 on 4e's, six each in turns) field +0.67%, found +0.43%, large +0.31%, the rest within 0.20%; 008 and 027
* Module format v7, 027's 4b-2: keyed documents gone, a keyed list of records ↵eunhhu2 days1-195/+230
| | | | holding bytes doing their work: no collections table, DOC, DOC_SIZE or REPLACE (106 opcodes now 103, those from LEN on moved up), keyed messages or a base revision (pure_app_message takes an input and a payload), no PureData document callbacks (a data host opens states and saves them together), no burst by document key; the header 38 bytes; pure-edit-module gone; pure-serve without /objects, ETag, If-Match and If-None-Match, 412 or an app a thread for a module of documents alone (one without states or outputs runs so); a hole closed: an instruction in no handler's, view's or function's block was never verified yet the load's later passes read its arguments (a damaged module read out of bounds once the header moved what the mutation test damaged), now blanked to CONST 0; checks moved: server_documents (DOCS_SOURCE, keyed byte documents with the app's revision, against a Python model, one and three connection threads, a restart) in place of the parity with editor.pure, pipelining and bursts through kill -9 on its inputs and views, the lent budget on a text view (lending a document's read is server_lent_views' already), clocks and tokens on it; message tests without a document host; the HTTP benchmark's document suite runs the earlier document server and the baselines alone; the count harness builds against either message API (runs.sh build: -DPURE_KEYED_MESSAGES before 4b); counts: b33 against b32 with batches within 0.002% but BFS -0.33%, without batches BFS +0.63%, knapsack +0.52%, join +0.38%; the bank's queries (s32 on a v6 module, s33 on v7) field +0.68%, found +0.44%, large +0.42%, the rest within 0.13%; 008 as v7, 027's 4b-2 as made and 4c designed
* Module format and executor: 027's 4b-1, a record field holding bytes (type ↵eunhhu2 days1-3/+7
| | | | 5): held as a text is but for UTF-8, its default a constant; a function's bytes in a signature this type too (PURE_SIGNATURE_BYTES gone); no list keyed by bytes; replaced, a text field becomes a bytes field keeping its value (pure_cell_as, kinds_carry); images' headers take its kind; the JSON edge reads it from a string; pure-run shows it as hex; an append asks a record's fields held as bytes from a mask the load makes (record_held) instead of each field's type twice, so b32 runs fewer instructions than b30 (BFS without batches -1.05%, join -0.25%, BFS with batches -0.36%, the rest within 0.04%; the bank's queries s30 against s32 -0.01% to +0.30%); unit test bytes_fields (bytes not UTF-8, a text field replaced by a bytes field, three refused); server_trees with a bytes field; 008 and 027
* Module executor and pure-serve: 027's 4a, a message's rows holding lists: ↵eunhhu2 days1-0/+128
| | | | rows whose records hold lists are read, as the message arrives, by stage 3's decoder into the event's lists (value_take into the rows' list), the event beginning with them; the JSON edge reads an object's array as its record's objects, 64 lists deep at most (items_of: an item's lists encoded first and the item moved down over them); the load no longer refuses them; a hole closed: the decoder read past the end where fewer than 4 bytes were left for a length (size - at - 4 wrapped around), unseen while it read only what was stored; unit test nested_rows (three events, four rows not whole changing nothing); check server_trees (300 random forests over HTTP against a Python model walking them in the same order, a require refusing some, a list field not an array, a field the record lacks and 65 lists deep refused, a restart); counts: b30 against b29 within 0.001% but BFS with batches +0.12%, the bank's queries -0.00% to +0.23%; 027's stage 4 planned in six steps (4a to 4f, the counts it starts from, 4b in two commits) and 4a as made; 008 on nested rows
* Research 140, 027's stage 2c measured: JSON read into a tree by recursive ↵eunhhu2 days1-8/+10
| | | | descent (aup's examples/json_tree.aup: value(d, i) -> Json, number giving the value and where it ends, each call's own let's lists, string's escapes joined by char(u), a depth-first walk passing items as values) against Python's reading walked so: JSONTestSuite's 95 y_ taken alike, no n_ taken, 2b's 6 i_ taken (a lone surrogate refused by char as encoding); 100,000 and 50,000 deep refused as memory (the frames fill the event's room, 0.2 s), 30,000 deep taken (past Python's json); 300 random documents alike; in make check (module_specs' digest now depth first, three paths); 142 lines against the same design's 105 in Python (json_rec.py; the bar missed: 24 lines of declarations, 23 of braces), 46 times cJSON's time and 1.6 times plain Python's (600 ns a byte, 248 Pure instructions; 71% in the steps, about 10% in calls, 66,000 calls a document, most of them small block fns like skip); characters against Python's ord in aup's check; probes: p5-chars compiles, p4-rec (a view calling itself) refused; instruction counts: the workloads' modules the same, batches within 0.001% of 2b (BFS -0.21%), the executor alone -0.001% to +0.30%, the bank's queries -0.30% to +0.11% (four runs each; bar 1%); json_rec_time.c (cJSON, a depth-first digest compared first); CHECKPOINT's direction row through 2c
* Research 140, 027's stage 2b measured: JSON read into a tree (aup's ↵eunhhu8 days1-0/+53
| | | | examples/json_tree.aup: a variant, lists in records, match, appends into a field's list, number(t)) and walked for a digest, against Python's reading: JSONTestSuite's 95 y_ taken alike, no n_ taken, 6 i_ taken; 300 random nested documents alike; in make check (module_specs, the suite and 100 random documents, three paths); 167 lines against the same design's 133 in Python (the bar missed: the type declarations), 40 times cJSON's time and 1.3 times plain Python's (556 ns a byte, 525 instructions); json_tree.py, json_tree_time.c (cJSON linked, digests compared first)
* Research 140, 027's stage 2a measured: whether bytes are JSON (aup's ↵eunhhu8 days1-18/+56
| | | | examples/json.aup, 80 lines against the same design's 90 in Python): JSONTestSuite's 95 y_ cases taken and 188 n_ refused, 21 of the 35 i_ taken, the three paths alike, in make check (module_specs, the 316 small cases in tests/specs/json under their MIT license, the two large ones made and hashed); 116 times C's time and 1.2 times plain Python's (299 ns a byte, 339 instructions); && and || made to decide alone (writing it, i < n && d[i] read past the end) with AND and OR kept where the right side cannot fail, every other module byte for byte the same; SHA-256 again with formats and joined bytes, 42 lines and 159 times C
* Research 140, 027's stage 1 measured: SHA-256 written in aup (its ↵eunhhu8 days1-0/+18
| | | | examples/sha256.aup) right for FIPS 180-2's examples and 138 more messages on every path, in make check against hashlib; 52 lines against Python's 28 (the bar missed: no formatting, no bytes joined or sliced, no lists as values, moved into stage 2), 176 times C's time and 0.43 of plain Python's (1,591 ns a byte; a block's 19,129 instructions, 39% of them the inlined fn's literals not folded); 044's check, time.c against the same algorithm in C (digests compared first) and the Python reference
* Batch executor, stage 5 of design 025: the sums into cells at once from ↵eunhhu11 days1-0/+70
| | | | round 0 (sums_in_words): the rows and e's in four bytes first (vector code) up to the first round whose e is not a whole number below 2^32, then each round's sums in turn in integers for a one- or four-byte cell while it stays in its bytes, as a number for an eight-byte cell while finite; where one is not so the executor's way goes on from that round and sum (nothing faulted or widened before it); join 4.9% fewer cycles; SUMS_SOURCE in check.py (sixteen rows met again and again, a one-byte column widened mid-batch, a four-byte one past 2^32, a masked fraction), 7,912 rounds at once and 3,228 the other way, the same three ways; seeds 1 2 3 5 23 (default and PURE_SELECT=all), the fixed sources (also sanitized) and the unit tests pass
* Batch executor, stage 4 of design 025: a column read at affine rows in ↵eunhhu12 days1-0/+60
| | | | memory's order in four or eight bytes is a view of the column itself (cells_at), no copy, read only while the pass runs; what outlives the pass (a local, a set's rows and value, an append's cells, a sum's rows and e, a filter's key, a read's rows) copies it (own); knapsack 4.0% fewer instructions, 6.6% fewer cycles; VIEW_SOURCE in check.py (a local holding a field the batch sets, a set from the same column in the order that overwrites what it reads, a set widening the column under a view), which differs and trips ASan's memcpy overlap when the copies are left out; seeds 1 2 3 5 23 (default and PURE_SELECT=all), the fixed sources (also sanitized) and the unit tests pass
* check.py: REDUCE_SOURCE, reductions that start with no lanes (a count, ↵eunhhu12 days1-0/+68
| | | | uniform max and min, a fraction, a sum past 2^53, a count in a nested loop, the same e then a masked or a lane's own and the other way), against the rounds one by one and the compiled code; it makes lanes from the one number 56 times and folds the one number 77 times
* Batch: filters, if !L.has(k) { a line appending } in a batch's block or in a ↵eunhhu2026-10-061-2/+118
| | | | walk's spread items (PLAN_FILTER; L only grown there, one filter a list, none in a nested LOOP): each round asked as the batch opens, its code run in the rounds it said no to, those rounds kept (k and the appends' cells) and asked again at the end in the rounds' order (L as it is and what the kept rounds before add), the rest added at once (pure_list_add_many) once the lists take them and the memory budget all of them (else, or a key added there already, the batch gives its rounds back); a list such a filter grows read in the block only as no earlier round's item would change (its count in the guard, its first, a place when it has no key, a field by key). Keys kept as bits (pure_list_bits) asked by a loop of lanes, fields of a DENSE list found at their keys (pure_list_dense); an outer lane's affine value in four bytes. The suite's BFS batched: PURE_JIT=0 93 to 39.6 ms (the compiled code 7.7, C 10). module_tier's walks filter d (seeds 1, 2, 3, 5, 23 alike: 5,806 filters decided in seed 23's); GRAPH_SOURCE: a graph built by a filter, searched twice, marks whose filter adds another key (EXISTS), under work and memory budgets
* Batch: a count's loop whose block begins if slot >= L.count { break } (L ↵eunhhu2026-10-061-1/+96
| | | | only grown in it) batches to L's count as each batch opens (PLAN_GUARD; fewer than LEAST rounds left there: the executor's, then batches again); a walk in a batch's block (its slice per lane, no where's range) runs each lane's items, its work counted as each opens: together round by round (BREAK as a nested loop's), or spread into lanes in the rounds' order a chunk of up to 1024 at a time when its items' rounds may run apart (PLAN_SPREAD: no break, the locals it sets reductions or its items' own: an outer lane's value read by each item's outer lane, a reduction added into its outer lane's in the items' order, an own local left as the last item that set it); rows in four bytes read and checked by loops of their own. A frontier walk of the suite's graph (each node's edges' to summed) PURE_JIT=0 55.2 to 10.4 ms (the compiled code 6.24). module_tier makes guarded loops with a walk in each (seeds 1, 2, 3, 5, 23 alike: 8,819 spread and 7,651 together in seed 23's); FRONT_SOURCE
* Batch: a batch's appends made at once (pure_list_add_many, a call a chunk of ↵eunhhu2026-10-061-0/+60
| | | | 128) when every list takes its items so (an event's list, no index into it, one append to it: FLAT not keyed at its end, DENSE by keys it holds at its count, HASHED by number; never past its arrays' limit) and the memory budget takes them all (each item only grows what it counts): columns grown and widened once, the first key there already the executor's EXISTS at its round; else round by round as before, tried at once again after a round that changed a list's form. 50,000 DENSE adds 5.18 to 0.65 ms (the compiled code 0.28). APPEND_SOURCE: keys there already at rounds q and p, keys past a DENSE list's from round p, two appends to one list, widened columns, under memory budgets (seeds 1, 2, 3, 5, 23 alike: 31,402 batches at once in seed 23's, 292 by the budget round by round)
* Batch: a walk (EACH) whose block has batch forms runs a batch of its items ↵eunhhu2026-10-061-3/+25
| | | | at a time: each lane an item's place (ROW of the walked list affine), its work counted as it opened, its place moved past the batch; no append to the walked list. module_tier makes walks of rounds apart (f, g, h, slices of g; seeds 1, 2, 3, 5, 23 alike: 1,891 walk batches in seed 1); BATCH_SOURCE walks a slice
* Batch: a sum into a cell, L[k].f = L[k].f + e (k computed twice the same and ↵eunhhu2026-10-061-3/+12
| | | | e, neither reading L; the set at k's row as found), keeps its rows and e from the pass (the second k, its find and the cell's read skipped) and adds them at the batch's end round by round as the executor adds and sets (a sum not finite: the executor's fault at its round; a column too narrow widened); a batch reading or setting those cells otherwise gives its rounds back. Recurrence steps one dispatch each. PURE_JIT=0 join 428 to 165 ms (the compiled code 78). module_tier makes sums into cells (seeds 1, 23 alike: 278 batches with them in seed 1); BATCH_SOURCE sums into three cells in turn
* Batch executor stage 5 (024): a recurrence (a local a round reads before it ↵eunhhu2026-10-061-5/+81
| | | | sets it, every set of it x = e of x, numbers, fields, states, the loop's value and locals the block does not set, in no branch's way; its sets planned PLAN_RECUR) runs ahead of the batch round by round as steps decoded once a batch, as the executor computes them (a round's fault gives the batch back), leaving its value at each other read in each lane; the batch's pass skips its sets. matmul's, knapsack's and join's table loops batch: matmul 50 to 46 ms. module_tier makes loops with a recurrence (x1) ahead of appends, sets and sums (seeds 1, 2, 3, 5, 23 alike); RECUR_SOURCE: two updates a round, a division by 0 and a product past the numbers at some round, under work and memory budgets
* Batch executor stage 4, first part (024): an item of numbers and bools ↵eunhhu2026-10-061-4/+11
| | | | appended in a batch's block (to a list the block reads nowhere else, no index into it, no nested loop) is made at the batch's end round by round through the executor's own step: keys there already, the memory budget, its undo log as the executor's; a fault there is the executor's at that round (the event undone: its kind, its instruction and the work so far tell), pure_batch_run's third way out. module_tier's handlers append (e, d) in rounds apart and run under a memory budget too (seeds 1, 2, 3, 5, 23 alike); knapsack's table loop batched (13.8 to 13.7 ms: the appends cost as the executor's)
* Batch executor stage 2 (024): a loop in the batch's block runs its rounds ↵eunhhu2026-10-051-5/+111
| | | | for every lane together (round t for the lanes with more than t rounds that did not break out; BREAK leaves it lane by lane; its slot as each round sets it), and x = x op e (op +, min, max; x read no other way) is a reduction: each lane's own from op's identity (-0 a sum's), folded in the rounds' order at the batch's end (a sum's lanes of several whole numbers only, below 2^53); nested rounds' work counted for each lane and checked before anything changes; lanes and masks no holder keeps freed between nested rounds. Planned at load: a local read before set in its loop's scope, or not a reduction's, keeps the loop round by round. PURE_JIT=0: roots 130.5 to 16.8 ms, matmul 914.7 to 68.2. module_tier makes nested loops with breaks and reductions (seeds 1, 2, 3, 23: 549 programs alike); NESTED_SOURCE
* Batch executor stage 1 (024): a count's loop whose block has batch forms ↵eunhhu2026-10-051-7/+86
| | | | (lane forms, locals set before read in every round, ifs, numbers set in items) runs, in the executor, up to 1024 rounds at a time: values uniform, affine or lanes (numbers, or whole numbers in four bytes), branches as masks, cells read by affine rows as strided columns, sets kept to the batch's end and made only when no round reads an earlier round's; a fault, a form it lacks or such a read gives the rounds back to the executor one by one, work counted as written. Planned at load (PURE_BATCH=0: none); the executor of a module without such loops is the same code (run aligned to 64: as many instructions and cycles), with one its own copy. knapsack with PURE_JIT=0: 1,056 to 13.7 ms. module_tier compares batches with the executor alone and makes loops of rounds apart; BATCH_SOURCE: widened columns, -0, a fault mid-batch, rounds reading the last's sets
* Pointer induction (023): an inner count's access by a key affine in its ↵eunhhu2026-10-051-5/+20
| | | | | | round, in range for every round (its opening found it) and in a full list, reads its cell through a pointer, a phi at the loop's head: the column (read from the list) plus the key at round 0 times four where the loop opens, its key's step a round times four added at its latch. The key's nodes, in range, give up nowhere (no snapshot kept), so a key read by such accesses alone is not made; a list found by key alone with such an access is full where the tier begins (else the generic code runs the nest). matmul 23.0 to 14.2 ms (3.1x to 1.9x C) module_tier's programs sweep lists by keys stepping with a round (in range or not, a list full or not)
* The tier's numbers' arithmetic in a run, each read by the next alone ↵eunhhu2026-10-051-2/+23
| | | | | | (constants between), checks only the last is finite, giving up at the run's first snapshot (the executor makes the run again and fails where it does); a snapshot's constants written as the constants they are, not kept in registers; the generic code's append in a loop keeping its books puts into a HASHED list as append does (its headroom kept). roots 12.2 to 10.9 ms (1.59x C), join 84 to 74 (2.5x) module_assumptions: a run of products and sums past the largest number (CHAIN_SOURCE)
* HASHED (023): an event's list keyed by numbers that came out of order keeps ↵eunhhu2026-10-051-5/+13
| | | | | | each item's cells at its key's slot in its members' table, found by key there (the key and its cells read at one slot, as C's table); its order made by a radix sort of the slots' keys when something asks for a position (twice an event, then FLAT or a tree). The tier finds by key alone in a HASHED list (its bits' bit 1, its limit all ones: the bounds checked at a loop's opening still hold) out of the line; a list keyed by each count in order is DENSE from its first item. join 531 to 108 ms (3.5x C), sort 98 to 58 (2.1x) arrays_equivalence counts the HASHED form against a tree; module_tier's programs read a HASHED list by key
* The tier's inner loops: a count's work paid for every round as it opens (the ↵eunhhu2026-10-051-3/+88
| | | | | | rounds it does not run given back where it breaks or gives up), a comparison only a branch reads its flags, a full list's keys read without its bits (a key's position found: its key's register), an add's or sub's register its operand's where that is read last. knapsack 16.5 to 8.5 ms (1.37x C), matmul 25.7 to 22.9 Two bugs the work checks found, there since 6cd7f5a: the outermost loop's round past the work budget gave up at the loop's instruction, with nothing on the stack (the executor opened it again: LIMIT where WORK), now at its iteration; and a phi where an if's ways meet took its merge's loop (-1), so a value made of it was taken as made outside its loop and made once for all rounds. pure-run's budget and work commands; module_tier compares the work and runs four small budgets; module_assumptions keeps both programs
* The tier, first (023): a nest of loops as SSA, its stack, locals and states ↵eunhhu2026-10-051-0/+124
| | | | values in registers (x20-x28 too, the frame's read again where it ends), whole numbers integers by their facts, the message's fields and lists' columns read once, invariants made before inner loops; where anything does not hold it gives up to the executor at the instruction (pure_run_rest) and the generic code goes on. roots 22.9 to 14.0 ms, knapsack 1,030 to 38.8, matmul 908 to 48.6; module_tier checks random handlers against the executor
* Keep the work an event may still do in a register, x30 among what is kept, ↵eunhhu2026-10-051-10/+43
| | | | small constants and powers of two's products in the instruction; where a check may give up, make the result in x16, the operands as they were for the executor (aup's check caught a register shared with its operand): BFS core 12.5 to 12.2 ms, 46.2M to 43.0M instructions (022)
* Compile each nest of loops twice, the second with whole numbers as integers ↵eunhhu2026-10-051-0/+62
| | | | (narrow columns, counts, their arithmetic within 2^53), giving up to the executor for the rest of a round when that fails: BFS core 14.9 to 13.4 ms (022)
* Check a pushed view that computes gets the newest value of writes made ↵eunhhu2026-10-041-9/+41
| | | | meanwhile, and queue=N's busy and its places coming back (020)
* Compute views with a body on view workers, off the connection threads, asked ↵eunhhu2026-10-041-0/+110
| | | | | | | | | | | | | | | | | | | | and pushed (020) - pure-serve --view-workers N (a CPU each; 0: on the connection threads): a GET of a view with a body (or a table with such a column) goes to the workers, which show it from the app's newest snapshot on readers of their own and give the answer back as an owner does (eventfd). The connection thread reads and writes its other connections meanwhile. - A snapshot of another module than the request was resolved with, or none (the app stopped), answers 503 stopped. Past an app's queue=N of them, 503 busy. - A push group of such a view hands one evaluation at a time; a newer snapshot meanwhile is evaluated after it; a group whose subscribers all left stays until its evaluation is back. - check.py server_view_workers: 8 askers of a long view while its app changes module (another view in its place) and then goes: every answer the old module's, the new one's, 503 or 404; streams closed at once leave nothing; --view-workers 0 as before.
* Wake each thread at most once a push, end streams only of stopped apps, and ↵eunhhu2026-10-041-0/+8
| | | | show a subscriber costs writes nothing (019)
* Push a view's value to its subscribers over SSE, once per group and interval ↵eunhhu2026-10-041-0/+251
| | | | (019)
* Let a view compute in its body, on a reader's own lists (018 stage 3)eunhhu2026-10-041-0/+69
|
* Count in loops that nest, break, and keep an event's own lists, every event ↵eunhhu2026-10-041-0/+64
| | | | bounded (018 stage 2)
* Keep transient states the app's while it runs, never stored (018 stage 1)eunhhu2026-10-041-0/+91
|
* Take an app's requests only with a token its tokens file grants (017, 3b)eunhhu2026-10-041-0/+196
| | | | | | | | | | | | | | | An app given a tokens file (tokens=FILE in --apps, --tokens FILE for the one app of a host) answers a request only when it bears a token (Authorization: Bearer) whose SHA-256 the file lists with a grant: read for its views, queries and documents, write for all, input=NAME for those inputs alone; 401 unauthorized (WWW-Authenticate: Bearer) without one, 403 forbidden past its grant. A delivery sealed by a key --sender names needs none. The digests are compared in constant time; a connection keeps its last token's digest, so the same token is hashed once. SHA-256 is the host's own (serve/sha256.c), no OpenSSL loaded. A reading puts a changed tokens file in place with the app running on; a file that is not one stops the app. pure-serve --make-token GRANT makes a token and its line. load takes PURE_LOAD_TOKEN.
* Change a running app in place, its states kept in memory (017, stage 3)eunhhu2026-10-041-13/+27
| | | | | | | | | | | | | | | | | | Stopping an app and starting it again reads its whole store again: a million items took 142 ms, as long as restarting the host (030). A running app whose line changed but not its store now goes quiet (what it took applied, saved and answered, its batches back, its outlets closed, its checkpoint finished), has its module replaced in memory (pure_app_replace carries its states, and the sends in its outbox, by name), its budgets set and its outlets made anew, and resumes; a module its states do not go into stops it. An app whose store changed, one a save of which is uncertain, and one that does not run still start from their stores. A reading says changed for the first. A snapshot is published again when the module changed though the states did not. server_apps_lifecycle reads the changing apps' views (lists among them) as they change, and moves an app's store; it passed 10 times alone, 5 under load and 6 with an ASan pure-serve.
* Start, stop and change apps on a running host on SIGHUP (017, stage 3)eunhhu2026-10-041-0/+253
| | | | | | | | | | | | | | | | | | | | | | | | A host of apps reads its --apps file again on SIGHUP, on a thread of its own (pure-apps, the one that takes the signal): a file with a wrong line changes nothing; the apps of lines gone stop and their routes go (404 no_app); those of changed lines, and those not running (they could not start, or a save of theirs is uncertain), start again from their stores, states carried by name; new ones take places of their own; a line out says what was done. An app stops by taking nothing more (503 stopped), applying, saving and answering what it took, waiting for its batches at other apps, then closing its outlets (what they did not deliver stays in the outbox) and its store. Its link streams find their inputs again as it starts. Owners' locks are made with their places; counts are kept over an app's runs; workers grow with the apps that run, a CPU each at most. server_apps_lifecycle adds, removes and changes apps, and changes the receivers (a local outlet's and a link's, their inputs at other numbers, taken by name) and the senders while 600 sends each and writes go: every send arrives once and in order, a write answered 200 is applied once and one answered 503 stopped not at all, the app left alone fails no request; an app whose store cannot be made starts at the next reading once it can; kill -9 and the host starts as the file says. It passed 10 times alone, 5 under load, 6 with an ASan pure-serve.
* Take a timer window again when the wall clock jumped in iteunhhu2026-10-011-9/+17
| | | | | | | | | server_timers compares the times an app saw with the wall clock read around them. WSL2's clock is set back about 2 s every 32 s (research 029): a window holding such a step says nothing of the times seen, and failed the check there 4 times in 20. It is taken again (up to three times) when the wall clock and the monotonic one moved more than 50 ms apart over it; the report counts the windows taken again.
* Hand sends to another app of the host with no socket (017)eunhhu2026-10-011-17/+67
| | | | | | | | | | | | | | | | | | | | | | send=OUTPUT=APP/INPUT in --apps wires an output to another app of the same host through a local outlet: no socket, seal or packet. The sender's turn queues its saved sends there, under the window links and wires have, and hands them, one batch at a time, to the receiving app's queue laid out as a link delivers records (first number, count, source APP.OUTPUT, the records as stored, and the sender's fields when the input takes them by name, which pure_module_takes checks at start). The receiver applies them after its last number heard; its save over, it tells the outlet what it heard and the sender's next turn trims its outbox so far. What it did not store is queued again from the outbox, after 100 ms when it had no room. An output now goes one way only: two outlets would trim each other's sends. server_apps_links: an app sends each of 400 messages to an app of another host over a link and to three apps of its own host (one of the same shape, one taking the fields by name, one with a memory budget that fills), with a kill -9 of the sending host between two requests; all arrive once and in order, and the full app's sends wait in the sender's outbox until it lets go of what it holds.
* Wire each app's sends, and take every app's links on one port (017)eunhhu2026-10-011-0/+75
| | | | | | | | | | | | | | | An app of --apps takes send=OUTPUT=URL, seal=OUTPUT=KEY and reply=OUTPUT=INPUT, as --send, --seal and --reply give the one app of a host; its sends' source is APP.OUTPUT. An app wired wrong stops alone, its wires closed. The host's --key, --sender, --senders, --sealed-only and --link-port now go with --apps: the one listener resolves a stream's input name APP/INPUT to that app's input (a stopped app or an unknown name is no input, REFUSED 1), its deliveries queued at that app's owner. 012 is unchanged. server_apps_links: an app of one host sends 300 messages, sealed, over a link to an app of another host, which takes them all once and in order.
* Move a connection to its app's home thread at its first request (017)eunhhu2026-10-011-7/+13
| | | | | | | | | | | | | | | | | | | | | | With several connection threads, a host of apps had each app's connections spread over all of them: every thread's round handed each owner a share, and every turn handed answers back to every thread. The apps took a third more CPU a request than as processes, with five times the voluntary switches, and when two apps were busy each waited a kernel tick at its 99th percentile (4.1 ms, against 1.5 ms as two processes). Each app now has a home thread (its place in --apps, modulo the threads). A connection whose first request names an app at home elsewhere moves there once, its head read and no ticket taken yet: the home thread adopts its descriptor and the bytes read, and reads the request again. Each app's requests then meet its owner from one thread, as from a process of its own. Two busy apps: 99th percentile 1.4 ms, the same 145k writes a second. server_apps restarts its apps on three connection threads, asks each app from fresh connections, and checks that some moved and all were answered right.
* Run many apps on one host, by turns on workers (017)eunhhu2026-10-011-0/+117
| | | | | | | | | | | | | | | | | | | | | | | | | | | | pure-serve --apps FILE hosts an app a line: NAME MODULE STORE and its budgets (memory=, work=, queue=). Each app's routes start with its name and reach its own module, states, snapshot and queries; a view is shown on the connection thread from its own app's snapshot, read once a round. No app has a thread of its own. Its owner is what used to be a thread's loop, now a turn: its due timers, what its wires delivered, and the requests waiting for it applied and saved once, its answers handed back. Workers (--workers N; one for a host of one app, else one a CPU at most) take apps from a queue in turn; a worker sleeps until the soonest timer. A turn takes every waiting request when no other app waits, else 256; it gathers the clients its last save answered only when alone. An app's queue budget refuses what would wait past it (503 busy) on the connection thread, before its owner sees it. An app whose module does not load, whose store does not open, or whose store is another app's stops alone: the start line says why, and its routes answer 503 stopped. The stop line counts each app's turns, events and refusals. A host of one app (--module) runs as before, its owner on one worker named pure-owner: every check passes unchanged. server_apps runs six apps of which two stop, floods one past its queue while another answers, refuses a walk past one app's work budget that another runs, holds an app's store from another process (that app answers 503 store_busy and applies nothing, the others save), and restarts.