Skip to content

tool/lambda: fix Bye() exit-code interpretation at non-exit ABS#1497

Open
calvinbeighle wants to merge 1 commit into
jart:masterfrom
calvinbeighle:fix-lambda-bye
Open

tool/lambda: fix Bye() exit-code interpretation at non-exit ABS#1497
calvinbeighle wants to merge 1 commit into
jart:masterfrom
calvinbeighle:fix-lambda-bye

Conversation

@calvinbeighle

Copy link
Copy Markdown

Bye() in tool/lambda/lambda.c reads mem[ip+2] as an exit code:

int rc = mem[ip + 2];  // (λ 0) [exitcode]
if (rc) Error(rc, "CONTINUATIONS EXHAUSTED");

That assumes ip is at a λ.N ABS whose body is exactly VAR N, which holds at the exit ABS at ROM position 24 (mem[24]=ABS, mem[25]=VAR, mem[26]=0) and gives a clean exit(0).

But Abs() calls Bye() for any ABS with empty contp, and the wrapper allows execution to terminate at other ABS positions whose bodies are not VAR N. The clearest case in bit mode: when the lazy input list bottoms out via NIL, control returns to ip=14 (the inner λ. 0 wr0 wr1). There mem[15]=APP and mem[16]=4 (a span operand), so the old code reports CONTINUATIONS EXHAUSTED and exits 4.

Reproducer

( cat /tmp/AIT/bin/take1k.blc; printf 'hello world!' ) | ./lambda

take1k.blc is from https://github.com/tromp/AIT.

Before:

010010110001
ERROR:	CONTINUATIONS EXHAUSTED
   ip:	14
  end:	211
 term:	put
[exit 4]

After:

010010110001
[exit 0]

Tromp's uni -b from AIT produces the same 12 output bytes and exits 0 on the same input.

Fix

Only interpret mem[ip+2] as an exit code when the body actually looks like VAR N (i.e. mem[ip+1] == VAR). This preserves the original intent — clean exit through the exit ABS at ROM position 24 is unchanged — and removes the spurious error in cases where the program terminates at a non-exit ABS.

void Bye(void) {
  if (mem[ip + 1] == VAR) {
    int rc = mem[ip + 2];
    if (rc) Error(rc, "CONTINUATIONS EXHAUSTED");
  }
  if (postdump) Dump(0, end, stderr);
  exit(0);
}

This is the bug Justine mentioned in the redbean #topics/lambda channel on 2026-04-19 ("mostly works however I think it has a subtle bug I never got around to fixing"). I haven't audited for other bugs.

Verification

Standalone build + 4 regression tests at https://github.com/calvinbeighle/lambda-c-fix, including byte-for-byte parity vs Tromp's uni on take1k.blc + 'hello world!'.

Bye() unconditionally treated mem[ip+2] as an exit code, assuming ip
was at a lambda whose body is VAR N. In practice the wrapper allows
termination at other ABS positions where mem[ip+2] is a span
operand. Reproducer:

  ( cat tromp_AIT/take1k.blc; printf 'hello world!' ) | ./lambda

old: CONTINUATIONS EXHAUSTED, exit 4
new: exit 0, output byte-for-byte matches Tromp's uni

Fix only interprets mem[ip+2] when the body actually starts with VAR.
@github-actions github-actions Bot added the tool label Apr 29, 2026
Comment thread tool/lambda/lambda.c
}

/*
* Called from Abs() when a lambda is reached with no continuation. The

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for finding the bug. There's no point in explaining a bug fix in the source code for all history. It's more appropriate to put that in the commit message. So long as the commit message follows the 72 column justification like other messages. Only LLMs I've seen write comments this way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants