codegen: either side Int is enough for == and !=, not both #137

Merged
will.anderson merged 1 commits from fix/eq-operand-inference into dev 2026-08-16 02:52:03 +00:00
Owner
let a: Int = 5
getint(5) == a      ->  str_eq(getint(5), a)   // SIGSEGV
getint(5) == 5      ->  getint(5) == 5         // fine

A function call whose return type codegen can't infer poisoned the operator, and a declared Int on the other side didn't save it. str_eq then read an integer as a char* and segfaulted. Only an integer literal on one side forced the numeric form — which is why this stayed invisible: the common case happened to be safe.

The check required both operands provably Int. Loosening to OR is strictly safer, not a trade-off:

  • when one side is a known Int, str_eq is always wrong (it dereferences that integer), while numeric comparison is at worst a wrong answer on an already ill-typed program;
  • when neither side is Int, nothing changes — string comparison is untouched.

Found by the test-framework agent while building the benchmark harness; it correctly declined to fix it mid-phase, since it's a codegen semantics change.

Verified — a semantics change earns more than an assertion:

  • 15/15 on a dedicated operator suite (string literals, string vars, string-returning calls, mixed var/call, != in every combination). The pre-change compiler scores 0/15 on the same file — it segfaults before printing anything.
  • self-hosting fixpoint byte-identical
  • the only difference in the compiler's own generated C is the intended one: a nested if becoming two sequential ifs in EqEq/NotEq. Nothing else moved.
  • neuron's full soul amalgam regenerates in 400 ms, exit 0, output byte-identical (1,270,212 bytes)
  • test_math 13/13, test_string 27/27, test_core 10/10, test_text 12/12 — 62 tests, 190 assertions, zero failures

Not fixed here, same family, flagged: Bool parameters aren't tracked as int-like, so cond == want between two Bool params still lowers to str_eq and segfaults. Found while writing this PR's own test harness — the first version crashed on exactly that, on both old and new compilers. Wants its own change.

```el let a: Int = 5 getint(5) == a -> str_eq(getint(5), a) // SIGSEGV getint(5) == 5 -> getint(5) == 5 // fine ``` A function call whose return type codegen can't infer **poisoned the operator**, and a declared `Int` on the other side didn't save it. `str_eq` then read an integer as a `char*` and segfaulted. Only an integer *literal* on one side forced the numeric form — which is why this stayed invisible: the common case happened to be safe. The check required **both** operands provably Int. Loosening to **OR** is strictly safer, not a trade-off: - when one side is a known Int, `str_eq` is *always* wrong (it dereferences that integer), while numeric comparison is at worst a wrong answer on an already ill-typed program; - when neither side is Int, nothing changes — string comparison is untouched. Found by the test-framework agent while building the benchmark harness; it correctly declined to fix it mid-phase, since it's a codegen semantics change. **Verified — a semantics change earns more than an assertion:** - **15/15** on a dedicated operator suite (string literals, string vars, string-returning calls, mixed var/call, `!=` in every combination). The pre-change compiler scores **0/15** on the same file — it segfaults before printing anything. - self-hosting fixpoint **byte-identical** - the **only** difference in the compiler's own generated C is the intended one: a nested `if` becoming two sequential `if`s in `EqEq`/`NotEq`. Nothing else moved. - neuron's full soul amalgam regenerates in 400 ms, exit 0, output **byte-identical** (1,270,212 bytes) - test_math 13/13, test_string 27/27, test_core 10/10, test_text 12/12 — **62 tests, 190 assertions, zero failures** **Not fixed here, same family, flagged:** Bool *parameters* aren't tracked as int-like, so `cond == want` between two Bool params still lowers to `str_eq` and segfaults. Found while writing this PR's own test harness — the first version crashed on exactly that, on both old and new compilers. Wants its own change.
will.anderson added 1 commit 2026-08-16 02:51:50 +00:00
codegen: either side Int is enough for == and !=, not both
El SDK CI - dev / build-and-test (pull_request) Failing after 12m20s
b55e6bfd53
let a: Int = 5
    getint(5) == a      ->  str_eq(getint(5), a)      SIGSEGV
    getint(5) == 5      ->  getint(5) == 5            fine

A function call whose return type codegen cannot infer poisoned the operator,
and a declared Int on the other side did not save it. str_eq then read an
integer as a char* and segfaulted. Only an integer LITERAL on one side forced
the numeric form, which is why the bug stayed invisible: the common case
happened to be safe.

The check required BOTH operands to be provably Int:

    if is_int_expr(left) { if is_int_expr(right) { numeric } }

Loosening to OR is strictly safer, not a trade:
  - when one side is a known Int, str_eq is ALWAYS wrong — it dereferences
    that integer — while numeric comparison is at worst a wrong answer on a
    program that was already ill-typed;
  - when neither side is Int nothing changes at all, so string comparison is
    untouched.

Found by the test-framework agent while building the benchmark harness; it
correctly declined to fix it mid-phase since it is a codegen semantics change.

VERIFIED, because a semantics change earns more than an assertion:
  - 15/15 on a dedicated operator suite covering string literals, string vars,
    string-returning calls, mixed var/call, and != in every combination. The
    pre-change compiler scores 0/15 on the same file: it segfaults before
    printing anything.
  - self-hosting fixpoint byte-identical
  - the ONLY difference in the compiler's own generated C is the intended one:
    a nested if becoming two sequential ifs, in EqEq and NotEq. Nothing else
    moved.
  - neuron's full soul amalgam regenerates in 400ms, exit 0, output
    BYTE-IDENTICAL at 1,270,212 bytes
  - test_math 13/13, test_string 27/27, test_core 10/10, test_text 12/12 —
    62 tests, 190 assertions, zero failures

NOT fixed here, same family, flagged for a decision: Bool PARAMETERS are not
tracked as int-like, so `cond == want` between two Bool params still lowers to
str_eq and segfaults. Found while writing this commit's own test harness — the
first version of it crashed on exactly that, on both the old and new compiler.
It needs the same treatment, and it wants its own change.
will.anderson merged commit b26dd47aef into dev 2026-08-16 02:52:03 +00:00
Sign in to join this conversation.