Closed dylanmckay closed 4 years ago
+1! Avr rust built into the compiler proper would be super useful. It's almost free of bugs now.
Not completely free of bugs :)
I will update the description to include some information about the state of the backend w.r.t bugs
Almost though 😄. Just a couple to go
In general we're quite welcoming of platforms upstream in rust-lang/rust so long as they don't place a maintenance burden on us! Some specific thoughts from what you're thinking:
#[cfg]
in libcore. Are these items left out due to "bugs in LLVM" or because they're fundamentally not supported at all on AVR? The former would be best done by "fixing LLVM" somehow whereas the latter makes things more tricky.In general we currently have all platforms with the same uniform interface of libcore/libstd, but it's not clear that'll remain true into as we keep picking up more platforms.
The patch you have looks pretty good, although I think we'd want to work more through the various #[cfg] in libcore. Are these items left out due to "bugs in LLVM" or because they're fundamentally not supported at all on AVR?
It is because of bugs in LLVM.
The only question in my mind is on support of i128
/u128
types - I think that for AVR, these aren't really useful. The i128
support in libcore
is currently commented out because of a bug, but there may be other undiscovered bugs as it is not a well tested codepath, and really exercises the register allocator as the AVR only has 32 bytes worth of general purpose registers.
It is still quite likely that we could get i128
working on AVR though, I don't know too much of the specific problems it triggers in the backend.
I think that the best way forward would be to propose the patch for real once libcore does compile without modification, or at least without many.
I think that the best way forward would be to propose the patch for real once libcore does compile without modification, or at least without many.
Sounds reasonable to me!
The only question in my mind is on support of i128/u128 types - I think that for AVR, these aren't really useful.
My main fear of not supporting i128
on AVR is that it will have a chilling effect on adoption of 128 bit integers for the sake of compatibility with AVR or other embedded platforms. E.g. if there is a library that uses 128 bit integers, AVR users that will want to use it will file issues to drop the usage. This might make 128 bit integers not "safe" to use in Rust code that strives to be portable, which I wouldn't like to happen.
a chilling effect on adoption of 128 bit integers for the sake of compatibility with AVR or other embedded platforms
I don't think that it's a terribly huge deal here. Small embedded devices already have giant limitations (no stdin / stdout, generally no allocator, etc.) that make it real hard to just drop in an arbitrary library. I recently learned that AVR GCC's double
is actually an alias for float
! I don't know if we are going to keep that strangeness or not, but it would affect crates way more than i128
.
I think we are always going to have special features that are used to make a crate suitable for embedded, just like we do for no-std.
no stdin / stdout, generally no allocator, etc.
You are describing the #![no_std]
ecosystem. There are libraries that target this ecosystem. And the general rule for that ecosystem is to take libcore
as a given, which also includes i128
. Each target that doesn't support i128
has a larger chilling effect inside this ecosystem, because an embedded target's "market share" is bigger inside the subset of the entire Rust ecosystem where the x86 family is not a very relevant player.
I don't know if we are going to keep that strangeness or not, but it would affect crates way more than i128.
Interesting! I do agree that if we'd alias f64
to f32
(or not provide it), it would affect the ecosystem more. However, if we can strive for consistency, why shouldn't we? It is definitely possible for us to implement i128
.
It is definitely possible for us to implement i128.
Absolutely, and I realize I didn't clearly state that I think we should implement i128 for AVR. However, any code that actually uses an i128
on an AVR is going to be in for a world of pain.
However, if we can strive for consistency, why shouldn't we?
Consistency with what, is the question. Consistency with GCC (f64
== f32
) or with every other Rust target (f64
!= f32
)?
You are describing the #![no_std] ecosystem.
Yep, which is why I said "special features that are used to make a crate suitable for embedded, just like we do for no-std." 😇
A larger problem that's been in the back of my mind since we originally landed the 16-bit usize
patch is that, fundamentally, Rust and Rust programmers tend to assume that usize
is the "native" size of a register. AFAIK, this is true for all the other platforms Rust targets, but not for AVR.
Consistency with what, is the question. Consistency with GCC (f64 == f32) or with every other Rust target (f64 != f32)?
The name f64
literally indicates that it has 64 bits. If you don't abide by the name then it becomes meaningless just like it is in C.
Good points here, I can see the concern around 128-bit integers. I definitely think that they should be supported, even though we shouldn't encourage their usage. I would hate to see crates having to add feature flags for things like trait impls on i128
types. This shouldn't really be an issue because all of the unused i128 stuff should be trimmed out by the linker.
The f32/64 problem is an interesting one. My main concern with making f64 actually 64-bits is that it means that C FFI could be very brittle. If developers don't know that AVR-GCC uses 32-bit doubles, then calls through FFI could read uninitialised memory or segfault.
I imagine we could solve this more-or-less by expecting users to use types from the libc
crate instead. We could add AVR-specific functionality to set c_double
to f32
. I think we can reasonably expect people to use the libc
crate in their FFI bindings.
Something to remember for merging, need to update the c_int
type used in the main()
signature: https://github.com/rust-lang/rust/pull/44906#discussion_r141843808
Edit: opened an issue for this since it affects MSP430 as well: https://github.com/rust-lang/rust/issues/44934
used in the main() signature
@mattico Maybe I've just been doing things in a weird way, but none of my code has made use of Rust's main
:
#[no_mangle]
pub extern fn main() {
More importantly, we can't really return because there's nothing to return to. Every program needs to run forever.
@mattico We will still definitely have to modify libc so the types match GCC for AVR
Oh, absolutely, I just don't know that main
will impact us at all.
@shepmaster Even on non-embedded platforms, the size of argc
in main doesn't matter given that we've had it wrong all this time and nobody has noticed except when inspecting the IR. There may be some RTOS that uses a standard main() entrypoint for its processes? Regardless, it's easy enough to fix.
It probably would've taken the same amount of time to submit a fix as it did to type this out, now that I think about it 😄
Is it just the libcore issues preventing this merge? Just so we know where to concentrate efforts.
The only issues I've had out of libcore are weird linker errors caused by I don't know what and also 32 bit bitshifting errors (missing intrinsic I think). I don't know if those are blocking the merge though.
chocol4te: Is it just the libcore issues preventing this merge? Just so we know where to concentrate efforts.
Yes - all of the required work here needs to be done within LLVM.
Restioson: The only issues I've had out of libcore are weird linker errors caused by I don't know what and also 32 bit bitshifting errors (missing intrinsic I think).
That's because all of the code that makes the AVR backend choke is commented out :)
Restioson: I don't know if those are blocking the merge though.
Not directly, but it'd be good to have fixed before the backend is merged. There are a couple of really annoying bugs like this that we should consider fixing before we merge, but they may not necessarily hold it up.
@dylanmckay LLVM6 has been merged https://github.com/rust-lang/rust/pull/47828 - what does that mean to this RFC?
@kellerkindt all of the issues listed in "Current issues in the AVR backend" are still true. It's likely that the current HEAD of avr-rust could be rebased and the interesting Rust-specific code merged, but that still doesn't get working code.
I'm personally still in favor of
I think that the best way forward would be to propose the patch for real once libcore does compile without modification, or at least without many.
Although having to avoid extra rebasing is nice.
I wonder the current state of Rust on AVR, now that we're half of a year later in development. I run a little Arduino projects group in my town, and I would love to be able to use Rust instead.
So, good news!
I think that the best way forward would be to propose the patch for real once libcore does compile without modification, or at least without many.
This is now the case!
The current avr-rust fork does not contain any modifications to libcore
.
The modifications required to support AVR from stock Rus are:
LLVMInitializeAVR{Target,TargetInfo,AsmPrinter,AsmParser, ...}
are declared and called.avr-unknown-unknown
has been added. This models avr-gcc's default behavior of building for the lowest common denominator unless explicitly specified. Unlike avr-gcc
which explicitly supports the -mmcu=<avr mcu name>
argument, no AVR-specific command line argument has been added for AVR. This means that a custom target specification JSON file must be written for every project. This is the case for many Rust embedded projects though.config.toml.example
file. Should AVR be included in upstream Rust by default, or should it also be opt-in?"avr-interrupt"
calling convention has been added. This allows extern "avr-interrupt" fn my_isr_handler() { .. }
. This would probably need to go through the RFC process to become stabilized, but I could be wrong.#[cfg(target_cpu = "...")]
has been added. The implementation can be found here. The implementation of this is target-independent, and thus it works for conditional compilation based on CPU for other architectures too, such as ARM. This allows the ruduino
crate to conditionally include a device-specific module that exposes all of the IOs, registers, and modules that are supported in the silicon. This most definitely needs to go through the RFC process before upstream.It's probably about time I send an RFC to LLVM-dev regarding promotion of the backend to non-experimental status.
You can see the full set of changes from upstream Rust to avr-rust here.
There's still a couple LLVM patches from the last two months we've cherry-picked at the moment, but the upstream Rust efforts to separate the emscripten version of LLVM from the version of LLVM used for all other targets has made it really easy to bring these changes in from rust-lang/llvm
repo, as it is now updated upstream regularly.
The <4 cherry-picked LLVM patches we have are currently in review in upstream LLVM, and so once a reviewer finds enough time, those patches will automatically float into upstream Rust. Upstream Rust also has target-specific Rust-specific patches, and so cherry-picked LLVM patches probably isn't really a blocker for merging avr-rust upstream
Any update on the status of Rust on AVR?
Also interested to know! For now I swtiched to hacking on STM32 blue pill, but I'd definitely want to get back to arduino once support for avr in rust is ready.
We @slowtec would also love to use Rust for our AVR projects and of course we would open source a lot of our production code :)
Bump, would love to see support made official. Gonna try using the fork for a project.
Update: At the moment, we're upgrading the fork to a newer version of Rust (avr-rust/rust#137). There are two bugs we've found that we've hit.
LLVM ERROR: ran out of registers during register allocation
This has been fixed in LLVM trunk in llvm/llvm-project@45eb4c7e55341c0b83a21dedecc092e273795eda. I'm cherry-picking this into our LLVM fork now. This bug was historically the biggest pain point in the backend, arising in most pointer-heavy code.
LLVM ERROR: Cannot select: t2: i16 = addrspacecast[1 -> 0] undef:i16 t1: i16 = undef In function: ZN4core3ptr87$LT$impl$u20$core..fmt..Debug$u20$for$u20$unsafe$u20$fn$LP$A$RP$$u20$.$GT$$u20$Ret$GT$3fmt17h0fdf8ca7140def9b
This one is harder to fix. It is caused by Rust mishandling function pointers on Harvard architectures where there are two separate pointer address spaces for accessing main memory and program memory.
The explanation is best made by @ecstatic-morse
The underlying issue is that const T always has addrspace(0). I think explicit casts (ptr as const T) should preserve the address space of their input here.
The bug is exposed by the std::fmt::Debug
implementation for functions and function pointers. Rust emits a pointer dereference with a target pointer type of addrspace(0)* i16
, using LLVM routines that will implicitly insert a bitcast (iN -> iM bits) or an address space cast if necessary. In the case of function pointers, Rust should codegen a addrspace(1)* i16
pointer, so that LLVM doesn't have to map (addrspacecast
) PROGMEM pointers to RAM pointers an impossible task because there is no memory mapping and the address spaces do not overlap.
This bug is the main blocker.
I am hoping upstream Rust pulls from LLVM master (AFAICT, it's pretty much at the 8.0 release) so we can remove a bunch of cherry-picks.
I recently got the https://github.com/dylanmckay/avr-compiler-integration-tests/ tests succeeding using an AVR emulator, which is great because there is no other test suite that actually executes the AVR assembly spit out by LLVM. I set up a GitLab runner to run tests for every AVR-Rust commit (via a GitLab repository mirror), but it isn't super useful because GitLab doesn't support CI builds on pull requests, as the actual code is hosted in a forked repository.
Thanks for the update @dylanmckay. We all appreciate the effort you’ve put into this.
We've now upgraded the fork ontop of Rust base rust-lang/rust@fb7cca33f, the blink program successfully compiles.
Did this issue stall?
Do you have any updates on this? Is the PR still stalled on the address space issue?
Hey @dylanmckay , sorry for bugging with this.. is there any update on the issue?
Hello all, here's a few comments from me
Cherry-picking fixes
Almost all of the required fixed to get libcore
working have been upstreamed to LLVM master and currently exist in the rust-lang/llvm fork. Yesterday I started a PR to update the AVR fork to 1.39.0 (avr-rust/rust#155), I only had to cherry-pick one fix that was not already there - avr-rust/llvm@4350776601bc671e6e055bfbe32add34b70d2635.
libcore cannot be compiled without modification
We no longer require a custom libcore fork to use AVR. There is currently only one modification to libcore
in the avr-rust fork - avr-rust/rust@44240ac59c5949b8a9fd191f5cd666d0206fbe85 - rewrite a pointer cast to get the right IR generating.
We are dependent on xargo for compiling AVR-Rust binaries - this will become easier with the std-aware Cargo initiatives going on (including the working group). Currently, AVR requires a compiled sysroot for each MCU type - we use Xargo at the moment to compile this lazily, but it will be much more seamless when Cargo becomes able to natively compile core/std for desired target specifications.
Any operations on function pointers other than 'call' access RAM, not program memory (avr-rust#68)
This has been fixed.
32-bit shifts generate calls to a compiler-rt routine that doesn't exist (avr-llvm/llvm#163)
This is still a painful issue. I suspect the best fix will be to write custom lowering code in the LLVM AVR backend that lowers these shifts to pure assembly, removing any ABI dependency on compiler-rt or libgcc. I am unsure how much larger the generated code size could be, it may not be a good idea.
I posted this comment up the thread
The "avr-interrupt" calling convention has been added. This allows extern "avr-interrupt" fn my_isr_handler() { .. }. This would probably need to go through the RFC process to become stabilized, but I could be wrong.
Can unstable calling conventions be added without going through the RFC process?
Currently, we have the "avr-interrupt"
and "avr-non-blocking-interrupt"
under the #![feature(abi_avr_interrupt)]
feature gate. Could these be upstreamed as unstable calling conventions, pending a future stabilization RFC?
Upstreaming an LLVM backend requires setting up a dedicated buildbot that runs tests for that backend, and maintaining it. Is this the case with Rust? How do we ensure that the test suite runs in AVR mode on every push? What did other backends (like WASM) do?
Does "Merging the avr-rust fork upstream" mean simply merging the two forks into one, but still requiring a build-from-source? Perhaps it's possible for a backend to be distributed, but nightly-only? What did the other backends do?
Other than that, the avr-rust specific patches are very thin, without any gnarly hacks nowadays.
The full patchset can be seen here https://github.com/rust-lang/rust/compare/master...avr-rust:avr-support
My WIP 1.39.0 rebase patchset (which is mostly identical) can be found here https://github.com/rust-lang/rust/compare/master...avr-rust:avr-support-1.39.0-4560ea788c. This should be merged to avr-rust/master in the next few days.
I can't think of anything specific that's blocking this - perhaps, it's time to submit patches and see how it goes?
https://github.com/rust-lang/rust/issues/44036
I mentioned this issue above, but I don't think it blocks upstreaming of the AVR backend. We should be able to land a working, useful version of rustc with AVR without it. I am sure there are workarounds and hacks we can do re. device detection in AVR crates in the meantime after the backend has been hypothetically merged, and before an official target-cpu querying API lands in official Rust.
He lives! Thanks for the update @dylanmckay , exciting progress.
Great work @dylanmckay! Thanks for keeping us informed.
A bit off-topic, but: would rustc for avr be able to do FFI with C libraries? There are plenty of mature libraries already available for avr but written in C/C++. It would be great to be able to create some rust-style wrappers for them and reuse them in our rust avr projects.
Rust already can do FFI with C in general.
Yes and that's really great! The question is though: does it translate to rust-avr?
As long as LLVM's C ABI for AVR matches that of gcc, it should work
Can unstable calling conventions be added without going through the RFC process?
My vote is yes, as the change to support the AVR calling conventions is basically just giving them a name and mapping them to the LLVM number. There's no fundamental code changes needed as we already have support for unstable conventions.
Upstreaming an LLVM backend requires setting up a dedicated buildbot that runs tests for that backend, and maintaining it. Is this the case with Rust? How do we ensure that the test suite runs in AVR mode on every push? What did other backends (like WASM) do?
Rust's tier system is a bit loosely defined, but I think that the right thing is to merge the AVR-specific code. Then AVR will be tier 3, which means it is not automatically built or tested, just hanging out in the code. At the very least, this means that those files will be kept up-to-date with compiler changes.
Does "Merging the avr-rust fork upstream" mean simply merging the two forks into one, but still requiring a build-from-source?
To my knowledge, it would be to submit the one commit as a PR. Our custom fork would effectively die / become a place to track AVR-specific details until it becomes a more widely-used target.
perhaps, it's time to submit patches
I think so.
@edvorg very on topic IMO
A bit off-topic, but: would rustc for avr be able to do FFI with C libraries? There are plenty of mature libraries already available for avr but written in C/C++.
Yes.. mostly. The AVR calling convention implemented by the AVR backend is designed to match AVR-GCC's (documented here). There are some quirks, but the LLVM patch D68524 I need to review should fix them.
Rust callsites will always be able to correctly invoke Rust or avr-clang compiled functions (as avr-clang C uses the same extern "C"
calling convention implementation as the Rust frontend), and inter operation with AVR-GCC should work, especially for simple function signatures, but it can choke at times (see the description of D68524 for more details).
Any update on this ? Just curious.
Submitted request to make LLVM AVR backend an official backend - https://reviews.llvm.org/D75099
@dylanmckay If accepted, what are the remaining pieces to closing this issue?
Hello all,
I would like to know the general opinions on merging the avr-rust fork into Rust proper.
The fork itself has became a lot more stable with less bugs in the recent months. It has also started attracting a number of people interested in using it.
You can find one of the more interesting projects using avr-rust on GitHub.
Blockers
LLVM 5.0
~Rust is currently on LLVM 4.0, which contains a working AVR backend, but there have been many bugfixes since then. We would need to wait for LLVM 5.0 to be supported (nearly finished: #43370) before we get a version of the AVR backend that has a few important bugs fixed.~
This is no longer a blocker. Upstream Rust is at LLVM 6.0 as of 2018-02-20.
Questions
Cherry-picking fixes
If AVR support was built into mainline, we'd need to be able to cherry-pick patches into Rust's LLVM fork. I don't imagine this would be much of a problem, as we already cherry-pick a number of important fixes into there.
All of the bugfixes cherry-picked into the avr-rust repository have already been upstreamed into LLVM trunk, which would continue to be the case if we merged the fork, as I am not a fan of the LLVM fork diverging too far from trunk.
Cherry-picking is necessary because of LLVM's 6-month release cycle.
Current issues in the AVR backend
There aren't any known bugs in the avr-rust/rust repository - all of the known bugs are issues in the AVR LLVM backend; here are some of the more interesting/impactful ones.
libcore
cannot be compiled without modificationThere is a milestone set up to track what bugs need to be fixed in order to get
libcore
compiling successfully without modification.This hasn't been much of a problem for users so far, as xargo will transparently compile
libcore
on the fly when needed, and we can override libcore inXargo.toml
.I am unsure what the Rust team thinks of merging a target which can't use the stock libcore.
Any operations on function pointers other than 'call' access RAM, not program memory (avr-rust/rust#68)
This is a symptom of AVR being the very first in-tree LLVM backend for a Harvard architecture. LLVM currently assumes that all functions reside in "the generic address space", which corresponds to RAM. Because of this, if you attempt to load/store through a function pointer, it will access RAM instead of the program memory.
Good news is that I have pending upstream LLVM patches to fix it (D37052, D37053, D37054, D37057).
32-bit shifts generate calls to a
compiler-rt
routine that doesn't exist (avr-llvm/llvm#163)Because there aren't many (if any) targets that don't support 32-bit shifts natively,
libgcc
andcompiler-rt
do not have 32-bit versions of shift routine, even though LLVM still happily generates a call to it.This causes an undefined symbol error whilst linking. This will only happen if you actually write code or use code that performs 32-bit shifts, as the linker is quite good at removing all dead code.
Note that I've had one user hit the missing shift bug due to compiling in release mode, which promoted a multiplication to a shift as an "optimisation".
Actual changes to merge
You can find all AVR-specific differences by looking at this diff.
Note that over half of that diff is just the README and Travis CI configuration - the actual code being upstreamed is very small; just some glue code to enable the AVR backend and a target specification.
This diff also conditionally disables parts of
libcore
for AVR - these fixes would not upstreamed, and are not strictly required as downstream users can use Xargo to compile a minifiedlibcore
for AVR).Links
AVR-Rust on Gitter AVR-Rust on GitHub