Skip to content
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

Book section: Tips for embedded C / C++ developers #9

Closed
japaric opened this issue Aug 10, 2018 · 17 comments
Closed

Book section: Tips for embedded C / C++ developers #9

japaric opened this issue Aug 10, 2018 · 17 comments
Assignees

Comments

@japaric
Copy link
Member

japaric commented Aug 10, 2018

Triage(2018-08-20)

We have identified 4 topics so far and need help writing them. See #9 (comment) for details.


From @japaric on July 17, 2018 14:44

We agreed on having a section containing tips for embedded developers that have a C / C++ background. For example, they may be used to using for loop + indexing on arrays but it's more efficient to use iterators in Rust so they should use that instead.

Anything else that should be covered? cc @thejpster

Copied from original issue: rust-embedded/wg#126

@japaric
Copy link
Member Author

japaric commented Aug 10, 2018

From @diwic on July 21, 2018 7:5

I wrote an article some time ago, about some Rust patterns that replaces C pointers. Might be helpful in this context too?

@japaric
Copy link
Member Author

japaric commented Aug 10, 2018

From @korken89 on July 21, 2018 10:4

One thing that is quite popular now is to use template meta-programming in C++ to configure hardware at compile time with guaranteed optimization. This is one part that kept me from coming to Rust for a long time, and I still have not found a way to do this well in Rust.

In C++ there is a continuous move towards constexpr functions, which (under certain requirements) are guaranteed to be evaluated at compile time. In Rust, it still seems to be dependent on the optimizer to evaluate an entire function, plus this causes issues that I have not been able to find good solutions for when generating data structures at compile time.
Granted that a lot can be done in build.rs and using include!, but finding good examples and how/when to use it was not trivial in my experience.

If we can answer these a bit I believe it would be good for convincing the embedded C++ (library) developer.

@japaric
Copy link
Member Author

japaric commented Aug 10, 2018

From @therealprof on July 21, 2018 10:19

@korken89 No idea where you got that from. It's pretty much exactly the other way around: In Rust variables are const by default and everything is fully propagated through the compilation passes meaning that most functions will just turn into hot air leaving only few instructions behind. All automatically and per default without fiddling with functions trying to convince the compiler to that something is const. It's truly zero cost abstractions something that C++ never really achieved.

Mind you, the incredibly optimised binaries is something you will only get with release builds; debug builds are a bloat nightmare.

The problems are more on the opposite side of the spectrum: The safety guarantees of Rust make it rather cumbersome to share data, work with self-referential data structures or (especially in embedded areas) work with dynamic amounts of data.

@japaric
Copy link
Member Author

japaric commented Aug 10, 2018

From @korken89 on July 21, 2018 11:27

@therealprof

No idea where you got that from.

I got it from a lot of embedded development (libraries, OSes, and firmwares) in C++ together with participation in the embedded and metaprogramming C++ communities, and now rewriting my C++ libraries into Rust.

In Rust variables are const by default and everything is fully propagated through the compilation passes meaning that most functions will just turn into hot air leaving only few instructions behind.

I am aware of what you speak, but it is up to the user to make sure that a function will be evaluated at compile time. The simple example, is to give inputs that are runtime dependent, without realizing it, and the compiler will gladly generate runtime code for it rather than to generate a compile time error.

Moreover, the LLVM backend is not omnipotent, and assuming that the compiler will optimize everything away does not always happen (unless there is something new I have missed that guarantees this), there are some really good talks on YouTube by Chandler Carruth on the optimization LLVM does, which gives a good view into how well it will optimize and what the limits are.

It's truly zero cost abstractions something that C++ never really achieved.

Here you are misinformed, template metaprogramming (type based) and constexpr metaprogramming (value based) are both true zero-cost abstractions as they are performed at compile time (while granted that template metaprogramming generally needs O1 to fully make everything disappear in the binary). Sadly I do not have a curated list of true zero cost abstractions, but this presentation by Chandler Carruth gives some insight into what can be done to generate zero cost abstractions.

But indeed, there are some parts of C++ which truly are not zero cost.

Mind you, the incredibly optimised binaries is something you will only get with release builds; debug builds are a bloat nightmare.

Indeed, and this is a significant issue. One way to solve it in C++ is to use value based metaprogramming and a constexpr variable as a compression point, then only the data structure remains.
Another approach is to do debug build on O1 rather than O0 (from a C++ perspective), however this is a specific trick to C++.

The problems are more on the opposite side of the spectrum: The safety guarantees of Rust make it rather cumbersome to share data, work with self-referential data structures or (especially in embedded areas) work with dynamic amounts of data.

I am talking about compile time data structures in this case (stored in flash or used to initialize a run-time data structure), run-time data follows the normal Rust way and it is assumed that the readers have read the Rust book before coming here. Please see the Introduction chapter for the recommendations/assumptions given to the reader.


Back to the question at hand now, as coming from a C++ perspective these questions are still active and I still believe that it should be covered to some extent.

@japaric
Copy link
Member Author

japaric commented Aug 10, 2018

From @therealprof on July 21, 2018 13:15

@korken89

I am aware of what you speak, but it is up to the user to make sure that a function will be evaluated at compile time.

In Rust? No. It's not even possible to ensure that. (Well, with the exception of const functions but they're only usable for initialisation of static data and often not even that).

It's truly zero cost abstractions something that C++ never really achieved.
Here you are misinformed,

No, not quite. I'm fully aware that this is a goal of C++ and can be achieved by expert programmers but is except for a few exceptions not done due to being extremely time consuming. Also with C++ that works only inside a single compilation unit or when excessively using code in header files (which a lot of other people, me included, frown upon), which is neither possible in Rust nor necessary.

Back to the question at hand now, as coming from a C++ perspective these questions are still active and I still believe that it should be covered to some extent.

I also have some C++ background (and consider myself an expert C programmer) and have experienced the exact opposite of what you're trying to say needs fixing/documentation. Proper zero cost abstractions in C++ require a tremendous amount of effort to implement and ecosystem support (which often simply does not exist) and just happen to work out of the box in Rust, which is one of the main reasons why I completely gave up on embedded C/C++ development.

I'd really like to learn more about the problems you're facing because I have a strong suspicion there's an easy fix to your problem and yes, that's something that should go in the book, too.

@japaric
Copy link
Member Author

japaric commented Aug 10, 2018

From @Ixrec on July 21, 2018 20:27

The last few comments sound extremely confused to me, probably in part because const means two completely different things in C++ and Rust (maybe one or both of you already understand this, but: in C++ a const variable is only immutable, and may still be a runtime value; in Rust a const is always known at compile-time), and because Rust's compile-time evaluation is only partially implemented and a small subset of it is stable.

In both C++ and Rust, it is trivial to force the compiler to evaluate something at compile-time or error if it cannot; you simply declare a variable to be constexpr (in C++) or const (in Rust). You can do a lot more with this today in C++ only because C++'s constexpr functions have been available for a while, while in Rust defining your own const fns is not stable yet. Only the const-ness of some functions in std is stable. Once it's stable to define your own const fns, and the scope of what const fns can do is sufficiently expanded, pretty much everything C++'s constexpr can do should be doable in Rust too.

For actual template metaprogramming that const/constexpr doesn't cover, I suspect a lot of what C++ can do today is already possible in Rust (most famously, we have a compile-time interpreter for a variation of Brain****, which I guess is using "trait metaprogramming"?), and once we get const generics I suspect everything that's feasible in C++ will also be feasible in Rust.

@korken89 Does that answer your questions?

@japaric
Copy link
Member Author

japaric commented Aug 10, 2018

From @korken89 on July 22, 2018 16:15

@therealprof

Proper zero cost abstractions in C++ require a tremendous amount of effort to implement and ecosystem support

Indeed, and I have been creating these kind of abstrations through header-only libraries as you said.
This is also what has taken me towards Rust, as it is a great pain in C++ - too many ways to subtly fail and generate non-optimal assembler (which also is why Compiler Explorer is my main tool to evaluate the generated assembler in).

I'd really like to learn more about the problems you're facing because I have a strong suspicion there's an easy fix to your problem and yes, that's something that should go in the book, too.

@Ixrec really hit it on the nail here, thanks for the clarifications! The building block that I use in libraries for embedded system is constexpr to guarantee compile time evaluation - and guaranteeing compile time evaluation is really important when providing strong guarantees in libraries.
The second part is generating compile time tables and data structures, but I think using build.rs is sufficient here. Examples could include: generating CRCxx tables as there are a large amount of different CRC polynomials, generating twiddle factor tables for FFT implementations based on the input sizes and if real/imaginary inputs will be available.

I was not aware about const fn and const generics, the RFCs were a really good read! Knowing this, I would probably put a note for the C++ users to say how to get constant evaluation now (to the extent it allows with const) and what we can expect in the future to still the questions for now.

@japaric
Copy link
Member Author

japaric commented Aug 10, 2018

From @therealprof on July 22, 2018 17:12

@korken89 You might want to have a look at macros, too, to generate tables and/or runtime expressions at compile time.

@japaric
Copy link
Member Author

japaric commented Aug 10, 2018

From @korken89 on July 22, 2018 18:21

@therealprof Thanks, I'm already there :) Trying to learn procedural macros now.

@japaric
Copy link
Member Author

japaric commented Aug 10, 2018

From @Ekleog on August 2, 2018 8:25

IMO, how to use the build system for cross-compiling to embedded systems is the most important thing to put in this section, if it's not already somewhere else. At least that's what I had most issues with when switching to Rust for embedded development :) I guess that's going to be better with upstreaming of xargo into cargo, though.

@japaric
Copy link
Member Author

japaric commented Aug 10, 2018

From @jamesmunns on August 2, 2018 8:36

@Ekleog check out rust-embedded/wg#48

@japaric
Copy link
Member Author

japaric commented Aug 21, 2018

@Ekleog

how to use the build system for cross-compiling to embedded systems is the most important thing to put in this section

By "build system" do you mean Cargo? The section after the introduction in the embedded book will cover how to use Cargo to cross compile a Rust program, but this info only applies to targets that have tier 1 or near tier 1 support. For the edition that's definitively going to cover Cortex-M, very likely RISCV and MSP430, and maybe Cortex-A and Cortex-R. For unsupported targets you should check out the last part of the embedonomicon.

Cross compiling to embedded Linux is trickier because those programs depend on a cross compiled glibc and you need to install gcc. That should have a separate section.

@japaric
Copy link
Member Author

japaric commented Aug 21, 2018

Triage: It sounds like we at least want to cover:

  • How to cross compile to embedded Linux
  • How to do compile time evaluation in Rust? Point to const fn (note: unstable) and to procedural macros. Do we have an example use case for this?
  • Use iterators instead of for loops. This strikes me as non-embedded specific. Can we just link to existing (official) material?
  • What C types, specially pointers, map to in Rust? Again, this seems non-embedded specific. Any (official) resource that we can link to?

Anything else that should be covered?

Also, we need help writing these tips! For the cross compilation stuff we can adapt my old rust-cross guide. If you want to help leave a comment here and proceed to send a PR to the embedded book and I'll review it! (the location of the markdown file is unimportant at this point)

@Ekleog
Copy link

Ekleog commented Aug 21, 2018

@japaric

By "build system" do you mean Cargo?

What I meant was this plus, for this section, how to link with C, whether with Rust-linking-to-C-library or with C-linking-to-Rust-library :) guess it could be a simple link to someplace in the official documentation, though, as once the Cargo side is done it's no longer really embedded-specific.

@adamgreig
Copy link
Member

Consider also talking about repr(align)

@adamgreig
Copy link
Member

First draft in #81

@japaric japaric removed this from the 2018 milestone Nov 20, 2018
@japaric japaric removed Blocks Rust 2018 book help wanted Extra attention is needed labels Nov 20, 2018
@japaric
Copy link
Member Author

japaric commented Dec 4, 2018

Done in #81

@japaric japaric closed this as completed Dec 4, 2018
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

No branches or pull requests

4 participants