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:
Vendored
BIN
Binary file not shown.
Vendored
+26
-1
@@ -1898,6 +1898,17 @@ el_val_t cg_expr(el_val_t expr) {
|
|||||||
if (str_eq(right_kind, EL_STR("Bool"))) {
|
if (str_eq(right_kind, EL_STR("Bool"))) {
|
||||||
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" == ")), right_c), EL_STR(")"));
|
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" == ")), right_c), EL_STR(")"));
|
||||||
}
|
}
|
||||||
|
if (str_eq(left_kind, EL_STR("Ident"))) {
|
||||||
|
if (str_eq(right_kind, EL_STR("Ident"))) {
|
||||||
|
el_val_t lname = el_get_field(left, EL_STR("name"));
|
||||||
|
el_val_t rname = el_get_field(right, EL_STR("name"));
|
||||||
|
if (is_int_name(lname)) {
|
||||||
|
if (is_int_name(rname)) {
|
||||||
|
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" == ")), right_c), EL_STR(")"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (str_eq(left_kind, EL_STR("Str"))) {
|
if (str_eq(left_kind, EL_STR("Str"))) {
|
||||||
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("str_eq("), left_c), EL_STR(", ")), right_c), EL_STR(")"));
|
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("str_eq("), left_c), EL_STR(", ")), right_c), EL_STR(")"));
|
||||||
}
|
}
|
||||||
@@ -1930,6 +1941,17 @@ el_val_t cg_expr(el_val_t expr) {
|
|||||||
if (str_eq(right_kind, EL_STR("Bool"))) {
|
if (str_eq(right_kind, EL_STR("Bool"))) {
|
||||||
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" != ")), right_c), EL_STR(")"));
|
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" != ")), right_c), EL_STR(")"));
|
||||||
}
|
}
|
||||||
|
if (str_eq(left_kind, EL_STR("Ident"))) {
|
||||||
|
if (str_eq(right_kind, EL_STR("Ident"))) {
|
||||||
|
el_val_t lname = el_get_field(left, EL_STR("name"));
|
||||||
|
el_val_t rname = el_get_field(right, EL_STR("name"));
|
||||||
|
if (is_int_name(lname)) {
|
||||||
|
if (is_int_name(rname)) {
|
||||||
|
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" != ")), right_c), EL_STR(")"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (str_eq(left_kind, EL_STR("Str"))) {
|
if (str_eq(left_kind, EL_STR("Str"))) {
|
||||||
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("!str_eq("), left_c), EL_STR(", ")), right_c), EL_STR(")"));
|
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("!str_eq("), left_c), EL_STR(", ")), right_c), EL_STR(")"));
|
||||||
}
|
}
|
||||||
@@ -1989,7 +2011,7 @@ el_val_t cg_expr(el_val_t expr) {
|
|||||||
el_val_t obj = el_get_field(expr, EL_STR("object"));
|
el_val_t obj = el_get_field(expr, EL_STR("object"));
|
||||||
el_val_t field = el_get_field(expr, EL_STR("field"));
|
el_val_t field = el_get_field(expr, EL_STR("field"));
|
||||||
el_val_t obj_c = cg_expr(obj);
|
el_val_t obj_c = cg_expr(obj);
|
||||||
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_get_field("), obj_c), EL_STR(", ")), c_str_lit(field)), EL_STR(")"));
|
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_get_field("), obj_c), EL_STR(", EL_STR(")), c_str_lit(field)), EL_STR("))"));
|
||||||
}
|
}
|
||||||
if (str_eq(kind, EL_STR("Index"))) {
|
if (str_eq(kind, EL_STR("Index"))) {
|
||||||
el_val_t obj = el_get_field(expr, EL_STR("object"));
|
el_val_t obj = el_get_field(expr, EL_STR("object"));
|
||||||
@@ -2005,6 +2027,9 @@ el_val_t cg_expr(el_val_t expr) {
|
|||||||
if (str_eq(kind, EL_STR("Array"))) {
|
if (str_eq(kind, EL_STR("Array"))) {
|
||||||
el_val_t elems = el_get_field(expr, EL_STR("elems"));
|
el_val_t elems = el_get_field(expr, EL_STR("elems"));
|
||||||
el_val_t n = native_list_len(elems);
|
el_val_t n = native_list_len(elems);
|
||||||
|
if (n == 0) {
|
||||||
|
return EL_STR("el_list_empty()");
|
||||||
|
}
|
||||||
el_val_t items = EL_STR("");
|
el_val_t items = EL_STR("");
|
||||||
el_val_t i = 0;
|
el_val_t i = 0;
|
||||||
while (i < n) {
|
while (i < n) {
|
||||||
|
|||||||
@@ -11,9 +11,18 @@
|
|||||||
* Link requirements: -lcurl (HTTP client + LLM), -lpthread (HTTP server).
|
* 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 "el_runtime.h"
|
||||||
|
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
#include <strings.h> /* strcasecmp */
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.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;
|
char* hp = sp2 + 1;
|
||||||
while (hp < hdr_end) {
|
while (hp < hdr_end) {
|
||||||
char* line_end = strstr(hp, "\r\n");
|
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) {
|
if (strncasecmp(hp, "Content-Length:", 15) == 0) {
|
||||||
content_length = strtol(hp + 15, NULL, 10);
|
content_length = strtol(hp + 15, NULL, 10);
|
||||||
if (content_length < 0) content_length = 0;
|
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;
|
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 ─────────────────────────────────────────────────────────────── */
|
/* ── UUID v4 ─────────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
static int _el_uuid_seeded = 0;
|
static int _el_uuid_seeded = 0;
|
||||||
|
|||||||
@@ -158,6 +158,8 @@ el_val_t json_array_len(el_val_t json_str);
|
|||||||
|
|
||||||
el_val_t time_now(void);
|
el_val_t time_now(void);
|
||||||
el_val_t time_now_utc(void);
|
el_val_t time_now_utc(void);
|
||||||
|
el_val_t sleep_secs(el_val_t secs);
|
||||||
|
el_val_t sleep_ms(el_val_t ms);
|
||||||
el_val_t time_format(el_val_t ts, el_val_t fmt);
|
el_val_t time_format(el_val_t ts, el_val_t fmt);
|
||||||
el_val_t time_to_parts(el_val_t ts);
|
el_val_t time_to_parts(el_val_t ts);
|
||||||
el_val_t time_from_parts(el_val_t secs, el_val_t ns, el_val_t tz);
|
el_val_t time_from_parts(el_val_t secs, el_val_t ns, el_val_t tz);
|
||||||
|
|||||||
@@ -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.
|
// 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" {
|
if op == "EqEq" {
|
||||||
// Integer literal on either side → arithmetic comparison
|
|
||||||
if left_kind == "Int" {
|
if left_kind == "Int" {
|
||||||
return "(" + left_c + " == " + right_c + ")"
|
return "(" + left_c + " == " + right_c + ")"
|
||||||
}
|
}
|
||||||
@@ -268,6 +271,17 @@ fn cg_expr(expr: Map<String, Any>) -> String {
|
|||||||
if right_kind == "Bool" {
|
if right_kind == "Bool" {
|
||||||
return "(" + left_c + " == " + right_c + ")"
|
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" {
|
if left_kind == "Str" {
|
||||||
return "str_eq(" + left_c + ", " + right_c + ")"
|
return "str_eq(" + left_c + ", " + right_c + ")"
|
||||||
}
|
}
|
||||||
@@ -301,6 +315,17 @@ fn cg_expr(expr: Map<String, Any>) -> String {
|
|||||||
if right_kind == "Bool" {
|
if right_kind == "Bool" {
|
||||||
return "(" + left_c + " != " + right_c + ")"
|
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" {
|
if left_kind == "Str" {
|
||||||
return "!str_eq(" + left_c + ", " + right_c + ")"
|
return "!str_eq(" + left_c + ", " + right_c + ")"
|
||||||
}
|
}
|
||||||
@@ -372,7 +397,12 @@ fn cg_expr(expr: Map<String, Any>) -> String {
|
|||||||
let obj = expr["object"]
|
let obj = expr["object"]
|
||||||
let field: String = expr["field"]
|
let field: String = expr["field"]
|
||||||
let obj_c: String = cg_expr(obj)
|
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" {
|
if kind == "Index" {
|
||||||
@@ -395,6 +425,9 @@ fn cg_expr(expr: Map<String, Any>) -> String {
|
|||||||
if kind == "Array" {
|
if kind == "Array" {
|
||||||
let elems = expr["elems"]
|
let elems = expr["elems"]
|
||||||
let n: Int = native_list_len(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 items = ""
|
||||||
let i = 0
|
let i = 0
|
||||||
while i < n {
|
while i < n {
|
||||||
|
|||||||
+28
-3
@@ -1703,9 +1703,11 @@ fn cg_expr(expr: Map<String, Any>) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// String equality: use str_eq() when either side is a string literal or ident.
|
// 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. Without the int-name check,
|
||||||
|
// `seen == idx` between two Int locals miscompiles to str_eq on what
|
||||||
|
// are integer values dressed as char* — segfault.
|
||||||
if op == "EqEq" {
|
if op == "EqEq" {
|
||||||
// Integer literal on either side → arithmetic comparison
|
|
||||||
if left_kind == "Int" {
|
if left_kind == "Int" {
|
||||||
return "(" + left_c + " == " + right_c + ")"
|
return "(" + left_c + " == " + right_c + ")"
|
||||||
}
|
}
|
||||||
@@ -1718,6 +1720,17 @@ fn cg_expr(expr: Map<String, Any>) -> String {
|
|||||||
if right_kind == "Bool" {
|
if right_kind == "Bool" {
|
||||||
return "(" + left_c + " == " + right_c + ")"
|
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" {
|
if left_kind == "Str" {
|
||||||
return "str_eq(" + left_c + ", " + right_c + ")"
|
return "str_eq(" + left_c + ", " + right_c + ")"
|
||||||
}
|
}
|
||||||
@@ -1751,6 +1764,17 @@ fn cg_expr(expr: Map<String, Any>) -> String {
|
|||||||
if right_kind == "Bool" {
|
if right_kind == "Bool" {
|
||||||
return "(" + left_c + " != " + right_c + ")"
|
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" {
|
if left_kind == "Str" {
|
||||||
return "!str_eq(" + left_c + ", " + right_c + ")"
|
return "!str_eq(" + left_c + ", " + right_c + ")"
|
||||||
}
|
}
|
||||||
@@ -1822,7 +1846,7 @@ fn cg_expr(expr: Map<String, Any>) -> String {
|
|||||||
let obj = expr["object"]
|
let obj = expr["object"]
|
||||||
let field: String = expr["field"]
|
let field: String = expr["field"]
|
||||||
let obj_c: String = cg_expr(obj)
|
let obj_c: String = cg_expr(obj)
|
||||||
return "el_get_field(" + obj_c + ", " + c_str_lit(field) + ")"
|
return "el_get_field(" + obj_c + ", EL_STR(" + c_str_lit(field) + "))"
|
||||||
}
|
}
|
||||||
|
|
||||||
if kind == "Index" {
|
if kind == "Index" {
|
||||||
@@ -1845,6 +1869,7 @@ fn cg_expr(expr: Map<String, Any>) -> String {
|
|||||||
if kind == "Array" {
|
if kind == "Array" {
|
||||||
let elems = expr["elems"]
|
let elems = expr["elems"]
|
||||||
let n: Int = native_list_len(elems)
|
let n: Int = native_list_len(elems)
|
||||||
|
if n == 0 { return "el_list_empty()" }
|
||||||
let items = ""
|
let items = ""
|
||||||
let i = 0
|
let i = 0
|
||||||
while i < n {
|
while i < n {
|
||||||
|
|||||||
Reference in New Issue
Block a user