-
Notifications
You must be signed in to change notification settings - Fork 1.7k
CMSE calling conventions #3884
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
base: master
Are you sure you want to change the base?
CMSE calling conventions #3884
Conversation
f30d3be to
21f5b58
Compare
thejpster
left a comment
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.
I had one quibble with the architecture terminology, but everything else looks good to me.
Co-authored-by: Jonathan 'theJPster' Pallant <github@thejpster.org.uk>
|
This has now been reviewed by several people from the rust embedded community and from Arm, so I think this is ready for T-lang to take a look. @rustbot label +I-lang-nominated |
Co-authored-by: Jacob Lifshay <programmerjake@gmail.com>
|
This looks reasonable to me, including the lint. @rfcbot merge lang |
|
Team member @joshtriplett has proposed to merge this. The next step is review by the rest of the tagged team members: No concerns currently listed. Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! cc @rust-lang/lang-advisors: FCP proposed for lang, please feel free to register concerns. |
| type T5 = extern "cmse-nonsecure-call" fn(_: i64, _: i64) -> WrappedI64; | ||
| ``` | ||
|
|
||
| An error is emitted when the program contains a signature that violates the calling convention's constraints: |
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.
Please explain the exact algorithm used for this check. There are potential semver concerns here so it'd be good to be clear about what is being checked here.
For instance, if I have a single-field struct like U64 without the repr(transparent), is that accepted or not?
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.
Sure, I'll clarify that.
The current implementation is to look at the BackendRepr, and accept anything passed as Primitive::Int(Integer::I64, _) | Primitive::Float(Float::F64). Because we're working with a subset of AAPCS, I don't think how values are passed can ever change?
We test for the case you mention, it emits an error (clang would emit an error in this case too):
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.
BackendRepr is an internal implementation detail of rustc and prone to changes. It seems risky to get ourselves into a corner where changing unspecified layout computation details is a breaking change.
(We do rely on BackendRepr for the SIMD vector ABI check, but that was a case of fixing a previous oversight which caused soundness trouble, so we had to minimize breakage. I wouldn't take that as a template for a new feature where we have the chance to do it right. Also, we don't rely just on BackendRepr there, we also check the PassMode, so really what we are checking there is the de-facto underlying ABI, at least on the level of detail that is represented in LLVM IR.)
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.
We test for the case you mention
That is not the case I mentioned. I suggested to remove repr(transparent), but I didn't suggest adding repr(C). (That will probably trigger an FFI lint, but since we are talking about hard errors here, whether or not there is a lint is largely irrelevant.)
Also, the fact that repr(C) inhibits BackendRepr::Scalar is a limitation we'd like to lift (rust-lang/rust#119183). We should not add new logic that relies on how repr(C) and BackendRepr correlate.
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.
So would a check based on the type (peeling off layers of repr(transparent) work better?
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.
Presumably, there can be at most 4 arguments.
module zero-sized types, yes.
The check is based just on the size of the arguments, each rounded up to the next multiple of 4. If that exceeds 16, the arguments do not fit.
I've updated the text.
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.
module zero-sized types, yes.
The fact that zero-sized arguments are skipped in some ABIs is an implementation detail. I would be hesistant to enshrine it in the language.
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.
ZSTs are used a lot in embedded Rust. It would be very nice to be able to pass ZSTs as an argument without it taking up the register budget.
Often hardware peripherals or certain state conditions are modeled using ZSTs.
For example, say we want a secure memcpy, that could look like:
fn secure_memcpy(dma: DMA, src: *const u8, src_len: usize, target: *mut u8, target_len: usize) -> DMAIf the DMA ZST were counted as one of the 4 possible arguments we couldn't make this work, even when there's not technical reason for it.
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.
You could store the arguments into a repr(C) struct...
But that is just a weird workaround. Given that the calling convention inherits from aapcs, and it does ignore zero-sized types, it feels like it should be OK to allow them here?
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.
it does ignore zero-sized types
Is that documented in the aapcs ABI description? C does not have zero-sized types, so this would surprise me. So it would be more correct to say that rustc happens to ignore ZST on aapcs calls at the moment, but that's not a deliberate choice (as in, t-lang approved or so) or a stable promise by any means. Generally we should be careful whenever we pass things over an ABI that do not exist in C, since if we make a stable choice here and C later makes a different choice, we are in trouble.
Our general ABI rules do not allow ignoring ZST, and in fact there are ABIs where they currently are not being ignored (specifically on Windows). That might be partially because we make some types ZST that shouldn't be ZST, but last time I checked it looked like we could not get away with a blanket "ZST are ignored" rule. We'd have to start making ABI-specific promises, which is a rabbit hole that we avoided so far.
f795ce0 to
ae12919
Compare
| An error is emitted when the program contains a signature that violates the calling convention's constraints: | ||
| The arguments fit if: | ||
|
|
||
| - the sum of their sizes, each rounded up to the next multiple of 4, is 16 bytes or less |
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.
So, a single argument of type (i32, (i8, i16), i64) is allowed?
That's... very strange and not at all what I expected. In the end I don't care that strongly as long as it is clearly specified, but implementation-wise this seems like it could become quite messy.
Is this our ABI to define or is it a standard ABI we have to follow? If it is the latter, allowing repr(Rust) types (this includes tuples) might be a mistake -- it would be the first time we make any kind of ABI guarantees for them.
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.
It is really just aapcs, so the 32-bit arm C calling convention. It already lints on passing repr(Rust) types, I'd be fine with actually disallowing them though. This is meant to be an FFI boundary, the user is meant to think about the constraints.
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.
The thing is that for normal aapcs, if we change repr(Rust) things won't fail to build. They might "just" stop working properly if you ignored the lints. This RFC turns things into a hard error, which typically has a different par for the lang team. But this feels worth explicitly calling out as an open question to them to get their vibes on it. After all, changing the size of a type can already lead to compilation failures due to repr(transparent).
Support for the
cmse-nonsecure-entryandcmse-nonsecure-callcalling conventions on ArmV8m (thumbv8*) targets, and a lint preventing (partially) uninitialized values from crossing the security boundary.The implementation is tracked in:
extern "cmse-nonsecure-entry"ABI rust#75835extern "cmse-nonsecure-call"ABI rust#81391Important
When responding to RFCs, try to use inline review comments (it is possible to leave an inline review comment for the entire file at the top) instead of direct comments for normal comments and keep normal comments for procedural matters like starting FCPs.
This keeps the discussion more organized.
@rustbot label +T-lang
Rendered