-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Add Cortex-M targets to the compiler + binary releases of core
#1645
Conversation
[compiler-rt] relatively easy. However, adding these targets to the compiler will make the setup | ||
process simpler (less steps, requires less tooling, etc). | ||
|
||
[extensive documentation]: http://japaric.github.io/copper |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Beware: Still vaporware.)
I’ve been meaning to try and get Rust running on a BBC micro:bit (when they become more available for sale) or a Teensy. They both have an ARM Cortex-M. These CPUs seem popular in 32-bit microcontrollers. Implementing this RFC would significantly lower the barrier to entry to Rust for hardware hobbyists. (Think projects that might use C/C++ on an Arduino.) |
Rust runs on the Teensy already, actually. |
Yes, but getting to hello world (or blinking LED) is fairly involved: you have to define you own target JSON file, compile |
For a hobby project, I can live with having to build libcore and maintain my own toolchain. My coworkers, some of whom have never heard of Rust, do mind. I see this as lowering the barrier of entry for using Rust in commercial ARM-based embedded projects. This is the number one barrier I have to adopting Rust at my job. |
*freestanding*. A freestanding crate is a crate that doesn't depend on an underlying OS, kernel or | ||
runtime. Currently, this is the full list of freestanding crates: | ||
|
||
- `alloc` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately I believe that alloc (and then transitively collections) depends on the libc crate right now, which may not be compile-able for these kinds of targets.
Although I've always wanted to remove the dependency here for this exact reason... We're just not quite poised yet to make that work. Also that being said it doesn't really affect this RFC, just a minor nit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, the crates listed here compile just fine for Cortex-M processors. (See Xargo). I haven't (yet) tried to write a program (for these targets) that uses alloc
+ collections
. But, liballoc
doesn't seem to contain any undefined reference to a libc
symbol (AFAICT):
$ arm-none-eabi-nm -u liballoc-bdc8710db91bbbe1.rlib
alloc-bdc8710db91bbbe1.0.o:
U __aeabi_memcpy
U __aeabi_unwind_cpp_pr0
U __rust_allocate
U __rust_deallocate
U __rust_reallocate
U __rust_reallocate_inplace
U __rust_usable_size
U _ZN4core9panicking5panic17h322e6888d03463ddE
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
__rust_allocate and friends are defined in alloc_system, which itself depends on libc for unix systems at least.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
__rust_allocate and friends are left undefined on purpose. That way one can define custom allocators.
Thanks for the well written RFC @japaric! I'm pretty excited about making bare-metal development as easy as possible, and it definitely sounds like avoiding compiler-rt and custom targets is a great boon to doing just that right now. My only hesitation on this would be figuring out the names of all these targets (there's quite a few!). I think it's fine to have a bunch, especially if this covers a massive portion of the embedded market. Between the detailed design and your alternative though, things get interesting. So today we actually do somewhat try to encode the CPU into the target itself, for example arm vs armv7 targets as well as i586 vs i686. The main motivation behind that is that dealing with Along those lines I'd be in favor of adding a target-per-CPU that we want to support, but the naming definitely seems like it may get tricky. Do you know if there are canonical names for these targets elsewhere? For example do the gcc toolchains pick reasonable target names or are they all just configured slightly differently? |
gcc uses a single triple for all these CPUs:
So one simply links to the appropriate one. Parenthesis: This sounds like a lot of setup steps but embedded C development usually involves an IDE. When using an IDE, there's only a single setup step: you graphically select your microcontroller (this is even more specific than selecting a CPU!) and the IDE takes cares of passing all the right flags to gcc, building and caching libraries, supplying the linker script and starup files, etc. For example, see the demo video here, you can skip to the minute 2.
FWIW, I was looking at the differences between all the CPUs from the POV of LLVM optimzations (you can do this by grepping for "cortex-m" in Rust llvm sources). And, AFAICT, there doesn't seem to be any difference in codegen between the alternative
If targets like
I know you have considered this format before but for binary releases ( |
Most of this looks relatively uncontroversial to me (and may not need an RFC). The target triples you are specifying look really weird though: From a quick skim the only other major change I see is the definition of a You are overriding |
Note that thumbv6m has no support for atomic operations at all. This means that liballoc and all crates that depend on it will not work on that architecture. You will only be able to use libcore with it. |
I love the idea of a The Rust OSDev community has been battling with these issues for a while, and a way to say "here's a |
Ideally you would just pass a All bare-metal rust projects that I've seen so far seem to simply not bother linking in compiler-rt, and instead just use a bare libcore. While this will usually work, it can cause linker errors if you try to perform some operations that aren't supported natively by LLVM, such as 64-bit division on 32-bit ARM. |
@Amanieu many OS-devs also need to turn off floating point, which requires a custom patch to libcore, so unless that ends up landing upstream, rustup probably can't do it yet :/ |
These "triples" match directly the CPU name. The alternative section has more standard triples:
Well, the
Actually, I think we could just reuse the
Yeah :-/. You get a "undefined reference to In the meantime, one could fork the alloc + collections crates to bubble up the oom as an error and use that. At least until the Allocator trait lands. |
Just disable interrupts while performing the operation and restore them afterwards. |
I would consider that a bug.
Possibly. We could just consider |
If there's a sensible implementation and as that symbol is not in compiler-rt, do you think we should provide an implementation of that symbol in
But the |
Hm, I guess as defined right now, I'd consider |
it's possible to package a rust-std component with a smaller set of crates so there is no need to define a new rust-core component. We can always add a new rust-core component later on if desired.
updated the RFC to re-use the existing rust-std component instead of defining a new rust-core component. (I think GitHub is now notifying subscribers when a new commit is pushed, but just in case) |
I think that the content of all these targets seems reasonable, as does the distribution strategy of 'rust-std' just containing everything it can for that target (which in this case is not std, but oh well in terms of naming). As before, I'm still hesitant on naming, so I wanted to lay out three possible strategies (two of which are in this RFC) to have it all in one place: CPU-basedProposed in this RFC, this is a pretty ergonomic method of specifying the target to the compiler. Tools (including Cargo/rustup) can easily understand this and we'll "just be adding more targets". The downside of this approach, however, is that it's not necessarily scalable as we probably don't want a target-per-cpu. It's also a divergence from today's targets which at least have some semblance of being a triple and conveying information. Triple-basedListed in the alternatives section, this is not as ergonomic than the previous method but aligns better with today's "triples". The introduction of architectures like "thumbv7m" where the actual target_arch is "arm" is somewhat odd though. Not unheard of though as "i586" is mapped to "x86" and "armv7" is mapped to "arm", just a little odd. The downside of this, though, is that we don't have a great vector today to ship as many artifacts as before (they're all target-based, not target + flags based). Another downside is that to be "the most usable" you'll have to pass a bunch of other flags to get code to optimize correctly.
|
(Take my opinion with a grain of salt since I haven’t actually tried to do this yet, but) I’d nitpick "not impossible" rather than "usable". The process of figuring out what to write in that JSON file, then fetching/patching/building your own libcore seems very manual and fragile at the moment. Far from Cargo’s goal of making builds reproducible as much as possible. |
I'm wary of loosening the definition of what we consider 'target triples' further. The problem of being able to specify all these platform variations is related to this thread about config-specific APIs, activating arbitrary cpu features. Also related to std-aware cargo which will need to understand that changing certain features may force a rebuild of std (e.g. when you activate SSE4 you rebuild std with SSE4). It seems to me that we need a concept of a target + some additional configuration. |
Exactly what the custom json target specs are. Perhaps we could improve this “additional configuration” in a way which would allow extending other target specifications, but otherwise seems to me like we’re already set on that regard. I do not disagree that the experience of using this “additional configuration” needs improvements, though. |
I don't think using CPU based is a good idea as it doesn't scale well and rustc doesn't use it for any of the other architectures. Keeping with standard llvm triples we just need to add
If someone wants to add any CPU specific optimizations then it's the same problem/solution as for the the other architectures which we can deal with separately. |
UpdatesI've sent a PR (rust-lang/rust#36874) that adds these target definitions (the A PR (rust-lang/rust#36794) that adds a The question is: Should these Cortex-M targets default to Second question: What should be the default of |
I don’t really understand what |
@SimonSapin It means that no dynamic linker is needed to finalize imports (which are usually handled through the many kinds of relocations). |
Ah never mind, I read @japaric’s last message as recommending |
In principal I'm also 100% in favor of this.
Yes, but that's for a particular relocation model in userspace. For the most part, I have seen scenarios where it makes a ton of sense to use PIC for bare-metal. What about |
Yes, absolutely. There are rare cases where one would want to have an unwinder on -M, but many -M targets have less RAM than the unwinder requires for its operation in the first place.
Definitely static. -M targets don't have an MMU, so in general, even if there would be startup code that can relocate them, they would always get relocated into one place (or one of two places, for SoCs that let you map either flash or RAM starting at zero). The complexity of such startup code, or the increase in overall code size cannot be justified. |
@alevy @whitequark Thanks both for the input! @alevy Sorry, I haven't updated the RFC body but we are leaning towards the alternative that defines 4 targets. Now, about your question, there are two targets you could use for Cortex-M4/7 devices: |
The target definitions have landed upstream in rust-lang/rust#36874 🎉. Thanks everyone for participating in the discussion! We couldn't have arrived at the current target definitions without everyone's input. ❤️ @alexcrichton what do we do with this RFC? It wasn't needed for just the target definitions (though it was very helpful!) and we are unlikely to do the binary release / rust-core component part. Should I update it to just talk about the new targets and then we can merge it in its updated form or should I just close it? (yes, I'd like to save myself the job of updating this ...) |
@japaric I'm personally ok with closing this as we tend to not have RFCs for new targets, let's see if this works... @rfcbot fcp close (as @japaric mentioned above) |
Team member alexcrichton has proposed to close this. The next step is review by the rest of the tagged teams: No concerns currently listed. See this document for info about what commands tagged team members can give me. |
I think in situations like this we don't need to wait for the bot - either the author or someone from the team could just close the RFC. |
Not sure if this is the right place to keep talking about, apologize if not... I'm trying out this newly merged feature from nightly on Tock. It basically works, which is great, except by default it's missing a few linker arguments that seem generally important. If I don't explicitly add...
I think these should probably be there by default. We don't have a platform with hardware floating point yet, but I tried compiling one of our cortex-m4 targets with the hardware floating point target for heck of it. I wasn't able to. I ended up with a linker error:
I'm not familiar enough with hardware floating point to intuit what the problem is, but if there is, in general, support for getting floating support right by default, I'll dig in further. |
IMO you should use rlibc for that, and not have libc a hard dependency on freestanding targets. |
That's a gcc multilib thing that tells gcc to pick the libraries that we compiled with soft ABI. arm-none-eabi-gcc ships with several libc/libm/etc. binaries that were compiled with different profiles (soft ABI, hard ABI, no FPU, etc.). That
Hmm, that sounds like you want to use the IMO, linker flags shouldn't be hard coded in a target definition. Linker flags should be supplied by Cargo / rust / user.
You can add all these linker flags using a build script. See this section of the related documentation. You want to use
Cargo can't inject this right now (cf. #1766) but you can add these using a [target.thumbv7m-none-eabi]
rustflags = ["-C", "link-arg=-mfloat-abi=soft"] |
@whitequark I think Tock has C components so they have to link to newlib. But, I agree that our targets shouldn't, by default, link to |
@japaric @whitequark there are no C components. rlibc seems like a reasonable fix for
Yes, that's exactly what I did.
My point is that the targets, as they are, cannot actually compile a rust program with floating point operations. In both soft- and hard-floating point cases, some extra linker flags are needed. I agree it's possible to get around this right now with
Are you saying you think the fix is to change Cargo's default for those targets? I'm not very familiar with how Cargo/rustc separate responsibilities, but if Cargo has precedent for doing that, it seems like a perfectly good solution to me. |
hmm, if there are no C components. Why are you passing
The only linker flag I need to use with any of these targets is
Those flags would make linking always fail if you don't have newlib installed, which is a totally
|
Those libraries are necessary for compiler intrinsics required by ARM. The way GCC has decided to divide things up is to leave those intrinsics to newlibic.
|
OK, if what you need are compiler intrinsics then what you really want to use, instead of newlib, is "libcompiler-rt.a" which has been renamed to libcompiler-builtins.rlib recently. As there are no binary release of that .rlib for these targets, you'll have to depend on rustc-builtins (possibly with its "c" Cargo feature enabled) to make Cargo compile the intrinsics for you. As for the other symbols that are not compiler intrinsics. Use |
If that means what I think it means, it's going to get very confusing especially with the current nomenclature already distinguishing between |
@rfcbot reviewed |
1 similar comment
@rfcbot reviewed |
🛎 This RFC is now entering its week-long final comment period for closing 🛎 |
All relevant subteam members have reviewed. No concerns remain. |
The final comment period is now complete. |
Alright, thanks again for all the discussion everyone! Closing. |
It's been long known that one can build Rust program for ARM Cortex-M microcontrollers.
This RFC proposes making these cross compilation targets more first-class by adding them to the
compiler and providing binary releases of standard crates like
core
for them.Rendered
cc @rust-lang/tools
cc @hackndev