Every major feature, with a plain verdict on its state and a
pointer to the test that proves the claim. The point of this document is that
it does not flatter the project. A feature is marked Works
only if a test compiles and runs it; Simplified
means the shape is real but a production system would want more;
Placeholder means the interface exists but the
hard part is explicitly not implemented, and is labeled as such in the code.
The five layers that guard dynamic plugin loading. Built as module-level
analyses over capability sets, fingerprints, and signatures — the mechanism
the plan specifies, without new surface syntax.
| Feature | Status | Proof |
| Fibers: suspend/resume, locals survive | Works |
test_codegen: “fiber: two suspend points” |
| Fork/await, concurrent forks, self-recursion via fork | Works |
test_codegen: four “fork:” tests |
[T+A].all() / .any() join/race | Works |
test_codegen: “[T+A].all()”, “[T+A].any()” |
MVCC patch <<, 8-thread contention | Works |
test_codegen: “MVCC: read-modify-write survives 8-thread contention” |
| -M propagation: params are read-only views | Works |
test_linter: 9 cases. A -M binding (the default for parameters and locals) makes everything reachable through it read-only — field writes, index writes, mutating methods (.push() etc), and << patches are all compile errors through a -M reference, even when the field or element is declared +M in its class. Adding +M to the parameter allows mutation. t (self) is exempt — the class’s own methods are the owner. This is transitive const, applied uniformly. |
| Database.Query codegen: builder chain → u_dbq_* C runtime | Works |
test_codegen: 1 case. U source that chains .select().where().orderBy().limit().getSQL() transpiles to C containing u_dbq_new, u_dbq_select, u_dbq_where, u_dbq_order_by, u_dbq_limit, u_dbq_get_sql. Runtime (u_db.h) builds dialect-aware SQL ($N for Postgres, ? for MySQL), binds parameters separately, and executes through the existing wire-protocol clients. The query builder chain returns UDbQuery* for fluent chaining; terminal operations (.fetchAll, .fetchRow, .execute) call u_db_exec underneath. |
| HTTP client codegen: verbs + builder chain → u_httpc_* C runtime | Works |
test_codegen: 1 case (4 sub-assertions). HTTP.get(url) → u_httpc_get, HTTP.post(url,body) → u_httpc_post, HTTP.request(url).method().header().bearer().send() → u_httpc_request/u_httpc_req_method/u_httpc_req_header/u_httpc_req_bearer/u_httpc_req_send, Response.status → u_httpc_resp_status. Runtime (u_http.h) adds URL parsing, high-level client API, request builder, response accessors, and URL encode/decode — all on top of the existing async HTTP/1.1 state machine. |
| Deadlines: resolve-before / timeout / deadlock-panic | Works |
test_codegen: “deadline runtime” |
| Pull streams (demand down, values up) | Works |
test_codegen: “pull streams: right-associative chain” |
| Time — monotonic + realtime clocks, calendar, ISO-8601, durations | Works |
Two clocks kept separate BY CONSTRUCTION (u_time.h): MONOTONIC for durations and deadlines (never jumps — immune to NTP, DST, and the user setting the clock) and REALTIME for human-facing timestamps and calendar work. Conflating them is the classic timer bug, so they are different functions. Calendar components (UTC and local), ISO-8601 format and parse with a verified round-trip, strftime-style formatting, and duration humanising (1500ms → 1.50s, 125000ms → 2m 5s). Portable: clock_gettime is POSIX and provided by wasi-libc, so identical source works natively and on WASM/WASI. All verified by a running test. |
| Environment / OS — env vars, args, exit, cwd, pid | Works |
The clearest one-interface / three-backend case (u_env.h). get/has/or/set/get_int for environment, process arguments, exit, cwd, pid, hostname. Natively everything works; on WASI the host supplies env and argv through wasi-libc; in a BROWSER there is no process environment at all, so getenv returns empty and cwd/pid degrade rather than fail — portable code keeps running instead of breaking. Verified running: set/get, typed get_int with fallback, args, cwd, pid. |
| Filesystem — read/write/append, stat, dirs, paths | Works |
POSIX-shaped because that is what every backend can express (u_fs.h): whole-file read_all/write_all/append (the 90% case, with a size cap so a surprise 10GB file cannot take the process down and a NUL terminator so text use is safe), stat (size/mtime/is_dir/is_file), mkdir/mkdirs (mkdir -p)/remove/rename, directory listing, and path helpers (basename, extension, join). Backends: native libc; WASI via wasi-libc, where the host must PRE-OPEN the directories the module may touch (capability-based — no ambient authority over the disk); browser via Emscripten MEMFS/NODEFS. Verified running end to end. |
| HTTP client — asynchronous, over U's own sockets | Works |
Deliberately not a libcurl binding (u_http.h). Writing HTTP/1.1 directly on u_net.h is what PROVES the socket layer is a sufficient transport — if a real protocol client could not be written against it, the transport was not done. It can. A request is a state machine (connect → send → read) driven by the fiber scheduler exactly like the GPU readback: each step advances as far as the socket allows, then returns SUSPENDED so the epoll reactor sleeps until more bytes arrive. No thread, no spin. Verified against a real loopback server: GET 200 with correct body, POST with a body echoed back, 404 handled as an ordinary response — and critically the run recorded 17 suspensions with a concurrent fiber interleaving 3 steps, so it genuinely took the async path rather than a lucky fast path. Parses the status line and Content-Length, reads to close when length is absent. Scope: no TLS yet (so http, not https), no chunked transfer-encoding, no redirect following, no connection reuse (Connection: close), no cookie jar. Browser target uses the platform fetch() behind the same interface, since raw sockets do not exist there. |
| TLS — https, mutual TLS, session tickets | Works |
Verified against real servers (u_tls.h): TLS 1.3 handshake (TLS_AES_256_GCM_SHA384), encrypted GET returning 200. TLS WRAPS a transport, so HTTP/MySQL/Postgres inherit encryption. Handshake is non-blocking (WANT_READ/WANT_WRITE) so a fiber suspends to the reactor. Verification and hostname checking are ON by default — OpenSSL does not check hostnames for you — and the test asserts the property that matters: a self-signed certificate is REJECTED when verifying. Mutual TLS works against a CERT_REQUIRED server behind a real CA: a client certificate is accepted, its absence is rejected, and a mismatched cert/key pair is caught at load time rather than failing silently at handshake. A bug this testing caught: client certs were being set on the SSL_CTX, but SSL_new has already copied the context by then — so the certificate was never sent, and under TLS 1.3 (where the client cert goes out after the client thinks the handshake finished) this looked like a successful handshake followed by zero bytes. Now set on the SSL itself. Session tickets: the NewSessionTicket callback fires and the saved session is accepted by a new connection; an actually-resumed handshake is NOT demonstrated — against the test server OpenSSL reports the session non-resumable and SSL_session_reused() stays 0, so the API is in place but resumption itself is unverified. Client side only. On loopback the handshake never returned WANT_*, so the suspend path is implemented but unexercised. |
| DNS — hostname resolution | Works |
Sockets took IP literals only; u_dns.h is what makes connecting to a hostname possible. Uses getaddrinfo, so IPv4, IPv6, /etc/hosts and the system resolver config all work without reimplementing any of it. Returns EVERY address rather than just the first, which is what lets a caller do happy-eyeballs or fail over when one address is unreachable. Verified running. Browsers resolve implicitly inside fetch(), so there is no separate DNS step on that target. |
| Encoding — base64, hex, percent, UTF-8 | Works |
Small, dependency-free, and used constantly (HTTP headers, URLs, JSON, crypto output). base64 with the URL-safe variant, hex, percent-encoding, and UTF-8 encode/decode/validate. Everything writes into a caller-supplied buffer and returns a length, so no allocation and no truncation surprises. Malformed input is rejected rather than guessed at: base64 refuses junk characters, hex refuses odd lengths, and UTF-8 validation rejects overlong forms and surrogates — accepting those is how mojibake and security bugs get in. Verified against known vectors (TWFu, YQ==, deadbeef). |
| Random — CSPRNG and seedable PRNG, UUIDv4 | Works |
Two generators, deliberately separate and named so they cannot be confused: u_rand_secure_* (OS CSPRNG — keys, tokens, session ids, salts) and u_rng_* (xoshiro256**, fast, seedable, REPRODUCIBLE — simulations, shuffling, tests). Reaching for the wrong one is the classic security bug, so the secure one has the obvious name. Both bounded generators use rejection sampling to avoid modulo bias. A CSPRNG failure is never silently downgraded to weak randomness. UUIDv4 always draws from the CSPRNG. Verified: secure bytes differ across calls, the seedable PRNG is bit-identical from the same seed, doubles land in [0,1), and UUIDs carry the right version/variant nibbles. |
| Regex — POSIX extended regular expressions | Works |
Wraps <regex.h>, which is in libc AND wasi-libc, so this needs no third-party dependency and works on both targets. Compile (with ignore-case and multiline), search with capture groups, test, count, and replace-all. Zero-width matches advance by a byte so replace cannot spin. Named honestly: this is POSIX ERE, not PCRE — no lookaround, no lazy quantifiers, no \d shorthand — though it also has none of the catastrophic-backtracking surprises those features bring. Verified: capture groups extracted, counts correct, replace-all correct, and invalid patterns rejected with a readable message. |
| Compression — gzip, zlib, raw deflate | Works |
Via zlib (u_compress.h). HTTP effectively assumes this: every client sends Accept-Encoding: gzip, so a client without it either lies in its headers or wastes bandwidth. All three container formats are exposed because they are NOT interchangeable — gzip for HTTP and files, zlib for PNG and protocols, raw deflate when the container comes from elsewhere. Plus crc32. Verified by round-trip and against the standard crc32 test vector (0xCBF43926). |
| HTTP cookie jar | Works |
Parses Set-Cookie (Domain, Path, Expires, Max-Age, Secure, HttpOnly), stores, expires, and serialises the correct subset back into a Cookie header. The matching rules are the whole point, so they are tested as security properties, not features: a Secure cookie is never sent over plain http; a host-only cookie (no Domain attribute) does NOT leak to subdomains; and the classic suffix attack is blocked — a cookie scoped to example.com is NOT sent to evilexample.com, because the domain match requires an explicit dot boundary. Max-Age=0 deletes. Verified running. |
| Structured logging | Works |
Log lines are DATA, not prose (u_log.h): typed key/value fields that can be filtered and aggregated, rather than values glued into a sentence. Level filtering, a service name on every line, and two sinks from the same call — human-readable text for a terminal, JSON for anything that ingests logs. JSON output is correctly TYPED (strings quoted, numbers and booleans not) and properly escaped (quotes, newlines, control characters). Logs go to stderr, leaving stdout for program output. Verified running. |
| Subprocess spawn | Works |
fork/exec with an explicit argv (u_proc.h), capturing stdout, stderr and the exit code, plus a PATH lookup. There is deliberately no "run this command line" entry point — system() hands the string to a shell, so any interpolated value becomes an injection vector. Verified running: output captured, a non-zero exit reported rather than swallowed, a missing program failing cleanly as 127. NATIVE ONLY: neither WASI nor the browser has a process model, and this is one of the few places a target genuinely cannot participate. |
| Test framework | Works |
Deliberately small (u_test.h): a runner, a summary with a CI-readable exit code, and assertions that report WHAT differed — got 3, want 4 rather than "assertion failed". A framework that only says "failed" makes you add printfs, which is the thing a test framework exists to prevent. Typed assertions for ints, strings and floats-with-tolerance, plus skip. Dogfooded: it is what runs the logging and subprocess tests. |
| Scheduler — clock, timers, intervals, cron; unified timer+IO event loop | Works |
U's answer to cron + a stream_select loop, verified by execution (u2c/runtime/u_sched.h). Clocks: u_now_mono_ms (MONOTONIC — for deadlines, immune to NTP/DST jumps) and u_now_wall_ms/u_now_unix_sec (REALTIME — for timestamps and cron matching); mixing these up is the classic timer bug, so they are separate by construction. Timers: u_sched_after (one-shot), u_sched_every (interval), u_sched_cron (calendar), plus cancel and fire-counts. Cron supports the 5 standard fields with *, numbers, lists (0,30), ranges (9-17), and steps (*/15), Sunday as 0 or 7 — 13 matcher cases verified correct. THE KEY INTEGRATION: timers and I/O share ONE wait. u_sched_next_timeout_ms() computes the next deadline and it is passed as the reactor's epoll timeout, so the process sleeps until EITHER a socket is ready OR a timer is due — never spinning, never overshooting a deadline. Proven by a running test: with a listener idle, the loop woke 6 times for timers AND still saw the socket become readable through the same u_net_select. Scope: single-threaded cooperative (matches U's fiber model); no persistence of schedules across restarts; cron is minute-granularity (no seconds field). |
Multiplexing — u_net_select (stream_select equivalent), fd-value-agnostic | Works |
Reports which of N streams are ready, the way PHP's stream_select does — but deliberately NOT select(2)-shaped, for a portability reason learned from the PHP-on-WASM port: select() uses fd_set BITSETS indexed by fd value and cannot see any fd ≥ FD_SETSIZE (1024), while WASI RANDOMIZES fds across [3, 231) — so a bitset-based select silently drops most sockets, and for (fd = 0; fd <= max_fd; fd++) loops become billions of iterations. U's API therefore takes an explicit array of fds and returns an explicit array of ready-masks: no bitsets, no 0..max_fd loops, correct whether fds are 3,4,5 (POSIX) or 918273645 (WASI). Backend is epoll natively; the same surface maps onto poll()/wasi:io/poll for a WASI build. Verified running: detects listener readability and composes with the scheduler's timeout for the unified event loop. |
| Sockets — TCP / UDP with epoll reactor, integrated with +A | Simplified |
Real POSIX sockets, verified by execution. u2c/runtime/u_net.h: TCP listen/connect/accept/read/write and UDP bind/sendto/recv, all non-blocking, all returning PENDING FRAMES (T +A values with the UGenericFrame prefix) driven by U's fiber scheduler. The architecturally new piece is the REACTOR: a bare ready-queue cannot drive a socket read — the frame would spin, since nothing says when the fd becomes readable — so an epoll set now provides readiness wakeups and the process SLEEPS instead of busy-polling. Verified two ways: (1) a full TCP echo (HELLO-U client→server→echo→client) plus a UDP datagram, driven end to end by the scheduler; (2) a forced suspend/wake test proving the reactor genuinely works — an accept with no client SUSPENDS 5/5 steps, epoll reports 0 ready before connect and 1 after, and the frame completes on wake (not a lucky fast path). Errors map to a UNetStatus enum shaped for U's catchable ! NetError returns. Scope: NATIVE target (POSIX/Linux epoll) — browsers forbid raw TCP, so the WASM backend for these interfaces is WebSockets/fetch and a raw-socket program is a native program (same one-interface/two-backends pattern as GPU). Not yet done: TLS, DNS resolution (IP literals only), async connect/write frames (write is retry-loop), kqueue/IOCP for BSD/Windows, and the wire-protocol clients (MySQL/Postgres/HTTP) that would sit ON this layer. |
| MySQL / MariaDB — wire-protocol client | Works |
Companion to the Postgres client: same shape, different wire (u_mysql.h). v10 handshake, mysql_native_password authentication built on the runtime's own SHA-1 (SHA1(pw) XOR SHA1(salt + SHA1(SHA1(pw)))), the text query protocol, length-encoded integers, column definitions, and row parsing — no libmysqlclient. Verified against a live MariaDB 10.11: auth, SELECT with named and ordered columns, INSERT reporting affected rows, SQL NULL distinguished from the empty string, and a server error reported with the connection still usable. MySQL frames every message as a 3-byte length plus a sequence number, and getting that sequence wrong is the classic first bug, so it is threaded explicitly and reset per command rather than assumed. The test seeds its own fixture and starts its own server. Scope: text protocol only — no prepared statements or binary parameters, no caching_sha2_password (MySQL 8's default; the server's auth-switch request is detected and reported rather than mishandled), no TLS-wrapped connection, no compression. |
| Prepared statements — real parameter binding (PostgreSQL) | Works |
The extended query protocol (Parse / Bind / Describe / Execute / Sync) sends the STATEMENT and the VALUES as separate wire messages, so a parameter can never be parsed as SQL no matter what it contains. That is the actual fix for injection — escaping is not. Verified against a live server: prepared INSERT and SELECT with bound parameters, a NULL parameter becoming SQL NULL rather than the string "NULL", and the case that matters — the payload greg'; DROP TABLE pp; -- is bound as a VALUE, matches nothing, and the table survives with all its rows. Statements are named and reusable. Scope: text-format parameters (format code 0); binary format is wired in the protocol but not yet exercised per-type, and MySQL prepared statements (COM_STMT_PREPARE, binary result rows) are not implemented. |
| HPACK — HTTP/2 header compression (RFC 7541) | Works |
HTTP/2 does not send header names as text: it sends INDICES into a table both peers maintain identically, so :method: GET is one byte. That shared mutable state is the whole difficulty — encoder and decoder must evict in lockstep, and a single disagreement desynchronises the connection permanently and silently, since HTTP/2 has no resynchronisation point. So this is verified against the RFC's own test vectors: C.1 integer encoding (including 1337 in a 5-bit prefix → 1f9a0a), C.2.1 literal-with-indexing byte-for-byte, C.2.4 indexed fields, and C.3.1 — decoding a real first request 8286 8441 0f77… into :method: GET, :scheme: http, :path: /, :authority: www.example.com with the dynamic table ending at exactly 57 bytes. Implements the 61-entry static table, the dynamic table with FIFO eviction and the RFC's size accounting (name + value + 32), prefix-coded integers, length-prefixed strings, all four field representations and dynamic-table size updates. Malformed input is refused rather than guessed: truncated continuations, overlong integers that would wrap, and out-of-range indices. Scope: Huffman-coded strings are DETECTED and reported (-2) rather than decoded — the H bit is optional to emit, so this interoperates, but a peer that Huffman-codes its headers is not yet readable. |
| HTTP/2 framing (RFC 7540) | Works |
The binary frame layer that lets many requests share one connection: a 9-byte header (24-bit length, type, flags, reserved bit + 31-bit stream id) plus payload. The stream id is what makes multiplexing work — frames for different requests interleave and are reassembled per stream, with stream 0 reserved for the connection itself. Encodes and decodes frame headers, the exact 24-byte connection preface (deliberately invalid HTTP/1.1 so a 1.1 server cannot misread what follows), and SETTINGS as 6-byte id/value pairs. The reserved high bit of the stream id is MASKED rather than trusted — forgetting that is a classic interop bug, and the test sets it to prove it is ignored. Truncated headers are refused. Scope: framing and HPACK only — no stream state machine, no flow control (WINDOW_UPDATE), no server push, and nothing yet drives it over a TLS connection with ALPN h2. |
| FTP client (RFC 959) | Works |
FTP is unusual in using TWO connections: a text CONTROL channel and a separate DATA connection per transfer. Passive mode throughout, because active mode asks the server to dial back to the client and no NAT in twenty years permits that. Multiline replies are consumed whole — a hyphen after the code (220-) means more lines follow, and reading only the first is the classic bug that makes every subsequent reply arrive off by one. Verified against a real server: login through a multiline banner, passive LIST, RETR returning exact file bytes, a STOR round-trip read back byte-identical, MKD/CWD/DELE, and a bad password refused rather than assumed. TYPE I is set on connect because ASCII mode mangles binary. PASV's port is split across two bytes (p1*256+p2), which is the part implementations get backwards. |
| WebSocket — RFC 6455 client | Works |
Not layered on HTTP/2. A WebSocket is an HTTP/1.1 Upgrade handshake, after which the connection stops being HTTP entirely and becomes its own framed protocol over TCP; TLS just wraps it (wss://). RFC 8441 added WS-over-HTTP/2 later as a patchily-supported extension. That is why this was small — it needed a TCP socket, an HTTP/1.1 handshake, SHA-1 and base64 for Sec-WebSocket-Accept, and secure random for masking keys, all of which U already had. Verified against a real server: handshake with the accept hash actually VERIFIED (skipping that check means trusting any 101 from any server), masked client frames (servers drop unmasked ones — the classic first bug), extended payload lengths past 125 bytes, automatic PONG replies, clean close, and rejection of a server that does not upgrade. |
| Unix domain sockets | Works |
Same API shape as TCP, different address family (u_net.h). Worth having for real reasons rather than completeness: local database connections use a unix socket by default, it skips the TCP stack entirely, and the filesystem permissions on the socket path ARE the access control — nothing is exposed to the network. Verified connecting over Postgres's own socket path. A latent bug this surfaced: u_net.h's include guard closed at line 387 while the file ran to 439, so the multiplexing section appended earlier sat OUTSIDE the guard and redefined itself whenever the header was included twice — which is exactly what happened once two modules both included it. Same bug class as the runtime's vectorization section. Guard moved to EOF; double-inclusion now tested. |
| PostgreSQL — wire-protocol client with SCRAM-SHA-256 | Works |
The first wire-protocol client, and the proof the transport layer is real (u_pgsql.h): PostgreSQL v3 protocol written against U's own u_net.h sockets and U's own crypto — no libpq. SCRAM-SHA-256 authentication is built from primitives the runtime already had (SHA-256, HMAC, PBKDF2), which is the only reason a genuinely authenticating client was possible here. Verified against a live PostgreSQL 16: SCRAM handshake, SELECT returning named columns and ordered rows, INSERT with its command tag, SQL NULL distinguished from the empty string (a distinction a careless client loses), and a server error reported with the connection still usable afterwards. The test seeds its own fixture rather than depending on state from a previous run. Scope: the simple query protocol — no prepared statements or binary parameter binding yet, no connection pooling, no TLS-wrapped connections (the pieces exist; they are not wired together here), and no LISTEN/NOTIFY or COPY. |
| SQLite — embedded database bindings | Works |
Complete SQLite bindings, verified by execution (u2c/runtime/u_sqlite.h, real libsqlite3 3.45): open/close, exec, prepare/bind/step/finalize/reset, typed column reads (int/real/text/null + names and count), transactions (begin/commit/rollback), last_insert_id, changes. Tested by a full CRUD cycle that RUNS: create table, three parameterized inserts, a bound-parameter SELECT returning the right 2 rows in the right order, and a transaction rollback restoring the row count. Every value goes through sqlite3_bind_* — never string concatenation, which is the runtime half of the guarantee the linter already enforces statically (structural-position interpolation is a compile error, value-position is parameterized). SQLite is the right embedded DB for U because it is in-process (no socket) and compiles to WASM (sql.js), so it works on BOTH targets — unlike a network DB, which is served by the socket layer instead. Shapes match the runtime's existing Database.Query[T]/Row types. Scope: the C binding surface; no U-level ORM/query-builder sugar yet, and BLOB binding is not exposed. |
| Case sensitivity — with near-miss hints | Works |
U is case-SENSITIVE for classes, functions and methods, and stays that way deliberately: case already carries meaning (types are Point and HTML, values are userName, type parameters must be multi-letter except T), and Type reflection needs one canonical spelling per name. Naming CONVENTIONS are not enforced — d point and f GetName() both compile — so the rule is about identity, not style. The ergonomic cost of case sensitivity is the pure case slip, so that is now named directly: an unknown identifier that differs from a known class, function or alias only by case reports "did you mean 'HTML'? names are case-sensitive". That recovers the forgiveness of a case-insensitive language without giving up the signal, and unlike PHP it applies uniformly rather than splitting the rules between names and variables. |
SQL[Conn] — the connection rides in brackets, and picks the dialect | Works |
Brackets rather than a first argument, for a reason that is not aesthetic: the connection selects the DIALECT, and the dialect decides the placeholder syntax the statement is compiled with — PostgreSQL writes $1, MySQL writes ?. That is a COMPILE-TIME decision, so a runtime first argument arrives too late: by the time you hold a connection value the statement text is already built. Brackets are also already U's compile-time parameter syntax (Box[I]) and already how the spec selects a connection — o Database[Database.Connection.Replica] { get, find }, which parses and lints clean. Verified by emission: no connection or SQL[Database.Connection.Postgres] gives $1 … $2, SQL[Database.Connection.MySQL] gives ? … ?, and the dialect is inherited through a subclass OR a type alias (d AppDb : Database.Connection.MySQL), so it is declared once instead of repeated at every call site. Values remain bound parameters in every dialect — the bracket changes the placeholder spelling, never whether a value is parameterised. Ordinary list indexing is not mistaken for a connection bracket. Scope: the bracket names a connection TYPE (dialect + eventually schema); binding a runtime connection INSTANCE so the statement auto-executes is still the remaining step. |
| Database relations — FK graph resolved at compile time | Works |
A port of Qbix's Db_Relation::compile() (u2c/relation.py), moved to BUILD time because every input is static. Qbix finds the ROOT store (the unique table no foreign key points out of), rejects more than one root, rejects zero (a cycle), and BFS-levels the graph — and the levels ARE the join order: "the order they should appear in JOIN statements, for the statements to make sense". The join order is therefore DERIVED FROM THE KEYS rather than written by hand. Doing it at compile time means Qbix's two runtime exceptions become build errors that name the tables involved, and the join sequence is emitted once instead of recomputed per query. Verified: users ← posts ← comments levels to [[posts],[comments]] and emits the joins in dependency order; two roots is rejected as "would produce a cross product"; a cycle is rejected as "no table to start the joins from"; and the same pair joined by two different keys is reported as ambiguous, which Qbix cannot detect at all. Scope: the graph and its checks are implemented in Python and callable; they are not yet reachable from U source as z f hooks. |
Schema from SQL DDL (--from-sql) | Works |
A model can be read from checked-in CREATE TABLE statements instead of scanning a live database (parse_sql_ddl). Usually the better build input: it is versioned so generated classes move with the code, it needs no server so CI can generate without credentials, it is the SAME artifact a migration applies so the two cannot drift, and its FOREIGN KEY declarations give the relation graph for free — the join order a query needs is already written in the DDL, so nobody restates it as hasOne/hasMany. Extracts columns with lengths and nullability (varchar(64), NOT NULL), maps SQL types to kinds (decimal(10,2)→float), and reads PRIMARY/UNIQUE/KEY including multi-column keys in order, which is what the leftmost-prefix index rule needs. A bug caught in the writing: matching the table body with a non-greedy \(.*?\) stops at the first ) — the one inside varchar(64) — silently truncating the column list; the body is now extracted by balancing parentheses, and the test asserts the LAST column survived. Unrecognised clauses (engine options, collations, CHECK) are skipped rather than failing the file. |
| Derived joins verified on SQLite, PostgreSQL and MariaDB | Works |
The joins are never hand-written — they are derived at compile time from the DDL's FOREIGN KEY declarations, and this test runs the SAME generated SQL against three real engines and requires identical results. From posts→users and comments→posts it derives root users, order [posts, comments], and emits LEFT JOIN posts ON posts.userId = users.id then LEFT JOIN comments ON comments.postId = posts.id. All three engines return identical rows, including the LEFT JOIN NULL for a user whose post has no comments. A useful thing this exposed: the DDL is dialect-specific — SQLite rejects MySQL's inline KEY name (cols) and Postgres wants it separate — but the relation graph derived from it is not, because FOREIGN KEY … REFERENCES is spelled the same everywhere. That is precisely the portability the layer exists for, demonstrated rather than asserted. Skips cleanly when an engine is unavailable. |
| Grouping, locking and portable upsert | Works |
An earlier pass cut these by applying the checkability rule too quickly; each in fact carries a real compile-time check. groupBy validates columns AND the select-list consistency rule — every selected column must be grouped or aggregated. Postgres enforces that always, MySQL only under ONLY_FULL_GROUP_BY, so the same query can pass locally and fail in production; catching it makes the query portable by construction. having is only meaningful over groups, and referencing a plain column there is either an error or a silent scan that where() would have indexed — both refused. lock spells differently per engine (FOR UPDATE vs LOCK IN SHARE MODE) and SQLite has no row-level locking at all, which is reported rather than silently emitted. upsert is the operation that most justifies a structured form: Postgres and SQLite REQUIRE a conflict target that MySQL FORBIDS, so writing it portably by hand is why people give up and special-case per engine. Here the conflict columns are DERIVED from the model's unique index, and one description emits ON DUPLICATE KEY UPDATE, ON CONFLICT (…) DO UPDATE SET … EXCLUDED.v and the SQLite excluded.v form correctly. It also carries a check no string can: an upsert against a table with no unique constraint has an unreachable update branch, and one where every supplied column is part of the key would change nothing — both refused. REPLACE INTO is refused on Postgres with the reason (it DELETES the old row, firing triggers and clearing unsupplied columns). Verified by execution: insert-then-conflict yields one updated row on SQLite, PostgreSQL 16 and MariaDB 10.11. |
| HTTP client surface — fluent, protocol-named | Works |
Node groups these as http and url; U uses classes, so the namespace is the protocol. https is deliberately NOT separate — it is HTTP with a certificate, so it is a URL scheme rather than a different API. Nothing new was needed for chaining: U's +A already IS the promise, so the scheduler drives the wait and the syntax stays direct rather than needing .then(). One-call verbs (HTTP.get/post/put/patch/delete/head) for the common case, and a fluent builder for the rest: HTTP.request(url).header(…).bearer(tok).json(data).timeout(5000).cookies(jar).followRedirects(3).send().json(). Auth helpers (bearer, basic) exist so nobody hand-builds an Authorization header. Responses decode with ok(), text(), json() (to a Tree), bytes() and header() — the decode is where the await naturally lands. Client-side cookies are a CookieJar passed to a request, backed by the jar whose domain-suffix and Secure rules are already tested. URL helpers live on HTTP (encodeURL, decodeURL, encodeQuery) rather than on URL, because URL is a template TAG and giving it static methods shadowed it — two tests caught that. Scope: the surface is bound, type-checked, and codegen lowers to u_http.h. The underlying client still lacks redirect-following, keep-alive and automatic gzip decoding. |
| Db EXECUTES — connection registry and executor | Works |
The missing link between "the compiler builds a statement" and "a program runs a query" (u_db.h). Everything above it already existed — the compiler emits a USqlStmt with placeholders and parameters apart, and the wire clients speak the protocols — but nothing turned a NAME from config into a live connection and pushed a statement through it. The registry is by name rather than by object, because main resolves to a DIALECT at compile time (which is how the placeholders were chosen) and to a SOCKET at run time; keeping those apart is what lets a build happen on a machine with no credentials. Connections open lazily, so a program that never queries never pays for a socket. Verified by execution against live PostgreSQL 16 and MariaDB 10.11: statements built exactly as SQL[main]`…` lowers them are prepared and executed, rows come back, and an unknown connection name is reported rather than guessed. The decisive case — the payload x'); DROP TABLE runq; -- is stored as a VALUE and the table survives with both rows, on both engines. Postgres uses the real extended protocol (Parse/Bind/Execute); MySQL substitutes at the DRIVER with proper quoting, because its prepared statements use a binary result protocol that is not implemented — the statement still travels as text plus a separate parameter list, and only that last hop merges them. |
Db layer BOUND to U — Database.Query / Database.Row | Works |
Until this, the whole Db layer lived in the COMPILER — query.py, relation.py, schema.py, about 40KB — and no U source could reach any of it. Now the surface is bound and type-checked: Database.Query gets select, where, orderBy, limit, offset, join, plus the terminals fetchAll, fetchRow, execute and getSQL; Database.Row gets the Qbix lifecycle — save, remove, retrieve, retrieveOrSave, set, get, toArray, wasRetrieved/wasModified/wasInserted, modifiedFields, getPrimaryKey, getTable, find. A generated model inherits the entire lifecycle, which is the integration that makes any of it usable. Everything that reaches the wire is +A, so a query suspends on the same fiber scheduler as every other wait instead of blocking. getSQL returns a string, deliberately — not a SQL value. The SQL type's guarantee is that it can only be built from a LITERAL: the text is fixed at compile time and only slots vary. A function that minted one at run time would hand back a value whose text is arbitrary, which is exactly the injection vector the type exists to exclude — the guarantee would be worthless. Turning strings into template literals at run time is not a pit of success, so getSQL is for INSPECTION (logging, EXPLAIN) and executing the query is how you run it. find was also dropped from the row instance: starting a query is not something an individual record does. Scope creep is refused and tested: groupBy, having, onDuplicateKeyUpdate, replace and lock exist in Db_Query and are deliberately NOT bound, because structure buys the compiler nothing for them — they belong in the template, which is already parameterised. Binding them is exactly how Db_Query reached 3,719 lines. Scope: the surface is bound, TYPE-CHECKED, and codegen LOWERS these calls to the u_dbq_* runtime query builder. The connection registry hands live connections to the wire clients (Postgres, MySQL, SQLite). groupBy, having, upsert and lock are also bound with compile-time checks (see next row). |
| Structured query builder — scoped by checkability | Works |
The builder exists to be legible to the compiler, not as a nicer way to write SQL: it and the SQL`…` template lower to the SAME artifact (statement + params) and neither is less safe. So an operation belongs here only if the compiler gains something from its being structured — select, where, orderBy, limit earn their place (column existence, index coverage); joins are DERIVED from the foreign-key graph rather than being a method; and aggregates, GROUP BY, CTEs and window functions are deliberately excluded, because structure buys nothing there. That is a principled boundary rather than an arbitrary one, and it is why the builder is ~250 lines rather than Qbix's 3,719 — sharding, caching, chunking and transactions stay runtime concerns. The headline check: leftmost-prefix index coverage. KEY (userId,id) serves a filter on userId and does nothing for one on title, so filtering by title reports "no index covers that prefix — this will scan the whole table" — the warning Qbix's signalMissingIndex only produces AFTER the query has run. Also caught before execution: unknown columns in where/select/orderBy, unsupported operators, and an INSERT missing a NOT NULL column with no default. IN () becomes 1 = 0 rather than a syntax error, because silently dropping the clause would WIDEN the query instead of narrowing it. Each method returns a new query, so the predicate accumulates as a value — which is what makes any of it checkable. Verified by execution: select/where/orderBy/IN and get() return identical rows on SQLite, PostgreSQL 16 and MariaDB 10.11. |
| Db relations — root, cycles and join order at compile time | Works |
A port of Qbix's Db_Relation::compile(), moved to BUILD time because every input is static (u2c/relation.py). Qbix does something worth stealing: it does not ask for a join ORDER, it DERIVES one — find the root store (referenced but never referencing), then level breadth-first, and the levels are the join order, so every JOIN references a store already in scope. Two structural mistakes fall out as errors rather than runtime exceptions: more than one root (joining them is a cross product, not one result shape) and no root at all (a cycle — nothing to start from). Both name the offending stores. validate() additionally reports non-fatal ambiguity: the same pair joined by two different keys, and a self-referencing store that needs an alias. Verified: posts→users, comments→posts yields root users, levels [[posts],[comments]], and emits LEFT JOIN posts ON posts.userId = users.id before the comments join. Qbix recomputes this per query; here it is computed once and costs nothing at run time. |
| Schema from checked-in SQL DDL | Works |
A model can come from versioned DDL instead of a live database (parse_sql_ddl): no server needed, it is the same artifact a migration applies, and its FOREIGN KEY declarations give the relation graph for free — so join order comes from the schema rather than being hand-written. Parses columns with type and length, nullability (NOT NULL wins; a bare column is nullable), AUTO_INCREMENT, PRIMARY KEY, UNIQUE KEY, plain KEY, and FOREIGN KEY … REFERENCES. The body is split on commas at paren depth zero rather than by regex, because a naive split breaks on decimal(10,2) and on KEY x (a,b) and silently truncates the column list — the test asserts the LAST column survives for exactly that reason. Multi-column index order is preserved, which is what leftmost-prefix index checking depends on. decimal(10,2) maps to float; type names are normalised across engines. |
Database.* types registered (Row, Query, Range, Relation, Connection) | Works |
The Qbix Db surface now exists as U types: Database.Row, Database.Query, Database.Range, Database.Relation, Database.Connection, Database.Expression and Row. Engine-agnostic by design, exactly as Db_Row is — a model must be the same type whether it talks to MySQL or Postgres, or the portability the layer exists for is lost; the engine appears only at the interface boundary (the connection and the SQL tag). Verified that a class GENERATED by schema pull — d Base.Streams.Stream : Database.Row with store, key, typed columns and indexes: [S] +G = ["id", "publisherId,name"] — now lints clean, so the schema→class→linter path connects end to end. Note the composite index is kept as a comma-joined string, which is the form leftmost-prefix checking reads. Scope: indexes and columns are declared by the generated subclass rather than on the builtins, because a list or map field on a builtin gets dragged into every structural pass that walks builtin fields (two tests caught this). |
| Inheritance from a generic parent | Works |
d Crate[T] : Box[T] used to attach no parent at all: the parser never consumed the parent's [T], so the type arguments fell out of the declaration as loose expression statements and the class inherited nothing. Worse, it was silent — reading an inherited field still type-checked, because member access is permissive on a class with no recorded fields, so nothing ever reported the loss. Fixed: the parent's type arguments are consumed and recorded (parent_type_args), the parent attaches, and fields merge. Verified by asserting the INHERITANCE rather than that a read passes — Crate now reports [count, extra, item]. Non-generic (d Circle : Shape) and dotted (d A.Base.Sub : A.Base) parents keep working, and d X : Y with NO body is still correctly a type ALIAS with an empty field map of its own. Scope: type-argument SUBSTITUTION is not applied — an inherited field declared item: T is recorded as T, not resolved to the instantiated argument. |
u2c config check — lockfile and .local manifest | Works |
Three jobs, separated by which config layers each may read. Resolve every Config[T]`path` binding against the checked-in layers. Lock the result in a committed u.lock.json, so a config change that alters the SQL the compiler emits appears as a DIFF rather than silently — the message names the consequence: "engine 'postgres' → 'mysql' — this changes the SQL that gets emitted". That is what makes config-driven engine selection safe instead of action at a distance, and it is the same principle as committing generated Base classes. Report which values .local/ still owes, derived from each bound type's fields without defaults — the only step that consults secrets at all. Verified end to end on a real project: correctly names db.main.password and db.main.user as missing while excluding port (has a default) and host (supplied). --write-lock records; --strict makes a missing value an error for deploy while a build only warns, so CI can typecheck a repo it has no secrets for. A bug this caught: the manifest first came back empty because d X : Y with no body is an ALIAS, and the lookup read its own (empty) field map instead of resolving through it — reporting success on an incomplete config, a green result meaning "I could not see anything". |
Config[T]`path` — typed config binding | Simplified |
Config supplies TWO things at TWO times, and the mechanism now keeps them apart. The checked-in layers (framework → plugins → config/app.json) give the TYPE — which engine, which keys exist — at BUILD time. .local/app.json gives the VALUES at RUN time. A build deliberately stops before .local/: it is gitignored, so a type inferred from it would differ between a laptop and CI, and compiling against secrets risks baking them into the binary. load_build_config and load_runtime_config now make that split explicit. Config[Database.Connection.MySQL]`db.main` validates at compile time that the path exists in the checked-in config (a missing path is an ERROR, never a silent empty — an empty config would make every later lookup succeed), and cross-checks the config's own engine string against the declared type. The type is the single source of truth; the JSON string is a cross-check — letting the string pick the type would mean editing a JSON file silently changes which SQL a program emits with no diff in the source. The binding produces its bracket type, so SQL[main] resolves the dialect through it: verified that a MySQL-bound name emits ? and a Postgres-bound name emits $1. A tag bracket naming a variable is resolved to that variable's declared type at lint time, so nothing downstream needs a special case. Not yet: the required-keys manifest is derivable (required_keys_for works, excluding fields with defaults) but no u2c config check command emits local.sample/app.json or reports what .local is missing; and no runtime registry yet hands a populated connection to u_pg_connect/u_my_connect. |
SQL[dialect] — the engine as a type argument | Works |
The compile-time input a SQL template needs is the DIALECT, not the connection. Placeholder syntax, quoting and type spelling are static properties of the engine; the live connection is only needed at runtime to actually execute. So the engine arrives as a type argument on the tag — SQL[Database.Connection.MySQL]`…` — and the two stages separate cleanly: compile takes the dialect and produces statement text plus param slots; runtime takes the connection and produces rows. Verified by emission: MySQL yields WHERE name = ? AND age > ?, Postgres yields WHERE name = $1 AND age > $2, and an application alias inherits its dialect through the parent chain, so d AppDb : Database.Connection.MySQL gives ? without restating it at every call site. Values remain parameters in every dialect. Both spellings work: SQL[Conn]`…` for the template and SQL[Conn](`…`) for the invoke sugar. This generalises beyond SQL — CSV delimiters/quoting, date formats and wire-format versions are the same shape: a static parameter that selects the encoder while the I/O stays deferred. |
| SQL lowering — statement and parameters kept apart | Works |
This is the change that makes the injection guarantee true rather than aspirational. SQL`…` used to emit u_str_concat(), splicing values straight into the statement — so the linter's "value position is parameterised" message was false, and the structural-position check was guarding the front door while the side door concatenated. Now each {{slot}} becomes a PLACEHOLDER ($1, $2, …) in the statement text and the value travels beside it in a params array, all the way to the wire. Verified by compiling and running: the payload greg'; DROP TABLE users; -- appears as params[0] and the statement text contains no value at all — only WHERE name = $1 AND age > $2. SQL therefore no longer maps to char*; its C type is USqlStmt, which is exactly the shape u_pg_prepare/u_pg_execute already consume. Scope: the statement is built but not yet auto-executed — binding a connection to a call site is the remaining step. |
HTML per-position slot types (__check__) | Works |
What a slot may be now depends on WHERE it lands, which is the spec's __check__ design ("pos.attr == 'style' ? Templates.CSS; pos.attr == 'href' ? Templates.URL"). Implemented: href/src/action want a URL, style wants CSS, and — the important one — a slot inside a <script> element is refused for any type but JS, because a value there is parsed as CODE and HTML escaping is simply the wrong tool. A plain S is still accepted in an attribute (the adapter can escape it) and in content. A DEFERRED slot has no known type at the template site, so it is not judged there — only the invoked form, where the value is bound, is checked. Composition-time </script> escaping lives in u_js_for_script(). Scope: the rules are a built-in table rather than a user-implementable z f __check__ hook. |
Template invoke sugar and the Handlebars tag | Works |
Both spellings of a template now carry distinct meaning, which is the settled model: Tag`…` is the TEMPLATE — its {{slots}} are parameters supplied by the caller's trailing {} bag, so they need not exist anywhere — while Tag(`…`) is SUGAR for calling it with the slot names in scope, and reports a missing name as an error. Verified both ways, for HTML and SQL, with every safety rule (on*, SQL structural) surviving the sugar. Handlebars is registered as the 15th tag: a SIBLING of HTML rather than a subtype, since substituting an unrendered template where markup is expected would print {{fieldA}} onto the page. Both produce markup; they differ only in who fills the slots and when — U server-side, the browser client-side — which is exactly Qbix's addTemplate pattern of one artifact usable from either side. Its slots are deferred, so they need no U binding at all. Scope: __check__ is still not consulted per position, and the invoked form does not yet lower to an actual call — codegen still emits the capture form. |
JS template tag — values into JavaScript | Works |
Interpolates U values into JavaScript by JSON-encoding each slot, so the result is a valid right-hand side (u_js.h, slot union S | I | N | L | Tree | JSON). The point is that JSON encoding alone is NOT sufficient, and all three gaps are closed and tested by execution: (1) the </script> break-out, which is a COMPOSITION hazard rather than a JavaScript one — < is unremarkable in JS, but the HTML parser scans for </script regardless of JavaScript string context, so the escaping lives in a separate u_js_for_script() applied when a JS value is embedded in HTML, and plain JS bound for a .js file is not over-escaped. (2) U+2028/U+2029 — legal inside a JSON string but historically illegal as literal characters in a JS string literal, so both are escaped. (3) Position — a JSON value is safe on the right-hand side of an expression but not inside a JS string literal or a JS template literal, where ${…} re-opens interpolation and turns data back into code; those positions are a compile error rather than an escaping problem, because no encoding makes them safe. Escaped quotes inside a string do not read as closers. Also re-escapes an existing JSON document while preserving its structure, and refuses rather than truncates when a buffer is too small. Scope: __validate__ is not called, so the JavaScript itself is not parsed; and since a U template literal is backtick-delimited, a JS template literal inside JS`…` needs its backticks escaped (interpolating into one is refused anyway). |
| Template tags — HTML / SQL / CSS with injection rules | Works |
The syntax is UPPERCASE with BACKTICKS — HTML`<p>{{nm}}</p>`, SQL`SELECT ...`, CSS`...` — and each tag produces its OWN type rather than a bare string, which is what makes an HTML value safe to nest into an HTML slot without re-escaping. Two injection rules are enforced at compile time, both from the spec's own adapter design: (1) SQL structural positions — SELECT * FROM {{table}} is refused because a table or column name cannot be parameterised, while WHERE age > {{age}} is fine because a value can; (2) HTML event-handler attributes — an interpolation inside onclick=, onmouseover= or any on* attribute is a compile error, because that value is parsed as JavaScript and no amount of HTML escaping makes it safe (the spec's TemplateLang example raises CompileError("inline JS not allowed") for exactly this). Ordinary attributes (href, class) and content slots stay allowed. Scope: the per-attribute TYPE rules from the spec (href should demand a URL type, style a CSS type) are not yet enforced — only the on* prohibition is; and a nested template inside a single-brace expression slot ({xs.map(ii => HTML`...`)}) does not yet resolve. |
Compile-time reflection — Phases 1, 2 and 3 (z f) | Simplified |
Phase 1 (model): Type/Field/Method built from existing linter state (u2c/reflect.py), handling unions (modifiers after a union bind to every alternative; splitting respects nesting) and generics (declaration PARAMS vs use-site ARGS, nested). Dumpable via u2c --reflect file.u. Phase 2 (evaluation): a z f now READS the compiler's own symbol table and walks it with ordinary U code — fieldsOf(nm).map(ff => ff.name).join(",") and fieldsOf(nm).filter(ff => ff.type.name == "N").len both evaluate at compile time. No macro DSL: same language, different stage. Reflection builtins (typeNamed, typeOf, allTypes, fieldsOf) and the reflection types (Type, Field, Method, Diagnostic, Position) are registered so z f bodies type-check. Member access, lambdas, collection pipelines, string ops and guard chains are supported, under a 100k-step budget so a runaway compile-time loop is a compile error rather than a hung build. Phase 3 (type feedback): a z f declared -> Type does not HAVE type Type at its call site — it PRODUCES one, and the compiler installs the result as that expression's static type. This is the keystone the adapter hooks need: it is how __output__ will give a query its row type. A mismatch is reported against the EVALUATED type ('val' expects Point, got Row), a guard chain can select a different type per branch, and re-entrant evaluation is reported as a compile-time type cycle rather than hanging. Scope: the evaluable subset is literals, arithmetic, comparison, member access, lambdas, collection pipelines and guard chains over reflected data — not arbitrary U. Adapters are now UNBLOCKED but not yet written; __schema__ (compile-time I/O to load an external schema) is not implemented, so the SQL adapters still cannot check real column names. |
| Math standard library (sqrt, pow, trig, log/exp, rounding, hypot, atan2, sign, constants) | Works |
A real math stdlib, emitted as direct libm calls — portable by construction (libm ships natively AND in Emscripten, so these lower to WASM unchanged). Registered as builtins so sqrt(x), pow(x,y), sin/cos/tan/asin/acos/atan/sinh/cosh/tanh, exp/ln/log2/log10, floor/ceil/round/trunc, fabs/hypot/atan2/fmod/fmin/fmax/copysign/cbrt/sign, and constants pi()/e()/tau() all type-check and compute correctly (compile+run tested: sqrt(2)=1.4142, pow(2,10)=1024, hypot(3,4)=5, ln(exp 1)=1, cbrt(27)=3, …). A user function of the same name OVERRIDES the builtin (the user's decl wins), verified by test. N is U's double. Scope: the real-valued double-domain functions; the Math. namespace-qualified spelling (Math.sqrt) is not yet wired (bare names work) — needs the namespace resolver to treat Math as built-in, a small follow-up. |
| +V vectorization | Works |
Real vendor-agnostic SIMD, now running by default. A [I +V] list's .map(*k)/.map(+k)/muladd and .sum() lower to runtime kernels written with GCC/Clang vector extensions (v8i32 __attribute__((vector_size(32)))), which the C compiler lowers to the target ISA: SSE/AVX on x86, NEON on ARM, and — answering the WASM question — WebAssembly SIMD 128 under emcc -msimd128. Vendor-agnostic by construction (the compiler picks the ISA), verified real (the map kernel emits pmuludq/paddd/movdqa in the x86 assembly) and correct (compile-LINK-run tests: map *3 → 108, sum → 36). Correction from a prior audit: this was mislabelled "hints only." The SIMD was real but sat behind an off-by-default U_VEC_ENABLE, so emitted calls never LINKED and the old tests only checked the call string appeared — never ran it. The SIMD tier is now always compiled; the multi-core fork-join tier stays opt-in behind U_VEC_ENABLE (needs pthread), with a serial fallback so single-core still gets real SIMD. Fixed a latent include-guard bug exposed by this (the vec section sat outside #endif U_RUNTIME_H, double-defining on re-include). Also emits #pragma GCC ivdep on inline .on()/map/filter +V handlers, and enforces the spec's "+V non-lane-representable handler is a compile error" rule. Remaining scope: 128/256-bit fixed width (not AVX-512/SVE); GPU warps (+R(GPU)) emit an OpenCL kernel-string builder but no dispatch/execution backend — real vendor-agnostic GPU (SPIR-V/WGSL) is a separate backend, named out of scope, not faked. |
| +R(GPU) — WebGPU/WGSL compute kernels (map, reduce, tiled, +A async) | Works |
Full path runs, including the +A async lowering. A U data.map(...) on a [I] +R(GPU) list routes via transpile_gpu() to a real WGSL compute shader + host dispatch (u2c/codegen/wgsl.py, u2c/runtime/u_gpu_async.h). Everything is verified by EXECUTION against real wgpu-native (bundled with wgpu-py), not just compilation: (1) WGSL kernels validated AND executed on a real device (map i32/f32, atomic sum); the full loop U source → compiler → WGSL → device → correct is pinned. (2) The complete host dispatch — upload, pipeline, dispatch, async mapAsync readback — compiles, LINKS against libwgpu_native, and RUNS to correct output. (3) Workgroup-memory tiling (the data-reuse tier): a var<workgroup> tree reduction emitted and RUN correctly (128 elems → 8256). (4) +A async, now fully lowered onto U's fiber scheduler: a GPU dispatch returns a PENDING FIBER FRAME (matching the UGenericFrame prefix) that U's real ready-queue scheduler drives cooperatively — u_gpu_map_step polls the device once per scheduler step and returns U_FIBER_SUSPENDED until the readback lands, then U_FIBER_DONE. Verified by execution: the frame is SUSPENDED right after dispatch (non-blocking), a concurrent fiber interleaves before the GPU completes, and the result is correct — the readback SUSPENDS A COROUTINE instead of blocking a poll loop, exactly like fetch(). This closes the last-turn caveat. Handler lowering enforces lane-representability (rejects outer capture / impure calls → CPU); default transpile() keeps the CPU fallback (no webgpu.h), so ordinary output still builds with plain gcc. Honest remaining scope: browser/WASM execution is unverified here (the API is identical via Emscripten --use-port=emdawnwebgpu, but there is no emcc in CI — native execution is what runs); a full tiled MATMUL (vs the tiled reduction that runs) is not yet emitted; and the +A frame is driven by the scheduler slice, with the compiler emitting the dispatch glue (emit_gpu_async_glue_c) — the auto-await INSERTION at the U call site reuses the same fork/await path as fetch() but is exercised via the runtime glue rather than a full end-to-end U-source async program. Named precisely. |
| +F — Function Domain Modifier (bare, typed, nullary; +R/+A/+N oracle combinations) | Simplified |
Core forms now work (compile + run), up from Placeholder. A +F type is a callable producing the base T, emitted as a real C function pointer: I +F(I) → int32_t (*)(int32_t), Config +F() → Config* (*)(void). All three spec forms run end to end: bare T +F and typed T +F(S) parameters (pass a function, call it through the param → 42), and the nullary T +F() thunk (producing a class, field read back → 42). A function name in +F-argument position resolves to its mangled symbol; +F-parameter functions are excluded from inlining. Note: (I, I) -> S and S +F(I, I) denote the same type (identical C representation), but only the +F spelling parses in parameter position today. Not yet done: the compiler-oracle modifier combinations — +F +R (bound closures capturing self), +F +A (auto-await), +F +N (null-check-before-call: compiles but returns garbage) — and [T +F] collections (fails "unknown type"). Core call-through works; the oracle combinations and heterogeneous collections do not. Named, not silent. |
The hashing, canonicalization, AND signatures are all real. The signatures are
genuine ECDSA over the standard curves — P-256 for ES256/OCP, secp256k1 for
EIP-712/Ethereum — with the elliptic-curve arithmetic verified against the
RFC 6979 test vector and known curve points. What was a marked placeholder is
now real elliptic-curve cryptography.
SPHINCS+ (SLH-DSA) is a stateless hash-based signature scheme standardized by
NIST in FIPS 205. Its security rests only on the hash function, so it resists the
quantum attacks that break ECDSA. This is not a shape or a stub: the
implementation is byte-identical to the SPHINCS+ reference.
The four that do not compile are language-boundary cases, not translator bugs, and
are named rather than hidden: