Skip to content

lua: fix build against newlib 4.6 and picolibc headers - #290

Open
coon42 wants to merge 1 commit into
reticulatedpines:devfrom
coon42:fix-lua-newlib-headers
Open

lua: fix build against newlib 4.6 and picolibc headers#290
coon42 wants to merge 1 commit into
reticulatedpines:devfrom
coon42:fix-lua-newlib-headers

Conversation

@coon42

@coon42 coon42 commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

I am playing around with AI agents (specifically claude code) to automate away annoying and boring side tasks and to pay off some technical debt so this PR is AI assisted. Please let me know what you think about this kind of PRs and if this could be useful for ML. I've tried to keep change sets small and reviewable.

Motivation is to fix the build issue of #286. While trying to build ML on my Arch machine, I did run into some other issues which were fixed by the AI as well. Even though everything is part of the build fix, I have split them up into seperate PRs in case the AI made wrong assumptions.

Here is the AIs report:

Refs #286.

What

Define _BEGIN_STD_C, _END_STD_C, __dead2, __noreturn and
__returns_twice (all #ifndef-guarded) in
modules/lua/dietlibc/include/sys/cdefs.h.

Why

lua bundles its own libc headers but has no setjmp.h, so lua/ldo.c's
#include <setjmp.h> reaches the toolchain's. That header pulls the above
macros in via an angle-bracket #include <sys/cdefs.h>, which
-Idietlibc/include/ resolves to our dietlibc copy rather than the
toolchain's. Ours defines none of them, so the declarations fail to parse:

/usr/arm-none-eabi/include/setjmp.h:15:48: error: expected declaration specifiers before '__dead2'
   15 | void    longjmp (jmp_buf __jmpb, int __retval) __dead2;
      |                                                ^~~~~~~

Everything after that first error is noise: with the declaration unparsed, gcc
reads it as an old-style function definition and every later typedef becomes
"storage class specified for parameter" — which is how one undefined macro
produces several hundred error lines. The #include <stdint.h> suggestions gcc
offers are red herrings.

The issue title blames the wrong thing

This is not a gcc problem. newlib 4.5 was immune because everything setjmp.h
needed came from a quoted #include "_ansi.h", which cannot be shadowed by
-I. newlib 4.6.0 added the <sys/cdefs.h> include and moved
__dead2/__returns_twice behind it; picolibc has always done it that way:

newlib 4.5   #include "_ansi.h"                     <- quoted, unshadowable
newlib 4.6   #include "_ansi.h"
             #include <sys/cdefs.h>                 <- new, shadowable
picolibc     #include <sys/cdefs.h>                 <- shadowable

So the trigger is the libc headers, not the compiler version. Verified against
the installed header on an Arch box running arm-none-eabi-newlib 4.6.0.20260123-1, and against the upstream newlib-4.6.0.20260123 and
newlib-4.5.0.20241231 sources.

All five macros are defined, not just the pair a given libc needs: newlib 4.6
trips on __dead2, picolibc trips earlier on _BEGIN_STD_C. The #ifndef
guards keep this inert where the toolchain defines them itself, so it stays
correct on newlib 4.5.

What this does not fix

System headers should not be reaching this build at all. Doing that properly
means -nostdinc plus a complete bundled header set, including a setjmp.h for
lua — which needs a jmp_buf definition matching whatever setjmp we link, and
getting its size wrong is stack corruption on the camera rather than a build
error. That wants more care than a build fix, hence Refs rather than Fixes.

This change invents no ABI — lua keeps the toolchain's jmp_buf, which measures
safe today:

jmp_buf, newlib 4.6 and picolibc alike:  _JBLEN 20 x _JBTYPE long long = 160 bytes
newlib setjmp we link from src/libs:     stmia r0!, {r4-r9,sl,fp,ip,sp,lr} = 44 bytes

lua over-allocates rather than overflowing. Worth being aware that this is luck
rather than design: a libc whose jmp_buf were smaller than the linked
implementation writes would corrupt the stack silently, with no build error.

Testing

Built platform/200D.101 with arm-none-eabi-gcc 15.2.1 from a clean tree: all
23 default modules and magiclantern.zip. Not tested on a physical camera.

🤖 Generated with Claude Code

lua bundles its own libc headers but has no setjmp.h, so lua/ldo.c's
#include <setjmp.h> reaches the toolchain's.  That header pulls in
__dead2 / __returns_twice / _BEGIN_STD_C via an angle-bracket
#include <sys/cdefs.h>, which -Idietlibc/include/ resolves to our
dietlibc copy rather than the toolchain's.  Ours defines none of them,
so the declarations fail to parse:

  /usr/arm-none-eabi/include/setjmp.h:15:48: error: expected declaration
  specifiers before '__dead2'

Everything after that is noise: with the declaration unparsed, gcc reads
it as an old-style function definition and every later typedef becomes
"storage class specified for parameter", which is why one undefined
macro produces several hundred error lines.

The compiler is not the trigger, despite the issue title.  newlib 4.5
was immune because everything setjmp.h needed came from a quoted
#include "_ansi.h", which cannot be shadowed by -I.  newlib 4.6.0 added
the <sys/cdefs.h> include and moved __dead2/__returns_twice behind it;
picolibc has always done it that way.  So this reproduces on gcc 14
through 16 given new enough headers, and moving between newlib and
picolibc does not avoid it.

Define all five macros, not just the pair a given libc needs: newlib 4.6
trips on __dead2 while picolibc trips earlier on _BEGIN_STD_C.  The
#ifndef guards keep this inert wherever the toolchain defines them
itself, so it stays correct on newlib 4.5.

Not fixed here: system headers should not be reaching this build at all.
Doing that properly means -nostdinc plus a complete bundled header set,
including a setjmp.h for lua.  That needs a jmp_buf definition matching
whatever setjmp we link, and getting its size wrong is stack corruption
on the camera rather than a build error, so it wants more care than a
build fix.  This change invents no ABI; lua keeps the toolchain's
jmp_buf, which measures safe today (160 byte buffer, 44 bytes written by
the newlib setjmp we link from src/libs).

Refs reticulatedpines#286.  Built for 200D.101: all 23 default modules and
magiclantern.zip.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@reticulatedpines

Copy link
Copy Markdown
Owner

The suggested fix doesn't seem appropriate to me - isn't it just hiding the real problem? We shouldn't be depending on system headers, given we link against a static lib that we provide directly.

The LLM seems to acknowledge this, but then give the bad "fix" anyway.

The hair splitting on gcc version vs libc version might be true. Hard to say. I do know that if I request the build system uses a specific local copy of gcc 14 binaries, the problem is not visible. So I'm not convinced on the LLM reasoning here. It's not very important, the real problem is our dependency on system header files.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants