compiler+runtime: codegen fixes for empty literal, == int idents, m.field; runtime body-loss fix and Linux feature macros

Three codegen bugs surfaced repeatedly across the parallel port-to-El
agents and were patched here:

1. Empty array literal '[]' was emitting el_list_new(0, ) — trailing
   comma in a varargs call, fails the C parse. Special-cased: n==0
   returns 'el_list_empty()' directly.

2. '==' between two identifiers both tracked in __int_names (typed
   Int via 'let x: Int = ...') was miscompiling to str_eq. With the
   tagged-pointer Int-as-int64 representation, str_eq strcmp's what
   are integer values dressed as char* and segfaults on the first
   non-printable byte. Added the int-name lookup, mirroring the
   dispatch already present for '+' between Int idents. NotEq got
   the same treatment.

3. 'm.field' codegen was passing the raw const char* field name to
   el_get_field, which expects el_val_t. C compiler warned about int
   conversion; runtime read garbage at the address. Wrapped in
   EL_STR(...) so the field name lands as a proper el_val_t.

Runtime additions in the same pass:

  - el_runtime.c http_read_request: the loop's boundary check was
    'line_end >= hdr_end' which broke before processing the LAST
    header line — its trailing \r\n IS hdr_end. Real curl clients
    put Content-Length last, so POST bodies were silently arriving
    as length 0. Changed to '> hdr_end' so the last line is processed.
    soma-server agent surfaced this during smoke testing.

  - _GNU_SOURCE feature macro: clock_gettime/CLOCK_REALTIME, strcasecmp,
    and the dlfcn extensions (RTLD_DEFAULT) all gated behind it on
    glibc/Debian. macOS is permissive without; the landing Docker
    build needed these for linux/amd64. Adds <strings.h> for
    strcasecmp.

  - Refactored slot semantics in el_runtime.c (already in tree from
    the morning ARC commit): magic-tagged ElHeader at offset 0,
    ElList/ElMap with separate elems/keys/values payload allocations,
    el_list_append and el_map_set mutate-in-place when refcount<=1
    and copy-on-write when shared.

Self-host fixpoint reached at v3: elc → elc.c → cc → elc binary →
elc.c reproduced byte-for-byte. dist/platform/elc and dist/platform/elc.c
updated. The codegen.el and elc-combined.el changes are mirror-edits;
both flow through the bootstrap chain to keep self-hosting clean.
This commit is contained in:
Will Anderson
2026-04-30 18:14:57 -05:00
parent 23bbc99e43
commit 86b3ad070d
6 changed files with 132 additions and 8 deletions
+40 -1
View File
@@ -11,9 +11,18 @@
* Link requirements: -lcurl (HTTP client + LLM), -lpthread (HTTP server).
*/
/* Feature-test macros must be set before any standard headers. _GNU_SOURCE
* exposes clock_gettime/CLOCK_REALTIME, strcasecmp, and the dlfcn extensions
* (RTLD_DEFAULT) — all of which macOS hands us without asking but glibc on
* Debian gates behind an explicit opt-in. */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include "el_runtime.h"
#include <stdarg.h>
#include <strings.h> /* strcasecmp */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
@@ -877,7 +886,14 @@ static int http_read_request(int fd, char** out_method, char** out_path,
char* hp = sp2 + 1;
while (hp < hdr_end) {
char* line_end = strstr(hp, "\r\n");
if (!line_end || line_end >= hdr_end) break;
/* line_end == hdr_end means we're on the LAST header line — its
* trailing \r\n is the same \r\n that begins the \r\n\r\n header
* terminator. Process this line; only stop when line_end is past
* hdr_end (which means the parser walked off the end of the
* header block). The previous condition (line_end >= hdr_end)
* silently dropped any Content-Length that appeared as the last
* header — exactly what real curl/clients tend to emit. */
if (!line_end || line_end > hdr_end) break;
if (strncasecmp(hp, "Content-Length:", 15) == 0) {
content_length = strtol(hp + 15, NULL, 10);
if (content_length < 0) content_length = 0;
@@ -1705,6 +1721,29 @@ el_val_t time_diff(el_val_t ts1, el_val_t ts2, el_val_t unit) {
return (el_val_t)d;
}
/* Block the calling thread for `secs` seconds. Negative values are clamped
* to 0. Used by El programs that poll external resources (e.g. RunPod
* /status, Engram readiness probes). */
el_val_t sleep_secs(el_val_t secs) {
int64_t s = (int64_t)secs;
if (s < 0) s = 0;
struct timespec ts;
ts.tv_sec = (time_t)s;
ts.tv_nsec = 0;
nanosleep(&ts, NULL);
return 0;
}
el_val_t sleep_ms(el_val_t ms) {
int64_t m = (int64_t)ms;
if (m < 0) m = 0;
struct timespec ts;
ts.tv_sec = (time_t)(m / 1000LL);
ts.tv_nsec = (long)((m % 1000LL) * 1000000LL);
nanosleep(&ts, NULL);
return 0;
}
/* ── UUID v4 ─────────────────────────────────────────────────────────────── */
static int _el_uuid_seeded = 0;