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
+36 -3
View File
@@ -253,9 +253,12 @@ fn cg_expr(expr: Map<String, Any>) -> String {
}
// String equality: use str_eq() when either side is a string literal or ident.
// Use plain == when comparing integer literals.
// Use plain == when comparing integer literals OR when both sides are
// identifiers tracked in __int_names (typed Int via `let x: Int = ...`).
// Without the int-name check, `seen == idx` between two Int locals
// miscompiles to str_eq(seen, idx), strcmp'ing what are integer values
// dressed as char* segfault on the first non-printable byte.
if op == "EqEq" {
// Integer literal on either side arithmetic comparison
if left_kind == "Int" {
return "(" + left_c + " == " + right_c + ")"
}
@@ -268,6 +271,17 @@ fn cg_expr(expr: Map<String, Any>) -> String {
if right_kind == "Bool" {
return "(" + left_c + " == " + right_c + ")"
}
if left_kind == "Ident" {
if right_kind == "Ident" {
let lname: String = left["name"]
let rname: String = right["name"]
if is_int_name(lname) {
if is_int_name(rname) {
return "(" + left_c + " == " + right_c + ")"
}
}
}
}
if left_kind == "Str" {
return "str_eq(" + left_c + ", " + right_c + ")"
}
@@ -301,6 +315,17 @@ fn cg_expr(expr: Map<String, Any>) -> String {
if right_kind == "Bool" {
return "(" + left_c + " != " + right_c + ")"
}
if left_kind == "Ident" {
if right_kind == "Ident" {
let lname: String = left["name"]
let rname: String = right["name"]
if is_int_name(lname) {
if is_int_name(rname) {
return "(" + left_c + " != " + right_c + ")"
}
}
}
}
if left_kind == "Str" {
return "!str_eq(" + left_c + ", " + right_c + ")"
}
@@ -372,7 +397,12 @@ fn cg_expr(expr: Map<String, Any>) -> String {
let obj = expr["object"]
let field: String = expr["field"]
let obj_c: String = cg_expr(obj)
return "el_get_field(" + obj_c + ", " + c_str_lit(field) + ")"
// el_get_field takes el_val_t for both args, so the field name
// string literal must be wrapped in EL_STR(). Without the wrap
// the C compiler treats the bare const char* as an int64 (warns
// -Wint-conversion) and the runtime reads gibberish at the address
// when looking up the key.
return "el_get_field(" + obj_c + ", EL_STR(" + c_str_lit(field) + "))"
}
if kind == "Index" {
@@ -395,6 +425,9 @@ fn cg_expr(expr: Map<String, Any>) -> String {
if kind == "Array" {
let elems = expr["elems"]
let n: Int = native_list_len(elems)
// Empty literal: el_list_new(0, ) generates malformed C (trailing
// comma in a varargs call). Emit el_list_empty() directly.
if n == 0 { return "el_list_empty()" }
let items = ""
let i = 0
while i < n {