-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for freestanding C++ #189
Comments
I'm currently traveling so my response will be brief, but I just wanted to say thank you for sharing this. This is cool! I have no experience with freestanding environments, but what you propose makes sense in spirit. For For Though in the freestanding case there is no suitable default, as far as I understand, so we'd have to initialise these refs to "unimplemented" functions (which terminate on call), and that is not very nice. We could instead initialise the functions refs to undefined extern declarations, which comes back to your proposal but with the added ability to also use member functions if needed. The downside is then that the functions must be defined, even when not used, which is also not very nice. I'm reluctant to introduce macros other than 0/1 and numerical values, to keep things simple. |
Oh, please do enjoy your travel, I'm in no rush at all. Thank you for taking the time to reply and your detailed feedback :)
If I'm being perfectly honest, I tried to go for the fastest evaluation path, in a fail fast manner, and I didn't necessarily spend a huge lot of time looking at the implementation details for what the best solution could be. It turns out your library is exceedingly viable for freestanding environments with no FPU, RTC, or filesystem, and while a basic demo test creates a 385kB binary which, for a machine where you have a bit less than 2MB available, is a lot, it's still acceptable for only a test.
I see it now, yes. This works for me. I just haven't seen how it works just yet, but that's pretty much irrelevant: it's there, and it's usable. If you don't like the idea of having a default which crashes, these could also just be no-ops. They'd just... do nothing. I'm a bit more partial to an std::terminate myself, as displaying nothing feels like a more frustrating debugging experience for the user, in case they forgot to set the console output.
As I didn't dig enough, I haven't noticed you don't have any other macros beyond 0/1 and numerical values, indeed, and I totally agree with you on that one. Couple of remarks, then: we could have a new In addition to the usage of std::copy, I could quickly send out a pull request. I'm not 100% sure how we could proceed for the unit testing of all that however, but spinning up a freestanding environment takes a few minutes of gcc compilation time in a github workflow, and I have scripts ready for the case of the PlayStation 1. It's just that running these tests can be a bit more challenging, at face value. And I haven't tried Linux-on-Linux freestanding gcc yet, and I'm not completely sure on how reliable that'd be as a test. Also, notable: as I said, I went for the fastest path to trying things out, and I used the following toggles, different from your defaults. Not totally sure this is relevant, but it's something to consider:
|
That's great to hear! I haven't really made a concerted effort to reduce binary size, although it was a consideration when writing the float-to-string implementation. The state-of-the-art algorithms do have much bigger tables, which seemed to me were not worth the cost. When you say there is no FPU, does that mean that floating point calculations are still possible, but emulated in software? Does this have any impact on C++ code, other than performance considerations? I'm asking mostly to see if it would make sense to add a compile-time toggle to remove the float-to-string code and all you to save more space. Another question on binary size: have you tweaked any of the Lastly, I'm dropping this here because I think it's very relevant; there was a recent blog article from the author of It inspired me to try compiling snitch without linking to any C or C++ runtime to see what we actually depend on. Oddly, even with
The snitch::small_string<128> s;
append(s, "abc");
append(s, s); // aliasing I initially thought this could cause UB because of overlapping memory ranges, but actually snitch::small_string<128> s;
append(s, "abc");
std::string_view old_s = s;
s.clear();
append(s, old_s); // now the ranges actually overlap Since the The only reason I didn't use
Here's how that would work for the console output: // Define you own printing function.
void my_console_print(std::string_view message) noexcept {
// Do whatever you need to do to print the provided characters...
}
// Then in main():
int main(int argc, const char* argv[]) {
// Replace the print function used for the command-line interface and debug messages:
snitch::cli::console_print = &my_console_print;
// Replace the print function used for test reporting:
snitch::tests.print_callback = &my_console_print;
} For files it's not possible today, but we could use a similar mechanism to override the open/write/close functions. The difficulty is managing the file state (e.g. void file_open(inplace_any<max_file_object_size>& storage, std::string_view path) {
storage.emplace<std::ofstream>(path);
}
void file_write(inplace_any<max_file_object_size>& storage, std::string_view message) {
storage.get<std::ofstream>() << message;
}
void file_close(inplace_any<max_file_object_size>& storage) {
storage.reset();
} (could be made a little nicer if we introduced a type-erased
I agree!
As above, I think we'd go for I like the idea of adding
I think spinning up a Playstation 1 environment might be overkill. Perhaps a simpler approach would be to set As to actually checking that the run is successful without having console output, we could simply check the return code of the test application (GitHub actions already do that automatically). That will be annoying to debug when the tests fails though... The nicer alternative would be to build our own "standard library", creating a shared library that only exposes wrappers around the |
Yes, the compiler will have to emit soft float implementation, which is fairly bloated, and slow. It should be considered the same as with exceptions or rtti. However, I didn't really see any float usage straight off? Wasn't your conversion code working at compilation time? Or I didn't necessarily spend too much time investigating what I was looking at.
Yes, this definitely helps. But the default values are very viable still, which is good for entry-level users.
Right, I see now you can manually define main; seen the proper documentation. It makes sense.
Wellll... it's actually not that odd. Console usually would be a tty interface through uart for instance, while files require an actual filesystem working in the backend, which isn't really a guarantee. The PS1 has a standard tty system in its kernel, but no default writable filesystem (it's cd-rom based). It's similar to other embedded environment too, not just the PS1.
So the problem is that you can't trust libstdc++ to honor the freestanding toggle. I was the one to file bugs against the gcc project in the first place which got freestanding mode in libstdc++ to even build and work not long ago. And the result is that when you compile gcc/g++, you specify you want to build a libstdc++ which is freestanding. The resulting libstdc++ will be smaller and have less features. Here's the gist: if you use the freestanding toggle, it'll affect the codegen, but not much the precompiled libstdc++ code itself. And a freestanding libstdc++ which has been built from source as freestanding will have missing headers like cstdio which won't get installed. In a normal gcc environment, you'd be able to compile code against cstdio for instance, even with the freestanding compiler toggle, simply because the header is present on the filesystem. My point being you wouldn't safely be able to rely on the freestanding alone to ensure the code properly compiles in a fully freestanding environment, and you could get false positive results. |
It's used to implement compile-time float-to-string serialization yes, but also for run-time serialization if
Interesting! OK, makes sense to keep them separate then.
I understand, that's unfortunate. I think the simplest alternative that is left would be to still compile against a non-freestanding STL, but check that the link-time dependencies don't include anything that would not be available in a freestanding environment. That sounds doable. |
For example, with the build options you selected above, g++ 11 on an x86_64 linux host with the regular STL, linking the resulting #include <snitch/snitch.hpp>
TEST_CASE("test") {
CHECK(1 == 2);
} And calling
We can add a test that checks this output against an allow-list; any entry not in the allow-list would flag a test failure. My understanding is that we can keep all of these except:
In particular I assume these ones are OK, but let me know if not:
Edit: Doing some more digging as to where some of these are coming from.
So:
... that worry was justified. While we can solve some of them ( From the GCC docs, I read:
And it seems it will always insert calls to these functions to manage aggregate memory, so having a dependency on these symbols seems unavoidable. So while we can get rid of explicit calls to all these in snitch code, the output binary will still refer to these functions, and I don't know how we could differentiate explicit calls from calls emitted by the compiler. Unless we choose to accept this as a caveat of the "is this going to compile in a freestanding environment?" check, it seems the only alternative left is to actually compile in an emulated freestanding environment. FYI: I started a branch with some of this in: https://github.com/snitch-org/snitch/tree/freestanding. If you want to pick it up, either fork it and have at it, or I can invite you as maintainer and you can contribute directly to the branch. |
I managed to get snitch to run properly on a freestanding C++ environment, namely the Sony PlayStation 1:
I encountered a few very minor issues however, and I'm reporting them here to see if we can create a working set of configuration settings to make this work. It'd be nice to have a
SNITCH_FREESTANDING
toggle that is.<cstring>
, so nostd::memmove
and nostd::memcpy
. These could easily be defined away so the user can replace them at compilation time with macros indicating the replacement of their choice, such as__builtin_memmove
and__builtin_memcpy
when using gcc. These occur insnitch_append.cpp
andsnitch_string_utility.cpp
.<cstdio>
, so nosnitch_console.cpp
norsnitch_file.cpp
. My opinion for these is to simply disable most of the implementation details in freestanding mode, and let the user provide alternative implementations for them, in the form of simple C-style externs, such asvoid snitch_freestanding_write_console(const char * data, size_t len)
,void * snitch_freestanding_open_for_write(const char * fname)
,void snitch_freestanding_write(void *, const char * data, size_t len)
, andvoid snitch_freestanding_close(void *)
, to simplify interoperability.I can issue a pull request to address these, if that's an acceptable design.
The text was updated successfully, but these errors were encountered: