Skip to content

Reduce libc surface for embedded/bare metal targets#326

Merged
attipaci merged 4 commits into
Sigmyne:mainfrom
activexray:feat/optional-libc-deps
May 20, 2026
Merged

Reduce libc surface for embedded/bare metal targets#326
attipaci merged 4 commits into
Sigmyne:mainfrom
activexray:feat/optional-libc-deps

Conversation

@activexray

@activexray activexray commented May 19, 2026

Copy link
Copy Markdown
Contributor

Motivation

I'm working on a safe Rust wrapper for SuperNOVAS and realized that we're quite close to having a library that would work on resource-constrained targets. This is potentially useful as this could enable SuperNOVAS on microcontrollers (maybe simple antenna drive electronics) or web-based analysis tools with WASM. Looking through the code base, there really is not that much that needs the C runtime or an allocator (which is the limiting factor for these targets). While this PR isn't everything (there are still calls to errno and snprintf, in which removal would enable true bare-metal support), this should get us most of the way there for another PR.

Changes

  1. timescale.c: NOVAS_NO_SYSTEM_CLOCK build flag

novas_set_current_time is the only function in the C99 core that needs system time information. On targets with no clock, this change allows users to build with -DNOVAS_NO_SYSTEM_CLOCK. Then, this function returns -1 with an error and descriptions. However, on bare-metal targets we then can avoid use of novas_set_current_time and not get linker errors!

The default behavior is of course unchanged.

  1. util.c: pluggable trace/error message handler

This is perhaps the biggest change. Currently, the debug/trace handling always uses stdio for its output. On other bare-metal platforms, there is either some hook for stdio (which we don't want to use), or some way to get IO. I'm thinking mainly on embedded, there are nice out-of-band logging frameworks that would be really nice to hook in here.

As such, I've replaced the fprintf calls in the five tracing helpers with a function-pointer hook.
This introduces a new public API:

 typedef void (*novas_error_handler)(const char *fmt, va_list args);
  novas_error_handler novas_set_error_handler(novas_error_handler handler);

So:

  • Default handler calls vfprintf(stderr, fmt, args) which is identical to current behavior
  • Passing NULL silences output entirely while keeping errno and return-code propagation intact
  • Users can redirect output to UART, ITM, an in-memory ring buffer, or syslog, anywhere relevant for their target

Future Work

For full bare-metal support we need to remove the snprintf calls and errno writes, but that was a bit more work and I thought we'd start with this. Already with this PR we should be able to run on some WASM platforms.

@codecov

codecov Bot commented May 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 79.31034% with 18 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
test/c99/test-errors.c 71.87% 18 Missing ⚠️

📢 Thoughts on this report? Let us know!

@activexray

activexray commented May 20, 2026

Copy link
Copy Markdown
Contributor Author

Working on tests right now to not hit codecov issues (and I suppose to make sure this all works haha)

@activexray

Copy link
Copy Markdown
Contributor Author

Those CI errors look to be related to the new IERS stuff and not a part of this PR

@attipaci

attipaci commented May 20, 2026

Copy link
Copy Markdown
Collaborator

Thanks Kiran! I think that is a good idea. But, we might need more compile config options still (e.g. WITHOUT_LIBC or NOVAS_NO_LIBC if you prefer), which then can be used to disable functionality in all the relevant places (including iers.c) as needed. Specifically to iers.c, that option could automatically imply WITHOUT_CURL also (or used alongside it), which already disables a lot of the EOP fetching bits. Beyond that you just have to disable the leap seconds list related stuff, and you should be good to go...

Comment thread src/c99/target.c
@activexray activexray force-pushed the feat/optional-libc-deps branch 2 times, most recently from 0440498 to 596001b Compare May 20, 2026 16:10
@activexray activexray force-pushed the feat/optional-libc-deps branch from 596001b to 7d22d15 Compare May 20, 2026 16:17
@activexray

Copy link
Copy Markdown
Contributor Author

Yeah NOVAS_NO_LIBC would be good. A question is how granular do you want the guards to be? In this PR, the only thing that's compile-type controlled is that system clock function. So on some platforms that have a libc but no clock, this would be good enough and you'd still have access to stdio.

@activexray

Copy link
Copy Markdown
Contributor Author

again pretty confused by CI stuff here

@attipaci

attipaci commented May 20, 2026

Copy link
Copy Markdown
Collaborator

A question is how granular do you want the guards to be?

I tend to go with simpler is (always) better. Fewer knobs means less chance of weird (unexpected) stuff and also less confusing for people trying to use them. So, I'd go with just no clock and/or no libc build options for now. If there is a pressing need later to distinguish more scenarios, we can always complicate it more (but hopefully we won't have to)...

Trying to target every possible platform is bound to lead to some pain down the road with maintaining. I'd say that whatever build switches we (you) add, we also have to come up with automated tests for them in the CI to ensure they do not get broken down the line. I'm not even sure how we can test for embedded platforms in Github Actions, but surely someone has come up with a way to do that (maybe some docker images)... I'm not adamant that this must be done for this PR, but at some point in the not too distant future -- if you want the support for embedded / bare metal platforms to stay properly maintained....

@attipaci

Copy link
Copy Markdown
Collaborator

The multi-arch CI has some issues (not the first time). It fails setting up the Docker images correctly, so it's nothing to do with the C stuff here. Re-running it once (or twice) usually clears it. And, eventually whoever maintains those images will fix it... :-). I've just started a re-run...

@attipaci

Copy link
Copy Markdown
Collaborator

It looks good to me at first glance. There is still missing code coverage. But there is no rush to get it sorted. :-)

@activexray

Copy link
Copy Markdown
Contributor Author

The missing coverage seems to be the failure paths of the tests, which I don't expect coverage of

@attipaci

Copy link
Copy Markdown
Collaborator

Oh, I see. I thought I disabled coverage tracking on the test code itself. I'm a bit surprised it is back...

@attipaci

Copy link
Copy Markdown
Collaborator

OK, I think I fixed the coverage on main. I'll merge this PR now. I'll probably do some slight tweaking on it after, but I don't expect it to be much... Thanks again!

@attipaci attipaci merged commit 0221999 into Sigmyne:main May 20, 2026
30 of 40 checks passed
@attipaci

Copy link
Copy Markdown
Collaborator

@kiranshila ,

I had an after thought. Setting the error handler to NULL is the same as novas_debug(NOVAS_DEBUG_OFF). Both effectively stop emitting error messages. So, the NULL option is a bit superfluous here. I think I'd prefer using the NULL to reinstate the default handler instead, and rely on novas_debug() to turn off messaging entirely, if not needed.

attipaci added a commit that referenced this pull request May 20, 2026
@activexray

Copy link
Copy Markdown
Contributor Author

Ah yeah that makes sense. I'll work on the rest of the libc stuff now and see if I can get a CI build for wasm or some embedded platform

activexray added a commit to activexray/SuperNOVAS that referenced this pull request May 22, 2026
- Add WITHOUT_LIBC=ON build mode for bare-metal/WASM targets without
  libc file I/O, heap, or system clock
- Add NOVAS_NO_SYSTEM_CLOCK build flag for targets without system clock
- Add pluggable trace/error message handler (novas_set_error_handler)
- Fix is*() functions to cast to unsigned char
- Remove spurious stdlib.h includes
- Fix novas_set_catalog() NUL-termination
- CI: add embedded ARM cross-compile build (WITHOUT_LIBC=1)
activexray added a commit to activexray/SuperNOVAS that referenced this pull request May 22, 2026
- Add WITHOUT_LIBC=ON build mode for bare-metal/WASM targets without
  libc file I/O, heap, or system clock
- Add NOVAS_NO_SYSTEM_CLOCK build flag for targets without system clock
- Add pluggable trace/error message handler (novas_set_error_handler)
- Fix is*() functions to cast to unsigned char
- Remove spurious stdlib.h includes
- Fix novas_set_catalog() NUL-termination
- CI: add embedded ARM cross-compile build (WITHOUT_LIBC=1)
attipaci pushed a commit that referenced this pull request May 22, 2026
- Add WITHOUT_LIBC=ON build mode for bare-metal/WASM targets without
  libc file I/O, heap, or system clock
- Add NOVAS_NO_SYSTEM_CLOCK build flag for targets without system clock
- Add pluggable trace/error message handler (novas_set_error_handler)
- Fix is*() functions to cast to unsigned char
- Remove spurious stdlib.h includes
- Fix novas_set_catalog() NUL-termination
- CI: add embedded ARM cross-compile build (WITHOUT_LIBC=1)
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