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

Consistent development experience #44

Closed
japaric opened this issue Feb 21, 2018 · 13 comments
Closed

Consistent development experience #44

japaric opened this issue Feb 21, 2018 · 13 comments

Comments

@japaric
Copy link
Member

japaric commented Feb 21, 2018

Ideally the development experience for the different embedded targets should be very similar so a
user can easily transition from one target to the target instead of having to redo the setup
process, learn about new tools, learn a new development process, migrate to different crates that
basically do the same things as the ones they were previously using, etc.

This problem can be tackled from different sides:

  • Tooling

It should be possible to use Xargo Cargo, the RLS and some IDE to do development for all the
embedded targets. One click "build, flash and debug" functionality should be present.

I had reports that VS Code plus the cortex-debug plugin works fine for Cortex-M development. The
plugin also has support for printing semihosting and ITM output to the console.

A CLI equivalent should also exist. For example, two subcommands like cargo flash and cargo debug should Do The Right Thing for each embedded target. This may require the user to specify, for
example, what parameters to pass to OpenOCD in some configuration file.

  • Same starting point

Ideally a minimal embedded program should look like this:

#![no_std]

// provides the entry point
extern crate arch_specific_crate;

fn main() {
    // and optionally some debugger based logging functionality
    log!("Hello, world!")
}

for all the embedded targets. This can be accomplish by standardizing a crate like cortex-m-rt and
replicating it for all the embedded targets, and by providing Cargo project templates (with linker
scripts, etc.) for each embedded target.

Cargo doesn't currently provide support for project templates (there was a pre-RFC discussion
months ago but it didn't go anywhere). The solution currently adopted in Cortex-M land is to just
cargo clone a crate from crates.io that serves as a template and go from there (cf.
cortex-m-quickstart). We could adopt that solution or push for proper project template support
in Cargo.

  • Large set of reusable crates

One of Rust's strengths is the crates.io ecosystem. Unfortunately most of the crates.io ecosystem
can't be used in no_std context. There are several reasons for this: the author is not aware that
their crate is compatible with no_std, the author is not willing to depend on unstable crates like
alloc to support no_std, etc.; but hopefully the portability WG will work on this issue.

There's currently an on going community effort of producing generic driver crates (crates to
interface external devices like sensors) that work with any platform that has an implementation of
the embedded-hal traits. Most of the participants are Cortex-M developers; to ensure these
crates and the embedded-hal abstractions work with all the other embedded targets AVR, MSP430 and
RISCV developers must also participate.

  • Ports of multitasking models

There are a few ways to do multitasking on bare metal: RTFM, a Tokio-like cooperative event loop,
good old threads, etc. All these multitasking models should be available for all the embedded
targets. Ideally the different ports of these models should expose the same or very similar APIs.

For more details about multitasking models check issue #45

cc @dvc94ch @pftbest @dylanmckay

@dylanmckay
Copy link
Contributor

I've raised avr-rust/rust-legacy-fork#93 to track porting embedded-hal to AVR.

It's not as high on the priority list compared to LLVM bugs though.

@dvc94ch
Copy link
Member

dvc94ch commented Feb 23, 2018

How are target files and programmer configuration files distributed? Does it make sense to include these in the ic/soc/bsp crates or not? For RISCV we expect to have many different soc's with different combinations of extensions and custom extensions. So it would make sense to have 2-3 common combinations in rust (maybe RV32G and RV64G (G means IMFDAC)) but have custom configurations like for example in the hifive crate (RV32IMAC).

@japaric
Copy link
Member Author

japaric commented Feb 23, 2018

@dvc94ch

How are target files

Ideally we should add a few, baseline targets to rustc itself and ideally users shouldn't need to deal with custom targets / target configuration files.

programmer configuration files

What are "programmer configuration files"?

Does it make sense to include these in the ic/soc/bsp crates or not?

Crates can't change the compilation target; at most they can't preven't generate compiler errors if the wrong target is used. If we must ship target configuration files I'd say they should go in the Cargo project template.

For RISCV we expect to have many different soc's with different combinations of extensions and custom extensions.

Hmm, could we have lowest common denominator target and enable "extensions" via llvm flags? llvm flags can go in .cargo/config (rustflags = ["-C", "llvm-args=+fpu"]); thought I'd rather not use llvm-args since they poke directly into the llvm backend. Or can the extensions only be specified in the llvm-target property of the target configuration? How is RISCV gcc dealing with enabling / disabling extensions?

RV64G

Are there 64-bit silicon implementations packed in microcontrollers packages? I think we should focus ourselves on RISCV microcontrollers and let someone deal with the riscv64-unknown-linux-gnu and similar targets. Though, I'm not gonna stop you from adding non bare-metal RISCV targets to rustc 😉.

have custom configurations like for example in the hifive crate

I don't see any target configuration file in that repo.


@dylanmckay

In another thread, you asked about having / distributing a bunch of AVR targets / target configuration files that are pretty much the same except for the target-cpu field. Could there, instead, be a single avr-none-elf target built into rustc and have the user set the target-cpu in .cargo/config (e.g. rustflags = ["-C", "target-cpu=attiny23"])? Ideally a Cargo project template configuration wizard would take care of generating the .cargo/config file after the user answers some configuration questions ("What AVR cpu are you targetting? e.g. attiny23").

Also, you probably mentioned this to me before but how does target-cpu affect LLVM's code generation?

@dvc94ch
Copy link
Member

dvc94ch commented Feb 23, 2018

What are "programmer configuration files"?

I meant openocd configuration. But these are now included in the openocd riscv port, so I'll remove them from the crate. So this issue is a non-issue.

RV64G

The cabi stuff can be parameterized over xlen (shared between 32bit and 64bit) as in the clang implementation [0] and the psabi docs [1]. I don't currently intend to port std to rv64.

For 32bit targets it's indeed only features that changes and max-atomic-width is either 0 or 32 depending on if it has the A extension or not.

riscv32imac-unknown-none.json

    "features": "+m,+a,+c",
    "max-atomic-width": 32,

For 32bit targets without +m there is a missing compiler intrinsic that needs to be implemented. [2]

Then there are various options in the privileged architecture manual that are configurable, but that can be handled with compile time options in the riscv crate.

[0] https://github.com/lowRISC/riscv-llvm/blob/master/clang/0004-RISCV-Implement-RISCV-ABI-lowering.patch
[1] https://github.com/riscv/riscv-elf-psabi-doc/blob/master/riscv-elf.md#-procedure-calling-convention
[2] andestech/riscv-compiler-rt@2409b3d

@dylanmckay
Copy link
Contributor

@japaric

In another thread, you asked about having / distributing a bunch of AVR targets / target configuration files that are pretty much the same except for the target-cpu field. Could there, instead, be a single avr-none-elf target built into rustc and have the user set the target-cpu in .cargo/config (e.g. rustflags = ["-C", "target-cpu=attiny23"])? Ideally a Cargo project template configuration wizard would take care of generating the .cargo/config file after the user answers some configuration questions ("What AVR cpu are you targetting? e.g. attiny23").

That could definitely work. It does feel a bit hacky though as .config/cargo wouldn't be the first place I'd like for a target CPU.

Still a better solution than hundreds of JSON files though!

Also, you probably mentioned this to me before but how does target-cpu affect LLVM's code generation?

Some MCUs have hardware multiplication, some don't. The AVR backend uses the target-cpu and maps it to a list of enabled CPU features.

For example

  • The backend will do hardware MUL if supported, software if not
  • Some CPUs support a subset of the "load program memory" instructions, codegen must choose a supported one otherwise the CPU crashes
  • Many of the older MCUs do not support postincrement/predecrement loading and storing to memory. These targets require pointers to explicitly be added/subtracted via arithmetic

@japaric
Copy link
Member Author

japaric commented Feb 26, 2018

@dvc94ch

For 32bit targets it's indeed only features that changes and max-atomic-width is either 0 or 32 depending on if it has the A extension or not.

OK, if max-atomic-width changes then we may need to have at least two targets one with -A and
one with +A as we can't change max-atomic-width from the command line. We have something like
this in Cortex-M land: the thumbv6m-none-eabi has max-atomic-width = 0; the other targets have
max-atomic-width = 32.

@dylanmckay out of curiosity do the AVR targets have max-atomic-width set to 0 or to some other
value?

Something interesting about max-atomic-width in Cortex-M land is that is not fine grained enough to
represent the real capabilities of a device. thumbv6m-none-eabi does support atomic loads / stores
(e.g. AtomicU32.{load,store}) but doesn't support CAS operations; however, because
max-atomic-width is set to 0 AtomicU32 is not available in core. cf. rust-lang/rust#45085


@dvc94ch @dylanmckay

Do any of the RISCV configurations / AVR target-cpu values change the ABI of the generated code.

For example, in Cortex-M land thumbv7em-none-eabihf generates code that uses the hard float ABI
whereas the other targets use the soft float ABI. The difference between the ABIs is that in the
hard float ABI floating point arguments (e.g. f32) are passed to a function (subroutine) via FPU
registers (s0, s1, etc.); in the soft float ABI floating point arguments are passed via general
purpose registers (r0, r1, etc.). This distinction is important when compiling and linking to C
code: the C code must be compiled with the same ABI (-mfloat-abi={soft,hard}) as the Rust code or
linking will fail.

If it's the case that some RISCV configurations / AVR target-cpu values have different ABIs then
we'll need a way to get pass that information to build scripts so they are able to correctly build C
code.

Build scripts have access to some information about targets via env variables that Cargo sets.
For example, these are the env variables that Cargo exposes for the x86_64-unknown-linux-gnu target:

CARGO_CFG_TARGET_ARCH=x86_64
CARGO_CFG_TARGET_ENDIAN=little
CARGO_CFG_TARGET_ENV=gnu
CARGO_CFG_TARGET_FAMILY=unix
CARGO_CFG_TARGET_FEATURE=fxsr,mmx,sse,sse2
CARGO_CFG_TARGET_HAS_ATOMIC=16,32,64,8,ptr
CARGO_CFG_TARGET_OS=linux
CARGO_CFG_TARGET_POINTER_WIDTH=64
CARGO_CFG_TARGET_THREAD_LOCAL=
CARGO_CFG_TARGET_VENDOR=unknown
TARGET=x86_64-unknown-linux-gnu

Notably -C target-feature, in the form of CARGO_CFG_TARGET_FEATURE, is there but target-cpu is
not. If information about the ABI is not present here (e.g. target-cpu) then we may need to rely
on the target name (e.g. avr-none-elf) to pick the right ABI when compiling C code.

Is there anything like this on AVR / RISCV?

@dvc94ch
Copy link
Member

dvc94ch commented Feb 26, 2018

There are many possible ABI's depending on the combination of features (xlen = E/32/64/128, flen = 0/32/64/128, 4*4=16 possible abi's). There are currently three calling conventions: soft float, hard float, and RV32E (only requires 16 instead of 32 registers for very small implementations) and two official ABI's (ILP32 for RV32G and LP64 for RV64G).

For now I'd suggest we stick to soft float (Integer Calling Convention) since that's what the only available mcu has (xlen=32, flen=0) and see what features are available in actual CPU's when they pop up.

@dylanmckay
Copy link
Contributor

dylanmckay commented Feb 27, 2018

@japaric

@dylanmckay out of curiosity do the AVR targets have max-atomic-width set to 0 or to some other
value?

It is currently the default of None, which Rust maps to the target point width, therefore 16.

Do any of the RISCV configurations / AVR target-cpu values change the ABI of the generated code.

From the AVR side - nope.

Notably -C target-feature, in the form of CARGO_CFG_TARGET_FEATURE, is there but target-cpu is
not. If information about the ABI is not present here (e.g. target-cpu) then we may need to rely
on the target name (e.g. avr-none-elf) to pick the right ABI when compiling C code.

I've made target-cpu a first-class configuration feature like target-arch in the AVR Rust fork. You can see the patch here. This is used by the ruduino crate to load mcu-specific HAL implementations.

@japaric
Copy link
Member Author

japaric commented Feb 27, 2018

@dvc94ch That's a lot but I expect that most of them are forward compatible? Like you can link flen=64 objects and flen=32 objects together. The different calling conventions are likely not linkable and we may different targets for each.

@dylanmckay

It is currently the default of None, which Rust maps to the target point width, therefore 16.

But does it generate the right thing? Does AtomicU16.compare_and_swap produce machine code that does the CAS loop? Or does it generate a call to an intrinsic?

I've made target-cpu a first-class configuration feature

That may require an RFC to land, or at the very least I expect that the portability WG will have the give the OK.

@dvc94ch
Copy link
Member

dvc94ch commented Feb 27, 2018

Hmm, doesn't passing a double require two registers if flen=32?

@japaric
Copy link
Member Author

japaric commented Mar 10, 2018

@dvc94ch Yeah, you are right the CPU registers have different sizes. Bad example. The linker doesn't like merging i686 objects and x86_64 objects; my flen example would probably be rejected by the linker in the same way.

@dylanmckay
Copy link
Contributor

@japaric

It is currently the default of None, which Rust maps to the target point width, therefore 16.

But does it generate the right thing? Does AtomicU16.compare_and_swap produce machine code that does the CAS loop? Or does it generate a call to an intrinsic?

A brief glance shows the std::atomics types use LLVM intrinsics.

The AVR backend implements the usual atomic load/store/add/sub operations, but not RMW operations like compare-and-swap. Currently it tells LLVM to expand these intrinsics into compiler runtime library calls. These would have to be implemented in compiler-rt, which doesn't officially support AVR outside of forks.

The RMW operations can be expanded into direct AVR machine code on many chips. The ATmega328p implements atomic instructions for example

I've made target-cpu a first-class configuration feature

That may require an RFC to land, or at the very least I expect that the portability WG will have the give the OK.

I agree. I've brought this feature up to the rust-lang folks before at rust-lang/rust#44036. For the meantime though, it works well in the fork, at least until a better solution exists.

@jamesmunns
Copy link
Member

Closing this as part of the 2024 triage.

As of today, much of this is covered by the compiler itself, as well as external tools like probe-rs.

If you think this was closed incorrectly, please leave a comment and we can revisit this decision in the next weekly WG meeting on Matrix.

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